openvibers/openvibe.link Commits
Node: ROS 2 robots (a bundled ros2 plugin: cmd_vel, battery, scan/range, odometry and camera, over rclpy or rosbridge); plugins may retry setup (#37)
OP OpenVibers committed 14 Oct 2026 · tag v0.3.0 · 0 files, +0 −0
Plan T15 B10: a ROS 2 bridge. A ROS 2 robot (TurtleBot 3 or 4, a Jackal, a home-built base on `cmd_vel`) joins OpenVibe.Bot through the Node like any other robot, under the same gate.
**New bundled plugin `ros2`** (`plugins/ros2`, Python, standard library only)
- `drive` → `geometry_msgs/msg/Twist` on `cmd_vel`.
- Differential: `throttle`/`steer`, with `angular.z = −steer × max_angular` (Bot's steer is positive to the right; ROS turns counter-clockwise).
- Holonomic: `x`/`y`/`rotation` → `linear.x`/`linear.y`/`angular.z` (Bot's mecanum axes are REP 103's).
- Scaled by `max_linear` m/s and `max_angular` rad/s, after Bot's limits.
- A zero Twist at every stop: the drive deadline, a lost heartbeat, `stop`, `estop`, `halt`, stdin closing, exit.
- `say` → `std_msgs/msg/String` on `say_topic`, when set.
- Telemetry:
- `sensor_msgs/msg/BatteryState` → percent and volts;
- `sensor_msgs/msg/Range`, or the nearest return in the front arc of a `sensor_msgs/msg/LaserScan` → `distance_cm` (Bot's "Distance ahead" readout);
- `nav_msgs/msg/Odometry` → `odom` {x, y, yaw, speed}.
- Camera: a JPEG `sensor_msgs/msg/CompressedImage` → frames through the existing `video` path (`camera.jpeg`), at most `fps` a second.
- Two transports:
- `rclpy`: the plugin is a ROS 2 node.
- `rosbridge`: JSON over a WebSocket. `openvibe_ros2/ws.py` is a small RFC 6455 client (ws and wss, masking, ping/pong, close, fragments, a 16 MB cap), so no ROS 2 libraries are needed where the Node runs.
- `auto` picks rclpy when it imports.
- When ROS is unreachable: the `ros_unreachable` fault, and commands are refused. When it reconnects: a zero Twist first, then `ready`, which ends the fault (#36).
**SDK: `retry_setup_s`** (`plugins/sdk`)
- A plugin whose setup fails only because something is not up yet (the robot's computer still booting) sets `retry_setup_s`.
- The runtime then sends the fault once, sets up again that often, and sends `ready` when it works.
- Before this, a failed setup stayed failed until the core restarted the plugin, because the runtime only polls a ready plugin. Opt-in: other plugins are unchanged.
**Packaging**
- `ros2` is in `config.Bundled`, the plugin tarball (`scripts/dist.sh`), CI's and the release's pytest runs, and the package install check.
- `install.sh --driver ros2` (or `ros2-holonomic`) installs the plugin and writes a rosbridge config with a tip on starting rosbridge.
**Docs**
- `plugins/ros2/README.md`: the mapping table, both transports, setup, TurtleBot 4 notes and every config key. A test checks the README documents each default.
- `docs/plugins.md` gains the `ros2` section and the `retry_setup_s` rule; README and `docs/install.md` name the driver.
**Tests**
- `plugins/ros2/tests` (15):
- describe in probe mode touches nothing;
- drive scaling and signs, clamping, holonomic axes;
- deadline, e-stop, heartbeat and EOF publish zero;
- say;
- battery, scan arc, range and odometry;
- camera frames from base64 and bytes, rate limit, non-JPEG ignored;
- unreachable → fault once → setup again → ready; a later drop → fault → reconnect → ready with a zero Twist;
- `_fill`/`_to_dict` and `RclpyBus` against a fake rclpy;
- `RosbridgeBus` and the WebSocket client against a local server (advertise once, subscribe, a ping, a 64-bit-length frame, close marks the bus down);
- a bad handshake and a bad scheme are refused.
- SDK tests: a failed setup stays failed without the setting; with it, it retries, sends the fault once, and becomes ready.
- All plugin suites pass (119). Go config and plugins tests pass. shellcheck and both installer scripts pass.
- Run as a process: probe describes; an unreachable rosbridge reports `ros_unreachable`.
The Bot side (the `ros2.diffdrive` and `ros2.holonomic` models with `--driver ros2`) follows in its own PR. `--driver ros2` reaches people once this ships in a Node release.
This commit has no diff to show.