🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
477 lines
14 KiB
Markdown
477 lines
14 KiB
Markdown
# Power Management Policy
|
|
|
|
**Date**: 2025-11-26
|
|
**Status**: ⚠️ **PRIORITY 1 - IMPLEMENT BEFORE PI TESTING**
|
|
**Priority**: CRITICAL - Must be implemented before hardware deployment
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
Dangerous Pi's power management policy determines when power-intensive operations are allowed based on hardware detection and battery status.
|
|
|
|
---
|
|
|
|
## Core Principle
|
|
|
|
**If UPS hardware is not detected, assume AC line power is available.**
|
|
|
|
This means:
|
|
- No battery-level restrictions on operations
|
|
- Power-intensive tasks are allowed
|
|
- Only constrained by Raspberry Pi's power delivery capability
|
|
- User takes responsibility for power stability
|
|
|
|
---
|
|
|
|
## Power States
|
|
|
|
### State 1: UPS Hardware Detected + AC Power
|
|
|
|
```python
|
|
{
|
|
"ups_available": True,
|
|
"power_source": "AC",
|
|
"battery_percentage": 100,
|
|
"restrictions": None
|
|
}
|
|
```
|
|
|
|
**Allowed Operations**: ALL
|
|
- Firmware flashing (bootloader + fullimage)
|
|
- Intensive PM3 operations
|
|
- System updates
|
|
- Multiple simultaneous PM3 devices
|
|
|
|
---
|
|
|
|
### State 2: UPS Hardware Detected + Battery Power
|
|
|
|
```python
|
|
{
|
|
"ups_available": True,
|
|
"power_source": "Battery",
|
|
"battery_percentage": 85, # Example
|
|
"restrictions": "See battery level policies below"
|
|
}
|
|
```
|
|
|
|
**Battery Level Policies**:
|
|
|
|
| Battery % | Allowed Operations | Restrictions |
|
|
|-----------|-------------------|--------------|
|
|
| 80-100% | ALL | Bootloader flashing allowed with warning |
|
|
| 50-79% | Most operations | Bootloader flashing blocked, fullimage allowed |
|
|
| 20-49% | Standard ops | No firmware flashing, PM3 commands OK |
|
|
| 10-19% | Critical mode | Read-only operations, no writes |
|
|
| 0-9% | Emergency | Initiate safe shutdown |
|
|
|
|
---
|
|
|
|
### State 3: UPS Hardware NOT Detected (Most Common)
|
|
|
|
```python
|
|
{
|
|
"ups_available": False,
|
|
"power_source": "Assumed AC",
|
|
"battery_percentage": None,
|
|
"restrictions": None
|
|
}
|
|
```
|
|
|
|
**Allowed Operations**: ALL
|
|
- **Assumption**: User is on stable AC power or external battery bank
|
|
- **Rationale**: If user doesn't have UPS HAT, we can't monitor battery anyway
|
|
- **Constraints**: Only limited by Pi Zero 2 W power delivery (~5V 2.5A typical)
|
|
- **User Responsibility**: Ensure stable power for firmware flashing
|
|
|
|
**Warning Display**: Show header widget warning that UPS is not detected (informational, dismissible)
|
|
|
|
---
|
|
|
|
## Implementation
|
|
|
|
### UPS Manager Detection
|
|
|
|
```python
|
|
# app/backend/managers/ups_manager.py
|
|
|
|
class UPSManager:
|
|
def get_power_restrictions(self) -> Dict[str, Any]:
|
|
"""Get current power restrictions based on hardware state.
|
|
|
|
Returns:
|
|
Dict with restriction info
|
|
"""
|
|
# UPS not available = assume AC power
|
|
if not self._status.is_available:
|
|
return {
|
|
"restricted": False,
|
|
"reason": None,
|
|
"power_source": "assumed_ac",
|
|
"ups_available": False,
|
|
"allow_firmware_flash": True,
|
|
"allow_bootloader_flash": True,
|
|
"allow_intensive_operations": True,
|
|
"message": "UPS not detected. Assuming stable AC power. Ensure power stability before firmware operations."
|
|
}
|
|
|
|
# UPS available - check battery state
|
|
if self._status.power_source == PowerSource.AC:
|
|
return {
|
|
"restricted": False,
|
|
"reason": None,
|
|
"power_source": "ac",
|
|
"ups_available": True,
|
|
"battery_percentage": self._status.battery_percentage,
|
|
"allow_firmware_flash": True,
|
|
"allow_bootloader_flash": True,
|
|
"allow_intensive_operations": True
|
|
}
|
|
|
|
# On battery power - apply restrictions
|
|
battery_pct = self._status.battery_percentage
|
|
|
|
if battery_pct >= 80:
|
|
return {
|
|
"restricted": False,
|
|
"reason": None,
|
|
"power_source": "battery",
|
|
"ups_available": True,
|
|
"battery_percentage": battery_pct,
|
|
"allow_firmware_flash": True,
|
|
"allow_bootloader_flash": True, # With warning
|
|
"allow_intensive_operations": True,
|
|
"warning": "On battery power. Bootloader flashing not recommended."
|
|
}
|
|
elif battery_pct >= 50:
|
|
return {
|
|
"restricted": True,
|
|
"reason": "Battery level below 80%",
|
|
"power_source": "battery",
|
|
"ups_available": True,
|
|
"battery_percentage": battery_pct,
|
|
"allow_firmware_flash": True, # Fullimage only
|
|
"allow_bootloader_flash": False,
|
|
"allow_intensive_operations": True,
|
|
"message": "Bootloader flashing disabled. Battery too low."
|
|
}
|
|
elif battery_pct >= 20:
|
|
return {
|
|
"restricted": True,
|
|
"reason": "Battery level below 50%",
|
|
"power_source": "battery",
|
|
"ups_available": True,
|
|
"battery_percentage": battery_pct,
|
|
"allow_firmware_flash": False,
|
|
"allow_bootloader_flash": False,
|
|
"allow_intensive_operations": True,
|
|
"message": "Firmware flashing disabled. Battery too low."
|
|
}
|
|
elif battery_pct >= 10:
|
|
return {
|
|
"restricted": True,
|
|
"reason": "Battery critically low",
|
|
"power_source": "battery",
|
|
"ups_available": True,
|
|
"battery_percentage": battery_pct,
|
|
"allow_firmware_flash": False,
|
|
"allow_bootloader_flash": False,
|
|
"allow_intensive_operations": False,
|
|
"message": "Critical battery. Read-only mode active."
|
|
}
|
|
else:
|
|
return {
|
|
"restricted": True,
|
|
"reason": "Battery emergency level",
|
|
"power_source": "battery",
|
|
"ups_available": True,
|
|
"battery_percentage": battery_pct,
|
|
"allow_firmware_flash": False,
|
|
"allow_bootloader_flash": False,
|
|
"allow_intensive_operations": False,
|
|
"message": "Emergency battery level. Shutting down soon.",
|
|
"shutdown_imminent": True
|
|
}
|
|
```
|
|
|
|
### Firmware Flash Service
|
|
|
|
```python
|
|
# app/backend/services/firmware_service.py (future implementation)
|
|
|
|
class FirmwareService:
|
|
def __init__(self, ups_manager: UPSManager):
|
|
self._ups_manager = ups_manager
|
|
|
|
async def can_flash_firmware(self, include_bootloader: bool = False) -> Dict[str, Any]:
|
|
"""Check if firmware flashing is allowed.
|
|
|
|
Args:
|
|
include_bootloader: Whether bootloader flash is requested
|
|
|
|
Returns:
|
|
Dict with allowed status and reason
|
|
"""
|
|
restrictions = self._ups_manager.get_power_restrictions()
|
|
|
|
if include_bootloader:
|
|
if not restrictions["allow_bootloader_flash"]:
|
|
return {
|
|
"allowed": False,
|
|
"reason": restrictions.get("message", "Bootloader flashing not allowed"),
|
|
"power_source": restrictions["power_source"],
|
|
"battery_percentage": restrictions.get("battery_percentage")
|
|
}
|
|
|
|
if not restrictions["allow_firmware_flash"]:
|
|
return {
|
|
"allowed": False,
|
|
"reason": restrictions.get("message", "Firmware flashing not allowed"),
|
|
"power_source": restrictions["power_source"],
|
|
"battery_percentage": restrictions.get("battery_percentage")
|
|
}
|
|
|
|
# Allowed - return info
|
|
return {
|
|
"allowed": True,
|
|
"power_source": restrictions["power_source"],
|
|
"warning": restrictions.get("warning"), # May be None
|
|
"battery_percentage": restrictions.get("battery_percentage")
|
|
}
|
|
```
|
|
|
|
### API Endpoint
|
|
|
|
```python
|
|
# app/backend/api/system.py
|
|
|
|
@router.get("/power/restrictions")
|
|
async def get_power_restrictions():
|
|
"""Get current power restrictions.
|
|
|
|
Returns:
|
|
Power restriction information
|
|
"""
|
|
try:
|
|
ups_manager = get_ups_manager()
|
|
restrictions = ups_manager.get_power_restrictions()
|
|
|
|
return {
|
|
"success": True,
|
|
**restrictions
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=str(e))
|
|
```
|
|
|
|
---
|
|
|
|
## Frontend Integration
|
|
|
|
### Firmware Flash UI
|
|
|
|
```typescript
|
|
// Before starting firmware flash
|
|
const checkPowerRestrictions = async (includeBootloader: boolean) => {
|
|
const response = await fetch("/api/system/power/restrictions");
|
|
const data = await response.json();
|
|
|
|
// UPS not available - show warning but allow
|
|
if (!data.ups_available) {
|
|
return confirm(
|
|
"⚠️ UPS hardware not detected.\n\n" +
|
|
"Ensure you have stable AC power before flashing firmware.\n" +
|
|
"Power loss during flashing can brick your Proxmark3.\n\n" +
|
|
"Continue with firmware flash?"
|
|
);
|
|
}
|
|
|
|
// On battery - check restrictions
|
|
if (includeBootloader && !data.allow_bootloader_flash) {
|
|
alert(
|
|
`❌ Bootloader flashing blocked\n\n` +
|
|
`${data.message}\n` +
|
|
`Battery: ${data.battery_percentage}% (minimum 80% required)`
|
|
);
|
|
return false;
|
|
}
|
|
|
|
if (!data.allow_firmware_flash) {
|
|
alert(
|
|
`❌ Firmware flashing blocked\n\n` +
|
|
`${data.message}\n` +
|
|
`Battery: ${data.battery_percentage}% (minimum 50% required)`
|
|
);
|
|
return false;
|
|
}
|
|
|
|
// Allowed with warning
|
|
if (data.warning) {
|
|
return confirm(`⚠️ ${data.warning}\n\nContinue anyway?`);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Hardware Assumptions
|
|
|
|
### Raspberry Pi Zero 2 W Power Limits
|
|
|
|
- **Max Current Draw**: ~2.5A @ 5V typical
|
|
- **Single PM3**: ~500mA typical, ~800mA peak
|
|
- **Multiple PM3s**: May exceed Pi's USB current limit
|
|
- **Recommendation**: Use powered USB hub for 3+ devices
|
|
|
|
### UPS HAT Detection
|
|
|
|
```python
|
|
# UPS detection via I2C
|
|
# If I2C device not present at address 0x36 (typical MAX17040):
|
|
# - smbus2 import fails → UPS unavailable
|
|
# - I2C read fails → UPS unavailable
|
|
# - Battery register reads 0 → UPS unavailable
|
|
|
|
# Any of above = assume AC power
|
|
```
|
|
|
|
---
|
|
|
|
## User Communication
|
|
|
|
### Dashboard Widget (UPS Not Detected)
|
|
|
|
```
|
|
⚠️ UPS hardware not detected. Battery monitoring unavailable.
|
|
|
|
Assuming stable AC power for all operations.
|
|
|
|
[Learn More] [Dismiss]
|
|
```
|
|
|
|
### Firmware Flash Dialog (No UPS)
|
|
|
|
```
|
|
⚠️ Power Stability Required
|
|
|
|
UPS hardware not detected. Ensure you have stable AC power.
|
|
|
|
Firmware flashing can take 30-60 seconds. Power loss during
|
|
this time can brick your Proxmark3 device.
|
|
|
|
✓ I have stable AC power connected
|
|
|
|
[Cancel] [Continue Anyway]
|
|
```
|
|
|
|
### Firmware Flash Dialog (Low Battery)
|
|
|
|
```
|
|
❌ Battery Too Low for Bootloader Flash
|
|
|
|
Current battery: 45%
|
|
Minimum required: 80%
|
|
|
|
Bootloader flashing requires high power stability. Please connect
|
|
to AC power or wait for battery to charge above 80%.
|
|
|
|
Fullimage flashing is still available (requires 50% minimum).
|
|
|
|
[Cancel] [Flash Fullimage Only]
|
|
```
|
|
|
|
---
|
|
|
|
## Testing Scenarios
|
|
|
|
### Test 1: No UPS Hardware
|
|
1. Boot system without UPS HAT
|
|
2. Navigate to firmware flash page
|
|
3. Verify: No restrictions, warning shown
|
|
4. Verify: Flash proceeds with user confirmation
|
|
|
|
### Test 2: UPS on AC Power
|
|
1. Boot with UPS HAT on AC
|
|
2. Navigate to firmware flash page
|
|
3. Verify: No restrictions, no warnings
|
|
4. Verify: Flash proceeds immediately
|
|
|
|
### Test 3: UPS on Battery (90%)
|
|
1. Disconnect AC power (battery 90%)
|
|
2. Navigate to firmware flash page
|
|
3. Verify: Bootloader flash allowed with warning
|
|
4. Verify: User must confirm warning
|
|
|
|
### Test 4: UPS on Battery (60%)
|
|
1. Discharge to 60%
|
|
2. Navigate to firmware flash page
|
|
3. Verify: Bootloader flash blocked
|
|
4. Verify: Fullimage flash allowed
|
|
|
|
### Test 5: UPS on Battery (30%)
|
|
1. Discharge to 30%
|
|
2. Navigate to firmware flash page
|
|
3. Verify: All firmware flashing blocked
|
|
4. Verify: PM3 commands still work
|
|
|
|
---
|
|
|
|
## Migration Notes
|
|
|
|
### Existing Code
|
|
|
|
Current firmware flash logic (if exists) may have hardcoded battery checks. Update to use `get_power_restrictions()` method.
|
|
|
|
### Future Implementation
|
|
|
|
When implementing Sprint 3 (Firmware Management):
|
|
1. Implement `FirmwareService` with power checks
|
|
2. Add `/api/system/power/restrictions` endpoint
|
|
3. Update frontend firmware flash UI
|
|
4. Add user confirmation dialogs
|
|
5. Add power stability warnings
|
|
|
|
---
|
|
|
|
## Security & Safety
|
|
|
|
1. **User Consent**: Always require explicit confirmation for risky operations
|
|
2. **Clear Warnings**: Explain consequences of power loss
|
|
3. **Conservative Defaults**: Err on side of safety when battery detected
|
|
4. **No Silent Failures**: Always inform user why operation was blocked
|
|
|
|
---
|
|
|
|
## Sleep Mode - Not Applicable
|
|
|
|
Dangerous Pi does not implement sleep/standby mode. The device operates in two states:
|
|
|
|
1. **Active** - WiFi enabled, all features available
|
|
2. **Shutdown** - Device powered off
|
|
|
|
**Why no sleep mode?**
|
|
|
|
- WiFi must remain active for remote access (primary use case)
|
|
- Sleep without WiFi makes device inaccessible remotely
|
|
- BLE-only wake would require dedicated mobile app
|
|
- Physical button wake defeats portable/remote use case
|
|
- Middle "sleep" state = worst of both worlds (draws power but inaccessible)
|
|
|
|
**Alternative for scheduled operation**: PiSugar users can use RTC scheduled wake-up for "sleep overnight, wake at 8am" scenarios:
|
|
|
|
1. Set wake alarm via PiSugar web interface or native I2C driver
|
|
2. Shutdown device: `sudo shutdown -h now`
|
|
3. Device wakes automatically at scheduled time
|
|
|
|
This is more power-efficient than any "sleep" mode would be.
|
|
|
|
---
|
|
|
|
**Status**: Design Complete
|
|
**Priority**: Implement before Sprint 3 (Firmware Management)
|
|
**Dependencies**: UPS Manager (✅ exists), Firmware Service (⏳ future)
|