From fe99c51703c3a1aaa4cb5cfd88c45525b6330c3f Mon Sep 17 00:00:00 2001 From: Joren Van Onder Date: Tue, 7 Jun 2016 14:22:05 +0200 Subject: [PATCH] [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). --- addons/hw_scanner/controllers/main.py | 111 +++++++++++++++----------- 1 file changed, 64 insertions(+), 47 deletions(-) diff --git a/addons/hw_scanner/controllers/main.py b/addons/hw_scanner/controllers/main.py index 8e5e8f6af18..ad0f47a2cda 100644 --- a/addons/hw_scanner/controllers/main.py +++ b/addons/hw_scanner/controllers/main.py @@ -22,6 +22,13 @@ except ImportError: _logger.error('Odoo module hw_scanner depends on the evdev python module') evdev = None +class ScannerDevice(): + def __init__(self, path): + self.evdev = evdev.InputDevice(path) + self.evdev.grab() + + self.barcode = [] + self.shift = False class Scanner(Thread): def __init__(self): @@ -29,6 +36,7 @@ class Scanner(Thread): self.lock = Lock() self.status = {'status':'connecting', 'messages':[]} self.input_dir = '/dev/input/by-id/' + self.open_devices = [] self.barcodes = Queue() self.keymap = { 2: ("1","!"), @@ -109,25 +117,33 @@ class Scanner(Thread): elif status == 'disconnected' and message: _logger.info('Disconnected Barcode Scanner: %s', message) - def get_device(self): + def get_devices(self): try: if not evdev: 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())] - scanners = [ device for device in devices if ('barcode' in device.lower()) or ('scanner' in device.lower())] - if len(scanners) > 0: - self.set_status('connected','Connected to '+scanners[0]) - return evdev.InputDevice(join(self.input_dir,scanners[0])) - elif len(keyboards) > 0: - self.set_status('connected','Connected to '+keyboards[0]) - return evdev.InputDevice(join(self.input_dir,keyboards[0])) + + new_devices = [device for device in listdir(self.input_dir) + if join(self.input_dir, device) not in [dev.evdev.fn for dev in self.open_devices]] + scanners = [device for device in new_devices + if (('kbd' in device) and ('keyboard' not in device.lower())) + or ('barcode' in device.lower()) or ('scanner' in device.lower())] + + for device in scanners: + _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: self.set_status('disconnected','Barcode Scanner Not Found') - return None + + return self.open_devices except Exception as e: self.set_status('error',str(e)) - return None + return [] + + def release_device(self, dev): + self.open_devices.remove(dev) def get_barcode(self): """ 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() 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): """ 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 @@ -160,49 +181,45 @@ class Scanner(Thread): barcode = [] shift = False - device = None + devices = None while True: # barcodes loop - if device: # ungrab device between barcodes and timeouts for plug & play - 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 + devices = self.get_devices() try: - device.grab() - shift = False - barcode = [] - 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 break - events = device.read() - for event in events: - if event.type == evdev.ecodes.EV_KEY: - #_logger.debug('Evdev Keyboard event %s',evdev.categorize(event)) - if event.value == 1: # keydown events - if event.code in self.keymap: - if shift: - barcode.append(self.keymap[event.code][1]) - else: - barcode.append(self.keymap[event.code][0]) - elif event.code == 42 or event.code == 54: # SHIFT - shift = True - elif event.code == 28: # ENTER, end of barcode - self.barcodes.put( (time.time(),''.join(barcode)) ) - barcode = [] - elif event.value == 0: #keyup events - if event.code == 42 or event.code == 54: # LEFT SHIFT - shift = False + for fd in r: + device = self._get_open_device_by_fd(fd) + + if not evdev.util.is_device(device.evdev.fn): + _logger.info('%s disconnected', str(device.evdev)) + self.release_device(device) + break + + events = device.evdev.read() + + for event in events: + if event.type == evdev.ecodes.EV_KEY: + # _logger.debug('Evdev Keyboard event %s',evdev.categorize(event)) + if event.value == 1: # keydown events + if event.code in self.keymap: + if device.shift: + 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: self.set_status('error',str(e))