v1.12.1: S88 ecrit toujours + code d'etat, plus de CO2 perime transmis
Probleme vu sur pro100: sonde S88 muette (panne cablage) mais write_data.py n'ecrivait rien -> la base gardait la derniere valeur (487 ppm d'hier) et la loop d'envoi la transmettait en boucle. - data_S88: nouvelle colonne s88_status (0=OK, 0xFF=sonde muette), comme npm_status/noise_status. Migration via create_db.py + set_config.py + self-heal. - S88/write_data.py: ecrit DESORMAIS une ligne a chaque cycle (CO2=0 + 0xFF si pas de reponse). Connexion SQLite timeout=10 (anti database-is-locked). - SARA_send_data_v2.py: lit s88_status; si 0xFF -> bytes 81-82 restent 0xFFFF (CO2 absent) au lieu d'envoyer une valeur perimee. Compatible bases non migrees. - database.html + launcher.php: badge statut + colonne dans les exports CSV. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,14 @@ 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
|
||||
|
||||
Modbus RTU 9600 8N1. Reads IR1..IR4 in one frame to get status + CO2.
|
||||
If status (IR1) is non-zero the reading is skipped (warm-up or error).
|
||||
|
||||
A row is ALWAYS written each run, with a status byte (like data_NPM.npm_status
|
||||
and data_NOISE.noise_status):
|
||||
s88_status = 0 -> OK, CO2 valid
|
||||
s88_status = 0xFF -> sensor not responding / read error (CO2 stored as 0)
|
||||
This is essential: without it the table keeps the last good value forever and
|
||||
loop/SARA_send_data_v2.py would keep transmitting a stale CO2 reading when the
|
||||
sensor is actually dead.
|
||||
'''
|
||||
|
||||
import serial
|
||||
@@ -14,6 +21,9 @@ DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db"
|
||||
DEFAULT_PORT = "/dev/ttyAMA5"
|
||||
BAUDRATE = 9600
|
||||
|
||||
STATUS_OK = 0x00
|
||||
STATUS_NO_RESPONSE = 0xFF
|
||||
|
||||
# Modbus slave address: 0xFE = "any address", any S88 responds regardless of
|
||||
# its configured individual address. Fine for single-sensor setups.
|
||||
SLAVE_ADDR = 0xFE
|
||||
@@ -80,20 +90,32 @@ def read_co2(ser):
|
||||
|
||||
|
||||
def main():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn = sqlite3.connect(DB_PATH, timeout=10)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Self-heal: ensure the table exists even if create_db.py was skipped during OTA.
|
||||
# Duplicates the canonical schema from sqlite/create_db.py — keep them in sync.
|
||||
# Self-heal: ensure the table + status column exist even if create_db.py was
|
||||
# skipped during OTA. Duplicates the canonical schema from sqlite/create_db.py
|
||||
# — keep them in sync.
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS data_S88 (
|
||||
timestamp TEXT,
|
||||
CO2 INTEGER
|
||||
CO2 INTEGER,
|
||||
s88_status INTEGER DEFAULT 0
|
||||
)
|
||||
""")
|
||||
try:
|
||||
cursor.execute("ALTER TABLE data_S88 ADD COLUMN s88_status INTEGER DEFAULT 0")
|
||||
except Exception:
|
||||
pass # Column already exists
|
||||
conn.commit()
|
||||
|
||||
port = get_config(cursor, "S88_port", DEFAULT_PORT)
|
||||
|
||||
# Default to the error state; only a clean read flips it to OK.
|
||||
co2_ppm = 0
|
||||
status = STATUS_NO_RESPONSE
|
||||
|
||||
ser = None
|
||||
try:
|
||||
ser = serial.Serial(
|
||||
port=port,
|
||||
@@ -103,36 +125,40 @@ def main():
|
||||
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))
|
||||
if co2 is not None:
|
||||
co2_ppm = int(round(co2))
|
||||
status = STATUS_OK
|
||||
else:
|
||||
print("S88 not responding -> writing error row (s88_status=0xFF)")
|
||||
except Exception as e:
|
||||
print(f"S88 serial/read error: {e} -> writing error row (s88_status=0xFF)")
|
||||
finally:
|
||||
if ser is not None:
|
||||
try:
|
||||
ser.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ALWAYS write a row so the send loop never reuses a stale value.
|
||||
try:
|
||||
cursor.execute("SELECT last_updated FROM timestamp_table LIMIT 1")
|
||||
row = cursor.fetchone()
|
||||
rtc_time_str = row[0]
|
||||
rtc_time_str = row[0] if row else "not connected"
|
||||
|
||||
cursor.execute(
|
||||
"INSERT INTO data_S88 (timestamp, CO2) VALUES (?, ?)",
|
||||
(rtc_time_str, co2_ppm),
|
||||
"INSERT INTO data_S88 (timestamp, CO2, s88_status) VALUES (?, ?, ?)",
|
||||
(rtc_time_str, co2_ppm, status),
|
||||
)
|
||||
conn.commit()
|
||||
print(f"CO2: {co2_ppm} ppm (saved at {rtc_time_str})")
|
||||
|
||||
if status == STATUS_OK:
|
||||
print(f"CO2: {co2_ppm} ppm (saved at {rtc_time_str})")
|
||||
else:
|
||||
print(f"S88 no data, s88_status=0x{status:02X} (saved at {rtc_time_str})")
|
||||
except Exception as e:
|
||||
print(f"S88 error: {e}")
|
||||
print(f"S88 DB write error: {e}")
|
||||
finally:
|
||||
try:
|
||||
ser.close()
|
||||
except Exception:
|
||||
pass
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user