Valider un message IoT¶
Objectif : comprendre le contrat qu'un message réel doit respecter avant
d'être accepté.
Ce que vous allez apprendre : parse_message. Un message arrivant d'un vrai
capteur (topic + payload JSON) doit respecter le contrat Forge IoT. parse_message
le valide et renvoie une Measurement, ou lève une ContractError portant un
code d'erreur. C'est exactement la validation que le subscriber applique en
production : ici on l'exerce à la main, sans broker.
Premier palier du niveau avancé de la progression IoT : la bascule vers le
temps réel. Après le niveau intermédiaire.
Ce que ce starter montre¶
- le contrat de message : topic
forge/{site}/{device_id}/telemetry+ payload ; - la validation
parse_message(topic, payload)→Measurement; - la gestion d'une
ContractErroravec son code (ex.TOPIC_PATTERN,
PAYLOAD_FIELD_MISSING) ; - un formulaire pour tester un message valide et un message fautif.
Aucun broker, aucune base : on apprend les règles que les vrais messages doivent
respecter.
Classes Forge utilisées¶
| Classe / fonction | Rôle dans ce starter | Référence |
|---|---|---|
forge_mvc_iot.mqtt.contract.parse_message |
Valider topic + payload → Measurement. |
Forge IoT : contrat |
ContractError |
Erreur de contrat, porte un code exploitable. |
Forge IoT : contrat |
Tester¶
Ouvrez https://localhost:8000/iot-contract. Le formulaire est pré-rempli avec un
message valide → Valider affiche la Measurement. Modifiez le topic (par
exemple retirez /telemetry) ou un champ du payload → la page affiche le code
et le message de l'erreur de contrat.
Le contrôleur¶
Créez le fichier ci-dessous, complet et copiable tel quel.
# mvc/controllers/iot_contract_controller.py
from core.http.request import Request
from core.http.response import Response
from core.mvc.controller.base_controller import BaseController
from forge_mvc_iot.mqtt.contract import ContractError, parse_message
_DEFAULT_TOPIC = "forge/atelier/capteur-1/telemetry"
_DEFAULT_PAYLOAD = '{"kind": "temperature", "value": 21.5, "unit": "C", "timestamp": "2026-06-01T10:00:00Z"}'
class IotContractController(BaseController):
"""Starter pédagogique : valider un message contre le contrat IoT."""
@staticmethod
def index(request: Request) -> Response:
return BaseController.render(
"iot_contract/index.html",
context={
"csrf_token": BaseController.csrf_token(request),
"topic": _DEFAULT_TOPIC,
"payload": _DEFAULT_PAYLOAD,
},
request=request,
)
@staticmethod
def validate(request: Request) -> Response:
topic = (request.form("topic") or "").strip()
payload = (request.form("payload") or "").strip()
context = {
"csrf_token": BaseController.csrf_token(request),
"topic": topic,
"payload": payload,
}
try:
measurement = parse_message(topic, payload)
except ContractError as exc:
context["error_code"] = exc.code
context["error"] = exc.message
return BaseController.render(
"iot_contract/index.html", context=context, request=request
)
context["measurement"] = {
"site": measurement.site,
"device_id": measurement.device_id,
"kind": measurement.kind,
"value": measurement.value,
"unit": measurement.unit,
"timestamp": measurement.timestamp,
}
return BaseController.render(
"iot_contract/index.html", context=context, request=request
)
Comprendre ce code¶
parse_message(topic, payload)applique tout le contrat : forme du topic,
JSON du payload, champs obligatoires, types, formats (timestamp ISO 8601 UTC…).- Une violation lève une
ContractErroravec uncode(ex.TOPIC_PATTERN,
PAYLOAD_PARSE,PAYLOAD_FIELD_MISSING), exploitable pour logs et tests. - En production, ce même appel rejette les messages mal formés avant tout
stockage. Vous venez d'exercer la porte d'entrée du système.
La vue¶
Créez le gabarit ci-dessous : formulaire de saisie, affichage de la
Measurement valide ou du code d'erreur.
<!-- mvc/views/iot_contract/index.html -->
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Valider un message IoT — Forge</title>
</head>
<body>
<h1>Valider un message IoT</h1>
{% if error %}
<p data-level="error"><strong>[{{ error_code }}]</strong> {{ error }}</p>
{% endif %}
{% if measurement %}
<p data-level="success">Message valide :</p>
<ul>
<li>Site : {{ measurement.site }}</li>
<li>Capteur : {{ measurement.device_id }}</li>
<li>Type : {{ measurement.kind }}</li>
<li>Valeur : {{ measurement.value }} {{ measurement.unit }}</li>
<li>Horodatage : {{ measurement.timestamp }}</li>
</ul>
{% endif %}
<form method="post" action="/iot-contract">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
<label>Topic <input type="text" name="topic" value="{{ topic }}" size="50"></label>
<label>Payload (JSON)
<textarea name="payload" rows="4" cols="60">{{ payload }}</textarea>
</label>
<button type="submit">Valider</button>
</form>
</body>
</html>
La route¶
Déclarez les deux routes (GET pour la page, POST pour la validation) dans
mvc/routes.py, à l'intérieur du groupe public.
# mvc/routes.py
from mvc.controllers.iot_contract_controller import IotContractController
with router.group("", public=True) as public:
public.add("GET", "/iot-contract", IotContractController.index, name="iot_contract_index")
public.add("POST", "/iot-contract", IotContractController.validate, name="iot_contract_validate")
À retenir¶
- Un message réel doit respecter le contrat : topic + payload conformes.
parse_messagevalide et renvoie uneMeasurement, sinon uneContractError.ContractError.codeidentifie précisément la règle violée.
Après ce starter¶
Vous connaissez le contrat. La suite : recevoir ces messages d'un vrai broker.