Files
moduleair_pro_4g/matrix/screenSensors/displayAll4_v2.cc
Your Name 6762196480 update
2025-03-31 15:52:57 +02:00

585 lines
18 KiB
C++

/*
__ __ _ _____ ____ _____ __
| \/ | / \|_ _| _ \|_ _\ \/ /
| |\/| | / _ \ | | | |_) || | \ /
| | | |/ ___ \| | | _ < | | / \
|_| |_/_/ \_\_| |_| \_\___/_/\_\
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 (axe x pour centrer le texte = 32 - (nb_lettre*7/2))
std::string pm10_status;
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);
}
}
rgb_matrix::Color pm10_color;
if (pm10 < 15) {
pm10_status = "BON";
pm10_color = myGREEN;
x = 20;
} else if (pm10 >= 15 && pm10 < 30) {
pm10_status = "MOYEN";
pm10_color = myYELLOW;
x = 15;
} else if (pm10 >= 30 && pm10 < 75) {
pm10_status = "DEGRADE";
pm10_color = myORANGE;
x=8;
} else {
pm10_status = "MAUVAIS";
pm10_color = myRED;
x=8;
}
// 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;
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);
}
}
rgb_matrix::Color pm25_color;
if (pm25 < 10) {
pm25_status = "BON";
pm25_color = myGREEN;
x += 20;
} else if (pm25 >= 10 && pm25 < 20) {
pm25_status = "MOYEN";
pm25_color = myYELLOW;
x += 20;
} else if (pm25 >= 20 && pm25 < 50) {
pm25_status = "DEGRADE";
pm25_color = myORANGE;
x += 8;
} else {
pm25_status = "MAUVAIS";
pm25_color = myRED;
x += 8;
}
// 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;
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, 0, 0, 0);
}
}
rgb_matrix::Color pm1_color;
if (pm1 < 10) {
pm1_status = "BON";
pm1_color = myGREEN;
x = 20;
} else if (pm1 >= 10 && pm1 < 20) {
pm1_status = "MOYEN";
pm1_color = myYELLOW;
x = 15;
} else if (pm1 >= 20 && pm1 < 50) {
pm1_status = "DEGRADE";
pm1_color = myORANGE;
x = 8;
} else {
pm1_status = "MAUVAIS";
pm1_color = myRED;
x = 8;
}
// 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;
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);
}
}
// Update CO2 status
rgb_matrix::Color co2_color;
if (co2 < 800) {
co2_status = "BON";
co2_color = myGREEN;
x += 20;
} else if (co2 >= 800 && co2 < 1500) {
co2_status = "AERER SVP";
co2_color = myORANGE;
x += 5;
} else {
co2_status = "AERER VITE";
co2_color = myRED;
x += 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;
}