ExoStack / ROS 2 interfaces

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

Typesensor_msgs/msg/LaserScan
Topic/<hub topic>, e.g. /hub_right
From → toESA sensing hub → synchronizer

One message carries every channel of a board, so the hubs need no custom message type. ESA sets four fields; the others keep their defaults.

ElementTypeUnitMeaning
header.stampbuiltin_interfaces/Times, nshub time since the last /sync_trigger: millis() minus the offset stored on the trigger
header.frame_idstringn/anot set (empty)
range_minfloat32countnumber of devices in deviceList[]
range_maxfloat32countnumber of channels, i.e. the length of intensities
intensities[i]float32[]per devicechannel 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, rangesfloat32, float32[]n/anot used (0, empty)
/hub_right: worked example of the manual (values illustrative)
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 order

Sync trigger

Typestd_msgs/msg/Int32
Topic/sync_trigger (fixed name)
From → tosynchronizer → every hub

A time reference rather than data: the value is not read.

ElementTypeUnitMeaning
dataint32n/aalways 0. On reception a hub stores millis() as its offset, so its later stamps count from this trigger
sent at 5 Hz while the synchronizer is in trigger mode
data: 0

Synchronized channels

Typestd_msgs/msg/Float32MultiArray
Topic/synced_sensors (output_topic of sensors.yaml)
From → tosynchronizer → detectors, cost evaluators

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.

ElementTypeUnitMeaning
layoutstd_msgs/MultiArrayLayoutn/anot used (empty)
data[i]float32[]as published by the hubchannel 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
021thigh_roll
122thigh_pitch
223thigh_yaw
324thigh_omega_x
425thigh_omega_y
526thigh_omega_z
627thigh_acc_x
728thigh_acc_y
829thigh_acc_z
930shank_roll
1031shank_pitch
1132shank_yaw
1233shank_omega_x
1334shank_omega_y
1435shank_omega_z
1536shank_acc_x
1637shank_acc_y
1738shank_acc_z
1839force_flexion
1940force_extension
2041fsr
with the shipped sensors.yaml
data: [3.2, -1.4, 0.6, ..., 41.8, 0.0, 2.9]    # 42 values

Gait event

Typestd_msgs/msg/Int32
Topic/<event topic>, e.g. /heel_strike_right
From → todetector → planner

A pulse: one message per detected event, nothing in between.

ElementTypeUnitMeaning
dataint32n/a1 = event detected. The planner reacts only to 1
one message per heel strike
data: 1

Estimator inputs

Typestd_msgs/msg/Float32
Topic/<speed_topic>, /<intent_topic>, /<gait_phase_topic>
From → toyour estimator → profile node or planner

Three scalar inputs share this layout; their meaning comes from the topic they are sent on.

ElementTypeUnitMeaning
data on speed_topicfloat32unit 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_topicfloat32labellocomotion 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_topicfloat32unit of the profile's tgait phase, used directly as the time argument t of a gait_phase_based planner
/walking_speed
data: 3.5

Assistance profile

Typestd_msgs/msg/Float32MultiArray
Topic/<profile_topic>, e.g. /profile/right_knee_flexion
From → toprofile node → planner

The profile the planner plays after the next gait event. Its first and last elements frame what the builder produced.

ElementTypeUnitMeaning
data[0]float32n/ak, the candidate number: 0 in Deployment, 1, 2, … in HIL
data[1]float32n/atype: 1 = list of waypoints, 2 = polynomial coefficients
data[2]float32t unitsstart of the profile window
data[3 … n−3]float32t units, set-point unitsbody: (t, value) pairs for type 1, coefficients for type 2
data[n−2]float32t unitsend of the profile window
data[n−1]float32n/astop: 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.

