Skip to content

Add lcmflow: animated LCM packet highway TUI#2456

Open
spomichter wants to merge 1 commit into
mainfrom
feat/lcmflow
Open

Add lcmflow: animated LCM packet highway TUI#2456
spomichter wants to merge 1 commit into
mainfrom
feat/lcmflow

Conversation

@spomichter

Copy link
Copy Markdown
Contributor

What

lcmflow — a real-time animated visualization of LCM traffic, sibling to lcmspy. Each topic is a lane on a highway; every packet drives across it as a vehicle:

  • Packet size picks the vehicle class: tiny telemetry (cmd_vel, tf, ≤256 B) are fast 1-cell dots; raw images and point clouds (>512 KiB) are 11-cell trucks at 0.65× speed. Five log-scale bands in between.
  • Bursts coalesce: packets arriving faster than a lane can fit merge into one vehicle with a ×N count badge (e.g. a 14 Hz raw image stream shows as ×15 convoys). Lossless — stats always reflect true packet counts/bytes.
  • Color by message type (Image=yellow, PointCloud2=purple, OccupancyGrid=blue, TFMessage=green, …), stable hash fallback for unknown types.
  • Per-lane Hz / bandwidth / total stats, global header stats, fading trails, scrollable lanes, DimOS tcss theme.
  • Keys: space pause, s cycle sort (arrival/traffic/name), q quit. lcmflow web serves the TUI in a browser via textual-serve (same as lcmspy).

How

  • dimos/utils/cli/lcmflow/lcmflow.py: renderer-agnostic model — PacketSpy(LCMSpy) collector with a thread-safe packet queue, Lane/Vehicle physics with on-ramp coalescing. Reuses lcmspy's Topic sliding-window stats.
  • dimos/utils/cli/lcmflow/run_lcmflow.py: Textual app using the line API (render_line/Strip) at 20 fps.
  • Entry points: lcmflow console script + dimos lcmflow subcommand; docs in docs/usage/cli.md and AGENTS.md.
  • Drive-by fix: removed a stray print(self.config) debug line in lcmspy.py that fired on every new topic and corrupted TUI output.

Testing

  • 11 unit tests (test_lcmflow.py): size classing, coalescing, class upgrade, convoy stretch, pruning, drain, pause semantics. All lcmspy tests still pass.
  • mypy + ruff clean.
  • Live-tested against dimos --replay run unitree-go2: 7 lanes at ~42 MiB/s, verified in a real xterm and via headless Textual screenshots.

Each topic is a lane; every packet drives across it as a vehicle.
Packet size picks the vehicle class (tiny fast dots for cmd_vel/tf,
long slow trucks for images and point clouds); bursts faster than a
lane can fit coalesce into one vehicle with a xN count badge.

- dimos/utils/cli/lcmflow/: model (size classes, lanes, coalescing),
  Textual TUI (line-API renderer, pause/sort/scroll, web mode)
- new entry points: lcmflow + dimos lcmflow
- lcmspy: remove stray debug print on new-topic creation
- docs: cli.md section, AGENTS.md CLI table
@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces lcmflow, a real-time animated TUI that visualizes LCM packet traffic as vehicles driving down per-topic highway lanes — each packet's size determining vehicle class, speed, and length, with high-frequency bursts coalescing into a single vehicle with a ×N badge. It also removes a stray print(self.config) debug call from lcmspy.py that was corrupting TUI output on every new topic.

  • lcmflow.py provides a renderer-agnostic model (Vehicle, Lane, Highway, PacketSpy) using a bounded deque for lock-free packet queuing between the LCM callback thread and the UI thread. Stats are tracked independently of visualization, so pause correctly freezes the animation without dropping stats.
  • run_lcmflow.py implements the Textual app with a ScrollView using the line-rendering API (render_line/Strip) at 20 fps, plus a HeaderBar refreshing at 0.5 Hz for global stats.
  • 11 focused unit tests cover size classing, coalescing, class upgrade, convoy stretch, pruning, drain, and pause semantics.

Confidence Score: 4/5

Safe to merge; all issues are cosmetic or affect optional subcommands only.

The model and rendering logic are correct and well-tested. The three findings are all non-blocking: a badge rendering off-by-one that silently suppresses 3-char count badges (×10–×999) on exactly 4-cell vehicles, the web subcommand inheriting lcmspy's pattern of using bare python instead of sys.executable, and a weak positional hash that can assign the same fallback color to anagram channel names.

The badge placement condition and web-server spawn command in run_lcmflow.py are the two spots worth a second look.

Important Files Changed

