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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user