[MERGE] point_of_sale: posbox related usability & reliability improvements + firefox bug fix

bzr revid: fva@openerp.com-20140210155318-1exkkl4ij93jtbq2
This commit is contained in:
Frédéric van der Essen 2014-02-10 16:53:18 +01:00
commit 82277b178b
5 changed files with 144 additions and 73 deletions

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import commands
import logging import logging
import simplejson import simplejson
import os import os
@ -11,7 +12,7 @@ import math
import md5 import md5
import openerp.addons.hw_proxy.controllers.main as hw_proxy import openerp.addons.hw_proxy.controllers.main as hw_proxy
import subprocess import subprocess
from threading import Thread from threading import Thread, Lock
from Queue import Queue, Empty from Queue import Queue, Empty
try: try:
@ -39,6 +40,7 @@ class EscposDriver(Thread):
def __init__(self): def __init__(self):
Thread.__init__(self) Thread.__init__(self)
self.queue = Queue() self.queue = Queue()
self.lock = Lock()
self.status = {'status':'connecting', 'messages':[]} self.status = {'status':'connecting', 'messages':[]}
def connected_usb_devices(self): def connected_usb_devices(self):
@ -47,6 +49,13 @@ class EscposDriver(Thread):
if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None: if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None:
connected.append(device) connected.append(device)
return connected return connected
def lockedstart(self):
self.lock.acquire()
if not self.isAlive():
self.daemon = True
self.start()
self.lock.release()
def get_escpos_printer(self): def get_escpos_printer(self):
try: try:
@ -105,6 +114,8 @@ class EscposDriver(Thread):
elif task == 'cashbox': elif task == 'cashbox':
if timestamp >= time.time() - 12: if timestamp >= time.time() - 12:
self.open_cashbox(printer) self.open_cashbox(printer)
elif task == 'printstatus':
self.print_status(printer)
elif task == 'status': elif task == 'status':
pass pass
@ -113,10 +124,31 @@ class EscposDriver(Thread):
_logger.error(e); _logger.error(e);
def push_task(self,task, data = None): def push_task(self,task, data = None):
if not self.isAlive(): self.lockedstart()
self.start()
self.queue.put((time.time(),task,data)) self.queue.put((time.time(),task,data))
def print_status(self,eprint):
localips = ['0.0.0.0','127.0.0.1','127.0.1.1']
ips = [ c.split(':')[1].split(' ')[0] for c in commands.getoutput("/sbin/ifconfig").split('\n') if 'inet addr' in c ]
ips = [ ip for ip in ips if ip not in localips ]
eprint.text('\n\n')
eprint.set(align='center',type='b',height=2,width=2)
eprint.text('PosBox Status\n')
eprint.text('\n')
eprint.set(align='center')
if len(ips) == 0:
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')
elif len(ips) == 1:
eprint.text('IP Address\n'+ips[0]+'\n')
else:
eprint.text('IP Addresses\n')
for ip in ips:
eprint.text(ip+'\n')
eprint.text('\n\n')
eprint.cut()
def print_receipt_body(self,eprint,receipt): def print_receipt_body(self,eprint,receipt):
def check(string): def check(string):
@ -134,7 +166,6 @@ class EscposDriver(Thread):
else: else:
return str(amount) return str(amount)
def printline(left, right='', width=40, ratio=0.5, indent=0): def printline(left, right='', width=40, ratio=0.5, indent=0):
lwidth = int(width * ratio) lwidth = int(width * ratio)
rwidth = width - lwidth rwidth = width - lwidth
@ -157,6 +188,7 @@ class EscposDriver(Thread):
# Receipt Header # Receipt Header
if receipt['company']['logo']: if receipt['company']['logo']:
eprint.set(align='center')
eprint.print_base64_image(receipt['company']['logo']) eprint.print_base64_image(receipt['company']['logo'])
eprint.text('\n') eprint.text('\n')
else: else:
@ -245,6 +277,8 @@ class EscposDriver(Thread):
driver = EscposDriver() driver = EscposDriver()
hw_proxy.drivers['escpos'] = driver hw_proxy.drivers['escpos'] = driver
driver.push_task('printstatus')
class EscposProxy(hw_proxy.Proxy): class EscposProxy(hw_proxy.Proxy):

View File

