Thursday, 25 November 2010

Printing Labels

I recently had a need to print labels from our database. The factory produces samples; the idea is that they enter the samples into the database, which spits out a label to stick on it. Analysts then update the record as the results come in.

After some rather lengthy discussions with my IT supplier we went for a Zebra GK420t label printer. The accompanying CD has a manual for programming in a language called ZPL, allowing me to set up my labels with complete flexibility.

Configuring a Printer Port

I am in a Windows environment, and some of this is specific to Windows.

The general strategy is to use Ruby to create a file, and then copy the file to the serial port. Most PCs nowadays do not have a serial port, but you can pretend the printer is on a serial port using the "net use" command at the DOS prompt. It is first necessary to share your printer on the network. Then, at the server where your project is run from, type something like this (comp01 is the computer's network name, zebra is the share name for the printer):
net use lpt4 \\comp01\zebra /persistent:yes

You can check if the printer is still assigned to that port using "net use".

If only it were that simple. Windows runs services in their own environment, so setting the printer at the prompt is great for when you are developing the project, as you run your web server from the command prompt. Then you go live, and now the project is run as a service on Tomcat or whatever, and has no clue about what you have done at the command prompt.

The way I got around this was to put code into a file in config/initial, like this:
printer = File.read('config/printer.cfg').strip
command = "net use lpt4 \"#{printer}\""
`net use lpt4 /delete`
result = `#{command}`

Everytime Rails starts up, this reads the printer name from a file called config/printer.cfg, deletes the old setting and applies the new.

Printing

Using the "copy" command, you can now send a file to the printer. I think it is useful to try that before getting into Ruby to ensure that works, so here is an example ZPL file (for labels 100x50 mm).
^XA
~SD15
^LL200
^PW750
^FO50,60^ADN,36,40^FDMy first line^FS
^FO50,110^ADN,36,20^FDA second line^FS
^FO50,160^ADN,36,20^FDThe third line^FS
^FO50,220^ADN,18,10^FDMy company^FS
^FO50,245^ADN,18,10^FDCreated at 1847 on 13/Sep/10^FS
^FO35,50^GB680,160,2^FS
^XZ
END

Here is some Ruby code that will generate a new file on the fly, and send it to the printer. Note that ZPL does not require that data is sent in the order in which it appears on the label.
require 'date'

class Printer
PRINTER = 'lpt4'
COMPANY = 'My Company Ltd'

def self.print_label lines
# Accept either a string with line breaks embedded,
# or an array of strings
# (assume each line is not too long)
lines = lines.split("\n") if lines.is_a? String

# A "here document" defines the basic label,
# With a box, date and company name
text = <<-END
^XA
~SD15
^LL200
^PW750
^FO50,#{70 + 50 * lines.length}^ADN,18,10^FD#{COMPANY}^FS
^FO50,#{95 + 50 * lines.length}^ADN,18,10^FDCreated at #{DateTime.now.strftime('%H%M on %d/%b/%y')}^FS
^FO35,50^GB680,#{10 + 50 * lines.length},2^FS
^FO50,#{60}^ADN,36,40^FD#{lines[0]}^FS
END

# Each line sent is now added
(1..lines.length).each do |i|
text << "^FO50,#{60 + 50 * i}^ADN,36,20^FD#{lines[i]}^FS\n"
end

# The file terminator is tagged on the end
text << "^XZ"

# The file is created
File.open("print.txt", "w") { |file| file << text }

# In Rails, stop at this point if in a unit test
return 'not printed' if RAILS_ENV == "test"

# Create a DOS command
command = "copy #{RAILS_ROOT}/print.txt #{PRINTER}"

# Usually Ruby will convert forward slashes to backslashes
# in Windows, but not here
command.gsub! '/', '\\'

# Use backticks for the system command
# so we can capture the output
result = `#{command}`

# Printed okay
return 'label printed' if result =~ /1 file\(s\) copied/

# Problem, so return the error message and command
"Label failed with error \"#{result}\", command was \"#{command}\""
end
end


Printer.print_label ["My first line", "A second line", "The third line"]

You may want to consider whether two people might try to print at the same time. If the second writes a new file before the first has printed it, both users will get the second label. My understanding is that Rails handles one request at a time, so I do not think this is a problem, and for me, uses will only be printing from a single computer anyway.

Struggling with Ruby: Contents Page

1 comment:

Paul said...

how can i do this with java?