Expose an Arm-based ROS 2 system as a discoverable device on Device Connect
Introduction
Understand ROS 2, Device Connect, and the example adapter
Set up ROS 2 and Device Connect on Arm Linux
Connect ROS 2 to Device Connect and call inspection RPCs
Explore deployment profiles and try the adapter with a Raspberry Pi 5 camera
Next Steps
Expose an Arm-based ROS 2 system as a discoverable device on Device Connect
How profiles select the hardware configuration
You’ve completed the core Device Connect task. The following profiles are optional extensions. You can try the adapter with a Raspberry Pi 5 camera and review the PuppyPi robot profile. You can also adapt a profile for your own Robot Operating System 2 (ROS 2) system.
You overrode the rpi5 profile earlier to point at a generic ROS 2 container. Each profile in profiles/ is a shell file that describes one deployment. It sets which driver runs, which container the driver talks to, and which ROS 2 setup scripts it sources.
| Profile | Driver | Default container | ROS 2 distribution | RPCs added to the shared inspection RPCs |
|---|---|---|---|---|
rpi5 | puppypi_device.py | test | Humble | PuppyPi remote procedure calls (RPCs) have no effect without a robot, so this profile works as a general test target |
rpi_camera | camera_device.py | pi_ros | Jazzy | get_raw_image(quality) |
puppypi | puppypi_device.py | puppypi_ros2 | Humble | run_action(action), set_velocity(x, y, yaw_rate), stop() |
For example, profiles/rpi_camera.env contains:
export DEVICE_PROFILE="rpi_camera"
export PROJECT_ROOT="${PROJECT_ROOT:-${HOME}/device_connect}"
export VENV_DIR="${VENV_DIR:-.venv}"
export DEVICE_TYPE="${DEVICE_TYPE:-sentinel_camera}"
export DRIVER_SCRIPT="${DRIVER_SCRIPT:-camera_device.py}"
export ROS_CONTAINER="${ROS_CONTAINER:-pi_ros}"
export ROS_EXEC_USER="${ROS_EXEC_USER:-root}"
export ROS_SETUP="${ROS_SETUP:-/opt/ros/jazzy/setup.bash}"
export WORKSPACE_SETUP="${WORKSPACE_SETUP:-${ROS_SETUP}}"
Every value uses the ${VAR:-default} form. You can therefore override any setting inline, as you did with ROS_CONTAINER and WORKSPACE_SETUP. For a new deployment, you can also point the launcher at your own profile file:
PROFILE_FILE=/path/to/my-robot.env ./ros2-device-connect/start_d2d.sh
Try a Raspberry Pi 5 with a camera
The rpi_camera configuration is the lowest-risk real hardware example in this Learning Path. It exposes a sensor capability rather than a motion capability. A ROS 2 image topic becomes a Device Connect RPC that any peer or agent can call for a photo. The caller doesn’t need to know the ROS 2 topic name, quality of service (QoS) settings, camera container, or image encoding details.
The camera must appear as a V4L2 video device, such as /dev/video0. A USB webcam works without extra setup.
Raspberry Pi Camera Modules connected by ribbon cable use the libcamera stack. They don’t always expose a frame-ready /dev/video0 that v4l2_camera can read.
If v4l2_camera can’t read frames from your Pi Camera Module, start with a USB webcam. Alternatively, set VIDEO_DEVICE to the video node that your camera stack provides.
On the Raspberry Pi 5, set up the same ~/device_connect workspace as earlier, then bring up the camera stack:
cd ~/device_connect
./ros2-device-connect/start_camera_ros2.sh
The start_camera_ros2.sh script is idempotent and does the following:
- Creates a
ros:jazzycontainer namedpi_roswith the camera passed through using--device=/dev/video0 - Installs
ros-jazzy-v4l2-camera,ros-jazzy-cv-bridge, andpython3-opencvinside the container - Copies
capture_frame.pyinto the container and startsv4l2_camera_nodein the background - Verifies that the
/image_rawtopic is being published
Start the adapter with the camera profile:
cd ~/device_connect
DEVICE_PROFILE=rpi_camera ./ros2-device-connect/start_d2d.sh
The device is announced as rpi_camera-d2d. It exposes the same inspection RPCs as before, plus get_raw_image:
@rpc(labels={"category": "camera", "direction": "read", "safety": "informational"})
async def get_raw_image(self, quality: int = DEFAULT_JPEG_QUALITY) -> dict[str, Any]:
...
result = run_ros(
f"python3 {CAPTURE_SCRIPT_PATH} --topic {CAMERA_TOPIC} "
f"--timeout {CAPTURE_TIMEOUT_S} --quality {quality_int}",
timeout=CAPTURE_EXEC_TIMEOUT_S,
)
The RPC runs capture_frame.py inside the container. The script subscribes to /image_raw, takes one frame, and returns the frame as a base64-encoded JPEG. It subscribes with ROS 2 qos_profile_sensor_data because v4l2_camera publishes with best-effort QoS. A subscriber that uses the default reliable QoS would never match the publisher, and the capture would silently time out.
This is the perception side of the robotics pattern: a ROS 2 sensor stream is converted into a typed Device Connect capability. The PuppyPi profile described next uses the same pattern for the action side of robotics.
From a client, call the RPC and save the image. Use the same client environment variables as earlier. Replace 127.0.0.1 with the IP address of the Raspberry Pi if you run the client on another machine:
import base64
from device_connect_agent_tools import connect
from device_connect_agent_tools.tools import invoke
connect()
reply = invoke("device(rpi_camera-d2d).function(get_raw_image)", params={"quality": 80})
with open("capture.jpg", "wb") as f:
f.write(base64.b64decode(reply["result"]["jpeg_base64"]))
The view_image.py script in the repository does the same thing against a device in fabric mode.
Understand the PuppyPi robot profile
The puppypi profile targets a
Hiwonder PuppyPi quadruped
, a small four-legged robot built around Raspberry Pi-class hardware and a ROS 2 control stack.
PuppyPi serves as the real robot example. The same Device Connect adapter pattern used for camera perception is extended to selected locomotion capabilities. PuppyPi shows how you can add motion control safely:
run_actionaccepts only a fixed allowlist of pre-recorded moves, such assit,stand, andwave, and rejects any other value.set_velocityrejects out-of-range values instead of clamping them.stoppublishes a zero-velocity command.
Robot bring-up includes starting puppy_control and enabling servo torque. For bring-up steps and the full safety model, see the
ros2-device-connect README
.
The important design point is that the adapter doesn’t expose arbitrary ROS 2 control. It turns a reviewed subset of the ROS 2 graph of the robot into the following Device Connect capabilities:
- Read-only diagnostics
- Allowlisted actions
- Bounded velocity
- An explicit stop command
To see this profile running on a real PuppyPi, watch the ROS 2 and Device Connect PuppyPi demo .
Build a profile for your own ROS 2 system
To connect a different ROS 2 system, follow the same pattern:
- Create a driver class that combines
Ros2InspectionMixinwithDeviceDriver. Add only the hardware-specific RPCs that you’ve reviewed. - Validate every caller-supplied value before it reaches
run_ros(), asget_topic_infoandget_raw_imagedo. - Add a
profiles/<name>.envfile that sets the following environment variables:DRIVER_SCRIPTROS_CONTAINERROS_EXEC_USERROS_SETUPWORKSPACE_SETUP
When the device needs to be reachable beyond your local network, run start_fabric.sh instead of start_d2d.sh with a device credentials file. This connects the device through a Device Connect server rather than Zenoh device-to-device (D2D) discovery. For more information, see
Deploy multi-network device meshes using Device Connect server and NATS
.
D2D mode runs with DEVICE_CONNECT_ALLOW_INSECURE=true and no transport authentication. Anyone on the same network segment can discover the device and call its RPCs. Use D2D mode only on a trusted network. Add authentication before you expose motion-control RPCs such as set_velocity on a less trusted network.
What you’ve learned
You’ve learned how profiles reuse the same shared core for a Raspberry Pi 5 camera and a ROS 2 robot, and how to add a profile for your own hardware.
To go further, you can also build a larger ROS 2 workload on Arm with Build a ROS 2 and Zenoh simulation environment on an Arm server .