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};
236
237/// Phase 257 (W0-B) — `install_node_typed` stub for builds without the cffi runtime.
238/// The typed-entry install seam needs the `rmw-cffi` executor; a `nros::node!()` pkg
239/// compiled without `rmw-cffi` still emits `__nros_component_<pkg>_install` (the macro
240/// can't see the umbrella's feature), so this stub keeps it linkable — it returns `-1`
241/// (no real executor to install on). The real impl is `node_runtime::install_node_typed`.
242///
243/// # Safety
244/// Signature parity with the real impl; the stub dereferences nothing.
245#[cfg(not(feature = "rmw-cffi"))]
246#[doc(hidden)]
247pub unsafe fn install_node_typed<C: node::ExecutableNode + 'static>(
248    _executor: *mut core::ffi::c_void,
249) -> i32
250where
251    C::State: 'static,
252{
253    -1
254}
255// Phase 212.N.12 — canonical `nros::node!()` macro. Replaces the legacy
256// `nros::node!()` macro (retired in the N.12 hard rename — both the
257// proc-macro forwarder and the Cargo metadata key are gone).
258pub use nros_macros::node;
259// Phase 212.N.9 — `nros::main!()` proc-macro family. One-line Entry-pkg
260// `main.rs` (replaces the legacy `build.rs + include!()` shape). See
261// `docs/design/0024-multi-node-workspace-layout.md` §11.6.
262pub use nros_macros::main;
263
264/// Define Zephyr's `rust_main` for a self-bringup Rust component package.
265///
266/// The macro is intended for `rust_cargo_application()` apps whose crate
267/// already invokes `nros::node!()`. It opens a Zephyr executor, registers
268/// the supplied component through [`ExecutorNodeRuntime`], and spins forever.
269// Phase 248 C7 (Method A) — gated on `rmw-cffi` only (needs `Executor`), NOT a
270// `platform-*` feature. This is a framework ENTRY macro (same category as
271// `nros::main!`'s zephyr `rust_main` codegen) — `#[macro_export]` so it emits
272// nothing unless a Zephyr example invokes it; the body's `::zephyr::*` /
273// `::nros_platform::zephyr::wait_network` resolve only in that zephyr-build
274// context (the example deps the `zephyr` crate + `nros-platform[platform-zephyr]`).
275#[cfg(feature = "rmw-cffi")]
276#[macro_export]
277macro_rules! zephyr_component_main {
278    ($node:ty) => {
279        #[unsafe(no_mangle)]
280        pub extern "C" fn rust_main() {
281            unsafe {
282                zephyr::set_logger().ok();
283            }
284            // Phase 248 C7 step 1 — relocated helper (was `$crate::platform::zephyr`).
285            let _ = ::nros_platform::zephyr::wait_network(2000);
286            // Phase 249 P1 — RMW register is board/platform-owned (Phase 248 C5a);
287            // the backend-agnostic `nros` crate cannot register (no backend dep), so
288            // the former no-op `$crate::__register_linked_rmw()` emit is removed. The
289            // Zephyr entry's explicit backend register lands via the board/platform
290            // boot path (verify under the phase-249 Zephyr e2e gate).
291            // Locator: `default_const()` = EMPTY locator → zenoh-pico
292            // multicast scouting, which native_sim NSOS can't satisfy.
293            // Bake `NROS_LOCATOR` at compile time (the example `build.rs`
294            // re-exports `CONFIG_NROS_ZENOH_LOCATOR` from Kconfig into that
295            // env). No baked value → falls back to the empty locator.
296            const BAKED_LOCATOR: ::core::option::Option<&str> = ::core::option_env!("NROS_LOCATOR");
297            let config = match BAKED_LOCATOR {
298                ::core::option::Option::Some(loc) if !loc.is_empty() => {
299                    $crate::ExecutorConfig::new(loc).node_name(<$node as $crate::Node>::NAME)
300                }
301                _ => {
302                    $crate::ExecutorConfig::default_const().node_name(<$node as $crate::Node>::NAME)
303                }
304            };
305            let executor = match $crate::Executor::open(&config) {
306                Ok(executor) => executor,
307                Err(_) => return,
308            };
309            let mut runtime = $crate::ExecutorNodeRuntime::from_executor(executor);
310            if runtime.register_node::<$node>().is_err() {
311                return;
312            }
313            // Readiness marker. The C/C++ Zephyr listeners print
314            // "Waiting for messages..." from their `main()` before the spin
315            // loop; the e2e harness polls for that substring to know the
316            // subscriber has declared before starting the talker (Phase 89.12).
317            // The Rust path's spin loop lives in this macro (the node only owns
318            // callbacks), so emit the same canonical marker here — without it a
319            // fully-working Rust listener never signals readiness and the e2e
320            // times out at 30 s (issue #35: the zenoh native_sim rust pubsub /
321            // service / action failures were this missing marker, not a
322            // transport fault — `Executor::open` + `register_node` had already
323            // succeeded).
324            ::log::info!("Waiting for messages");
325            loop {
326                let _ = runtime.spin_once(::core::time::Duration::from_millis(10));
327            }
328        }
329    };
330}
331
332// Re-export node types
333pub use nros_node::{NodeConfig, PublisherHandle, StandaloneNode, SubscriberHandle};
334
335// Re-export publisher/subscriber options (topic + QoS; always available).
336pub use nros_node::{PublisherOptions, SubscriberOptions};
337
338// Re-export timer types
339pub use nros_node::{TimerCallbackFn, TimerDuration, TimerHandle, TimerMode, TimerState};
340
341// Re-export transport types (middleware-agnostic)
342pub use nros_rmw::{
343    Publisher, QosDurabilityPolicy, QosHistoryPolicy, QosLivelinessPolicy, QosOverride,
344    QosOverrideRole, QosOverrideValue, QosPolicyMask, QosReliabilityPolicy, QosSettings, Rmw,
345    RmwConfig, ServiceClientTrait, ServiceInfo, ServiceRequest, ServiceServerTrait, Session,
346    SessionMode, Subscriber, TopicInfo, Transport, TransportConfig, TransportError,
347};
348
349/// Phase 108.B — standard ROS-2-equivalent QoS profiles. Match
350/// upstream `rmw_qos_profile_default` etc. field-by-field. Backends
351/// validate against these synchronously at create time; no silent
352/// downgrade.
353pub mod qos {
354    use crate::{
355        QosDurabilityPolicy, QosHistoryPolicy, QosLivelinessPolicy, QosReliabilityPolicy,
356        QosSettings,
357    };
358
359    /// `rmw_qos_profile_default`-equivalent: reliable + volatile +
360    /// keep-last(10), automatic liveliness, no deadline / lifespan.
361    pub const DEFAULT: QosSettings = QosSettings {
362        reliability: QosReliabilityPolicy::Reliable,
363        durability: QosDurabilityPolicy::Volatile,
364        history: QosHistoryPolicy::KeepLast,
365        liveliness_kind: QosLivelinessPolicy::Automatic,
366        depth: 10,
367        deadline_ms: 0,
368        lifespan_ms: 0,
369        liveliness_lease_ms: 0,
370        avoid_ros_namespace_conventions: false,
371    };
372
373    /// `rmw_qos_profile_sensor_data`-equivalent: best-effort +
374    /// volatile + keep-last(5).
375    pub const SENSOR_DATA: QosSettings = QosSettings {
376        reliability: QosReliabilityPolicy::BestEffort,
377        depth: 5,
378        ..DEFAULT
379    };
380
381    /// `rmw_qos_profile_services_default`-equivalent.
382    pub const SERVICES_DEFAULT: QosSettings = DEFAULT;
383
384    /// `rmw_qos_profile_parameters`-equivalent: depth = 1000.
385    pub const PARAMETERS: QosSettings = QosSettings {
386        depth: 1000,
387        ..DEFAULT
388    };
389
390    /// `rmw_qos_profile_system_default`-equivalent.
391    pub const SYSTEM_DEFAULT: QosSettings = DEFAULT;
392}
393
394// Re-export safety types when feature is enabled
395#[cfg(feature = "safety-e2e")]
396pub use nros_rmw::{IntegrityStatus, SafetyValidator, crc32};
397
398// Phase 248 C7 step 1 — the `nros::platform::zephyr` module (the
399// `wait_for_network` FFI wrapper) RELOCATED to `nros-platform`
400// (`nros_platform::zephyr::wait_network`); callers reference it via
401// `::nros_platform::zephyr::wait_network`. nros no longer hosts a platform
402// helper module. (The `zephyr_component_main!` macro relocation is C7 step 2.)
403//
404/// Backend-specific internal types.
405///
406/// These types are implementation details of the transport backends.
407/// Most users should use the high-level APIs (`Executor`, etc.)
408/// instead of these types directly.
409///
410/// The `Rmw*` type aliases resolve to whichever backend is active at compile time,
411/// providing a backend-agnostic way to reference concrete transport types.
412pub mod internals {
413    // ── Backend-agnostic type aliases ────────────────────────────────────
414    // These resolve to the concrete types of the active RMW backend.
415    // Today the only exposed backend at this layer is the cffi shim.
416
417    #[cfg(feature = "rmw-cffi")]
418    pub type RmwSession = nros_rmw_cffi::CffiSession;
419    #[cfg(feature = "rmw-cffi")]
420    pub type RmwPublisher = nros_rmw_cffi::CffiPublisher;
421    #[cfg(feature = "rmw-cffi")]
422    pub type RmwSubscriber = nros_rmw_cffi::CffiSubscriber;
423    #[cfg(feature = "rmw-cffi")]
424    pub type RmwServiceServer = nros_rmw_cffi::CffiServiceServer;
425    #[cfg(feature = "rmw-cffi")]
426    pub type RmwServiceClient = nros_rmw_cffi::CffiServiceClient;
427
428    /// Phase 124.A — zero-copy publisher slot type. Lives in the
429    /// `internals` module so `nros-c` can construct + transmute the
430    /// lifetime when boxing the slot for the C-side `_loan` /
431    /// `_commit` / `_discard` token plumbing.
432    #[cfg(all(feature = "rmw-cffi", feature = "lending"))]
433    pub type RmwSlot<'a> = nros_rmw_cffi::CffiSlot<'a>;
434
435    /// Phase 124.A — zero-copy subscriber view type.
436    #[cfg(all(feature = "rmw-cffi", feature = "lending"))]
437    pub type RmwView<'a> = nros_rmw_cffi::CffiView<'a>;
438
439    /// Open a new middleware session.
440    ///
441    /// Wraps the backend-specific session constructor behind a common signature.
442    /// Used by the C API (`nros-c`); Rust users should prefer `Executor::open()`.
443    ///
444    /// Phase 156 — consults `$NROS_RMW` (when std + the env var is set)
445    /// to pin the primary backend by name, mirroring what `Executor::open`
446    /// does for Rust callers. Without this, C bridges built with two
447    /// linked backends (e.g. xrce + dds) get whichever ctor fires
448    /// first via linkme — non-deterministic across link orderings +
449    /// often the wrong backend for the bridge's intended primary.
450    #[cfg(feature = "rmw-cffi")]
451    pub fn open_session(
452        locator: &str,
453        mode: nros_rmw::SessionMode,
454        domain_id: u32,
455        node_name: &str,
456    ) -> Result<RmwSession, nros_rmw::TransportError> {
457        use nros_rmw::Rmw;
458
459        // Phase 249 P4b.1 — every linked backend self-registered via
460        // its `.init_array` ctor before `main` (RFC-0042 §D3.3); no
461        // runtime section walk.
462
463        let config = nros_rmw::RmwConfig {
464            locator,
465            mode,
466            domain_id,
467            node_name,
468            namespace: "",
469            properties: &[],
470        };
471        // Phase 156 — honor `$NROS_RMW` env-var primary selector
472        // when present so C bridges built with multiple linked
473        // backends (e.g. xrce + dds) pin the primary deterministically
474        // instead of taking whichever linkme ctor fires first.
475        // Phase 155.B — propagate the real `TransportError` instead of
476        // collapsing every backend failure to `ConnectionFailed`. The
477        // C-side `nros_support_init` decodes the variant into a
478        // specific `NROS_RET_*` code so "init -> -X" tells the user
479        // which precondition the backend rejected.
480        #[cfg(feature = "std")]
481        if let Some(name) = std::env::var("NROS_RMW").ok().filter(|s| !s.is_empty()) {
482            return nros_rmw_cffi::CffiRmw::open_with_rmw(&name, &config);
483        }
484        nros_rmw_cffi::CffiRmw.open(&config)
485    }
486
487    /// Drive middleware I/O for pull-based backends.
488    ///
489    /// Delegates to [`Session::drive_io()`](nros_rmw::Session::drive_io),
490    /// which each backend implements appropriately (no-op for push-based,
491    /// poll for pull-based).
492    ///
493    /// Used by the C API executor before polling handles.
494    #[cfg(feature = "rmw-cffi")]
495    pub fn drive_session_io(session: &mut RmwSession, timeout_ms: i32) {
496        use nros_rmw::Session;
497        let _ = session.drive_io(timeout_ms);
498    }
499}
500
501// Re-export types that don't depend on RMW (always available)
502pub use nros_node::{
503    ExecutorConfig, ExecutorSemantics, GuardConditionHandle, HandleId, HandleSet, InvocationMode,
504    NodeError, RawCancelCallback, RawGoalCallback, RawServiceCallback, RawSubscriptionCallback,
505    ReadinessSnapshot, SpinOnceResult, SpinOptions, SpinPeriodPollingResult, Trigger,
506};
507
508// Re-export RMW-dependent types (require an active transport backend)
509#[cfg(feature = "rmw-cffi")]
510pub use nros_node::{
511    ActionClient, ActionClientCore, ActionServer, ActionServerCore, ActionServerHandle,
512    ActionServerRawHandle, ActiveGoal, CompletedGoal, EmbeddedPublisher, EmbeddedRawPublisher,
513    EmbeddedServiceClient, EmbeddedServiceServer, Executor, FeedbackStream, GoalFeedbackStream,
514    LoanError, NodeHandle, Promise, PublishLoan, RawActionClientSpec, RawActionServerSpec,
515    RawActiveGoal, RawSubscription, RecvView, SessionHandle, SessionSpec, Subscription,
516};
517
518// Phase 173.5 — board config traits. `BoardConfig` (read locator /
519// domain) + `BoardTransportConfig` (the generator writes nros.toml
520// `[[transport]]` IP / baud into a NanoRosOwned board `Config`).
521// Named `BoardTransportConfig` to avoid colliding with the
522// transport-layer `TransportConfig` already re-exported above.
523pub use nros_platform::{BoardConfig, BoardTransportConfig};
524
525// Phase 216.A.1 — `DispatchStrategy` enum. User-visible at
526// `nros::DispatchStrategy`; the canonical home is `nros_platform::
527// board::dispatch` so the C ABI symbol the `nros::node!()` macro emits
528// (`__nros_node_<pkg>_dispatch_strategy() -> u8`) lives next to the
529// other board-side trampolines.
530pub use nros_platform::DispatchStrategy;
531
532/// Implementation detail — used by `nros::node!()` macro expansion.
533///
534/// Re-exports `nros_platform` so the macro's emitted trampoline can
535/// reference `RuntimeCtx` / `RuntimeError` / the `Node*Fn`
536/// fn-pointer aliases without forcing every consumer Node pkg's
537/// `Cargo.toml` to carry an explicit `nros-platform` dep on top of
538/// `nros`. Phase 212.M-F.13 path (b).
539///
540/// Not part of the public API — paths under this module may change at
541/// any time. End users should depend on `nros` alone and invoke
542/// `nros::node!()`; the macro routes through here automatically.
543#[doc(hidden)]
544pub mod __macro_support {
545    pub use ::nros_platform;
546}
547
548// Phase 110.B / 110.G — scheduling-context API surface. Consumers
549// of the Phase 110 cyclic / TT scheduler need these types to
550// describe schedules and bind handles; re-exporting them here
551// keeps user code free of `nros_node::executor::sched_context`
552// path noise. Gated on `rmw-cffi`: the source module is
553// `#[cfg(any(has_rmw, test))]` in nros-node, so it only exists once
554// an RMW backend is linked (matches the re-export block above).
555#[cfg(feature = "rmw-cffi")]
556pub use nros_node::executor::sched_context::{
557    DeadlinePolicy, OptUs, Priority, SchedClass, SchedContext, SchedContextId,
558    TimeTriggeredSchedule, TimeTriggeredScheduleError, TimeTriggeredWindow,
559};
560
561#[cfg(all(feature = "std", feature = "rmw-cffi"))]
562pub use nros_node::SpinPeriodResult;
563
564// Re-export service types
565pub use nros_core::{ServiceClient, ServiceServer};
566
567// Re-export action types
568pub use nros_core::{
569    CancelResponse, GoalId, GoalInfo, GoalResponse, GoalStatus, GoalStatusStamped, RosAction,
570};
571
572// Re-export lifecycle types (always available, no_std compatible)
573pub use nros_core::{LifecycleState, LifecycleTransition, TransitionResult};
574pub use nros_node::{LifecycleCallbackFn, LifecycleError, LifecyclePollingNode};
575
576/// Re-export of the full lifecycle module so examples can reach
577/// `LifecycleCallbackSlot`, `LifecyclePollingNodeCtx`, etc.
578pub mod lifecycle {
579    pub use nros_core::lifecycle::{LifecycleState, LifecycleTransition, TransitionResult};
580    pub use nros_node::lifecycle::*;
581}
582
583// Phase 128.G — bridge surface re-exports. Gated behind the
584// `bridge` / `config` umbrella features so single-backend builds
585// don't pull in `nros-bridge` (or, for `config`, the TOML stack).
586#[cfg(feature = "bridge")]
587pub use nros_bridge as bridge;
588
589#[cfg(feature = "config")]
590pub use nros_bridge::run_from_config;
591
592// Re-export parameter types
593pub use nros_params::{
594    MandatoryParameter, OptionalParameter, Parameter, ParameterBuilder, ParameterDescriptor,
595    ParameterError, ParameterServer, ParameterType, ParameterValue, ParameterVariant,
596    ReadOnlyParameter, SetParameterResult,
597};
598// Phase 172.H — runtime parameter-override persistence backends.
599/// Hosted file-backed parameter store (the only built-in backend today).
600#[cfg(feature = "std")]
601pub use nros_params::FileParamStore;
602pub use nros_params::{NullParamStore, ParamStore, ParamStoreError};
603
604/// Prelude module for convenient imports
605///
606/// Import everything you need with a single statement:
607/// ```
608/// use nros::prelude::*;
609/// ```
610pub mod prelude {
611    pub use crate::{
612        CdrReader, CdrWriter, Deserialize, Logger, MessageInfo, NodeConfig, PublisherHandle,
613        QosDurabilityPolicy, QosHistoryPolicy, QosReliabilityPolicy, QosSettings, RosMessage,
614        RosService, Serialize, StandaloneNode, SubscriberHandle, TopicInfo,
615    };
616
617    // Re-export component-mode API.
618    #[cfg(feature = "rmw-cffi")]
619    pub use crate::NodeExecutorRuntime;
620    #[cfg(feature = "std")]
621    pub use crate::SourceMetadataExport;
622    pub use crate::{
623        ActionTag, Callback, CallbackEffectKind, CallbackEffects, DeclaredNode,
624        DeclaredNodeRuntime, EntityKind, MetadataRecorder, Node, NodeActionClient,
625        NodeActionServer, NodeContext, NodeDeclError, NodeOptions, NodeParameter, NodePublisher,
626        NodeResult, NodeRuntime, NodeRuntimeAdapter, NodeServiceClient, NodeServiceServer,
627        NodeSubscription, NodeTimer, ParameterDefault, RuntimeNodeRecord, ServiceTag,
628        SourceLocationMetadata, SourceNameKind, SubscriptionTag, node, record_node_metadata,
629        register_node,
630    };
631
632    // Re-export lifecycle types
633    pub use crate::{
634        LifecycleCallbackFn, LifecycleError, LifecyclePollingNode, LifecycleState,
635        LifecycleTransition, TransitionResult,
636    };
637
638    // Re-export executor config + handle types (always available)
639    pub use crate::{
640        ExecutorConfig, GuardConditionHandle, HandleId, HandleSet, InvocationMode, NodeError,
641        SessionMode, SpinOnceResult, SpinOptions, SpinPeriodPollingResult, TransportError, Trigger,
642    };
643
644    // Re-export RMW-dependent executor + handle types
645    #[cfg(feature = "rmw-cffi")]
646    pub use crate::{
647        EmbeddedPublisher, EmbeddedServiceClient, Executor, FeedbackStream, NodeHandle, Promise,
648        Subscription,
649    };
650
651    // Publisher/Subscriber options (topic + QoS).
652    pub use crate::{PublisherOptions, SubscriberOptions};
653
654    #[cfg(all(feature = "std", feature = "rmw-cffi"))]
655    pub use crate::SpinPeriodResult;
656
657    // Re-export parameter types
658    pub use crate::{ParameterServer, ParameterType, ParameterValue};
659
660    // Re-export typed parameter API (rclrs-compatible builder pattern)
661    pub use crate::{
662        MandatoryParameter, OptionalParameter, ParameterBuilder, ParameterError, ParameterVariant,
663        ReadOnlyParameter,
664    };
665
666    // Re-export action types
667    pub use crate::{GoalId, GoalInfo, GoalResponse, GoalStatus, GoalStatusStamped, RosAction};
668
669    // Re-export Time, Duration, Clock from core
670    pub use nros_core::{Clock, ClockType, Duration, Time};
671
672    // Re-export timer types
673    pub use crate::{TimerCallbackFn, TimerDuration, TimerHandle, TimerMode};
674}
675
676/// Derive macros for message types
677///
678/// Use these macros to generate message serialization code.
679/// These macros help you create custom message types that are compatible
680/// with ROS 2's CDR serialization format.
681pub mod derive {
682    pub use nros_macros::RosMessage;
683}
684
685#[cfg(test)]
686mod tests {
687    #[test]
688    fn test_prelude_imports() {
689        // This test just verifies that the prelude compiles
690        use crate::prelude::*;
691
692        let _ = NodeConfig::new("test_node", "/");
693        let _ = QosSettings::BEST_EFFORT;
694    }
695
696    /// Verify the Node* canonical trait + context + result types
697    /// resolve after the Component→Node hard rename. The Component*
698    /// aliases were dropped in the same phase; their absence is
699    /// enforced by the workspace audit (no live `Component*` ident
700    /// remains in core / examples / tests).
701    #[test]
702    fn node_context_types_resolve() {
703        // Canonical "Node*" trait + context names (post-rename).
704        fn _take_node_ctx<N: crate::Node>(_: &mut crate::NodeContext<'_, dyn crate::NodeRuntime>) {}
705        // Result type resolves.
706        let _: crate::NodeResult<()> = Ok(());
707    }
708}