ExoStack / Reference

API & reference

The ESA API, the ExoCore executables, the messages they exchange, troubleshooting and the known limitations of v1.0.

ESA API

Public interface of the SoftExoWalk class in ESA.h.

FunctionArgumentsPurpose
setupCommunication("wifi", ssid, pass, agent_ip, port)
("usb")
Selects the micro-ROS transport: Wi-Fi (UDP) or USB (serial).
initializeSystem() → boolConfigures every deviceList[] row: analog pins, digital input modes, I²C drivers; sizes the message.
setupNode(name = "microROS_Node")Names the board's ROS 2 node.
setupPublisher(topic = "", pub_rate = 0)Sensing hub: topic and publication rate in Hz (also the filter design rate).
setupActuation(topic, ActuatorType, out, x1 = -1, x2 = -1)Actuation: one command topic per actuator, e.g. ACT_FESTO_VEAB_L26; up to 5 per board.
bootSystem()Creates the publisher, timer, subscriptions and executor; initializes the actuators. Always last.
readSensorsData()Reads every device once into the message (call in loop()).
publish(reconnect = false)Spins the executor; with true, rebuilds the link after an agent restart.
actuate(topic = "")Spins the executor and drives all actuators, or only the one on topic.
emergencyShutdown()Stops the board's ROS entities.
get_actCount / get_actCMD(id) / get_pubRate / get_debugFlag()Number of actuators, latest command of actuator id, publication rate, debug flag.

ExoCore executables

The six executables of the exo_stack package. Launch files start one process per YAML entry.

ExecutableConfigured byStarted byRole
syncn/aros2 run exo_stack syncsynchronizes the hubs into /synced_sensors
gait_event_detectorgait_events.yamlgait_event_detector_launch.pyone detector per entry
mplannermplanner.yamlmplanner_launch.pyone motion planner per entry
actuation_profileprofile.yamlprofile_launch.pyone profile node per entry (lookup table or HIL candidate)
costprofile.yaml (HIL)cost_launch.pyper-gait-cycle cost
hilprofile.yaml (HIL)hil_launch.pyHIL optimizer (start it last)

Troubleshooting

SymptomLikely causeRemedy
Status LED on the board blinks rapidlya ROS entity could not be createdstart the agent; check IP, port and topic names
Board restarts every few secondswatchdog: loop() blockedremove delays and blocking calls from the sketch and drivers
USB link drops or never connectsthe sketch writes to Serialremove all Serial calls when using the USB transport
Analog channel stuck at 0 or full scale with Wi-Fipin on ADC2move it to GPIO 32–39
New I²C device not foundduplicate bus–address pair, registry full, or header included elsewheresee the rules in §2.5
Duplicate-definition errors when compilingtwo copies of the ESA librarykeep one folder in ~/Arduino/libraries
/synced_sensors silenta configured hub is not publishing, or a topic name differsevery hub in sensors.yaml must be running
Warning Expected N intensities, got Mchannel list and board disagreealign sensors.yaml with the device list (§2.2-C)
FileNotFoundError … exo_stack_resourcesfolder not in the home directorymove it to ~/exo_stack_resources
module has no attribute …class name differs from file namerename one of them (§2.7)
Planner warns of a profile-type mismatchsuffixes of builder, planner and profile_type differuse List everywhere or Function everywhere
HIL stops after candidate #1optimizer started before profile and cost stages; or cost_topic differsstart HIL last (§2.3)
Profile number mismatch … Ignoringa late cost from the previous candidatenormal once after each change
PASE cannot start the agentagent workspace not sourcedadd it to ~/.bashrc (§2.1-E)

ROS 2 and micro-ROS interfaces

Table S3 lists the topic of every ESA hub and ExoCore node and what each element of its message means. Each node and every message layout is detailed, with examples, under ROS 2 interfaces.

