95 lines
4.1 KiB
Markdown
95 lines
4.1 KiB
Markdown
# Sanad Fleet Agents
|
|
|
|
Per-robot agents that report to the YS Lootah fleet server, each shipped as
|
|
**full Docker** with a user-level **systemd auto-start service**. Deploy, manage,
|
|
and remove them over SSH with one interactive script.
|
|
|
|
```
|
|
Project/fleet/
|
|
fleet_install.sh # interactive + scriptable deploy/manage tool
|
|
fleet_test_server.py # your workstation standing in as the fleet server (tests)
|
|
agents/
|
|
g1/ sanad_api_g1.py — MAP uploader (POST …/{sn}/map)
|
|
r1/ sanad_api_r1.py — TELEMETRY unitree_hg (POST …/telemetry)
|
|
go2/ sanad_api_go2.py — TELEMETRY unitree_go (POST …/telemetry) [unverified on hw]
|
|
```
|
|
|
|
All three are self-contained (own `Dockerfile`, `.env.example`). Deploy uses
|
|
plain `docker build`/`docker run` — **no docker-compose needed on the robot**
|
|
(R1/Go2 don't have it). Source is pushed from this canonical workstation copy via
|
|
`rsync`; images build natively on each arm64 robot; a **user systemd unit**
|
|
(linger-enabled, **no sudo**) owns start/stop/auto-start-on-boot.
|
|
|
|
## Install / manage
|
|
|
|
Interactive (asks robot type → IP → detects installed-or-not → right actions):
|
|
|
|
```bash
|
|
./fleet_install.sh
|
|
```
|
|
|
|
Scriptable:
|
|
|
|
```bash
|
|
./fleet_install.sh install <g1|r1|go2> <ip> [--sn NAME] [--server-ip IP] [--port N] [--token TOK]
|
|
./fleet_install.sh data <g1|r1|go2> <ip> # show what it's currently sending
|
|
./fleet_install.sh status <g1|r1|go2> <ip>
|
|
./fleet_install.sh logs <g1|r1|go2> <ip>
|
|
./fleet_install.sh test <g1|r1|go2> <ip> # end-to-end vs workstation server
|
|
./fleet_install.sh uninstall <g1|r1|go2> <ip> # service + container + image + dir
|
|
```
|
|
|
|
`install`/`uninstall` are detected automatically in interactive mode: if the
|
|
agent is already on the robot it offers **data / status / logs / reinstall /
|
|
uninstall**; otherwise it prompts for **robot name (SN)**, server IP, port, token
|
|
and installs.
|
|
|
|
### Auto-start service
|
|
|
|
Each install writes `~/.config/systemd/user/sanad-api-<type>.service`, enables
|
|
linger (`loginctl enable-linger`, no sudo), and `systemctl --user enable --now`s
|
|
it. It starts on boot and restarts on crash. Manage with:
|
|
|
|
```bash
|
|
ssh unitree@<ip> 'systemctl --user status sanad-api-r1'
|
|
ssh unitree@<ip> 'systemctl --user restart sanad-api-r1'
|
|
```
|
|
|
|
## Fleet (this deployment)
|
|
|
|
| robot | agent | ip | ssh user | arch | DDS iface |
|
|
|---|---|---|---|---|---|
|
|
| G1 | map | `10.255.254.58` | `unitree` (key) | arm64 | — |
|
|
| R1 | telemetry | `10.255.254.82` | `unitree` (key) | arm64 | `eth10` |
|
|
| Go2 | telemetry | *(when available)* | `unitree` | arm64 | `eth0` |
|
|
|
|
Workstation fleet server (for tests): **10.255.254.83** via `fleet_test_server.py`.
|
|
|
|
## What each agent sends, and when
|
|
|
|
**G1 map** → `POST /api/v1/fleet/ingest/{sn}/map` — on change (scan ~30 s;
|
|
size+mtime→sha256). Sends the RTAB-Map `.db` + places as `multipart` (file `db` +
|
|
`meta` JSON `{sn,name,format:"rtabmap_db",size_bytes,sha256,points[…]}`) or
|
|
`base64json`. Reads web_nav3 `maps/<robot>/*.db` + `web/data/<robot>/places/*.json`.
|
|
|
|
**R1 / Go2 telemetry** → `POST /api/v1/fleet/ingest/telemetry` — every ~2 s;
|
|
heartbeat (`battery:null,status:offline`) when DDS is silent so it stays online:
|
|
```json
|
|
{ "sn":"r1_82", "mac":"…", "battery":74, "charging":false,
|
|
"status":"idle", "position":{"x":…,"y":…}|null, "faults":[], "ts":… }
|
|
```
|
|
- R1 (`unitree_hg`): battery `rt/lf/bmsstate`, faults/liveness `rt/lowstate`.
|
|
- Go2 (`unitree_go`): battery from **`rt/lowstate.bms_state`** (nested), no separate BMS topic.
|
|
- Both: `status` derived (charging/moving/idle/offline); position optional; read-only (never moves).
|
|
|
|
Auth on every request: `Authorization: Bearer <device_token>`.
|
|
|
|
## Notes / follow-ups
|
|
|
|
- **Point at the real fleet server:** re-run `install … --server-ip <fleet> --token <real> --sn <id>`.
|
|
- **G1 real maps** live *inside* the `p4_Foxy_sanad` nav container (not host-mounted).
|
|
Add a `maps/` bind-mount to Package_4's nav service, or share a volume, so the
|
|
uploader sees real maps (it reports 0 until then; `test` seeds a fixture).
|
|
- **Go2** agent is written from the `unitree_go` SDK layout but **not yet run on a
|
|
Go2** — confirm the `bms_state` current sign (charging) and sportmodestate fields.
|