@ -4,7 +4,7 @@ import os
import time import time
from os import listdir from os import listdir
from os.path import join from os.path import join
from threading import Thread from threading import Thread, Lock
from select import select from select import select
from Queue import Queue, Empty from Queue import Queue, Empty
@ -26,6 +26,7 @@ except ImportError:
class Scanner(Thread): class Scanner(Thread):
def __init__(self): def __init__(self):
Thread.__init__(self) Thread.__init__(self)
self.lock = Lock()
self.status = {'status':'connecting', 'messages':[]} self.status = {'status':'connecting', 'messages':[]}
self.input_dir = '/dev/input/by-id/' self.input_dir = '/dev/input/by-id/'
self.barcodes = Queue() self.barcodes = Queue()
@ -86,6 +87,12 @@ class Scanner(Thread):
57:(" "," "), 57:(" "," "),
} }
def lockedstart(self):
self.lock.acquire()
if not self.isAlive():
self.start()
self.lock.release()
def set_status(self, status, message = None): def set_status(self, status, message = None):
if status == self.status['status']: if status == self.status['status']:
if message != None and message != self.status['messages'][-1]: if message != None and message != self.status['messages'][-1]:
@ -102,8 +109,6 @@ class Scanner(Thread):
elif status == 'disconnected' and message: elif status == 'disconnected' and message:
_logger.warning('Disconnected Barcode Scanner: '+message) _logger.warning('Disconnected Barcode Scanner: '+message)
def get_device(self): def get_device(self):
try: try:
if not evdev: if not evdev:
@ -135,6 +140,8 @@ class Scanner(Thread):
busy reading another barcode busy reading another barcode
""" """
self.lockedstart()
while True: while True:
try: try:
timestamp, barcode = self.barcodes.get(True, 5) timestamp, barcode = self.barcodes.get(True, 5)
@ -144,8 +151,7 @@ class Scanner(Thread):
return '' return ''
def get_status(self): def get_status(self):
if not s.isAlive(): self.lockedstart()
s.start()
return self.status return self.status
def run(self): def run(self):
@ -209,7 +215,6 @@ hw_proxy.drivers['scanner'] = s
class ScannerDriver(hw_proxy.Proxy): class ScannerDriver(hw_proxy.Proxy):
@http.route('/hw_proxy/scanner', type='json', auth='none', cors='*') @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
def scanner(self): def scanner(self):
if not s.isAlive():
s.start()
return s.get_barcode() return s.get_barcode()

View File

