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:
rUv
2025-06-07 12:47:54 +00:00
parent c378b705ca
commit 90f03bac7d
26 changed files with 9846 additions and 105 deletions

View File

@@ -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__ = []

View File

@@ -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."""

View File

@@ -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."""

View File

@@ -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()

View File

@@ -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."""