From ee3fa52bbe1933a593ad765ca2bc008f0375b0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Fri, 24 Jan 2014 18:31:19 +0100 Subject: [PATCH 1/6] [IMP] hw_escpos: codepage switch sequence for all encodings supported by the TM-T20 printer bzr revid: fva@openerp.com-20140124173119-naktj3usjuapb9sf --- addons/hw_escpos/controllers/main.py | 8 +++++-- addons/hw_escpos/escpos/constants.py | 32 ++++++++++++++++++++++++++++ addons/hw_escpos/escpos/escpos.py | 16 ++++++++++++-- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index e8915a48fed..4fde44de4b3 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -49,19 +49,23 @@ class EscposDriver(hw_proxy.Proxy): @http.route('/hw_proxy/open_cashbox', type='json', auth='admin') def open_cashbox(self): - print 'ESC/POS: OPEN CASHBOX' + _logger.info('ESC/POS: OPEN CASHBOX') eprint = self.get_escpos_printer() if eprint != None: eprint.cashdraw(2) eprint.cashdraw(5) + else: + _logger.error('ESC/POS: OPEN CASHBOX: could not find printer') @http.route('/hw_proxy/print_receipt', type='json', auth='admin') def print_receipt(self, receipt): - print 'ESC/POS: PRINT RECEIPT' + _logger.info('ESC/POS: PRINT RECEIPT') eprint = self.get_escpos_printer() if eprint != None: self.print_receipt_body(eprint,receipt) eprint.cut() + else: + _logger.error('ESC/POS: PRINT RECEIPT: could not find printer') def print_receipt_body(self,eprint,receipt): diff --git a/addons/hw_escpos/escpos/constants.py b/addons/hw_escpos/escpos/constants.py index ebffc62729e..723c67013f0 100644 --- a/addons/hw_escpos/escpos/constants.py +++ b/addons/hw_escpos/escpos/constants.py @@ -44,10 +44,42 @@ TXT_ENC_PC865 = '\x1b\x74\x05' # PC865 Nordic TXT_ENC_KANJI6 = '\x1b\x74\x06' # One-pass Kanji, Hiragana TXT_ENC_KANJI7 = '\x1b\x74\x07' # One-pass Kanji TXT_ENC_KANJI8 = '\x1b\x74\x08' # One-pass Kanji +TXT_ENC_PC851 = '\x1b\x74\x0b' # PC851 Greek +TXT_ENC_PC853 = '\x1b\x74\x0c' # PC853 Turkish +TXT_ENC_PC857 = '\x1b\x74\x0d' # PC857 Turkish +TXT_ENC_PC737 = '\x1b\x74\x0e' # PC737 Greek +TXT_ENC_8859_7 = '\x1b\x74\x0f' # ISO8859-7 Greek TXT_ENC_WPC1252 = '\x1b\x74\x10' # WPC1252 TXT_ENC_PC866 = '\x1b\x74\x11' # PC866 Cyrillic #2 TXT_ENC_PC852 = '\x1b\x74\x12' # PC852 Latin2 TXT_ENC_PC858 = '\x1b\x74\x13' # PC858 Euro +TXT_ENC_KU42 = '\x1b\x74\x14' # KU42 Thai +TXT_ENC_TIS11 = '\x1b\x74\x15' # TIS11 Thai +TXT_ENC_TIS18 = '\x1b\x74\x1a' # TIS18 Thai +TXT_ENC_TCVN3 = '\x1b\x74\x1e' # TCVN3 Vietnamese +TXT_ENC_TCVN3B = '\x1b\x74\x1f' # TCVN3 Vietnamese +TXT_ENC_PC720 = '\x1b\x74\x20' # PC720 Arabic +TXT_ENC_WPC775 = '\x1b\x74\x21' # WPC775 Baltic Rim +TXT_ENC_PC855 = '\x1b\x74\x22' # PC855 Cyrillic +TXT_ENC_PC861 = '\x1b\x74\x23' # PC861 Icelandic +TXT_ENC_PC862 = '\x1b\x74\x24' # PC862 Hebrew +TXT_ENC_PC864 = '\x1b\x74\x25' # PC864 Arabic +TXT_ENC_PC869 = '\x1b\x74\x26' # PC869 Greek +TXT_ENC_8859_2 = '\x1b\x74\x27' # ISO8859-2 Latin2 +TXT_ENC_8859_9 = '\x1b\x74\x28' # ISO8859-2 Latin9 +TXT_ENC_PC1098 = '\x1b\x74\x29' # PC1098 Farsi +TXT_ENC_PC1118 = '\x1b\x74\x2a' # PC1118 Lithuanian +TXT_ENC_PC1119 = '\x1b\x74\x2b' # PC1119 Lithuanian +TXT_ENC_PC1125 = '\x1b\x74\x2c' # PC1125 Ukrainian +TXT_ENC_WPC1250 = '\x1b\x74\x2d' # WPC1250 Latin2 +TXT_ENC_WPC1251 = '\x1b\x74\x2e' # WPC1251 Cyrillic +TXT_ENC_WPC1253 = '\x1b\x74\x2f' # WPC1253 Greek +TXT_ENC_WPC1254 = '\x1b\x74\x30' # WPC1254 Turkish +TXT_ENC_WPC1255 = '\x1b\x74\x31' # WPC1255 Hebrew +TXT_ENC_WPC1256 = '\x1b\x74\x32' # WPC1256 Arabic +TXT_ENC_WPC1257 = '\x1b\x74\x33' # WPC1257 Baltic Rim +TXT_ENC_WPC1258 = '\x1b\x74\x34' # WPC1258 Vietnamese +TXT_ENC_KZ1048 = '\x1b\x74\x35' # KZ-1048 Kazakhstan TXT_ENC_KATAKANA_MAP = { # Maps UTF-8 Katakana symbols to KATAKANA Page Codes diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py index 0e9a965f2ff..df96646c9d7 100644 --- a/addons/hw_escpos/escpos/escpos.py +++ b/addons/hw_escpos/escpos/escpos.py @@ -207,16 +207,28 @@ class Escpos: encoded = '' encoding = self.encoding # we reuse the last encoding to prevent code page switches at every character encodings = { + # TODO use ordering to prevent useless switches + # TODO Support other encodings not natively supported by python ( Thai, Khazakh, Kanjis ) 'cp437': TXT_ENC_PC437, 'cp850': TXT_ENC_PC850, 'cp852': TXT_ENC_PC852, + 'cp857': TXT_ENC_PC857, 'cp858': TXT_ENC_PC858, - 'cp865': TXT_ENC_PC860, + 'cp860': TXT_ENC_PC860, 'cp863': TXT_ENC_PC863, 'cp865': TXT_ENC_PC865, 'cp866': TXT_ENC_PC866, + 'cp862': TXT_ENC_PC862, + 'cp720': TXT_ENC_PC720, + 'iso8859_2': TXT_ENC_8859_2, + 'iso8859_7': TXT_ENC_8859_7, + 'iso8859_9': TXT_ENC_8859_9, + 'cp1254' : TXT_ENC_WPC1254, + 'cp1255' : TXT_ENC_WPC1255, + 'cp1256' : TXT_ENC_WPC1256, + 'cp1257' : TXT_ENC_WPC1257, + 'cp1258' : TXT_ENC_WPC1258, 'katakana' : TXT_ENC_KATAKANA, - # TODO Support other encodings not natively supported by python } remaining = copy.copy(encodings) From f1fa4fa2305159d15f5c41ff8b83ae1d19b6b077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Sun, 26 Jan 2014 19:35:00 +0100 Subject: [PATCH 2/6] [IMP] hw_proxy: better driver status reporting with hw_proxy/status, improved printing reliability bzr revid: fva@openerp.com-20140126183500-ejbm9d4jafl817tq --- addons/hw_escpos/controllers/main.py | 123 ++++++++++++++++++++------ addons/hw_proxy/controllers/main.py | 53 ++++++++++- addons/hw_scanner/controllers/main.py | 41 ++++++--- 3 files changed, 174 insertions(+), 43 deletions(-) diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 4fde44de4b3..5d5808208df 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -10,6 +10,8 @@ import random import math import openerp.addons.hw_proxy.controllers.main as hw_proxy import subprocess +from threading import Thread +from Queue import Queue, Empty try: import usb.core @@ -27,11 +29,16 @@ from openerp.addons.web.controllers.main import manifest_list, module_boot, html _logger = logging.getLogger(__name__) -class EscposDriver(hw_proxy.Proxy): - - supported_printers = [ - { 'vendor' : 0x04b8, 'product' : 0x0e03, 'name' : 'Epson TM-T20' } - ] +class EscposDriver(Thread): + def __init__(self): + Thread.__init__(self) + self.queue = None + self.status = {'status':'connecting', 'messages':[]} + + self.supported_printers = [ + { 'vendor' : 0x04b8, 'product' : 0x0e03, 'name' : 'Epson TM-T20' }, + { 'vendor' : 0x04b8, 'product' : 0x0202, 'name' : 'Epson TM-T70' }, + ] def connected_usb_devices(self,devices): connected = [] @@ -41,32 +48,74 @@ class EscposDriver(hw_proxy.Proxy): return connected def get_escpos_printer(self): - printers = self.connected_usb_devices(self.supported_printers) - if len(printers) > 0: - return escpos.printer.Usb(printers[0]['vendor'], printers[0]['product']) - else: + try: + printers = self.connected_usb_devices(self.supported_printers) + 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)) return None - - @http.route('/hw_proxy/open_cashbox', type='json', auth='admin') - def open_cashbox(self): - _logger.info('ESC/POS: OPEN CASHBOX') - eprint = self.get_escpos_printer() - if eprint != None: - eprint.cashdraw(2) - eprint.cashdraw(5) + + def get_status(self): + self.push_task('status') + return self.status + + def open_cashbox(printer): + printer.cashdraw(2) + printer.cashdraw(5) + + def set_status(self, status, message = None): + if status == self.status['status']: + if message != None and message != self.status['messages'][-1]: + self.status['messages'].append(message) else: - _logger.error('ESC/POS: OPEN CASHBOX: could not find printer') - - @http.route('/hw_proxy/print_receipt', type='json', auth='admin') - def print_receipt(self, receipt): - _logger.info('ESC/POS: PRINT RECEIPT') - eprint = self.get_escpos_printer() - if eprint != None: - self.print_receipt_body(eprint,receipt) - eprint.cut() - else: - _logger.error('ESC/POS: PRINT RECEIPT: could not find printer') - + self.status['status'] = status + if message: + self.status['messages'] = [message] + else: + self.status['messages'] = [] + + if status == 'error' and message: + _logger.error('ESC/POS Error: '+message) + elif status == 'disconnected' and message: + _logger.warning('ESC/POS Device Disconnected: '+message) + + def run(self): + self.queue = Queue() + while True: + try: + timestamp, task, data = self.queue.get(True) + + printer = self.get_escpos_printer() + + if printer == None: + if task != 'status': + self.queue.put((timestamp,task,data)) + time.sleep(5) + continue + elif task == 'receipt': + if timestamp >= time.time() - 1 * 60 * 60: + self.print_receipt_body(printer,data) + printer.cut() + elif task == 'cashbox': + if timestamp >= time.time() * 12: + self.open_cashbox(printer) + elif task == 'status': + pass + + except Exception as e: + self.set_status('error', str(e)) + _logger.error(e); + + def push_task(self,task, data = None): + if not self.isAlive(): + self.start() + self.queue.put((time.time(),task,data)) + def print_receipt_body(self,eprint,receipt): def check(string): @@ -207,4 +256,20 @@ class EscposDriver(hw_proxy.Proxy): +'/'+ str(receipt['date']['year']).zfill(4) +' '+ str(receipt['date']['hour']).zfill(2) +':'+ str(receipt['date']['minute']).zfill(2) ) + +driver = EscposDriver() + +hw_proxy.drivers['escpos'] = driver +class EscposProxy(hw_proxy.Proxy): + + @http.route('/hw_proxy/open_cashbox', type='json', auth='admin') + def open_cashbox(self): + _logger.info('ESC/POS: OPEN CASHBOX') + driver.push_task('cashbox') + + @http.route('/hw_proxy/print_receipt', type='json', auth='admin') + def print_receipt(self, receipt): + _logger.info('ESC/POS: PRINT RECEIPT') + driver.push_task('receipt',receipt) + diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index 6ddf94843fd..1888c2304d1 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -7,6 +7,7 @@ import openerp import time import random import subprocess +import simplejson import werkzeug import werkzeug.wrappers _logger = logging.getLogger(__name__) @@ -17,13 +18,23 @@ from openerp.http import request from openerp.addons.web.controllers.main import manifest_list, module_boot, html_template +# drivers modules must add to drivers an object with a get_status() method +# so that 'status' can return the status of all active drivers +drivers = {} + class Proxy(http.Controller): def __init__(self): self.scale = 'closed' self.scale_weight = 0.0 + def get_status(self): + statuses = {} + for driver in drivers: + statuses[driver] = drivers[driver].get_status() + return statuses + @http.route('/hw_proxy/hello', type='http', auth='admin') - def helloajx(self): + def hello(self): return request.make_response('ping', { 'Cache-Control': 'no-cache', 'Content-Type': 'text/html; charset=utf-8', @@ -31,9 +42,43 @@ class Proxy(http.Controller): 'Access-Control-Allow-Methods': 'GET', }) - @http.route('/hw_proxy/handshake', type='json', auth='admin') - def handshake(self): - return True + @http.route('/hw_proxy/status', type='http', auth='admin') + def status_http(self): + resp = '\n\n

