rosbridge integration¶
use_rosbridge speaks the rosbridge JSON protocol over a WebSocket via
pure-pip roslibpy - no ROS environment needed on the agent's machine. This
gives rosbridge two properties no other strands-robots transport has:
- ROS 1 robots - rosbridge_suite ships for ROS 1 (and ROS 2) alike. Drive the NASA Curiosity rover Gazebo simulation (ROS 1 Noetic), or any ROS 1 system.
- No ROS install on this machine - the agent can run on macOS, CI, WSL, or any laptop and introspect and drive robots across a network.
from strands import Agent
from strands_robots.mesh import RosbridgeRobot
# Stock NASA-sim wiring: cmd_vel/odom topics and safety limits preconfigured.
rover = RosbridgeRobot.from_curiosity(host="192.168.1.20")
agent = Agent(tools=rover.tools)
agent("drive forward for 5 seconds, then report the odometry")
Requirements¶
Install the [rosbridge] extra:
The robot side runs rosbridge_server with rosapi enabled - standard in every
rosbridge install. rosbridge is unauthenticated by default: use on trusted
networks only. rosauth is out of scope.
# ROS 1 Noetic example
apt-get install ros-noetic-rosbridge-suite
roslaunch rosbridge_server rosbridge_websocket.launch
# ROS 2 example
apt-get install ros-<distro>-rosbridge-suite
ros2 launch rosbridge_server rosbridge_websocket_launch.xml
Actions¶
| Action | Required args | Returns |
|---|---|---|
status |
- | roslibpy availability + connectivity to host:port |
list_topics |
- | Every topic rosapi reports, with its type where rosapi reports one (rosapi /rosapi/topics) |
list_services |
- | Services (rosapi /rosapi/services) |
echo |
topic (type auto-resolved) |
N samples as JSON |
publish |
topic, type |
Publishes N messages built from fields |
service_call |
service, type |
Service response as JSON |
Examples¶
from strands_robots import use_rosbridge
# Check connectivity
use_rosbridge(action="status", host="192.168.1.20")
# Graph introspection
use_rosbridge(action="list_topics")
use_rosbridge(action="list_services")
# Subscribe and read one sample (type auto-resolved via rosapi)
use_rosbridge(action="echo", topic="/curiosity_mars_rover/odom", count=1)
# Publish a velocity command
use_rosbridge(action="publish",
topic="/curiosity_mars_rover/ackermann_drive_controller/cmd_vel",
type="geometry_msgs/Twist",
fields={"linear": {"x": 1.0}, "angular": {"z": 0.0}})
# Call a service with a JSON request
use_rosbridge(action="service_call", service="/some_service",
type="std_srvs/Trigger",
fields={})
Graph introspection uses the rosapi node's services. Interface types are
ROS 1-style two-segment names (e.g. geometry_msgs/Twist); field payloads are
plain JSON dicts, exactly as rosbridge transmits them.
RosbridgeRobot¶
For mobile bases that expose the standard cmd_vel / odometry / scan topic
trio, RosbridgeRobot wraps that wiring so a remote ROS 1 or remote robot
drives like any other strands robot - the same Agent(tools=[robot]) pattern
used for simulated and hardware arms.
Constructor¶
from strands_robots.mesh import RosbridgeRobot
robot = RosbridgeRobot(
node_name="my_robot",
cmd_vel_topic="/cmd_vel",
odom_topic="/odom",
scan_topic="/scan", # optional
host="192.168.1.20",
port=9090,
cmd_vel_type="geometry_msgs/Twist", # defaults; matches ROS 1
odom_type=None, # auto-resolved via rosapi when omitted
scan_type=None,
max_linear=2.0, # m/s clamp
max_angular=1.0, # rad/s clamp
max_duration=30.0, # longest accepted drive() hold
publish_rate=10.0, # Hz
)
All parameters are optional except node_name, cmd_vel_topic, and odom_topic.
Address contract¶
host and port are the two halves of one websocket address, ws://<host>:<port>,
and each is graded twice: by the domain every dialled address in the package
shares, then by this transport's own narrower rule. For the port that is the
16-bit space followed by autobahn's ceiling of 65534; for the host it is "a string
a websocket URI can carry" followed by the bare-hostname allowlist, which is
stricter than the shared domain and refuses spellings a URI would accept (a
bracketed IPv6 literal, for one). Either half being unusable is reported the same
way whichever it is - use_rosbridge returns an error result and RosbridgeRobot
raises ValueError - and both are refused before a socket is dialled, so the
caller learns the same thing whether or not roslibpy is installed.
Drive contract¶
drive() takes the fleet-standard (linear, angular, duration, count) shape,
and three of its guarantees are fleet-standard too: every value is checked
against the same numeric domains the ROS 2 and
RTPS bridges use, a bare single-shot command latches, and
a timed command is followed by a trailing zero Twist. The velocity clamps and the
max_duration ceiling are shared with the Ackermann car bridge
(AckermannRosRobot, which declares a max_speed and a max_duration of its
own) but not with those two, which accept no ceilings because neither knows the
limits of the robot it drives and put the requested value on the wire
unclamped.
# Direct, programmatic control:
robot.drive(linear=1.0, angular=0.0, duration=2.0) # hold for 2 seconds
robot.drive(linear=1.0) # latch until stop() - single-shot
robot.stop() # publishes zero Twist; gated like any cmd_vel publish
pose = robot.get_pose() # read one odometry sample
scan = robot.get_scan() # read one laser scan (error if no scan_topic)
Safety semantics (fleet-wide unless marked):
- Finite-input guards: non-finite (NaN, inf) linear or angular velocities are rejected before any publish.
- Single-shot latch: a bare single-message
drive()(noduration, nocount > 1) publishes once and latches in the robot's controller untilstop()is called. This is standard cmd_vel behavior. - Velocity clamps (not on every mobile base): linear and angular are
independently clamped to
max_linearandmax_angular.AckermannRosRobotclamps to amax_speedof its own the same way. The ROS 2 and RTPS bridges accept no velocity ceiling and put the requested value on the wire unchanged. - Loud duration rejection (not on every mobile base):
durationmust be positive, finite, and at mostmax_duration; anything else returns a detailed error and nothing is published.AckermannRosRobotrefuses a longer hold against amax_durationof its own, which is lower, so a hold this bridge accepts can be refused on that car. The ROS 2 and RTPS bridges accept any positive finite hold. - Timed-command trailing zero: every drive with a
durationargument and a non-zero command (or multi-message publish) automatically publishes a single zero Twist afterwards - even if the main publish failed - so a timed drive does not leave the robot with a live velocity. That zero is itself a gated command, so when it is the call that fails - a declined approval, a rate limit, a transport error -drivereturns that failure instead of the hold's success, naming the still-live topic and telling you to halt withstop. This was previously this bridge's alone; the ROS 2 and RTPS bridges inherit it from the shared mobile base now, so a timed drive self-stops on all three. - stop() needs no prior state: the stop method publishes a zero Twist
regardless of whether an earlier command succeeded, and there is no enable
handshake to satisfy first. It is not exempt from the operator gate: that
gate is keyed on the command surface rather than on the payload, so a halt to
a blocklisted
cmd_velis approved exactly like a full-speed drive on every mobile base, this one included. Pre-approve the topic withSTRANDS_ROS2_COMMAND_ALLOWif the halt must go out unattended.
| Method | ROS action | Notes |
|---|---|---|
drive(linear, angular, duration=, count=) |
publish Twist to cmd_vel_topic |
duration holds the command at publish_rate Hz; no duration latches until stop |
stop() |
publish zero Twist |
needs no prior state; gated on the surface like drive() |
get_pose() |
echo odom_topic |
returns up to 1 sample |
get_scan() |
echo scan_topic |
error when no scan_topic configured |
.tools |
- | per-instance named agent tools |
from_curiosity¶
The NASA Curiosity rover Gazebo simulation (ROS 1 Noetic) is pre-wired:
rover = RosbridgeRobot.from_curiosity(
host="localhost", # or Docker container IP / another machine
port=9090,
node_name="curiosity", # optional
# ... any constructor param as override
)
The stock wiring uses the rover's ackermann_drive_controller (consumes
geometry_msgs/Twist directly - no kinematic model needed on the agent side),
and imports safety limits (max_linear=2.0 m/s, max_angular=1.0 rad/s,
max_duration=30s) ported from the strands-robots-ros2 registry.
Curiosity quickstart (Docker, headless)¶
Build a self-contained ROS 1 Noetic + Gazebo + rosbridge image, then launch the agent on your machine:
# 1. Sim container: ROS1 Noetic + Gazebo (server only) + rosbridge
docker build -t curiosity-sim - <<'EOF'
FROM ros:noetic-robot
RUN apt-get update && apt-get install -y --no-install-recommends \
ros-noetic-gazebo-ros ros-noetic-gazebo-ros-control \
ros-noetic-gazebo-plugins ros-noetic-controller-manager \
ros-noetic-joint-state-controller ros-noetic-effort-controllers \
ros-noetic-position-controllers ros-noetic-velocity-controllers \
ros-noetic-robot-state-publisher ros-noetic-xacro \
ros-noetic-rosbridge-suite git \
&& rm -rf /var/lib/apt/lists/*
RUN git clone --depth 1 https://github.com/mark-gl/curiosity_mars_rover_ws.git /ws
WORKDIR /ws
RUN bash -lc "source /opt/ros/noetic/setup.bash && catkin_make \
-DCATKIN_WHITELIST_PACKAGES='ackermann_drive_controller;curiosity_mars_rover_description;curiosity_mars_rover_control;curiosity_mars_rover_gazebo'"
CMD bash -lc "source /ws/devel/setup.bash && \
roslaunch curiosity_mars_rover_gazebo main_mars_terrain.launch gui:=false rviz:=false & \
sleep 30 && roslaunch rosbridge_server rosbridge_websocket.launch"
EOF
docker run -d --name curiosity -p 9090:9090 curiosity-sim
# 2. Agent side (this machine - no ROS needed)
pip install "strands-robots[rosbridge]" strands-agents
python examples/rosbridge/curiosity_agent.py
The simulation takes ~30 seconds to warm up. Then the agent will read the rover's initial pose, execute two drive legs with a turn, and report the displacement.
Security¶
rosbridge is unauthenticated by default. The WebSocket accepts connections from any network address on the port. For trustworthy deployments:
- Trusted networks only: use rosbridge on private intranets, never expose port 9090 to the internet.
- rosauth out of scope: the
rosauthROS package can add authentication, but configuration and key distribution are operator responsibilities; this library does not provision them. - Network isolation: firewall the rosbridge port to known agent machine IPs.