diff --git a/orchestrateur/sensors/button.py b/orchestrateur/sensors/button.py index f383547..483b3d7 100644 --- a/orchestrateur/sensors/button.py +++ b/orchestrateur/sensors/button.py @@ -5,7 +5,6 @@ from sensors.lock import grove_lock from shared.logging import log button = 2 -button_switch_state = 0 grovepi.pinMode(button, "INPUT") button_callback = None @@ -15,7 +14,9 @@ def read_button_state(): if not grove_lock.acquire(timeout=0.2): return None try: - return grovepi.digitalRead(button) + val = grovepi.digitalRead(button) + # Force strict binary output (0 or 1) + return 1 if val == 1 else 0 except Exception as e: log(f"BTN Error: {e}") return None @@ -23,21 +24,47 @@ def read_button_state(): grove_lock.release() def monitor_button(): - global button_switch_state - last_button_state = button_switch_state + last_stable_state = 0 + candidate_state = 0 + consecutive_count = 0 + # Require 3 consecutive identical reads (~60ms) to confirm a valid state change + REQUIRED_CONSECUTIVE_READS = 3 + + # Minimum time gap (in seconds) between allowed button triggers (cooldown) + DEBOUNCE_COOLDOWN = 0.3 + last_trigger_time = 0 + while True: current_state = read_button_state() if current_state is not None: - # Rising edge detection (0 -> 1 transition) - if current_state == 1 and last_button_state == 0: - if button_callback: - button_callback() - last_button_state = current_state - time.sleep(0.02) # Fast 20ms poll when lock is clear + if current_state == candidate_state: + consecutive_count += 1 + else: + candidate_state = current_state + consecutive_count = 1 + + # State is confirmed stable across multiple reads + if consecutive_count >= REQUIRED_CONSECUTIVE_READS: + now = time.time() + + # Rising edge detection (0 -> 1 transition) with cooldown timer + if candidate_state == 1 and last_stable_state == 0: + if (now - last_trigger_time) > DEBOUNCE_COOLDOWN: + last_trigger_time = now + if button_callback: + try: + button_callback() + except Exception as e: + log(f"[Button] Callback exception: {e}") + + last_stable_state = candidate_state + + time.sleep(0.02) # 20ms poll interval else: - # Lock was busy; retry quickly without updating last_button_state + # Lock was busy or read failed; reset candidate counter to reject noisy spikes + consecutive_count = 0 time.sleep(0.01) def start_button_monitoring_thread():