add button event and screeens
This commit is contained in:
135
CLAUDE.md
Normal file
135
CLAUDE.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
ModuleAir Pro 4G is an IoT air quality monitoring system built on Raspberry Pi CM4 with 4G cellular connectivity. It collects real-time environmental data through multiple sensors, stores data locally in SQLite, transmits to remote servers via cellular modem, and displays live data on a 128x64 RGB LED matrix.
|
||||||
|
|
||||||
|
## System Architecture
|
||||||
|
|
||||||
|
### Hardware Components
|
||||||
|
- **NPM Sensor**: Particulate matter (PM1/PM2.5/PM10) via Modbus RTU
|
||||||
|
- **MH-Z19**: CO2 sensor via UART serial
|
||||||
|
- **BME280**: Temperature/humidity/pressure via I2C
|
||||||
|
- **SARA R4**: 4G cellular modem for data transmission
|
||||||
|
- **Matrix LED**: 128x64 RGB display for real-time visualization
|
||||||
|
- **Push Button**: GPIO6 (GND) for screen mode cycling
|
||||||
|
- **RTC Module (DS3231)**: Real-time clock via I2C
|
||||||
|
- **Optional**: Sensirion SFA30 formaldehyde sensor
|
||||||
|
|
||||||
|
### Software Stack
|
||||||
|
- **Python 3**: Core data collection and processing
|
||||||
|
- **SQLite Database**: Local storage at `/var/www/moduleair_pro_4g/sqlite/`
|
||||||
|
- **Apache/PHP**: Web interface for configuration and monitoring
|
||||||
|
- **Systemd Services**: Automated data collection and transmission
|
||||||
|
- **C++ Matrix Library**: RGB LED matrix control (rpi-rgb-led-matrix)
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
Sensors → Python Scripts → SQLite → {SARA R4 → Remote Servers, Matrix Display, Web Interface}
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Installation & Setup
|
||||||
|
```bash
|
||||||
|
# Full system installation (run once)
|
||||||
|
sudo /var/www/moduleair_pro_4g/services/setup_services.sh
|
||||||
|
|
||||||
|
# Matrix LED library compilation
|
||||||
|
cd /var/www/moduleair_pro_4g/matrix/lib && make
|
||||||
|
|
||||||
|
# Apache configuration
|
||||||
|
sudo sed -i 's|DocumentRoot /var/www/html|DocumentRoot /var/www/moduleair_pro_4g|' /etc/apache2/sites-available/000-default.conf
|
||||||
|
sudo systemctl reload apache2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Service Management
|
||||||
|
```bash
|
||||||
|
# Check all moduleair services status
|
||||||
|
systemctl list-timers | grep moduleair
|
||||||
|
systemctl status moduleair-npm-data.service
|
||||||
|
|
||||||
|
# Manual data collection
|
||||||
|
python3 /var/www/moduleair_pro_4g/NPM/get_data_modbus_v3.py
|
||||||
|
python3 /var/www/moduleair_pro_4g/MH-Z19/write_data.py
|
||||||
|
|
||||||
|
# Manual data transmission
|
||||||
|
python3 /var/www/moduleair_pro_4g/loop/SARA_send_data_v2.py
|
||||||
|
|
||||||
|
# Restart services
|
||||||
|
sudo systemctl restart moduleair-sara-data.timer
|
||||||
|
sudo systemctl restart moduleair-npm-data.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Matrix LED Display & Button Control
|
||||||
|
```bash
|
||||||
|
# Compile matrix programs
|
||||||
|
cd /var/www/moduleair_pro_4g/matrix/lib && make
|
||||||
|
|
||||||
|
# Compile all display programs
|
||||||
|
g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status.cc -o /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status -lrgbmatrix -lsqlite3
|
||||||
|
g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2.cc -o /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2 -lrgbmatrix -lsqlite3
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
# Start button-controlled display system
|
||||||
|
sudo systemctl restart moduleair-boot.service
|
||||||
|
|
||||||
|
# Manual button controller start
|
||||||
|
sudo python3 /var/www/moduleair_pro_4g/matrix/button_screen_controller.py
|
||||||
|
|
||||||
|
# Test button functionality only
|
||||||
|
sudo python3 /var/www/moduleair_pro_4g/test_button_controller.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hardware Configuration Required
|
||||||
|
- Enable all UART ports in `/boot/firmware/config.txt`
|
||||||
|
- Enable I2C interface
|
||||||
|
- Set device permissions: `sudo chmod 777 /dev/ttyAMA* /dev/i2c-1`
|
||||||
|
- Configure sudo permissions for www-data user
|
||||||
|
|
||||||
|
## Automated Services (Systemd)
|
||||||
|
|
||||||
|
- **NPM Data**: Every 10 seconds (particulate matter)
|
||||||
|
- **CO2 Data**: Every 10 seconds
|
||||||
|
- **BME280 Data**: Every 120 seconds (temperature/humidity/pressure)
|
||||||
|
- **SARA Data Transmission**: Every 60 seconds (4G upload)
|
||||||
|
- **Database Cleanup**: Daily
|
||||||
|
- **Matrix Boot Display**: Once at startup (shows logo then starts button controller)
|
||||||
|
- **Matrix Button Controller**: Continuous (managed by boot service)
|
||||||
|
|
||||||
|
## Matrix Display Modes
|
||||||
|
|
||||||
|
The system features 4 button-controlled display modes accessible via GPIO6 push button:
|
||||||
|
|
||||||
|
1. **Network Status**: 4G and WiFi connectivity status with signal strength
|
||||||
|
2. **All 4 Sensors**: PM1, PM2.5, PM10, and CO2 measurements with quality indicators
|
||||||
|
3. **CO2 + PM + Network**: CO2, PM2.5, PM10 readings plus network status summary
|
||||||
|
4. **Blank Screen**: All pixels off (black screen)
|
||||||
|
|
||||||
|
Press the button connected to GPIO6 (GND when pressed) to cycle through modes. The system includes:
|
||||||
|
- 500ms debounce to prevent accidental double-presses
|
||||||
|
- Automatic program restart if display crashes
|
||||||
|
- Clean shutdown handling with proper GPIO cleanup
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
SQLite database stores sensor readings with timestamps. Key tables include sensor data with datetime stamps for time-series analysis.
|
||||||
|
|
||||||
|
## Communication Protocols
|
||||||
|
|
||||||
|
- **Modbus RTU**: NPM particulate matter sensor
|
||||||
|
- **UART Serial**: CO2 sensor, 4G modem
|
||||||
|
- **I2C**: Environmental sensors, RTC
|
||||||
|
- **HTTP/HTTPS**: Data transmission to remote servers
|
||||||
|
- **MQTT**: Optional publishing via SARA module
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- Main config: `/var/www/moduleair_pro_4g/config.json`
|
||||||
|
- Service logs: `/var/www/moduleair_pro_4g/logs/`
|
||||||
|
- Matrix input files: `/var/www/moduleair_pro_4g/matrix/input_*.txt`
|
||||||
|
|
||||||
|
## Web Interface
|
||||||
|
|
||||||
|
Local Apache server serves management interface at device IP address with real-time data visualization and system configuration.
|
||||||
97
README.md
97
README.md
@@ -1,6 +1,15 @@
|
|||||||
# moduleair_pro_4g
|
# ModuleAir Pro 4G
|
||||||
Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
|
Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
|
||||||
|
|
||||||
|
**Capteurs d'air intérieur IoT avec écran LED RGB 128x64 contrôlé par bouton.**
|
||||||
|
|
||||||
|
## Fonctionnalités
|
||||||
|
- **Capteurs multi-polluants**: PM1, PM2.5, PM10, CO2, température, humidité
|
||||||
|
- **Connectivité 4G/WiFi**: Transmission automatique des données via modem SARA R4
|
||||||
|
- **Affichage LED interactif**: 4 modes d'écran commutables par bouton-poussoir
|
||||||
|
- **Interface web locale**: Configuration et visualisation en temps réel
|
||||||
|
- **Base de données SQLite**: Stockage local avec nettoyage automatique
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
## General
|
## General
|
||||||
```
|
```
|
||||||
@@ -144,48 +153,68 @@ Switch off on-board sound:
|
|||||||
|
|
||||||
Add `isolcpus=3` to `/boot/firmware/cmdline.txt`
|
Add `isolcpus=3` to `/boot/firmware/cmdline.txt`
|
||||||
|
|
||||||
## Start matrix loop at boot
|
## Affichage Matrix LED avec contrôle par bouton
|
||||||
|
|
||||||
We can use systemd to create a service (better than con because Cron doesn’t monitor the script; if it fails, it won’t restart automatically.).
|
Le système dispose de 4 modes d'affichage contrôlés par un bouton-poussoir connecté au GPIO6 (GND quand pressé):
|
||||||
```
|
|
||||||
sudo nano /etc/systemd/system/matrix_display.service
|
### Modes d'affichage
|
||||||
|
|
||||||
|
1. **État du réseau**: Statut de connectivité 4G et WiFi avec qualité du signal
|
||||||
|
2. **Tous les capteurs**: PM1, PM2.5, PM10 et CO2 avec indicateurs de qualité
|
||||||
|
3. **CO2 + PM + Réseau**: CO2, PM2.5, PM10 et statut réseau résumé
|
||||||
|
4. **Écran noir**: Tous les pixels éteints
|
||||||
|
|
||||||
|
### Compilation des programmes d'affichage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compiler la bibliothèque matrix
|
||||||
|
cd /var/www/moduleair_pro_4g/matrix/lib && make
|
||||||
|
|
||||||
|
# Compiler tous les programmes d'affichage
|
||||||
|
cd /var/www/moduleair_pro_4g
|
||||||
|
g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status.cc -o /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status -lrgbmatrix -lsqlite3
|
||||||
|
g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2.cc -o /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2 -lrgbmatrix -lsqlite3
|
||||||
|
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
|
||||||
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
and we add the following:
|
### Contrôle du système d'affichage
|
||||||
```
|
|
||||||
[Unit]
|
|
||||||
Description=Matrix Display Script
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
```bash
|
||||||
ExecStart=/var/www/moduleair_pro_4g/matrix/screen_sensors_loop
|
# Démarrer le système avec contrôle par bouton
|
||||||
WorkingDirectory=/var/www/moduleair_pro_4g/matrix
|
sudo systemctl restart moduleair-boot.service
|
||||||
Restart=always
|
|
||||||
User=root
|
|
||||||
Group=root
|
|
||||||
|
|
||||||
[Install]
|
# Démarrage manuel du contrôleur de bouton
|
||||||
WantedBy=multi-user.target
|
sudo python3 /var/www/moduleair_pro_4g/matrix/button_screen_controller.py
|
||||||
|
|
||||||
|
# Test du bouton uniquement
|
||||||
|
sudo python3 /var/www/moduleair_pro_4g/test_button_controller.py
|
||||||
|
|
||||||
|
# Arrêter le système
|
||||||
|
sudo systemctl stop moduleair-boot.service
|
||||||
|
|
||||||
|
# Vérifier le statut
|
||||||
|
sudo systemctl status moduleair-boot.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Then Reload systemd and Enable the Service:
|
### Fonctionnalités du contrôleur
|
||||||
```
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
sudo systemctl enable matrix_display.service
|
|
||||||
sudo systemctl start matrix_display.service
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
You can check/restart/stop this service (restart combines stop and start)
|
|
||||||
|
|
||||||
```
|
|
||||||
sudo systemctl status matrix_display.service
|
|
||||||
sudo systemctl stop matrix_display.service
|
|
||||||
sudo systemctl restart matrix_display.service
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
- **Debounce 500ms**: Évite les pressions accidentelles multiples
|
||||||
|
- **Redémarrage automatique**: Relance le programme d'affichage s'il plante
|
||||||
|
- **Arrêt propre**: Nettoyage GPIO et termination des processus
|
||||||
|
- **Gestion des signaux**: Réagit aux signaux SIGINT/SIGTERM
|
||||||
|
|
||||||
|
### Branchement du bouton
|
||||||
|
|
||||||
|
Connecter un bouton-poussoir entre GPIO6 et GND. Le système utilise une résistance de pull-up interne, donc aucune résistance externe n'est nécessaire.
|
||||||
|
|
||||||
|
## Hardware requis
|
||||||
|
|
||||||
|
- **Raspberry Pi CM4** avec connecteur GPIO
|
||||||
|
- **Bouton-poussoir** connecté GPIO6 → GND
|
||||||
|
- **Écran Matrix LED 128x64** FM6126A
|
||||||
|
- **Capteur NPM** (particules) via Modbus RTU
|
||||||
|
- **Capteur MH-Z19** (CO2) via UART
|
||||||
|
- **Module BME280** (T/H/P) via I2C
|
||||||
|
- **Modem SARA R4** (4G) via UART
|
||||||
|
- **Module RTC DS3231** via I2C
|
||||||
|
|||||||
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()
|
||||||
@@ -14,6 +14,9 @@ g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matri
|
|||||||
|
|
||||||
Pour lancer:
|
Pour lancer:
|
||||||
sudo /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status
|
sudo /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status
|
||||||
|
|
||||||
|
Pour arréter:
|
||||||
|
sudo systemctl stop moduleair-boot.service
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "led-matrix.h"
|
#include "led-matrix.h"
|
||||||
|
|||||||
BIN
matrix/screenSensors/blank_screen
Normal file
BIN
matrix/screenSensors/blank_screen
Normal file
Binary file not shown.
90
matrix/screenSensors/blank_screen.cc
Normal file
90
matrix/screenSensors/blank_screen.cc
Normal 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;
|
||||||
|
}
|
||||||
BIN
matrix/screenSensors/displayCO2_PM_Network
Normal file
BIN
matrix/screenSensors/displayCO2_PM_Network
Normal file
Binary file not shown.
366
matrix/screenSensors/displayCO2_PM_Network.cc
Normal file
366
matrix/screenSensors/displayCO2_PM_Network.cc
Normal 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;
|
||||||
|
}
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Simple boot script - run animation then sensor display
|
# Boot script with button-controlled screen cycling
|
||||||
# sudo chmod +x /var/www/moduleair_pro_4g/services/matrix_boot.sh
|
# sudo chmod +x /var/www/moduleair_pro_4g/services/matrix_boot.sh
|
||||||
# sudo /var/www/moduleair_pro_4g/services/matrix_boot.sh
|
# sudo /var/www/moduleair_pro_4g/services/matrix_boot.sh
|
||||||
|
|
||||||
|
# to stop the service
|
||||||
|
# sudo systemctl stop moduleair-boot.service
|
||||||
|
|
||||||
echo "$(date) - Starting boot animation..."
|
echo "$(date) - Starting boot animation..."
|
||||||
sudo /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png
|
sudo /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png
|
||||||
|
|
||||||
#0echo "$(date) - Boot animation done, starting sensor display..."
|
echo "$(date) - Boot animation done, starting button-controlled display..."
|
||||||
#sudo /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2
|
echo "$(date) - Press button on GPIO6 to cycle: Network -> All Sensors -> CO2+PM+Network -> Blank"
|
||||||
|
sudo python3 /var/www/moduleair_pro_4g/matrix/button_screen_controller.py
|
||||||
|
|
||||||
echo "$(date) - Boot animation done, starting network display..."
|
echo "$(date) - Button controller started"
|
||||||
sudo /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status
|
|
||||||
|
|
||||||
echo "$(date) - Both programs started"
|
|
||||||
63
test_button_simple.py
Normal file
63
test_button_simple.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple polling-based button test for GPIO6
|
||||||
|
No edge detection - just polls the pin state
|
||||||
|
"""
|
||||||
|
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
|
BUTTON_PIN = 6
|
||||||
|
|
||||||
|
def signal_handler(signum, frame):
|
||||||
|
print("Cleaning up GPIO...")
|
||||||
|
GPIO.cleanup()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Simple Button Test - Polling GPIO6")
|
||||||
|
print("Press the button connected to GPIO6 (GND when pressed)")
|
||||||
|
print("Press Ctrl+C to exit")
|
||||||
|
|
||||||
|
# Clean up any existing GPIO setup
|
||||||
|
try:
|
||||||
|
GPIO.cleanup()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Set up GPIO
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setup(BUTTON_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
|
||||||
|
# Set up signal handler
|
||||||
|
signal.signal(signal.SIGINT, signal_handler)
|
||||||
|
signal.signal(signal.SIGTERM, signal_handler)
|
||||||
|
|
||||||
|
print(f"GPIO{BUTTON_PIN} setup complete. Current state: {GPIO.input(BUTTON_PIN)}")
|
||||||
|
print("Monitoring for button presses (1=released, 0=pressed)...")
|
||||||
|
|
||||||
|
last_state = GPIO.input(BUTTON_PIN)
|
||||||
|
button_press_count = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
current_state = GPIO.input(BUTTON_PIN)
|
||||||
|
|
||||||
|
# Detect falling edge (button press)
|
||||||
|
if last_state == 1 and current_state == 0:
|
||||||
|
button_press_count += 1
|
||||||
|
print(f"Button press #{button_press_count} detected!")
|
||||||
|
time.sleep(0.5) # Simple debounce
|
||||||
|
|
||||||
|
last_state = current_state
|
||||||
|
time.sleep(0.01) # Poll every 10ms
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
GPIO.cleanup()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user