#!/usr/bin/env python3 """Introspection JsonRPC de nymea via WebSocket (port 4444) avec authentification. Séquence (comme le NymeaService Flutter) : 1. WebSocket connect ws://host:4444 2. JSONRPC.Hello -> welcome (authenticationRequired ?) 3. JSONRPC.Authenticate -> token (si auth requise) 4. JSONRPC.Introspect -> la référence complète de l'API Le token est ensuite inclus dans chaque requête. Usage: pip3 install websockets --break-system-packages python3 nymea_introspect_ws.py > introspect.json Le welcome + la liste des méthodes vont sur stderr ; le JSON d'introspection sur stdout. """ import asyncio, json, sys host = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" user = sys.argv[2] if len(sys.argv) > 2 else "" pwd = sys.argv[3] if len(sys.argv) > 3 else "" port = 4444 try: import websockets except ImportError: sys.stderr.write("ERREUR: pip3 install websockets --break-system-packages\n") sys.exit(2) def err(msg): sys.stderr.write(msg + "\n") async def main(): uri = f"ws://{host}:{port}" err(f"== Connexion {uri} ==") async with websockets.connect(uri, max_size=None) as ws: rid = 0 token = "" async def call(method, params=None): nonlocal rid rid += 1 msg = {"id": rid, "method": method, "params": params or {}} if token: msg["token"] = token await ws.send(json.dumps(msg)) # lire jusqu'à trouver la réponse à ce rid (ignorer notifications) while True: raw = await ws.recv() data = json.loads(raw) if data.get("id") == rid: return data # sinon c'est une notification, on continue # 1. Hello hello = await call("JSONRPC.Hello") err("== WELCOME ==") err(json.dumps(hello, indent=2)[:2000]) p = hello.get("params", hello) auth_required = p.get("authenticationRequired", False) already_auth = p.get("authenticated", False) # 2. Authenticate si nécessaire if auth_required and not already_auth: if not user: err("\n!! Auth requise mais pas d'identifiants. " "Usage: python3 ... ") sys.exit(3) err(f"\n== Authenticate user={user} ==") a = await call("JSONRPC.Authenticate", { "username": user, "password": pwd, "deviceName": "etm-docs-introspect", }) ap = a.get("params", {}) token = ap.get("token", "") if not token: err("!! Authentification échouée: " + json.dumps(ap)[:500]) sys.exit(4) err("== Token obtenu ==") # 3. Introspect err("\n== Introspect ==") intro = await call("JSONRPC.Introspect") # résumé sur stderr ip = intro.get("params", intro) methods = ip.get("methods", {}) notifs = ip.get("notifications", {}) types = ip.get("types", {}) err(f" méthodes: {len(methods)}") err(f" notifications: {len(notifs)}") err(f" types: {len(types)}") if methods: err("\n== Liste des méthodes ==") for m in sorted(methods.keys()): err(" " + m) # JSON complet sur stdout print(json.dumps(intro, indent=2, ensure_ascii=False)) asyncio.run(main())