202 lines
7.5 KiB
Markdown
202 lines
7.5 KiB
Markdown
# EngineAI Fleet — EngineAI PM01 agent
|
|
|
|
On-robot agent that reports the **EngineAI PM01** ("T800")'s live state to the
|
|
**YS Lootah fleet server / Eco system**.
|
|
|
|
> **Status: LIVE.** Running on `10.210.136.150` as the system service
|
|
> `sanad-api-eng`, posting to `https://eco-dev.yslootahrobotics.com` every 2 s.
|
|
> `https://eco.yslootahrobotics.com` is configured but need token to
|
|
> enable it.
|
|
|
|
```
|
|
telemetry ok: battery=21 charging=False status=idle mode=pd_sitdown pos=None
|
|
motor_max=55.0 faults=1 map=no_map -> eco-dev=200
|
|
```
|
|
|
|
---
|
|
|
|
## 1. Layout
|
|
|
|
```
|
|
engineai_fleet/
|
|
├── README.md ← this file
|
|
├── agent/
|
|
│ ├── sanad_api_eng.py ← THE AGENT (deployed as-is)
|
|
│ ├── .env.example ← every setting, documented
|
|
│ ├── requirements.txt ← requests (rclpy comes from the robot's ROS)
|
|
│ ├── sanad-api-eng.service ← systemd unit (root, survives reboot)
|
|
│ └── AGENT_README.md ← agent internals: backends, field mapping
|
|
├── tools/
|
|
│ └── probe_eng.sh ← read-only robot discovery
|
|
└── docs/
|
|
└── PM01_INTERFACE.md ← what the PM01 actually exposes
|
|
```
|
|
|
|
---
|
|
|
|
## 2. The robot
|
|
|
|
| | |
|
|
|---|---|
|
|
| Host | `10.210.136.150` (NIC `wlP1p1s0`) |
|
|
| SSH | `ubuntu@10.210.136.150` |
|
|
| Display name | `pm01_150` |
|
|
| Hardware | NVIDIA Jetson AGX Orin, arm64, L4T R36.4.3 |
|
|
| Board serial | `1421326045624` |
|
|
| OS | Ubuntu 22.04.5 LTS, kernel `5.15.148-6-engine-tegra` |
|
|
| ROS | Humble, **`ROS_DOMAIN_ID=69`**, CycloneDDS pinned to `eth1` |
|
|
| Product tag | `t800` (config dir `pm01`) |
|
|
| Install dir | `/opt/sanad_api_eng/` |
|
|
| Service | `systemctl sanad-api-eng` (**system** unit, root) |
|
|
|
|
---
|
|
|
|
## 3. How it gets its data
|
|
|
|
Four ROS 2 topics from the robot's own stack, all read-only:
|
|
|
|
| what | source |
|
|
|---|---|
|
|
| battery, voltage, current, charging | `/hardware/power_info` (`interface_protocol/msg/PowerInfo`) |
|
|
| **motor + MOSFET temperatures, motor faults** | `/hardware/motor_debug` (`MotorDebug`) — 25 motors |
|
|
| locomotion → `status: moving` | `/hardware/joint_state` (`JointState`) velocities |
|
|
| motion mode + allowed transitions | `/motion/motion_state` (`MotionState`) |
|
|
| OS, kernel, arch, board, L4T, storage, MAC | read directly from the host |
|
|
| project logs | docker json-log of the `sanad-t8` container |
|
|
|
|
### The field mapping actually in use
|
|
|
|
```ini
|
|
ENG_SOURCE=ros2
|
|
ENG_TOPIC_POWER=/hardware/power_info ENG_TYPE_POWER=interface_protocol/msg/PowerInfo
|
|
ENG_TOPIC_MOTORS=/hardware/motor_debug ENG_TYPE_MOTORS=interface_protocol/msg/MotorDebug
|
|
ENG_TOPIC_JOINTS=/hardware/joint_state ENG_TYPE_JOINTS=interface_protocol/msg/JointState
|
|
ENG_TOPIC_MOTION=/motion/motion_state ENG_TYPE_MOTION=interface_protocol/msg/MotionState
|
|
|
|
ENG_FIELD_SOC=percentage ENG_FIELD_VOLTAGE=voltage
|
|
ENG_FIELD_CURRENT=current ENG_FIELD_TEMPS=motor_temperature
|
|
ENG_FIELD_MOS_TEMPS=mos_temperature
|
|
ENG_FIELD_MOTION=current_motion_task
|
|
ENG_SOC_SCALE=percent ENG_CURRENT_SIGN=-1 # ← measured, see §4
|
|
ENG_ROS_QOS=best_effort ROS_DOMAIN_ID=69
|
|
|
|
ENG_JOINT_MIN_PERIOD=0.05 # 500 Hz → 20 Hz (protects the robot's CPU)
|
|
ENG_MOTOR_MIN_PERIOD=0.2 # 100 Hz → 5 Hz
|
|
|
|
MAC_INTERFACE=wlP1p1s0
|
|
REMOTE_PORTS=8014,8001,9002,9003,8000,8080
|
|
```
|
|
|
|
Nothing above is hard-coded in the agent — it is all `.env`. Retargeting is an
|
|
`.env` edit + restart, never a code change.
|
|
|
|
---
|
|
|
|
## 4. What it sends
|
|
|
|
One JSON object every 2 s to `POST /api/v1/fleet/ingest/telemetry` with
|
|
`Authorization: Bearer <token>`, plus `/{sn}/alert`, `/{sn}/logs`, `/{sn}/map`
|
|
and `/{sn}/remote` — **to every enabled server**. Verified field-by-field
|
|
against the robot's own readings:
|
|
|
|
| robot ground truth | agent sends |
|
|
|---|---|
|
|
| `power_info percentage: 21.0` | `battery: 21` |
|
|
| `power_info voltage: 53.3` | `voltage_v: 53.3` |
|
|
| `power_info current: +2.47` (discharging) | `current_a: -2.47`, `charging: false` |
|
|
| `power_info current_limit: 90.0` | `current_limit_a: 90.0` |
|
|
| `motor_debug motor_temperature[25]` | `motor_temp: {max:55.1, avg:29.5, min:24.0, count:25}` |
|
|
| `motor_debug mos_temperature[25]` | `motor_temp.mos_max: 45.6, mos_avg: 29.9` |
|
|
| `motion_state current_motion_task` | `control.mode: "pd_sitdown"` |
|
|
| `motion_state available_transition_motions` | `control.switchable_modes: [...]` |
|
|
| `df /` → 250.6 GB, 201 GB free | `storage: {total_gb:250.64, free_gb:201.3, used_percent:14.6}` |
|
|
|
|
Also registers the Sanad Dashboard (`http://10.210.136.150:8014`) and
|
|
`ssh ubuntu@10.210.136.150` for the fleet UI, and ships the Sanad app's logs as
|
|
`project_logs: "sanad-t8-logs"`.
|
|
|
|
### The battery sign — the one trap on this robot
|
|
|
|
`PowerInfo.current` stays **positive while the pack drains**. Measured over
|
|
15 minutes, idle and off-charger: `27% → 22%`, `54.74 V → 53.3 V`, current
|
|
`+1.7 … +2.5 A` throughout. So positive means *discharging* here — the opposite
|
|
of the ROS `BatteryState` convention.
|
|
|
|
Hence `ENG_CURRENT_SIGN=-1`. With the default `+1` the robot would report
|
|
`charging: true` and `status: "charging"` forever while its battery went flat —
|
|
a failure that looks exactly like healthy telemetry.
|
|
|
|
### Fields that are `null` — and why
|
|
|
|
- **`position`** — the PM01 publishes **no odometry topic at all**
|
|
(no `/odom`, no `/tf`, no `amcl_pose`); its motion stack is a whole-body
|
|
controller, not a navigation stack, and the Sanad nav bringup is not running.
|
|
`null` means "not available", never `{x: 0, y: 0}` — a fabricated origin would
|
|
park the robot in the corner of the fleet map and look like real data.
|
|
One `.env` line turns it on the day localisation runs (`ENG_POSITION_SOURCE`).
|
|
- **`battery_detail.temp_c` / `soh` / `cycles`** — `PowerInfo` carries no pack
|
|
temperature, state-of-health or cycle count. Left unmapped rather than pointed
|
|
at a plausible-looking wrong field.
|
|
- **`map`** — `no_map`; there are no saved maps on this robot yet.
|
|
|
|
---
|
|
|
|
## 5. Operating it
|
|
|
|
```bash
|
|
# live log
|
|
ssh ubuntu@10.210.136.150 'sudo journalctl -u sanad-api-eng -f'
|
|
|
|
# what it is sending right now
|
|
ssh ubuntu@10.210.136.150 \
|
|
"sudo journalctl -u sanad-api-eng -n 20 --no-pager | grep -oE 'telemetry ok:.*'"
|
|
|
|
# service control
|
|
ssh ubuntu@10.210.136.150 'sudo systemctl restart sanad-api-eng'
|
|
ssh ubuntu@10.210.136.150 'sudo systemctl status sanad-api-eng'
|
|
|
|
# change a setting (then restart)
|
|
ssh ubuntu@10.210.136.150 'sudo nano /opt/sanad_api_eng/.env && sudo systemctl restart sanad-api-eng'
|
|
```
|
|
|
|
### Updating the agent code
|
|
|
|
```bash
|
|
scp agent/sanad_api_eng.py ubuntu@10.210.136.150:/tmp/
|
|
ssh ubuntu@10.210.136.150 \
|
|
'sudo install -m755 /tmp/sanad_api_eng.py /opt/sanad_api_eng/ && sudo systemctl restart sanad-api-eng'
|
|
```
|
|
|
|
`.env` is never overwritten by this, so the tokens stay put.
|
|
|
|
### Changing the serial
|
|
|
|
`SN` is the primary key for every `/{sn}/` route. Changing it creates a **new**
|
|
robot entry on the server rather than renaming the existing one:
|
|
|
|
```bash
|
|
ssh ubuntu@10.210.136.150 \
|
|
"sudo sed -i 's#^SN=.*#SN=<real-serial>#' /opt/sanad_api_eng/.env && \
|
|
sudo systemctl restart sanad-api-eng"
|
|
```
|
|
|
|
### Re-discovering the robot's interface
|
|
|
|
```bash
|
|
bash tools/probe_eng.sh 10.210.136.150 ubuntu
|
|
```
|
|
|
|
Read-only — no install, no writes, publishes to no topic, safe on a live robot.
|
|
Every line it prints is labelled with the `.env` variable it feeds.
|
|
|
|
### Verifying without touching the live feed
|
|
|
|
```bash
|
|
sudo -E python3 /opt/sanad_api_eng/sanad_api_eng.py --dry-run # builds payloads, never POSTs
|
|
sudo -E python3 /opt/sanad_api_eng/sanad_api_eng.py --once # one real post, then exits
|
|
```
|
|
|
|
(both need the ROS overlay: `set +u; . /app/applications/install/bringup/ros_env.sh`)
|
|
|
|
|