🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
352 lines
11 KiB
Python
352 lines
11 KiB
Python
"""WiFi Service Layer.
|
|
|
|
This service provides WiFi management operations that can be consumed by
|
|
multiple interfaces (REST API, BLE GATT, etc.).
|
|
"""
|
|
from typing import Optional, List, Dict, Any
|
|
|
|
from ..managers.wifi_manager import WiFiManager, WiFiMode, WiFiStatus, WiFiNetwork
|
|
from .pm3_service import PM3ServiceError, PM3ServiceResult
|
|
|
|
|
|
class WiFiService:
|
|
"""WiFi service layer for network management.
|
|
|
|
This service can be used by multiple interfaces:
|
|
- REST API (FastAPI endpoints)
|
|
- BLE GATT (Bluetooth handlers)
|
|
- Plugin system (future)
|
|
|
|
It encapsulates:
|
|
- Network scanning
|
|
- Connection management
|
|
- WiFi mode switching
|
|
- Status queries
|
|
"""
|
|
|
|
def __init__(self, wifi_manager: Optional[WiFiManager] = None):
|
|
"""Initialize WiFi service.
|
|
|
|
Args:
|
|
wifi_manager: WiFi manager instance (creates new if not provided)
|
|
"""
|
|
self.wifi_manager = wifi_manager or WiFiManager()
|
|
|
|
async def get_status(self) -> PM3ServiceResult:
|
|
"""Get current WiFi status.
|
|
|
|
Returns:
|
|
PM3ServiceResult with WiFi status (mode, interfaces, connections)
|
|
"""
|
|
try:
|
|
status = await self.wifi_manager.get_status()
|
|
|
|
return PM3ServiceResult(
|
|
success=True,
|
|
data={
|
|
"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,
|
|
"mode": iface.mode # "AP" or "managed"
|
|
}
|
|
for iface in status.interfaces
|
|
],
|
|
"client": {
|
|
"ssid": status.current_ssid,
|
|
"ip": status.current_ip
|
|
} if status.current_ssid else None,
|
|
"access_point": {
|
|
"ssid": status.ap_ssid,
|
|
"ip": status.ap_ip
|
|
},
|
|
"supports_dual": status.supports_dual
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="wifi_status_error",
|
|
message="Failed to get WiFi status",
|
|
details=str(e)
|
|
)
|
|
)
|
|
|
|
async def scan_networks(
|
|
self,
|
|
interface: Optional[str] = None
|
|
) -> PM3ServiceResult:
|
|
"""Scan for available WiFi networks.
|
|
|
|
Args:
|
|
interface: Interface to scan with (default: auto-select)
|
|
|
|
Returns:
|
|
PM3ServiceResult with list of available networks
|
|
"""
|
|
try:
|
|
networks = await self.wifi_manager.scan_networks(interface)
|
|
|
|
return PM3ServiceResult(
|
|
success=True,
|
|
data={
|
|
"networks": [
|
|
{
|
|
"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
|
|
],
|
|
"count": len(networks)
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="wifi_scan_error",
|
|
message="Failed to scan for networks",
|
|
details=str(e)
|
|
)
|
|
)
|
|
|
|
async def connect(
|
|
self,
|
|
ssid: str,
|
|
password: Optional[str] = None,
|
|
interface: Optional[str] = None,
|
|
hidden: bool = False
|
|
) -> PM3ServiceResult:
|
|
"""Connect to a WiFi network.
|
|
|
|
Args:
|
|
ssid: Network SSID
|
|
password: Network password (if encrypted)
|
|
interface: Interface to use (default: auto-select)
|
|
hidden: Whether network is hidden
|
|
|
|
Returns:
|
|
PM3ServiceResult indicating connection success/failure
|
|
"""
|
|
try:
|
|
success = await self.wifi_manager.connect_to_network(
|
|
ssid=ssid,
|
|
password=password,
|
|
interface=interface,
|
|
hidden=hidden
|
|
)
|
|
|
|
if success:
|
|
# Get updated status to include new connection info
|
|
status = await self.wifi_manager.get_status()
|
|
|
|
return PM3ServiceResult(
|
|
success=True,
|
|
data={
|
|
"message": f"Successfully connected to {ssid}",
|
|
"ssid": ssid,
|
|
"ip": status.current_ip
|
|
}
|
|
)
|
|
else:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="connection_failed",
|
|
message=f"Failed to connect to {ssid}",
|
|
details="Connection attempt failed or timed out"
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="wifi_connect_error",
|
|
message=f"Error connecting to {ssid}",
|
|
details=str(e)
|
|
)
|
|
)
|
|
|
|
async def disconnect(
|
|
self,
|
|
interface: Optional[str] = None
|
|
) -> PM3ServiceResult:
|
|
"""Disconnect from current network.
|
|
|
|
Args:
|
|
interface: Interface to disconnect (default: first connected)
|
|
|
|
Returns:
|
|
PM3ServiceResult indicating success/failure
|
|
"""
|
|
try:
|
|
success = await self.wifi_manager.disconnect_from_network(interface)
|
|
|
|
if success:
|
|
return PM3ServiceResult(
|
|
success=True,
|
|
data={"message": "Disconnected from network"}
|
|
)
|
|
else:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="disconnect_failed",
|
|
message="Failed to disconnect",
|
|
details="No active connection found or disconnect failed"
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="wifi_disconnect_error",
|
|
message="Error disconnecting from network",
|
|
details=str(e)
|
|
)
|
|
)
|
|
|
|
async def set_mode(self, mode: str) -> PM3ServiceResult:
|
|
"""Set WiFi operation mode.
|
|
|
|
Args:
|
|
mode: WiFi mode ("ap", "client", "dual", "auto", "off")
|
|
|
|
Returns:
|
|
PM3ServiceResult indicating success/failure
|
|
"""
|
|
try:
|
|
# Validate and convert mode string to enum
|
|
try:
|
|
wifi_mode = WiFiMode(mode)
|
|
except ValueError:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="invalid_mode",
|
|
message=f"Invalid WiFi mode: {mode}",
|
|
details=f"Valid modes: ap, client, dual, auto, off"
|
|
)
|
|
)
|
|
|
|
success = await self.wifi_manager.set_mode(wifi_mode)
|
|
|
|
if success:
|
|
return PM3ServiceResult(
|
|
success=True,
|
|
data={
|
|
"message": f"WiFi mode set to {mode}",
|
|
"mode": mode
|
|
}
|
|
)
|
|
else:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="mode_change_failed",
|
|
message=f"Failed to set WiFi mode to {mode}",
|
|
details="Mode change operation failed"
|
|
)
|
|
)
|
|
|
|
except ValueError as e:
|
|
# Handle dual mode without USB adapter
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="mode_not_supported",
|
|
message=str(e),
|
|
details="Dual mode requires USB WiFi adapter"
|
|
)
|
|
)
|
|
except Exception as e:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="wifi_mode_error",
|
|
message="Error setting WiFi mode",
|
|
details=str(e)
|
|
)
|
|
)
|
|
|
|
async def get_saved_networks(self) -> PM3ServiceResult:
|
|
"""Get list of saved networks.
|
|
|
|
Returns:
|
|
PM3ServiceResult with list of saved networks
|
|
"""
|
|
try:
|
|
networks = await self.wifi_manager.get_saved_networks()
|
|
|
|
return PM3ServiceResult(
|
|
success=True,
|
|
data={
|
|
"networks": networks,
|
|
"count": len(networks)
|
|
}
|
|
)
|
|
|
|
except Exception as e:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="saved_networks_error",
|
|
message="Failed to get saved networks",
|
|
details=str(e)
|
|
)
|
|
)
|
|
|
|
async def forget_network(self, ssid: str) -> PM3ServiceResult:
|
|
"""Forget a saved network.
|
|
|
|
Args:
|
|
ssid: SSID of network to forget
|
|
|
|
Returns:
|
|
PM3ServiceResult indicating success/failure
|
|
"""
|
|
try:
|
|
success = await self.wifi_manager.forget_network(ssid)
|
|
|
|
if success:
|
|
return PM3ServiceResult(
|
|
success=True,
|
|
data={
|
|
"message": f"Network '{ssid}' forgotten",
|
|
"ssid": ssid
|
|
}
|
|
)
|
|
else:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="network_not_found",
|
|
message=f"Network '{ssid}' not found in saved networks"
|
|
)
|
|
)
|
|
|
|
except Exception as e:
|
|
return PM3ServiceResult(
|
|
success=False,
|
|
error=PM3ServiceError(
|
|
code="forget_network_error",
|
|
message=f"Error forgetting network '{ssid}'",
|
|
details=str(e)
|
|
)
|
|
)
|