Files
pi-pm3/VISUALIZATION_PLAN.md
michael 4f35df1781 Initial commit - Phase 3/4
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 13:46:22 -08:00

16 KiB

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)

"""
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

# 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

// 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>
  )
}
// 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

// 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

// 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)

// 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

# 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

// 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

{
  "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 for styling and claude.md for architecture details.