diff --git a/CONVENTIONS.md b/CONVENTIONS.md index b515a48..7785b7e 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -3,8 +3,7 @@ ## Nommage des fichiers - Un capteur = un fichier `sensors/.md` en minuscules, sans tiret dans le nom s'il n'est pas dans la marque (`nextpm.md`, `sps30.md`, `bme280.md`). -- Un parser = un fichier `parsers/-.md` (`udp-miotiq.md`, `mqtt-tb.md`). -- Un format = un fichier `formats/.md` décrivant le schéma et les exemples. +- Un format = un fichier `formats/.md` décrivant le schéma et les exemples. Les parsers associés (ex. descripteur binaire Miotiq) vivent dans le même dossier, nommés par transport : `formats/-.md` (`udp-miotiq.md`, `mqtt-tb.md`). ## Style Markdown diff --git a/README.md b/README.md index 3dee893..8acfbd9 100644 --- a/README.md +++ b/README.md @@ -10,15 +10,14 @@ Documentation de référence pour tous les capteurs AirCarto : protocoles de com aircarto-protocols/ ├── CONVENTIONS.md Nommage, versioning, style doc ├── data-budget.md Estimation conso cellulaire vs budget Miotiq 1 Go/10 ans -├── formats/ Formats d'échange de données +├── formats/ Formats d'échange et parsers associés │ ├── json-payload.md Format JSON canonique des mesures │ ├── iso-pollutant-codes.md Mapping ISO_XX → polluant / grandeur -│ └── mqtt.md Topics et conventions MQTT -├── sensors/ Un fichier par capteur -│ ├── _TEMPLATE.md Gabarit à copier pour tout nouveau capteur -│ └── nextpm.md NextPM (Tera Sensor) — UART -└── parsers/ Parsers côté serveur / passerelle - └── udp-miotiq.md Webhook Miotiq (UDP → HTTPS JSON) +│ ├── mqtt.md Topics et conventions MQTT +│ └── udp-miotiq.md Webhook Miotiq (UDP → HTTPS JSON) + descripteur binaire +└── sensors/ Un fichier par capteur + ├── _TEMPLATE.md Gabarit à copier pour tout nouveau capteur + └── nextpm.md NextPM (Tera Sensor) — UART ``` ## Index des capteurs @@ -31,12 +30,12 @@ aircarto-protocols/ | Nom | Transport | Doc | État | |-----------------|------------------|-----------------------------------------------|-------------------------------| -| UDP Miotiq | UDP → HTTPS JSON | [parsers/udp-miotiq.md](parsers/udp-miotiq.md) | Descripteur NebuleAir Pro 4G + legacy MobileAir | +| UDP Miotiq | UDP → HTTPS JSON | [formats/udp-miotiq.md](formats/udp-miotiq.md) | Descripteur NebuleAir Pro 4G + legacy MobileAir | ## Comment ajouter une entrée - **Nouveau capteur** : copier `sensors/_TEMPLATE.md` vers `sensors/.md`, remplir les sections, mettre à jour l'index ci-dessus. -- **Nouveau format / parser** : créer le fichier sous `formats/` ou `parsers/`, mettre à jour l'index. +- **Nouveau format ou parser** : créer le fichier sous `formats/`, mettre à jour l'index. - Voir [CONVENTIONS.md](CONVENTIONS.md) pour le style et le nommage. ## Pourquoi ce repo @@ -45,7 +44,7 @@ Avant : chaque firmware AirCarto (NebuleAir, ModuleAir, MobileAir…) redéfinis Ici on centralise la **spécification** : -- **Capteur → Miotiq** : payload UDP binaire, décodé côté Miotiq via un *descripteur* ([`parsers/udp-miotiq.md`](parsers/udp-miotiq.md)). +- **Capteur → Miotiq** : payload UDP binaire, décodé côté Miotiq via un *descripteur* ([`formats/udp-miotiq.md`](formats/udp-miotiq.md)). - **Miotiq → serveur AirCarto** : JSON canonique 2026 ([`formats/json-payload.md`](formats/json-payload.md)) posté sur `api.aircarto.com/receive_data`. - **Vocabulaire polluants** : codes ISO LCSQA ([`formats/iso-pollutant-codes.md`](formats/iso-pollutant-codes.md)). - **Capteurs physiques** : docs individuelles sous `sensors/` (protocole UART/I2C, câblage, commandes). diff --git a/data-budget.md b/data-budget.md index 75350e4..a3f3499 100644 --- a/data-budget.md +++ b/data-budget.md @@ -90,6 +90,6 @@ Les deux scénarios recommandés tiennent très confortablement dans le budget d ## À faire -- [ ] Remplacer cette estimation par une **mesure réelle** à partir des rapports de conso Miotiq (API `/api/device/detail` renvoie des compteurs de volume ; cf. [`parsers/udp-miotiq.md`](parsers/udp-miotiq.md)). +- [ ] Remplacer cette estimation par une **mesure réelle** à partir des rapports de conso Miotiq (API `/api/device/detail` renvoie des compteurs de volume ; cf. [`formats/udp-miotiq.md`](formats/udp-miotiq.md)). - [ ] Vérifier la politique de comptage Miotiq exacte : payload UDP seul, UDP+IP, ou avec signaling ? - [ ] Mettre à jour ce document quand MobileAir migrera vers un descripteur Miotiq (payload probablement > 17 B). diff --git a/formats/iso-pollutant-codes.md b/formats/iso-pollutant-codes.md index fbb2cf6..1a4d439 100644 --- a/formats/iso-pollutant-codes.md +++ b/formats/iso-pollutant-codes.md @@ -1,6 +1,6 @@ # Codes polluants ISO — convention AirCarto -Les descripteurs Miotiq (voir [`parsers/udp-miotiq.md`](../parsers/udp-miotiq.md)) et le JSON canonique (voir [`json-payload.md`](json-payload.md)) utilisent des codes `ISO_XX` pour désigner les polluants et grandeurs physiques. Ces codes suivent la nomenclature du **LCSQA** (Laboratoire Central de Surveillance de la Qualité de l'Air), basée sur la norme **ISO 7168**. +Les descripteurs Miotiq (voir [`udp-miotiq.md`](udp-miotiq.md)) et le JSON canonique (voir [`json-payload.md`](json-payload.md)) utilisent des codes `ISO_XX` pour désigner les polluants et grandeurs physiques. Ces codes suivent la nomenclature du **LCSQA** (Laboratoire Central de Surveillance de la Qualité de l'Air), basée sur la norme **ISO 7168**. Les codes vont théoriquement de `ISO_01` à `ISO_99`. Seuls ceux effectivement mesurés par au moins un capteur AirCarto sont documentés ici. diff --git a/formats/json-payload.md b/formats/json-payload.md index b40ee98..5ec3d4e 100644 --- a/formats/json-payload.md +++ b/formats/json-payload.md @@ -24,7 +24,7 @@ Le **type de capteur** est passé en query string. Modèles supportés : - **Tous les champs sont optionnels** sauf `device_id`. Un capteur n'envoie que les champs qu'il mesure. - **Valeur sentinelle `-1`** : donnée non disponible ou capteur non renseigné (ex. pas de GPS fix, pas de pression). - **Valeur sentinelle `255` (`0xFF`)** sur `error_flags`, `npm_status`, `device_status` uniquement : le firmware du capteur est antérieur à l'introduction du champ. À interpréter comme « non disponible », **pas** comme « toutes les erreurs actives ». Les nouveaux firmwares initialisent ces octets et envoient une valeur ≤ 254. -- **Champs `_unit`** : optionnels, ajoutés par Miotiq quand la colonne `units` du descripteur est remplie et que l'export JSON est `Y` (voir [`parsers/udp-miotiq.md`](../parsers/udp-miotiq.md)). Format : `" "`. Le backend peut les ignorer, la valeur canonique est toujours le champ sans suffixe. +- **Champs `_unit`** : optionnels, ajoutés par Miotiq quand la colonne `units` du descripteur est remplie et que l'export JSON est `Y` (voir [`udp-miotiq.md`](udp-miotiq.md)). Format : `" "`. Le backend peut les ignorer, la valeur canonique est toujours le champ sans suffixe. ## Identification diff --git a/parsers/udp-miotiq.md b/formats/udp-miotiq.md similarity index 90% rename from parsers/udp-miotiq.md rename to formats/udp-miotiq.md index 24cfabe..19f6fd7 100644 --- a/parsers/udp-miotiq.md +++ b/formats/udp-miotiq.md @@ -8,29 +8,11 @@ Chemin de données : Capteur ──UDP──> Miotiq ──HTTPS POST JSON──> api.aircarto.com/receive_data ──> PostgreSQL + InfluxDB ``` -**Principe** : chaque capteur a un **descripteur Miotiq** (format texte pipe-séparé) qui décrit l'ordonnancement et le décodage de sa charge utile. Miotiq applique ce descripteur à l'ingestion, et produit le **JSON canonique AirCarto** (voir [`formats/json-payload.md`](../formats/json-payload.md)) qui est posté sur le backend. +**Principe** : chaque capteur a un **descripteur Miotiq** (format texte pipe-séparé) qui décrit l'ordonnancement et le décodage de sa charge utile. Miotiq applique ce descripteur à l'ingestion et poste sur le backend le **JSON canonique AirCarto** spécifié dans [`json-payload.md`](json-payload.md) — c'est ce JSON-là que consomment les scripts PHP `receive_data`. -## Enveloppe JSON reçue du webhook +Ce document couvre uniquement le **format du descripteur** et le **layout binaire** par capteur. Le schéma du JSON de sortie (champs, unités, bitfields, exemple complet) vit dans [`json-payload.md`](json-payload.md). -Le corps `application/json` reçu par le script PHP contient : - -```json -{ - "payload": "", - "customerId": "string", - "rcvTime": 1713830400, - "srcIP": "10.x.x.x", - "srcImsi": "208xxxxxxxxxxx" -} -``` - -| Champ | Type | Description | -|--------------|---------|--------------------------------------------------------------------| -| `payload` | string | **Base64** des octets UDP envoyés par le capteur. | -| `customerId` | string | Identifiant client Miotiq. | -| `rcvTime` | integer | Timestamp Unix UTC de la réception Miotiq, en secondes. | -| `srcIP` | string | IP source du modem cellulaire (côté opérateur). | -| `srcImsi` | string | IMSI de la SIM, sert à rattacher la mesure à un capteur en DB. | +Métadonnées transport ajoutées par Miotiq en marge du JSON décodé (utiles pour l'audit / le rattachement device) : `srcImsi` (IMSI de la SIM — clé de correspondance device en DB), `rcvTime` (timestamp Unix UTC réception Miotiq), `srcIP`, `customerId`. En mode webhook « raw » (sans descripteur), Miotiq peut aussi poster le datagramme brut sous `payload` encodé base64 — utile si le backend veut re-décoder côté serveur (voir le squelette Python plus bas). ## Format descripteur Miotiq @@ -43,7 +25,7 @@ Un descripteur est une suite de lignes, une par champ. **Format officiel Miotiq* | Colonne | Description | |----------------------|--------------------------------------------------------------------------------------------------| | `length` | Taille du champ en **caractères hexadécimaux** (2 chars = 1 octet). | -| `variable name` | Identifiant logique du champ. Les codes polluants suivent [ISO 7168 AirCarto](../formats/iso-pollutant-codes.md). | +| `variable name` | Identifiant logique du champ. Les codes polluants suivent [ISO 7168 AirCarto](iso-pollutant-codes.md). | | `base function` | Fonction de décodage appliquée aux octets bruts. Voir tableau ci-dessous. | | `units` | Unité physique finale (`ugm3`, `degC`, `%`, `hPa`, `ppb`, `dB`, `V`, `A`, `W`, `m/s`, `degrees`, `count`). Vide pour les champs d'état / versions. | | `equation` | Expression de transformation appliquée à la valeur décodée, où `x` est la valeur. Ex. `x/10`, `x/100`, `(x-32)*5/9`. Vide = pas de transformation. | @@ -155,16 +137,16 @@ Layout octet par octet : | 60 | 2 | `charger_status` | | | Bitfield, cf. firmware | | 62 | 2 | `wind_speed` | m/s | /10 | | | 64 | 2 | `wind_direction` | degrés | | 0–359, 0 = Nord | -| 66 | 1 | `error_flags` | | | Bitfield erreurs système, détail dans [`formats/json-payload.md`](../formats/json-payload.md#error_flags--bitfield-système-1-octet). `0xFF` = firmware ancien. | -| 67 | 1 | `npm_status` | | | Bitfield statut NextPM (copie du `STATE` UART, voir [`sensors/nextpm.md`](../sensors/nextpm.md) et [`json-payload.md`](../formats/json-payload.md#npm_status--bitfield-nextpm-1-octet)). `0xFF` = firmware ancien. | -| 68 | 1 | `device_status` | | | Bitfield état boîtier, détail dans [`formats/json-payload.md`](../formats/json-payload.md#device_status--bitfield-boîtier-1-octet). `0xFF` = firmware ancien. | +| 66 | 1 | `error_flags` | | | Bitfield erreurs système, détail dans [`json-payload.md`](json-payload.md#error_flags--bitfield-système-1-octet). `0xFF` = firmware ancien. | +| 67 | 1 | `npm_status` | | | Bitfield statut NextPM (copie du `STATE` UART, voir [`sensors/nextpm.md`](../sensors/nextpm.md) et [`json-payload.md`](json-payload.md#npm_status--bitfield-nextpm-1-octet)). `0xFF` = firmware ancien. | +| 68 | 1 | `device_status` | | | Bitfield état boîtier, détail dans [`json-payload.md`](json-payload.md#device_status--bitfield-boîtier-1-octet). `0xFF` = firmware ancien. | | 69 | 1 | `version_major` | | | Version firmware `X.y.z` | | 70 | 1 | `version_minor` | | | `x.Y.z` | | 71 | 1 | `version_patch` | | | `x.y.Z` | | 72 | 11 | `reserved` | | | À ignorer (évolution future du descripteur) | | **83** | total | | | | | -> Les champs `latitude`, `longitude`, `misc` présents dans le JSON final (voir [`json-payload.md`](../formats/json-payload.md#géolocalisation--contexte)) **ne sont pas** dans ce descripteur 83B. **À intégrer** : voir la section [À faire](#à-faire) — l'emplacement naturel est le bloc `reserved` de 11 octets (4+4+1 = 9 octets suffisent). +> Les champs `latitude`, `longitude`, `misc` présents dans le JSON final (voir [`json-payload.md`](json-payload.md#géolocalisation--contexte)) **ne sont pas** dans ce descripteur 83B. **À intégrer** : voir la section [À faire](#à-faire) — l'emplacement naturel est le bloc `reserved` de 11 octets (4+4+1 = 9 octets suffisent). ### MobileAir (17 octets — legacy, pré-descripteur)