Contrat Raspberry Pi et ESP32
Cette page fixe le contrat d’échange entre le kiosque Raspberry Pi, le broker Mosquitto local et les ESP32 installés dans les emplacements de charge. Elle sert de référence commune pour le firmware MicroPython, le kiosque tactile, les tests simulateur et la supervision.
Principes
Le Raspberry reste l’orchestrateur local de la borne : il valide l’accès avec le backend ou le cache dégradé, puis publie les commandes physiques.
Chaque ESP32 contrôle un seul emplacement : serrure, relais, DHT22, INA219, état câble et état boîtier.
Le backend ingère la télémétrie et les ACK via le bridge MQTT, mais ne doit pas être nécessaire pour couper localement une charge dangereuse.
En production,
MQTT_ACK_REQUIREDdoit être activé sur le Raspberry.Le contrat est aussi présent en code dans
raspberry/app/mqtt_contract.py. Les tests utilisent ce module pour éviter qu’un changement de firmware, de kiosque ou de simulateur casse le format des messages sans être détecté.
Topics
Le format production est structuré par station, borne et emplacement :
station/<mqtt_prefix>/<slot_id>/cmd
station/<mqtt_prefix>/<slot_id>/ack
station/<mqtt_prefix>/<slot_id>/status
station/<mqtt_prefix>/<slot_id>/telemetry
station/<mqtt_prefix>/<slot_id>/alert
Exemple avec mqtt_prefix=station-demo/borne-1 et slot_id=slot1 :
station/station-demo/borne-1/slot1/cmd
station/station-demo/borne-1/slot1/telemetry
Le format historique station/<slot_id>/... reste accepté pour les tests
locaux, mais le format structuré doit être privilégié pour la production.
Commande START
Le Raspberry publie START après validation de l’accès et création de la
session backend ou locale.
{
"command_id": "cmd-start-001",
"action": "START",
"issued_at": "2026-05-17T14:00:00+00:00",
"session_id": "d4b8b4d1-8b67-45e7-aef6-0123456789ab",
"station": "Station Demo",
"borne": "Borne 1",
"emplacement": "1",
"source": "raspberry"
}
Champs obligatoires : command_id, action, issued_at et
session_id. Les champs station, borne, emplacement et
source sont recommandés pour les logs et le diagnostic terrain.
Comportement attendu côté ESP32 :
refuser la commande si une charge est déjà active sur l’emplacement ;
vérifier que la serrure et le relais peuvent être commandés ;
répondre par ACK avec le même
command_id;passer en
reservedouwaiting_plugsi l’accès est accepté ;passer en
charginguniquement quand le câble est présent et le boîtier fermé.
Commande STOP
Le Raspberry publie STOP lorsqu’un utilisateur arrête la charge, lorsqu’une
session est clôturée ou lorsqu’une anomalie impose l’arrêt.
{
"command_id": "cmd-stop-001",
"action": "STOP",
"issued_at": "2026-05-17T14:30:00+00:00",
"session_id": "d4b8b4d1-8b67-45e7-aef6-0123456789ab",
"station": "Station Demo",
"borne": "Borne 1",
"emplacement": "1",
"source": "raspberry"
}
Champs obligatoires : command_id, action, issued_at et
session_id. Le session_id doit être celui de la session active sur
l’emplacement.
Comportement attendu côté ESP32 :
couper le relais de charge ;
remettre l’emplacement dans un état sûr ;
publier un ACK
stoppedouidle;continuer à publier
statuspour confirmer l’état physique.
ACK
Chaque commande doit produire un ACK sur .../<slot_id>/ack.
{
"command_id": "cmd-start-001",
"action": "START",
"status": "accepted",
"session_id": "d4b8b4d1-8b67-45e7-aef6-0123456789ab",
"message": "Emplacement réservé.",
"source": "esp32",
"timestamp": "2026-05-17T14:00:01Z"
}
Champs obligatoires : command_id, action et status. Le Raspberry
compare le command_id et l’action avec la commande publiée. Un ACK pour une
autre commande est ignoré.
Statuts acceptés :
Action |
Statuts confirmants |
Statuts de refus |
|---|---|---|
|
|
|
|
|
|
Le Raspberry ignore un ACK dont le command_id ou l’action ne correspond pas
à la commande en cours.
Status
Le status est un heartbeat métier léger.
{
"state": "charging",
"session_id": "d4b8b4d1-8b67-45e7-aef6-0123456789ab",
"timestamp": "2026-05-17T14:01:00Z"
}
Champ obligatoire : state. Le session_id devient obligatoire dès que
state vaut charging ou charging_started.
États canoniques côté Raspberry :
reserved -> access_validated -> waiting_plug -> charging -> stopping -> stopped
\ /
---------- error --------
waiting_cable est encore accepté en lecture comme alias historique de
waiting_plug.
Le backend conserve le même cycle dans charging_session.charge_state. Les
ACK et statuts MQTT le font évoluer, puis l’endpoint de clôture de session passe
l’état à stopped en même temps que end_time. Cette duplication contrôlée
est volontaire : le Raspberry protège l’action physique locale, le backend
protège la cohérence métier et la supervision.
Télémétrie
La télémétrie est publiée régulièrement sur .../<slot_id>/telemetry.
{
"slot_id": "slot1",
"session_id": "d4b8b4d1-8b67-45e7-aef6-0123456789ab",
"state": "charging",
"client_id": "esp32-slot1",
"firmware_version": "1.0.0",
"hardware_mac": "AA:BB:CC:DD:EE:FF",
"ip_address": "192.168.1.42",
"current": 1.35,
"voltage": 41.7,
"power": 56.3,
"energy_kwh": 0.12,
"temperature": 31.5,
"humidity": 44.0,
"door": true,
"cable": true,
"lock": true,
"intrusion": false,
"timestamp": "2026-05-17T14:02:00Z"
}
Champs minimaux : slot_id et state. Pendant une charge active,
session_id doit être présent afin de rattacher les mesures à la bonne
session. Les champs électriques, serrure, câble et porte peuvent être absents
tant que le matériel n’est pas câblé, mais ils deviennent obligatoires dans la
version de production complète.
Le backend crée une télémétrie de session uniquement si session_id correspond
à une session active de l’emplacement. Les données capteurs restent stockées
pour la supervision même si la session est absente ou invalide.
Les champs firmware_version, hardware_mac et ip_address peuvent être
présents dans les messages status, ack et telemetry. Le backend les
utilise pour mettre à jour l’inventaire du Device associé au slot_id.
slot_id reste l’identifiant logique de l’emplacement ; hardware_mac est
l’identité physique de la carte ESP32.
Incidents attendus
Incident |
Détection ESP32 |
Action locale attendue |
Message backend |
|---|---|---|---|
Câble retiré |
|
couper le relais, publier |
alerte |
Boîtier ouvert |
|
couper le relais, publier |
alerte |
Intrusion explicite |
|
couper le relais, publier |
alerte |
Serrure déverrouillée en charge |
|
couper le relais, publier |
alerte |
Surchauffe |
|
couper le relais si la charge est active |
alerte |
Surintensité |
|
couper le relais si la charge est active |
alerte |
État critique |
|
passer en état sûr |
alerte |
Tests de contrat
Les tests automatisés verrouillent déjà les points principaux :
raspberry/tests/test_mqtt_client.pyvérifie les topics, payloads START/STOP et ACK ;raspberry/tests/test_mqtt_contract.pyvérifie les champs obligatoires, les statuts confirmants et les alertes attendues ;raspberry/tests/test_charge_state.pyvérifie la machine d’état locale ;backend/api/tests/test_mqtt_ingestion.pyvérifie l’ingestion des status, ACK, télémétries et alertes ;backend/api/tests/test_end_to_end_charge_flow.pyvérifie le scénario QR mobile -> kiosque -> START -> télémétrie de l’emplacement réservé -> STOP.