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

@@ -0,0 +1,48 @@
"""BLE GATT server implementation for Dangerous Pi.
This module provides Bluetooth Low Energy (BLE) GATT server functionality
that reuses the service layer for business logic, ensuring consistency
with the REST API.
Components:
- DangerousPiGATTServer: GATT handlers that call service layer
- BlueZGATTAdapter: Bridges bless library to GATT handlers
- Characteristic UUIDs: Service and characteristic definitions
"""
from .gatt_server import DangerousPiGATTServer
from .characteristics import (
PM3CharacteristicUUIDs,
WiFiCharacteristicUUIDs,
SystemCharacteristicUUIDs,
UpdateCharacteristicUUIDs,
)
# BlueZ adapter (requires bless library)
try:
from .bluez_adapter import (
BlueZGATTAdapter,
get_ble_adapter,
start_ble_server,
stop_ble_server,
)
BLESS_AVAILABLE = True
except ImportError:
BlueZGATTAdapter = None
get_ble_adapter = None
start_ble_server = None
stop_ble_server = None
BLESS_AVAILABLE = False
__all__ = [
"DangerousPiGATTServer",
"PM3CharacteristicUUIDs",
"WiFiCharacteristicUUIDs",
"SystemCharacteristicUUIDs",
"UpdateCharacteristicUUIDs",
"BlueZGATTAdapter",
"get_ble_adapter",
"start_ble_server",
"stop_ble_server",
"BLESS_AVAILABLE",
]

View File

@@ -0,0 +1,457 @@
"""BlueZ GATT adapter using the bless library.
This module bridges our GATT server handlers to BlueZ via bless,
enabling BLE peripheral functionality on Linux.
Architecture:
bless BLEServer (handles BlueZ D-Bus)
|
BlueZGATTAdapter (this file - bridges handlers)
|
DangerousPiGATTServer (handlers call service layer)
|
Service Layer (business logic)
"""
import asyncio
import logging
import sys
import threading
from typing import Dict, Optional, Any, Union
from bless import (
BlessServer,
BlessGATTCharacteristic,
GATTCharacteristicProperties,
GATTAttributePermissions,
)
from .gatt_server import DangerousPiGATTServer
from .characteristics import (
PM3CharacteristicUUIDs,
WiFiCharacteristicUUIDs,
SystemCharacteristicUUIDs,
UpdateCharacteristicUUIDs,
)
logger = logging.getLogger(__name__)
# Device name for BLE advertising
DEFAULT_DEVICE_NAME = "Dangerous-Pi"
class BlueZGATTAdapter:
"""Adapter connecting bless BLE server to our GATT handlers.
This class:
- Initializes the bless BLE server
- Registers all services and characteristics via GATT dictionary
- Routes read/write requests to DangerousPiGATTServer handlers
- Sends notifications via bless
"""
def __init__(self, device_name: str = DEFAULT_DEVICE_NAME):
"""Initialize the BlueZ GATT adapter.
Args:
device_name: BLE device name for advertising
"""
self.device_name = device_name
self.server: Optional[BlessServer] = None
self.gatt_server = DangerousPiGATTServer()
self._is_running = False
self._loop: Optional[asyncio.AbstractEventLoop] = None
# Platform-specific trigger for async coordination
self._trigger: Union[asyncio.Event, threading.Event]
if sys.platform in ["darwin", "win32"]:
self._trigger = threading.Event()
else:
self._trigger = asyncio.Event()
@property
def is_running(self) -> bool:
"""Check if BLE server is running."""
return self._is_running
def _build_gatt_dict(self) -> Dict:
"""Build the GATT dictionary for bless.
Returns:
Dictionary mapping service UUIDs to characteristic definitions
"""
return {
# PM3 Service
PM3CharacteristicUUIDs.SERVICE: {
PM3CharacteristicUUIDs.COMMAND_WRITE: {
"Properties": GATTCharacteristicProperties.write,
"Permissions": GATTAttributePermissions.writeable,
"Value": None,
},
PM3CharacteristicUUIDs.COMMAND_RESULT: {
"Properties": (
GATTCharacteristicProperties.read |
GATTCharacteristicProperties.notify
),
"Permissions": GATTAttributePermissions.readable,
"Value": bytearray(b'{}'),
},
PM3CharacteristicUUIDs.STATUS: {
"Properties": GATTCharacteristicProperties.read,
"Permissions": GATTAttributePermissions.readable,
"Value": bytearray(b'{"connected": false}'),
},
PM3CharacteristicUUIDs.SESSION_CREATE: {
"Properties": GATTCharacteristicProperties.write,
"Permissions": GATTAttributePermissions.writeable,
"Value": None,
},
PM3CharacteristicUUIDs.SESSION_RELEASE: {
"Properties": GATTCharacteristicProperties.write,
"Permissions": GATTAttributePermissions.writeable,
"Value": None,
},
},
# WiFi Service
WiFiCharacteristicUUIDs.SERVICE: {
WiFiCharacteristicUUIDs.STATUS: {
"Properties": GATTCharacteristicProperties.read,
"Permissions": GATTAttributePermissions.readable,
"Value": bytearray(b'{"mode": "unknown"}'),
},
WiFiCharacteristicUUIDs.SCAN: {
"Properties": (
GATTCharacteristicProperties.write |
GATTCharacteristicProperties.notify
),
"Permissions": (
GATTAttributePermissions.readable |
GATTAttributePermissions.writeable
),
"Value": None,
},
WiFiCharacteristicUUIDs.CONNECT: {
"Properties": GATTCharacteristicProperties.write,
"Permissions": GATTAttributePermissions.writeable,
"Value": None,
},
WiFiCharacteristicUUIDs.MODE: {
"Properties": (
GATTCharacteristicProperties.read |
GATTCharacteristicProperties.write
),
"Permissions": (
GATTAttributePermissions.readable |
GATTAttributePermissions.writeable
),
"Value": bytearray(b'client'),
},
},
# System Service
SystemCharacteristicUUIDs.SERVICE: {
SystemCharacteristicUUIDs.INFO: {
"Properties": (
GATTCharacteristicProperties.read |
GATTCharacteristicProperties.notify
),
"Permissions": GATTAttributePermissions.readable,
"Value": bytearray(b'{}'),
},
SystemCharacteristicUUIDs.SHUTDOWN: {
"Properties": GATTCharacteristicProperties.write,
"Permissions": GATTAttributePermissions.writeable,
"Value": None,
},
SystemCharacteristicUUIDs.RESTART: {
"Properties": GATTCharacteristicProperties.write,
"Permissions": GATTAttributePermissions.writeable,
"Value": None,
},
},
# Update Service
UpdateCharacteristicUUIDs.SERVICE: {
UpdateCharacteristicUUIDs.CHECK: {
"Properties": (
GATTCharacteristicProperties.write |
GATTCharacteristicProperties.notify
),
"Permissions": (
GATTAttributePermissions.readable |
GATTAttributePermissions.writeable
),
"Value": None,
},
UpdateCharacteristicUUIDs.PROGRESS: {
"Properties": (
GATTCharacteristicProperties.read |
GATTCharacteristicProperties.notify
),
"Permissions": GATTAttributePermissions.readable,
"Value": bytearray(b'{"progress": 0}'),
},
},
}
def _on_read(self, characteristic: BlessGATTCharacteristic) -> bytearray:
"""Handle BLE read request.
Note: This callback is called synchronously from the D-Bus event handler.
We cannot block on async operations here as it would deadlock the event loop.
Instead, we return cached values and schedule async updates in the background.
Args:
characteristic: The characteristic being read
Returns:
Characteristic value as bytearray
"""
uuid = str(characteristic.uuid).lower()
logger.info("BLE Read request for characteristic %s", uuid)
# Return the current cached value (synchronously)
# Async handlers update these values in the background
if characteristic.value:
logger.info("Read returning cached: %s", characteristic.value[:50] if len(characteristic.value) > 50 else characteristic.value)
return characteristic.value
# Return default JSON if no cached value
default_value = bytearray(b'{"status": "initializing"}')
logger.info("Read returning default: %s", default_value)
return default_value
def _on_write(self, characteristic: BlessGATTCharacteristic, value: Any):
"""Handle BLE write request.
Args:
characteristic: The characteristic being written
value: Value being written
"""
uuid = str(characteristic.uuid).lower() # Use lowercase to match our UUID format
logger.info("BLE Write request for characteristic %s: %s", uuid, value)
# Update the characteristic value
characteristic.value = value
handler = self.gatt_server.get_characteristic_handler(uuid)
if handler and handler.write_handler:
try:
# Convert value to bytes if needed
if isinstance(value, bytearray):
data = bytes(value)
elif isinstance(value, bytes):
data = value
else:
data = bytes(value)
# Run async handler in event loop
if self._loop and self._loop.is_running():
asyncio.run_coroutine_threadsafe(
handler.write_handler(data),
self._loop
)
except Exception as e:
logger.error("Error handling write for %s: %s", uuid, e)
def _on_subscribe(self, characteristic: BlessGATTCharacteristic, **kwargs):
"""Handle subscription to characteristic notifications."""
logger.info("Client subscribed to %s", characteristic.uuid)
def _on_unsubscribe(self, characteristic: BlessGATTCharacteristic, **kwargs):
"""Handle unsubscription from characteristic notifications."""
logger.info("Client unsubscribed from %s", characteristic.uuid)
async def start(self) -> bool:
"""Start the BLE GATT server.
Returns:
True if started successfully, False otherwise
"""
if self._is_running:
logger.warning("BLE server already running")
return True
try:
self._loop = asyncio.get_running_loop()
# Create bless server
self.server = BlessServer(name=self.device_name, loop=self._loop)
# Set up callbacks (bless 0.3.0+ API)
self.server.read_request_func = self._on_read
self.server.write_request_func = self._on_write
# Build and add GATT structure
gatt = self._build_gatt_dict()
await self.server.add_gatt(gatt)
# Start the server (begins advertising)
await self.server.start()
# Start GATT server handlers
await self.gatt_server.start()
# Register notification callbacks
self._setup_notification_callbacks()
self._is_running = True
logger.info("BLE GATT server started, advertising as '%s'", self.device_name)
return True
except Exception as e:
logger.error("Failed to start BLE server: %s", e)
import traceback
traceback.print_exc()
self._is_running = False
return False
def _setup_notification_callbacks(self):
"""Set up notification callbacks for characteristics that support notify."""
notify_chars = [
PM3CharacteristicUUIDs.COMMAND_RESULT,
WiFiCharacteristicUUIDs.SCAN,
SystemCharacteristicUUIDs.INFO,
UpdateCharacteristicUUIDs.CHECK,
UpdateCharacteristicUUIDs.PROGRESS,
]
for uuid in notify_chars:
self._register_notification_callback(uuid)
def _register_notification_callback(self, uuid: str):
"""Register notification callback for a characteristic.
Args:
uuid: Characteristic UUID
"""
async def send_notification(value: bytes):
if self.server and self._is_running:
try:
char = self.server.get_characteristic(uuid)
if char:
char.value = bytearray(value)
service_uuid = self._get_service_uuid_for_characteristic(uuid)
# update_value is not async in bless 0.3+
self.server.update_value(service_uuid, uuid)
logger.debug("Notification sent for %s", uuid)
except Exception as e:
logger.error("Failed to send notification for %s: %s", uuid, e)
self.gatt_server.register_notification_callback(uuid, send_notification)
def _get_service_uuid_for_characteristic(self, char_uuid: str) -> str:
"""Get the service UUID that contains a characteristic.
Args:
char_uuid: Characteristic UUID
Returns:
Service UUID
"""
# Check each service's characteristics
if char_uuid in [
PM3CharacteristicUUIDs.COMMAND_WRITE,
PM3CharacteristicUUIDs.COMMAND_RESULT,
PM3CharacteristicUUIDs.STATUS,
PM3CharacteristicUUIDs.SESSION_CREATE,
PM3CharacteristicUUIDs.SESSION_RELEASE,
]:
return PM3CharacteristicUUIDs.SERVICE
elif char_uuid in [
WiFiCharacteristicUUIDs.STATUS,
WiFiCharacteristicUUIDs.SCAN,
WiFiCharacteristicUUIDs.CONNECT,
WiFiCharacteristicUUIDs.MODE,
]:
return WiFiCharacteristicUUIDs.SERVICE
elif char_uuid in [
SystemCharacteristicUUIDs.INFO,
SystemCharacteristicUUIDs.SHUTDOWN,
SystemCharacteristicUUIDs.RESTART,
]:
return SystemCharacteristicUUIDs.SERVICE
elif char_uuid in [
UpdateCharacteristicUUIDs.CHECK,
UpdateCharacteristicUUIDs.PROGRESS,
]:
return UpdateCharacteristicUUIDs.SERVICE
else:
return PM3CharacteristicUUIDs.SERVICE # Default
async def stop(self):
"""Stop the BLE GATT server."""
if not self._is_running:
return
try:
await self.gatt_server.stop()
if self.server:
await self.server.stop()
self.server = None
self._is_running = False
logger.info("BLE server stopped")
except Exception as e:
logger.error("Error stopping BLE server: %s", e)
async def send_notification(self, uuid: str, value: bytes) -> bool:
"""Send a notification for a characteristic.
Args:
uuid: Characteristic UUID
value: Notification value
Returns:
True if sent successfully
"""
if not self.server or not self._is_running:
return False
try:
char = self.server.get_characteristic(uuid)
if char:
char.value = bytearray(value)
service_uuid = self._get_service_uuid_for_characteristic(uuid)
# update_value is not async in bless 0.3+
self.server.update_value(service_uuid, uuid)
return True
except Exception as e:
logger.error("Error sending notification for %s: %s", uuid, e)
return False
# Singleton instance for application use
_adapter_instance: Optional[BlueZGATTAdapter] = None
def get_ble_adapter() -> BlueZGATTAdapter:
"""Get or create the singleton BLE adapter instance.
Returns:
BlueZGATTAdapter instance
"""
global _adapter_instance
if _adapter_instance is None:
_adapter_instance = BlueZGATTAdapter()
return _adapter_instance
async def start_ble_server(device_name: str = DEFAULT_DEVICE_NAME) -> bool:
"""Start the BLE GATT server.
Args:
device_name: BLE device name for advertising
Returns:
True if started successfully
"""
adapter = get_ble_adapter()
adapter.device_name = device_name
return await adapter.start()
async def stop_ble_server():
"""Stop the BLE GATT server."""
adapter = get_ble_adapter()
await adapter.stop()

