diff --git a/README.md b/README.md index 6ffbcf5..3661670 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,29 @@ This repository contains open source hardware and software for package drop-off lockers and parcel dimensioning. -It is built around the Pi Pico and MicroPython. +Devices are built around the Pi Pico and MicroPython, and support both bidirectional USB HID and serial communication with a host computer. + +# Devices + +## Parcel Dimensioner + +This device uses a US-100 or Parallax Ping ultrasonic sensor to measure the height of a package and transmit the size to a PC. By rotating the package, all the dimensions can be determined. + +It optionally supports output to a SSD1306 OLED screen and hardware buttons for zero/tare and unit switching. + +There is currently no schematic or board layout, as the entire device is a Pi Pico, a sensor, a display, and eight wires to connect them. + +### Installing + +See source comments for wiring instructions. + +The files in src/dimensioner must be copied to the Pi Pico. + +Dependencies: Install these MicroPython packages on the Pico: usb_device_hid, ssd1306 + +## Package Locker Kiosk Controller + +See source code in src/kiosk and schematics. # License diff --git a/src/dimensioner.py b/src/dimensioner.py deleted file mode 100644 index 09bd68f..0000000 --- a/src/dimensioner.py +++ /dev/null @@ -1,255 +0,0 @@ -# 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. - -# -# Interface between an ultrasonic rangefinder and USB HID, for dimensioning packages. -# - -# HID report bytes to PC: -# 1. Status: 0x01 if fault detected, 0x02 if at zero, -# 0x03 if distance unstable, 0x04 if OK, 0x05 if reading under zero, -# 0x06 if outside max sensor range, 0x07 if below minimum range, -# 0x08 if bad command from host, 0x09 if distance too large to fit in two bytes, 0x00 for other errors. -# If status is 0x01, 0x06, 0x07, 0x08, 0x09, or 0x00, bytes 2-6 will be 0x00. -# 2. Units: 0x01 for mm (default on boot), 0x02 for cm, 0x03 for in, 0x04 for feet -# 3. Sign: 0x00 if measurement positive or zero, 0x01 if negative (under zero) -# 4. First byte of distance to target -# 5. Second byte of distance to target -# 6. Fractional part of distance, .00 to .99 (0x00 to 0x63) -# -# Distance bytes allow transmitting distances from 0.00 to 65535.99 (0x0000 0x00 to 0xffff 0x63) -# Sign byte also allows distances under zero, down to -65535 - -# HID report from PC: -# First byte: -# 0x00: Request re-zeroing (use currently read distance as the distance to the empty measuring platform) -# 0x01: Set units to mm -# 0x02: Set units to cm -# 0x03: Set units to in -# 0x04: Set units to ft -# 0xAC: Set ambient temperature for calibration to the following byte in degrees C (ignored on US-100 sensor) - -from sys import stdin, exit -import math -from machine import Pin, time_pulse_us -from utime import sleep, sleep_us -import usb.device -from micropython import const -from usb.device.hid import HIDInterface - -print("PostalPoint(r) Parcel Dimensioner") -print("Firmware version 1.0") - -# -# Hardware configuration -# -SENSOR_TYPE = "PING" # "PING" for Parallax PING))) or "US-100" for US-100 -SENSOR_MAX_MM = 3000 # Maximum range sensor supports and is accurate within -SENSOR_MIN_MM = 40 # Minimum range below which sensor is not accurate -SENSOR_PIN = 4 # Analog pin for PING sensor -SENSOR_TX = 4 # Digital pin for US-100 -SENSOR_RX = 5 # Digital pin for US-100 -ZERO_RANGE_MM = 3 # +/- mm away from zero before a non-zero value is transmitted -SAMPLE_SIZE = 10 # Number of times to sample distance per reading, averaging the results - -# -# USB configuration -# -VID = 0x1209 # USB Vendor ID -PID = 0xA002 # USB Product ID -MANU = "PostalPortal LLC" # USB manufacturer string -PROD = "PostalPoint Parcel Dimensioner" # 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, 0x02, # Report ID 2 - 0x09, 0x01, # Usage (Vendor Usage 1) - 0x95, 0x01, # Report Count (1 byte) - 0x75, 0x30, # Report Size (48 bits/6 bytes) - 0x81, 0x00, # Input (Data,Ary,Abs) - 0x85, 0x02, # Report ID 2 - 0x09, 0x01, # Usage (Vendor Usage 1) - 0x95, 0x01, # Report Count (1 bytes) - 0x75, 0x10, # Report Size (16 bits/2 bytes) - 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] == 0x00: - # Command to set zero distance - setZeroDistance() - elif report_data[0] > 0x00 and report_data[0] < 0x05: - # Set units to send measurements in - reportUnits = getUnitsFromByte(report_data[0]) - elif report_data[0] == 0xAC: - # Set ambient temperature in degrees C - ambientTempC = report_data[1] - else: - # Can't understand command - send_data(b'\x01\x08\x00\x00\x00\x00\x00') - - def send_data(self, data): - while self.busy(): - machine.idle() - 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) - -zeroDistance = 100 -reportUnits = "mm" -ambientTempC = 20 - - -def setZeroDistance(): - zeroDistance = getAverageDistanceMM() - -def convertMMToUnits(mm, units="in"): - if units == "mm": - return mm - elif units == "cm": - return mm / 10.0 - elif units == "in": - return mm / 25.4 - elif units == "ft": - return convertMMToUnits(mm, "in") / 12.0 - else: - return 0 - -def getDistanceMM(): - if SENSOR_TYPE == "PING": - pin = Pin(SENSOR_PIN, Pin.OUT) - pin.value(0) - sleep_us(5) - pin.value(1) - sleep_us(5) - pin.value(0) - pin = Pin(SENSOR_PIN, Pin.IN) - pulseDuration = time_pulse_us(pin, 1, timeout_us=500000) # Timeout after half a second - if pulseDuration < 0: - # -1 or -2 means we timed out waiting for the pulse - return pulseDuration - pulseDuration = pulseDuration / 2 # Convert round-trip time to one-way time - speedOfSoundMetersPerSecond = (331.5 + (0.6 * ambientTempC)) - speedOfSoundMMPerUS = speedOfSoundMetersPerSecond / 1000.0 - return pulseDuration * speedOfSoundMMPerUS - elif SENSOR_TYPE == "US-100": - pass # TODO - return 0 - -def getAverageDistanceMM(samples = SAMPLE_SIZE): - i = 0 - total = 0.0 - while i < samples: - total = total + getDistanceMM() - sleep_us(250) # PING datasheet says wait a minimum of 200us between measurements - return total / samples - -def getUnitsAsByte(units): - if units == "mm": - return 0x01 - elif units == "cm": - return 0x02 - elif units == "in": - return 0x03 - elif units == "ft": - return 0x04 - else: - return 0x00 - -def getUnitsFromByte(byte): - if byte == 0x01: - return "mm" - elif byte == 0x02: - return "cm" - elif byte == 0x03: - return "in" - elif byte == 0x04: - return "ft" - else: - return "mm" - -while True: - sleep(.25) - dist = getAverageDistanceMM() - if dist < 0: - # distance measurement timed out, ultrasonic ping never came back? - usbinterface.send_data(b'\x01\x01\x00\x00\x00\x00\x00') - continue - size = zeroDistance - dist - sizeInUnits = convertMMToUnits(size, reportUnits) - reportData = bytearray([]) - if size < SENSOR_MIN_MM: - # Under minimum accurate range - usbinterface.send_data(b'\x01\x07\x00\x00\x00\x00\x00') - continue - if size > SENSOR_MAX_MM: - # Above maximum accurate range - usbinterface.send_data(b'\x01\x06\x00\x00\x00\x00\x00') - continue - if abs(sizeInUnits) > 65535: - # Value too large to fit in available bytes - usbinterface.send_data(b'\x01\x09\x00\x00\x00\x00\x00') - continue - - negativeByte = 0x00 - statusByte = 0x04 - if size < 0: - negativeByte = 0x01 - statusByte = 0x05 - - if size < ZERO_RANGE_MM and size > ZERO_RANGE_MM * -1: - # Within zero range, send zero - usbinterface.send_data(b'\x01\x02' + bytes([getUnitsAsByte(reportUnits), negativeByte]) + b'\x00\x00\x00') - continue - - distanceBytes = math.floor(sizeInUnits).to_bytes(2, 'big') + round(sizeInUnits % 1 * 100).to_bytes(1, 'big') - - usbinterface.send_data(b'\x01' + bytes([statusByte, getUnitsAsByte(reportUnits), negativeByte]) + distanceBytes) - diff --git a/src/dimensioner/dimensioner_screen.py b/src/dimensioner/dimensioner_screen.py new file mode 100644 index 0000000..a454f55 --- /dev/null +++ b/src/dimensioner/dimensioner_screen.py @@ -0,0 +1,190 @@ +# 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 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 +from ssd1306big import setOLED, line1, line2 +import framebuf +import math + +ENABLE_DISPLAY = True # Set to False to ignore display commands +DISPLAY_WIDTH = 128 +DISPLAY_HEIGHT = 64 +DISPLAY_SDA_PIN = 0 +DISPLAY_SCL_PIN = 1 + +i2c = I2C(0,sda=Pin(DISPLAY_SDA_PIN), scl=Pin(DISPLAY_SCL_PIN), freq=400000) +oled = SSD1306_I2C(DISPLAY_WIDTH, DISPLAY_HEIGHT, i2c) + + +def bootDisplay(firmwareVersion): + if ENABLE_DISPLAY: + # 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(" Parcel", 0, 32, 1) + oled.text(" Dimensioner", 0, 40, 1) + firmVerStr = "FW Ver " + firmwareVersion + oled.text(f"{firmVerStr:^16}", 0, 56, 1) + oled.show() + sleep_ms(1500) + +def errorDisplay(code, message = ""): + global oled + if ENABLE_DISPLAY: + setOLED(oled) + oled.fill(0) + if code == 1: + line1(" FAULT! ") + oled.text(message, 0, 22, 2) + if message == "Sensor timeout": + pass + elif code == 3: + line1("UNSTABLE") + oled.text("Distance is not", 0, 22, 2) + oled.text("stable. Waiting", 0, 30, 2) + oled.text("for it to settle", 0, 38, 2) + oled.text("down and stop", 0, 46, 2) + oled.text("moving around.", 0, 54, 2) + elif code == 5: + line1("UNDER 0!") + oled.text("Object farther", 0, 22, 2) + oled.text("from platform", 0, 30, 2) + oled.text("than it should", 0, 38, 2) + oled.text("be. Check and", 0, 46, 2) + oled.text("re-zero.", 0, 54, 2) + elif code == 6: + line1("TOO FAR!") + oled.text("Distance is too", 0, 22, 2) + oled.text("far from sensor", 0, 30, 2) + oled.text("for accurate", 0, 38, 2) + oled.text("measurement.", 0, 46, 2) + elif code == 7: + line1("TOO NEAR") + oled.text("Object is too", 0, 22, 2) + oled.text("close to sensor", 0, 30, 2) + oled.text("for accurate", 0, 38, 2) + oled.text("measurement.", 0, 46, 2) + elif code == 8: + line1("HOST ERR") + oled.text("Connected PC", 0, 22, 2) + oled.text("sent an invalid", 0, 30, 2) + oled.text("command.", 0, 38, 2) + elif code == 9: + line1("BAD UNIT") + oled.text("Measurement too", 0, 22, 2) + oled.text("large to display", 0, 30, 2) + oled.text("with current", 0, 38, 2) + oled.text("unit setting.", 0, 46, 2) + oled.text("Change units.", 0, 54, 2) + oled.show() + +def updateDisplay(measurement, units, tempC): + global oled + if ENABLE_DISPLAY: + setOLED(oled) + wholeNumber = str(math.floor(abs(measurement))) + decimalPart = str(abs(measurement % 1) * 1000) + numberLine = " " + if measurement < 0: + numberLine = "-" + + if units == "mm": + numberLine = numberLine + f"{wholeNumber:>5}.{decimalPart:.1}" + elif units == "cm": + numberLine = numberLine + f"{wholeNumber:>4}.{decimalPart:.2}" + elif units == "in": + numberLine = numberLine + f"{wholeNumber:>4}.{decimalPart:.2}" + elif units == "ft": + numberLine = numberLine + f"{wholeNumber:>3}.{decimalPart:.3}" + + oled.fill(0) # Fill with black to clear screen + line1(numberLine) # Write measurement to screen + oled.text(f"{units:^16}", 0, 22, 1) # Write units centered below measurement + if measurement == 0: # Write zero indicator on right below measurement + oled.text(" ZERO", 0, 22, 1) + tempStr = str(tempC) + "C" + oled.text(f"{tempStr:>16}", 0, 50, 1) # Write temperature in bottom-right corner + oled.show() + +def clearDisplay(): + oled.fill(0) + oled.show() \ No newline at end of file diff --git a/src/dimensioner/dimensioner_utils.py b/src/dimensioner/dimensioner_utils.py new file mode 100644 index 0000000..b89343d --- /dev/null +++ b/src/dimensioner/dimensioner_utils.py @@ -0,0 +1,175 @@ +# 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. + +# +# Some functions live here so they don't clutter up the main program file. +# + +import sys +from _thread import start_new_thread +from utime import sleep + +class DeltaError(ValueError): + pass + +def getUnitsAsByte(units): + units = units.lower() + if units == "mm": + return 0x01 + elif units == "cm": + return 0x02 + elif units == "in": + return 0x03 + elif units == "ft": + return 0x04 + else: + return 0x00 + +def getUnitsFromByte(byte): + if byte == 0x01: + return "mm" + elif byte == 0x02: + return "cm" + elif byte == 0x03: + return "in" + elif byte == 0x04: + return "ft" + else: + return "mm" + +def convertMMToUnits(mm, units="in"): + units = units.lower() + if units == "mm": + return mm + elif units == "cm": + return mm / 10.0 + elif units == "in": + return mm / 25.4 + elif units == "ft": + return convertMMToUnits(mm, "in") / 12.0 + else: + return 0 + +# +# +# Serial communication +# +# + +# +# USB serial communication for the Raspberry Pi Pico (RD2040) using the second RD2040 +# thread/processor (written by Dorian Wiskow - Janaury 2021) +# + +# +# global variables to share between both threads/processors +# +bufferSize = 1024 # size of circular buffer to allocate +buffer = [' '] * bufferSize # circuolar incomming USB serial data buffer (pre fill) +bufferEcho = True # USB serial port echo incooming characters (True/False) +bufferNextIn, bufferNextOut = 0,0 # pointers to next in/out character in circualr buffer +terminateThread = False # tell 'bufferSTDIN' function to terminate (True/False) +# +# bufferSTDIN() function to execute in parallel on second Pico RD2040 thread/processor +# +def bufferSTDIN(): + global buffer, bufferSize, bufferEcho, bufferNextIn, terminateThread + + while True: # endless loop + if terminateThread: # if requested by main thread ... + break # ... exit loop + buffer[bufferNextIn] = sys.stdin.read(1) # wait for/store next byte from USB serial + if bufferEcho: # if echo is True ... + print(buffer[bufferNextIn], end='') # ... output byte to USB serial + bufferNextIn += 1 # bump pointer + if bufferNextIn == bufferSize: # ... and wrap, if necessary + bufferNextIn = 0 +# +# instantiate second 'background' thread on RD2040 dual processor to monitor and buffer +# incomming data from 'stdin' over USB serial port using ‘bufferSTDIN‘ function (above) +# +bufferSTDINthread = start_new_thread(bufferSTDIN, ()) + +# +# function to check if a byte is available in the buffer and if so, return it +# +def getByteBuffer(): + global buffer, bufferSize, bufferNextOut, bufferNextIn + + if bufferNextOut == bufferNextIn: # if no unclaimed byte in buffer ... + return '' # ... return a null string + n = bufferNextOut # save current pointer + bufferNextOut += 1 # bump pointer + if bufferNextOut == bufferSize: # ... wrap, if necessary + bufferNextOut = 0 + return (buffer[n]) # return byte from buffer + +# +# function to check if a line is available in the buffer and if so return it +# otherwise return a null string +# +# NOTE 1: a line is one or more bytes with the last byte being LF (\x0a) +# 2: a line containing only a single LF byte will also return a null string +# +def getLineBuffer(): + global buffer, bufferSize, bufferNextOut, bufferNextIn + + if bufferNextOut == bufferNextIn: # if no unclaimed byte in buffer ... + return '' # ... RETURN a null string + + n = bufferNextOut # search for LF or CR in unclaimed bytes + while n != bufferNextIn: + if buffer[n] == '\x0a' or buffer[n] == '\x0d': # if LF or CR found ... + break # ... exit loop ('n' pointing to LF) + n += 1 # bump pointer + if n == bufferSize: # ... wrap, if necessary + n = 0 + if (n == bufferNextIn): # if no LF found ... + return '' # ... RETURN a null string + + line = '' # LF found in unclaimed bytes at pointer 'n' + n += 1 # bump pointer past LF + if n == bufferSize: # ... wrap, if necessary + n = 0 + + while bufferNextOut != n: # BUILD line to RETURN until LF or CR pointer 'n' hit + + if buffer[bufferNextOut] == '\x0a' or buffer[bufferNextOut] == '\x0d': # if current byte is LF or CR ... + bufferNextOut += 1 # bump pointer + if bufferNextOut == bufferSize: # ... wrap, if necessary + bufferNextOut = 0 + break # and exit loop, ignoring (i.e. strip) LF/CR byte + line = line + buffer[bufferNextOut] # add byte to line + bufferNextOut += 1 # bump pointer + if bufferNextOut == bufferSize: # wrap, if necessary + bufferNextOut = 0 + return line # RETURN unclaimed line of input diff --git a/src/dimensioner/main.py b/src/dimensioner/main.py new file mode 100644 index 0000000..a5029af --- /dev/null +++ b/src/dimensioner/main.py @@ -0,0 +1,406 @@ +# 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. + +# +# Interface between an ultrasonic rangefinder and USB HID, for dimensioning packages. +# Requires the usb_device_hid MicroPython package/library to be installed. +# + +# HID report bytes to PC: +# 1. Status: 0x01 if fault detected, 0x02 if at zero, +# 0x03 if distance unstable, 0x04 if OK, 0x05 if reading under zero, +# 0x06 if outside max sensor range, 0x07 if below minimum range, +# 0x08 if bad command from host, 0x09 if distance too large to fit in two bytes, 0x00 for other errors. +# If status is 0x01, 0x06, 0x07, 0x08, 0x09, or 0x00, bytes 2-6 will be 0x00. +# 2. Units: 0x01 for mm, 0x02 for cm, 0x03 for in, 0x04 for feet +# 3. Sign: 0x00 if measurement positive or zero, 0x01 if negative (under zero) +# 4. First byte of distance to target +# 5. Second byte of distance to target +# 6. Fractional part of distance, .00 to .99 (0x00 to 0x63) +# +# Distance bytes allow transmitting distances from 0.00 to 65535.99 (0x0000 0x00 to 0xffff 0x63) +# Sign byte also allows distances under zero, down to -65535 + +# HID report from PC: +# First byte: +# 0x00: Request re-zeroing (use currently read distance as the distance to the empty measuring platform) +# 0x01: Set units to mm +# 0x02: Set units to cm +# 0x03: Set units to in +# 0x04: Set units to ft +# 0xAC: Set ambient temperature for calibration to the following byte in degrees C (ignored on US-100 sensor) + +import sys +import select +import math +from machine import Pin, UART, time_pulse_us +from time import sleep, sleep_us, ticks_diff, ticks_ms +import usb.device +from micropython import const +from usb.device.hid import HIDInterface +import ujson as json +from dimensioner_utils import * +from dimensioner_screen import updateDisplay, errorDisplay, bootDisplay, clearDisplay + +# +# Hardware configuration +# +SENSOR_TYPE = "US-100" # "PING" for Parallax PING))) or "US-100" for US-100 +SENSOR_MAX_MM = 3000 # Maximum range sensor supports and is accurate within +SENSOR_MIN_MM = 100 # Minimum range below which sensor is not accurate +SENSOR_PIN = 18 # Analog pin for PING sensor +SENSOR_TX = 4 # Digital pin for US-100 +SENSOR_RX = 5 # Digital pin for US-100 +ZERO_RANGE_MM = 5 # +/- mm away from zero before a non-zero value is transmitted +SAMPLE_SIZE = 50 # Number of times to sample distance per reading, averaging the results +UNSTABLE_DELTA_MM = 25 # Max difference between largest and smallest samples (before averaging), larger than this will report as unstable +ZERO_BUTTON_PIN = 13 # Pin to read if hardware zero button is pressed +UNITS_BUTTON_PIN = 14 # Pin to read if hardware unit change button is pressed +FIRMWARE_VERSION = "1.0" + +# +# USB configuration +# +VID = 0x1209 # USB Vendor ID +PID = 0xA002 # USB Product ID +MANU = "PostalPortal LLC" # USB manufacturer string +PROD = "PostalPoint Parcel Dimensioner" # USB product string +INTERFACE = "PostalPoint" # Interface string +USBHID_ENABLED = False # Disable USB, use serial output only (good for debugging) + +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, 0x02, # Report ID 2 to host + 0x09, 0x01, # Usage (Vendor Usage 1) + 0x95, 0x01, # Report Count (1 byte) + 0x75, 0x30, # Report Size (48 bits/6 bytes) + 0x81, 0x00, # Input (Data,Ary,Abs) + 0x85, 0x02, # Report ID 2 from host + 0x09, 0x01, # Usage (Vendor Usage 1) + 0x95, 0x01, # Report Count (1 bytes) + 0x75, 0x10, # Report Size (16 bits/2 bytes) + 0x91, 0x01, # Output (Data,Ary,Abs) + 0xc0 # End Collection + ]), + set_report_buf=bytearray(3), + protocol=const(0x00), + interface_str=INTERFACE, + ) + + def on_set_report(self, report_data, _report_id, _report_type): + if _report_id != 2: + return + try: + if report_data[1] == 0x00: + # Command to set zero distance + setZeroDistance() + print("A0: ZEROED") + elif report_data[1] > 0x00 and report_data[0] < 0x05: + # Set units to send measurements in + setUnits(getUnitsFromByte(report_data[1])) + print("A2: UNIT: "+reportUnits) + elif report_data[1] == 0xAC: + # Set ambient temperature in degrees C + setAmbientTemp(report_data[2]) + print("A1: TEMP: "+ambientTempC) + else: + # Can't understand command + send_data(b'\x02\x08\x00\x00\x00\x00\x00') + print("E8: Unsupported Command from Host") + errorDisplay(8) + except: + send_data(b'\x02\x08\x00\x00\x00\x00\x00') + print("E8: Unsupported Command from Host") + errorDisplay(8) + + def send_data(self, data): + #return + while self.busy(): + machine.idle() + self.send_report(data) + +usbinterface = USBHIDInterface() +if USBHID_ENABLED: + 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) + +zeroButtonPin = Pin(ZERO_BUTTON_PIN, Pin.IN, Pin.PULL_UP) +unitsButtonPin = Pin(UNITS_BUTTON_PIN, Pin.IN, Pin.PULL_UP) + +bootDisplay(FIRMWARE_VERSION) +print(PROD) +print("Version " + FIRMWARE_VERSION) +print("Sensor: " + SENSOR_TYPE) + +zeroDistance = 500 +reportUnits = "in" +ambientTempC = 20 + +def saveSettings(): + global zeroDistance, reportUnits, ambientTempC + jsondata = {"zeroDistance": zeroDistance, "reportUnits": reportUnits, "ambientTempC": ambientTempC} + try: + with open('settings.json', 'w') as f: + json.dump(jsondata, f) + except: + print("E0: Could not save settings to flash memory.") + +def loadSettings(): + global zeroDistance, reportUnits, ambientTempC + try: + with open('settings.json', 'r') as f: + data = json.load(f) + zeroDistance = data["zeroDistance"] + reportUnits = data["reportUnits"] + ambientTempC = data["ambientTempC"] + printConfig() + except: + print("Warning: Could not load settings from flash memory. Saving current values instead.") + saveSettings() + +def printConfig(): + print("I0: Zero: ", end="") + print(zeroDistance, end="mm\n") + print("I1: Units: " + reportUnits) + print("I2: Temp C: ", end="") + print(getAmbientTemp()) + +def getAmbientTemp(): + if SENSOR_TYPE == "US-100": + # Ignore the set value, we have a sensor for this + return getUS100Temp() + else: + return ambientTempC + +def sendToDisplay(measurement): + updateDisplay(measurement, reportUnits, getAmbientTemp()) + +def setUnits(units): + global reportUnits + reportUnits = units.lower() + saveSettings() + +def setAmbientTemp(tempC): + global ambientTempC + ambientTempC = tempC + saveSettings() + +def setZeroDistance(): + global zeroDistance + try: + zeroDistance = getAverageDistanceMM() + saveSettings() + except DeltaError: + pass + +def getUS100Temp(): + sensorUart.write(b'\x50') + time = ticks_ms() + buffer = bytearray(1) + while not sensorUart.any(): + if ticks_diff(ticks_ms(), time) > 100: + return -1 + sleep_us(100) + sensorUart.readinto(buffer, 1) + return buffer[0] - 45 + +def getDistanceMM(): + if SENSOR_TYPE == "PING": + pin = Pin(SENSOR_PIN, Pin.OUT) + pin.value(0) + sleep_us(5) + pin.value(1) + sleep_us(5) + pin.value(0) + pin = Pin(SENSOR_PIN, Pin.IN, Pin.PULL_DOWN) + pulseDuration = time_pulse_us(pin, 1, 500000) + if pulseDuration < 0: + # -1 or -2 means we timed out waiting for the pulse + return pulseDuration + pulseDuration = pulseDuration / 2 # Convert round-trip time to one-way time + speedOfSoundMetersPerSecond = (331.5 + (0.6 * ambientTempC)) + speedOfSoundMMPerUS = speedOfSoundMetersPerSecond / 1000.0 + return pulseDuration * speedOfSoundMMPerUS + elif SENSOR_TYPE == "US-100": + sensorUart.write(b'\x55') + time = ticks_ms() + buffer = bytearray(2) + while not sensorUart.any(): + if ticks_diff(ticks_ms(), time) > 100: + return -1 + sleep_us(100) + sensorUart.readinto(buffer, 2) + return buffer[0] * 256 + buffer[1] + + return 0 + +def getAverageDistanceMM(samples = SAMPLE_SIZE): + i = 0 + total = 0.0 + minV = 999999 + maxV = 0 + while i < samples: + sample = getDistanceMM() + if sample < 0: + return sample + if i == 0: + minV = sample + maxV = sample + if sample < minV: + minV = sample + if sample > maxV: + maxV = sample + total = total + sample + i = i + 1 + sleep_us(1000) # PING datasheet says wait a minimum of 200us between measurements + if abs(maxV - minV) > UNSTABLE_DELTA_MM: + raise DeltaError + return total / samples + + + +loadSettings() + +if SENSOR_TYPE == "US-100": + sensorUart = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5)) + sensorUart.init(bits=8, parity=None, stop=2) + +try: + while True: + if zeroButtonPin.value() == 0: + setZeroDistance() + if unitsButtonPin.value() == 0: + unitNumber = getUnitsAsByte(reportUnits) + unitNumber = unitNumber + 1 + if unitNumber > 4: + unitNumber = 1 + setUnits(getUnitsFromByte(unitNumber)) + buffLine = getLineBuffer() # Read a command from the serial connection if available + if buffLine: + data = buffLine.strip().upper() + try: + if data == "": + pass + elif data == "ZERO": + print("A0: ZEROED") + setZeroDistance() + elif data.startswith("TEMP:"): + setAmbientTemp(data[5:]) + print("A1: TEMP: "+ambientTempC) + elif data.startswith("UNIT:"): + setUnits(data[5:]) + print("A2: UNIT: "+reportUnits) + elif data == "?": + printConfig() + else: + errorDisplay(8) + print("E8: Unsupported Command from Host: ", data) + except: + errorDisplay(8) + print("E8: Unsupported Command from Host") + try: + dist = getAverageDistanceMM() + if dist < 0: + # distance measurement timed out, ultrasonic ping never came back? + usbinterface.send_data(b'\x02\x01\x00\x00\x00\x00\x00') + errorDisplay(1, "Sensor timeout") + print("E1: Sensor Timeout") + continue + size = zeroDistance - dist + sizeInUnits = convertMMToUnits(size, reportUnits) + reportData = bytearray([]) + if dist < SENSOR_MIN_MM: + # Under minimum accurate range + usbinterface.send_data(b'\x02\x07\x00\x00\x00\x00\x00') + errorDisplay(7) + print("E7: Under Minimum Range") + continue + if dist > SENSOR_MAX_MM: + # Above maximum accurate range + usbinterface.send_data(b'\x02\x06\x00\x00\x00\x00\x00') + errorDisplay(6) + print("E6: Over Maximum Range") + continue + if abs(sizeInUnits) > 65535: + # Value too large to fit in available bytes + usbinterface.send_data(b'\x02\x09\x00\x00\x00\x00\x00') + errorDisplay(9) + print("E9: Value Too Large, Use Different Units") + continue + + negativeByte = 0x00 + statusByte = 0x04 + if size < 0: + negativeByte = 0x01 + statusByte = 0x05 + + if size < ZERO_RANGE_MM and size > ZERO_RANGE_MM * -1: + # Within zero range, send zero + usbinterface.send_data(b'\x02\x02' + bytes([getUnitsAsByte(reportUnits), negativeByte]) + b'\x00\x00\x00') + sendToDisplay(0) + print("S2: Zero") + continue + + distanceBytes = math.floor(abs(sizeInUnits)).to_bytes(2, 'big') + round(abs(sizeInUnits) % 1 * 100).to_bytes(1, 'big') + + if USBHID_ENABLED: + usbinterface.send_data(b'\x02' + bytes([statusByte, getUnitsAsByte(reportUnits), negativeByte]) + distanceBytes) + + if statusByte == 0x04: + print("S4: ", end="") + elif statusByte == 0x05: + print("S5: ", end="") + if size < 0: + print("-", end="") + + print(abs(round(sizeInUnits, 2)), end="") + print(reportUnits.lower(), end=" ") + print("RAW:" + str(dist) + "mm") + sendToDisplay(round(sizeInUnits, 2)) + except DeltaError: + usbinterface.send_data(b'\x02\x03\x00\x00\x00\x00\x00') + errorDisplay(3) + print("E3: Unstable") +except KeyboardInterrupt: + terminateThread = True + clearDisplay() + sys.exit() diff --git a/src/dimensioner/ssd1306big.py b/src/dimensioner/ssd1306big.py new file mode 100644 index 0000000..3260f62 --- /dev/null +++ b/src/dimensioner/ssd1306big.py @@ -0,0 +1,517 @@ +# Source: https://github.com/nickpmulder/ssd1306big +# Modified March 2025 by PostalPortal to remove things that aren't needed +# +# This is a driver for ssd1306 i2c oled displays using micropython. +# This was written for a Raspberry Pi Pico but should work on any microcontroller running micropython. +# It was based originally on the ssd1306.py module, but a new, larger font was drawn using framebuffer lines by Nick Mulder +# This module is open source, and can be used for free for any purpose. +# The default wiring is SDA to GP8 and SCL to GP9 for Rasberry Pi Pico + + +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +oled = False + +#The Alphabet +def A(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+5,(p.y)+1,1) + oled.line((p.x)+5,(p.y)+1,(p.x)+10,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+11,(p.x)+8,(p.y)+11,1) + +def B(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+6,(p.y)+1,1) + oled.line((p.x)+6,(p.y)+1,(p.x)+8,(p.y)+3,1) + oled.line((p.x)+8,(p.y)+3,(p.x)+8,(p.y)+4,1) + oled.line((p.x)+8,(p.y)+4,(p.x)+6,(p.y)+7,1) + oled.line((p.x)+5,(p.y)+7,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+6,(p.y)+7,(p.x)+9,(p.y)+10,1) + oled.line((p.x)+9,(p.y)+10,(p.x)+9,(p.y)+12,1) + oled.line((p.x)+9,(p.y)+12,(p.x)+6,(p.y)+15,1) + oled.line((p.x)+6,(p.y)+15,(p.x)+1,(p.y)+15,1) + +def C(p): + oled.line((p.x)+10,(p.y)+2,(p.x)+9,(p.y)+1,1) + oled.line((p.x)+9,(p.y)+1,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+1,(p.y)+12,1) + oled.line((p.x)+1,(p.y)+12,(p.x)+4,(p.y)+15,1) + oled.line((p.x)+4,(p.y)+15,(p.x)+8,(p.y)+15,1) + oled.line((p.x)+8,(p.y)+15,(p.x)+10,(p.y)+13,1) + +def D(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+6,(p.y)+1,1) + oled.line((p.x)+6,(p.y)+1,(p.x)+9,(p.y)+3,1) + oled.line((p.x)+9,(p.y)+3,(p.x)+9,(p.y)+12,1) + oled.line((p.x)+9,(p.y)+12,(p.x)+6,(p.y)+15,1) + oled.line((p.x)+6,(p.y)+15,(p.x)+1,(p.y)+15,1) + +def E(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+9,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+7,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+9,(p.y)+15,1) + +def F(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+9,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+6,(p.y)+7,1) + +def G(p): + oled.line((p.x)+9,(p.y)+2,(p.x)+8,(p.y)+1,1) + oled.line((p.x)+8,(p.y)+1,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+1,(p.y)+12,1) + oled.line((p.x)+1,(p.y)+12,(p.x)+4,(p.y)+15,1) + oled.line((p.x)+4,(p.y)+15,(p.x)+8,(p.y)+15,1) + oled.line((p.x)+8,(p.y)+15,(p.x)+10,(p.y)+13,1) + oled.line((p.x)+10,(p.y)+13,(p.x)+10,(p.y)+9,1) + oled.line((p.x)+10,(p.y)+9,(p.x)+6,(p.y)+9,1) + +def H(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+9,(p.y)+7,1) + oled.line((p.x)+9,(p.y)+15,(p.x)+9,(p.y)+1,1) + + +def I(p): + oled.line((p.x)+1,(p.y)+1,(p.x)+9,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+9,(p.y)+15,1) + oled.line((p.x)+5,(p.y)+15,(p.x)+5,(p.y)+1,1) + +def J(p): + oled.line((p.x)+9,(p.y)+1,(p.x)+9,(p.y)+10,1) + oled.line((p.x)+9,(p.y)+10,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+1,(p.y)+10,1) + +def K(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+9,(p.x)+8,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+7,(p.x)+9,(p.y)+15,1) + +def L(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+9,(p.y)+15,1) + +def M(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+5,(p.y)+7,1) + oled.line((p.x)+9,(p.y)+1,(p.x)+5,(p.y)+7,1) + oled.line((p.x)+9,(p.y)+15,(p.x)+9,(p.y)+1,1) + +def N(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+9,(p.y)+15,1) + oled.line((p.x)+9,(p.y)+15,(p.x)+9,(p.y)+1,1) + +def O(p): + oled.line((p.x)+10,(p.y)+5,(p.x)+8,(p.y)+1,1) + oled.line((p.x)+8,(p.y)+1,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+1,(p.y)+12,1) + oled.line((p.x)+1,(p.y)+12,(p.x)+4,(p.y)+15,1) + oled.line((p.x)+4,(p.y)+15,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+10,(p.y)+12,1) + oled.line((p.x)+10,(p.y)+12,(p.x)+10,(p.y)+5,1) + +def P(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+7,(p.y)+1,1) + oled.line((p.x)+7,(p.y)+1,(p.x)+9,(p.y)+4,1) + oled.line((p.x)+9,(p.y)+4,(p.x)+9,(p.y)+6,1) + oled.line((p.x)+9,(p.y)+6, (p.x)+6,(p.y)+9,1) + oled.line((p.x)+5,(p.y)+9,(p.x)+1,(p.y)+9,1) + +def Q(p): + oled.line((p.x)+10,(p.y)+5,(p.x)+8,(p.y)+1,1) + oled.line((p.x)+8,(p.y)+1,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+1,(p.y)+12,1) + oled.line((p.x)+1,(p.y)+12,(p.x)+4,(p.y)+15,1) + oled.line((p.x)+4,(p.y)+15,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+10,(p.y)+12,1) + oled.line((p.x)+10,(p.y)+12,(p.x)+10,(p.y)+5,1) + oled.line((p.x)+6,(p.y)+10,(p.x)+10,(p.y)+15,1) + +def R(p): + oled.line((p.x)+1,(p.y)+15,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+7,(p.y)+1,1) + oled.line((p.x)+7,(p.y)+1,(p.x)+9,(p.y)+4,1) + oled.line((p.x)+9,(p.y)+4,(p.x)+9,(p.y)+6,1) + oled.line((p.x)+9,(p.y)+6, (p.x)+6,(p.y)+9,1) + oled.line((p.x)+5,(p.y)+9,(p.x)+1,(p.y)+9,1) + oled.line((p.x)+5,(p.y)+9,(p.x)+9,(p.y)+15,1) + +def S(p): + oled.line((p.x)+9,(p.y)+2,(p.x)+7,(p.y)+1,1) + oled.line((p.x)+7,(p.y)+1,(p.x)+3,(p.y)+1,1) + oled.line((p.x)+3,(p.y)+1,(p.x)+2,(p.y)+2,1) + oled.line((p.x)+3,(p.y)+1,(p.x)+2,(p.y)+2,1) + oled.line((p.x)+2,(p.y)+2,(p.x)+1,(p.y)+5,1) + oled.line((p.x)+1,(p.y)+5,(p.x)+5,(p.y)+7,1) + oled.line((p.x)+5,(p.y)+7,(p.x)+9,(p.y)+8,1) + oled.line((p.x)+9,(p.y)+8,(p.x)+10,(p.y)+11,1) + oled.line((p.x)+10,(p.y)+11,(p.x)+10,(p.y)+13,1) + oled.line((p.x)+10,(p.y)+13,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+4,(p.y)+15,1) + oled.line((p.x)+4,(p.y)+15,(p.x)+1,(p.y)+13,1) + #oled.line((p.x)+10,(p.y)+13,(p.x)+7,(p.y)+15,1) + +def T(p): + oled.line((p.x)+5,(p.y)+15,(p.x)+5,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+9,(p.y)+1,1) + +def U(p): + oled.line((p.x)+1,(p.y)+1,(p.x)+1,(p.y)+13,1) + oled.line((p.x)+1,(p.y)+13,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+9,(p.y)+13,1) + oled.line((p.x)+9,(p.y)+13,(p.x)+9,(p.y)+1,1) + +def V(p): + oled.line((p.x)+1,(p.y)+1,(p.x)+5,(p.y)+15,1) + oled.line((p.x)+5,(p.y)+15,(p.x)+9,(p.y)+1,1) + +def W(p): + oled.line((p.x)+1,(p.y)+1,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+5,(p.y)+8,1) + oled.line((p.x)+5,(p.y)+8,(p.x)+8,(p.y)+15,1) + oled.line((p.x)+8,(p.y)+15,(p.x)+10,(p.y)+1,1) + +def X(p): + oled.line((p.x)+1,(p.y)+1,(p.x)+9,(p.y)+15,1) + oled.line((p.x)+9,(p.y)+1,(p.x)+1,(p.y)+15,1) + +def Y(p): + oled.line((p.x)+5,(p.y)+15,(p.x)+5,(p.y)+7,1) + oled.line((p.x)+5,(p.y)+7,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+5,(p.y)+7,(p.x)+10,(p.y)+1,1) + +def Z(p): + oled.line((p.x)+1,(p.y)+1,(p.x)+9,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+9,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+9,(p.y)+15,1) + +def period(p): + oled.line((p.x)+1,(p.y)+14,(p.x)+2,(p.y)+14,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+2,(p.y)+15,1) + +def exclam(p): + oled.line((p.x)+1,(p.y)+14,(p.x)+1,(p.y)+15,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+1,(p.y)+10,1) + +def plus(p): + oled.line((p.x)+5,(p.y)+5,(p.x)+5,(p.y)+11,1) + oled.line((p.x)+2,(p.y)+8,(p.x)+8,(p.y)+8,1) + +def minus(p): + oled.line((p.x)+2,(p.y)+8,(p.x)+8,(p.y)+8,1) + +def equal(p): + oled.line((p.x)+2,(p.y)+6,(p.x)+8,(p.y)+6,1) + oled.line((p.x)+2,(p.y)+9,(p.x)+8,(p.y)+9,1) + +def comma(p): + oled.line((p.x)+1,(p.y)+13,(p.x)+1,(p.y)+14,1) + oled.line((p.x)+2,(p.y)+13,(p.x)+2,(p.y)+17,1) + oled.line((p.x)+1,(p.y)+17,(p.x)+2,(p.y)+17,1) + +def colon(p): + oled.line((p.x)+1,(p.y)+14,(p.x)+2,(p.y)+14,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+2,(p.y)+15,1) + oled.line((p.x)+1,(p.y)+6,(p.x)+2,(p.y)+6,1) + oled.line((p.x)+1,(p.y)+5,(p.x)+2,(p.y)+5,1) + +def slash(p): + oled.line((p.x)+9,(p.y)+1,(p.x)+1,(p.y)+15,1) + +def question(p): + oled.line((p.x)+5,(p.y)+14,(p.x)+6,(p.y)+14,1) + oled.line((p.x)+5,(p.y)+15,(p.x)+6,(p.y)+15,1) + oled.line((p.x)+5,(p.y)+10,(p.x)+5,(p.y)+8,1) + oled.line((p.x)+5,(p.y)+8,(p.x)+8,(p.y)+6,1) + oled.line((p.x)+8,(p.y)+6,(p.x)+9,(p.y)+2,1) + oled.line((p.x)+8,(p.y)+1,(p.x)+4,(p.y)+1,1) + +def amp(p): + #& + oled.line((p.x)+4,(p.y)+7,(p.x)+2,(p.y)+5,1) + oled.line((p.x)+2,(p.y)+5,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+3,(p.y)+2,1) + oled.line((p.x)+3,(p.y)+2,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+6,(p.y)+1,1) + oled.line((p.x)+6,(p.y)+1,(p.x)+7,(p.y)+2,1) + oled.line((p.x)+7,(p.y)+2,(p.x)+8,(p.y)+3,1) + oled.line((p.x)+8,(p.y)+3,(p.x)+8,(p.y)+4,1) + oled.line((p.x)+8,(p.y)+4,(p.x)+6,(p.y)+6,1) + oled.line((p.x)+6,(p.y)+6,(p.x)+1,(p.y)+10,1) + oled.line((p.x)+1,(p.y)+10,(p.x)+1,(p.y)+13,1) + oled.line((p.x)+1,(p.y)+13,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+6,(p.y)+15,1) + oled.line((p.x)+6,(p.y)+15,(p.x)+9,(p.y)+9,1) + oled.line((p.x)+4,(p.y)+8,(p.x)+10,(p.y)+15,1) + +def zero(p): + oled.line((p.x)+10,(p.y)+5,(p.x)+8,(p.y)+1,1) + oled.line((p.x)+8,(p.y)+1,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+1,(p.y)+12,1) + oled.line((p.x)+1,(p.y)+12,(p.x)+4,(p.y)+15,1) + oled.line((p.x)+4,(p.y)+15,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+10,(p.y)+12,1) + oled.line((p.x)+10,(p.y)+12,(p.x)+10,(p.y)+5,1) + oled.line((p.x)+9,(p.y)+4,(p.x)+2,(p.y)+12,1) + +def one(p): + oled.line((p.x)+5,(p.y)+15,(p.x)+5,(p.y)+1,1) + oled.line((p.x)+5,(p.y)+1,(p.x)+2,(p.y)+3,1) + +def two(p): + oled.line((p.x)+1,(p.y)+3,(p.x)+2,(p.y)+1,1) + oled.line((p.x)+2,(p.y)+1,(p.x)+7,(p.y)+1,1) + oled.line((p.x)+7,(p.y)+1,(p.x)+9,(p.y)+3,1) + oled.line((p.x)+9,(p.y)+3,(p.x)+9,(p.y)+6,1) + oled.line((p.x)+9,(p.y)+6,(p.x)+2,(p.y)+13,1) + oled.line((p.x)+2,(p.y)+13,(p.x)+1,(p.y)+15,1) + oled.line((p.x)+1,(p.y)+15,(p.x)+10,(p.y)+15,1) + +def three(p): + oled.line((p.x)+1,(p.y)+3,(p.x)+2,(p.y)+1,1) + oled.line((p.x)+2,(p.y)+1,(p.x)+7,(p.y)+1,1) + oled.line((p.x)+7,(p.y)+1,(p.x)+9,(p.y)+3,1) + oled.line((p.x)+9,(p.y)+3,(p.x)+9,(p.y)+5,1) + oled.line((p.x)+9,(p.y)+5,(p.x)+7,(p.y)+7,1) + oled.line((p.x)+7,(p.y)+7,(p.x)+4,(p.y)+7,1) + oled.line((p.x)+7,(p.y)+8,(p.x)+9,(p.y)+9,1) + oled.line((p.x)+9,(p.y)+9,(p.x)+9,(p.y)+12,1) + oled.line((p.x)+9,(p.y)+12,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+1,(p.y)+13,1) + +def four(p): + oled.line((p.x)+8,(p.y)+1,(p.x)+8,(p.y)+15,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+9,(p.y)+7,1) + +def five(p): + oled.line((p.x)+9,(p.y)+1,(p.x)+1,(p.y)+1,1) + oled.line((p.x)+1,(p.y)+1,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+7,(p.y)+7,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+7,(p.y)+8,(p.x)+9,(p.y)+9,1) + oled.line((p.x)+9,(p.y)+9,(p.x)+9,(p.y)+12,1) + oled.line((p.x)+9,(p.y)+12,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+1,(p.y)+13,1) + +def six(p): + oled.line((p.x)+10,(p.y)+3,(p.x)+8,(p.y)+1,1) + oled.line((p.x)+8,(p.y)+1,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+1,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+7,(p.x)+1,(p.y)+12,1) + oled.line((p.x)+1,(p.y)+12,(p.x)+4,(p.y)+15,1) + oled.line((p.x)+4,(p.y)+15,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+10,(p.y)+13,1) + oled.line((p.x)+10,(p.y)+13,(p.x)+10,(p.y)+9,1) + oled.line((p.x)+10,(p.y)+9,(p.x)+8,(p.y)+7,1) + oled.line((p.x)+8,(p.y)+7,(p.x)+4,(p.y)+7,1) + oled.line((p.x)+4,(p.y)+7,(p.x)+2,(p.y)+9,1) + +def seven(p): + oled.line((p.x)+1,(p.y)+1,(p.x)+10,(p.y)+1,1) + oled.line((p.x)+10,(p.y)+1,(p.x)+3,(p.y)+15,1) + +def eight(p): + oled.line((p.x)+4,(p.y)+7,(p.x)+2,(p.y)+5,1) + oled.line((p.x)+2,(p.y)+5,(p.x)+2,(p.y)+3,1) + oled.line((p.x)+2,(p.y)+3,(p.x)+3,(p.y)+2,1) + oled.line((p.x)+3,(p.y)+2,(p.x)+4,(p.y)+1,1) + oled.line((p.x)+4,(p.y)+1,(p.x)+6,(p.y)+1,1) + oled.line((p.x)+6,(p.y)+1,(p.x)+7,(p.y)+2,1) + oled.line((p.x)+7,(p.y)+2,(p.x)+8,(p.y)+3,1) + oled.line((p.x)+8,(p.y)+3,(p.x)+8,(p.y)+5,1) + oled.line((p.x)+8,(p.y)+5,(p.x)+6,(p.y)+7,1) + oled.line((p.x)+1,(p.y)+10,(p.x)+1,(p.y)+13,1) + oled.line((p.x)+1,(p.y)+13,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+9,(p.y)+13,1) + oled.line((p.x)+9,(p.y)+13,(p.x)+9,(p.y)+10,1) + oled.line((p.x)+9,(p.y)+10,(p.x)+6,(p.y)+7,1) + oled.line((p.x)+6,(p.y)+7,(p.x)+4,(p.y)+7,1) + oled.line((p.x)+4,(p.y)+7,(p.x)+2,(p.y)+9,1) + +def nine(p): + oled.line((p.x)+10,(p.y)+6,(p.x)+8,(p.y)+8,1) + oled.line((p.x)+8,(p.y)+8,(p.x)+3,(p.y)+8,1) + oled.line((p.x)+3,(p.y)+8,(p.x)+1,(p.y)+5,1) + oled.line((p.x)+1,(p.y)+5,(p.x)+1,(p.y)+3,1) + oled.line((p.x)+1,(p.y)+3,(p.x)+3,(p.y)+1,1) + oled.line((p.x)+3,(p.y)+1,(p.x)+8,(p.y)+1,1) + oled.line((p.x)+8,(p.y)+1,(p.x)+10,(p.y)+3,1) + oled.line((p.x)+10,(p.y)+3,(p.x)+10,(p.y)+10,1) + oled.line((p.x)+10,(p.y)+10,(p.x)+9,(p.y)+13,1) + oled.line((p.x)+9,(p.y)+13,(p.x)+7,(p.y)+15,1) + oled.line((p.x)+7,(p.y)+15,(p.x)+3,(p.y)+15,1) + oled.line((p.x)+3,(p.y)+15,(p.x)+1,(p.y)+13,1) + +def space(p): + pass + +#positon object + +class Pos: + def __init__(self, x, y): + self.x=x + self.y=y + +#define position x and y +p0=Pos(0,0) +p1=Pos(15,0) +p2=Pos(30,0) +p3=Pos(45,0) +p4=Pos(60,0) +p5=Pos(75,0) +p6=Pos(90,0) +p7=Pos(105,0) +p8=Pos(0,22) +p9=Pos(15,22) +p10=Pos(30,22) +p11=Pos(45,22) +p12=Pos(60,22) +p13=Pos(75,22) +p14=Pos(90,22) +p15=Pos(105,22) +p16=Pos(0,44) +p17=Pos(15,44) +p18=Pos(30,44) +p19=Pos(45,44) +p20=Pos(60,44) +p21=Pos(75,44) +p22=Pos(90,44) +p23=Pos(105,44) + +line1Array=[p0,p1,p2,p3,p4,p5,p6,p7] +line2Array=[p8,p9,p10,p11,p12,p13,p14,p15] +line3Array=[p16,p17,p18,p19,p20,p21,p22,p23] +displayArray=[p0,p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,p11,p12,p13,p14,p15,p16,p17,p18,p19,p20,p21,p22,p23] + + + +def display(text, posArray): + for i in range (len(text)): + if text[i]=="A" or text[i]=="a": + A(posArray[i]) + if text[i]=="B" or text[i]=="b": + B(posArray[i]) + if text[i]=="C" or text[i]=="c": + C(posArray[i]) + if text[i]=="D" or text[i]=="d": + D(posArray[i]) + if text[i]=="E" or text[i]=="e": + E(posArray[i]) + if text[i]=="F" or text[i]=="f": + F(posArray[i]) + if text[i]=="G" or text[i]=="g": + G(posArray[i]) + if text[i]=="H" or text[i]=="h": + H(posArray[i]) + if text[i]=="I" or text[i]=="i": + I(posArray[i]) + if text[i]=="J" or text[i]=="j": + J(posArray[i]) + if text[i]=="K" or text[i]=="k": + K(posArray[i]) + if text[i]=="L" or text[i]=="l": + L(posArray[i]) + if text[i]=="M" or text[i]=="m": + M(posArray[i]) + if text[i]=="N" or text[i]=="n": + N(posArray[i]) + if text[i]=="O" or text[i]=="o": + O(posArray[i]) + if text[i]=="P" or text[i]=="p": + P(posArray[i]) + if text[i]=="Q" or text[i]=="q": + Q(posArray[i]) + if text[i]=="R" or text[i]=="r": + R(posArray[i]) + if text[i]=="S" or text[i]=="s": + S(posArray[i]) + if text[i]=="T" or text[i]=="t": + T(posArray[i]) + if text[i]=="U" or text[i]=="u": + U(posArray[i]) + if text[i]=="V" or text[i]=="v": + V(posArray[i]) + if text[i]=="W" or text[i]=="w": + W(posArray[i]) + if text[i]=="X" or text[i]=="x": + X(posArray[i]) + if text[i]=="Y" or text[i]=="y": + Y(posArray[i]) + if text[i]=="Z" or text[i]=="z": + Z(posArray[i]) + if text[i]=="0": + zero(posArray[i]) + if text[i]=="1": + one(posArray[i]) + if text[i]=="2": + two(posArray[i]) + if text[i]=="3": + three(posArray[i]) + if text[i]=="4": + four(posArray[i]) + if text[i]=="5": + five(posArray[i]) + if text[i]=="6": + six(posArray[i]) + if text[i]=="7": + seven(posArray[i]) + if text[i]=="8": + eight(posArray[i]) + if text[i]=="9": + nine(posArray[i]) + if text[i]==".": + period(posArray[i]) + if text[i]=="!": + exclam(posArray[i]) + if text[i]=="?": + question(posArray[i]) + if text[i]=="/": + slash(posArray[i]) + if text[i]==":": + colon(posArray[i]) + if text[i]==",": + comma(posArray[i]) + if text[i]=="&": + amp(posArray[i]) + if text[i]=="+": + plus(posArray[i]) + if text[i]=="-": + minus(posArray[i]) + if text[i]=="=": + equal(posArray[i]) + if text[i]==" ": + space(posArray[i]) + +def setOLED(ol): + global oled + oled = ol + +def line1(line1text): + display(line1text, line1Array) + +def line2(line2text): + display(line2text, line2Array) + +def line3(line3text): + display(line3text, line3Array) \ No newline at end of file diff --git a/src/kiosk-hid.py b/src/kiosk/kiosk-hid.py similarity index 100% rename from src/kiosk-hid.py rename to src/kiosk/kiosk-hid.py diff --git a/src/kiosk-serial.py b/src/kiosk/kiosk-serial.py similarity index 100% rename from src/kiosk-serial.py rename to src/kiosk/kiosk-serial.py