""" stream_client.py — Envoie un flux JSONL vers le serveur moulin-mapper. Usage: python stream_client.py --file run_L.jsonl --url ws://127.0.0.1:8211/ws/ingest --token moulin-2026 --speed 1.0 --speed 0 : aussi vite que possible (pas de délai) --speed 1 : temps réel (deltas de t respectés) --stdin : lit depuis stdin au lieu d'un fichier (bridge ROV) """ import argparse import json import sys import time import asyncio try: import websockets except ImportError: print("Installe d'abord : pip install websockets") sys.exit(1) async def stream(url: str, token: str, lines, speed: float): # Ajoute le token comme query param sep = "&" if "?" in url else "?" full_url = f"{url}{sep}token={token}" t_prev = None sent = 0 t_file_prev = None print(f"[moulin-stream] connexion → {full_url}") while True: try: async with websockets.connect(full_url, ping_interval=20, ping_timeout=30) as ws: print("[moulin-stream] connecté") for line in lines: line = line.strip() if not line: continue try: rec = json.loads(line) except json.JSONDecodeError: continue t_rec = rec.get("t", 0.0) # Timing if speed > 0 and t_file_prev is not None: dt_file = t_rec - t_file_prev if dt_file > 0: await asyncio.sleep(dt_file / speed) t_file_prev = t_rec await ws.send(json.dumps(rec)) sent += 1 if sent % 100 == 0: print(f"\r[moulin-stream] {sent} lignes envoyées, t={t_rec:.1f}s", end="", flush=True) print(f"\n[moulin-stream] terminé — {sent} enregistrements envoyés") return except (websockets.exceptions.ConnectionClosed, ConnectionRefusedError, OSError) as e: print(f"\n[moulin-stream] connexion perdue ({e}), reconnexion dans 3s…") await asyncio.sleep(3.0) # Redémarre depuis le début si on a perdu la connexion # (les données sont déjà lues depuis un itérateur — on ne peut pas rembobiner stdin) print("[moulin-stream] AVERTISSEMENT: relance depuis le début du fichier") break def main(): parser = argparse.ArgumentParser(description="Stream JSONL vers moulin-mapper") src = parser.add_mutually_exclusive_group(required=True) src.add_argument("--file", help="Fichier JSONL à envoyer") src.add_argument("--stdin", action="store_true", help="Lit depuis stdin") parser.add_argument("--url", default="ws://127.0.0.1:8211/ws/ingest", help="URL WebSocket du serveur (ex: wss://lab.freeboxos.fr/moulin-live/ws/ingest)") parser.add_argument("--token", default="moulin-2026", help="Token d'authentification") parser.add_argument("--speed", type=float, default=1.0, help="Multiplicateur de vitesse (0=max, 1=temps réel, 2=2×)") args = parser.parse_args() if args.file: with open(args.file, "r") as f: lines = f.readlines() print(f"[moulin-stream] fichier: {args.file} ({len(lines)} lignes)") else: lines = sys.stdin asyncio.run(stream(args.url, args.token, lines, args.speed)) if __name__ == "__main__": main()