v1.10.0: intégration capteur CCS811 (TVOC/eCO2, I2C)

Nouveau capteur de qualité d'air CCS811 sur le bus I2C, calqué sur le
pattern S88 (local-only, pas encore dans le payload de transmission).

- CCS811/get_data.py (lecture live) + write_data.py (timer 10s, self-heal table)
- table data_CCS811 (timestamp, eCO2, TVOC) dans create_db.py
- config CCS811 (bool) + CCS811_address (0x5A/0x5B, défaut 0x5A) dans set_config.py
- service+timer systemd nebuleair-ccs811-data (10s) + ajout boucle d'activation
- admin.html: case d'activation + dropdown adresse I2C
- sensors.html: carte Get Data (TVOC + eCO2)
- database.html + launcher.php: consultation/export/stats data_CCS811
- lib adafruit-circuitpython-ccs811 dans installation_part1.sh
- CCS811/README.md: câblage, adresses, warning clock-stretching I2C sur Pi
- CLAUDE.md + changelog mis à jour

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
PaulVua
2026-06-02 14:27:11 +02:00
parent 4767b145b2
commit 4f3d273981
14 changed files with 455 additions and 9 deletions

98
CCS811/write_data.py Normal file
View File

@@ -0,0 +1,98 @@
'''
Script to get air-quality values from the AMS CCS811 sensor and write to database.
/usr/bin/python3 /var/www/nebuleair_pro_4g/CCS811/write_data.py
CCS811 is a MOX gas sensor: eCO2 (equivalent CO2 in ppm, derived from VOCs) and
TVOC (Total VOC in ppb). TVOC is the primary measurement of interest.
I2C, library adafruit-circuitpython-ccs811. Address from config_table
(key CCS811_address, e.g. "0x5A" / "0x5B"), default 0x5A.
'''
import sqlite3
import sys
import time
DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db"
DEFAULT_ADDRESS = 0x5A
DATA_READY_RETRIES = 30
DATA_READY_DELAY = 0.2 # seconds
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 main():
conn = sqlite3.connect(DB_PATH)
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.
cursor.execute("""
CREATE TABLE IF NOT EXISTS data_CCS811 (
timestamp TEXT,
eCO2 INTEGER,
TVOC INTEGER
)
""")
conn.commit()
addr_str = get_config(cursor, "CCS811_address", "0x5A")
try:
address = int(str(addr_str), 16)
except ValueError:
address = DEFAULT_ADDRESS
try:
import board
import busio
import adafruit_ccs811
except Exception as e:
print(f"CCS811: library import failed: {e}")
conn.close()
sys.exit(1)
try:
i2c = busio.I2C(board.SCL, board.SDA)
ccs811 = adafruit_ccs811.CCS811(i2c, address=address)
except Exception as e:
print(f"CCS811: cannot init at {hex(address)}: {e}")
conn.close()
sys.exit(1)
try:
ready = False
for _ in range(DATA_READY_RETRIES):
if ccs811.data_ready:
ready = True
break
time.sleep(DATA_READY_DELAY)
if not ready:
print("CCS811: data not ready (warming up?), skipping.")
return
eco2 = int(ccs811.eco2)
tvoc = int(ccs811.tvoc)
cursor.execute("SELECT last_updated FROM timestamp_table LIMIT 1")
row = cursor.fetchone()
rtc_time_str = row[0]
cursor.execute(
"INSERT INTO data_CCS811 (timestamp, eCO2, TVOC) VALUES (?, ?, ?)",
(rtc_time_str, eco2, tvoc),
)
conn.commit()
print(f"eCO2: {eco2} ppm, TVOC: {tvoc} ppb (saved at {rtc_time_str})")
except Exception as e:
print(f"CCS811 error: {e}")
finally:
conn.close()
if __name__ == "__main__":
main()