Full barcode scanner firmware: display driver, USB HID and CDC serial, vendor USB commands, etc
This commit is contained in:
parent
82566d09bf
commit
ff7b33d49a
@ -1,8 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
mkdir -p workspace/src/barcodescanner
|
||||
cp ../src/barcodescanner/main.py workspace/src/barcodescanner/main.py
|
||||
cp ../src/barcodescanner/manifest.py workspace/src/barcodescanner/manifest.py
|
||||
cp ../src/barcodescanner/*.py workspace/src/barcodescanner/
|
||||
cd workspace
|
||||
git clone https://github.com/micropython/micropython.git --branch=master --depth=1
|
||||
cd micropython
|
||||
|
||||
81
src/barcodescanner/config.py
Normal file
81
src/barcodescanner/config.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Copyright 2025 PostalPortal LLC and Netsyms Technologies LLC
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that
|
||||
# the following conditions are met:
|
||||
# 1. Redistributions of source code must retain
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder
|
||||
# nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
# THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
#
|
||||
# Hardware configuration
|
||||
#
|
||||
UART_ID = 0 # TX/RX: UART 0: 0/1, 12/13, or 16/17; UART 1: 4/5 or 8/9
|
||||
UART_TX_PIN=0
|
||||
UART_RX_PIN=1
|
||||
TRIGGER_BUTTON_PIN = 12 # Pin to read for scan trigger button, connect trigger button between this and ground
|
||||
TRIGGER_PIN = 13 # Pin that connects to the scan module's trigger line, pulls the line low while the user is pressing the trigger button
|
||||
UP_BUTTON_PIN = 14 # Pin to read for navigation up button
|
||||
DOWN_BUTTON_PIN = 15 # Pin to read for navigation down button
|
||||
LED_PIN = "LED" # 3 in prod
|
||||
FIRMWARE_VERSION = "0.0.1"
|
||||
|
||||
#
|
||||
# Scanner configuration
|
||||
#
|
||||
SCAN_GAP_MS = 50 # Amount of time to wait for more characters from the scan engine before sending a barcode
|
||||
MAX_BARCODE_LENGTH = 8192 # Barcodes longer than this from the scan engine are assumed to be a glitch
|
||||
TESTMODE = True # Sends a simulated barcode scan every 5 seconds
|
||||
|
||||
#
|
||||
# USB configuration
|
||||
#
|
||||
VID = 0x1209 # USB Vendor ID
|
||||
PID = 0xA003 # USB Product ID
|
||||
MANU = "PostalPortal LLC" # USB manufacturer string
|
||||
PROD = "PostalPoint Barcode Scanner" # USB product string
|
||||
INTERFACE = "PostalPoint" # Interface string
|
||||
USBHID_ENABLED = True # Disable USB, use serial output only (good for debugging)
|
||||
|
||||
|
||||
#
|
||||
# Display configuration
|
||||
#
|
||||
ENABLE_DISPLAY = True # Set to False to ignore display commands
|
||||
DISPLAY_WIDTH = 128
|
||||
DISPLAY_HEIGHT = 64
|
||||
CHAR_WIDTH = 8
|
||||
CHAR_HEIGHT = 8
|
||||
# Available pin combinations for I2C:
|
||||
# I2C 0 – SDA: GP0/GP4/GP8/GP12/GP16/GP20
|
||||
# I2C 0 – SCL: GP1/GP5/GP9/GP13/GP17/GP21
|
||||
# I2C 1 – SDA: GP2/GP6/GP10/GP14/GP18/GP26
|
||||
# I2C 1 – SCL: GP3/GP7/GP11/GP15/GP19/GP27
|
||||
# Just don't conflict with the pin assignments for the sensor and buttons!
|
||||
DISPLAY_SDA_PIN = 4
|
||||
DISPLAY_SCL_PIN = 5
|
||||
DISPLAY_I2C_CONTROLLER = 0
|
||||
81
src/barcodescanner/feedback.py
Normal file
81
src/barcodescanner/feedback.py
Normal file
@ -0,0 +1,81 @@
|
||||
# Copyright 2026 PostalPortal LLC
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that
|
||||
# the following conditions are met:
|
||||
# 1. Redistributions of source code must retain
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder
|
||||
# nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
# THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from machine import Pin
|
||||
from time import sleep_us
|
||||
import config
|
||||
from oledscreen import iconDisplay, centerText
|
||||
|
||||
onboardLED = Pin(config.LED_PIN, Pin.OUT)
|
||||
|
||||
# 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 firmwareUpdateMessage():
|
||||
centerText("Firmware Update", False, True)
|
||||
|
||||
def feedbackBuzzer(feedback):
|
||||
if feedback == "OK":
|
||||
iconDisplay("OK")
|
||||
pulseLED("fast")
|
||||
elif feedback == "ERR":
|
||||
iconDisplay("ERR")
|
||||
pulseLED("fast")
|
||||
pulseLED("fast")
|
||||
pulseLED("fast")
|
||||
elif feedback == "POW":
|
||||
iconDisplay("POW")
|
||||
#pulseLED("normal")
|
||||
elif feedback == "NAF":
|
||||
iconDisplay("NAF")
|
||||
pulseLED("normal")
|
||||
pulseLED("fast")
|
||||
pulseLED("fast")
|
||||
pulseLED("normal")
|
||||
@ -32,278 +32,158 @@
|
||||
|
||||
|
||||
from sys import stdin, exit
|
||||
from _thread import start_new_thread
|
||||
import machine
|
||||
from machine import Pin, USBDevice
|
||||
from machine import Pin, USBDevice, UART
|
||||
from utime import sleep, sleep_ms, sleep_us
|
||||
import usb.device
|
||||
from micropython import const
|
||||
from usb.device.hid import HIDInterface
|
||||
import time
|
||||
|
||||
from config import *
|
||||
from oledscreen import bootDisplay, clearDisplay, brightDisplay, mainDisplay, centerText
|
||||
from scannerusb import initUSBHID, createAndSendBarcodeReports
|
||||
from feedback import feedbackBuzzer
|
||||
from scanmode import isScanInhibited, setModeID, getCurrentModeID, processNewListUSBReport
|
||||
from watchdog import startwatchdog, feedwatchdog
|
||||
|
||||
print("PostalPoint(r) Barcode Scanner")
|
||||
print("Firmware version 0.0.1")
|
||||
print("Firmware version " + FIRMWARE_VERSION)
|
||||
|
||||
onboardLED = Pin("LED", Pin.OUT)
|
||||
startwatchdog()
|
||||
|
||||
uart = UART(UART_ID, baudrate=9600, bits=8, parity=None, stop=1, tx=UART_TX_PIN, rx=UART_RX_PIN, txbuf=100, rxbuf=MAX_BARCODE_LENGTH)
|
||||
|
||||
# VID and PID of the USB device.
|
||||
VID = 0x1209 # USB Vendor ID
|
||||
PID = 0xA003 # USB Product ID
|
||||
MANU = "PostalPortal LLC" # USB manufacturer string
|
||||
PROD = "PostalPoint Barcode Scanner" # USB product string
|
||||
INTERFACE = "PostalPoint" # Interface string
|
||||
triggerButton = Pin(TRIGGER_BUTTON_PIN, Pin.IN, Pin.PULL_UP)
|
||||
triggerPin = Pin(TRIGGER_PIN, Pin.IN)
|
||||
menuUpButton = Pin(UP_BUTTON_PIN, Pin.IN, Pin.PULL_UP)
|
||||
menuDownButton = Pin(DOWN_BUTTON_PIN, Pin.IN, Pin.PULL_UP)
|
||||
|
||||
class USBHIDInterface(HIDInterface):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
bytes([
|
||||
0x05, 0x8C, # Usage Page (barcode scanner)
|
||||
0x09, 0x02, # Usage (Barcode Scanner)
|
||||
0xA1, 0x01, # Collection (Application)
|
||||
0x09, 0x12, # Usage (Scanned Data Report)
|
||||
0xA1, 0x02, # Collection (Logical)
|
||||
0x85, 0x02, # Report ID 2
|
||||
0x15, 0x00, # Logical Minimum 0
|
||||
0x26, 0xFF, 0x00, # Logical Maximum 255
|
||||
0x75, 0x08, # Report Size (8 bits)
|
||||
0x95, 0x01, # Report Count (1 byte long)
|
||||
0x05, 0x01, # Usage Page 0x01 (Generic Desktop Ctrls)
|
||||
0x09, 0x3B, # Usage (Byte count)
|
||||
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x95, 0x03, # Report Count (3 bytes long)
|
||||
0x05, 0x8C, # Usage Page (barcode scanner)
|
||||
0x09, 0xFB, # Usage (0xFB, Symbology Identifier 1)
|
||||
0x09, 0xFC, # Usage (0xFC, Symbology Identifier 2)
|
||||
0x09, 0xFD, # Usage (0xFD, Symbology Identifier 3)
|
||||
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x95, 0x38, # Report Count (56 bytes long)
|
||||
0x05, 0x8C, # Usage Page (barcode scanner)
|
||||
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, 0x69, 0xFF, # Usage Page (Vendor Defined 0xFF69, everyone else does FF66)
|
||||
0x09, 0x04, # Usage (0x04)
|
||||
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)
|
||||
0x75, 0x01, # Report Size (1 bit)
|
||||
0x95, 0x08, # Report Count (8 * size = 1 byte total)
|
||||
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(64),
|
||||
protocol=const(0x00),
|
||||
interface_str=INTERFACE,
|
||||
)
|
||||
scanButtonState = 1
|
||||
|
||||
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 triggerButtonHandler(pin):
|
||||
global scanButtonState
|
||||
scanButtonState = pin.value()
|
||||
|
||||
def send_data(self, data=None):
|
||||
while self.busy():
|
||||
machine.idle()
|
||||
if data is None:
|
||||
triggerButton.irq(handler=triggerButtonHandler, trigger=(Pin.IRQ_FALLING | Pin.IRQ_RISING))
|
||||
|
||||
menuUpButtonPressed = False
|
||||
menuDownButtonPressed = False
|
||||
menuUpButtonLastPressTime = time.ticks_ms()
|
||||
menuDownButtonLastPressTime = time.ticks_ms()
|
||||
|
||||
def menuUpButtonHandler(pin):
|
||||
global menuUpButtonPressed, menuUpButtonLastPressTime
|
||||
if pin.value() == 0:
|
||||
# Don't allow button "wobble" to cause multiple events in quick succession,
|
||||
# ignore button presses when they're less than 100ms apart
|
||||
if time.ticks_diff(time.ticks_ms(), menuUpButtonLastPressTime) < 100:
|
||||
return
|
||||
else:
|
||||
self.send_report(data)
|
||||
|
||||
usbinterface = USBHIDInterface()
|
||||
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)
|
||||
|
||||
def split(data, n):
|
||||
return [data[i:i+n] for i in range(0, len(data), n)]
|
||||
|
||||
def pad_data(chunk, size):
|
||||
if len(chunk) >= size:
|
||||
return chunk[:size]
|
||||
return chunk + b'\x00' * (size - len(chunk))
|
||||
|
||||
def createAndSendBarcodeReports(barcodeData):
|
||||
global usbinterface
|
||||
chunks = split(barcodeData, 56)
|
||||
for i, chunk in enumerate(chunks):
|
||||
lastByte = b'\x01'
|
||||
if len(chunks) - 1 == i:
|
||||
lastByte = b'\x00'
|
||||
report = b'\x02' + bytes([len(chunk)]) + b'\x00\x00\x00' + pad_data(chunk, 56) + b'\x00\x00' + lastByte
|
||||
usbinterface.send_data(report)
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
feedbackBuzzer("POW")
|
||||
while True:
|
||||
sleep(1)
|
||||
if inhibitScan == False:
|
||||
feedbackBuzzer("OK")
|
||||
createAndSendBarcodeReports(b'test barcode')
|
||||
sleep(2)
|
||||
menuUpButtonPressed = True
|
||||
menuUpButtonLastPressTime = time.ticks_ms()
|
||||
|
||||
def menuDownButtonHandler(pin):
|
||||
global menuDownButtonPressed, menuDownButtonLastPressTime
|
||||
if pin.value() == 0:
|
||||
if time.ticks_diff(time.ticks_ms(), menuDownButtonLastPressTime) < 100:
|
||||
return
|
||||
menuDownButtonPressed = True
|
||||
menuDownButtonLastPressTime = time.ticks_ms()
|
||||
|
||||
except KeyboardInterrupt: # trap Ctrl-C input
|
||||
terminateThread = True # signal second 'background' thread to terminate
|
||||
exit()
|
||||
menuUpButton.irq(handler=menuUpButtonHandler, trigger=Pin.IRQ_FALLING)
|
||||
menuDownButton.irq(handler=menuDownButtonHandler, trigger=Pin.IRQ_FALLING)
|
||||
|
||||
#
|
||||
#
|
||||
#
|
||||
# TODO: Send scan engine configuration commands over UART
|
||||
#
|
||||
#
|
||||
#
|
||||
|
||||
feedwatchdog()
|
||||
feedbackBuzzer("POW")
|
||||
feedwatchdog()
|
||||
mainDisplay(scanButtonState == 0)
|
||||
|
||||
if USBHID_ENABLED:
|
||||
initUSBHID()
|
||||
|
||||
feedwatchdog()
|
||||
|
||||
lastTestSend = time.ticks_ms()
|
||||
|
||||
uartBuffer = bytearray()
|
||||
lastBufferActivity = None
|
||||
bufferOverflow = False
|
||||
|
||||
#
|
||||
# Main loop
|
||||
#
|
||||
while True:
|
||||
feedwatchdog()
|
||||
try:
|
||||
# Refresh OLED
|
||||
mainDisplay(scanButtonState == 0)
|
||||
feedwatchdog()
|
||||
# Handle menu buttons
|
||||
if menuUpButtonPressed:
|
||||
setModeID(getCurrentModeID() - 1)
|
||||
menuUpButtonPressed = False
|
||||
if menuDownButtonPressed:
|
||||
setModeID(getCurrentModeID() + 1)
|
||||
menuDownButtonPressed = False
|
||||
|
||||
feedwatchdog()
|
||||
|
||||
# Set trigger line for scan engine
|
||||
if isScanInhibited():
|
||||
triggerPin.init(Pin.IN)
|
||||
else:
|
||||
if scanButtonState == 0:
|
||||
triggerPin.init(Pin.OUT)
|
||||
triggerPin.value(0)
|
||||
else:
|
||||
triggerPin.init(Pin.IN)
|
||||
|
||||
feedwatchdog()
|
||||
|
||||
# Read scan data from engine
|
||||
while uart.any():
|
||||
feedwatchdog()
|
||||
data = uart.read(uart.any())
|
||||
if data:
|
||||
if not bufferOverflow:
|
||||
uartBuffer.extend(data)
|
||||
lastBufferActivity = time.ticks_ms()
|
||||
if len(uartBuffer) > MAX_BARCODE_LENGTH:
|
||||
uartBuffer.clear()
|
||||
bufferOverflow = True
|
||||
lastBufferActivity = None
|
||||
|
||||
# Send scan data to host
|
||||
if lastBufferActivity is not None and time.ticks_diff(time.ticks_ms(), lastBufferActivity) > SCAN_GAP_MS:
|
||||
if not isScanInhibited() and uartBuffer and not bufferOverflow:
|
||||
createAndSendBarcodeReports(bytes(uartBuffer))
|
||||
feedbackBuzzer("OK")
|
||||
uartBuffer.clear()
|
||||
lastBufferActivity = None
|
||||
bufferOverflow = False
|
||||
|
||||
feedwatchdog()
|
||||
|
||||
if TESTMODE and time.ticks_diff(time.ticks_ms(), lastTestSend) > 5000:
|
||||
createAndSendBarcodeReports(b'test barcode 12345')
|
||||
feedbackBuzzer("OK")
|
||||
lastTestSend = time.ticks_ms()
|
||||
|
||||
feedwatchdog()
|
||||
|
||||
# Handle config from host if USB code queued it for processing
|
||||
processNewListUSBReport()
|
||||
|
||||
feedwatchdog()
|
||||
# Take a tiny bit of time off
|
||||
machine.idle()
|
||||
except Exception as e:
|
||||
# Dump exceptions over serial for debugging
|
||||
print(e)
|
||||
machine.reset()
|
||||
|
||||
@ -1,4 +1,11 @@
|
||||
# Build manifest for PostalPoint Kiosk Controller (USB HID)
|
||||
# Build manifest for PostalPoint Barcode Scanner
|
||||
include("$(MPY_DIR)/ports/rp2/boards/manifest.py")
|
||||
require("usb-device-hid")
|
||||
require("ssd1306")
|
||||
module("config.py")
|
||||
module("watchdog.py")
|
||||
module("scanmode.py")
|
||||
module("feedback.py")
|
||||
module("oledscreen.py")
|
||||
module("scannerusb.py")
|
||||
module("main.py")
|
||||
|
||||
194
src/barcodescanner/oledscreen.py
Normal file
194
src/barcodescanner/oledscreen.py
Normal file
@ -0,0 +1,194 @@
|
||||
# Copyright 2026 PostalPortal LLC
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that
|
||||
# the following conditions are met:
|
||||
# 1. Redistributions of source code must retain
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder
|
||||
# nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
# THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# This code displays device info on an attached screen.
|
||||
# Requires the ssd1306 module/package installed.
|
||||
# Connect to GPIO 0 and 1 for i2c
|
||||
#
|
||||
|
||||
from machine import Pin, I2C
|
||||
from ssd1306 import SSD1306_I2C
|
||||
from time import sleep_ms
|
||||
import framebuf
|
||||
import math
|
||||
from config import *
|
||||
from scanmode import getCurrentModeStr, getModes, modeListIsSet, getPrevModeStr, getNextModeStr
|
||||
|
||||
i2c = None
|
||||
oled = None
|
||||
if ENABLE_DISPLAY:
|
||||
i2c = I2C(DISPLAY_I2C_CONTROLLER, sda=Pin(DISPLAY_SDA_PIN), scl=Pin(DISPLAY_SCL_PIN), freq=400000)
|
||||
oled = SSD1306_I2C(DISPLAY_WIDTH, DISPLAY_HEIGHT, i2c)
|
||||
|
||||
|
||||
def bootDisplay():
|
||||
if ENABLE_DISPLAY:
|
||||
try:
|
||||
# Bootup dead pixel self-test
|
||||
oled.fill(0)
|
||||
oled.show()
|
||||
oled.fill(1)
|
||||
oled.show()
|
||||
sleep_ms(500)
|
||||
oled.fill(0)
|
||||
oled.show()
|
||||
# 128x32 PostalPoint logo bitmap
|
||||
logo = bytearray([
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0xc0, 0x40, 0x40, 0xc0, 0x80,
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xfc, 0x0e, 0x0a, 0x1a, 0x13, 0xf1, 0x19, 0x08, 0x08, 0xfc, 0xfc, 0x0c, 0x08,
|
||||
0x19, 0xf1, 0xf1, 0x1b, 0x0a, 0x0e, 0xfc, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xf8, 0xf8,
|
||||
0x1c, 0x1c, 0x1c, 0xf8, 0xf8, 0xf0, 0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xc0, 0xc0, 0x00,
|
||||
0x00, 0x00, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xc0, 0x00, 0x00, 0x80, 0xf0, 0xf8, 0xf8, 0xe0, 0xe0,
|
||||
0x00, 0x00, 0x80, 0xc0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0x40, 0x00, 0x00, 0xf0, 0xfc, 0xfe, 0x0e,
|
||||
0x00, 0x00, 0xf0, 0xf8, 0xf8, 0x1c, 0x1c, 0x18, 0xf8, 0xf8, 0xf0, 0x00, 0x00, 0x80, 0xc0, 0xe0,
|
||||
0xe0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0xc8, 0xfc, 0xfe, 0x0c, 0x00, 0xc0, 0xe0, 0xe0, 0xe0,
|
||||
0xe0, 0xe0, 0xc0, 0x00, 0x00, 0x00, 0xf0, 0xf8, 0xf8, 0xe0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7f, 0xc0, 0x80, 0x80, 0x80, 0x07, 0x0e, 0x0a, 0x1a, 0xf1, 0xf1, 0x1b, 0x0a,
|
||||
0x0e, 0x0f, 0x07, 0x80, 0x80, 0xc0, 0x7f, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x7f, 0x3f, 0x07,
|
||||
0x07, 0x07, 0x03, 0x03, 0x01, 0x00, 0x00, 0x1f, 0x3f, 0x7f, 0x70, 0x70, 0x38, 0x1f, 0x0f, 0x03,
|
||||
0x20, 0x71, 0x73, 0x77, 0x76, 0x3e, 0x3c, 0x08, 0x00, 0x18, 0x3f, 0x7f, 0x73, 0x70, 0x30, 0x00,
|
||||
0x0c, 0x3f, 0x7f, 0x73, 0x70, 0x30, 0x3f, 0x7f, 0x3f, 0x00, 0x00, 0x3f, 0x7f, 0x7f, 0x20, 0x00,
|
||||
0x70, 0x7f, 0x7f, 0x0f, 0x07, 0x07, 0x07, 0x03, 0x03, 0x01, 0x00, 0x0c, 0x3f, 0x3f, 0x73, 0x70,
|
||||
0x70, 0x3f, 0x1f, 0x0f, 0x00, 0x70, 0x7f, 0x3f, 0x0f, 0x00, 0x20, 0x7e, 0x7f, 0x1f, 0x01, 0x20,
|
||||
0x7f, 0x7f, 0x0f, 0x00, 0x00, 0x3f, 0x7f, 0x7f, 0x70, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x02, 0x06, 0x07, 0x07, 0x06, 0x02,
|
||||
0x02, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||
fbuf = framebuf.FrameBuffer(logo, 128, 32, framebuf.MONO_VLSB)
|
||||
oled.blit(fbuf, 0, 0, 0)
|
||||
# Show info
|
||||
oled.text("Barcode Scanner", 0, 36, 1)
|
||||
firmVerStr = "FW Ver " + FIRMWARE_VERSION
|
||||
oled.text(f"{firmVerStr:^16}", 0, 56, 1)
|
||||
oled.show()
|
||||
sleep_ms(1500)
|
||||
mainDisplay()
|
||||
except:
|
||||
pass
|
||||
|
||||
# Center a line of text on the screen (vertical and horizontal)
|
||||
# If arrows, display up and down arrows above / below the text.
|
||||
# If showNow, clears the display before drawing, and displays the text as well. Otherwise this function only renders the display.
|
||||
# If arrows, the aboveLine and belowLine text will be rendered, centered horizontally, above/below the arrows.
|
||||
def centerText(text, arrows=False, showNow = False, aboveLine = "", belowLine = ""):
|
||||
if ENABLE_DISPLAY:
|
||||
if showNow:
|
||||
oled.fill(0)
|
||||
textWidth = len(text) * CHAR_WIDTH
|
||||
vCenter = DISPLAY_HEIGHT // 2
|
||||
textY = ((DISPLAY_HEIGHT - CHAR_HEIGHT) // 2)
|
||||
oled.text(text, (DISPLAY_WIDTH - textWidth) // 2, textY, 1)
|
||||
if arrows:
|
||||
arrowLineXDistance = CHAR_WIDTH // 2
|
||||
arrowLineYDistance = CHAR_HEIGHT // 2
|
||||
topArrowPoint = vCenter - CHAR_HEIGHT - arrowLineYDistance
|
||||
bottomArrowPoint = vCenter + CHAR_HEIGHT + arrowLineYDistance
|
||||
xArrowCenterPoint = DISPLAY_WIDTH // 2
|
||||
topLineY = vCenter - CHAR_HEIGHT - (arrowLineYDistance // 2)
|
||||
bottomLineY = vCenter + CHAR_HEIGHT + (arrowLineYDistance // 2)
|
||||
oled.line(xArrowCenterPoint - arrowLineXDistance, topArrowPoint + arrowLineYDistance, xArrowCenterPoint, topArrowPoint, 1) # Left-top arrow part
|
||||
oled.line(xArrowCenterPoint + arrowLineXDistance, topArrowPoint + arrowLineYDistance, xArrowCenterPoint, topArrowPoint, 1) # Right-top arrow part
|
||||
oled.line(xArrowCenterPoint - arrowLineXDistance, bottomArrowPoint - arrowLineYDistance, xArrowCenterPoint, bottomArrowPoint, 1) # Left-bottom arrow part
|
||||
oled.line(xArrowCenterPoint + arrowLineXDistance, bottomArrowPoint - arrowLineYDistance, xArrowCenterPoint, bottomArrowPoint, 1) # Right-bottom arrow part
|
||||
# Horizontal lines above/below center text, with a gap in the middle for the arrows
|
||||
oled.line(0, topLineY, xArrowCenterPoint - (arrowLineXDistance * 2), topLineY, 1)
|
||||
oled.line(xArrowCenterPoint + (arrowLineXDistance * 2), topLineY, DISPLAY_WIDTH, topLineY, 1)
|
||||
oled.line(0, bottomLineY, xArrowCenterPoint - (arrowLineXDistance * 2), bottomLineY, 1)
|
||||
oled.line(xArrowCenterPoint + (arrowLineXDistance * 2), bottomLineY, DISPLAY_WIDTH, bottomLineY, 1)
|
||||
# Top and bottom text lines, outside the arrows and lines
|
||||
oled.text(aboveLine, (DISPLAY_WIDTH - 1 - (len(aboveLine) * CHAR_WIDTH)) // 2, 6, 1)
|
||||
oled.text(belowLine, (DISPLAY_WIDTH - 1 - (len(belowLine) * CHAR_WIDTH)) // 2, DISPLAY_HEIGHT - CHAR_HEIGHT - 6, 1)
|
||||
if showNow:
|
||||
oled.show()
|
||||
|
||||
|
||||
def mainDisplay(scanInProgress = False):
|
||||
if ENABLE_DISPLAY:
|
||||
try:
|
||||
oled.fill(0)
|
||||
if modeListIsSet():
|
||||
if modeListIsSet() and len(getModes()) > 1:
|
||||
centerText(getCurrentModeStr(), True, False, getPrevModeStr(), getNextModeStr())
|
||||
else:
|
||||
centerText(getCurrentModeStr(), False, False)
|
||||
if scanInProgress:
|
||||
# Outline around edge of display
|
||||
oled.line(0, 0, DISPLAY_WIDTH - 1, 0, 1)
|
||||
oled.line(0, 0, 0, DISPLAY_HEIGHT - 1, 1)
|
||||
oled.line(DISPLAY_WIDTH - 1, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, 1)
|
||||
oled.line(0, DISPLAY_HEIGHT - 1, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1, 1)
|
||||
oled.show()
|
||||
except:
|
||||
pass
|
||||
|
||||
def iconDisplay(icon):
|
||||
# TODO: add code to render icons
|
||||
if icon == "ERR":
|
||||
pass
|
||||
elif icon == "OK":
|
||||
pass
|
||||
elif icon == "NAF":
|
||||
pass
|
||||
elif icon == "POW":
|
||||
bootDisplay()
|
||||
|
||||
def clearDisplay(showMain = False):
|
||||
if ENABLE_DISPLAY:
|
||||
try:
|
||||
oled.fill(0)
|
||||
oled.show()
|
||||
if showMain:
|
||||
mainDisplay()
|
||||
except:
|
||||
pass
|
||||
|
||||
def brightDisplay():
|
||||
if ENABLE_DISPLAY:
|
||||
try:
|
||||
oled.fill(1)
|
||||
oled.show()
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
146
src/barcodescanner/scanmode.py
Normal file
146
src/barcodescanner/scanmode.py
Normal file
@ -0,0 +1,146 @@
|
||||
# Copyright 2026 PostalPortal LLC
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that
|
||||
# the following conditions are met:
|
||||
# 1. Redistributions of source code must retain
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder
|
||||
# nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
# THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
currentScanModeIndex = 1
|
||||
gotScanModeList = True
|
||||
scanModeList = [""]
|
||||
inhibitScan = False
|
||||
|
||||
newUsbListReport = None
|
||||
|
||||
def queueNewListReportForProcessing(report_data):
|
||||
global newUsbListReport
|
||||
newUsbListReport = bytes(report_data)
|
||||
|
||||
# Parse report 0x55 if there's one queued for processing
|
||||
def processNewListUSBReport():
|
||||
global newUsbListReport
|
||||
if newUsbListReport is None:
|
||||
return
|
||||
report = newUsbListReport
|
||||
newUsbListReport = None
|
||||
if len(report) < 2:
|
||||
# Not valid at all
|
||||
return
|
||||
|
||||
listlen = report[1]
|
||||
if listlen > len(report) - 2:
|
||||
# Length is claiming to be longer than the actual data is
|
||||
return
|
||||
|
||||
payload = report[2 : 2 + listlen]
|
||||
newScanModeList = []
|
||||
for part in payload.split(b'\x00'):
|
||||
if part:
|
||||
try:
|
||||
newScanModeList.append(part.decode('ascii'))
|
||||
except:
|
||||
# Handle error on non-ASCII byte (value > 127)
|
||||
continue
|
||||
if len(newScanModeList) > 0:
|
||||
setScanModeIndex(1)
|
||||
setModeList(newScanModeList)
|
||||
setModeListIsSet(True)
|
||||
else:
|
||||
setScanModeIndex(0)
|
||||
setModeList([""])
|
||||
setModeListIsSet(False)
|
||||
|
||||
|
||||
def setInhibitScan(b):
|
||||
global inhibitScan
|
||||
inhibitScan = b
|
||||
|
||||
def isScanInhibited():
|
||||
global inhibitScan
|
||||
return inhibitScan
|
||||
|
||||
def getCurrentModeStr():
|
||||
global gotScanModeList, scanModeList, currentScanModeIndex
|
||||
if gotScanModeList:
|
||||
return scanModeList[currentScanModeIndex - 1]
|
||||
else:
|
||||
return scanModeList[0]
|
||||
|
||||
def getPrevModeStr():
|
||||
global gotScanModeList, scanModeList, currentScanModeIndex
|
||||
if gotScanModeList and currentScanModeIndex > 1:
|
||||
return scanModeList[currentScanModeIndex - 2]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def getNextModeStr():
|
||||
global gotScanModeList, scanModeList, currentScanModeIndex
|
||||
if gotScanModeList and len(scanModeList) > currentScanModeIndex:
|
||||
return scanModeList[currentScanModeIndex]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def getCurrentModeID():
|
||||
global gotScanModeList, currentScanModeIndex
|
||||
if gotScanModeList:
|
||||
return currentScanModeIndex
|
||||
else:
|
||||
return 0
|
||||
|
||||
def setModeID(indx):
|
||||
global gotScanModeList, scanModeList, currentScanModeIndex
|
||||
if gotScanModeList:
|
||||
if indx > len(scanModeList):
|
||||
currentScanModeIndex = 1
|
||||
elif indx < 1:
|
||||
currentScanModeIndex = len(scanModeList)
|
||||
else:
|
||||
currentScanModeIndex = indx
|
||||
else:
|
||||
currentScanModeIndex = 0
|
||||
|
||||
def setScanModeIndex(idx):
|
||||
global currentScanModeIndex
|
||||
currentScanModeIndex = idx
|
||||
|
||||
def setModeListIsSet(b):
|
||||
global gotScanModeList
|
||||
gotScanModeList = b
|
||||
|
||||
def getModes():
|
||||
global scanModeList
|
||||
return scanModeList
|
||||
|
||||
def setModeList(modelist):
|
||||
global scanModeList
|
||||
scanModeList = modelist
|
||||
|
||||
def modeListIsSet():
|
||||
global gotScanModeList
|
||||
return gotScanModeList
|
||||
267
src/barcodescanner/scannerusb.py
Normal file
267
src/barcodescanner/scannerusb.py
Normal file
@ -0,0 +1,267 @@
|
||||
# Copyright 2026 PostalPortal LLC
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that
|
||||
# the following conditions are met:
|
||||
# 1. Redistributions of source code must retain
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder
|
||||
# nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
# THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import machine
|
||||
import usb.device
|
||||
from usb.device.hid import HIDInterface
|
||||
from config import *
|
||||
from scanmode import setInhibitScan, setScanModeIndex, setModeList, setModeListIsSet, setModeID, queueNewListReportForProcessing, getCurrentModeID
|
||||
from feedback import feedbackBuzzer, firmwareUpdateMessage
|
||||
from watchdog import feedwatchdog
|
||||
import time
|
||||
from sys import stdout
|
||||
|
||||
usbinterface = None
|
||||
|
||||
class USBHIDInterface(HIDInterface):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
bytes([
|
||||
0x05, 0x8C, # Usage Page (barcode scanner)
|
||||
0x09, 0x02, # Usage (Barcode Scanner)
|
||||
0xA1, 0x01, # Collection (Application)
|
||||
0x09, 0x12, # Usage (Scanned Data Report)
|
||||
0xA1, 0x02, # Collection (Logical)
|
||||
0x85, 0x02, # Report ID 2
|
||||
0x15, 0x00, # Logical Minimum 0
|
||||
0x26, 0xFF, 0x00, # Logical Maximum 255
|
||||
0x75, 0x08, # Report Size (8 bits)
|
||||
0x95, 0x01, # Report Count (1 byte long)
|
||||
0x05, 0x01, # Usage Page 0x01 (Generic Desktop Ctrls)
|
||||
0x09, 0x3B, # Usage (Byte count)
|
||||
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x95, 0x03, # Report Count (3 bytes long)
|
||||
0x05, 0x8C, # Usage Page (barcode scanner)
|
||||
0x09, 0xFB, # Usage (0xFB, Symbology Identifier 1)
|
||||
0x09, 0xFC, # Usage (0xFC, Symbology Identifier 2)
|
||||
0x09, 0xFD, # Usage (0xFD, Symbology Identifier 3)
|
||||
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x95, 0x38, # Report Count (56 bytes long)
|
||||
0x05, 0x8C, # Usage Page (barcode scanner)
|
||||
0x09, 0xFE, # Usage 0xFE (Decoded Data)
|
||||
0x82, 0x02, 0x01, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Buffered Bytes)
|
||||
0x95, 0x01, # Report Count (1 byte)
|
||||
0x06, 0x66, 0xFF, # Usage Page (Vendor Defined 0xFF66, everyone does this)
|
||||
0x09, 0x04, # Usage (0x04)
|
||||
0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x95, 0x01, # Report Count (1 byte)
|
||||
0x06, 0x69, 0xFF, # Usage Page (Vendor Defined 0xFF69
|
||||
0x09, 0x55, # Usage (0x55)
|
||||
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)
|
||||
0x75, 0x01, # Report Size (1 bit)
|
||||
0x95, 0x08, # Report Count (8 * size = 1 byte total)
|
||||
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(64),
|
||||
protocol=0x00,
|
||||
interface_str=INTERFACE,
|
||||
)
|
||||
|
||||
def on_set_report(self, report_data, report_id, report_type):
|
||||
#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
|
||||
setInhibitScan(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
|
||||
firmwareUpdateMessage()
|
||||
machine.bootloader()
|
||||
|
||||
elif report_id == 0x05:
|
||||
# Got mode list from host
|
||||
# Don't process it here though since we're running in an interrupt
|
||||
queueNewListReportForProcessing(report_data[:])
|
||||
|
||||
elif report_id == 0x55:
|
||||
# Host says to switch modes
|
||||
setModeID(report_data[1])
|
||||
|
||||
def send_data(self, data=None):
|
||||
if data is None:
|
||||
return True
|
||||
start = time.ticks_ms()
|
||||
while self.busy():
|
||||
# Wait for queue to open up, but drop this report on timeout
|
||||
# because the host isn't paying attention to us and this will deadlock if we wait forever
|
||||
if time.ticks_diff(time.ticks_ms(), start) > 5:
|
||||
return False
|
||||
machine.idle()
|
||||
self.send_report(data)
|
||||
return True
|
||||
|
||||
def initUSBHID():
|
||||
global usbinterface, USBHIDInterface
|
||||
usbinterface = USBHIDInterface()
|
||||
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)
|
||||
|
||||
def split(data, n):
|
||||
return [data[i:i+n] for i in range(0, len(data), n)]
|
||||
|
||||
def pad_data(chunk, size):
|
||||
if len(chunk) >= size:
|
||||
return chunk[:size]
|
||||
return chunk + b'\x00' * (size - len(chunk))
|
||||
|
||||
def createAndSendBarcodeReports(barcodeData):
|
||||
global usbinterface
|
||||
chunks = split(barcodeData, 56)
|
||||
for i, chunk in enumerate(chunks):
|
||||
lastByte = b'\x01'
|
||||
if len(chunks) - 1 == i:
|
||||
lastByte = b'\x00'
|
||||
report = b'\x02' + bytes([len(chunk)]) + b'\x00\x00\x00' + pad_data(chunk, 56) + b'\x00' + bytes([getCurrentModeID()]) + lastByte
|
||||
if USBHID_ENABLED:
|
||||
if not usbinterface.send_data(report):
|
||||
break # Stop sending barcode over USB, the host didn't get this chunk but it might get the next and only have half a barcode
|
||||
feedwatchdog()
|
||||
# Write barcode data to serial out for non-HID system compatibility
|
||||
try:
|
||||
stdout.buffer.write(barcodeData.rstrip(b"\r\n")) # Strip any trailing newlines before sending our own
|
||||
feedwatchdog()
|
||||
stdout.buffer.write(b"\r\n")
|
||||
except:
|
||||
pass
|
||||
48
src/barcodescanner/watchdog.py
Normal file
48
src/barcodescanner/watchdog.py
Normal file
@ -0,0 +1,48 @@
|
||||
# Copyright 2026 PostalPortal LLC
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or
|
||||
# without modification, are permitted provided that
|
||||
# the following conditions are met:
|
||||
# 1. Redistributions of source code must retain
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer.
|
||||
# 2. Redistributions in binary form must reproduce
|
||||
# the above copyright notice, this list of conditions
|
||||
# and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# 3. Neither the name of the copyright holder
|
||||
# nor the names of its contributors may be used
|
||||
# to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
# CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
|
||||
# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
||||
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
|
||||
# WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
# THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from machine import WDT
|
||||
from config import *
|
||||
|
||||
wdt = None
|
||||
|
||||
def startwatchdog():
|
||||
global wdt
|
||||
wdt = WDT(timeout=5000)
|
||||
|
||||
|
||||
def feedwatchdog():
|
||||
global wdt
|
||||
if wdt is not None:
|
||||
wdt.feed()
|
||||
elif TESTMODE:
|
||||
print("feeding watchdog")
|
||||
Loading…
x
Reference in New Issue
Block a user