---json
{"name": "Sound sensor and spectrum"}
---
====== Sound sensor and spectrum in Foxglove ======
Import [[https://github.com/robotical/axiom-ros2/blob/main/examples/foxglove/one-axiom-sound.json|one-axiom-sound.json]] to display all 15 scalar
attributes from a Robotical Sound Sensor and its latest nine frequency-band
levels. The spectrum user script is embedded in the layout: importing the JSON
also installs the script in that layout. No custom Foxglove extension is needed.
The readable source is [[https://github.com/robotical/axiom-ros2/blob/main/examples/foxglove/sound-spectrum.ts|sound-spectrum.ts]].
For coloured bars with frequency labels instead of the built-in spectrum Plot,
install the optional [[:axiom:ros2:sound_sensor#custom_axiom_spectrum_panel|Axiom Spectrum panel]]. It reads
the same existing ''%%sample%%'' topic directly, without the user script or a separate
ROS ''%%bands%%'' topic. You can add it alongside these scalar plots or import its
spectrum-only layout. Host setup and acquisition are the same for both options.
===== Prepare the host and select the sensor =====
Complete the repository's [[:axiom:ros2:getting_started|host setup]] and the
[[:axiom:ros2:foxglove#install_the_bridge_and_connect_a_viewer|Foxglove bridge installation]].
Use the current host driver from ''%%main%%'', rebuilt and sourced after updating. The sound sensor's firmware profile must
advertise type ''%%RoboticalSoundSensor%%'', its scalar attributes, and the nine-band
''%%bands%%'' attribute. The layout uses existing decoded data; it requires no firmware
change for that profile.
Use board key ''%%axiom1%%'' in ''%%config/boards.yaml%%'', retaining your actual firmware
''%%id%%'' or USB/Wi-Fi connection settings. Keep automatic connection, reconnection
and acquisition enabled as described in [[:axiom:ros2:automatic_startup|automatic startup]].
If a board file exists, merge these aliases into its existing entry and preserve its identity/connection settings. For a new file, the following complete single-board example selects ''axiom1'' by firmware ID. Replace the illustrative ID with yours from ''/axiom_discovery/boards'', save it as ''config/boards.yaml'', and do not create a second entry for the same board:
discovery:
include_unconfigured: false
axioms:
axiom1:
id: '001122334455'
topic_aliases:
sound/sample: {type: RoboticalSoundSensor, source: sample}
sound/splSlow: {type: RoboticalSoundSensor, source: splSlow}
sound/splFast: {type: RoboticalSoundSensor, source: splFast}
sound/rms: {type: RoboticalSoundSensor, source: rms}
sound/peak: {type: RoboticalSoundSensor, source: peak}
sound/crest: {type: RoboticalSoundSensor, source: crest}
sound/zcr: {type: RoboticalSoundSensor, source: zcr}
sound/centroid: {type: RoboticalSoundSensor, source: centroid}
sound/spread: {type: RoboticalSoundSensor, source: spread}
sound/flatness: {type: RoboticalSoundSensor, source: flatness}
sound/rolloff85: {type: RoboticalSoundSensor, source: rolloff85}
sound/flux: {type: RoboticalSoundSensor, source: flux}
sound/f0: {type: RoboticalSoundSensor, source: f0}
sound/seq: {type: RoboticalSoundSensor, source: seq}
sound/status: {type: RoboticalSoundSensor, source: status}
sound/clipCount: {type: RoboticalSoundSensor, source: clipCount}
When editing an existing file, preserve its other boards and settings. Each alias binds only when there
is exactly one matching online, fresh sensor with resolved metadata on that
board. If several sound sensors are connected, add the same ''%%bus: '...'%%'' and
''%%address: '...'%%'' selectors to **every** sound alias. Copy their exact strings from
''%%/axiom1/devices%%''; firmware addresses are hexadecimal and may encode the slot as
well as the I2C address. The aliases and script must all select the same sensor.
Canonical model/location topics remain available alongside these aliases.
No ROS driver code changes are part of this layout. If your installed driver
predates scalar outputs, build and source the current checkout before starting
it. In a prepared terminal at the repository root:
# Load the ROS environment and built workspace in this terminal.
source scripts/setup_env.sh
# Build the updated packages before restarting ROS processes.
colcon build --base-paths src --symlink-install
# Load the ROS environment and built workspace in this terminal.
source scripts/setup_env.sh
A layout or TypeScript-source update alone needs no ROS rebuild. Board alias
configuration changes require restarting the affected driver.
===== Start the demo =====
The commands below run on the ROS host. If your board file is elsewhere, substitute its actual path in the launch command. Source ''%%scripts/setup_env.sh%%'' in each
terminal; use the same ROS domain and middleware settings throughout.
Terminal 1, start the driver:
# Load the ROS environment and built workspace in this terminal.
source scripts/setup_env.sh
# Start discovery using the selected board configuration.
ros2 launch axiom_driver axiom.launch.py boards_file:=config/boards.yaml
Terminal 2, inspect the discovered device and its aliases:
# Load the ROS environment and built workspace in this terminal.
source scripts/setup_env.sh
# Inspect messages using the shown type and QoS.
ros2 topic echo /axiom1/devices std_msgs/msg/String --once \
--qos-reliability reliable --qos-durability transient_local --qos-depth 1 --full-length
# Check the topic type and its publisher count.
ros2 topic info /axiom1/sound/splSlow
# Inspect messages using the shown type and QoS.
ros2 topic echo /axiom1/sound/splSlow --once --qos-reliability best_effort
# Inspect messages using the shown type and QoS.
ros2 topic echo /axiom1/sound/sample --once --qos-reliability best_effort --full-length
Expect ''%%splSlow%%'' to be ''%%std_msgs/msg/Float64%%'' with one publisher and a numeric
''%%data%%'' field. The other 14 scalars also use Float64. ''%%sample%%'' is
''%%axiom_interfaces/msg/DeviceSample%%''; its ''%%values_json%%'' contains a ''%%bands%%'' entry
with ''%%unit: "dB"%%'', nine decoded numbers in ''%%value%%'', and validity information.
The sample header provides its timestamp and sensor frame. Inventory
''%%output_info%%'' records the scalar message types and units.
Automatic startup connects and enables acquisition. An echo or Foxglove
subscription alone does not start it. To use a 5 Hz board publication rate for
this demo, optionally run in Terminal 2:
# Send this discrete request and check the returned acknowledgement.
ros2 service call /axiom1/publish_data_subscription \
axiom_interfaces/srv/PublishedDataSubscription '{rate_hz: 5.0}'
This changes delivery for the board, not the sound sensor's audio sampling or
spectral-analysis settings.
Terminal 3, start the bridge:
# Load the ROS environment and built workspace in this terminal.
source scripts/setup_env.sh
# Expose this ROS session to the Foxglove viewer.
ros2 launch foxglove_bridge foxglove_bridge_launch.xml
Open Foxglove and choose **Open connection → Foxglove WebSocket**. Connect to
''%%ws://localhost:8765%%'' when the viewer runs on the ROS host, or
''%%ws://ROS_HOST_IP:8765%%'' from another computer. Use the ROS host's address, not the
Axiom board's address. Connection/account prerequisites are covered in the
[[:axiom:ros2:foxglove#install_the_bridge_and_connect_a_viewer|bridge guide]].
===== Import and use the layout =====
- Copy ''%%examples/foxglove/one-axiom-sound.json%%'' to the viewer computer if the checkout is on another host. The script is already inside that JSON.
- Open **Layouts → Import from file...**, select the JSON, then open the newly imported layout and save it as **Axiom: sound sensor**.
- In the **User Scripts** sidebar, check that **Axiom sound spectrum** is present and has no errors once the sample topic is available. The script subscribes to ''%%/axiom1/sound/sample%%'' and produces ''%%/foxglove_script/axiom1/sound/spectrum%%'' inside Foxglove.
- Speak or clap near the sensor and check that level plots and the spectrum update. Use different sounds to compare the distribution across bands; the display represents nine measured bands, not a full-resolution FFT.
The left column contains the guide, A-weighted levels, signal levels and sensor
status/counters. The right column contains the spectrum, spectral frequencies,
ratios and flux. Time plots use a sliding 30-second window and receipt time,
because Float64 messages have no header. Their aligned axes do not establish
hardware clock synchronisation.
^ Plot ^ Attributes ^ Units ^
| Sound pressure levels | ''%%splSlow%%'', ''%%splFast%%'' | dBA |
| Signal levels | ''%%rms%%'', ''%%peak%%'', ''%%crest%%'' | dB |
| Spectral frequencies | ''%%centroid%%'', ''%%spread%%'', ''%%rolloff85%%'', ''%%f0%%'' | Hz |
| Spectral ratios | ''%%zcr%%'', ''%%flatness%%'' | Dimensionless, 0–1 |
| Spectral flux | ''%%flux%%'' | Firmware value, 0–255 |
| Sequence, status and clipping | ''%%seq%%'', ''%%status%%'', ''%%clipCount%%'' | Firmware values, 0–255 |
| Latest sound spectrum | ''%%bands%%'' from the sample JSON | dB against band index |
The spectrum uses Foxglove's **Array index** mode and the expression
''%%/foxglove_script/axiom1/sound/spectrum.bands_db[:]%%''. Each new sample replaces
the displayed spectrum. The frequency key is also shown in the guide panel:
^ Index ^ 0 ^ 1 ^ 2 ^ 3 ^ 4 ^ 5 ^ 6 ^ 7 ^ 8 ^
| Hz | 31.5 | 63 | 125 | 250 | 500 | 1000 | 2000 | 4000 | 8000 |
These positions follow the current sound descriptor's ''%%bands.el%%'' labels. The
script also exposes that order numerically as ''%%frequency_hz%%'' and retains the
original sample header. It does not apply the firmware's offsets/divisors again
or turn band levels into A-weighted readings. Calibration and the dB reference
remain properties of the sensor. Status is shown as its numeric bitmask; sequence
and clipping fields retain the firmware's 8-bit representation.
Invalid/nonfinite/null band readings become NaN gaps, including a whole invalid
frame. Malformed JSON, a missing band attribute, a non-nine-band profile, a
different unit, or another sensor type produces no script output. The script
keeps no history or cached readings. Foxglove can still retain the last displayed
frame when input stops or is skipped; check publisher count and inventory
freshness before treating it as a current reading.
The derived topic exists **only in Foxglove**. It is not a ROS publisher and will
not appear in ''%%ros2 topic list%%''. The original ''%%sample%%'' retains everything needed
to reproduce the spectrum from a recording. This sensor output contains features
and band levels, so it cannot provide waveform playback in the Audio panel.
==== Another board namespace or existing layout ====
The supplied aliases avoid changing scripts when the sensor moves between slots.
For a board namespace other than ''%%axiom1%%'', replace every ''%%/axiom1/%%'' occurrence in
the JSON before import, including inside the embedded source and derived topic
paths. This also changes ''%%/foxglove_script/axiom1/...%%''. Keep panel expressions,
the script's ''%%inputs%%'', its ''%%Input<...>%%'' type and output topic consistent. Board
configuration must use the matching namespace.
To add the spectrum to another layout, create a User Script and paste
[[https://github.com/robotical/axiom-ros2/blob/main/examples/foxglove/sound-spectrum.ts|sound-spectrum.ts]], adjusting both input-topic occurrences
and the output topic as needed. Add a Plot panel with **X-axis value type → Array
index** and the derived topic's ''%%.bands_db[:]%%'' field as Y. Editing the standalone
file does not update an imported layout; edit the script in Foxglove or update
the JSON's embedded ''%%sourceCode%%'', then reimport. Replacing a JSON file on disk
does not change a layout already saved in Foxglove.
===== Custom Axiom Spectrum panel =====
For frequency-labelled coloured bars, install the optional custom panel instead of relying on the built-in Plot's array display. It reads the same existing ''axiom_interfaces/msg/DeviceSample'' directly; it needs neither the embedded user script nor a new ROS ''bands'' topic. Add it beside the scalar plots or use the supplied spectrum-only layout.
{{ :axiom:ros2:axiom-spectrum-panel-0-1-1.jpg?760&nolink |Axiom Spectrum custom panel with synthetic sound data}}
//Illustration of the extension using synthetic data; it is not a physical sound-sensor capture.//
==== Build and install the extension ====
Use **Node.js 24 or later** on the computer building the extension. Building can happen on another computer; only the finished ''.foxe'' file needs to reach the Foxglove viewer. The ROS driver needs no Node runtime and no rebuild for an extension-only update. From the repository root:
# Enter the extension project inside the checkout.
cd examples/foxglove/spectrum-panel
# Confirm a suitable Node.js version before installing dependencies.
node --version
# Install the locked extension build dependencies.
npm ci
# Check the TypeScript interfaces.
npm run typecheck
# Run the panel's unit tests.
npm test
# Build and package the installable Foxglove extension.
npm run package
# Find the packaged extension to install or copy to the viewer computer.
ls dist/*.foxe
The current package is ''dist/robotical.axiom-spectrum-0.1.1.foxe''. On the **viewer computer**, open Foxglove's user-profile menu, choose **Extensions → Install local extension…** and select that file, or drag the ''.foxe'' file into an open visualisation. A saved JSON layout does not install its required extension. After changing extension source, rebuild/repackage and install the updated ''.foxe''; changing files in a checkout does not update an already installed extension.
==== Use the panel and layout ====
- Connect Foxglove to the running ROS bridge as above.
- Choose **Add panel → Axiom Spectrum**.
- In the panel's **Source** dropdown, select the sound sensor's actual canonical ''sample'' topic or the ''/axiom1/sound/sample'' alias. The alias is a convenience default, not a required generated topic.
- Set **Spectrum scale** minimum/maximum dB for the display; the default is −20 to 100 dB.
- Save the layout. Source and scale are stored with it.
For a spectrum-only view, import [[https://raw.githubusercontent.com/robotical/axiom-ros2/main/examples/foxglove/spectrum-panel/one-axiom-spectrum.json|one-axiom-spectrum.json]] from ''examples/foxglove/spectrum-panel/'' on the viewer computer, then open the imported layout. It initially selects the sound alias above. Choose a different source when using another board or sensor. Install the extension first so Foxglove can resolve its panel type.
Bars show already-decoded dB values, including valid negative readings. They clip visually at the selected scale limits while numeric labels/tooltips retain the actual values. Frequency labels come from ''bands.meta.el'' with ''bands.meta.eu: Hz'', up to 64 bands; categories have equal visual spacing. The colours follow Axiom's teal/cream/blue/yellow/orange palette and Foxglove's light/dark theme. They distinguish frequencies, not calibrated noise-safety thresholds.
Invalid or nonfinite bins have no bar and show a dash. Malformed frames clear the old chart and explain the issue. Seeking playback or changing source clears previous readings. Samples more than five seconds behind the viewer timeline are dimmed as stale; paused/disconnected timelines can retain their last display. Use fresh samples and online inventory to confirm the sensor is live. The panel subscribes only and sends no device commands.
==== Try labelled dummy data ====
After building dependencies above, from the extension directory:
# Generate the bundled 45-second synthetic MCAP recording.
npm run demo
Open ''dist/axiom-spectrum-dummy.mcap'' as a **local file** in Foxglove, add the installed panel or import its layout, and play. This needs no ROS bridge or hardware. The recording includes speech-like levels, a bass-heavy spectrum, invalid bins/frames, malformed JSON and recovery. It is an explicitly synthetic UI fixture, not captured audio or a hardware calibration.
Official reference: [[https://docs.foxglove.dev/docs/extensions/local-development|installing local Foxglove extensions]].
===== Troubleshoot and finish =====
^ Symptom ^ Check ^
| No sound aliases | Board namespace, type ''%%RoboticalSoundSensor%%'', sensor freshness, alias ambiguity and identical bus/address selectors |
| Generic sample exists, scalar topics absent | Build/source/restart a driver containing scalar outputs |
| ROS echoes work, plots are empty | Bridge host/domain, imported layout and the ''%%/axiom1/sound/...%%'' paths |
| Scalars update, spectrum does not | ''%%sound/sample%%'' alias, script errors, nine ''%%bands.value%%'' entries, ''%%unit: "dB"%%'', validity and sensor type |
| Script reports an unknown input topic/type | Connect the data source and ensure the sample publisher is discovered; check both input-topic strings in the script |
| Derived topic absent from ROS CLI | Expected: it is local to Foxglove |
| Unexpected units or clipped trace | Inspect ''%%output_info%%''; dBA/dB are separate, ratio/counter/flux axes are fixed, other vertical scales are automatic |
| Frozen spectrum | Check fresh samples and online inventory; Foxglove can retain the last frame after disconnect or rejected input |
| Custom panel is missing or unknown | Install the packaged ''.foxe'' on the viewer before opening its layout; importing JSON does not install it. |
| Old script or extra panels | Reimport and open the new layout; saved layouts do not follow file changes |
Stop the bridge in Terminal 3. In Terminal 2, stop publication and disconnect:
# Send this discrete request and check the returned acknowledgement.
ros2 service call /axiom1/publish_data_subscription \
axiom_interfaces/srv/PublishedDataSubscription '{rate_hz: 0.0}'
# Send this discrete request and check the returned acknowledgement.
ros2 service call /axiom1/disconnect axiom_interfaces/srv/Disconnect '{}'
Then stop the driver in Terminal 1.
Return to [[:axiom:ros2:foxglove|Foxglove dashboards]] or [[:axiom:ros2:start|Axiom with ROS 2]].