diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 5eb3efd1b8e..c57238bd659 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -16,6 +16,14 @@ import pickle import re import subprocess import traceback + +try: + from .. escpos import * + from .. escpos.exceptions import * + from .. escpos.printer import Usb +except ImportError: + escpos = printer = None + from threading import Thread, Lock from Queue import Queue, Empty @@ -24,13 +32,6 @@ try: except ImportError: usb = None -try: - from .. import escpos - from ..escpos import printer - from ..escpos import supported_devices -except ImportError: - escpos = printer = None - from PIL import Image from openerp import http @@ -110,24 +111,19 @@ class EscposDriver(Thread): self.start() def get_escpos_printer(self): - try: - printers = self.connected_usb_devices() - if len(printers) > 0: - self.set_status('connected','Connected to '+printers[0]['name']) - return escpos.printer.Usb(printers[0]['vendor'], printers[0]['product']) - else: - self.set_status('disconnected','Printer Not Found') - return None - except Exception as e: - self.set_status('error',str(e)) + + printers = self.connected_usb_devices() + if len(printers) > 0: + self.set_status('connected','Connected to '+printers[0]['name']) + return Usb(printers[0]['vendor'], printers[0]['product']) + else: + self.set_status('disconnected','Printer Not Found') return None def get_status(self): self.push_task('status') return self.status - - def open_cashbox(self,printer): printer.cashdraw(2) printer.cashdraw(5) @@ -150,11 +146,13 @@ class EscposDriver(Thread): _logger.warning('ESC/POS Device Disconnected: '+message) def run(self): + if not escpos: _logger.error('ESC/POS cannot initialize, please verify system dependencies.') return while True: try: + error = True timestamp, task, data = self.queue.get(True) printer = self.get_escpos_printer() @@ -162,6 +160,7 @@ class EscposDriver(Thread): if printer == None: if task != 'status': self.queue.put((timestamp,task,data)) + error = False time.sleep(5) continue elif task == 'receipt': @@ -178,11 +177,25 @@ class EscposDriver(Thread): self.print_status(printer) elif task == 'status': pass + error = False + except NoDeviceError as e: + print "No device found %s" %str(e) + except HandleDeviceError as e: + print "Impossible to handle the device due to previous error %s" % str(e) + except TicketNotPrinted as e: + print "The ticket does not seems to have been fully printed %s" % str(e) + except NoStatusError as e: + print "Impossible to get the status of the printer %s" % str(e) except Exception as e: self.set_status('error', str(e)) errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n' _logger.error(errmsg); + finally: + if error: + self.queue.put((timestamp, task, data)) + if printer: + printer.close() def push_task(self,task, data = None): self.lockedstart() diff --git a/addons/hw_escpos/escpos/constants.py b/addons/hw_escpos/escpos/constants.py index 7b9d92c61e1..0d1559520d6 100644 --- a/addons/hw_escpos/escpos/constants.py +++ b/addons/hw_escpos/escpos/constants.py @@ -8,6 +8,13 @@ CTL_FF = '\x0c' # Form feed CTL_CR = '\x0d' # Carriage return CTL_HT = '\x09' # Horizontal tab CTL_VT = '\x0b' # Vertical tab + +# RT Status commands +DLE_EOT_PRINTER = '\x10\x04\x01' # Transmit printer status +DLE_EOT_OFFLINE = '\x10\x04\x02' +DLE_EOT_ERROR = '\x10\x04\x03' +DLE_EOT_PAPER = '\x10\x04\x04' + # Printer hardware HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes HW_SELECT = '\x1b\x3d\x01' # Printer select diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py index fc899883a6d..9fbcda93993 100644 --- a/addons/hw_escpos/escpos/escpos.py +++ b/addons/hw_escpos/escpos/escpos.py @@ -1,13 +1,5 @@ # -*- coding: utf-8 -*- -''' -@author: Manuel F Martinez -@organization: Bashlinux -@copyright: Copyright (c) 2012 Bashlinux -@license: GPL -''' - -import logging import time import copy import io @@ -21,19 +13,15 @@ import xml.dom.minidom as minidom from PIL import Image -_logger = logging.getLogger(__name__) - try: import jcconv except ImportError: jcconv = None - _logger.warning('ESC/POS: please install jcconv for improved Japanese receipt printing:\n # pip install jcconv') try: import qrcode except ImportError: qrcode = None - _logger.warning('ESC/POS: please install the qrcode python module for qrcode printing in point of sale receipts:\n # pip install qrcode') from constants import * from exceptions import * @@ -107,14 +95,14 @@ class StyleStack: 'off': TXT_BOLD_OFF, 'on': TXT_BOLD_ON, # must be issued after 'size' command - # because ESC ! resets ESC E + # because ESC ! resets ESC - '_order': 10, }, 'font': { 'a': TXT_FONT_A, 'b': TXT_FONT_B, # must be issued after 'size' command - # because ESC ! resets ESC M + # because ESC ! resets ESC - '_order': 10, }, 'size': { @@ -122,7 +110,7 @@ class StyleStack: 'double-height': TXT_2HEIGHT, 'double-width': TXT_2WIDTH, 'double': TXT_DOUBLE, - '_order': 1, + '_order': 1, }, 'color': { 'black': TXT_COLOR_BLACK, @@ -181,9 +169,8 @@ class StyleStack: def to_escpos(self): """ converts the current style to an escpos command string """ cmd = '' - # Sort commands because some commands affect others (see _order attributes above) ordered_cmds = self.cmds.keys() - ordered_cmds.sort(lambda x, y: cmp(self.cmds[x]['_order'], self.cmds[y]['_order'])) + ordered_cmds.sort(lambda x,y: cmp(self.cmds[x]['_order'], self.cmds[y]['_order'])) for style in ordered_cmds: cmd += self.cmds[style][self.get(style)] return cmd diff --git a/addons/hw_escpos/escpos/exceptions.py b/addons/hw_escpos/escpos/exceptions.py index adbe6484f3e..51d860f1368 100644 --- a/addons/hw_escpos/escpos/exceptions.py +++ b/addons/hw_escpos/escpos/exceptions.py @@ -78,3 +78,39 @@ class CashDrawerError(Error): def __str__(self): return "Valid pin must be set to send pulse" + +class NoStatusError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 70 + + def __str__(self): + return "Impossible to get status from the printer" + +class TicketNotPrinted(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 80 + + def __str__(self): + return "A part of the ticket was not been printed" + +class NoDeviceError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 90 + + def __str__(self): + return "Impossible to find the printer Device" + +class HandleDeviceError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 100 + + def __str__(self): + return "Impossible to handle device" diff --git a/addons/hw_escpos/escpos/printer.py b/addons/hw_escpos/escpos/printer.py index 523f9a39779..37a530d8373 100644 --- a/addons/hw_escpos/escpos/printer.py +++ b/addons/hw_escpos/escpos/printer.py @@ -1,10 +1,4 @@ #!/usr/bin/python -''' -@author: Manuel F Martinez -@organization: Bashlinux -@copyright: Copyright (c) 2012 Bashlinux -@license: GPL -''' import usb.core import usb.util @@ -14,6 +8,7 @@ import socket from escpos import * from constants import * from exceptions import * +from time import sleep class Usb(Escpos): """ Define USB printer """ @@ -26,6 +21,9 @@ class Usb(Escpos): @param in_ep : Input end point @param out_ep : Output end point """ + + self.errorText = "ERROR PRINTER\n\n\n\n\n\n"+PAPER_FULL_CUT + self.idVendor = idVendor self.idProduct = idProduct self.interface = interface @@ -33,35 +31,102 @@ class Usb(Escpos): self.out_ep = out_ep self.open() - def open(self): """ Search device on USB tree and set is as escpos device """ + self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) if self.device is None: - print "Cable isn't plugged in" - - if self.device.is_kernel_driver_active(0): - try: - self.device.detach_kernel_driver(0) - except usb.core.USBError as e: - print "Could not detatch kernel driver: %s" % str(e) - + raise NoDeviceError() try: + if self.device.is_kernel_driver_active(self.interface): + self.device.detach_kernel_driver(self.interface) self.device.set_configuration() - self.device.reset() + usb.util.claim_interface(self.device, self.interface) except usb.core.USBError as e: - print "Could not set configuration: %s" % str(e) + raise HandleDeviceError(e) + def close(self): + i = 0 + while True: + try: + if not self.device.is_kernel_driver_active(self.interface): + usb.util.release_interface(self.device, self.interface) + self.device.attach_kernel_driver(self.interface) + usb.util.dispose_resources(self.device) + else: + self.device = None + return True + except usb.core.USBError as e: + i += 1 + if i > 100: + return False + + sleep(0.1) def _raw(self, msg): """ Print any command sent in raw format """ - self.device.write(self.out_ep, msg, self.interface) + if len(msg) != self.device.write(self.out_ep, msg, self.interface): + self.device.write(self.out_ep, self.errorText, self.interface) + raise TicketNotPrinted() + + def __extract_status(self): + maxiterate = 0 + rep = None + while rep == None: + maxiterate += 1 + if maxiterate > 10000: + raise NoStatusError() + r = self.device.read(self.in_ep, 20, self.interface).tolist() + while len(r): + rep = r.pop() + return rep + def get_printer_status(self): + status = { + 'printer': {}, + 'offline': {}, + 'error' : {}, + 'paper' : {}, + } + + self.device.write(self.out_ep, DLE_EOT_PRINTER, self.interface) + printer = self.__extract_status() + self.device.write(self.out_ep, DLE_EOT_OFFLINE, self.interface) + offline = self.__extract_status() + self.device.write(self.out_ep, DLE_EOT_ERROR, self.interface) + error = self.__extract_status() + self.device.write(self.out_ep, DLE_EOT_PAPER, self.interface) + paper = self.__extract_status() + + status['printer']['status_code'] = printer + status['printer']['status_error'] = not ((printer & 147) == 18) + status['printer']['online'] = not bool(printer & 8) + status['printer']['recovery'] = bool(printer & 32) + status['printer']['paper_feed_on'] = bool(printer & 64) + status['printer']['drawer_pin_high'] = bool(printer & 4) + status['offline']['status_code'] = offline + status['offline']['status_error'] = not ((offline & 147) == 18) + status['offline']['cover_open'] = bool(offline & 4) + status['offline']['paper_feed_on'] = bool(offline & 8) + status['offline']['paper'] = not bool(offline & 32) + status['offline']['error'] = bool(offline & 64) + status['error']['status_code'] = error + status['error']['status_error'] = not ((error & 147) == 18) + status['error']['recoverable'] = bool(error & 4) + status['error']['autocutter'] = bool(error & 8) + status['error']['unrecoverable'] = bool(error & 32) + status['error']['auto_recoverable'] = not bool(error & 64) + status['paper']['status_code'] = paper + status['paper']['status_error'] = not ((paper & 147) == 18) + status['paper']['near_end'] = bool(paper & 12) + status['paper']['present'] = not bool(paper & 96) + + return status def __del__(self): """ Release USB interface """ if self.device: - usb.util.dispose_resources(self.device) + self.close() self.device = None @@ -134,3 +199,4 @@ class Network(Escpos): def __del__(self): """ Close TCP connection """ self.device.close() + diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py index 237b301bb0e..057674c1ca1 100644 --- a/addons/hw_scanner/controllers/main.py +++ b/addons/hw_scanner/controllers/main.py @@ -107,7 +107,7 @@ class Scanner(Thread): if status == 'error' and message: _logger.error('Barcode Scanner Error: '+message) elif status == 'disconnected' and message: - _logger.warning('Disconnected Barcode Scanner: '+message) + _logger.info('Disconnected Barcode Scanner: %s', message) def get_device(self): try: @@ -168,41 +168,41 @@ class Scanner(Thread): device.ungrab() except Exception as e: self.set_status('error',str(e)) - device = self.get_device() - if not device: - time.sleep(5) # wait until a suitable device is plugged else: - try: - device.grab() - shift = False - barcode = [] + time.sleep(5) # wait until a suitable device is plugged + device = self.get_device() - while True: # keycode loop - r,w,x = select([device],[],[],5) - if len(r) == 0: # timeout - break - events = device.read() + try: + device.grab() + shift = False + barcode = [] - for event in events: - if event.type == evdev.ecodes.EV_KEY: - #_logger.debug('Evdev Keyboard event %s',evdev.categorize(event)) - if event.value == 1: # keydown events - if event.code in self.keymap: - if shift: - barcode.append(self.keymap[event.code][1]) - else: - barcode.append(self.keymap[event.code][0]) - elif event.code == 42 or event.code == 54: # SHIFT - shift = True - elif event.code == 28: # ENTER, end of barcode - self.barcodes.put( (time.time(),''.join(barcode)) ) - barcode = [] - elif event.value == 0: #keyup events - if event.code == 42 or event.code == 54: # LEFT SHIFT - shift = False + while True: # keycode loop + r,w,x = select([device],[],[],5) + if len(r) == 0: # timeout + break + events = device.read() - except Exception as e: - self.set_status('error',str(e)) + for event in events: + if event.type == evdev.ecodes.EV_KEY: + #_logger.debug('Evdev Keyboard event %s',evdev.categorize(event)) + if event.value == 1: # keydown events + if event.code in self.keymap: + if shift: + barcode.append(self.keymap[event.code][1]) + else: + barcode.append(self.keymap[event.code][0]) + elif event.code == 42 or event.code == 54: # SHIFT + shift = True + elif event.code == 28: # ENTER, end of barcode + self.barcodes.put( (time.time(),''.join(barcode)) ) + barcode = [] + elif event.value == 0: #keyup events + if event.code == 42 or event.code == 54: # LEFT SHIFT + shift = False + + except Exception as e: + self.set_status('error',str(e)) s = Scanner() @@ -212,5 +212,4 @@ class ScannerDriver(hw_proxy.Proxy): @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*') def scanner(self): return s.get_barcode() - - +