Axiohm A758 helper library for Python
Jacek Kowalski
2016-05-03 645ea69c369785e5df4131866d72b09c200c37ba
commit | author | age
bd2ed9 1 from __future__ import division
JK 2
3 import serial
4
5 class Axiohm:
6     CODEPAGE_437 = 0
7     CODEPAGE_850 = 1
8     CODEPAGE_852 = 2
9     CODEPAGE_860 = 3
10     CODEPAGE_863 = 4
11     CODEPAGE_865 = 5
12     CODEPAGE_858 = 6
13     CODEPAGE_866 = 7
14     
15     codepage_mapping = {
16         CODEPAGE_437: 'cp437',
17         CODEPAGE_850: 'cp850',
18         CODEPAGE_852: 'cp852',
19         CODEPAGE_860: 'cp860',
20         CODEPAGE_863: 'cp863',
21         CODEPAGE_865: 'cp865',
22         CODEPAGE_866: 'cp866',
23     }
24     
25     CUT_MODE_PARTIAL = 0
26     CUT_MODE_PARTIAL_FEED = 66
27     CUT_MODE_FULL = 1
28     CUT_MODE_FULL_FEED = 65
29     
30     BARCODE_TEXT_NONE = 0
31     BARCODE_TEXT_ABOVE = 1
32     BARCODE_TEXT_BELOW = 2
33     BARCODE_TEXT_BOTH = 3
34     
35     BARCODE_UPCA = 65
36     BARCODE_UPCE = 66
37     BARCODE_EAN13 = 67
38     BARCODE_EAN8 = 68
39     BARCODE_CODE39 = 69
40     BARCODE_INT2OF5 = 70
41     BARCODE_CODABAR = 71
42     BARCODE_CODE93 = 72
43     BARCODE_CODE128 = 73
44     BARCODE_PDF417 = 75
45     
46     STATION_RECEIPT = 1
47     STATION_SLIP = 4
48     
49     ALIGN_LEFT = 0
50     ALIGN_CENTER = 1
51     ALIGN_RIGHT = 2
52     
53     PITCH_STANDARD = 0
54     PITCH_COMPRESSED = 1
55     
645ea6 56     IMAGE_MODE_8DOT_SINGLE = 0
JK 57     IMAGE_MODE_8DOT_DOUBLE = 1
58     IMAGE_MODE_24DOT_SINGLE = 32
59     IMAGE_MODE_24DOT_DOUBLE = 33
60     
bd2ed9 61     def __init__(self, **kwargs):
JK 62         self.serial = serial.Serial(**kwargs)
63         self.currentStation = self.STATION_RECEIPT
64         self.currentCodepage = self.CODEPAGE_437
65     
66     # CONTROL
67     
68     def beep(self):
69         self.serial.write("\x1b\x07")
70
71     def reset(self):
72         self.currentStation = self.STATION_RECEIPT
73         self.currentCodepage = self.CODEPAGE_437
74         self.serial.write("\x1b\x40")
75     
76     def selectReceipt(self):
77         self.currentStation = self.STATION_RECEIPT
78         self.serial.write("\x1e")
79     
80     def selectSlip(self):
81         self.currentStation = self.STATION_SLIP
82         self.serial.write("\x1c")
83
84     def getStatus(self):
85         self.serial.write("\x1b\x76")
86         data = ord(self.serial.read(1))
87         return {
88             'receiptPaperLow':  ((data & 0x01) != 0),
89             'receiptCoverOpen': ((data & 0x02) != 0),
90             'receiptPaperOut':  ((data & 0x04) != 0),
91             'jam':              ((data & 0x08) != 0),
92             'leadingEdge':      ((data & 0x20) != 0),
93             'trailingEdge':     ((data & 0x40) != 0),
94             'headOverheat':     ((data & 0x80) != 0),
95         }
96     
97     def getModel(self):
98         self.serial.write("\x1d\x49\x01")
99         model = ord(self.serial.read(1))
100         self.serial.write("\x1d\x49\x02")
101         type = ord(self.serial.read(1))
102         
103         knownModels = {
104             28: "Axiohm A758",
105             26: "Axiohm A756",
106         }
107         modelName = "Unknown"
108         if model in knownModels:
109             modelName = knownModels[model]
110         
111         return {
112             'model':       model,
113             'modelName':   modelName,
114             'twoByteCode': ((type & 0x01) != 0),
115             'knife':       ((type & 0x02) != 0),
116             'micr':        ((type & 0x08) != 0),
117         }
118     
119     # CUTTING
120     
121     def cutMode(self, mode, offset=0):
122         self.serial.write("\x1d\x56" + chr(mode))
123         if mode == 65 or mode == 66:
124             self.serial.write(chr(offset))
125     
126     def cutFeedFull(self):
127         self.cutMode(Axiohm.CUT_MODE_FULL_FEED)
128     
129     def cutFeedPartial(self):
130         self.cutMode(Axiohm.CUT_MODE_PARTIAL_FEED)
131     
132     def cutFull(self):
133         self.serial.write("\x1b\x69")
134     
135     def cutPartial(self):
136         self.serial.write("\x1b\x6d")
137     
138     def cut(self):
139         self.cutFeedFull()
140     
141     # FORMATTING
142     
143     def clear(self):
144         self.serial.write("\x10")
145     
146     def setDoubleWide(self):
147         self.serial.write("\x12")
148     
149     def setSingleWide(self):
150         self.serial.write("\x13")
151     
152     def setPitch(self, pitch = PITCH_STANDARD):
153         self.serial.write("\x1b\x16" + chr(pitch))
154     
155     def setCodePage(self, codepage):
156         self.currentCodepage = codepage
157         self.serial.write("\x1b\x52" + chr(codepage))
158     
159     def rotateCCW(self):
160         self.serial.write("\x1b\x12")
161     
162     def rotateCW(self):
163         self.serial.write("\x1b\x56\x01")
164     
165     def upsideDown(self):
166         self.serial.write("\x1b\x7b\x01")
167     
168     def align(self, alignment):
169         assert self.currentStation != self.STATION_SLIP, 'Slip station prints cannot be aligned'
170         self.serial.write("\x1b\x61" + chr(alignment))
171     
172     def alignLeft(self):
173         self.align(self.ALIGN_LEFT)
174     
175     def alignCenter(self):
176         self.align(self.ALIGN_CENTER)
177     
178     def alignRight(self):
179         self.align(self.ALIGN_RIGHT)
180     
181     def setBarcodeTextLocation(self, location = BARCODE_TEXT_NONE):
182         self.serial.write("\x1d\x48" + chr(location))
183     
184     def setBarcodeTextPitch(self, pitch = PITCH_STANDARD):
185         self.serial.write("\x1d\x66" + chr(pitch))
186     
a242be 187     def setBarcodeHeight(self, height):
JK 188         self.serial.write("\x1d\x68" + chr(height))
bd2ed9 189     
JK 190     def setBarcodeHeightInches(self, inches = 1):
191         if self.currentStation == self.STATION_SLIP:
192             self.setBarcodeHeight(int(inches * 216))
193         else:
194             self.setBarcodeHeight(int(inches * 203))
195     
196     def setBarcodeHeightMilimeters(self, milimeters = 8.5):
197         if self.currentStation == self.STATION_SLIP:
198             self.setBarcodeHeight(int(milimeters / 8.5))
199         else:
200             self.setBarcodeHeight(int(milimeters / 8))
a242be 201     
JK 202     def setBarcodeWidth(self, width):
203         self.serial.write("\x1d\x77" + chr(width))
bd2ed9 204     
JK 205     # FEEDING
206     
207     def feedLines(self, lines = 1):
208         if lines < 0:
209             assert self.currentStation != self.STATION_SLIP, 'Receipt station cannot be reverse feed'
210             lines = -lines
211             self.serial.write("\x1d")
212         self.serial.write("\x14" + chr(lines))
213     
214     def feedDots(self, dots = 1):
215         if dots < 0:
216             assert self.currentStation != self.STATION_SLIP, 'Receipt station cannot be reverse feed'
217             dots = -dots
218             self.serial.write("\x1d")
219         
220         while(dots > 255):
221             self.serial.write("\x15\xff")
222             dots -= 255
223         
224         self.serial.write("\x15" + chr(dots))
225     
226     def feedInches(self, inches = 0.1):
227         if self.currentStation == self.STATION_SLIP:
228             self.feedDots(int(inches * 72))
229         else:
230             self.feedDots(int(inches * 203))
231     
232     def feedMilimeters(self, milimeters = 10):
233         if self.currentStation == self.STATION_SLIP:
234             self.feedDots(int(milimeters * 19 / 2.388))
235         else:
236             self.feedDots(int(milimeters * 7 / 2.47))
237     
238     # PRINT POSITION
239     
bd81c1 240     def moveAbsolute(self, dots = 0):
bd2ed9 241         self.serial.write("\x1b\x24" + chr(dots % 256) + chr(int(dots/256)))
JK 242     
243     def moveAbsoluteInches(self, inches):
244         if self.currentStation == self.STATION_SLIP:
245             self.moveAbsolute(int(inches * 660 / 4.752))
246         else:
247             self.moveAbsolute(int(inches * 576 / 2.835))
248     
249     def moveAbsoluteMilimeters(self, milimeters):
250         if self.currentStation == self.STATION_SLIP:
251             self.moveAbsolute(int(milimeters * 660 / 120.7))
252         else:
253             self.moveAbsolute(int(milimeters * 576 / 72))
254     
255     def moveRelative(self, dots):
256         if dots < 0:
257             dots = 65536 + dots
bd81c1 258         
bd2ed9 259         self.serial.write("\x1b\x5c" + chr(dots % 256) + chr(int(dots/256)))
JK 260     
261     def moveRelativeInches(self, inches):
262         if self.currentStation == self.STATION_SLIP:
263             self.moveRelative(int(inches * 660 / 4.752))
264         else:
265             self.moveRelative(int(inches * 576 / 2.835))
266     
267     def moveRelativeMilimeters(self, milimeters):
268         if self.currentStation == self.STATION_SLIP:
269             self.moveRelative(int(milimeters * 660 / 120.7))
270         else:
271             self.moveRelative(int(milimeters * 576 / 72))
272     
bd81c1 273     def marginLeft(self, dots = 0):
JK 274         self.serial.write("\x1d\x4c" + chr(dots % 256) + chr(int(dots/256)))
275     
bd2ed9 276     # PRINTING
JK 277     
278     def printUnicode(self, line=""):
279         printData = ""
280         for char in line:
281             try:
282                 printData += char.encode(self.codepage_mapping[self.currentCodepage])
283             except ValueError:
284                 for codepage, encoding in self.codepage_mapping.iteritems():
285                     try:
286                         charEncoded = char.encode(encoding)
287                         self.currentCodepage = codepage
288                         printData += "\x1b\x52" + chr(codepage) + charEncoded
289                         break
290                     except ValueError:
291                         pass
292                 else:
293                     raise
294         self.printText(printData)
295     
296     def printText(self, lines):
297         self.serial.write(lines)
298     
299     def printLine(self, line=""):
300         self.printText(line + "\r\n")
301     
302     def printLineUnicode(self, line=""):
303         self.printUnicode(line + "\r\n")
304     
305     def printBarcode(self, type, data):
306         assert(type > 64)
307         self.serial.write("\x1d\x6b" + chr(type) + chr(len(data)) + data)
308     
a242be 309     def printCode128(self, bytelist):
JK 310         assert 103 <= bytelist[0] <= 105
311         assert bytelist[1] < 103
312         
313         checksum = bytelist[0]
314         for i, item in enumerate(bytelist):
315             checksum = (checksum + i*item) % 103
316         
317         bytelist.append(checksum)
318         self.printBarcode(self.BARCODE_CODE128, str(bytearray(bytelist)))
319     
bd2ed9 320     def printLinesRotatedCCW(self, lines, startingPosition = 0):
JK 321         maxLineLength = max([len(line) for line in lines])
322         lines = [line.ljust(maxLineLength, ' ') for line in lines]
323         
324         self.rotateCCW()
325         for column in xrange(maxLineLength-1, -1, -1):
326             if startingPosition != 0:
327                 self.moveAbsolute(startingPosition)
328             self.printLineUnicode(''.join([line[column] for line in lines]))
329     
645ea6 330     # IMAGES
JK 331     
332     def selectLogo(self, id):
333         self.serial.write("\x1d\x23" + chr(id % 256))
334     
335     def printLogo(self, mode = 0):
336         self.serial.write("\x1d\x2f" + chr(mode % 4))
337     
338     def printImage(self, image, modeNumber = 33):
339         assert self.currentStation == self.STATION_RECEIPT, \
340             'Printing images on slip station is not supported yet'
341         
342         # Pillow library image
343         image = image.convert("1")
344         from bitstring import BitArray
345         bits = BitArray(bytes=image.tobytes())
346         
347         mode = {
348             self.IMAGE_MODE_8DOT_SINGLE:  { "height": 8,  "width": 288 },
349             self.IMAGE_MODE_8DOT_DOUBLE:  { "height": 8,  "width": 576 },
350             self.IMAGE_MODE_24DOT_SINGLE: { "height": 24, "width": 288 },
351             self.IMAGE_MODE_24DOT_DOUBLE: { "height": 24, "width": 576 },
352         }[modeNumber]
353         
354         if image.height % mode['height'] > 0:
355             bits.append(BitArray(length = image.width * (mode['height'] - image.height % mode['height'])))
356         
357         for startLine in xrange(0, image.height, mode['height']):
358             printData = b''
359             start = startLine * image.width
360             end = start + image.width * mode['height']
361             for x in xrange(0, min(image.width, mode['width'])):
362                 printData += ( bits[start + x : end : image.width].bytes )
363             
364             length = int(len(printData) / (mode['height'] / 8))
365             self.serial.write("\x1b\x2a" + chr(modeNumber) + chr(length % 256) + chr(int(length / 256)))
366             self.serial.write(printData)
367             self.serial.write("\r\n")
368     
bd2ed9 369     # SLIP
JK 370     
371     def waitForSlip(self):
372         self.serial.write("\x1b\x63\x30\x04")
373     
374     def ejectSlip(self):
375         self.serial.write("\x0c")