Robot WebUI service and storage API
Runtime and addressing
The current service is FastAPI plus a React frontend. main.py mounts api_routes.py at /api, the command socket at /ws, and the audio proxy at /ws/audio. Route references use the effective mounted paths. The current adapter factory constructs Ros2RobotAdapter unconditionally: setting an old fake-adapter variable does not select a simulated backend in this checkout.
The development Compose UI profile publishes WebUI at host port 8000 by default, Unity animation at 9099, browser-audio WebSocket at 3033, and the browser-audio helper page at 3034. Individual development launch scripts use different ports. Inspect the deployed Compose/environment before using examples.
The API files inspected do not implement a login or per-route authorization layer. A reverse proxy may enforce access separately. Keep control endpoints on the intended robot network or authenticated ingress. Documentation hosting does not expose these endpoints.
Robot configuration and assets
GET /api/robot/config returns robot name, profile, storage root, configuration directory and availability. Named configuration reads try .yaml, .yml, then .json; a missing name returns path: null and empty data rather than HTTP 404. POST /api/robot/config/{name} writes YAML, accepting either the raw object or { "data": ... }.
Storage helpers resolve paths from environment: HR_STORAGE_DIR, CONFIGS_DIR, PERFORMANCES_DIR, VIRTUAL_DIR, FACES_DATA_DIR, TTS_TMP_OUTPUT_DIR, and ASR_TMP_OUTPUT_DIR. The default storage root is /hr/local/storage; TTS and ASR temporary paths default beneath HR_DIR. Configuration volumes must be writable for editor saves; the development Compose's read-only configuration mount can prevent writes.
GET /api/virtual-project selects a preferred available project and returns loader/asset metadata. Manifest-activated Sophia assets require SOPHIA_UNITY_ENABLED and an existing SOPHIA_UNITY_DIR. A missing build returns descriptive metadata with an error rather than making a working Unity player appear. Modern loaders advertise data, framework, WASM and streaming-assets URLs.
Node settings
Read /api/settings/nodes, then use the exact returned node name (which can contain slashes) in the description/configuration paths. Description provides the editor schema; configuration provides current values. Updating values, saving them, resetting them and deleting a saved override are different operations.
curl --fail-with-body "$ROBOT_UI_URL/api/settings/nodes"
curl --fail-with-body "$ROBOT_UI_URL/api/teleops/catalog"
curl --fail-with-body "$ROBOT_UI_URL/api/teleops/state"
The ROS adapter delegates settings to the current configuration backend. Its ROS service calls wait up to two seconds for service availability and up to ten seconds for a result. Failures can be returned as { "ok": false, "error": ... }; HTTP success is not sufficient evidence of an applied runtime setting.
Behavior tree paths are stored beneath the storage root's behavior_trees directory. GET returns path/data, POST writes data or the raw object, and DELETE removes the file if present. Tree path resolution uses safe_join to reject escape from the configured root. Saving a file is not the same as activating that tree in a running behavior engine.
Motors and torque
GET /api/motors and /api/motors/states return normalized configuration/latest state plus robot and source: ros2. The state is sourced from the configuration/backend parameter snapshot, not a synchronous physical read of every motor.
| Operation | Input and semantics |
|---|---|
POST /api/motors/control |
enabled boolean; calls actuator control for actuators: ["all"], with control code 1 for enabled and 2 for disabled |
POST /api/motors/command |
name, numeric value, optional numeric min/max; clamps to supplied bounds then publishes TargetPosture |
POST /api/motors/torque |
enabled, optional names; named motors call torque service, absent names publish global torque command |
POST /api/motors/config |
motors list; rows are grouped by configured assembly; writes assembly settings and generated motor configuration, updates parameters and requests Feetech reload |
Motion values are defined by the selected motor/profile mapping; do not assume raw servo steps and normalized actuator values are interchangeable. Optional client-supplied bounds are not a replacement for calibrated mechanical limits. Publishing returns acknowledgement of publication, not proof of completed motion.
Missing motor name or invalid value yields application ok: false. Saving rows with missing/unknown assemblies produces warnings; if no rows match, the result is unsuccessful. The save can affect multiple files and is not documented as a transactional operation. Back up calibrated configuration before changing it.
Performance storage and concurrency
Use GET /api/performances?tree=true for the tree or omit it for flat files. Read a performance before saving; retain its revision.
{
"baseRevision": "REVISION_FROM_READ",
"data": {"name":"Example","nodes":[]}
}
POSTing a changed baseRevision without force returns HTTP 409 with detail.error, detail.id and detail.currentRevision. Omission of baseRevision bypasses this conflict check; clients should include it. force: true overrides it intentionally.
IDs are normalized and reject empty, dot or parent-traversal segments. .yaml is the default extension. Multi-timeline payloads write a directory and remove timeline files omitted from the new payload. Deleting a directory performance removes it recursively. Moves save the target then delete the source; clients must not assume a cross-file atomic rename.
The active storage helper selects common storage from IDs equal to shared or starting with shared/; otherwise it selects the robot root. Although some older protocol examples mention a source query, current route handlers do not declare or pass that query. Use IDs returned by the tree instead of relying on an ignored source selector.
Properties endpoints merge stored properties. Duration-estimation and preview endpoints call runtime services and may synthesize/play content. See the command protocol for playback state and revisions.
Media and video
/api/video/fov.mjpg and /api/video/fov.jpg expose field-of-view imagery from the adapter. Check the current handler's availability behavior when the camera is absent. TTS and ASR file lists are separate endpoints; their media are served from /external/tts/* and /asr/*. The /tts and /asr directory entry paths redirect into the UI browsers. Do not mirror robot media into the public documentation.
Unsupported navigation
The current /api/navigation/locations, /go-to, /go-to-position and /stop handlers explicitly return ok: false with an unsupported message. Their existence is not evidence of a mobile navigation capability.
Evidence: webui/api/webui_api/{main.py,api_routes.py,runtime_paths.py,performance_storage.py,adapters/ros2.py,adapters/factory.py} beneath head-ros-nodes/ros. The route reference covers individual declarations and explicit handler errors.
Field-of-view HTTP contract
GET /api/video/fov.jpg accepts optional integer after and timeout seconds (default 0.5, clamped to 0–5). With no image it returns a placeholder JPEG and X-Frame-Unavailable: true, not an HTTP failure. When waiting after a known sequence, no newer frame within the timeout returns 204 with no image body. A real frame includes X-Frame-Sequence, X-Frame-Timestamp, no-store headers and image content type. Inspect status/header before decoding bytes.
GET /api/video/fov.mjpg waits up to one second for its first frame and returns 503 if none is available. Streaming uses multipart/x-mixed-replace; boundary=frame; each part includes content type/length. Optional frames is bounded to 300; absent/zero means an unbounded stream, while negative input normalizes to the same unbounded path. WEBUI_FOV_MAX_FPS defaults to 15. The response disables proxy buffering and caching. A client disconnect ends its stream; no robot perception process is stopped.
TTS/ASR directory list objects contain path, parent, and items; items carry name/path, kind, URL, size (null for directories), and modification timestamp. Invalid or nonexistent directories return 404. This file-list JSON is not audio bytes.