Some checks failed
Build & Deploy docs / build-deploy (push) Failing after 10m17s
- 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>
100 lines
3.5 KiB
Python
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()) |