umsh_ulcp_runtime/
temperature.rs

1//! Append-only temperature inventory, independent of sensor drivers.
2
3use heapless::Vec;
4use umsh_ulcp::{Status, temperature};
5use umsh_ulcp_device::{MAX_TEMPERATURE_SENSORS, TEMPERATURE_NAMES_MAX};
6
7/// Owns labels and ordered source identifiers for one physical boot.
8/// Session resets must retain this object. Sources identify driver instances,
9/// while labels are opaque display text and may repeat.
10pub struct TemperatureInventory<S: Copy> {
11    sources: Vec<S, MAX_TEMPERATURE_SENSORS>,
12    names: Vec<u8, TEMPERATURE_NAMES_MAX>,
13}
14
15impl<S: Copy> Default for TemperatureInventory<S> {
16    fn default() -> Self {
17        Self::new()
18    }
19}
20
21impl<S: Copy> TemperatureInventory<S> {
22    pub const fn new() -> Self {
23        Self {
24            sources: Vec::new(),
25            names: Vec::new(),
26        }
27    }
28
29    /// Register atomically. Existing entries can never be changed or removed.
30    pub fn register(&mut self, source: S, name: &str) -> Result<usize, Status> {
31        let mut encoded = [0; temperature::NAME_MAX_BYTES + 1];
32        let len = temperature::encode_names(&[name], &mut encoded)
33            .map_err(|_| Status::INVALID_ARGUMENT)?;
34        if self.sources.is_full() || self.names.len() + len > TEMPERATURE_NAMES_MAX {
35            return Err(Status::NOMEM);
36        }
37        let index = self.sources.len();
38        // Both capacities were checked before changing either array.
39        let _ = self.sources.push(source);
40        self.names
41            .extend_from_slice(&encoded[..len])
42            .expect("checked name capacity");
43        Ok(index)
44    }
45
46    /// Capture before acquisition. Registrations made afterward belong to the
47    /// next sample, even when individual sensor reads yield to other tasks.
48    pub fn snapshot(&self) -> Vec<S, MAX_TEMPERATURE_SENSORS> {
49        self.sources.clone()
50    }
51
52    /// Names are inventory data; this never accesses a sensor or driver.
53    pub fn read_names(&self, out: &mut [u8]) -> Result<usize, Status> {
54        let len = self.names.len();
55        let dest = out.get_mut(..len).ok_or(Status::NOMEM)?;
56        dest.copy_from_slice(&self.names);
57        Ok(len)
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    fn names<S: Copy>(inventory: &TemperatureInventory<S>) -> std::vec::Vec<u8> {
66        let mut out = [0; TEMPERATURE_NAMES_MAX];
67        let len = inventory.read_names(&mut out).unwrap();
68        out[..len].to_vec()
69    }
70
71    #[test]
72    fn temperature_inventory_owns_names_and_keeps_snapshot_indices() {
73        let mut inventory = TemperatureInventory::new();
74        assert!(inventory.snapshot().is_empty());
75        assert_eq!(inventory.read_names(&mut []), Ok(0));
76        let mut label = std::string::String::from("電池");
77        assert_eq!(inventory.register(10, &label), Ok(0));
78        label.clear();
79        let captured = inventory.snapshot();
80        assert_eq!(inventory.register(20, "電池"), Ok(1));
81        assert_eq!(&captured[..], &[10]);
82        assert_eq!(&inventory.snapshot()[..], &[10, 20]);
83        assert_eq!(
84            temperature::names(&names(&inventory))
85                .unwrap()
86                .collect::<std::vec::Vec<_>>(),
87            ["電池", "電池"]
88        );
89        let mut too_short = [0xaa; 1];
90        assert_eq!(inventory.read_names(&mut too_short), Err(Status::NOMEM));
91        assert_eq!(too_short, [0xaa]);
92    }
93
94    #[test]
95    fn temperature_inventory_registration_failures_are_atomic() {
96        let mut inventory = TemperatureInventory::new();
97        inventory.register(0, "MCU die").unwrap();
98        let initial = names(&inventory);
99        for label in ["", "a\0b", &"a".repeat(65)] {
100            assert_eq!(inventory.register(1, label), Err(Status::INVALID_ARGUMENT));
101            assert_eq!(names(&inventory), initial);
102            assert_eq!(&inventory.snapshot()[..], &[0]);
103        }
104        for index in 1..MAX_TEMPERATURE_SENSORS {
105            inventory.register(index, "a").unwrap();
106        }
107        let full = names(&inventory);
108        assert_eq!(inventory.register(99, "b"), Err(Status::NOMEM));
109        assert_eq!(names(&inventory), full);
110        assert_eq!(inventory.snapshot().len(), MAX_TEMPERATURE_SENSORS);
111
112        let mut inventory = TemperatureInventory::new();
113        for index in 0..4 {
114            inventory.register(index, &"a".repeat(64)).unwrap();
115        }
116        let before = names(&inventory);
117        assert_eq!(inventory.register(4, "abcdefghijkl"), Err(Status::NOMEM));
118        assert_eq!(inventory.snapshot().len(), 4);
119        assert_eq!(names(&inventory), before);
120        assert_eq!(inventory.register(4, "abcdefghijk"), Ok(4));
121        assert_eq!(names(&inventory).len(), TEMPERATURE_NAMES_MAX);
122    }
123}