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:
351
app/backend/services/wifi_service.py
Normal file
351
app/backend/services/wifi_service.py
Normal file
@@ -0,0 +1,351 @@
|
||||
"""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)
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user