1use core::borrow::Borrow;
7use core::num::NonZeroU8;
8use core::ops::{BitOrAssign, Index, IndexMut};
9use embassy_futures::select::{Either, select};
10use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
11use embassy_time::Duration;
12use embassy_time::Timer;
13use heapless::{LinearMap, Vec};
14
15#[doc(hidden)] pub const CELL_COUNT_U8: u8 = 4;
18
19pub const CELL_COUNT: usize = CELL_COUNT_U8 as usize;
21
22#[doc(hidden)] pub const SEGMENT_COUNT: usize = 8;
25
26#[doc(hidden)] pub const MULTIPLEX_SLEEP: Duration = Duration::from_millis(3);
29
30pub const ANIMATION_MAX_FRAMES: usize = 16;
32pub type Animation = Vec<AnimationFrame, ANIMATION_MAX_FRAMES>;
34
35const BLINK_OFF_DELAY: Duration = Duration::from_millis(50);
36const BLINK_ON_DELAY: Duration = Duration::from_millis(150);
37
38#[derive(Debug, Clone, Copy, Default)]
40#[cfg_attr(feature = "defmt", derive(defmt::Format))]
41pub enum BlinkState {
42 #[default]
44 Solid,
45 BlinkingAndOn,
47 BlinkingButOff,
49}
50
51#[derive(Clone, Copy, Debug)]
53pub struct AnimationFrame {
54 pub text: [char; CELL_COUNT],
56 pub duration: embassy_time::Duration,
58}
59
60impl AnimationFrame {
61 #[must_use]
64 pub const fn new(text: [char; CELL_COUNT], duration: embassy_time::Duration) -> Self {
65 Self { text, duration }
66 }
67}
68
69#[must_use]
71pub fn circular_outline_animation(clockwise: bool) -> Animation {
72 const FRAME_DURATION: Duration = Duration::from_millis(120);
73 const CLOCKWISE: [[char; 4]; 8] = [
74 ['\'', '\'', '\'', '\''],
75 ['\'', '\'', '\'', '"'],
76 [' ', ' ', ' ', '>'],
77 [' ', ' ', ' ', ')'],
78 ['_', '_', '_', '_'],
79 ['*', '_', '_', '_'],
80 ['<', ' ', ' ', ' '],
81 ['(', '\'', '\'', '\''],
82 ];
83 const COUNTER: [[char; 4]; 8] = [
84 ['(', '\'', '\'', '\''],
85 ['<', ' ', ' ', ' '],
86 ['*', '_', '_', '_'],
87 ['_', '_', '_', '_'],
88 [' ', ' ', ' ', ')'],
89 [' ', ' ', ' ', '>'],
90 ['\'', '\'', '\'', '"'],
91 ['\'', '\'', '\'', '\''],
92 ];
93
94 let mut animation = Animation::new();
95 let frames = if clockwise { &CLOCKWISE } else { &COUNTER };
96 for text in frames {
97 animation
98 .push(AnimationFrame::new(*text, FRAME_DURATION))
99 .expect("animation exceeds frame capacity");
100 }
101 animation
102}
103
104#[derive(Clone)]
106#[doc(hidden)] pub enum Led4Command {
108 Text {
110 blink_state: BlinkState,
112 text: [char; CELL_COUNT],
114 },
115 Animation(Animation),
117}
118
119#[doc(hidden)] pub type Led4CommandSignal = Signal<CriticalSectionRawMutex, Led4Command>;
122
123#[doc(hidden)] pub fn signal_text(
126 led4_command_signal: &Led4CommandSignal,
127 text: [char; CELL_COUNT],
128 blink_state: BlinkState,
129) {
130 led4_command_signal.signal(Led4Command::Text { blink_state, text });
131}
132
133#[doc(hidden)] pub fn signal_animation<I>(led4_command_signal: &Led4CommandSignal, animation: I)
136where
137 I: IntoIterator,
138 I::Item: Borrow<AnimationFrame>,
139{
140 let mut frames: Animation = Animation::new();
141 for animation_frame in animation {
142 let animation_frame = *animation_frame.borrow();
143 frames
144 .push(animation_frame)
145 .expect("animation fits within ANIMATION_MAX_FRAMES");
146 }
147 led4_command_signal.signal(Led4Command::Animation(frames));
148}
149
150pub trait Led4 {
189 fn write_text(&self, text: [char; CELL_COUNT], blink_state: BlinkState);
193
194 fn animate_text<I>(&self, animation: I)
198 where
199 I: IntoIterator,
200 I::Item: Borrow<AnimationFrame>;
201}
202
203#[doc(hidden)] pub async fn run_command_loop<F>(
206 led4_command_signal: &'static Led4CommandSignal,
207 mut write_text: F,
208) -> !
209where
210 F: FnMut([char; CELL_COUNT]),
211{
212 let mut command = Led4Command::Text {
213 blink_state: BlinkState::default(),
214 text: [' '; CELL_COUNT],
215 };
216
217 loop {
218 command = match command {
219 Led4Command::Text { blink_state, text } => {
220 run_text_loop(blink_state, text, led4_command_signal, &mut write_text).await
221 }
222 Led4Command::Animation(animation) => {
223 run_animation_loop(animation, led4_command_signal, &mut write_text).await
224 }
225 };
226 }
227}
228
229async fn run_text_loop<F>(
230 mut blink_state: BlinkState,
231 text: [char; CELL_COUNT],
232 led4_command_signal: &'static Led4CommandSignal,
233 write_text: &mut F,
234) -> Led4Command
235where
236 F: FnMut([char; CELL_COUNT]),
237{
238 loop {
239 match blink_state {
240 BlinkState::Solid => {
241 write_text(text);
242 return led4_command_signal.wait().await;
243 }
244 BlinkState::BlinkingAndOn => {
245 write_text(text);
246 match select(led4_command_signal.wait(), Timer::after(BLINK_ON_DELAY)).await {
247 Either::First(command) => return command,
248 Either::Second(()) => blink_state = BlinkState::BlinkingButOff,
249 }
250 }
251 BlinkState::BlinkingButOff => {
252 write_text([' '; CELL_COUNT]);
253 match select(led4_command_signal.wait(), Timer::after(BLINK_OFF_DELAY)).await {
254 Either::First(command) => return command,
255 Either::Second(()) => blink_state = BlinkState::BlinkingAndOn,
256 }
257 }
258 }
259 }
260}
261
262async fn run_animation_loop<F>(
263 animation: Animation,
264 led4_command_signal: &'static Led4CommandSignal,
265 write_text: &mut F,
266) -> Led4Command
267where
268 F: FnMut([char; CELL_COUNT]),
269{
270 if animation.is_empty() {
271 return led4_command_signal.wait().await;
272 }
273
274 let frames = animation;
275 let len = frames.len();
276 let mut index = 0;
277 loop {
278 let frame = frames[index];
279 write_text(frame.text);
280 match select(led4_command_signal.wait(), Timer::after(frame.duration)).await {
281 Either::First(command) => return command,
282 Either::Second(()) => index = (index + 1) % len,
283 }
284 }
285}
286
287#[derive(Debug, Clone, Copy, Eq, PartialEq)]
289#[cfg_attr(feature = "defmt", derive(defmt::Format))]
290#[doc(hidden)] pub enum Led4BitsToIndexesError {
292 Full,
294}
295
296#[doc(hidden)] pub type BitsToIndexes = LinearMap<NonZeroU8, Vec<u8, CELL_COUNT>, CELL_COUNT>;
301
302#[doc(hidden)] pub trait Led4OutputAdapter {
305 type Error;
307
308 fn set_segments_from_nonzero_bits(&mut self, bits: NonZeroU8);
310
311 fn set_cells_active(&mut self, indexes: &[u8], active: bool) -> Result<(), Self::Error>;
316}
317
318#[derive(Debug)]
320#[doc(hidden)] pub enum Led4SimpleLoopError<E> {
322 BitsToIndexes(Led4BitsToIndexesError),
324 Output(E),
326}
327
328#[doc(hidden)] pub async fn run_simple_loop<T>(
339 led4_output_adapter: &mut T,
340 bit_matrix_signal: &'static Signal<CriticalSectionRawMutex, BitMatrixLed4>,
341) -> Result<core::convert::Infallible, Led4SimpleLoopError<T::Error>>
342where
343 T: Led4OutputAdapter,
344{
345 let mut bit_matrix_led4 = BitMatrixLed4::default();
346 let mut bits_to_indexes = BitsToIndexes::default();
347 'outer: loop {
348 bit_matrix_led4
349 .bits_to_indexes(&mut bits_to_indexes)
350 .map_err(Led4SimpleLoopError::BitsToIndexes)?;
351
352 match bits_to_indexes.iter().next() {
353 None => bit_matrix_led4 = bit_matrix_signal.wait().await,
354 Some((&bits, indexes)) if bits_to_indexes.len() == 1 => {
355 led4_output_adapter.set_segments_from_nonzero_bits(bits);
356 led4_output_adapter
357 .set_cells_active(indexes, true)
358 .map_err(Led4SimpleLoopError::Output)?;
359 bit_matrix_led4 = bit_matrix_signal.wait().await;
360 led4_output_adapter
361 .set_cells_active(indexes, false)
362 .map_err(Led4SimpleLoopError::Output)?;
363 }
364 _ => loop {
365 for (bits, indexes) in &bits_to_indexes {
366 led4_output_adapter.set_segments_from_nonzero_bits(*bits);
367 led4_output_adapter
368 .set_cells_active(indexes, true)
369 .map_err(Led4SimpleLoopError::Output)?;
370 let timeout_or_signal =
371 select(Timer::after(MULTIPLEX_SLEEP), bit_matrix_signal.wait()).await;
372 led4_output_adapter
373 .set_cells_active(indexes, false)
374 .map_err(Led4SimpleLoopError::Output)?;
375 if let Either::Second(notification) = timeout_or_signal {
376 bit_matrix_led4 = notification;
377 continue 'outer;
378 }
379 }
380 },
381 }
382 }
383}
384
385#[derive(Debug, Clone, PartialEq, Eq)]
387#[cfg_attr(feature = "defmt", derive(defmt::Format))]
388#[doc(hidden)] pub struct BitMatrixLed4([u8; CELL_COUNT]);
390
391impl BitMatrixLed4 {
392 #[must_use]
393 pub const fn new(bits: [u8; CELL_COUNT]) -> Self {
394 Self(bits)
395 }
396
397 #[must_use]
398 pub fn from_text(text: &[char; CELL_COUNT]) -> Self {
399 let bytes = text.map(|char| Leds::ASCII_TABLE.get(char as usize).copied().unwrap_or(0));
400 Self::new(bytes)
401 }
402
403 pub fn iter(&self) -> impl Iterator<Item = &u8> {
404 self.0.iter()
405 }
406
407 pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, u8> {
408 self.0.iter_mut()
409 }
410
411 pub fn bits_to_indexes(
418 &self,
419 bits_to_indexes: &mut BitsToIndexes,
420 ) -> Result<(), Led4BitsToIndexesError> {
421 bits_to_indexes.clear();
422 for (&bits, index) in self.iter().zip(0..CELL_COUNT_U8) {
423 if let Some(nonzero_bits) = NonZeroU8::new(bits) {
424 if let Some(indexes) = bits_to_indexes.get_mut(&nonzero_bits) {
425 indexes
426 .push(index)
427 .map_err(|_| Led4BitsToIndexesError::Full)?;
428 } else {
429 let indexes =
430 Vec::from_slice(&[index]).map_err(|_| Led4BitsToIndexesError::Full)?;
431 bits_to_indexes
432 .insert(nonzero_bits, indexes)
433 .map_err(|_| Led4BitsToIndexesError::Full)?;
434 }
435 }
436 }
437 Ok(())
438 }
439}
440
441impl Default for BitMatrixLed4 {
442 fn default() -> Self {
443 Self([0; CELL_COUNT])
444 }
445}
446
447impl BitOrAssign<u8> for BitMatrixLed4 {
448 fn bitor_assign(&mut self, rhs: u8) {
449 self.iter_mut().for_each(|bits| *bits |= rhs);
450 }
451}
452
453impl Index<usize> for BitMatrixLed4 {
454 type Output = u8;
455
456 #[expect(clippy::indexing_slicing, reason = "Caller validates indexing")]
457 fn index(&self, index: usize) -> &Self::Output {
458 &self.0[index]
459 }
460}
461
462impl IndexMut<usize> for BitMatrixLed4 {
463 #[expect(clippy::indexing_slicing, reason = "Caller validates indexing")]
464 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
465 &mut self.0[index]
466 }
467}
468
469impl IntoIterator for BitMatrixLed4 {
470 type Item = u8;
471 type IntoIter = core::array::IntoIter<u8, CELL_COUNT>;
472
473 fn into_iter(self) -> Self::IntoIter {
474 self.0.into_iter()
475 }
476}
477
478impl<'a> IntoIterator for &'a BitMatrixLed4 {
479 type Item = &'a u8;
480 type IntoIter = core::slice::Iter<'a, u8>;
481
482 fn into_iter(self) -> Self::IntoIter {
483 self.0.iter()
484 }
485}
486
487impl<'a> IntoIterator for &'a mut BitMatrixLed4 {
488 type Item = &'a mut u8;
489 type IntoIter = core::slice::IterMut<'a, u8>;
490
491 fn into_iter(self) -> Self::IntoIter {
492 self.0.iter_mut()
493 }
494}
495
496struct Leds;
497
498impl Leds {
499 const SEG_A: u8 = 0b_0000_0001;
500 const SEG_B: u8 = 0b_0000_0010;
501 const SEG_C: u8 = 0b_0000_0100;
502 const SEG_D: u8 = 0b_0000_1000;
503 const SEG_E: u8 = 0b_0001_0000;
504 const SEG_F: u8 = 0b_0010_0000;
505
506 const ASCII_TABLE: [u8; 128] = [
507 0b_0000_0000,
508 0b_0000_0000,
509 0b_0000_0000,
510 0b_0000_0000,
511 0b_0000_0000,
512 0b_0000_0000,
513 0b_0000_0000,
514 0b_0000_0000,
515 0b_0000_0000,
516 0b_0000_0000,
517 0b_0000_0000,
518 0b_0000_0000,
519 0b_0000_0000,
520 0b_0000_0000,
521 0b_0000_0000,
522 0b_0000_0000,
523 0b_0000_0000,
524 0b_0000_0000,
525 0b_0000_0000,
526 0b_0000_0000,
527 0b_0000_0000,
528 0b_0000_0000,
529 0b_0000_0000,
530 0b_0000_0000,
531 0b_0000_0000,
532 0b_0000_0000,
533 0b_0000_0000,
534 0b_0000_0000,
535 0b_0000_0000,
536 0b_0000_0000,
537 0b_0000_0000,
538 0b_0000_0000,
539 0b_0000_0000,
540 0b_1000_0110,
541 Self::SEG_A | Self::SEG_B,
542 0b_0000_0000,
543 0b_0000_0000,
544 0b_0000_0000,
545 0b_0000_0000,
546 Self::SEG_A,
547 Self::SEG_A | Self::SEG_F,
548 Self::SEG_C | Self::SEG_D,
549 Self::SEG_D | Self::SEG_E,
550 0b_0000_0000,
551 0b_0000_0000,
552 0b_0100_0000,
553 0b_1000_0000,
554 0b_0000_0000,
555 0b_0011_1111,
556 0b_0000_0110,
557 0b_0101_1011,
558 0b_0100_1111,
559 0b_0110_0110,
560 0b_0110_1101,
561 0b_0111_1101,
562 0b_0000_0111,
563 0b_0111_1111,
564 0b_0110_1111,
565 0b_0000_0000,
566 0b_0000_0000,
567 Self::SEG_E | Self::SEG_F,
568 0b_0000_0000,
569 Self::SEG_B | Self::SEG_C,
570 0b_0000_0000,
571 0b_0000_0000,
572 0b_0111_0111,
573 0b_0111_1100,
574 0b_0011_1001,
575 0b_0101_1110,
576 0b_0111_1001,
577 0b_0111_0001,
578 0b_0011_1101,
579 0b_0111_0110,
580 0b_0000_0110,
581 0b_0001_1110,
582 0b_0111_0110,
583 0b_0011_1000,
584 0b_0001_0101,
585 0b_0101_0100,
586 0b_0011_1111,
587 0b_0111_0011,
588 0b_0110_0111,
589 0b_0101_0000,
590 0b_0110_1101,
591 0b_0111_1000,
592 0b_0011_1110,
593 0b_0010_1010,
594 0b_0001_1101,
595 0b_0111_0110,
596 0b_0110_1110,
597 0b_0101_1011,
598 0b_0011_1001,
599 0b_0000_0000,
600 0b_0000_1111,
601 0b_0000_0000,
602 0b_0000_1000,
603 0b_0000_0000,
604 0b_0111_0111,
605 0b_0111_1100,
606 0b_0011_1001,
607 0b_0101_1110,
608 0b_0111_1001,
609 0b_0111_0001,
610 0b_0011_1101,
611 0b_0111_0100,
612 0b_0001_0000,
613 0b_0001_1110,
614 0b_0111_0110,
615 0b_0011_1000,
616 0b_0001_0101,
617 0b_0101_0100,
618 0b_0101_1100,
619 0b_0111_0011,
620 0b_0110_0111,
621 0b_0101_0000,
622 0b_0110_1101,
623 0b_0111_1000,
624 0b_0011_1110,
625 0b_0010_1010,
626 0b_0001_1101,
627 0b_0111_0110,
628 0b_0110_1110,
629 0b_0101_1011,
630 0b_0011_1001,
631 0b_0000_0110,
632 0b_0000_1111,
633 0b_0100_0000,
634 0b_0000_0000,
635 ];
636}
637
638#[cfg(test)]
639mod tests {
640 use super::{BitMatrixLed4, CELL_COUNT};
641
642 #[test]
643 fn from_text_maps_known_characters() {
644 let bit_matrix_led4 = BitMatrixLed4::from_text(&['1', '2', '3', '4']);
645 assert_eq!(bit_matrix_led4.iter().count(), CELL_COUNT);
646 }
647}