comparison zebra.py @ 31:f77ca0963d6d 0.2.0

Fix language bug, convert to pyproject.toml
author Ben Croston <ben@croston.org>
date Wed, 12 Feb 2025 15:13:51 +0000
parents 63d1260cc64e
children
comparison
equal deleted inserted replaced
30:ddc251fb92aa 31:f77ca0963d6d
1 #!/usr/bin/env python3 1 #!/usr/bin/env python3
2 2
3 # Copyright (c) 2011-2020 Ben Croston 3 # Copyright (c) 2011-2025 Ben Croston
4 # 4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy of 5 # Permission is hereby granted, free of charge, to any person obtaining a copy of
6 # this software and associated documentation files (the "Software"), to deal in 6 # this software and associated documentation files (the "Software"), to deal in
7 # the Software without restriction, including without limitation the rights to 7 # the Software without restriction, including without limitation the rights to
8 # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
28 import win32print 28 import win32print
29 else: 29 else:
30 IS_WINDOWS = False 30 IS_WINDOWS = False
31 import subprocess 31 import subprocess
32 32
33
33 class Zebra: 34 class Zebra:
34 """A class to communicate with (Zebra) label printers""" 35 """A class to communicate with (Zebra) label printers"""
35 36
36 def __init__(self, queue=None): 37 def __init__(self, queue=None):
37 """queue - name of the printer queue (optional)""" 38 """queue - name of the printer queue (optional)"""
38 self.queue = queue 39 self.queue = queue
39 40
40 def _output_unix(self, commands): 41 def _output_unix(self, commands):
41 if self.queue == 'zebra_python_unittest': 42 if self.queue == 'zebra_python_unittest':
42 p = subprocess.Popen(['cat','-'], stdin=subprocess.PIPE) 43 p = subprocess.Popen(['cat', '-'], stdin=subprocess.PIPE)
43 else: 44 else:
44 p = subprocess.Popen(['lpr','-P{}'.format(self.queue),'-oraw'], stdin=subprocess.PIPE) 45 p = subprocess.Popen(
46 ['lpr', '-P{}'.format(self.queue), '-oraw'], stdin=subprocess.PIPE
47 )
45 p.communicate(commands) 48 p.communicate(commands)
46 p.stdin.close() 49 p.stdin.close()
47 50
48 def _output_win(self, commands): 51 def _output_win(self, commands):
49 if self.queue == 'zebra_python_unittest': 52 if self.queue == 'zebra_python_unittest':
50 print(commands) 53 print(commands)
51 return 54 return
52 hPrinter = win32print.OpenPrinter(self.queue) 55 hPrinter = win32print.OpenPrinter(self.queue)
53 try: 56 try:
54 hJob = win32print.StartDocPrinter(hPrinter, 1, ('Label',None,'RAW')) 57 win32print.StartDocPrinter(hPrinter, 1, ('Label', None, 'RAW'))
55 try: 58 try:
56 win32print.StartPagePrinter(hPrinter) 59 win32print.StartPagePrinter(hPrinter)
57 win32print.WritePrinter(hPrinter, commands) 60 win32print.WritePrinter(hPrinter, commands)
58 win32print.EndPagePrinter(hPrinter) 61 win32print.EndPagePrinter(hPrinter)
59 finally: 62 finally:
66 69
67 commands - commands to send to the printer. Converted to a byte string if necessary. 70 commands - commands to send to the printer. Converted to a byte string if necessary.
68 encoding - Encoding used if 'commands' is not a byte string 71 encoding - Encoding used if 'commands' is not a byte string
69 """ 72 """
70 assert self.queue is not None 73 assert self.queue is not None
71 if type(commands) != bytes: 74 if not isinstance(commands, bytes):
72 commands = str(commands).encode(encoding=encoding) 75 commands = str(commands).encode(encoding=encoding)
73 if IS_WINDOWS: 76 if IS_WINDOWS:
74 self._output_win(commands) 77 self._output_win(commands)
75 else: 78 else:
76 self._output_unix(commands) 79 self._output_unix(commands)
82 self.output('\nU\n') 85 self.output('\nU\n')
83 86
84 def _getqueues_unix(self): 87 def _getqueues_unix(self):
85 queues = [] 88 queues = []
86 try: 89 try:
87 output = subprocess.check_output(['lpstat','-p'], universal_newlines=True) 90 output = subprocess.check_output(
91 ['lpstat', '-p'],
92 universal_newlines=True,
93 env={"LANG": "en_US.UTF-8"}
94 )
88 except subprocess.CalledProcessError: 95 except subprocess.CalledProcessError:
89 return [] 96 return []
90 for line in output.split('\n'): 97 for line in output.split('\n'):
91 if line.startswith('printer'): 98 if line.startswith('printer'):
92 queues.append(line.split(' ')[1]) 99 queues.append(line.split(' ')[1])
93 return queues 100 return queues
94 101
95 def _getqueues_win(self): 102 def _getqueues_win(self):
96 printers = [] 103 printers = []
97 for (a,b,name,d) in win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL): 104 for a, b, name, d in win32print.EnumPrinters(win32print.PRINTER_ENUM_LOCAL):
98 printers.append(name) 105 printers.append(name)
99 return printers 106 return printers
100 107
101 def getqueues(self): 108 def getqueues(self):
102 """Returns a list of printer queues on local machine""" 109 """Returns a list of printer queues on local machine"""
119 """ 126 """
120 commands = '\n' 127 commands = '\n'
121 if direct_thermal: 128 if direct_thermal:
122 commands += 'OD\n' 129 commands += 'OD\n'
123 if label_height: 130 if label_height:
124 commands += 'Q%s,%s\n'%(label_height[0],label_height[1]) 131 commands += 'Q%s,%s\n' % (label_height[0], label_height[1])
125 if label_width: 132 if label_width:
126 commands += 'q%s\n'%label_width 133 commands += 'q%s\n' % label_width
127 self.output(commands) 134 self.output(commands)
128 135
129 def reset_default(self): 136 def reset_default(self):
130 """Reset the printer to factory settings using EPL2""" 137 """Reset the printer to factory settings using EPL2"""
131 self.output('\n^default\n') 138 self.output('\n^default\n')
133 def reset(self): 140 def reset(self):
134 """Resets the printer using EPL2 - equivalent to switching off/on""" 141 """Resets the printer using EPL2 - equivalent to switching off/on"""
135 self.output('\n^@\n') 142 self.output('\n^@\n')
136 143
137 def autosense(self): 144 def autosense(self):
138 """Run AutoSense by sending an EPL2 command 145 """Run AutoSense by sending an EPL2 command
139 Get the printer to detect label and gap length and set the sensor levels 146 Get the printer to detect label and gap length and set the sensor levels
140 """ 147 """
141 self.output('\nxa\n') 148 self.output('\nxa\n')
142 149
143 def store_graphic(self, name, filename): 150 def store_graphic(self, name, filename):
144 """Store a 1 bit PCX file on the label printer, using EPL2. 151 """Store a 1 bit PCX file on the label printer, using EPL2.
145 152
146 name - name to be used on printer 153 name - name to be used on printer
147 filename - local filename 154 filename - local filename
148 """ 155 """
149 assert filename.lower().endswith('.pcx') 156 assert filename.lower().endswith('.pcx')
150 commands = '\nGK"%s"\n'%name 157 commands = '\nGK"%s"\n' % name
151 commands += 'GK"%s"\n'%name 158 commands += 'GK"%s"\n' % name
152 size = os.path.getsize(filename) 159 size = os.path.getsize(filename)
153 commands += 'GM"%s"%s\n'%(name,size) 160 commands += 'GM"%s"%s\n' % (name, size)
154 self.output(commands) 161 self.output(commands)
155 self.output(open(filename,'rb').read()) 162 self.output(open(filename, 'rb').read())
156 163
157 def print_graphic(self, x, y, width, length, data, qty): 164 def print_graphic(self, x, y, width, length, data, qty):
158 """Print a label from 1 bit data, using EPL2 165 """Print a label from 1 bit data, using EPL2
159 166
160 x,y - top left coordinates of the image, in dots 167 x,y - top left coordinates of the image, in dots
161 width - width of image, in dots. Must be a multiple of 8. 168 width - width of image, in dots. Must be a multiple of 8.
162 length - length of image, in dots 169 length - length of image, in dots
163 data - raw graphical data, in bytes 170 data - raw graphical data, in bytes
164 qty - number of labels to print 171 qty - number of labels to print
165 """ 172 """
166 assert type(data) == bytes 173 assert isinstance(data, bytes)
167 assert width % 8 == 0 # make sure width is a multiple of 8 174 assert width % 8 == 0 # make sure width is a multiple of 8
168 assert (width//8) * length == len(data) 175 assert (width // 8) * length == len(data)
169 commands = b"\nN\nGW%d,%d,%d,%d,%s\nP%d\n"%(x, y, width//8, length, data, qty) 176 commands = b'\nN\nGW%d,%d,%d,%d,%s\nP%d\n' % (
177 x,
178 y,
179 width // 8,
180 length,
181 data,
182 qty,
183 )
170 self.output(commands) 184 self.output(commands)
185
171 186
172 if __name__ == '__main__': 187 if __name__ == '__main__':
173 z = Zebra() 188 z = Zebra()
174 print('Printer queues found:',z.getqueues()) 189 print('Printer queues found:', z.getqueues())
175 z.setqueue('zebra_python_unittest') 190 z.setqueue('zebra_python_unittest')
176 z.setup(direct_thermal=True, label_height=(406,32), label_width=609) # 3" x 2" direct thermal label 191 z.setup(
177 z.store_graphic('logo','logo.pcx') 192 direct_thermal=True, label_height=(406, 32), label_width=609
193 ) # 3" x 2" direct thermal label
194 z.store_graphic('logo', 'logo.pcx')
178 label = """ 195 label = """
179 N 196 N
180 GG419,40,"logo" 197 GG419,40,"logo"
181 A40,80,0,4,1,1,N,"Tangerine Duck 4.4%" 198 A40,80,0,4,1,1,N,"Tangerine Duck 4.4%"
182 A40,198,0,3,1,1,N,"Duty paid on 39.9l" 199 A40,198,0,3,1,1,N,"Duty paid on 39.9l"
183 A40,240,0,3,1,1,N,"Gyle: 127 Best Before: 16/09/2011" 200 A40,240,0,3,1,1,N,"Gyle: 127 Best Before: 16/09/2011"
184 A40,320,0,4,1,1,N,"Pump & Truncheon" 201 A40,320,0,4,1,1,N,"Pump & Truncheon"
185 P1 202 P1
186 """ 203 """
187 z.output(label) 204 z.output(label)
188