From 42d1223b147ab01a163d46451347c95bc859e569 Mon Sep 17 00:00:00 2001 From: Eric FELIXINE Date: Mon, 4 May 2026 18:13:48 -0400 Subject: [PATCH] GeoServer workspace Digitribe + InfluxDB support + data flow diagrams --- geoserver_config_status.md | 68 ++++++++++++++++++++++++++++++++++++++ simulator.py | 63 ++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 geoserver_config_status.md diff --git a/geoserver_config_status.md b/geoserver_config_status.md new file mode 100644 index 00000000..1f4b054f --- /dev/null +++ b/geoserver_config_status.md @@ -0,0 +1,68 @@ +# Configuration GeoServer - Smart City Digital Twin + +## État au 04 Mai 2026 (22h15) + +### ✅ Réalisé +1. **Workspace créé** : `Digitribe` (via REST API) + - URL: https://geoserver.digitribe.fr/geoserver/web/?workspace=Digitribe +2. **Tentatives d'entrepôts** : + - `brokers_postgis` (docker-postgis-1) - créé mais connexion instable + - `digital_twin_postgis` (digital-twin-postgis) - base "digitribe" inexistante + - `digitribe_brokers` (docker-postgis-1, base "geoserver") - erreur "Unable to encrypt connection parameters" + - `brokers_shapefile` (Shapefile) - créé mais vide (pas de fichiers .shp) + +### ❌ Problèmes rencontrés +- **Erreur** : "Failed to find the datastore factory" / "Unable to encrypt connection parameters" +- **Cause probable** : Paramètres de connexion PostGIS mal formatés ou problème de chiffrement GeoServer +- **Identifiants testés** : + - docker-postgis-1 : user=`geoserver`, password=`geoserver`, db=`geoserver` + - digital-twin-postgis : user=`gis_user`, password=`gis_pass` (probable) + - frost_http-database-1 : user=`sensorthings`, password=`Digitribe972` (probable) + +### 📋 Configuration pour MapStore +Une fois l'entrepôt fonctionnel, voici comment l'utiliser dans MapStore : + +```javascript +// Exemple de configuration MapStore (WMS) +{ + "type": "wms", + "url": "https://geoserver.digitribe.fr/geoserver/wms", + "name": "Digitribe:broker_sensors", + "format": "image/png", + "workspace": "Digitribe" +} +``` + +### 🔄 Prochaines étapes (pour reprise) +1. **Corriger l'entrepôt PostGIS** : + - Vérifier que le container GeoServer peut joindre le container PostGIS + - Tester la connexion via `psql` depuis le container GeoServer + - Utiliser le format XML correct pour les paramètres chiffrés +2. **Ajouter des données spatiales** : + - Importer les données des capteurs (depuis InfluxDB ou FROST) + - Créer des vues géographiques dans PostGIS +3. **Publier les couches** : + - `broker_sensors` (positions des capteurs MQTT) + - `sensor_data` (données temps réel) +4. **Configurer MapStore** : + - Ajouter GeoServer comme source WMS/WFS + - Créer une carte avec les couches du workspace Digitribe + +### 🔧 Commandes de diagnostic +```bash +# Tester la connexion depuis GeoServer +docker exec geoserver_stack-geoserver-1 psql -h digital-twin-postgis -U gis_user -d digitribe -c "\dt" + +# Vérifier les logs GeoServer +docker logs geoserver_stack-geoserver-1 --tail 50 | grep -i "error\|datastore" + +# Recréer l'entrepôt avec le bon format +curl -X PUT "https://geoserver.digitribe.fr/geoserver/rest/workspaces/Digitribe/datastores/digitribe_brokers" \ + -u "admin:Digitribe972" \ + -H "Content-Type: application/xml" \ + -d '...' # (XML avec paramètres corrects) +``` + +--- +**Fichiers** : `geoserver_config_status.md` (ce fichier) +**Statut** : Workspace ✅ | Entrepôts ⚠️ (à debugguer) | Prêt pour MapStore ❌ \ No newline at end of file diff --git a/simulator.py b/simulator.py index f758c091..22ea0143 100644 --- a/simulator.py +++ b/simulator.py @@ -29,6 +29,10 @@ import urllib.request, urllib.error from datetime import datetime, timezone from typing import Any +# InfluxDB support +import influxdb_client +from influxdb_client.client.write_api import SYNCHRONOUS + # ============================================================================= # Configuration # ============================================================================= @@ -45,6 +49,24 @@ OR_REALM = os.environ.get("OR_REALM", "smartcity") OR_TOKEN_REALM = os.environ.get("OR_TOKEN_REALM", "master") # Realm pour obtention token FROST_URL = os.environ.get("FROST_URL", "http://frost_http-web-1:8080/FROST-Server/v1.1") +# InfluxDB config +ENABLE_INFLUX = os.environ.get("ENABLE_INFLUX", "1") == "1" +INFLUX_URL = os.environ.get("INFLUX_URL", "http://digital-twin-influxdb:8086") +INFLUX_ORG = os.environ.get("INFLUX_ORG", "digitribe") +INFLUX_BUCKET = os.environ.get("INFLUX_BUCKET", "iot_data") +INFLUX_TOKEN = os.environ.get("INFLUX_TOKEN", "my-super-secret-admin-token") + +# Initialize InfluxDB client +_influx_client = None +_influx_write_api = None +if ENABLE_INFLUX: + try: + _influx_client = influxdb_client.InfluxDBClient(url=INFLUX_URL, token=INFLUX_TOKEN, org=INFLUX_ORG) + _influx_write_api = _influx_client.write_api(write_options=SYNCHRONOUS) + print(f"[INFLUX] ✅ Connected to {INFLUX_URL}") + except Exception as e: + print(f"[INFLUX] ❌ Connection failed: {e}") + SENSOR_COUNTS = { "traffic": int(os.environ.get("SENSOR_COUNT_traffic", "3")), "airquality": int(os.environ.get("SENSOR_COUNT_airquality", "2")), @@ -724,7 +746,35 @@ def publish_openremote(sid: str, sensor: dict, values: dict) -> bool: "attributes": attrs, } return _or_put(asset_id, payload) -# ============================================================================= + +def publish_influx(sid: str, sensor: dict, values: dict) -> bool: + """Write sensor data to InfluxDB.""" + if not _influx_write_api: + return False + try: + stype = sensor["type"] + lat = sensor.get("lat", BASE_LAT) + lon = sensor.get("lon", BASE_LON) + + points = [] + for field, value in values.items(): + if isinstance(value, (int, float)): + p = influxdb_client.Point(stype)\ + .tag("sensor_id", sid)\ + .tag("location", sensor.get("name", sid))\ + .field(field, float(value))\ + .field("lat", float(lat))\ + .field("lon", float(lon)) + points.append(p) + + if points: + _influx_write_api.write(bucket=INFLUX_BUCKET, record=points) + print(f" 📈 InfluxDB: {len(points)} points written") + return True + except Exception as e: + print(f" ⚠️ InfluxDB → {e}") + return False + def main(): print("╔══════════════════════════════════════════════════╗") print("║ Smart City Simulator — Martinique ║") @@ -812,6 +862,17 @@ def main(): val = round(random.uniform(lo, hi), 1) ok_fr = publish_frost(sid, sensor, field, val) print(f" 📊 FROST: {'✅' if ok_fr else '❌'}") + + # --- InfluxDB --- + if ENABLE_INFLUX: + influx_vals = {} + for field, val_range in ranges.items(): + if isinstance(val_range, tuple) and len(val_range) == 2: + lo, hi = val_range + if isinstance(lo, (int, float)): + influx_vals[field] = round(random.uniform(lo, hi), 1) + ok_influx = publish_influx(sid, sensor, influx_vals) + print(f" 📈 InfluxDB: {'✅' if ok_influx else '❌'}") # --- BunkerM HTTP --- if os.getenv("BUNKERM_HTTP", "0") == "1":