Complete the host-driver setup and leave discovery running. Automatic startup, stable board names and optional identity rules are described in automatic startup. The examples use the namespace /axiom. Substitute your configured namespace and the actual discovered sensor identities. boards.yaml describes board connections and optional aliases; sensors do not need to be listed individually.
First copy your full board namespace from /axiom_discovery/boards. In a prepared ROS terminal, replace the placeholder below; the variable is local to this terminal:
ros2 topic list -t --no-daemon
ros2 service list -t
BOARD_NS='PASTE_DISCOVERED_BOARD_NAMESPACE_HERE'
ros2 topic echo "$BOARD_NS/devices" std_msgs/msg/String \
--qos-reliability reliable --qos-durability transient_local \
--qos-depth 1 --full-length --once
Measurements use names such as /axiom/lsm6ds_b1_76a/data_raw, /axiom/vl53l4cd_b1_129/range and /axiom/amg8833_b1_169/image. The pattern is /<board>/<lowercase-model>_b<bus>_<hex-address>/<output>. Bus and address always distinguish sensor instances, so another sensor of the same model does not rename an existing one. Unusual model/bus characters are escaped. Copy exact paths from the topic list or public_topics in the device inventory.
Inventory topic is the relative sensor root; unresolved metadata rows have topic: null and should be skipped. public_topics.x.data_raw identifies a published IMU topic. Other payload groups have separate group_<id> paths. The map gains outputs after their first sample and becomes empty when publishers retire. Output-only devices can expose commands without measurement topics. Their command_topics array advertises direct angle/colour inputs, while commands lists the remaining acknowledged services. These are separate from the measurement public_topics map.
The parallel output_info[group][suffix] map records each canonical output's actual message_type and unit. A null unit means unspecified/unitless or no single unit for a compound/generic message. See an inventory example.
| Output suffix | Type and units |
|---|---|
| data_raw | sensor_msgs/msg/Imu; acceleration m/s² including gravity, angular velocity rad/s; no fused orientation |
| range | sensor_msgs/msg/Range; scalar distance in metres; invalid readings are NaN |
| image | sensor_msgs/msg/Image; thermal °C or supported multizone distances in metres, 32FC1; invalid pixels are NaN |
| temperature | sensor_msgs/msg/Temperature; °C |
| humidity | sensor_msgs/msg/RelativeHumidity; fraction from 0 to 1 |
| Light attribute, e.g. ambient | sensor_msgs/msg/Illuminance; recognised scalar light quantities in lux |
| Other scalar attribute, e.g. UVI | std_msgs/msg/Float64, Bool or String; published units in output_info |
| grid | axiom_interfaces/msg/ThermalGrid; dimensions and pixel temperatures |
| sample | axiom_interfaces/msg/DeviceSample; decoded values, validity and timing |
| data | std_msgs/msg/String; JSON decoded measurement view |
A descriptor with both thermal and distance grids uses thermal/image and range/image to distinguish them. Supported profiles supply generic sample and data; standard typed outputs additionally need a supported mapping and known units. A Device Library profile does not automatically add a custom binary decoder.
Old bus/device topic and command paths are removed. Update topic selections, recording lists, exact QoS override keys and alias source selectors when pulling this version. Frame IDs and firmware bus/address fields remain unchanged. See the full contracts and migration keys.
An LTR-390 exposes ambient as Illuminance in lux and UVI as Float64 in index units, in addition to generic sample and data. Other numeric scalars use decoded SI values when known, otherwise decoded engineering values and their original units. Bool and text fields use Bool and String. Invalid numeric readings publish NaN; invalid bool/text readings are omitted. Arrays retain their existing image/grid or generic handling, and automatically mapped compound fields are not duplicated as automatic scalar topics.
Float64, Bool and String have no timestamp or unit field. Use output_info for units and sample for timing, validity and decoded values. Attribute suffixes retain case and escape unsafe names; a reserved data attribute uses attribute_data. Copy exact paths rather than constructing them from a model label.
The standard-message guide covers fields, compatible units, aliases and the optional devInfoJson.ros mapping schema. Optional mappings are included in current main; update, rebuild and restart the host driver to use them; they are not required for the defaults above. A mapping declaration does not install a device profile or change firmware acquisition.
With Axiom powered, connected and acquiring, plug in a supported sensor. Its publishers and descriptor services appear once its metadata is resolved. Unplug only that sensor, leaving Axiom connected. Its interfaces retire after firmware and driver detection. Reconnecting the sensor restores supported interfaces and measurements without another acquisition call.
A subscriber can keep a topic name registered after its publisher disappears. Replace the placeholder with a discovered topic and check publisher count:
SENSOR_TOPIC='PASTE_DISCOVERED_SENSOR_TOPIC_HERE'
ros2 topic info "$SENSOR_TOPIC" --verbose
With normal discovery startup, an unexpected disconnection is recovered automatically. A board reappearing over USB is verified again; if both transports are allowed, a verified Wi-Fi endpoint can provide fallback under the same namespace. The board must remain powered for Wi-Fi fallback. An intentional Disconnect is retained until an explicit Connect or process restart.
With the explicit manual settings shown below, reconnect the board and request acquisition again:
ros2 service call /axiom/connect axiom_interfaces/srv/Connect '{device_uri: ""}'
ros2 service call /axiom/publish_data_subscription \
axiom_interfaces/srv/PublishedDataSubscription '{rate_hz: 20.0}'
auto_reconnect: false leaves unexpected loss recovery manual. An intentional disconnect disables automatic reconnect. The subscription rate controls delivery, rather than the sensor's sampling rate.
Merge aliases into an existing board entry, keeping its connection settings:
topic_aliases:
imu/data_raw: {type: LSM6DS, source: data_raw}
range: {type: VL53L4CD, source: range}
thermal/image: {type: AMG8833, source: image}
light: {type: LTR-390, source: ambient}
uv: {type: LTR-390, source: UVI}
This is a fragment, not a replacement board file. Under board axiom, the matching aliases become /axiom/imu/data_raw, /axiom/range, /axiom/thermal/image, /axiom/light and /axiom/uv. They add convenience paths carrying the same messages. Header-bearing messages retain source timestamps and frame IDs; Float64, Bool and String remain headerless. Alias source values use the canonical output suffixes in the table above. If an alias uses a generated readable path, the alias takes precedence: the automatic output at that exact path is suppressed, even while the alias is unbound, and is not listed in public_topics. An alias binds only when exactly one matching device is online, resolved and fresh. For ambiguity, add string bus and address selectors from the inventory, or use the canonical sensor topic. Stop acquisition/disconnect, then relaunch the driver after changing its read-only configuration.
Servo angles now use subscribed std_msgs/msg/Float64 topics in radians. LED colours use std_msgs/msg/ColorRGBA with normalized RGB values and one topic per pixel for indexed LEDs. Copy the relative inputs, types and units from devices.command_topics and prefix them with the board namespace. Before a controller starts, a command topic can have one driver subscription and no publisher.
The former angle/colour services are removed. Enable/disable, LED brightness/off and supported configuration remain acknowledged services in commands. Measurement aliases do not redirect command inputs. The supported four-tier StackLight pattern is also an acknowledged service. Use the RViz lighting panel for whole colour, brightness, Off and stack controls. Follow Servo and LED command topics for direct examples and the joint-slider demo for an interactive controller and optional right-leg preview.
Remaining acknowledged descriptor services appear under /axiom/MODEL_bBUS_ADDRESS/commands/COMMAND. The driver also uses that path family for typed command-topic inputs, so use inventory commands versus command_topics to distinguish them. Discover the exact service and inspect its descriptor's argument order, units and allowed values:
ros2 service list -t
BOARD_NS='PASTE_DISCOVERED_BOARD_NAMESPACE_HERE'
ros2 topic echo "$BOARD_NS/device_metadata" std_msgs/msg/String \
--qos-reliability reliable --qos-durability transient_local \
--qos-depth 1 --full-length --once
For a sensor exposing set_sample_rate, replace the service path below and choose a rate it actually advertises; 52 Hz is an illustrative LSM6DS value:
RATE_SERVICE='PASTE_DISCOVERED_SAMPLE_RATE_SERVICE_HERE'
ros2 service call "$RATE_SERVICE" \
axiom_interfaces/srv/DeviceCommand '{values: [52.0]}'
Numeric values follow descriptor order and units. No-argument commands use {values: []}. Success means firmware acknowledgement, not physical completion of actuator movement. The four-tier vt: stacklight contract is supported as described in the lighting guide. Other unsupported custom value formats are not callable.
The current host driver uses only the new generated sensor paths. There is no option to publish the old bus/device family. Before starting an updated driver, migrate your local board/parameter files, subscriptions, plot selections and bag-recording topic lists. Git pulls do not replace ignored local configuration such as config/boards.yaml.
| Earlier setting or interface | Current setting or interface |
|---|---|
| Generated IMU bus_1/device_76a/imu/data_raw | lsm6ds_b1_76a/data_raw |
| Thermal grid bus_1/device_169/thermal/grid | amg8833_b1_169/grid |
| Thermal image bus_1/device_169/thermal/image | amg8833_b1_169/image |
| Multizone distance image bus_1/device_129/range/image | vl53l5cx_b1_129/image |
| IMU alias source imu/data_raw | source: data_raw |
| Thermal-grid alias source thermal/grid | source: grid |
| Ordinary thermal/range-image alias sources thermal/image or range/image | source: image; only dual-image descriptors retain separate image suffixes |
| Per-sensor bus_1/device_76a/commands/set_sample_rate | lsm6ds_b1_76a/commands/set_sample_rate |
| Inventory topic | New relative sensor root, or null before metadata resolves |
| Servo-angle DeviceCommand service | Advertised command_topics Float64 topic in radians |
| Indexed LED-colour DeviceCommand service | Advertised ColorRGBA topics ending in pixel_0, pixel_1, etc. |
The paths above are relative examples beneath your board namespace. Copy your actual sensor paths from the live inventory; addresses and bus numbers are not fixed requirements. Aliases such as imu/data_raw or thermal/image remain valid user-chosen destination names with the new source values. They do not enable old generated topics.
QoS override keys match the exact current relative path, with no fallback to an old key. For example, in a board entry:
sensor_qos.overrides:
vl53l4cd_b1_129/range:
reliability: reliable
depth: 20
Replace the example key with the sensor path from your inventory, without the board namespace. An alias uses its own exact QoS key. Old recordings still contain their recorded topic names; select or remap those paths when replaying them.
Firmware bus/address fields, message units, timestamps and coordinate-frame IDs are unchanged by this naming migration. A frame such as axiom_bus_1_device_76a is a coordinate-frame identifier, not a legacy topic path. Preserve the correct mounting/TF configuration for your sensor.
Disable active actuators and wait for acknowledgement before stopping their controller. Stop affected ROS processes, then update a clean checkout on main, rebuild and source the workspace:
git pull --ff-only origin main
source scripts/setup_env.sh
rosdep install --from-paths src --ignore-src -r -y
colcon build --base-paths src --symlink-install
source scripts/setup_env.sh
Build the optional demo packages too if you use them, as described in their walkthroughs. Refresh each reused tmux pane with source scripts/setup_env.sh and restart the driver, bridges and visualisers with the same ROS domain and compatible middleware settings. Check the topic list and inventory again before using saved layouts.
The normal axiom.launch.py already handles multiple physical boards independently. No endpoint file is required. For selected boards and readable names, use a complete discovery board file such as:
discovery:
usb: true
wifi: true
include_unconfigured: false
axioms:
axiom/front:
id: '001122334455'
axiom/rear:
id: 'aabbccddeeff'
Replace both example IDs with the firmware IDs from /axiom_discovery/boards. Save the file and start it:
ros2 launch axiom_driver axiom.launch.py boards_file:=/absolute/path/boards.yaml
Connection and acquisition start automatically. The board keys give /axiom/front and /axiom/rear namespaces and distinct default frame prefixes. Use one entry per physical board; verified USB and Wi-Fi endpoints for the same ID share one driver, preferring USB. Setting include_unconfigured: false keeps other discovered boards out of acquisition.
For independent demos on shared Wi-Fi, combine this board selection with a different ROS domain per demo. Namespaces alone do not isolate ROS traffic; domains do not select physical boards or provide ownership across computers. See shared-network setup.
Use this alternative when you want to start a driver per configured endpoint and control connection through services. It does not run discovery or switch transports automatically. This board file is for multi_axiom.launch.py; it contains endpoint parameters, with no discovery settings or id rules. Stop the discovery launch first.
Use separate namespace keys and endpoints for different physical Axioms. Replace the USB path and illustrative Wi-Fi address below:
axioms:
axiom/front:
transport: serial
serial.port: /dev/serial/by-id/REPLACE_WITH_FRONT_AXIOM
auto_connect: false
auto_reconnect: false
autosub: false
axiom/rear:
transport: ws
device_uri: ws://192.0.2.11/ws
auto_connect: false
auto_reconnect: false
autosub: false
Save this in the board file selected by machine.env, with one axioms mapping. Stop/disconnect any existing single-board driver before starting the multi-board launch. The tmux helper can hold the launch in Terminal 1, service calls in Terminal 2, and one board's monitor in each of Terminals 3 and 4.
In Terminal 1:
ros2 launch axiom_driver multi_axiom.launch.py \
boards_file:="$AXIOM_ROS_BOARDS_FILE"
In Terminal 2:
ros2 service call /axiom/front/connect axiom_interfaces/srv/Connect '{device_uri: ""}'
ros2 service call /axiom/front/publish_data_subscription \
axiom_interfaces/srv/PublishedDataSubscription '{rate_hz: 20.0}'
ros2 service call /axiom/rear/connect axiom_interfaces/srv/Connect '{device_uri: ""}'
ros2 service call /axiom/rear/publish_data_subscription \
axiom_interfaces/srv/PublishedDataSubscription '{rate_hz: 20.0}'
Each driver has independent connection, acquisition, metadata, queues, aliases and frame prefix. Duplicate namespaces, frame prefixes and configured endpoints are rejected within this launch. Current host-driver sessions also take per-user endpoint and firmware-MAC locks on the same computer. Discovery deduplicates USB and Wi-Fi endpoints by firmware MAC; advisory locks do not coordinate other computers or unrelated applications.
Automatic connection, recovery and acquisition are the defaults. The explicit manual example above disables all three flags. For automatic multi-board startup without endpoint configuration, use axiom.launch.py with optional identity rules as described in automatic startup.
Stop echoes/viewers, then clean up in Terminal 2 while the launch is still running:
ros2 service call /axiom/front/publish_data_subscription \
axiom_interfaces/srv/PublishedDataSubscription '{rate_hz: 0.0}'
ros2 service call /axiom/front/disconnect axiom_interfaces/srv/Disconnect '{}'
ros2 service call /axiom/rear/publish_data_subscription \
axiom_interfaces/srv/PublishedDataSubscription '{rate_hz: 0.0}'
ros2 service call /axiom/rear/disconnect axiom_interfaces/srv/Disconnect '{}'
Finally stop the launch with Ctrl+C. Other consumers should use ROS subscriptions, rather than additional firmware acquisition connections to the same board.
Published walkthrough: Sensors and multiple boards.