Configure an image
Needs first: Organize a larger workspace
Configure an image when it needs more than a board: an address on a network, a router to dial, a per-image build knob, a flash or console override, or a smaller binary for a constrained board.
Everything here goes in the image’s row of the bringup’s system.toml, or in
a file that row names. The toolchain bakes it in at build time: an embedded
image has no configuration file on the device and parses nothing at run time.
Cargo.toml and CMakeLists.txt carry language facts only, and nothing a
board implies is written anywhere: the build derives it from the board’s
descriptor.
Building and configuring
nros build prepares everything the build needs (generated messages, the
resolved system, the image’s build settings) and then runs the host build
tool. A project that stays in west, CMake, NuttX make or an IDE runs the
preparation step, nros configure, from that tool’s own configure step
instead.
Before you start
- A workspace with a bringup and at least one image, as in Organize a larger workspace.
Steps
See what the image resolves to
nros config show prints the resolved configuration of the bringup, with the
file and table each value came from:
nros config show
# Resolved config for system 'host_node' (bringup pkg: host_node_bringup)
# source: …/src/host_node_bringup/system.toml
[system]
rmw = zenoh # system.toml [system]
domain_id = 0 # system.toml [system]
Resolution is a fixed ladder, not a merge: a command-line or build flag, then
[image.<id>], then [system], then the board descriptor’s [knobs], then
the built-in default. nros config explain prints the build-time knob ladder
for a platform.
Retarget the image
board is the one key that has to change. The image’s id is the name you
pass to nros build:
[image.mps2-an385-baremetal]
board = "mps2-an385-baremetal"
locator = "tcp/10.0.2.2:7447"
locator is the router or agent the image dials. Without it, the image dials
[system] locator, then the board’s default.
Give the image an address
On a board where nano-ros owns the network (the reference FreeRTOS, ThreadX
and bare-metal boards), the address is in a board network file the image
names with os_config. On Linux, Zephyr and NuttX the operating system owns
the network, and you set the address in its own configuration.
-
Name the file on the image:
[image.talker] board = "mps2-an385-baremetal" os_config = ["board-net.toml"] -
Write one table per interface, beside
system.toml. On QEMU’s built-in network,hostis enough:# board-net.toml [eth0] network = "qemu" host = 10 # 10.0.2.10 -
For a network of your own, define it in the same file and state the router on the image:
# board-net-sensor.toml [eth0] network = "lab" address = "192.168.50.20" [network.lab] subnet = "192.168.50.0/24" gateway = "192.168.50.1"[image.sensor] board = "mps2-an385-baremetal" os_config = ["board-net-sensor.toml"] locator = "tcp/192.168.50.1:7447"Two images on one network each define it, and the definitions must agree. A mistake fails the build and names the image: an address outside the subnet, two images with one address, or a network no file defines.
-
To run that image under QEMU on a host bridge, say how it joins:
[image.sensor.run] nic = { bridge = "qemu-br" }nros runpasses the-nicvalue to QEMU. An image on a network other thanqemuwith nonicis physical only.
The middleware’s own file
How a session finds its peers is the middleware’s own configuration, in its
own language: rmw/zenoh.json5 or rmw/cyclonedds.xml beside system.toml,
the same documents ROS 2 reads. To ship a different file for one image, name
it:
[image.qemu]
board = "mps2-an385-freertos"
rmw_config = "rmw/zenoh-qemu.json5"
Configure zenoh and Use Cyclone DDS show the files themselves.
Per-image build knobs
A build knob one image needs, and its board does not state, goes in
[image.<id>.knobs.<group>]:
[image.linux.knobs.log]
max_level = "warn" # NROS_LOG_MAX_LEVEL
[image.linux.knobs.executor]
max_cbs = 16 # NROS_EXECUTOR_MAX_CBS
An unknown key, a wrong type or an out-of-range value stops nros build,
naming the key. An application’s own build variable, one the knob registry
does not hold, goes in env = { NAME = "value" } instead.
Override how a board is flashed and watched
A board’s descriptor states its flasher and console. An image overrides any key of them:
[image.lab.flash]
command = "pyocd flash -t stm32f767zi {hex}"
prepare = [{ say = "Set BOOT0 to 1, then press RESET", wait_for = "usb:0483:df11" }]
[image.lab.console]
baud = 921600
Both tables are checked when system.toml is read, and nros flash and
nros monitor use them.
Shrink the image for a small board
-
Build with the size profile. Name it on the image, and
nros buildpasses it to whichever tool it drives:[image.mps2-an385-baremetal] board = "mps2-an385-baremetal" profile = "nros-minsizerel" -
Drop what a brokered client does not need. These are ordinary build environment variables, and a value you export wins over the generated one:
NROS_LINK_IP=0 ZPICO_NO_SMOLTCP=1 \ NROS_SMOLTCP_MAX_SOCKETS=1 NROS_SMOLTCP_MAX_UDP_SOCKETS=1 \ nros build mps2-an385-baremetalNROS_LINK_IP=0drops zenoh-pico’s TCP and UDP links on a serial-only image, andZPICO_NO_SMOLTCP=1skips the smoltcp glue on bare metal. A knob you always want for a board belongs in that board’s descriptor[knobs], not on every command line. -
Keep the heap at the board default for a
nros::main!image. Its executor backing is one large allocation; shrinkNROS_HEAP_SIZEonly on a hand-written direct-mode image whose peak you measured.
Moving from ethernet to a serial link is the largest saving. The measured sizes are in Build outputs.
Example
A bare-metal image on a lab network, with its own router and a log ceiling:
# src/sensor_bringup/system.toml
[system]
name = "sensor"
rmw = "zenoh"
domain_id = 0
[[component]]
pkg = "sensor_node"
class = "sensor_node::Sensor"
name = "sensor"
[image.sensor]
board = "mps2-an385-baremetal"
os_config = ["board-net-sensor.toml"]
locator = "tcp/192.168.50.1:7447"
[image.sensor.run]
nic = { bridge = "qemu-br" }
[image.sensor.knobs.log]
max_level = "info"
nros build sensor
A hand-written no_std app
An app that writes its own main() instead of using nros::main!() keeps its
network settings in a config.toml beside it, compiled in with
include_str!. It has the board network file’s tables, plus a [node] table
for the session:
# config.toml
[node]
domain_id = 0
locator = "tcp/10.0.2.2:7447"
[eth0]
network = "qemu"
host = 10
use nros_board_mps2_an385::{Config, run};
fn main() -> ! {
run(Config::from_toml(include_str!("../config.toml")), |config| {
let exec = ExecutorConfig::new(config.zenoh_locator)
.domain_id(config.domain_id);
// ...
})
}
If it doesn’t work
- The build names a retired key.
ip,gateway,netmask,transportand[image.<id>.net.<if>]moved to the board network file and the locator. The message names the new spelling. - A board network file is refused. The board’s network belongs to the
host OS (
linux,freertos-posix,threadx-linux), or the board declares no interfaces. Zephyr boards take their address from Kconfig. - An image with a zenoh file is refused. It also states a
locator. With a file, the file is the wiring; remove one of the two.
See also
- system.toml keys: every image key, the network file, the run, flash and console tables.
- Environment variables: the build-time and runtime variables, and the buffer knobs.
- Configuration surface: the Kconfig symbols and where each decision is made.
- Retired configuration files.