Table S3. ROS 2 and micro-ROS interfaces. Every topic that connects the ESA hubs and the ExoCore nodes, grouped by the node that publishes it. Names in angle brackets are set in the file or call shown under them; all types are standard ROS 2 messages.
TopicTypeFrom → toContent of the message
ESA sensing hub: micro-ROS node, one per board (×N)
/<hub topic>setupPublisher(); sensors.yamlsensor_msgs/ LaserScanhub → synchronizerintensities[i]: channel i, in device-list order: AI in volts (0–3.3), DI 0 or 1, I²C in the driver's units; range_min: number of devices; range_max: number of channels; header.stamp: hub time since the last /sync_trigger. Other fields unused. Sent at the setupPublisher() rate.
/sync_triggerfixed namestd_msgs/ Int32synchronizer → every hubdata = 0, sent at 5 Hz in trigger mode; on reception a hub stores millis() as its time offset.
ESA actuation hub: micro-ROS node (×M)
/<command topic>setupActuation(); output_topic in mplanner.yamlstd_msgs/ Float32planner → actuation hubdata: set-point in the driver's unit (kPa for ACT_FESTO_VEAB_L26, degrees for ACT_SG90_SERVO). Kept as the actuator's latest command; 0 until the first message.
Synchronizer: sync (×1)
/synced_sensorsoutput_topic in sensors.yamlstd_msgs/ Float32MultiArraysynchronizer → detectors, cost evaluatorsdata: the channels of every hub, concatenated in sensors.yaml order (hub by hub, channel by channel), for each set of hub messages whose stamps agree within slop_time. No time stamp.
Gait-event detector: gait_event_detector (×E)
/<event topic>output_topic in gait_events.yamlstd_msgs/ Int32detector → plannerdata = 1 at each detected event; nothing is sent between events.
External estimators and sensors: your own nodes, optional
/<speed_topic>profile.yamlstd_msgs/ Float32estimator → profile nodewalking speed, in the unit of the lookup-table keys.
/<intent_topic>profile.yamlstd_msgs/ Float32estimator → profile nodelocomotion intent; 0 (standing) raises the profile's e-stop flag.
/<gait_phase_topic>mplanner.yamlstd_msgs/ Float32estimator → plannergait phase, used as the time argument t by gait_phase_based planners.
/<external_sensors_topic>cost block of profile.yamlstd_msgs/ Float32MultiArraysensor → cost evaluatorany vector, e.g. metabolic rate or EMG, handed to the cost plug-in with the synchronized channels.
/<additional_gait_marker_topic>cost block of profile.yamlstd_msgs/ Int32your node → cost evaluatormarker value appended to the next cost sample, e.g. a flexion–extension split.
Profile node: actuation_profile (×P)
/<profile_topic>profile_topic of the planner entrystd_msgs/ Float32MultiArrayprofile node → planner[k, type, start, body…, end, stop]. k: candidate number, 0 in Deployment; type: 1 = list, 2 = coefficients; start, end: profile window, in units of t; body: (t, value) pairs or coefficients; stop: 1 = emergency stop. Sent every 1/rate in Deployment, once per candidate in HIL.
Motion planner: mplanner (×P)
/<output_topic>mplanner.yamlstd_msgs/ Float32planner → actuation hub or any ROS 2 driverset-point at rate: next_point(t) inside the profile window; default_output outside it or on e-stop; the manual command in manual mode.
/<planner>/profile_statusplanner namestd_msgs/ Float32MultiArrayplanner → cost evaluator[k, cycle] at each gait event: the candidate being played and its gait-cycle count (0 in Deployment).
/<planner>/manual_modeplanner namestd_msgs/ Int32PASE or you → planner1 = manual override on, 0 = back to profiles.
/<planner>/manual_cmdplanner namestd_msgs/ Float32PASE or you → plannerset-point sent while manual mode is on.
Cost evaluator: cost (×C, one per profile with a cost)
/<cost_topic>profile.yamlstd_msgs/ Float32MultiArraycost evaluator → HIL optimizer[k, cost]: mean of the per-cycle costs over gait_cycles_to_consider cycles, after skipping gait_cycles_to_skip; k = 0 in Deployment.
HIL optimizer: hil (×H, one per profile in HIL mode)
/<hil_output_topic>profile.yamlstd_msgs/ Float32MultiArrayoptimizer → profile node[k, p1 … pn]: candidate number and one parameter per optimized dimension; a new candidate follows each cost it accepts. On stop, every round and the best candidate are written to ~/exo_stack_resources/hil_results/.

All ExoCore topics use best-effort reliability, and the hubs publish and subscribe best-effort over micro-ROS.

k ties the HIL messages together: the cost evaluator counts only cycles of the candidate in profile_status, and the optimizer accepts only a cost carrying the k it proposed.

ExoStack uses standard message types only, so no custom message package has to be built for micro-ROS.

Known limitations of v1.0

KNOWN LIMITATIONS OF v1.0
  • Device-list values dev_misc_data and dev_misc_data2 reach the I²C drivers that read them (the BNO085 channel count); the NAU7802 calibration and the AS5600 direction pin come from the drivers' registration lines.
  • Function profiles (Builder1_Function with PolynomialFunction or CubicPolynomialFunction) and PCHIPInterpolatedList do not run in v1.0; use a _List builder with LinearInterpolatedList.
  • PASE writes the approach gait_phase, which the planner does not recognise, and adds init_points and gp_params to Bayesian profiles, which OPT_BAYESIAN does not accept: configure phase-based planners and Bayesian profiles in the YAML files.
  • The I²C registry holds 8 entries, all used by the shipped drivers (§2.5).
Corrections are planned for the next release.