# 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[0] == 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()
