From 8ef34a68d31ef78e4f4e83a8183659f901a0b3a8 Mon Sep 17 00:00:00 2001 From: Ubuntu Date: Thu, 24 Sep 2026 09:24:47 +0000 Subject: [PATCH 1/3] squash commit, initial LP for basic rpi5 profile without camera or quadruped --- .../ros2-device-connect/_index.md | 72 +++++ .../ros2-device-connect/_next-steps.md | 8 + .../ros2-device-connect/background.md | 65 +++++ .../ros2-device-connect/how-it-works.md | 265 ++++++++++++++++++ .../profiles-and-hardware.md | 134 +++++++++ .../ros2-device-connect/setup.md | 122 ++++++++ 6 files changed, 666 insertions(+) create mode 100644 content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_next-steps.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md create mode 100644 content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/setup.md diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md new file mode 100644 index 0000000000..26c417e012 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md @@ -0,0 +1,72 @@ +--- +title: Get started with ROS 2 and Device Connect on Arm +description: Learn how to expose a ROS 2 system running in Docker on an Arm-based Linux device as a discoverable Device Connect device, and inspect its ROS 2 graph through remote procedure calls. + +minutes_to_complete: 30 + +who_is_this_for: This is an introductory topic for robotics and edge developers who want to make a ROS 2 system discoverable and callable by other devices and AI agents using Device Connect, without changing the ROS 2 application itself. + +learning_objectives: + - Explain how a Device Connect adapter bridges a containerized ROS 2 system to a Device Connect network + - Set up ROS 2 in Docker and the Device Connect Python packages on an Arm-based Linux machine + - Run the adapter in device-to-device (D2D) mode and call read-only ROS 2 inspection RPCs from a Python client + - Describe how deployment profiles map the same adapter onto real hardware such as a Raspberry Pi 5 with a camera + +prerequisites: + - An Arm-based Linux machine, such as a Raspberry Pi 5, an Arm cloud instance, or an Arm-based laptop, running Ubuntu 22.04 or later + - Basic familiarity with Python, the Linux command line, and ROS 2 concepts such as nodes and topics + +author: Kieran Hejmadi + +# New Learning Paths are opted in for the next manual generated summary/FAQ run. +# The generator resets this to false after a successful write. +generate_summary_faq: true + +# Optional one-shot controls: set either field to true to regenerate just that +# generated section the next time the summary/FAQ tool runs. The tool resets +# them to false after a successful write. +rerun_summary: false +rerun_faqs: false + +### Tags +skilllevels: Introductory +subjects: Libraries +armips: + - Cortex-A + - Neoverse +tools_software_languages: + - ROS 2 + - Docker + - Python + - Zenoh +operatingsystems: + - Linux + +further_reading: + - resource: + title: ros2-device-connect example repository + link: https://github.com/odincodeshen/ros2-device-connect + type: website + - resource: + title: Device Connect repository + link: https://github.com/arm/device-connect + type: website + - resource: + title: ROS 2 documentation + link: https://docs.ros.org/en/humble/ + type: documentation + - resource: + title: Device-to-Device communication with Device Connect + link: /learning-paths/embedded-and-microcontrollers/device-connect-d2d/ + type: website + - resource: + title: Build a ROS 2 and Zenoh simulation environment on an Arm server + link: /learning-paths/cross-platform/ros2-zenoh-arm/ + type: website + +### FIXED, DO NOT MODIFY +# ================================================================================ +weight: 1 # _index.md always has weight of 1 to order correctly +layout: "learningpathall" # All files under learning paths have this same wrapper +learning_path_main_page: "yes" # This should be surfaced when looking for related content. Only set for _index.md of learning path content. +--- diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_next-steps.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_next-steps.md new file mode 100644 index 0000000000..727b395ddd --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_next-steps.md @@ -0,0 +1,8 @@ +--- +# ================================================================================ +# FIXED, DO NOT MODIFY THIS FILE +# ================================================================================ +weight: 21 # The weight controls the order of the pages. _index.md always has weight 1. +title: "Next Steps" # Always the same, html page title. +layout: "learningpathall" # All files under learning paths have this same wrapper for Hugo processing. +--- diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md new file mode 100644 index 0000000000..340c4a0c0f --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md @@ -0,0 +1,65 @@ +--- +title: Understand ROS 2, Device Connect, and the example adapter +description: Learn what ROS 2 and Device Connect each provide, and how the ros2-device-connect adapter exposes a ROS 2 container as a Device Connect device. +weight: 2 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Why connect ROS 2 to Device Connect? + +A robot or smart camera built on ROS 2 already has a rich internal graph of nodes, topics, and services. That graph is designed for components *inside* the robot to talk to each other. It isn't designed for other devices on your network, or for AI agents, to discover the robot and ask it structured questions such as "which topics are you publishing?" or "capture a photo". + +Device Connect fills that gap. In this Learning Path, you'll run a small adapter next to an unchanged ROS 2 system on an Arm-based Linux machine. The adapter registers the ROS 2 system as a Device Connect device and exposes a safe set of remote procedure calls (RPCs) that any peer or agent can discover and invoke. + +## ROS 2 in brief + +ROS 2 (Robot Operating System 2) is an open-source middleware and toolset for building robotics applications. Applications are split into *nodes* that exchange data over *topics* (publish/subscribe), *services* (request/response), and *actions* (long-running goals). ROS 2 publishes official `arm64` packages and container images, so it runs natively on Arm platforms from a Raspberry Pi to a Neoverse cloud server. + +This Learning Path uses ROS 2 inside a Docker container and doesn't go deeper into ROS 2 itself. For more background, see: + +- [ROS 2 install guide](/install-guides/ros2/) to install ROS 2 natively on Arm Linux and run the talker and listener demo +- [Build a ROS 2 and Zenoh simulation environment on an Arm server](/learning-paths/cross-platform/ros2-zenoh-arm/) for a full containerized ROS 2 robotics workload on Arm, including the `rmw_zenoh` middleware + +## Device Connect in brief + +[Device Connect](https://github.com/arm/device-connect) is an open-source framework that standardizes how edge devices advertise themselves and exchange structured messages, so that peer devices and AI agents can discover and control them through the same driver model. The pieces you'll use are: + +- `DeviceDriver`: a Python base class you subclass to describe a device +- `@rpc` and `@emit`: decorators that expose a method as a callable function or declare an event the device publishes +- `DeviceRuntime`: the runtime that brings a driver online on the messaging network +- `device-connect-agent-tools`: a client library to discover devices and invoke their RPCs from a script or an AI agent + +Device Connect supports two deployment styles. In *device-to-device (D2D)* mode, devices find each other directly on the local network using [Zenoh](https://zenoh.io/), with no server. In *server* (or *fabric*) mode, devices connect through a shared broker so they can be reached across networks. This Learning Path uses D2D mode. + +To learn the SDK primitives in more depth, see these Learning Paths: + +- [Device-to-Device communication with Device Connect](/learning-paths/embedded-and-microcontrollers/device-connect-d2d/) for the developer model and a sensor-to-monitor example +- [Deploy multi-network device meshes using Device Connect server and NATS](/learning-paths/embedded-and-microcontrollers/device-connect-server/) for server mode +- [Connect AI agents to edge devices using Device Connect and Strands](/learning-paths/embedded-and-microcontrollers/device-connect-strands/) for driving devices from an AI agent + +## The ros2-device-connect example + +The [ros2-device-connect](https://github.com/odincodeshen/ros2-device-connect) repository contains the adapter you'll run. It doesn't modify or link against ROS 2. Instead, it reaches the ROS 2 graph by running ROS 2 command-line tools inside the ROS 2 container with `docker exec`: + +```output +Python client ──Zenoh (D2D)──▶ Device Connect adapter ──docker exec──▶ ROS 2 container +(agent tools) (DeviceDriver on host) (ros2 topic list, ...) +``` + +The repository is organized in three layers: + +| Layer | Files | Role | +|---|---|---| +| Shared core | `ros2_common.py` | The `docker exec` bridge plus `Ros2InspectionMixin`, which provides six read-only inspection RPCs for nodes, topics, services, packages, interfaces, and topic info | +| Hardware drivers | `puppypi_device.py`, `camera_device.py` | `DeviceDriver` subclasses that combine the shared core with hardware-specific RPCs, such as capturing a camera frame | +| Profiles and launchers | `profiles/*.env`, `start_d2d.sh`, `start_fabric.sh` | Per-deployment settings that select the driver, the ROS 2 container, and the ROS 2 setup scripts | + +The adapter is deliberately narrow. It exposes read-only inspection plus a small, reviewed set of hardware-specific RPCs. It never offers arbitrary topic publishing or service passthrough, so a remote caller can't drive the ROS 2 system in ways the driver author didn't intend. + +## What you've learned and what's next + +You've learned that ROS 2 organizes a robot's internal software as a graph of nodes and topics, and that Device Connect makes a device discoverable and callable by peers and agents. The ros2-device-connect adapter joins the two by wrapping ROS 2 command-line tools in Device Connect RPCs. + +Next, you'll install Docker, ROS 2, and the Device Connect packages on your Arm-based Linux machine. diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md new file mode 100644 index 0000000000..b99252540b --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md @@ -0,0 +1,265 @@ +--- +title: Connect ROS 2 to Device Connect and call inspection RPCs +description: Learn the layered programming pattern behind the ros2-device-connect adapter, start it in D2D mode against a ROS 2 container, and call its ROS 2 inspection RPCs from a Python client. +weight: 4 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## The programming pattern: four layers around one ROS 2 command + +The adapter doesn't add a new way to talk to ROS 2. It wraps the same `ros2` command you ran from your terminal in the setup section, one layer at a time. Each layer adds one thing to the layer beneath it: + +| Layer | What it is | What it adds | Example | +|---|---|---|---| +| 1. ROS 2 command | A `ros2` CLI call run in the container from the host | The ROS 2 query itself | `docker exec ros2_test ... ros2 topic list` | +| 2. Python wrapper | `run_ros()` | The same command, called from Python and returning structured output | `run_ros("ros2 topic list")` | +| 3. RPC | An `@rpc` method on `Ros2InspectionMixin` | A named function that peers and agents can discover and call over the network | `get_ros_topics()` | +| 4. Device | A driver class run by `DeviceRuntime` | A device that bundles those RPCs and joins the network | `PuppyPiRos2Driver` | + +Layer 1 is all you need when you have a shell on the machine. Layers 2 to 4 let a caller *without* shell access run the same query, safely and by name. + +The code excerpts in this section are simplified to show the pattern. The full versions are in `ros2_common.py` and `puppypi_device.py` in the repository. + +## Layer 1: run a ROS 2 command in the container + +You've already used this layer. From the host, `docker exec` runs a `ros2` command inside the container after sourcing the ROS 2 environment: + +```bash +docker exec ros2_test bash -lc 'source /opt/ros/humble/setup.bash && ros2 topic list' +``` + +```output +/chatter +/parameter_events +/rosout +``` + +Every RPC in this Learning Path ends up running a command like this one. + +## Layer 2: wrap the command in Python + +`run_ros()` in `ros2_common.py` builds that same `docker exec` command: + +```python +def run_ros(command: str, timeout: float = 10.0) -> dict[str, Any]: + script = f"source {ROS_SETUP} && source {WORKSPACE_SETUP} && {command}" + return run(["docker", "exec", "-u", EXEC_USER, CONTAINER, "bash", "-lc", script], timeout=timeout) +``` + +The container name, user, and setup scripts come from environment variables, so the same function works with any ROS 2 container. It returns a dictionary with `ok`, `stdout`, and `stderr` instead of printed text, and a timeout stops a stalled ROS 2 command from hanging the adapter. + +## Layer 3: expose the command as an RPC + +`Ros2InspectionMixin` turns `run_ros()` calls into Device Connect RPCs. The `@rpc()` decorator is what makes a method discoverable and callable over the network: + +```python +class Ros2InspectionMixin: + @rpc() + async def get_ros_topics(self, limit: int = 200, contains: str = "") -> dict[str, Any]: + """List active ROS2 topics.""" + return lines(run_ros("ros2 topic list"), limit=limit, contains=contains) + + @rpc() + async def get_topic_info(self, topic: str) -> dict[str, Any]: + """Return read-only metadata for a specific ROS2 topic.""" + if not TOPIC_NAME_RE.fullmatch(topic): + return {"ok": False, "error": "topic must match ^/[A-Za-z0-9_/]+$"} + return run_ros(f"ros2 topic info {topic}") +``` + +This is also where you apply safety rules, because it's the boundary where remote input arrives. `get_ros_topics` caps and filters its output with the `lines()` helper. `get_topic_info` validates the topic name before it reaches a shell command. + +The mixin provides six inspection RPCs in total, for nodes, topics, services, packages, interfaces, and topic info. They all follow the same pattern. + +## Layer 4: build the device + +The final layer is a driver class that inherits from both `Ros2InspectionMixin` and `DeviceDriver`. The mixin supplies the shared ROS 2 RPCs. `DeviceDriver` makes the class a Device Connect device, and you add any hardware-specific RPCs alongside the shared ones. The `rpi5` profile runs this driver from `puppypi_device.py`: + +```python +class PuppyPiRos2Driver(Ros2InspectionMixin, DeviceDriver): + device_type = os.getenv("DEVICE_TYPE", "quadruped") + + @rpc() + async def echo(self, text: str) -> dict[str, str]: + """Echo text for connectivity testing.""" + return {"echo": text} + + # get_status(), run_action(), set_velocity(), stop() ... + + +async def main() -> None: + runtime = DeviceRuntime(driver=PuppyPiRos2Driver(), device_id=os.getenv("DEVICE_ID")) + await runtime.run() +``` + +`DeviceRuntime` connects the driver to the messaging network and announces every `@rpc` method, both inherited and its own, to peers. To support new hardware, you write a new class at this layer. Layers 1 to 3 stay the same. + +## Start the adapter + +The `start_d2d.sh` launcher loads a profile, activates the `.venv` environment, sets D2D defaults (the Zenoh backend, TCP port 7447, and a device ID of `-d2d`), and runs the driver script that the profile names. + +Open a terminal on your Arm-based Linux machine and start the adapter with the `rpi5` profile, pointing it at the `ros2_test` container: + +```bash +cd ~/device_connect +DEVICE_PROFILE=rpi5 PROJECT_ROOT=$HOME/device_connect \ + ROS_CONTAINER=ros2_test WORKSPACE_SETUP=/opt/ros/humble/setup.bash \ + ./ros2-device-connect/start_d2d.sh +``` + +The inline variables override the values in `profiles/rpi5.env`. You need the `WORKSPACE_SETUP` override because the `rpi5` profile defaults to a robot workspace that doesn't exist in a plain `ros:humble` container. + +The adapter stays in the foreground. Its log shows that it has joined the Zenoh network in D2D mode as `rpi5-d2d`: + +```output +WARNING - Running in INSECURE mode (DEVICE_CONNECT_ALLOW_INSECURE=true). Do NOT use this in production! +INFO - Using ZENOH messaging backend +INFO - Driver connected: raspberry_pi +INFO - Subscribed to commands on device-connect.default.rpi5-d2d.cmd +INFO - D2D mode: skipping registry registration, using presence announcements +``` + +## Call the RPCs from a client + +Open a second terminal on the same machine. In `~/device_connect`, create a file named `client.py`: + +```python +import json + +from device_connect_agent_tools import connect, discover +from device_connect_agent_tools.tools import invoke + +DEVICE = "rpi5-d2d" + +CALLS = [ + ("echo", {"text": "hello from arm"}), + ("get_status", {}), + ("get_ros_topics", {}), + ("get_ros_packages", {"contains": "std_msgs"}), + ("get_topic_info", {"topic": "/chatter"}), + ("get_topic_info", {"topic": "bad;rm"}), +] + +connect() + +found = discover("device(*)") +print("discovered:", [d["device_id"] for d in found["results"]]) + +for function, params in CALLS: + result = invoke(f"device({DEVICE}).function({function})", params=params) + print(f"--- {function}", json.dumps(result, indent=2)) +``` + +The client uses two calls from the agent tools. `discover("device(*)")` lists every device on the network, and `invoke()` calls one function on a named device using a `device().function()` selector. The client doesn't know about ROS 2 or Docker. It only sees the device and its RPCs. + +Set the client's connection settings and run it: + +```bash +cd ~/device_connect +export MESSAGING_BACKEND=zenoh +export ZENOH_CONNECT=tcp/127.0.0.1:7447 +export DEVICE_CONNECT_DISCOVERY_MODE=d2d +export DEVICE_CONNECT_ALLOW_INSECURE=true +export TENANT=default +.venv/bin/python client.py +``` + +`ZENOH_CONNECT` points the client at the adapter's Zenoh endpoint. To reach the adapter from another machine on your LAN, replace `127.0.0.1` with the adapter host's IP address. + +The output is similar to the following, shortened here for readability: + +```output +discovered: ['rpi5-d2d'] +--- echo { + "success": true, + "device_id": "rpi5-d2d", + "function": "echo", + "result": { + "echo": "hello from arm" + } +} +--- get_status { + ... + "result": { + "device": "puppypi", + "container": "ros2_test", + "container_status": "running", + "ros_ok": false, + "ros_output": [ + "ROS_DISTRO=humble" + ], + ... + } +} +--- get_ros_topics { + ... + "result": { + "ok": true, + "count": 3, + "items": [ + "/chatter", + "/parameter_events", + "/rosout" + ], + "truncated": false, + ... + } +} +--- get_ros_packages { + ... + "result": { + "ok": true, + "count": 1, + "items": [ + "std_msgs" + ], + ... + } +} +--- get_topic_info { + ... + "result": { + "ok": true, + "topic": "/chatter", + "stdout": "Type: std_msgs/msg/String\nPublisher count: 1\nSubscription count: 0\n", + "stderr": "" + } +} +--- get_topic_info { + ... + "result": { + "ok": false, + "error": "topic must match ^/[A-Za-z0-9_/]+$" + } +} +``` + +Here's what each result shows: + +- `echo` confirms the full round trip from the client over Zenoh to the adapter. +- `get_ros_topics` returns the same three topics you saw at layer 1. It's the same `ros2 topic list` command, reached through all four layers. `get_topic_info` adds the message type of `/chatter`. +- `get_ros_packages` shows the `contains` filter reducing the package list to `std_msgs`. +- The second `get_topic_info` call is rejected at layer 3, so no command runs in the container. +- `get_status` reports that the container is running ROS 2 Humble. `ros_ok` is `false` because this driver's health check looks for PuppyPi robot packages, which aren't in a plain `ros:humble` container. This is expected. + +## Clean up + +Stop the adapter with `Ctrl+C` in its terminal. If you started it in the background, stop it with: + +```bash +pkill -f puppypi_device.py +``` + +The `ros2_test` container keeps running. When you no longer need it, remove it: + +```bash +docker rm -f ros2_test +``` + +## What you've accomplished and what's next + +You've learned the adapter's four-layer pattern: a ROS 2 command run in the container, a Python wrapper around it, an `@rpc` method that exposes it, and a driver class that inherits from `Ros2InspectionMixin` and `DeviceDriver` to bundle those RPCs into a device. You started the adapter in D2D mode and called its RPCs from a Python client that knows nothing about ROS 2. + +Next, you'll see how other profiles reuse layers 1 to 3 with different layer 4 drivers for real hardware, such as a Raspberry Pi 5 with a camera. diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md new file mode 100644 index 0000000000..e8d6e9c453 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md @@ -0,0 +1,134 @@ +--- +title: Use deployment profiles and try the adapter on real hardware +description: Learn how ros2-device-connect profiles select drivers for different hardware, and how to run the adapter on a Raspberry Pi 5 with a camera or on a ROS 2 robot. +weight: 5 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## How profiles select the hardware configuration + +In the previous section, you overrode the `rpi5` profile 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 | The PuppyPi RPCs, which 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: + +```bash +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, so you can 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: + +```bash +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 turns a Raspberry Pi 5 with a camera into a device that any peer or agent can ask for a photo. The camera must appear as a V4L2 video device, such as `/dev/video0`. A USB webcam works without extra setup. + +{{% notice Note %}} +Raspberry Pi Camera Modules connected by ribbon cable use the libcamera stack, and 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, or set `VIDEO_DEVICE` to the video node your camera stack provides. +{{% /notice %}} + +On the Raspberry Pi 5, set up the same `~/device_connect` workspace as in the setup section, then bring up the camera stack: + +```bash +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:jazzy` container named `pi_ros` with the camera passed through using `--device=/dev/video0` +- installs `ros-jazzy-v4l2-camera`, `ros-jazzy-cv-bridge`, and `python3-opencv` inside the container +- copies `capture_frame.py` into the container and starts `v4l2_camera_node` in the background +- verifies that the `/image_raw` topic is being published + +Start the adapter with the camera profile: + +```bash +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`: + +```python +@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 it as a base64-encoded JPEG. It subscribes with ROS 2's `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. + +From a client, call the RPC and save the image. Use the same client environment variables as in the previous section, replacing `127.0.0.1` with the Raspberry Pi's IP address if you run the client on another machine: + +```python +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 repository's `view_image.py` script does the same thing against a device in fabric mode. + +## Try a ROS 2 robot + +The `puppypi` profile targets a [Hiwonder PuppyPi quadruped](https://www.hiwonder.com/products/puppypi) running ROS 2 Humble. It shows how you can add motion control safely: + +- `run_action` accepts only a fixed allowlist of pre-recorded moves, such as `sit`, `stand`, and `wave`, and rejects any other value. +- `set_velocity` rejects out-of-range values instead of clamping them. +- `stop` publishes a zero-velocity command. + +Robot bring-up includes starting `puppy_control` and enabling servo torque. For those steps and the full safety model, see the [ros2-device-connect README](https://github.com/odincodeshen/ros2-device-connect#startup-checklist). + +//TODO - Odin to provide a video of his quadruped? + +## Build a profile for your own ROS 2 system + +To connect a different ROS 2 system, follow the same pattern: + +1. Create a driver class that combines `Ros2InspectionMixin` with `DeviceDriver`, and add only the hardware-specific RPCs you've reviewed. +2. Validate every caller-supplied value before it reaches `run_ros()`, as `get_topic_info` and `get_raw_image` do. +3. Add a `profiles/.env` file that sets `DRIVER_SCRIPT`, `ROS_CONTAINER`, `ROS_EXEC_USER`, `ROS_SETUP`, and `WORKSPACE_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 D2D discovery. For more information, see [Deploy multi-network device meshes using Device Connect server and NATS](/learning-paths/embedded-and-microcontrollers/device-connect-server/). + +{{% notice Warning %}} +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, and add authentication before you expose motion-control RPCs such as `set_velocity` on a less trusted network. +{{% /notice %}} + +## What you've learned + +In this Learning Path, you: + +- learned how the ros2-device-connect adapter makes an unchanged ROS 2 system discoverable and callable through Device Connect, by wrapping ROS 2 CLI calls in `@rpc` methods run with `docker exec` +- set up a ROS 2 Humble container and the Device Connect Python packages on an Arm-based Linux machine +- ran the adapter in D2D mode and called read-only inspection RPCs from a Python client, including one call rejected by input validation +- saw 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, try driving the adapter from an AI agent with [Connect AI agents to edge devices using Device Connect and Strands](/learning-paths/embedded-and-microcontrollers/device-connect-strands/), or build a larger ROS 2 workload on Arm with [Build a ROS 2 and Zenoh simulation environment on an Arm server](/learning-paths/cross-platform/ros2-zenoh-arm/). diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/setup.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/setup.md new file mode 100644 index 0000000000..4cb501d645 --- /dev/null +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/setup.md @@ -0,0 +1,122 @@ +--- +title: Set up ROS 2 and Device Connect on Arm Linux +description: Install Docker, start a ROS 2 Humble container with a demo publisher, and install the Device Connect Python packages and the ros2-device-connect adapter. +weight: 3 + +### FIXED, DO NOT MODIFY +layout: learningpathall +--- + +## Before you begin + +Run every command in this section on your Arm-based Linux machine. The instructions assume Ubuntu 22.04 or later on an `aarch64` host, such as a Raspberry Pi 5 or an Arm cloud instance. Confirm the architecture: + +```bash +uname -m +``` + +```output +aarch64 +``` + +By the end of this section, you'll have: + +- a ROS 2 Humble container publishing a demo topic +- a Python virtual environment with the Device Connect packages +- the ros2-device-connect adapter cloned and ready to run + +## Install Docker + +The adapter talks to ROS 2 through Docker, so you need Docker Engine. Follow the [Docker Engine install guide](/install-guides/docker/docker-engine/), including the step that adds your user to the `docker` group. The adapter runs `docker exec` as your user, so Docker must work without `sudo`. + +## Start a ROS 2 container + +You don't need to install ROS 2 on the host. The official `ros:humble` image is multi-architecture, so Docker pulls the `arm64` variant automatically. If you'd prefer a native install, see the [ROS 2 install guide](/install-guides/ros2/). + +Start a long-running container named `ros2_test`: + +```bash +docker run -d --name ros2_test ros:humble tail -f /dev/null +``` + +The `tail -f /dev/null` command keeps the container alive so you can run ROS 2 commands inside it with `docker exec`. Start a demo publisher that sends a `std_msgs/msg/String` message on the `/chatter` topic once per second: + +```bash +docker exec -d ros2_test bash -lc 'source /opt/ros/humble/setup.bash && ros2 topic pub -r 1 /chatter std_msgs/msg/String "{data: hello}"' +``` + +Check that the topic is being published: + +```bash +docker exec ros2_test bash -lc 'source /opt/ros/humble/setup.bash && ros2 node list && ros2 topic list' +``` + +The output lists the ROS 2 topics: + +```output +/chatter +/parameter_events +/rosout +``` + +The node list is empty because `ros2 topic pub` runs as a hidden node. This container now stands in for a real robot's ROS 2 stack. + +## Create the workspace + +The adapter's launch scripts expect a single project directory that holds the adapter repository and a Python virtual environment named `.venv`. Create that directory and clone both the adapter and the Device Connect source: + +```bash +mkdir -p ~/device_connect +cd ~/device_connect +git clone https://github.com/odincodeshen/ros2-device-connect.git +git clone https://github.com/arm/device-connect.git +``` + +## Install uv and the Device Connect packages + +This Learning Path uses [uv](https://docs.astral.sh/uv/) to create the Python environment. Install it: + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh +export PATH="$HOME/.local/bin:$PATH" +uv --version +``` + +Create a Python virtual environment in the project directory, and install the Device Connect edge SDK and agent tools from the cloned source. The Device Connect packages require Python 3.11 or later and are tested on Python 3.11, 3.12, and 3.13. This example uses 3.12, but you can pass any supported version to `--python`. If that version isn't installed on your machine, uv downloads it for you. + +This host Python environment is separate from the Python version inside the ROS 2 container, so your choice here doesn't need to match your ROS 2 distribution. + +```bash +cd ~/device_connect +uv venv --python 3.12 .venv +VIRTUAL_ENV=.venv uv pip install \ + -e device-connect/packages/device-connect-edge \ + -e device-connect/packages/device-connect-agent-tools +``` + +The `device-connect-edge` package is the device runtime that the adapter runs on. The `device-connect-agent-tools` package is the client you'll use to discover the adapter and call its RPCs. + +Verify that both packages import: + +```bash +.venv/bin/python -c "import device_connect_edge, device_connect_agent_tools; print('Device Connect OK')" +``` + +```output +Device Connect OK +``` + +Your workspace now looks like this: + +```output +~/device_connect/ +├── .venv/ # Python environment with Device Connect +├── device-connect/ # Device Connect SDK source +└── ros2-device-connect/ # ROS 2 adapter +``` + +## What you've accomplished and what's next + +You've installed Docker, started a ROS 2 Humble container publishing on `/chatter`, and created a Python environment with the Device Connect packages next to the ros2-device-connect adapter. + +Next, you'll look at how the adapter code works, start it in D2D mode, and query the ROS 2 container through Device Connect. From 8a4ff9e347444647aaca7ec5b946bfd3dde0ef19 Mon Sep 17 00:00:00 2001 From: Kieran Hejmadi Date: Fri, 25 Sep 2026 14:06:34 +0000 Subject: [PATCH 2/3] incorporate Odin's changes --- .../ros2-device-connect/_index.md | 4 +++- .../ros2-device-connect/background.md | 4 ++++ .../ros2-device-connect/how-it-works.md | 17 +++++++++++++++++ .../profiles-and-hardware.md | 12 +++++++++--- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md index 26c417e012..655433c7c8 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md @@ -16,7 +16,9 @@ prerequisites: - An Arm-based Linux machine, such as a Raspberry Pi 5, an Arm cloud instance, or an Arm-based laptop, running Ubuntu 22.04 or later - Basic familiarity with Python, the Linux command line, and ROS 2 concepts such as nodes and topics -author: Kieran Hejmadi +author: + - Kieran Hejmadi + - Odin Shen # New Learning Paths are opted in for the next manual generated summary/FAQ run. # The generator resets this to false after a successful write. diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md index 340c4a0c0f..1df7faf287 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md @@ -13,6 +13,8 @@ A robot or smart camera built on ROS 2 already has a rich internal graph of node Device Connect fills that gap. In this Learning Path, you'll run a small adapter next to an unchanged ROS 2 system on an Arm-based Linux machine. The adapter registers the ROS 2 system as a Device Connect device and exposes a safe set of remote procedure calls (RPCs) that any peer or agent can discover and invoke. +For robotics, this pattern matters because a robot is not a single API. It is a live graph of sensors, controllers, diagnostics, services, and safety-critical actions. ROS 2 remains the robot's internal software graph. Device Connect adds an external capability layer, where the device owner chooses which ROS 2 operations become discoverable, typed, and remotely callable functions. + ## ROS 2 in brief ROS 2 (Robot Operating System 2) is an open-source middleware and toolset for building robotics applications. Applications are split into *nodes* that exchange data over *topics* (publish/subscribe), *services* (request/response), and *actions* (long-running goals). ROS 2 publishes official `arm64` packages and container images, so it runs natively on Arm platforms from a Raspberry Pi to a Neoverse cloud server. @@ -58,6 +60,8 @@ The repository is organized in three layers: The adapter is deliberately narrow. It exposes read-only inspection plus a small, reviewed set of hardware-specific RPCs. It never offers arbitrary topic publishing or service passthrough, so a remote caller can't drive the ROS 2 system in ways the driver author didn't intend. +This means the same adapter pattern can cover different robotics surfaces. A camera profile can expose perception data, a robot profile can expose diagnostics and bounded motion commands, and both can be called through the same Device Connect discovery and RPC model. + ## What you've learned and what's next You've learned that ROS 2 organizes a robot's internal software as a graph of nodes and topics, and that Device Connect makes a device discoverable and callable by peers and agents. The ros2-device-connect adapter joins the two by wrapping ROS 2 command-line tools in Device Connect RPCs. diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md index b99252540b..755dfed89b 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md @@ -73,6 +73,23 @@ This is also where you apply safety rules, because it's the boundary where remot The mixin provides six inspection RPCs in total, for nodes, topics, services, packages, interfaces, and topic info. They all follow the same pattern. +The table below shows how the Device Connect RPC names map back to the ROS 2 operations they run or wrap: + +| Device Connect RPC | ROS 2 operation inside the container | Purpose | +|---|---|---| +| `get_ros_nodes()` | `ros2 node list` | List active ROS 2 nodes | +| `get_ros_topics()` | `ros2 topic list` | List active ROS 2 topics | +| `get_ros_services()` | `ros2 service list` | List active ROS 2 services | +| `get_ros_packages()` | `ros2 pkg list` | List installed ROS 2 packages | +| `get_ros_interfaces()` | `ros2 interface list` | List available ROS 2 message and service interfaces | +| `get_topic_info(topic)` | `ros2 topic info ` | Inspect one topic after validating the topic name | +| `get_raw_image()` | Subscribe to `/image_raw`, then JPEG/base64 encode one frame | Expose a camera stream as a callable perception RPC | +| `run_action(action)` | Call `/puppy_control/runActionGroup` with an allowlisted action file | Run a pre-approved PuppyPi motion | +| `set_velocity(x, y, yaw_rate)` | Publish once to `/puppy_control/velocity` | Send a bounded PuppyPi velocity command | +| `stop()` | Publish a zero-velocity command | Stop PuppyPi motion | + +Device Connect does not replace ROS 2. It wraps selected ROS 2 operations as discoverable, typed, remotely callable capabilities. + ## Layer 4: build the device The final layer is a driver class that inherits from both `Ros2InspectionMixin` and `DeviceDriver`. The mixin supplies the shared ROS 2 RPCs. `DeviceDriver` makes the class a Device Connect device, and you add any hardware-specific RPCs alongside the shared ones. The `rpi5` profile runs this driver from `puppypi_device.py`: diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md index e8d6e9c453..a8e956f2ee 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md +++ b/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md @@ -39,7 +39,9 @@ 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 turns a Raspberry Pi 5 with a camera into a device that any peer or agent can ask for a photo. The camera must appear as a V4L2 video device, such as `/dev/video0`. A USB webcam works without extra setup. +The `rpi_camera` configuration is the lowest-risk real hardware example in this Learning Path. It exposes a sensor capability, not 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 does not need to know the ROS 2 topic name, 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. {{% notice Note %}} Raspberry Pi Camera Modules connected by ribbon cable use the libcamera stack, and 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, or set `VIDEO_DEVICE` to the video node your camera stack provides. @@ -81,6 +83,8 @@ async def get_raw_image(self, quality: int = DEFAULT_JPEG_QUALITY) -> dict[str, The RPC runs `capture_frame.py` inside the container. The script subscribes to `/image_raw`, takes one frame, and returns it as a base64-encoded JPEG. It subscribes with ROS 2's `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 below 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 in the previous section, replacing `127.0.0.1` with the Raspberry Pi's IP address if you run the client on another machine: ```python @@ -98,7 +102,9 @@ The repository's `view_image.py` script does the same thing against a device in ## Try a ROS 2 robot -The `puppypi` profile targets a [Hiwonder PuppyPi quadruped](https://www.hiwonder.com/products/puppypi) running ROS 2 Humble. It shows how you can add motion control safely: +The `puppypi` profile targets a [Hiwonder PuppyPi quadruped](https://www.hiwonder.com/products/puppypi), a small four-legged robot built around Raspberry Pi-class hardware and a ROS 2 control stack. In this Learning Path, PuppyPi serves as the real robot example: the same Device Connect adapter pattern used for camera perception is extended to selected locomotion capabilities. + +It shows how you can add motion control safely: - `run_action` accepts only a fixed allowlist of pre-recorded moves, such as `sit`, `stand`, and `wave`, and rejects any other value. - `set_velocity` rejects out-of-range values instead of clamping them. @@ -106,7 +112,7 @@ The `puppypi` profile targets a [Hiwonder PuppyPi quadruped](https://www.hiwonde Robot bring-up includes starting `puppy_control` and enabling servo torque. For those steps and the full safety model, see the [ros2-device-connect README](https://github.com/odincodeshen/ros2-device-connect#startup-checklist). -//TODO - Odin to provide a video of his quadruped? +The important design point is that the adapter does not expose arbitrary ROS 2 control. It turns a reviewed subset of the robot's ROS 2 graph into Device Connect capabilities: read-only diagnostics, allowlisted actions, bounded velocity, and an explicit stop command. ## Build a profile for your own ROS 2 system From d74f2137866f161e8b307e3f1bd27481100fa976 Mon Sep 17 00:00:00 2001 From: Kieran Hejmadi Date: Mon, 28 Sep 2026 12:59:46 +0000 Subject: [PATCH 3/3] Squash commit - Move ros2-device-connect learning path to cross-platform add video link minor readbility improvements --- .../ros2-device-connect/_index.md | 6 ++++++ .../ros2-device-connect/_next-steps.md | 0 .../ros2-device-connect/background.md | 4 ++-- .../ros2-device-connect/how-it-works.md | 0 .../ros2-device-connect/profiles-and-hardware.md | 2 ++ .../ros2-device-connect/setup.md | 4 ++-- 6 files changed, 12 insertions(+), 4 deletions(-) rename content/learning-paths/{embedded-and-microcontrollers => cross-platform}/ros2-device-connect/_index.md (96%) rename content/learning-paths/{embedded-and-microcontrollers => cross-platform}/ros2-device-connect/_next-steps.md (100%) rename content/learning-paths/{embedded-and-microcontrollers => cross-platform}/ros2-device-connect/background.md (97%) rename content/learning-paths/{embedded-and-microcontrollers => cross-platform}/ros2-device-connect/how-it-works.md (100%) rename content/learning-paths/{embedded-and-microcontrollers => cross-platform}/ros2-device-connect/profiles-and-hardware.md (98%) rename content/learning-paths/{embedded-and-microcontrollers => cross-platform}/ros2-device-connect/setup.md (95%) diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md b/content/learning-paths/cross-platform/ros2-device-connect/_index.md similarity index 96% rename from content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md rename to content/learning-paths/cross-platform/ros2-device-connect/_index.md index 655433c7c8..4bb1ceb9b4 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_index.md +++ b/content/learning-paths/cross-platform/ros2-device-connect/_index.md @@ -44,6 +44,12 @@ tools_software_languages: operatingsystems: - Linux +### Cross-platform metadata only +shared_path: true +shared_between: + - automotive + - embedded-and-microcontrollers + further_reading: - resource: title: ros2-device-connect example repository diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_next-steps.md b/content/learning-paths/cross-platform/ros2-device-connect/_next-steps.md similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/_next-steps.md rename to content/learning-paths/cross-platform/ros2-device-connect/_next-steps.md diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md b/content/learning-paths/cross-platform/ros2-device-connect/background.md similarity index 97% rename from content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md rename to content/learning-paths/cross-platform/ros2-device-connect/background.md index 1df7faf287..5fd219529b 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/background.md +++ b/content/learning-paths/cross-platform/ros2-device-connect/background.md @@ -11,13 +11,13 @@ layout: learningpathall A robot or smart camera built on ROS 2 already has a rich internal graph of nodes, topics, and services. That graph is designed for components *inside* the robot to talk to each other. It isn't designed for other devices on your network, or for AI agents, to discover the robot and ask it structured questions such as "which topics are you publishing?" or "capture a photo". -Device Connect fills that gap. In this Learning Path, you'll run a small adapter next to an unchanged ROS 2 system on an Arm-based Linux machine. The adapter registers the ROS 2 system as a Device Connect device and exposes a safe set of remote procedure calls (RPCs) that any peer or agent can discover and invoke. +Device Connect fills that gap. In this Learning Path, you'll run a small adapter next to an unchanged ROS 2 system on an Arm-based Linux machine. The adapter registers the ROS 2 system as a Device Connect device and exposes a safe set of remote procedure calls (RPCs) that a peer or agent can discover and invoke. For robotics, this pattern matters because a robot is not a single API. It is a live graph of sensors, controllers, diagnostics, services, and safety-critical actions. ROS 2 remains the robot's internal software graph. Device Connect adds an external capability layer, where the device owner chooses which ROS 2 operations become discoverable, typed, and remotely callable functions. ## ROS 2 in brief -ROS 2 (Robot Operating System 2) is an open-source middleware and toolset for building robotics applications. Applications are split into *nodes* that exchange data over *topics* (publish/subscribe), *services* (request/response), and *actions* (long-running goals). ROS 2 publishes official `arm64` packages and container images, so it runs natively on Arm platforms from a Raspberry Pi to a Neoverse cloud server. +ROS 2 (Robot Operating System 2) is an open source middleware and toolset for building robotics applications. Applications are split into *nodes* that exchange data over *topics* (publish/subscribe), *services* (request/response), and *actions* (long-running goals). ROS 2 publishes official `arm64` packages and container images, so it runs natively on Arm platforms from a Raspberry Pi to a Neoverse cloud server. This Learning Path uses ROS 2 inside a Docker container and doesn't go deeper into ROS 2 itself. For more background, see: diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md b/content/learning-paths/cross-platform/ros2-device-connect/how-it-works.md similarity index 100% rename from content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/how-it-works.md rename to content/learning-paths/cross-platform/ros2-device-connect/how-it-works.md diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md b/content/learning-paths/cross-platform/ros2-device-connect/profiles-and-hardware.md similarity index 98% rename from content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md rename to content/learning-paths/cross-platform/ros2-device-connect/profiles-and-hardware.md index a8e956f2ee..b046dfef4e 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/profiles-and-hardware.md +++ b/content/learning-paths/cross-platform/ros2-device-connect/profiles-and-hardware.md @@ -114,6 +114,8 @@ Robot bring-up includes starting `puppy_control` and enabling servo torque. For The important design point is that the adapter does not expose arbitrary ROS 2 control. It turns a reviewed subset of the robot's ROS 2 graph into Device Connect capabilities: read-only diagnostics, allowlisted actions, bounded velocity, and an explicit stop command. +To see this profile running on a real PuppyPi, watch the [ROS 2 and Device Connect PuppyPi demo](https://www.youtube.com/watch?v=R4DJ2_jKvZQ). + ## Build a profile for your own ROS 2 system To connect a different ROS 2 system, follow the same pattern: diff --git a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/setup.md b/content/learning-paths/cross-platform/ros2-device-connect/setup.md similarity index 95% rename from content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/setup.md rename to content/learning-paths/cross-platform/ros2-device-connect/setup.md index 4cb501d645..08d7d8d143 100644 --- a/content/learning-paths/embedded-and-microcontrollers/ros2-device-connect/setup.md +++ b/content/learning-paths/cross-platform/ros2-device-connect/setup.md @@ -63,12 +63,12 @@ The node list is empty because `ros2 topic pub` runs as a hidden node. This cont ## Create the workspace -The adapter's launch scripts expect a single project directory that holds the adapter repository and a Python virtual environment named `.venv`. Create that directory and clone both the adapter and the Device Connect source: +The adapter's launch scripts expect a single project directory that holds the adapter repository and a Python virtual environment named `.venv`. Create that directory and clone both the adapter and the Device Connect source. The adapter is cloned at the `ros2_dc_lp` tag, which is the version this Learning Path was tested with: ```bash mkdir -p ~/device_connect cd ~/device_connect -git clone https://github.com/odincodeshen/ros2-device-connect.git +git clone --branch ros2_dc_lp https://github.com/odincodeshen/ros2-device-connect.git git clone https://github.com/arm/device-connect.git ```