add button event and screeens

This commit is contained in:
Your Name
2025-06-20 11:03:50 +02:00
parent 2be04b6950
commit 5a2426becb
10 changed files with 936 additions and 41 deletions

View 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()

View File

@@ -14,6 +14,9 @@ g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matri
Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status
Pour arréter:
sudo systemctl stop moduleair-boot.service
*/
#include "led-matrix.h"

Binary file not shown.

View File

@@ -0,0 +1,90 @@
/*
____ _ _ _ _ _ __
| __ )| | / \ | \ | | |/ /
| _ \| | / _ \ | \| | ' /
| |_) | |___ / ___ \| |\ | . \
|____/|_____/_/ \_\_| \_|_|\_\
Simple blank screen (black) for LED matrix
Used when user wants to turn off the display
Pour compiler:
g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenSensors/blank_screen.cc -o /var/www/moduleair_pro_4g/matrix/screenSensors/blank_screen -lrgbmatrix
Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/screenSensors/blank_screen
*/
#include "led-matrix.h"
#include "graphics.h"
#include <unistd.h>
#include <iostream>
#include <signal.h>
#include <atomic>
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
std::atomic<bool> running(true);
void signal_handler(int signum) {
running = false;
}
int main(int argc, char *argv[]) {
std::cout << "Blank Screen Display started" << std::endl;
// Handle signals for graceful exit
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Initialize LED matrix
std::cout << "Initializing LED matrix..." << std::endl;
RGBMatrix::Options defaults;
defaults.hardware_mapping = "moduleair_pinout";
defaults.rows = 64;
defaults.cols = 128;
defaults.chain_length = 1;
defaults.parallel = 1;
defaults.row_address_type = 3;
defaults.show_refresh_rate = false;
defaults.brightness = 100;
defaults.pwm_bits = 1;
defaults.panel_type = "FM6126A";
defaults.disable_hardware_pulsing = false;
rgb_matrix::RuntimeOptions runtime_opt;
runtime_opt.gpio_slowdown = 4;
runtime_opt.daemon = 0;
runtime_opt.drop_privileges = 0;
Canvas *canvas = RGBMatrix::CreateFromOptions(defaults, runtime_opt);
if (canvas == NULL) {
std::cerr << "Error creating LED matrix canvas" << std::endl;
return 1;
}
std::cout << "LED matrix initialized successfully" << std::endl;
// Clear the screen (turn all pixels black)
canvas->Clear();
std::cout << "Screen cleared - all pixels black" << std::endl;
// Main loop - just keep the screen blank
std::cout << "Blank screen active - press Ctrl+C to exit" << std::endl;
while (running) {
// Ensure screen stays clear
canvas->Clear();
// Sleep for 5 seconds before next clear
for (int i = 0; i < 5 && running; i++) {
sleep(1);
}
}
// Clean up
std::cout << "Program terminating, cleaning up..." << std::endl;
canvas->Clear();
delete canvas;
std::cout << "Blank Screen Display terminated" << std::endl;
return 0;
}

Binary file not shown.

View File

