[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:
Frédéric van der Essen 2014-04-24 19:12:59 +02:00
parent 3452694885
commit f3da22c067
8 changed files with 383 additions and 82 deletions

View File

@ -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):
"""

View File

@ -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:

View File

@ -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:

View File

@ -0,0 +1,3 @@
import main
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -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

100
addons/hw_scale/mettler.py Executable file
View File

@ -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)

View File

@ -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;
},

View File

@ -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 });
},
});