3.3 KiB
sanad_api_r1 — R1 fleet telemetry agent
Pushes the Unitree R1 EDU's live status to the YS Lootah fleet server. One
robot type = one folder = its own Docker image (sibling of sanad_api_g1).
This build covers the "Main statuses" endpoint — the R1 map is skipped by request:
POST {SERVER_URL}/api/v1/fleet/ingest/telemetry Authorization: Bearer <device_token>
{ "sn": "r1_0000", "mac": "…", "battery": 74, "charging": false,
"status": "idle", "position": {"x": …, "y": …}|null, "faults": [] }
Sent every ~2 s. If DDS state can't be read it still sends a heartbeat
(battery: null, status: "offline") so the robot shows as online.
Where each field comes from (R1 EDU, unitree_hg DDS)
| field | source |
|---|---|
battery, charging |
rt/lf/bmsstate (BmsState_): soc 0–100; charging = current > +0.05 A — same as SanadR1 arm_controller.get_battery |
faults[] |
rt/lowstate (LowState_): low battery, motor over-temp, comms-stale |
status |
derived (charging / moving / idle / offline); optional loco FSM read (ids 0/1/4/811, GET-only) with R1_READ_FSM=1 |
position |
R1 localizes with stereo VSLAM (ROS side). This agent is ROS-free, so position is null unless R1_POSITION_SOURCE=rosbridge (reads /odom) |
mac |
primary NIC hardware address |
Safety: never commands motion — only the read-only
GET_FSM_IDRPC is ever issued to the R1.
Install (full Docker) — on the R1 backpack
One command — builds the DDS image, creates .env, starts the container (auto-restarts on boot):
./install.sh # creates .env if missing, then build + up -d
# (edit .env when prompted: SERVER_URL, DEVICE_TOKEN, SN, DDS_INTERFACE=eth10)
./install.sh --logs # follow logs
./install.sh --simulate # one synthetic post (server smoke test, no robot)
./install.sh --down # stop + remove
Or the raw compose flow:
cp .env.example .env # SERVER_URL, DEVICE_TOKEN, SN, DDS_INTERFACE=eth10
docker compose up -d --build
docker compose logs -f
Test without a robot (synthetic state, exercises the full upload path):
docker compose run --rm sanad-api-r1 --simulate --once # one synthetic post
docker compose run --rm sanad-api-r1 --dry-run # print telemetry, no POST
# or bare:
SERVER_URL=… DEVICE_TOKEN=… SN=r1_0000 python3 sanad_api_r1.py --simulate -v
Key env (full list in .env.example)
| var | meaning |
|---|---|
SERVER_URL, DEVICE_TOKEN |
given by YS Lootah — required |
SN |
fleet id, e.g. r1_0000 |
DDS_INTERFACE |
robot link — eth10 on the R1 backpack |
R1_READ_FSM |
1 = read loco FSM for status (GET-only) |
R1_POSITION_SOURCE |
none (default) · rosbridge |
POLL_INTERVAL |
seconds between posts (default 2) |
Build note
Build on the R1 backpack (arm64). The image bundles the DDS stack
(CycloneDDS + the vendored unitree_sdk2py wheel under vendor/), mirroring
SanadR1's recipe. network_mode: host is required for DDS visibility. If
unitree_sdk2py can't load, the agent logs a warning and sends heartbeats.
Porting
sanad_api_go2 is the same pattern; the Go2 uses unitree_go (not unitree_hg)
DDS, so swap the message imports (BmsState_/LowState_ idl path) and topic
names for the Go2 SDK.