#41 — RF Monitoring (Heatmap RSSI Temps Réel)
PLANIFIÉ
Priorité: 🔴 CRITIQUE · Type: TYPE E · Conteneur: rgz-grafana · Code: config/grafana/dashboards/rf_heatmap.json
Dépendances: #37 dashboards-grafana, #39 snmp-poller
Description
Outil de visualisation temps réel de la couverture RF (Radio-Frequency) sur l'ensemble du réseau ACCESS RGZ. Heatmap Grafana Geomap panel affichant l'intensité du signal RSSI (Received Signal Strength Indicator) pour 8 villes principales du Bénin : Cotonou, Porto-Novo, Parakou, Abomey-Calavi, Bohicon, Natitingou, Kandi et Lokossa.
Les coordonnées GPS de chaque Access Point proviennent de la table reseller_sites (latitude, longitude, city, ap_ip, last_rssi_dbm). Le RSSI est collecté toutes les 5 minutes via #39 SNMP poller dans TimescaleDB.
La heatmap utilise un dégradé de couleurs pour visualiser la qualité du signal : vert (fort >-65dBm), jaune (moyen -65 à -75dBm), rouge (faible <-75dBm). Un clic sur un point affiche l'AP associé, son revendeur, son NAS-ID, uptime SLA et données de trafic.
Architecture Interne
Dataflow:
1. CPE Ubiquiti → SNMPv3 getters (RSSI, quality, errors)
2. rgz-beat: app/tasks/monitoring.py → snmp_poll() toutes les 5min
3. snmp_metrics TimescaleDB:
└─ hypertable: time, nas_id, ap_ip, metric_name, metric_value
4. Grafana PostgreSQL datasource:
└─ SELECT time, city, latitude, longitude, ap_ip, last_rssi_dbm
FROM reseller_sites rs
LEFT JOIN snmp_metrics sm ON rs.ap_ip = sm.ap_ip
WHERE metric_name = 'signal_strength' AND time > NOW() - INTERVAL '10 min'
5. Geomap Panel:
└─ Overlay points avec couleurs dégradées
└─ Tooltip: AP name, RSSI dBm, vendor, uptime%, last_update
6. Kibana (ElasticSearch) + Prometheus:
└─ Alerte si RSSI < -80dBm sur >30min → SMS #61 incident
Configuration Geomap Grafana:
- Map provider: OpenStreetMap (mapbox optionnel)
- Marker size: RSSI en pixels (rapport linear -90 à -50 → 5 à 50px)
- Color scheme: green (#4CAF50) > yellow (#FFC107) > red (#F44336)
- Initial zoom: 7 (viewport Bénin complet)
- Refresh interval: 5 minutes (sync avec SNMP)Configuration
# .env pour Grafana PostgreSQL datasource
GRAFANA_DATASOURCE_PG_HOST=rgz-db
GRAFANA_DATASOURCE_PG_PORT=5432
GRAFANA_DATASOURCE_PG_DATABASE=rgz
GRAFANA_DATASOURCE_PG_USER=grafana_ro
GRAFANA_DATASOURCE_PG_PASSWORD=${PG_GRAFANA_PASSWORD}
GRAFANA_DATASOURCE_PG_SSL_MODE=require
# Prometheus scrape config pour rgz-api (expose /metrics RSSI)
PROMETHEUS_SCRAPE_INTERVAL=5m
PROMETHEUS_ALERT_RSSI_THRESHOLD=-80 # dBm
PROMETHEUS_ALERT_RSSI_DURATION=30m
# SNMP Poller config (app/config.py)
SNMP_COMMUNITY=RGZ_PUBLIC_v3
SNMP_VERSION=3
SNMP_SECURITY_LEVEL=authPriv
SNMP_USERNAME=monitoring
SNMP_AUTH_PROTOCOL=SHA
SNMP_PRIV_PROTOCOL=AESEndpoints API
| Méthode | Route | Réponse |
|---|---|---|
| GET | /api/v1/monitoring/rssi/current | {items: [{nas_id, ap_ip, city, lat, lon, rssi_dbm, timestamp, quality%}], total} |
| GET | /api/v1/monitoring/rssi/{nas_id}/history?from=&to= | {items: [{timestamp, rssi_dbm, quality%}], total, pages} |
| GET | /api/v1/monitoring/coverage-map?days=7 | GeoJSON {type: FeatureCollection, features: [{geometry: {type: Point, coordinates: [lon, lat]}, properties: {ap_ip, rssi, uptime%}}]} |
| POST | /api/v1/monitoring/rssi-alert | Webhook Prometheus : {status: firing, alerts: [{labels: {nas_id, city, rssi_dbm}, value}]} |
Commandes Utiles
# Tester collecte SNMP depuis rgz-beat
docker exec rgz-beat python -c "
from app.tasks.monitoring import snmp_poll_rssi
import asyncio
asyncio.run(snmp_poll_rssi())
"
# Vérifier métriques RSSI en DB
docker exec rgz-db psql -U rgz -d rgz -c "
SELECT nas_id, ap_ip, metric_value as rssi_dbm, time
FROM snmp_metrics
WHERE metric_name = 'signal_strength'
ORDER BY time DESC
LIMIT 10;
"
# Consulter dashboard Grafana (via Traefik)
curl -H "Authorization: Bearer ${GRAFANA_API_TOKEN}" \
https://grafana-rgz.duckdns.org/api/dashboards/uid/rf_heatmap
# Simuler alerte Prometheus RSSI
docker exec rgz-prometheus curl -X POST \
http://localhost:9093/api/v1/alerts \
-d '{
"status": "firing",
"alerts": [{
"labels": {"nas_id": "access_kossou", "city": "Cotonou", "rssi_dbm": "-85"},
"annotations": {"summary": "RSSI dégradé"}
}]
}'
# Exporter heatmap en PNG
docker exec rgz-grafana grafana-cli admin export-dashboard \
--dashboard-uid rf_heatmap --folder-title "RF" > rf_heatmap.jsonImplémentation TODO
- [ ] Créer hypertable TimescaleDB
snmp_metrics(time BIGINT, nas_id TEXT, ap_ip TEXT, metric_name, metric_value FLOAT8) - [ ] Implémenter
app/tasks/monitoring.py::snmp_poll_rssi()avec SNMPv3 (pysnmp library) - [ ] Ajouter Celery task Beat :
rgz.snmp.poll every 5min queue=rgz.monitoring - [ ] Créer Grafana datasource PostgreSQL (pgstmt avec timerange macro)
- [ ] Développer dashboard JSON : Geomap panel + Legend panel + Gauge (RSSI min/max)
- [ ] Ajouter Prometheus scrape job :
job_name: rssi_metrics→rgz-api:8000/metrics?metric=rssi - [ ] Créer AlertingRule : RSSI < -80dBm pour >30min → Alertmanager
- [ ] Implémenter webhook
/api/v1/monitoring/rssi-alert→ déclenche SMS #61 - [ ] Tests : SNMP mocking, requête TimescaleDB complexe, PromQL expressions
- [ ] Documentation : guide terrain lire heatmap, interpréter couleurs RSSI
Dernière mise à jour: 2026-02-21