2026-09-01 19:33:28 +00:00
2026-08-27 16:24:12 +04:00
2026-08-27 16:24:12 +04:00
2026-08-27 16:24:12 +04:00
2026-08-27 16:24:12 +04:00
2026-08-27 16:24:12 +04:00
2026-09-01 19:33:28 +00:00

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 and one line away from enabled — see §5.

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

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}/remoteto 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 / cyclesPowerInfo carries no pack temperature, state-of-health or cycle count. Left unmapped rather than pointed at a plausible-looking wrong field.
  • mapno_map; there are no saved maps on this robot yet.

5. Operating it

# 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

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:

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 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

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)

Description
No description provided
Readme
Languages
Python 92.6%
Shell 7.4%