This commit adds the complete Dangerous Pi web management interface with all MVP features implemented and tested locally. ## New Features ### Backend (Python + FastAPI) - Complete FastAPI backend with async support - 40+ API endpoints (Health, PM3, WiFi, Updates, UPS, BLE, Plugins) - 6 managers: Session, WiFi, Update, UPS, BLE, Plugin - SQLite database with sessions, config, history, crash reports - Server-Sent Events (SSE) for real-time notifications - Mock PM3 worker for development without hardware ### WiFi Manager - Interface detection (USB vs built-in) - Network scanning with signal strength - Mode switching (AP/Client/Dual/Auto/Off) - Network connection with password support - Hidden SSID and saved networks support - Static IP and DHCP configuration - 10 WiFi API endpoints ### Update Manager - GitHub releases API integration - Automatic periodic update checks - Semantic version comparison - Update download with progress tracking - SHA256 checksum verification - Automatic installation with backup and rollback - PM3 client rebuild after updates - 6 Update API endpoints ### UPS Manager - I2C battery monitoring (MAX17040-compatible) - Battery percentage, voltage, current tracking - Power source detection (AC/Battery) - Safe shutdown triggers at configurable thresholds - Event callbacks for battery warnings - SSE and BLE notification integration - 3 UPS API endpoints ### BLE Manager - Bluetooth Low Energy notification support - Auto-detects BLE capability - Multiple notification types (updates, battery, shutdown, etc.) - BLE advertising management - Device connection tracking - 4 BLE API endpoints ### Plugin Framework - Dynamic plugin loading/unloading - Plugin lifecycle management (load, enable, disable, unload) - Hook system for extensibility - JSON-based metadata - Example "Hello World" plugin included - 7 Plugin API endpoints ### Frontend (Remix.js + React) - Cyberpunk-themed responsive UI - Dashboard with system status - PM3 command interface with history - Settings page with WiFi and Update management - Command logs viewer - Theme toggle (Dark/Light/Auto) - Server-side rendering (SSR) - Mobile-first responsive design ### System Integration - Systemd service with security hardening - Automated install/uninstall scripts - Environment configuration template - Hardware access groups (i2c, bluetooth, gpio, dialout) - Pi-gen stage 04 integration for OS image building - Port conflict resolution with ttyd-bash - I2C interface auto-enable for UPS HAT ### Testing - test_backend.py - Backend API tests - test_ups.py - UPS manager tests - test_ble.py - BLE manager tests - test_plugins.py - Plugin manager tests - All tests passing locally ### Documentation - 12 comprehensive documentation files - claude.md - AI development guide - WIFI_MANAGER.md - WiFi management guide - UPDATE_MANAGER.md - Update system guide - PORT_CONFLICT.md - Port conflict resolution guide - MVP_COMPLETE.md - MVP implementation summary - PROJECT_STATUS.md - Project status and roadmap - systemd/README.md - Service management docs - pi-gen integration documentation ## Technical Details - ~5,000+ lines of backend code - 11 Python dependencies (smbus2 added for UPS) - FastAPI with async/await throughout - Type hints and docstrings on all functions - RESTful API design with SSE for notifications - Security hardening (non-root, protected dirs, resource limits) ## Next Steps - Deploy to Raspberry Pi Zero 2 W hardware - Test with real Proxmark3 device - Test UPS HAT integration - Test BLE on Pi hardware - Build custom OS image with pi-gen - Performance optimization for Pi Zero 2 W 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
184 lines
6.0 KiB
Python
184 lines
6.0 KiB
Python
"""Proxmark3 worker for executing commands."""
|
|
import asyncio
|
|
from dataclasses import dataclass
|
|
from typing import Optional
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
from .. import config
|
|
|
|
|
|
@dataclass
|
|
class PM3CommandResult:
|
|
"""Result from a PM3 command execution."""
|
|
success: bool
|
|
output: str
|
|
error: Optional[str] = None
|
|
|
|
|
|
class PM3Worker:
|
|
"""Worker for executing Proxmark3 commands using the built-in pm3 module."""
|
|
|
|
def __init__(self, device_path: str = None):
|
|
"""Initialize the PM3 worker.
|
|
|
|
Args:
|
|
device_path: Path to PM3 device (default: from config)
|
|
"""
|
|
self.device_path = device_path or config.PM3_DEVICE
|
|
self._device = None
|
|
self._connected = False
|
|
self._lock = asyncio.Lock()
|
|
self._pm3_module = None
|
|
|
|
async def _import_pm3(self):
|
|
"""Import the pm3 module (lazy loading)."""
|
|
if self._pm3_module is None:
|
|
try:
|
|
# The pm3 module should be available after pm3 client installation
|
|
import pm3
|
|
self._pm3_module = pm3
|
|
except ImportError as e:
|
|
raise ImportError(
|
|
"pm3 module not found. Ensure Proxmark3 client is installed with Python support. "
|
|
f"Error: {e}"
|
|
)
|
|
return self._pm3_module
|
|
|
|
async def connect(self):
|
|
"""Connect to the Proxmark3 device."""
|
|
async with self._lock:
|
|
if self._connected:
|
|
return
|
|
|
|
try:
|
|
pm3 = await self._import_pm3()
|
|
|
|
# Run blocking pm3.open() in executor
|
|
loop = asyncio.get_event_loop()
|
|
self._device = await loop.run_in_executor(
|
|
None,
|
|
pm3.open,
|
|
self.device_path
|
|
)
|
|
self._connected = True
|
|
except Exception as e:
|
|
raise ConnectionError(f"Failed to connect to PM3 at {self.device_path}: {e}")
|
|
|
|
async def disconnect(self):
|
|
"""Disconnect from the Proxmark3 device."""
|
|
async with self._lock:
|
|
if self._device:
|
|
try:
|
|
# Close the device connection
|
|
# Note: pm3 module may not have explicit close, connection closes when object is deleted
|
|
self._device = None
|
|
self._connected = False
|
|
except Exception as e:
|
|
print(f"Error disconnecting from PM3: {e}")
|
|
|
|
async def is_connected(self) -> bool:
|
|
"""Check if connected to PM3 device."""
|
|
if not self._connected:
|
|
# Try to check if device exists
|
|
return Path(self.device_path).exists()
|
|
return self._connected
|
|
|
|
async def execute_command(self, command: str) -> PM3CommandResult:
|
|
"""Execute a Proxmark3 command.
|
|
|
|
Args:
|
|
command: PM3 command to execute (e.g., "hw version")
|
|
|
|
Returns:
|
|
PM3CommandResult with success status, output, and optional error
|
|
"""
|
|
async with self._lock:
|
|
try:
|
|
# Ensure we're connected
|
|
if not self._connected:
|
|
await self.connect()
|
|
|
|
if not self._device:
|
|
return PM3CommandResult(
|
|
success=False,
|
|
output="",
|
|
error="Not connected to PM3 device"
|
|
)
|
|
|
|
# Execute command in executor (blocking call)
|
|
loop = asyncio.get_event_loop()
|
|
|
|
try:
|
|
# Use .cmd() method to execute command
|
|
output = await loop.run_in_executor(
|
|
None,
|
|
self._device.cmd,
|
|
command
|
|
)
|
|
|
|
# pm3.cmd() returns the output string
|
|
return PM3CommandResult(
|
|
success=True,
|
|
output=str(output) if output else "",
|
|
error=None
|
|
)
|
|
except Exception as cmd_error:
|
|
return PM3CommandResult(
|
|
success=False,
|
|
output="",
|
|
error=f"Command execution failed: {cmd_error}"
|
|
)
|
|
|
|
except Exception as e:
|
|
return PM3CommandResult(
|
|
success=False,
|
|
output="",
|
|
error=str(e)
|
|
)
|
|
|
|
|
|
# Mock PM3 Worker for testing without actual hardware
|
|
class MockPM3Worker(PM3Worker):
|
|
"""Mock PM3 worker for testing without hardware."""
|
|
|
|
def __init__(self, device_path: str = None):
|
|
super().__init__(device_path)
|
|
self._mock_responses = {
|
|
"hw version": "Proxmark3 RFID instrument\n client: RRG/Iceman/master/v4.14831",
|
|
"hw status": "Device: PM3 GENERIC\nBootrom: master/v4.14831\nOS: master/v4.14831",
|
|
"hw tune": "Measuring antenna characteristics, please wait...\n# LF antenna: 50.00 V @ 125.00 kHz",
|
|
}
|
|
|
|
async def connect(self):
|
|
"""Mock connect."""
|
|
self._connected = True
|
|
|
|
async def disconnect(self):
|
|
"""Mock disconnect."""
|
|
self._connected = False
|
|
|
|
async def is_connected(self) -> bool:
|
|
"""Mock connection check."""
|
|
return True
|
|
|
|
async def execute_command(self, command: str) -> PM3CommandResult:
|
|
"""Mock command execution."""
|
|
await asyncio.sleep(0.1) # Simulate command delay
|
|
|
|
# Return mock response if available
|
|
for cmd_prefix, response in self._mock_responses.items():
|
|
if command.startswith(cmd_prefix):
|
|
return PM3CommandResult(
|
|
success=True,
|
|
output=response,
|
|
error=None
|
|
)
|
|
|
|
# Default response for unknown commands
|
|
return PM3CommandResult(
|
|
success=True,
|
|
output=f"Mock response for: {command}",
|
|
error=None
|
|
)
|