From 48dfe4d76b832f4884cf7a93fb0d118920f05b3b Mon Sep 17 00:00:00 2001 From: "waheed.brown@arm.com" Date: Mon, 6 Apr 2026 15:11:05 -0500 Subject: [PATCH 1/3] feat: add reachy nats sidecar support --- .../device_connect/reachy_mini_driver.py | 59 +++++++++++++------ .../device_connect/run_reachy_nats.py | 52 ++++++++++++++++ tests/test_device_connect_drivers.py | 16 +++++ 3 files changed, 108 insertions(+), 19 deletions(-) create mode 100644 strands_robots/device_connect/run_reachy_nats.py diff --git a/strands_robots/device_connect/reachy_mini_driver.py b/strands_robots/device_connect/reachy_mini_driver.py index 91789f9..121fb3b 100644 --- a/strands_robots/device_connect/reachy_mini_driver.py +++ b/strands_robots/device_connect/reachy_mini_driver.py @@ -1,9 +1,14 @@ """ReachyMiniDriver — Device Connect DeviceDriver for Pollen Reachy Mini robots. -Auto-detects hardware variant via the daemon's ``wireless_version`` flag: +By default it auto-detects the hardware variant via the daemon's +``wireless_version`` flag: - **Wireless** (has onboard Pi): uses Zenoh transport for real-time I/O. - **Lite** (USB-only, no Pi): uses WebSocket to the daemon directly. +You can override this with ``transport_mode="websocket"`` or +``transport_mode="zenoh"`` when auto-detection is not the right choice for the +surrounding network topology. + REST API calls go through reachy_transport.api() for daemon/move operations. """ @@ -16,13 +21,22 @@ from device_connect_sdk.drivers import DeviceDriver, emit, on, rpc from device_connect_sdk.types import DeviceIdentity, DeviceStatus -from strands_robots.device_connect.reachy_transport import ( - api, - rpy_to_pose, - identity_pose, - ZenohLink, - WebSocketLink, -) +try: + from strands_robots.device_connect.reachy_transport import ( + api, + rpy_to_pose, + identity_pose, + ZenohLink, + WebSocketLink, + ) +except ImportError: + from reachy_transport import ( # type: ignore + api, + rpy_to_pose, + identity_pose, + ZenohLink, + WebSocketLink, + ) logger = logging.getLogger(__name__) @@ -41,11 +55,15 @@ def __init__( host: str = "reachy-mini.local", prefix: str = "reachy_mini", api_port: int = 8000, + transport_mode: str = "auto", ): super().__init__() self._host = host self._prefix = prefix self._api_port = api_port + if transport_mode not in {"auto", "websocket", "zenoh"}: + raise ValueError("transport_mode must be one of: auto, websocket, zenoh") + self._transport_mode = transport_mode self._latest_joints: Optional[dict] = None self._latest_imu: Optional[dict] = None self._hw = None @@ -64,18 +82,21 @@ def status(self) -> DeviceStatus: return DeviceStatus(availability="idle") async def connect(self) -> None: - """Connect to the Reachy Mini, auto-detecting Wireless vs Lite.""" - try: - status = await asyncio.to_thread( - api, self._host, self._api_port, "/api/daemon/status" - ) - is_lite = not status.get("wireless_version", True) - except Exception: - is_lite = False - - if is_lite: + """Connect to the Reachy Mini using the selected transport mode.""" + use_websocket = self._transport_mode == "websocket" + + if self._transport_mode == "auto": + try: + status = await asyncio.to_thread( + api, self._host, self._api_port, "/api/daemon/status" + ) + use_websocket = not status.get("wireless_version", True) + except Exception: + use_websocket = False + + if use_websocket: self._hw = WebSocketLink(self._host, self._api_port) - logger.info("Connected to Reachy Mini Lite at %s (WebSocket)", self._host) + logger.info("Connected to Reachy Mini at %s (WebSocket)", self._host) else: self._hw = ZenohLink(self.transport, self._prefix) logger.info("Connected to Reachy Mini at %s (Zenoh)", self._host) diff --git a/strands_robots/device_connect/run_reachy_nats.py b/strands_robots/device_connect/run_reachy_nats.py new file mode 100644 index 0000000..0d591e6 --- /dev/null +++ b/strands_robots/device_connect/run_reachy_nats.py @@ -0,0 +1,52 @@ +"""Run Reachy Mini Device Connect against a NATS broker. + +This sidecar is intended for cases where the robot already has an existing +local control/demo stack. It talks to the Reachy daemon over REST/WebSocket and +bridges structured RPCs into Device Connect over NATS. +""" + +import asyncio +import os + +from device_connect_sdk import DeviceRuntime + +try: + from strands_robots.device_connect.reachy_mini_driver import ReachyMiniDriver +except ImportError: + from reachy_mini_driver import ReachyMiniDriver # type: ignore + + +def _env(name: str, default: str) -> str: + return os.environ.get(name, default).strip() or default + + +async def main() -> None: + nats_url = _env("NATS_URL", "nats://localhost:4222") + nats_credentials_file = os.environ.get("NATS_CREDENTIALS_FILE", "").strip() or None + device_id = _env("DEVICE_ID", "reachy-mini-1") + host = _env("REACHY_HOST", "127.0.0.1") + prefix = _env("REACHY_PREFIX", "reachy_mini") + transport_mode = _env("REACHY_TRANSPORT_MODE", "websocket") + api_port = int(_env("REACHY_PORT", "8000")) + + driver = ReachyMiniDriver( + host=host, + prefix=prefix, + api_port=api_port, + transport_mode=transport_mode, + ) + runtime_kwargs = { + "driver": driver, + "device_id": device_id, + "messaging_backend": "nats", + "messaging_urls": [nats_url], + "allow_insecure": True, + } + if nats_credentials_file: + runtime_kwargs["nats_credentials_file"] = nats_credentials_file + runtime = DeviceRuntime(**runtime_kwargs) + await runtime.run() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/tests/test_device_connect_drivers.py b/tests/test_device_connect_drivers.py index 5340eae..9a69536 100644 --- a/tests/test_device_connect_drivers.py +++ b/tests/test_device_connect_drivers.py @@ -580,6 +580,22 @@ def test_connect_subscribes(self, mock_api): self.assertIn("reachy_mini/joint_positions", topics) self.assertIn("reachy_mini/imu_data", topics) + @patch("strands_robots.device_connect.reachy_mini_driver.WebSocketLink") + @patch("strands_robots.device_connect.reachy_mini_driver.api") + def test_connect_forces_websocket_mode(self, mock_api, MockWebSocketLink): + mock_api.return_value = {"wireless_version": True} + mock_link = AsyncMock() + MockWebSocketLink.return_value = mock_link + driver = self.ReachyMiniDriver(transport_mode="websocket") + driver._transport = AsyncMock() + asyncio.run(driver.connect()) + MockWebSocketLink.assert_called_once_with("reachy-mini.local", 8000) + mock_link.start.assert_awaited_once() + + def test_invalid_transport_mode(self): + with self.assertRaises(ValueError): + self.ReachyMiniDriver(transport_mode="bad-mode") + def test_disconnect(self): driver = self._make_driver() asyncio.run(driver.disconnect()) From 2ab650d7e238d90f379d7a9babf1821770b24f70 Mon Sep 17 00:00:00 2001 From: "waheed.brown@arm.com" Date: Mon, 6 Apr 2026 20:01:48 -0500 Subject: [PATCH 2/3] docs: add reachy nats sidecar guide --- strands_robots/device_connect/GUIDE.md | 4 + strands_robots/device_connect/reachy-guide.md | 279 +++++++++++------- 2 files changed, 180 insertions(+), 103 deletions(-) diff --git a/strands_robots/device_connect/GUIDE.md b/strands_robots/device_connect/GUIDE.md index 143f8f7..5db71a6 100644 --- a/strands_robots/device_connect/GUIDE.md +++ b/strands_robots/device_connect/GUIDE.md @@ -390,3 +390,7 @@ print(invoke_device('reachy-mini-1', 'look', {'pitch': -15, 'yaw': 30})) print(invoke_device('reachy-mini-1', 'nod')) " ``` + +For the full Raspberry Pi setup, including the working NATS sidecar pattern +that reuses an existing local Reachy daemon, see `reachy-guide.md` in this +directory. diff --git a/strands_robots/device_connect/reachy-guide.md b/strands_robots/device_connect/reachy-guide.md index d35413e..b54a2ae 100644 --- a/strands_robots/device_connect/reachy-guide.md +++ b/strands_robots/device_connect/reachy-guide.md @@ -1,29 +1,54 @@ -# Reachy Mini — Cloud Zenoh Mesh Guide +# Reachy Mini — Device Connect NATS Guide -Connect a Reachy Mini Lite (USB) to the AWS-hosted Zenoh mesh via Device Connect so any agent on the mesh can discover and control it. +Connect a Reachy Mini on a Raspberry Pi to a remote Device Connect NATS broker. -## Prerequisites +This guide covers the working setup we validated on the Pi at `10.118.156.11`: -- Reachy Mini Lite plugged in via USB -- Python 3.12+, `uv` installed +- the existing Reachy browser-control demo continues to own the local robot daemon +- a separate Device Connect sidecar runs alongside it +- the sidecar bridges Reachy RPCs into Device Connect over NATS -## 1. Start the Reachy Daemon +## Where this code belongs -The daemon bridges USB serial to a local REST/WebSocket API on port 9002. +The Pi-side Device Connect bridge belongs in the `robots` repo, not in +`conference-room`. -```bash -# From the repo root (uses uv inline script, auto-installs reachy-mini) -uv run --python 3.13 --with reachy-mini start_reachy_daemon.py``` +- `conference-room` owns the app-specific Reachy browser demo: + `apps/reachy_mini_browser_control/...` +- `robots` owns the reusable Strands + Device Connect Reachy integration: + `strands_robots/device_connect/reachy_mini_driver.py` + `strands_robots/device_connect/reachy_transport.py` + `strands_robots/device_connect/run_reachy_nats.py` -Expected output: +If you need to document how the local Pi demo starts Reachy, keep that in the +`conference-room` README. If you need to document how Reachy registers into +Device Connect, keep that here. +## Two valid deployment patterns + +### Option A: Reuse an existing local Reachy daemon + +Use this when the Pi is already running the browser-control demo or another +local Reachy app that exposes the daemon on `127.0.0.1:8000`. + +This is the safest option if you do not want to disturb an existing demo. + +### Option B: Start a dedicated Reachy daemon from this repo + +Use this when no local Reachy daemon is already running. + +That path is what Sourav described originally: + +```bash +source .venv/bin/activate +python strands_robots/device_connect/start_reachy_daemon.py ``` -Starting reachy-mini-daemon on /dev/cu.usbmodemXXXX (API port 9002) -``` -Leave this running in a separate terminal. +It starts a local daemon on port `9002` and is fine for a standalone setup. + +## Recommended setup on a Pi with an existing Reachy demo -## 2. Clone and Set Up the Strands Robots SDK +### 1. Prepare the `robots` repo ```bash git clone --branch feat/device-connect-integration-draft \ @@ -31,135 +56,183 @@ git clone --branch feat/device-connect-integration-draft \ cd robots ./strands_robots/device_connect/setup.sh source .venv/bin/activate -uv pip install websockets # required dependency not yet in setup +uv pip install websockets +``` + +### 2. Verify the local Reachy daemon + +If the Pi already runs `reachy_mini_browser_control`, verify that the local +daemon is healthy: + +```bash +curl http://127.0.0.1:8000/api/daemon/status +``` + +Expected output includes: + +```json +{ + "state": "running", + "wireless_version": false +} ``` -## 3. Connect to the Cloud Zenoh Mesh +If nothing is listening on port `8000`, use **Option B** above to start a +dedicated daemon on `9002` instead. -Set environment variables (every terminal that talks to the mesh): +### 3. Start the Device Connect sidecar + +If you already have a daemon on `127.0.0.1:8000`, run the sidecar against that +daemon: ```bash -export ZENOH_CONNECT=tcp/zenoh-nlb-2cb0b84309701828.elb.us-east-1.amazonaws.com:7447 -export ZENOH_MODE=client +cd /path/to/robots +source .venv/bin/activate + +export MESSAGING_BACKEND=nats +export NATS_URL='nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222' +export MESSAGING_URLS="$NATS_URL" export DEVICE_CONNECT_ALLOW_INSECURE=true + +python strands_robots/device_connect/run_reachy_nats.py +``` + +By default, the runner uses: + +- `DEVICE_ID=reachy-mini-1` +- `REACHY_HOST=127.0.0.1` +- `REACHY_PORT=8000` +- `REACHY_TRANSPORT_MODE=websocket` + +You can override them if needed: + +```bash +export DEVICE_ID='reachy-mini-1' +export REACHY_HOST='127.0.0.1' +export REACHY_PORT='8000' +export REACHY_TRANSPORT_MODE='websocket' +python strands_robots/device_connect/run_reachy_nats.py +``` + +Expected successful logs: + +```text +INFO - Using NATS messaging backend +INFO - Connected to NATS broker: ['nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222'] +INFO - Driver connected: reachy_mini +INFO - Device registered: ... +INFO - Subscribed to commands on device-connect.default.reachy-mini-1.cmd ``` -Start the Device Connect runtime (bridges the local daemon to the cloud mesh): +### 4. Sourav's original direct-runtime form + +If you prefer not to use the helper runner, this is the equivalent direct form. + +When reusing an existing daemon on the Pi, change the original `api_port=9002` +example to `api_port=8000`: ```bash +cd /path/to/robots +source .venv/bin/activate +export MESSAGING_BACKEND=nats +export NATS_URL='nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222' +export MESSAGING_URLS="$NATS_URL" +export DEVICE_CONNECT_ALLOW_INSECURE=true + python -c " import asyncio, os from strands_robots.device_connect import ReachyMiniDriver from device_connect_sdk import DeviceRuntime -driver = ReachyMiniDriver(host='localhost', api_port=9002) +driver = ReachyMiniDriver( + host='localhost', + api_port=8000, + transport_mode='websocket', +) runtime = DeviceRuntime( driver=driver, device_id='reachy-mini-1', - messaging_urls=[os.environ['ZENOH_CONNECT']], + messaging_urls=[os.environ['NATS_URL']], + messaging_backend='nats', allow_insecure=True, ) asyncio.run(runtime.run()) " ``` -Expected output: - -``` -INFO - Using ZENOH messaging backend -INFO - Connected to ZENOH broker: ['tcp/zenoh-nlb-...amazonaws.com:7447'] -INFO - Driver connected: reachy_mini -INFO - Device registered: registration_id=... -INFO - Subscribed to commands on device-connect.default.reachy-mini-1.cmd -``` - -Leave this running. The robot is now on the mesh as `reachy-mini-1`. +Use `api_port=9002` only if you started the dedicated daemon from +`start_reachy_daemon.py`. -## 4. Invoke Commands from Any Mesh Client +## Verify from a client -From another terminal (with the same env vars and venv activated): +From another terminal: ```bash -source robots/.venv/bin/activate -export ZENOH_CONNECT=tcp/zenoh-nlb-2cb0b84309701828.elb.us-east-1.amazonaws.com:7447 -export ZENOH_MODE=client +cd /path/to/robots +source .venv/bin/activate +export MESSAGING_BACKEND=nats +export NATS_URL='nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222' +export MESSAGING_URLS="$NATS_URL" export DEVICE_CONNECT_ALLOW_INSECURE=true ``` -### Move antennas +Discover devices: -```python -from device_connect_agent_tools import connect, invoke_device -connect() -r = invoke_device('reachy-mini-1', 'antennas', {'left': 30, 'right': -30}) -print('RESULT:', r) -# {'success': True, 'result': {'status': 'success', 'left': 30, 'right': -30}} -``` - -### Look (head pose) - -```python -invoke_device('reachy-mini-1', 'look', {'pitch': -15, 'yaw': 15, 'roll': 0}) -# pitch: up/down (negative = look up), yaw: left/right, roll: tilt -``` - -### Expressions +```bash +python -c " +from device_connect_agent_tools import connect, discover_devices +from device_connect_agent_tools.connection import disconnect -```python -invoke_device('reachy-mini-1', 'nod') # yes gesture -invoke_device('reachy-mini-1', 'shake') # no gesture -invoke_device('reachy-mini-1', 'happy') # antenna wiggle +connect() +try: + print(discover_devices()) +finally: + disconnect() +" ``` -### Sequence example +Invoke RPCs: -```python +```bash +python -c " from device_connect_agent_tools import connect, invoke_device -import time +from device_connect_agent_tools.connection import disconnect connect() -invoke_device('reachy-mini-1', 'look', {'pitch': -10, 'yaw': 15}) -time.sleep(1) -invoke_device('reachy-mini-1', 'nod') -time.sleep(2) -invoke_device('reachy-mini-1', 'look', {'pitch': 0, 'yaw': 0, 'roll': 0}) -print('Done!') +try: + print(invoke_device('reachy-mini-1', 'look', {'pitch': -15, 'yaw': 15})) + print(invoke_device('reachy-mini-1', 'nod')) +finally: + disconnect() +" ``` -## Available RPCs - -| RPC | Parameters | Description | -|-----|-----------|-------------| -| `look` | `pitch`, `roll`, `yaw`, `x`, `y`, `z` | Set head pose (degrees / mm) | -| `antennas` | `left`, `right` | Set antenna angles (degrees) | -| `body` | `yaw` | Set body yaw (degrees) | -| `nod` | — | Yes gesture | -| `shake` | — | No gesture | -| `happy` | — | Antenna wiggle | -| `getJoints` | — | Current joint positions | -| `getImu` | — | IMU sensor data | -| `enableMotors` | `motor_ids` (optional) | Torque on | -| `disableMotors` | `motor_ids` (optional) | Torque off | -| `wakeUp` | — | Enable motors + wake animation | -| `sleep` | — | Sleep animation + disable motors | -| `stopMotion` | — | Stop all motion | -| `getDaemonStatus` | — | Daemon status and motor state | -| `playMove` | `move_name`, `library` | Play recorded move (`emotions` or `dance`) | -| `listMoves` | `library` | List available moves | - -## Process Summary - -You need **three terminals**: - -| Terminal | Command | Purpose | -|----------|---------|---------| -| 1 | `python start_reachy_daemon.py` | USB serial daemon (port 9002) | -| 2 | Device Connect runtime script (step 3) | Bridges daemon to cloud Zenoh mesh | -| 3 | `invoke_device(...)` calls (step 4) | Send commands to the robot | +## File inventory + +These are the reusable Reachy Device Connect files in this repo: + +- `strands_robots/device_connect/reachy_mini_driver.py` + Reachy-specific Device Connect RPC driver +- `strands_robots/device_connect/reachy_transport.py` + REST/WebSocket and Zenoh transport helpers +- `strands_robots/device_connect/run_reachy_nats.py` + Small sidecar runner for NATS registration +- `strands_robots/device_connect/start_reachy_daemon.py` + Helper for standalone daemon startup when you are not reusing an existing Pi + daemon ## Troubleshooting -- **`No USB serial device found`** — Check that Reachy is plugged in (`ls /dev/cu.usbmodem*`) -- **`ModuleNotFoundError: websockets`** — Run `uv pip install websockets` -- **`KeyboardInterrupt` on import** — The `cv2` import can hang; make sure the daemon terminal is separate from the Device Connect terminal -- **Connection refused on port 9002** — Daemon not running; start it first (step 1) +- `Authorization Violation` + The NATS credentials are valid enough to authenticate, but not valid for the + broker you are pointing at. +- `permissions violation for publish to "device-connect.default.registry"` + The credentials authenticate, but the broker ACLs do not allow Device Connect + registration. +- `Connection refused` on port `8000` + No local Reachy daemon is running there. Either start the browser-control demo + or run `start_reachy_daemon.py` and point the sidecar at `9002`. +- `No USB serial device found` + Only relevant when starting a dedicated daemon directly from this repo. +- `ModuleNotFoundError: websockets` + Run `uv pip install websockets`. From 2aeb3d23c3d2448c47ac61bfa1a00ccbd240d37e Mon Sep 17 00:00:00 2001 From: "waheed.brown@arm.com" Date: Mon, 6 Apr 2026 21:55:10 -0500 Subject: [PATCH 3/3] docs: document private reachy tenant config --- strands_robots/device_connect/GUIDE.md | 6 +- strands_robots/device_connect/reachy-guide.md | 56 +++++++++++++++++-- .../device_connect/run_reachy_nats.py | 2 + 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/strands_robots/device_connect/GUIDE.md b/strands_robots/device_connect/GUIDE.md index 5db71a6..741cf35 100644 --- a/strands_robots/device_connect/GUIDE.md +++ b/strands_robots/device_connect/GUIDE.md @@ -391,6 +391,6 @@ print(invoke_device('reachy-mini-1', 'nod')) " ``` -For the full Raspberry Pi setup, including the working NATS sidecar pattern -that reuses an existing local Reachy daemon, see `reachy-guide.md` in this -directory. +For the full Raspberry Pi setup, including the working NATS sidecar pattern, +required `TENANT` / `DEVICE_ID` settings, and the "reuse the existing local +Reachy daemon" flow, see `reachy-guide.md` in this directory. diff --git a/strands_robots/device_connect/reachy-guide.md b/strands_robots/device_connect/reachy-guide.md index b54a2ae..2634628 100644 --- a/strands_robots/device_connect/reachy-guide.md +++ b/strands_robots/device_connect/reachy-guide.md @@ -97,8 +97,46 @@ export DEVICE_CONNECT_ALLOW_INSECURE=true python strands_robots/device_connect/run_reachy_nats.py ``` +### Configuration requirements + +The exact required settings depend on which broker you are trying to join. + +#### Public broker + +This configuration is enough for the public broker: + +```bash +export MESSAGING_BACKEND=nats +export NATS_URL='nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222' +export MESSAGING_URLS="$NATS_URL" +export DEVICE_CONNECT_ALLOW_INSECURE=true +``` + +#### Private broker / Device Connect dashboard + +For the private broker that backs the Device Connect dashboard, the working +configuration must include these parameters: + +```bash +export MESSAGING_BACKEND=nats +export NATS_URL='nats://137.184.86.16:4222' +export MESSAGING_URLS="$NATS_URL" +export TENANT='souravpati' +export DEVICE_ID='reachy-mini-1' +export NATS_CREDENTIALS_FILE='/path/to/souravpati-reachy-mini-1.creds.json' +export DEVICE_CONNECT_ALLOW_INSECURE=true +export REACHY_HOST='127.0.0.1' +export REACHY_PORT='8000' +export REACHY_TRANSPORT_MODE='websocket' +``` + +The critical piece is `TENANT='souravpati'`. Without that, the same device ID +and credentials connect to the private broker but fail to register on the +expected Device Connect subjects. + By default, the runner uses: +- `TENANT=default` - `DEVICE_ID=reachy-mini-1` - `REACHY_HOST=127.0.0.1` - `REACHY_PORT=8000` @@ -107,10 +145,12 @@ By default, the runner uses: You can override them if needed: ```bash +export TENANT='souravpati' export DEVICE_ID='reachy-mini-1' export REACHY_HOST='127.0.0.1' export REACHY_PORT='8000' export REACHY_TRANSPORT_MODE='websocket' +export NATS_CREDENTIALS_FILE='/path/to/souravpati-reachy-mini-1.creds.json' python strands_robots/device_connect/run_reachy_nats.py ``` @@ -121,7 +161,7 @@ INFO - Using NATS messaging backend INFO - Connected to NATS broker: ['nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222'] INFO - Driver connected: reachy_mini INFO - Device registered: ... -INFO - Subscribed to commands on device-connect.default.reachy-mini-1.cmd +INFO - Subscribed to commands on device-connect.souravpati.reachy-mini-1.cmd ``` ### 4. Sourav's original direct-runtime form @@ -135,8 +175,11 @@ example to `api_port=8000`: cd /path/to/robots source .venv/bin/activate export MESSAGING_BACKEND=nats -export NATS_URL='nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222' +export NATS_URL='nats://137.184.86.16:4222' export MESSAGING_URLS="$NATS_URL" +export TENANT='souravpati' +export DEVICE_ID='reachy-mini-1' +export NATS_CREDENTIALS_FILE='/path/to/souravpati-reachy-mini-1.creds.json' export DEVICE_CONNECT_ALLOW_INSECURE=true python -c " @@ -151,9 +194,11 @@ driver = ReachyMiniDriver( ) runtime = DeviceRuntime( driver=driver, - device_id='reachy-mini-1', + device_id=os.environ['DEVICE_ID'], + tenant=os.environ['TENANT'], messaging_urls=[os.environ['NATS_URL']], messaging_backend='nats', + nats_credentials_file=os.environ['NATS_CREDENTIALS_FILE'], allow_insecure=True, ) asyncio.run(runtime.run()) @@ -171,8 +216,9 @@ From another terminal: cd /path/to/robots source .venv/bin/activate export MESSAGING_BACKEND=nats -export NATS_URL='nats://nats-nlb-52ebd3849ae4cb02.elb.us-east-1.amazonaws.com:4222' +export NATS_URL='nats://137.184.86.16:4222' export MESSAGING_URLS="$NATS_URL" +export TENANT='souravpati' export DEVICE_CONNECT_ALLOW_INSECURE=true ``` @@ -229,6 +275,8 @@ These are the reusable Reachy Device Connect files in this repo: - `permissions violation for publish to "device-connect.default.registry"` The credentials authenticate, but the broker ACLs do not allow Device Connect registration. +- Connects but registers under the wrong tenant + For the private broker/dashboard path, set `TENANT='souravpati'`. - `Connection refused` on port `8000` No local Reachy daemon is running there. Either start the browser-control demo or run `start_reachy_daemon.py` and point the sidecar at `9002`. diff --git a/strands_robots/device_connect/run_reachy_nats.py b/strands_robots/device_connect/run_reachy_nats.py index 0d591e6..a670499 100644 --- a/strands_robots/device_connect/run_reachy_nats.py +++ b/strands_robots/device_connect/run_reachy_nats.py @@ -23,6 +23,7 @@ def _env(name: str, default: str) -> str: async def main() -> None: nats_url = _env("NATS_URL", "nats://localhost:4222") nats_credentials_file = os.environ.get("NATS_CREDENTIALS_FILE", "").strip() or None + tenant = _env("TENANT", "default") device_id = _env("DEVICE_ID", "reachy-mini-1") host = _env("REACHY_HOST", "127.0.0.1") prefix = _env("REACHY_PREFIX", "reachy_mini") @@ -38,6 +39,7 @@ async def main() -> None: runtime_kwargs = { "driver": driver, "device_id": device_id, + "tenant": tenant, "messaging_backend": "nats", "messaging_urls": [nats_url], "allow_insecure": True,