Files
pi-pm3/app/backend/api/wifi.py
michael 4f35df1781 Initial commit - Phase 3/4
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 13:46:22 -08:00

320 lines
8.2 KiB
Python

"""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}")