From 2377bc07fd3cd6339cb51bfc47e829df71ba5d8b Mon Sep 17 00:00:00 2001 From: Eric FELIXINE Date: Tue, 19 May 2026 16:22:26 -0400 Subject: [PATCH] Session 2026-05-19: OpenRemote map display investigation, cleanup, fresh install - Investigated map display issues (agentLink, GeoJSON coords, realm config) - Cleaned up all dashboards and containers - Fresh Manager installation (PostgreSQL in recovery) - Updated TODO.md with current status - GeoJSON proxy: fixed coordinate order (lon/lat) - Session resume saved --- TODO.md | 11 ++- geojson-proxy/geojson_proxy.py | 122 ++++++++++++++++++--------------- simulator.py | 3 +- 3 files changed, 78 insertions(+), 58 deletions(-) diff --git a/TODO.md b/TODO.md index db9b9230..fc4864d6 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ # Smart City Digital Twin — TODO List -> Dernière mise à jour : 2026-05-17 20:00 +> Dernière mise à jour : 2026-05-19 22:15 ## ✅ Complété (5/13) | ID | Tâche | @@ -14,7 +14,7 @@ ## 🔴 Bloqué (5/13) | ID | Tâche | Raison | |----|-------|--------| -| p1-or | OpenRemote agents MQTT | API 403, UI headless ne rend pas | +| p1-or | OpenRemote agents MQTT + map display | PostgreSQL en recovery après reinstallation | | p4-ditto | Ditto.digitribe.fr | MongoDB localhost hardcodé | | p1-prometheus | Prometheus + Grafana | Réseau interne inaccessible | | p3-kepler | KeplerGL | Image Docker incomplète, build npm trop long | @@ -37,6 +37,13 @@ | metabase | data-visualization | ✅ | | contexus | iot | ✅ | +## 📝 Notes 2026-05-19 +- **OpenRemote map display** : Investigation approfondie — points ne s'affichent pas malgré toutes les conditions remplies (location, agentLink, showOnDashboard, bon realm) +- **Décision** : Repartir de zéro avec installation fraîche du Manager +- **PostgreSQL** : En recovery (fsync du data directory) — peut prendre 10+ minutes +- **Prochaines étapes** : Attendre PostgreSQL → Vérifier Manager/Keycloak → Lancer simulateur → Créer dashboard via UI → Vérifier affichage +- **Custom project** : Répertoire `/home/eric/openremote/custom-project/` cloné — prêt pour développement custom + ## Credentials - **GeoServer**: admin / Digitribe972 - **PostGIS dédié**: smartcity / SmartCity972 (port 5433) diff --git a/geojson-proxy/geojson_proxy.py b/geojson-proxy/geojson_proxy.py index 5275f7db..b82d60c0 100644 --- a/geojson-proxy/geojson_proxy.py +++ b/geojson-proxy/geojson_proxy.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """GeoJSON proxy service for OpenRemote assets map display. -Fetches IoT sensor assets from OpenRemote REST API and serves them as GeoJSON. +Fetches all assets with location from OpenRemote REST API and serves them as GeoJSON. """ import json @@ -12,31 +12,10 @@ from http.server import HTTPServer, BaseHTTPRequestHandler OR_URL = os.environ.get("OR_URL", "http://openremote_manager_1:8080") OR_ADMIN_USER = os.environ.get("OR_ADMIN_USER", "admin") -OR_ADMIN_PASS = os.environ.get("OR_ADMIN_PASS", "") +OR_ADMIN_PASS = os.environ.get("OR_ADMIN_PASS", "Digitribe972") OR_REALM = os.environ.get("OR_REALM", "master") OR_CLIENT_SECRET = os.environ.get("OR_CLIENT_SECRET", "0oQjzTfiEELYmj5jFwT4iIuWUDtQDvVa") -# All known IOTSensor asset IDs (from simulator ASSET_MAP + DB) -ASSET_IDS = [ - "429858caca3341f56fbf65", "301218322f5aaca9d6d168", "bd35fe2a90133118b9b004", - "da59ec9301c4efd3fd55c4", "834f4b7b9df848f5c5c2d8", - "0f922351a9894bc0144c94", "4f83219bbee703b3e0a255", "381cc31ab83dd66ed4be37", - "808b73c22ecd19589a33be", "03c18679226329183b44b6", - "0ee6689f5c0499643d48eb", "8fb6b2d0601d98b47a4172", "0c00bda9e5075d12d59694", - "ae981dc9d155d1313b9acf", "96020cc5aef95c5fda7bb4", - "0be31930e45d2eb5c12ccd", "1802e76e3432d5eda1deb7", "08edb6518750d50644afe3", - "93d09bfac36d2ed95fc858", "7942726d84d2bd29de1e5d", - "9942f881ab6df375d8d9fa", "5400fdf5c51a4fe4f5a89c", "1a3bf32aa5208892e68965", - "d3725f922f96085f2df3f7", "13be192a8c23dd8fdceada", - "1f4302946b1a4a1ded23f6", "35e6ef027ed9a157ad8780", "526538589aa981bdc77ce9", - "d4a6ac7f34d64e581937c0", "40bbe989be2ae5b2a98b30", - # Additional assets from DB - "8b8f50aa8d13d65b2bafb7", "d642131a593c1cddcca3df", - "f4afeba492308772a9a1a4", "988232e4b779fd2cde2157", - "b75157bd68fde1577eda4d", "f388f67ec11e7860c352a3", - "ba30baf1fb3c69bdcc1b44", -] - _token_cache = {"token": "", "expires": 0} @@ -66,47 +45,80 @@ def get_token(): def fetch_assets(): - """Fetch IoT sensor assets from OpenRemote REST API.""" + """Fetch all assets with location from OpenRemote REST API.""" token = get_token() features = [] - for asset_id in ASSET_IDS: + # Query all assets with location attribute + try: + # Use the asset query API to get all assets with location + query = json.dumps({ + "attributes": { + "location": { + "value": {"$exists": True} + } + } + }).encode() + req = urllib.request.Request( + f"{OR_URL}/api/{OR_REALM}/asset/query", + data=query, + headers={ + "Authorization": f"Bearer {token}", + "Accept": "application/json", + "Content-Type": "application/json" + }, + method="POST" + ) + with urllib.request.urlopen(req, timeout=30) as r: + assets = json.loads(r.read().decode()) + if not isinstance(assets, list): + assets = [assets] + except Exception as e: + # Fallback: try to get all assets and filter try: req = urllib.request.Request( - f"{OR_URL}/api/{OR_REALM}/asset/{asset_id}", + f"{OR_URL}/api/{OR_REALM}/asset?limit=100", headers={"Authorization": f"Bearer {token}", "Accept": "application/json"} ) - with urllib.request.urlopen(req, timeout=5) as r: - asset = json.loads(r.read().decode()) - attrs = asset.get("attributes", {}) - location = attrs.get("location", {}) - value = location.get("value") if isinstance(location, dict) else None - coords = value.get("coordinates") if isinstance(value, dict) else None - if not coords or len(coords) < 2: - continue + with urllib.request.urlopen(req, timeout=30) as r: + assets = json.loads(r.read().decode()) + if not isinstance(assets, list): + assets = [assets] + except Exception as e2: + return {"type": "FeatureCollection", "features": [], "error": str(e2)} - props = { - "id": asset.get("id"), - "name": asset.get("name", ""), - "type": asset.get("type", ""), - "realm": asset.get("realm", ""), - } - # Add sensorType for color mapping - sensor_type = attrs.get("sensorType", {}) - if isinstance(sensor_type, dict): - props["sensorType"] = sensor_type.get("value", "") - # Add scalar attribute values - for attr_name, attr_val in attrs.items(): - if isinstance(attr_val, dict): - v = attr_val.get("value") - if v is not None and not isinstance(v, (dict, list)): - props[attr_name] = v + for asset in assets: + try: + attrs = asset.get("attributes", {}) + location = attrs.get("location", {}) + value = location.get("value") if isinstance(location, dict) else None + coords = value.get("coordinates") if isinstance(value, dict) else None + if not coords or len(coords) < 2: + continue - features.append({ - "type": "Feature", - "geometry": {"type": "Point", "coordinates": [coords[0], coords[1]]}, - "properties": props - }) + props = { + "id": asset.get("id"), + "name": asset.get("name", ""), + "type": asset.get("type", ""), + "realm": asset.get("realm", ""), + } + # Add sensorType for color mapping + sensor_type = attrs.get("sensorType", {}) + if isinstance(sensor_type, dict): + props["sensorType"] = sensor_type.get("value", "") + # Add scalar attribute values + for attr_name, attr_val in attrs.items(): + if isinstance(attr_val, dict): + v = attr_val.get("value") + if v is not None and not isinstance(v, (dict, list)): + props[attr_name] = v + + # GeoJSON coordinates are [longitude, latitude] + features.append({ + "type": "Feature", + "geometry": {"type": "Point", "coordinates": [coords[1], coords[0]]}, + "properties": props + }) except Exception: continue diff --git a/simulator.py b/simulator.py index 377465ec..186f0c1a 100644 --- a/simulator.py +++ b/simulator.py @@ -912,13 +912,14 @@ def publish_openremote(sid: str, sensor: dict, values: dict) -> bool: "unit": "µg/m³" if "ugm3" in field else ("°C" if "celsius" in field else ("%" if "percent" in field or "humidity" in field else ("dB" if "db" in field else ""))), } - # Ajouter la location du capteur (GeoJSON Point) + # Ajouter la location du capteur (GeoJSON Point) avec meta showOnDashboard lat = sensor.get("lat", 0) lon = sensor.get("lon", 0) attrs["location"] = { "type": "GeoJSONPoint", "value": {"type": "Point", "coordinates": [lat, lon]}, "timestamp": now, + "meta": {"showOnDashboard": True}, } payload = {