8fbdae62db66c64ac7361f22e735a21d085281d4
[odoo/odoo.git] / addons / hw_scanner / controllers / main.py
1 # -*- coding: utf-8 -*-
2 import logging
3 import os
4 import time
5 from os import listdir
6 from os.path import join
7 from threading import Thread, Lock
8 from select import select
9 from Queue import Queue, Empty
10
11 import openerp
12 import openerp.addons.hw_proxy.controllers.main as hw_proxy
13 from openerp import http
14 from openerp.http import request
15 from openerp.tools.translate import _
16
17 _logger = logging.getLogger(__name__)
18
19 try:
20     import evdev
21 except ImportError:
22     _logger.error('OpenERP module hw_scanner depends on the evdev python module')
23     evdev = None
24
25
26 class Scanner(Thread):
27     def __init__(self):
28         Thread.__init__(self)
29         self.lock = Lock()
30         self.status = {'status':'connecting', 'messages':[]}
31         self.input_dir = '/dev/input/by-id/'
32         self.barcodes = Queue()
33         self.keymap = {
34             2: ("1","!"),
35             3: ("2","@"),
36             4: ("3","#"),
37             5: ("4","$"),
38             6: ("5","%"),
39             7: ("6","^"),
40             8: ("7","&"),
41             9: ("8","*"),
42             10:("9","("), 
43             11:("0",")"), 
44             12:("-","_"), 
45             13:("=","+"), 
46             # 14 BACKSPACE
47             # 15 TAB 
48             16:("q","Q"), 
49             17:("w","W"),
50             18:("e","E"),
51             19:("r","R"),
52             20:("t","T"),
53             21:("y","Y"),
54             22:("u","U"),
55             23:("i","I"),
56             24:("o","O"),
57             25:("p","P"),
58             26:("[","{"),
59             27:("]","}"),
60             # 28 ENTER
61             # 29 LEFT_CTRL
62             30:("a","A"),
63             31:("s","S"),
64             32:("d","D"),
65             33:("f","F"),
66             34:("g","G"),
67             35:("h","H"),
68             36:("j","J"),
69             37:("k","K"),
70             38:("l","L"),
71             39:(";",":"),
72             40:("'","\""),
73             41:("`","~"),
74             # 42 LEFT SHIFT
75             43:("\\","|"),
76             44:("z","Z"),
77             45:("x","X"),
78             46:("c","C"),
79             47:("v","V"),
80             48:("b","B"),
81             49:("n","N"),
82             50:("m","M"),
83             51:(",","<"),
84             52:(".",">"),
85             53:("/","?"),
86             # 54 RIGHT SHIFT
87             57:(" "," "),
88         }
89
90     def lockedstart(self):
91         self.lock.acquire()
92         if not self.isAlive():
93             self.start()
94         self.lock.release()
95
96     def set_status(self, status, message = None):
97         if status == self.status['status']:
98             if message != None and message != self.status['messages'][-1]:
99                 self.status['messages'].append(message)
100         else:
101             self.status['status'] = status
102             if message:
103                 self.status['messages'] = [message]
104             else:
105                 self.status['messages'] = []
106
107         if status == 'error' and message:
108             _logger.error('Barcode Scanner Error: '+message)
109         elif status == 'disconnected' and message:
110             _logger.warning('Disconnected Barcode Scanner: '+message)
111
112     def get_device(self):
113         try:
114             if not evdev:
115                 return None
116             devices   = [ device for device in listdir(self.input_dir)]
117             keyboards = [ device for device in devices if ('kbd' in device) and ('keyboard' not in device.lower())]
118             scanners  = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())]
119             if len(scanners) > 0:
120                 self.set_status('connected','Connected to '+scanners[0])
121                 return evdev.InputDevice(join(self.input_dir,scanners[0]))
122             elif len(keyboards) > 0:
123                 self.set_status('connected','Connected to '+keyboards[0])
124                 return evdev.InputDevice(join(self.input_dir,keyboards[0]))
125             else:
126                 self.set_status('disconnected','Barcode Scanner Not Found')
127                 return None
128         except Exception as e:
129             self.set_status('error',str(e))
130             return None
131
132     @http.route('/hw_proxy/Vis_scanner_connected', type='json', auth='none', cors='*')
133     def is_scanner_connected(self):
134         return self.get_device() != None
135     
136     def get_barcode(self):
137         """ Returns a scanned barcode. Will wait at most 5 seconds to get a barcode, and will
138             return barcode scanned in the past if they are not older than 5 seconds and have not
139             been returned before. This is necessary to catch barcodes scanned while the POS is
140             busy reading another barcode
141         """
142
143         self.lockedstart()
144
145         while True:
146             try:
147                 timestamp, barcode = self.barcodes.get(True, 5)
148                 if timestamp > time.time() - 5: 
149                     return barcode
150             except Empty:
151                 return ''
152     
153     def get_status(self):
154         self.lockedstart()
155         return self.status
156
157     def run(self):
158         """ This will start a loop that catches all keyboard events, parse barcode
159             sequences and put them on a timestamped queue that can be consumed by
160             the point of sale's requests for barcode events 
161         """
162         
163         self.barcodes = Queue()
164         
165         barcode  = []
166         shift    = False
167         device   = None
168
169         while True: # barcodes loop
170             if device:  # ungrab device between barcodes and timeouts for plug & play
171                 try:
172                     device.ungrab() 
173                 except Exception as e:
174                     self.set_status('error',str(e))
175             device = self.get_device()
176             if not device:
177                 time.sleep(5)   # wait until a suitable device is plugged
178             else:
179                 try:
180                     device.grab()
181                     shift = False
182                     barcode = []
183
184                     while True: # keycode loop
185                         r,w,x = select([device],[],[],5)
186                         if len(r) == 0: # timeout
187                             break
188                         events = device.read()
189
190                         for event in events:
191                             if event.type == evdev.ecodes.EV_KEY:
192                                 #_logger.debug('Evdev Keyboard event %s',evdev.categorize(event))
193                                 if event.value == 1: # keydown events
194                                     if event.code in self.keymap: 
195                                         if shift:
196                                             barcode.append(self.keymap[event.code][1])
197                                         else:
198                                             barcode.append(self.keymap[event.code][0])
199                                     elif event.code == 42 or event.code == 54: # SHIFT
200                                         shift = True
201                                     elif event.code == 28: # ENTER, end of barcode
202                                         self.barcodes.put( (time.time(),''.join(barcode)) )
203                                         barcode = []
204                                 elif event.value == 0: #keyup events
205                                     if event.code == 42 or event.code == 54: # LEFT SHIFT
206                                         shift = False
207
208                 except Exception as e:
209                     self.set_status('error',str(e))
210
211 s = Scanner()
212
213 hw_proxy.drivers['scanner'] = s
214
215 class ScannerDriver(hw_proxy.Proxy):
216     @http.route('/hw_proxy/scanner', type='json', auth='none', cors='*')
217     def scanner(self):
218         return s.get_barcode()
219         
220