#!/usr/bin/env python3 """ Crée les 60 agents MQTT OpenRemote pour lier les topics du simulateur aux assets IOTSensor existants (realm smartcity). Chaque agent MQTT: - type: urn:openremote:agent:mqtt - agentLink -> asset IOTSensor (avec location) - topic: smartcity/{type}/{index} (1-indexé) Utilise l'API REST OpenRemote avec auth admin. """ import json import urllib.request import urllib.error import urllib.parse import uuid import sys import time OR_BASE = "http://localhost:8080" OR_REALM = "smartcity" OR_USER = "admin" OR_PASS = "Digitribe972" # Mapping des 60 capteurs simulateur -> noms d'assets OpenRemote # Format: (type, index_1based) -> nom_partiel_asset # Les assets ont des noms comme "Fort-de-France Centre (traffic)", "Fort-de-France Lamartine (airquality)", etc. SENSOR_TOPIC_TO_ASSET = { # traffic (index 1-10) ("traffic", 1): "Fort-de-France Centre (traffic)", ("traffic", 2): "Le Lamentin Aéroport (traffic)", ("traffic", 3): "Le Robert D110 (traffic)", ("traffic", 4): "Sainte-Anne Plage (traffic)", ("traffic", 5): "Saint-Joseph D1 (traffic)", ("traffic", 6): "Trinité Centre", ("traffic", 7): "Le François D2", ("traffic", 8): "Ducos Penitencier", ("traffic", 9): "Schœlcher Morne", ("traffic", 10): "Case-Pilote Bourg", # airquality (index 1-10) ("airquality", 1): "Fort-de-France Lamartine (airquality)", ("airquality", 2): "Le Lamentin Zac (airquality)", ("airquality", 3): "Le Robert Bourg (airquality)", ("airquality", 4): "Sainte-Anne Village (airquality)", ("airquality", 5): "Saint-Joseph Morne (airquality)", ("airquality", 6): "Trinité Eglise", ("airquality", 7): "Le François Bourg", ("airquality", 8): "Ducos Centre", ("airquality", 9): "Schœlcher Plage", ("airquality", 10): "Case-Pilote D1", # parking (index 1-10) ("parking", 1): "Fort-de-France Place Clémenceau (parking)", ("parking", 2): "Le Lamentin Centre Commercial (parking)", ("parking", 3): "Le Robert Stade (parking)", ("parking", 4): "Sainte-Anne Mairie (parking)", ("parking", 5): "Saint-Joseph Ecole (parking)", ("parking", 6): "Trinité Port", ("parking", 7): "Le François Mairie", ("parking", 8): "Ducos ZI", ("parking", 9): "Schœlcher Bourg", ("parking", 10): "Case-Pilote Stade", # noise (index 1-10) ("noise", 1): "Fort-de-France Théâtre (noise)", ("noise", 2): "Le Lamentin Zone Industrielle (noise)", ("noise", 3): "Le Robert Bourg (noise)", ("noise", 4): "Sainte-Anne Plage (noise)", ("noise", 5): "Saint-Joseph Morne (noise)", ("noise", 6): "Trinité Centre", ("noise", 7): "Le François Bourg", ("noise", 8): "Ducos Penitencier", ("noise", 9): "Schœlcher Morne", ("noise", 10): "Case-Pilote Village", # weather (index 1-10) ("weather", 1): "Fort-de-France Meteo (weather)", ("weather", 2): "Le Lamentin Aéroport (weather)", ("weather", 3): "Le Robert Bourg (weather)", ("weather", 4): "Sainte-Anne Village (weather)", ("weather", 5): "Saint-Joseph Morne (weather)", ("weather", 6): "Trinité Eglise", ("weather", 7): "Le François Bourg", ("weather", 8): "Ducos Centre", ("weather", 9): "Schœlcher Plage", ("weather", 10): "Case-Pilote D1", # light (index 1-10) ("light", 1): "Fort-de-France Place (light)", ("light", 2): "Le Lamentin Rond-point (light)", ("light", 3): "Le Robert D110 (light)", ("light", 4): "Sainte-Anne Plage (light)", ("light", 5): "Saint-Joseph D1 (light)", ("light", 6): "Trinité Centre", ("light", 7): "Le François D2", ("light", 8): "Ducos Penitencier", ("light", 9): "Schœlcher Morne", ("light", 10): "Case-Pilote Bourg", } def http_request(url, method="GET", data=None, headers=None, auth=None): """Effectue une requête HTTP et retourne (status_code, body)""" req = urllib.request.Request(url, method=method) if headers: for k, v in headers.items(): req.add_header(k, v) if auth: import base64 creds = base64.b64encode(f"{auth[0]}:{auth[1]}".encode()).decode() req.add_header("Authorization", f"Basic {creds}") if data: req.data = json.dumps(data).encode("utf-8") req.add_header("Content-Type", "application/json") try: resp = urllib.request.urlopen(req, timeout=10) return resp.status, resp.read().decode("utf-8") except urllib.error.HTTPError as e: return e.code, e.read().decode("utf-8") except Exception as e: return 0, str(e) def get_admin_token(): """Obtient un token admin via l'API Keycloak""" url = f"{OR_BASE}/auth/realms/{OR_REALM}/protocol/openid-connect/token" data = urllib.parse.urlencode({ "grant_type": "password", "client_id": "openremote", "username": OR_USER, "password": OR_PASS, }).encode() req = urllib.request.Request(url, data=data, method="POST") req.add_header("Content-Type", "application/x-www-form-urlencoded") try: resp = urllib.request.urlopen(req, timeout=10) body = json.loads(resp.read().decode()) return body.get("access_token") except Exception as e: print(f"❌ Erreur auth: {e}") return None def find_asset_by_name(token, name): """Recherche un asset par nom exact""" url = f"{OR_BASE}/api/{OR_REALM}/asset?name={urllib.parse.quote(name)}&limit=1" status, body = http_request(url, headers={"Authorization": f"Bearer {token}"}) if status == 200: assets = json.loads(body) if assets: return assets[0] return None def find_assets_by_name_prefix(token, prefix): """Recherche des assets par préfixe de nom""" url = f"{OR_BASE}/api/{OR_REALM}/asset?name={urllib.parse.quote(prefix)}&limit=20" status, body = http_request(url, headers={"Authorization": f"Bearer {token}"}) if status == 200: return json.loads(body) return [] def create_mqtt_agent(token, agent_name, asset_id, topic): """Crée un agent MQTT et le lie à un asset via agentLink""" agent_id = str(uuid.uuid4())[:22] # OpenRemote utilise des IDs tronqués agent = { "name": agent_name, "type": "urn:openremote:agent:mqtt", "realm": OR_REALM, "attributes": { "agentLink": { "name": "agentLink", "type": "JSON", "value": {"type": "AgentLink", "id": asset_id}, }, "topic": { "name": "topic", "type": "Text", "value": topic, }, }, } url = f"{OR_BASE}/api/{OR_REALM}/agent" status, body = http_request( url, method="POST", data=agent, headers={"Authorization": f"Bearer {token}"}, ) return status, body def main(): print("=" * 60) print("OpenRemote — Création des 60 agents MQTT") print("=" * 60) # 1. Authentification print("\n🔑 Authentification...") token = get_admin_token() if not token: print("❌ Impossible de s'authentifier. Vérifiez les credentials.") sys.exit(1) print("✅ Token obtenu") # 2. Créer les agents created = 0 failed = 0 skipped = 0 for (sensor_type, index), asset_name in SENSOR_TOPIC_TO_ASSET.items(): topic = f"smartcity/{sensor_type}/{index}" agent_name = f"MQTT Agent {asset_name}" print(f"\n[{created + failed + skipped + 1}/60] {sensor_type}/{index} → {asset_name}") # Chercher l'asset asset = find_asset_by_name(token, asset_name) if not asset: # Essayer sans le suffixe (type) short_name = asset_name.split(" (")[0] assets = find_assets_by_name_prefix(token, short_name) if assets: # Prendre celui avec location for a in assets: attrs = a.get("attributes", {}) if attrs.get("location", {}).get("value", {}).get("coordinates"): asset = a break if not asset and assets: asset = assets[0] if not asset: print(f" ⚠️ Asset non trouvé: '{asset_name}', skipping") skipped += 1 continue asset_id = asset["id"] print(f" 📍 Asset trouvé: {asset.get('name', '?')} ({asset_id})") # Créer l'agent MQTT status, body = create_mqtt_agent(token, agent_name, asset_id, topic) if status == 200: print(f" ✅ Agent créé → topic: {topic}") created += 1 elif status == 409: print(f" ⏭️ Agent déjà existant, skipping") skipped += 1 else: print(f" ❌ Erreur {status}: {body[:200]}") failed += 1 time.sleep(0.1) # Pas surcharger l'API print(f"\n{'=' * 60}") print(f"📊 Résultat: {created} créés, {failed} erreurs, {skipped} skipped") print(f"{'=' * 60}") if __name__ == "__main__": main()