From 61401a1ab7898ab28479bba14b81a59444d950b7 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Fri, 12 Aug 2022 11:17:09 +0200 Subject: [PATCH] Develop kort-client --- 99-evolis.rules | 1 + kort_client/api.py | 6 + kort_client/card_detectors.py | 102 +++++++++++++ kort_client/run.py | 32 +++- kort_client/usb_barcode_scanner/__init__.py | 0 kort_client/usb_barcode_scanner/scanner.py | 158 ++++++++++++++++++++ match.sh | 9 ++ pyproject.toml | 1 + 8 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 99-evolis.rules create mode 100644 kort_client/card_detectors.py create mode 100644 kort_client/usb_barcode_scanner/__init__.py create mode 100755 kort_client/usb_barcode_scanner/scanner.py create mode 100755 match.sh diff --git a/99-evolis.rules b/99-evolis.rules new file mode 100644 index 0000000..d5b2524 --- /dev/null +++ b/99-evolis.rules @@ -0,0 +1 @@ +SUBSYSTEM=="usb", ATTRS{idVendor}=="0f49", ATTRS{idProduct}=="0b00", MODE="0664", GROUP="dialout" diff --git a/kort_client/api.py b/kort_client/api.py index 0a1300f..6cdda86 100644 --- a/kort_client/api.py +++ b/kort_client/api.py @@ -24,6 +24,7 @@ class KortAPI: "printer": "app/kort/api/v1/printers/", "next_job": "app/kort/api/v1/printers/{}/jobs/next/", "job_status": "app/kort/api/v1/jobs/{}/status/", + "chip_number": "app/kort/api/v1/jobs/{}/chip_number/", } def __init__(self): @@ -54,6 +55,11 @@ class KortAPI: r = self._do_request("PUT", "job_status", [job_id], data=data) return r + def set_chip_number(self, job_id: int, chip_number: str) -> dict[str, Any]: + data = {"chip_number": chip_number} + r = self._do_request("PUT", "chip_number", [job_id], data=data) + return r + def set_printer_status( self, printer_id: int, status: str, status_text: str = None ) -> dict[str, Any]: diff --git a/kort_client/card_detectors.py b/kort_client/card_detectors.py new file mode 100644 index 0000000..6a08bee --- /dev/null +++ b/kort_client/card_detectors.py @@ -0,0 +1,102 @@ +import os +import subprocess # noqa + +from .usb_barcode_scanner.scanner import BarcodeReader + + +class CardDetector: + id_ = None + + def read_id(self): + raise NotImplementedError() + + def abort(self): + pass + + def clear(self): + pass + + @classmethod + def get_id(cls): + if not cls.id_: + raise NotImplementedError() + return cls.id_ + + @classmethod + def get_card_detectors(cls): + card_detectors = cls.__subclasses__() + return {d.get_id(): d() for d in card_detectors} + + +class EvolisCardDetector(CardDetector): + """This is a card detector for Evolis card printers like Evolis Primacy. + + To use it, you have to get the evocom tool from the Evolis customer support. + Then place it at /opt/evocom/evocom. + """ + + id_ = "evolis_evocom" + EVOCOM_PATH = "/opt/evocom/evocom" + + def get_detector(self): + from kort_client.run import KortClientException + + files = [] + for __, __, files in os.walk("/dev/"): + files = files + break + files = {f for f in files if f.startswith("hidraw")} + possible_interfaces = [] + for hid_interface in list(files): + path_to_read = f"/sys/class/hidraw/{hid_interface}/device/uevent" + with open(path_to_read) as f: + content = f.read() + if "rfid" in content.lower(): + possible_interfaces.append(hid_interface) + + if len(possible_interfaces) != 1: + raise KortClientException("No or more than one reader found") + + return os.path.join("/dev", possible_interfaces[0]) + + def get_printer(self): + from kort_client.run import KortClientException + + files = [] + for __, __, files in os.walk("/dev/usb/"): + files += files + + files = {f for f in files if f.startswith("lp")} + + if len(files) != 1: + raise KortClientException("No or more than one printer found") + + suffix = list(files)[0] + return f"/dev/usb/{suffix}" + + def read_id(self): + from kort_client.run import KortClientException + + reader = BarcodeReader(self.get_detector()) + # Transport the card to the printer + try: + r = subprocess.run( # noqa + [self.EVOCOM_PATH, "-p", self.get_printer(), "Sis"] + ) + except (subprocess.CalledProcessError, OSError) as e: + raise KortClientException(e) + + # Read the card + chip_number = reader.read_barcode() + + try: + r.check_returncode() + except (subprocess.CalledProcessError, OSError) as e: + raise KortClientException(e) + return chip_number + + def clear(self): + subprocess.run([self.EVOCOM_PATH, "-p", self.get_printer(), "Ser"]) # noqa + + def abort(self): + self.clear() diff --git a/kort_client/run.py b/kort_client/run.py index 970ba4d..2a4ec40 100644 --- a/kort_client/run.py +++ b/kort_client/run.py @@ -7,6 +7,7 @@ import cups import requests from kort_client.api import KortAPIException +from kort_client.card_detectors import CardDetector class KortClientException(Exception): @@ -22,6 +23,9 @@ class PrintClient: self.api = api self.printer_id = None self.conn = cups.Connection() + self.card_detectors = CardDetector.get_card_detectors() + self.card_detector = None + self.card_detector_in_process = False def run(self): try: @@ -35,6 +39,8 @@ class PrintClient: "An error occured, try again in five seconds: {}".format(e), fg="red", ) + if self.card_detector_in_process and self.card_detector: + self.card_detector.abort() else: printer_status = "online" status_text = "" @@ -56,6 +62,8 @@ class PrintClient: self.api.set_printer_status( self.printer_id, "offline", "Printer client was stopped by user." ) + if self.card_detector and self.card_detector_in_process: + self.card_detector.abort() raise def _validate_printer(self): @@ -77,6 +85,13 @@ class PrintClient: self._validate_printer() + self.card_detector = self.card_detectors.get( + self.printer_config["card_detector"], None + ) + self.card_detector_in_process = False + if self.card_detector: + self.card_detector.clear() + next_job = self.api.get_next_job(self.printer_id) print(next_job) @@ -84,9 +99,24 @@ class PrintClient: job_id = next_job["id"] click.secho("Got new print job {}".format(next_job), fg="green") + self.api.set_job_status( + job_id, + "in_progress", + ) + if not self.printer_config.get("generate_number_on_server"): # Now do something to set the number, but irrelevant for this example - pass + click.secho("Generate number on server disabled", fg="yellow") + if self.card_detector: + self.card_detector_in_process = True + chip_number = self.card_detector.read_id() + if chip_number: + next_job = self.api.set_chip_number(job_id, str(chip_number)) + time.sleep(1) + else: + raise KortClientException("No valid chip number read") + else: + raise KortClientException("No card detector configured") if next_job["card"]["chip_number"]: # Download PDF file diff --git a/kort_client/usb_barcode_scanner/__init__.py b/kort_client/usb_barcode_scanner/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/kort_client/usb_barcode_scanner/scanner.py b/kort_client/usb_barcode_scanner/scanner.py new file mode 100755 index 0000000..4c01be7 --- /dev/null +++ b/kort_client/usb_barcode_scanner/scanner.py @@ -0,0 +1,158 @@ +#!/usr/bin/python + +# Inspired by https://www.piddlerintheroot.com/barcode-scanner/ +# https://www.raspberrypi.org/forums/viewtopic.php?f=45&t=55100 +# from 'brechmos' - thank-you! + +CHARMAP_LOWERCASE = { + 4: "a", + 5: "b", + 6: "c", + 7: "d", + 8: "e", + 9: "f", + 10: "g", + 11: "h", + 12: "i", + 13: "j", + 14: "k", + 15: "l", + 16: "m", + 17: "n", + 18: "o", + 19: "p", + 20: "q", + 21: "r", + 22: "s", + 23: "t", + 24: "u", + 25: "v", + 26: "w", + 27: "x", + 28: "y", + 29: "z", + 30: "1", + 31: "2", + 32: "3", + 33: "4", + 34: "5", + 35: "6", + 36: "7", + 37: "8", + 38: "9", + 39: "0", + 44: " ", + 45: "-", + 46: "=", + 47: "[", + 48: "]", + 49: "\\", + 51: ";", + 52: "'", + 53: "~", + 54: ",", + 55: ".", + 56: "/", +} +CHARMAP_UPPERCASE = { + 4: "A", + 5: "B", + 6: "C", + 7: "D", + 8: "E", + 9: "F", + 10: "G", + 11: "H", + 12: "I", + 13: "J", + 14: "K", + 15: "L", + 16: "M", + 17: "N", + 18: "O", + 19: "P", + 20: "Q", + 21: "R", + 22: "S", + 23: "T", + 24: "U", + 25: "V", + 26: "W", + 27: "X", + 28: "Y", + 29: "Z", + 30: "!", + 31: "@", + 32: "#", + 33: "$", + 34: "%", + 35: "^", + 36: "&", + 37: "*", + 38: "(", + 39: ")", + 44: " ", + 45: "_", + 46: "+", + 47: "{", + 48: "}", + 49: "|", + 51: ":", + 52: '"', + 53: "~", + 54: "<", + 55: ">", + 56: "?", +} +CR_CHAR = 40 +SHIFT_CHAR = 2 +ERROR_CHARACTER = "?" + + +class BarcodeReader: + def __init__(self, device_path="/dev/hidraw0"): + self.device_path = device_path + self.f = open(self.device_path, "rb") + + def read_barcode(self): + barcode_string_output = "" + # barcode can have a 'shift' character; this switches the character set + # from the lower to upper case variant for the next character only. + CHARMAP = CHARMAP_LOWERCASE + while True: + # step through returned character codes, ignore zeroes + for char_code in [element for element in self.f.read(8) if element > 0]: + if char_code == CR_CHAR: + # all barcodes end with a carriage return + self.f.close() + return barcode_string_output + if char_code == SHIFT_CHAR: + # use uppercase character set next time + CHARMAP = CHARMAP_UPPERCASE + else: + # if the charcode isn't recognized, use ? + barcode_string_output += CHARMAP.get(char_code, ERROR_CHARACTER) + # reset to lowercase character map + CHARMAP = CHARMAP_LOWERCASE + + +def barcode_reader(dev="/dev/hidraw0"): + barcode_string_output = "" + # barcode can have a 'shift' character; this switches the character set + # from the lower to upper case variant for the next character only. + CHARMAP = CHARMAP_LOWERCASE + with open(dev, "rb") as fp: + while True: + # step through returned character codes, ignore zeroes + for char_code in [element for element in fp.read(8) if element > 0]: + if char_code == CR_CHAR: + # all barcodes end with a carriage return + return barcode_string_output + if char_code == SHIFT_CHAR: + # use uppercase character set next time + CHARMAP = CHARMAP_UPPERCASE + else: + # if the charcode isn't recognized, use ? + barcode_string_output += CHARMAP.get(char_code, ERROR_CHARACTER) + # reset to lowercase character map + CHARMAP = CHARMAP_LOWERCASE diff --git a/match.sh b/match.sh new file mode 100755 index 0000000..db5591a --- /dev/null +++ b/match.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +FILES=/dev/hidraw* +for f in $FILES +do + FILE=${f##*/} + DEVICE="$(cat /sys/class/hidraw/${FILE}/device/uevent | grep HID_NAME | cut -d '=' -f2)" + printf "%s \t %s\n" $FILE "$DEVICE" +done \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 873dab7..ddf75d0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ usersettings = "^1.1.5" requests = "^2.27.1" requests-oauthlib = "^1.3.1" pycups = "^2.0.1" +usb-barcode-scanner-julz = "^0.2" [tool.poetry.dev-dependencies] safety = "^1.8.5" -- GitLab