add button event and screeens
This commit is contained in:
208
matrix/button_screen_controller.py
Normal file
208
matrix/button_screen_controller.py
Normal file
@@ -0,0 +1,208 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user