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