Skip to main content

nros/
lib.rs

1//! # nros
2//!
3//! A lightweight ROS 2 client library for embedded systems.
4//!
5//! This crate provides a unified API for building ROS 2 nodes in Rust,
6//! with support for `no_std` environments and embedded targets.
7//!
8//! ## Features
9//!
10//! - **no_std compatible**: Works on bare-metal and RTOS targets
11//! - **Zero-copy where possible**: Minimizes memory allocations
12//! - **Type-safe**: Compile-time verification of message types
13//! - **ROS 2 compatible**: Interoperates with standard ROS 2 nodes via rmw_zenoh
14//!
15//! ## Quick Start
16//!
17//! ```ignore
18//! use nros::prelude::*;
19//! use std_msgs::msg::Int32;
20//!
21//! let config = ExecutorConfig::from_env().node_name("my_node");
22//! let mut executor = Executor::open(&config)?;
23//!
24//! let node = executor.node_builder("my_node").build()?;
25//! let publisher = executor.node_mut(node).create_publisher::<Int32>("/my_topic")?;
26//! publisher.publish(&Int32 { data: 42 })?;
27//!
28//! executor.node_mut(node).create_subscription::<Int32, _>("/topic", |msg: &Int32| {
29//!     println!("Received: {}", msg.data);
30//! })?;
31//!
32//! executor.spin_blocking(SpinOptions::default());
33//! ```
34//!
35//! ## Executor Sizing
36//!
37//! The executor's static memory layout is controlled via environment variables
38//! at build time:
39//!
40//! - **`NROS_EXECUTOR_MAX_CBS`** (default 4) — maximum number of registered
41//!   callbacks (subscriptions + timers + services + guard conditions).
42//! - **`NROS_EXECUTOR_ARENA_SIZE`** (default 4096) — byte budget for storing
43//!   callback closures inline.
44//!
45//! For messages larger than the default 1024-byte receive buffer, size the
46//! subscription via the builder's `.rx_buffer::<N>()` knob (e.g.
47//! `node_mut(id).subscription(t).typed::<M>().rx_buffer::<4096>().build(cb)`).
48//!
49//! ## Transport Backends
50//!
51//! Phase 248 C5c — `nros` is RMW- and platform-AGNOSTIC. It carries only the
52//! `rmw-cffi` vtable; the concrete backend (zenoh / xrce / cyclonedds) enters
53//! the link graph via the board crate (embedded), the board-less app's own
54//! `nros-rmw-*` dep (native), or the `nros-c`/`nros-cpp` staticlib root (D3),
55//! and self-registers through the `RMW_INIT_ENTRIES` walker at `Executor::open`.
56//! The concrete session type is resolved automatically; advanced users can
57//! access it via `nros::internals::RmwSession`.
58//!
59//! ## Crate Features
60//!
61//! `nros` exposes only FUNCTIONAL features — `std`/`alloc`, the `rmw-cffi`
62//! vtable, `lending`, `bridge`/`config`, `param-services`,
63//! `lifecycle-services`, `safety-e2e`, `stream`, `ffi-sync`, and the ROS
64//! edition (`ros-humble`/`ros-iron`). There are NO `platform-*` or concrete
65//! `rmw-*` selector features (Phase 248 C7). Platform + RMW are selected by the
66//! board / staticlib root via dependencies, not `nros` features. The
67//! `zephyr_component_main!` entry macro is gated only on `rmw-cffi` (it's
68//! framework entry codegen, like `nros::main!`), not a platform feature.
69//!
70//! **ROS version** (select one):
71//! - `ros-humble` - ROS 2 Humble
72//! - `ros-iron` - ROS 2 Iron
73//!
74//! **Other**:
75//! - `std` (default) - Enable standard library support
76//! - `alloc` - Enable heap allocation without full std
77//!
78//! ## Further Reading
79//!
80//! - [`guide`] — tutorials: getting started, services, configuration,
81//!   ROS 2 interop, and troubleshooting
82//! - [Message Generation](https://github.com/jerry73204/nano-ros/blob/main/docs/guides/message-generation.md)
83//!   — codegen reference (all options, output structure, bundled interfaces)
84//! - [Environment Variables](https://github.com/jerry73204/nano-ros/blob/main/docs/reference/environment-variables.md)
85//!   — complete buffer tuning reference
86//! - [ROS 2 Interop](https://github.com/jerry73204/nano-ros/blob/main/docs/reference/rmw_zenoh_interop.md)
87//!   — protocol details (key expressions, liveliness, attachments)
88//! - [Examples](https://github.com/jerry73204/nano-ros/tree/main/examples)
89//!   — working examples by platform (native, QEMU, ESP32, Zephyr)
90
91#![no_std]
92
93// ── Feature validation (mutual exclusivity) ─────────────────────────────
94// Phase 248 C5c/C7 — `nros` carries NO `platform-*` selector features, so the
95// platform mutual-exclusion `compile_error!` is gone. The platform is selected
96// by the board / staticlib root via an `nros-platform` dep, and nros-node picks
97// the kernel primitive at runtime (C2 wake-probe).
98// Only `rmw-cffi` is exposed at this layer; the cffi shim selects the
99// concrete backend at the C ABI level via the `RMW_INIT_ENTRIES` walker.
100
101// At most one ROS edition.
102#[cfg(all(feature = "ros-humble", feature = "ros-iron"))]
103compile_error!("`ros-humble` and `ros-iron` are mutually exclusive — select one ROS edition.");
104
105#[cfg(feature = "std")]
106extern crate std;
107
108#[cfg(feature = "alloc")]
109extern crate alloc;
110
111// Phase 216.A.5 — the `nros::node!()` proc-macro emits absolute paths
112// under `::nros::*` (so downstream Node pkgs only need a single `nros`
113// dep). For the in-crate macro-expansion test in `node.rs`, alias the
114// `nros` crate name to itself so those absolute paths resolve. Gated on
115// `cfg(test)` to keep the alias out of normal builds.
116#[cfg(test)]
117extern crate self as nros;
118
119// Phase 248 C5c — the umbrella's force-link statics
120// (`__FORCE_LINK_{PLATFORM_CFFI,ZENOH,XRCE,CYCLONEDDS_SYS}`) are REMOVED along
121// with `nros`'s concrete-backend deps. `nros` no longer references any concrete
122// RMW or platform crate, so it has nothing to force-link. Registration + the
123// `nros_platform_*` link anchor now live with whoever owns the concrete crate:
124//   * embedded   — the BOARD crate force-links its backend + calls
125//     `<backend>::register()` in its boot path (C5a);
126//   * board-less native — the APP owns `nros-rmw-*` + a `#[used]` force-link in
127//     its `main.rs`, and `nros-platform-cffi[posix-c-port]` anchors the C symbols;
128//   * C/C++ staticlib — `nros-c`/`nros-cpp` bundle one backend (D3) and anchor
129//     `nros-platform` themselves.
130
131// Phase 249 P1 — `__register_linked_rmw()` (a Phase 248 C5c no-op kept only so the
132// `nros::main!` framework's call sites compiled) is REMOVED along with those call
133// sites. Backend registration never routed through the backend-agnostic `nros` crate:
134// hosted auto-registers via the `RMW_INIT_ENTRIES` walk at `Executor::open`; embedded
135// boards perform the explicit `<backend>::register()` in their boot path (C5a). One
136// Rust trigger = the board/app explicit register (phase-249).
137
138pub mod dispatch_tag;
139pub mod guide;
140pub mod node;
141pub mod node_metadata;
142/// Phase 212.M.5.a.2 — executor-backed component runtime.
143///
144/// Binds [`Node`] / [`ExecutableNode`] to a live
145/// [`Executor`] so a Node pkg can actually run (versus
146/// [`MetadataRecorder`](node_metadata::MetadataRecorder) which
147/// is the planner-side metadata sink).
148///
149/// Gated on `rmw-cffi`; the underlying [`Executor`] is only present
150/// when an RMW backend is linked.
151#[cfg(feature = "rmw-cffi")]
152pub mod node_runtime;
153
154/// Phase 212.L.5 — top-level init API.
155///
156/// Re-exported flat at the crate root: `nros::init()`,
157/// `nros::init_with_launch_auto()`, `nros::init_with_launch(path)`,
158/// `nros::init_with_args(args)`, `nros::Context`, `nros::InitError`.
159#[cfg(feature = "std")]
160pub mod init;
161
162#[cfg(feature = "std")]
163pub use init::{
164    Context, ContextSource, InitError, init, init_with_args, init_with_launch,
165    init_with_launch_auto,
166};
167
168/// Compile-time opaque storage sizes for FFI consumers.
169///
170/// See [`sizes`] for the `export_size!` pattern used to expose these values
171/// to `nros-c` / `nros-cpp` at build time.
172pub mod sizes;
173
174/// CDR encapsulation constants and helpers for FFI layers that handle raw
175/// CDR bytes (e.g. nros-c, nros-cpp action and service paths).
176pub mod cdr {
177    pub use nros_serdes::{
178        CDR_BE_HEADER, CDR_HEADER_LEN, CDR_LE_HEADER, strip_cdr_header, write_cdr_le_header,
179    };
180}
181
182// Re-export core types
183pub use nros_core::{
184    CdrReader, CdrWriter, Clock, ClockType, DeserError, Deserialize, Duration, Logger, MessageInfo,
185    PUBLISHER_GID_SIZE, RawMessageInfo, RosMessage, RosService, SerError, Serialize, Time,
186};
187
188// Re-export heapless for generated message types and examples
189pub use nros_core::heapless;
190
191// Re-export component-mode API
192#[cfg(feature = "rmw-cffi")]
193pub use node::NodeExecutorRuntime;
194// Phase 212.M.5.a.2 — executor-backed runtime entry points.
195// (`component_register_symbol` retired in the Phase 212.N.7 closing
196// sweep — the helper had no live callers after the BSP baker + macro
197// extern emit were deleted.)
198pub use node::{
199    ActionExecutor, Callback, CallbackCtx, CallbackEffects, ClientDispatch, DeclaredNode,
200    DeclaredNodeRuntime, ExecutableNode, MISSING_NODE_EXPORT_ERROR, Node, NodeActionClient,
201    NodeActionServer, NodeContext, NodeDeclError, NodeOptions, NodeParameter, NodePublisher,
202    NodeResult, NodeRuntime, NodeRuntimeAdapter, NodeServiceClient, NodeServiceServer,
203    NodeSubscription, NodeTimer, PublisherResolver, RuntimeNodeRecord, TickCtx,
204    record_node_metadata, register_node,
205};
206// Phase 212.M.5.a.4 — internal helper consumed by `nros::node!()`
207// for the BSP dispatch path. Public-but-doc-hidden so the macro expand
208// resolves it as `::nros::__private_node_state_into_raw`.
209#[cfg(feature = "alloc")]
210#[doc(hidden)]
211pub use node::__private_node_state_into_raw;
212#[cfg(feature = "std")]
213pub use node_metadata::SourceMetadataExport;
214pub use node_metadata::{
215    CallbackEffectKind, CallbackEffectMetadata, EntityKind, EntityMetadata, MetadataRecorder,
216    MetadataString, NodeMetadata, NodeMetadataError, ParameterDefault, SourceLocationMetadata,
217    SourceNameKind,
218};
219#[doc(hidden)]
220pub use node_metadata::{CallbackId, EntityId, NodeId};
221// Phase 216.A.4 — opaque tag types Node authors hold on `Self::State`
222// and match against the `Callback<'_>` delivered to
223// `ExecutableNode::on_callback`.
224pub use dispatch_tag::{ActionTag, ServiceTag, SubscriptionTag};
225#[cfg(feature = "rmw-cffi")]
226pub use node_runtime::{
227    ExecutorError,
228    ExecutorNodeRuntime,
229    RegisteredNode,
230    // Phase 257 (W0-B) — the uniform cross-language component-install seam backing
231    // `__nros_component_<pkg>_install` (nros::node!): register an ExecutableNode on the
232    // shared executor a foreign typed entry hands in. (`register_node_borrowed` stays
233    // crate-internal — it returns the private `ComponentCell`.)
234    install_node_typed,
235    // Phase 268 W1 — same seam with both `<param>` initials AND `<node name= namespace=>`
236    // identity injection; the variant `nros::node!()` now emits (RFC-0046).
237    install_node_typed_with_node_identity,
238    // W4a — same seam, seeding the node's NodeContext with launch-baked `<param>` initials.
239    install_node_typed_with_params,
240};
241
242/// Phase 257 (W0-B) — `install_node_typed` stub for builds without the cffi runtime.
243/// The typed-entry install seam needs the `rmw-cffi` executor; a `nros::node!()` pkg
244/// compiled without `rmw-cffi` still emits `__nros_component_<pkg>_install` (the macro
245/// can't see the umbrella's feature), so this stub keeps it linkable — it returns `-1`
246/// (no real executor to install on). The real impl is `node_runtime::install_node_typed`.
247///
248/// # Safety
249/// Signature parity with the real impl; the stub dereferences nothing.
250#[cfg(not(feature = "rmw-cffi"))]
251#[doc(hidden)]
252pub unsafe fn install_node_typed<C: node::ExecutableNode + 'static>(
253    _executor: *mut core::ffi::c_void,
254) -> i32
255where
256    C::State: 'static,
257{
258    -1
259}
260
261/// W4a — `install_node_typed_with_params` stub for builds without the cffi runtime.
262/// Signature parity with `node_runtime::install_node_typed_with_params`; returns `-1`.
263///
264/// # Safety
265/// The stub dereferences nothing.
266#[cfg(not(feature = "rmw-cffi"))]
267#[doc(hidden)]
268pub unsafe fn install_node_typed_with_params<C: node::ExecutableNode + 'static>(
269    _executor: *mut core::ffi::c_void,
270    _params: &[(&str, &str)],
271) -> i32
272where
273    C::State: 'static,
274{
275    -1
276}
277
278/// Phase 268 W1 — `install_node_typed_with_node_identity` stub for builds without the
279/// cffi runtime. Signature parity with the real impl; returns `-1`.
280///
281/// # Safety
282/// The stub dereferences nothing.
283#[cfg(not(feature = "rmw-cffi"))]
284#[doc(hidden)]
285pub unsafe fn install_node_typed_with_node_identity<C: node::ExecutableNode + 'static>(
286    _executor: *mut core::ffi::c_void,
287    _params: &[(&str, &str)],
288    _node_identity: Option<(&'static str, &'static str)>,
289) -> i32
290where
291    C::State: 'static,
292{
293    -1
294}
295// Phase 212.N.12 — canonical `nros::node!()` macro. Replaces the legacy
296// `nros::node!()` macro (retired in the N.12 hard rename — both the
297// proc-macro forwarder and the Cargo metadata key are gone).
298pub use nros_macros::node;
299// Phase 212.N.9 — `nros::main!()` proc-macro family. One-line Entry-pkg
300// `main.rs` (replaces the legacy `build.rs + include!()` shape). See
301// `docs/design/0024-multi-node-workspace-layout.md` §11.6.
302pub use nros_macros::main;
303
304/// Define Zephyr's `rust_main` for a self-bringup Rust component package.
305///
306/// The macro is intended for `rust_cargo_application()` apps whose crate
307/// already invokes `nros::node!()`. It opens a Zephyr executor, registers
308/// the supplied component through [`ExecutorNodeRuntime`], and spins forever.
309// Phase 248 C7 (Method A) — gated on `rmw-cffi` only (needs `Executor`), NOT a
310// `platform-*` feature. This is a framework ENTRY macro (same category as
311// `nros::main!`'s zephyr `rust_main` codegen) — `#[macro_export]` so it emits
312// nothing unless a Zephyr example invokes it; the body's `::zephyr::*` /
313// `::nros_platform::zephyr::wait_network` resolve only in that zephyr-build
314// context (the example deps the `zephyr` crate + `nros-platform[platform-zephyr]`).
315#[cfg(feature = "rmw-cffi")]
316#[macro_export]
317macro_rules! zephyr_component_main {
318    ($node:ty) => {
319        #[unsafe(no_mangle)]
320        pub extern "C" fn rust_main() {
321            unsafe {
322                zephyr::set_logger().ok();
323            }
324            // Phase 248 C7 step 1 — relocated helper (was `$crate::platform::zephyr`).
325            let _ = ::nros_platform::zephyr::wait_network(2000);
326            // Phase 249 P1 — RMW register is board/platform-owned (Phase 248 C5a);
327            // the backend-agnostic `nros` crate cannot register (no backend dep).
328            // Issue 0155 — the "board/platform boot path" that was supposed to
329            // register never fired for pure-Rust Zephyr images: the zephyr
330            // module emits a STRONG `nros_app_register_backends` stub for the
331            // Kconfig-selected RMW (zephyr/CMakeLists.txt Phase 160.A), but
332            // only the C/C++ `nros_cpp_init` path ever CALLED it — a Rust-only
333            // image reached `Executor::open` with no backend registered and
334            // died with Transport(ConnectionFailed) (silently, pre-0155).
335            // Call the hook explicitly, exactly like the C++ init path.
336            unsafe extern "C" {
337                fn nros_app_register_backends();
338            }
339            unsafe { nros_app_register_backends() };
340            // Issue 0163 — a pure-Rust image has no `libnros_c.a`, so the
341            // zenoh/xrce backend must ride in THIS staticlib and be referenced
342            // from the app crate or rustc's staticlib DCE drops the whole
343            // backend closure (the `#[no_mangle]` C export included — the same
344            // hazard nros-c's FORCE_LINK anchor documents). These cfg's are
345            // evaluated against the EXPANDING app crate's features (`rmw-zenoh`
346            // / `rmw-xrce` forward to the real backend deps); the direct call
347            // is both the force-link reference and the registration, and is
348            // idempotent with the `nros_app_register_backends` hook above
349            // (duplicate named registration is an in-place overwrite).
350            // cyclonedds needs nothing here: its register entry lives in the
351            // Zephyr module's C++ lib and the hook above calls it.
352            #[cfg(feature = "rmw-zenoh")]
353            {
354                let _ = ::nros_rmw_zenoh::register();
355            }
356            #[cfg(feature = "rmw-xrce")]
357            {
358                let _ = ::nros_rmw_xrce_cffi::register();
359            }
360            // Locator: `default_const()` = EMPTY locator → zenoh-pico
361            // multicast scouting, which native_sim NSOS can't satisfy.
362            // Bake `NROS_LOCATOR` at compile time (the example `build.rs`
363            // re-exports `CONFIG_NROS_ZENOH_LOCATOR` from Kconfig into that
364            // env). No baked value → falls back to the empty locator.
365            const BAKED_LOCATOR: ::core::option::Option<&str> = ::core::option_env!("NROS_LOCATOR");
366            // Domain: the example `build.rs` bakes `CONFIG_NROS_DOMAIN_ID`
367            // into `NROS_DOMAIN_ID` the same way (its comment has promised
368            // this consumption since phase-225; the phase-277 macro rework
369            // dropped it — issue 0161: every Rust cyclonedds image silently
370            // ran domain 0 regardless of the Kconfig bake).
371            const BAKED_DOMAIN: ::core::option::Option<&str> =
372                ::core::option_env!("NROS_DOMAIN_ID");
373            let domain_id: u32 = match BAKED_DOMAIN {
374                ::core::option::Option::Some(d) => match d.parse() {
375                    ::core::result::Result::Ok(v) => v,
376                    ::core::result::Result::Err(_) => {
377                        panic!("nros zephyr entry: NROS_DOMAIN_ID baked non-numeric: {d:?}")
378                    }
379                },
380                ::core::option::Option::None => 0,
381            };
382            // #166 / phase-286 W1 — native_sim test parallelism. The test
383            // harness launches the image with `-testargs --nros-locator=<loc>`
384            // and starts a per-test zenohd on that (ephemeral) port; preferring
385            // it over the build-time bake lets every test dial a DISTINCT router,
386            // retiring the shared-baked-port serialization of the zenoh e2e
387            // lanes. Provided by `nros-platform-zephyr` (argv-backed, process
388            // lifetime); returns NULL on real embedded → the bake stands.
389            unsafe extern "C" {
390                fn nros_runtime_locator_override() -> *const ::core::ffi::c_char;
391            }
392            let runtime_locator: ::core::option::Option<&str> = {
393                let p = unsafe { nros_runtime_locator_override() };
394                if p.is_null() {
395                    ::core::option::Option::None
396                } else {
397                    match unsafe { ::core::ffi::CStr::from_ptr(p) }.to_str() {
398                        ::core::result::Result::Ok(s) if !s.is_empty() => {
399                            ::core::option::Option::Some(s)
400                        }
401                        _ => ::core::option::Option::None,
402                    }
403                }
404            };
405            let effective_locator = runtime_locator.or(match BAKED_LOCATOR {
406                ::core::option::Option::Some(loc) if !loc.is_empty() => {
407                    ::core::option::Option::Some(loc)
408                }
409                _ => ::core::option::Option::None,
410            });
411            let config = match effective_locator {
412                ::core::option::Option::Some(loc) => {
413                    $crate::ExecutorConfig::new(loc).node_name(<$node as $crate::Node>::NAME)
414                }
415                ::core::option::Option::None => {
416                    $crate::ExecutorConfig::default_const().node_name(<$node as $crate::Node>::NAME)
417                }
418            }
419            .domain_id(domain_id);
420            // Issue 0155 — fail LOUD (repo rule: panic, not silent
421            // early-return). A silent `return` here idles the image with zero
422            // output; the zephyr-cyclonedds rust lane was undiagnosable until
423            // this printed the real error.
424            let executor = match $crate::Executor::open(&config) {
425                Ok(executor) => executor,
426                Err(e) => {
427                    panic!("nros zephyr entry: Executor::open failed: {e:?}");
428                }
429            };
430            let mut runtime = $crate::ExecutorNodeRuntime::from_executor(executor);
431            if let Err(e) = runtime.register_node::<$node>() {
432                panic!("nros zephyr entry: register_node failed: {e:?}");
433            }
434            // Readiness marker. The C/C++ Zephyr listeners print
435            // "Waiting for messages..." from their `main()` before the spin
436            // loop; the e2e harness polls for that substring to know the
437            // subscriber has declared before starting the talker (Phase 89.12).
438            // The Rust path's spin loop lives in this macro (the node only owns
439            // callbacks), so emit the same canonical marker here — without it a
440            // fully-working Rust listener never signals readiness and the e2e
441            // times out at 30 s (issue #35: the zenoh native_sim rust pubsub /
442            // service / action failures were this missing marker, not a
443            // transport fault — `Executor::open` + `register_node` had already
444            // succeeded).
445            ::log::info!("Waiting for messages");
446            loop {
447                let _ = runtime.spin_once(::core::time::Duration::from_millis(10));
448            }
449        }
450    };
451}
452
453// Re-export node types
454pub use nros_node::{NodeConfig, PublisherHandle, StandaloneNode, SubscriberHandle};
455
456// Re-export publisher/subscriber options (topic + QoS; always available).
457pub use nros_node::{PublisherOptions, SubscriberOptions};
458
459// Re-export timer types
460pub use nros_node::{TimerCallbackFn, TimerDuration, TimerHandle, TimerMode, TimerState};
461
462// Re-export transport types (middleware-agnostic)
463pub use nros_rmw::{
464    Publisher, QosDurabilityPolicy, QosHistoryPolicy, QosLivelinessPolicy, QosOverride,
465    QosOverrideRole, QosOverrideValue, QosPolicyMask, QosReliabilityPolicy, QosSettings, Rmw,
466    RmwConfig, ServiceClientTrait, ServiceInfo, ServiceRequest, ServiceServerTrait, Session,
467    SessionMode, Subscriber, TopicInfo, Transport, TransportConfig, TransportError,
468};
469
470/// Phase 108.B — standard ROS-2-equivalent QoS profiles. Match
471/// upstream `rmw_qos_profile_default` etc. field-by-field. Backends
472/// validate against these synchronously at create time; no silent
473/// downgrade.
474pub mod qos {
475    use crate::{
476        QosDurabilityPolicy, QosHistoryPolicy, QosLivelinessPolicy, QosReliabilityPolicy,
477        QosSettings,
478    };
479
480    /// `rmw_qos_profile_default`-equivalent: reliable + volatile +
481    /// keep-last(10), automatic liveliness, no deadline / lifespan.
482    pub const DEFAULT: QosSettings = QosSettings {
483        reliability: QosReliabilityPolicy::Reliable,
484        durability: QosDurabilityPolicy::Volatile,
485        history: QosHistoryPolicy::KeepLast,
486        liveliness_kind: QosLivelinessPolicy::Automatic,
487        depth: 10,
488        deadline_ms: 0,
489        lifespan_ms: 0,
490        liveliness_lease_ms: 0,
491        avoid_ros_namespace_conventions: false,
492        tx_express: false,
493    };
494
495    /// `rmw_qos_profile_sensor_data`-equivalent: best-effort +
496    /// volatile + keep-last(5).
497    pub const SENSOR_DATA: QosSettings = QosSettings {
498        reliability: QosReliabilityPolicy::BestEffort,
499        depth: 5,
500        ..DEFAULT
501    };
502
503    /// `rmw_qos_profile_services_default`-equivalent.
504    pub const SERVICES_DEFAULT: QosSettings = DEFAULT;
505
506    /// `rmw_qos_profile_parameters`-equivalent: depth = 1000.
507    pub const PARAMETERS: QosSettings = QosSettings {
508        depth: 1000,
509        ..DEFAULT
510    };
511
512    /// `rmw_qos_profile_system_default`-equivalent.
513    pub const SYSTEM_DEFAULT: QosSettings = DEFAULT;
514}
515
516// Re-export safety types when feature is enabled
517#[cfg(feature = "safety-e2e")]
518pub use nros_rmw::{IntegrityStatus, SafetyValidator, crc32};
519
520// Phase 248 C7 step 1 — the `nros::platform::zephyr` module (the
521// `wait_for_network` FFI wrapper) RELOCATED to `nros-platform`
522// (`nros_platform::zephyr::wait_network`); callers reference it via
523// `::nros_platform::zephyr::wait_network`. nros no longer hosts a platform
524// helper module. (The `zephyr_component_main!` macro relocation is C7 step 2.)
525//
526/// Backend-specific internal types.
527///
528/// These types are implementation details of the transport backends.
529/// Most users should use the high-level APIs (`Executor`, etc.)
530/// instead of these types directly.
531///
532/// The `Rmw*` type aliases resolve to whichever backend is active at compile time,
533/// providing a backend-agnostic way to reference concrete transport types.
534pub mod internals {
535    // ── Backend-agnostic type aliases ────────────────────────────────────
536    // These resolve to the concrete types of the active RMW backend.
537    // Today the only exposed backend at this layer is the cffi shim.
538
539    #[cfg(feature = "rmw-cffi")]
540    pub type RmwSession = nros_rmw_cffi::CffiSession;
541    #[cfg(feature = "rmw-cffi")]
542    pub type RmwPublisher = nros_rmw_cffi::CffiPublisher;
543    #[cfg(feature = "rmw-cffi")]
544    pub type RmwSubscriber = nros_rmw_cffi::CffiSubscriber;
545    #[cfg(feature = "rmw-cffi")]
546    pub type RmwServiceServer = nros_rmw_cffi::CffiServiceServer;
547    #[cfg(feature = "rmw-cffi")]
548    pub type RmwServiceClient = nros_rmw_cffi::CffiServiceClient;
549
550    /// Phase 124.A — zero-copy publisher slot type. Lives in the
551    /// `internals` module so `nros-c` can construct + transmute the
552    /// lifetime when boxing the slot for the C-side `_loan` /
553    /// `_commit` / `_discard` token plumbing.
554    #[cfg(all(feature = "rmw-cffi", feature = "lending"))]
555    pub type RmwSlot<'a> = nros_rmw_cffi::CffiSlot<'a>;
556
557    /// Phase 124.A — zero-copy subscriber view type.
558    #[cfg(all(feature = "rmw-cffi", feature = "lending"))]
559    pub type RmwView<'a> = nros_rmw_cffi::CffiView<'a>;
560
561    /// Open a new middleware session.
562    ///
563    /// Wraps the backend-specific session constructor behind a common signature.
564    /// Used by the C API (`nros-c`); Rust users should prefer `Executor::open()`.
565    ///
566    /// Phase 156 — consults `$NROS_RMW` (when std + the env var is set)
567    /// to pin the primary backend by name, mirroring what `Executor::open`
568    /// does for Rust callers. Without this, C bridges built with two
569    /// linked backends (e.g. xrce + dds) get whichever ctor fires
570    /// first via linkme — non-deterministic across link orderings +
571    /// often the wrong backend for the bridge's intended primary.
572    #[cfg(feature = "rmw-cffi")]
573    pub fn open_session(
574        locator: &str,
575        mode: nros_rmw::SessionMode,
576        domain_id: u32,
577        node_name: &str,
578    ) -> Result<RmwSession, nros_rmw::TransportError> {
579        use nros_rmw::Rmw;
580
581        // Phase 249 P4b.1 — every linked backend self-registered via
582        // its `.init_array` ctor before `main` (RFC-0042 §D3.3); no
583        // runtime section walk.
584
585        let config = nros_rmw::RmwConfig {
586            locator,
587            mode,
588            domain_id,
589            node_name,
590            namespace: "",
591            properties: &[],
592        };
593        // Phase 156 — honor `$NROS_RMW` env-var primary selector
594        // when present so C bridges built with multiple linked
595        // backends (e.g. xrce + dds) pin the primary deterministically
596        // instead of taking whichever linkme ctor fires first.
597        // Phase 155.B — propagate the real `TransportError` instead of
598        // collapsing every backend failure to `ConnectionFailed`. The
599        // C-side `nros_support_init` decodes the variant into a
600        // specific `NROS_RET_*` code so "init -> -X" tells the user
601        // which precondition the backend rejected.
602        #[cfg(feature = "std")]
603        if let Some(name) = std::env::var("NROS_RMW").ok().filter(|s| !s.is_empty()) {
604            return nros_rmw_cffi::CffiRmw::open_with_rmw(&name, &config);
605        }
606        nros_rmw_cffi::CffiRmw.open(&config)
607    }
608
609    /// Drive middleware I/O for pull-based backends.
610    ///
611    /// Delegates to [`Session::drive_io()`](nros_rmw::Session::drive_io),
612    /// which each backend implements appropriately (no-op for push-based,
613    /// poll for pull-based).
614    ///
615    /// Used by the C API executor before polling handles.
616    #[cfg(feature = "rmw-cffi")]
617    pub fn drive_session_io(session: &mut RmwSession, timeout_ms: i32) {
618        use nros_rmw::Session;
619        let _ = session.drive_io(timeout_ms);
620    }
621}
622
623// Re-export types that don't depend on RMW (always available)
624pub use nros_node::{
625    BOOT_SET_DOMAIN, BOOT_SET_LOCATOR, BOOT_SET_NAMESPACE, BOOT_SET_NODE_NAME, BakedBootConfig,
626    BootConfig, BootConfigError, DOMAIN_ID_EXPLICIT_ZERO_C_ABI, DOMAIN_ID_MAX, ExecutorConfig,
627    ExecutorSemantics, GuardConditionHandle, HandleId, HandleSet, InvocationMode,
628    NROS_BOOT_CONFIG_MAGIC, NROS_BOOT_CONFIG_VERSION, NodeError, RawCancelCallback,
629    RawGoalCallback, RawServiceCallback, RawSubscriptionCallback, ReadinessSnapshot,
630    SpinOnceResult, SpinOptions, SpinPeriodPollingResult, Trigger, baked_domain_from_c_abi,
631};
632
633// RFC-0052 / phase-296 W3b — on-target contract-monitor types. Baked
634// `system_monitors.rs` uses the fully-qualified `::nros_node::executor::
635// monitor::*` path; this re-export lets hand-written entries and fixtures
636// reach the same types through the `nros` umbrella (they install the
637// tables via `Executor::set_monitor_table` / `set_age_table` and drain
638// with `drain_violations`). The monitor module is `has_rmw`-gated in
639// nros-node (it names entity types), so mirror that with `rmw-cffi`.
640#[cfg(feature = "rmw-cffi")]
641pub mod monitor {
642    pub use nros_node::executor::monitor::{
643        AgeMonitorSpec, MonitorSpec, PubMonitorCell, SubMonitorCell, Violation,
644    };
645}
646
647// Re-export RMW-dependent types (require an active transport backend)
648#[cfg(feature = "rmw-cffi")]
649pub use nros_node::{
650    ActionClient, ActionClientCore, ActionServer, ActionServerCore, ActionServerHandle,
651    ActionServerRawHandle, ActiveGoal, CompletedGoal, EmbeddedPublisher, EmbeddedRawPublisher,
652    EmbeddedServiceClient, EmbeddedServiceServer, Executor, ExecutorSizing, FeedbackStream,
653    GoalFeedbackStream, LoanError, NodeHandle, Promise, PublishLoan, RawActionClientSpec,
654    RawActionServerSpec, RawActiveGoal, RawSubscription, RecvView, SessionHandle, SessionSpec,
655    Subscription,
656};
657
658// phase-271 (issue #110) — per-entry executor sizing helper: the orchestration
659// codegen's `build_executor` sizes its backing to the system's callback count
660// via `nros::arena_size_for(CALLBACK_COUNT)` + `ExecutorSizing`, replacing the
661// workspace-global `NROS_EXECUTOR_MAX_CBS`.
662#[cfg(feature = "rmw-cffi")]
663pub use nros_node::config::arena_size_for;
664
665// Phase 173.5 — board config traits. `BoardConfig` (read locator /
666// domain) + `BoardTransportConfig` (the generator writes nros.toml
667// `[[transport]]` IP / baud into a NanoRosOwned board `Config`).
668// Named `BoardTransportConfig` to avoid colliding with the
669// transport-layer `TransportConfig` already re-exported above.
670pub use nros_platform::{BoardConfig, BoardTransportConfig};
671
672// Phase 216.A.1 — `DispatchStrategy` enum. User-visible at
673// `nros::DispatchStrategy`; the canonical home is `nros_platform::
674// board::dispatch` so the C ABI symbol the `nros::node!()` macro emits
675// (`__nros_node_<pkg>_dispatch_strategy() -> u8`) lives next to the
676// other board-side trampolines.
677pub use nros_platform::DispatchStrategy;
678
679/// Implementation detail — used by `nros::node!()` macro expansion.
680///
681/// Re-exports `nros_platform` so the macro's emitted trampoline can
682/// reference `RuntimeCtx` / `RuntimeError` / the `Node*Fn`
683/// fn-pointer aliases without forcing every consumer Node pkg's
684/// `Cargo.toml` to carry an explicit `nros-platform` dep on top of
685/// `nros`. Phase 212.M-F.13 path (b).
686///
687/// Not part of the public API — paths under this module may change at
688/// any time. End users should depend on `nros` alone and invoke
689/// `nros::node!()`; the macro routes through here automatically.
690#[doc(hidden)]
691pub mod __macro_support {
692    pub use ::nros_platform;
693}
694
695// Phase 110.B / 110.G — scheduling-context API surface. Consumers
696// of the Phase 110 cyclic / TT scheduler need these types to
697// describe schedules and bind handles; re-exporting them here
698// keeps user code free of `nros_node::executor::sched_context`
699// path noise. Gated on `rmw-cffi`: the source module is
700// `#[cfg(any(has_rmw, test))]` in nros-node, so it only exists once
701// an RMW backend is linked (matches the re-export block above).
702#[cfg(feature = "rmw-cffi")]
703pub use nros_node::executor::sched_context::{
704    DeadlineAction, DeadlinePolicy, OptUs, Priority, SchedClass, SchedContext, SchedContextId,
705    TimeTriggeredSchedule, TimeTriggeredScheduleError, TimeTriggeredWindow,
706};
707
708#[cfg(all(feature = "std", feature = "rmw-cffi"))]
709pub use nros_node::SpinPeriodResult;
710
711// Re-export service types
712pub use nros_core::{ServiceClient, ServiceServer};
713
714// Re-export action types
715pub use nros_core::{
716    CancelResponse, GoalId, GoalInfo, GoalResponse, GoalStatus, GoalStatusStamped, RosAction,
717};
718
719// Re-export lifecycle types (always available, no_std compatible)
720pub use nros_core::{LifecycleState, LifecycleTransition, TransitionResult};
721pub use nros_node::{LifecycleCallbackFn, LifecycleError, LifecyclePollingNode};
722
723/// Re-export of the full lifecycle module so examples can reach
724/// `LifecycleCallbackSlot`, `LifecyclePollingNodeCtx`, etc.
725pub mod lifecycle {
726    pub use nros_core::lifecycle::{LifecycleState, LifecycleTransition, TransitionResult};
727    pub use nros_node::lifecycle::*;
728}
729
730// Phase 128.G — bridge surface re-exports. Gated behind the
731// `bridge` / `config` umbrella features so single-backend builds
732// don't pull in `nros-bridge` (or, for `config`, the TOML stack).
733#[cfg(feature = "bridge")]
734pub use nros_bridge as bridge;
735
736#[cfg(feature = "config")]
737pub use nros_bridge::run_from_config;
738
739// Re-export parameter types
740pub use nros_params::{
741    MandatoryParameter, OptionalParameter, Parameter, ParameterBuilder, ParameterDescriptor,
742    ParameterError, ParameterServer, ParameterType, ParameterValue, ParameterVariant,
743    ReadOnlyParameter, SetParameterResult,
744};
745// Phase 172.H — runtime parameter-override persistence backends.
746/// Hosted file-backed parameter store (the only built-in backend today).
747#[cfg(feature = "std")]
748pub use nros_params::FileParamStore;
749pub use nros_params::{NullParamStore, ParamStore, ParamStoreError};
750
751/// Prelude module for convenient imports
752///
753/// Import everything you need with a single statement:
754/// ```
755/// use nros::prelude::*;
756/// ```
757pub mod prelude {
758    pub use crate::{
759        CdrReader, CdrWriter, Deserialize, Logger, MessageInfo, NodeConfig, PublisherHandle,
760        QosDurabilityPolicy, QosHistoryPolicy, QosReliabilityPolicy, QosSettings, RosMessage,
761        RosService, Serialize, StandaloneNode, SubscriberHandle, TopicInfo,
762    };
763
764    // Re-export component-mode API.
765    #[cfg(feature = "rmw-cffi")]
766    pub use crate::NodeExecutorRuntime;
767    #[cfg(feature = "std")]
768    pub use crate::SourceMetadataExport;
769    pub use crate::{
770        ActionTag, Callback, CallbackEffectKind, CallbackEffects, DeclaredNode,
771        DeclaredNodeRuntime, EntityKind, MetadataRecorder, Node, NodeActionClient,
772        NodeActionServer, NodeContext, NodeDeclError, NodeOptions, NodeParameter, NodePublisher,
773        NodeResult, NodeRuntime, NodeRuntimeAdapter, NodeServiceClient, NodeServiceServer,
774        NodeSubscription, NodeTimer, ParameterDefault, RuntimeNodeRecord, ServiceTag,
775        SourceLocationMetadata, SourceNameKind, SubscriptionTag, node, record_node_metadata,
776        register_node,
777    };
778
779    // Re-export lifecycle types
780    pub use crate::{
781        LifecycleCallbackFn, LifecycleError, LifecyclePollingNode, LifecycleState,
782        LifecycleTransition, TransitionResult,
783    };
784
785    // Re-export executor config + handle types (always available)
786    pub use crate::{
787        ExecutorConfig, GuardConditionHandle, HandleId, HandleSet, InvocationMode, NodeError,
788        SessionMode, SpinOnceResult, SpinOptions, SpinPeriodPollingResult, TransportError, Trigger,
789    };
790
791    // Re-export RMW-dependent executor + handle types
792    #[cfg(feature = "rmw-cffi")]
793    pub use crate::{
794        EmbeddedPublisher, EmbeddedServiceClient, Executor, FeedbackStream, NodeHandle, Promise,
795        Subscription,
796    };
797
798    // Publisher/Subscriber options (topic + QoS).
799    pub use crate::{PublisherOptions, SubscriberOptions};
800
801    #[cfg(all(feature = "std", feature = "rmw-cffi"))]
802    pub use crate::SpinPeriodResult;
803
804    // Re-export parameter types
805    pub use crate::{ParameterServer, ParameterType, ParameterValue};
806
807    // Re-export typed parameter API (rclrs-compatible builder pattern)
808    pub use crate::{
809        MandatoryParameter, OptionalParameter, ParameterBuilder, ParameterError, ParameterVariant,
810        ReadOnlyParameter,
811    };
812
813    // Re-export action types
814    pub use crate::{GoalId, GoalInfo, GoalResponse, GoalStatus, GoalStatusStamped, RosAction};
815
816    // Re-export Time, Duration, Clock from core
817    pub use nros_core::{Clock, ClockType, Duration, Time};
818
819    // Re-export timer types
820    pub use crate::{TimerCallbackFn, TimerDuration, TimerHandle, TimerMode};
821}
822
823/// Derive macros for message types
824///
825/// Use these macros to generate message serialization code.
826/// These macros help you create custom message types that are compatible
827/// with ROS 2's CDR serialization format.
828pub mod derive {
829    pub use nros_macros::RosMessage;
830}
831
832#[cfg(test)]
833mod tests {
834    #[test]
835    fn test_prelude_imports() {
836        // This test just verifies that the prelude compiles
837        use crate::prelude::*;
838
839        let _ = NodeConfig::new("test_node", "/");
840        let _ = QosSettings::BEST_EFFORT;
841    }
842
843    /// Verify the Node* canonical trait + context + result types
844    /// resolve after the Component→Node hard rename. The Component*
845    /// aliases were dropped in the same phase; their absence is
846    /// enforced by the workspace audit (no live `Component*` ident
847    /// remains in core / examples / tests).
848    #[test]
849    fn node_context_types_resolve() {
850        // Canonical "Node*" trait + context names (post-rename).
851        fn _take_node_ctx<N: crate::Node>(_: &mut crate::NodeContext<'_, dyn crate::NodeRuntime>) {}
852        // Result type resolves.
853        let _: crate::NodeResult<()> = Ok(());
854    }
855}