208 lines
7.7 KiB
Python
208 lines
7.7 KiB
Python
#!/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() |