diff --git a/README.md b/README.md new file mode 100644 index 0000000..03d727d --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +A menu-driven application launcher for a Raspberry Pi Zero with an Adafruit OLED bonnet. diff --git a/cinput.py b/cinput.py new file mode 100644 index 0000000..9b0151c --- /dev/null +++ b/cinput.py @@ -0,0 +1,41 @@ +import board +from digitalio import DigitalInOut, Direction, Pull +from enum import Enum, auto +from time import sleep + +class Button(Enum): + BTN_A = auto() + BTN_B = auto() + BTN_C = auto() + DIR_U = auto() + DIR_R = auto() + DIR_D = auto() + DIR_L = auto() + +class ControlInput: + _buttons = { + Button.BTN_A: DigitalInOut(board.D5), + Button.BTN_B: DigitalInOut(board.D6), + Button.BTN_C: DigitalInOut(board.D4), + Button.DIR_U: DigitalInOut(board.D17), + Button.DIR_R: DigitalInOut(board.D23), + Button.DIR_L: DigitalInOut(board.D27), + Button.DIR_D: DigitalInOut(board.D22), + } + + _pressed = set() + + def __init__(self): + for button in self._buttons: + self._buttons[button].direction = Direction.INPUT + self._buttons[button].pull = Pull.UP + + def get_one_shot(self): + while True: + for button in self._buttons: + if not self._buttons[button].value and not button in self._pressed: + self._pressed.add(button) + return button + elif self._buttons[button].value: + self._pressed.discard(button) + diff --git a/font5x8.bin b/font5x8.bin new file mode 100644 index 0000000..9a0563b Binary files /dev/null and b/font5x8.bin differ diff --git a/graphics.py b/graphics.py new file mode 100644 index 0000000..fa9d7d3 --- /dev/null +++ b/graphics.py @@ -0,0 +1,24 @@ +from board import SCL, SDA +from busio import I2C +from adafruit_ssd1306 import SSD1306_I2C + +class Graphics: + OLED_WIDTH = 128 + OLED_HEIGHT = 64 + OLED_ADDR = 0x3C + LINE_HEIGHT = 9 + MAX_LINES = 7 + + def __init__(self): + i2c = I2C(SCL, SDA) + self._display = SSD1306_I2C( + self.OLED_WIDTH, self.OLED_HEIGHT, i2c, addr = self.OLED_ADDR) + + def clear(self): + self._display.fill(0) + + def text(self, text, x, y, c): + self._display.text(text, x, y, c) + + def show(self): + self._display.show() diff --git a/main.py b/main.py index d5bab10..5a72c5d 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,24 @@ -from menu import Menu -from icecream import ic +import os +from menu import Menu, MenuItem, MenuType +from cinput import ControlInput +from graphics import Graphics -ic(Menu) +menu_config = [ + MenuItem("Information", + MenuType.EXEC_PLUGIN), + MenuItem("Apps", + MenuType.SUB_MENU), + MenuItem("Reboot", + MenuType.EXEC_CMD), + MenuItem("Shutdown", + MenuType.EXEC_CMD)] + +cinput = ControlInput() +graphics = Graphics() +menu = Menu(menu_config, cinput, graphics) +try: + menu.get_selection() +except KeyboardInterrupt: + graphics.clear() + graphics.show() + os._exit(0) diff --git a/menu.py b/menu.py index 0d70ef8..ae94d41 100644 --- a/menu.py +++ b/menu.py @@ -1,5 +1,10 @@ +from board import SCL, SDA +from busio import I2C +from adafruit_ssd1306 import SSD1306_I2C from enum import Enum, auto from dataclasses import dataclass +from cinput import ControlInput, Button +from graphics import Graphics class MenuType(Enum): SUB_MENU = auto() @@ -11,12 +16,35 @@ class MenuItem: display: str menu_type: MenuType -Menu = [ - MenuItem("df -h", - MenuType.EXEC_CMD), - MenuItem("Chess", - MenuType.EXEC_PLUGIN), - MenuItem("Reboot", - MenuType.EXEC_CMD), - MenuItem("Shutdown", - MenuType.EXEC_CMD)] +class Menu: + def __init__( + self, config: list[MenuItem], cinput: ControlInput, graphics: Graphics): + self._selected_index = 0 + self._menu = config + self._input = cinput + self._graphics = graphics + + def _print(self, items: list[MenuItem]): + self._graphics.clear() + top_index = 0 if self._selected_index < Graphics.MAX_LINES else Graphics.MAX_LINES - self._selected_index + 1 + + for idx in range(top_index, top_index + Graphics.MAX_LINES): + if idx >= len(items): + break + marker = "> " if idx == self._selected_index else " " + self._graphics.text(marker + items[idx].display, 0, (idx - top_index) * Graphics.LINE_HEIGHT, 1) + + self._graphics.show() + + def get_selection(self): + while True: + self._print(self._menu) + pressed = self._input.get_one_shot() + if pressed == Button.DIR_U: + self._selected_index -= 1 + if self._selected_index < 0: + self._selected_index = len(self._menu) - 1 + elif pressed == Button.DIR_D: + self._selected_index += 1 + if self._selected_index >= len(self._menu): + self._selected_index = 0