Add Dangerous Pi MVP implementation - complete backend and system integration

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>
This commit is contained in:
michael
2025-11-26 08:11:36 -08:00
parent 0a586c5360
commit 1da6730735
68 changed files with 12673 additions and 5 deletions

View File

@@ -0,0 +1,183 @@
"""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
)