"""System API endpoints.""" from fastapi import APIRouter, HTTPException, Request from pydantic import BaseModel from typing import Optional, Dict from ..managers.session_manager import SessionManager from ..managers.ups_manager import get_ups_manager from ..managers.ble_manager import get_ble_manager router = APIRouter() session_manager = SessionManager() class CreateSessionRequest(BaseModel): force_takeover: bool = False class CreateSessionResponse(BaseModel): success: bool session_id: Optional[str] = None error: Optional[str] = None class SessionInfo(BaseModel): session_id: str client_ip: str created_at: float last_activity: float time_remaining: float class SystemInfo(BaseModel): """System information response.""" hostname: str uptime: float cpu_temp: Optional[float] memory_used: float memory_total: float disk_used: float disk_total: float @router.post("/session/create", response_model=CreateSessionResponse) async def create_session(request: Request, body: CreateSessionRequest): """Create a new session for PM3 access.""" client_ip = request.client.host if request.client else "unknown" user_agent = request.headers.get("user-agent") success, session_id, error = await session_manager.create_session( client_ip=client_ip, user_agent=user_agent, force_takeover=body.force_takeover ) return CreateSessionResponse( success=success, session_id=session_id, error=error ) @router.post("/session/{session_id}/release") async def release_session(session_id: str): """Release an active session.""" success = await session_manager.release_session(session_id) if not success: raise HTTPException(status_code=404, detail="Session not found") return {"success": True, "message": "Session released"} @router.get("/session/active", response_model=Optional[SessionInfo]) async def get_active_session(): """Get information about the active session.""" session = session_manager.get_active_session() if not session: return None import time from .. import config time_remaining = config.SESSION_TIMEOUT - (time.time() - session.last_activity) return SessionInfo( session_id=session.session_id, client_ip=session.client_ip, created_at=session.created_at, last_activity=session.last_activity, time_remaining=max(0, time_remaining) ) @router.get("/info", response_model=SystemInfo) async def get_system_info(): """Get system information.""" import platform import psutil from pathlib import Path # Get CPU temperature (Raspberry Pi specific) cpu_temp = None try: temp_file = Path("/sys/class/thermal/thermal_zone0/temp") if temp_file.exists(): cpu_temp = int(temp_file.read_text()) / 1000.0 except Exception: pass # Get memory info memory = psutil.virtual_memory() # Get disk info disk = psutil.disk_usage('/') return SystemInfo( hostname=platform.node(), uptime=psutil.boot_time(), cpu_temp=cpu_temp, memory_used=memory.used, memory_total=memory.total, disk_used=disk.used, disk_total=disk.total ) @router.get("/config") async def get_config(): """Get system configuration (non-sensitive values).""" from .. import config return { "pm3_device": config.PM3_DEVICE, "session_timeout": config.SESSION_TIMEOUT, "wifi_mode": "auto", # TODO: Get from wifi manager "ble_enabled": config.BLE_ENABLED, "auth_enabled": config.AUTH_ENABLED, "https_enabled": config.HTTPS_ENABLED } @router.post("/restart") async def restart_system(): """Restart the backend application.""" # TODO: Implement graceful restart return {"success": True, "message": "Restart initiated"} @router.post("/shutdown") async def shutdown_system(): """Initiate system shutdown.""" # TODO: Implement safe shutdown sequence return {"success": True, "message": "Shutdown initiated"} class UPSStatusResponse(BaseModel): """UPS status response model.""" battery_percentage: float voltage: float current: float power_source: str battery_status: str time_remaining: Optional[int] = None temperature: Optional[float] = None last_updated: Optional[str] = None is_available: bool error_message: Optional[str] = None class UPSThresholdsRequest(BaseModel): """Request to set UPS battery thresholds.""" shutdown_threshold: Optional[float] = None warning_threshold: Optional[float] = None @router.get("/ups/status", response_model=UPSStatusResponse) async def get_ups_status(): """Get current UPS battery status.""" ups_manager = get_ups_manager() status = await ups_manager.get_status() return UPSStatusResponse( battery_percentage=status.battery_percentage, voltage=status.voltage, current=status.current, power_source=status.power_source.value, battery_status=status.battery_status.value, time_remaining=status.time_remaining, temperature=status.temperature, last_updated=status.last_updated, is_available=status.is_available, error_message=status.error_message ) @router.post("/ups/thresholds") async def set_ups_thresholds(request: UPSThresholdsRequest): """Set UPS battery thresholds for warnings and shutdown.""" ups_manager = get_ups_manager() if request.shutdown_threshold is not None: ups_manager.set_shutdown_threshold(request.shutdown_threshold) if request.warning_threshold is not None: ups_manager.set_warning_threshold(request.warning_threshold) return { "success": True, "message": "UPS thresholds updated" } @router.post("/ups/shutdown") async def trigger_ups_shutdown(delay: int = 30): """Trigger safe shutdown via UPS manager. Args: delay: Delay in seconds before shutdown (default: 30) """ ups_manager = get_ups_manager() await ups_manager.shutdown_device(delay=delay) return { "success": True, "message": f"Shutdown initiated with {delay}s delay" } class BLEStatusResponse(BaseModel): """BLE status response model.""" enabled: bool available: bool advertising: bool connected_devices: int device_name: str queued_notifications: int class SendNotificationRequest(BaseModel): """Request to send a BLE notification.""" type: str message: str data: Optional[Dict] = None @router.get("/ble/status", response_model=BLEStatusResponse) async def get_ble_status(): """Get BLE manager status.""" ble_manager = get_ble_manager() status = await ble_manager.get_status() return BLEStatusResponse(**status) @router.post("/ble/advertising/start") async def start_ble_advertising(): """Start BLE advertising.""" ble_manager = get_ble_manager() if not ble_manager.is_available(): raise HTTPException(status_code=503, detail="BLE not available") await ble_manager.start_advertising() return { "success": True, "message": "BLE advertising started" } @router.post("/ble/advertising/stop") async def stop_ble_advertising(): """Stop BLE advertising.""" ble_manager = get_ble_manager() await ble_manager.stop_advertising() return { "success": True, "message": "BLE advertising stopped" } @router.post("/ble/notify") async def send_ble_notification(request: SendNotificationRequest): """Send a BLE notification to connected devices.""" ble_manager = get_ble_manager() if not ble_manager.is_available(): raise HTTPException(status_code=503, detail="BLE not available") from ..managers.ble_manager import NotificationType # Validate notification type try: notification_type = NotificationType(request.type) except ValueError: raise HTTPException(status_code=400, detail=f"Invalid notification type: {request.type}") await ble_manager.send_notification( notification_type=notification_type, message=request.message, data=request.data ) return { "success": True, "message": "Notification sent" }