ove/macros.rs
1// Copyright (C) 2026 Kamil Lulko <kamil.lulko@gmail.com>
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4//
5// This file is part of oveRTOS.
6
7//! User-facing macros for the oveRTOS Rust SDK.
8//!
9//! All `#[macro_export]` macros are re-exported at crate root, so the
10//! location of this file is purely organizational.
11
12/// Generate `Drop` and thread-safety impls (plus `Debug` for the
13/// non-generic forms) for an oveRTOS handle wrapper struct that stores a
14/// nullable handle in a field named `handle`.
15///
16/// # Variants
17///
18/// - `(Name, destroy_fn, deinit_fn)` — emits `Debug`, `Drop`, `Send`, `Sync`.
19/// - `(Name, destroy_fn, deinit_fn, send_only)` — emits `Debug`, `Drop`,
20/// `Send`. No `Sync` impl.
21/// - `(Name<const N: usize>, destroy_fn, deinit_fn)` — const-generic;
22/// emits `Drop`, `Send`, `Sync` only (no `Debug`).
23#[doc(hidden)]
24#[macro_export]
25macro_rules! ove_handle_impl {
26 // Non-generic, Send + Sync
27 ($name:ident, $destroy:ident, $deinit:ident) => {
28 $crate::ove_handle_impl!(@debug $name);
29 $crate::ove_handle_impl!(@drop $name, $destroy, $deinit);
30 unsafe impl Send for $name {}
31 unsafe impl Sync for $name {}
32 };
33
34 // Non-generic, Send only (no Sync)
35 ($name:ident, $destroy:ident, $deinit:ident, send_only) => {
36 $crate::ove_handle_impl!(@debug $name);
37 $crate::ove_handle_impl!(@drop $name, $destroy, $deinit);
38 unsafe impl Send for $name {}
39 };
40
41 // Const-generic, Send + Sync
42 ($name:ident<const $N:ident: usize>, $destroy:ident, $deinit:ident) => {
43 impl<const $N: usize> Drop for $name<$N> {
44 fn drop(&mut self) {
45 if self.handle.is_null() { return; }
46 #[cfg(not(zero_heap))]
47 unsafe { $crate::bindings::$destroy(self.handle) }
48 #[cfg(zero_heap)]
49 unsafe { $crate::bindings::$deinit(self.handle) }
50 }
51 }
52 unsafe impl<const $N: usize> Send for $name<$N> {}
53 unsafe impl<const $N: usize> Sync for $name<$N> {}
54 };
55
56 (@debug $name:ident) => {
57 impl core::fmt::Debug for $name {
58 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
59 f.debug_struct(stringify!($name))
60 .field("handle", &format_args!("{:p}", self.handle))
61 .finish()
62 }
63 }
64 };
65
66 (@drop $name:ident, $destroy:ident, $deinit:ident) => {
67 impl Drop for $name {
68 fn drop(&mut self) {
69 if self.handle.is_null() { return; }
70 #[cfg(not(zero_heap))]
71 unsafe { $crate::bindings::$destroy(self.handle) }
72 #[cfg(zero_heap)]
73 unsafe { $crate::bindings::$deinit(self.handle) }
74 }
75 }
76 };
77}
78
79// Legacy `log_fmt!`/`log_inf!`/`log_wrn!`/`log_err!` macros deleted in
80// favour of the `log` crate facade. See `ove::log` for the `log::Log`
81// backend; call `ove::log::init()` once in `ove_main`, then use
82// `log::info!`/`warn!`/`error!`/`debug!`/`trace!` like any other Rust
83// crate.
84
85/// Declare a safe accessor for TFLite model data generated by `convert.py`.
86///
87/// Generates the `extern "C"` linkage for the `_model_data` and
88/// `_model_data_len` symbols and wraps them in a function returning
89/// `&'static [u8]`. No `extern` or `unsafe` needed in application code.
90///
91/// # Example
92///
93/// ```ignore
94/// ove::model_data!(preprocessor_model,
95/// g_audio_preprocessor_int8_model_data,
96/// g_audio_preprocessor_int8_model_data_len);
97///
98/// let data: &[u8] = preprocessor_model();
99/// ```
100#[macro_export]
101macro_rules! model_data {
102 ($fn_name:ident, $data_sym:ident, $len_sym:ident) => {
103 unsafe extern "C" {
104 safe static $data_sym: u8;
105 safe static $len_sym: u32;
106 }
107 fn $fn_name() -> &'static [u8] {
108 unsafe { core::slice::from_raw_parts(&$data_sym, $len_sym as usize) }
109 }
110 };
111}
112
113// Legacy `ove::main!(fn_name)` declarative macro deleted in favour of
114// the `#[ove::main]` proc-macro attribute provided by the `ove-macros`
115// crate (re-exported at the crate root in `lib.rs`).
116//
117// Migration:
118// fn app_main() { ... }
119// ove::main!(app_main);
120// becomes
121// #[ove::main]
122// fn app_main() { ... }
123//
124// Lifetime caveat (same for both forms): anything worker threads
125// access after `app_main` returns must have `'static` storage — use
126// `static`/`InitCell`/`InitMut` cells. A stack-local value in
127// `app_main` is dropped when the function unwinds; a thread that
128// captured a reference into it dangles. On FreeRTOS the failure is
129// immediate (scheduler reclaims the main stack); on POSIX/NuttX/Zephyr
130// the UB is latent.
131
132// ---------------------------------------------------------------------------
133// Unified creation macros
134//
135// These macros work in both heap and zero-heap modes. In heap mode they call
136// `new()`. In zero-heap mode they declare function-local `static` storage
137// and call `from_static()`.
138// ---------------------------------------------------------------------------
139
140/// Create a [`crate::Mutex<T>`] around `$val` that works in both heap and
141/// zero-heap modes.
142///
143/// Use `()` as the value for a "no-data" lock (i.e. `Mutex<()>` used
144/// purely to synchronise access to other state) — matches
145/// `std::sync::Mutex<()>` / `parking_lot::Mutex<()>` convention.
146#[cfg(has_sync)]
147#[macro_export]
148macro_rules! mutex {
149 ($val:expr) => {{
150 #[cfg(not(zero_heap))]
151 {
152 $crate::Mutex::new($val).unwrap()
153 }
154 #[cfg(zero_heap)]
155 {
156 static mut _S: $crate::ffi::ove_mutex_storage_t = unsafe { core::mem::zeroed() };
157 unsafe { $crate::Mutex::from_static(core::ptr::addr_of_mut!(_S), $val) }.unwrap()
158 }
159 }};
160}
161
162/// Create a [`crate::RecursiveMutex`] that works in both heap and zero-heap modes.
163#[cfg(has_sync)]
164#[macro_export]
165macro_rules! recursive_mutex {
166 () => {{
167 #[cfg(not(zero_heap))]
168 {
169 $crate::RecursiveMutex::new().unwrap()
170 }
171 #[cfg(zero_heap)]
172 {
173 static mut _S: $crate::ffi::ove_mutex_storage_t = unsafe { core::mem::zeroed() };
174 unsafe { $crate::RecursiveMutex::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
175 }
176 }};
177}
178
179/// Create a [`crate::Semaphore`] that works in both heap and zero-heap modes.
180#[cfg(has_sync)]
181#[macro_export]
182macro_rules! semaphore {
183 ($initial:expr, $max:expr) => {{
184 #[cfg(not(zero_heap))]
185 {
186 $crate::Semaphore::new($initial, $max).unwrap()
187 }
188 #[cfg(zero_heap)]
189 {
190 static mut _S: $crate::ffi::ove_sem_storage_t = unsafe { core::mem::zeroed() };
191 unsafe { $crate::Semaphore::from_static(core::ptr::addr_of_mut!(_S), $initial, $max) }
192 .unwrap()
193 }
194 }};
195}
196
197/// Create an [`crate::Event`] that works in both heap and zero-heap modes.
198#[cfg(has_sync)]
199#[macro_export]
200macro_rules! event {
201 () => {{
202 #[cfg(not(zero_heap))]
203 {
204 $crate::Event::new().unwrap()
205 }
206 #[cfg(zero_heap)]
207 {
208 static mut _S: $crate::ffi::ove_event_storage_t = unsafe { core::mem::zeroed() };
209 unsafe { $crate::Event::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
210 }
211 }};
212}
213
214/// Create a [`crate::CondVar`] that works in both heap and zero-heap modes.
215#[cfg(has_sync)]
216#[macro_export]
217macro_rules! condvar {
218 () => {{
219 #[cfg(not(zero_heap))]
220 {
221 $crate::CondVar::new().unwrap()
222 }
223 #[cfg(zero_heap)]
224 {
225 static mut _S: $crate::ffi::ove_condvar_storage_t = unsafe { core::mem::zeroed() };
226 unsafe { $crate::CondVar::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
227 }
228 }};
229}
230
231/// Create an [`crate::EventGroup`] that works in both heap and zero-heap modes.
232#[cfg(has_eventgroup)]
233#[macro_export]
234macro_rules! eventgroup {
235 () => {{
236 #[cfg(not(zero_heap))]
237 {
238 $crate::EventGroup::new().unwrap()
239 }
240 #[cfg(zero_heap)]
241 {
242 static mut _S: $crate::ffi::ove_eventgroup_storage_t = unsafe { core::mem::zeroed() };
243 unsafe { $crate::EventGroup::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
244 }
245 }};
246}
247
248/// Create a [`crate::audio::Graph`] that works in both heap and zero-heap modes.
249///
250/// Mirrors the C `ove_audio_graph_create(pg, frames, nodes, channels,
251/// sample_bytes)` macro: in zero-heap mode emits a per-call-site `static`
252/// backing array sized by `nodes * frames * channels * sample_bytes` bytes
253/// and attaches it to the graph automatically; in heap mode just calls
254/// [`crate::audio::Graph::new`]. Yields a [`crate::Result`] wrapping a
255/// [`crate::audio::Graph`].
256///
257/// All arguments must be constant expressions.
258///
259/// # Example
260/// ```ignore
261/// // 2 non-sink nodes (source + processor), 512 frames, mono, S16 (2 bytes).
262/// let mut graph = ove::audio_graph!(512, 2, 1, 2)?;
263/// ```
264#[cfg(has_audio)]
265#[macro_export]
266macro_rules! audio_graph {
267 ($frames:expr, $nodes:expr, $channels:expr, $sample_bytes:expr) => {{
268 #[cfg(not(zero_heap))]
269 {
270 $crate::audio::Graph::new($frames)
271 }
272 #[cfg(zero_heap)]
273 {
274 const _STORAGE_BYTES: usize =
275 ($nodes) * ($frames) as usize * ($channels) * ($sample_bytes);
276 #[repr(C, align(4))]
277 struct _AudioBufStorage([u8; _STORAGE_BYTES]);
278 static mut _AUDIO_BUF: _AudioBufStorage = _AudioBufStorage([0; _STORAGE_BYTES]);
279 let slice: &'static mut [u8] = unsafe {
280 core::slice::from_raw_parts_mut(
281 core::ptr::addr_of_mut!(_AUDIO_BUF) as *mut u8,
282 _STORAGE_BYTES,
283 )
284 };
285 $crate::audio::Graph::new_with_storage($frames, slice)
286 }
287 }};
288}
289
290/// Create a [`crate::Queue`] that works in both heap and zero-heap modes.
291///
292/// # Example
293/// ```ignore
294/// let q = ove::queue!(u32, 8);
295/// ```
296#[cfg(has_queue)]
297#[macro_export]
298macro_rules! queue {
299 ($T:ty, $N:expr) => {{
300 #[cfg(not(zero_heap))]
301 {
302 $crate::Queue::<$T, $N>::new().unwrap()
303 }
304 #[cfg(zero_heap)]
305 {
306 static mut _S: $crate::ffi::ove_queue_storage_t = unsafe { core::mem::zeroed() };
307 static mut _B: [core::mem::MaybeUninit<$T>; $N] =
308 unsafe { core::mem::MaybeUninit::uninit().assume_init() };
309 unsafe {
310 $crate::Queue::<$T, $N>::from_static(
311 core::ptr::addr_of_mut!(_S),
312 core::ptr::addr_of_mut!(_B) as *mut _,
313 )
314 }
315 .unwrap()
316 }
317 }};
318}
319
320/// Create a [`crate::Timer`] that works in both heap and zero-heap modes.
321///
322/// # Example
323/// ```ignore
324/// let t = ove::timer!(my_callback, 100, false);
325/// ```
326#[cfg(has_timer)]
327#[macro_export]
328macro_rules! timer {
329 ($callback:expr, $period_ms:expr, $one_shot:expr) => {{
330 #[cfg(not(zero_heap))]
331 {
332 $crate::Timer::new($callback, $period_ms, $one_shot).unwrap()
333 }
334 #[cfg(zero_heap)]
335 {
336 static mut _S: $crate::ffi::ove_timer_storage_t = unsafe { core::mem::zeroed() };
337 unsafe {
338 $crate::Timer::from_static(
339 core::ptr::addr_of_mut!(_S),
340 $callback,
341 $period_ms,
342 $one_shot,
343 )
344 }
345 .unwrap()
346 }
347 }};
348}
349
350/// Create a [`crate::JoinHandle`] for a thread that works in both heap
351/// and zero-heap modes.
352///
353/// Uses the safe `fn()` entry pattern (no `StopToken`). The name is
354/// automatically null-terminated. In zero-heap mode the storage is
355/// emitted as a `static $crate::ThreadStorage<$stack>` and passed by
356/// reference to [`crate::Builder::spawn_static_simple`] — no `static mut`,
357/// no `addr_of_mut!`, no `unsafe` in user-visible expansion.
358///
359/// # Example
360/// ```ignore
361/// let h = ove::thread!("worker", my_entry, Priority::Normal, 4096);
362/// h.detach(); // fire-and-forget; or store in InitCell<JoinHandle<()>>.
363/// ```
364#[macro_export]
365macro_rules! thread {
366 ($name:expr, $entry:expr, $prio:expr, $stack:expr) => {{
367 // SAFETY: `concat!($name, "\0")` is a comptime literal that ends
368 // with a single nul; the caller's $name must not contain inner
369 // nuls (responsibility of the macro caller — same as today).
370 let _name = unsafe {
371 core::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes())
372 };
373 #[cfg(not(zero_heap))]
374 {
375 $crate::Thread::builder()
376 .name(_name)
377 .priority($prio)
378 .stack_size($stack)
379 .spawn_simple($entry)
380 .unwrap()
381 }
382 #[cfg(zero_heap)]
383 {
384 static _STORAGE: $crate::ThreadStorage<$stack> = $crate::ThreadStorage::new();
385 $crate::Thread::builder()
386 .name(_name)
387 .priority($prio)
388 .spawn_static_simple(&_STORAGE, $entry)
389 .unwrap()
390 }
391 }};
392}
393
394/// Create a [`crate::Stream`] that works in both heap and zero-heap modes.
395///
396/// # Example
397/// ```ignore
398/// let s = ove::stream!(256, 1);
399/// ```
400#[cfg(has_stream)]
401#[macro_export]
402macro_rules! stream {
403 ($N:expr, $trigger:expr) => {{
404 #[cfg(not(zero_heap))]
405 {
406 $crate::Stream::<$N>::new($trigger).unwrap()
407 }
408 #[cfg(zero_heap)]
409 {
410 static mut _S: $crate::ffi::ove_stream_storage_t = unsafe { core::mem::zeroed() };
411 static mut _B: [u8; $N] = [0u8; $N];
412 unsafe {
413 $crate::Stream::<$N>::from_static(
414 core::ptr::addr_of_mut!(_S),
415 core::ptr::addr_of_mut!(_B) as *mut _,
416 $trigger,
417 )
418 }
419 .unwrap()
420 }
421 }};
422}
423
424/// Create a [`crate::Workqueue`] that works in both heap and zero-heap modes.
425///
426/// `$name` must be a `c"..."` literal — the underlying API requires a
427/// null-terminated string and the C-string literal makes the compiler
428/// enforce that.
429///
430/// # Example
431/// ```ignore
432/// let wq = ove::workqueue!(c"myq", Priority::Normal, 4096);
433/// ```
434#[cfg(has_workqueue)]
435#[macro_export]
436macro_rules! workqueue {
437 ($name:expr, $prio:expr, $stack:expr) => {{
438 #[cfg(not(zero_heap))]
439 {
440 $crate::Workqueue::new($name, $prio, $stack).unwrap()
441 }
442 #[cfg(all(zero_heap, not(rtos_zephyr)))]
443 {
444 static mut _S: $crate::ffi::ove_workqueue_storage_t = unsafe { core::mem::zeroed() };
445 // 8-byte align (ARM AAPCS) is sufficient on FreeRTOS/NuttX/POSIX.
446 #[repr(C, align(8))]
447 struct AlignedStack([u8; $stack]);
448 static mut _STACK: AlignedStack = AlignedStack([0u8; $stack]);
449 unsafe {
450 $crate::Workqueue::from_static(
451 core::ptr::addr_of_mut!(_S),
452 $name,
453 $prio,
454 $stack,
455 core::ptr::addr_of_mut!(_STACK) as *mut _,
456 )
457 }
458 .unwrap()
459 }
460 #[cfg(all(zero_heap, rtos_zephyr))]
461 {
462 // Zephyr workqueues run on a kernel thread; the stack needs
463 // the same MPU treatment as `thread!` — power-of-2 aligned
464 // and rounded up by a 128-byte FPU guard pad. Without
465 // this, the workqueue crashes inside `z_reset_time_slice`
466 // on the first work-handler dispatch.
467 static mut _S: $crate::ffi::ove_workqueue_storage_t = unsafe { core::mem::zeroed() };
468 const _STACK_TOTAL: usize = ($stack + 128usize).next_power_of_two();
469 #[repr(C, align(8192))]
470 struct ZStack([u8; _STACK_TOTAL]);
471 static mut _STACK: ZStack = ZStack([0u8; _STACK_TOTAL]);
472 unsafe {
473 $crate::Workqueue::from_static(
474 core::ptr::addr_of_mut!(_S),
475 $name,
476 $prio,
477 $stack,
478 core::ptr::addr_of_mut!(_STACK) as *mut _,
479 )
480 }
481 .unwrap()
482 }
483 }};
484}
485
486/// Wrap a safe Rust `fn()` into an `ove_work_fn` C trampoline.
487///
488/// The oveRTOS C work handler has no `user_data` slot, so this just
489/// generates a `unsafe extern "C"` trampoline that calls the supplied
490/// safe function. Use this with [`crate::work!`] so app code never has to write
491/// `unsafe extern "C"` itself.
492///
493/// # Example
494/// ```ignore
495/// fn on_work() { /* ... */ }
496/// let w = ove::work!(ove::work_handler!(on_work));
497/// ```
498#[cfg(has_workqueue)]
499#[macro_export]
500macro_rules! work_handler {
501 ($f:ident) => {{
502 unsafe extern "C" fn _tramp(_w: $crate::ffi::ove_work_t) {
503 $f();
504 }
505 Some(_tramp as unsafe extern "C" fn($crate::ffi::ove_work_t))
506 }};
507}
508
509/// Create a [`crate::Work`] item that works in both heap and zero-heap modes.
510///
511/// The handler must be an `ove_work_fn` (an `unsafe extern "C" fn`). To
512/// avoid writing that yourself, wrap a safe `fn()` with [`work_handler!`].
513#[cfg(has_workqueue)]
514#[macro_export]
515macro_rules! work {
516 ($handler:expr) => {{
517 #[cfg(not(zero_heap))]
518 {
519 $crate::Work::new($handler).unwrap()
520 }
521 #[cfg(zero_heap)]
522 {
523 static mut _S: $crate::ffi::ove_work_storage_t = unsafe { core::mem::zeroed() };
524 unsafe { $crate::Work::from_static(core::ptr::addr_of_mut!(_S), $handler) }.unwrap()
525 }
526 }};
527}
528
529/// Create a [`crate::Watchdog`] that works in both heap and zero-heap modes.
530///
531/// The argument is the timeout in **milliseconds**, matching
532/// [`crate::Watchdog::new`].
533#[cfg(has_watchdog)]
534#[macro_export]
535macro_rules! watchdog {
536 ($timeout_ns:expr) => {{
537 #[cfg(not(zero_heap))]
538 {
539 $crate::Watchdog::new($timeout_ns).unwrap()
540 }
541 #[cfg(zero_heap)]
542 {
543 static mut _S: $crate::ffi::ove_watchdog_storage_t = unsafe { core::mem::zeroed() };
544 unsafe { $crate::Watchdog::from_static(core::ptr::addr_of_mut!(_S), $timeout_ns) }
545 .unwrap()
546 }
547 }};
548}
549
550/// Declare a `static` wrapped in [`crate::InitCell`] for cross-thread shared state.
551///
552/// # Example
553/// ```ignore
554/// ove::shared!(QUEUE: Queue<u32, 8>);
555/// // then use QUEUE.send(...) directly — Deref eliminates .get()
556/// ```
557#[macro_export]
558macro_rules! shared {
559 ($vis:vis $name:ident : $ty:ty) => {
560 $vis static $name: $crate::InitCell<$ty> = $crate::InitCell::new();
561 };
562}
563
564/// Declare a `static` wrapped in [`crate::InitMut`] for single-owner mutable state.
565///
566/// # Example
567/// ```ignore
568/// ove::shared_mut!(ENGINE: DspEngine);
569/// ```
570#[macro_export]
571macro_rules! shared_mut {
572 ($vis:vis $name:ident : $ty:ty) => {
573 $vis static $name: $crate::InitMut<$ty> = $crate::InitMut::new();
574 };
575}
576
577/// Declare a `static` [`crate::lvgl::EventHandler<T>`] for LVGL event callbacks.
578///
579/// Bundles a `&'static InitCell<T>` of shared state with a safe
580/// `fn(&T, EventCtx)` callback. Pass the resulting static to
581/// [`EventTarget::on_with`](crate::lvgl::EventTarget::on_with) or
582/// [`EventTarget::on_clicked_with`](crate::lvgl::EventTarget::on_clicked_with).
583///
584/// # Example
585/// ```ignore
586/// struct NavState { page: i32 }
587/// ove::shared!(NAV: ove::lvgl::LvCell<NavState>);
588/// fn on_next(state: &ove::lvgl::LvCell<NavState>, _e: ove::lvgl::EventCtx<'_>) {
589/// state.update(|s| NavState { page: s.page + 1 });
590/// }
591/// ove::event_handler!(NAV_NEXT: LvCell<NavState> = &NAV, on_next);
592/// // ...later: button.on_clicked_with(&NAV_NEXT);
593/// ```
594#[cfg(has_lvgl)]
595#[macro_export]
596macro_rules! event_handler {
597 ($vis:vis $name:ident : $t:ty = $cell:expr, $fn:expr) => {
598 $vis static $name: $crate::lvgl::EventHandler<$t> =
599 $crate::lvgl::EventHandler::new($cell, $fn);
600 };
601}
602
603/// Create a [`crate::net_tls::Session`] that works in both heap and zero-heap
604/// modes. In zero-heap mode the session draws its mbedTLS state from a
605/// file-scope static and the per-protocol static buffer
606/// (`CONFIG_OVE_NET_TLS_HEAP_SIZE`).
607#[cfg(has_net_tls)]
608#[macro_export]
609macro_rules! tls_session {
610 () => {{
611 #[cfg(not(zero_heap))]
612 {
613 $crate::net_tls::Session::new().unwrap()
614 }
615 #[cfg(zero_heap)]
616 {
617 static mut _S: $crate::ffi::ove_tls_storage_t = unsafe { core::mem::zeroed() };
618 unsafe { $crate::net_tls::Session::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
619 }
620 }};
621}
622
623/// Create a [`crate::net_http::Client`] that works in both heap and zero-heap
624/// modes. In zero-heap mode the response body and headers borrow into the
625/// client's embedded `_resp_buf[CONFIG_OVE_NET_HTTP_MAX_RESPONSE]` and are
626/// valid until the next request.
627#[cfg(has_net_http)]
628#[macro_export]
629macro_rules! http_client {
630 () => {{
631 #[cfg(not(zero_heap))]
632 {
633 $crate::net_http::Client::new().unwrap()
634 }
635 #[cfg(zero_heap)]
636 {
637 static mut _S: $crate::ffi::ove_http_client_storage_t = unsafe { core::mem::zeroed() };
638 unsafe { $crate::net_http::Client::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
639 }
640 }};
641}
642
643/// Create a [`crate::net_mqtt::Client`] that works in both heap and zero-heap
644/// modes. In zero-heap mode the per-connection RX/TX buffers
645/// (`CONFIG_OVE_NET_MQTT_RX_BUF` / `CONFIG_OVE_NET_MQTT_TX_BUF`) live in the
646/// client storage.
647#[cfg(has_net_mqtt)]
648#[macro_export]
649macro_rules! mqtt_client {
650 () => {{
651 #[cfg(not(zero_heap))]
652 {
653 $crate::net_mqtt::Client::new().unwrap()
654 }
655 #[cfg(zero_heap)]
656 {
657 static mut _S: $crate::ffi::ove_mqtt_client_storage_t = unsafe { core::mem::zeroed() };
658 unsafe { $crate::net_mqtt::Client::from_static(core::ptr::addr_of_mut!(_S)) }.unwrap()
659 }
660 }};
661}