From cad1c06422bc761a61e8c25c7698e64d64579054 Mon Sep 17 00:00:00 2001 From: Eric FELIXINE Date: Mon, 4 May 2026 22:25:23 -0400 Subject: [PATCH] Simulator: Add source+mqttTopic traceability for Fiware brokers --- simulator.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/simulator.py b/simulator.py index cec951eb..3ac9c0db 100644 --- a/simulator.py +++ b/simulator.py @@ -193,7 +193,7 @@ STELLIO_INLINE_CONTEXT = [ "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld", ] -def _ngsi_payload(sid: str, sensor: dict, context: list | dict = ORION_CONTEXT) -> dict: +def _ngsi_payload(sid: str, sensor: dict, context: list | dict = ORION_CONTEXT, source: str = "simulator", topic: str = "") -> dict: """Construit un payload NGSI-LD avec Smart Data Models officiels.""" stype = sensor["type"] model_type = SMART_MODEL_MAPPING.get(stype, "Device") @@ -210,6 +210,11 @@ def _ngsi_payload(sid: str, sensor: dict, context: list | dict = ORION_CONTEXT) "coordinates": [sensor["lon"], sensor["lat"]]}}, "name": {"type": "Property", "value": sensor["name"]}, "batteryLevel": {"type": "Property", "value": random.randint(60, 100)}, + # NOUVEAU: Traçabilité MQTT (Conforme NGSI-LD) + # "source" est un champ standard NGSI-LD (ETSI) + # "mqttTopic" est une propriété personnalisée (étendue autorisée) + "source": {"type": "Property", "value": source}, + "mqttTopic": {"type": "Property", "value": topic}, } # Attributs spécifiques par type de modèle @@ -264,7 +269,7 @@ def _ngsi_payload(sid: str, sensor: dict, context: list | dict = ORION_CONTEXT) return payload -def _frost_payload(sid: str, sensor: dict) -> dict: +def _frost_payload(sid: str, sensor: dict, source: str = "simulator", topic: str = "") -> dict: """Construit un payload SensorThings pour FROST-Server.""" stype = sensor["type"] ranges = SENSOR_RANGES.get(stype, {}) @@ -300,7 +305,12 @@ def _frost_payload(sid: str, sensor: dict) -> dict: thing_payload = { "name": f"Thing_{sid}", "description": f"Smart City {stype} sensor in Martinique", - "properties": {"sensorType": stype, "region": "Martinique"}, + "properties": { + "sensorType": stype, + "region": "Martinique", + "source": source, # Traçabilité + "mqttTopic": topic # Traçabilité + }, } return thing_payload, datastreams @@ -456,7 +466,10 @@ STELLIO_TENANT = os.environ.get("STELLIO_TENANT", "urn:ngsi-ld:tenant:default") def publish_stellio(sid: str, sensor: dict) -> bool: """Publie sur Stellio via Traefik (gère le 409).""" - entity = _ngsi_payload(sid, sensor, context=STELLIO_INLINE_CONTEXT) + # Topic MQTT correspondant (pour traçabilité) + stype = sensor["type"] + topic = f"city/sensors/{stype}/{sid}" + entity = _ngsi_payload(sid, sensor, context=STELLIO_INLINE_CONTEXT, source="simulator", topic=topic) # Stellio a besoin du @context pour résoudre les vocabulaires NGSI-LD # (uri.etsi.org résolu depuis le JAR embarqué) url = f"{STELLIO_URL}/ngsi-ld/v1/entities" @@ -496,7 +509,10 @@ def publish_stellio(sid: str, sensor: dict) -> bool: def publish_orion(sid: str, sensor: dict) -> bool: """Publie sur Orion-LD (POST create, PATCH update).""" import socket - entity = _ngsi_payload(sid, sensor) + # Topic MQTT correspondant (pour traçabilité) + stype = sensor["type"] + topic = f"city/sensors/{stype}/{sid}" + entity = _ngsi_payload(sid, sensor, source="simulator", topic=topic) if not hasattr(publish_orion, "orion_ip"): try: publish_orion.orion_ip = socket.gethostbyname("fiware-gis-quickstart-orion-1") @@ -611,7 +627,10 @@ def publish_frost(sid: str, sensor: dict, field: str, value: float) -> bool: return False # Premier appel pour ce capteur : créer Thing + tous les Datastreams - thing_payload, datastreams = _frost_payload(sid, sensor) + # Topic MQTT pour traçabilité + stype = sensor["type"] + topic = f"city/sensors/{stype}/{sid}" + thing_payload, datastreams = _frost_payload(sid, sensor, source="simulator", topic=topic) print(f" 📊 FROST: POST Thing {sid}...") tid = _http_post(f"{FROST_URL}/Things", thing_payload, FROST_HEADERS) if not tid: