Monitoring de flotte¶
Un device silencieux est un device suspect — la supervision de flotte transforme l'absence de signal en alerte actionnable avant que le problème ne devienne critique.
Le problème du device silencieux¶
Le mode de défaillance le plus fréquent dans une flotte IoT n'est pas le crash visible — c'est le silence. Un device qui cesse de reporter ses données sans générer d'erreur explicite. La batterie est à plat. La carte SIM a expiré. Le câble RS-485 s'est décroché. Le service systemd est tombé en boucle de redémarrage et ne remonte plus rien. La passerelle réseau a changé d'adresse IP.
Sans supervision active, ces défaillances passent inaperçues jusqu'à ce qu'un opérateur de terrain constate physiquement l'absence de données dans l'interface. Sur des installations distantes ou difficiles d'accès, cette découverte peut prendre des jours.
Le monitoring de flotte inverse ce paradigme : c'est le système qui détecte et alerte, pas l'opérateur qui surveille.
Le heartbeat comme signal de vie¶
Le heartbeat est le mécanisme fondamental de détection de devices silencieux. Chaque device envoie périodiquement un message de présence, indépendamment de ses données métier.
Implémentation côté device¶
# heartbeat.py — service de heartbeat sur gateway Linux
import time
import json
import socket
import subprocess
import paho.mqtt.client as mqtt
from pathlib import Path
DEVICE_ID = open("/etc/device-id").read().strip()
MQTT_HOST = "mqtt-prod.industrial.local"
MQTT_PORT = 8883
HB_INTERVAL = 60 # secondes — configurable via desired state
def collect_heartbeat() -> dict:
"""Collecte les métriques système pour le heartbeat."""
# Uptime
with open("/proc/uptime") as f:
uptime_s = float(f.read().split()[0])
# Charge CPU (moyenne 1 min)
with open("/proc/loadavg") as f:
load_1m = float(f.read().split()[0])
# Mémoire disponible
mem_info = {}
with open("/proc/meminfo") as f:
for line in f:
key, val = line.split(":")
mem_info[key.strip()] = int(val.strip().split()[0]) # kB
# Température CPU (Raspberry Pi)
try:
temp_raw = Path("/sys/class/thermal/thermal_zone0/temp").read_text()
cpu_temp_c = int(temp_raw.strip()) / 1000.0
except FileNotFoundError:
cpu_temp_c = None
# Espace disque disponible
stat = subprocess.run(["df", "-BM", "/"], capture_output=True, text=True)
disk_free_mb = int(stat.stdout.splitlines()[1].split()[3].rstrip("M"))
# Compteur de services en échec
failed = subprocess.run(
["systemctl", "list-units", "--state=failed", "--no-legend"],
capture_output=True, text=True
)
failed_services = len(failed.stdout.strip().splitlines()) if failed.stdout.strip() else 0
return {
"device_id": DEVICE_ID,
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"firmware_version": "2.4.0",
"uptime_s": int(uptime_s),
"cpu_load_1m": load_1m,
"cpu_temp_c": cpu_temp_c,
"mem_free_mb": mem_info.get("MemAvailable", 0) // 1024,
"disk_free_mb": disk_free_mb,
"failed_services": failed_services,
"connected_zigbee_devices": _count_zigbee_devices(),
}
def _count_zigbee_devices() -> int:
try:
result = subprocess.run(
["mosquitto_sub", "-h", "127.0.0.1", "-t", "zigbee2mqtt/bridge/devices",
"-C", "1", "-W", "2"],
capture_output=True, text=True
)
devices = json.loads(result.stdout)
return len([d for d in devices if d.get("interview_completed")])
except Exception:
return -1 # Erreur de collecte
def run():
client = mqtt.Client(client_id=f"{DEVICE_ID}-heartbeat", protocol=mqtt.MQTTv5)
# Configurer TLS (omis ici pour la lisibilité)
client.connect(MQTT_HOST, MQTT_PORT)
client.loop_start()
while True:
payload = collect_heartbeat()
client.publish(
f"fleet/{DEVICE_ID}/heartbeat",
json.dumps(payload),
qos=1,
retain=True # Le dernier heartbeat reste disponible pour les late subscribers
)
time.sleep(HB_INTERVAL)
if __name__ == "__main__":
run()
Détection de silence côté serveur¶
Le serveur fleet manager calcule l'âge du dernier heartbeat reçu. Si cet âge dépasse HB_INTERVAL × 3 (3 intervalles manqués consécutifs), l'alerte "device silencieux" est déclenchée.
-- Requête TimescaleDB / PostgreSQL pour détecter les devices silencieux
SELECT
device_id,
MAX(timestamp) AS last_seen,
NOW() - MAX(timestamp) AS silence_duration,
EXTRACT(EPOCH FROM (NOW() - MAX(timestamp))) AS silence_s
FROM fleet_heartbeats
GROUP BY device_id
HAVING EXTRACT(EPOCH FROM (NOW() - MAX(timestamp))) > 180 -- > 3 × 60 s
ORDER BY silence_s DESC;
Métriques clés par catégorie de device¶
Chaque type de device a ses métriques spécifiques. Le tableau suivant liste les métriques importantes pour les types de devices les plus courants.
| Catégorie | Métrique | Seuil warning | Seuil critical | Unité |
|---|---|---|---|---|
| Toutes | Âge dernier heartbeat | > 3 min | > 10 min | secondes |
| Toutes | Failed services systemd | ≥ 1 | ≥ 3 | count |
| Gateway Linux | CPU température | > 70 °C | > 80 °C | °C |
| Gateway Linux | Mémoire libre | < 300 MB | < 100 MB | MB |
| Gateway Linux | Disque libre | < 5 GB | < 1 GB | MB |
| Gateway Linux | Charge CPU (1 min) | > 2,0 | > 3,5 | load |
| Gateway Linux | Devices Zigbee connectés | < 80 % attendus | < 50 % attendus | % |
| Capteur BLE/LoRa | Niveau batterie | < 20 % | < 10 % | % |
| Capteur BLE/LoRa | RSSI | < -80 dBm | < -95 dBm | dBm |
| Capteur LoRa | SNR | < 5 dB | < 0 dB | dB |
| Mobile terrain | Batterie | < 25 % | < 10 % | % |
| Mobile terrain | Signal cellulaire | < -100 dBm | < -110 dBm | dBm |
| Mobile terrain | GPS précision | > 10 m | > 50 m | mètres |
Architecture de supervision¶
graph TB
subgraph DEVICES ["Devices terrain"]
D1[Gateway #1\nheartbeat toutes les 60 s]
D2[Gateway #2\nheartbeat toutes les 60 s]
Dn[Gateway #N\nheartbeat toutes les 60 s]
C1[Capteurs IoT\nmétriques embarquées]
end
subgraph INGEST ["Couche d'ingestion"]
MQTT[Broker MQTT\nMosquitto / EMQX]
TELE[Télémétrie collector\nTelegraf / Vector]
end
subgraph STORE ["Stockage"]
TSDB[Time-series DB\nInfluxDB v3 / TimescaleDB]
ALERT_DB[État des alertes\nPostgreSQL]
end
subgraph PROCESS ["Traitement et alerting"]
RULES[Moteur de règles\nCapacitor / Alertmanager]
SILENCE[Détection silence\nCron 30 s]
end
subgraph NOTIFY ["Notification"]
PD[PagerDuty\nOn-call rotation]
SLACK[Slack / Teams\ncanal #iot-alertes]
SMS[SMS / Appel\nurgences critiques]
TICKET[Ticketing\nJira / Zammad]
end
subgraph VIZ ["Visualisation"]
GRAF[Grafana\nDashboards]
MOBILE_APP[App mobile\nopérateurs terrain]
end
D1 & D2 & Dn & C1 --> MQTT
MQTT --> TELE
TELE --> TSDB
TSDB --> RULES
TSDB --> SILENCE
RULES --> ALERT_DB
SILENCE --> ALERT_DB
ALERT_DB --> PD & SLACK & SMS & TICKET
TSDB --> GRAF
ALERT_DB --> GRAF
GRAF --> MOBILE_APP Configuration des alertes dans Grafana / Alertmanager¶
Règle Prometheus — heartbeat manquant¶
# prometheus/alerts/fleet.yml
groups:
- name: fleet_health
interval: 1m
rules:
# Device silencieux depuis > 3 minutes
- alert: DeviceSilent
expr: |
(time() - fleet_heartbeat_last_seen_timestamp) > 180
for: 0m
labels:
severity: warning
annotations:
summary: "Device silencieux : {{ $labels.device_id }}"
description: >
Le device {{ $labels.device_id }} (site: {{ $labels.site }})
n'a pas envoyé de heartbeat depuis
{{ $value | humanizeDuration }}.
# Batterie critique
- alert: LowBattery
expr: fleet_device_battery_percent < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Batterie critique : {{ $labels.device_id }}"
description: >
Batterie à {{ $value }}% sur {{ $labels.device_id }}.
Intervention terrain requise.
# Température CPU gateway élevée
- alert: GatewayCpuOverheat
expr: fleet_device_cpu_temp_celsius > 80
for: 3m
labels:
severity: critical
annotations:
summary: "Surchauffe gateway : {{ $labels.device_id }}"
# Pourcentage de devices actifs trop bas
- alert: FleetHealthDegraded
expr: |
(count(fleet_heartbeat_last_seen_timestamp > time() - 300)
/ count(fleet_heartbeat_last_seen_timestamp)) < 0.95
for: 5m
labels:
severity: critical
annotations:
summary: "Santé flotte dégradée — {{ $value | humanizePercentage }} de devices actifs"
Dashboards terrain¶
Un dashboard de flotte efficace répond en moins de 5 secondes à la question de l'opérateur de permanence : "Est-ce que tout va bien en ce moment ?"
Vue de synthèse — niveau top¶
| Panneau | Contenu | Actualisation |
|---|---|---|
| Santé globale | % devices actifs (objectif : ≥ 99 %) | 30 s |
| Carte géographique | Devices verts / orange / rouge sur carte | 30 s |
| Alertes actives | Compteur par sévérité | Temps réel |
| Dernières 24 h | Graphe nombre de devices actifs vs temps | — |
| Batteries critiques | Liste devices < 15 % | 5 min |
| File d'attente OTA | Devices en cours de mise à jour | 1 min |
Vue détail — device individuel¶
Accessible en cliquant sur un device dans la carte ou la liste :
- Historique des heartbeats sur 7 jours (timeline verte = actif, rouge = absent)
- Graphes CPU, mémoire, température sur 24 h
- Log des 10 derniers événements (connexions, déconnexions, alertes, mises à jour)
- État du desired state vs reported state
- Boutons d'action : redémarrer le service, déclencher une mise à jour, wipe
SLA et KPI de flotte¶
Pour un contrat de service ou un engagement interne, définir les KPI de flotte permet de mesurer objectivement la qualité opérationnelle.
| KPI | Formule | Objectif typique |
|---|---|---|
| Disponibilité flotte | Devices actifs / Total × 100 | ≥ 99,0 % |
| MTTD (détection) | Temps moyen entre panne et alerte | < 5 min |
| MTTR (restauration) | Temps moyen entre alerte et résolution | < 4 h |
| Couverture firmware | Devices à jour / Total × 100 | 100 % sous 30 j |
| Taux d'alerte fausse | Faux positifs / Total alertes | < 5 % |
| Données manquées | Mesures perdues / Attendues | < 0,1 % |
Ce qu'il faut retenir¶
- Le heartbeat toutes les 60 secondes (3 intervalles manqués = alerte) est le mécanisme universel de détection de devices silencieux.
- Les métriques à superviser varient selon le type de device : température CPU et disque pour les gateways, batterie et RSSI pour les capteurs sans fil.
- L'architecture Prometheus + Alertmanager + Grafana couvre l'ensemble des besoins de supervision avec des outils open-source matures.
- Les KPI de flotte (disponibilité, MTTD, MTTR) permettent de mesurer et d'engager la qualité de service opérationnelle.
Section suivante : Architecture cloud IoT — de la gateway au cloud : ingestion à grande échelle, pipelines de données et services managés.