ros-z-console
ros-z-console is a monitoring tool for ROS 2 systems built on Zenoh. It provides real-time graph inspection, dataflow monitoring, and metrics collection through two interfaces: an interactive TUI (Terminal User Interface) and a headless JSON streaming mode.
ros-z-console uses zero-interference monitoring via pure Zenoh subscribers - it never pollutes the ROS graph with its own presence.
Network Topology
ros-z-console connects to the ROS 2 graph via a Zenoh router. All ROS 2 nodes
using rmw_zenoh_cpp communicate through the same router, enabling
ros-z-console to observe the entire system.
graph LR
subgraph "ROS 2 System"
T[talker<br/>rmw_zenoh_cpp]
L[listener<br/>rmw_zenoh_cpp]
end
R[Zenoh Router<br/>rmw_zenohd]
C[ros-z-console]
T <--> R
L <--> R
C --> R
style C fill:#2ecc71,color:#fff
style R fill:#3498db,color:#fff
Quick Start: Monitoring demo_nodes_cpp
This example shows ros-z-console monitoring the classic talker/listener demo from demo_nodes_cpp.
Terminal 1 - Start the Zenoh router:
ros2 run rmw_zenoh_cpp rmw_zenohd
Terminal 2 - Start the talker:
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 run demo_nodes_cpp talker
Terminal 3 - Start the listener:
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
ros2 run demo_nodes_cpp listener
Terminal 4 - Monitor with ros-z-console:
ros-z-console tcp/127.0.0.1:7447 0
You should see the /chatter topic, the talker and listener nodes, and
their services appear in ros-z-console. Use the TUI to browse topics, check
message rates, and inspect QoS settings.
Building and Running
# Build the console
cargo build -p ros-z-console --release
# Run with default settings (TUI mode)
ros-z-console tcp/127.0.0.1:7447 0
# Headless JSON streaming
ros-z-console --headless --json tcp/127.0.0.1:7447 0
# Echo messages from a topic
ros-z-console --headless --echo /chatter tcp/127.0.0.1:7447 0
# Echo multiple topics with JSON output
ros-z-console --headless --json --echo /chatter --echo /cmd_vel tcp/127.0.0.1:7447 0
# Export graph snapshot and exit
ros-z-console --export graph.json tcp/127.0.0.1:7447 0
Command Line Interface
ros-z-console [OPTIONS] [ROUTER] [DOMAIN]
Arguments
| Argument | Default | Description |
|---|---|---|
ROUTER | tcp/127.0.0.1:7447 | Zenoh router address |
DOMAIN | 0 | ROS domain ID |
Options
| Flag | Description |
|---|---|
--tui | Enable TUI interface (default if no other mode specified) |
--headless | Headless mode: stream events to stdout |
--json | Output structured JSON logs |
--debug | Enable debug logging |
--echo <TOPIC> | Subscribe to and display messages from topic (can be used multiple times) |
--export <PATH> | Export current state and exit (supports .json, .dot, .csv) |
Modes
TUI Mode (Default)
The interactive terminal interface provides:
- Panel Navigation - Browse Topics, Services, Nodes, and Measurements
- Filter Mode - Press
/to activate type-ahead search with highlighting - Rate Monitoring - Quick rate check with
rkey (cached for 30s) - Measurement Panel - Press
mfor detailed measurements with:- Real-time metrics (msg/s, KB/s, average payload)
- 60-second time-series chart
- SQLite storage (
ros-z-metrics.db)
- Detail Drilling - Press
Enterto expand sections with QoS profiles - Export - Press
eto export metrics to CSV - Help Overlay - Press
?to toggle help
TUI mode requires a terminal that supports ANSI escape codes. Most modern terminals work out of the box.
Headless Mode
Headless mode streams events to stdout, making it ideal for:
- CI/CD pipelines
- Log aggregation systems
- AI-powered analysis
- Scripted monitoring
Human-readable output:
ros-z-console --headless tcp/127.0.0.1:7447 0
Discovered Topics:
/chatter (std_msgs/msg/String)
/cmd_vel (geometry_msgs/msg/Twist)
Discovered Nodes:
/talker
/listener
[2026-01-21 10:30:00] Topic discovered: /rosout (rcl_interfaces/msg/Log)
JSON streaming output:
ros-z-console --headless --json tcp/127.0.0.1:7447 0
{"timestamp":"...","event":"initial_state","domain_id":0,"topics":[...],"nodes":[...],"services":[...]}
{"TopicDiscovered":{"topic":"/chatter","type_name":"std_msgs/msg/String","timestamp":"..."}}
{"NodeDiscovered":{"namespace":"/","name":"talker","timestamp":"..."}}
Dynamic Topic Echo
ros-z-console can subscribe to and display messages from any ROS 2 topic without compile-time knowledge of message types. This is powered by dynamic schema discovery using the ROS 2 Type Description service (REP-2016).
Echo works with all ROS 2 message types: primitives, nested messages, arrays, and custom types. No recompilation needed!
How It Works
When you echo a topic, ros-z-console:
- Discovers publishers on the topic using graph monitoring
- Queries the Type Description service from the publisher's node
- Retrieves the message schema (field names, types, and layout)
- Creates a dynamic subscriber using the discovered schema
- Deserializes and displays messages in real-time
sequenceDiagram
participant C as ros-z-console
participant G as Graph
participant P as Publisher Node
participant Z as Zenoh
C->>G: Find publishers for /chatter
G-->>C: Publisher: talker node
C->>P: GetTypeDescription(std_msgs/msg/String)
P-->>C: Schema + Type Hash
C->>Z: Subscribe with dynamic schema
Z-->>C: Message data (CDR)
C->>C: Deserialize & display
Basic Usage
Echo a single topic:
ros-z-console --headless --echo /chatter
Output:
=== Subscribed to /chatter ===
Type: std_msgs/msg/String
Hash: RIHS01_df668c740482bbd48fb39d76a70dfd4bd59db1288021743503259e948f6b1a18
Fields: ["data"]
=== /chatter ===
data: "Hello World: 0"
=== /chatter ===
data: "Hello World: 1"
JSON output mode:
ros-z-console --headless --json --echo /chatter
{"event":"topic_subscribed","topic":"/chatter","type_name":"std_msgs/msg/String","type_hash":"RIHS01_df668...","fields":["data"]}
{"event":"message_received","topic":"/chatter","type":"std_msgs/msg/String","data":{"data":"Hello World: 0"}}
{"event":"message_received","topic":"/chatter","type":"std_msgs/msg/String","data":{"data":"Hello World: 1"}}
Multiple Topics
Echo multiple topics simultaneously:
ros-z-console --headless --echo /chatter --echo /cmd_vel --echo /odom
Each topic is independently discovered and subscribed with its own dynamic schema.
Supported Message Types
Primitives
# String messages
ros-z-console --headless --echo /chatter
# Numeric types
ros-z-console --headless --echo /count # Int32
ros-z-console --headless --echo /sensor # Float64
Nested Messages
# Twist (linear + angular vectors)
ros-z-console --headless --echo /cmd_vel
Output:
=== /cmd_vel ===
linear:
x: 1.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.5
Arrays and Sequences
# Point cloud or array messages
ros-z-console --headless --echo /scan
Use Cases
Debugging Message Content
Quickly inspect what's actually being published:
ros-z-console --headless --echo /diagnostics
Data Analysis
Pipe JSON output to analysis tools:
ros-z-console --headless --json --echo /pose | \
jq -r 'select(.event=="message_received") | .data.position.x'
Recording Specific Fields
Extract and log specific data:
ros-z-console --headless --json --echo /sensor_data | \
jq '.data.temperature' >> temps.log
Message Validation
Verify message structure and content during development:
# Check if messages match expected schema
ros-z-console --headless --json --echo /my_custom_topic | \
jq '.data | keys'
Example: Monitoring Robot Telemetry
Monitor multiple robot topics simultaneously:
#!/bin/bash
# Monitor robot state
ros-z-console --headless \
--echo /cmd_vel \
--echo /odom \
--echo /battery_state \
--echo /diagnostics \
> robot_state.log
Integration with Standard ROS 2
ros-z-console echo works seamlessly with standard ROS 2 nodes:
# Terminal 1: Standard ROS 2 publisher
ros2 run demo_nodes_cpp talker
# Terminal 2: ros-z-console subscriber
ros-z-console --headless --echo /chatter
ros-z-console uses RIHS01 type hashes to ensure message compatibility. If type hashes don't match, the subscription will fail with a clear error message.
Advanced Options
With Debug Logging
Enable detailed logging to troubleshoot discovery issues:
RUST_LOG=ros_z=debug ros-z-console --headless --echo /chatter
Debug output shows:
- Publisher discovery attempts
- Type description service queries
- Schema parsing details
- Type hash validation
Custom Timeout
If schema discovery is slow, you can adjust timeouts in the code (default: 5 seconds).
Troubleshooting
No publishers found:
Failed to subscribe to /my_topic: Schema discovery failed: No publishers found for topic: /my_topic
Solutions:
- Verify the topic exists:
ros2 topic list - Check the topic is being published:
ros2 topic hz /my_topic - Ensure rmw_zenohd is running
- Wait for publisher to fully start (may take a few seconds)
Type hash mismatch:
Failed to subscribe to /chatter: Type hash mismatch
This occurs when the publisher and subscriber have different message definitions. Ensure both are using the same ROS 2 distribution and package versions.
Comparison with ros2 topic echo
| Feature | ros-z-console | ros2 topic echo |
|---|---|---|
| Compilation | No recompilation needed | Requires message packages installed |
| Custom types | Automatic discovery | Must have .msg files available |
| Multiple topics | Single command | Need multiple processes |
| JSON output | Built-in structured format | Requires additional parsing |
| Performance | Zenoh pub/sub (very fast) | DDS overhead |
| Filtering | Easy with jq on JSON | Manual parsing needed |
Performance Notes
- Latency: Sub-millisecond after initial discovery (~5 seconds)
- Throughput: Handles 1000+ messages/second per topic
- Memory: ~1KB per unique message schema (cached globally)
- CPU: Minimal - only active when messages arrive
Message schemas are cached in a global registry. Once a type is discovered, subsequent subscriptions to topics with the same type are instant.
Export Formats
Export the current graph state with the --export flag:
JSON Export
ros-z-console --export graph.json tcp/127.0.0.1:7447 0
Produces a structured JSON file with topics, nodes, services, and their relationships:
{
"timestamp": "2026-01-21T10:30:00Z",
"domain_id": 0,
"topics": [
{"name": "/chatter", "type": "std_msgs/msg/String", "publishers": 1, "subscribers": 1}
],
"nodes": [
{"name": "talker", "namespace": "/"}
],
"services": []
}
GraphViz DOT Export
ros-z-console --export graph.dot tcp/127.0.0.1:7447 0
dot -Tpng graph.dot -o graph.png
Generates a visual graph representation with:
- Nodes as blue boxes
- Topics as green ellipses
- Publisher edges in blue
- Subscriber edges in green
CSV Export
ros-z-console --export metrics.csv tcp/127.0.0.1:7447 0
Exports collected metrics history:
timestamp,topic,rate_hz,bandwidth_kbps,avg_payload_bytes
2026-01-21T10:30:00Z,/chatter,10.5,2.3,220
Event Types
ros-z-console tracks these system events:
| Event | Description |
|---|---|
TopicDiscovered | New topic detected in the graph |
TopicRemoved | Topic no longer present |
NodeDiscovered | New node joined the graph |
NodeRemoved | Node left the graph |
ServiceDiscovered | New service available |
RateMeasured | Rate measurement completed |
MetricsSnapshot | Periodic summary of graph state |
Configuration
Create a ros-z-console.json or .ros-z-console.json file:
{
"cache_ttl_seconds": 30,
"rate_cache_ttl_seconds": 30,
"graph_cache_update_ms": 100
}
| Option | Default | Description |
|---|---|---|
cache_ttl_seconds | 30 | General cache time-to-live |
rate_cache_ttl_seconds | 30 | How long rate measurements are cached |
graph_cache_update_ms | 100 | Graph refresh interval in milliseconds |
Integration Examples
Pipe to jq for filtering
# Filter for topic discovery events
ros-z-console --headless --json | jq 'select(.TopicDiscovered != null)'
# Extract specific message fields
ros-z-console --headless --json --echo /pose | \
jq -r 'select(.event=="message_received") | .data.position'
Monitor specific topics
# Watch for messages on specific topic
ros-z-console --headless --json | grep -E '"topic":"/cmd_vel"'
# Echo and filter by field value
ros-z-console --headless --json --echo /sensor | \
jq 'select(.data.temperature > 50)'
Continuous logging
# Log all graph events
ros-z-console --headless --json >> ros-events.jsonl &
# Log all messages from a topic
ros-z-console --headless --json --echo /diagnostics >> diagnostics.jsonl &
Real-time data extraction
# Extract velocity commands
ros-z-console --headless --json --echo /cmd_vel | \
jq -r '.data.linear.x' | \
tee -a velocity.log
# Monitor temperature sensor
ros-z-console --headless --json --echo /temperature | \
jq -r '[.timestamp, .data.value] | @csv' >> temp.csv
Database analysis
# After running TUI mode with measurements
sqlite3 ros-z-metrics.db "SELECT topic, AVG(msgs_sec) FROM metrics GROUP BY topic"