Initial commit - Phase 3/4

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
michael
2026-01-06 13:45:29 -08:00
parent 1da6730735
commit 4f35df1781
323 changed files with 98287 additions and 1195 deletions

View File

@@ -1,15 +1,13 @@
"""WiFi management API endpoints."""
"""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 ..managers.wifi_manager import (
wifi_manager,
WiFiMode,
WiFiStatus,
WiFiNetwork,
WiFiInterface,
)
from ..services.container import container
router = APIRouter()
@@ -37,7 +35,7 @@ class WiFiNetworkResponse(BaseModel):
class SetModeRequest(BaseModel):
"""Request to set WiFi mode."""
mode: WiFiMode
mode: str # "ap", "client", "dual", "auto", "off"
class ConnectRequest(BaseModel):
@@ -48,213 +46,217 @@ class ConnectRequest(BaseModel):
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.
Returns WiFi mode, interface information, and connection status.
Uses WiFiService for business logic.
"""
try:
status: WiFiStatus = await wifi_manager.get_status()
result = await container.wifi_service.get_status()
return WiFiStatusResponse(
mode=status.mode.value,
interfaces=[
{
"name": iface.name,
"mac": iface.mac,
"is_usb": iface.is_usb,
"is_up": iface.is_up,
"connected": iface.connected,
"ssid": iface.ssid,
"ip_address": iface.ip_address,
}
for iface in status.interfaces
],
current_ssid=status.current_ssid,
current_ip=status.current_ip,
ap_ssid=status.ap_ssid,
ap_ip=status.ap_ip,
supports_dual=status.supports_dual,
if not result.success:
raise HTTPException(
status_code=_service_error_to_http_status(result.error.code),
detail=result.error.message
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get WiFi status: {e}")
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
Returns:
List of available networks
"""
try:
networks = await wifi_manager.scan_networks(interface)
result = await container.wifi_service.scan_networks(interface)
return [
WiFiNetworkResponse(
ssid=net.ssid,
bssid=net.bssid,
signal_strength=net.signal_strength,
frequency=net.frequency,
encrypted=net.encrypted,
in_use=net.in_use,
)
for net in networks
]
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to scan networks: {e}")
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)
Returns:
Success status
"""
try:
success = await wifi_manager.set_mode(request.mode)
result = await container.wifi_service.set_mode(request.mode)
if not success:
raise HTTPException(status_code=500, detail="Failed to set WiFi 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": f"WiFi mode set to {request.mode.value}",
"mode": request.mode.value,
}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to set WiFi mode: {e}")
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)
Returns:
Success status
"""
try:
success = await wifi_manager.connect_to_network(
ssid=request.ssid,
password=request.password,
interface=request.interface,
hidden=request.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
)
if not success:
raise HTTPException(status_code=500, detail="Failed to connect to network")
return {
"success": True,
"message": f"Connected to {request.ssid}",
"ssid": request.ssid,
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to connect to network: {e}")
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.
Returns:
List of WiFi interfaces with their status
Uses WiFiService for business logic.
"""
try:
interfaces = await wifi_manager.detect_interfaces()
result = await container.wifi_service.get_status()
return {
"interfaces": [
{
"name": iface.name,
"mac": iface.mac,
"is_usb": iface.is_usb,
"is_up": iface.is_up,
"connected": iface.connected,
"ssid": iface.ssid,
"ip_address": iface.ip_address,
}
for iface in interfaces
],
"supports_dual": len(interfaces) >= 2,
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get interfaces: {e}")
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)
Returns:
Success status
"""
try:
success = await wifi_manager.disconnect_from_network(interface)
result = await container.wifi_service.disconnect(interface)
if not success:
raise HTTPException(status_code=500, detail="Failed to disconnect")
if not result.success:
raise HTTPException(
status_code=_service_error_to_http_status(result.error.code),
detail=result.error.message
)
return {
"success": True,
"message": "Disconnected from network",
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to disconnect: {e}")
return {
"success": True,
"message": result.data["message"]
}
@router.get("/saved")
async def get_saved_networks():
"""Get list of saved networks.
Returns:
List of saved networks
Uses WiFiService for business logic.
"""
try:
networks = await wifi_manager.get_saved_networks()
return {"networks": networks}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get saved networks: {e}")
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
Returns:
Success status
"""
try:
success = await wifi_manager.forget_network(ssid)
result = await container.wifi_service.forget_network(ssid)
if not success:
raise HTTPException(status_code=404, detail=f"Network {ssid} not found")
if not result.success:
raise HTTPException(
status_code=_service_error_to_http_status(result.error.code),
detail=result.error.message
)
return {
"success": True,
"message": f"Forgot network {ssid}",
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to forget network: {e}")
return {
"success": True,
"message": result.data["message"]
}
@router.post("/static-ip")
@@ -267,18 +269,18 @@ async def set_static_ip(
):
"""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)
Returns:
Success status
"""
try:
success = await wifi_manager.set_static_ip(
success = await container.wifi_manager.set_static_ip(
interface, ip_address, netmask, gateway, dns
)
@@ -297,14 +299,14 @@ async def set_static_ip(
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
Returns:
Success status
"""
try:
success = await wifi_manager.enable_dhcp(interface)
success = await container.wifi_manager.enable_dhcp(interface)
if not success:
raise HTTPException(status_code=500, detail="Failed to enable DHCP")