1use num_enum::TryFromPrimitive;
2use std::fmt;
3use uom::si::electric_current::microampere;
4use uom::si::electric_potential::{microvolt, millivolt};
5use uom::si::f64::{ElectricCurrent, ElectricPotential, Frequency, Power, Time};
6use uom::si::frequency::hertz;
7use uom::si::time::millisecond;
8use zerocopy::byteorder::little_endian::{I32, U16};
9use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14#[cfg(feature = "python")]
15use uom::si::{electric_current::ampere, electric_potential::volt, power::watt};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23#[repr(u16)]
24pub enum GraphSampleRate {
25 Sps2 = 0,
27 Sps10 = 1,
29 Sps50 = 2,
31 Sps1000 = 3,
33}
34
35impl GraphSampleRate {
36 const SEQUENCE_TICKS_PER_SECOND: u16 = 1000;
37
38 const fn sample_count_per_second(self) -> u16 {
39 match self {
40 Self::Sps2 => 2,
41 Self::Sps10 => 10,
42 Self::Sps50 => 50,
43 Self::Sps1000 => 1000,
44 }
45 }
46
47 pub const fn sequence_step(self) -> u16 {
49 Self::SEQUENCE_TICKS_PER_SECOND / self.sample_count_per_second()
50 }
51
52 pub fn frequency(self) -> Frequency {
54 Frequency::new::<hertz>(f64::from(self.sample_count_per_second()))
55 }
56
57 pub fn sequence_counter_frequency() -> Frequency {
59 Frequency::new::<hertz>(f64::from(Self::SEQUENCE_TICKS_PER_SECOND))
60 }
61
62 pub fn auxiliary_voltage_lsb(self) -> ElectricPotential {
67 match self {
68 Self::Sps2 => ElectricPotential::new::<millivolt>(0.1),
69 Self::Sps10 | Self::Sps50 | Self::Sps1000 => ElectricPotential::new::<millivolt>(1.0),
70 }
71 }
72
73 pub const fn from_sequence_step(step: u16) -> Option<Self> {
75 match step {
76 500 => Some(Self::Sps2),
77 100 => Some(Self::Sps10),
78 20 => Some(Self::Sps50),
79 1 => Some(Self::Sps1000),
80 _ => None,
81 }
82 }
83
84 pub fn missing_samples(self, previous: u16, current: u16) -> u16 {
86 let elapsed_ticks = current.wrapping_sub(previous);
87 elapsed_ticks.saturating_sub(self.sequence_step()) / self.sequence_step()
88 }
89}
90
91pub fn sequence_elapsed(previous: u16, current: u16) -> Time {
93 let elapsed_ticks = current.wrapping_sub(previous);
94 Time::new::<millisecond>(f64::from(elapsed_ticks))
95}
96
97impl fmt::Display for GraphSampleRate {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 match self {
100 Self::Sps2 => write!(f, "2 SPS"),
101 Self::Sps10 => write!(f, "10 SPS"),
102 Self::Sps50 => write!(f, "50 SPS"),
103 Self::Sps1000 => write!(f, "1000 SPS"),
104 }
105 }
106}
107
108#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
122#[repr(C)]
123struct AdcQueueSampleWire {
124 pub sequence: U16,
126 pub marker: U16,
128 pub vbus_uv: I32,
130 pub ibus_ua: I32,
132 pub cc1_raw: U16,
134 pub cc2_raw: U16,
136 pub vdp_raw: U16,
138 pub vdm_raw: U16,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
147#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
148#[cfg_attr(feature = "python", pyo3::pyclass(get_all, skip_from_py_object))]
149pub struct AdcQueueSampleRaw {
150 pub sequence: u16,
151 pub marker: u16,
152 pub vbus_uv: i32,
153 pub ibus_ua: i32,
154 pub cc1_raw: u16,
155 pub cc2_raw: u16,
156 pub vdp_raw: u16,
157 pub vdm_raw: u16,
158}
159
160impl From<AdcQueueSampleWire> for AdcQueueSampleRaw {
161 fn from(raw: AdcQueueSampleWire) -> Self {
162 Self {
163 sequence: raw.sequence.get(),
164 marker: raw.marker.get(),
165 vbus_uv: raw.vbus_uv.get(),
166 ibus_ua: raw.ibus_ua.get(),
167 cc1_raw: raw.cc1_raw.get(),
168 cc2_raw: raw.cc2_raw.get(),
169 vdp_raw: raw.vdp_raw.get(),
170 vdm_raw: raw.vdm_raw.get(),
171 }
172 }
173}
174
175impl From<AdcQueueSampleRaw> for AdcQueueSampleWire {
176 fn from(raw: AdcQueueSampleRaw) -> Self {
177 Self {
178 sequence: U16::new(raw.sequence),
179 marker: U16::new(raw.marker),
180 vbus_uv: I32::new(raw.vbus_uv),
181 ibus_ua: I32::new(raw.ibus_ua),
182 cc1_raw: U16::new(raw.cc1_raw),
183 cc2_raw: U16::new(raw.cc2_raw),
184 vdp_raw: U16::new(raw.vdp_raw),
185 vdm_raw: U16::new(raw.vdm_raw),
186 }
187 }
188}
189
190#[derive(Debug, Clone, PartialEq, Eq)]
192#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
193#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
194pub struct AdcQueueRawData {
195 pub samples: Vec<AdcQueueSampleRaw>,
196}
197
198impl AdcQueueRawData {
199 const SAMPLE_SIZE: usize = 20;
200
201 pub fn from_bytes(bytes: &[u8]) -> Result<Self, crate::error::KMError> {
203 if !bytes.len().is_multiple_of(Self::SAMPLE_SIZE) {
204 return Err(crate::error::KMError::InvalidPacket(format!(
205 "AdcQueue payload length must be a multiple of {}, got {}",
206 Self::SAMPLE_SIZE,
207 bytes.len()
208 )));
209 }
210
211 let samples = bytes
212 .chunks_exact(Self::SAMPLE_SIZE)
213 .map(|sample| {
214 AdcQueueSampleWire::ref_from_bytes(sample)
215 .map(|wire| AdcQueueSampleRaw::from(*wire))
216 .map_err(|_| crate::error::KMError::InvalidPacket("Failed to parse AdcQueue sample".to_string()))
217 })
218 .collect::<Result<_, _>>()?;
219
220 Ok(Self { samples })
221 }
222
223 pub fn decode(&self, rate: GraphSampleRate) -> AdcQueueData {
225 AdcQueueData {
226 rate,
227 samples: self
228 .samples
229 .iter()
230 .copied()
231 .map(|sample| AdcQueueSample::from_raw(sample, rate))
232 .collect(),
233 }
234 }
235
236 pub fn sequence_range(&self) -> Option<(u16, u16)> {
237 self.samples
238 .first()
239 .zip(self.samples.last())
240 .map(|(first, last)| (first.sequence, last.sequence))
241 }
242
243 pub fn has_dropped_samples(&self, rate: GraphSampleRate) -> bool {
244 self.samples
245 .windows(2)
246 .any(|window| rate.missing_samples(window[0].sequence, window[1].sequence) > 0)
247 }
248
249 pub fn to_bytes(&self) -> Vec<u8> {
250 self.samples
251 .iter()
252 .copied()
253 .flat_map(|sample| AdcQueueSampleWire::from(sample).as_bytes().to_vec())
254 .collect()
255 }
256}
257
258#[derive(Debug, Clone, Copy, PartialEq)]
260#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
261#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object, name = "AdcQueueSample"))]
262pub struct AdcQueueSample {
263 pub sequence: u16,
264 pub marker: u16,
266 pub vbus: ElectricPotential,
267 pub ibus: ElectricCurrent,
268 pub power: Power,
269 pub cc1: ElectricPotential,
270 pub cc2: ElectricPotential,
271 pub vdp: ElectricPotential,
272 pub vdm: ElectricPotential,
273}
274
275impl AdcQueueSample {
276 pub fn from_raw(raw: AdcQueueSampleRaw, rate: GraphSampleRate) -> Self {
278 let vbus = ElectricPotential::new::<microvolt>(f64::from(raw.vbus_uv));
279 let ibus = ElectricCurrent::new::<microampere>(f64::from(raw.ibus_ua));
280 let auxiliary_voltage_lsb = rate.auxiliary_voltage_lsb();
281
282 Self {
283 sequence: raw.sequence,
284 marker: raw.marker,
285 vbus,
286 ibus,
287 power: vbus * ibus,
288 cc1: auxiliary_voltage_lsb * f64::from(raw.cc1_raw),
289 cc2: auxiliary_voltage_lsb * f64::from(raw.cc2_raw),
290 vdp: auxiliary_voltage_lsb * f64::from(raw.vdp_raw),
291 vdm: auxiliary_voltage_lsb * f64::from(raw.vdm_raw),
292 }
293 }
294}
295
296#[derive(Debug, Clone, PartialEq)]
298#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
299#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object, name = "AdcQueueData"))]
300pub struct AdcQueueData {
301 pub rate: GraphSampleRate,
302 pub samples: Vec<AdcQueueSample>,
303}
304
305impl AdcQueueData {
306 pub fn from_bytes_with_rate(bytes: &[u8], rate: GraphSampleRate) -> Result<Self, crate::error::KMError> {
308 Ok(AdcQueueRawData::from_bytes(bytes)?.decode(rate))
309 }
310
311 pub fn sequence_range(&self) -> Option<(u16, u16)> {
313 self.samples
314 .first()
315 .zip(self.samples.last())
316 .map(|(first, last)| (first.sequence, last.sequence))
317 }
318
319 pub fn has_dropped_samples(&self) -> bool {
321 self.samples
322 .windows(2)
323 .any(|window| self.rate.missing_samples(window[0].sequence, window[1].sequence) > 0)
324 }
325}
326
327#[cfg(feature = "python")]
328#[pyo3::pymethods]
329impl AdcQueueRawData {
330 #[getter]
331 fn samples(&self) -> Vec<AdcQueueSampleRaw> {
332 self.samples.clone()
333 }
334
335 #[pyo3(name = "sequence_range")]
336 fn py_sequence_range(&self) -> Option<(u16, u16)> {
337 self.sequence_range()
338 }
339
340 #[pyo3(name = "has_dropped_samples")]
341 fn py_has_dropped_samples(&self, rate_index: u16) -> pyo3::PyResult<bool> {
342 let rate = GraphSampleRate::try_from(rate_index).map_err(|_| {
343 pyo3::exceptions::PyValueError::new_err(format!("Invalid graph sample rate index: {rate_index}"))
344 })?;
345 Ok(self.has_dropped_samples(rate))
346 }
347
348 #[pyo3(name = "decode")]
350 fn py_decode(&self, rate_index: u16) -> pyo3::PyResult<AdcQueueData> {
351 let rate = GraphSampleRate::try_from(rate_index).map_err(|_| {
352 pyo3::exceptions::PyValueError::new_err(format!("Invalid graph sample rate index: {rate_index}"))
353 })?;
354 Ok(self.decode(rate))
355 }
356
357 fn __repr__(&self) -> String {
358 format!("AdcQueueRawData({} samples)", self.samples.len())
359 }
360
361 fn __str__(&self) -> String {
362 self.__repr__()
363 }
364}
365
366#[cfg(feature = "python")]
367#[pyo3::pymethods]
368impl AdcQueueData {
369 #[getter]
370 fn rate_index(&self) -> u16 {
371 self.rate as u16
372 }
373
374 #[getter]
375 fn samples(&self) -> Vec<AdcQueueSample> {
376 self.samples.clone()
377 }
378
379 #[pyo3(name = "sequence_range")]
380 fn py_sequence_range(&self) -> Option<(u16, u16)> {
381 self.sequence_range()
382 }
383
384 #[pyo3(name = "has_dropped_samples")]
385 fn py_has_dropped_samples(&self) -> bool {
386 self.has_dropped_samples()
387 }
388
389 fn __repr__(&self) -> String {
390 format!("AdcQueueData({} samples)", self.samples.len())
391 }
392
393 fn __str__(&self) -> String {
394 self.__repr__()
395 }
396}
397
398#[cfg(feature = "python")]
399#[pyo3::pymethods]
400impl AdcQueueSample {
401 #[getter]
402 fn sequence(&self) -> u16 {
403 self.sequence
404 }
405 #[getter]
406 fn marker(&self) -> u16 {
407 self.marker
408 }
409 #[getter]
410 fn vbus_v(&self) -> f64 {
411 self.vbus.get::<volt>()
412 }
413 #[getter]
414 fn ibus_a(&self) -> f64 {
415 self.ibus.get::<ampere>()
416 }
417 #[getter]
418 fn power_w(&self) -> f64 {
419 self.power.get::<watt>()
420 }
421 #[getter]
422 fn cc1_v(&self) -> f64 {
423 self.cc1.get::<volt>()
424 }
425 #[getter]
426 fn cc2_v(&self) -> f64 {
427 self.cc2.get::<volt>()
428 }
429 #[getter]
430 fn vdp_v(&self) -> f64 {
431 self.vdp.get::<volt>()
432 }
433 #[getter]
434 fn vdm_v(&self) -> f64 {
435 self.vdm.get::<volt>()
436 }
437
438 fn __repr__(&self) -> String {
439 format!(
440 "AdcQueueSample(seq={}, vbus={:.3}V, ibus={:.3}A, power={:.3}W)",
441 self.sequence,
442 self.vbus.get::<volt>(),
443 self.ibus.get::<ampere>(),
444 self.power.get::<watt>()
445 )
446 }
447
448 fn __str__(&self) -> String {
449 self.__repr__()
450 }
451}