Filename Overview
dimos/utils/cli/lcmflow/lcmflow.py Core model layer: Vehicle/Lane/Highway physics, PacketSpy queue, coalescing logic. One off-by-one in badge placement condition; otherwise well-structured and thread-safe by design (bounded deque, GIL-safe popleft/append).
dimos/utils/cli/lcmflow/run_lcmflow.py Textual TUI renderer: HighwayView (line API), HeaderBar, LCMFlowApp. Web mode spawns textual-serve with bare python command (same issue as lcmspy). Style identity comparison is an intentional valid optimization.
dimos/utils/cli/lcmflow/test_lcmflow.py 11 unit tests covering size classes, coalescing, class upgrade, stretch, prune, drain, and pause semantics. Tests are well-targeted and confirm intentional behavior.
dimos/utils/cli/lcmspy/lcmspy.py Drive-by fix: removes stray print(self.config) debug line that fired on every new topic and corrupted TUI output. Straightforward and correct.
dimos/utils/cli/lcmflow/demo_lcmflow.py Headless screenshot harness using Textual's run_test pilot; injects synthetic packet streams and saves SVG. Self-contained and harmless.
dimos/robot/cli/dimos.py Adds lcmflow subcommand with the same argv-passthrough pattern used by lcmspy. Correct and consistent.
pyproject.toml Adds lcmflow console script entry point. textual-serve (needed for lcmflow web) is not listed as a dependency, matching the existing lcmspy behavior where it is treated as an optional import.

Sequence Diagram

sequenceDiagram
    participant LCM as LCM Network
    participant PS as PacketSpy (LCM callback thread)
    participant HW as Highway
    participant HV as HighwayView (Textual @ 20 fps)
    participant HD as HeaderBar (@ 0.5 Hz)

    LCM->>PS: msg(channel, data)
    PS->>PS: super().msg() — update Topic stats
    PS->>PS: pending.append((channel, len(data)))

    loop Every 50 ms
        HV->>HW: tick(road_width)
        HW->>PS: drain() — always, even when paused
        alt not paused
            HW->>HW: "_clock += dt"
            loop each packet
                HW->>HW: lane.spawn(nbytes, clock)
            end
            HW->>HW: lane.prune(clock, road_width)
        else paused
            HW->>HW: discard drained packets
        end
        HV->>HV: _sorted_lanes() snapshot
        HV->>HV: refresh() — render_line per visible row
    end

    loop Every 500 ms
        HD->>PS: freq / kbps_hr / total_traffic_hr
        HD->>HD: update header markup
    end
Loading

Reviews (1): Last reviewed commit: "Add lcmflow: animated LCM packet highway..." | Re-trigger Greptile

badge = f"×{min(vehicle.count, 999)}" # noqa: RUF001 — intentional UI glyph
badge_style = Style(color=theme.BACKGROUND, bgcolor=lane.color, bold=True)
start = tail + max(0, (length - 1 - len(badge)) // 2)
if start >= 0 and start + len(badge) < head:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Off-by-one in badge placement guard: start + len(badge) < head excludes the case where the badge ends exactly one cell before the head character. For a 4-cell vehicle (indices [tail, tail+1, tail+2, head]) with a 3-char badge (counts 10–999), start + 3 == head which fails the strict-less-than check, so the badge is silently dropped even though it fits perfectly without touching the head cell. The condition should be <= head.

Suggested change
if start >= 0 and start + len(badge) < head:
if start >= 0 and start + len(badge) <= head:


from textual_serve.server import Server # type: ignore[import-not-found]

server = Server(f"python {os.path.abspath(__file__)}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The web subcommand spawns textual-serve with a hard-coded python executable. In virtual environments where python is not on PATH (only python3, or a venv-local python3.12), lcmflow web will fail at runtime. This copies the same pattern from run_lcmspy.py, but since this PR introduces the feature, using sys.executable here would be the right fix for both.

Suggested change
server = Server(f"python {os.path.abspath(__file__)}")
server = Server(f"{sys.executable} {os.path.abspath(__file__)}")

Comment on lines +139 to +140
digest = sum(channel.encode())
return FALLBACK_PALETTE[digest % len(FALLBACK_PALETTE)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The fallback hash sum(channel.encode()) sums byte values, so any two channel names that are anagrams of each other (e.g. /ab and /ba) hash to the same bucket and get the same color. A slightly stronger but still stable alternative uses position-weighted byte values to distinguish permutations.

Suggested change
digest = sum(channel.encode())
return FALLBACK_PALETTE[digest % len(FALLBACK_PALETTE)]
digest = sum(b * (i + 1) for i, b in enumerate(channel.encode()))
return FALLBACK_PALETTE[digest % len(FALLBACK_PALETTE)]

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.25862% with 11 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/utils/cli/lcmflow/lcmflow.py 94.36% 4 Missing and 4 partials ⚠️
dimos/robot/cli/dimos.py 40.00% 3 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Jun 10, 2026
@leshy

leshy commented Jun 11, 2026

Copy link
Copy Markdown
Member

is this a real PR? I wouldn't merge this,or put in experimental

@spomichter

Copy link
Copy Markdown
Contributor Author

No just for fun / maybe experimental

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants