cu_embedded_registry/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6#[cfg(not(feature = "std"))]
7use alloc::{boxed::Box, format};
8use core::any::{Any, TypeId};
9use cu29::prelude::*;
10use embedded_io::{Read, Write};
11use spin::Mutex;
12
13type SerialEntry = Box<dyn Any + Send>;
14
15struct SerialSlot {
16    type_id: TypeId,
17    entry: SerialEntry,
18}
19
20pub const MAX_SERIAL_SLOTS: usize = 8;
21
22struct SerialSlots {
23    entries: [Option<SerialSlot>; MAX_SERIAL_SLOTS],
24}
25
26impl SerialSlots {
27    const fn new() -> Self {
28        Self {
29            entries: [None, None, None, None, None, None, None, None],
30        }
31    }
32}
33
34static SERIAL_SLOTS: Mutex<SerialSlots> = Mutex::new(SerialSlots::new());
35
36/// Register a serial port at the specified slot index.
37///
38/// # Arguments
39///
40/// * `slot` - The slot index (0 to MAX_SERIAL_SLOTS-1)
41/// * `serial` - The serial port implementing embedded-io Read/Write traits
42///
43/// # Returns
44///
45/// Returns `CuResult<()>` indicating success or failure
46pub fn register<S, E>(slot: usize, serial: S) -> CuResult<()>
47where
48    S: Write<Error = E> + Read<Error = E> + Send + 'static,
49{
50    if slot >= MAX_SERIAL_SLOTS {
51        return Err(CuError::from(format!(
52            "Serial registry slot {slot} out of range (max {MAX_SERIAL_SLOTS})"
53        )));
54    }
55    let mut slots = SERIAL_SLOTS.lock();
56    slots.entries[slot] = Some(SerialSlot {
57        type_id: TypeId::of::<S>(),
58        entry: Box::new(serial),
59    });
60    Ok(())
61}
62
63/// Take (consume) a serial port from the specified slot index.
64///
65/// # Arguments
66///
67/// * `slot` - The slot index (0 to MAX_SERIAL_SLOTS-1)
68///
69/// # Returns
70///
71/// Returns `Some(S)` if the slot contains a serial port of type S, `None` otherwise.
72/// Note: This consumes the serial port from the slot, leaving it empty.
73pub fn take<S, E>(slot: usize) -> Option<S>
74where
75    S: Write<Error = E> + Read<Error = E> + Send + 'static,
76{
77    if slot >= MAX_SERIAL_SLOTS {
78        return None;
79    }
80    let mut slots = SERIAL_SLOTS.lock();
81    let record = slots.entries[slot].take()?;
82    if record.type_id != TypeId::of::<S>() {
83        slots.entries[slot] = Some(record);
84        return None;
85    }
86    record.entry.downcast::<S>().map(|boxed| *boxed).ok()
87}
88
89/// Get the slot status for debugging purposes.
90///
91/// # Returns
92///
93/// Returns an array indicating which slots are occupied.
94pub fn slot_status() -> [bool; MAX_SERIAL_SLOTS] {
95    let slots = SERIAL_SLOTS.lock();
96    let mut status = [false; MAX_SERIAL_SLOTS];
97    for (i, slot) in slots.entries.iter().enumerate() {
98        status[i] = slot.is_some();
99    }
100    status
101}
102
103/// Clear all slots. Useful for testing.
104#[cfg(test)]
105pub fn clear_all() {
106    let mut slots = SERIAL_SLOTS.lock();
107    for slot in &mut slots.entries {
108        *slot = None;
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use embedded_io::{ErrorKind, ErrorType};
116
117    #[derive(Debug)]
118    struct TestError;
119
120    impl embedded_io::Error for TestError {
121        fn kind(&self) -> ErrorKind {
122            ErrorKind::Other
123        }
124    }
125
126    struct MockSerial;
127
128    impl ErrorType for MockSerial {
129        type Error = TestError;
130    }
131
132    impl embedded_io::Write for MockSerial {
133        fn write(&mut self, _buf: &[u8]) -> Result<usize, Self::Error> {
134            Ok(0)
135        }
136
137        fn flush(&mut self) -> Result<(), Self::Error> {
138            Ok(())
139        }
140    }
141
142    impl embedded_io::Read for MockSerial {
143        fn read(&mut self, _buf: &mut [u8]) -> Result<usize, Self::Error> {
144            Ok(0)
145        }
146    }
147
148    #[test]
149    fn test_register_and_take() {
150        clear_all();
151
152        let serial = MockSerial;
153        assert!(register(0, serial).is_ok());
154
155        let status = slot_status();
156        assert!(status[0]);
157        assert!(!status[1]);
158
159        let recovered: Option<MockSerial> = take(0);
160        assert!(recovered.is_some());
161
162        let status = slot_status();
163        assert!(!status[0]);
164    }
165
166    #[test]
167    fn test_slot_bounds() {
168        clear_all();
169
170        let serial = MockSerial;
171        assert!(register(MAX_SERIAL_SLOTS, serial).is_err());
172
173        let recovered: Option<MockSerial> = take(MAX_SERIAL_SLOTS);
174        assert!(recovered.is_none());
175    }
176}