powersync-docs/scripts/nymea_introspect_ws.py
Patrick Schurig b26274595c
Some checks failed
Build & Deploy docs / build-deploy (push) Failing after 10m17s
feat: référence API JsonRPC générée depuis introspect.json
- Nouveau générateur scripts/gen_api_reference.py : 19 namespaces →
  docs/api/metier/ (10) + docs/api/systeme/ (9) + notifications.md +
  types.md (96 types · 55 enums · 4 flags) + SUMMARY.md literate-nav
- Badges permissionScope (perm-none/control/configure/admin) dans extra.css
- Guide docs/integrations/jsonrpc-api.md (connexion TCP/WS, auth, conventions énergie)
- mkdocs.yml : Référence API dans la nav, REST→JsonRPC renommé
- mkdocs build --strict : 0 warnings · --check idempotent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 08:15:37 +02:00

100 lines
3.5 KiB
Python

#!/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 <host> <user> <password> > 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 ... <host> <user> <password>")
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())