Files
nebuleair_pro_4g/CCS811/write_data.py
PaulVua 46c73acb7e v1.10.1: OTA installe les deps pip + filtre lectures parasites CCS811
Découvert en vérif SSH sur nebuleair-pro100 : le timer CCS811 échouait en
ModuleNotFoundError car l'OTA fait git pull mais ne réinstallait jamais les
dépendances pip (installation_part1.sh ne tourne qu'à l'install neuve).

- requirements.txt: source unique de vérité des deps Python
- installation_part1.sh: install via requirements.txt (chemin relatif au script,
  le repo n'est pas encore cloné dans /var/www à cette étape)
- update_firmware.sh: nouvelle étape 2a, pip install -r requirements.txt
  (idempotent) -> les capteurs déjà déployés récupèrent les libs manquantes à l'OTA
- CCS811/write_data.py + get_data.py: skip des lectures eCO2 < 400 ppm
  (échantillon 0/0 parasite juste après init du driver, plancher physique = 400)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 15:51:58 +02:00

106 lines
3.0 KiB
Python

'''
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)
# eCO2 has a physical floor of 400 ppm. Just after the driver (re)inits,
# the CCS811 can return a 0/0 sample before its first valid measurement is
# ready — those are spurious, drop them (next 10 s tick will retry).
if eco2 < 400:
print(f"CCS811: reading not settled (eCO2={eco2}), skipping.")
return
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()