From 239bdfea69227c6b8b8bf9b7e9d19b3b512dfdc5 Mon Sep 17 00:00:00 2001 From: PaulVua Date: Mon, 1 Jun 2026 16:15:37 +0200 Subject: [PATCH] v1.9.13: Capteur CO2 Senseair S88 - scaffolding Table data_S88, flag config S88 + port configurable S88_port (default /dev/ttyAMA5), service/timer systemd 10s, carte sensors.html, endpoint launcher.php, toggle admin.html. read_co2() est un stub NotImplementedError en attente du datasheet. Co-Authored-By: Claude Opus 4.7 (1M context) --- S88/get_data.py | 72 +++++++++++++++++++++++++++++++++ S88/write_data.py | 81 ++++++++++++++++++++++++++++++++++++++ VERSION | 2 +- changelog.json | 13 ++++++ html/admin.html | 9 +++++ html/launcher.php | 19 +++++++-- html/sensors.html | 69 ++++++++++++++++++++++++++++++++ services/setup_services.sh | 34 +++++++++++++++- sqlite/create_db.py | 8 ++++ sqlite/set_config.py | 2 + 10 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 S88/get_data.py create mode 100644 S88/write_data.py diff --git a/S88/get_data.py b/S88/get_data.py new file mode 100644 index 0000000..bd67e0c --- /dev/null +++ b/S88/get_data.py @@ -0,0 +1,72 @@ +''' +Live read of the Senseair S88 CO2 sensor (used by the web "Get Data" button). +Prints a JSON object: {"CO2": } or {"error": ""}. + +Usage: /usr/bin/python3 /var/www/nebuleair_pro_4g/S88/get_data.py [port] +If no port is given, the script reads S88_port from config_table. +''' + +import json +import sqlite3 +import sys + +import serial + +DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db" +DEFAULT_PORT = "/dev/ttyAMA5" +BAUDRATE = 9600 + + +def get_port_from_config(): + try: + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + cursor.execute("SELECT value FROM config_table WHERE key = ?", ("S88_port",)) + row = cursor.fetchone() + conn.close() + return row[0] if row else DEFAULT_PORT + except Exception: + return DEFAULT_PORT + + +def read_co2(ser): + # TODO: implement the Senseair S88 read protocol once the datasheet is provided. + # Expected return: integer CO2 concentration in ppm, or None on failure. + raise NotImplementedError("Senseair S88 read protocol not implemented yet") + + +def main(): + port = sys.argv[1] if len(sys.argv) > 1 else get_port_from_config() + + try: + ser = serial.Serial( + port=port, + baudrate=BAUDRATE, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS, + timeout=1, + ) + except Exception as e: + print(json.dumps({"error": f"Cannot open {port}: {e}"})) + return + + try: + co2 = read_co2(ser) + if co2 is None: + print(json.dumps({"error": "No data from S88"})) + return + print(json.dumps({"CO2": int(round(co2))})) + except NotImplementedError as e: + print(json.dumps({"error": str(e)})) + except Exception as e: + print(json.dumps({"error": f"S88 read error: {e}"})) + finally: + try: + ser.close() + except Exception: + pass + + +if __name__ == "__main__": + main() diff --git a/S88/write_data.py b/S88/write_data.py new file mode 100644 index 0000000..10cdd16 --- /dev/null +++ b/S88/write_data.py @@ -0,0 +1,81 @@ +''' +Script to get CO2 values from Senseair S88 sensor and write to database +/usr/bin/python3 /var/www/nebuleair_pro_4g/S88/write_data.py + +Port and protocol details come from config_table (key S88_port). +The actual sensor read protocol is implemented in read_co2() below. +''' + +import serial +import sqlite3 +import sys + +DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db" +DEFAULT_PORT = "/dev/ttyAMA5" +BAUDRATE = 9600 + + +def get_config(cursor, key, default): + cursor.execute("SELECT value FROM config_table WHERE key = ?", (key,)) + row = cursor.fetchone() + return row[0] if row else default + + +def read_co2(ser): + # TODO: implement the Senseair S88 read protocol once the datasheet is provided. + # Expected return: integer CO2 concentration in ppm, or None on failure. + raise NotImplementedError("Senseair S88 read protocol not implemented yet") + + +def main(): + conn = sqlite3.connect(DB_PATH) + cursor = conn.cursor() + + port = get_config(cursor, "S88_port", DEFAULT_PORT) + + try: + ser = serial.Serial( + port=port, + baudrate=BAUDRATE, + parity=serial.PARITY_NONE, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS, + timeout=1, + ) + except Exception as e: + print(f"Error opening serial port {port}: {e}") + conn.close() + sys.exit(1) + + try: + co2 = read_co2(ser) + if co2 is None: + print("Failed to read CO2 from S88.") + return + + co2_ppm = int(round(co2)) + + cursor.execute("SELECT last_updated FROM timestamp_table LIMIT 1") + row = cursor.fetchone() + rtc_time_str = row[0] + + cursor.execute( + "INSERT INTO data_S88 (timestamp, CO2) VALUES (?, ?)", + (rtc_time_str, co2_ppm), + ) + conn.commit() + print(f"CO2: {co2_ppm} ppm (saved at {rtc_time_str})") + except NotImplementedError as e: + print(f"S88 not ready: {e}") + except Exception as e: + print(f"S88 error: {e}") + finally: + try: + ser.close() + except Exception: + pass + conn.close() + + +if __name__ == "__main__": + main() diff --git a/VERSION b/VERSION index 1fe2d37..2a72e6f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.9.12 +1.9.13 diff --git a/changelog.json b/changelog.json index eff8ce2..df0036c 100644 --- a/changelog.json +++ b/changelog.json @@ -1,5 +1,18 @@ { "versions": [ + { + "version": "1.9.13", + "date": "2026-06-01", + "changes": { + "features": [ + "Capteur CO2 Senseair S88: scaffolding complet (table SQLite data_S88, flag config S88 + port configurable S88_port par défaut /dev/ttyAMA5, service/timer systemd 10s, carte sensors.html, endpoint launcher.php ?type=s88, toggle admin.html 'Send CO2 sensor data'). La fonction read_co2() est un stub NotImplementedError en attente du datasheet du protocole — le service tourne mais log l'erreur sans planter." + ], + "improvements": [], + "fixes": [], + "compatibility": [] + }, + "notes": "Ajout du capteur CO2 Senseair S88. Pour activer après MAJ: exécuter sqlite/create_db.py + sqlite/set_config.py (pour la migration table+config), puis services/setup_services.sh. La lecture sensor est désactivée tant que le datasheet n'est pas intégré." + }, { "version": "1.9.12", "date": "2026-05-28", diff --git a/html/admin.html b/html/admin.html index bd4b5d2..ce6d4b9 100755 --- a/html/admin.html +++ b/html/admin.html @@ -139,6 +139,13 @@ +
+ + +
+