← Dashboard

Architecture & Flux

Comment Clouka communique avec un routeur Teltonika — de l'adoption initiale Ă  la tĂ©lĂ©mĂ©trie temps rĂ©el et l'exĂ©cution de commandes Ă  distance.

Vue d'ensemble

Routeur Teltonika
clouka-agent.sh
Service procd permanent
↳ mosquitto_pub → tĂ©lĂ©mĂ©trie
↳ mosquitto_sub ← commandes
↳ LWT → statut offline
gsmctl / gpsctl
Collecte 4G/GPS Teltonika
MQTT
→ telemetry
→ status
← commands
→ response
:1883
Plateforme Clouka
RMQTT (broker)
Reçoit / distribue les messages
Vector
MQTT → ClickHouse pipeline
ClickHouse
Métriques time-series
MySQL
Devices, comptes, commandes
Laravel (API + UI)
mqtt:listen · Dashboard Livewire
HTTP
→ /adopt
→ /install
:8000
Utilisateur
Dashboard
Livewire · poll 5s
Page détail
Graphes · onglets
Page adoption
Script bootstrap curl
1

Adoption — Premier dĂ©marrage

HTTP uniquement
1
Opérateur

Ouvre /adopt, copie la commande curl

2
Teltonika

Exécute : curl ".../api/v1/adopt/pltk_xxx" | sh

3
Clouka

Retourne le script bootstrap (sh) avec token embarqué

4
Teltonika

Détecte : SERIAL (mnf_info), FIRMWARE (/etc/version), MODEL, MAC

5
Teltonika

POST /api/v1/device/install { platform_token, serial, firmware, model, mac }

6
Clouka

Crée le device en DB, génÚre dvtk_xxx, répond { device_token, mqtt_host, mqtt_port }

7
Teltonika

Sauve token → /etc/clouka/device.token · config → /etc/clouka/config

8
Teltonika

GET /devices/{serial}/script → tĂ©lĂ©charge clouka-agent.sh

9
Teltonika

clouka-agent.sh install → service procd actif + dĂ©marrage auto

# Ce que l'opérateur colle sur la console SSH
curl -sf "https://clouka.io/api/v1/adopt/pltk_ViiYU..." | sh
# Réponse de /api/v1/device/install
{
"device_token": "dvtk_AbCdEf...",
"serial": "1234567890AB",
"mqtt_host": "192.168.1.10",
"mqtt_port": 1883,
"message": "Device registered"
}
# Fichiers créés sur le routeur
/etc/clouka/device.token # dvtk_AbCdEf...
/etc/clouka/config # MQTT_HOST/PORT/SERIAL
/usr/bin/clouka-agent.sh # agent permanent
✅ À la fin de cette phase, le routeur apparaĂźt dans le dashboard avec le statut Offline (pas encore de tĂ©lĂ©mĂ©trie). Il passe Online dĂšs que l'agent dĂ©marre.
2

TĂ©lĂ©mĂ©trie — Envoi toutes les 5 secondes

MQTT → ClickHouse
1
Agent

Collecte les métriques systÚme : CPU, RAM, température, uptime

2
Agent

Collecte 4G via gsmctl : opérateur, RSRP, RSRQ, SINR, bande, Cell ID, IMEI

3
Agent

Collecte réseau : IP WAN, bytes RX/TX, clients LAN/WiFi

4
Agent

Collecte GPS via gpsctl (si disponible) : lat, lon, vitesse, satellites

5
Agent

mosquitto_pub → devices/{serial}/telemetry (JSON, QoS 1)

6
RMQTT

Reçoit et distribue le message à tous les abonnés

7
Vector

AbonnĂ© MQTT → transforme le JSON → insĂšre dans ClickHouse

8
mqtt:listen

Met Ă  jour MySQL : last_seen_at, status=online, firmware, model

9
Livewire

Poll 5s → relit MySQL + ClickHouse → met à jour le dashboard

# Topic MQTT
devices/1234567890AB/telemetry
# Payload JSON (extrait)
{
"uptime": 86423,
"cpu_load_1m": 0.12,
"mem_used": 134217728,
"mem_total": 536870912,
"temperature": 52.0,
"operator": "Orange F",
"technology": "LTE",
"signal_rsrp": -85,
"signal_rsrq": -10,
"signal_sinr": 12,
"band": "B3",
"wan_ip": "10.128.4.5",
"wan_rx_bytes": 10485760,
"gps_lat": 48.8566,
"gps_lon": 2.3522
}
ClickHouse
Stocke chaque message comme une ligne dans telemetry. PartitionnĂ© par mois, indexĂ© par (serial, ts). RequĂȘtes sub-seconde mĂȘme sur des millions de lignes.
MySQL
Ne stocke que l'état courant du device (last_seen_at, status, firmware, model). Pas de time-series ici.
3

Commandes — Envoi depuis le dashboard

MQTT bidirectionnel
1
Opérateur

Clique sur "Reboot" (ou Backup, Refresh
) dans le dashboard

2
Laravel

CrĂ©e un enregistrement Command en DB (status: pending → sent)

3
Laravel

MqttPublisherService publie sur devices/{serial}/commands

4
RMQTT

Distribue le message au routeur abonné sur ce topic

5
Agent

mosquitto_sub reçoit la commande JSON

6
Agent

execute_command() : switch/case sur l'action (reboot, backup, exec
)

7
Agent

Publie le résultat sur devices/{serial}/response { id, status, output }

8
mqtt:listen

Reçoit la réponse, met à jour Command en DB (status: done/failed, result, completed_at)

9
Livewire

Poll suivant → onglet Commandes affiche le rĂ©sultat

# Commande envoyée par Laravel
Topic : devices/1234567890AB/commands
{
"id": "550e8400-e29b-41d4-a716",
"action": "reboot",
"payload": {}
}
# Réponse de l'agent
Topic : devices/1234567890AB/response
{
"id": "550e8400-e29b-41d4-a716",
"status": "done",
"output": "Reboot scheduled in 5s"
}
# Commandes disponibles
refresh_telemetry → force un envoi immĂ©diat
reboot → redĂ©marre le routeur (5s)
backup → sysupgrade -b /tmp/backup.tar.gz
exec → { "payload": { "script": "..." } }
4

DĂ©tection hors ligne — LWT + timeout

Last Will Testament

Cas 1 — DĂ©connexion propre (reboot, arrĂȘt)

Agent

Reçoit signal SIGTERM (arrĂȘt procd) → exĂ©cute le bloc stop

Agent

Publie devices/{serial}/status = "offline" (retained, QoS 1)

mqtt:listen

Reçoit → Device::update(status: offline)

Dashboard

Badge passe rouge au prochain poll (≀ 5s)

Cas 2 — Perte rĂ©seau brutale (coupure 4G)

RMQTT

Keepalive expirĂ© → publie automatiquement le LWT enregistrĂ©

RMQTT

devices/{serial}/status = "offline" (configuré au connect par mosquitto_sub)

mqtt:listen

Reçoit → Device::update(status: offline)

isOnline()

Fallback : last_seen_at > 30s → retourne false mĂȘme sans LWT

# LWT déclaré par mosquitto_sub au démarrage
mosquitto_sub \
-h "$MQTT_HOST" -p "$MQTT_PORT" \
-t "devices/$SERIAL/commands" \
--will-topic "devices/$SERIAL/status" \
--will-payload "offline" \
--will-retain
# Logique isOnline() dans Device.php
public function isOnline(): bool
{
return $this->last_seen_at
?->gt(now()->subSeconds(30))
?? false;
}
⚡ Le LWT est dĂ©clarĂ© au moment de la connexion MQTT, pas Ă  la dĂ©connexion. RMQTT le publie automatiquement si la connexion se coupe sans DISCONNECT propre.

Stack technique

RMQTT :1883
Broker MQTT

Écrit en Rust. LĂ©ger, performant. Écoute sur :1883. API HTTP sur :6060 pour les stats.

Vector :interne
Pipeline données

AbonnĂ© MQTT (source) → parse JSON (transform VRL) → insert batch ClickHouse (sink). ZĂ©ro code.

ClickHouse :8124
Time-series DB

Stocke la tĂ©lĂ©mĂ©trie. MergeTree engine, partitionnĂ© par mois. RequĂȘtes analytiques en ms.

MySQL :3310
Base relationnelle

Devices, comptes, commandes. Données structurées et état courant uniquement.

Laravel 11 :8000
Backend + API

API REST pour l'adoption. mqtt:listen pour la synchro MySQL. Livewire pour l'UI réactive.

Livewire :XHR
UI réactive

Poll toutes les 5s via XHR. Pas de rechargement de page. wire:ignore sur les graphes Chart.js.

Topics MQTT — Convention

Topic Direction QoS Retained Contenu
devices/{serial}/telemetry Router → Cloud 1 Non JSON complet (CPU, RAM, 4G, GPS, rĂ©seau
) toutes les 5s
devices/{serial}/status Router → Cloud 1 Oui "online" au dĂ©marrage · "offline" via LWT ou arrĂȘt propre
devices/{serial}/commands Cloud → Router 1 Non JSON : { id, action, payload } — envoyĂ© par Laravel
devices/{serial}/response Router → Cloud 1 Non JSON : { id, status, output } — rĂ©ponse de l'agent
devices/{serial}/events Router → Cloud 1 Non RĂ©servĂ© — Ă©vĂ©nements futurs (changement SIM, alerte
)