Files
aircarto-protocols/formats/json-payload.md
Your Name cd369a209f docs(miotiq): move ping trigger to 0x02 for backward compatibility
Deployed firmwares hardcode command=0x01 (legacy from when the byte was
named `version`); treating 0x01 as ping would have flagged every existing
frame as a diagnostic. Keep 0x00/0x01 as normal-data and use 0x02 as the
explicit ping trigger.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 17:34:10 +02:00

16 KiB
Raw Blame History

Format JSON canonique — mesures capteurs AirCarto (2026)

JSON envoyé par les capteurs AirCarto (ou par le webhook Miotiq en leur nom) au serveur central.

Endpoint

POST https://api.aircarto.com/receive_data?device_type=<modèle>
Content-Type: application/json

Le type de capteur est passé en query string. Modèles supportés :

device_type Description
NebuleAir Station fixe NebuleAir
NebuleAir_Pro Station fixe NebuleAir Pro (4G)
ModuleAir Module AirCarto
ModuleAir_Pro Module AirCarto Pro (4G)
MobileAir Capteur mobile

Règles générales

  • 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 udp-miotiq.md). Format : "<valeur> <unité>". Le backend peut les ignorer, la valeur canonique est toujours le champ sans suffixe.

Identification

Champ Type Description
device_id string Identifiant unique, représentation hexadécimale de 16 caractères (8 octets ASCII). Convertir hex → ASCII pour obtenir le numéro de série imprimé sur le boîtier. Ex. "4430353234313938""D0524198".
signal_quality int Qualité du signal réseau (dB, souvent RSSI négatif).
command int Type de trame. 0x00 / 0x01 = données mesure (les autres champs sont valides ; 0x01 est conservé pour la rétrocompatibilité avec les firmwares qui hardcodaient l'ancien champ version). 0x02 = ping test (trame émise volontairement par le firmware pour vérifier le lien capteur → Miotiq → backend ; le backend ne doit pas archiver les mesures associées). Voir Commande / type de trame.

Commande / type de trame

Le champ command (1 octet, offset 9 du payload binaire) discrimine la nature de la trame.

Valeur Sens Action backend attendue
0x00 données Trame de mesure normale. Décoder et persister les champs métier comme d'habitude.
0x01 données (legacy) Identique à 0x00. Émis par les firmwares antérieurs au renommage versioncommand qui hardcodaient cet octet à 0x01 ; conservé comme alias de mesure normale pour ne pas casser le parc déployé.
0x02 ping test Trame de diagnostic émise par le firmware pour vérifier le chemin capteur → Miotiq → backend. Logger la réception (timestamp, device_id, signal_quality) puis ne pas archiver les autres champs comme mesures réelles — leur contenu n'est pas garanti significatif.

Toute autre valeur doit être traitée comme une trame de données (0x00) en attendant qu'elle soit officiellement allouée — éviter de rejeter la trame sur la seule base d'un command inconnu pour rester forward-compatible.

Note historique : ce champ s'appelait version avant 2026-04-27 et était hardcodé 0x01 côté firmware. C'est cette valeur héritée qui force 0x01 à rester une trame de données et pousse le ping sur 0x02. Le versioning du protocole est exclusivement porté par version_major/minor/patch (voir Version firmware).

Polluants (codes ISO LCSQA)

Le mapping complet vit dans formats/iso-pollutant-codes.md. Liste rappel :

Champ Grandeur Unité
ISO_68 PM1 µg/m³
ISO_39 PM2.5 µg/m³
ISO_24 PM10 µg/m³
ISO_54 Température °C
ISO_55 Humidité %
ISO_53 Pression hPa
ISO_03 NO₂ ppb
ISO_05 H₂S ppb
ISO_21 NH₃ ppb
ISO_04 CO ppb
ISO_08 O₃ ppb

Les codes ISO vont théoriquement de ISO_01 à ISO_99. Seuls les polluants effectivement mesurés par le capteur sont présents dans le JSON.

Bruit

Champ Unité Description
noise_cur_leq dB Niveau sonore équivalent continu (Leq) courant.
noise_cur_level dB Niveau sonore instantané courant.
max_noise dB Niveau sonore maximal sur la période.

Comptage particulaire NPM (Naneos Partector)

Champ Unité Description
npm_ch1 count Comptage canal 1.
npm_ch2 count Comptage canal 2.
npm_ch3 count Comptage canal 3.
npm_ch4 count Comptage canal 4.
npm_ch5 count Comptage canal 5.
npm_temp °C Température interne du module NPM.
npm_humidity % Humidité interne du module NPM.
npm_status int Statut NPM — bitfield, voir ci-dessous.

Alimentation

Champ Unité Description
battery_voltage V Tension batterie.
battery_current A Courant batterie.
solar_voltage V Tension panneau solaire.
solar_power W Puissance panneau solaire.
charger_status int Code de statut du chargeur MPPT.

Vent

Champ Unité Description
wind_speed m/s Vitesse du vent.
wind_direction degrés Direction du vent, 0360 (0 = Nord).

Diagnostic & firmware

error_flags — bitfield système (1 octet)

Erreurs matérielles détectées par le capteur. 255 (0xFF) = firmware ancien, champ non supporté.

Bit Masque Nom Signification
0 0x01 RTC_DISCONNECTED Module RTC DS3231 non détecté (I2C).
1 0x02 RTC_RESET RTC en date par défaut (année 2000).
2 0x04 BME280_ERROR Capteur BME280 non détecté ou erreur.
3 0x08 NPM_ERROR Capteur NextPM non détecté ou erreur.
4 0x10 ENVEA_ERROR Capteurs Envea non détectés ou erreur.
5 0x20 NOISE_ERROR Capteur bruit NSRT MK4 non détecté.
6 0x40 MPPT_ERROR Chargeur solaire MPPT non détecté.
7 0x80 WIND_ERROR / CO2_ERROR NebuleAir : vent non détecté. ModuleAir : CO₂ non détecté.

Exemple : error_flags = 5 → RTC déconnecté + BME280 en erreur.

npm_status — bitfield NextPM (1 octet)

Registre d'état interne du capteur NextPM. Copie du byte STATE de la trame UART NextPM (voir sensors/nextpm.md). 255 = firmware ancien.

Bit Masque Nom Signification
0 0x01 SLEEP_STATE Capteur en veille.
1 0x02 DEGRADED_STATE Erreur mineure, précision réduite.
2 0x04 NOT_READY Démarrage en cours (~15 s).
3 0x08 HEAT_ERROR Humidité > 60 % pendant > 10 min.
4 0x10 TRH_ERROR T/HR interne hors spécification.
5 0x20 FAN_ERROR Ventilateur hors plage.
6 0x40 MEMORY_ERROR Accès mémoire impossible.
7 0x80 LASER_ERROR Aucune particule > 240 s, erreur laser.

Exemple : npm_status = 40HEAT_ERROR + FAN_ERROR.

device_status — bitfield boîtier (1 octet)

État général du boîtier capteur. 255 = firmware ancien.

Bit Masque Nom Signification
0 0x01 SARA_REBOOTED Modem SARA a rebooté (hardware) au cycle précédent.
1 0x02 WIFI_CONNECTED Device connecté en WiFi (mode atelier).
2 0x04 HOTSPOT_ACTIVE Hotspot WiFi actif (mode configuration).
3 0x08 GPS_NO_FIX Pas de position GPS valide.
4 0x10 BATTERY_LOW Tension batterie sous seuil critique.
5 0x20 DISK_FULL Espace disque critique (< 5 %).
6 0x40 DB_ERROR Erreur d'accès à la base SQLite locale.
7 0x80 BOOT_RECENT Device redémarré récemment (uptime < 5 min).

Exemple : device_status = 145 (0x91) → modem reboot + batterie faible + boot récent.

Version firmware

Champ Type Description
version_major int Numéro majeur (X.y.z).
version_minor int Numéro mineur (x.Y.z).
version_patch int Numéro de patch (x.y.Z).

Reconstitution : f"{version_major}.{version_minor}.{version_patch}" → ex. "1.2.3".

Géolocalisation & contexte

Champ Type Unité Description
latitude number degrés Latitude GPS WGS84 (décimal).
longitude number degrés Longitude GPS WGS84 (décimal).
misc int Contexte de mesure, voir table ci-dessous.
misc Contexte
0 Aucun
1 Mesure en intérieur
2 Mesure en extérieur
3 Mesure en voiture
4 Mesure en piéton
5 Mesure en vélo
6 Mesure en transport en commun

Enum extensible — de nouvelles valeurs peuvent être ajoutées (7, 8, …) sans casser l'encodage 1 octet. Un consommateur qui rencontre une valeur inconnue doit la traiter comme contexte non renseigné (équivalent à 0), pas rejeter la trame.

Exemple complet (NebuleAir_Pro)

{
  "device_id": "4430353234313938",
  "signal_quality": -22,
  "signal_quality_unit": "-22 dB",
  "command": 0,
  "ISO_68": 0.8,  "ISO_68_unit": "0,8 ugm3",
  "ISO_54": 25.5, "ISO_54_unit": "25.5 °C",
  "noise_cur_leq":   25.5, "noise_cur_leq_unit":   "25,5 dB",
  "noise_cur_level": 25.5, "noise_cur_unit":       "25,5 dB",
  "max_noise":       25.5, "max_noise_unit":       "25,5 dB",
  "npm_ch1": 255, "npm_ch1_unit": "255 nb",
  "npm_ch2": 255, "npm_ch2_unit": "255 nb",
  "npm_ch3": 255, "npm_ch3_unit": "255 nb",
  "npm_ch4": 255, "npm_ch4_unit": "255 nb",
  "npm_ch5": 255, "npm_ch5_unit": "255 nb",
  "battery_voltage": 25.5, "battery_voltage_unit": "25,5 V",
  "battery_current": 25.5, "battery_current_unit": "25,5 A",
  "solar_voltage":   25.5, "solar_voltage_unit":   "25,5 V",
  "solar_power":    255,   "solar_power_unit":     "255 W",
  "npm_temp":       25.5,  "npm_temp_unit":        "25,5 °C",
  "npm_humidity":   25.5,  "npm_humidity_unit":    "25,5 %",
  "wind_speed":     25.5,  "wind_speed_unit":      "25,5 m/s",
  "wind_direction": 255,   "wind_direction_unit":  "255 degrees",
  "charger_status": 255,   "charger_status_unit":  "255",
  "error_flags":   0,
  "npm_status":    0,
  "device_status": 0,
  "version_major": 1,
  "version_minor": 2,
  "version_patch": 3,
  "latitude":  43.2964,
  "longitude": 5.36978,
  "misc": 2
}

Notes d'intégration

Côté capteur / firmware

  • Envoyer uniquement les champs mesurés par ton modèle. Omettre les autres, ou les remplir à -1 si leur présence est structurellement attendue par un parser amont (ex. descripteur Miotiq à taille fixe).
  • Initialiser error_flags, npm_status, device_status à 0. Les laisser à 0xFF uniquement si tu ne sais pas renseigner (= firmware non migré), pour que le backend interprète bien « non disponible ».

Côté backend

  • Ignorer toute valeur -1 (ne pas la stocker comme une mesure).
  • Ignorer error_flags, npm_status, device_status si == 255 — c'est un firmware ancien, l'absence de diagnostic n'est pas une alarme.
  • Les _unit sont purement informatifs / de debug. La valeur métier est toujours le champ sans suffixe.
  • device_id est hex dans le JSON ; convertir en ASCII (bytes.fromhex(v).decode('ascii')) pour afficher le numéro de série lisible.
  • Lire command avant de persister la trame : si command == 0x02 (ping test), logger la réception (timestamp, device_id, signal_quality) sans archiver les mesures. Une trame command absent, 0x00 ou 0x01 est une trame de données normale (0x01 = legacy, voir Commande / type de trame).

Historique

Date Révision Changement
2026-04-23 v1 Version initiale inventée (schéma imbriqué avec token/ts/measurements) — remplacée.
2026-04-23 v2 Format officiel AirCarto 2026 : schéma plat, bitfields détaillés, compat firmware ancien.
2026-04-27 v3 Champ version (offset 9 du binaire, hardcodé 0x01 côté firmware) renommé command et réaffecté à un type de trame : 0x00 = données, 0x01 = ping test. Ajout de la section « Commande / type de trame ». Versioning protocole reste sur version_major/minor/patch.
2026-04-27 v4 Rétrocompatibilité : ping test déplacé de 0x01 vers 0x02. Les firmwares déployés émettent déjà 0x01 (héritage de l'ancien champ version) ; les compter comme pings aurait masqué toutes leurs mesures. 0x00 et 0x01 sont désormais tous deux des trames de données normales, 0x02 est le déclencheur explicite du ping.