diff --git a/config.json b/config.json deleted file mode 100644 index 2bfe55c..0000000 --- a/config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "debug": 1, - "logging": 1, - "loglevel": 4, - "logfile": "petcard.log", - "display": "SSD1306", - "controls": { - "up": "0", - "down": "3", - "select": "1", - "back": "2" - } -} \ No newline at end of file diff --git a/lib/ssd1306.py b/lib/ssd1306.py deleted file mode 100644 index 1bb6679..0000000 --- a/lib/ssd1306.py +++ /dev/null @@ -1,155 +0,0 @@ -# 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 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: - # displays with width of 64 pixels are shifted 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.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] = 0x80 # 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) \ No newline at end of file diff --git a/main.py b/main.py index 2b23097..232de12 100644 --- a/main.py +++ b/main.py @@ -5,81 +5,79 @@ # Licensed under the MIT license # -# -# Importing libraries -# - from machine import Pin -import time - -# -# Define global variables -# +import machine +#import network, urequest, machine +# Define variables CONSOLE_WELCOME = """ Petcard Bootloader - v1.0 Copyright (c) 2025 FreedTapestry21 Licensed under the MIT license """ +UPDATE = False +MIRROR = "https://mirror.pixelated.sh/dev/petcard" # Initialize OK button (with pull down resistor) -ok = Pin(1, Pin.IN, Pin.PULL_DOWN) +ok = Pin(0, Pin.IN, Pin.PULL_DOWN) -# -# Function(s) -# +# Define functions +def download(file, name): + print("Downloading " + name + "...") + f = open(file) + r = urequests.get(MIRROR + "/" + file) + f.write(r.text) + f.close() + r.close + print("Downloaded " + name + "!") + +def connect(ssid, psk): + nic = network.WLAN(network.WLAN.IF_STA) + nic.active(True) + nic.connect(ssid, psk) + while not sta_if.isconnected(): + pass + +def boot(): + print("Loading system...") + with open("petcard.py") as app: + exec(app.read()) def main(): - # Check if a boot interuption was triggered (you can trigger this by holding down the OK button) and wait for 10 seconds - # In later revisions I'd like this to trigger some kind of update screen to easily update the Petcard - INTERUPT = False - if ok.value() == 1: - INTERUPT = True - - if INTERUPT == True: return 1 - else: return 0 - -# -# Classes -# - -class Instance: - def __init__(self): - self.config_manager = system.ConfigurationManager() - self.logger = system.Logger(self.config_manager.config) - self.pet = system.Pet(self.config_manager, self.logger) - self.input_controller = system.InputController(self.config_manager.config["controls"], self.logger) - - # Select display driver - if self.config_manager.config["display"] == "SSD1306": - self.display_driver = system.SSD1306Driver() - else: - self.logger.log(f"No available driver was found for display {self.config_manager.config['display']}!", 0) - self.display_driver = system.DisplayDriver() # DO NOT REMOVE - - self.display = system.Display(self.display_driver, 128, 64, self.logger) - self.ui = system.UserInterface(self.display, self.input_controller, self.logger) - self.power = system.Power(self.config_manager, self.display, self.input_controller, self.logger) - -# -# Entry point -# - -if __name__ == '__main__': - if main() == 1: - time.sleep(10) - machine.soft_reset() print(CONSOLE_WELCOME) - print("Importing SYSTEM module...") - import system - print("Importing APP module...") - import petcard - print("Starting Petcard") - instance = Instance() - app = petcard.Application(instance.config_manager, instance.logger, instance.pet, instance.display_driver, instance.display, instance.input_controller, instance.ui, instance.power) - app.run() + UPDATE = False + if ok.value() == 1: + UPDATE = True + + if UPDATE == True: + print("Petcard bootloader has been interupted by the OK button") + inp = input("Are you sure you want to enter the Petcard recovery process? (y/N)") + if inp == "" or inp == "n" or inp == "N": + print("Rebooting to resume boot process...") + machine.soft_reset() + if inp == "y" or inp == "Y": + print("Entering Petcard recovery process...") + print("Please enter your Wi-Fi credentials") + ssid = input("SSID: ") + psk = input("Password: ") + #connect(ssid, psk) + print('Connected to network "' + str(ssid) + '"') + + # Downloading system.py + # Disabled due to being stuck with a Pico for now + #download("system.py", "Petcard system") + #download("petcard.py", "Petcard") + print("System recovery complete, restarting system...") + machine.soft_reset() + else: + print("'" + inp + "' is not recognized as an option! Rebooting...") + machine.soft_reset() + else: + boot() -# -# End Of File (EOF) -# \ No newline at end of file + + +# Check if button is pressed for recovery/update process +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/petcard.py b/petcard.py deleted file mode 100644 index 1baa96a..0000000 --- a/petcard.py +++ /dev/null @@ -1,90 +0,0 @@ -# -# Petcard Application - v1.0 -# Copyright (c) 2025 FreedTapestry21 -# -# Licensed under the MIT license -# - -# -# Importing libraries -# - -import sys, json, machine, _thread, time, random - -# Import SYSTEM module -import system - -# -# Classes -# - -# Main system & application controller -class Application: - def __init__(self, config_manager, logger, pet, display_driver, display, input_controller, ui, power): - self.config_manager = config_manager - self.logger = logger - self.pet = pet - self.display_driver = display_driver - self.display = display - self.input_controller = input_controller - self.ui = ui - self.power = power - - # Initializes all system components - def initialize(self): - self.logger.log("Initializing Petcard System...", 3) - self.display.initialize() - self.logger.log("Initializing Petcard health manager...", 3) - _thread.start_new_thread(self._pet_health_manager, ()) - self.logger.log("System initialization complete", 3) - - # Main system loop - def run(self): - try: - self.initialize() - - # Create main menu - main_menu_items = [ - system.MenuItem("Food", self._action, "f"), - system.MenuItem("Drinks", self._action, "d"), - system.MenuItem("Play", self._action, "p"), - system.MenuItem("Save", self._save), - system.MenuItem("Sleep", self.power.sleep) - ] - - main_menu = system.Menu(main_menu_items, self.logger) - - # Main menu loop - while True: - selected_item = self.ui.show_menu(main_menu) - if selected_item: - result = selected_item.execute() - - except Exception as e: - self.logger.log(f"System error: {e}", 0) - self.power.shutdown("System encountered an error") - - # Defines what to do when a certain action is triggered - def _action(self, act): - if act == "f": - self.pet.nom("pancakes", 10) - elif act == "d": - self.pet.slurp("water", 10) - elif act == "p": - self.pet.play("petted", 10) - else: self.logger.log(f"Invalid action {act}", 1) - - return - - def _save(self): - self.logger.log("Saving game progression...", 4) - self.config_manager.save_config() - self.logger.log("Game progression has been saved!", 4) - - def _pet_health_manager(self): - while True: - time.sleep(random.randint(10, 60)) - self.pet.wombicombi() -# -# End Of File (EOF) -# \ No newline at end of file diff --git a/system.py b/system.py index 545bd11..4f02430 100644 --- a/system.py +++ b/system.py @@ -5,374 +5,108 @@ # Licensed under the MIT license # -# # Importing libraries -# +import sys, time, machine, framebuf, ssd1306, _thread -import sys, time, json, machine, framebuf, ssd1306, random +# Start Petcard frontend on core 1 +with open("petcard.py") as app: + _thread.start_new_thread(exec(app.read()), ()) -# -# Classes -# +# Initialize global variables +CONFIG = json.loads(open("config.json", "r").read()) -# Manages system configuration -class ConfigurationManager: - def __init__(self, config_file="config.json"): - self.config_file = config_file - self.config = self._load_config() - - # Load configuration from file - def _load_config(self): - try: - with open(self.config_file, "r") as file: - return json.loads(file.read()) - except Exception as e: - print(f"Failed to load config: {e}") - return self._get_default_config() - - def save_config(self): - try: - with open(self.config_file, "w") as file: - json.dump(self.config, file) - except Exception as e: - print(f"Failed to save config: {e}") - - # Return default configuration - def _get_default_config(self): - return { - "debug": 1, - "logging": 0, - "loglevel": 3, - "logfile": "petcard.log", - "display": "SSD1306", - "controls": { - "up": "0", - "down": "3", - "select": "1", - "back": "2" - }, - "pet": { - "name": "Pet", - "level": 0, - "happiness": 100, - "hunger": 100, - "thirst": 100, - } - } +TEXT_HEIGHT = 8 +PADDING = 4 -# Handles logging functionality with timestamp formatting -class Logger: - def __init__(self, config): - self.log_level = config.get("loglevel") - self.logging_enabled = config.get("logging") - self.log_file = config.get("logfile") - - def log(self, message, log_level, ignore_console=False): - timestamp = self._get_timestamp() - - if log_level == 0: - formatted_message = f"[CRITICAL] [{timestamp}] {message}" - elif log_level == 1: - formatted_message = f"[ERROR] [{timestamp}] {message}" - elif log_level == 2: - formatted_message = f"[WARNING] [{timestamp}] {message}" - elif log_level == 3: - formatted_message = f"[INFO] [{timestamp}] {message}" - elif log_level == 4: - formatted_message = f"[DEBUG] [{timestamp}] {message}" - - if self.log_level <= log_level: - if not ignore_console: - print(formatted_message) - - if self.logging_enabled == 1: - self._write_to_file(formatted_message) - - def _get_timestamp(self): - # Generate formatted timestamp - date = time.localtime() - return f"{date[0]}-{date[1]}-{date[2]} {date[3]}:{date[4]}:{date[5]}" - - def _write_to_file(self, message): - # Write message to log file - try: - with open(self.log_file, "a") as file: - file.write(message + '\n') - except Exception as e: - print(f"Failed to write to log file: {e}") - - # Get configuration value - def get(self, key, default=None): - return self.config.get(key, default) - -class Pet: - def __init__(self, config, logger): - self.config = config - self.logger = logger - - def play(self, name, amount): - self.config.config["pet"]["happiness"] = self.config.config["pet"]["happiness"] + (amount * ((self.config.config["pet"]["level"] / 100)+1)) - self.logger.log(f"You {self.config.config['pet']['name']} {name} and gained {str(amount)} of happiness! Your happiness bar is now at {str(self.config.config['pet']['happiness'])}!", 3) - - def nom(self, name, amount): - self.config.config["pet"]["hunger"] = self.config.config["pet"]["hunger"] + (amount * ((self.config.config["pet"]["level"] / 100)+1)) - self.logger.log(f"You fed {self.config.config['pet']['name']} {name} and gained {str(amount)} of food! Your food bar is now at {str(self.config.config['pet']['hunger'])}!", 3) - - def slurp(self, name, amount): - self.config.config["pet"]["thirst"] = self.config.config["pet"]["thirst"] + (amount * ((self.config.config["pet"]["level"] / 100)+1)) - self.logger.log(f"You gave {self.config.config['pet']['name']} {name} and gained {str(amount)} of hydration! Your hydration bar is now at {str(self.config.config['pet']['thirst'])}!", 3) - - def wombicombi(self): - self.logger.log("Health depletion triggered", 4) - self.config.config["pet"]["happiness"] = round(self.config.config["pet"]["happiness"] - ((100 - self.config.config["pet"]["level"]) * random.uniform(0.0, 0.2))) - self.config.config["pet"]["hunger"] = round(self.config.config["pet"]["hunger"] - ((100 - self.config.config["pet"]["level"]) * random.uniform(0.0, 0.2))) - self.config.config["pet"]["thirst"] = round(self.config.config["pet"]["thirst"] - ((100 - self.config.config["pet"]["level"]) * random.uniform(0.0, 0.2))) - self.logger.log(f"Health is now at the following values...", 4) - self.logger.log(f"Happiness: {self.config.config['pet']['happiness']}", 4) - self.logger.log(f"Food: {self.config.config['pet']['hunger']}", 4) - self.logger.log(f"Hydration: {self.config.config['pet']['thirst']}", 4) - -# Handles input from buttons/controls -class InputController: - def __init__(self, control_config, logger): - self.controls = control_config - self.logger = logger - self.pins = {} - self._initialize_pins() - - # Initialize pin objects for all controls - def _initialize_pins(self): - for control_name, pin_number in self.controls.items(): - try: - self.pins[control_name] = machine.Pin( - int(pin_number), - machine.Pin.IN, - machine.Pin.PULL_DOWN - ) - if control_name == "select": - self.pins["select"].irq(trigger=machine.Pin.IRQ_RISING, handler=lambda p:None) - self.logger.log(f"Set interupt pin to {control_name}", 4) - except Exception as e: - self.logger.log(f"Failed to initialize pin for {control_name}: {e}", 1) - - # Wait for and return the first button press detected - def wait_for_input(self): - while True: - for control_name, pin in self.pins.items(): - try: - if pin.value() == 1: - time.sleep(0.05) # Debounce delay - if pin.value() == 1: # Confirm press - return control_name - except Exception as e: - self.logger.log(f"Error reading pin {control_name}: {e}", 1) - time.sleep(0.01) # Small delay to prevent excessive polling - -# Base class for display drivers - implement all methods in subclasses -class DisplayDriver: - def initialize(self): - raise NotImplementedError("DisplayDriver subclass must implement initialize()") - - def show(self): - raise NotImplementedError("DisplayDriver subclass must implement show()") - - def clear(self): - raise NotImplementedError("DisplayDriver subclass must implement clear()") - - def blit(self, buffer, x=0, y=0): - raise NotImplementedError("DisplayDriver subclass must implement blit()") - -# SSD1306 OLED display driver implementation -class SSD1306Driver(DisplayDriver): - def __init__(self, width=128, height=64, sda_pin=4, scl_pin=5): - self.width = width - self.height = height - self.sda_pin = sda_pin - self.scl_pin = scl_pin - self.display = None - self.i2c = None - - # Initializes the SSD1306 display - def initialize(self): - self.i2c = machine.I2C(0, sda=machine.Pin(self.sda_pin), scl=machine.Pin(self.scl_pin)) - self.display = ssd1306.SSD1306_I2C(self.width, self.height, self.i2c) - - # Updates the physical display - def show(self): - if self.display: - self.display.show() - - # Clears the display - def clear(self): - if self.display: - self.display.fill(0) - - # Blit buffer to display - def blit(self, buffer, x=0, y=0): - if self.display: - self.display.blit(buffer, x, y) - - -# Main display controller with frame buffer management +# Initialize classes class Display: - def __init__(self, driver, width, height, logger): - self.width = width - self.height = height - self.driver = driver - self.logger = logger - self.pages = height // 8 - self.buffer = framebuf.FrameBuffer( - bytearray(self.pages * width), - width, - height, - framebuf.MONO_VLSB - ) + def __init__(self, disp, x, y) -> None: + self.x = x + self.y = y + self.type = disp + self.pages = y // 8 + self.buffer = framebuf.FrameBuffer(bytearray(self.pages * x), x, y, framebuf.MONO_VLSB) + + def log(self, msg, IGN=False): # IGN => IGNore to report to console (Only log) + if CONFIG["debug"] == 1: + date = time.localtime() + stamp = str(date[0]) + "-" + str(date[1]) + "-" + str(date[2]) + " " + str(date[3]) + ":" + str(date[4]) + ":" + str(date[5]) + if IGN == False: + text = "[" + stamp + "] " + msg + print(text) + else: + if msg == "": + text = msg + else: + text = "[" + stamp + "] " + msg + if CONFIG["logging"] == 1: # Write log to log file + with open(CONFIG["logfile"], "a") as file: + file.write(text + '\n') + file.close() - # Initializes the display driver - def initialize(self): - try: - self.driver.initialize() - self.logger.log("Display initialized successfully", 4) - except Exception as e: - self.logger.log(f"Display initialization failed: {e}", 0) - - # Update the physical display with buffer contents + def init(self): + if self.type == "SSD1306": + self.i2c = machine.I2C(0, sda=machine.Pin(4), scl=machine.Pin(5)) + self.display = ssd1306.SSD1306_I2C(128, 64, self.i2c) + def update(self): - try: - self.driver.blit(self.buffer, 0, 0) - self.driver.show() - except Exception as e: - self.logger.log(f"Display update failed: {e}", 1) - - # Clear the display buffer - def clear(self): - self.buffer.fill(0) - - # Draw text on the buffer - def draw_text(self, text, x, y, color=1): - self.buffer.text(text, x, y, color) - - # Draw filled rectangle on the buffer - def draw_filled_rect(self, x, y, width, height, color=1): - self.buffer.fill_rect(x, y, width, height, color) + self.display.blit(self.buffer, 0, 0) + self.display.show() -# Object for a single menu item, can be passed through to the Menu class -class MenuItem: - def __init__(self, text, action=None, args=None): - self.text = text - self.action = action - self.args = args +class Controls: + def __init__(self): + pass - # Execute the menu item's action - def execute(self): - if callable(self.action) == True: - if self.args: return self.action(self.args) - else: return self.action() - return self.action + def waitfor(self): + while True: + for x in CONFIG["controls"]: + if machine.Pin(int(CONFIG["controls"][x]), machine.Pin.IN, machine.Pin.PULL_DOWN).value() == 1: + for y in CONFIG["controls"]: + if CONFIG["controls"][y] == x: + return y + else: pass -# Menu system with navigation and rendering -class Menu: - - TEXT_HEIGHT = 8 - PADDING = 4 - - def __init__(self, items, logger): - self.items = [item if isinstance(item, MenuItem) else MenuItem(str(item)) for item in items] - self.logger = logger - self.selection = 0 - - # Handles menu navigation selection - def navigate(self, direction): - if direction == "up": - self.selection = (self.selection - 1) % len(self.items) # The percentage ensures the result wraps around within the valid range of the list - elif direction == "down": - self.selection = (self.selection + 1) % len(self.items) - - # Gets the currently selected menu item - def get_selected_item(self): - if 0 <= self.selection < len(self.items): - return self.items[self.selection] - return None - - def render(self, display): - # Render menu to display - display.clear() - - # Draw selection highlight - highlight_y = self.selection * (self.TEXT_HEIGHT + self.PADDING) + 2 - display.draw_filled_rect(0, highlight_y, display.width, 10, 1) - - # Draw menu items - y_pos = self.PADDING - for i, item in enumerate(self.items): - if y_pos < display.height: - if i == self.selection: color = 0 # Normal color for non-selected item - else: color = 1 # Inverted color for selected item - display.draw_text(item.text, 0, y_pos, color) - y_pos += self.TEXT_HEIGHT + self.PADDING - +class UI: + def __init__(self): + self.selection = -1 + def menu(self, display, controls, entries): + if self.selection == -1: + self.selection = 0 + while True: + inp = controls.waitfor() + if inp == "down": + self.selection = self.selection + 1 + if inp == "up": + self.selection = self.selection - 1 + if self.selection >= len(entries): + self.selection = 0 + if self.selection < 0: + self.slection = len(entries) + print(str(self.selection)) + display.buffer.fill(0) + display.buffer.fill_rect(0, int(self.selection*TEXT_HEIGHT) + int(self.selection*PADDING) + 2, display.x, 10, 1) + i = PADDING + q = 0 + for x in entries: + if i < display.y: + if q == self.selection: + display.buffer.text(x, 0, i, 0) + else: + display.buffer.text(x, 0, i, 1) + i = i + TEXT_HEIGHT + PADDING + q = q + 1 + else: + pass + display.update() + time.sleep(0.2) + display.update() + return display -# Main UI controller managing menus and interactions -class UserInterface: - def __init__(self, display, input_controller, logger): - self.display = display - self.input_controller = input_controller - self.logger = logger - - # Displays the menu and handles navigation until selection is made - def show_menu(self, menu): - menu.render(self.display) - - while True: - try: - user_input = self.input_controller.wait_for_input() - self.logger.log(f"Menu input received: {user_input}", 4) - - if user_input in ["up", "down"]: - menu.navigate(user_input) - menu.render(self.display) - elif user_input == "select": - selected_item = menu.get_selected_item() - if selected_item: - return selected_item - elif user_input == "back": - return None - - time.sleep(0.2) # Prevent input spam - - except Exception as e: - self.logger.log(f"Error in menu navigation: {e}", 1) +# Initialize functions -# Handles power management -class Power: - def __init__(self, config, display, input_controller, logger): - self.config = config - self.display = display - self.input_controller = input_controller - self.logger = logger - - def sleep(self): - self.logger.log("Entering sleep mode", 3) - self.logger.log("Turning off display", 4) - self.display.clear() - self.display.update() - self.logger.log("Waiting for select to be pressed...", 4) - while True: - if self.input_controller.wait_for_input() == "select": - break - self.logger.log("Returning from sleep mode", 4) - return - - def shutdown(self, message=""): - # Exit the system with optional message - if message: - self.logger.log(message, 3) - self.logger.log("System shutting down...", 3) - self.display.clear() - self.display.update() - machine.deepsleep() - -# -# End Of File (EOF) -# \ No newline at end of file +def quit(msg=""): + if msg == '': + sys.exit() + else: + print("[" + NAME + "] " + msg) \ No newline at end of file