"""WiFi management API endpoints. Refactored to use WiFiService for all business logic. Endpoints are now thin adapters that convert HTTP requests/responses. """ from fastapi import APIRouter, HTTPException from pydantic import BaseModel from typing import List, Optional from ..services.container import container router = APIRouter() class WiFiStatusResponse(BaseModel): """WiFi status response.""" mode: str interfaces: List[dict] current_ssid: Optional[str] current_ip: Optional[str] ap_ssid: Optional[str] ap_ip: Optional[str] supports_dual: bool class WiFiNetworkResponse(BaseModel): """WiFi network response.""" ssid: str bssid: str signal_strength: int frequency: int encrypted: bool in_use: bool class SetModeRequest(BaseModel): """Request to set WiFi mode.""" mode: str # "ap", "client", "dual", "auto", "off" class ConnectRequest(BaseModel): """Request to connect to network.""" ssid: str password: Optional[str] = None interface: Optional[str] = None hidden: bool = False def _service_error_to_http_status(error_code: str) -> int: """Map service error codes to HTTP status codes. Args: error_code: Service error code Returns: HTTP status code """ codes = { "wifi_status_error": 500, "wifi_scan_error": 500, "connection_failed": 503, "wifi_connect_error": 500, "disconnect_failed": 500, "wifi_disconnect_error": 500, "invalid_mode": 400, "mode_not_supported": 400, "mode_change_failed": 500, "wifi_mode_error": 500, "saved_networks_error": 500, "network_not_found": 404, "forget_network_error": 500, } return codes.get(error_code, 500) @router.get("/status", response_model=WiFiStatusResponse) async def get_wifi_status(): """Get current WiFi status and available interfaces. Uses WiFiService for business logic. """ result = await container.wifi_service.get_status() if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) data = result.data return WiFiStatusResponse( mode=data["mode"], interfaces=data["interfaces"], current_ssid=data["client"]["ssid"] if data.get("client") else None, current_ip=data["client"]["ip"] if data.get("client") else None, ap_ssid=data["access_point"]["ssid"], ap_ip=data["access_point"]["ip"], supports_dual=data["supports_dual"] ) @router.get("/scan", response_model=List[WiFiNetworkResponse]) async def scan_networks(interface: Optional[str] = None): """Scan for available WiFi networks. Uses WiFiService for business logic. Args: interface: Optional interface to scan with """ result = await container.wifi_service.scan_networks(interface) if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) return [ WiFiNetworkResponse(**net) for net in result.data["networks"] ] @router.post("/mode") async def set_wifi_mode(request: SetModeRequest): """Set WiFi operation mode. Uses WiFiService for business logic. Args: request: Mode to set (ap, client, dual, auto, off) """ result = await container.wifi_service.set_mode(request.mode) if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) return { "success": True, "message": result.data["message"], "mode": result.data["mode"] } @router.post("/connect") async def connect_to_network(request: ConnectRequest): """Connect to a WiFi network. Uses WiFiService for business logic. Args: request: Connection details (SSID, password, interface, hidden) """ result = await container.wifi_service.connect( ssid=request.ssid, password=request.password, interface=request.interface, hidden=request.hidden ) if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) return { "success": True, "message": result.data["message"], "ssid": result.data["ssid"], "ip": result.data.get("ip") } @router.get("/interfaces") async def get_interfaces(): """Get available WiFi interfaces. Uses WiFiService for business logic. """ result = await container.wifi_service.get_status() if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) return { "interfaces": result.data["interfaces"], "supports_dual": result.data["supports_dual"] } @router.post("/disconnect") async def disconnect_from_network(interface: Optional[str] = None): """Disconnect from current network. Uses WiFiService for business logic. Args: interface: Interface to disconnect (optional) """ result = await container.wifi_service.disconnect(interface) if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) return { "success": True, "message": result.data["message"] } @router.get("/saved") async def get_saved_networks(): """Get list of saved networks. Uses WiFiService for business logic. """ result = await container.wifi_service.get_saved_networks() if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) return {"networks": result.data["networks"]} @router.delete("/saved/{ssid}") async def forget_network(ssid: str): """Forget a saved network. Uses WiFiService for business logic. Args: ssid: SSID of network to forget """ result = await container.wifi_service.forget_network(ssid) if not result.success: raise HTTPException( status_code=_service_error_to_http_status(result.error.code), detail=result.error.message ) return { "success": True, "message": result.data["message"] } @router.post("/static-ip") async def set_static_ip( interface: str, ip_address: str, netmask: str = "255.255.255.0", gateway: Optional[str] = None, dns: Optional[List[str]] = None, ): """Set static IP for interface. NOTE: This is a direct manager operation (not yet in WiFiService). TODO: Move to WiFiService in future iteration. Args: interface: Interface name ip_address: Static IP address netmask: Network mask (default: 255.255.255.0) gateway: Gateway IP (optional) dns: DNS servers (optional) """ try: success = await container.wifi_manager.set_static_ip( interface, ip_address, netmask, gateway, dns ) if not success: raise HTTPException(status_code=500, detail="Failed to set static IP") return { "success": True, "message": f"Static IP {ip_address} set for {interface}", } except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to set static IP: {e}") @router.post("/dhcp") async def enable_dhcp(interface: str): """Enable DHCP for interface. NOTE: This is a direct manager operation (not yet in WiFiService). TODO: Move to WiFiService in future iteration. Args: interface: Interface name """ try: success = await container.wifi_manager.enable_dhcp(interface) if not success: raise HTTPException(status_code=500, detail="Failed to enable DHCP") return { "success": True, "message": f"DHCP enabled for {interface}", } except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to enable DHCP: {e}")