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:
476
POWER_MANAGEMENT_POLICY.md
Normal file
476
POWER_MANAGEMENT_POLICY.md
Normal file
@@ -0,0 +1,476 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user