diff --git a/src/barcodescanner/main.py b/src/barcodescanner/main.py index 1cf130d..021b836 100644 --- a/src/barcodescanner/main.py +++ b/src/barcodescanner/main.py @@ -35,7 +35,7 @@ from sys import stdin, exit from _thread import start_new_thread import machine from machine import Pin, USBDevice -from utime import sleep +from utime import sleep, sleep_ms, sleep_us import usb.device from micropython import const from usb.device.hid import HIDInterface @@ -43,6 +43,8 @@ from usb.device.hid import HIDInterface print("PostalPoint(r) Barcode Scanner") print("Firmware version 0.0.1") +onboardLED = Pin("LED", Pin.OUT) + # VID and PID of the USB device. VID = 0x1209 # USB Vendor ID @@ -79,9 +81,9 @@ class USBHIDInterface(HIDInterface): 0x09, 0xFE, # Usage 0xFE (Decoded Data) 0x82, 0x02, 0x01, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) 0x95, 0x02, # Report Count (2 bytes) - 0x06, 0x66, 0xFF, # Usage Page (Vendor Defined 0xFF66) + 0x06, 0x69, 0xFF, # Usage Page (Vendor Defined 0xFF69, everyone else does FF66) 0x09, 0x04, # Usage (0x04) - 0x09, 0x00, # Usage (0x00) + 0x09, 0x05, # Usage (0x05) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x05, 0x8C, # Usage Page (Bar Code Scanner Page) 0x25, 0x01, # Logical Maximum (1) @@ -90,17 +92,129 @@ class USBHIDInterface(HIDInterface): 0x09, 0xFF, # Usage (0xFF, Decode Data Continued) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, # End Collection (Logical) + 0x09, 0x14, # Usage (Trigger report) + 0xA1, 0x02, # Collection (Logical) + 0x85, 0x04, # Report ID (4, trigger report from host) + 0x15, 0x00, # Logical Minimum (0) + 0x25, 0x01, # Logical Maximum (1) + 0x75, 0x01, # Report Size (1) + 0x95, 0x08, # Report Count (8) + 0x09, 0x00, # Usage (0x00, filler bit) + 0x09, 0x5F, # Usage (0x5F, prevent read of barcodes) + 0x09, 0x60, # Usage (0x60, initiate barcode read) + 0x09, 0x00, # Usage (0x00, filler) + 0x09, 0x84, # Usage (0x84, sound powerup beep) + 0x09, 0x85, # Usage (0x85, sound error beep) + 0x09, 0x86, # Usage (0x86, sound success beep) + 0x09, 0x87, # Usage (0x87, sound not-on-file beep) + 0x91, 0x82, # Output (Data,Var,Abs,Volatile) + 0xC0, # End Collection (Logical) + 0x06, 0x69, 0xFF, # Usage Page (Vendor 0xFF69) + 0x09, 0x30, # Usage (Vendor-defined: Configuration report for setting up scan modes) + 0xA1, 0x02, # Collection (Logical) + 0x85, 0x05, # Report ID (5) + 0x15, 0x00, # Logical Minimum (0) + 0x26, 0xFF, 0x00, # Logical Maximum (255) + 0x75, 0x08, # Report Size (8 bits) + 0x95, 0x01, # Report Count (1 byte) + 0x05, 0x01, # Usage Page (Generic Desktop) + 0x09, 0x3B, # Usage (Byte Count) + 0x91, 0x02, # Output (Data,Var,Abs) + 0x95, 0x3E, # Report Count (62 bytes) + 0x06, 0x69, 0xFF, # Usage Page (Vendor Defined 0xFF69) + 0x09, 0x05, # Usage (0x05, Config ASCII Data) + 0x92, 0x02, 0x01, # Output (Data,Var,Abs,Buffered Bytes) + 0xC0, # End Collection (Logical) + 0x06, 0x69, 0xFF, # Usage Page (Vendor 0xFF69) + 0x09, 0x31, # Usage (Vendor-defined: Scan mode switch) + 0xA1, 0x02, # Collection (Logical) + 0x85, 0x55, # Report ID (0x55) + 0x15, 0x00, # Logical Minimum (0) + 0x26, 0xFF, 0x00, # Logical Maximum (255) + 0x75, 0x08, # Report Size (8 bits) + 0x95, 0x01, # Report Count (1 byte) + 0x06, 0x69, 0xFF, # Usage Page (Vendor 0xFF69) + 0x09, 0x55, # Usage (0x55) + 0x91, 0x02, # Output (Data,Var,Abs) + 0xC0, # End Collection (Logical) + 0x06, 0x69, 0xFF, # Usage Page (Vendor 0xFF69) + 0x09, 0xF1, # Usage (Vendor-defined: Firmware update trigger) + 0xA1, 0x02, # Collection (Logical) + 0x85, 0xF1, # Report ID (0xF1) + 0x15, 0x00, # Logical Minimum (0) + 0x26, 0xFF, 0x00, # Logical Maximum (255) + 0x75, 0x08, # Report Size (8 bits) + 0x95, 0x01, # Report Count (1 byte) + 0x06, 0x69, 0xFF, # Usage Page (Vendor 0xFF69) + 0x09, 0xF1, # Usage (0xF1) + 0x91, 0x02, # Output (Data,Var,Abs) + 0xC0, # End Collection (Logical) 0xC0, # End Collection (Application) - + # + # NOTES: + # + # Report ID 2 is 63-bytes to the host. + # Byte 0 is the number of characters of barcode data to send in this report. + # Bytes 1, 2, and 3 are the AIM barcode identifier string. Nobody really uses this, some cheap scanners fudge it to QR code. + # Bytes 4 through 60 are the barcode data. Padded with NUL (0x00) bytes if the barcode is shorter than the available space. + # Bytes 61 and 62 are vendor-specific, this isn't part of the HID spec but everyone does it (see report 5 below for what we do) + # because they're copying everyone else. Honeywell uses it for another symbology ID. + # Byte 63 is 0x01 (b00000001) if the barcode data couldn't fit in a single report; + # it means more reports are coming right away with the rest of the data. + # If byte 63 is 0x00 it means there's no more barcode to send and the host can process it now. + # + # Report ID 4 is a single byte from the host: 76543210 + # If pos. 7, 6, 5, or 4 is 1, the host wants us to make a particular beep sound/light indication. + # If pos. 1 is 1, host wants us to not send any barcode data or do any scans. + # If pos. 2 is 1, host wants us to start reading for barcodes without the user pressing a button. + # + # Report ID 5 is 63 bytes from the host to this device. + # This is a custom report for this specific device. + # The first byte is the data length. + # The rest of it is a "list" of ASCII strings, delimited with a NUL byte. + # The strings are displayed on our display and can be switched between by the user. + # The index of the string (as sent by the host) is sent as the second vendor byte in report 2. + # This allows the user to select a scan context/UI mode in the program, and the program + # can switch to it automatically when a barcode is scanned in that mode. + # + # Report ID 0x55 is a single byte from the host. + # It tells us to change our scan context to a string index from the most recent Report 5 list, + # overriding the user's last selection. This is for when the user changed modes in software, + # to keep the state in sync between the scanner and PC. + # + # Report ID 0xF1 triggers the Pi Pico to switch to firmware update mode when the byte sent is also 0xF1. + # The Pico will re-enumerate and appear as a USB flash drive so the firmware file can be dropped in. ]), - set_report_buf=bytearray(1), + set_report_buf=bytearray(64), protocol=const(0x00), interface_str=INTERFACE, ) - def on_set_report(self, report_data, _report_id, _report_type): - if report_data[0] == 0x50: - print("Entering firmware update mode, power cycle to undo. Goodbye for now!") + def on_set_report(self, report_data, report_id, report_type): + global inhibitScan, feedbackBuzzer + #createAndSendBarcodeReports(bytes([report_id, report_type, report_data[0], report_data[1], reverse_byte(report_data[1])])) + + if report_id == 0x04: + report_byte = report_data[1] + # HID POS trigger report + # Disable/enable scanning based on bit flag + inhibitScan = (report_byte >> 1 & 1 == 1) + + if (report_byte >> 7 & 1 == 1): + # not-on-file beep, 10000000 + feedbackBuzzer("NAF") + elif (report_byte >> 6 & 1 == 1): + # success beep, 01000000 + feedbackBuzzer("OK") + elif (report_byte >> 5 & 1 == 1): + # error beep, 00100000 + feedbackBuzzer("ERR") + elif (report_byte >> 4 & 1 == 1): + # powerup beep, 00010000 + feedbackBuzzer("POW") + + elif report_id == 0xF1 and report_data[1] == 0xF1: + # Enter firmware update mode machine.bootloader() def send_data(self, data=None): @@ -116,6 +230,47 @@ usb.device.get().active(False) usb.device.get().config(usbinterface, builtin_driver=True, manufacturer_str=MANU, product_str=PROD, id_vendor=VID, id_product=PID) usb.device.get().active(True) + +# Host can set this true and we won't send any scans. +inhibitScan = False + +# Pulse the LED on and off with brightness fade +def pulseLED(speed): + r = 600 + if speed == "fast": + r = 300 + elif speed == "normal": + r = 600 + elif speed == "slow": + r = 1000 + + for i in range(0,r): + onboardLED.on() + sleep_us(i) + onboardLED.off() + sleep_us(r-i) + for i in range(0,r): + onboardLED.on() + sleep_us(r-i) + onboardLED.off() + sleep_us(i) + +def feedbackBuzzer(feedback): + global onboardLED + if feedback == "OK": + pulseLED("fast") + elif feedback == "ERR": + pulseLED("fast") + pulseLED("fast") + pulseLED("fast") + elif feedback == "POW": + pulseLED("slow") + elif feedback == "NAF": + pulseLED("normal") + pulseLED("fast") + pulseLED("fast") + pulseLED("normal") + def send_data(d): print(d) @@ -127,7 +282,7 @@ def pad_data(chunk, size): return chunk[:size] return chunk + b'\x00' * (size - len(chunk)) -def createAndSendReports(barcodeData): +def createAndSendBarcodeReports(barcodeData): global usbinterface chunks = split(barcodeData, 56) for i, chunk in enumerate(chunks): @@ -140,11 +295,13 @@ def createAndSendReports(barcodeData): try: + feedbackBuzzer("POW") while True: sleep(1) - #createAndSendReports(b'POSTALPOINTROCKS') - createAndSendReports(b'very long barcode please to help very long barcode please to help very long barcode please to help very long barcode please to help very long barcode please to help') - sleep(10) + if inhibitScan == False: + feedbackBuzzer("OK") + createAndSendBarcodeReports(b'test barcode') + sleep(2) except KeyboardInterrupt: # trap Ctrl-C input