v1.9.14: Senseair S88 - implémentation lecture Modbus RTU
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>
This commit is contained in:
166
S88/README.md
Normal file
166
S88/README.md
Normal file
@@ -0,0 +1,166 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user