From aa42a213bbcc9fd22461b6017039b1f5f1e0fa79 Mon Sep 17 00:00:00 2001 From: Eric FELIXINE Date: Mon, 4 May 2026 10:35:18 -0400 Subject: [PATCH] fix: Stellio/Orion - use NGSI-LD core context only (remove raw.githubusercontent.com) - STELLIO_INLINE_CONTEXT: replaced long inline dict with uri.etsi.org core URL - ORION_CONTEXT: removed raw.githubusercontent.com URLs (not accessible from containers) - publish_stellio(): added NGSILD-Tenant header (urn:ngsi-ld:tenant:default) - publish_stellio(): keep @context in payload (Stellio needs it to resolve vocabulary) - Added STELLIO_URL and STELLIO_TENANT env vars for host-based execution via Traefik Fixes: Stellio 503 JsonLdError, Orion-LD context resolution failures. Tested: Stellio 201, Orion-LD 207. --- simulator.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/simulator.py b/simulator.py index 08143a2d..f1d93717 100644 --- a/simulator.py +++ b/simulator.py @@ -131,14 +131,10 @@ for stype, locs in SENSOR_LOCATIONS.items(): # ============================================================================= # Contextes NGSI-LD : core + Smart Data Models # https://smartdatamodels.org pour les @context officiels -STELLIO_CONTEXT_URL = "https://gitea.digitribe.fr/eric/smart-city-digital-twin-martinique/raw/branch/master/contexts" +# Contexte NGSI-LD pur pour Orion-LD (vocabulaires standards uniquement) +# Orion-LD ne peut pas résoudre raw.githubusercontent.com — utiliser uri.etsi.org uniquement ORION_CONTEXT = [ - f"{STELLIO_CONTEXT_URL}/ngsi-ld-core.jsonld", - "https://raw.githubusercontent.com/smart-data-models/dataModel.Environment/master/context.jsonld", - "https://raw.githubusercontent.com/smart-data-models/dataModel.Transportation/master/context.jsonld", - "https://raw.githubusercontent.com/smart-data-models/dataModel.Parking/master/context.jsonld", - "https://raw.githubusercontent.com/smart-data-models/dataModel.Weather/master/context.jsonld", - "https://raw.githubusercontent.com/smart-data-models/dataModel.Device/master/context.jsonld", + "https://uri.etsi.org/ngsi-ld/v1/ngsi-ld-core-context.jsonld", ] # Mapping sensor type → Smart Data Model type NGSI-LD @@ -155,7 +151,16 @@ FROST_HEADERS = {"Accept": "application/json", "Content-Type": "application/json # Cache FROST : éviter de recréer Thing/Datastream _frost_cache: dict[str, tuple[str, str]] = {} # (sid, field) -> (thing_id, ds_id) -def _ngsi_payload(sid: str, sensor: dict) -> dict: +# Contexte NGSI-LD pur pour Stellio et Orion-LD (vocabulaires standards uniquement) +# Stellio et Orion-LD embarquent le contexte core NGSI-LD : https://uri.etsi.org/ngsi-ld/ +# On n'utilise PAS les vocabulaires smartdatamodels.org distants (inaccessibles depuis les containers) +# Les types d'entité Smart Data Models (AirQualityObserved, etc.) sont reconnus par leur nom +# Les propriétés spécifiques sont stockées telles quelles (vocabulaire libre) +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: """Construit un payload NGSI-LD avec Smart Data Models officiels.""" stype = sensor["type"] model_type = SMART_MODEL_MAPPING.get(stype, "Device") @@ -163,7 +168,7 @@ def _ngsi_payload(sid: str, sensor: dict) -> dict: # Attributs communs à tous les modèles payload = { - "@context": ORION_CONTEXT, + "@context": context, "id": f"urn:ngsi-ld:{model_type}:{sid}", "type": model_type, "dateObserved": {"type": "Property", "value": now}, @@ -413,19 +418,24 @@ try: except: pass ORION_URL = f"http://{ORION_IP or ORION_HOST}:1026" if ORION_IP else "http://fiware-gis-quickstart-orion-1:1026" -STELLIO_URL = "http://stellio-api-gateway:8080" +STELLIO_URL = os.environ.get("STELLIO_URL", "http://stellio-api-gateway:8080") # Configuration OpenRemote (URLs dynamiques) OR_URL = os.environ.get("OR_URL", "http://openremote-manager-1:8080") # Hostname Docker interne OR_REALM = os.environ.get("OR_REALM", "smartcity") # Default: smartcity OR_TOKEN_URL = os.environ.get("OR_TOKEN_URL", f"http://openremote-keycloak-1:8080/auth/realms/{OR_TOKEN_REALM}/protocol/openid-connect/token") OR_TOKEN_TTL = int(os.environ.get("OR_TOKEN_TTL", "3600")) # Refresh token every hour +STELLIO_TENANT = os.environ.get("STELLIO_TENANT", "urn:ngsi-ld:tenant:default") + def publish_stellio(sid: str, sensor: dict) -> bool: - """Publie sur Stellio (gère le 409).""" - entity = _ngsi_payload(sid, sensor) - url = f"{STELLIO_URL}/ngsi-ld/v1/entities" # Sans options=upsert + """Publie sur Stellio via Traefik (gère le 409).""" + entity = _ngsi_payload(sid, sensor, context=STELLIO_INLINE_CONTEXT) + # 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" headers = { - "Content-Type": "application/ld+json", - "Accept": "application/ld+json", + "Content-Type": "application/ld+json", + "Accept": "application/ld+json", + "NGSILD-Tenant": STELLIO_TENANT, } try: body = json.dumps(entity).encode()