a2_printer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
@@ -0,0 +1,32 @@
1
+ A2 Printer
2
+ ==========
3
+
4
+ A small library for working with cheap thermal "A2" printers, such as those available from Adafruit and Sparkfun.
5
+
6
+ Simple example
7
+ ------
8
+
9
+ serial_connection = SerialConnection.new("/dev/serial")
10
+ printer = A2Printer.new(serial_connection)
11
+
12
+ printer.begin
13
+ printer.println("Hello world!")
14
+ printer.feed
15
+ printer.bold_on
16
+ printer.println("BOOM!")
17
+ printer.bold_off
18
+
19
+
20
+ Writing to a file
21
+ --------
22
+
23
+ This can be useful if you're going to use some other mechanism to send the bytes to the printer
24
+
25
+ commands_file = File.open("serial_commands.data", "w")
26
+ printer = A2Printer.new(commands_file)
27
+ printer.begin
28
+ printer.println("Hello world!")
29
+
30
+ # etc ...
31
+
32
+ commands_file.close
@@ -0,0 +1,332 @@
1
+ require "serial_connection"
2
+
3
+ class A2Printer
4
+ def initialize(connection)
5
+ @connection = connection
6
+ @print_mode = 0
7
+ end
8
+
9
+ def test_page
10
+ write_bytes(18, 84)
11
+ end
12
+
13
+ def begin(heat_time=150)
14
+ reset()
15
+
16
+ heat_interval = 50 # 2 is default from page 23 of datasheet. Controls speed of printing and darkness
17
+ print_density = 15 # Not sure what the default is. Testing shows the max helps darken text. From page 23.
18
+ print_break_time = 15 # Not sure what the default is. Testing shows the max helps darken text. From page 23.
19
+
20
+ write_bytes(27, 55)
21
+ write_bytes(7) # Default 64 dots = 8*('7'+1)
22
+ write_bytes(heat_time) # Default 80 or 800us
23
+ write_bytes(heat_interval) # Default 2 or 20us
24
+
25
+ # Modify the print density and timeout
26
+ write_bytes(18, 35)
27
+ print_setting = (print_density<<4) | print_break_time
28
+ write_bytes(print_setting) # Combination of print_density and print_break_time
29
+ end
30
+
31
+ # reset printer
32
+ def reset
33
+ write_bytes(27, 64)
34
+ end
35
+
36
+ # reset formatting
37
+ def set_default
38
+ online
39
+ normal
40
+ underline_off
41
+ justify(:left)
42
+ set_line_height(32)
43
+ set_barcode_height(50)
44
+ end
45
+
46
+ # Feeds by the specified number of lines
47
+ def feed(lines=1)
48
+ # The datasheet claims sending bytes 27, 100, <x> will work
49
+ # but it feeds much much more.
50
+ lines.times { write(10) }
51
+ end
52
+
53
+ # Feeds by the specified number of rows of pixels
54
+ def feed_rows(rows)
55
+ write_bytes(27, 74, rows)
56
+ end
57
+
58
+ def flush
59
+ write_bytes(12)
60
+ end
61
+
62
+ def test_page
63
+ write_bytes(18, 84)
64
+ end
65
+
66
+ def print(string)
67
+ string.bytes { |b| write(b) }
68
+ end
69
+
70
+ def println(string)
71
+ print(string + "\n")
72
+ end
73
+
74
+ def write(c)
75
+ return if (c == 0x13)
76
+ write_bytes(c)
77
+ end
78
+
79
+ def write_bytes(*bytes)
80
+ bytes.each { |b| @connection.putc(b) }
81
+ end
82
+
83
+ # Character commands
84
+
85
+ INVERSE_MASK = (1 << 1)
86
+ UPDOWN_MASK = (1 << 2)
87
+ BOLD_MASK = (1 << 3)
88
+ DOUBLE_HEIGHT_MASK = (1 << 4)
89
+ DOUBLE_WIDTH_MASK = (1 << 5)
90
+ STRIKE_MASK = (1 << 6)
91
+
92
+ def set_print_mode(mask)
93
+ @print_mode |= mask;
94
+ write_print_mode
95
+ end
96
+
97
+ def unset_print_mode(mask)
98
+ @print_mode &= ~mask;
99
+ write_print_mode
100
+ end
101
+
102
+ def write_print_mode
103
+ write_bytes(27, 33, @print_mode)
104
+ end
105
+
106
+ # This will reset bold, inverse, strikeout, upside down and font size
107
+ # It does not reset underline, justification or line height
108
+ def normal
109
+ @print_mode = 0
110
+ write_print_mode
111
+ end
112
+
113
+ def inverse_on
114
+ set_print_mode(INVERSE_MASK)
115
+ end
116
+
117
+ def inverse_off
118
+ unset_print_mode(INVERSE_MASK)
119
+ end
120
+
121
+ def upside_down_on
122
+ set_print_mode(UPDOWN_MASK);
123
+ end
124
+
125
+ def upside_down_off
126
+ unset_print_mode(UPDOWN_MASK);
127
+ end
128
+
129
+ def double_height_on
130
+ set_print_mode(DOUBLE_HEIGHT_MASK)
131
+ end
132
+
133
+ def double_height_off
134
+ unset_print_mode(DOUBLE_HEIGHT_MASK)
135
+ end
136
+
137
+ def double_width_on
138
+ set_print_mode(DOUBLE_WIDTH_MASK)
139
+ end
140
+
141
+ def double_width_off
142
+ unset_print_mode(DOUBLE_WIDTH_MASK)
143
+ end
144
+
145
+ def strike_on
146
+ set_print_mode(STRIKE_MASK)
147
+ end
148
+
149
+ def strike_off
150
+ unset_print_mode(STRIKE_MASK)
151
+ end
152
+
153
+ def bold_on
154
+ set_print_mode(BOLD_MASK)
155
+ end
156
+
157
+ def bold_off
158
+ unset_print_mode(BOLD_MASK)
159
+ end
160
+
161
+ def set_size(size)
162
+ byte = case size
163
+ when :small
164
+ 0
165
+ when :medium
166
+ 10
167
+ when :large
168
+ 25
169
+ end
170
+
171
+ write_bytes(29, 33, byte, 10)
172
+ end
173
+
174
+ # Underlines of different weights can be produced:
175
+ # 0 - no underline
176
+ # 1 - normal underline
177
+ # 2 - thick underline
178
+ def underline_on(weight=1)
179
+ write_bytes(27, 45, weight)
180
+ end
181
+
182
+ def underline_off
183
+ underline_on(0)
184
+ end
185
+
186
+ def justify(position)
187
+ byte = case position
188
+ when :left
189
+ 0
190
+ when :center
191
+ 1
192
+ when :right
193
+ 2
194
+ end
195
+
196
+ write_bytes(0x1B, 0x61, byte)
197
+ end
198
+
199
+ # Bitmaps
200
+
201
+ class Bitmap
202
+ attr_reader :width, :height
203
+
204
+ def initialize(width_or_source, height=nil, source=nil)
205
+ if height.nil? && source.nil?
206
+ set_source(width_or_source)
207
+ extract_width_and_height_from_data
208
+ else
209
+ set_source(source)
210
+ @width = width_or_source
211
+ @height = height
212
+ end
213
+ end
214
+
215
+ def each_block
216
+ row_start = 0
217
+ width_in_bytes = width / 8
218
+ while row_start < height do
219
+ chunk_height = ((height - row_start) > 255) ? 255 : (height - row_start)
220
+ bytes = (0...(width_in_bytes * chunk_height)).map { @data.getbyte }
221
+ yield width_in_bytes, chunk_height, bytes
222
+ row_start += 255
223
+ end
224
+ end
225
+
226
+ private
227
+
228
+ def set_source(source)
229
+ if source.respond_to?(:getbyte)
230
+ @data = source
231
+ else
232
+ @data = StringIO.new(source.map(&:chr).join)
233
+ end
234
+ end
235
+
236
+ def extract_width_and_height_from_data
237
+ tmp = @data.getbyte
238
+ @width = (@data.getbyte << 8) + tmp
239
+ tmp = @data.getbyte
240
+ @height = (@data.getbyte << 8) + tmp
241
+ end
242
+ end
243
+
244
+ def print_bitmap(*args)
245
+ bitmap = Bitmap.new(*args)
246
+ return if (bitmap.width > 384) # maximum width of the printer
247
+ bitmap.each_block do |w, h, bytes|
248
+ write_bytes(18, 42)
249
+ write_bytes(h, w)
250
+ write_bytes(*bytes)
251
+ end
252
+ end
253
+
254
+ # def print_bitmap(stream)
255
+ # tmp = stream.getbyte
256
+ # width = (stream.getbyte << 8) + tmp
257
+ #
258
+ # tmp = stream.getbyte
259
+ # height = (stream.getbyte << 8) + tmp
260
+ #
261
+ # print_bitmap(width, height, stream)
262
+ # end
263
+
264
+ # Barcodes
265
+
266
+ def set_barcode_height(val)
267
+ # default is 50
268
+ write_bytes(29, 104, val)
269
+ end
270
+
271
+ UPC_A = 0
272
+ UPC_E = 1
273
+ EAN13 = 2
274
+ EAN8 = 3
275
+ CODE39 = 4
276
+ I25 = 5
277
+ CODEBAR = 6
278
+ CODE93 = 7
279
+ CODE128 = 8
280
+ CODE11 = 9
281
+ MSI = 10
282
+
283
+ def print_barcode(text, type)
284
+ write_bytes(29, 107, type) # set the type first
285
+ text.bytes { |b| write(b) }
286
+ write(0) # Terminator
287
+ end
288
+
289
+ # Take the printer offline. Print commands sent after this will be
290
+ # ignored until `online` is called
291
+ def offline
292
+ write_bytes(27, 61, 0)
293
+ end
294
+
295
+ # Take the printer back online. Subsequent print commands will be
296
+ # obeyed.
297
+ def online
298
+ write_bytes(27, 61, 1)
299
+ end
300
+
301
+ # Put the printer into a low-energy state immediately
302
+ def sleep
303
+ sleep_after(0)
304
+ end
305
+
306
+ # Put the printer into a low-energy state after the given number
307
+ # of seconds
308
+ def sleep_after(seconds)
309
+ write_bytes(27, 56, seconds)
310
+ end
311
+
312
+ # Wake the printer from a low-energy state. This command will wait
313
+ # for 50ms (as directed by the datasheet) before allowing further
314
+ # commands to be send.
315
+ def wake
316
+ write_bytes(255)
317
+ # delay(50) # ?
318
+ end
319
+
320
+ # ==== not working? ====
321
+ def tab
322
+ write(9)
323
+ end
324
+
325
+ def set_char_spacing(spacing)
326
+ write_bytes(27, 32, 0, 10)
327
+ end
328
+
329
+ def set_line_height(val=32)
330
+ write_bytes(27, 51, val) # default is 32
331
+ end
332
+ end
@@ -0,0 +1,18 @@
1
+ require "serialport"
2
+
3
+ class SerialConnection
4
+ def initialize(port=nil)
5
+ port_str = port || "/dev/tty.usbserial-FTF8DKJT"
6
+ baud_rate = 19200
7
+ data_bits = 8
8
+ stop_bits = 1
9
+ parity = SerialPort::NONE
10
+ @serial = SerialPort.new(port_str, baud_rate, data_bits, stop_bits, parity)
11
+ @serial.sync = true
12
+ end
13
+
14
+ def putc(byte)
15
+ @serial.putc(byte)
16
+ end
17
+ end
18
+
@@ -0,0 +1,194 @@
1
+ require "test_helper"
2
+ require "a2_printer"
3
+
4
+ describe A2Printer do
5
+ def sent_bytes
6
+ @test_connection.bytes
7
+ end
8
+
9
+ before do
10
+ @test_connection = TestConnection.new
11
+ @printer = A2Printer.new(@test_connection)
12
+ end
13
+
14
+ it "can be reset" do
15
+ @printer.reset
16
+ sent_bytes.must_equal [27, 64]
17
+ end
18
+
19
+ it "prints a test page" do
20
+ @printer.test_page
21
+ sent_bytes.must_equal [18, 84]
22
+ end
23
+
24
+ it "can be flushed" do
25
+ @printer.flush
26
+ sent_bytes.must_equal [12]
27
+ end
28
+
29
+ it "can be set offline" do
30
+ @printer.offline
31
+ sent_bytes.must_equal [27, 61, 0]
32
+ end
33
+
34
+ it "can be set offline" do
35
+ @printer.online
36
+ sent_bytes.must_equal [27, 61, 1]
37
+ end
38
+
39
+ describe "feeding" do
40
+ it "feeds a single line" do
41
+ @printer.feed
42
+ sent_bytes.must_equal [10]
43
+ end
44
+
45
+ it "feeds multiple lines" do
46
+ @printer.feed(3)
47
+ sent_bytes.must_equal [10, 10, 10]
48
+ end
49
+ end
50
+
51
+ describe "initializing" do
52
+ it "resets the print settings" do
53
+ @printer.begin(100)
54
+ sent_bytes.must_equal [
55
+ 27, 64, # reset
56
+ 27, 55, # start control parameter command
57
+ 7, # dots
58
+ 100, # heat time
59
+ 50, # heat interval
60
+ 18, 35, # start print density command
61
+ (15 << 4) | 15 # print density
62
+ ]
63
+ end
64
+
65
+ it "sets the defaults" do
66
+ @printer.expects(:online)
67
+ @printer.expects(:justify).with(:left)
68
+ @printer.expects(:normal)
69
+ @printer.expects(:underline_off)
70
+ @printer.expects(:set_line_height).with(32)
71
+ @printer.set_default
72
+ end
73
+ end
74
+
75
+ describe "printing characters" do
76
+ it "sends the characters as bytes" do
77
+ @printer.print("hello")
78
+ sent_bytes.must_equal %w(h e l l o).map(&:ord)
79
+ end
80
+
81
+ it "sends a newline character when printing with println" do
82
+ @printer.println("hello")
83
+ sent_bytes.must_equal %w(h e l l o).map(&:ord) + [10]
84
+ end
85
+ end
86
+
87
+ describe "printing bitmaps" do
88
+ describe "shorter than 255 rows" do
89
+ before do
90
+ @printer.print_bitmap(width=8, height=8, [1,2,3,4,5,6,7,8])
91
+ end
92
+
93
+ it "writes the prefix" do
94
+ sent_bytes[0,2].must_equal [18, 42]
95
+ end
96
+
97
+ it "writes the number of rows of the bitmap" do
98
+ sent_bytes[2,1].must_equal [8]
99
+ end
100
+
101
+ it "writes the width of the bitmap in bytes" do
102
+ sent_bytes[3,1].must_equal [1]
103
+ end
104
+
105
+ it "writes the bitmap data" do
106
+ sent_bytes[4..-1].must_equal [1,2,3,4,5,6,7,8]
107
+ end
108
+ end
109
+
110
+ describe "longer than 255 rows" do
111
+ before do
112
+ @printer.print_bitmap(width=8, height=256, [3]*256)
113
+ end
114
+
115
+ it "writes the prefix" do
116
+ sent_bytes[0,2].must_equal [18, 42]
117
+ end
118
+
119
+ it "writes the number of rows of the first bitmap chunk" do
120
+ sent_bytes[2,1].must_equal [255]
121
+ end
122
+
123
+ it "writes the width of the bitmap in bytes" do
124
+ sent_bytes[3,1].must_equal [1]
125
+ end
126
+
127
+ it "writes the first chunk of bitmap data" do
128
+ sent_bytes[4,255].must_equal [3] * 255
129
+ end
130
+
131
+ it "writes the prefix a second time" do
132
+ sent_bytes[259,2].must_equal [18, 42]
133
+ end
134
+
135
+ it "writes the number of rows of the second bitmap chunk" do
136
+ sent_bytes[261,1].must_equal [1]
137
+ end
138
+
139
+ it "writes the width of the bitmap in bytes" do
140
+ sent_bytes[262,1].must_equal [1]
141
+ end
142
+
143
+ it "writes the second chunk of bitmap data" do
144
+ sent_bytes[263..-1].must_equal [3]
145
+ end
146
+ end
147
+
148
+ describe "from an IO object" do
149
+ before do
150
+ data = StringIO.new(([3]*8).map(&:chr).join)
151
+ @printer.print_bitmap(width=8, height=8, data)
152
+ end
153
+
154
+ it "writes the prefix" do
155
+ sent_bytes[0,2].must_equal [18, 42]
156
+ end
157
+
158
+ it "writes the number of rows of the bitmap" do
159
+ sent_bytes[2,1].must_equal [8]
160
+ end
161
+
162
+ it "writes the width of the bitmap in bytes" do
163
+ sent_bytes[3,1].must_equal [1]
164
+ end
165
+
166
+ it "writes the bitmap data" do
167
+ sent_bytes[4..-1].must_equal [3,3,3,3,3,3,3,3]
168
+ end
169
+ end
170
+
171
+ describe "where width and height aren't given" do
172
+ before do
173
+ data = StringIO.new(([8,0,8,0] + [3]*8).map(&:chr).join)
174
+ @printer.print_bitmap(data)
175
+ end
176
+
177
+ it "writes the prefix" do
178
+ sent_bytes[0,2].must_equal [18, 42]
179
+ end
180
+
181
+ it "writes the number of rows of the bitmap" do
182
+ sent_bytes[2,1].must_equal [8]
183
+ end
184
+
185
+ it "writes the width of the bitmap in bytes" do
186
+ sent_bytes[3,1].must_equal [1]
187
+ end
188
+
189
+ it "writes the bitmap data" do
190
+ sent_bytes[4..-1].must_equal [3,3,3,3,3,3,3,3]
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,18 @@
1
+ require "minitest/autorun"
2
+ require "rubygems"
3
+ require "bundler"
4
+ Bundler.require(:default, :test)
5
+ require "mocha"
6
+
7
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
8
+
9
+ class TestConnection
10
+ attr_reader :bytes
11
+ def initialize
12
+ @bytes = []
13
+ end
14
+ def putc(byte)
15
+ @bytes << byte
16
+ end
17
+ end
18
+
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: a2_printer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - James Adam
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: serialport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.4
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.4
30
+ - !ruby/object:Gem::Dependency
31
+ name: mocha
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description:
47
+ email: james@lazyatom.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files:
51
+ - README.md
52
+ files:
53
+ - README.md
54
+ - Gemfile
55
+ - test/a2_printer_test.rb
56
+ - test/test_helper.rb
57
+ - lib/a2_printer.rb
58
+ - lib/serial_connection.rb
59
+ homepage: http://lazyatom.com
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options:
63
+ - --main
64
+ - README.md
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ segments:
74
+ - 0
75
+ hash: 2772800540462270427
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ segments:
83
+ - 0
84
+ hash: 2772800540462270427
85
+ requirements: []
86
+ rubyforge_project:
87
+ rubygems_version: 1.8.23
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Sending commands to a small thermal printer
91
+ test_files: []