engineai_fleet/agent/.env.example
2026-08-27 16:24:12 +04:00

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