570 lines
18 KiB
C++
570 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
|
|
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;
|
|
} |