aboutsummaryrefslogtreecommitdiff
path: root/embassy-lora
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-11-29 16:26:31 +0100
committerDario Nieuwenhuis <[email protected]>2023-11-29 16:26:31 +0100
commit1b9925e3da0ec42b770f736cd22325203c97cb47 (patch)
tree2b03eb826cd9a9fce945921cff9c04a7e59f98f1 /embassy-lora
parentb4bc9ac028568dfb3896dfb00cbd1e181863fd64 (diff)
Move embassy-lora, lora examples to lora-phy repo.
Diffstat (limited to 'embassy-lora')
-rw-r--r--embassy-lora/Cargo.toml31
-rw-r--r--embassy-lora/src/fmt.rs258
-rw-r--r--embassy-lora/src/iv.rs317
-rw-r--r--embassy-lora/src/lib.rs40
4 files changed, 0 insertions, 646 deletions
diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml
deleted file mode 100644
index f2ba379b2..000000000
--- a/embassy-lora/Cargo.toml
+++ /dev/null
@@ -1,31 +0,0 @@
1[package]
2name = "embassy-lora"
3version = "0.1.0"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6
7[package.metadata.embassy_docs]
8src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/"
9src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/"
10features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/unstable-pac", "time", "defmt"]
11target = "thumbv7em-none-eabi"
12
13[features]
14stm32wl = ["dep:embassy-stm32"]
15time = ["embassy-time", "lorawan-device"]
16defmt = ["dep:defmt", "lorawan-device/defmt"]
17
18[dependencies]
19
20defmt = { version = "0.3", optional = true }
21log = { version = "0.4.14", optional = true }
22
23embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true }
24embassy-sync = { version = "0.4.0", path = "../embassy-sync" }
25embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true }
26embedded-hal-async = { version = "=1.0.0-rc.1" }
27embedded-hal = { version = "0.2", features = ["unproven"] }
28
29futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] }
30lora-phy = { version = "2" }
31lorawan-device = { version = "0.11.0", default-features = false, features = ["async"], optional = true }
diff --git a/embassy-lora/src/fmt.rs b/embassy-lora/src/fmt.rs
deleted file mode 100644
index 78e583c1c..000000000
--- a/embassy-lora/src/fmt.rs
+++ /dev/null
@@ -1,258 +0,0 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9macro_rules! assert {
10 ($($x:tt)*) => {
11 {
12 #[cfg(not(feature = "defmt"))]
13 ::core::assert!($($x)*);
14 #[cfg(feature = "defmt")]
15 ::defmt::assert!($($x)*);
16 }
17 };
18}
19
20macro_rules! assert_eq {
21 ($($x:tt)*) => {
22 {
23 #[cfg(not(feature = "defmt"))]
24 ::core::assert_eq!($($x)*);
25 #[cfg(feature = "defmt")]
26 ::defmt::assert_eq!($($x)*);
27 }
28 };
29}
30
31macro_rules! assert_ne {
32 ($($x:tt)*) => {
33 {
34 #[cfg(not(feature = "defmt"))]
35 ::core::assert_ne!($($x)*);
36 #[cfg(feature = "defmt")]
37 ::defmt::assert_ne!($($x)*);
38 }
39 };
40}
41
42macro_rules! debug_assert {
43 ($($x:tt)*) => {
44 {
45 #[cfg(not(feature = "defmt"))]
46 ::core::debug_assert!($($x)*);
47 #[cfg(feature = "defmt")]
48 ::defmt::debug_assert!($($x)*);
49 }
50 };
51}
52
53macro_rules! debug_assert_eq {
54 ($($x:tt)*) => {
55 {
56 #[cfg(not(feature = "defmt"))]
57 ::core::debug_assert_eq!($($x)*);
58 #[cfg(feature = "defmt")]
59 ::defmt::debug_assert_eq!($($x)*);
60 }
61 };
62}
63
64macro_rules! debug_assert_ne {
65 ($($x:tt)*) => {
66 {
67 #[cfg(not(feature = "defmt"))]
68 ::core::debug_assert_ne!($($x)*);
69 #[cfg(feature = "defmt")]
70 ::defmt::debug_assert_ne!($($x)*);
71 }
72 };
73}
74
75macro_rules! todo {
76 ($($x:tt)*) => {
77 {
78 #[cfg(not(feature = "defmt"))]
79 ::core::todo!($($x)*);
80 #[cfg(feature = "defmt")]
81 ::defmt::todo!($($x)*);
82 }
83 };
84}
85
86#[cfg(not(feature = "defmt"))]
87macro_rules! unreachable {
88 ($($x:tt)*) => {
89 ::core::unreachable!($($x)*)
90 };
91}
92
93#[cfg(feature = "defmt")]
94macro_rules! unreachable {
95 ($($x:tt)*) => {
96 ::defmt::unreachable!($($x)*)
97 };
98}
99
100macro_rules! panic {
101 ($($x:tt)*) => {
102 {
103 #[cfg(not(feature = "defmt"))]
104 ::core::panic!($($x)*);
105 #[cfg(feature = "defmt")]
106 ::defmt::panic!($($x)*);
107 }
108 };
109}
110
111macro_rules! trace {
112 ($s:literal $(, $x:expr)* $(,)?) => {
113 {
114 #[cfg(feature = "log")]
115 ::log::trace!($s $(, $x)*);
116 #[cfg(feature = "defmt")]
117 ::defmt::trace!($s $(, $x)*);
118 #[cfg(not(any(feature = "log", feature="defmt")))]
119 let _ = ($( & $x ),*);
120 }
121 };
122}
123
124macro_rules! debug {
125 ($s:literal $(, $x:expr)* $(,)?) => {
126 {
127 #[cfg(feature = "log")]
128 ::log::debug!($s $(, $x)*);
129 #[cfg(feature = "defmt")]
130 ::defmt::debug!($s $(, $x)*);
131 #[cfg(not(any(feature = "log", feature="defmt")))]
132 let _ = ($( & $x ),*);
133 }
134 };
135}
136
137macro_rules! info {
138 ($s:literal $(, $x:expr)* $(,)?) => {
139 {
140 #[cfg(feature = "log")]
141 ::log::info!($s $(, $x)*);
142 #[cfg(feature = "defmt")]
143 ::defmt::info!($s $(, $x)*);
144 #[cfg(not(any(feature = "log", feature="defmt")))]
145 let _ = ($( & $x ),*);
146 }
147 };
148}
149
150macro_rules! warn {
151 ($s:literal $(, $x:expr)* $(,)?) => {
152 {
153 #[cfg(feature = "log")]
154 ::log::warn!($s $(, $x)*);
155 #[cfg(feature = "defmt")]
156 ::defmt::warn!($s $(, $x)*);
157 #[cfg(not(any(feature = "log", feature="defmt")))]
158 let _ = ($( & $x ),*);
159 }
160 };
161}
162
163macro_rules! error {
164 ($s:literal $(, $x:expr)* $(,)?) => {
165 {
166 #[cfg(feature = "log")]
167 ::log::error!($s $(, $x)*);
168 #[cfg(feature = "defmt")]
169 ::defmt::error!($s $(, $x)*);
170 #[cfg(not(any(feature = "log", feature="defmt")))]
171 let _ = ($( & $x ),*);
172 }
173 };
174}
175
176#[cfg(feature = "defmt")]
177macro_rules! unwrap {
178 ($($x:tt)*) => {
179 ::defmt::unwrap!($($x)*)
180 };
181}
182
183#[cfg(not(feature = "defmt"))]
184macro_rules! unwrap {
185 ($arg:expr) => {
186 match $crate::fmt::Try::into_result($arg) {
187 ::core::result::Result::Ok(t) => t,
188 ::core::result::Result::Err(e) => {
189 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
190 }
191 }
192 };
193 ($arg:expr, $($msg:expr),+ $(,)? ) => {
194 match $crate::fmt::Try::into_result($arg) {
195 ::core::result::Result::Ok(t) => t,
196 ::core::result::Result::Err(e) => {
197 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204pub struct NoneError;
205
206pub trait Try {
207 type Ok;
208 type Error;
209 fn into_result(self) -> Result<Self::Ok, Self::Error>;
210}
211
212impl<T> Try for Option<T> {
213 type Ok = T;
214 type Error = NoneError;
215
216 #[inline]
217 fn into_result(self) -> Result<T, NoneError> {
218 self.ok_or(NoneError)
219 }
220}
221
222impl<T, E> Try for Result<T, E> {
223 type Ok = T;
224 type Error = E;
225
226 #[inline]
227 fn into_result(self) -> Self {
228 self
229 }
230}
231
232#[allow(unused)]
233pub(crate) struct Bytes<'a>(pub &'a [u8]);
234
235impl<'a> Debug for Bytes<'a> {
236 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237 write!(f, "{:#02x?}", self.0)
238 }
239}
240
241impl<'a> Display for Bytes<'a> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 write!(f, "{:#02x?}", self.0)
244 }
245}
246
247impl<'a> LowerHex for Bytes<'a> {
248 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249 write!(f, "{:#02x?}", self.0)
250 }
251}
252
253#[cfg(feature = "defmt")]
254impl<'a> defmt::Format for Bytes<'a> {
255 fn format(&self, fmt: defmt::Formatter) {
256 defmt::write!(fmt, "{:02x}", self.0)
257 }
258}
diff --git a/embassy-lora/src/iv.rs b/embassy-lora/src/iv.rs
deleted file mode 100644
index d22beb337..000000000
--- a/embassy-lora/src/iv.rs
+++ /dev/null
@@ -1,317 +0,0 @@
1#[cfg(feature = "stm32wl")]
2use embassy_stm32::interrupt;
3#[cfg(feature = "stm32wl")]
4use embassy_stm32::interrupt::InterruptExt;
5#[cfg(feature = "stm32wl")]
6use embassy_stm32::pac;
7#[cfg(feature = "stm32wl")]
8use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
9#[cfg(feature = "stm32wl")]
10use embassy_sync::signal::Signal;
11use embedded_hal::digital::v2::OutputPin;
12use embedded_hal_async::delay::DelayUs;
13use embedded_hal_async::digital::Wait;
14use lora_phy::mod_params::RadioError::*;
15use lora_phy::mod_params::{BoardType, RadioError};
16use lora_phy::mod_traits::InterfaceVariant;
17
18/// Interrupt handler.
19#[cfg(feature = "stm32wl")]
20pub struct InterruptHandler {}
21
22#[cfg(feature = "stm32wl")]
23impl interrupt::typelevel::Handler<interrupt::typelevel::SUBGHZ_RADIO> for InterruptHandler {
24 unsafe fn on_interrupt() {
25 interrupt::SUBGHZ_RADIO.disable();
26 IRQ_SIGNAL.signal(());
27 }
28}
29
30#[cfg(feature = "stm32wl")]
31static IRQ_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
32
33#[cfg(feature = "stm32wl")]
34/// Base for the InterfaceVariant implementation for an stm32wl/sx1262 combination
35pub struct Stm32wlInterfaceVariant<CTRL> {
36 board_type: BoardType,
37 rf_switch_rx: Option<CTRL>,
38 rf_switch_tx: Option<CTRL>,
39}
40
41#[cfg(feature = "stm32wl")]
42impl<'a, CTRL> Stm32wlInterfaceVariant<CTRL>
43where
44 CTRL: OutputPin,
45{
46 /// Create an InterfaceVariant instance for an stm32wl/sx1262 combination
47 pub fn new(
48 _irq: impl interrupt::typelevel::Binding<interrupt::typelevel::SUBGHZ_RADIO, InterruptHandler>,
49 rf_switch_rx: Option<CTRL>,
50 rf_switch_tx: Option<CTRL>,
51 ) -> Result<Self, RadioError> {
52 interrupt::SUBGHZ_RADIO.disable();
53 Ok(Self {
54 board_type: BoardType::Stm32wlSx1262, // updated when associated with a specific LoRa board
55 rf_switch_rx,
56 rf_switch_tx,
57 })
58 }
59}
60
61#[cfg(feature = "stm32wl")]
62impl<CTRL> InterfaceVariant for Stm32wlInterfaceVariant<CTRL>
63where
64 CTRL: OutputPin,
65{
66 fn set_board_type(&mut self, board_type: BoardType) {
67 self.board_type = board_type;
68 }
69 async fn set_nss_low(&mut self) -> Result<(), RadioError> {
70 pac::PWR.subghzspicr().modify(|w| w.set_nss(false));
71 Ok(())
72 }
73 async fn set_nss_high(&mut self) -> Result<(), RadioError> {
74 pac::PWR.subghzspicr().modify(|w| w.set_nss(true));
75 Ok(())
76 }
77 async fn reset(&mut self, _delay: &mut impl DelayUs) -> Result<(), RadioError> {
78 pac::RCC.csr().modify(|w| w.set_rfrst(true));
79 pac::RCC.csr().modify(|w| w.set_rfrst(false));
80 Ok(())
81 }
82 async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
83 while pac::PWR.sr2().read().rfbusys() {}
84 Ok(())
85 }
86
87 async fn await_irq(&mut self) -> Result<(), RadioError> {
88 unsafe { interrupt::SUBGHZ_RADIO.enable() };
89 IRQ_SIGNAL.wait().await;
90 Ok(())
91 }
92
93 async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
94 match &mut self.rf_switch_tx {
95 Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
96 None => (),
97 };
98 match &mut self.rf_switch_rx {
99 Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
100 None => Ok(()),
101 }
102 }
103 async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
104 match &mut self.rf_switch_rx {
105 Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
106 None => (),
107 };
108 match &mut self.rf_switch_tx {
109 Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
110 None => Ok(()),
111 }
112 }
113 async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
114 match &mut self.rf_switch_rx {
115 Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
116 None => (),
117 };
118 match &mut self.rf_switch_tx {
119 Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
120 None => Ok(()),
121 }
122 }
123}
124
125/// Base for the InterfaceVariant implementation for an stm32l0/sx1276 combination
126pub struct Stm32l0InterfaceVariant<CTRL, WAIT> {
127 board_type: BoardType,
128 nss: CTRL,
129 reset: CTRL,
130 irq: WAIT,
131 rf_switch_rx: Option<CTRL>,
132 rf_switch_tx: Option<CTRL>,
133}
134
135impl<CTRL, WAIT> Stm32l0InterfaceVariant<CTRL, WAIT>
136where
137 CTRL: OutputPin,
138 WAIT: Wait,
139{
140 /// Create an InterfaceVariant instance for an stm32l0/sx1276 combination
141 pub fn new(
142 nss: CTRL,
143 reset: CTRL,
144 irq: WAIT,
145 rf_switch_rx: Option<CTRL>,
146 rf_switch_tx: Option<CTRL>,
147 ) -> Result<Self, RadioError> {
148 Ok(Self {
149 board_type: BoardType::Stm32l0Sx1276, // updated when associated with a specific LoRa board
150 nss,
151 reset,
152 irq,
153 rf_switch_rx,
154 rf_switch_tx,
155 })
156 }
157}
158
159impl<CTRL, WAIT> InterfaceVariant for Stm32l0InterfaceVariant<CTRL, WAIT>
160where
161 CTRL: OutputPin,
162 WAIT: Wait,
163{
164 fn set_board_type(&mut self, board_type: BoardType) {
165 self.board_type = board_type;
166 }
167 async fn set_nss_low(&mut self) -> Result<(), RadioError> {
168 self.nss.set_low().map_err(|_| NSS)
169 }
170 async fn set_nss_high(&mut self) -> Result<(), RadioError> {
171 self.nss.set_high().map_err(|_| NSS)
172 }
173 async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
174 delay.delay_ms(10).await;
175 self.reset.set_low().map_err(|_| Reset)?;
176 delay.delay_ms(10).await;
177 self.reset.set_high().map_err(|_| Reset)?;
178 delay.delay_ms(10).await;
179 Ok(())
180 }
181 async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
182 Ok(())
183 }
184 async fn await_irq(&mut self) -> Result<(), RadioError> {
185 self.irq.wait_for_high().await.map_err(|_| Irq)
186 }
187
188 async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
189 match &mut self.rf_switch_tx {
190 Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
191 None => (),
192 };
193 match &mut self.rf_switch_rx {
194 Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
195 None => Ok(()),
196 }
197 }
198 async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
199 match &mut self.rf_switch_rx {
200 Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
201 None => (),
202 };
203 match &mut self.rf_switch_tx {
204 Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
205 None => Ok(()),
206 }
207 }
208 async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
209 match &mut self.rf_switch_rx {
210 Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
211 None => (),
212 };
213 match &mut self.rf_switch_tx {
214 Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
215 None => Ok(()),
216 }
217 }
218}
219
220/// Base for the InterfaceVariant implementation for a generic Sx126x LoRa board
221pub struct GenericSx126xInterfaceVariant<CTRL, WAIT> {
222 board_type: BoardType,
223 nss: CTRL,
224 reset: CTRL,
225 dio1: WAIT,
226 busy: WAIT,
227 rf_switch_rx: Option<CTRL>,
228 rf_switch_tx: Option<CTRL>,
229}
230
231impl<CTRL, WAIT> GenericSx126xInterfaceVariant<CTRL, WAIT>
232where
233 CTRL: OutputPin,
234 WAIT: Wait,
235{
236 /// Create an InterfaceVariant instance for an nrf52840/sx1262 combination
237 pub fn new(
238 nss: CTRL,
239 reset: CTRL,
240 dio1: WAIT,
241 busy: WAIT,
242 rf_switch_rx: Option<CTRL>,
243 rf_switch_tx: Option<CTRL>,
244 ) -> Result<Self, RadioError> {
245 Ok(Self {
246 board_type: BoardType::Rak4631Sx1262, // updated when associated with a specific LoRa board
247 nss,
248 reset,
249 dio1,
250 busy,
251 rf_switch_rx,
252 rf_switch_tx,
253 })
254 }
255}
256
257impl<CTRL, WAIT> InterfaceVariant for GenericSx126xInterfaceVariant<CTRL, WAIT>
258where
259 CTRL: OutputPin,
260 WAIT: Wait,
261{
262 fn set_board_type(&mut self, board_type: BoardType) {
263 self.board_type = board_type;
264 }
265 async fn set_nss_low(&mut self) -> Result<(), RadioError> {
266 self.nss.set_low().map_err(|_| NSS)
267 }
268 async fn set_nss_high(&mut self) -> Result<(), RadioError> {
269 self.nss.set_high().map_err(|_| NSS)
270 }
271 async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> {
272 delay.delay_ms(10).await;
273 self.reset.set_low().map_err(|_| Reset)?;
274 delay.delay_ms(20).await;
275 self.reset.set_high().map_err(|_| Reset)?;
276 delay.delay_ms(10).await;
277 Ok(())
278 }
279 async fn wait_on_busy(&mut self) -> Result<(), RadioError> {
280 self.busy.wait_for_low().await.map_err(|_| Busy)
281 }
282 async fn await_irq(&mut self) -> Result<(), RadioError> {
283 self.dio1.wait_for_high().await.map_err(|_| DIO1)?;
284 Ok(())
285 }
286
287 async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> {
288 match &mut self.rf_switch_tx {
289 Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?,
290 None => (),
291 };
292 match &mut self.rf_switch_rx {
293 Some(pin) => pin.set_high().map_err(|_| RfSwitchRx),
294 None => Ok(()),
295 }
296 }
297 async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> {
298 match &mut self.rf_switch_rx {
299 Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
300 None => (),
301 };
302 match &mut self.rf_switch_tx {
303 Some(pin) => pin.set_high().map_err(|_| RfSwitchTx),
304 None => Ok(()),
305 }
306 }
307 async fn disable_rf_switch(&mut self) -> Result<(), RadioError> {
308 match &mut self.rf_switch_rx {
309 Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?,
310 None => (),
311 };
312 match &mut self.rf_switch_tx {
313 Some(pin) => pin.set_low().map_err(|_| RfSwitchTx),
314 None => Ok(()),
315 }
316 }
317}
diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs
deleted file mode 100644
index 653c98253..000000000
--- a/embassy-lora/src/lib.rs
+++ /dev/null
@@ -1,40 +0,0 @@
1#![no_std]
2#![feature(async_fn_in_trait, impl_trait_projections)]
3#![allow(stable_features, unknown_lints, async_fn_in_trait)]
4//! embassy-lora holds LoRa-specific functionality.
5
6pub(crate) mod fmt;
7
8/// interface variants required by the external lora physical layer crate (lora-phy)
9pub mod iv;
10
11#[cfg(feature = "time")]
12use embassy_time::{Duration, Instant, Timer};
13
14/// A convenience timer to use with the LoRaWAN crate
15#[cfg(feature = "time")]
16pub struct LoraTimer {
17 start: Instant,
18}
19
20#[cfg(feature = "time")]
21impl LoraTimer {
22 pub fn new() -> Self {
23 Self { start: Instant::now() }
24 }
25}
26
27#[cfg(feature = "time")]
28impl lorawan_device::async_device::radio::Timer for LoraTimer {
29 fn reset(&mut self) {
30 self.start = Instant::now();
31 }
32
33 async fn at(&mut self, millis: u64) {
34 Timer::at(self.start + Duration::from_millis(millis)).await
35 }
36
37 async fn delay_ms(&mut self, millis: u64) {
38 Timer::after_millis(millis).await
39 }
40}