Message layouts
What every element of every ExoStack message means, with its standard type and an example.
ExoStack uses only standard message types, so no interface package has to be built, not even for micro-ROS. The meaning therefore lies in the layout: which element of a message carries what. The same Float32MultiArray carries a profile on one topic and a cost on another. Each layout below names its standard type, linked to the official definition, and lists every element.
- Hub message:
LaserScan - Sync trigger:
Int32 - Synchronized channels:
Float32MultiArray - Gait event:
Int32 - Estimator inputs:
Float32 - Assistance profile:
Float32MultiArray - Actuator set-point:
Float32 - Profile status:
Float32MultiArray - Manual override:
Int32 - Cost:
Float32MultiArray - HIL candidate:
Float32MultiArray - External sensors:
Float32MultiArray - Additional gait marker:
Int32
Hub message
/<hub topic>, e.g. /hub_rightOne message carries every channel of a board, so the hubs need no custom message type. ESA sets four fields; the others keep their defaults.
| Element | Type | Unit | Meaning |
|---|---|---|---|
header.stamp | builtin_interfaces/Time | s, ns | hub time since the last /sync_trigger: millis() minus the offset stored on the trigger |
header.frame_id | string | n/a | not set (empty) |
range_min | float32 | count | number of devices in deviceList[] |
range_max | float32 | count | number of channels, i.e. the length of intensities |
intensities[i] | float32[] | per device | channel i in device-list order: AI in volts (0–3.3; a battery channel ×1.34, at most 4.2), DI 0 or 1, I²C in the driver's units (e.g. N for the NAU7802, degrees for BNO085 angles) |
angle_*, time_increment, scan_time, ranges | float32, float32[] | n/a | not used (0, empty) |
header:
stamp: {sec: 12, nanosec: 340000000} # 12.34 s since the last /sync_trigger
frame_id: ''
range_min: 5.0 # devices: 2 IMUs, 2 load cells, 1 FSR
range_max: 21.0 # channels
intensities: [4.1, -12.6, 87.0, ...] # 21 values, in device-list orderSync trigger
A time reference rather than data: the value is not read.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data | int32 | n/a | always 0. On reception a hub stores millis() as its offset, so its later stamps count from this trigger |
data: 0
Synchronized channels
/synced_sensors (output_topic of sensors.yaml)The channels of every hub listed in sensors.yaml, concatenated hub by hub in the file's order, each hub's channels in its sensors order. Algorithms never index this array: they ask for [hub, channel] pairs and SensorUtil finds the index.
| Element | Type | Unit | Meaning |
|---|---|---|---|
layout | std_msgs/MultiArrayLayout | n/a | not used (empty) |
data[i] | float32[] | as published by the hub | channel i of the concatenation; no time stamp is included |
Index map with the shipped sensors.yaml
Two hubs, hubl on /hub_left and hubr on /hub_right, publish the same 21 channels, so /synced_sensors carries 42 values.
| index (hubl) | index (hubr) | channel |
|---|---|---|
| 0 | 21 | thigh_roll |
| 1 | 22 | thigh_pitch |
| 2 | 23 | thigh_yaw |
| 3 | 24 | thigh_omega_x |
| 4 | 25 | thigh_omega_y |
| 5 | 26 | thigh_omega_z |
| 6 | 27 | thigh_acc_x |
| 7 | 28 | thigh_acc_y |
| 8 | 29 | thigh_acc_z |
| 9 | 30 | shank_roll |
| 10 | 31 | shank_pitch |
| 11 | 32 | shank_yaw |
| 12 | 33 | shank_omega_x |
| 13 | 34 | shank_omega_y |
| 14 | 35 | shank_omega_z |
| 15 | 36 | shank_acc_x |
| 16 | 37 | shank_acc_y |
| 17 | 38 | shank_acc_z |
| 18 | 39 | force_flexion |
| 19 | 40 | force_extension |
| 20 | 41 | fsr |
data: [3.2, -1.4, 0.6, ..., 41.8, 0.0, 2.9] # 42 values
Gait event
A pulse: one message per detected event, nothing in between.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data | int32 | n/a | 1 = event detected. The planner reacts only to 1 |
data: 1
Estimator inputs
/<speed_topic>, /<intent_topic>, /<gait_phase_topic>Three scalar inputs share this layout; their meaning comes from the topic they are sent on.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data on speed_topic | float32 | unit of the table keys (e.g. km/h) | walking speed; the lookup plug-in picks or interpolates the table row for it |
data on intent_topic | float32 | label | locomotion intent: the key of an intent table in Lookup_Speed_Intent_Segmentwise; 0 = standing, which raises the profile's stop flag |
data on gait_phase_topic | float32 | unit of the profile's t | gait phase, used directly as the time argument t of a gait_phase_based planner |
data: 3.5
Assistance profile
/<profile_topic>, e.g. /profile/right_knee_flexionThe profile the planner plays after the next gait event. Its first and last elements frame what the builder produced.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data[0] | float32 | n/a | k, the candidate number: 0 in Deployment, 1, 2, … in HIL |
data[1] | float32 | n/a | type: 1 = list of waypoints, 2 = polynomial coefficients |
data[2] | float32 | t units | start of the profile window |
data[3 … n−3] | float32 | t units, set-point units | body: (t, value) pairs for type 1, coefficients for type 2 |
data[n−2] | float32 | t units | end of the profile window |
data[n−1] | float32 | n/a | stop: 1 = emergency stop (intent 0), else 0 |
What the builders put in the body
List builders add the default value 20 at the start and end of the window. The input is a lookup-table row in Deployment or the candidate's parameters in HIL.
| Builder | type | input | body |
|---|---|---|---|
Builder1_List | 1 | [start, peak_t, peak_v, end] | (start, 20) (peak_t, peak_v) (end, 20) |
Builder2_List | 1 | [start, t1, v1, t2, v2, end] | (start, 20) (t1, v1) (t2, v2) (end, 20) |
Builder_Nwaypoint_List | 1 | [start, t1, v1, …, tN, vN, end] | (start, 20) (t1, v1) … (tN, vN) (end, 20) |
Builder1_Function | 2 | [start, c0, c1, …, cn, end] | c0 … cn |
In v1.0, type-2 profiles do not run (see Known limitations).
data: [0, 1, 50, 50, 20, 200, 500, 600, 500, 800, 20, 800, 0] # k = 0 (Deployment), type = 1 (list), start = 50 # waypoints (t, value): (50, 20) (200, 500) (600, 500) (800, 20) # end = 800, stop = 0
Actuator set-point
/<output_topic> = the actuator's command topic, e.g. /right/hip_ppr_cmdThe only message an actuator receives. The actuation hub keeps the latest value per actuator and applies it in every actuate().
| Element | Type | Unit | Meaning |
|---|---|---|---|
data | float32 | driver unit | set-point: kPa for ACT_FESTO_VEAB_L26, degrees for ACT_SG90_SERVO; 0 until the first message |
data: 412.5
Profile status
Sent at every gait event, it tells the cost evaluator which candidate is being played and how many cycles of it have passed.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data[0] | float32 | n/a | k of the active profile (0 in Deployment) |
data[1] | float32 | cycles | cycle: gait cycles since the candidate became active; counts only in HIL, 0 in Deployment |
data: [3.0, 4.0] # candidate 3, fourth gait cycle
Manual override
/<planner>/manual_mode (Int32) and /<planner>/manual_cmd (Float32)Two topics per planner let you drive the actuator by hand, e.g. to test it. The command shares the Float32 set-point layout.
| Element | Type | Unit | Meaning |
|---|---|---|---|
manual_mode: data | int32 | n/a | 1 = manual override on, 0 = back to the profiles |
manual_cmd: data | float32 | driver unit | set-point sent on output_topic while manual mode is on |
ros2 topic pub --once /right_hip_extension/manual_mode std_msgs/msg/Int32 "{data: 1}"
ros2 topic pub --once /right_hip_extension/manual_cmd std_msgs/msg/Float32 "{data: 150.0}" Cost
/<cost_topic>, e.g. /cost1One message per evaluated candidate.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data[0] | float32 | n/a | k of the candidate the cost belongs to (0 in Deployment) |
data[1] | float32 | cost-plug-in unit | cost: mean of the per-cycle costs over gait_cycles_to_consider cycles, after skipping gait_cycles_to_skip |
data: [3.0, 27.4]
HIL candidate
/<hil_output_topic>, e.g. /hil_output1The next parameter set to try. Its parameters are the builder's inputs, in the order of mean and bounds in hil_hyperparams.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data[0] | float32 | n/a | k, the candidate number, increasing from 1 |
data[1 … n] | float32 | builder-input units | p₁ … pₙ, one value per optimized dimension |
data: [3, 50, 200, 200, 400, 200, 700] # -> profile node (Builder2_List): [3, 1, 50, 50, 20, 200, 200, 400, 200, 700, 20, 700, 0]
External sensors
/<external_sensors_topic>Signals that are not on a hub, e.g. a metabolic system or an EMG amplifier, reach the cost plug-in through this topic.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data[i] | float32[] | your units | any vector of fixed length; the cost plug-in receives it as one sample row |
data: [312.0, 0.84] # e.g. metabolic power (W), respiratory quotient
Additional gait marker
A marker for events the cost plug-in needs inside a cycle, e.g. the flexion–extension split.
| Element | Type | Unit | Meaning |
|---|---|---|---|
data | int32 | n/a | marker value, appended to the next cost sample and then reset to 0 |
data: 1