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:
98
CCS811/write_data.py
Normal file
98
CCS811/write_data.py
Normal 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()
|
||||
Reference in New Issue
Block a user