236 lines
12 KiB
Plaintext
236 lines
12 KiB
Plaintext
# sanad_api_eng — EngineAI PM01. Copy to .env and fill in.
|
|
# ONE agent = telemetry + map + logs + alerts + remote, posted to EVERY server.
|
|
#
|
|
# Every topic name and field path used to read robot state is an env var in this
|
|
# file. Nothing is hard-coded in the agent. Discover the real ones with:
|
|
#
|
|
# bash tools/probe_eng.sh 10.210.136.150 ubuntu
|
|
#
|
|
# then set them below and restart — no code change, no rebuild.
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# FLEET SERVERS — the same payload is posted to EVERY enabled server
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# Each server has its OWN token. eco and eco-dev maintain INDEPENDENT token
|
|
# stores: a token minted on one is rejected with 401 by the other. Reusing a
|
|
# single token across both silently 401s forever on whichever server did not
|
|
# issue it — which is why the token is per-slot, not global.
|
|
|
|
# ── server 1 (primary) ───────────────────────────────────────────────────────
|
|
SERVER_URL=https://eco-dev.yslootahrobotics.com
|
|
DEVICE_TOKEN=REPLACE_WITH_ECO_DEV_TOKEN
|
|
SERVER_NAME=eco-dev
|
|
SERVER_ENABLE=1
|
|
|
|
# ── server 2 ─────────────────────────────────────────────────────────────────
|
|
# Configured and ready. Paste the token eco issues for this robot and flip
|
|
# SERVER_2_ENABLE to 1 — that is the whole change, then restart the service.
|
|
SERVER_2_URL=https://eco.yslootahrobotics.com
|
|
SERVER_2_TOKEN=
|
|
SERVER_2_NAME=eco
|
|
SERVER_2_ENABLE=0
|
|
# Set to 1 ONLY if eco is ever configured to accept the primary DEVICE_TOKEN.
|
|
SERVER_2_SHARE_TOKEN=0
|
|
|
|
# ── servers 3..5 (unused; same three keys each) ──────────────────────────────
|
|
#SERVER_3_URL=
|
|
#SERVER_3_TOKEN=
|
|
#SERVER_3_ENABLE=0
|
|
|
|
# ── identity ─────────────────────────────────────────────────────────────────
|
|
# The robot's serial — keys the robot on the server ({sn} routes). Changing it
|
|
# later creates a SECOND robot entry on the server rather than renaming this one.
|
|
SN=REPLACE_WITH_PM01_SERIAL
|
|
# Friendly display name shown on the dashboard.
|
|
ROBOT_NAME=pm01_150
|
|
ROBOT_BRAND=engineai
|
|
ROBOT_TYPE=humanoid
|
|
ROBOT_MODEL=pm01
|
|
# Maps subdir (<MAPS_DIR>/<ROBOT>/…) + X-Robot-Name header.
|
|
ROBOT=sanad
|
|
# Optional: app data dir whose size is reported inside storage. Empty = omit.
|
|
STORAGE_DATA_PATH=/home/ubuntu/sanad_t8/data
|
|
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# PM01 STATE SOURCE — the one robot-specific section
|
|
# ═════════════════════════════════════════════════════════════════════════════
|
|
# auto = ros2 if rclpy imports, else http if ENG_STATE_URL is set, else none
|
|
# (heartbeat: battery null, status offline).
|
|
# ros2 = subscribe ENG_TOPIC_* — the deployed setting.
|
|
# http = poll ENG_STATE_URL for one JSON object; map fields with ENG_FIELD_*.
|
|
# none = never read; always heartbeat.
|
|
ENG_SOURCE=ros2
|
|
|
|
# ── backend: ros2 ────────────────────────────────────────────────────────────
|
|
# Verified live on the robot with `ros2 topic list -t` (see docs/PM01_INTERFACE.md).
|
|
# The types are the vendor's own interface_protocol messages; they import only
|
|
# after the ROS overlay is sourced, which the systemd unit does via ros_env.sh.
|
|
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
|
|
# The PM01 publishes NO odometry topic — leave empty (position reports null).
|
|
# Fill this in the day a nav stack starts publishing one.
|
|
ENG_TOPIC_ODOM=
|
|
ENG_TYPE_ODOM=nav_msgs/msg/Odometry
|
|
|
|
# Must match the robot's domain or ROS 2 discovery silently sees nothing.
|
|
# The PM01 stack runs on 69 with CycloneDDS pinned to eth1 — the unit sources
|
|
# /app/applications/install/bringup/ros_env.sh, which sets all three.
|
|
ROS_DOMAIN_ID=69
|
|
|
|
# Subscription reliability. Keep best_effort: a RELIABLE subscriber receives
|
|
# NOTHING from a BEST_EFFORT publisher (the subscription is created, no error is
|
|
# raised, and the field silently stays null forever), while a BEST_EFFORT
|
|
# subscriber reads from either kind.
|
|
ENG_ROS_QOS=best_effort
|
|
|
|
# ── decimation (protects the robot's own CPU) ────────────────────────────────
|
|
# /hardware/joint_state publishes at 500 Hz and /hardware/motor_debug at 100 Hz.
|
|
# Telemetry resamples every 2 s, so running a Python callback on every message
|
|
# would burn the robot's compute for nothing. Seconds between PROCESSED messages:
|
|
ENG_JOINT_MIN_PERIOD=0.05
|
|
ENG_MOTOR_MIN_PERIOD=0.2
|
|
|
|
# ── field mapping (applies to EVERY backend) ─────────────────────────────────
|
|
# Dotted paths into the message/JSON. They work over both ROS message objects
|
|
# and plain dicts, support list indices ("cell_temp[0]") and wildcards
|
|
# ("joints[*].temp"), and yield null for any missing link — a wrong path
|
|
# degrades a field, it never crashes the agent. Empty = field unavailable.
|
|
ENG_FIELD_SOC=percentage
|
|
ENG_FIELD_VOLTAGE=voltage
|
|
ENG_FIELD_CURRENT=current
|
|
ENG_FIELD_CURRENT_LIMIT=current_limit
|
|
ENG_FIELD_POWER_ERR=error_code
|
|
ENG_FIELD_POWER_ENABLE=enable
|
|
# PowerInfo carries NO pack temperature / SOH / cycle count — leaving these
|
|
# empty reports null. Filling them with a wrong path would fabricate data.
|
|
ENG_FIELD_TEMP=
|
|
ENG_FIELD_SOH=
|
|
ENG_FIELD_CYCLES=
|
|
# MotorDebug — the PM01 DOES publish per-motor temperatures (25 of them), plus
|
|
# the driver MOSFET temperatures, plus per-motor fault/offline flags.
|
|
ENG_FIELD_TEMPS=motor_temperature
|
|
ENG_FIELD_MOS_TEMPS=mos_temperature
|
|
ENG_FIELD_MOTOR_ERR=error_code
|
|
ENG_FIELD_MOTOR_OFFLINE=offline
|
|
# JointState velocities → the "moving" status.
|
|
ENG_FIELD_VEL=velocity
|
|
# MotionState → control.mode + control.switchable_modes (read-only).
|
|
ENG_FIELD_MOTION=current_motion_task
|
|
ENG_FIELD_TRANSITIONS=available_transition_motions
|
|
ENG_FIELD_X=pose.pose.position.x
|
|
ENG_FIELD_Y=pose.pose.position.y
|
|
ENG_FIELD_FW=
|
|
|
|
# ── unit conventions (verified on the real robot) ────────────────────────────
|
|
# PowerInfo.percentage is already 0..100 → percent (never scale).
|
|
ENG_SOC_SCALE=percent
|
|
# +1 = positive current means CHARGING (the ROS BatteryState convention).
|
|
# MEASURED on this PM01: current stays POSITIVE (~2 A) while the pack drains
|
|
# (27%→26%, 54.74→54.58 V) — i.e. positive = DISCHARGE. Hence -1. Setting this
|
|
# to +1 would report a discharging robot as "charging" forever.
|
|
ENG_CURRENT_SIGN=-1
|
|
# PowerInfo already publishes volts and amps.
|
|
ENG_VOLTAGE_SCALE=1
|
|
ENG_CURRENT_SCALE=1
|
|
|
|
# ── position ─────────────────────────────────────────────────────────────────
|
|
# none | ros2 | rosbridge | http.
|
|
# The PM01's motion stack is a whole-body controller, not a navigation stack:
|
|
# there is NO odometry topic and no localisation running, so position is null.
|
|
# That is "not available", never a fabricated origin.
|
|
# ros2 — set ENG_TOPIC_ODOM too, the day a nav bringup publishes one
|
|
# http — read the Sanad nav API (works as soon as its bringup is alive)
|
|
# rosbridge — read /odom over the rosbridge websocket
|
|
ENG_POSITION_SOURCE=none
|
|
ENG_POSITION_URL=http://127.0.0.1:8014/api/nav/status
|
|
ENG_POSITION_FIELD_X=pose.x
|
|
ENG_POSITION_FIELD_Y=pose.y
|
|
ENG_POSITION_INTERVAL=2
|
|
ROSBRIDGE_URL=ws://127.0.0.1:9090
|
|
|
|
# ── identity / networking ────────────────────────────────────────────────────
|
|
# Which NIC's MAC is reported as the robot identity. wlP1p1s0 is the wifi NIC
|
|
# that carries the robot's LAN address.
|
|
MAC_INTERFACE=wlP1p1s0
|
|
|
|
# ── fault thresholds ─────────────────────────────────────────────────────────
|
|
LOW_SOC=50
|
|
MOTOR_TEMP_MAX=85
|
|
MOS_TEMP_MAX=100
|
|
# max |joint velocity| above which status becomes "moving"
|
|
MOVING_VEL=0.15
|
|
|
|
# ── cadence / transport ──────────────────────────────────────────────────────
|
|
POLL_INTERVAL=2
|
|
VERIFY_TLS=1
|
|
HTTP_TIMEOUT=30
|
|
TZ_OFFSET_HOURS=4
|
|
|
|
# ── map sync (uploaded ONCE per content PER SERVER; status in telemetry "map") ──
|
|
# pgm+yaml sets are rendered to PNG with pure stdlib; RTAB-Map .db is sent
|
|
# as-is (skipped above the server's upload cap).
|
|
MAPS_DIR=/home/ubuntu/sanad_t8/data/maps
|
|
EXTRA_MAP_DIRS=
|
|
DATA_DIR=/home/ubuntu/sanad_t8/data
|
|
STATE_DIR=/var/lib/sanad_api_eng
|
|
MAP_SELECT=all
|
|
MAP_UPLOAD_MODE=multipart
|
|
MAP_POLL_INTERVAL=30
|
|
MAP_MAX_UPLOAD_MB=7
|
|
WEB_NAV3_URL=
|
|
|
|
# ── logs + alerts ────────────────────────────────────────────────────────────
|
|
LOGS_INTERVAL=60
|
|
# auto = find a RUNNING sanad* docker container and tail its json-log. On this
|
|
# robot that is "sanad-t8". Reading it needs root, which the system service has.
|
|
PROJECT_LOG_CONTAINER=auto
|
|
PROJECT_LOG_PATH=
|
|
PROJECT_LOG_LABEL=
|
|
PROJECT_LOG_BACKFILL=100
|
|
ALERT_SCAN_INTERVAL=10
|
|
ALERT_LOG_COOLDOWN=300
|
|
|
|
# ── remote (register a dashboard URL + ssh for the fleet UI) ─────────────────
|
|
# 8014 is the Sanad Dashboard on this robot. 9002/9003 are EngineAI's own
|
|
# dashboard and Foxglove — kept as fallbacks if Sanad is ever stopped.
|
|
REMOTE_ENABLE=1
|
|
REMOTE_PORTS=8014,8001,9002,9003,8000,8080
|
|
REMOTE_KIND=web
|
|
REMOTE_INTERVAL=60
|
|
REMOTE_URL=
|
|
REMOTE_HOST=
|
|
SSH_REGISTER=1
|
|
# The robot's LOGIN user. Left empty, ssh registration is skipped rather than
|
|
# registering a wrong user that silently fails for whoever tries it.
|
|
SSH_USER=ubuntu
|
|
SSH_PORT=22
|
|
|
|
# ── control panel (READ-ONLY; remote mode-SWITCH is off by design) ───────────
|
|
# control.mode and control.switchable_modes come from /motion/motion_state — the
|
|
# robot's own words, no id table to guess. This URL only adds arm/teleop detail.
|
|
CONTROL_STATUS_URL=http://127.0.0.1:8014/api/controller/status
|
|
CONTROL_ENABLE=0
|
|
|
|
# ── CPU: raw subscriptions for the high-rate topics ─────────────────────────
|
|
# 1 (default) subscribes /hardware/joint_state and /hardware/motor_debug with
|
|
# raw=True, so the rate gate above runs BEFORE deserialization and a dropped
|
|
# message is never turned into a Python object. Measured 27.2% -> 23.8% of one
|
|
# core. Set 0 to use ordinary subscriptions (identical data, more CPU).
|
|
#
|
|
# Measured cost of each subscription, as % of ONE core (12 available):
|
|
# all four topics 26.5% | without joint_state 6.5% | without both 2.2%
|
|
# i.e. the 500 Hz joint_state is ~20% on its own. If you ever need that back,
|
|
# set ENG_TOPIC_JOINTS= (empty): the agent drops to ~6% and "status" stops
|
|
# reporting "moving" (control.mode still shows the real motion task).
|
|
ENG_RAW_SUBSCRIBE=1
|
|
|
|
# How often a repeated POST failure to the same server is reported in full.
|
|
# Suppressed occurrences are counted and shown on the next line that prints.
|
|
ERROR_LOG_COOLDOWN=60
|