1 # -*- coding: utf-8 -*-
14 import openerp.addons.hw_proxy.controllers.main as hw_proxy
19 from threading import Thread, Lock
20 from Queue import Queue, Empty
29 from ..escpos import printer
30 from ..escpos import supported_devices
32 escpos = printer = None
36 from openerp import http
37 from openerp.http import request
38 from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template
39 from openerp.tools.translate import _
41 _logger = logging.getLogger(__name__)
44 class EscposDriver(Thread):
49 self.status = {'status':'connecting', 'messages':[]}
51 def supported_devices(self):
52 if not os.path.isfile('escpos_devices.pickle'):
53 return supported_devices.device_list
56 f = open('escpos_devices.pickle','r')
59 except Exception as e:
60 self.set_status('error',str(e))
61 return supported_devices.device_list
63 def add_supported_device(self,device_string):
64 r = re.compile('[0-9A-Fa-f]{4}:[0-9A-Fa-f]{4}');
65 match = r.search(device_string)
67 match = match.group().split(':')
68 vendor = int(match[0],16)
69 product = int(match[1],16)
70 name = device_string.split('ID')
75 _logger.info('ESC/POS: adding support for device: '+match[0]+':'+match[1]+' '+name)
77 device_list = supported_devices.device_list[:]
78 if os.path.isfile('escpos_devices.pickle'):
80 f = open('escpos_devices.pickle','r')
81 device_list = pickle.load(f)
83 except Exception as e:
84 self.set_status('error',str(e))
92 f = open('escpos_devices.pickle','w+')
94 pickle.dump(device_list,f)
96 except Exception as e:
97 self.set_status('error',str(e))
99 def connected_usb_devices(self):
102 for device in self.supported_devices():
103 if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
104 connected.append(device)
107 def lockedstart(self):
109 if not self.isAlive():
113 def get_escpos_printer(self):
115 printers = self.connected_usb_devices()
116 if len(printers) > 0:
117 self.set_status('connected','Connected to '+printers[0]['name'])
118 return escpos.printer.Usb(printers[0]['vendor'], printers[0]['product'])
120 self.set_status('disconnected','Printer Not Found')
122 except Exception as e:
123 self.set_status('error',str(e))
126 def get_status(self):
127 self.push_task('status')
132 def open_cashbox(self,printer):
136 def set_status(self, status, message = None):
137 _logger.info(status+' : '+ (message or 'no message'))
138 if status == self.status['status']:
139 if message != None and (len(self.status['messages']) == 0 or message != self.status['messages'][-1]):
140 self.status['messages'].append(message)
142 self.status['status'] = status
144 self.status['messages'] = [message]
146 self.status['messages'] = []
148 if status == 'error' and message:
149 _logger.error('ESC/POS Error: '+message)
150 elif status == 'disconnected' and message:
151 _logger.warning('ESC/POS Device Disconnected: '+message)
155 _logger.error('ESC/POS cannot initialize, please verify system dependencies.')
159 timestamp, task, data = self.queue.get(True)
161 printer = self.get_escpos_printer()
165 self.queue.put((timestamp,task,data))
168 elif task == 'receipt':
169 if timestamp >= time.time() - 1 * 60 * 60:
170 self.print_receipt_body(printer,data)
172 elif task == 'xml_receipt':
173 if timestamp >= time.time() - 1 * 60 * 60:
174 printer.receipt(data)
175 elif task == 'cashbox':
176 if timestamp >= time.time() - 12:
177 self.open_cashbox(printer)
178 elif task == 'printstatus':
179 self.print_status(printer)
180 elif task == 'status':
183 except Exception as e:
184 self.set_status('error', str(e))
185 errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n'
186 _logger.error(errmsg);
188 def push_task(self,task, data = None):
190 self.queue.put((time.time(),task,data))
192 def print_status(self,eprint):
193 localips = ['0.0.0.0','127.0.0.1','127.0.1.1']
194 ips = [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
195 ips = [ ip for ip in ips if ip not in localips ]
197 eprint.set(align='center',type='b',height=2,width=2)
198 eprint.text('PosBox Status\n')
200 eprint.set(align='center')
203 eprint.text('ERROR: Could not connect to LAN\n\nPlease check that the PosBox is correc-\ntly connected with a network cable,\n that the LAN is setup with DHCP, and\nthat network addresses are available')
205 eprint.text('IP Address:\n'+ips[0]+'\n')
207 eprint.text('IP Addresses:\n')
212 eprint.text('\nHomepage:\nhttp://'+ips[0]+':8069\n')
217 def print_receipt_body(self,eprint,receipt):
220 return string != True and bool(string) and string.strip()
223 return ("{0:."+str(receipt['precision']['price'])+"f}").format(amount)
226 return ("{0:."+str(receipt['precision']['money'])+"f}").format(amount)
228 def quantity(amount):
229 if math.floor(amount) != amount:
230 return ("{0:."+str(receipt['precision']['quantity'])+"f}").format(amount)
234 def printline(left, right='', width=40, ratio=0.5, indent=0):
235 lwidth = int(width * ratio)
236 rwidth = width - lwidth
237 lwidth = lwidth - indent
240 if len(left) != lwidth:
241 left = left + ' ' * (lwidth - len(left))
243 right = right[-rwidth:]
244 if len(right) != rwidth:
245 right = ' ' * (rwidth - len(right)) + right
247 return ' ' * indent + left + right + '\n'
250 taxes = receipt['tax_details']
252 eprint.text(printline(tax['tax']['name'],price(tax['amount']), width=40,ratio=0.6))
255 if receipt['company']['logo']:
256 eprint.set(align='center')
257 eprint.print_base64_image(receipt['company']['logo'])
260 eprint.set(align='center',type='b',height=2,width=2)
261 eprint.text(receipt['company']['name'] + '\n')
263 eprint.set(align='center',type='b')
264 if check(receipt['shop']['name']):
265 eprint.text(receipt['shop']['name'] + '\n')
266 if check(receipt['company']['contact_address']):
267 eprint.text(receipt['company']['contact_address'] + '\n')
268 if check(receipt['company']['phone']):
269 eprint.text('Tel:' + receipt['company']['phone'] + '\n')
270 if check(receipt['company']['vat']):
271 eprint.text('VAT:' + receipt['company']['vat'] + '\n')
272 if check(receipt['company']['email']):
273 eprint.text(receipt['company']['email'] + '\n')
274 if check(receipt['company']['website']):
275 eprint.text(receipt['company']['website'] + '\n')
276 if check(receipt['header']):
277 eprint.text(receipt['header']+'\n')
278 if check(receipt['cashier']):
279 eprint.text('-'*32+'\n')
280 eprint.text('Served by '+receipt['cashier']+'\n')
284 eprint.set(align='center')
285 for line in receipt['orderlines']:
286 pricestr = price(line['price_display'])
287 if line['discount'] == 0 and line['unit_name'] == 'Unit(s)' and line['quantity'] == 1:
288 eprint.text(printline(line['product_name'],pricestr,ratio=0.6))
290 eprint.text(printline(line['product_name'],ratio=0.6))
291 if line['discount'] != 0:
292 eprint.text(printline('Discount: '+str(line['discount'])+'%', ratio=0.6, indent=2))
293 if line['unit_name'] == 'Unit(s)':
294 eprint.text( printline( quantity(line['quantity']) + ' x ' + price(line['price']), pricestr, ratio=0.6, indent=2))
296 eprint.text( printline( quantity(line['quantity']) + line['unit_name'] + ' x ' + price(line['price']), pricestr, ratio=0.6, indent=2))
298 # Subtotal if the taxes are not included
300 if money(receipt['subtotal']) != money(receipt['total_with_tax']):
301 eprint.text(printline('','-------'));
302 eprint.text(printline(_('Subtotal'),money(receipt['subtotal']),width=40, ratio=0.6))
304 #eprint.text(printline(_('Taxes'),money(receipt['total_tax']),width=40, ratio=0.6))
309 eprint.text(printline('','-------'));
310 eprint.set(align='center',height=2)
311 eprint.text(printline(_(' TOTAL'),money(receipt['total_with_tax']),width=40, ratio=0.6))
315 eprint.set(align='center')
316 for line in receipt['paymentlines']:
317 eprint.text(printline(line['journal'], money(line['amount']), ratio=0.6))
320 eprint.set(align='center',height=2)
321 eprint.text(printline(_(' CHANGE'),money(receipt['change']),width=40, ratio=0.6))
322 eprint.set(align='center')
326 if receipt['total_discount'] != 0:
327 eprint.text(printline(_('Discounts'),money(receipt['total_discount']),width=40, ratio=0.6))
330 #eprint.text(printline(_('Taxes'),money(receipt['total_tax']),width=40, ratio=0.6))
333 if check(receipt['footer']):
334 eprint.text('\n'+receipt['footer']+'\n\n')
335 eprint.text(receipt['name']+'\n')
336 eprint.text( str(receipt['date']['date']).zfill(2)
337 +'/'+ str(receipt['date']['month']+1).zfill(2)
338 +'/'+ str(receipt['date']['year']).zfill(4)
339 +' '+ str(receipt['date']['hour']).zfill(2)
340 +':'+ str(receipt['date']['minute']).zfill(2) )
343 driver = EscposDriver()
345 driver.push_task('printstatus')
347 hw_proxy.drivers['escpos'] = driver
349 class EscposProxy(hw_proxy.Proxy):
351 @http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*')
352 def open_cashbox(self):
353 _logger.info('ESC/POS: OPEN CASHBOX')
354 driver.push_task('cashbox')
356 @http.route('/hw_proxy/print_receipt', type='json', auth='none', cors='*')
357 def print_receipt(self, receipt):
358 _logger.info('ESC/POS: PRINT RECEIPT')
359 driver.push_task('receipt',receipt)
361 @http.route('/hw_proxy/print_xml_receipt', type='json', auth='none', cors='*')
362 def print_xml_receipt(self, receipt):
363 _logger.info('ESC/POS: PRINT XML RECEIPT')
364 driver.push_task('xml_receipt',receipt)
366 @http.route('/hw_proxy/escpos/add_supported_device', type='http', auth='none', cors='*')
367 def add_supported_device(self, device_string):
368 _logger.info('ESC/POS: ADDED NEW DEVICE:'+device_string)
369 driver.add_supported_device(device_string)
370 return "The device:\n"+device_string+"\n has been added to the list of supported devices.<br/><a href='/hw_proxy/status'>Ok</a>"
372 @http.route('/hw_proxy/escpos/reset_supported_devices', type='http', auth='none', cors='*')
373 def reset_supported_devices(self):
375 os.remove('escpos_devices.pickle')
376 except Exception as e:
378 return 'The list of supported devices has been reset to factory defaults.<br/><a href="/hw_proxy/status">Ok</a>'