Runbook d’exploitation locale
Ce runbook regroupe les commandes utiles pour démarrer, vérifier et diagnostiquer la station en environnement de développement ou de démonstration.
Démarrage standard
Copier la configuration locale :
Copy-Item .env.example .env
Démarrer la stack :
docker compose up -d --build
Vérifier les services :
docker compose ps
Vérifier la santé applicative Django :
curl http://localhost:8000/api/health/live/
curl http://localhost:8000/api/health/ready/
live confirme que le processus HTTP répond. ready confirme que l’API
peut utiliser la base de données, le cache et la configuration critique. Le
healthcheck Docker du service backend utilise ready.
Vérifier la chaîne asynchrone :
docker compose exec celery celery -A config inspect ping --timeout=3
docker compose exec mqtt-bridge python manage.py check_mqtt_bridge_health
Le worker celery est considéré sain s’il répond au ping Celery. Le service
mqtt-bridge écrit un heartbeat local tant qu’il est connecté au broker MQTT ;
le healthcheck échoue si ce heartbeat est absent ou trop ancien.
Le service celery-beat publie les tâches périodiques de supervision :
contrôle des sessions sans télémétrie et envoi des notifications d’alertes en
attente.
Services attendus pour une démonstration complète :
backend;frontend;raspberry;mqtt;mqtt-bridge;celery;celery-beat;redis;db;grafana;mailpit;reverse-proxy.
URLs utiles
Via Nginx :
https://localhost/ frontend
https://localhost/api/ API Django
https://localhost/admin/ admin Django
https://localhost/kiosk/ kiosque Raspberry
https://localhost/grafana/ Grafana
Ports directs :
8000 Django
5173 Vite
9000 kiosque Raspberry
3000 Grafana
8025 Mailpit
1025 SMTP Mailpit
1883 Mosquitto
5432 PostgreSQL/TimescaleDB
Scénario complet sans matériel
Le script scripts/run-demo-scenario.ps1 joue un parcours de démonstration de
bout en bout sans ESP32 physique :
création d’une réservation QR sur la station de démonstration ;
création d’une session mobile de borne ;
connexion de l’utilisateur de démonstration ;
sélection et autorisation de la réservation ;
consommation par le kiosque ;
publication
STARTvers le slot MQTT ;simulation de télémétrie uniquement pour l’emplacement validé ;
publication
STOP;clôture de session backend ;
résumé final reservation/session/device.
Lorsque la borne possède un mqtt_prefix, le script et le simulateur utilisent
le topic structuré station/<mqtt_prefix>/<slot_id>/telemetry. Cela valide le
routage production station/borne/emplacement au lieu de publier sur un simple
station/<slot_id>/telemetry.
Commande :
.\scripts\run-demo-scenario.ps1 -Reference DEMOQR1 -Emplacement 1 -TelemetryCount 5
Résultat attendu :
réservation
terminee;session
closed;télémétrie attachée à la session ;
device de l’emplacement revenu
online;aucune simulation parasite sur les autres emplacements.
Pour rejouer plus vite lorsque la stack est déjà lancée :
.\scripts\run-demo-scenario.ps1 -SkipDockerStart -TelemetryCount 3
Supervision MQTT et Grafana
Démarrer les services nécessaires à la supervision :
docker compose up -d mqtt mqtt-bridge celery celery-beat grafana
Publier cinq mesures simulées :
docker compose run --rm mqtt-simulator python manage.py simulate_mqtt_telemetry --slots slot1 --scenario normal --interval 1 --count 5
Vérifier que les messages passent sur MQTT :
docker compose exec mqtt mosquitto_sub -h localhost -t 'station/#' -v -C 10 -W 10
Vérifier que les mesures sont stockées :
docker compose exec backend python manage.py shell -c "from api.models import CapteurData; from django.db.models import Count; print(list(CapteurData.objects.values('capteur__type').annotate(n=Count('id_data')).order_by('capteur__type')))"
Les types attendus sont :
temperature;humidity;current;voltage;power;door;cable.
Le dashboard Grafana à ouvrir en premier est Station - Vue globale supervision.
Il permet ensuite d’accéder au détail Station - Supervision technique.
Diagnostic du bridge MQTT
Si les messages sont visibles sur MQTT mais pas en base :
vérifier que
mqtt-bridgetourne ;vérifier que
celeryetcelery-beattournent ;lire les logs des deux services ;
republier une télémétrie de test.
Commandes :
docker compose ps mqtt mqtt-bridge celery celery-beat redis db
docker compose logs --tail=100 mqtt-bridge
docker compose logs --tail=100 celery
docker compose logs --tail=100 celery-beat
docker compose restart mqtt-bridge celery celery-beat
Le bridge reçoit les messages et les met en file. Celery exécute réellement l’ingestion et écrit en base. Celery Beat planifie les contrôles récurrents qui ne dépendent pas directement d’un message MQTT entrant.
Test de commande ESP32
Envoyer une commande via le Raspberry :
curl -X POST http://localhost:9000/mqtt/command \
-H "Content-Type: application/json" \
-d '{"slot_id":"slot1","action":"START","session_id":"demo"}'
Observer les topics :
docker compose exec mqtt mosquitto_sub -h localhost -t 'station/#' -v -C 5 -W 10
En présence d’un ESP32 réel, on doit voir :
la commande sur
station/slot1/cmd;l’ACK sur
station/slot1/ack;les statuts sur
station/slot1/status;la télémétrie périodique sur
station/slot1/telemetry.
Validation ACK sans matériel
Même sans ESP32 physique, le comportement de sécurité peut être validé : si
MQTT_ACK_REQUIRED vaut true, le kiosque doit refuser de considérer une
commande comme réussie lorsqu’aucun ACK n’arrive sur station/<slot_id>/ack.
Test automatisé du client MQTT Raspberry :
.\.venv\Scripts\python.exe -m pytest raspberry\tests -q
Test manuel avec la stack Docker, sans ESP32 connecté :
$env:MQTT_ACK_REQUIRED="true"
docker compose up -d --force-recreate raspberry
curl -X POST http://localhost:9000/mqtt/command `
-H "Content-Type: application/json" `
-d '{"slot_id":"slot1","action":"START","session_id":"ack-test"}'
Résultat attendu :
réponse HTTP
503;message indiquant l’absence de confirmation ESP32 ;
aucune charge considérée comme réellement démarrée côté kiosque.
Revenir au mode de développement sans ESP32 :
$env:MQTT_ACK_REQUIRED="false"
docker compose up -d --force-recreate raspberry
Sur une vraie borne, MQTT_ACK_REQUIRED doit rester à true.
Sécurisation Mosquitto sans matériel
Le broker de développement accepte les connexions anonymes pour faciliter les tests locaux. Avant un déploiement terrain, préparer les comptes MQTT :
.\scripts\init-mqtt-auth.ps1
Le script crée mosquitto/config/passwords avec les comptes :
station_backend;raspberry_kiosk;esp32_slot1;esp32_slot2;esp32_slot3;esp32_station_demo_borne_1_slot1;esp32_station_demo_borne_1_slot2;esp32_station_demo_borne_1_slot3.
Avec docker-compose.prod.yml, mosquitto/config/mosquitto.secure.conf est
monté explicitement comme configuration Mosquitto. Le service refuse donc de
démarrer si mosquitto/config/passwords n’a pas été généré. Les règles dans
mosquitto/config/acl doivent garantir qu’un ESP32 ne peut publier que sur
son propre emplacement.
Les comptes esp32_slotX servent aux tests locaux historiques
station/slotX/.... Les comptes esp32_station_demo_borne_1_slotX servent
au routage plus réaliste station/station-demo/borne-1/slotX/....
Variables de production recommandées :
MQTT_USERNAME=station_backend
MQTT_PASSWORD=<mot de passe station_backend>
MQTT_RASPBERRY_USERNAME=raspberry_kiosk
MQTT_RASPBERRY_PASSWORD=<mot de passe raspberry_kiosk>
MQTT_ACK_REQUIRED=True
Le compte backend et le compte Raspberry doivent rester distincts. Le backend ingère la supervision, tandis que le Raspberry orchestre les commandes physiques locales.
Contrôle recommandé avec Mosquitto sécurisé :
docker compose exec mqtt mosquitto_pub -h localhost -u esp32_slot1 -P <mot-de-passe> -t station/slot1/status -m '{"state":"idle"}'
docker compose exec mqtt mosquitto_pub -h localhost -u esp32_slot1 -P <mot-de-passe> -t station/slot2/status -m '{"state":"idle"}'
docker compose exec mqtt mosquitto_pub -h localhost -u esp32_station_demo_borne_1_slot1 -P <mot-de-passe> -t station/station-demo/borne-1/slot1/status -m '{"state":"idle"}'
docker compose exec mqtt mosquitto_pub -h localhost -u esp32_station_demo_borne_1_slot1 -P <mot-de-passe> -t station/station-demo/borne-1/slot2/status -m '{"state":"idle"}'
La première commande de chaque paire doit réussir, la seconde doit être refusée par l’ACL.
Test RFID Raspberry
Sans lecteur physique :
curl -X POST http://localhost:9000/rfid/scan \
-H "Content-Type: application/json" \
-d '{"uid":"04A3BC89F2"}'
Avec ACR122U sur Raspberry :
sudo apt install pcscd pcsc-tools python3-dev swig
sudo systemctl enable --now pcscd
pcsc_scan
Variables à définir sur le Raspberry :
export RFID_READER_BACKEND=acr122u
export BACKEND_API_BASE_URL=http://<ip-pc>:8000/api
export MQTT_HOST=<ip-pc>
export TERMINAL_API_SECRET=<secret-du-.env>
Mode dégradé
Forcer une synchronisation du cache :
curl -X POST http://localhost:9000/offline/sync
Lire l’état local :
curl http://localhost:9000/offline/status
Rejouer les événements offline :
curl -X POST http://localhost:9000/offline/events/sync
Le mode dégradé n’est pas un mode autonome complet. Il sert uniquement à réutiliser des droits déjà synchronisés et encore valides si le backend devient temporairement indisponible.
Contrôles de déploiement
Avant une bascule vers un environnement exposé, exécuter les contrôles Django avec les variables de production chargées :
docker compose exec backend python manage.py check --deploy
Le contrôle doit rester sans erreur api.E. Les avertissements api.W sont
à traiter avant ouverture du service hors réseau de développement : activer HSTS
après validation HTTPS, limiter ALLOWED_HOSTS, configurer les identifiants
MQTT, chiffrer MQTT si le trafic sort du réseau local de confiance et remplacer
le mot de passe Grafana de démonstration.
Email académique avec Mailpit
Le projet utilise Mailpit pour capturer les emails en local et en démonstration. Cela permet de montrer des notifications réelles côté Django sans dépendre d’un service SMTP payant.
Ouvrir l’interface Mailpit :
http://localhost:8025
Configuration locale attendue :
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
EMAIL_HOST=mailpit
EMAIL_PORT=1025
DEFAULT_FROM_EMAIL=Station <no-reply@station.local>
Les emails utiles au projet académique sont envoyés vers Mailpit :
QR code de réservation envoyé automatiquement à l’utilisateur lorsque le mode d’accès choisi est
qr;notification des techniciens assignés lorsqu’une alerte terrain active dépasse le délai de persistance configuré ;
futurs emails de vérification de compte ou de réinitialisation de mot de passe si ces parcours sont activés.
Mailpit n’est pas un fournisseur d’envoi de production : il capture les messages pour les tests et la soutenance.
Le scénario QR email est activé par RESERVATION_QR_EMAIL_ENABLED=True. Le
message contient le code de réservation, la station, la borne, l’emplacement, le
créneau et une version HTML du QR code. L’envoi réussi est journalisé dans
JournalSecurite avec l’action qr_access_email_sent.
Politique d’alertes
Le backend distingue les alertes courtes des incidents qui persistent. Une alerte terrain suit le cycle suivant :
nouvelle: premier signal reçu, aucune notification immédiate ;active: même anomalie observée à nouveau ;prise_en_charge: un technicien affecté à la station a accepté l’intervention ;resolue: retour à une télémétrie normale.
La prise en charge et la résolution manuelle sont disponibles dans le tableau de
bord technicien. Le backend enregistre le technicien, les dates
acknowledged_at / resolved_at et la note de résolution. Un technicien ne
peut agir que sur les alertes rattachées à ses stations ; l’administrateur garde
une portée globale pour arbitrer les cas transverses.
Les notifications email sont envoyées uniquement aux techniciens actifs affectés
à la station concernée, et seulement pour les types listés dans
ALERT_TECHNICIAN_EMAIL_TYPES. Les délais recommandés sont ceux du fichier
.env.example :
60secondes pour une alerte critique ;120secondes pour une alerte haute ou moyenne ;1800secondes de délai de relance si l’alerte reste active.
Traiter les notifications en attente manuellement :
docker compose exec backend python manage.py send_alert_notifications
Contrôler les sessions actives sans télémétrie manuellement :
docker compose exec backend python manage.py check_session_telemetry_health
Ce contrôle crée une alerte session_without_telemetry si une session ouverte
ne reçoit plus de mesure depuis SESSION_TELEMETRY_STALE_MINUTES minutes,
2 par défaut. L’alerte se résout automatiquement quand la télémétrie revient
ou quand la session est clôturée.
En fonctionnement normal, ces deux commandes sont exécutées automatiquement par
celery-beat toutes les 60 secondes par défaut, via
CELERY_BEAT_ALERT_NOTIFICATIONS_SECONDS et
CELERY_BEAT_SESSION_TELEMETRY_HEALTH_SECONDS.
L’alerte firmware_outdated est levée si une version attendue est définie
pour le Device ou via EXPECTED_ESP32_FIRMWARE_VERSION et si un ESP32
remonte une version différente. Elle se résout lorsque la version remontée
redevient conforme. Elle ne doit pas déclencher d’email technicien par défaut :
seul l’administrateur ou le responsable technique décide de planifier et
appliquer une mise à jour firmware.
Politique firmware et OTA
La politique firmware est volontairement séparée de la maintenance terrain :
l’ESP32 remonte automatiquement
firmware_version,hardware_macetip_addressdans les messagesstatus,ackettelemetry;le backend compare la version remontée avec la version attendue de l’équipement ou avec
EXPECTED_ESP32_FIRMWARE_VERSION;l’API admin
/api/admin/devices/expose l’inventaire, le statut firmware et les champs OTA ;l’API admin
/api/admin/devices/<id>/schedule-ota/enregistre la version cible et l’URL du binaire validé par l’administrateur ;le statut
update_scheduledsignifie qu’une mise à jour est approuvée, mais l’application réelle du firmware dépend encore du canal opérationnel retenu sur le matériel final.
En projet académique, la mise à jour peut rester manuelle via Thonny tant que le matériel n’est pas disponible. Le point important pour la production est que la décision soit tracée côté admin, que la non-conformité soit visible en supervision, et que l’alerte se ferme automatiquement lorsque l’ESP32 remonte la version attendue.
SMS de production
En développement, SMS_BACKEND=console suffit : aucun SMS réel n’est envoyé
et le backend journalise seulement une tentative d’envoi. En production, choisir
un fournisseur explicite :
Fournisseur |
Variables requises |
|---|---|
|
|
|
|
Le backend ovh n’est pas activé dans le profil durci : l’API REST OVH impose
une signature historique en SHA-1, ce qui crée une alerte CodeQL bloquante. Pour
ce projet, utiliser console en démonstration locale et brevo ou
twilio pour une démonstration avec envoi réel.
Réglages recommandés avant ouverture du service :
SMS_REQUIRE_VERIFIED_PHONE=Truepour bloquer l’envoi vers un téléphone non vérifié ;SMS_RESEND_COOLDOWN_SECONDS=60ou plus pour limiter le bouton de renvoi ;SMS_ACCESS_CODE_ALPHABET=0123456789ABCDpour rester compatible avec un clavier matriciel 4x4 ;SMS_ACCESS_CODE_LENGTH=8pour éviter les codes trop courts ;SMS_ACCESS_CODE_MAX_ATTEMPTS=5pour verrouiller un code après trop d’erreurs ;SMS_HTTP_TIMEOUT_SECONDS=10pour éviter de bloquer trop longtemps une requête utilisateur si le fournisseur ne répond pas ;SMS_SENDER_NAMElimité à un libellé court compatible avec le fournisseur.
Le profil utilisateur expose telephone_verifie. Un changement de numéro
depuis le profil remet automatiquement cette valeur à False. L’admin peut la
remettre à True après vérification opérationnelle du numéro.
Tests automatisés
Tests backend ciblés télémétrie :
docker compose run --rm backend-test python -m pytest api/tests/test_mqtt_ingestion.py api/tests/test_mqtt_simulator.py
Tests terminal et accès :
docker compose run --rm backend-test python -m pytest api/tests/test_terminal_auth.py
Vérification syntaxe ESP32 :
python -m py_compile esp32\emplacement1\main.py esp32\wokwi\main.py
Documentation
Construire la documentation :
.\docs\make.bat html
Si sphinx-build n’est pas dans le PATH :
$env:DEBUG="True"
$env:SECRET_KEY="docs-local-secret-key-not-for-production"
.\.venv\Scripts\python.exe -m sphinx -b html docs\source docs\build\html
Contrôles avant démonstration
Avant une présentation, vérifier :
une réservation de test existe ;
le backend est joignable depuis le Raspberry ;
le broker MQTT est joignable depuis le Raspberry et les ESP32 ;
mqtt-bridge,celeryetcelery-beatsont lancés pour alimenter Grafana et maintenir les alertes métier ;le dashboard Grafana affiche des points récents ;
le lecteur RFID lit bien un UID ;
l’ESP32 répond aux commandes avec un ACK.