T3 API IoBase

Format d'ingestion IoT (CloudGate) v1.0 · Flux 01 + 04

Flux 01 — Sensor uplinks

CloudGate
IoBase
trame LoRa reçue
on_lora_message → build_iobase_msg
POST /v1/payloads
Bearer DAP + X-CloudGate-Id + X-Device-Id
200 · {"status":"success"}
décodé + écrit Indaba
si 401 → fn_trigger_refresh_on_401
→ build_dap_refresh (mutex) → retry

Endpoint

URLPOST <iobase_host>/v1/payloads
MéthodePOST
Content-Typeapplication/json
Le chemin de la spec PDF est faux

La spécification API_IoBase_CloudGate_v1.0.pdf annonce /api/v1/cloudgate/payloads. Ce chemin renvoie un 404 systématique : le chemin réellement déployé, confirmé par Terega le 31/07/2026, est /v1/payloads. Le reste de la spec (headers et corps du message) est correct.

Headers obligatoires

HeaderSource canvasDescription
X-CloudGate-Idglobal.cloudgate_device_nameIdentifiant unique de la passerelle
X-Device-Idmsg.DevEUIDevEUI de la balise émettrice
Authorization'Bearer ' .. global.iobase_tokenBearer DAP. Voir Chap. 02.
Content-Typehardcodedapplication/json

Body JSON (champs requis)

ChampTypeRequisSource canvas / valeur
equipment_modelStringOuiConfiguré par balise dans le Cockpit CloudGate (onglet Balises → Modifier)
payloadString hexOuiHex lowercase de msg.payload (trame LoRa raw)
siteStringOuiConfiguré par balise dans le Cockpit CloudGate (onglet Balises → Modifier)
datasourceStringOuiConfiguré par balise dans le Cockpit CloudGate (onglet Balises → Modifier)
metricsArray[String]OuiConfiguré par balise. Au moins une valeur : IoBase rejette une liste vide.
optionsStringNonglobal.iobase_options. Omis du body si vide.
timestampString ISO 8601Ouios.date('!%Y-%m-%dT%H:%M:%SZ', os.time())

Exemple de POST réel

POST /v1/payloads HTTP/1.1
Host: terega-staging.iot-cloud-gate.api.indasuite.io-base.com
Content-Type: application/json
Authorization: Bearer AT9d8f7a6e5b4c3d2e1a0f9e8d7c6b5a4e
X-CloudGate-Id: cloudgate-XX
X-Device-Id: 0004A30B010593C1

{
  "equipment_model": "asystom_sentinel",
  "payload": "00a2070e4c210239360be184f67dd87c417cc87a...",
  "site": "usine_nord",
  "datasource": "transportation",
  "metrics": ["50001m_iot_pit002", "50001m_iot_tt002"],
  "timestamp": "2026-04-25T14:35:22Z"
}

Codes de réponse

CodeStatutComportement canvas
200OK décodé + écrit Indabaglobal.last_iobase_ok_ts = os.time()
202Accepted (queued pour traitement)Idem 200
400Bad Request (champ obligatoire manquant)Log erreur. Pas de retry. À investiguer côté config (probablement global.iobase_* mal set).
401/403Authentication failedfn_trigger_refresh_on_401 → build_dap_refresh → retry du POST avec nouveau token
500Erreur serveurJournalisé. La mesure n'est pas rejouée — il n'existe pas de file sur cette passerelle

Flux 04 — Health

Endpoint

URLPOST <iobase_host>/gateway/health
MéthodePOST
Content-Typeapplication/json
AuthBearer <global.iobase_token>

Cadence d'émission

Body Health

{
  "timestamp":           1714056789,
  "source":              "cloudgate",
  "device_id":           "cloudgate-XX",
  "life_counter":        6579,
  "uptime_seconds":      6833,
  "lora_uplinks_count":  33,
  "last_lora_ts":        1714056772,
  "last_iobase_ok_ts":   1714056774,
  "last_asystom_ok_ts":  1714056775,
  "lora_status":         "active",
  "gsm_rssi":            -85,
  "gsm_signal_quality":  null,
  "gsm_operator":        "F-Bouygues Telecom",
  "gsm_technology":      "GSM",
  "gsm_rfband":          "GSM 900",
  "modem_model":         "BG96",
  "sim_status":          null
}

