ROS 2
The ROS 2 bridge allows you to put a live OmniLoop control plane on a running robot. This is where OmniLoop’s halt, live-mutate, and no-restart capabilities matter most — bringing up or debugging real hardware, not just tuning a simulation.
The bridge handles data in three directions:
- Telemetry (Robot -> Dashboard): Subscribes to topics; each message is automatically flattened into scalar readouts.
- Control (Dashboard -> Robot): Exposes parameters as sliders or toggles; edits in the dashboard are pushed as ROS 2 parameter updates.
- Lifecycle (Dashboard -> Robot): Drives managed (
rclcpp_lifecycle) nodes through their state machine — configure / activate / deactivate / cleanup — from the dashboard, with the live state reflected back.
Start the bridge with your desired telemetry topics and parameters:
import rclpyfrom omniloop.integrations.ros2 import OmniLoopRosBridge
rclpy.init()bridge = OmniLoopRosBridge( node_name="omniloop_bridge", telemetry_topics=[ ("/imu/base_vel", "std_msgs/msg/Float64") ], params=[ dict(name="max_speed", type="float", kind="slider", min=0.0, max=2.0, step=0.05, default=1.0, target_node="/controller") ], lifecycle_nodes=["/controller"], # drive a managed node from the dashboard)bridge.spin() # Blocks; publishes telemetry and applies dashboard editsFeatures
Section titled “Features”- Message Flattening: OmniLoop automatically unwraps standard messages (like
std_msgs .data) and walks nested fields to create clean, readable scalars in the dashboard. - Parameter Push: When you move a slider in the dashboard, the bridge uses an
AsyncParameterClientto dynamically update the parameter on the target node.
Managed (lifecycle) nodes
Section titled “Managed (lifecycle) nodes”Pass lifecycle_nodes=[...] to drive managed nodes from the control plane. Each entry is a node name ("/controller") or a dict ({"node": "/controller", "label": "Controller"}). For each managed node the bridge adds two dashboard controls under a Lifecycle group:
- a transition control — set its value to
configure,activate,deactivate,cleanup, orshutdownto request that transition; - a readonly state readout (
<node>_lifecycle_state) reflecting the node’s live primary state (unconfigured/inactive/active/finalized).
Transitions are issued through each node’s change_state service (asynchronously, so a timer callback never blocks the executor), and state is polled via get_state. Because these are ROS 2 service calls — not node teardown — a deactivate/activate cycle does not destroy the bridge’s telemetry subscriptions (nor the managed node’s own publishers/subscriptions, which live across the inactive state by design).
For scripted control outside the dashboard, LifecycleController wraps the same services directly:
from omniloop.integrations.ros2 import LifecycleController
lc = LifecycleController(node)lc.transition("/controller", "configure")lc.transition("/controller", "activate")lc.request_state("/controller", lambda state_id, label: print(label))| Parameter | Type | Description |
|---|---|---|
lifecycle_nodes |
List | Managed nodes to drive: a node name or {"node", "label"} dict. |
lifecycle_poll_hz |
Float | How often to refresh managed-node state readouts (default 2.0). |