🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
702 lines
16 KiB
Markdown
702 lines
16 KiB
Markdown
# Dangerous Pi - Visualization & Guided Workflows Plan
|
|
|
|
**Status**: 🎯 In Planning
|
|
**Priority**: High (Post-MVP Phase 1)
|
|
**Target Timeline**: 4-6 weeks implementation
|
|
**Last Updated**: 2025-11-26
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
Dangerous Pi will implement data visualization and guided workflows using **Victory Charts**, a cross-platform charting library that works seamlessly across Web (Remix), React Native (mobile), and Electron (desktop) applications.
|
|
|
|
### Key Decisions
|
|
|
|
- **Charting Library**: Victory (cross-platform, mobile-first)
|
|
- **Bundle Impact**: ~50KB (acceptable via code splitting)
|
|
- **Primary Target**: Mobile/smartphone users (📱 80% of usage)
|
|
- **Architecture**: Shared components across all platforms
|
|
|
|
---
|
|
|
|
## 1. Why Victory Charts?
|
|
|
|
### Cross-Platform Requirements
|
|
|
|
| Platform | Framework | Victory Package | Rendering |
|
|
|----------|-----------|-----------------|-----------|
|
|
| **Web** | Remix.js + React | `victory` | SVG/Canvas |
|
|
| **Mobile** | React Native | `victory-native` | Native |
|
|
| **Desktop** | Electron + React | `victory` | SVG/Canvas |
|
|
|
|
**Code Reuse**: ~90% of chart components shared across platforms
|
|
|
|
### Comparison with Alternatives
|
|
|
|
| Feature | Victory | Chart.js | Recharts | Nivo |
|
|
|---------|---------|----------|----------|------|
|
|
| React Native Support | ✅ Native | ❌ None | ⚠️ Wrapper | ❌ None |
|
|
| Touch Gestures | ✅ Built-in | ⚠️ Limited | ⚠️ Limited | ✅ Good |
|
|
| Bundle Size | 50KB | 30KB | 45KB | 120KB |
|
|
| Mobile-First | ✅ Yes | ❌ No | ❌ No | ✅ Yes |
|
|
| **Score** | **9/10** | 5/10 | 6/10 | 6/10 |
|
|
|
|
**Winner**: Victory (only library with true cross-platform support)
|
|
|
|
---
|
|
|
|
## 2. Architecture Overview
|
|
|
|
### Data Flow
|
|
|
|
```
|
|
Proxmark3 Hardware
|
|
↓ USB
|
|
Pi Zero 2 W Backend (Python)
|
|
↓ Execute PM3 command
|
|
PM3 Python Module (SWIG)
|
|
↓ Text output
|
|
Parser Layer (NEW)
|
|
↓ Structured JSON
|
|
FastAPI Endpoint (Enhanced)
|
|
↓ REST/SSE
|
|
Frontend (Remix/React)
|
|
↓ Victory Charts
|
|
User (Mobile/Desktop)
|
|
```
|
|
|
|
### New Backend Components
|
|
|
|
#### Parser Module (`app/backend/parsers/pm3_output.py`)
|
|
|
|
```python
|
|
"""
|
|
Convert PM3 text output into structured data for visualization.
|
|
"""
|
|
|
|
def parse_antenna_tuning(output: str) -> dict:
|
|
"""
|
|
Parse hw tune / hf tune / lf tune output.
|
|
|
|
Input: "# LF antenna: 50.00 V @ 125.00 kHz"
|
|
Output: {
|
|
"frequency": 125.0,
|
|
"voltage": 50.0,
|
|
"type": "lf",
|
|
"optimal": voltage > 45.0
|
|
}
|
|
"""
|
|
|
|
def parse_waveform_data(output: str) -> dict:
|
|
"""
|
|
Parse data samples command output.
|
|
|
|
Output: {
|
|
"samples": [1, 2, 3, ...],
|
|
"sample_rate": 48000,
|
|
"total_samples": 10000
|
|
}
|
|
"""
|
|
|
|
def parse_protocol_trace(output: str) -> dict:
|
|
"""
|
|
Parse hf list / lf list output into timeline.
|
|
|
|
Output: {
|
|
"frames": [
|
|
{
|
|
"timestamp": 1234,
|
|
"direction": "tag_to_reader",
|
|
"data": "0x01 0x02",
|
|
"crc": "ok"
|
|
},
|
|
...
|
|
]
|
|
}
|
|
"""
|
|
|
|
def parse_tag_detection(output: str) -> dict:
|
|
"""
|
|
Parse hf search / lf search results.
|
|
|
|
Output: {
|
|
"found": true,
|
|
"tag_type": "MIFARE Classic 1K",
|
|
"uid": "01234567",
|
|
"signal_strength": "good"
|
|
}
|
|
"""
|
|
```
|
|
|
|
#### Enhanced API Response
|
|
|
|
```python
|
|
# app/backend/api/pm3.py
|
|
|
|
class CommandWithDataResponse(BaseModel):
|
|
"""Enhanced response with visualization data."""
|
|
success: bool
|
|
output: str # Original text (backwards compatible)
|
|
data: Optional[Dict] = None # Structured data for charts
|
|
visualization_type: Optional[str] = None # Chart type hint
|
|
# visualization_type values: "waveform", "tune", "trace", "summary"
|
|
|
|
@router.post("/command-with-data", response_model=CommandWithDataResponse)
|
|
async def execute_command_with_visualization(request: CommandRequest):
|
|
"""Execute PM3 command and return structured data."""
|
|
|
|
# Execute command
|
|
result = await pm3_worker.execute_command(request.command)
|
|
|
|
# Parse output based on command type
|
|
data = None
|
|
viz_type = None
|
|
|
|
if request.command.startswith(('hw tune', 'hf tune', 'lf tune')):
|
|
data = parse_antenna_tuning(result.output)
|
|
viz_type = "tune"
|
|
|
|
elif request.command.startswith('data'):
|
|
data = parse_waveform_data(result.output)
|
|
viz_type = "waveform"
|
|
|
|
elif request.command.startswith(('hf list', 'lf list')):
|
|
data = parse_protocol_trace(result.output)
|
|
viz_type = "trace"
|
|
|
|
elif request.command.startswith(('hf search', 'lf search')):
|
|
data = parse_tag_detection(result.output)
|
|
viz_type = "summary"
|
|
|
|
return CommandWithDataResponse(
|
|
success=result.success,
|
|
output=result.output,
|
|
data=data,
|
|
visualization_type=viz_type
|
|
)
|
|
```
|
|
|
|
### Frontend Components
|
|
|
|
#### Shared Chart Library (`app/shared/components/charts/`)
|
|
|
|
**Cross-platform components used by Web + React Native + Electron**
|
|
|
|
```typescript
|
|
// TuneChart.tsx
|
|
import { VictoryLine, VictoryChart, VictoryAxis, VictoryTheme } from 'victory'
|
|
|
|
interface TuneData {
|
|
frequency: number
|
|
voltage: number
|
|
optimal?: boolean
|
|
}
|
|
|
|
export function TuneChart({
|
|
data,
|
|
title,
|
|
thresholdVoltage = 40
|
|
}: {
|
|
data: TuneData[],
|
|
title: string,
|
|
thresholdVoltage?: number
|
|
}) {
|
|
return (
|
|
<VictoryChart
|
|
theme={VictoryTheme.material}
|
|
height={300}
|
|
padding={{ top: 40, bottom: 40, left: 60, right: 40 }}
|
|
>
|
|
<VictoryAxis
|
|
label="Frequency (kHz)"
|
|
style={{
|
|
axisLabel: { fontSize: 14, padding: 30, fill: '#9ca3af' }
|
|
}}
|
|
/>
|
|
<VictoryAxis
|
|
dependentAxis
|
|
label="Voltage (V)"
|
|
style={{
|
|
axisLabel: { fontSize: 14, padding: 40, fill: '#9ca3af' }
|
|
}}
|
|
/>
|
|
|
|
{/* Threshold line */}
|
|
<VictoryLine
|
|
data={[
|
|
{ x: Math.min(...data.map(d => d.frequency)), y: thresholdVoltage },
|
|
{ x: Math.max(...data.map(d => d.frequency)), y: thresholdVoltage }
|
|
]}
|
|
style={{
|
|
data: { stroke: "#ffaa00", strokeWidth: 1, strokeDasharray: "4,4" }
|
|
}}
|
|
/>
|
|
|
|
{/* Actual data */}
|
|
<VictoryLine
|
|
data={data}
|
|
x="frequency"
|
|
y="voltage"
|
|
style={{
|
|
data: { stroke: "#00ffff", strokeWidth: 3 }
|
|
}}
|
|
animate={{
|
|
duration: 500,
|
|
onLoad: { duration: 500 }
|
|
}}
|
|
/>
|
|
</VictoryChart>
|
|
)
|
|
}
|
|
```
|
|
|
|
```typescript
|
|
// WaveformChart.tsx
|
|
import { VictoryLine, VictoryChart, VictoryZoomContainer } from 'victory'
|
|
|
|
export function WaveformChart({ samples }: { samples: number[] }) {
|
|
// Downsample if > 1000 points for performance
|
|
const displaySamples = samples.length > 1000
|
|
? downsample(samples, 1000)
|
|
: samples
|
|
|
|
const data = displaySamples.map((value, index) => ({ x: index, y: value }))
|
|
|
|
return (
|
|
<VictoryChart
|
|
height={400}
|
|
containerComponent={
|
|
<VictoryZoomContainer
|
|
zoomDimension="x"
|
|
allowPan={true}
|
|
allowZoom={true}
|
|
minimumZoom={{ x: 1 }}
|
|
/>
|
|
}
|
|
>
|
|
<VictoryLine
|
|
data={data}
|
|
style={{
|
|
data: { stroke: "#00ffff", strokeWidth: 2 }
|
|
}}
|
|
/>
|
|
</VictoryChart>
|
|
)
|
|
}
|
|
|
|
function downsample(data: number[], targetSize: number): number[] {
|
|
const step = Math.floor(data.length / targetSize)
|
|
return data.filter((_, i) => i % step === 0)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Guided Workflows
|
|
|
|
### Framework Architecture
|
|
|
|
```typescript
|
|
// app/frontend/app/components/GuidedWorkflow.tsx
|
|
|
|
interface WorkflowStep {
|
|
id: string
|
|
title: string
|
|
description: string
|
|
component: React.ComponentType<StepProps>
|
|
validation?: (data: any) => boolean
|
|
helpText?: string
|
|
}
|
|
|
|
interface StepProps {
|
|
data: Record<string, any>
|
|
onUpdate: (data: Record<string, any>) => void
|
|
onComplete: () => void
|
|
}
|
|
|
|
export function GuidedWorkflow({
|
|
steps,
|
|
onComplete
|
|
}: {
|
|
steps: WorkflowStep[],
|
|
onComplete: (data: any) => void
|
|
}) {
|
|
const [currentStep, setCurrentStep] = useState(0)
|
|
const [stepData, setStepData] = useState<Record<string, any>>({})
|
|
|
|
const canProceed = () => {
|
|
const step = steps[currentStep]
|
|
return !step.validation || step.validation(stepData)
|
|
}
|
|
|
|
const CurrentStepComponent = steps[currentStep].component
|
|
|
|
return (
|
|
<div className="workflow-container">
|
|
{/* Progress bar */}
|
|
<div className="progress-steps">
|
|
{steps.map((step, i) => (
|
|
<div
|
|
key={step.id}
|
|
className={`step ${i === currentStep ? 'active' : ''} ${i < currentStep ? 'completed' : ''}`}
|
|
>
|
|
{i < currentStep ? '✓' : i + 1} {step.title}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Current step */}
|
|
<div className="step-content">
|
|
<h2>{steps[currentStep].title}</h2>
|
|
<p>{steps[currentStep].description}</p>
|
|
|
|
<CurrentStepComponent
|
|
data={stepData}
|
|
onUpdate={(data) => setStepData({ ...stepData, ...data })}
|
|
onComplete={() => {
|
|
if (currentStep < steps.length - 1) {
|
|
setCurrentStep(currentStep + 1)
|
|
} else {
|
|
onComplete(stepData)
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{steps[currentStep].helpText && (
|
|
<div className="help-text">{steps[currentStep].helpText}</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Navigation */}
|
|
<div className="step-navigation">
|
|
{currentStep > 0 && (
|
|
<button onClick={() => setCurrentStep(currentStep - 1)}>
|
|
← Back
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={() => {
|
|
if (currentStep < steps.length - 1) {
|
|
setCurrentStep(currentStep + 1)
|
|
} else {
|
|
onComplete(stepData)
|
|
}
|
|
}}
|
|
disabled={!canProceed()}
|
|
>
|
|
{currentStep < steps.length - 1 ? 'Next →' : 'Finish'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Priority Workflows to Implement
|
|
|
|
#### 1. HF/LF/HW Tune (Week 1-2)
|
|
|
|
**Steps**:
|
|
1. Select frequency type (HF/LF/HW)
|
|
2. Run tuning command
|
|
3. Display real-time chart
|
|
4. Show optimal/warning indicators
|
|
|
|
**Files**:
|
|
- `app/frontend/app/routes/workflows/tune.tsx`
|
|
- `app/shared/components/charts/TuneChart.tsx`
|
|
|
|
#### 2. ID Transponder (Week 2-3)
|
|
|
|
**Steps**:
|
|
1. Select frequency (HF/LF)
|
|
2. Place tag on antenna
|
|
3. Run detection command
|
|
4. Display tag information card
|
|
|
|
**Files**:
|
|
- `app/frontend/app/routes/workflows/id-tag.tsx`
|
|
- Component: Tag info card with icon
|
|
|
|
#### 3. Clone MIFARE Classic 1K (Week 3-4)
|
|
|
|
**Steps**:
|
|
1. Tune HF antenna
|
|
2. Read source card (64 blocks)
|
|
3. Verify read success
|
|
4. Write to target card
|
|
5. Verify write success
|
|
|
|
**Files**:
|
|
- `app/frontend/app/routes/workflows/clone-mifare.tsx`
|
|
- Progress tracking component
|
|
|
|
#### 4. Clone to T5577 (Week 4-5)
|
|
|
|
**Steps**:
|
|
1. Tune LF antenna
|
|
2. Read source tag
|
|
3. Configure T5577 settings
|
|
4. Write to T5577
|
|
5. Verify clone
|
|
|
|
**Files**:
|
|
- `app/frontend/app/routes/workflows/clone-t5577.tsx`
|
|
|
|
#### 5. Sniffing Utility (Week 5-6)
|
|
|
|
**Steps**:
|
|
1. Configure sniffing parameters
|
|
2. Start capture
|
|
3. Real-time frame display
|
|
4. Stop capture
|
|
5. Analyze captured data
|
|
|
|
**Files**:
|
|
- `app/frontend/app/routes/workflows/sniff.tsx`
|
|
- `app/shared/components/charts/ProtocolTimeline.tsx`
|
|
|
|
---
|
|
|
|
## 4. Implementation Roadmap
|
|
|
|
### Phase 1: Foundation (Week 1)
|
|
|
|
**Backend**:
|
|
- [ ] Create `app/backend/parsers/pm3_output.py`
|
|
- [ ] Implement antenna tuning parser
|
|
- [ ] Add `/api/pm3/command-with-data` endpoint
|
|
- [ ] Write parser unit tests
|
|
|
|
**Frontend**:
|
|
- [ ] Install Victory: `npm install victory`
|
|
- [ ] Create `app/shared/components/charts/` directory
|
|
- [ ] Implement `TuneChart.tsx`
|
|
- [ ] Test with mock data
|
|
|
|
**Deliverable**: Working HF/LF tune visualization
|
|
|
|
### Phase 2: Guided Workflows (Week 2-3)
|
|
|
|
**Frontend**:
|
|
- [ ] Create `GuidedWorkflow.tsx` framework
|
|
- [ ] Implement HF/LF Tune workflow
|
|
- [ ] Implement ID Transponder workflow
|
|
- [ ] Add progress indicators
|
|
- [ ] Mobile touch optimization
|
|
|
|
**Deliverable**: Two working guided workflows
|
|
|
|
### Phase 3: Advanced Workflows (Week 3-4)
|
|
|
|
**Backend**:
|
|
- [ ] Implement waveform parser
|
|
- [ ] Implement trace parser
|
|
- [ ] Add streaming support for sniffing
|
|
|
|
**Frontend**:
|
|
- [ ] Clone MIFARE Classic workflow
|
|
- [ ] Clone T5577 workflow
|
|
- [ ] `WaveformChart.tsx` with pan/zoom
|
|
- [ ] Progress tracking components
|
|
|
|
**Deliverable**: Complete cloning workflows
|
|
|
|
### Phase 4: Sniffing & Polish (Week 5-6)
|
|
|
|
**Frontend**:
|
|
- [ ] Sniffing utility implementation
|
|
- [ ] `ProtocolTimeline.tsx` chart
|
|
- [ ] Real-time data streaming
|
|
- [ ] Export capabilities (CSV/JSON)
|
|
|
|
**Polish**:
|
|
- [ ] Performance optimization
|
|
- [ ] Code splitting setup
|
|
- [ ] Mobile gesture improvements
|
|
- [ ] Accessibility improvements
|
|
|
|
**Deliverable**: Production-ready visualization system
|
|
|
|
---
|
|
|
|
## 5. Bundle Size Management
|
|
|
|
### Code Splitting Strategy
|
|
|
|
```typescript
|
|
// Lazy load charts only when needed
|
|
import { lazy, Suspense } from 'react'
|
|
|
|
const TuneChart = lazy(() => import('~/shared/components/charts/TuneChart'))
|
|
|
|
function TunePage() {
|
|
return (
|
|
<Suspense fallback={<div>Loading chart...</div>}>
|
|
<TuneChart data={data} />
|
|
</Suspense>
|
|
)
|
|
}
|
|
```
|
|
|
|
### Bundle Impact Analysis
|
|
|
|
| Component | Size (gzipped) | When Loaded |
|
|
|-----------|----------------|-------------|
|
|
| Base App | ~120KB | Initial |
|
|
| Victory Core | +35KB | On-demand |
|
|
| TuneChart | +5KB | Workflow page |
|
|
| WaveformChart | +8KB | Workflow page |
|
|
| **Total (worst case)** | **~170KB** | ✅ Acceptable |
|
|
|
|
**Optimization**: Most users won't load all charts in one session.
|
|
|
|
---
|
|
|
|
## 6. Cross-Platform Strategy
|
|
|
|
### React Native App (Future)
|
|
|
|
```typescript
|
|
// Same component, different import!
|
|
// Web version (Remix)
|
|
import { VictoryLine } from 'victory'
|
|
|
|
// React Native version
|
|
import { VictoryLine } from 'victory-native'
|
|
|
|
// Component code is IDENTICAL
|
|
export function TuneChart({ data }) {
|
|
return (
|
|
<VictoryChart>
|
|
<VictoryLine data={data} />
|
|
</VictoryChart>
|
|
)
|
|
}
|
|
```
|
|
|
|
### BLE Integration for Mobile
|
|
|
|
Enhanced BLE Manager will support:
|
|
- Command execution via BLE (offline PM3 operations)
|
|
- Status queries via BLE
|
|
- Guided workflow triggers via BLE
|
|
- Real-time data streaming via BLE
|
|
|
|
**Use Case**: User operates PM3 with phone via BLE, no WiFi needed.
|
|
|
|
---
|
|
|
|
## 7. Testing Strategy
|
|
|
|
### Backend Parser Tests
|
|
|
|
```python
|
|
# test_pm3_parsers.py
|
|
|
|
def test_parse_antenna_tuning():
|
|
output = "# LF antenna: 50.00 V @ 125.00 kHz"
|
|
result = parse_antenna_tuning(output)
|
|
assert result["voltage"] == 50.0
|
|
assert result["frequency"] == 125.0
|
|
assert result["optimal"] == True # > 45V threshold
|
|
```
|
|
|
|
### Frontend Chart Tests
|
|
|
|
```typescript
|
|
// Mock data testing (no hardware needed)
|
|
const mockTuneData = [
|
|
{ frequency: 120, voltage: 45 },
|
|
{ frequency: 125, voltage: 50 },
|
|
{ frequency: 130, voltage: 48 }
|
|
]
|
|
|
|
test('TuneChart renders with mock data', () => {
|
|
render(<TuneChart data={mockTuneData} />)
|
|
expect(screen.getByText(/Voltage/)).toBeInTheDocument()
|
|
})
|
|
```
|
|
|
|
### Mobile Touch Testing
|
|
|
|
- Test on actual smartphone (iPhone/Android)
|
|
- Verify pinch-zoom works smoothly
|
|
- Check 44px touch target compliance
|
|
- Test landscape orientation
|
|
|
|
---
|
|
|
|
## 8. Dependencies
|
|
|
|
### NPM Packages
|
|
|
|
```json
|
|
{
|
|
"dependencies": {
|
|
"victory": "^37.0.0", // ~35KB gzipped
|
|
"victory-native": "^37.0.0" // For React Native (later)
|
|
}
|
|
}
|
|
```
|
|
|
|
### Python Packages
|
|
|
|
No new dependencies - use standard library for parsing.
|
|
|
|
---
|
|
|
|
## 9. Success Metrics
|
|
|
|
### Technical
|
|
- [ ] Bundle size stays under 200KB (gzipped)
|
|
- [ ] Charts render in < 200ms on mobile
|
|
- [ ] Touch gestures work smoothly (60fps)
|
|
- [ ] Parser accuracy > 95% for all PM3 commands
|
|
|
|
### UX
|
|
- [ ] Users complete workflows 80% faster than manual commands
|
|
- [ ] Mobile users can operate PM3 with one hand
|
|
- [ ] Reduce support requests by 50% (guided workflows)
|
|
|
|
---
|
|
|
|
## 10. Future Enhancements
|
|
|
|
### After Initial Implementation
|
|
|
|
1. **Waveform Annotations**
|
|
- Mark interesting signal features
|
|
- Save/load annotated captures
|
|
|
|
2. **Chart Exports**
|
|
- Export as PNG/SVG
|
|
- Export data as CSV/JSON
|
|
- Share via mobile apps
|
|
|
|
3. **Advanced Analytics**
|
|
- Signal quality metrics
|
|
- Antenna tuning history tracking
|
|
- Success rate statistics
|
|
|
|
4. **Offline Support**
|
|
- Cache chart data locally
|
|
- Service worker for offline workflows
|
|
- Sync when reconnected
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Victory Charts** provides the perfect foundation for Dangerous Pi's visualization needs:
|
|
- ✅ True cross-platform support (Web, Mobile, Desktop)
|
|
- ✅ Mobile-first with touch gestures built-in
|
|
- ✅ Shared components = faster development
|
|
- ✅ Acceptable bundle size with code splitting
|
|
- ✅ Perfect for guided workflow UX
|
|
|
|
**Next Step**: Begin Phase 1 implementation (Backend parsers + Victory setup)
|
|
|
|
**Questions?** See [UI_GUIDELINES.md](UI_GUIDELINES.md) for styling and [claude.md](claude.md) for architecture details.
|