Buildertypeinputbody
Builder1_List1[start, peak_t, peak_v, end](start, 20) (peak_t, peak_v) (end, 20)
Builder2_List1[start, t1, v1, t2, v2, end](start, 20) (t1, v1) (t2, v2) (end, 20)
Builder_Nwaypoint_List1[start, t1, v1, …, tN, vN, end](start, 20) (t1, v1) … (tN, vN) (end, 20)
Builder1_Function2[start, c0, c1, …, cn, end]c0 … cn

In v1.0, type-2 profiles do not run (see Known limitations).

Deployment, Builder2_List, table row [50, 200, 500, 600, 500, 800]
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

Typestd_msgs/msg/Float32
Topic/<output_topic> = the actuator's command topic, e.g. /right/hip_ppr_cmd
From → toplanner → actuation hub or any ROS 2 driver

The only message an actuator receives. The actuation hub keeps the latest value per actuator and applies it in every actuate().

ElementTypeUnitMeaning
datafloat32driver unitset-point: kPa for ACT_FESTO_VEAB_L26, degrees for ACT_SG90_SERVO; 0 until the first message
pressure set-point in kPa
data: 412.5

Profile status

Typestd_msgs/msg/Float32MultiArray
Topic/<planner>/profile_status
From → toplanner → cost evaluator

Sent at every gait event, it tells the cost evaluator which candidate is being played and how many cycles of it have passed.

ElementTypeUnitMeaning
data[0]float32n/ak of the active profile (0 in Deployment)
data[1]float32cyclescycle: gait cycles since the candidate became active; counts only in HIL, 0 in Deployment
/right_knee_flexion/profile_status
data: [3.0, 4.0]      # candidate 3, fourth gait cycle

Manual override

Typestd_msgs/msg/Int32
Topic/<planner>/manual_mode (Int32) and /<planner>/manual_cmd (Float32)
From → toPASE or you → planner

Two topics per planner let you drive the actuator by hand, e.g. to test it. The command shares the Float32 set-point layout.

ElementTypeUnitMeaning
manual_mode: dataint32n/a1 = manual override on, 0 = back to the profiles
manual_cmd: datafloat32driver unitset-point sent on output_topic while manual mode is on
terminal
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

Typestd_msgs/msg/Float32MultiArray
Topic/<cost_topic>, e.g. /cost1
From → tocost evaluator → HIL optimizer

One message per evaluated candidate.

ElementTypeUnitMeaning
data[0]float32n/ak of the candidate the cost belongs to (0 in Deployment)
data[1]float32cost-plug-in unitcost: mean of the per-cycle costs over gait_cycles_to_consider cycles, after skipping gait_cycles_to_skip
/cost1
data: [3.0, 27.4]

HIL candidate

Typestd_msgs/msg/Float32MultiArray
Topic/<hil_output_topic>, e.g. /hil_output1
From → toHIL optimizer → profile node

The next parameter set to try. Its parameters are the builder's inputs, in the order of mean and bounds in hil_hyperparams.

ElementTypeUnitMeaning
data[0]float32n/ak, the candidate number, increasing from 1
data[1 … n]float32builder-input unitsp₁ … pₙ, one value per optimized dimension
profile1 of the shipped profile.yaml: six dimensions
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

Typestd_msgs/msg/Float32MultiArray
Topic/<external_sensors_topic>
From → toyour sensor node → cost evaluator

Signals that are not on a hub, e.g. a metabolic system or an EMG amplifier, reach the cost plug-in through this topic.

ElementTypeUnitMeaning
data[i]float32[]your unitsany vector of fixed length; the cost plug-in receives it as one sample row
illustrative
data: [312.0, 0.84]      # e.g. metabolic power (W), respiratory quotient

Additional gait marker

Typestd_msgs/msg/Int32
Topic/<additional_gait_marker_topic>
From → toyour node → cost evaluator

A marker for events the cost plug-in needs inside a cycle, e.g. the flexion–extension split.

ElementTypeUnitMeaning
dataint32n/amarker value, appended to the next cost sample and then reset to 0
illustrative
data: 1