7.7 KiB
RMon UI Specification
Technology
- Framework:
egui+eframe(immediate-mode Rust GUI) - Render backend:
glow(OpenGL 2.1+) — chosen for maximum Linux compatibility - Plotting:
egui_plotfor interactive panels; customeguipainter (line strip primitives) for high-throughput paths (>100k points/frame) - Async runtime:
tokio(runs in a background thread; communicates with egui viastd::sync::mpsc) - SSH tunnel management: spawns system
sshsubprocess
Deployment
Single binary. On Linux, dynamically links only:
libGL(OpenGL driver, always present on any desktop Linux)libX11/libxcb(X11, or Wayland vialibwayland-clientwithwaylandfeature)libc(glibc, standard on all Linux distributions)
On Windows: fully static (links Win32 APIs and ANGLE for OpenGL ES via DirectX).
Pre-built agent binaries for all supported architectures are embedded in the UI binary at compile time using include_bytes!. This allows the UI to deploy the agent without any extra files.
Layout
┌─────────────────────────────────────────────────────────────────────┐
│ [Connect ▼] [host: myserver] [⏺ Record] [⏹ Stop] [Status: OK] │ ← Toolbar
├────────────────┬────────────────────────────────────────────────────┤
│ Signal Tree │ Plot Panel 1 │ Plot Panel 2 │
│ │ │ │
│ ▼ imu │ [signal A] [signal B] [✕] │ [signal C] [✕] │
│ ▼ accel │ ┌──────────────────────────┐ │ ┌─────────────┐ │
│ accel_x ══╪═►│ time-series plot │ │ │ │ │
│ accel_y │ │ │ │ │ │ │
│ accel_z │ │ ╭─╮ ╭──╮ │ │ │ │ │
│ ▼ gyro │ │ ╯ ╰──╯ ╰─ │ │ │ │ │
│ gyro_x │ │ │ │ └─────────────┘ │
│ gyro_y │ │ ┼────────────────────── │ │ │
│ ▼ temps │ └──────────────────────────┘ │ [+ Add Plot] │
│ inlet │ [cursor A: t=12.3s val=1.2] │ │
│ outlet │ [cursor B: t=15.1s Δt=2.8s] │ │
│ │ [A-B: Δval=-0.3 mean=1.05] │ │
└───────────────┴────────────────────────────────┴───────────────────┘
Connection Dialog
Shown on startup (or via toolbar "Connect" button) when not connected.
Fields:
- SSH Host: free text, matched against
~/.ssh/configHost entries (autocomplete offered) - Agent Port: numeric, default
7891 - Agent Config: path on remote machine, default
~/.rmon/config.toml
On connect:
- Detect remote arch via
ssh {host} uname -m - Check / upload agent binary (see
docs/architecture.mdconnection flow) - Start agent if not running
- Establish port forward
- Perform protocol handshake
- Fetch signal list → populate signal tree
Connection errors are shown inline with a retry button.
Signal Tree Panel
- Collapsible tree mirroring the
pathfield of eachSignalInfo - Each leaf shows: label, unit, last value (live-updated), colored dot (source status)
- Drag-and-drop: drag a signal leaf onto a plot panel to add it
- Right-click menu: "Add to plot 1 / 2 / new plot", "Signal info" (opens info popup), "Start recording", "Stop recording"
- Search/filter bar at the top of the panel
- Recording state shown with red ⏺ icon on the signal leaf
Plot Panels
Any number of plot panels can be open (horizontally tiled by default, resizable/detachable).
Axes
- X axis: time (absolute UTC or relative to a reference point; toggled in toolbar)
- Y axis: auto-range by default; can be locked manually
- Zoom: scroll wheel (time axis), Ctrl+scroll (Y axis)
- Pan: click-drag on the plot
Signals in a Plot
- Each signal rendered as a colored line
- Legend at top of plot (click to hide/show individual signals)
- Color is auto-assigned, can be changed via right-click on the legend entry
- Line style (solid/dashed/dotted) and width configurable per signal
Cursors
- Add cursors via toolbar button or right-click on plot → "Add cursor"
- Drag to position; snaps to nearest sample if within threshold
- Measurement bar below the plot shows:
- Per cursor: timestamp, value for each visible signal
- Between two cursors: Δt, Δvalue, mean, min, max over the interval
Math Signals
- Right-click plot → "Add math signal"
- Expression editor (simple formula:
a + b,a * 2.0,a - b, etc. wherea,bare signal IDs) - Math signals appear in the legend with an
f(x)icon and can be added to other plots
High-Throughput Rendering
When a signal has more points than pixels in the X direction, the renderer decimates using min-max downsampling (preserves peaks/troughs visually). This runs on the CPU in plot_panel.rs before passing vertices to egui's painter.
Toolbar
- Connect / Disconnect dropdown (shows current host or "Not connected")
- Time range selector: presets (last 10s / 1min / 10min / 1h) or custom range picker
- Time display toggle: absolute UTC vs relative
- Record button: opens signal selection dialog → starts recording for selected signals
- Status indicator: green = connected, yellow = reconnecting, red = disconnected
Keyboard Shortcuts
| Key | Action |
|---|---|
Space |
Pause / resume live scrolling |
R |
Reset all plot zoom/pan to live view |
C |
Add cursor to focused plot |
Del |
Remove selected cursor |
Ctrl+N |
Add new plot panel |
Ctrl+W |
Close focused plot panel |
Ctrl+F |
Focus signal tree search |
Ctrl+S |
Save current layout to file |
Session Persistence
On clean exit, the UI saves the current session to ~/.config/rmon/last_session.toml:
- Connected host alias
- Open plot panels and their signal assignments
- Cursor positions
- Time range and zoom state
On next launch with the same host, it offers to restore the session.
State Machine (connection)
Disconnected
│ user clicks Connect
▼
Deploying (copying/verifying agent binary)
│ agent ready
▼
Tunneling (SSH port-forward process running)
│ port open
▼
Handshaking (TCP open, sent Handshake, awaiting HandshakeAck)
│ accepted
▼
Connected (normal operation, DataBatch messages flowing)
│ tunnel drops / error
▼
Reconnecting (exponential backoff, max 30s, reuses same tunnel if still up)
│ max retries exceeded
▼
Disconnected
Async / UI Thread Boundary
tokioruntime runs in a dedicated OS thread- The egui render loop runs on the main thread (required by most platform window systems)
- Communication:
std::sync::mpsc::channel— the tokio tasks pushUiEventvariants (DataBatch, SignalList, Status, Error) to the UI; the UI pushesCommandvariants (Subscribe, QueryHistory, StartRecording) to tokio eframe::App::update()drains the incoming event channel each frame before rendering