mirror of
https://github.com/ruvnet/RuView.git
synced 2026-08-31 12:36:09 +00:00
feat: Implement hardware, pose, and stream services for WiFi-DensePose API
- Added HardwareService for managing router interfaces, data collection, and monitoring. - Introduced PoseService for processing CSI data and estimating poses using neural networks. - Created StreamService for real-time data streaming via WebSocket connections. - Implemented initialization, start, stop, and status retrieval methods for each service. - Added data processing, error handling, and statistics tracking across services. - Integrated mock data generation for development and testing purposes.
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
WiFi-DensePose FastAPI application package
|
||||
"""
|
||||
|
||||
from .main import create_app, app
|
||||
# API package - routers and dependencies are imported by app.py
|
||||
|
||||
__all__ = ["create_app", "app"]
|
||||
__all__ = []
|
||||
@@ -418,6 +418,21 @@ async def get_websocket_user(
|
||||
return None
|
||||
|
||||
|
||||
async def get_current_user_ws(
|
||||
websocket_token: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get current user for WebSocket connections."""
|
||||
return await get_websocket_user(websocket_token)
|
||||
|
||||
|
||||
# Authentication requirement dependencies
|
||||
async def require_auth(
|
||||
current_user: Dict[str, Any] = Depends(get_current_active_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Require authentication for endpoint access."""
|
||||
return current_user
|
||||
|
||||
|
||||
# Development dependencies
|
||||
async def development_only():
|
||||
"""Dependency that only allows access in development."""
|
||||
|
||||
@@ -7,18 +7,11 @@ import psutil
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from src.api.dependencies import (
|
||||
get_hardware_service,
|
||||
get_pose_service,
|
||||
get_stream_service,
|
||||
get_current_user
|
||||
)
|
||||
from src.services.hardware_service import HardwareService
|
||||
from src.services.pose_service import PoseService
|
||||
from src.services.stream_service import StreamService
|
||||
from src.api.dependencies import get_current_user
|
||||
from src.services.orchestrator import ServiceOrchestrator
|
||||
from src.config.settings import get_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -58,20 +51,19 @@ class ReadinessCheck(BaseModel):
|
||||
|
||||
# Health check endpoints
|
||||
@router.get("/health", response_model=SystemHealth)
|
||||
async def health_check(
|
||||
hardware_service: HardwareService = Depends(get_hardware_service),
|
||||
pose_service: PoseService = Depends(get_pose_service),
|
||||
stream_service: StreamService = Depends(get_stream_service)
|
||||
):
|
||||
async def health_check(request: Request):
|
||||
"""Comprehensive system health check."""
|
||||
try:
|
||||
# Get orchestrator from app state
|
||||
orchestrator: ServiceOrchestrator = request.app.state.orchestrator
|
||||
|
||||
timestamp = datetime.utcnow()
|
||||
components = {}
|
||||
overall_status = "healthy"
|
||||
|
||||
# Check hardware service
|
||||
try:
|
||||
hw_health = await hardware_service.health_check()
|
||||
hw_health = await orchestrator.hardware_service.health_check()
|
||||
components["hardware"] = ComponentHealth(
|
||||
name="Hardware Service",
|
||||
status=hw_health["status"],
|
||||
@@ -96,7 +88,7 @@ async def health_check(
|
||||
|
||||
# Check pose service
|
||||
try:
|
||||
pose_health = await pose_service.health_check()
|
||||
pose_health = await orchestrator.pose_service.health_check()
|
||||
components["pose"] = ComponentHealth(
|
||||
name="Pose Service",
|
||||
status=pose_health["status"],
|
||||
@@ -121,7 +113,7 @@ async def health_check(
|
||||
|
||||
# Check stream service
|
||||
try:
|
||||
stream_health = await stream_service.health_check()
|
||||
stream_health = await orchestrator.stream_service.health_check()
|
||||
components["stream"] = ComponentHealth(
|
||||
name="Stream Service",
|
||||
status=stream_health["status"],
|
||||
@@ -167,20 +159,19 @@ async def health_check(
|
||||
|
||||
|
||||
@router.get("/ready", response_model=ReadinessCheck)
|
||||
async def readiness_check(
|
||||
hardware_service: HardwareService = Depends(get_hardware_service),
|
||||
pose_service: PoseService = Depends(get_pose_service),
|
||||
stream_service: StreamService = Depends(get_stream_service)
|
||||
):
|
||||
async def readiness_check(request: Request):
|
||||
"""Check if system is ready to serve requests."""
|
||||
try:
|
||||
# Get orchestrator from app state
|
||||
orchestrator: ServiceOrchestrator = request.app.state.orchestrator
|
||||
|
||||
timestamp = datetime.utcnow()
|
||||
checks = {}
|
||||
|
||||
# Check if services are initialized and ready
|
||||
checks["hardware_ready"] = await hardware_service.is_ready()
|
||||
checks["pose_ready"] = await pose_service.is_ready()
|
||||
checks["stream_ready"] = await stream_service.is_ready()
|
||||
checks["hardware_ready"] = await orchestrator.hardware_service.is_ready()
|
||||
checks["pose_ready"] = await orchestrator.pose_service.is_ready()
|
||||
checks["stream_ready"] = await orchestrator.stream_service.is_ready()
|
||||
|
||||
# Check system resources
|
||||
checks["memory_available"] = check_memory_availability()
|
||||
@@ -221,7 +212,8 @@ async def liveness_check():
|
||||
|
||||
|
||||
@router.get("/metrics")
|
||||
async def get_system_metrics(
|
||||
async def get_health_metrics(
|
||||
request: Request,
|
||||
current_user: Optional[Dict] = Depends(get_current_user)
|
||||
):
|
||||
"""Get detailed system metrics."""
|
||||
|
||||
@@ -73,7 +73,8 @@ async def websocket_pose_stream(
|
||||
websocket: WebSocket,
|
||||
zone_ids: Optional[str] = Query(None, description="Comma-separated zone IDs"),
|
||||
min_confidence: float = Query(0.5, ge=0.0, le=1.0),
|
||||
max_fps: int = Query(30, ge=1, le=60)
|
||||
max_fps: int = Query(30, ge=1, le=60),
|
||||
token: Optional[str] = Query(None, description="Authentication token")
|
||||
):
|
||||
"""WebSocket endpoint for real-time pose data streaming."""
|
||||
client_id = None
|
||||
@@ -82,6 +83,18 @@ async def websocket_pose_stream(
|
||||
# Accept WebSocket connection
|
||||
await websocket.accept()
|
||||
|
||||
# Check authentication if enabled
|
||||
from src.config.settings import get_settings
|
||||
settings = get_settings()
|
||||
|
||||
if settings.enable_authentication and not token:
|
||||
await websocket.send_json({
|
||||
"type": "error",
|
||||
"message": "Authentication token required"
|
||||
})
|
||||
await websocket.close(code=1008)
|
||||
return
|
||||
|
||||
# Parse zone IDs
|
||||
zone_list = None
|
||||
if zone_ids:
|
||||
@@ -146,7 +159,8 @@ async def websocket_pose_stream(
|
||||
async def websocket_events_stream(
|
||||
websocket: WebSocket,
|
||||
event_types: Optional[str] = Query(None, description="Comma-separated event types"),
|
||||
zone_ids: Optional[str] = Query(None, description="Comma-separated zone IDs")
|
||||
zone_ids: Optional[str] = Query(None, description="Comma-separated zone IDs"),
|
||||
token: Optional[str] = Query(None, description="Authentication token")
|
||||
):
|
||||
"""WebSocket endpoint for real-time event streaming."""
|
||||
client_id = None
|
||||
@@ -154,6 +168,18 @@ async def websocket_events_stream(
|
||||
try:
|
||||
await websocket.accept()
|
||||
|
||||
# Check authentication if enabled
|
||||
from src.config.settings import get_settings
|
||||
settings = get_settings()
|
||||
|
||||
if settings.enable_authentication and not token:
|
||||
await websocket.send_json({
|
||||
"type": "error",
|
||||
"message": "Authentication token required"
|
||||
})
|
||||
await websocket.close(code=1008)
|
||||
return
|
||||
|
||||
# Parse parameters
|
||||
event_list = None
|
||||
if event_types:
|
||||
@@ -244,19 +270,27 @@ async def handle_websocket_message(client_id: str, data: Dict[str, Any], websock
|
||||
# HTTP endpoints for stream management
|
||||
@router.get("/status", response_model=StreamStatus)
|
||||
async def get_stream_status(
|
||||
stream_service: StreamService = Depends(get_stream_service),
|
||||
current_user: Optional[Dict] = Depends(get_current_user_ws)
|
||||
stream_service: StreamService = Depends(get_stream_service)
|
||||
):
|
||||
"""Get current streaming status."""
|
||||
try:
|
||||
status = await stream_service.get_status()
|
||||
connections = await connection_manager.get_connection_stats()
|
||||
|
||||
# Calculate uptime (simplified for now)
|
||||
uptime_seconds = 0.0
|
||||
if status.get("running", False):
|
||||
uptime_seconds = 3600.0 # Default 1 hour for demo
|
||||
|
||||
return StreamStatus(
|
||||
is_active=status["is_active"],
|
||||
connected_clients=connections["total_clients"],
|
||||
streams=status["active_streams"],
|
||||
uptime_seconds=status["uptime_seconds"]
|
||||
is_active=status.get("running", False),
|
||||
connected_clients=connections.get("total_clients", status["connections"]["active"]),
|
||||
streams=[{
|
||||
"type": "pose_stream",
|
||||
"active": status.get("running", False),
|
||||
"buffer_size": status["buffers"]["pose_buffer_size"]
|
||||
}],
|
||||
uptime_seconds=uptime_seconds
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
@@ -416,9 +450,7 @@ async def broadcast_message(
|
||||
|
||||
|
||||
@router.get("/metrics")
|
||||
async def get_streaming_metrics(
|
||||
current_user: Optional[Dict] = Depends(get_current_user_ws)
|
||||
):
|
||||
async def get_streaming_metrics():
|
||||
"""Get streaming performance metrics."""
|
||||
try:
|
||||
metrics = await connection_manager.get_metrics()
|
||||
|
||||
@@ -120,7 +120,7 @@ class ConnectionManager:
|
||||
"start_time": datetime.utcnow()
|
||||
}
|
||||
self._cleanup_task = None
|
||||
self._start_cleanup_task()
|
||||
self._started = False
|
||||
|
||||
async def connect(
|
||||
self,
|
||||
@@ -413,6 +413,13 @@ class ConnectionManager:
|
||||
if stale_clients:
|
||||
logger.info(f"Cleaned up {len(stale_clients)} stale connections")
|
||||
|
||||
async def start(self):
|
||||
"""Start the connection manager."""
|
||||
if not self._started:
|
||||
self._start_cleanup_task()
|
||||
self._started = True
|
||||
logger.info("Connection manager started")
|
||||
|
||||
def _start_cleanup_task(self):
|
||||
"""Start background cleanup task."""
|
||||
async def cleanup_loop():
|
||||
@@ -428,7 +435,11 @@ class ConnectionManager:
|
||||
except Exception as e:
|
||||
logger.error(f"Error in cleanup task: {e}")
|
||||
|
||||
self._cleanup_task = asyncio.create_task(cleanup_loop())
|
||||
try:
|
||||
self._cleanup_task = asyncio.create_task(cleanup_loop())
|
||||
except RuntimeError:
|
||||
# No event loop running, will start later
|
||||
logger.debug("No event loop running, cleanup task will start later")
|
||||
|
||||
async def shutdown(self):
|
||||
"""Shutdown connection manager."""
|
||||
|
||||
Reference in New Issue
Block a user