Skip to main content

nros_platform/board/
embassy_entry.rs

1//! [`EmbassyBoardEntry`] — Phase 216.C.1.
2//!
3//! Board-side hook for **Embassy** integration. The
4//! `nros::main!()` proc-macro (Phase 216.C.3) generates an
5//! `#[embassy_executor::main] async fn main(spawner: Spawner)` entry
6//! point that calls `<MyBoard as EmbassyBoardEntry>::init_hardware`
7//! from inside the framework-generated body and then spawns the
8//! returned dispatch [`NodeDispatchRuntime`] onto a long-lived
9//! Embassy task.
10//!
11//! ```ignore
12//! // Generated by `nros::main!()` for an Embassy board crate:
13//! #[embassy_executor::main]
14//! async fn main(spawner: embassy_executor::Spawner) {
15//!     let (exec, mut runtime) = <MyBoard as EmbassyBoardEntry>::init_hardware(spawner);
16//!     // … spawn dispatch / spin tasks against `runtime` …
17//! }
18//! ```
19//!
20//! ## Layering — why both `Spawner` and `Executor` are opaque
21//!
22//! `nros-platform` sits **below** `nros` and `embassy_executor` in
23//! the dep graph. Naming either type concretely here would invert
24//! the dep arrow:
25//!
26//! - `type Spawner: 'static;` — board crates plug in
27//!   `embassy_executor::Spawner` at impl-time; `nros-platform`
28//!   stays free of an `embassy_executor` dep.
29//! - `type Executor: 'static;` — board crates plug in
30//!   `nros::Executor`; `nros-platform` stays free of an `nros` dep
31//!   (which would cycle, since `nros` already depends on
32//!   `nros-platform`).
33//!
34//! ## Sync `init_hardware`
35//!
36//! The Phase 216.C spec sketch showed `async fn init_hardware(...)`,
37//! but the parallel `RticBoardEntry` (Phase 216.B.1) is sync. We
38//! match that shape so the Phase 216.B.3 / 216.C.3 macro routing
39//! stays symmetrical. A board crate that needs to await before
40//! handing back its `(Executor, Runtime)` pair can spawn an internal
41//! init task from inside `init_hardware` and have that task drive
42//! the async work — Embassy's `Spawner::spawn` is sync.
43//!
44//! If a real Embassy bring-up forces async at the trait boundary,
45//! lift to `async fn init_hardware` in a follow-up; edition 2024
46//! supports `async fn` in traits, with the usual `dyn`-incompat
47//! caveat that `EmbassyBoardEntry` would inherit (it's not
48//! object-safe today and isn't expected to need to be).
49
50use super::{Board, runtime::NodeDispatchRuntime};
51
52/// Board-side hook for Embassy integration (Phase 216.C.1).
53///
54/// Implementations live in per-board Embassy crates such as
55/// `nros-board-embassy-stm32f4` (Phase 216.C.2). Each impl wires
56/// the Embassy [`Self::Spawner`] handle through to the board's
57/// peripheral init code and hands back the
58/// `(Self::Executor, Self::Runtime)` pair the
59/// proc-macro-generated entry point then drives.
60pub trait EmbassyBoardEntry: Board {
61    /// Embassy spawner handle. Typically `embassy_executor::Spawner`
62    /// on real boards; kept abstract so `nros-platform` doesn't take
63    /// a transitive `embassy_executor` dep (see the module-level
64    /// "Layering" note).
65    type Spawner: 'static;
66
67    /// Executor type the board hands back. Concrete board impls plug
68    /// in `nros::Executor`; the assoc type keeps `nros-platform`
69    /// free of an `nros` dep.
70    type Executor: 'static;
71
72    /// Dispatch sink the macro spawns into an Embassy task. Required
73    /// to be a [`NodeDispatchRuntime`] so signaled callbacks can be
74    /// enqueued from the spawner-driven dispatch task.
75    type Runtime: NodeDispatchRuntime + 'static;
76
77    /// Capacity of the per-board `embassy_sync::channel::Channel`
78    /// used by `signal_callback` to enqueue callbacks for the
79    /// dispatch task. 32 covers typical Node loads without
80    /// exhausting RAM; boards with very high callback density
81    /// override.
82    const CHANNEL_CAPACITY: usize = 32;
83
84    /// Run from inside the proc-macro-generated
85    /// `#[embassy_executor::main]` body. Returns the
86    /// `(Executor, Runtime)` pair the macro then spawns dispatch /
87    /// spin tasks against.
88    ///
89    /// Sync on purpose — see the module-level "Sync
90    /// `init_hardware`" note for the trade-off vs the spec's
91    /// `async fn` sketch.
92    fn init_hardware(spawner: Self::Spawner) -> (Self::Executor, Self::Runtime);
93}