read_co2() lit IR1..IR4 en une trame (status + CO2) à 9600 8N1, adresse 0xFE 'any address', avec vérification CRC16-Modbus et rejet de la mesure si status non-nul (warm-up ou erreur). CRC requête/réponse validés contre les exemples du datasheet TDE14367. Doc protocole consolidée dans S88/README.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
167 lines
6.5 KiB
Markdown
167 lines
6.5 KiB
Markdown
# Senseair S88 — Capteur CO2 NDIR
|
||
|
||
Notes essentielles extraites des datasheets Senseair (Product Specification PSP14279
|
||
rev 3, et "Modbus on Senseair S88" TDE14367 rev 5). Les PDF originaux ne sont pas
|
||
versionnés (trop lourds, pas utiles sur les capteurs).
|
||
|
||
## Modèle
|
||
|
||
- **Senseair S88 Residential** — Article No. 004-1-0100
|
||
- Capteur CO2 miniature NDIR (Non-Dispersive InfraRed)
|
||
- Dimensions : 33.9 × 19.6 × 9.7 mm — poids < 5 g
|
||
- Compatibilité registres Modbus avec le Senseair S8
|
||
|
||
## Caractéristiques mesure
|
||
|
||
| Paramètre | Valeur |
|
||
|---|---|
|
||
| Gaz mesuré | CO2 |
|
||
| Plage | 400 – 10 000 ppm |
|
||
| Intervalle de mesure | 2 s |
|
||
| Précision 400–3000 ppm | ±25 ppm ±3% de la lecture |
|
||
| Précision 3000–10000 ppm | ±10% de la lecture |
|
||
| Temps de chauffe | ≤ 10 s |
|
||
| Temps de réponse t63% | ≤ 30 s |
|
||
| Conditions d'opération | 0–50 °C, 0–85 %RH (sans condensation, dew point ≤ 35 °C) |
|
||
| Dépendance pression | 1.6 % par kPa d'écart à la pression normale |
|
||
| Durée de vie | > 15 ans |
|
||
| Maintenance | Sans entretien (ABC : Automatic Baseline Correction activé par défaut) |
|
||
|
||
## Alimentation
|
||
|
||
- **Tension** : 4.5 – 5.25 V (le 5V du Pi convient)
|
||
- **Courant pic** : ≤ 300 mA (pendant la rampe de la lampe IR)
|
||
- **Courant moyen** : ≤ 30 mA
|
||
- Non protégée contre surtensions / inversion polarité — attention au câblage
|
||
|
||
## Pinout
|
||
|
||
```
|
||
G+ ●─┐ ┌─● DVCC_out (3.3V, sortie régulateur interne — ne PAS utiliser)
|
||
G0 ●─┤ ├─● UART_TxD (3.3V CMOS, sortie capteur)
|
||
Alarm_OC●─┤ ├─● UART_RxD (3.3V CMOS, entrée capteur)
|
||
PWM 1kHz●─┘ ├─● UART_R/T (direction RS-485, à laisser flottant en UART direct)
|
||
└─● bCAL_in (entrée calibration manuelle)
|
||
```
|
||
|
||
### Câblage vers Raspberry Pi (UART 3.3V direct)
|
||
|
||
| S88 | Raspberry Pi CM4 |
|
||
|---|---|
|
||
| G+ | 5V |
|
||
| G0 | GND |
|
||
| UART_TxD | RxD du ttyAMAx (ex. GPIO15 pour ttyAMA0) |
|
||
| UART_RxD | TxD du ttyAMAx (ex. GPIO14 pour ttyAMA0) |
|
||
| UART_R/T | non connecté |
|
||
|
||
Les niveaux UART du S88 sont 3.3V CMOS — directement compatibles avec le Pi.
|
||
Pas besoin de level shifter, pas besoin de RS-485 transceiver.
|
||
|
||
## Protocole Modbus RTU
|
||
|
||
- **Mode** : RTU (seul mode supporté)
|
||
- **Baudrate** : 9600 par défaut (19200 aussi supporté)
|
||
- **Format** : 8 bits de données, **pas de parité**, 1 stop bit en réception / 2 stop bits en transmission (config par défaut)
|
||
- **Adresse esclave** : 1–247 (configurable via HR). **0xFE = "any address"** — répondue par n'importe quel S88, utile quand on ne connaît pas l'adresse individuelle (à n'utiliser qu'en bench, pas en réseau multi-capteurs)
|
||
- **Adresse 0** : broadcast (commandes write seulement)
|
||
- **Réponse timeout** : ≤ 180 ms
|
||
|
||
### Fonctions supportées
|
||
|
||
| Code | Fonction |
|
||
|---|---|
|
||
| 0x03 | Read Holding Registers (config, plage 0x0000–0x0020) |
|
||
| 0x04 | Read Input Registers (mesures, plage 0x0000–0x001F) |
|
||
| 0x06 | Write Single Register |
|
||
| 0x10 | Write Multiple Registers |
|
||
| 0x2B / 0x0E | Read Device Identification (Vendor Name, ProductCode, MajorMinorRevision) |
|
||
|
||
### Input Registers (mesures, fonction 0x04)
|
||
|
||
| Reg | Offset | Nom | Description |
|
||
|---|---|---|---|
|
||
| **IR1** | 0x0000 | MeterStatus | Bits d'état (DI1=Fatal error, DI3=Algorithm error, DI4=Output error, DI5=Self-diagnostic error, DI6=Out of range, DI7=Memory error, DI8=Warm Up) |
|
||
| IR2 | 0x0001 | AlarmStatus | Réservé |
|
||
| IR3 | 0x0002 | OutputStatus | DI33=Alarm Output status, DI34=PWM Output status |
|
||
| **IR4** | **0x0003** | **Space CO2** | **Concentration CO2 en ppm (uint16)** ⚠ voir note scaling |
|
||
| IR5 | 0x0004 | Space Temp | Température capteur (au-dessus de l'ambiant à cause de l'auto-échauffement) |
|
||
| IR6 | 0x0005 | Synchro | Incrémenté chaque cycle de mesure |
|
||
| IR7 | 0x0006 | Vbb | Tension VBB pendant lamp ramp (LSB = 1 mV) |
|
||
| IR22 | 0x0015 | PWM Output | Valeur PWM (0x3FFF = 100%) |
|
||
| IR24+IR25 | 0x0017/18 | ETC | Elapsed Time Counter (heures), 4 octets |
|
||
| IR28 | 0x001B | Memory Map version | |
|
||
| IR29 | 0x001C | FW version | high byte = Main, low byte = Sub |
|
||
| IR30+IR31 | 0x001D/1E | Sensor Serial Number | 4 octets |
|
||
|
||
⚠ **Scaling CO2** : la plupart des S88 retournent la valeur directement en ppm.
|
||
**Certains futurs modèles** de la famille S88 divisent par 10 (400 ppm → renvoie 40).
|
||
À vérifier au bench. Le ProductCode (lu via fonction 0x2B/0x0E objet 0x01) permet
|
||
d'identifier le modèle — pour le S88 Residential 004-1-0100 c'est ppm directement.
|
||
|
||
### Exemple : lire CO2 seul (IR4)
|
||
|
||
**Requête maître** (adresse 0xFE, function 04, start 0x0003, qty 0x0001) :
|
||
|
||
```
|
||
FE 04 00 03 00 01 25 C5
|
||
└┬┘ └┬┘ └──┬──┘ └──┬──┘ └─┬─┘
|
||
addr fn start qty CRC (low byte first)
|
||
```
|
||
|
||
**Réponse esclave** (CO2 = 400 ppm = 0x0190) :
|
||
|
||
```
|
||
FE 04 02 01 90 AC DB
|
||
└┬┘ └┬┘ └┬┘ └──┬──┘ └─┬─┘
|
||
addr fn count value CRC
|
||
```
|
||
|
||
### Exemple : lire status + CO2 en une commande (IR1 à IR4)
|
||
|
||
**Requête maître** :
|
||
|
||
```
|
||
FE 04 00 00 00 04 E5 C6
|
||
```
|
||
|
||
**Réponse esclave** (status=0, CO2=400ppm) :
|
||
|
||
```
|
||
FE 04 08 00 00 00 00 00 00 01 90 16 E6
|
||
└┬┘ └┬┘ └┬┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └─┬─┘
|
||
addr fn cnt IR1=0 IR2=0 IR3=0 IR4=400 CRC
|
||
```
|
||
|
||
C'est la séquence recommandée pour le scraping périodique : un seul appel,
|
||
on récupère l'état + la valeur. Si IR1 (status) ≠ 0, ne pas écrire la mesure.
|
||
|
||
## Notes EEPROM
|
||
|
||
Les Holding Registers sont mappés en EEPROM (sauf HR1–HR4 et HR22) :
|
||
|
||
- Limite EEPROM : **< 10 000 cycles d'écriture** sur la durée de vie
|
||
- Une écriture multi-registres compte pour 1 cycle
|
||
- Attendre **≥ 180 ms** après écriture d'un HR avant power-down/reset
|
||
|
||
⚠ Ne JAMAIS écrire les HR depuis une boucle qui tourne souvent — réservé à la
|
||
configuration initiale (changement de baudrate, d'adresse Modbus, etc.).
|
||
|
||
## Implémentation NebuleAir
|
||
|
||
Voir `S88/write_data.py` et `S88/get_data.py`. Le module Python `minimalmodbus`
|
||
ou `pymodbus` peut être utilisé, ou directement `pyserial` avec calcul CRC16 manuel.
|
||
|
||
Pour lecture périodique simple :
|
||
|
||
```python
|
||
# Pseudocode — voir write_data.py pour la vraie implémentation
|
||
request = b'\xFE\x04\x00\x00\x00\x04' + crc16(...) # IR1..IR4
|
||
ser.write(request)
|
||
response = ser.read(13) # FE 04 08 + 8 octets data + 2 CRC
|
||
if response[0] == 0xFE and response[1] == 0x04:
|
||
status = (response[3] << 8) | response[4]
|
||
co2_ppm = (response[9] << 8) | response[10]
|
||
if status == 0:
|
||
# OK, enregistrer co2_ppm
|
||
```
|