@ -162,25 +162,37 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
}, },
// find a proxy and connects to it. for options see find_proxy // find a proxy and connects to it. for options see find_proxy
// - force_ip : only try to connect to the specified ip.
// - port: what port to listen to (default 8069)
// - progress(fac) : callback for search progress ( fac in [0,1] )
autoconnect: function(options){ autoconnect: function(options){
var self = this; var self = this;
this.set_connection_status('connecting',{}); this.set_connection_status('connecting',{});
var found_url = new $.Deferred();
var success = new $.Deferred(); var success = new $.Deferred();
this.find_proxy(options)
.then(function(proxies){ if ( options.force_ip ){
if(proxies.length > 0){ // if the ip is forced by server config, bailout on fail
self.connect(proxies[0]) found_url = this.try_hard_to_connect(options.force_ip, options)
.then(function(){ }else if( localStorage['hw_proxy_url'] ){
success.resolve(); // try harder when we remember a good proxy url
},function(){ found_url = this.try_hard_to_connect(localStorage['hw_proxy_url'], options)
self.set_connection_status('disconnected'); .then(null,function(){
success.reject(); return self.find_proxy(options);
}); });
}else{ }else{
self.set_connection_status('disconnected'); // just find something quick
success.reject(); found_url = this.find_proxy(options);
} }
success = found_url.then(function(url){
return self.connect(url);
}); });
success.fail(function(){
self.set_connection_status('disconnected');
});
return success; return success;
}, },
@ -217,10 +229,50 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
} }
}, },
// returns as a deferred a list of valid hosts urls that can be used as proxy. // try several time to connect to a known proxy url
try_hard_to_connect: function(url,options){
options = options || {};
var port = ':' + (options.port || '8069');
this.set_connection_status('connecting');
if(url.indexOf('//') < 0){
url = 'http://'+url;
}
if(url.indexOf(':',5) < 0){
url = url+port;
}
// try real hard to connect to url, with a 1sec timeout and up to 'retries' retries
function try_real_hard_to_connect(url, retries, done){
done = done || new $.Deferred();
var c = $.ajax({
url: url + '/hw_proxy/hello',
method: 'GET',
timeout: 1000,
})
.done(function(){
done.resolve(url);
})
.fail(function(){
if(retries > 0){
try_real_hard_to_connect(url,retries-1,done);
}else{
done.reject();
}
});
return done;
}
return try_real_hard_to_connect(url,3);
},
// returns as a deferred a valid host url that can be used as proxy.
// options: // options:
// - port: what port to listen to (default 8069) // - 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] ) // - progress(fac) : callback for search progress ( fac in [0,1] )
find_proxy: function(options){ find_proxy: function(options){
options = options || {}; options = options || {};
@ -228,36 +280,17 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
var port = ':' + (options.port || '8069'); var port = ':' + (options.port || '8069');
var urls = []; var urls = [];
var found = false; var found = false;
var proxies = [];
var done = new $.Deferred();
var parallel = 8; var parallel = 8;
var done = new $.Deferred(); // will be resolved with the proxies valid urls
var threads = []; var threads = [];
var progress = 0; var progress = 0;
this.set_connection_status('connecting');
if(options.force_ip){ urls.push('http://localhost'+port);
var url = options.force_ip; for(var i = 0; i < 256; i++){
if(url.indexOf('//') < 0){ urls.push('http://192.168.0.'+i+port);
url = 'http://'+url; urls.push('http://192.168.1.'+i+port);
} urls.push('http://10.0.0.'+i+port);
if(url.indexOf(':',5) < 0){
url = url+port;
}
urls.push(url);
}else{
if(localStorage['hw_proxy_url']){
urls.push(localStorage['hw_proxy_url']);
}
urls.push('http://localhost'+port);
for(var i = 0; i < 256; i++){
urls.push('http://192.168.0.'+i+port);
urls.push('http://192.168.1.'+i+port);
urls.push('http://192.168.2.'+i+port);
urls.push('http://10.0.0.'+i+port);
}
} }
var prog_inc = 1/urls.length; var prog_inc = 1/urls.length;
@ -269,40 +302,39 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
} }
} }
function thread(url,done){ function thread(done){
if(!url){ var url = urls.shift();
done = done || new $.Deferred();
if( !url || found || !self.searching_for_proxy ){
done.resolve(); done.resolve();
return done;
} }
var c = $.ajax({ var c = $.ajax({
url: url + '/hw_proxy/hello', url: url + '/hw_proxy/hello',
method: 'GET', method: 'GET',
timeout: 300, timeout: 400,
}).done(function(){ }).done(function(){
found = true; found = true;
update_progress(); update_progress();
proxies.push(url);
done.resolve(url); done.resolve(url);
}) })
.fail(function(){ .fail(function(){
update_progress(); update_progress();
var next_url = urls.shift(); thread(done);
if(found ||! self.searching_for_proxy || !next_url){
done.resolve();
}else{
thread(next_url,done);
}
}); });
return done; return done;
} }
this.searching_for_proxy = true; this.searching_for_proxy = true;
for(var i = 0; i < Math.min(parallel,urls.length); i++){ for(var i = 0, len = Math.min(parallel,urls.length); i < len; i++){
threads.push(thread(urls.shift(),new $.Deferred())); threads.push(thread());
} }
var done = new $.Deferred();
$.when.apply($,threads).then(function(){ $.when.apply($,threads).then(function(){
var urls = []; var urls = [];
for(var i = 0; i < arguments.length; i++){ for(var i = 0; i < arguments.length; i++){
@ -310,7 +342,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
urls.push(arguments[i]); urls.push(arguments[i]);
} }
} }
done.resolve(urls); done.resolve(urls[0]);
}); });
return done; return done;

View File

@ -258,8 +258,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa
var total = order ? order.getTotalTaxIncluded() : 0; var total = order ? order.getTotalTaxIncluded() : 0;
var taxes = order ? total - order.getTotalTaxExcluded() : 0; var taxes = order ? total - order.getTotalTaxExcluded() : 0;
this.el.querySelector('.summary .total > .value').innerText = this.format_currency(total); this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total);
this.el.querySelector('.summary .total .subentry .value').innerText = this.format_currency(taxes); this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes);
}, },
}); });

View File

@ -635,9 +635,9 @@
Shop: <t t-esc="widget.pos.shop.name"/><br /> Shop: <t t-esc="widget.pos.shop.name"/><br />
<br /> <br />
<t t-if="widget.pos.config.receipt_header"> <t t-if="widget.pos.config.receipt_header">
<pre> <div style='text-align:center'>
<t t-esc="widget.pos.config.receipt_header" /> <t t-esc="widget.pos.config.receipt_header" />
</pre> </div>
<br /> <br />
</t> </t>
<table> <table>
@ -711,9 +711,9 @@
</table> </table>
<t t-if="widget.pos.config.receipt_footer"> <t t-if="widget.pos.config.receipt_footer">
<br /> <br />
<pre> <div style='text-align:center'>
<t t-esc="widget.pos.config.receipt_footer" /> <t t-esc="widget.pos.config.receipt_footer" />
</pre> </div>
</t> </t>
</div> </div>
</t> </t>