@@ -0,0 +1,366 @@
/*
____ ___ ____ _ _ _____ _______ _____ ____ _ __
/ ___/ _ \___ \ | \ | | ____|_ _\ \ / / _ \| _ \| |/ /
| | | | | |__) || \| | _| | | \ \ /\ / / | | | |_) | ' /
| |__| |_| / __/ | |\ | |___ | | \ V V /| |_| | _ <| . \
\____\___/_____||_| \_|_____| |_| \_/\_/ \___/|_| \_\_|\_\
Combined CO2 + PM2.5 + PM10 + Network Status Display
Shows 3 key measurements plus network connectivity status
Pour compiler:
g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenSensors/displayCO2_PM_Network.cc -o /var/www/moduleair_pro_4g/matrix/screenSensors/displayCO2_PM_Network -lrgbmatrix -lsqlite3
Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/screenSensors/displayCO2_PM_Network
*/
#include "led-matrix.h"
#include "graphics.h"
#include <unistd.h>
#include <string.h>
#include <fstream>
#include <iostream>
#include <signal.h>
#include <atomic>
#include <sqlite3.h>
#include <sstream>
#include <iomanip>
#include <chrono>
#include <ctime>
#include <algorithm>
#include <cmath>
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
std::atomic<bool> running(true);
// Define color codes
#define RESET "\033[0m"
#define RED "\033[31m"
#define GREEN "\033[32m"
#define YELLOW "\033[33m"
#define BLUE "\033[34m"
#define MAGENTA "\033[35m"
#define CYAN "\033[36m"
#define WHITE "\033[37m"
// Path to the SQLite database
const std::string DB_PATH = "/var/www/moduleair_pro_4g/sqlite/sensors.db";
void signal_handler(int signum) {
running = false;
}
void log(const std::string& type, const std::string& message) {
std::string color;
if (type == "BLUE") color = BLUE;
else if (type == "GREEN") color = GREEN;
else if (type == "YELLOW") color = YELLOW;
else if (type == "RED") color = RED;
else color = WHITE;
std::cout << color << message << RESET << std::endl;
}
// Function to retrieve latest sensor data
bool get_sensor_data(sqlite3* db, float& pm25, float& pm10, float& co2) {
sqlite3_stmt* stmt;
bool pm_success = false;
bool co2_success = false;
log("BLUE", "Querying sensor data from database...");
// Get PM2.5 and PM10 data from NPM table
const char* pm_query = "SELECT PM25, PM10 FROM data_NPM ORDER BY rowid DESC LIMIT 1";
if (sqlite3_prepare_v2(db, pm_query, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
pm25 = sqlite3_column_double(stmt, 0);
pm10 = sqlite3_column_double(stmt, 1);
pm_success = true;
std::cout << " Retrieved PM values - PM2.5: " << pm25 << ", PM10: " << pm10 << std::endl;
}
sqlite3_finalize(stmt);
}
// Get CO2 data
const char* co2_query = "SELECT CO2 FROM data_CO2 ORDER BY rowid DESC LIMIT 1";
if (sqlite3_prepare_v2(db, co2_query, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
co2 = sqlite3_column_double(stmt, 0);
co2_success = true;
std::cout << " Retrieved CO2 value: " << co2 << " ppm" << std::endl;
}
sqlite3_finalize(stmt);
}
return pm_success && co2_success;
}
// Function to get network status
bool get_network_status(sqlite3* db, std::string& network_status, std::string& wifi_status) {
sqlite3_stmt* stmt;
bool status_found = false;
// Get 4G network status
const char* network_query = "SELECT value FROM config_table WHERE key='SARA_network_status'";
if (sqlite3_prepare_v2(db, network_query, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
network_status = (char*)sqlite3_column_text(stmt, 0);
status_found = true;
} else {
network_status = "unknown";
}
sqlite3_finalize(stmt);
}
// Get WiFi status
const char* wifi_query = "SELECT value FROM config_table WHERE key='WIFI_status'";
if (sqlite3_prepare_v2(db, wifi_query, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
wifi_status = (char*)sqlite3_column_text(stmt, 0);
} else {
wifi_status = "unknown";
}
sqlite3_finalize(stmt);
}
return status_found;
}
// Function to get color based on air quality thresholds
rgb_matrix::Color get_quality_color(float value, const std::string& type) {
if (type == "pm25") {
if (value < 10) return rgb_matrix::Color(0, 255, 0); // Green - Good
else if (value < 20) return rgb_matrix::Color(255, 255, 0); // Yellow - Moderate
else if (value < 50) return rgb_matrix::Color(255, 165, 0); // Orange - Degraded
else return rgb_matrix::Color(255, 0, 0); // Red - Bad
} else if (type == "pm10") {
if (value < 15) return rgb_matrix::Color(0, 255, 0); // Green - Good
else if (value < 30) return rgb_matrix::Color(255, 255, 0); // Yellow - Moderate
else if (value < 75) return rgb_matrix::Color(255, 165, 0); // Orange - Degraded
else return rgb_matrix::Color(255, 0, 0); // Red - Bad
} else if (type == "co2") {
if (value < 800) return rgb_matrix::Color(0, 255, 0); // Green - Good
else if (value < 1500) return rgb_matrix::Color(255, 165, 0); // Orange - Moderate
else return rgb_matrix::Color(255, 0, 0); // Red - Bad
}
return rgb_matrix::Color(255, 255, 255); // White default
}
// Function to get status text based on thresholds
std::string get_status_text(float value, const std::string& type) {
if (type == "pm25") {
if (value < 10) return "BON";
else if (value < 20) return "MOYEN";
else if (value < 50) return "DEGRADE";
else return "MAUVAIS";
} else if (type == "pm10") {
if (value < 15) return "BON";
else if (value < 30) return "MOYEN";
else if (value < 75) return "DEGRADE";
else return "MAUVAIS";
} else if (type == "co2") {
if (value < 800) return "BON";
else if (value < 1500) return "AERER SVP";
else return "AERER VITE";
}
return "UNKNOWN";
}
// Function to draw network connection indicators
void draw_network_indicators(Canvas* canvas, const std::string& network_status, const std::string& wifi_status) {
rgb_matrix::Color green(0, 255, 0);
rgb_matrix::Color red(255, 0, 0);
rgb_matrix::Color orange(255, 165, 0);
rgb_matrix::Color yellow(255, 255, 0);
// Draw 4G indicator (small rectangle)
rgb_matrix::Color net_color = (network_status == "connected") ? green :
(network_status == "connecting" || network_status == "booting") ? yellow : red;
for (int x = 100; x < 108; x++) {
for (int y = 2; y < 8; y++) {
canvas->SetPixel(x, y, net_color.r, net_color.g, net_color.b);
}
}
// Draw WiFi indicator (small rectangle)
rgb_matrix::Color wifi_color = (wifi_status == "connected") ? green :
(wifi_status == "hotspot") ? orange : red;
for (int x = 112; x < 120; x++) {
for (int y = 2; y < 8; y++) {
canvas->SetPixel(x, y, wifi_color.r, wifi_color.g, wifi_color.b);
}
}
}
// Main display function
void draw_combined_screen(Canvas* canvas, float pm25, float pm10, float co2,
const std::string& network_status, const std::string& wifi_status) {
rgb_matrix::Color white(255, 255, 255);
rgb_matrix::Color cyan(0, 255, 255);
rgb_matrix::Color bg_color(0, 0, 0);
rgb_matrix::Font title_font, value_font, small_font;
// Load fonts
if (!title_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf") ||
!value_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/9x18B.bdf") ||
!small_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) {
std::cerr << "Error loading fonts!" << std::endl;
return;
}
canvas->Clear();
// Draw title
rgb_matrix::DrawText(canvas, title_font, 2, title_font.baseline() + 2, cyan, &bg_color, "CO2 + PM + NET", 0);
// Draw network indicators in top right
draw_network_indicators(canvas, network_status, wifi_status);
// Format sensor values
std::stringstream ss_pm25, ss_pm10, ss_co2;
ss_pm25 << std::fixed << std::setprecision(1) << pm25;
ss_pm10 << std::fixed << std::setprecision(1) << pm10;
ss_co2 << std::fixed << std::setprecision(0) << co2;
std::string str_pm25 = ss_pm25.str();
std::string str_pm10 = ss_pm10.str();
std::string str_co2 = ss_co2.str();
// Get colors and status for each measurement
rgb_matrix::Color pm25_color = get_quality_color(pm25, "pm25");
rgb_matrix::Color pm10_color = get_quality_color(pm10, "pm10");
rgb_matrix::Color co2_color = get_quality_color(co2, "co2");
std::string pm25_status = get_status_text(pm25, "pm25");
std::string pm10_status = get_status_text(pm10, "pm10");
std::string co2_status = get_status_text(co2, "co2");
// Layout: 3 measurements with proper spacing to avoid overlap
int y_start = 16;
int y_spacing = 16; // Increased spacing to prevent overlap
// PM2.5 - using smaller positioning to fit everything
rgb_matrix::DrawText(canvas, small_font, 2, y_start, cyan, &bg_color, "PM2.5:", 0);
rgb_matrix::DrawText(canvas, small_font, 32, y_start, white, &bg_color, str_pm25.c_str(), 0);
rgb_matrix::DrawText(canvas, small_font, 55, y_start, pm25_color, &bg_color, pm25_status.c_str(), 0);
// PM10
y_start += y_spacing;
rgb_matrix::DrawText(canvas, small_font, 2, y_start, cyan, &bg_color, "PM10:", 0);
rgb_matrix::DrawText(canvas, small_font, 32, y_start, white, &bg_color, str_pm10.c_str(), 0);
rgb_matrix::DrawText(canvas, small_font, 55, y_start, pm10_color, &bg_color, pm10_status.c_str(), 0);
// CO2
y_start += y_spacing;
rgb_matrix::DrawText(canvas, small_font, 2, y_start, cyan, &bg_color, "CO2:", 0);
rgb_matrix::DrawText(canvas, small_font, 28, y_start, white, &bg_color, str_co2.c_str(), 0);
rgb_matrix::DrawText(canvas, small_font, 55, y_start, co2_color, &bg_color, co2_status.c_str(), 0);
// Draw network status text at bottom - simplified to fit
y_start += y_spacing;
std::string net_text = std::string("4G:") + (network_status == "connected" ? "OK" : "NO") +
std::string(" WiFi:") + (wifi_status == "connected" ? "OK" : "NO");
rgb_matrix::DrawText(canvas, small_font, 2, y_start, rgb_matrix::Color(150, 150, 150), &bg_color, net_text.c_str(), 0);
// Draw timestamp
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
auto tm = *std::localtime(&time_t);
std::stringstream time_ss;
time_ss << std::put_time(&tm, "%H:%M");
rgb_matrix::DrawText(canvas, small_font, 100, 62, rgb_matrix::Color(100, 100, 100), &bg_color,
time_ss.str().c_str(), 0);
}
int main(int argc, char *argv[]) {
log("BLUE", "CO2 + PM + Network Display started");
// Handle signals for graceful exit
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Initialize database connection
sqlite3* db;
std::cout << "Opening SQLite database at " << DB_PATH << std::endl;
int rc = sqlite3_open(DB_PATH.c_str(), &db);
if (rc) {
std::cerr << "Error opening SQLite database: " << sqlite3_errmsg(db) << std::endl;
sqlite3_close(db);
return 1;
}
// Initialize LED matrix
log("BLUE", "Initializing LED matrix...");
RGBMatrix::Options defaults;
defaults.hardware_mapping = "moduleair_pinout";
defaults.rows = 64;
defaults.cols = 128;
defaults.chain_length = 1;
defaults.parallel = 1;
defaults.row_address_type = 3;
defaults.show_refresh_rate = false;
defaults.brightness = 100;
defaults.pwm_bits = 1;
defaults.panel_type = "FM6126A";
defaults.disable_hardware_pulsing = false;
rgb_matrix::RuntimeOptions runtime_opt;
runtime_opt.gpio_slowdown = 4;
runtime_opt.daemon = 0;
runtime_opt.drop_privileges = 0;
Canvas *canvas = RGBMatrix::CreateFromOptions(defaults, runtime_opt);
if (canvas == NULL) {
std::cerr << "Error creating LED matrix canvas" << std::endl;
sqlite3_close(db);
return 1;
}
log("GREEN", "LED matrix initialized successfully");
// Main loop
log("BLUE", "Starting display loop");
while (running) {
float pm25 = 0, pm10 = 0, co2 = 0;
std::string network_status, wifi_status;
// Get sensor data
bool sensor_success = get_sensor_data(db, pm25, pm10, co2);
bool network_success = get_network_status(db, network_status, wifi_status);
if (!sensor_success) {
std::cerr << "Error retrieving sensor data from database" << std::endl;
}
if (!network_success) {
std::cerr << "Error retrieving network data from database" << std::endl;
network_status = "unknown";
wifi_status = "unknown";
}
// Draw the combined screen
draw_combined_screen(canvas, pm25, pm10, co2, network_status, wifi_status);
// Sleep before next update
std::cout << "Update complete, sleeping for 5 seconds..." << std::endl;
for (int i = 0; i < 5 && running; i++) {
sleep(1);
}
}
// Clean up
std::cout << "Program terminating, cleaning up..." << std::endl;
canvas->Clear();
delete canvas;
sqlite3_close(db);
std::cout << "CO2 + PM + Network Display terminated" << std::endl;
return 0;
}