Skip to content

GUI Refactor: Real-time Waveform Visualization and Modern Interface #1

@muqiuhan

Description

@muqiuhan

Background

The current GUI module (host/gui.py) works—serial connection, ELF parsing, and variable read/write are all functional. But periodic read just dumps values into a table. Watching numbers scroll by isn't useful for tuning.

When tuning PID parameters, what you actually need is to see the step response curve of motor_speed—overshoot, settling time, oscillation. You can't extract any of that from a stream of numbers.

The protocol already supports periodic sampling (commands 0x11–0x18): send an address list once, and the device uploads data on its own schedule. The GUI doesn't use this capability—it's still polling with QTimer for single reads, wasting the protocol's design.

Goals

  1. Real-time waveform display with multi-variable overlay, zoom, pause, and export
  2. Leverage the protocol's periodic sampling instead of polling
  3. A better-looking interface with a dark theme and sensible layout

Tech Stack

Component Choice Reason
GUI Framework PySide6 Already in use, no migration needed
Waveform Plotting pyqtgraph Qt OpenGL-based, handles 10ms refresh easily, integrates naturally with PySide6
Theme Catppuccin Mocha Dark theme with high contrast, easy on the eyes during debugging sessions
Layout Three-panel Variable list on the left, toolbar on top, waveform in the center

Tauri + React + ECharts would produce a prettier interface, but it requires a WebSocket bridge to the Python backend and pushes development time to 2–3 weeks. pyqtgraph can deliver a working prototype in one week—let's start there.

Interface Design

Layout

┌─────────────────────────────────────────────────────────────────┐
│  sparam              [COM3 ▼] [115200 ▼] [Connect]   ⚙ ⏹      │
├────────────┬────────────────────────────────────────────────────┤
│            │                                                    │
│  🔍 Search │                Waveform Panel                      │
│  ────────  │                                                    │
│ ▸ motor_   │    ┌──────────────────────────────────────────┐   │
│   speed    │    │  ╭─────────────────────────────────────╮ │   │
│   current  │    │  │  〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰〰   │ │   │
│   kp       │    │  ╰─────────────────────────────────────╯ │   │
│   ki       │    └──────────────────────────────────────────┘   │
│            ├────────────────────────────────────────────────────┤
│            │   ┌─────────┐ ┌─────────┐ ┌─────────┐              │
│            │   │speed    │ │current  │ │kp       │              │
│            │   │1234.56  │ │56.78    │ │1.50     │              │
│            │   └─────────┘ └─────────┘ └─────────┘              │
│            ├────────────────────────────────────────────────────┤
│            │   Log: 10:23:45 WRITE kp <= 1.5                    │
│            │       10:23:46 READ speed = 1234                   │
└────────────┴────────────────────────────────────────────────────┘

Sidebar: Variable list with search and collapsible groups. Double-click to add to monitor.

Toolbar: Serial configuration and global actions.

Main area: Waveform takes visual priority. Value cards below show current readings and trends. Log panel records operation history.

Color Scheme

Catppuccin Mocha dark theme:

Element Hex Usage
Background #1e1e2e Window base
Sidebar #181825 Left panel
Card #313244 Inputs, value cards
Border #45475a Dividers
Primary text #cdd6f4 Titles, values
Muted text #a6adc8 Secondary info
Accent #89b4fa Buttons, selection, first waveform
Success #a6e3a1 Connected, ACK
Warning #f9e2af NACK
Error #f38ba8 Disconnected, errors

Waveform colors cycle through blue, green, orange, purple, cyan for multi-variable plots.

Waveform Features

  • Auto-scaling Y-axis
  • Scrolling X-axis window (configurable: 5s/10s/30s/∞)
  • Mouse wheel zoom, drag to pan
  • Click to show precise value at cursor
  • Pause/resume for screenshots and analysis
  • Export to PNG or CSV
  • Multi-variable overlay with distinct colors

Value Cards

Each monitored variable gets a card:

┌──────────────────┐
│ motor_speed      │  Variable name
│ 1234.56          │  Current value (large)
│ ▲ +2.3%          │  Trend indicator
└──────────────────┘

A colored stripe on the left matches the waveform color.

Implementation

Directory Structure

host/
├── sparam/                  # Backend logic (unchanged)
├── gui/                     # Refactored GUI
│   ├── main.py              # Entry point
│   ├── main_window.py       # Main window layout
│   ├── widgets/             # Custom widgets
│   │   ├── sidebar.py       # Variable list
│   │   ├── toolbar.py       # Toolbar
│   │   ├── waveform_plot.py # Waveform display
│   │   ├── value_card.py    # Value cards
│   │   └── log_panel.py     # Log panel
│   └── styles/
│       └── catppuccin.py    # Theme QSS
├── cli.py
└── pyproject.toml

Core Classes

WaveformPlot — Waveform widget wrapping pyqtgraph:

class WaveformPlot(QWidget):
    def add_variable(self, name: str, color: str): ...
    def remove_variable(self, name: str): ...
    def update_data(self, name: str, timestamp: float, value: float): ...
    def set_time_window(self, seconds: float): ...
    def pause(self): ...
    def resume(self): ...
    def export_png(self, filepath: str): ...
    def export_csv(self, filepath: str): ...

DeviceManager — Handles periodic sampling and data distribution:

class DeviceManager:
    def start_monitor(self, variables: List[Variable], rate: int): ...
    def stop_monitor(self): ...
    def add_callback(self, callback): ...

Data Flow

flowchart LR
    subgraph Device
        A[sparam.c] -->|Periodic sampling| B[UART DMA]
    end
    
    subgraph Host
        C[SerialConnection] -->|Raw bytes| D[DeviceManager]
        D -->|Decode frame| E[Protocol.decode]
        E -->|name, timestamp, value| F[Callback dispatch]
        F --> G[WaveformPlot]
        F --> H[ValueCard]
    end
    
    B -->|Serial| C
Loading

The device uploads data on its own after receiving a periodic read command. DeviceManager runs a receive thread, parses frames, and dispatches results to waveform widgets and value cards.

Key Changes

Replace polling with event-driven model

Current:

self.monitor_timer.timeout.connect(self.poll_monitored_var)
# Request every N ms, wait for response

New:

device_manager.start_monitor(variables, rate=3)  # Send once
device_manager.add_callback(self._on_data_received)  # Register callback
# Device uploads continuously, callback fires passively

Ring buffer for waveform data

Each variable has a ring buffer (default 1000 points). When full, oldest data is discarded. Pause stops updates but preserves history.

Thread safety

Data reception runs in a worker thread; UI updates must happen on the main thread. Use Qt Signal for cross-thread communication:

class DeviceManager(QObject):
    data_received = Signal(str, float, float)

device_manager.data_received.connect(waveform_plot.update_data)

Potential Risks

  • pyqtgraph may stutter at high refresh rates (>50Hz) or with many variables (>10). Mitigation: limit buffer size, reduce draw precision
  • Serial packet loss can cause frame boundary misalignment. The existing frame header search recovers, but lost data cannot be retransmitted
  • Large Y-axis range differences between variables (e.g., 0–100 vs 0–10000) compress waveforms. May need dual Y-axes or split views
  • Windows high-DPI displays may appear blurry without explicit high-DPI scaling
  • pyqtgraph depends on OpenGL; may not render correctly in VMs or remote desktop sessions

Future Extensions

  • Variable grouping by module/function
  • Waveform playback from CSV history
  • FFT analysis for frequency-domain view
  • Remote access via WebSocket + web frontend
  • Multi-device simultaneous monitoring

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions