from fastapi import FastAPI, WebSocket, WebSocketDisconnect\nfrom fastapi.responses import StreamingResponse, FileResponse\nfrom fastapi.staticfiles import StaticFiles\nimport asyncio, os, time, glob, json, numpy as np, threading, math\nfrom collections import deque\nfrom typing import Optional\n\napp = FastAPI(title="Glass Curtain Wall Test System - Real Data")\napp.mount("/", StaticFiles(directory="static", html=True), name="static")\n\n# Configuration - change to your CSV save directory\nCSV_DIR = "."  # by default, same dir as where C program saves files; change to absolute path if needed\nCSV_GLOB = "ai0_accel_*.csv"  # pattern used by your C program\nPOLL_INTERVAL = 0.05  # seconds - how often we check for new CSV lines\nFFT_WINDOW = 1024  # number of samples for FFT\nSAMPLE_RATE_FALLBACK = 1000.0  # Hz if not determinable from CSV\n\nclients = set()\ndata_queue = asyncio.Queue(maxsize=10000)\nspectrum_subscribers = set()\n\ndef find_latest_csv():\n    files = glob.glob(os.path.join(CSV_DIR, CSV_GLOB))\n    if not files:\n        return None\n    files.sort(key=os.path.getmtime, reverse=True)\n    return files[0]\n\ndef tail_file(path, queue: asyncio.Queue, stop_event: threading.Event):\n    loop = asyncio.get_event_loop()\n    try:\n        f = open(path, "r", encoding="utf-8", errors="ignore")\n    except Exception as e:\n        print("tail_file open error:", e)\n        return\n    f.seek(0, os.SEEK_END)\n    print("Tailing file:", path)\n    while not stop_event.is_set():\n        line = f.readline()\n        if not line:\n            time.sleep(POLL_INTERVAL)\n            continue\n        line = line.strip()\n        if not line:\n            continue\n        parts = [p.strip() for p in line.split(",")]\n        if len(parts) < 4:\n            continue\n        try:\n            t = float(parts[0])\n            volt = float(parts[1])\n            accel_g = float(parts[2])\n            accel_mps2 = float(parts[3])\n            sample = {"t": t, "volt": volt, "accel_g": accel_g, "accel_mps2": accel_mps2}\n            loop.call_soon_threadsafe(asyncio.create_task, queue.put(sample))\n        except Exception as e:\n            print("parse error:", e, "line:", line)\n            continue\n    f.close()\n\nasync def csv_watcher_task():\n    stop_event = None\n    tail_thread = None\n    current = None\n    while True:\n        latest = find_latest_csv()\n        if latest != current:\n            if tail_thread and stop_event:\n                stop_event.set()\n                tail_thread.join(timeout=1.0)\n            current = latest\n            if current:\n                stop_event = threading.Event()\n                tail_thread = threading.Thread(target=tail_file, args=(current, data_queue, stop_event), daemon=True)\n                tail_thread.start()\n                print("Started tail for", current)\n            else:\n                print("No CSV found; waiting...")\n        await asyncio.sleep(1.0)\n\n@app.on_event("startup")\nasync def startup_event():\n    asyncio.create_task(csv_watcher_task())\n\n@app.websocket("/ws")\nasync def websocket_endpoint(websocket: WebSocket):\n    await websocket.accept()\n    clients.add(websocket)\n    print("Client connected:", websocket.client)\n    try:\n        while True:\n            sample = await data_queue.get()\n            await websocket.send_text(json.dumps({"type":"sample","data": sample}))\n    except WebSocketDisconnect:\n        print("Client disconnected")\n        clients.discard(websocket)\n    except Exception as e:\n        print("WS error:", e)\n        clients.discard(websocket)\n\n@app.get("/")\ndef index():\n    return FileResponse(os.path.join("static","index.html"))\n\n@app.get("/health")\ndef health():\n    return {"status":"ok"}\n