93 lines
2.8 KiB
Python
93 lines
2.8 KiB
Python
|
|
"""GPIO service for Pi Zero - edge-triggered monitoring.
|
||
|
|
|
||
|
|
Polls GPIO pins and exposes state changes for inclusion in other payloads.
|
||
|
|
Keeps UART separate (handled by arduino_service).
|
||
|
|
"""
|
||
|
|
|
||
|
|
import gevent
|
||
|
|
|
||
|
|
try:
|
||
|
|
import RPi.GPIO as GPIO
|
||
|
|
_GPIO_AVAILABLE = True
|
||
|
|
except ImportError:
|
||
|
|
_GPIO_AVAILABLE = False
|
||
|
|
print("[GPIO] RPi.GPIO not available - running in mock mode")
|
||
|
|
|
||
|
|
|
||
|
|
# Pin assignments
|
||
|
|
PIN_THEME_SWITCH = 20 # Physical switch for light/dark theme
|
||
|
|
|
||
|
|
|
||
|
|
class GPIOService:
|
||
|
|
"""Monitors GPIO pins and tracks state changes."""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
self._running = False
|
||
|
|
self._greenlet = None
|
||
|
|
|
||
|
|
# Theme switch state
|
||
|
|
self._theme_switch_state = False # False = light, True = dark
|
||
|
|
self._theme_switch_pending = None # None = no change, bool = new value
|
||
|
|
|
||
|
|
if _GPIO_AVAILABLE:
|
||
|
|
GPIO.setmode(GPIO.BCM)
|
||
|
|
GPIO.setwarnings(False)
|
||
|
|
# Input with software pull-down (belt + suspenders with hardware pulldown)
|
||
|
|
GPIO.setup(PIN_THEME_SWITCH, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
||
|
|
|
||
|
|
def start(self):
|
||
|
|
"""Start background polling."""
|
||
|
|
if self._running:
|
||
|
|
return
|
||
|
|
self._running = True
|
||
|
|
|
||
|
|
# Read initial state
|
||
|
|
if _GPIO_AVAILABLE:
|
||
|
|
self._theme_switch_state = GPIO.input(PIN_THEME_SWITCH) == GPIO.HIGH
|
||
|
|
else:
|
||
|
|
self._theme_switch_state = True # Mock: default dark
|
||
|
|
|
||
|
|
self._greenlet = gevent.spawn(self._poll_loop)
|
||
|
|
print(f"[GPIO] Started, theme_switch initial={self._theme_switch_state}")
|
||
|
|
|
||
|
|
def stop(self):
|
||
|
|
"""Stop background polling."""
|
||
|
|
self._running = False
|
||
|
|
if self._greenlet:
|
||
|
|
self._greenlet.kill()
|
||
|
|
self._greenlet = None
|
||
|
|
if _GPIO_AVAILABLE:
|
||
|
|
GPIO.cleanup([PIN_THEME_SWITCH])
|
||
|
|
|
||
|
|
def _poll_loop(self):
|
||
|
|
"""Poll GPIO at ~20Hz, detect edges."""
|
||
|
|
while self._running:
|
||
|
|
gevent.sleep(0.05) # 20Hz
|
||
|
|
|
||
|
|
if _GPIO_AVAILABLE:
|
||
|
|
current = GPIO.input(PIN_THEME_SWITCH) == GPIO.HIGH
|
||
|
|
else:
|
||
|
|
current = self._theme_switch_state # Mock: no change
|
||
|
|
|
||
|
|
if current != self._theme_switch_state:
|
||
|
|
self._theme_switch_state = current
|
||
|
|
self._theme_switch_pending = current
|
||
|
|
print(f"[GPIO] Theme switch changed to {current}")
|
||
|
|
|
||
|
|
def get_theme_switch_change(self):
|
||
|
|
"""Get pending theme switch change, if any.
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
bool or None: New state if changed since last call, None otherwise.
|
||
|
|
"""
|
||
|
|
if self._theme_switch_pending is not None:
|
||
|
|
value = self._theme_switch_pending
|
||
|
|
self._theme_switch_pending = None
|
||
|
|
return value
|
||
|
|
return None
|
||
|
|
|
||
|
|
@property
|
||
|
|
def theme_switch(self):
|
||
|
|
"""Current theme switch state (True = dark, False = light)."""
|
||
|
|
return self._theme_switch_state
|