Skip to main content

ut325f_rs/
reading.rs

1use anyhow::anyhow;
2use anyhow::Result;
3use std::mem;
4use std::time::SystemTime;
5
6use crate::utils::system_time_to_unix_seconds;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq)]
9#[repr(u8)]
10pub enum HoldType {
11    Current = 0,
12    Maximum = 1,
13    Minimum = 2,
14    Average = 3,
15}
16
17impl TryFrom<u8> for HoldType {
18    type Error = ();
19
20    fn try_from(value: u8) -> Result<Self, Self::Error> {
21        match value {
22            0 => Ok(Self::Current),
23            1 => Ok(Self::Maximum),
24            2 => Ok(Self::Minimum),
25            3 => Ok(Self::Average),
26            _ => Err(()),
27        }
28    }
29}
30
31/// A reading from the Uni-T UT325F meter.
32#[derive(Debug, Copy, Clone)]
33pub struct Reading {
34    pub timestamp: SystemTime,
35    pub current_temps_c: [f32; 4],
36    pub held_temps_c: [f32; 4],
37    pub hold_type: HoldType,
38    pub meter_temp_c: f32,
39}
40
41impl Reading {
42    pub const N_BYTES: usize = 56;
43    pub const SYNC: [u8; 5] = [0xaa, 0x55, 0x00, 0x34, 0x01];
44    pub const N_SYNC_BYTES: usize = Self::SYNC.len();
45
46    fn unpack_f32(buf: &[u8], offset: &mut usize) -> Result<f32> {
47        let size = mem::size_of::<f32>();
48        if *offset + size > buf.len() {
49            return Err(anyhow!("Read beyond buffer"));
50        }
51        let bytes = &buf[*offset..*offset + size];
52        let value = f32::from_le_bytes(bytes.try_into().unwrap());
53        *offset += size;
54        Ok(value)
55    }
56
57    fn unpack_u8(buf: &[u8], offset: &mut usize) -> Result<u8> {
58        let size = mem::size_of::<u8>();
59        if *offset + size > buf.len() {
60            return Err(anyhow!("Read beyond buffer"));
61        }
62        let value = buf[*offset];
63        *offset += size;
64        Ok(value)
65    }
66
67    fn unpack_u16(buf: &[u8], offset: &mut usize) -> Result<u16> {
68        let size = mem::size_of::<u16>();
69        if *offset + size > buf.len() {
70            return Err(anyhow!("Read beyond buffer"));
71        }
72        let bytes = &buf[*offset..*offset + size];
73        let value = u16::from_le_bytes(bytes.try_into().unwrap());
74        *offset += size;
75        Ok(value)
76    }
77
78    fn unpack_u32(buf: &[u8], offset: &mut usize) -> Result<u32> {
79        let size = mem::size_of::<u32>();
80        if *offset + size > buf.len() {
81            return Err(anyhow!("Read beyond buffer"));
82        }
83        let bytes = &buf[*offset..*offset + size];
84        let value = u32::from_le_bytes(bytes.try_into().unwrap());
85        *offset += size;
86        Ok(value)
87    }
88
89    pub fn parse(buf: &[u8; Self::N_BYTES]) -> Result<Self> {
90        if buf.len() != Self::N_BYTES {
91            return Err(anyhow!("Incorrect buffer size"));
92        }
93        if buf[..Self::N_SYNC_BYTES] != Self::SYNC {
94            return Err(anyhow!("Bad sync header"));
95        }
96
97        let mut offset = Self::N_SYNC_BYTES;
98        let timestamp = SystemTime::now();
99        let mut current_temps_c = [0.0; 4];
100        for temp in current_temps_c.iter_mut() {
101            *temp = Self::unpack_f32(buf, &mut offset)?;
102        }
103        for temp in current_temps_c.iter_mut() {
104            let error = Self::unpack_u8(buf, &mut offset)?;
105            if error != 0 {
106                *temp = f32::NAN;
107            }
108        }
109        let mut held_temps_c = [0.0; 4];
110        for temp in held_temps_c.iter_mut() {
111            *temp = Self::unpack_f32(buf, &mut offset)?;
112        }
113        for temp in held_temps_c.iter_mut() {
114            let error = Self::unpack_u8(buf, &mut offset)?;
115            if error != 0 {
116                *temp = f32::NAN;
117            }
118        }
119        let meter_temp_c = Self::unpack_f32(buf, &mut offset)?;
120        Self::unpack_u32(buf, &mut offset)?; // unknown
121        let hold_type_raw = Self::unpack_u8(buf, &mut offset)?;
122        let hold_type =
123            HoldType::try_from(hold_type_raw).map_err(|_| anyhow!("Invalid HoldType"))?;
124        Self::unpack_u16(buf, &mut offset)?; // checksum??
125
126        if offset == Self::N_BYTES {
127            Ok(Self {
128                timestamp,
129                current_temps_c,
130                held_temps_c,
131                hold_type,
132                meter_temp_c,
133            })
134        } else {
135            Err(anyhow!("Failed to parse all bytes"))
136        }
137    }
138
139    pub fn print_current_temps(&self) {
140        print!(
141            "{:.3}",
142            system_time_to_unix_seconds(self.timestamp).unwrap()
143        );
144        for temp in self.current_temps_c.iter() {
145            print!(" {:7.3}", temp);
146        }
147        println!();
148    }
149
150    pub fn print_all_temps(&self) {
151        print!(
152            "{:.3}",
153            system_time_to_unix_seconds(self.timestamp).unwrap()
154        );
155        for temp in &self.current_temps_c {
156            print!(" {:7.3}", temp);
157        }
158        print!(" {:?}", self.hold_type);
159        for temp in &self.held_temps_c {
160            print!(" {:7.3}", temp);
161        }
162        println!();
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_parse_reading_from_bytes() -> Result<()> {
172        #[rustfmt::skip]
173        let test_bytes: [u8; Reading::N_BYTES] = [
174            0xaa, 0x55, 0x00, 0x34, 0x01, // Sync header
175            0x98, 0x94, 0xd5, 0x41,       // current_temps_c[0]
176            0x00, 0x00, 0x00, 0x00,       // current_temps_c[1]
177            0x2d, 0x02, 0xd5, 0x41,       // current_temps_c[2]
178            0x6c, 0x25, 0x85, 0x42,       // current_temps_c[3]
179            0x00, 0x30, 0x30, 0x30,       // current_temp_errors
180            0x98, 0x94, 0xd5, 0x41,       // held_temps_c[0]
181            0x00, 0x00, 0x00, 0x00,       // held_temps_c[1]
182            0x2d, 0x02, 0xd5, 0x41,       // held_temps_c[2]
183            0x6c, 0x25, 0x85, 0x42,       // held_temps_c[3]
184            0x00, 0x00, 0x00, 0x00,       // held_temp_errors
185            0x00, 0x80, 0xd2, 0x41,       // meter_temp_c
186            0x00, 0x00, 0x00, 0x00,       // unknown
187            0x00,                         // hold_type
188            0x0d, 0x15,                   // checksum perhaps
189        ];
190
191        let reading_result = Reading::parse(&test_bytes)?;
192
193        assert_eq!(reading_result.current_temps_c[0], 26.697556);
194        assert!(reading_result.current_temps_c[1].is_nan());
195        assert!(reading_result.current_temps_c[2].is_nan());
196        assert!(reading_result.current_temps_c[3].is_nan());
197
198        assert_eq!(reading_result.held_temps_c[0], 26.697556);
199        assert_eq!(reading_result.held_temps_c[1], 0.0);
200        assert_eq!(reading_result.held_temps_c[2], 26.626062);
201        assert_eq!(reading_result.held_temps_c[3], 66.57309);
202
203        assert_eq!(reading_result.meter_temp_c, 26.3125);
204        assert_eq!(reading_result.hold_type, HoldType::Current);
205
206        Ok(())
207    }
208
209    #[test]
210    fn test_parse_bad_sync() -> Result<()> {
211        let mut buffer = [0u8; Reading::N_BYTES];
212        buffer[0] = 0x00; // Corrupt the sync header
213        let reading_result = Reading::parse(&buffer);
214        assert!(reading_result.is_err());
215        assert_eq!(reading_result.unwrap_err().to_string(), "Bad sync header");
216        Ok(())
217    }
218
219    #[test]
220    fn test_parse_invalid_hold_type() -> Result<()> {
221        let mut buffer = [0u8; Reading::N_BYTES];
222        buffer[..Reading::N_SYNC_BYTES].copy_from_slice(&Reading::SYNC);
223        buffer[Reading::N_BYTES - 3] = 0xff; // Invalid HoldType value
224        let reading_result = Reading::parse(&buffer);
225        assert!(reading_result.is_err());
226        assert_eq!(reading_result.unwrap_err().to_string(), "Invalid HoldType");
227        Ok(())
228    }
229}