This commit is contained in:
Your Name
2025-03-31 15:12:09 +02:00
parent fb389bec03
commit 1caf48b5a4
6 changed files with 1137 additions and 4 deletions

View File

@@ -5,7 +5,7 @@ Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
## General ## General
``` ```
sudo apt update sudo apt update
sudo apt install git gh apache2 sqlite3 php php-sqlite3 python3 python3-pip jq g++ autossh i2c-tools python3-smbus -y sudo apt install git gh apache2 sqlite3 php php-sqlite3 libsqlite3-dev python3 python3-pip jq g++ autossh i2c-tools python3-smbus -y
sudo pip3 install pyserial requests sensirion-shdlc-sfa3x RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil --break-system-packages sudo pip3 install pyserial requests sensirion-shdlc-sfa3x RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil --break-system-packages
sudo git clone http://gitea.aircarto.fr/PaulVua/moduleair_pro_4g.git /var/www/moduleair_pro_4g sudo git clone http://gitea.aircarto.fr/PaulVua/moduleair_pro_4g.git /var/www/moduleair_pro_4g
sudo mkdir /var/www/moduleair_pro_4g/logs sudo mkdir /var/www/moduleair_pro_4g/logs

Binary file not shown.

View File

@@ -0,0 +1,551 @@
/*
__ __ _ _____ ____ _____ __
| \/ | / \|_ _| _ \|_ _\ \/ /
| |\/| | / _ \ | | | |_) || | \ /
| | | |/ ___ \| | | _ < | | / \
|_| |_/_/ \_\_| |_| \_\___/_/\_\
Script to launch screens to display compounds
Get values from SQLite database:
- NextPM data from data_NPM table
- CO2 data from data_MHZ19 or data_CO2 table
- Additional sensors as needed
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/displayAll4_v1.cc -o /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v1 -lrgbmatrix -lsqlite3
Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v1
*/
#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>
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
std::atomic<bool> running(true);
// 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;
}
// Helper function to get current time as string for logging
std::string get_current_time() {
auto now = std::chrono::system_clock::now();
std::time_t now_time = std::chrono::system_clock::to_time_t(now);
std::stringstream ss;
ss << std::put_time(std::localtime(&now_time), "%Y-%m-%d %H:%M:%S");
return ss.str();
}
// Function to retrieve latest NPM data from the database
bool get_npm_data(sqlite3* db, float& pm10, float& pm25, float& pm1) {
sqlite3_stmt* stmt;
bool success = false;
std::cout << get_current_time() << " - Querying NPM data from database..." << std::endl;
// First, check if the table exists
const char* table_check = "SELECT name FROM sqlite_master WHERE type='table' AND name='data_NPM';";
if (sqlite3_prepare_v2(db, table_check, -1, &stmt, NULL) != SQLITE_OK) {
std::cerr << "SQL error checking table: " << sqlite3_errmsg(db) << std::endl;
return false;
}
bool table_exists = false;
if (sqlite3_step(stmt) == SQLITE_ROW) {
table_exists = true;
}
sqlite3_finalize(stmt);
if (!table_exists) {
std::cerr << "Error: data_NPM table does not exist" << std::endl;
return false;
}
// Query to get the average of the last 6 NPM measurements
const char* query = "SELECT AVG(PM10), AVG(PM25), AVG(PM1) FROM data_NPM ORDER BY rowid DESC LIMIT 6";
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) != SQLITE_OK) {
std::cerr << "SQL error preparing query: " << sqlite3_errmsg(db) << std::endl;
return false;
}
if (sqlite3_step(stmt) == SQLITE_ROW) {
pm10 = sqlite3_column_double(stmt, 0);
pm25 = sqlite3_column_double(stmt, 1);
pm1 = sqlite3_column_double(stmt, 2);
std::cout << " Retrieved NPM values - PM10: " << pm10
<< ", PM2.5: " << pm25
<< ", PM1: " << pm1 << std::endl;
success = true;
} else {
std::cerr << " No NPM data found in database" << std::endl;
}
sqlite3_finalize(stmt);
return success;
}
// Function to retrieve latest CO2 data from the database
bool get_co2_data(sqlite3* db, float& co2) {
sqlite3_stmt* stmt;
bool success = false;
std::cout << get_current_time() << " - Querying CO2 data from database..." << std::endl;
// Try data_CO2 table directly
const char* query_co2 = "SELECT CO2 FROM data_CO2 ORDER BY rowid DESC LIMIT 1";
if (sqlite3_prepare_v2(db, query_co2, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
co2 = sqlite3_column_double(stmt, 0);
std::cout << " Retrieved CO2 value from data_CO2: " << co2 << " ppm" << std::endl;
success = true;
} else {
std::cout << " No data found in data_CO2 table" << std::endl;
}
sqlite3_finalize(stmt);
} else {
std::cerr << " Error preparing CO2 query: " << sqlite3_errmsg(db) << std::endl;
// Check if table exists
const char* table_check = "SELECT name FROM sqlite_master WHERE type='table' AND name='data_CO2';";
if (sqlite3_prepare_v2(db, table_check, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
std::cout << " data_CO2 table exists but query failed" << std::endl;
} else {
std::cout << " data_CO2 table does not exist in database" << std::endl;
}
sqlite3_finalize(stmt);
}
}
return success;
}
// Store previous values to check for changes
struct SensorData {
float pm10 = 0, pm25 = 0, pm1 = 0, co2 = 0;
std::string pm10_str, pm25_str, pm1_str, co2_str;
std::string pm10_status, pm25_status, pm1_status, co2_status;
rgb_matrix::Color pm10_color, pm25_color, pm1_color, co2_color;
};
// Initial screen setup - draw all static elements
void setup_screen(Canvas *canvas) {
std::cout << get_current_time() << " - Setting up initial screen layout..." << std::endl;
canvas->Clear();
rgb_matrix::Color myCYAN(0, 255, 255);
rgb_matrix::Color bg_color(0, 0, 0);
rgb_matrix::Font font1;
if (!font1.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) {
std::cerr << "Error loading font!" << std::endl;
return;
}
int letter_spacing = 0;
int x, y1;
// Draw static labels for PM10
x = 0;
y1 = font1.baseline() - 1;
rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "PM10 µg/m³", letter_spacing);
std::cout << " Drew PM10 label" << std::endl;
// Draw static labels for PM2.5
x = 64;
rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "PM2.5 µg/m³", letter_spacing);
std::cout << " Drew PM2.5 label" << std::endl;
// Draw static labels for PM1
x = 0;
y1 += 33;
rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "PM1 µg/m³", letter_spacing);
std::cout << " Drew PM1 label" << std::endl;
// Draw static labels for CO2
x = 64;
rgb_matrix::DrawText(canvas, font1, x, y1, myCYAN, &bg_color, "CO₂ ppm", letter_spacing);
std::cout << " Drew CO2 label" << std::endl;
}
// Function to update only the values and status messages on the screen
void update_screen_values(Canvas *canvas, SensorData& prevData, float pm10, float pm25, float pm1, float co2) {
std::cout << get_current_time() << " - Updating screen values..." << std::endl;
rgb_matrix::Color myWHITE(255, 255, 255);
rgb_matrix::Color myGREEN(0, 255, 0); // Good
rgb_matrix::Color myYELLOW(255, 255, 0); // Moderate
rgb_matrix::Color myORANGE(255, 165, 0); // Degraded
rgb_matrix::Color myRED(255, 0, 0); // Bad
rgb_matrix::Color bg_color(0, 0, 0);
rgb_matrix::Font font1;
rgb_matrix::Font font2;
if (!font1.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) {
std::cerr << "Error loading font1!" << std::endl;
return;
}
if (!font2.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/9x18B.bdf")) {
std::cerr << "Error loading font2!" << std::endl;
return;
}
int letter_spacing = 0;
int x, y1, y2, y3;
// Convert float values to strings with 1 decimal place
std::stringstream ss_pm10, ss_pm25, ss_pm1, ss_co2;
ss_pm10 << std::fixed << std::setprecision(1) << pm10;
ss_pm25 << std::fixed << std::setprecision(1) << pm25;
ss_pm1 << std::fixed << std::setprecision(1) << pm1;
ss_co2 << std::fixed << std::setprecision(0) << co2;
std::string str_pm10 = ss_pm10.str();
std::string str_pm25 = ss_pm25.str();
std::string str_pm1 = ss_pm1.str();
std::string str_co2 = ss_co2.str();
// Define coordinates for values and status messages
x = 0;
y1 = font1.baseline() - 1;
y2 = y1 + font2.baseline() + 1;
y3 = y1 + y2 + 4;
// Update PM10 value and status if changed
if (str_pm10 != prevData.pm10_str) {
std::cout << " Updating PM10 value from " << prevData.pm10_str << " to " << str_pm10 << std::endl;
// Clear previous value area
for (int i = 0; i < 60; i++) {
for (int j = y1 + 1; j < y2 + 5; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm10.c_str(), letter_spacing);
prevData.pm10_str = str_pm10;
}
// Determine PM10 status
std::string pm10_status;
rgb_matrix::Color pm10_color;
if (pm10 < 15) {
pm10_status = "BON";
pm10_color = myGREEN;
} else if (pm10 >= 15 && pm10 < 30) {
pm10_status = "MOYEN";
pm10_color = myYELLOW;
} else if (pm10 >= 30 && pm10 < 75) {
pm10_status = "DEGRADE";
pm10_color = myORANGE;
} else {
pm10_status = "MAUVAIS";
pm10_color = myRED;
}
// Update PM10 status if changed
if (pm10_status != prevData.pm10_status || pm10_color.r != prevData.pm10_color.r ||
pm10_color.g != prevData.pm10_color.g || pm10_color.b != prevData.pm10_color.b) {
std::cout << " Updating PM10 status to " << pm10_status << std::endl;
// Clear previous status area
for (int i = 0; i < 60; i++) {
for (int j = y2 + 6; j < y3 + 10; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, pm10_color, &bg_color, pm10_status.c_str(), letter_spacing);
prevData.pm10_status = pm10_status;
prevData.pm10_color = pm10_color;
}
// Update PM2.5 value and status
x = 64;
if (str_pm25 != prevData.pm25_str) {
std::cout << " Updating PM2.5 value from " << prevData.pm25_str << " to " << str_pm25 << std::endl;
// Clear previous value area
for (int i = 0; i < 60; i++) {
for (int j = y1 + 1; j < y2 + 5; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm25.c_str(), letter_spacing);
prevData.pm25_str = str_pm25;
}
// Determine PM2.5 status
std::string pm25_status;
rgb_matrix::Color pm25_color;
if (pm25 < 10) {
pm25_status = "BON";
pm25_color = myGREEN;
} else if (pm25 >= 10 && pm25 < 20) {
pm25_status = "MOYEN";
pm25_color = myYELLOW;
} else if (pm25 >= 20 && pm25 < 50) {
pm25_status = "DEGRADE";
pm25_color = myORANGE;
} else {
pm25_status = "MAUVAIS";
pm25_color = myRED;
}
// Update PM2.5 status if changed
if (pm25_status != prevData.pm25_status || pm25_color.r != prevData.pm25_color.r ||
pm25_color.g != prevData.pm25_color.g || pm25_color.b != prevData.pm25_color.b) {
std::cout << " Updating PM2.5 status to " << pm25_status << std::endl;
// Clear previous status area
for (int i = 0; i < 60; i++) {
for (int j = y2 + 6; j < y3 + 10; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, pm25_color, &bg_color, pm25_status.c_str(), letter_spacing);
prevData.pm25_status = pm25_status;
prevData.pm25_color = pm25_color;
}
// Update PM1 value and status
x = 0;
y1 += 33;
y2 += 33;
y3 += 33;
if (str_pm1 != prevData.pm1_str) {
std::cout << " Updating PM1 value from " << prevData.pm1_str << " to " << str_pm1 << std::endl;
// Clear previous value area
for (int i = 0; i < 60; i++) {
for (int j = y1 + 1; j < y2 + 5; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm1.c_str(), letter_spacing);
prevData.pm1_str = str_pm1;
}
// Determine PM1 status
std::string pm1_status;
rgb_matrix::Color pm1_color;
if (pm1 < 10) {
pm1_status = "BON";
pm1_color = myGREEN;
} else if (pm1 >= 10 && pm1 < 20) {
pm1_status = "MOYEN";
pm1_color = myYELLOW;
} else if (pm1 >= 20 && pm1 < 50) {
pm1_status = "DEGRADE";
pm1_color = myORANGE;
} else {
pm1_status = "MAUVAIS";
pm1_color = myRED;
}
// Update PM1 status if changed
if (pm1_status != prevData.pm1_status || pm1_color.r != prevData.pm1_color.r ||
pm1_color.g != prevData.pm1_color.g || pm1_color.b != prevData.pm1_color.b) {
std::cout << " Updating PM1 status to " << pm1_status << std::endl;
// Clear previous status area
for (int i = 0; i < 60; i++) {
for (int j = y2 + 6; j < y3 + 10; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, pm1_color, &bg_color, pm1_status.c_str(), letter_spacing);
prevData.pm1_status = pm1_status;
prevData.pm1_color = pm1_color;
}
// Update CO2 value and status
x = 64;
if (str_co2 != prevData.co2_str) {
std::cout << " Updating CO2 value from " << prevData.co2_str << " to " << str_co2 << std::endl;
// Clear previous value area
for (int i = 0; i < 60; i++) {
for (int j = y1 + 1; j < y2 + 5; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_co2.c_str(), letter_spacing);
prevData.co2_str = str_co2;
}
// Determine CO2 status
std::string co2_status;
rgb_matrix::Color co2_color;
if (co2 < 800) {
co2_status = "BON";
co2_color = myGREEN;
} else if (co2 >= 800 && co2 < 1500) {
co2_status = "AERER SVP";
co2_color = myORANGE;
} else {
co2_status = "AERER VITE";
co2_color = myRED;
}
// Update CO2 status if changed
if (co2_status != prevData.co2_status || co2_color.r != prevData.co2_color.r ||
co2_color.g != prevData.co2_color.g || co2_color.b != prevData.co2_color.b) {
std::cout << " Updating CO2 status to " << co2_status << std::endl;
// Clear previous status area
for (int i = 0; i < 60; i++) {
for (int j = y2 + 6; j < y3 + 10; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, co2_color, &bg_color, co2_status.c_str(), letter_spacing);
prevData.co2_status = co2_status;
prevData.co2_color = co2_color;
}
// Update stored values
prevData.pm10 = pm10;
prevData.pm25 = pm25;
prevData.pm1 = pm1;
prevData.co2 = co2;
}
// Function to dump database tables for debugging
void list_database_tables(sqlite3* db) {
sqlite3_stmt* stmt;
const char* query = "SELECT name FROM sqlite_master WHERE type='table';";
std::cout << "====== DATABASE TABLES ======" << std::endl;
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char* table_name = sqlite3_column_text(stmt, 0);
std::cout << "Table: " << table_name << std::endl;
}
} else {
std::cerr << "Error listing tables: " << sqlite3_errmsg(db) << std::endl;
}
sqlite3_finalize(stmt);
std::cout << "============================" << std::endl;
}
int main(int argc, char *argv[]) {
std::cout << "===== Starting ModuleAir Display Program =====" << std::endl;
std::cout << get_current_time() << " - Program started" << std::endl;
// Handle signals for graceful exit
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Initialize database connection
sqlite3* db;
std::cout << get_current_time() << " - 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;
}
// List available tables for debugging
list_database_tables(db);
// Initialize LED matrix
std::cout << get_current_time() << " - 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; // Adjust based on your Raspberry Pi model
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;
}
std::cout << get_current_time() << " - LED matrix initialized successfully" << std::endl;
// Set up the initial screen layout (static elements)
setup_screen(canvas);
// Structure to store previous data values
SensorData prevData;
// Initial sensor values
float pm10 = 0, pm25 = 0, pm1 = 0, co2 = 0;
int update_count = 0;
// Main loop
std::cout << get_current_time() << " - Starting main loop..." << std::endl;
while (running) {
update_count++;
std::cout << "\n" << get_current_time() << " - Update cycle #" << update_count << " started" << std::endl;
// Get data from database
bool npm_success = get_npm_data(db, pm10, pm25, pm1);
bool co2_success = get_co2_data(db, co2);
if (!npm_success) {
std::cerr << "Error retrieving NPM data from database" << std::endl;
}
if (!co2_success) {
std::cerr << "Error retrieving CO2 data from database" << std::endl;
}
// Update just the values and status messages
update_screen_values(canvas, prevData, pm10, pm25, pm1, co2);
// Sleep before next update
std::cout << get_current_time() << " - Update complete, sleeping for 10 seconds..." << std::endl;
for (int i = 0; i < 10 && running; i++) {
sleep(1); // Sleep in 1-second increments to be more responsive to stop signals
}
}
// Clean up
std::cout << get_current_time() << " - Program terminating, cleaning up..." << std::endl;
canvas->Clear();
delete canvas;
sqlite3_close(db);
std::cout << get_current_time() << " - Program terminated" << std::endl;
return 0;
}

Binary file not shown.

View File

@@ -0,0 +1,570 @@
/*
__ __ _ _____ ____ _____ __
| \/ | / \|_ _| _ \|_ _\ \/ /
| |\/| | / _ \ | | | |_) || | \ /
| | | |/ ___ \| | | _ < | | / \
|_| |_/_/ \_\_| |_| \_\___/_/\_\
Script to launch screens to display compounds
Get values from SQLite database:
- NextPM data from data_NPM table
- CO2 data from data_MHZ19 or data_CO2 table
- Additional sensors as needed
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/displayAll4_v2.cc -o /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2 -lrgbmatrix -lsqlite3
Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2
*/
#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>
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 NPM data from the database
bool get_npm_data(sqlite3* db, float& pm10, float& pm25, float& pm1) {
sqlite3_stmt* stmt;
bool success = false;
std::cout << " - Querying latest NPM data from database..." << std::endl;
// First, check if the table exists
const char* table_check = "SELECT name FROM sqlite_master WHERE type='table' AND name='data_NPM';";
if (sqlite3_prepare_v2(db, table_check, -1, &stmt, NULL) != SQLITE_OK) {
std::cerr << "SQL error checking table: " << sqlite3_errmsg(db) << std::endl;
return false;
}
bool table_exists = false;
if (sqlite3_step(stmt) == SQLITE_ROW) {
table_exists = true;
}
sqlite3_finalize(stmt);
if (!table_exists) {
std::cerr << "Error: data_NPM table does not exist" << std::endl;
return false;
}
// Query to get the latest NPM measurement
const char* query = "SELECT PM10, PM25, PM1 FROM data_NPM ORDER BY rowid DESC LIMIT 1";
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) != SQLITE_OK) {
std::cerr << "SQL error preparing query: " << sqlite3_errmsg(db) << std::endl;
return false;
}
if (sqlite3_step(stmt) == SQLITE_ROW) {
pm10 = sqlite3_column_double(stmt, 0);
pm25 = sqlite3_column_double(stmt, 1);
pm1 = sqlite3_column_double(stmt, 2);
std::cout << " Retrieved latest NPM values - PM10: " << pm10
<< ", PM2.5: " << pm25
<< ", PM1: " << pm1 << std::endl;
success = true;
} else {
std::cerr << " No NPM data found in database" << std::endl;
}
sqlite3_finalize(stmt);
return success;
}
// Function to retrieve latest CO2 data from the database
bool get_co2_data(sqlite3* db, float& co2) {
sqlite3_stmt* stmt;
bool success = false;
std::cout << " - Querying CO2 data from database..." << std::endl;
// Try data_CO2 table directly
const char* query_co2 = "SELECT CO2 FROM data_CO2 ORDER BY rowid DESC LIMIT 1";
if (sqlite3_prepare_v2(db, query_co2, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
co2 = sqlite3_column_double(stmt, 0);
std::cout << " Retrieved CO2 value from data_CO2: " << co2 << " ppm" << std::endl;
success = true;
} else {
std::cout << " No data found in data_CO2 table" << std::endl;
}
sqlite3_finalize(stmt);
} else {
std::cerr << " Error preparing CO2 query: " << sqlite3_errmsg(db) << std::endl;
// Check if table exists
const char* table_check = "SELECT name FROM sqlite_master WHERE type='table' AND name='data_CO2';";
if (sqlite3_prepare_v2(db, table_check, -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
std::cout << " data_CO2 table exists but query failed" << std::endl;
} else {
std::cout << " data_CO2 table does not exist in database" << std::endl;
}
sqlite3_finalize(stmt);
}
}
return success;
}
// Store previous values to check for changes
struct SensorData {
float pm10 = 0, pm25 = 0, pm1 = 0, co2 = 0;
std::string pm10_str, pm25_str, pm1_str, co2_str;
std::string pm10_status, pm25_status, pm1_status, co2_status;
rgb_matrix::Color pm10_color, pm25_color, pm1_color, co2_color;
};
// Initial screen setup - draw all static elements
void setup_screen(Canvas *canvas) {
std::cout << " - Setting up initial screen layout..." << std::endl;
canvas->Clear();
rgb_matrix::Color myCYAN(0, 255, 255);
rgb_matrix::Color bg_color(0, 0, 0);
rgb_matrix::Font font1;
if (!font1.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) {
std::cerr << "Error loading font!" << std::endl;
return;
}
int letter_spacing = 0;
// Calculate absolute positions for each row
// Instead of using relative positions with increments
int top_row_y = font1.baseline()-1;
int bottom_row_y = 33 + font1.baseline()-1; // Fixed position for second row
// Draw static labels for top row
rgb_matrix::DrawText(canvas, font1, 0, top_row_y, myCYAN, &bg_color, "PM10 µg/m³", letter_spacing);
rgb_matrix::DrawText(canvas, font1, 64, top_row_y, myCYAN, &bg_color, "PM2.5 µg/m³", letter_spacing);
rgb_matrix::DrawText(canvas, font1, 0, bottom_row_y, myCYAN, &bg_color, "PM1 µg/m³", letter_spacing);
rgb_matrix::DrawText(canvas, font1, 64, bottom_row_y, myCYAN, &bg_color, "CO₂ ppm", letter_spacing);
}
// Function to update only the values and status messages on the screen
void update_screen_values(Canvas *canvas, SensorData& prevData, float pm10, float pm25, float pm1, float co2) {
log("BLUE", "Updating screen values...");
rgb_matrix::Color myWHITE(255, 255, 255);
rgb_matrix::Color myGREEN(0, 255, 0); // Good
rgb_matrix::Color myYELLOW(255, 255, 0); // Moderate
rgb_matrix::Color myORANGE(255, 165, 0); // Degraded
rgb_matrix::Color myRED(255, 0, 0); // Bad
rgb_matrix::Color bg_color(0, 0, 0);
rgb_matrix::Font font1;
rgb_matrix::Font font2;
if (!font1.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) {
std::cerr << "Error loading font1!" << std::endl;
return;
}
if (!font2.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/9x18B.bdf")) {
std::cerr << "Error loading font2!" << std::endl;
return;
}
int letter_spacing = 0;
int x, y1, y2, y3;
// Convert float values to strings with 1 decimal place
std::stringstream ss_pm10, ss_pm25, ss_pm1, ss_co2;
ss_pm10 << std::fixed << std::setprecision(1) << pm10;
ss_pm25 << std::fixed << std::setprecision(1) << pm25;
ss_pm1 << std::fixed << std::setprecision(1) << pm1;
ss_co2 << std::fixed << std::setprecision(0) << co2;
std::string str_pm10 = ss_pm10.str();
std::string str_pm25 = ss_pm25.str();
std::string str_pm1 = ss_pm1.str();
std::string str_co2 = ss_co2.str();
// Define coordinates for values and status messages
x = 0;
y1 = font1.baseline() - 1; //ligne tout en haut (bleue PM10 µg/m³ )
y2 = y1 + font2.baseline() + 1; //ligne pour les chiffres (blanc bold)
y3 = y1 + y2 + 4; //ligne pour les adjectifs (BON, MOYEN, ETC)
/*
____ __ __ _ ___
| _ \| \/ / |/ _ \
| |_) | |\/| | | | | |
| __/| | | | | |_| |
|_| |_| |_|_|\___/
*/
// Update PM10 value and status if changed
if (str_pm10 != prevData.pm10_str) {
std::cout << " Updating PM10 value from " << prevData.pm10_str << " to " << str_pm10 << std::endl;
// On clear l'espace chiffre blanc
for (int i = 0; i < 60; i++) { //axe x (on clear de 0 à 60)
for (int j = y1 + 2; j < y2 + 1; j++) { //axe y (on clear de la ligne y1 à y2)
canvas->SetPixel(i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm10.c_str(), letter_spacing);
prevData.pm10_str = str_pm10;
}
// Determine PM10 status
std::string pm10_status;
rgb_matrix::Color pm10_color;
if (pm10 < 15) {
pm10_status = "BON";
pm10_color = myGREEN;
} else if (pm10 >= 15 && pm10 < 30) {
pm10_status = "MOYEN";
pm10_color = myYELLOW;
} else if (pm10 >= 30 && pm10 < 75) {
pm10_status = "DEGRADE";
pm10_color = myORANGE;
} else {
pm10_status = "MAUVAIS";
pm10_color = myRED;
}
// Update PM10 status if changed
std::cout << " Updating PM10 status to " << pm10_status << std::endl;
// on clear l'espace texte BON, MOYEN
for (int i = 0; i < 60; i++) {
for (int j = y2 + 4; j < y3 + 1; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, pm10_color, &bg_color, pm10_status.c_str(), letter_spacing);
/*
____ __ __ ____ ____
| _ \| \/ |___ \ | ___|
| |_) | |\/| | __) | |___ \
| __/| | | |/ __/ _ ___) |
|_| |_| |_|_____(_)____/
*/
// Update PM2.5 value and status
x = 64;
if (str_pm25 != prevData.pm25_str) {
std::cout << " Updating PM2.5 value from " << prevData.pm25_str << " to " << str_pm25 << std::endl;
// Clear previous value area
for (int i = 0; i < 60; i++) {
for (int j = y1 + 2; j < y2 + 1; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm25.c_str(), letter_spacing);
prevData.pm25_str = str_pm25;
}
// Determine PM2.5 status
std::string pm25_status;
rgb_matrix::Color pm25_color;
if (pm25 < 10) {
pm25_status = "BON";
pm25_color = myGREEN;
} else if (pm25 >= 10 && pm25 < 20) {
pm25_status = "MOYEN";
pm25_color = myYELLOW;
} else if (pm25 >= 20 && pm25 < 50) {
pm25_status = "DEGRADE";
pm25_color = myORANGE;
} else {
pm25_status = "MAUVAIS";
pm25_color = myRED;
}
// Update PM2.5 status
std::cout << " Updating PM2.5 status to " << pm25_status << std::endl;
// Clear previous status area
for (int i = 0; i < 60; i++) {
for (int j = y2 + 2; j < y3 + 1; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, pm25_color, &bg_color, pm25_status.c_str(), letter_spacing);
/*
____ __ __ _
| _ \| \/ / |
| |_) | |\/| | |
| __/| | | | |
|_| |_| |_|_|
*/
// Update PM1 value and status
x = 0;
y1 += 34;
y2 += 34;
y3 += 33;
if (str_pm1 != prevData.pm1_str) {
std::cout << " Updating PM1 value from " << prevData.pm1_str << " to " << str_pm1 << std::endl;
// Clear previous value area
for (int i = 0; i < 60; i++) {
for (int j = y1 + 3; j < y2 + 1; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_pm1.c_str(), letter_spacing);
prevData.pm1_str = str_pm1;
}
// Determine PM1 status
std::string pm1_status;
rgb_matrix::Color pm1_color;
if (pm1 < 10) {
pm1_status = "BON";
pm1_color = myGREEN;
} else if (pm1 >= 10 && pm1 < 20) {
pm1_status = "MOYEN";
pm1_color = myYELLOW;
} else if (pm1 >= 20 && pm1 < 50) {
pm1_status = "DEGRADE";
pm1_color = myORANGE;
} else {
pm1_status = "MAUVAIS";
pm1_color = myRED;
}
// Update
std::cout << " Updating PM1 status to " << pm1_status << std::endl;
// Clear previous status area
for (int i = 0; i < 60; i++) {
for (int j = y2 + 3; j < y3 + 1; j++) {
canvas->SetPixel(x + i, j, 255, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, pm1_color, &bg_color, pm1_status.c_str(), letter_spacing);
/*
____ ___ ____
/ ___/ _ \___ \
| | | | | |__) |
| |__| |_| / __/
\____\___/_____|
*/
// Update CO2 value and status
x = 64;
if (str_co2 != prevData.co2_str) {
std::cout << " Updating CO2 value from " << prevData.co2_str << " to " << str_co2 << std::endl;
// Clear previous value area
for (int i = 0; i < 60; i++) {
for (int j = y1 + 3; j < y2 + 1; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new value
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, str_co2.c_str(), letter_spacing);
prevData.co2_str = str_co2;
}
// Determine CO2 status
std::string co2_status;
rgb_matrix::Color co2_color;
if (co2 < 800) {
co2_status = "BON";
co2_color = myGREEN;
} else if (co2 >= 800 && co2 < 1500) {
co2_status = "AERER SVP";
co2_color = myORANGE;
} else {
co2_status = "AERER VITE";
co2_color = myRED;
}
// Update CO2 status
std::cout << " Updating CO2 status to " << co2_status << std::endl;
// Clear previous status area
for (int i = 0; i < 60; i++) {
for (int j = y2 + 4; j < y3 + 1; j++) {
canvas->SetPixel(x + i, j, 0, 0, 0);
}
}
// Draw new status
rgb_matrix::DrawText(canvas, font1, x, y3, co2_color, &bg_color, co2_status.c_str(), letter_spacing);
}
/*
__ __ _ ___ _ _
| \/ | / \ |_ _| \ | |
| |\/| | / _ \ | || \| |
| | | |/ ___ \ | || |\ |
|_| |_/_/ \_\___|_| \_|
*/
int main(int argc, char *argv[]) {
log("BLUE", "Program 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; // Adjust based on your Raspberry Pi model
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");
// Set up the initial screen layout (static elements)
setup_screen(canvas);
// Structure to store previous data values
SensorData prevData;
// Initial sensor values
float pm10 = 0, pm25 = 0, pm1 = 0, co2 = 0;
/*
_ ___ ___ ____
| | / _ \ / _ \| _ \
| | | | | | | | | |_) |
| |__| |_| | |_| | __/
|_____\___/ \___/|_|
*/
log("BLUE", "Starting main loop");
while (running) {
// Get data from database
bool npm_success = get_npm_data(db, pm10, pm25, pm1);
bool co2_success = get_co2_data(db, co2);
if (!npm_success) {
std::cerr << "Error retrieving NPM data from database" << std::endl;
}
if (!co2_success) {
std::cerr << "Error retrieving CO2 data from database" << std::endl;
}
// Update just the values and status messages
update_screen_values(canvas, prevData, pm10, pm25, pm1, co2);
// Sleep before next update
std::cout << " - Update complete, sleeping for 10 seconds..." << std::endl;
for (int i = 0; i < 10 && running; i++) {
sleep(1); // Sleep in 1-second increments to be more responsive to stop signals
}
}
// Clean up
std::cout << " - Program terminating, cleaning up..." << std::endl;
canvas->Clear();
delete canvas;
sqlite3_close(db);
std::cout << " - Program terminated" << std::endl;
return 0;
}

View File

@@ -1,4 +1,11 @@
/* /*
__ __ _ _____ ____ _____ __
| \/ | / \|_ _| _ \|_ _\ \/ /
| |\/| | / _ \ | | | |_) || | \ /
| | | |/ ___ \| | | _ < | | / \
|_| |_/_/ \_\_| |_| \_\___/_/\_\
Script to launch screens to display compounds Script to launch screens to display compounds
Get values from Get values from
NextPM -> "/var/www/moduleair_pro_4g/matrix/input_NPM.txt" NextPM -> "/var/www/moduleair_pro_4g/matrix/input_NPM.txt"
@@ -8,7 +15,12 @@ Sensirion ->
sudo /var/www/moduleair_pro_4g/matrix/screen_sensors_loop sudo /var/www/moduleair_pro_4g/matrix/screen_sensors_loop
Pour compiler: Pour compiler:
g++ -Iinclude -Llib screen_sensors_loop.cc -o screen_sensors_loop -lrgbmatrix g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/screenSensor/test_image.cc -o /var/www/moduleair_pro_4g/matrix/screenSensor/test_image.cc -lrgbmatrix
pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/screenSensors/screen_sensors_loop
*/ */
#include "led-matrix.h" #include "led-matrix.h"
@@ -30,7 +42,7 @@ void signal_handler(int signum) {
} }
//message d'accueil //message d'accueil (OLD)
void draw_welcome_screen(Canvas *canvas) { void draw_welcome_screen(Canvas *canvas) {
canvas->Clear(); canvas->Clear();
@@ -171,7 +183,7 @@ int main(int argc, char *argv[]) {
return 1; return 1;
// Display welcome screen once // Display welcome screen once
draw_welcome_screen(canvas); //draw_welcome_screen(canvas);
// Initialize variables for main loop // Initialize variables for main loop
std::string input_file_NPM = "/var/www/moduleair_pro_4g/matrix/input_NPM.txt"; std::string input_file_NPM = "/var/www/moduleair_pro_4g/matrix/input_NPM.txt";