Update button_controller.py

Fixed bug when running on a non-RPi board.
This commit is contained in:
imonlinux
2025-11-30 00:49:59 -08:00
committed by GitHub
parent 5ef680d734
commit 57b20a1ef1
+25 -11
View File
@@ -9,7 +9,8 @@ This controller:
Implementation note: Implementation note:
- Uses a simple polling loop with RPi.GPIO instead of kernel edge detection. - Uses a simple polling loop with RPi.GPIO instead of kernel edge detection.
- This avoids "Failed to add edge detection" errors on some HATs/overlays. - On non-Raspberry Pi hosts (or where RPi.GPIO is unusable), the controller
cleanly disables itself and logs an info message.
""" """
from __future__ import annotations from __future__ import annotations
@@ -24,10 +25,13 @@ from .models import ServerState
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
try: # Try to import RPi.GPIO, but gracefully handle *any* failure.
import RPi.GPIO as GPIO # On non-RPi hosts, RPi.GPIO may be installed but will raise RuntimeError
except ImportError: # pragma: no cover - non-RPi hosts # at import time ("This module can only be run on a Raspberry Pi!").
GPIO = None try: # pragma: no cover - behavior depends on host platform
import RPi.GPIO as GPIO # type: ignore[import]
except Exception: # ImportError, RuntimeError, etc.
GPIO = None # type: ignore[assignment]
@dataclass @dataclass
@@ -48,6 +52,8 @@ class ButtonController:
- Interprets level transitions as press/release. - Interprets level transitions as press/release.
- Schedules actions on the asyncio loop via loop.call_soon_threadsafe(). - Schedules actions on the asyncio loop via loop.call_soon_threadsafe().
- Uses the EventBus to toggle mic mute. - Uses the EventBus to toggle mic mute.
On non-RPi hosts (or when RPi.GPIO isn't usable), it is effectively a no-op.
""" """
def __init__( def __init__(
@@ -84,13 +90,18 @@ class ButtonController:
self._press_time: float | None = None self._press_time: float | None = None
self._last_level: int | None = None self._last_level: int | None = None
# If disabled in config, do nothing.
if not self._cfg.enabled: if not self._cfg.enabled:
_LOGGER.info("ButtonController disabled in config; not initializing GPIO") _LOGGER.info(
"ButtonController disabled in config; not initializing GPIO"
)
return return
# If RPi.GPIO is unavailable or not usable on this host, also do nothing.
if GPIO is None: if GPIO is None:
_LOGGER.warning( _LOGGER.info(
"RPi.GPIO not available; hardware button support disabled" "RPi.GPIO not available or not usable on this host; "
"hardware button support disabled"
) )
return return
@@ -107,7 +118,11 @@ class ButtonController:
self._cfg.poll_interval_seconds, self._cfg.poll_interval_seconds,
) )
except Exception: except Exception:
_LOGGER.exception("Failed to configure GPIO pin %s for button", self._cfg.pin) _LOGGER.exception(
"Failed to configure GPIO pin %s for button; "
"hardware button support disabled",
self._cfg.pin,
)
return return
# Start polling thread # Start polling thread
@@ -128,7 +143,7 @@ class ButtonController:
while not getattr(self.state, "shutdown", False): while not getattr(self.state, "shutdown", False):
try: try:
level = GPIO.input(self._cfg.pin) level = GPIO.input(self._cfg.pin) # type: ignore[call-arg]
except Exception: except Exception:
_LOGGER.exception("Error reading GPIO pin %s", self._cfg.pin) _LOGGER.exception("Error reading GPIO pin %s", self._cfg.pin)
break break
@@ -220,4 +235,3 @@ class ButtonController:
"set_mic_mute", "set_mic_mute",
{"state": new_state}, {"state": new_state},
) )