Hardware Proxy Status

\n' + statuses = self.get_status() + for driver in statuses: + + status = statuses[driver] + + if status['status'] == 'connecting': + color = 'black' + elif status['status'] == 'connected': + color = 'green' + else: + color = 'red' + + resp += "

"+driver+' : '+status['status']+"

\n" + resp += "\n" + resp += "\n\n\n\n" + + return request.make_response(resp,{ + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/html; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + }) + + @http.route('/hw_proxy/status', type='json', auth='admin') + def status_json(self): + return request.make_response(simplejson.dumps(self.get_status()),{ + 'Cache-Control': 'no-cache', + 'Content-Type': 'text/html; charset=utf-8', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + }) @http.route('/hw_proxy/scan_item_success', type='json', auth='admin') def scan_item_success(self, ean): diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py index cd70a353372..3e487cab404 100644 --- a/addons/hw_scanner/controllers/main.py +++ b/addons/hw_scanner/controllers/main.py @@ -26,7 +26,9 @@ except ImportError: class Scanner(Thread): def __init__(self): Thread.__init__(self) + self.status = {'status':'connecting', 'messages':[]} self.input_dir = '/dev/input/by-id/' + self.barcodes = Queue() self.keymap = { 2: ("1","!"), 3: ("2","@"), @@ -84,6 +86,24 @@ class Scanner(Thread): 57:(" "," "), } + def set_status(self, status, message = None): + if status == self.status['status']: + if message != None and message != self.status['messages'][-1]: + self.status['messages'].append(message) + else: + self.status['status'] = status + if message: + self.status['messages'] = [message] + else: + self.status['messages'] = [] + + if status == 'error' and message: + _logger.error('Barcode Scanner Error: '+message) + elif status == 'disconnected' and message: + _logger.warning('Disconnected Barcode Scanner: '+message) + + + def get_device(self): try: if not evdev: @@ -92,14 +112,16 @@ class Scanner(Thread): keyboards = [ device for device in devices if 'kbd' in device ] scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower()) ] if len(scanners) > 0: + self.set_status('connected','Connected to '+scanners[0]) return evdev.InputDevice(join(self.input_dir,scanners[0])) elif len(keyboards) > 0: + self.set_status('connected','Connected to '+keyboards[0]) return evdev.InputDevice(join(self.input_dir,keyboards[0])) else: - _logger.error('Could not find the Barcode Scanner') + self.set_status('disconnected','Barcode Scanner Not Found') return None except Exception as e: - _logger.error('Found the Barcode Scanner, but unable to access it\n Exception: ' + str(e)) + self.set_status('error',str(e)) return None @http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='admin') @@ -120,6 +142,9 @@ class Scanner(Thread): return barcode except Empty: return '' + + def get_status(self): + return self.status def run(self): """ This will start a loop that catches all keyboard events, parse barcode @@ -138,7 +163,7 @@ class Scanner(Thread): try: device.ungrab() except Exception as e: - _logger.error('Unable to release barcode scanner\n Exception:'+str(e)) + self.set_status('error',str(e)) device = self.get_device() if not device: time.sleep(5) # wait until a suitable device is plugged @@ -173,17 +198,13 @@ class Scanner(Thread): shift = False except Exception as e: - _logger.error('Could not read Barcode Scanner Events:\n Exception: '+str(e)) + self.set_status('error',str(e)) s = Scanner() +hw_proxy.drivers['scanner'] = s + class ScannerDriver(hw_proxy.Proxy): - @http.route('/hw_proxy/is_scanner_connected', type='json', auth='admin') - def is_scanner_connected(self): - if not s.isAlive(): - s.start() - return s.get_device() != None - @http.route('/hw_proxy/scanner', type='json', auth='admin') def scanner(self): if not s.isAlive(): From 7e535526cb8938e6f2b384667ee0fe70e57d1c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Sun, 26 Jan 2014 20:40:55 +0100 Subject: [PATCH 3/6] [FIX] hw_proxy: fix harmless exception on proxy restart bzr revid: fva@openerp.com-20140126194055-pietbyi7ezvdm9ch --- addons/hw_escpos/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index 5d5808208df..36e655cd465 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -32,7 +32,7 @@ _logger = logging.getLogger(__name__) class EscposDriver(Thread): def __init__(self): Thread.__init__(self) - self.queue = None + self.queue = Queue() self.status = {'status':'connecting', 'messages':[]} self.supported_printers = [ From 1e09d3b3f507223d1cc2216dfa06cf59a9c38152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Sun, 26 Jan 2014 22:55:28 +0100 Subject: [PATCH 4/6] [IMP] point_of_sale: Realtime printer & scanner online status bzr revid: fva@openerp.com-20140126215528-q32v2q8keh6m0ufo --- addons/hw_proxy/controllers/main.py | 13 ++-- addons/point_of_sale/static/src/css/pos.css | 9 ++- addons/point_of_sale/static/src/js/devices.js | 74 ++++++++++++++----- addons/point_of_sale/static/src/js/models.js | 17 +++-- addons/point_of_sale/static/src/js/widgets.js | 30 +++++++- addons/point_of_sale/static/src/xml/pos.xml | 5 +- 6 files changed, 112 insertions(+), 36 deletions(-) diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index 1888c2304d1..3ea2de4424b 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -42,6 +42,10 @@ class Proxy(http.Controller): 'Access-Control-Allow-Methods': 'GET', }) + @http.route('/hw_proxy/handshake', type='json', auth='admin') + def handshake(self): + return True + @http.route('/hw_proxy/status', type='http', auth='admin') def status_http(self): resp = '\n\n

