diff --git a/Makefile b/Makefile index 28ef669..f56e6f8 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,10 @@ $(BUILD_DIR)/$(TARGET): # ACTIONS +.PHONY: usermod +usermod: + sudo usermod -a -G adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,render,netdev,lpadmin,gpio,i2c,spi $(shell whoami) + .PHONY: remove-passwd remove-passwd: sudo passwd -d $(shell whoami) @@ -26,11 +30,7 @@ remove-passwd: .PHONY: apt-install apt-install: sudo apt update - sudo apt install -y weston seatd git libglfw3-dev libgl-dev libasound2-dev libv4l-dev python3-pip python3-smbus i2c-tools - -.PHONY: pip-install -pip-install: - pip3 install luma.oled --break-system-packages + sudo apt install -y weston seatd git libglfw3-dev libgl-dev libasound2-dev libv4l-dev i2c-tools python3-gpiozero python3-evdev python3-luma.oled .PHONY: disable-cloud-init disable-cloud-init: @@ -45,7 +45,7 @@ setup-services: sudo systemctl enable getty@tty1.service seatd.service .PHONY: install -install: remove-passwd apt-install pip-install disable-cloud-init setup-services +install: remove-passwd apt-install disable-cloud-init setup-services .PHONY: clean clean: diff --git a/README.md b/README.md index 1d162e3..595cd08 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,10 @@ make -f Makefile.dev rasp-install * https://www.raspberrypi.com/documentation/computers/configuration.html#cmdline * https://int10h.org/oldschool-pc-fonts/fontlist/font?ibm_bios * https://raspberrypihq.com/use-a-push-button-with-raspberry-pi-gpio/ +* https://sourceforge.net/p/raspberry-gpio-python/wiki/BasicUsage/ +* https://sourceforge.net/p/raspberry-gpio-python/wiki/Inputs/ +* https://gpiozero.readthedocs.io/en/stable/ +* https://python-evdev.readthedocs.io/en/latest/tutorial.html#injecting-input ## TODO diff --git a/project/forge_project.cfg b/project/forge_project.cfg index 9a0c7d6..7ce9c52 100644 --- a/project/forge_project.cfg +++ b/project/forge_project.cfg @@ -258,7 +258,7 @@ SAVE_FILE_PREFIX=forge_default_save # R on (qwerty) -KEY_RANDOMIZE=1335 +KEY_RANDOMIZE=1082 # SHIFT+R (qwerty) KEY_RESET=11082 # D (qwerty) diff --git a/pyproject.toml b/pyproject.toml index 4625127..865b2f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,8 @@ authors = [ ] requires-python = ">=3.14" dependencies = [ + "evdev>=2.0.0", + "gpiozero>=2.0.1.post3", "luma-oled>=3.16.0", ] diff --git a/src/forge_invar/__init__.py b/src/forge_invar/__init__.py index 879f9e7..487ef1d 100644 --- a/src/forge_invar/__init__.py +++ b/src/forge_invar/__init__.py @@ -5,12 +5,13 @@ import time from .display import get_display from .forge_data import ForgeData from .i2c import I2C +from .input import Inputs from .tools import debug_img, gen_img, sentences def main_loop() -> None: t0 = time.time() - with ForgeData() as data, I2C() as device: + with ForgeData() as data, I2C() as device, Inputs(): device.update_display(get_display(data, t0)) while True: @@ -26,7 +27,9 @@ def main() -> None: parser = argparse.ArgumentParser() parser.add_argument( - "--mode", default="default", choices=["default", "gen-img", "debug-img", "sentences"] + "--mode", + default="default", + choices=["default", "gen-img", "debug-img", "sentences"], ) args = parser.parse_args() diff --git a/src/forge_invar/constants.py b/src/forge_invar/constants.py index f256606..af11fe6 100644 --- a/src/forge_invar/constants.py +++ b/src/forge_invar/constants.py @@ -1,5 +1,7 @@ import pathlib +from evdev import ecodes + PROJECT_PATH = pathlib.Path(__file__).parent.parent.parent RESOURCES_PATH = PROJECT_PATH / "resources" @@ -15,7 +17,7 @@ FONT_2_PATH = RESOURCES_PATH / "Px437_IBM_BIOS.ttf" LOADING_IMG_PATH = RESOURCES_PATH / "loading.png" BACKGROUND_IMG_PATH = RESOURCES_PATH / "background.png" -ESTIMATED_BOOT_TIME_SECONDS = 18.0 +ESTIMATED_BOOT_TIME_SECONDS = 5.0 SELECTED_RECT_BOUNDS = [ (0, 0, 0, 0), @@ -53,3 +55,6 @@ EXTRA_GROUP = 3 GROUPS_DELTA = [0, 12, 33] GROUPS_SIZE = [6, 7, 2] + +GPIO_BOUNCE = 0.05 +GPIO_BUTTONS = [(21, [ecodes.KEY_R])] diff --git a/src/forge_invar/forge_data.py b/src/forge_invar/forge_data.py index 71db7b8..868615a 100644 --- a/src/forge_invar/forge_data.py +++ b/src/forge_invar/forge_data.py @@ -5,6 +5,7 @@ import typing from .constants import ( BACKUP_INTERVAL, + ESTIMATED_BOOT_TIME_SECONDS, EXTRA_GROUP, FORGE_SAVE_BACKUP_PATH, FORGE_SAVE_PATH, @@ -74,6 +75,8 @@ class ForgeData: self.state = ForgeState.RUNNING elif "time" in self.__data and self.time == 0: self.state = ForgeState.STARTED + elif self.time > 0 and self.time < ESTIMATED_BOOT_TIME_SECONDS: + self.state = ForgeState.RUNNING def update(self) -> bool: if self.__data.read(): diff --git a/src/forge_invar/input.py b/src/forge_invar/input.py new file mode 100644 index 0000000..bfd7693 --- /dev/null +++ b/src/forge_invar/input.py @@ -0,0 +1,43 @@ +import typing + +import gpiozero +from evdev import ecodes, uinput + +from .constants import GPIO_BOUNCE, GPIO_BUTTONS + + +class Button: + def __init__(self, ui: uinput.UInput, gpio_pin: int, keycodes: list[int]) -> None: + self.__ui = ui + self.__btn = gpiozero.Button(gpio_pin, pull_up=True, bounce_time=GPIO_BOUNCE) + self.__keycodes = keycodes + self.__btn.when_activated = self.press + self.__btn.when_deactivated = self.release + + def press(self) -> None: + for keycode in self.__keycodes: + self.__ui.write(ecodes.EV_KEY, keycode, 1) + self.__ui.syn() + + def release(self) -> None: + for keycode in self.__keycodes: + self.__ui.write(ecodes.EV_KEY, keycode, 0) + self.__ui.syn() + + +class Inputs: + def __init__(self): + self.__buttons: list[Button] = [] + self.__ui: uinput.UInput | None = None + + def __enter__(self) -> typing.Self: + self.__ui = uinput.UInput() + + for gpio_pin, keycodes in GPIO_BUTTONS: + self.__buttons += [Button(self.__ui, gpio_pin, keycodes)] + + return self + + def __exit__(self, *_) -> None: + assert self.__ui + self.__ui.close() diff --git a/system/hdmi/start.sh b/system/hdmi/start.sh index 4ec9979..4f14f4c 100755 --- a/system/hdmi/start.sh +++ b/system/hdmi/start.sh @@ -9,7 +9,9 @@ cd $PROJECT_DIR rm -rf *.log touch * +sudo chmod a+rw /dev/uinput + weston --shell=kiosk-shell.so --xwayland --config=${PROJECT_DIR}/system/${WESTON_MOD}/weston.ini --log=${PROJECT_DIR}/weston.log \ -- make --silent start 2>&1 > /dev/null -reboot 2>&1 /dev/null +# sudo reboot 2>&1 /dev/null diff --git a/system/ntsc/start.sh b/system/ntsc/start.sh index 1ede145..a0e4856 100755 --- a/system/ntsc/start.sh +++ b/system/ntsc/start.sh @@ -9,7 +9,9 @@ cd $PROJECT_DIR rm -rf *.log touch * +sudo chmod a+rw /dev/uinput + weston --shell=kiosk-shell.so --xwayland --config=${PROJECT_DIR}/system/${WESTON_MOD}/weston.ini --log=${PROJECT_DIR}/weston.log \ -- make --silent start 2>&1 > /dev/null -reboot 2>&1 /dev/null +sudo reboot 2>&1 /dev/null diff --git a/system/pal/start.sh b/system/pal/start.sh index 1d1b581..a926c2c 100755 --- a/system/pal/start.sh +++ b/system/pal/start.sh @@ -9,7 +9,9 @@ cd $PROJECT_DIR rm -rf *.log touch * +sudo chmod a+rw /dev/uinput + weston --shell=kiosk-shell.so --xwayland --config=${PROJECT_DIR}/system/${WESTON_MOD}/weston.ini --log=${PROJECT_DIR}/weston.log \ -- make --silent start 2>&1 > /dev/null -reboot 2>&1 /dev/null +sudo reboot 2>&1 /dev/null diff --git a/uv.lock b/uv.lock index c966893..8001f1c 100644 --- a/uv.lock +++ b/uv.lock @@ -42,11 +42,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/c0/e27a1e516a89af7194fc497f4b96d9601771ca41bb66fd5738113df80282/cbor2-6.1.5-cp315-cp315t-win_arm64.whl", hash = "sha256:fd34b35b0a2b366f5b4bd53489ccd10d7576b0d4dd68db38ef64b4e617ea8f76", size = 294495, upload-time = "2026-10-01T18:09:32.192Z" }, ] +[[package]] +name = "colorzero" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/ca/688824a06e8c4d04c7d2fd2af2d8da27bed51af20ee5f094154e1d680334/colorzero-2.0.tar.gz", hash = "sha256:e7d5a5c26cd0dc37b164ebefc609f388de24f8593b659191e12d85f8f9d5eb58", size = 25382, upload-time = "2021-03-15T23:42:23.261Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/a6/ddd0f130e44a7593ac6c55aa93f6e256d2270fd88e9d1b64ab7f22ab8fde/colorzero-2.0-py2.py3-none-any.whl", hash = "sha256:0e60d743a6b8071498a56465f7719c96a5e92928f858bab1be2a0d606c9aa0f8", size = 26573, upload-time = "2021-03-15T23:42:21.757Z" }, +] + +[[package]] +name = "evdev" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/07/eb30593524303d1367f0dc00c1c66582c994dba412a47751e34bad9fa29b/evdev-2.0.0.tar.gz", hash = "sha256:442fb3f4c8dfc9e61e901133c356220c02d663eca8f34722e0cecdd637eba504", size = 33766, upload-time = "2026-08-23T10:31:26.835Z" } + [[package]] name = "forge-invar" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "evdev" }, + { name = "gpiozero" }, { name = "luma-oled" }, ] @@ -57,7 +77,11 @@ dev = [ ] [package.metadata] -requires-dist = [{ name = "luma-oled", specifier = ">=3.16.0" }] +requires-dist = [ + { name = "evdev", specifier = ">=2.0.0" }, + { name = "gpiozero", specifier = ">=2.0.1.post3" }, + { name = "luma-oled", specifier = ">=3.16.0" }, +] [package.metadata.requires-dev] dev = [ @@ -65,6 +89,18 @@ dev = [ { name = "ty", specifier = ">=0.0.92" }, ] +[[package]] +name = "gpiozero" +version = "2.0.1.post3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorzero" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/63/864a3c36842cf7b05808aadf906763a024a1b13f2a4c8144e3163de91255/gpiozero-2.0.1.post3.tar.gz", hash = "sha256:745feab6df463ac2e9de10c67e2dd9f396e668ba4e281e92381d6c460100a8f7", size = 168403, upload-time = "2026-07-27T10:03:32.628Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/cb/ec5917b454b2e82874769a348dc74cfcec5b447bf1061c827edea9702451/gpiozero-2.0.1.post3-py3-none-any.whl", hash = "sha256:d491734803a9bc6036602e6b8bbd73fb25c33da88de0fcd11a40e890b8bc2d3b", size = 151465, upload-time = "2026-07-27T10:03:31.195Z" }, +] + [[package]] name = "luma-core" version = "2.6.0" @@ -166,6 +202,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" }, ] +[[package]] +name = "setuptools" +version = "84.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/b3/c19426f783614d625699a4b247faf628ea9ab1db228674491767b417f9e1/setuptools-84.2.1.tar.gz", hash = "sha256:3ff8b3b5492a0aa66960f4419365a86b84e013070d4313e4d4a6894f5d64d6bc", size = 1230289, upload-time = "2026-10-20T19:01:37.742Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/55/6dc6d6bb953bdfc52519a0059592c52b6e3b8ad51cf8256dffceb55b0610/setuptools-84.2.1-py3-none-any.whl", hash = "sha256:ca140b45d5325a9478f880e351484f62fef15f336462e449ca0c03c873d589c2", size = 876263, upload-time = "2026-10-20T19:01:35.678Z" }, +] + [[package]] name = "smbus2" version = "0.6.1"