View File

@@ -0,0 +1,112 @@
"""GATT Characteristic UUIDs for Dangerous Pi BLE interface.
This module defines the UUIDs for all GATT services and characteristics
used by the Dangerous Pi BLE interface.
UUID Namespace: Dangerous Pi uses custom 128-bit UUIDs.
Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12 hex chars)
Services are differentiated by the 5th group:
- PM3: 0000xxxx (0000-000f)
- WiFi: 0001xxxx (0010-001f)
- System: 0002xxxx (0020-002f)
- Update: 0003xxxx (0030-003f)
"""
from dataclasses import dataclass
def _uuid(suffix: str) -> str:
"""Generate a Dangerous Pi UUID with the given 4-char suffix.
Args:
suffix: 4-character hex suffix (e.g., "0001", "0010")
Returns:
Full 128-bit UUID string
"""
return f"d4c3b2a1-0000-1000-8000-00805f9b{suffix}"
@dataclass
class PM3CharacteristicUUIDs:
"""PM3 GATT Service and Characteristics.
Service for Proxmark3 operations (command execution, status).
"""
# Service UUID
SERVICE = _uuid("0000")
# Characteristics
COMMAND_WRITE = _uuid("0001") # Write: Execute PM3 command
COMMAND_RESULT = _uuid("0002") # Notify: Command result
STATUS = _uuid("0003") # Read: Get PM3 status
SESSION_CREATE = _uuid("0004") # Write: Create session
SESSION_RELEASE = _uuid("0005") # Write: Release session
SESSION_INFO = _uuid("0006") # Read: Get session info
@dataclass
class WiFiCharacteristicUUIDs:
"""WiFi GATT Service and Characteristics.
Service for WiFi network management (scan, connect, status).
"""
# Service UUID
SERVICE = _uuid("0010")
# Characteristics
STATUS = _uuid("0011") # Read: Get WiFi status
SCAN = _uuid("0012") # Write: Trigger scan, Notify: Results
CONNECT = _uuid("0013") # Write: Connect to network
DISCONNECT = _uuid("0014") # Write: Disconnect
MODE = _uuid("0015") # Read/Write: WiFi mode
SAVED_NETWORKS = _uuid("0016") # Read: Get saved networks
FORGET_NETWORK = _uuid("0017") # Write: Forget network
@dataclass
class SystemCharacteristicUUIDs:
"""System GATT Service and Characteristics.
Service for system operations (info, shutdown, restart).
"""
# Service UUID
SERVICE = _uuid("0020")
# Characteristics
INFO = _uuid("0021") # Read: Get system info
SHUTDOWN = _uuid("0022") # Write: Initiate shutdown
RESTART = _uuid("0023") # Write: Initiate restart
LOGS = _uuid("0024") # Read: Get service logs
@dataclass
class UpdateCharacteristicUUIDs:
"""Update GATT Service and Characteristics.
Service for software update management.
"""
# Service UUID
SERVICE = _uuid("0030")
# Characteristics
CHECK = _uuid("0031") # Write: Check for updates, Notify: Result
DOWNLOAD = _uuid("0032") # Write: Download update
INSTALL = _uuid("0033") # Write: Install update
PROGRESS = _uuid("0034") # Read/Notify: Update progress
RELEASE_NOTES = _uuid("0035") # Read: Get release notes
# Characteristic properties
class CharacteristicProperties:
"""Standard GATT characteristic properties."""
READ = "read"
WRITE = "write"
WRITE_WITHOUT_RESPONSE = "write-without-response"
NOTIFY = "notify"
INDICATE = "indicate"
# Characteristic descriptors
CHARACTERISTIC_USER_DESCRIPTION_UUID = "00002901-0000-1000-8000-00805f9b34fb"
CLIENT_CHARACTERISTIC_CONFIG_UUID = "00002902-0000-1000-8000-00805f9b34fb"

