04 API Asystom Advisor
Uplink push (Flux 02) et downlink polling (Flux 03) opérationnels en réel depuis la CloudGate vers le serveur Advisor (environnement de staging). Les 4 globals (asystom_url, asystom_auth_b64, asystom_network, asystom_client) sont configurés dans startup_init et persistés. Les trames des balises sont routées et le serveur accuse réception (HTTP 200). Validation confirmée par les logs canvas (ASYSTOM_OUT → ASYSTOM_RESP status 200) et le polling downlink (ASYSTOM_DL).
Référence
Document : Lorawan_Network_Integration.pdf publié par Asystom (Third-party LoRaWAN network integration with Asystom Services).
Flux 02 — Uplink push
Endpoint
| URL | POST <asystom_url>/lorawan |
|---|---|
| Méthode | POST |
| Content-Type | application/json |
| Auth | Authorization: Basic <base64(user:password)> |
Headers obligatoires
| Header | Source canvas |
|---|---|
Authorization | 'Basic ' .. global.asystom_auth_b64 |
Content-Type | application/json |
X-Network | global.asystom_network (network identifier passé en header HTTP, recommandation Asystom plutôt qu'en body) |
Body JSON (Loriot-like)
Le format de body dépend du LNS source (Actility, Kerlink, Loriot, Multitech). Asystom fournit un exemple Loriot complet dans la doc PDF que le déserializer Advisor comprend déjà. La chaîne canvas envoie un format Loriot-compatible :
{
"endDevice": {
"devEui": "0004A30B010593C1",
"devAddr": "00DA5139"
},
"fPort": 4,
"fCntUp": 1182,
"payload": "00A2070E4C210239360BE184F67DD87C417CC87A...",
"encodingType": "HEXA",
"recvTime": 1714056772000,
"client": "<asystom_client>",
"dataRate": "SF7BW125",
"gwInfo": [
{
"rfRegion": "EU868",
"rssi": -85,
"snr": 10.5
}
]
}
Champs et leur source
| Champ | Source canvas |
|---|---|
endDevice.devEui | msg.DevEUI |
endDevice.devAddr | msg.DevAddr |
fPort | msg.fport |
fCntUp | msg.FCntUp |
payload | Hex uppercase de msg.payload |
encodingType | Hardcoded "HEXA" |
recvTime | os.time() * 1000 (epoch ms) |
client | global.asystom_client (Asystom client identifier, utilisé aussi pour le polling downlink) |
dataRate | Hardcoded "SF7BW125" pour l'instant (TODO : extraire du msg) |
gwInfo[0].rfRegion | Hardcoded "EU868" |
gwInfo[0].rssi | msg.rssi |
gwInfo[0].snr | msg.snr |
Codes de réponse
| Code | Statut | Comportement canvas |
|---|---|---|
| 201 | Frame processed | global.last_asystom_ok_ts = os.time() |
| 401 | Invalid credentials | Asystom = Basic auth (pas DAP). 401 = mauvais credentials → log + alerte. Ne déclenche pas le retry DAP. |
| 500 | Server Error | Mis en queue offline (queueenabled=true), replay auto. |
Observation 2026-06-10 (env staging) : l'uplink reçoit actuellement HTTP 200 (frame parsée et accusée) plutôt que le 201 spec. Comportement constant, traité comme succès côté canvas. À reconfirmer avec Asystom lors du passage en environnement de production.
Flux 03 — Downlink polling
Basic auth
vide si rien à pousser
multi-msg → lora-app input
msg.app.queue[DevEUI]
wait window après prochain uplink balise
ou cmd 64 (reboot)
Endpoint
| URL | GET <asystom_url>/getPendingMsgs?client=<asystom_client> |
|---|---|
| Méthode | GET |
| Content-Type | application/x-www-form-urlencoded |
| Auth | Basic <asystom_auth_b64> |
Cadence
Inject cron asystom downlink poll avec repeat=60 (60s) et onceDelay=30. Donc 1 poll toutes les 60 secondes.
Réponse
{
"device_list": {
"0": "00-04-A3-0B-01-05-93-C1",
"1": "00-04-A3-0B-01-05-93-C2"
},
"payload": {
"0": "054114003C003C006801",
"1": "00"
},
"cmd": {
"0": "70",
"1": "64"
}
}
Si device_list est vide, aucun downlink à émettre. Sinon, pour chaque index i, la balise device_list[i] doit recevoir un downlink avec payload[i] sur FPort cmd[i].
Parsing canvas
Le node parse_asystom_dl extrait les 3 sections via regex Lua (le sandbox LuvitRED n'a pas json.decode garanti) :
local section_devices = body:match('"device_list"%s*:%s*(%b{})')
local section_payloads = body:match('"payload"%s*:%s*(%b{})')
local section_cmds = body:match('"cmd"%s*:%s*(%b{})')
-- Pour chaque section, extraire les paires "i":"value"
for k, v in section:gmatch('"([^"]+)"%s*:%s*"([^"]*)"') do t[k] = v end
-- Recompacter le DevEUI : "AA-BB-CC-DD-EE-FF-00-11" → "AABBCCDDEEFF0011"
local deveui = mac:gsub('-', ''):upper()
Émission du downlink LoRa
Pour chaque device, le parser construit :
msg.app = {
queue = {
[DevEUI] = {
data = payload_hex,
fport = tonumber(cmd_str),
confirmed = false,
},
},
}
Et l'émet sur output 0 (multi-msg). La sortie est wirée vers le node lora-app input qui queue le downlink. Au prochain uplink de la balise (Class A), le downlink est envoyé pendant la wait window.
FPort downlink connus
| FPort | Action Asystom | Format payload |
|---|---|---|
| 64 | Reboot balise | "00" ou TODO précis Asystom |
| 70 | Set scheduling frequency | 054114XXXXXXXX6801 avec X = secondes/10 little-endian |
Helper Lua main_flow.build_frequency_payload(seconds) et build_frequency_command(seconds) dans main_flow.lua pour générer ces payloads.
Globals à configurer
| Variable | Description | Source |
|---|---|---|
global.asystom_url | URL base du serveur Asystom Advisor | Asystom |
global.asystom_auth_b64 | Base64 de user:password | Asystom |
global.asystom_network | Identifiant unique du réseau LoRaWAN (passé en header X-Network) | convention déploiement (ex: cloudgate-XX) |
global.asystom_client | Asystom client identifier. Utilisé en body uplink ET en query param du polling DL. | Asystom |