[IMP] hw_scanner: support multiple simultaneously attached devices

The hw_scanner module not only supports barcode readers (although that
was it's original intention). It also happens to support certain card
readers (like the MagTek Dynamag we use for Mercury) because they act
the same way as barcode scanners (like a keyboard and end sequence with
ENTER).

Before we supported Mercury there was no real reason to support multiple
devices on the POSBox, because it didn't make much sense to attach >1
barcode reader to the same POSBox. With Mercury however, this is a real
use case, as users want both a barcode reader and a card reader.

This commit implements support for this, while keeping the functionality
as close as possible to how it was before (eg. care was taken to not
break plug and play support).
This commit is contained in:
Joren Van Onder 2016-06-07 14:22:05 +02:00
parent 6140300da0
commit fe99c51703
1 changed files with 64 additions and 47 deletions

View File

@ -22,6 +22,13 @@ except ImportError:
_logger.error('Odoo module hw_scanner depends on the evdev python module') _logger.error('Odoo module hw_scanner depends on the evdev python module')
evdev = None evdev = None
class ScannerDevice():
def __init__(self, path):
self.evdev = evdev.InputDevice(path)
self.evdev.grab()
self.barcode = []
self.shift = False
class Scanner(Thread): class Scanner(Thread):
def __init__(self): def __init__(self):
@ -29,6 +36,7 @@ class Scanner(Thread):
self.lock = Lock() self.lock = Lock()
self.status = {'status':'connecting', 'messages':[]} self.status = {'status':'connecting', 'messages':[]}
self.input_dir = '/dev/input/by-id/' self.input_dir = '/dev/input/by-id/'
self.open_devices = []
self.barcodes = Queue() self.barcodes = Queue()
self.keymap = { self.keymap = {
2: ("1","!"), 2: ("1","!"),
@ -109,25 +117,33 @@ class Scanner(Thread):
elif status == 'disconnected' and message: elif status == 'disconnected' and message:
_logger.info('Disconnected Barcode Scanner: %s', message) _logger.info('Disconnected Barcode Scanner: %s', message)
def get_device(self): def get_devices(self):
try: try:
if not evdev: if not evdev:
return None return None
devices = [ device for device in listdir(self.input_dir)]
keyboards = [ device for device in devices if ('kbd' in device) and ('keyboard' not in device.lower())] new_devices = [device for device in listdir(self.input_dir)
scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())] if join(self.input_dir, device) not in [dev.evdev.fn for dev in self.open_devices]]
if len(scanners) > 0: scanners = [device for device in new_devices
self.set_status('connected','Connected to '+scanners[0]) if (('kbd' in device) and ('keyboard' not in device.lower()))
return evdev.InputDevice(join(self.input_dir,scanners[0])) or ('barcode' in device.lower()) or ('scanner' in device.lower())]
elif len(keyboards) > 0:
self.set_status('connected','Connected to '+keyboards[0]) for device in scanners:
return evdev.InputDevice(join(self.input_dir,keyboards[0])) _logger.debug('opening device %s', join(self.input_dir,device))
self.open_devices.append(ScannerDevice(join(self.input_dir,device)))
if self.open_devices:
self.set_status('connected','Connected to '+ str([dev.evdev.name for dev in self.open_devices]))
else: else:
self.set_status('disconnected','Barcode Scanner Not Found') self.set_status('disconnected','Barcode Scanner Not Found')
return None
return self.open_devices
except Exception as e: except Exception as e:
self.set_status('error',str(e)) self.set_status('error',str(e))
return None return []
def release_device(self, dev):
self.open_devices.remove(dev)
def get_barcode(self): def get_barcode(self):
""" Returns a scanned barcode. Will wait at most 5 seconds to get a barcode, and will """ Returns a scanned barcode. Will wait at most 5 seconds to get a barcode, and will
@ -150,6 +166,11 @@ class Scanner(Thread):
self.lockedstart() self.lockedstart()
return self.status return self.status
def _get_open_device_by_fd(self, fd):
for dev in self.open_devices:
if dev.evdev.fd == fd:
return dev
def run(self): def run(self):
""" This will start a loop that catches all keyboard events, parse barcode """ This will start a loop that catches all keyboard events, parse barcode
sequences and put them on a timestamped queue that can be consumed by sequences and put them on a timestamped queue that can be consumed by
@ -160,49 +181,45 @@ class Scanner(Thread):
barcode = [] barcode = []
shift = False shift = False
device = None devices = None
while True: # barcodes loop while True: # barcodes loop
if device: # ungrab device between barcodes and timeouts for plug & play devices = self.get_devices()
try:
device.ungrab()
except Exception as e:
device = None
self.set_status('error',str(e))
else:
time.sleep(5) # wait until a suitable device is plugged
device = self.get_device()
if not device:
continue
try: try:
device.grab()
shift = False
barcode = []
while True: # keycode loop while True: # keycode loop
r,w,x = select([device],[],[],5) r,w,x = select({dev.fd: dev for dev in [d.evdev for d in devices]},[],[],5)
if len(r) == 0: # timeout if len(r) == 0: # timeout
break break
events = device.read()
for event in events: for fd in r:
if event.type == evdev.ecodes.EV_KEY: device = self._get_open_device_by_fd(fd)
#_logger.debug('Evdev Keyboard event %s',evdev.categorize(event))
if event.value == 1: # keydown events if not evdev.util.is_device(device.evdev.fn):
if event.code in self.keymap: _logger.info('%s disconnected', str(device.evdev))
if shift: self.release_device(device)
barcode.append(self.keymap[event.code][1]) break
else:
barcode.append(self.keymap[event.code][0]) events = device.evdev.read()
elif event.code == 42 or event.code == 54: # SHIFT
shift = True for event in events:
elif event.code == 28: # ENTER, end of barcode if event.type == evdev.ecodes.EV_KEY:
self.barcodes.put( (time.time(),''.join(barcode)) ) # _logger.debug('Evdev Keyboard event %s',evdev.categorize(event))
barcode = [] if event.value == 1: # keydown events
elif event.value == 0: #keyup events if event.code in self.keymap:
if event.code == 42 or event.code == 54: # LEFT SHIFT if device.shift:
shift = False device.barcode.append(self.keymap[event.code][1])
else:
device.barcode.append(self.keymap[event.code][0])
elif event.code == 42 or event.code == 54: # SHIFT
device.shift = True
elif event.code == 28: # ENTER, end of barcode
_logger.debug('pushing barcode %s from %s', ''.join(device.barcode), str(device.evdev))
self.barcodes.put( (time.time(),''.join(device.barcode)) )
device.barcode = []
elif event.value == 0: #keyup events
if event.code == 42 or event.code == 54: # LEFT SHIFT
device.shift = False
except Exception as e: except Exception as e:
self.set_status('error',str(e)) self.set_status('error',str(e))