From 098d881d15127c8d8ca164f53ca99638329ec84e Mon Sep 17 00:00:00 2001 From: Aditya Pulipaka Date: Sun, 19 Oct 2025 04:10:32 -0500 Subject: [PATCH] basic web dashboard for control outputs and strategy. --- .../__pycache__/main.cpython-313.pyc | Bin 21540 -> 27121 bytes ai_intelligence_layer/main.py | 117 ++++- ai_intelligence_layer/static/dashboard.html | 474 ++++++++++++++++++ hpcsim/__pycache__/api.cpython-313.pyc | Bin 4776 -> 4776 bytes 4 files changed, 590 insertions(+), 1 deletion(-) create mode 100644 ai_intelligence_layer/static/dashboard.html diff --git a/ai_intelligence_layer/__pycache__/main.cpython-313.pyc b/ai_intelligence_layer/__pycache__/main.cpython-313.pyc index d97d7fa3b4561955ae7286219eb4a63c4a3c14b7..2f3e0ffb7a54130bead52b8fdc2495d6185b775e 100644 GIT binary patch delta 8900 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 delta 4799 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 Il@=KIzrr5_oB#j- diff --git a/ai_intelligence_layer/main.py b/ai_intelligence_layer/main.py index d955e1a..f2fe67a 100644 --- a/ai_intelligence_layer/main.py +++ b/ai_intelligence_layer/main.py @@ -6,11 +6,15 @@ Supports WebSocket connections from Pi for bidirectional control. """ from fastapi import FastAPI, HTTPException, status, WebSocket, WebSocketDisconnect from fastapi.middleware.cors import CORSMiddleware +from fastapi.staticfiles import StaticFiles +from fastapi.responses import FileResponse from contextlib import asynccontextmanager import logging import asyncio import random from typing import Dict, Any, List +from datetime import datetime +import json from config import get_settings from models.input_models import ( @@ -52,11 +56,14 @@ class ConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] + self.vehicle_counter = 0 async def connect(self, websocket: WebSocket): await websocket.accept() self.active_connections.append(websocket) + self.vehicle_counter += 1 logger.info(f"Pi client connected. Total connections: {len(self.active_connections)}") + return self.vehicle_counter def disconnect(self, websocket: WebSocket): self.active_connections.remove(websocket) @@ -74,7 +81,38 @@ class ConnectionManager: except Exception as e: logger.error(f"Error broadcasting to client: {e}") +class DashboardManager: + """Manages WebSocket connections for dashboard clients.""" + + def __init__(self): + self.active_dashboards: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_dashboards.append(websocket) + logger.info(f"Dashboard connected. Total dashboards: {len(self.active_dashboards)}") + + def disconnect(self, websocket: WebSocket): + if websocket in self.active_dashboards: + self.active_dashboards.remove(websocket) + logger.info(f"Dashboard disconnected. Total dashboards: {len(self.active_dashboards)}") + + async def broadcast(self, message: Dict[str, Any]): + """Broadcast message to all connected dashboards.""" + disconnected = [] + for dashboard in self.active_dashboards: + try: + await dashboard.send_json(message) + except Exception as e: + logger.error(f"Error broadcasting to dashboard: {e}") + disconnected.append(dashboard) + + # Clean up disconnected dashboards + for dashboard in disconnected: + self.disconnect(dashboard) + websocket_manager = ConnectionManager() +dashboard_manager = DashboardManager() @asynccontextmanager @@ -118,6 +156,15 @@ app.add_middleware( allow_headers=["*"], ) +# Mount static files +app.mount("/static", StaticFiles(directory="static"), name="static") + + +@app.get("/") +async def dashboard(): + """Serve the dashboard HTML page.""" + return FileResponse("static/dashboard.html") + @app.get("/api/health", response_model=HealthResponse) async def health_check(): @@ -297,6 +344,30 @@ async def analyze_strategies(request: AnalyzeRequest): """ +@app.websocket("/ws/dashboard") +async def websocket_dashboard_endpoint(websocket: WebSocket): + """ + WebSocket endpoint for dashboard clients. + Broadcasts vehicle connection status and lap data updates. + """ + await dashboard_manager.connect(websocket) + + try: + # Keep connection alive + while True: + # Receive any messages (mostly just keepalive pings) + data = await websocket.receive_text() + # Echo back if needed + if data == "ping": + await websocket.send_text("pong") + except WebSocketDisconnect: + logger.info("[Dashboard] Client disconnected") + except Exception as e: + logger.error(f"[Dashboard] Error: {e}") + finally: + dashboard_manager.disconnect(websocket) + + @app.websocket("/ws/pi") async def websocket_pi_endpoint(websocket: WebSocket): """ @@ -309,7 +380,7 @@ async def websocket_pi_endpoint(websocket: WebSocket): """ global current_race_context, last_control_command - await websocket_manager.connect(websocket) + vehicle_id = await websocket_manager.connect(websocket) # Clear telemetry buffer for fresh connection # This ensures lap counting starts from scratch for each Pi session @@ -320,6 +391,13 @@ async def websocket_pi_endpoint(websocket: WebSocket): logger.info("[WebSocket] Telemetry buffer cleared for new connection") + # Notify dashboards of new vehicle connection + await dashboard_manager.broadcast({ + "type": "vehicle_connected", + "vehicle_id": vehicle_id, + "timestamp": datetime.now().isoformat() + }) + try: # Send initial welcome message await websocket.send_json({ @@ -407,6 +485,23 @@ async def websocket_pi_endpoint(websocket: WebSocket): "reasoning": control_command.get("reasoning", "") }) + # Broadcast to dashboards with strategy + await dashboard_manager.broadcast({ + "type": "lap_data", + "vehicle_id": vehicle_id, + "lap_data": enriched, + "control_output": { + "brake_bias": control_command["brake_bias"], + "differential_slip": control_command["differential_slip"] + }, + "strategy": { + "strategy_name": top_strategy.strategy_name, + "risk_level": top_strategy.risk_level, + "brief_description": top_strategy.brief_description + } if top_strategy else None, + "timestamp": datetime.now().isoformat() + }) + logger.info(f"{'='*60}\n") except Exception as e: @@ -426,6 +521,19 @@ async def websocket_pi_endpoint(websocket: WebSocket): "differential_slip": 5, # Neutral "message": f"Collecting data ({len(buffer_data)}/3 laps)" }) + + # Broadcast to dashboards (no strategy yet) + await dashboard_manager.broadcast({ + "type": "lap_data", + "vehicle_id": vehicle_id, + "lap_data": enriched, + "control_output": { + "brake_bias": 5, + "differential_slip": 5 + }, + "strategy": None, + "timestamp": datetime.now().isoformat() + }) except Exception as e: logger.error(f"[WebSocket] Error processing telemetry: {e}") @@ -454,6 +562,13 @@ async def websocket_pi_endpoint(websocket: WebSocket): # Clear buffer when connection closes to ensure fresh start for next connection telemetry_buffer.clear() logger.info("[WebSocket] Telemetry buffer cleared on disconnect") + + # Notify dashboards of vehicle disconnect + await dashboard_manager.broadcast({ + "type": "vehicle_disconnected", + "vehicle_id": vehicle_id, + "timestamp": datetime.now().isoformat() + }) def generate_control_command( diff --git a/ai_intelligence_layer/static/dashboard.html b/ai_intelligence_layer/static/dashboard.html new file mode 100644 index 0000000..1072211 --- /dev/null +++ b/ai_intelligence_layer/static/dashboard.html @@ -0,0 +1,474 @@ + + + + + + F1 AI Intelligence Layer - Vehicle Dashboard + + + +
+

🏎️ F1 AI Intelligence Layer Dashboard

+

Real-time vehicle telemetry, strategy generation, and control outputs

+
+ ● Connecting... +
+
+ +
+
+ No vehicle connections yet. Waiting for Pi clients to connect... +
+
+ + + + diff --git a/hpcsim/__pycache__/api.cpython-313.pyc b/hpcsim/__pycache__/api.cpython-313.pyc index 9e596c04d78191fecbc33008860c75486884aec2..69959e8d1c4b76bb74718d6134c08a651a81a9e4 100644 GIT binary patch delta 19 ZcmZ3Xx