Vérif terrain sur pro100 : à 100 kHz le CCS811 renvoie des valeurs corrompues 0x8000 (32768) par clock-stretching, et le modèle oneshot-reset-toutes-les-10s ne donne que le 1er échantillon post-init (garbage). Refonte : - CCS811/daemon.py: service long-running (Type=simple, Restart=always). Init 1x, boucle lecture/écriture 10s, filtre eCO2 dans [400,8192], re-init auto sur erreurs I2C répétées. Remplace write_data.py (supprimé). - CCS811/get_data.py: lit la dernière ligne data_CCS811 au lieu du capteur (évite la collision I2C avec le daemon -> corruption observée). - setup_services.sh: service daemon + self-heal suppression de l'ancien .timer; activation hors boucle timers. - launcher.php: .timer -> .service (map statut + allowedServices x2). - update_firmware.sh: redémarre le daemon à l'OTA. - doc: README (archi daemon + I2C 10kHz confirmé requis), CLAUDE.md, changelog. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
4.4 KiB
Markdown
102 lines
4.4 KiB
Markdown
# CCS811 — Capteur qualité d'air (eCO2 / TVOC)
|
||
|
||
Capteur de gaz **MOX** (oxyde métallique) AMS CCS811. Connecté en **I2C**.
|
||
|
||
## ⚠ À lire avant de câbler
|
||
|
||
Le CCS811 **n'est pas** un capteur CO2 NDIR comme le Senseair S88. C'est un capteur
|
||
de COV (composés organiques volatils) qui mesure :
|
||
|
||
- **TVOC** (Total Volatile Organic Compounds) — en **ppb**. C'est la mesure réellement
|
||
utile / fiable du capteur, et celle qui nous intéresse ici.
|
||
- **eCO2** (CO2 *équivalent*) — en **ppm**, plage 400–8192. Valeur *calculée* à partir
|
||
du TVOC par un algorithme interne, ce **n'est pas** une mesure directe du CO2. Pour
|
||
un vrai CO2, utiliser le S88. On stocke quand même l'eCO2 (gratuit, vient de la même
|
||
lecture) mais ne pas le confondre avec une mesure NDIR.
|
||
|
||
## ⚠ Clock-stretching I2C sur Raspberry Pi
|
||
|
||
Le CCS811 utilise massivement le **clock-stretching** I2C. Le contrôleur I2C matériel
|
||
du Raspberry Pi (BSC) gère **mal** le clock-stretching (bug matériel documenté). Sans
|
||
mitigation, les lectures échouent typiquement en `OSError` / `Remote I/O error`.
|
||
|
||
**Mitigation** : ralentir le bus I2C dans `/boot/firmware/config.txt` :
|
||
|
||
```
|
||
dtparam=i2c_arm_baudrate=10000
|
||
```
|
||
|
||
(10 kHz au lieu de 100 kHz par défaut.) Reboot ensuite.
|
||
|
||
**Confirmé nécessaire sur le terrain** (nebuleair-pro100, juin 2026) : à 100 kHz le
|
||
CCS811 renvoie des valeurs corrompues 0x8000+ (32768) par intermittence et finit en
|
||
état d'erreur. À 10 kHz c'est stable. Ce réglage n'est pas géré par le repo (fichier
|
||
hors `/var/www`), il doit être posé à la main sur chaque capteur équipé d'un CCS811.
|
||
|
||
Vérifier la présence du capteur :
|
||
|
||
```bash
|
||
sudo i2cdetect -y 1 # doit montrer 5a (ou 5b selon la broche ADDR)
|
||
```
|
||
|
||
## Adresse I2C
|
||
|
||
- **0x5A** : ADDR à GND — défaut des breakouts **Adafruit**. Valeur par défaut du firmware.
|
||
- **0x5B** : ADDR à VDD — défaut des breakouts **SparkFun** / modules génériques.
|
||
|
||
Configurable dans `admin.html` (clé config `CCS811_address`, dropdown 0x5A / 0x5B).
|
||
|
||
## Câblage I2C
|
||
|
||
| CCS811 | Raspberry Pi |
|
||
|---|---|
|
||
| VCC / VIN | 3.3V |
|
||
| GND | GND |
|
||
| SDA | SDA (GPIO2) |
|
||
| SCL | SCL (GPIO3) |
|
||
| WAK / nWAKE | GND (réveil permanent ; sinon laisser le module gérer) |
|
||
| ADDR | GND → 0x5A, VDD → 0x5B |
|
||
|
||
⚠ La plupart des breakouts CCS811 sont en **3.3V** logique. Ne pas alimenter en 5V
|
||
sans level-shifter sauf si le module embarque son propre régulateur + shifter.
|
||
|
||
## Burn-in / conditionnement
|
||
|
||
- **Burn-in initial** : ~48 h de fonctionnement continu avant des valeurs stables (1ère mise en service).
|
||
- **Warm-up** à chaque démarrage : ~20 min pour des valeurs fiables. Au démarrage le
|
||
capteur renvoie souvent eCO2=400 ppm / TVOC=0 ppb (valeurs de repos).
|
||
|
||
## Implémentation NebuleAir
|
||
|
||
⚠ **Architecture : daemon, PAS un timer oneshot** (contrairement aux autres capteurs).
|
||
Le CCS811 doit être initialisé **une seule fois** puis lu en continu :
|
||
|
||
- chaque (ré)init fait un reset + app_start, et les premiers échantillons juste après
|
||
sont du garbage (eCO2 = 0, ou valeurs 0x8000+ = 32768 dues au clock-stretching) ;
|
||
- un cycle reset toutes les 10 s empêche l'algorithme de baseline de se construire.
|
||
|
||
Composants :
|
||
|
||
- `CCS811/daemon.py` — service long-running (`nebuleair-ccs811-data.service`,
|
||
`Type=simple`, `Restart=always`). Init une fois, puis boucle : toutes les 10 s,
|
||
lit un échantillon **valide** (eCO2 ∈ [400, 8192], le reste est jeté) et l'écrit
|
||
dans `data_CCS811 (timestamp, eCO2, TVOC)`. Re-init automatique du capteur après
|
||
plusieurs erreurs I2C consécutives.
|
||
- `CCS811/get_data.py` — bouton "Get Data" du web. **Ne lit PAS le capteur** (ça
|
||
entrerait en collision I2C avec le daemon et corromprait la sonde) : renvoie la
|
||
**dernière ligne** de `data_CCS811`. Affiche `{"eCO2","TVOC","timestamp"}` ou
|
||
`{"error": "..."}`.
|
||
|
||
Librairie Python : `adafruit-circuitpython-ccs811` (dans `requirements.txt`,
|
||
installée par `installation_part1.sh` ET par `update_firmware.sh`). La table est
|
||
créée par `sqlite/create_db.py` et self-healée par `daemon.py`
|
||
(CREATE TABLE IF NOT EXISTS) — garder les deux schémas synchro.
|
||
|
||
Activation : `admin.html` → case "Send VOC sensor data (CCS811)".
|
||
|
||
### Pistes d'amélioration (non implémentées)
|
||
|
||
Le CCS811 supporte une compensation température/humidité (`SET_ENV_DATA`). Comme le
|
||
boîtier embarque déjà un BME280, on pourrait lui pousser temp/hum périodiquement
|
||
pour améliorer la précision. Non fait pour garder le daemon simple.
|