Sémantique des champs

life_counter
Compteur de vie 0→32000 cyclique. Indaba alerting détecte si figé entre 2 health POSTs.
uptime_seconds
os.time() - global.start_time. Reset à chaque reboot, pas à chaque deploy.
lora_uplinks_count
Cumul depuis le boot. Permet de calculer le débit moyen côté serveur.
lora_status
Dérivé de la fraîcheur de la dernière trame : active (< 5 min), idle (< 30 min), stale (≥ 30 min).
last_iobase_ok_ts / last_asystom_ok_ts
Timestamp du dernier POST 2xx. Permet de mesurer la santé sortante.
gsm_rssi
RSSI en dBm depuis l'API admin CG /api/diagnostics?device=wwan0. Plage typique -50 (excellent) à -110 (perdu).
gsm_operator + gsm_technology + gsm_rfband + modem_model
Pour identifier le modem (ex : Quectel BG96), la technologie radio (GSM 900 / LTE / 4G), et l'opérateur cellulaire.

Compatibilité legacy

Endpoint legacy
Le mock cockpit accepte aussi POST /sensor/data (ancien format des premières versions de la chaîne) en réponse 201, pour compatibilité avec les flow exports des sessions de dev. Le canvas actuel utilise exclusivement /v1/payloads.

Deux environnements IoBase

La passerelle peut servir deux environnements IoBase distincts — par exemple une recette et une production. Ils ne partagent rien : chacun a son adresse, ses identifiants, son jeton, son verrou de renouvellement et son propre fichier d'état d'authentification.

EnvironnementVariablesFichier d'état
Principal (défaut)iobase_host, dap_host, dap_client_id, dap_api_key dap_state.json
Secondles mêmes, suffixées _b dap_state_b.json

Chaque balise porte l'environnement vers lequel partent ses mesures, choisi dans le cockpit (onglet Balises → ✎ Modifier). Une balise sans consigne suit l'environnement principal.

Le second environnement est inerte tant qu'il n'est pas configuré

Tant que iobase_host_b est vide, le second environnement n'existe pas : une balise qui lui serait affectée repart sur l'environnement principal plutôt que d'échouer, en le signalant dans le journal par IOBASE_ENV_FALLBACK. Le cockpit ne propose d'ailleurs le choix qu'une fois l'adresse renseignée — proposer une destination vers laquelle la passerelle refuse d'envoyer serait mentir à l'opérateur.

Les quatre valeurs du second environnement se saisissent dans le cockpit, sans redéploiement du flux. Son enrôlement, en revanche, exige une validation par un administrateur côté plateforme, comme pour le premier.

Ce qui empêche une mesure de partir

Quatre champs sont obligatoires par balise : site, source de données, modèle d'équipement et métriques. S'il en manque un, la passerelle n'envoie pas et journalise IOBASE_SKIP avec la liste des champs absents — plutôt que d'essuyer un refus 400 dont le motif serait à deviner. Le compteur d'abandons et le détail du dernier sont visibles dans les variables iobase_skip_count et iobase_last_skip.

Un capteur déclaré generic emprunte exactement la même chaîne : seule la transmission vers Asystom est coupée. Voir Ajouter un capteur.

Configuration

Deux niveaux, à ne pas confondre :

NiveauCe qui s'y règleOù
Par balise site, source de données, modèle d'équipement, métriques, type de capteur, environnement de destination Cockpit, onglet Balises → ✎ Modifier
Global adresses des plateformes, identifiants d'authentification, noms des environnements Cockpit, onglet Globals (rôle admin) — voir la référence des variables
Les anciens défauts globaux ne servent plus

Jusqu'au 2 août 2026, iobase_site, iobase_datasource et iobase_equipment_model fournissaient une valeur par défaut à toutes les balises. Ces champs sont désormais obligatoires par balise et ces variables ne sont plus jamais lues par le traitement des mesures. Le cockpit les masque. La variable iobase_metrics_default, elle, n'existe plus du tout.