Hardware Proxy Status

\n' @@ -71,14 +75,9 @@ class Proxy(http.Controller): 'Access-Control-Allow-Methods': 'GET', }) - @http.route('/hw_proxy/status', type='json', auth='admin') + @http.route('/hw_proxy/status_json', type='json', auth='admin') def status_json(self): - return request.make_response(simplejson.dumps(self.get_status()),{ - 'Cache-Control': 'no-cache', - 'Content-Type': 'text/html; charset=utf-8', - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET', - }) + return self.get_status() @http.route('/hw_proxy/scan_item_success', type='json', auth='admin') def scan_item_success(self, ean): diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 41fc5030519..80515952317 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -379,13 +379,16 @@ td { width:20px; height:16px; color: white; } -.pos .oe_icon.oe_red{ +.pos .oe_status .oe_red, +.pos .oe_icon.oe_red { color: rgb(197, 52, 0); } -.pos .oe_icon.oe_green{ +.pos .oe_statu .oe_green, +.pos .oe_icon.oe_green { color: rgb(94, 185, 55); } -.pos .oe_icon.oe_orange{ +.pos .oe_status .oe_orange, +.pos .oe_icon.oe_orange { color: rgb(239, 153, 65); } diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index e86bb7c368e..f31fb89cf0e 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -105,38 +105,80 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.bypass_proxy = false; this.connection = null; - this.connected = new $.Deferred(); - this.status = 'disconnected'; + this.connected = null; + this.host = ''; + this.keptalive = false; + this.status = {}; - window.proxy = this; + this.status_callback = options.status_callback || null; + this.set_connection_status('disconnected'); + + window.hw_proxy = this; }, - close: function(){ - if(this.status !== 'disconnected'){ + set_connection_status: function(status,drivers){ + this.status.status = status; + this.status.drivers = status === 'disconnected' ? {} : this.status.drivers; + this.status.drivers = drivers ? drivers : this.status.drivers; + if(this.status_callback){ + this.status_callback(status,drivers); + } + }, + disconnect: function(){ + if(this.status.status !== 'disconnected'){ this.connection.destroy(); - this.status = 'disconnected'; + this.set_connection_status('disconnected'); } }, connect : function(url){ var self = this; this.connection = new instance.web.Session(undefined,url); - this.status = 'connecting'; + this.host = url; + this.set_connection_status('connecting',{}); + if(this.connected){ + this.connected.reject(); + } + this.connected = new $.Deferred(); - return this.message('handshake').then(function(){ - self.status = 'connected'; - localStorage['hw_proxy_url'] = url; - self.connected.resolve(); + return this.message('handshake').then(function(response){ + if(response){ + self.set_connection_status('connected'); + localStorage['hw_proxy_url'] = url; + self.connected.resolve(); + self.keepalive(); + }else{ + self.set_connection_status('disconnected'); + self.connected.reject(); + console.error('Connection refused by the Proxy'); + } },function(){ - self.status = 'disconnected'; + self.set_connection_status('disconnected'); self.connected.reject(); console.error('Could not connect to the Proxy'); }); }, + keepalive: function(){ + var self = this; + if(!this.keptalive){ + this.keptalive = true; + function status(){ + self.connection.rpc('/hw_proxy/status_json',{}) + .then(function(driver_status){ + self.set_connection_status('connected',driver_status); + },function(){ + self.set_connection_status('disconnected'); + }).always(function(){ + setTimeout(status,5000); + }); + } + status(); + }; + }, message : function(name,params){ var callbacks = this.notifications[name] || []; for(var i = 0; i < callbacks.length; i++){ callbacks[i](params); } - if(this.status !== 'disconnected'){ + if(this.status.status !== 'disconnected'){ return this.connection.rpc('/hw_proxy/' + name, params || {}); }else{ return (new $.Deferred()).reject(); @@ -245,8 +287,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } this.notifications[name].push(callback); }, - - //a product has been scanned and recognized with success // ean is a parsed ean object @@ -702,7 +742,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal disconnect_from_proxy: function(){ this.remote_scanning = false; }, - connect_to_proxy: function(){ + connect_to_proxy: function(){ //FIXME prevent double connections var self = this; this.remote_scanning = true; @@ -714,13 +754,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal if(!self.remote_scanning){ return; } - self.pos.set('proxy_status','connected'); self.scan(barcode); waitforbarcode(); }, function(){ setTimeout(waitforbarcode,5000); - self.pos.set('proxy_status','disconnected'); }); }); }, diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 58f87b6f4d3..aae0263de7b 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -55,7 +55,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 'synch': { state:'connected', pending:0 }, 'orders': new module.OrderCollection(), 'selectedOrder': null, - 'proxy_status': 'connecting', + 'proxy_status': {status: 'connecting', drivers: {}}, }); this.bind('change:synch',function(pos,synch){ @@ -92,17 +92,24 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.barcode_reader.disconnect_from_proxy(); }, + set_proxy_status: function(status,drivers){ + this.set('proxy_status',{status:status,drivers:drivers || {}}); + }, connect_to_proxy: function(){ var self = this; this.barcode_reader.disconnect_from_proxy(); this.pos_widget.loading_message(_t('Connecting to the PosBox'),0); - this.set('proxy_status', 'connecting'); + this.set_proxy_status('connecting'); this.pos_widget.loading_skip(function(){ self.proxy.stop_searching(); - self.set('proxy_status', 'disconnected'); + this.set_proxy_status('disconnected'); }); + this.proxy.status_callback = function(status,driver_status){ + self.set_proxy_status(status,driver_status); + }; + return this.proxy.find_proxy({ force_ip: self.config.proxy_ip || undefined, progress: function(prog){ @@ -114,9 +121,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if(self.config.iface_scan_via_proxy){ self.barcode_reader.connect_to_proxy(); } - self.set('proxy_status', 'connected'); + self.set_proxy_status('connected'); }else{ - self.set('proxy_status', 'disconnected'); + self.set_proxy_status('disconnected'); } }); }, diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 393b0b2aa8b..1bfda516578 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -856,13 +856,39 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa // this is used to notify the user if the pos is connected to the proxy module.ProxyStatusWidget = module.StatusWidget.extend({ template: 'ProxyStatusWidget', + set_smart_status: function(status){ + if(status.status === 'connected'){ + var warning = false; + var msg = '' + if(this.pos.config.iface_scan_via_proxy){ + var scanner = status.drivers.scanner ? status.drivers.scanner.status : false; + if( scanner != 'connected' && scanner != 'connecting'){ + warning = true; + msg += _t('Scanner'); + } + } + if( this.pos.config.iface_print_via_proxy || + this.pos.config.iface_cashdrawer ){ + var printer = status.drivers.escpos ? status.drivers.escpos.status : false; + if( printer != 'connected' && printer != 'connecting'){ + warning = true; + msg = msg ? msg + ' & ' : msg; + msg += _t('Printer'); + } + } + msg = msg ? msg + ' ' + _t('Offline') : msg; + this.set_status(warning ? 'warning' : 'connected', msg); + }else{ + this.set_status(status.status,''); + } + }, start: function(){ var self = this; - this.set_status(this.pos.get('proxy_status'),''); + this.set_smart_status(this.pos.get('proxy_status')); this.pos.bind('change:proxy_status', function(pos,status){ - self.set_status(status,''); + self.set_smart_status(status); }); this.$el.click(function(){ diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 179d4acb412..748fcbfee1b 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -117,13 +117,16 @@
- +
+
+ +
From c9879fcd1ee5ac3bda28e64a5617cd30318c73f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 27 Jan 2014 01:22:03 +0100 Subject: [PATCH 5/6] [IMP] point_of_sale: refactor proxy to use event based status notifications bzr revid: fva@openerp.com-20140127002203-y2qnhwf0z708gbrx --- addons/point_of_sale/static/src/js/devices.js | 81 ++++++++++++------- addons/point_of_sale/static/src/js/models.js | 29 ++----- addons/point_of_sale/static/src/js/widgets.js | 6 +- 3 files changed, 61 insertions(+), 55 deletions(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index f31fb89cf0e..926fc3f91b6 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -81,8 +81,9 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // connected to the Point of Sale. As the communication only goes from the POS to the proxy, // methods are used both to signal an event, and to fetch information. - module.ProxyDevice = instance.web.Class.extend({ - init: function(options){ + module.ProxyDevice = instance.web.Class.extend(openerp.PropertiesMixin,{ + init: function(parent,options){ + openerp.PropertiesMixin.init.call(this,parent); var self = this; options = options || {}; url = options.url || 'http://localhost:8069'; @@ -105,57 +106,68 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.bypass_proxy = false; this.connection = null; - this.connected = null; this.host = ''; this.keptalive = false; - this.status = {}; - this.status_callback = options.status_callback || null; + this.set('status',{}); + this.set_connection_status('disconnected'); window.hw_proxy = this; }, set_connection_status: function(status,drivers){ - this.status.status = status; - this.status.drivers = status === 'disconnected' ? {} : this.status.drivers; - this.status.drivers = drivers ? drivers : this.status.drivers; - if(this.status_callback){ - this.status_callback(status,drivers); - } + oldstatus = this.get('status'); + newstatus = {}; + newstatus.status = status; + newstatus.drivers = status === 'disconnected' ? {} : oldstatus.drivers; + newstatus.drivers = drivers ? drivers : newstatus.drivers; + this.set('status',newstatus); }, disconnect: function(){ - if(this.status.status !== 'disconnected'){ + if(this.get('status').status !== 'disconnected'){ this.connection.destroy(); this.set_connection_status('disconnected'); } }, - connect : function(url){ + connect: function(url){ var self = this; this.connection = new instance.web.Session(undefined,url); this.host = url; this.set_connection_status('connecting',{}); - if(this.connected){ - this.connected.reject(); - } - this.connected = new $.Deferred(); return this.message('handshake').then(function(response){ if(response){ self.set_connection_status('connected'); localStorage['hw_proxy_url'] = url; - self.connected.resolve(); self.keepalive(); }else{ self.set_connection_status('disconnected'); - self.connected.reject(); console.error('Connection refused by the Proxy'); } },function(){ self.set_connection_status('disconnected'); - self.connected.reject(); console.error('Could not connect to the Proxy'); }); }, + autoconnect: function(opts){ + var self = this; + this.set_connection_status('connecting',{}); + var success = new $.Deferred(); + this.find_proxy(opts) + .then(function(proxies){ + if(proxies.length > 0){ + self.connect(proxies[0]) + .then(function(){ + success.resolve(); + },function(){ + success.reject(); + }); + }else{ + success.reject(); + } + }); + return success; + }, keepalive: function(){ var self = this; if(!this.keptalive){ @@ -178,7 +190,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal for(var i = 0; i < callbacks.length; i++){ callbacks[i](params); } - if(this.status.status !== 'disconnected'){ + if(this.get('status').status !== 'disconnected'){ return this.connection.rpc('/hw_proxy/' + name, params || {}); }else{ return (new $.Deferred()).reject(); @@ -205,6 +217,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal var threads = []; var progress = 0; + this.set_connection_status('connecting'); + if(options.force_ip){ urls.push(options.force_ip); }else{ @@ -277,6 +291,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal stop_searching: function(){ this.searching_for_proxy = false; + this.set_connection_status('disconnected'); }, // this allows the client to be notified when a proxy call is made. The notification @@ -505,6 +520,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.pos = attributes.pos; this.action_callback = {}; this.proxy = attributes.proxy; + this.remote_scanning = false; + this.remote_active = 0; this.action_callback_stack = []; @@ -513,6 +530,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.price_prefix_set = attributes.price_prefix_set || {'23':''}; this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''}; this.client_prefix_set = attributes.client_prefix_set || {'042':''}; + }, save_callbacks: function(){ @@ -742,25 +760,32 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal disconnect_from_proxy: function(){ this.remote_scanning = false; }, - connect_to_proxy: function(){ //FIXME prevent double connections + connect_to_proxy: function(){ //FIXME prevent connection losses var self = this; this.remote_scanning = true; + if(this.remote_active >= 1){ + return; + } + this.remote_active = 1; - this.proxy.connected.then(function waitforbarcode(){ - if(!self.remote_scanning){ - return; - } + function waitforbarcode(){ return self.proxy.connection.rpc('/hw_proxy/scanner',{}).then(function(barcode){ + self.scan(barcode); if(!self.remote_scanning){ + this.remote_active = 0; return; } - self.scan(barcode); waitforbarcode(); }, function(){ + if(!self.remote_scanning){ + this.remote_active = 0; + return; + } setTimeout(waitforbarcode,5000); }); - }); + } + waitforbarcode(); }, }); diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index aae0263de7b..f623b21374c 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -23,7 +23,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.flush_mutex = new $.Mutex(); // used to make sure the orders are sent to the server once at time this.pos_widget = attributes.pos_widget; - this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy + this.proxy = new module.ProxyDevice(this); // used to communicate to the hardware devices via a local proxy this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy}); // used to read barcodes this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy this.db = new module.PosDB(); // a local database used to search trough products and categories & store pending orders @@ -55,7 +55,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal 'synch': { state:'connected', pending:0 }, 'orders': new module.OrderCollection(), 'selectedOrder': null, - 'proxy_status': {status: 'connecting', drivers: {}}, }); this.bind('change:synch',function(pos,synch){ @@ -91,39 +90,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.barcode_reader.disconnect(); this.barcode_reader.disconnect_from_proxy(); }, - - set_proxy_status: function(status,drivers){ - this.set('proxy_status',{status:status,drivers:drivers || {}}); - }, connect_to_proxy: function(){ var self = this; this.barcode_reader.disconnect_from_proxy(); this.pos_widget.loading_message(_t('Connecting to the PosBox'),0); - this.set_proxy_status('connecting'); - this.pos_widget.loading_skip(function(){ self.proxy.stop_searching(); - this.set_proxy_status('disconnected'); }); - - this.proxy.status_callback = function(status,driver_status){ - self.set_proxy_status(status,driver_status); - }; - - return this.proxy.find_proxy({ + this.proxy.autoconnect({ force_ip: self.config.proxy_ip || undefined, progress: function(prog){ self.pos_widget.loading_progress(prog); }, - }).then(function(proxies){ - if(proxies.length > 0){ - self.proxy.connect(proxies[0]); - if(self.config.iface_scan_via_proxy){ - self.barcode_reader.connect_to_proxy(); - } - self.set_proxy_status('connected'); - }else{ - self.set_proxy_status('disconnected'); + }).then(function(){ + if(self.config.iface_scan_via_proxy){ + self.barcode_reader.connect_to_proxy(); } }); }, diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 1bfda516578..f887a537d39 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -885,10 +885,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa start: function(){ var self = this; - this.set_smart_status(this.pos.get('proxy_status')); + this.set_smart_status(this.pos.proxy.get('status')); - this.pos.bind('change:proxy_status', function(pos,status){ - self.set_smart_status(status); + this.pos.proxy.on('change:status',this,function(eh,status){ //FIXME remove duplicate changes + self.set_smart_status(status.newValue); }); this.$el.click(function(){ From 85daaccb619c2da8e3f05b72468d99bf24e060fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Mon, 27 Jan 2014 13:36:14 +0100 Subject: [PATCH 6/6] [FIX] point_of_sale: prevent barcode scanner connection loss bzr revid: fva@openerp.com-20140127123614-vjrcwgh7s8rs2y2u --- addons/point_of_sale/static/src/js/devices.js | 63 ++++++++++++------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 926fc3f91b6..57b23afd49f 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -129,6 +129,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.set_connection_status('disconnected'); } }, + + // connects to the specified url connect: function(url){ var self = this; this.connection = new instance.web.Session(undefined,url); @@ -149,35 +151,43 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal console.error('Could not connect to the Proxy'); }); }, - autoconnect: function(opts){ + + // find a proxy and connects to it. for options see find_proxy + autoconnect: function(options){ var self = this; this.set_connection_status('connecting',{}); var success = new $.Deferred(); - this.find_proxy(opts) + this.find_proxy(options) .then(function(proxies){ if(proxies.length > 0){ self.connect(proxies[0]) .then(function(){ success.resolve(); },function(){ + self.set_connection_status('disconnected'); success.reject(); }); }else{ + self.set_connection_status('disconnected'); success.reject(); } }); return success; }, + + // starts a loop that updates the connection status keepalive: function(){ var self = this; if(!this.keptalive){ this.keptalive = true; function status(){ - self.connection.rpc('/hw_proxy/status_json',{}) + self.connection.rpc('/hw_proxy/status_json',{},{timeout:500}) .then(function(driver_status){ self.set_connection_status('connected',driver_status); },function(){ - self.set_connection_status('disconnected'); + if(self.get('status').status !== 'connecting'){ + self.set_connection_status('disconnected'); + } }).always(function(){ setTimeout(status,5000); }); @@ -185,6 +195,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal status(); }; }, + message : function(name,params){ var callbacks = this.notifications[name] || []; for(var i = 0; i < callbacks.length; i++){ @@ -197,18 +208,15 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } }, - test_connection: function(host,timeout){ - return $.ajax({ - url: host + '/hw_proxy/hello', - method: 'GET', - timeout: timeout || 10000, - }); - }, - + // returns as a deferred a list of valid hosts urls that can be used as proxy. + // options: + // - port: what port to listen to (default 8069) + // - force_ip : limit the search to the specified ip + // - progress(fac) : callback for search progress ( fac in [0,1] ) find_proxy: function(options){ options = options || {}; var self = this; - var port = ':8069'; + var port = ':' + (options.port || '8069'); var urls = []; var found = false; var proxies = []; @@ -249,8 +257,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal if(!url){ done.resolve(); } - var c = self.test_connection(url, 300) - .done(function(){ + var c = $.ajax({ + url: url + '/hw_proxy/hello', + method: 'GET', + timeout: 300, + }).done(function(){ found = true; update_progress(); proxies.push(url); @@ -757,10 +768,10 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal disconnect: function(){ $('body').off('keypress', this.handler) }, - disconnect_from_proxy: function(){ - this.remote_scanning = false; - }, - connect_to_proxy: function(){ //FIXME prevent connection losses + + // the barcode scanner will listen on the hw_proxy/scanner interface for + // scan events until disconnect_from_proxy is called + connect_to_proxy: function(){ var self = this; this.remote_scanning = true; if(this.remote_active >= 1){ @@ -769,17 +780,18 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.remote_active = 1; function waitforbarcode(){ - return self.proxy.connection.rpc('/hw_proxy/scanner',{}).then(function(barcode){ - self.scan(barcode); + return self.proxy.connection.rpc('/hw_proxy/scanner',{},{timeout:7500}) + .then(function(barcode){ if(!self.remote_scanning){ - this.remote_active = 0; + self.remote_active = 0; return; } + self.scan(barcode); waitforbarcode(); }, function(){ if(!self.remote_scanning){ - this.remote_active = 0; + self.remote_active = 0; return; } setTimeout(waitforbarcode,5000); @@ -787,6 +799,11 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal } waitforbarcode(); }, + + // the barcode scanner will stop listening on the hw_proxy/scanner remote interface + disconnect_from_proxy: function(){ + this.remote_scanning = false; + }, }); }