View File

@@ -0,0 +1,719 @@
"""GATT Server implementation for Dangerous Pi.
This GATT server provides BLE access to all Dangerous Pi functionality by
reusing the service layer. This ensures identical behavior between REST API
and BLE interface - NO CODE DUPLICATION!
Architecture:
BLE GATT Characteristic Handler
Service Layer (PM3Service, WiFiService, etc.)
Managers/Workers (PM3Worker, WiFiManager, etc.)
The handlers are thin adapters, just like REST endpoints.
"""
import asyncio
import json
import logging
from typing import Dict, Any, Optional, Callable
from dataclasses import dataclass
from ..services.container import container
from .characteristics import (
PM3CharacteristicUUIDs,
WiFiCharacteristicUUIDs,
SystemCharacteristicUUIDs,
UpdateCharacteristicUUIDs,
)
logger = logging.getLogger(__name__)
@dataclass
class CharacteristicHandler:
"""Configuration for a GATT characteristic handler."""
uuid: str
properties: list # ["read", "write", "notify"]
read_handler: Optional[Callable] = None
write_handler: Optional[Callable] = None
description: str = ""
class DangerousPiGATTServer:
"""GATT server for Dangerous Pi BLE interface.
This server exposes Dangerous Pi functionality over BLE by reusing
the service layer. All business logic comes from services, ensuring
consistency with the REST API.
Key Features:
- Uses ServiceContainer for all operations (same as REST!)
- Converts BLE data formats to/from service calls
- Sends notifications for async operations
- Zero business logic duplication
"""
def __init__(self):
"""Initialize GATT server."""
self.is_running = False
self._notification_callbacks: Dict[str, Callable] = {}
self._characteristic_handlers: Dict[str, CharacteristicHandler] = {}
# Register all characteristic handlers
self._register_pm3_characteristics()
self._register_wifi_characteristics()
self._register_system_characteristics()
self._register_update_characteristics()
logger.info("GATT server initialized with %d characteristics",
len(self._characteristic_handlers))
# ========================================================================
# PM3 Characteristic Handlers
# ========================================================================
def _register_pm3_characteristics(self):
"""Register PM3 GATT characteristics.
All handlers delegate to PM3Service - NO business logic here!
"""
self._characteristic_handlers.update({
PM3CharacteristicUUIDs.COMMAND_WRITE: CharacteristicHandler(
uuid=PM3CharacteristicUUIDs.COMMAND_WRITE,
properties=["write"],
write_handler=self._handle_pm3_command_write,
description="Execute PM3 command"
),
PM3CharacteristicUUIDs.STATUS: CharacteristicHandler(
uuid=PM3CharacteristicUUIDs.STATUS,
properties=["read"],
read_handler=self._handle_pm3_status_read,
description="Get PM3 status"
),
PM3CharacteristicUUIDs.SESSION_CREATE: CharacteristicHandler(
uuid=PM3CharacteristicUUIDs.SESSION_CREATE,
properties=["write"],
write_handler=self._handle_session_create_write,
description="Create PM3 session"
),
PM3CharacteristicUUIDs.SESSION_RELEASE: CharacteristicHandler(
uuid=PM3CharacteristicUUIDs.SESSION_RELEASE,
properties=["write"],
write_handler=self._handle_session_release_write,
description="Release PM3 session"
),
})
async def _handle_pm3_command_write(self, value: bytes) -> Dict[str, Any]:
"""Handle PM3 command execution via BLE.
This is a THIN ADAPTER - delegates to PM3Service!
Args:
value: JSON bytes: {"command": "hw version", "session_id": "..."}
Returns:
Response dict to send via notification
"""
try:
# Parse BLE request
data = json.loads(value.decode('utf-8'))
command = data.get("command")
session_id = data.get("session_id")
if not command:
return {
"success": False,
"error": {"code": "invalid_request", "message": "Command required"}
}
# ✅ REUSE PM3Service - same logic as REST API!
result = await container.pm3_service.execute_command(
command=command,
session_id=session_id
)
# Convert service result to BLE response
if result.success:
response = {
"success": True,
"output": result.data["output"],
"command": result.data["command"]
}
else:
response = {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
# Send notification with result
await self._notify_characteristic(
PM3CharacteristicUUIDs.COMMAND_RESULT,
json.dumps(response).encode('utf-8')
)
return response
except json.JSONDecodeError:
return {
"success": False,
"error": {"code": "invalid_json", "message": "Invalid JSON"}
}
except Exception as e:
logger.error("Error handling PM3 command: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
async def _handle_pm3_status_read(self) -> bytes:
"""Handle PM3 status read via BLE.
This is a THIN ADAPTER - delegates to PM3Service!
Returns:
JSON bytes with PM3 status
"""
try:
# ✅ REUSE PM3Service - same logic as REST API!
result = await container.pm3_service.get_status()
if result.success:
# Handle both single-device and multi-device responses
if "devices" in result.data:
# Multi-device mode: summarize status
devices = result.data["devices"]
connected_count = sum(1 for d in devices if d.get("connected"))
response = {
"success": True,
"device_count": len(devices),
"connected_count": connected_count,
"devices": devices
}
else:
# Legacy single-device mode
response = {
"success": True,
"connected": result.data.get("connected", False),
"device_path": result.data.get("device_path"),
"version": result.data.get("version"),
"session_active": result.data.get("session_active", False)
}
else:
response = {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
return json.dumps(response).encode('utf-8')
except Exception as e:
logger.error("Error getting PM3 status: %s", e)
return json.dumps({
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}).encode('utf-8')
async def _handle_session_create_write(self, value: bytes) -> Dict[str, Any]:
"""Handle session creation via BLE.
Args:
value: JSON bytes: {"force_takeover": false}
"""
try:
data = json.loads(value.decode('utf-8'))
force_takeover = data.get("force_takeover", False)
# ✅ REUSE PM3Service
result = container.pm3_service.create_session(
force_takeover=force_takeover
)
if result.success:
return {
"success": True,
"session_id": result.data["session_id"]
}
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error creating session: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
async def _handle_session_release_write(self, value: bytes) -> Dict[str, Any]:
"""Handle session release via BLE.
Args:
value: JSON bytes: {"session_id": "..."}
"""
try:
data = json.loads(value.decode('utf-8'))
session_id = data.get("session_id")
if not session_id:
return {
"success": False,
"error": {"code": "invalid_request", "message": "Session ID required"}
}
# ✅ REUSE PM3Service
result = container.pm3_service.release_session(session_id)
if result.success:
return {"success": True, "message": result.data["message"]}
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error releasing session: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
# ========================================================================
# WiFi Characteristic Handlers
# ========================================================================
def _register_wifi_characteristics(self):
"""Register WiFi GATT characteristics."""
self._characteristic_handlers.update({
WiFiCharacteristicUUIDs.STATUS: CharacteristicHandler(
uuid=WiFiCharacteristicUUIDs.STATUS,
properties=["read"],
read_handler=self._handle_wifi_status_read,
description="Get WiFi status"
),
WiFiCharacteristicUUIDs.SCAN: CharacteristicHandler(
uuid=WiFiCharacteristicUUIDs.SCAN,
properties=["write", "notify"],
write_handler=self._handle_wifi_scan_write,
description="Scan for WiFi networks"
),
WiFiCharacteristicUUIDs.CONNECT: CharacteristicHandler(
uuid=WiFiCharacteristicUUIDs.CONNECT,
properties=["write"],
write_handler=self._handle_wifi_connect_write,
description="Connect to WiFi network"
),
WiFiCharacteristicUUIDs.MODE: CharacteristicHandler(
uuid=WiFiCharacteristicUUIDs.MODE,
properties=["read", "write"],
read_handler=self._handle_wifi_mode_read,
write_handler=self._handle_wifi_mode_write,
description="WiFi mode"
),
})
async def _handle_wifi_status_read(self) -> bytes:
"""Handle WiFi status read via BLE."""
try:
# ✅ REUSE WiFiService
result = await container.wifi_service.get_status()
if result.success:
return json.dumps(result.data).encode('utf-8')
else:
return json.dumps({
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}).encode('utf-8')
except Exception as e:
logger.error("Error getting WiFi status: %s", e)
return json.dumps({
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}).encode('utf-8')
async def _handle_wifi_scan_write(self, value: bytes) -> Dict[str, Any]:
"""Handle WiFi scan request via BLE."""
try:
data = json.loads(value.decode('utf-8')) if value else {}
interface = data.get("interface")
# ✅ REUSE WiFiService
result = await container.wifi_service.scan_networks(interface)
if result.success:
# Send scan results via notification
await self._notify_characteristic(
WiFiCharacteristicUUIDs.SCAN,
json.dumps(result.data).encode('utf-8')
)
return {"success": True, "count": result.data["count"]}
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error scanning WiFi: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
async def _handle_wifi_connect_write(self, value: bytes) -> Dict[str, Any]:
"""Handle WiFi connect request via BLE."""
try:
data = json.loads(value.decode('utf-8'))
ssid = data.get("ssid")
password = data.get("password")
hidden = data.get("hidden", False)
if not ssid:
return {
"success": False,
"error": {"code": "invalid_request", "message": "SSID required"}
}
# ✅ REUSE WiFiService
result = await container.wifi_service.connect(
ssid=ssid,
password=password,
hidden=hidden
)
if result.success:
return {
"success": True,
"ssid": result.data["ssid"],
"ip": result.data.get("ip")
}
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error connecting to WiFi: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
async def _handle_wifi_mode_read(self) -> bytes:
"""Handle WiFi mode read via BLE."""
try:
result = await container.wifi_service.get_status()
if result.success:
return json.dumps({"mode": result.data["mode"]}).encode('utf-8')
else:
return json.dumps({
"success": False,
"error": {"code": result.error.code, "message": result.error.message}
}).encode('utf-8')
except Exception as e:
return json.dumps({
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}).encode('utf-8')
async def _handle_wifi_mode_write(self, value: bytes) -> Dict[str, Any]:
"""Handle WiFi mode change via BLE."""
try:
data = json.loads(value.decode('utf-8'))
mode = data.get("mode")
if not mode:
return {
"success": False,
"error": {"code": "invalid_request", "message": "Mode required"}
}
# ✅ REUSE WiFiService
result = await container.wifi_service.set_mode(mode)
if result.success:
return {"success": True, "mode": result.data["mode"]}
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error setting WiFi mode: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
# ========================================================================
# System Characteristic Handlers
# ========================================================================
def _register_system_characteristics(self):
"""Register System GATT characteristics."""
self._characteristic_handlers.update({
SystemCharacteristicUUIDs.INFO: CharacteristicHandler(
uuid=SystemCharacteristicUUIDs.INFO,
properties=["read"],
read_handler=self._handle_system_info_read,
description="Get system information"
),
SystemCharacteristicUUIDs.SHUTDOWN: CharacteristicHandler(
uuid=SystemCharacteristicUUIDs.SHUTDOWN,
properties=["write"],
write_handler=self._handle_system_shutdown_write,
description="Initiate system shutdown"
),
SystemCharacteristicUUIDs.RESTART: CharacteristicHandler(
uuid=SystemCharacteristicUUIDs.RESTART,
properties=["write"],
write_handler=self._handle_system_restart_write,
description="Initiate system restart"
),
})
async def _handle_system_info_read(self) -> bytes:
"""Handle system info read via BLE."""
try:
# ✅ REUSE SystemService
result = await container.system_service.get_info()
if result.success:
return json.dumps(result.data).encode('utf-8')
else:
return json.dumps({
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}).encode('utf-8')
except Exception as e:
logger.error("Error getting system info: %s", e)
return json.dumps({
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}).encode('utf-8')
async def _handle_system_shutdown_write(self, value: bytes) -> Dict[str, Any]:
"""Handle system shutdown request via BLE."""
try:
data = json.loads(value.decode('utf-8')) if value else {}
delay = data.get("delay", 0)
# ✅ REUSE SystemService
result = await container.system_service.shutdown(delay=delay)
if result.success:
return {"success": True, "message": result.data["message"]}
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error initiating shutdown: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
async def _handle_system_restart_write(self, value: bytes) -> Dict[str, Any]:
"""Handle system restart request via BLE."""
try:
data = json.loads(value.decode('utf-8')) if value else {}
delay = data.get("delay", 0)
# ✅ REUSE SystemService
result = await container.system_service.restart(delay=delay)
if result.success:
return {"success": True, "message": result.data["message"]}
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error initiating restart: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
# ========================================================================
# Update Characteristic Handlers
# ========================================================================
def _register_update_characteristics(self):
"""Register Update GATT characteristics."""
self._characteristic_handlers.update({
UpdateCharacteristicUUIDs.CHECK: CharacteristicHandler(
uuid=UpdateCharacteristicUUIDs.CHECK,
properties=["write", "notify"],
write_handler=self._handle_update_check_write,
description="Check for updates"
),
UpdateCharacteristicUUIDs.PROGRESS: CharacteristicHandler(
uuid=UpdateCharacteristicUUIDs.PROGRESS,
properties=["read", "notify"],
read_handler=self._handle_update_progress_read,
description="Get update progress"
),
})
async def _handle_update_check_write(self, value: bytes) -> Dict[str, Any]:
"""Handle update check request via BLE."""
try:
# ✅ REUSE UpdateService
result = await container.update_service.check_for_updates()
if result.success:
# Send result via notification
await self._notify_characteristic(
UpdateCharacteristicUUIDs.CHECK,
json.dumps(result.data).encode('utf-8')
)
return result.data
else:
return {
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}
except Exception as e:
logger.error("Error checking for updates: %s", e)
return {
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}
async def _handle_update_progress_read(self) -> bytes:
"""Handle update progress read via BLE."""
try:
# ✅ REUSE UpdateService
result = await container.update_service.get_progress()
if result.success:
return json.dumps(result.data).encode('utf-8')
else:
return json.dumps({
"success": False,
"error": {
"code": result.error.code,
"message": result.error.message
}
}).encode('utf-8')
except Exception as e:
logger.error("Error getting update progress: %s", e)
return json.dumps({
"success": False,
"error": {"code": "internal_error", "message": str(e)}
}).encode('utf-8')
# ========================================================================
# GATT Server Management
# ========================================================================
async def start(self):
"""Start the GATT server."""
logger.info("Starting Dangerous Pi GATT server")
self.is_running = True
logger.info("GATT server started with %d characteristics",
len(self._characteristic_handlers))
async def stop(self):
"""Stop the GATT server."""
logger.info("Stopping Dangerous Pi GATT server")
self.is_running = False
logger.info("GATT server stopped")
def get_characteristic_handler(self, uuid: str) -> Optional[CharacteristicHandler]:
"""Get handler for a characteristic UUID.
Args:
uuid: Characteristic UUID
Returns:
CharacteristicHandler or None if not found
"""
return self._characteristic_handlers.get(uuid)
async def _notify_characteristic(self, uuid: str, value: bytes):
"""Send notification for a characteristic.
Args:
uuid: Characteristic UUID
value: Notification value (bytes)
"""
if uuid in self._notification_callbacks:
try:
await self._notification_callbacks[uuid](value)
logger.debug("Sent notification for %s", uuid)
except Exception as e:
logger.error("Error sending notification for %s: %s", uuid, e)
else:
logger.debug("No notification callback registered for %s", uuid)
def register_notification_callback(self, uuid: str, callback: Callable):
"""Register callback for sending notifications.
Args:
uuid: Characteristic UUID
callback: Async callable that sends the notification
"""
self._notification_callbacks[uuid] = callback
logger.debug("Registered notification callback for %s", uuid)
def get_all_characteristics(self) -> Dict[str, CharacteristicHandler]:
"""Get all registered characteristic handlers.
Returns:
Dict mapping UUID to CharacteristicHandler
"""
return self._characteristic_handlers.copy()