Finish full HID descriptor, implement trigger report handling

This commit is contained in:
Skylar Ittner 2026-02-16 00:50:58 -07:00
parent b58c9c844f
commit 82566d09bf

View File

@ -35,7 +35,7 @@ from sys import stdin, exit
from _thread import start_new_thread from _thread import start_new_thread
import machine import machine
from machine import Pin, USBDevice from machine import Pin, USBDevice
from utime import sleep from utime import sleep, sleep_ms, sleep_us
import usb.device import usb.device
from micropython import const from micropython import const
from usb.device.hid import HIDInterface from usb.device.hid import HIDInterface
@ -43,6 +43,8 @@ from usb.device.hid import HIDInterface
print("PostalPoint(r) Barcode Scanner") print("PostalPoint(r) Barcode Scanner")
print("Firmware version 0.0.1") print("Firmware version 0.0.1")
onboardLED = Pin("LED", Pin.OUT)
# VID and PID of the USB device. # VID and PID of the USB device.
VID = 0x1209 # USB Vendor ID VID = 0x1209 # USB Vendor ID
@ -79,9 +81,9 @@ class USBHIDInterface(HIDInterface):
0x09, 0xFE, # Usage 0xFE (Decoded Data) 0x09, 0xFE, # Usage 0xFE (Decoded Data)
0x82, 0x02, 0x01, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes) 0x82, 0x02, 0x01, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes)
0x95, 0x02, # Report Count (2 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, 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) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x8C, # Usage Page (Bar Code Scanner Page) 0x05, 0x8C, # Usage Page (Bar Code Scanner Page)
0x25, 0x01, # Logical Maximum (1) 0x25, 0x01, # Logical Maximum (1)
@ -90,17 +92,129 @@ class USBHIDInterface(HIDInterface):
0x09, 0xFF, # Usage (0xFF, Decode Data Continued) 0x09, 0xFF, # Usage (0xFF, Decode Data Continued)
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, # End Collection (Logical) 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) 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), protocol=const(0x00),
interface_str=INTERFACE, interface_str=INTERFACE,
) )
def on_set_report(self, report_data, _report_id, _report_type): def on_set_report(self, report_data, report_id, report_type):
if report_data[0] == 0x50: global inhibitScan, feedbackBuzzer
print("Entering firmware update mode, power cycle to undo. Goodbye for now!") #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() machine.bootloader()
def send_data(self, data=None): 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().config(usbinterface, builtin_driver=True, manufacturer_str=MANU, product_str=PROD, id_vendor=VID, id_product=PID)
usb.device.get().active(True) 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): def send_data(d):
print(d) print(d)
@ -127,7 +282,7 @@ def pad_data(chunk, size):
return chunk[:size] return chunk[:size]
return chunk + b'\x00' * (size - len(chunk)) return chunk + b'\x00' * (size - len(chunk))
def createAndSendReports(barcodeData): def createAndSendBarcodeReports(barcodeData):
global usbinterface global usbinterface
chunks = split(barcodeData, 56) chunks = split(barcodeData, 56)
for i, chunk in enumerate(chunks): for i, chunk in enumerate(chunks):
@ -140,11 +295,13 @@ def createAndSendReports(barcodeData):
try: try:
feedbackBuzzer("POW")
while True: while True:
sleep(1) sleep(1)
#createAndSendReports(b'POSTALPOINTROCKS') if inhibitScan == False:
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') feedbackBuzzer("OK")
sleep(10) createAndSendBarcodeReports(b'test barcode')
sleep(2)
except KeyboardInterrupt: # trap Ctrl-C input except KeyboardInterrupt: # trap Ctrl-C input