🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
12 KiB
Dangerous Pi - BLE GATT Server Guide
Status: ✅ Implemented - Ready for Integration Architecture: Service Layer Pattern - Zero Code Duplication
🎯 Overview
The Dangerous Pi BLE GATT server provides Bluetooth Low Energy access to all Dangerous Pi functionality by reusing the exact same service layer as the REST API. This ensures:
✅ Zero code duplication - Business logic written once ✅ Identical behavior - BLE and REST behave exactly the same ✅ Easy testing - Service tests cover both interfaces ✅ Consistent errors - Same error codes and handling
🏗️ Architecture
┌─────────────────────────────────────────┐
│ Mobile App (React Native / Flutter) │
└──────────────┬──────────────────────────┘
│ BLE GATT Protocol
▼
┌─────────────────────────────────────────┐
│ BLE GATT Server (gatt_server.py) │
│ - Characteristic handlers │
│ - JSON encoding/decoding │
│ - Notification management │
│ - THIN ADAPTERS only │
└──────────────┬──────────────────────────┘
│ Delegates to
▼
┌─────────────────────────────────────────┐
│ Service Layer (SHARED with REST!) │
│ - PM3Service │
│ - WiFiService │
│ - SystemService │
│ - UpdateService │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Managers/Workers │
│ - PM3Worker, WiFiManager, etc. │
└─────────────────────────────────────────┘
Key Point: BLE handlers and REST endpoints are BOTH thin adapters. They share 100% of business logic through the service layer.
📡 GATT Services & Characteristics
PM3 Service
Service UUID: 00000000-1234-5678-1234-56789abcdef0
| Characteristic | UUID | Properties | Description |
|---|---|---|---|
| Command Write | ...def1 | Write | Execute PM3 command |
| Command Result | ...def2 | Notify | Command execution result |
| Status | ...def3 | Read | Get PM3 status |
| Session Create | ...def4 | Write | Create new session |
| Session Release | ...def5 | Write | Release session |
| Session Info | ...def6 | Read | Get session information |
WiFi Service
Service UUID: 00000000-1234-5678-1234-56789abcdef10
| Characteristic | UUID | Properties | Description |
|---|---|---|---|
| Status | ...def11 | Read | Get WiFi status |
| Scan | ...def12 | Write, Notify | Scan for networks |
| Connect | ...def13 | Write | Connect to network |
| Disconnect | ...def14 | Write | Disconnect from network |
| Mode | ...def15 | Read, Write | Get/Set WiFi mode |
| Saved Networks | ...def16 | Read | List saved networks |
| Forget Network | ...def17 | Write | Forget saved network |
System Service
Service UUID: 00000000-1234-5678-1234-56789abcdef20
| Characteristic | UUID | Properties | Description |
|---|---|---|---|
| Info | ...def21 | Read | Get system information |
| Shutdown | ...def22 | Write | Initiate shutdown |
| Restart | ...def23 | Write | Initiate restart |
| Logs | ...def24 | Read | Get service logs |
Update Service
Service UUID: 00000000-1234-5678-1234-56789abcdef30
| Characteristic | UUID | Properties | Description |
|---|---|---|---|
| Check | ...def31 | Write, Notify | Check for updates |
| Download | ...def32 | Write | Download update |
| Install | ...def33 | Write | Install update |
| Progress | ...def34 | Read, Notify | Update progress |
| Release Notes | ...def35 | Read | Get release notes |
📱 Client Integration Examples
PM3 Command Execution
// React Native / JavaScript example
// 1. Connect to Dangerous Pi
const device = await manager.connectToDevice(deviceId);
await device.discoverAllServicesAndCharacteristics();
// 2. Create session
const sessionChar = "00000000-1234-5678-1234-56789abcdef4";
const sessionRequest = JSON.stringify({ force_takeover: false });
await device.writeCharacteristicWithResponseForService(
PM3_SERVICE_UUID,
sessionChar,
base64.encode(sessionRequest)
);
// 3. Execute PM3 command
const commandChar = "00000000-1234-5678-1234-56789abcdef1";
const command = JSON.stringify({
command: "hw version",
session_id: receivedSessionId
});
await device.writeCharacteristicWithResponseForService(
PM3_SERVICE_UUID,
commandChar,
base64.encode(command)
);
// 4. Listen for result notification
const resultChar = "00000000-1234-5678-1234-56789abcdef2";
device.monitorCharacteristicForService(
PM3_SERVICE_UUID,
resultChar,
(error, characteristic) => {
if (characteristic) {
const result = JSON.parse(base64.decode(characteristic.value));
console.log("Command result:", result.output);
}
}
);
WiFi Network Scan
// 1. Trigger scan
const scanChar = "00000000-1234-5678-1234-56789abcdef12";
// Listen for scan results first
device.monitorCharacteristicForService(
WIFI_SERVICE_UUID,
scanChar,
(error, characteristic) => {
if (characteristic) {
const result = JSON.parse(base64.decode(characteristic.value));
console.log("Found networks:", result.networks);
// Display networks in UI
}
}
);
// Trigger scan (empty JSON or specify interface)
await device.writeCharacteristicWithResponseForService(
WIFI_SERVICE_UUID,
scanChar,
base64.encode(JSON.stringify({}))
);
WiFi Connect
const connectChar = "00000000-1234-5678-1234-56789abcdef13";
const connectRequest = JSON.stringify({
ssid: "MyNetwork",
password: "mypassword",
hidden: false
});
await device.writeCharacteristicWithResponseForService(
WIFI_SERVICE_UUID,
connectChar,
base64.encode(connectRequest)
);
System Information
const infoChar = "00000000-1234-5678-1234-56789abcdef21";
const infoBytes = await device.readCharacteristicForService(
SYSTEM_SERVICE_UUID,
infoChar
);
const info = JSON.parse(base64.decode(infoBytes.value));
console.log("CPU:", info.cpu.percent + "%");
console.log("Memory:", info.memory.percent + "%");
console.log("Temperature:", info.cpu.temperature + "°C");
🔄 Data Formats
All BLE characteristics use JSON for data exchange (encoded as UTF-8 bytes).
Request Format (Write Characteristics)
{
"param1": "value1",
"param2": "value2"
}
Response Format (Read/Notify Characteristics)
Success Response:
{
"success": true,
"data": {
"key": "value"
}
}
Error Response:
{
"success": false,
"error": {
"code": "error_code",
"message": "Human-readable message"
}
}
Error Codes
Same error codes as REST API:
session_locked- Another session is activepm3_not_connected- Proxmark3 not connectedcommand_failed- PM3 command failedconnection_failed- WiFi connection failedinvalid_mode- Invalid WiFi modenetwork_not_found- Network not in saved listupdate_check_error- Failed to check for updates- etc.
🧪 Testing
Unit Tests
The GATT server has comprehensive unit tests:
# Run BLE GATT tests
pytest tests/unit/ble/test_gatt_server.py
# Test coverage
pytest tests/unit/ble/ --cov=app/backend/ble
Test Coverage:
- ✅ Characteristic handler registration
- ✅ Delegation to services (PM3, WiFi, System, Update)
- ✅ JSON encoding/decoding
- ✅ Error handling
- ✅ Notification system
- ✅ Data format conversion
Integration Testing with Mobile App
# Python example using bleak library
import asyncio
import json
from bleak import BleakClient
async def test_pm3_command():
async with BleakClient("AA:BB:CC:DD:EE:FF") as client:
# Write command
command_char = "00000000-1234-5678-1234-56789abcdef1"
command_data = json.dumps({
"command": "hw version",
"session_id": "test"
})
await client.write_gatt_char(
command_char,
command_data.encode('utf-8')
)
# Read status
status_char = "00000000-1234-5678-1234-56789abcdef3"
status_bytes = await client.read_gatt_char(status_char)
status = json.loads(status_bytes.decode('utf-8'))
print("PM3 Status:", status)
asyncio.run(test_pm3_command())
🔐 Security Considerations
Authentication
- BLE pairing should be required for sensitive operations
- Consider implementing challenge-response authentication
- Session IDs provide multi-user coordination
Encryption
- Use BLE encryption (LE Secure Connections)
- All data is already JSON, easy to add encryption layer
- Consider end-to-end encryption for sensitive commands
Access Control
- Session management prevents concurrent access conflicts
- Can implement per-characteristic permissions
- Rate limiting for command execution
🚀 Integration with BLEManager
The GATT server integrates with the existing BLEManager:
# In app/backend/managers/ble_manager.py
from ..ble.gatt_server import DangerousPiGATTServer
class BLEManager:
def __init__(self):
# Existing notification support
self._notification_queue = asyncio.Queue()
# NEW: GATT server
self._gatt_server = None
async def initialize(self):
"""Initialize BLE with GATT server."""
# Existing notification setup
await self._setup_notifications()
# NEW: Initialize and start GATT server
self._gatt_server = DangerousPiGATTServer()
await self._gatt_server.start()
# Register notification callbacks with actual BLE implementation
# (BlueZ, etc.)
self._register_gatt_characteristics()
def _register_gatt_characteristics(self):
"""Register GATT characteristics with BLE stack."""
for uuid, handler in self._gatt_server.get_all_characteristics().items():
# Register with BlueZ or other BLE implementation
# Set up read/write callbacks
# Connect to GATT server handlers
pass
📊 Benefits Summary
Code Reuse
| Component | REST API | BLE GATT | Shared |
|---|---|---|---|
| Business Logic | ❌ | ❌ | ✅ Service Layer |
| Session Management | ❌ | ❌ | ✅ Service Layer |
| Error Handling | ❌ | ❌ | ✅ Service Layer |
| PM3 Commands | ❌ | ❌ | ✅ Service Layer |
| WiFi Operations | ❌ | ❌ | ✅ Service Layer |
| Code Duplication | 0% | 0% | 100% Shared |
Maintenance
- Fix bugs once in service layer
- Add features once in service layer
- Test once with service tests
- Both REST and BLE get the fix/feature automatically
Consistency
- REST and BLE guaranteed identical behavior
- Same error codes and messages
- Same data validation
- Same session management
🎯 Next Steps
- Complete BlueZ Integration - Connect GATT server to BlueZ D-Bus
- Mobile App Development - Build React Native / Flutter app
- Security Hardening - Implement BLE pairing and encryption
- Performance Testing - Test with real Proxmark3 hardware
- Documentation - Mobile app integration guide
📚 References
Summary: The BLE GATT server is a perfect demonstration of the service layer pattern. Every handler is a thin adapter that delegates to services, achieving zero code duplication and guaranteed consistency with the REST API. 🚀