# 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. # # This version implements USB HID to receive the door command and send status. # from sys import stdin, exit from _thread import start_new_thread import machine from machine import Pin, USBDevice from utime import sleep import usb.device from micropython import const from usb.device.hid import HIDInterface print("PostalPoint(r) Shipping Kiosk Door Controller") print("Firmware version 1.2.1") print("USB HID build") led = Pin(25, Pin.OUT) # Onboard LED relay = Pin(4, Pin.OUT) # Relay control pin latchStatus = Pin(15, Pin.IN, Pin.PULL_DOWN) # Read status of door latch switch latchSend = Pin(14, Pin.OUT) # Send signal to latch switch latchNotifyCounter = 0 # Don't send latch status constantly latchNotifyInterval = 50 # Number of main loop executions between notifications uspsLock = Pin(1, Pin.IN, Pin.PULL_DOWN) # Read arrow lock switch uspsLockSend = Pin(0, Pin.OUT) # Send to arrow lock switch uspsLockOpen = False led.value(0) relay.value(0) uspsLockSend.value(1) # Read status of USPS arrow lock to see if it's NC or NO # The switch will be depressed when the lock is closed # Assume it's locked when power is turned on uspsLockValueWhenLocked = uspsLock.value() # Read if jumper is set to slow mode fastMode = True selectSlow = Pin(18, Pin.IN, Pin.PULL_DOWN) selectEmit = Pin(17, Pin.OUT) selectEmit.value(1) # Output on middle pin fastMode = selectSlow.value() != 1 selectEmit.value(0) # Turn off pin, not needed after boot if fastMode: print("Door mode: Fast") else: print("Door mode: Slow") if uspsLockValueWhenLocked == 0: print("Arrow lock switch: Normally Closed or Not Present") else: print("Arrow lock switch: Normally Open") print("Boot complete") # VID and PID of the USB device. VID = 0x1209 # USB Vendor ID PID = 0xA001 # USB Product ID MANU = "PostalPortal LLC" # USB manufacturer string PROD = "PostalPoint Kiosk Controller" # USB product string INTERFACE = "PostalPoint" # Interface string class USBHIDInterface(HIDInterface): def __init__(self): super().__init__( bytes([ 0x06, 0x69, 0xff, # Usage Page (Vendor Defined) 0x09, 0x01, # Usage (Vendor Usage 1) 0x19, 0x01, 0x29, 0x01, 0xa1, 0x01, # Collection (Application) 0x15, 0x00, # Logical Minimum (0) 0x26, 0xff, 0x00, # Logical Maximum (255) 0x85, 0x01, # Report ID 1 0x09, 0x01, # Usage (Vendor Usage 1) 0x95, 0x01, # Report Count (1 byte) 0x75, 0x08, # Report Size (8 bits) 0x81, 0x00, # Input (Data,Ary,Abs) 0x85, 0x01, # Report ID 1 0x09, 0x01, # Usage (Vendor Usage 1) 0x95, 0x01, # Report Count (1 bytes) 0x75, 0x08, # Report Size (8 bits) 0x91, 0x01, # Output (Data,Ary,Abs) 0xc0 # End Collection ]), set_report_buf=bytearray(1), protocol=const(0x00), interface_str=INTERFACE, ) def on_set_report(self, report_data, _report_id, _report_type): if report_data[1] == 0x50: print("Entering firmware update mode, power cycle to undo. Goodbye for now!") machine.bootloader() else: unlockDoor(False) def send_data(self, data=None): while self.busy(): machine.idle() if data is None: self.send_report(b'\x01\xFF') 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) def unlockDoor(usps = False): print("DO", end="") led.value(1) relay.value(1) if usps == True: usbinterface.send_data(b'\x01\x05') else: usbinterface.send_data(b'\x01\x04') if fastMode: sleep(0.5) else: sleep(10) relay.value(0) led.value(0) print("OR") try: while True: if uspsLock.value() != uspsLockValueWhenLocked: # Arrow lock override, open door, but then don't again until re-locked if uspsLockOpen == False: uspsLockOpen = True print("USPS") unlockDoor(True) if uspsLock.value() == uspsLockValueWhenLocked: uspsLockOpen = False latchSend.value(1) if latchStatus.value() == 1: usbinterface.send_data(b'\x01\x21') else: usbinterface.send_data(b'\x01\x20') latchSend.value(0) sleep(0.1) except KeyboardInterrupt: # trap Ctrl-C input terminateThread = True # signal second 'background' thread to terminate exit()