01 Architecture & Flux de données
Topologie globale et détail des 4 flux exigés au devis
Vue d'ensemble
La CloudGate CG8102 est à la fois la gateway LoRa (packet forwarder), le LoRaWAN Network Server et l'Application Server. Elle remplace la combinaison Kerlink + Loriot de la Mission 1. Côté soft, tout le code de Mission 2 est implanté dans le canvas LuvitRED (port Lua/Luvit de Node-RED, embarqué dans le firmware CG).
┌────────────────────────────────┐
│ IoBase (data lake Terega) │
│ Indaba alerting + dataviz │
│ │
│ POST /api/v1/cloudgate/ │◀── Flux 01 + 04
│ payloads │ (Bearer DAP)
│ POST /gateway/health │
└────────────────────────────────┘
┌──────────────────┐ ┌─────────────────────────────────────────────────────┐
│ Balise Asystom │ LoRa │ CloudGate CG8102 │
│ Sentinel x N │─────▶│ ┌───────────────────────────────────────────┐ │
│ FPort 1/4 uplink │ │ │ LuvitRED canvas (workspace Klaud Main) │ │
│ FPort 64/70 dl │ │ │ • on_lora_message (entry point) │ │
│ │ │ │ • build_iobase_msg → POST /payloads │─────┼──── Flux 01 ──┐
│ │ │ │ • build_asystom_msg → POST /lorawan │─────┼──── Flux 02 ──┤ ▶
│ │ │ │ • build_audit_line → /mnt/data/.jsonl │ │ │
│ │ │ │ • build_health_msg → POST /health │─────┼──── Flux 04 ──┤
│ │ │ │ • build_asystom_dl_get → poll cycle 60s │─────┼──── Flux 03 ──┤
│ │ │ │ • DAP register / Token / refresh │─────┼──── Auth ─────┘
│ │◀─────│ │ • cg_login + cg_metrics (modem RSSI) │ │
│ │ │ │ • heartbeat life_counter │ │
│ │ │ │ • offline queue 15 j (queueing config) │ │
│ │ │ └───────────────────────────────────────────┘ │
└──────────────────┘ └─────────────────────────────────────────────────────┘
┌────────────────────────────────┐
│ Asystom Advisor │
│ Auth Basic + X-Network │
│ POST /lorawan │◀── Flux 02
│ GET /getPendingMsgs?client= │──▶ Flux 03
└────────────────────────────────┘
Les 4 flux exigés au devis
FLUX 01 · 0,5 j
Payload brute capteurs → IoBase
Chaque trame LoRa reçue est transformée en POST
/api/v1/cloudgate/payloads avec Bearer DAP. Body au format spec PDF v1.0 : equipment_model, payload (hex), site, datasource, metrics[], timestamp ISO 8601. Headers X-CloudGate-Id + X-Device-Id. Voir Chap. 03.FLUX 02 · 1 j
Payload brute capteurs → Asystom Advisor
Mêmes trames, en parallèle, vers Asystom. POST
/lorawan avec Basic auth + header X-Network. Body Loriot-like : endDevice.devEui, fPort, fCntUp, payload, recvTime, dataRate, gwInfo[]. Voir Chap. 04.FLUX 03 · 2 j
Asystom downlink polling → balises
Toutes les 60s, GET
/getPendingMsgs?client=<id>. Si la réponse contient des device_list/payload/cmd, la chaîne parse_asystom_dl émet un msg.app = { queue = { [DevEUI] = { data, fport, confirmed=false } } } vers le node lora-app qui en fait un downlink LoRa Class A.FLUX 04 · 1 j
Santé CloudGate → IoBase
Heartbeat 1×/h + 1× par event capteur. POST
/gateway/health. Body : life_counter, uptime, lora_uplinks_count, lora_status (active/idle/stale), gsm_rssi, gsm_operator, gsm_technology, gsm_rfband, modem_model, last_iobase_ok_ts, last_asystom_ok_ts.Anatomie d'une trame uplink (Flux 01 + 02 + 04 simultanés)
T0 Balise émet uplink LoRa → CloudGate packet forwarder
T+1ms lora-srv déchiffre AppSKey/NwkSKey → décompose la trame
T+2ms lora-app forward la trame avec metadata vers le flow-node Balise-01
│
▼
on_lora_message (function)
│ global.lora_uplinks_count++
│ global.last_lora_ts = os.time()
│ global.last_lora_deveui = msg.DevEUI
│ p({tag='LORA_IN', DevEUI=..., fport=..., FCntUp=..., rssi=..., snr=...})
├──▶ lora out (debug sidebar)
│
├──▶ build_iobase_msg ──▶ http POST /api/v1/cloudgate/payloads (Bearer DAP)
│ └──▶ handle_iobase_resp ──┬──▶ dap out (debug)
│ └──▶ trigger_refresh_on_401 (si 401)
│ └──▶ build_dap_refresh (mutex)
│
├──▶ build_audit_line ──▶ file out append /mnt/data/luvitred/uplinks-YYYY-MM-DD.jsonl
│
├──▶ build_asystom_msg ──▶ http POST /lorawan (Basic auth)
│ └──▶ handle_asystom_resp ──▶ asystom out (debug)
│
└──▶ build_health_msg (event-triggered) ──▶ http POST /gateway/health (Bearer DAP)
└──▶ handle_health_resp ──┬──▶ health out
└──▶ trigger_refresh_on_401
Composants du canvas (résumé)
| Catégorie | Nodes | Rôle |
|---|---|---|
| Entrée LoRa | lora-srv · lora-app · Balise-01 (lora device) |
Packet forwarder + LNS + AppServer + flow-node device-spécifique. Sortie vers on_lora_message. |
| Dispatcher | on_lora_message (function) |
Compteurs + 5 wires sortantes : lora out, build_iobase, build_audit, build_asystom, build_health. |
| Flux 01 IoBase | build_iobase_msg → iobase POST (queueenabled) → handle_iobase_resp |
Build body spec v1.0 + headers, POST avec Bearer, queue offline si 5xx/timeout. |
| Flux 02 Asystom | build_asystom_msg → asystom POST (queueenabled) → handle_asystom_resp |
Build body Loriot-like + Basic auth + X-Network header, POST. |
| Flux 03 DL polling | asystom downlink poll (cron 60s) → build_asystom_dl_get → http GET → parse_asystom_dl → lora-app |
GET getPendingMsgs, parse JSON, multi-msg vers lora-app.queue par DevEUI. |
| Flux 04 Health | health hourly (cron 3600s) + on_lora_message → build_health_msg → health POST → handle_health_resp |
Body 11 champs métriques. POST 1×/h + à chaque event capteur. |
| Audit local | build_audit_line → audit jsonl (file out append) + audit rotate daily 02:00 (cron) → audit_rotate → file out delete |
JSONL append per day dans /mnt/data, cleanup > 15 jours quotidien. |
| DAP auth | 3 inject + 3 build + dap POST + handle_dap_resp + persist_dap + save dap state + chaîne restore au boot |
Register/Token/refresh single-use protégé par mutex. Persistance flash. Voir Chap. 02. |
| Heartbeat life | life tick 1s → life_tick → life counter file out + load life @startup → file in → life_restore |
Compteur 0→32000 cyclique persisté toutes les 60s, restauré au boot. |
| CG métriques | cg admin login + cg metrics poll (cron 300s) → 3 endpoints (internet/diag wwan0/sim) → handle_cg_metrics |
Récupère GSM RSSI, opérateur, technology, RF band depuis l'admin local CG. |
| Queue offline | queueing config klaud_offline (config node) |
RAM 50 + flash 15000 entries dans /mnt/data/luvitred/queue/. Replay auto à la reconnexion. |
| Init | startup init → fn_startup_init |
Set globals au boot : start_time, hosts, credentials, defaults. |
Stockage local
/mnt/data/luvitred/
ubifs ~79 Mo libres. Toutes les données runtime persistantes.
└─ life_counter.json
Compteur de vie persisté toutes les 60s. Restauré au boot.
└─ dap_state.json
device_code + access_token + refresh_token + saved_at. Re-écrit à chaque token update. Restauré au boot.
└─ uplinks-YYYY-MM-DD.jsonl
Append-only des trames LoRa, 1 ligne JSON par uplink. Rétention 15 j par cron quotidien.
└─ queue/
Queue offline du node http-request natif (queueenabled=true). 15000 entries max, FIFO retry.
/overlay/etc/luvitred/store/lora_db/
jffs2 16 Mo. LevelDB du node lora-app : sessions OTAA, AppSKey/NwkSKey/DevAddr/FCnt persistés.
Resilience & KPIs
Coupure WAN courte
Trames mises en queue par
queueenabled=true. Replay automatique à la reconnexion.Coupure WAN longue (> 1 h)
Détection transition WAN false→true par
cg metrics poll → trigger refresh DAP préventif → puis replay queue.Token DAP expiré
POST → 401 →
fn_trigger_refresh_on_401 → build_dap_refresh (mutex) → nouveau token → la trame est replay automatiquement avec le nouveau token (queueenabled).Reboot CG
Tokens restaurés depuis
dap_state.json au boot. life_counter restauré. Aucun re-enrôlement Jean-Marc nécessaire.Refresh concurrent
Mutex
global.dap_refreshing → 1 seul appel /refresh à la fois. Protège le single-use du refresh_token.Stockage queue
15000 entries × ~500 bytes = ~7,5 Mo. Au pic 1 trame/min × 15 j = 21600 trames. Rétention > 15 j garantie pour cadence ≤ 1 trame/min.