[IMP] point_of_sale, hw_scale: first commit for the hw_scale module, which handles scale connections for the POS
bzr revid: fva@openerp.com-20140424171259-hi9ma6w0fkdfrqnv
This commit is contained in:
parent
3452694885
commit
f3da22c067
|
@ -24,9 +24,6 @@ from openerp.addons.web.controllers.main import manifest_list, module_boot, html
|
||||||
drivers = {}
|
drivers = {}
|
||||||
|
|
||||||
class Proxy(http.Controller):
|
class Proxy(http.Controller):
|
||||||
def __init__(self):
|
|
||||||
self.scale = 'closed'
|
|
||||||
self.scale_weight = 0.0
|
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
statuses = {}
|
statuses = {}
|
||||||
|
@ -154,40 +151,6 @@ class Proxy(http.Controller):
|
||||||
"""
|
"""
|
||||||
print "help_canceled"
|
print "help_canceled"
|
||||||
|
|
||||||
@http.route('/hw_proxy/weighting_start', type='json', auth='none', cors='*')
|
|
||||||
def weighting_start(self):
|
|
||||||
if self.scale == 'closed':
|
|
||||||
print "Opening (Fake) Connection to Scale..."
|
|
||||||
self.scale = 'open'
|
|
||||||
self.scale_weight = 0.0
|
|
||||||
time.sleep(0.1)
|
|
||||||
print "... Scale Open."
|
|
||||||
else:
|
|
||||||
print "WARNING: Scale already Connected !!!"
|
|
||||||
|
|
||||||
@http.route('/hw_proxy/weighting_read_kg', type='json', auth='none', cors='*')
|
|
||||||
def weighting_read_kg(self):
|
|
||||||
if self.scale == 'open':
|
|
||||||
print "Reading Scale..."
|
|
||||||
time.sleep(0.025)
|
|
||||||
self.scale_weight += 0.01
|
|
||||||
print "... Done."
|
|
||||||
return self.scale_weight
|
|
||||||
else:
|
|
||||||
print "WARNING: Reading closed scale !!!"
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
@http.route('/hw_proxy/weighting_end', type='json', auth='none', cors='*')
|
|
||||||
def weighting_end(self):
|
|
||||||
if self.scale == 'open':
|
|
||||||
print "Closing Connection to Scale ..."
|
|
||||||
self.scale = 'closed'
|
|
||||||
self.scale_weight = 0.0
|
|
||||||
time.sleep(0.1)
|
|
||||||
print "... Scale Closed."
|
|
||||||
else:
|
|
||||||
print "WARNING: Scale already Closed !!!"
|
|
||||||
|
|
||||||
@http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*')
|
@http.route('/hw_proxy/payment_request', type='json', auth='none', cors='*')
|
||||||
def payment_request(self, price):
|
def payment_request(self, price):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
import controllers
|
||||||
|
|
||||||
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# OpenERP, Open Source Management Solution
|
||||||
|
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 of the
|
||||||
|
# License, or (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
'name': 'Weighting Scale Hardware Driver',
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Hardware Drivers',
|
||||||
|
'sequence': 6,
|
||||||
|
'summary': 'Hardware Driver for Weighting Scales',
|
||||||
|
'description': """
|
||||||
|
Barcode Scanner Hardware Driver
|
||||||
|
================================
|
||||||
|
|
||||||
|
This module allows the point of sale to connect to a scale using a USB HSM Serial Scale Interface,
|
||||||
|
such as the Mettler Toledo Ariva.
|
||||||
|
|
||||||
|
""",
|
||||||
|
'author': 'OpenERP SA',
|
||||||
|
'depends': ['hw_proxy'],
|
||||||
|
'test': [
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
||||||
|
|
||||||
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,3 @@
|
||||||
|
import main
|
||||||
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||||
|
|
|
@ -0,0 +1,202 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
from os import listdir
|
||||||
|
from os.path import join
|
||||||
|
from threading import Thread, Lock
|
||||||
|
from select import select
|
||||||
|
from Queue import Queue, Empty
|
||||||
|
from bitstring import BitArray
|
||||||
|
|
||||||
|
import openerp
|
||||||
|
import openerp.addons.hw_proxy.controllers.main as hw_proxy
|
||||||
|
from openerp import http
|
||||||
|
from openerp.http import request
|
||||||
|
from openerp.tools.translate import _
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import serial
|
||||||
|
except ImportError:
|
||||||
|
_logger.error('OpenERP module hw_scale depends on the pyserial python module')
|
||||||
|
serial = None
|
||||||
|
|
||||||
|
|
||||||
|
class Scale(Thread):
|
||||||
|
def __init__(self):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.lock = Lock()
|
||||||
|
self.scalelock = Lock()
|
||||||
|
self.status = {'status':'connecting', 'messages':[]}
|
||||||
|
self.input_dir = '/dev/serial/by-id/'
|
||||||
|
self.weight = 0
|
||||||
|
self.weight_info = 'ok'
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
def lockedstart(self):
|
||||||
|
with self.lock:
|
||||||
|
if not self.isAlive():
|
||||||
|
self.daemon = True
|
||||||
|
self.start()
|
||||||
|
|
||||||
|
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('Scale Error: '+message)
|
||||||
|
elif status == 'disconnected' and message:
|
||||||
|
_logger.warning('Disconnected Scale: '+message)
|
||||||
|
|
||||||
|
def get_device(self):
|
||||||
|
try:
|
||||||
|
devices = [ device for device in listdir(self.input_dir)]
|
||||||
|
scales = [ device for device in devices if ('mettler' in device.lower()) or ('toledo' in device.lower()) ]
|
||||||
|
if len(scales) > 0:
|
||||||
|
print join(self.input_dir,scales[0])
|
||||||
|
self.set_status('connected','Connected to '+scales[0])
|
||||||
|
# s = serial.Serial("/dev/serial/by-id/usb-METTLER_TOLEDO_15_kg_DI_Firmware_CKOR_F_Ser_CDC-if00",baudrate=9600,bytesize=serial.SEVENBITS,parity=serial.PARITY_EVEN)
|
||||||
|
return serial.Serial(join(self.input_dir,scales[0]),
|
||||||
|
baudrate = 9600,
|
||||||
|
bytesize = serial.SEVENBITS,
|
||||||
|
stopbits = serial.STOPBITS_ONE,
|
||||||
|
parity = serial.PARITY_EVEN,
|
||||||
|
#xonxoff = serial.XON,
|
||||||
|
timeout = 0.01,
|
||||||
|
writeTimeout= 0.01)
|
||||||
|
else:
|
||||||
|
self.set_status('disconnected','Scale Not Found')
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
self.set_status('error',str(e))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_weight(self):
|
||||||
|
self.lockedstart()
|
||||||
|
return self.weight
|
||||||
|
|
||||||
|
def get_weight_info(self):
|
||||||
|
self.lockedstart()
|
||||||
|
return self.weight_info
|
||||||
|
|
||||||
|
def get_status(self):
|
||||||
|
self.lockedstart()
|
||||||
|
return self.status
|
||||||
|
|
||||||
|
def read_weight(self):
|
||||||
|
with self.scalelock:
|
||||||
|
if self.device:
|
||||||
|
try:
|
||||||
|
self.device.write('W')
|
||||||
|
time.sleep(0.2)
|
||||||
|
answer = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
char = self.device.read(1)
|
||||||
|
if not char:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
answer.append(char)
|
||||||
|
|
||||||
|
if '?' in answer:
|
||||||
|
stat = ord(answer[answer.index('?')+1])
|
||||||
|
if stat == 0:
|
||||||
|
self.weight_info = 'ok'
|
||||||
|
else:
|
||||||
|
self.weight_info = []
|
||||||
|
if stat & 1 :
|
||||||
|
self.weight_info.append('moving')
|
||||||
|
if stat & 1 << 1:
|
||||||
|
self.weight_info.append('over_capacity')
|
||||||
|
if stat & 1 << 2:
|
||||||
|
self.weight_info.append('negative')
|
||||||
|
if stat & 1 << 3:
|
||||||
|
self.weight_info.append('outside_zero_capture_range')
|
||||||
|
if stat & 1 << 4:
|
||||||
|
self.weight_info.append('center_of_zero')
|
||||||
|
if stat & 1 << 5:
|
||||||
|
self.weight_info.append('net_weight')
|
||||||
|
else:
|
||||||
|
answer = answer[1:-1]
|
||||||
|
if 'N' in answer:
|
||||||
|
answer = answer[0:-1]
|
||||||
|
self.weight = float(''.join(answer))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.set_status('error',str(e))
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
def set_zero(self):
|
||||||
|
with self.scalelock:
|
||||||
|
if self.device:
|
||||||
|
try:
|
||||||
|
self.device.write('Z')
|
||||||
|
except Exception as e:
|
||||||
|
self.set_status('error',str(e))
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
def set_tare(self):
|
||||||
|
with self.scalelock:
|
||||||
|
if self.device:
|
||||||
|
try:
|
||||||
|
self.device.write('T')
|
||||||
|
except Exception as e:
|
||||||
|
self.set_status('error',str(e))
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
def clear_tare(self):
|
||||||
|
with self.scalelock:
|
||||||
|
if self.device:
|
||||||
|
try:
|
||||||
|
self.device.write('C')
|
||||||
|
except Exception as e:
|
||||||
|
self.set_status('error',str(e))
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.device = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
if self.device:
|
||||||
|
self.read_weight()
|
||||||
|
time.sleep(0.05)
|
||||||
|
else:
|
||||||
|
with self.scalelock:
|
||||||
|
self.device = self.get_device()
|
||||||
|
if not self.device:
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
s = Scale()
|
||||||
|
|
||||||
|
hw_proxy.drivers['scale'] = s
|
||||||
|
|
||||||
|
class ScaleDriver(hw_proxy.Proxy):
|
||||||
|
@http.route('/hw_proxy/scale_read/', type='json', auth='none', cors='*')
|
||||||
|
def scale_read(self):
|
||||||
|
return {'weight':s.get_weight(), 'unit':'kg', 'info':s.get_weight_info()}
|
||||||
|
|
||||||
|
@http.route('/hw_proxy/scale_zero/', type='json', auth='none', cors='*')
|
||||||
|
def scale_zero(self):
|
||||||
|
s.set_zero()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@http.route('/hw_proxy/scale_tare/', type='json', auth='none', cors='*')
|
||||||
|
def scale_tare(self):
|
||||||
|
s.set_tare()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@http.route('/hw_proxy/scale_clear_tare/', type='json', auth='none', cors='*')
|
||||||
|
def scale_clear_tare(self):
|
||||||
|
s.clear_tare()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
import serial
|
||||||
|
import time
|
||||||
|
import sys
|
||||||
|
from bitstring import BitArray
|
||||||
|
|
||||||
|
path = "/dev/serial/by-id/usb-METTLER_TOLEDO_15_kg_DI_Firmware_CKOR_F_Ser_CDC-if00"
|
||||||
|
|
||||||
|
device = serial.Serial(path,
|
||||||
|
baudrate = 9600,
|
||||||
|
bytesize = serial.SEVENBITS,
|
||||||
|
stopbits = serial.STOPBITS_ONE,
|
||||||
|
parity = serial.PARITY_EVEN,
|
||||||
|
#xonxoff = serial.XON,
|
||||||
|
timeout = 0.1,
|
||||||
|
writeTimeout= 0.1)
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
cmd = 'weight'
|
||||||
|
else:
|
||||||
|
cmd = sys.argv[1]
|
||||||
|
|
||||||
|
def write(stuff):
|
||||||
|
print stuff
|
||||||
|
device.write(stuff)
|
||||||
|
|
||||||
|
def read_answer():
|
||||||
|
answer = []
|
||||||
|
while True:
|
||||||
|
char = device.read(1)
|
||||||
|
if not char:
|
||||||
|
return answer
|
||||||
|
else:
|
||||||
|
answer.append(char)
|
||||||
|
|
||||||
|
def print_answer(answer):
|
||||||
|
print answer
|
||||||
|
if '?' in answer:
|
||||||
|
status = answer[answer.index('?')+1]
|
||||||
|
print 'status_bits: '+BitArray(int=ord(status),length=8).bin
|
||||||
|
|
||||||
|
|
||||||
|
if cmd == 'weight':
|
||||||
|
while True:
|
||||||
|
time.sleep(0.25)
|
||||||
|
write('W')
|
||||||
|
time.sleep(0.25)
|
||||||
|
print_answer(read_answer())
|
||||||
|
|
||||||
|
if cmd == 'interactive':
|
||||||
|
weight = 0
|
||||||
|
status = ''
|
||||||
|
while True:
|
||||||
|
time.sleep(0.25)
|
||||||
|
device.write('W')
|
||||||
|
answer = read_answer()
|
||||||
|
if '?' in answer:
|
||||||
|
oldstatus = status
|
||||||
|
b = answer[answer.index('?')+1]
|
||||||
|
if b == '\x00' or b == ' ':
|
||||||
|
pass # ignore status
|
||||||
|
elif b == 'B':
|
||||||
|
status = 'too_heavy'
|
||||||
|
elif b == 'D':
|
||||||
|
status = 'negative'
|
||||||
|
elif b == 'A' or b == 'Q' or b == '\x01':
|
||||||
|
status = 'moving'
|
||||||
|
else:
|
||||||
|
status = 'unknown'
|
||||||
|
print b.__repr__(), BitArray(int=ord(b),length=8).bin
|
||||||
|
if oldstatus != status:
|
||||||
|
print status
|
||||||
|
else:
|
||||||
|
oldweight = weight
|
||||||
|
answer = answer[1:-1]
|
||||||
|
if 'N' in answer:
|
||||||
|
answer = answer[0:-1]
|
||||||
|
weight = float(''.join(answer))
|
||||||
|
if oldweight != weight:
|
||||||
|
print weight
|
||||||
|
|
||||||
|
|
||||||
|
elif cmd == 'zero':
|
||||||
|
time.sleep(1)
|
||||||
|
write('Z')
|
||||||
|
time.sleep(1)
|
||||||
|
print_answer(read_answer())
|
||||||
|
|
||||||
|
elif cmd == 'test':
|
||||||
|
time.sleep(1)
|
||||||
|
write('A')
|
||||||
|
time.sleep(1)
|
||||||
|
write('B')
|
||||||
|
time.sleep(1)
|
||||||
|
answer = read_answer()
|
||||||
|
if '@' in answer:
|
||||||
|
print 'all test passed'
|
||||||
|
else:
|
||||||
|
print_answer(answer)
|
||||||
|
|
|
@ -384,47 +384,17 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal
|
||||||
return this.message('help_canceled');
|
return this.message('help_canceled');
|
||||||
},
|
},
|
||||||
|
|
||||||
//the client is starting to weight
|
// returns the weight on the scale.
|
||||||
weighting_start: function(){
|
scale_read: function(){
|
||||||
var ret = new $.Deferred();
|
|
||||||
if(!this.weighting){
|
|
||||||
this.weighting = true;
|
|
||||||
this.message('weighting_start').always(function(){
|
|
||||||
ret.resolve();
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
console.error('Weighting already started!!!');
|
|
||||||
ret.resolve();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
|
|
||||||
// the client has finished weighting products
|
|
||||||
weighting_end: function(){
|
|
||||||
var ret = new $.Deferred();
|
|
||||||
if(this.weighting){
|
|
||||||
this.weighting = false;
|
|
||||||
this.message('weighting_end').always(function(){
|
|
||||||
ret.resolve();
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
console.error('Weighting already ended !!!');
|
|
||||||
ret.resolve();
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
},
|
|
||||||
|
|
||||||
//returns the weight on the scale.
|
|
||||||
// is called at regular interval (up to 10x/sec) between a weighting_start()
|
|
||||||
// and a weighting_end()
|
|
||||||
weighting_read_kg: function(){
|
|
||||||
var self = this;
|
var self = this;
|
||||||
var ret = new $.Deferred();
|
var ret = new $.Deferred();
|
||||||
this.message('weighting_read_kg',{})
|
console.log('scale_read');
|
||||||
|
this.message('scale_read',{})
|
||||||
.then(function(weight){
|
.then(function(weight){
|
||||||
|
console.log(weight)
|
||||||
ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
|
ret.resolve(self.use_debug_weight ? self.debug_weight : weight);
|
||||||
}, function(){ //failed to read weight
|
}, function(){ //failed to read weight
|
||||||
ret.resolve(self.use_debug_weight ? self.debug_weight : 0.0);
|
ret.resolve(self.use_debug_weight ? self.debug_weight : {weight:0.0, info:'ok'});
|
||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
|
@ -526,12 +526,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||||
});
|
});
|
||||||
|
|
||||||
queue.schedule(function(){
|
queue.schedule(function(){
|
||||||
return self.pos.proxy.weighting_start()
|
return self.pos.proxy.scale_read().then(function(weight){
|
||||||
},{ important: true });
|
self.set_weight(weight.weight);
|
||||||
|
|
||||||
queue.schedule(function(){
|
|
||||||
return self.pos.proxy.weighting_read_kg().then(function(weight){
|
|
||||||
self.set_weight(weight);
|
|
||||||
});
|
});
|
||||||
},{duration:50, repeat: true});
|
},{duration:50, repeat: true});
|
||||||
|
|
||||||
|
@ -584,9 +580,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
||||||
$('body').off('keyup',this.hotkey_handler);
|
$('body').off('keyup',this.hotkey_handler);
|
||||||
|
|
||||||
this.pos.proxy_queue.clear();
|
this.pos.proxy_queue.clear();
|
||||||
this.pos.proxy_queue.schedule(function(){
|
|
||||||
self.pos.proxy.weighting_end();
|
|
||||||
},{ important: true });
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue