1pub use device_envoy_core::led::{Led, LedLevel, OnLevel};
58pub mod led_generated;
59#[cfg(target_os = "none")]
60#[doc(hidden)]
61pub use paste;
62
63#[cfg(target_os = "none")]
64use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
65#[cfg(target_os = "none")]
66use embassy_time::{Duration, Timer};
67#[cfg(target_os = "none")]
68use esp_hal::gpio::{Level, Output};
69#[cfg(target_os = "none")]
70use heapless::Vec;
71
72#[cfg(target_os = "none")]
73#[doc(hidden)] pub const DEFAULT_MAX_STEPS: usize = 32;
75
76#[cfg(target_os = "none")]
77#[derive(Clone)]
78#[doc(hidden)] pub enum LedCommand<const MAX_STEPS: usize> {
80 Set(LedLevel),
81 Animate(Vec<(LedLevel, Duration), MAX_STEPS>),
82}
83
84#[cfg(target_os = "none")]
85#[doc(hidden)] pub type LedOuterStatic<const MAX_STEPS: usize> =
87 Signal<CriticalSectionRawMutex, LedCommand<MAX_STEPS>>;
88
89#[cfg(target_os = "none")]
90#[doc(hidden)] pub struct LedStatic<const MAX_STEPS: usize> {
92 outer: LedOuterStatic<MAX_STEPS>,
93}
94
95#[cfg(target_os = "none")]
96impl<const MAX_STEPS: usize> LedStatic<MAX_STEPS> {
97 #[doc(hidden)] pub const fn new() -> Self {
99 Self {
100 outer: Signal::new(),
101 }
102 }
103
104 #[doc(hidden)] pub fn outer(&self) -> &LedOuterStatic<MAX_STEPS> {
106 &self.outer
107 }
108}
109
110#[cfg(target_os = "none")]
111#[doc(hidden)] pub fn set_pin_for_led_level(led_level: LedLevel, pin: &mut Output<'_>, on_level: OnLevel) {
113 let pin_level = match (led_level, on_level) {
114 (LedLevel::On, OnLevel::High) | (LedLevel::Off, OnLevel::Low) => Level::High,
115 (LedLevel::Off, OnLevel::High) | (LedLevel::On, OnLevel::Low) => Level::Low,
116 };
117 pin.set_level(pin_level);
118}
119
120#[cfg(target_os = "none")]
121#[doc(hidden)] pub async fn run_set_level_loop<const MAX_STEPS: usize>(
123 led_level: LedLevel,
124 outer_static: &'static LedOuterStatic<MAX_STEPS>,
125 pin: &mut Output<'_>,
126 on_level: OnLevel,
127) -> LedCommand<MAX_STEPS> {
128 set_pin_for_led_level(led_level, pin, on_level);
129
130 loop {
131 match outer_static.wait().await {
132 LedCommand::Set(new_led_level) => {
133 if new_led_level == led_level {
134 continue;
135 }
136 return LedCommand::Set(new_led_level);
137 }
138 other => return other,
139 }
140 }
141}
142
143#[cfg(target_os = "none")]
144#[doc(hidden)] pub async fn run_animation_loop<const MAX_STEPS: usize>(
146 animation: Vec<(LedLevel, Duration), MAX_STEPS>,
147 outer_static: &'static LedOuterStatic<MAX_STEPS>,
148 pin: &mut Output<'_>,
149 on_level: OnLevel,
150) -> LedCommand<MAX_STEPS> {
151 if animation.is_empty() {
152 return LedCommand::Animate(animation);
153 }
154
155 let mut frame_index = 0;
156
157 loop {
158 let (led_level, duration) = animation[frame_index];
159
160 set_pin_for_led_level(led_level, pin, on_level);
161
162 frame_index = (frame_index + 1) % animation.len();
163
164 match embassy_futures::select::select(Timer::after(duration), outer_static.wait()).await {
165 embassy_futures::select::Either::First(_) => {}
166 embassy_futures::select::Either::Second(command) => return command,
167 }
168 }
169}
170
171#[cfg(target_os = "none")]
196#[doc(hidden)]
197#[macro_export]
198macro_rules! led {
199 ($($tt:tt)*) => { $crate::__led_impl! { $($tt)* } };
200}
201
202#[cfg(target_os = "none")]
204#[doc(hidden)]
205#[macro_export]
206macro_rules! __led_impl {
207 (
208 $vis:vis $name:ident {
209 $($fields:tt)*
210 }
211 ) => {
212 $crate::__led_impl! {
213 @__parse
214 vis: $vis,
215 name: $name,
216 pin: [],
217 max_steps: [],
218 fields: [ $($fields)* ]
219 }
220 };
221
222 (@__parse
223 vis: $vis:vis,
224 name: $name:ident,
225 pin: [],
226 max_steps: [$($max_steps:expr)?],
227 fields: [ pin: $pin:ident $(, $($rest:tt)*)? ]
228 ) => {
229 $crate::__led_impl! {
230 @__parse
231 vis: $vis,
232 name: $name,
233 pin: [$pin],
234 max_steps: [$($max_steps)?],
235 fields: [ $($($rest)*)? ]
236 }
237 };
238 (@__parse
239 vis: $vis:vis,
240 name: $name:ident,
241 pin: [$_pin_seen:ident],
242 max_steps: [$($max_steps:expr)?],
243 fields: [ pin: $pin:ident $(, $($rest:tt)*)? ]
244 ) => {
245 compile_error!("led! duplicate `pin` field");
246 };
247
248 (@__parse
249 vis: $vis:vis,
250 name: $name:ident,
251 pin: [$($pin:ident)?],
252 max_steps: [],
253 fields: [ max_steps: $max_steps:expr $(, $($rest:tt)*)? ]
254 ) => {
255 $crate::__led_impl! {
256 @__parse
257 vis: $vis,
258 name: $name,
259 pin: [$($pin)?],
260 max_steps: [$max_steps],
261 fields: [ $($($rest)*)? ]
262 }
263 };
264 (@__parse
265 vis: $vis:vis,
266 name: $name:ident,
267 pin: [$($pin:ident)?],
268 max_steps: [$_max_steps_seen:expr],
269 fields: [ max_steps: $max_steps:expr $(, $($rest:tt)*)? ]
270 ) => {
271 compile_error!("led! duplicate `max_steps` field");
272 };
273
274 (@__parse
275 vis: $vis:vis,
276 name: $name:ident,
277 pin: [$($pin:ident)?],
278 max_steps: [$($max_steps:expr)?],
279 fields: [ ]
280 ) => {
281 $crate::__led_impl! {
282 @__finish
283 vis: $vis,
284 name: $name,
285 pin: [$($pin)?],
286 max_steps: [$($max_steps)?]
287 }
288 };
289
290 (@__parse
291 vis: $vis:vis,
292 name: $name:ident,
293 pin: [$($pin:ident)?],
294 max_steps: [$($max_steps:expr)?],
295 fields: [ $field:ident : $value:expr $(, $($rest:tt)*)? ]
296 ) => {
297 compile_error!("led! unknown field; expected `pin` or `max_steps`");
298 };
299
300 (@__finish
301 vis: $vis:vis,
302 name: $name:ident,
303 pin: [],
304 max_steps: [$($max_steps:expr)?]
305 ) => {
306 compile_error!("led! missing required `pin` field");
307 };
308
309 (@__finish
310 vis: $vis:vis,
311 name: $name:ident,
312 pin: [$pin:ident],
313 max_steps: []
314 ) => {
315 $crate::__led_impl!(@__emit vis: $vis, name: $name, pin: $pin, max_steps: $crate::led::DEFAULT_MAX_STEPS);
316 };
317
318 (@__finish
319 vis: $vis:vis,
320 name: $name:ident,
321 pin: [$pin:ident],
322 max_steps: [$max_steps:expr]
323 ) => {
324 $crate::__led_impl!(@__emit vis: $vis, name: $name, pin: $pin, max_steps: $max_steps);
325 };
326
327 (
328 @__emit
329 vis: $vis:vis,
330 name: $name:ident,
331 pin: $pin:ident,
332 max_steps: $max_steps:expr
333 ) => {
334 $crate::led::paste::paste! {
335 #[cfg(target_os = "none")]
336 const [<$name:upper _MAX_STEPS>]: usize = $max_steps;
337
338 #[cfg(target_os = "none")]
339 #[allow(non_upper_case_globals)]
340 static [<$name:upper _STATIC>]: $crate::led::LedStatic<{ [<$name:upper _MAX_STEPS>] }> =
341 $crate::led::LedStatic::new();
342
343 #[cfg(target_os = "none")]
344 #[allow(non_camel_case_types)]
345 $vis struct $name(&'static $crate::led::LedOuterStatic<{ [<$name:upper _MAX_STEPS>] }>);
346
347 #[cfg(target_os = "none")]
348 impl $name {
349 $vis const MAX_STEPS: usize = [<$name:upper _MAX_STEPS>];
350
351 pub fn new(
352 pin: $crate::esp_hal::peripherals::$pin<'static>,
353 on_level: $crate::led::OnLevel,
354 spawner: embassy_executor::Spawner,
355 ) -> $crate::Result<Self> {
356 let pin_output = $crate::esp_hal::gpio::Output::new(
357 pin,
358 $crate::esp_hal::gpio::Level::Low,
359 $crate::esp_hal::gpio::OutputConfig::default(),
360 );
361 let token = [<__led_task_ $name:snake>](
362 [<$name:upper _STATIC>].outer(),
363 pin_output,
364 on_level,
365 );
366 spawner.spawn(token).map_err($crate::Error::TaskSpawn)?;
367 Ok(Self([<$name:upper _STATIC>].outer()))
368 }
369 }
370
371 #[cfg(target_os = "none")]
372 impl $crate::led::Led for $name {
373 fn set_level(&self, led_level: $crate::led::LedLevel) {
374 self.0.signal($crate::led::LedCommand::Set(led_level));
375 }
376
377 fn animate<I>(&self, frames: I)
378 where
379 I: IntoIterator,
380 I::Item: ::core::borrow::Borrow<(
381 $crate::led::LedLevel,
382 embassy_time::Duration,
383 )>,
384 {
385 let mut animation: heapless::Vec<
386 ($crate::led::LedLevel, embassy_time::Duration),
387 { [<$name:upper _MAX_STEPS>] },
388 > = heapless::Vec::new();
389 for frame in frames {
390 let frame = *::core::borrow::Borrow::borrow(&frame);
391 animation
392 .push(frame)
393 .expect("LED animation fits within MAX_STEPS");
394 }
395 self.0.signal($crate::led::LedCommand::Animate(animation));
396 }
397 }
398
399 #[cfg(target_os = "none")]
400 #[embassy_executor::task]
401 async fn [<__led_task_ $name:snake>](
402 outer_static: &'static $crate::led::LedOuterStatic<{ [<$name:upper _MAX_STEPS>] }>,
403 mut pin: $crate::esp_hal::gpio::Output<'static>,
404 on_level: $crate::led::OnLevel,
405 ) -> ! {
406 let mut command = $crate::led::LedCommand::Set($crate::led::LedLevel::Off);
407 $crate::led::set_pin_for_led_level($crate::led::LedLevel::Off, &mut pin, on_level);
408
409 loop {
410 command = match command {
411 $crate::led::LedCommand::Set(led_level) => {
412 $crate::led::run_set_level_loop(led_level, outer_static, &mut pin, on_level).await
413 }
414 $crate::led::LedCommand::Animate(animation) => {
415 $crate::led::run_animation_loop(animation, outer_static, &mut pin, on_level).await
416 }
417 };
418 }
419 }
420 }
421 };
422}
423
424#[cfg(target_os = "none")]
425#[doc(inline)]
426pub use led;