Skip to main content

nros_platform/board/
entry.rs

1//! [`BoardEntry`] — Phase 212.N.1.
2//!
3//! The single boot-driver trait every Entry pkg `main.rs` invokes:
4//!
5//! ```ignore
6//! fn main() {
7//!     let _ = <MyBoard as BoardEntry>::run(|runtime| {
8//!         run_plan(runtime)         // codegen-emitted (212.N.4)
9//!     });
10//! }
11//! ```
12//!
13//! `run` owns the full lifecycle:
14//!
15//! 1. [`super::BoardInit::init_hardware`]
16//! 2. device bring-up — link layer to L2, then the carrier / DHCP gate if the
17//!    board has an IP stack. This is the `run` BODY's job, usually delegated to
18//!    a family helper (`nros_board_freertos::run_entry`, …). There is no mixin
19//!    trait for it: phase-206 W4 / issue 1067 removed `TransportBringup` and
20//!    `NetworkWait`, which had zero and zero production callers respectively,
21//!    and whose "skipped if not implemented" order was not expressible in Rust.
22//! 4. Open executor, build `RuntimeCtx`, invoke `setup(runtime)`.
23//! 5. Spin executor to completion (or termination signal).
24//! 6. [`super::BoardExit::exit_success`] / `exit_failure`.
25//!
26//! The exact body lives in the family driver crates (212.N.2); the
27//! trait here pins the signature so codegen + user Entry pkg can
28//! call it without knowing the family.
29
30use super::runtime::RuntimeCtx;
31
32/// Deploy-metadata overlay threaded from `nros::main!()` into the board's
33/// boot config (issue #48 cause 1).
34///
35/// The `nros::main!()` macro reads the Entry pkg's
36/// `[package.metadata.nros.deploy.<board>]` block at expansion time and bakes
37/// the present keys here. Each field is `None` when the deploy block omitted
38/// it, so the board overlays only the supplied values onto its own
39/// `Config::default()` (the firmware's compiled-in default stays the source of
40/// truth for everything the deploy block does not name).
41///
42/// Boards whose `BoardEntry::run` ignores network/locator config (POSIX hosts,
43/// RTIC/Embassy MCUs that take their transport elsewhere) inherit the default
44/// [`BoardEntry::run_with_deploy`] body, which drops the overlay and calls
45/// [`BoardEntry::run`] — so adding a *network* field here never touches those
46/// boards. The exception is [`node_name`](DeployOverlay::node_name): hosted
47/// boards override `run_with_deploy` to apply it to the boot config (issue #98),
48/// since the ROS graph node name is a launch identity, not a network knob.
49#[derive(Clone, Copy, Default, Debug)]
50pub struct DeployOverlay {
51    /// `locator = "tcp/10.0.2.2:7451"` — the zenoh/RMW endpoint the firmware
52    /// dials. `None` → keep the board default.
53    pub locator: Option<&'static str>,
54    /// `ip = "10.0.2.15"` — static guest IP. `None` → keep the board default.
55    pub ip: Option<[u8; 4]>,
56    /// `gateway = "10.0.2.2"` — default route. `None` → keep the board default.
57    pub gateway: Option<[u8; 4]>,
58    /// `netmask = "255.255.255.0"`. `None` → keep the board default.
59    pub netmask: Option<[u8; 4]>,
60    /// `domain_id = 0` — ROS 2 domain. `None` → keep the board default.
61    pub domain_id: Option<u32>,
62    /// `transport = "xrce"` — select a board custom transport that must be
63    /// installed BEFORE the linked RMW registers (e.g. an XRCE-over-UART vtable).
64    /// `None` → the board's default transport. Honored by
65    /// [`BoardEntry::setup_transport`] (phase-244.D1).
66    pub transport: Option<&'static str>,
67    /// The ROS graph node name for the primary session, baked from the launch
68    /// file's single `<node name=…>` / `system.toml` `[[component]].name` (issue
69    /// #98). `None` → the board default (`from_env()`'s `"node"`). Only set by
70    /// `nros::main!` when the launch declares exactly one node — multiple nodes
71    /// share one primary session, so naming it after one component would be
72    /// wrong (per-node naming is the deferred multi-node piece). Applied to the
73    /// boot `ExecutorConfig` by the board, so unlike `locator` this IS honored on
74    /// hosted boards (locator stays env-driven; node name is a launch identity).
75    pub node_name: Option<&'static str>,
76    /// Issue #101 / RFC-0045 — the patchable baked boot-config static
77    /// (`.nros_boot_config`), emitted by `nros::main!` for embedded targets and
78    /// read by the board to resolve node_name/locator/domain. `None` on hosted /
79    /// when the macro emits no static.
80    pub boot_config: Option<&'static nros_platform_api::BakedBootConfig>,
81}
82
83/// Per-board boot driver.
84///
85/// Implementations live in the family driver crates
86/// (`nros-board-linux`, `nros-board-freertos`, …). Per-board crates
87/// (`nros-board-mps2-an385-freertos`, …) plug the family.
88pub trait BoardEntry: super::Board {
89    /// Drive the full boot → user-closure → exit flow.
90    ///
91    /// `setup` receives a `&mut RuntimeCtx` with overlay knobs from
92    /// the launch file / CLI args. Returning `Err` from `setup` makes
93    /// `run` route to [`super::BoardExit::exit_failure`]; `Ok`
94    /// proceeds to executor spin + clean exit.
95    ///
96    /// **Returns `Result`, not `!`.** The legacy
97    /// `nros-board-common::board_init::BoardEntry::run` diverged;
98    /// 212.N keeps the option to return so unit tests can drive it
99    /// in a hosted process without `exit()` killing the test
100    /// harness. Production boards still call `exit_*` from inside
101    /// `run`'s body after spin returns.
102    fn run<F, E>(setup: F) -> Result<(), E>
103    where
104        F: FnOnce(&mut RuntimeCtx<'_>) -> Result<(), E>,
105        E: core::fmt::Debug;
106
107    /// Boot like [`run`](Self::run) but apply a deploy-metadata overlay to the
108    /// board's boot config first (issue #48 cause 1).
109    ///
110    /// The default body **ignores** `deploy` and forwards to
111    /// [`run`](Self::run); boards that compile a network/locator config (the
112    /// FreeRTOS / bare-metal firmware boards) override it to overlay the
113    /// supplied fields onto their `Config::default()`. `nros::main!()` calls
114    /// this (not `run`) for `target_os = "none"` OwnedSpin targets so the
115    /// `[package.metadata.nros.deploy.<board>]` block stops being inert.
116    fn run_with_deploy<F, E>(_deploy: &DeployOverlay, setup: F) -> Result<(), E>
117    where
118        F: FnOnce(&mut RuntimeCtx<'_>) -> Result<(), E>,
119        E: core::fmt::Debug,
120    {
121        Self::run(setup)
122    }
123
124    /// phase-271 (issue #110) — boot like [`run_with_deploy`](Self::run_with_deploy)
125    /// but size the executor's callback table + arena to the entry's OWN declared
126    /// topology (`max_cbs` / `max_sched_contexts`, from the entry's
127    /// `[package.metadata.nros.entry]`), instead of the workspace-global
128    /// `NROS_EXECUTOR_MAX_CBS` build const.
129    ///
130    /// Sizes are plain `usize`s (not `nros::ExecutorSizing`) because
131    /// `nros-platform` sits below `nros`; the hosted board converts them. A
132    /// `max_sched_contexts` of `0` means "use the build default". The **default
133    /// body IGNORES the sizing** and forwards to
134    /// [`run_with_deploy`](Self::run_with_deploy), so every board except the
135    /// hosted (Linux) one — which opens via `Executor::open` and could grow its
136    /// arena — is byte-identical; `nros-board-linux` overrides this to
137    /// `Executor::open_sized`. `nros::main!()` emits this (instead of
138    /// `run_with_deploy`) only when the entry declares `max_callbacks`.
139    fn run_with_deploy_sized<F, E>(
140        deploy: &DeployOverlay,
141        _max_cbs: usize,
142        _max_sched_contexts: usize,
143        setup: F,
144    ) -> Result<(), E>
145    where
146        F: FnOnce(&mut RuntimeCtx<'_>) -> Result<(), E>,
147        E: core::fmt::Debug,
148    {
149        Self::run_with_deploy(deploy, setup)
150    }
151
152    /// **Custom-transport install seam.** Install a board-specific transport
153    /// selected by `deploy.transport`, BEFORE the linked RMW registers
154    /// (phase-244.D1).
155    ///
156    /// `nros::main!()` always emits a `setup_transport` call (gated on
157    /// `target_os = "none"`) immediately before `__register_linked_rmw()`,
158    /// so that the vtable is in place before the XRCE backend registers —
159    /// the ordering `set_custom_transport_ops` requires.
160    ///
161    /// **This method is intentionally kept** — it is not dead code. The
162    /// **default no-op** is correct for every board whose transport is
163    /// registered automatically (Zenoh, native sockets, etc.). The only
164    /// current override is **`nros-board-mps2-an385`** with the
165    /// `xrce-transport` feature, which installs an XRCE-over-UART vtable
166    /// when `deploy.transport == Some("xrce")`. Future boards that need to
167    /// pre-register a custom transport vtable should override this method in
168    /// the same pattern.
169    ///
170    /// Failures are the board's to handle (it owns `exit_failure`).
171    fn setup_transport(_deploy: &DeployOverlay) {}
172}