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égorieNodesRô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_msgiobase POST (queueenabled) → handle_iobase_resp Build body spec v1.0 + headers, POST avec Bearer, queue offline si 5xx/timeout.
Flux 02 Asystom build_asystom_msgasystom 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_gethttp GETparse_asystom_dllora-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_msghealth POSThandle_health_resp Body 11 champs métriques. POST 1×/h + à chaque event capteur.
Audit local build_audit_lineaudit jsonl (file out append) + audit rotate daily 02:00 (cron) → audit_rotatefile 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 1slife_ticklife counter file out + load life @startupfile inlife_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 initfn_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_401build_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.