/* __ __ _ _____ ____ _____ __ | \/ | / \|_ _| _ \|_ _\ \/ / | |\/| | / _ \ | | | |_) || | \ / | | | |/ ___ \| | | _ < | | / \ |_| |_/_/ \_\_| |_| \_\___/_/\_\ 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; }