[IMP] point_of_sale, hw_scale: first commit for the hw_scale module, which handles...
authorFrédéric van der Essen <fva@openerp.com>
Thu, 24 Apr 2014 17:12:59 +0000 (19:12 +0200)
committerFrédéric van der Essen <fva@openerp.com>
Thu, 24 Apr 2014 17:12:59 +0000 (19:12 +0200)
bzr revid: fva@openerp.com-20140424171259-hi9ma6w0fkdfrqnv

addons/hw_proxy/controllers/main.py
addons/hw_scale/__init__.py [new file with mode: 0644]
addons/hw_scale/__openerp__.py [new file with mode: 0644]
addons/hw_scale/controllers/__init__.py [new file with mode: 0644]
addons/hw_scale/controllers/main.py [new file with mode: 0644]
addons/hw_scale/mettler.py [new file with mode: 0755]
addons/point_of_sale/static/src/js/devices.js
addons/point_of_sale/static/src/js/screens.js

index ea9b45c..c65c796 100644 (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):
         """
diff --git a/addons/hw_scale/__init__.py b/addons/hw_scale/__init__.py
new file mode 100644 (file)
index 0000000..a208bc1
--- /dev/null
@@ -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:
+
diff --git a/addons/hw_scale/__openerp__.py b/addons/hw_scale/__openerp__.py
new file mode 100644 (file)
index 0000000..c986d88
--- /dev/null
@@ -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:
diff --git a/addons/hw_scale/controllers/__init__.py b/addons/hw_scale/controllers/__init__.py
new file mode 100644 (file)
index 0000000..b5f0bcc
--- /dev/null
@@ -0,0 +1,3 @@
+import main
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+
diff --git a/addons/hw_scale/controllers/main.py b/addons/hw_scale/controllers/main.py
new file mode 100644 (file)
index 0000000..4c8e1ff
--- /dev/null
@@ -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
+        
+        
diff --git a/addons/hw_scale/mettler.py b/addons/hw_scale/mettler.py
new file mode 100755 (executable)
index 0000000..5c5df97
--- /dev/null
@@ -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)
+    
index f46b156..316aea5 100644 (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;
         },
index 1d6f018..c58e7d8 100644 (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 });
         },
     });