#!/usr/bin/env python3 """ Button-controlled screen mode cycling for ModuleAir Pro 4G Connected to GPIO6 (GND), cycles through 4 screen modes: 1. Network Status 2. All 4 Sensors (displayAll4_v2) 3. CO2 + PM2.5 + PM10 + Network Status 4. Blank Screen (black) Usage: sudo python3 /var/www/moduleair_pro_4g/matrix/button_screen_controller.py """ import RPi.GPIO as GPIO import subprocess import time import signal import sys import os # GPIO pin configuration BUTTON_PIN = 6 # GPIO6, connected to GND when pressed # Screen mode configuration SCREEN_MODES = { 0: { 'name': 'Network Status', 'program': '/var/www/moduleair_pro_4g/matrix/screenNetwork/network_status', 'args': ['--led-no-hardware-pulse', '--led-row-addr-type=3', '--led-panel-type=FM6126A'] }, 1: { 'name': 'All 4 Sensors', 'program': '/var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2', 'args': ['--led-no-hardware-pulse', '--led-row-addr-type=3', '--led-panel-type=FM6126A'] }, 2: { 'name': 'CO2 + PM + Network', 'program': '/var/www/moduleair_pro_4g/matrix/screenSensors/displayCO2_PM_Network', 'args': ['--led-no-hardware-pulse', '--led-row-addr-type=3', '--led-panel-type=FM6126A'] }, 3: { 'name': 'Blank Screen', 'program': '/var/www/moduleair_pro_4g/matrix/screenSensors/blank_screen', 'args': ['--led-no-hardware-pulse', '--led-row-addr-type=3', '--led-panel-type=FM6126A'] } } class ScreenController: def __init__(self): self.current_mode = 0 self.current_process = None self.running = True self.last_button_press = 0 self.debounce_time = 0.5 # 500ms debounce self.last_button_state = True # Button starts as HIGH (pulled up) # Set up GPIO - clean initialization try: # Only cleanup if GPIO was previously initialized if GPIO.getmode() is not None: GPIO.cleanup() except: pass GPIO.setmode(GPIO.BCM) GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP) # Set up signal handlers signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGTERM, self.signal_handler) print(f"Screen controller initialized. Current mode: {SCREEN_MODES[self.current_mode]['name']}") print("Using polling method for button detection (more reliable)") def check_button_pressed(self): """Check for button press using polling method""" current_button_state = GPIO.input(BUTTON_PIN) # Detect falling edge (button pressed) if self.last_button_state == True and current_button_state == False: current_time = time.time() if current_time - self.last_button_press >= self.debounce_time: self.last_button_press = current_time self.cycle_screen_mode() self.last_button_state = current_button_state def cycle_screen_mode(self): """Cycle to next screen mode""" print(f"Button pressed! Cycling from mode {self.current_mode}") # Stop current program self.stop_current_program() # Move to next mode old_mode = self.current_mode self.current_mode = (self.current_mode + 1) % len(SCREEN_MODES) print(f"Switching from mode {old_mode} to mode {self.current_mode}: {SCREEN_MODES[self.current_mode]['name']}") # Start new program self.start_current_program() print(f"Mode switch completed successfully") def stop_current_program(self): """Stop the currently running display program""" if self.current_process: try: self.current_process.terminate() self.current_process.wait(timeout=3) print("Previous display program terminated") except subprocess.TimeoutExpired: self.current_process.kill() print("Previous display program killed (timeout)") except Exception as e: print(f"Error stopping previous program: {e}") finally: self.current_process = None # Kill any remaining matrix display processes (be very specific to avoid killing ourselves) try: subprocess.run(['pkill', '-f', '/var/www/moduleair_pro_4g/matrix/screenNetwork/network_status'], check=False) subprocess.run(['pkill', '-f', '/var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4'], check=False) subprocess.run(['pkill', '-f', '/var/www/moduleair_pro_4g/matrix/screenSensors/displayCO2'], check=False) subprocess.run(['pkill', '-f', '/var/www/moduleair_pro_4g/matrix/screenSensors/blank_screen'], check=False) time.sleep(0.5) # Give processes time to exit except Exception as e: print(f"Error killing matrix processes: {e}") def start_current_program(self): """Start the display program for current mode""" mode_config = SCREEN_MODES[self.current_mode] program_path = mode_config['program'] if not os.path.exists(program_path): print(f"Warning: Program {program_path} does not exist") return try: # Build command with arguments cmd = [program_path] + mode_config['args'] print(f"Starting: {' '.join(cmd)}") # Start the new program self.current_process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=os.setsid ) print(f"Started {mode_config['name']} (PID: {self.current_process.pid})") except Exception as e: print(f"Error starting program {program_path}: {e}") self.current_process = None def signal_handler(self, signum, frame): """Handle shutdown signals""" print(f"Received signal {signum}, shutting down...") print(f"Signal received at frame: {frame}") self.running = False self.cleanup() sys.exit(0) def cleanup(self): """Clean up resources""" print("Cleaning up...") self.stop_current_program() GPIO.cleanup() def run(self): """Main loop""" # Start with initial screen mode self.start_current_program() try: while self.running: # Check for button press self.check_button_pressed() # Check if current process is still running if self.current_process and self.current_process.poll() is not None: print(f"Display program ended unexpectedly (exit code: {self.current_process.returncode})") self.current_process = None # Restart the current program time.sleep(1) self.start_current_program() time.sleep(0.1) # Check 10 times per second for responsive button except KeyboardInterrupt: print("Keyboard interrupt received") finally: self.cleanup() def main(): """Main function""" print("ModuleAir Pro 4G - Button Screen Controller") print("Press button on GPIO6 to cycle through screen modes") print("Modes: Network Status -> All 4 Sensors -> CO2+PM+Network -> Blank Screen") controller = ScreenController() controller.run() if __name__ == "__main__": main()