[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 = {}
|
||||
|
||||
class Proxy(http.Controller):
|
||||
def __init__(self):
|
||||
self.scale = 'closed'
|
||||
self.scale_weight = 0.0
|
||||
|
||||
def get_status(self):
|
||||
statuses = {}
|
||||
|
@ -154,40 +151,6 @@ class Proxy(http.Controller):
|
|||
"""
|
||||
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='*')
|
||||
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');
|
||||
},
|
||||
|
||||
//the client is starting to weight
|
||||
weighting_start: 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(){
|
||||
// returns the weight on the scale.
|
||||
scale_read: function(){
|
||||
var self = this;
|
||||
var ret = new $.Deferred();
|
||||
this.message('weighting_read_kg',{})
|
||||
console.log('scale_read');
|
||||
this.message('scale_read',{})
|
||||
.then(function(weight){
|
||||
console.log(weight)
|
||||
ret.resolve(self.use_debug_weight ? self.debug_weight : 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;
|
||||
},
|
||||
|
|
|
@ -526,12 +526,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa
|
|||
});
|
||||
|
||||
queue.schedule(function(){
|
||||
return self.pos.proxy.weighting_start()
|
||||
},{ important: true });
|
||||
|
||||
queue.schedule(function(){
|
||||
return self.pos.proxy.weighting_read_kg().then(function(weight){
|
||||
self.set_weight(weight);
|
||||
return self.pos.proxy.scale_read().then(function(weight){
|
||||
self.set_weight(weight.weight);
|
||||
});
|
||||
},{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);
|
||||
|
||||
this.pos.proxy_queue.clear();
|
||||
this.pos.proxy_queue.schedule(function(){
|
||||
self.pos.proxy.weighting_end();
|
||||
},{ important: true });
|
||||
},
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue