Search code examples
pythonmultithreadingsignalsevdev

Interrupt a blocking evdev reading


I have a module that captures all reads from a barcode scanner. My problem is that I can not close it properly. After sending a KeyboardInterrupt or SystemExit signal, it stops only when I scan a barcode.

I tried to add this method to the BarcodeScanner class, but it still doesn't work:

def interrupt(self):
    """Envoi la demande d'arrêt."""

    super(BarcodeScanner, self).interrupt()
    self.device.write(ecodes.EV_KEY, ecodes.KEY_ESC, 0)
    self.device.write(ecodes.EV_SYN, ecodes.SYN_REPORT, 0)

How can I do that?

interruptable.py

#!/usr/bin/env python3
# -*-coding:Utf-8 -*


class Interruptable:
    """Classe intégrant une boucle sans fin pouvant être stoppée proprement."""

    interruptables = []

    def __init__(self):
        """Crée un objet interruptible."""

        self._interrupted = False
        Interruptable.interruptables.append(self)

    def interrupt(self):
        """Envoi la demande d'arrêt."""

        self._interrupted = True

    def reset(self):
        """Permet le redémarrage d'un service."""

        self._interrupted = False

    def interrupted(self):
        """Verifie si la demande d'arrêt à déjà été envoyée."""

        return self._interrupted

barcode_scanner.py

#!/usr/bin/env python3
# -*-coding:Utf-8 -*

from evdev import InputDevice, ecodes, list_devices, categorize
import signal, sys
import threading
from time import sleep

from tymsoft.interruptable import Interruptable

class BarcodeScanner(Interruptable):
    """TODO"""

    def __init__(self, device_name='Bar Code', layout='AZERTY_FR'):
        self.modifiers = {
            # 0: None, 29: u'LCTRL', 42: u'LSHFT', 54: u'RSHFT', 56: u'LALT', 97:u'RCTRL', 100: u'RALT'
            0: 0, 29: 1, 42: 2, 54: 3, 56: 4, 97: 5, 100: 6
        }
        self.layouts = {
            'AZERTY_FR': {
                # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                2: [u'&', u'', u'1', u'1', u'', u'', u''],
                3: [u'é', u'', u'2', u'2', u'', u'', u'~'],
                4: [u'"', u'', u'3', u'3', u'', u'', u'#'],
                5: [u'\'', u'', u'4', u'4', u'', u'', u'{'],
                6: [u'(', u'', u'5', u'5', u'', u'', u'['],
                7: [u'-', u'', u'6', u'6', u'', u'', u'|'],
                8: [u'è', u'', u'7', u'7', u'', u'', u'`'],
                9: [u'_', u'', u'8', u'8', u'', u'', u'\\'],
                10: [u'ç', u'', u'9', u'9', u'', u'', u'^'],
                11: [u'à', u'', u'0', u'0', u'', u'', u'@'],
                12: [u')', u'', u'°', u'°', u'', u'', u']'],
                13: [u'=', u'', u'+', u'+', u'', u'', u'}'],
                15: [u'\t', u'', u'', u'', u'', u'', u''],
                16: [u'a', u'', u'A', u'A', u'', u'', u''],
                17: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                18: [u'e', u'', u'E', u'E', u'', u'', u'€'],
                19: [u'r', u'', u'R', u'R', u'', u'', u''],
                20: [u't', u'', u'T', u'T', u'', u'', u''],
                21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                22: [u'u', u'', u'U', u'U', u'', u'', u''],
                23: [u'i', u'', u'I', u'I', u'', u'', u''],
                24: [u'o', u'', u'O', u'O', u'', u'', u''],
                25: [u'p', u'', u'P', u'P', u'', u'', u''],
                26: [u'^', u'', u'¨', u'¨', u'', u'', u''],
                27: [u'$', u'', u'£', u'£', u'', u'', u'¤'],
                28: [u'\n', u'', u'', u'', u'', u'', u''],
                30: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                31: [u's', u'', u'S', u'S', u'', u'', u''],
                32: [u'd', u'', u'D', u'D', u'', u'', u''],
                33: [u'f', u'', u'F', u'F', u'', u'', u''],
                34: [u'g', u'', u'G', u'G', u'', u'', u''],
                35: [u'h', u'', u'H', u'H', u'', u'', u''],
                36: [u'j', u'', u'J', u'J', u'', u'', u''],
                37: [u'k', u'', u'K', u'K', u'', u'', u''],
                38: [u'l', u'', u'L', u'L', u'', u'', u''],
                39: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                40: [u'ù', u'', u'%', u'%', u'', u'', u''],
                41: [u'²', u'', u'', u'', u'', u'', u''],
                43: [u'*', u'', u'µ', u'µ', u'', u'', u''],
                44: [u'w', u'', u'W', u'W', u'', u'', u''],
                45: [u'x', u'', u'X', u'X', u'', u'', u''],
                46: [u'c', u'', u'C', u'C', u'', u'', u''],
                47: [u'v', u'', u'V', u'V', u'', u'', u''],
                48: [u'b', u'', u'B', u'B', u'', u'', u''],
                49: [u'n', u'', u'N', u'N', u'', u'', u''],
                50: [u',', u'', u'?', u'?', u'', u'', u''],
                51: [u';', u'', u'.', u'.', u'', u'', u''],
                52: [u':', u'', u'/', u'/', u'', u'', u''],
                53: [u'!', u'', u'§', u'§', u'', u'', u''],
                57: [u' ', u'', u' ', u' ', u'', u'', u''],
                86: [u'<', u'', u'>', u'>', u'', u'', u''],
                96: [u'\n', u'', u'', u'', u'', u'', u'']
            },
            'QWERTY_US': {
                # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                2: [u'1', u'', u'!', u'!', u'', u'', u''],
                3: [u'2', u'', u'@', u'@', u'', u'', u''],
                4: [u'3', u'', u'#', u'#', u'', u'', u''],
                5: [u'4', u'', u'$', u'$', u'', u'', u''],
                6: [u'5', u'', u'%', u'%', u'', u'', u''],
                7: [u'6', u'', u'^', u'^', u'', u'', u''],
                8: [u'7', u'', u'&', u'&', u'', u'', u''],
                9: [u'8', u'', u'*', u'*', u'', u'', u''],
                10: [u'9', u'', u'(', u'(', u'', u'', u''],
                11: [u'0', u'', u')', u')', u'', u'', u''],
                12: [u'-', u'', u'_', u'_', u'', u'', u''],
                13: [u'=', u'', u'+', u'+', u'', u'', u''],
                15: [u'\t', u'', u'', u'', u'', u'', u''],
                16: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                17: [u'w', u'', u'W', u'W', u'', u'', u''],
                18: [u'e', u'', u'E', u'E', u'', u'', u''],
                19: [u'r', u'', u'R', u'R', u'', u'', u''],
                20: [u't', u'', u'T', u'T', u'', u'', u''],
                21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                22: [u'u', u'', u'U', u'U', u'', u'', u''],
                23: [u'i', u'', u'I', u'I', u'', u'', u''],
                24: [u'o', u'', u'O', u'O', u'', u'', u''],
                25: [u'p', u'', u'P', u'P', u'', u'', u''],
                26: [u'[', u'', u'{', u'{', u'', u'', u''],
                27: [u']', u'', u'}', u'}', u'', u'', u''],
                28: [u'\n', u'', u'', u'', u'', u'', u''],
                30: [u'a', u'', u'A', u'A', u'', u'', u''],
                31: [u's', u'', u'S', u'S', u'', u'', u''],
                32: [u'd', u'', u'D', u'D', u'', u'', u''],
                33: [u'f', u'', u'F', u'F', u'', u'', u''],
                34: [u'g', u'', u'G', u'G', u'', u'', u''],
                35: [u'h', u'', u'H', u'H', u'', u'', u''],
                36: [u'j', u'', u'J', u'J', u'', u'', u''],
                37: [u'k', u'', u'K', u'K', u'', u'', u''],
                38: [u'l', u'', u'L', u'L', u'', u'', u''],
                39: [u';', u'', u':', u':', u'', u'', u''],
                40: [u'\'', u'', u'"', u'"', u'', u'', u''],
                41: [u'`', u'', u'~', u'~', u'', u'', u''],
                43: [u'\\', u'', u'|', u'|', u'', u'', u''],
                44: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                45: [u'x', u'', u'X', u'X', u'', u'', u''],
                46: [u'c', u'', u'C', u'C', u'', u'', u''],
                47: [u'v', u'', u'V', u'V', u'', u'', u''],
                48: [u'b', u'', u'B', u'B', u'', u'', u''],
                49: [u'n', u'', u'N', u'N', u'', u'', u''],
                50: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                51: [u',', u'', u'<', u'<', u'', u'', u''],
                52: [u'.', u'', u'>', u'>', u'', u'', u''],
                53: [u'/', u'', u'?', u'?', u'', u'', u''],
                57: [u' ', u'', u' ', u' ', u'', u'', u''],
                96: [u'\n', u'', u'', u'', u'', u'', u'']
            }
        }
        self.eol = [(0, 28), (0, 96), (29, 39)]

        self.layout = layout

        self.device = None
        devices = map(InputDevice, list_devices())
        for device in devices:
            if device_name in device.name:
                self.device = InputDevice(device.fn)
                print('Lecteur de codes-barres : ' + device.name)
                break

        self._listeners = []
        Interruptable.__init__(self)
        self.device.grab()

    def start_capture(self):
        """TODO"""

        def run():
            barcode = ''
            modifier = 0
            keycode = 0
            for event in self.device.read_loop():
                if not self.interrupted():
                    if event.type == ecodes.EV_KEY:
                        data = categorize(event)
                        if data.keystate == 1:
                            if data.scancode in self.modifiers:
                                modifier = data.scancode
                            elif data.scancode in self.layouts[self.layout]:
                                keycode = data.scancode
                    elif event.type == ecodes.EV_SYN:
                        if (modifier, keycode) in self.eol:
                            if len(barcode) > 0:
                                for listener in self._listeners:
                                    listener(barcode)
                                barcode = ''
                        elif keycode != 0:
                            barcode += self.layouts[self.layout][keycode][self.modifiers[modifier]]
                        modifier = 0
                        keycode = 0
                else:
                    self.device.ungrab()
                    break


        thread = threading.Thread(target=run, name='barcode_scanner')
        thread.start()

    def register_listener(self, callback):
        self._listeners.append(callback)

Solution

  • I solved the issue by replacing the read_loop() method by a loop of select()+read(). In this way, I could put a timeout on the select.

    #!/usr/bin/env python3
    # -*-coding:Utf-8 -*
    
    from evdev import InputDevice, ecodes, list_devices, categorize
    from select import select
    import signal, sys
    import threading
    from time import sleep
    
    from tymsoft.interruptable import Interruptable
    
    class BarcodeScanner(Interruptable):
        """TODO"""
    
        def __init__(self, device_name='Bar Code', layout='AZERTY_FR'):
            self.modifiers = {
                # 0: None, 29: u'LCTRL', 42: u'LSHFT', 54: u'RSHFT', 56: u'LALT', 97:u'RCTRL', 100: u'RALT'
                0: 0, 29: 1, 42: 2, 54: 3, 56: 4, 97: 5, 100: 6
            }
            self.layouts = {
                'AZERTY_FR': {
                    # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                    2: [u'&', u'', u'1', u'1', u'', u'', u''],
                    3: [u'é', u'', u'2', u'2', u'', u'', u'~'],
                    4: [u'"', u'', u'3', u'3', u'', u'', u'#'],
                    5: [u'\'', u'', u'4', u'4', u'', u'', u'{'],
                    6: [u'(', u'', u'5', u'5', u'', u'', u'['],
                    7: [u'-', u'', u'6', u'6', u'', u'', u'|'],
                    8: [u'è', u'', u'7', u'7', u'', u'', u'`'],
                    9: [u'_', u'', u'8', u'8', u'', u'', u'\\'],
                    10: [u'ç', u'', u'9', u'9', u'', u'', u'^'],
                    11: [u'à', u'', u'0', u'0', u'', u'', u'@'],
                    12: [u')', u'', u'°', u'°', u'', u'', u']'],
                    13: [u'=', u'', u'+', u'+', u'', u'', u'}'],
                    15: [u'\t', u'', u'', u'', u'', u'', u''],
                    16: [u'a', u'', u'A', u'A', u'', u'', u''],
                    17: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                    18: [u'e', u'', u'E', u'E', u'', u'', u'€'],
                    19: [u'r', u'', u'R', u'R', u'', u'', u''],
                    20: [u't', u'', u'T', u'T', u'', u'', u''],
                    21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                    22: [u'u', u'', u'U', u'U', u'', u'', u''],
                    23: [u'i', u'', u'I', u'I', u'', u'', u''],
                    24: [u'o', u'', u'O', u'O', u'', u'', u''],
                    25: [u'p', u'', u'P', u'P', u'', u'', u''],
                    26: [u'^', u'', u'¨', u'¨', u'', u'', u''],
                    27: [u'$', u'', u'£', u'£', u'', u'', u'¤'],
                    28: [u'\n', u'', u'', u'', u'', u'', u''],
                    30: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                    31: [u's', u'', u'S', u'S', u'', u'', u''],
                    32: [u'd', u'', u'D', u'D', u'', u'', u''],
                    33: [u'f', u'', u'F', u'F', u'', u'', u''],
                    34: [u'g', u'', u'G', u'G', u'', u'', u''],
                    35: [u'h', u'', u'H', u'H', u'', u'', u''],
                    36: [u'j', u'', u'J', u'J', u'', u'', u''],
                    37: [u'k', u'', u'K', u'K', u'', u'', u''],
                    38: [u'l', u'', u'L', u'L', u'', u'', u''],
                    39: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                    40: [u'ù', u'', u'%', u'%', u'', u'', u''],
                    41: [u'²', u'', u'', u'', u'', u'', u''],
                    43: [u'*', u'', u'µ', u'µ', u'', u'', u''],
                    44: [u'w', u'', u'W', u'W', u'', u'', u''],
                    45: [u'x', u'', u'X', u'X', u'', u'', u''],
                    46: [u'c', u'', u'C', u'C', u'', u'', u''],
                    47: [u'v', u'', u'V', u'V', u'', u'', u''],
                    48: [u'b', u'', u'B', u'B', u'', u'', u''],
                    49: [u'n', u'', u'N', u'N', u'', u'', u''],
                    50: [u',', u'', u'?', u'?', u'', u'', u''],
                    51: [u';', u'', u'.', u'.', u'', u'', u''],
                    52: [u':', u'', u'/', u'/', u'', u'', u''],
                    53: [u'!', u'', u'§', u'§', u'', u'', u''],
                    57: [u' ', u'', u' ', u' ', u'', u'', u''],
                    86: [u'<', u'', u'>', u'>', u'', u'', u''],
                    96: [u'\n', u'', u'', u'', u'', u'', u'']
                },
                'QWERTY_US': {
                    # [NORMAL, LCTRL, LSHFT, RSHFT, LALT, RCTRL, RALT]
                    2: [u'1', u'', u'!', u'!', u'', u'', u''],
                    3: [u'2', u'', u'@', u'@', u'', u'', u''],
                    4: [u'3', u'', u'#', u'#', u'', u'', u''],
                    5: [u'4', u'', u'$', u'$', u'', u'', u''],
                    6: [u'5', u'', u'%', u'%', u'', u'', u''],
                    7: [u'6', u'', u'^', u'^', u'', u'', u''],
                    8: [u'7', u'', u'&', u'&', u'', u'', u''],
                    9: [u'8', u'', u'*', u'*', u'', u'', u''],
                    10: [u'9', u'', u'(', u'(', u'', u'', u''],
                    11: [u'0', u'', u')', u')', u'', u'', u''],
                    12: [u'-', u'', u'_', u'_', u'', u'', u''],
                    13: [u'=', u'', u'+', u'+', u'', u'', u''],
                    15: [u'\t', u'', u'', u'', u'', u'', u''],
                    16: [u'q', u'', u'Q', u'Q', u'', u'', u''],
                    17: [u'w', u'', u'W', u'W', u'', u'', u''],
                    18: [u'e', u'', u'E', u'E', u'', u'', u''],
                    19: [u'r', u'', u'R', u'R', u'', u'', u''],
                    20: [u't', u'', u'T', u'T', u'', u'', u''],
                    21: [u'y', u'', u'Y', u'Y', u'', u'', u''],
                    22: [u'u', u'', u'U', u'U', u'', u'', u''],
                    23: [u'i', u'', u'I', u'I', u'', u'', u''],
                    24: [u'o', u'', u'O', u'O', u'', u'', u''],
                    25: [u'p', u'', u'P', u'P', u'', u'', u''],
                    26: [u'[', u'', u'{', u'{', u'', u'', u''],
                    27: [u']', u'', u'}', u'}', u'', u'', u''],
                    28: [u'\n', u'', u'', u'', u'', u'', u''],
                    30: [u'a', u'', u'A', u'A', u'', u'', u''],
                    31: [u's', u'', u'S', u'S', u'', u'', u''],
                    32: [u'd', u'', u'D', u'D', u'', u'', u''],
                    33: [u'f', u'', u'F', u'F', u'', u'', u''],
                    34: [u'g', u'', u'G', u'G', u'', u'', u''],
                    35: [u'h', u'', u'H', u'H', u'', u'', u''],
                    36: [u'j', u'', u'J', u'J', u'', u'', u''],
                    37: [u'k', u'', u'K', u'K', u'', u'', u''],
                    38: [u'l', u'', u'L', u'L', u'', u'', u''],
                    39: [u';', u'', u':', u':', u'', u'', u''],
                    40: [u'\'', u'', u'"', u'"', u'', u'', u''],
                    41: [u'`', u'', u'~', u'~', u'', u'', u''],
                    43: [u'\\', u'', u'|', u'|', u'', u'', u''],
                    44: [u'z', u'', u'Z', u'Z', u'', u'', u''],
                    45: [u'x', u'', u'X', u'X', u'', u'', u''],
                    46: [u'c', u'', u'C', u'C', u'', u'', u''],
                    47: [u'v', u'', u'V', u'V', u'', u'', u''],
                    48: [u'b', u'', u'B', u'B', u'', u'', u''],
                    49: [u'n', u'', u'N', u'N', u'', u'', u''],
                    50: [u'm', u'\n', u'M', u'M', u'', u'', u''],
                    51: [u',', u'', u'<', u'<', u'', u'', u''],
                    52: [u'.', u'', u'>', u'>', u'', u'', u''],
                    53: [u'/', u'', u'?', u'?', u'', u'', u''],
                    57: [u' ', u'', u' ', u' ', u'', u'', u''],
                    96: [u'\n', u'', u'', u'', u'', u'', u'']
                }
            }
            self.eol = [(0, 28), (0, 96), (29, 39)]
    
            self.layout = layout
    
            self.device = None
            devices = map(InputDevice, list_devices())
            for device in devices:
                if device_name in device.name:
                    self.device = InputDevice(device.fn)
                    print('Lecteur de codes-barres : ' + device.name)
                    break
    
            self._listeners = []
            Interruptable.__init__(self)
            self.device.grab()
    
        def start_capture(self):
            """TODO"""
    
            def run():
                barcode = ''
                modifier = 0
                keycode = 0
                while not self.interrupted():
                    select([self.device], [], [], 0.25)
                    try:
                        for event in self.device.read():
                                if event.type == ecodes.EV_KEY:
                                    data = categorize(event)
                                    if data.keystate == 1:
                                        if data.scancode in self.modifiers:
                                            modifier = data.scancode
                                        elif data.scancode in self.layouts[self.layout]:
                                            keycode = data.scancode
                                elif event.type == ecodes.EV_SYN:
                                    if (modifier, keycode) in self.eol:
                                        if len(barcode) > 0:
                                            for listener in self._listeners:
                                                listener(barcode)
                                            barcode = ''
                                    elif keycode != 0:
                                        barcode += self.layouts[self.layout][keycode][self.modifiers[modifier]]
                                    modifier = 0
                                    keycode = 0
                    except BlockingIOError:
                        pass
                self.device.ungrab()
    
            thread = threading.Thread(target=run, name='barcode_scanner')
            thread.start()
    
        def register_listener(self, callback):
            self._listeners.append(callback)