diff --git a/pico_2026/main.py b/pico_2026/main.py index 23db4a4..ea22565 100644 --- a/pico_2026/main.py +++ b/pico_2026/main.py @@ -2,6 +2,9 @@ import wifi_client import rate +from machine import I2C, Pin +from ssd1306 import SSD1306_I2C + # set SSID and PASSWORD variables to the configurations of the laptop access point SSID = 'Oger-fi' PASSWORD = 'Karlfreitag!' @@ -9,6 +12,10 @@ SERVER_PORT = 3000 SERVER_IP_LOWER = 100 SERVER_IP_UPPER = 102 +# Initialize I2C and OLED +i2c = I2C(0, sda=Pin(8), scl=Pin(9), freq=400000) +display = SSD1306_I2C(128, 64, i2c) + # connect to laptop hotspot wifi_client.wifi_connect(SSID, PASSWORD) wifi_client.search_server(SERVER_IP_LOWER, SERVER_IP_UPPER, SERVER_PORT) @@ -18,7 +25,7 @@ wifi_client.search_server(SERVER_IP_LOWER, SERVER_IP_UPPER, SERVER_PORT) # define Pins 0-4 for rate buttons (5 buttons) rate_button_pins:list = [0, 1, 2, 3, 4] send_button_pin:int = 6 -rate.start_rating(rate_button_pins, send_button_pin) +rate.start_rating(rate_button_pins, send_button_pin, display) diff --git a/pico_2026/rate.py b/pico_2026/rate.py index 910af42..b140903 100644 --- a/pico_2026/rate.py +++ b/pico_2026/rate.py @@ -12,6 +12,48 @@ USER_ID = 0 RATING = 0 BUTTON_SEND = None RATE_BUTTONS = [] +DISPLAY = None + +# Helper to update display based on state +def refresh_display(message=None): + if not DISPLAY: + return + + DISPLAY.fill(0) + + if message: + # Show temporary message (Success, Error, Confirmed) + DISPLAY.text(message, 10, 30) + elif CURRENT_STATE == STATE_USER_ID: + # 1. User ID Mode + DISPLAY.text("Inchometer", 25, 0) + + # Display binary representation: _ _ _ _ _ + # Bit 4 is leftmost button, Bit 0 is rightmost + binary_str = "" + for i in range(5): + bit_pos = 4 - i + if USER_ID & (1 << bit_pos): + binary_str += "X " + else: + binary_str += "_ " + + DISPLAY.text(binary_str.strip(), 30, 30) + + elif CURRENT_STATE == STATE_RATING: + # 3. Rating Mode + DISPLAY.text("Select Rating", 10, 0) + + if RATING == 0: + rating_text = "_" + else: + rating_text = str(RATING) + + # Draw "big" text (simulated with scale if supported, or just centered) + # Standard ssd1306 doesn't have big fonts, so we just center it + DISPLAY.text(rating_text, 60, 30) + + DISPLAY.show() # initialize a specific Pin as Input with a pull-down resistor def initialize_pin(pin_number:int): @@ -41,56 +83,58 @@ def rate_button_pressed(pin:Pin) -> None: return if CURRENT_STATE == STATE_USER_ID: - # Binary input: each button toggles a bit - # Assume rightmost (highest index) is bit 0 bit_pos = len(RATE_BUTTONS) - 1 - button_index USER_ID ^= (1 << bit_pos) - print(f"Current User ID: {USER_ID} (binary: {bin(USER_ID)})") + print(f"Current User ID: {USER_ID}") elif CURRENT_STATE == STATE_RATING: - # Rating selection (1 to 5/6) - # Assuming the button index + 1 is the rating RATING = button_index + 1 print(f"Current Rating selected: {RATING}") - # Activate send pin to confirm selection + refresh_display() activate_sendPin() # Send/Confirm button is pressed def sendButton_pressed(pin) -> None: global CURRENT_STATE, USER_ID, RATING - # deactivate send pin to prevent multiple sends deactivate_sendPin() if CURRENT_STATE == STATE_USER_ID: - print(f"Confirmed User ID: {USER_ID}") + # 2. Confirmed ID display + refresh_display(f"Confirmed: {USER_ID}") + time.sleep(1) + CURRENT_STATE = STATE_RATING - print("Please select rating (1-5) and press send.") + refresh_display() elif CURRENT_STATE == STATE_RATING: if RATING == 0: - print("Please select a rating first!") activate_sendPin() return - print(f"Confirmed Rating: {RATING}") - print(f"Sending request: User {USER_ID}, Rating {RATING}") + # 4. Sending / Success / Error + refresh_display("Sending...") - # send a request to the access point - try: - wifi_client.send_request(USER_ID, RATING) - except Exception as e: - print(f"Error sending request: {e}") + success = wifi_client.send_request(USER_ID, RATING) + + if success: + refresh_display("Success") + else: + refresh_display("Error") + + time.sleep(5) # Display status for 5 seconds # Reset for next rating USER_ID = 0 RATING = 0 CURRENT_STATE = STATE_USER_ID - print("Done. Ready for next User ID input.") + refresh_display() # start the rating program -def start_rating(rate_button_pins:list, button_send_pin:int) -> None: - global RATE_BUTTONS, BUTTON_SEND +def start_rating(rate_button_pins:list, button_send_pin:int, oled_display=None) -> None: + global RATE_BUTTONS, BUTTON_SEND, DISPLAY + + DISPLAY = oled_display # initialize all pins as buttons and add them to the list RATE_BUTTONS = [] @@ -102,8 +146,8 @@ def start_rating(rate_button_pins:list, button_send_pin:int) -> None: # initialize send button BUTTON_SEND = initialize_pin(button_send_pin) - print(f"System started. Pins: {rate_button_pins}, Send: {button_send_pin}") - print("Please enter User ID (binary) and press send.") + print("System started.") + refresh_display() while True: time.sleep(1) diff --git a/pico_2026/ssd1306.py b/pico_2026/ssd1306.py new file mode 100644 index 0000000..cb2eb6f --- /dev/null +++ b/pico_2026/ssd1306.py @@ -0,0 +1,158 @@ +# MicroPython SSD1306 OLED driver, I2C and SPI interfaces + +from micropython import const +import framebuf + + +# register definitions +SET_CONTRAST = const(0x81) +SET_ENTIRE_ON = const(0xA4) +SET_NORM_INV = const(0xA6) +SET_DISP = const(0xAE) +SET_MEM_ADDR = const(0x20) +SET_COL_ADDR = const(0x21) +SET_PAGE_ADDR = const(0x22) +SET_DISP_START_LINE = const(0x40) +SET_SEG_REMAP = const(0xA0) +SET_MUX_RATIO = const(0xA8) +SET_COM_OUT_DIR = const(0xC0) +SET_DISP_OFFSET = const(0xD3) +SET_COM_PIN_CFG = const(0xDA) +SET_DISP_CLK_DIV = const(0xD5) +SET_PRECHARGE = const(0xD9) +SET_VCOM_DESEL = const(0xDB) +SET_CHARGE_PUMP = const(0x8D) + + +# Subclassing FrameBuffer provides support for graphics primitives +# http://docs.micropython.org/en/latest/pyboard/library/framebuf.html +class SSD1306(framebuf.FrameBuffer): + def __init__(self, width, height, external_vcc): + self.width = width + self.height = height + self.external_vcc = external_vcc + self.pages = self.height // 8 + self.buffer = bytearray(self.pages * self.width) + super().__init__(self.buffer, self.width, self.height, framebuf.MONO_VLSB) + self.init_display() + + def init_display(self): + for cmd in ( + SET_DISP | 0x00, # off + # address setting + SET_MEM_ADDR, + 0x00, # horizontal + # resolution and layout + SET_DISP_START_LINE | 0x00, + SET_SEG_REMAP | 0x01, # column addr 127 is mapped to SEG0 + SET_MUX_RATIO, + self.height - 1, + SET_COM_OUT_DIR | 0x08, # scan from COM[N] to COM0 + SET_DISP_OFFSET, + 0x00, + SET_COM_PIN_CFG, + 0x02 if self.width > 2 * self.height else 0x12, + # timing and driving scheme + SET_DISP_CLK_DIV, + 0x80, + SET_PRECHARGE, + 0x22 if self.external_vcc else 0xF1, + SET_VCOM_DESEL, + 0x30, # 0.83*Vcc + # display + SET_CONTRAST, + 0xFF, # maximum + SET_ENTIRE_ON, # output follows RAM contents + SET_NORM_INV, # not inverted + # charge pump + SET_CHARGE_PUMP, + 0x10 if self.external_vcc else 0x14, + SET_DISP | 0x01, + ): # on + self.write_cmd(cmd) + self.fill(0) + self.show() + + def poweroff(self): + self.write_cmd(SET_DISP | 0x00) + + def poweron(self): + self.write_cmd(SET_DISP | 0x01) + + def contrast(self, contrast): + self.write_cmd(SET_CONTRAST) + self.write_cmd(contrast) + + def invert(self, invert): + self.write_cmd(SET_NORM_INV | (invert & 1)) + + def show(self): + x0 = 0 + x1 = self.width - 1 + if self.width == 64: + # SSD1306 only has 128x64 pixels. For 64x48 displays, + # the columns are offset by 32. + x0 += 32 + x1 += 32 + self.write_cmd(SET_COL_ADDR) + self.write_cmd(x0) + self.write_cmd(x1) + self.write_cmd(SET_PAGE_ADDR) + self.write_cmd(0) + self.pages - 1 + self.write_cmd(self.pages - 1) + self.write_data(self.buffer) + + +class SSD1306_I2C(SSD1306): + def __init__(self, width, height, i2c, addr=0x3C, external_vcc=False): + self.i2c = i2c + self.addr = addr + self.temp = bytearray(2) + self.write_list = [b"\x40", None] # Co=0, D/C#=1 + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.temp[0] = 0x00 # Co=1, D/C#=0 + self.temp[1] = cmd + self.i2c.writeto(self.addr, self.temp) + + def write_data(self, buf): + self.write_list[1] = buf + self.i2c.writevto(self.addr, self.write_list) + + +class SSD1306_SPI(SSD1306): + def __init__(self, width, height, spi, dc, res, cs, external_vcc=False): + self.rate = 10 * 1024 * 1024 + dc.init(dc.OUT, value=0) + res.init(res.OUT, value=0) + cs.init(cs.OUT, value=1) + self.spi = spi + self.dc = dc + self.res = res + self.cs = cs + import time + + self.res(1) + time.sleep_ms(1) + self.res(0) + time.sleep_ms(10) + self.res(1) + super().__init__(width, height, external_vcc) + + def write_cmd(self, cmd): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(0) + self.cs(0) + self.spi.write(bytearray([cmd])) + self.cs(1) + + def write_data(self, buf): + self.spi.init(baudrate=self.rate, polarity=0, phase=0) + self.cs(1) + self.dc(1) + self.cs(0) + self.spi.write(buf) + self.cs(1) diff --git a/pico_2026/wifi_client.py b/pico_2026/wifi_client.py index 20e91ba..1707334 100644 --- a/pico_2026/wifi_client.py +++ b/pico_2026/wifi_client.py @@ -39,20 +39,25 @@ def wifi_connect(SSID:str, PASSWORD:str) -> None: # send a HTTP-request to the access point with the rate value -def send_request(user_id:int, rating:int) -> None: +def send_request(user_id:int, rating:int) -> bool: # create HTTP-URL URL = f"http://{IP_ADRESS_SERVER}:{str(SERVER_PORT)}/ratings/{user_id}/{rating}" print(URL) - # create request - response = urequests.get(URL) + try: + # create request + response = urequests.get(URL) - # print HTTP-answer of the access point - print("Server response:") - print(response.text) - print() - response.close() + # print HTTP-answer of the access point + print("Server response:") + print(response.text) + print() + response.close() + return True + except Exception as e: + print(f"Request failed: {e}") + return False # test, if there can be a connection to the given ip adress and port