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}