docs: merge parsers/ into formats/ and drop misleading JSON wrapper

Parsers and formats are tightly linked (a parser produces a format) and
the split made cross-links heavy for a single parser file. Also removed
the confusing "Enveloppe JSON" block in udp-miotiq.md that mixed the
raw webhook wrapper with what the backend actually consumes — the
decoded payload schema lives in json-payload.md and is now referenced
directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-04-24 11:02:10 +02:00
parent fee8b7dc15
commit 33c4472350
6 changed files with 21 additions and 41 deletions

View File

@@ -3,8 +3,7 @@
## Nommage des fichiers
- Un capteur = un fichier `sensors/<nom>.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/<transport>-<origine>.md` (`udp-miotiq.md`, `mqtt-tb.md`).
- Un format = un fichier `formats/<nom>.md` décrivant le schéma et les exemples.
- Un format = un fichier `formats/<nom>.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/<transport>-<origine>.md` (`udp-miotiq.md`, `mqtt-tb.md`).
## Style Markdown

View File

@@ -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/<nom>.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).

View File

@@ -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).

View File

@@ -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.

View File

@@ -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 `<nom>_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 : `"<valeur> <unité>"`. Le backend peut les ignorer, la valeur canonique est toujours le champ sans suffixe.
- **Champs `<nom>_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 : `"<valeur> <unité>"`. Le backend peut les ignorer, la valeur canonique est toujours le champ sans suffixe.
## Identification

View File

@@ -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": "<base64 du datagramme UDP d'origine>",
"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 | | 0359, 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)