From a74caf0f0a0b601837392750102078c7130e633a Mon Sep 17 00:00:00 2001 From: Rudis Muiznieks Date: Sat, 23 Apr 2022 13:37:54 -0500 Subject: [PATCH] implemented top level menu --- README.md | 1 + cinput.py | 41 +++++++++++++++++++++++++++++++++++++++++ font5x8.bin | Bin 0 -> 1282 bytes graphics.py | 24 ++++++++++++++++++++++++ main.py | 26 +++++++++++++++++++++++--- menu.py | 46 +++++++++++++++++++++++++++++++++++++--------- 6 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 README.md create mode 100644 cinput.py create mode 100644 font5x8.bin create mode 100644 graphics.py 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 0000000000000000000000000000000000000000..9a0563ba2dae1dc1d0b1b7ecb2e54e5113f3f540 GIT binary patch literal 1282 zcmXw3F>l*e5I$10{Ai>OqY^L;uo9&xs-z5NTtlD*ih5KiLlo?$F36^GP|Jyf4E0jD zLuHrZ%h+xn(jh<#Btfzi{RKsb0$utax^(lZ!Ki^k0DC8(cjWQCyLaDxJf5_~fW&+B z;F0H@JUH=8FY!#nObZw6zn4 zjUs@K#rYN*rdNEO1Pkd*mPTsc^S>BBnV1y6d{|k5ZDVrxgKR8=zjT7sOycL8lr6GBq8Oxc(z-zxK-+Mhj)*4rXS~& ziIRIf3is{<)s(5DfD56>lB^Iqhuks)D~cvu)Pulrf*`1)sS78HA_G{e%EsHbKTZTw zlnDe=j{JBBgjNr*o!F)XYfKV~Qm!*Ms#SyTIr-QCQL2j44XQ5ZR$?MQ)8#gIgE}JY zUR+!x!P96r0M^3K1K;57w-^vT*{$8Kxy9G79~at~dya(SQ8|v!{0h?Tm1E>=Me!NG zQT+6@upP(oXdA1Iw&N)J9H5{0Aor&|kopOq;s=<{>UZk1B#Qg=zDbmK zdviD#*lpi0pSOG5n!r!5UQIi_Ua!-^E_K{$R;>=w@cy&A8}p63ncDcr`u)39%^}no zG7qM{Js1o(9nLG~aK5r~I(={b-n7R#eXjlxWKa*xLD)u7-ocauKs%Vu#rfD;6hTA#%t}%>Prt4SAT-VpMl7?3aqa{k4 zVLqUXoV3*lk5&uG7F(QN1fXMU0CVMvP}T_ViUQXNMRL7Zt?B>6U*QUAP+Ey<&~IRl z27O3ULqd9nM;1YJW+XEbeBx1r&Tf+6&^W<-I2>-JvYgM$De7??`o|b>6`oo}i=6Vy zcbD&8n$56j?w(4gyY_Hz&$b7Xf&JT)gPo3tpAk|?lF}wg5R8L>?+*jcgW*2MVp8t! r=UA7qlM~h|w5qNAP4y=Jc^C##(KN7mCP`3|q#~?;uvP#Ug;>IW4$UpB literal 0 HcmV?d00001 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