diff --git a/README.md b/README.md index 76a88a5..e9069fe 100755 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64. ## General ``` 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 git clone http://gitea.aircarto.fr/PaulVua/moduleair_pro_4g.git /var/www/moduleair_pro_4g sudo mkdir /var/www/moduleair_pro_4g/logs diff --git a/matrix/screenSensors/displayAll4_v1 b/matrix/screenSensors/displayAll4_v1 new file mode 100644 index 0000000..0cbadff Binary files /dev/null and b/matrix/screenSensors/displayAll4_v1 differ diff --git a/matrix/screenSensors/displayAll4_v1.cc b/matrix/screenSensors/displayAll4_v1.cc new file mode 100644 index 0000000..03c412a --- /dev/null +++ b/matrix/screenSensors/displayAll4_v1.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using rgb_matrix::RGBMatrix; +using rgb_matrix::Canvas; + +std::atomic 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; +} \ No newline at end of file diff --git a/matrix/screenSensors/displayAll4_v2 b/matrix/screenSensors/displayAll4_v2 new file mode 100644 index 0000000..46e6144 Binary files /dev/null and b/matrix/screenSensors/displayAll4_v2 differ diff --git a/matrix/screenSensors/displayAll4_v2.cc b/matrix/screenSensors/displayAll4_v2.cc new file mode 100644 index 0000000..5cfef54 --- /dev/null +++ b/matrix/screenSensors/displayAll4_v2.cc @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using rgb_matrix::RGBMatrix; +using rgb_matrix::Canvas; + +std::atomic 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; +} \ No newline at end of file diff --git a/matrix/screenSensors/screen_sensors_loop.cc b/matrix/screenSensors/screen_sensors_loop.cc index 4db8e48..643b509 100644 --- a/matrix/screenSensors/screen_sensors_loop.cc +++ b/matrix/screenSensors/screen_sensors_loop.cc @@ -1,4 +1,11 @@ /* + __ __ _ _____ ____ _____ __ + | \/ | / \|_ _| _ \|_ _\ \/ / + | |\/| | / _ \ | | | |_) || | \ / + | | | |/ ___ \| | | _ < | | / \ + |_| |_/_/ \_\_| |_| \_\___/_/\_\ + + Script to launch screens to display compounds Get values from 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 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" @@ -30,7 +42,7 @@ void signal_handler(int signum) { } -//message d'accueil +//message d'accueil (OLD) void draw_welcome_screen(Canvas *canvas) { canvas->Clear(); @@ -171,7 +183,7 @@ int main(int argc, char *argv[]) { return 1; // Display welcome screen once - draw_welcome_screen(canvas); + //draw_welcome_screen(canvas); // Initialize variables for main loop std::string input_file_NPM = "/var/www/moduleair_pro_4g/matrix/input_NPM.txt";