1use bytes::Bytes;
2use uom::si::electric_current::milliampere;
3use uom::si::electric_potential::millivolt;
4use uom::si::f64::{ElectricCurrent, ElectricPotential, Time};
5use uom::si::time::millisecond;
6use zerocopy::byteorder::little_endian::{I16, U16, U32};
7use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
8
9use crate::constants::*;
10use crate::error::KMError;
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15#[cfg(feature = "python")]
16use uom::si::{electric_current::ampere, electric_potential::volt};
17
18#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
20#[repr(C)]
21pub struct PdStatusRaw {
22 pub type_id: u8,
23 pub timestamp24: [u8; 3], pub vbus_mv: U16,
25 pub ibus_ma: I16, pub cc1_mv: U16,
27 pub cc2_mv: U16,
28}
29
30#[derive(Debug, Clone, Copy, PartialEq)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
33pub struct PdStatus {
34 pub type_id: u8,
35 pub timestamp_ticks: u32,
37 pub vbus: ElectricPotential,
38 pub ibus: ElectricCurrent,
39 pub cc1: ElectricPotential,
40 pub cc2: ElectricPotential,
41}
42
43impl From<PdStatusRaw> for PdStatus {
44 fn from(raw: PdStatusRaw) -> Self {
45 let timestamp = u32::from_le_bytes([raw.timestamp24[0], raw.timestamp24[1], raw.timestamp24[2], 0]);
47
48 Self {
49 type_id: raw.type_id,
50 timestamp_ticks: timestamp,
51 vbus: ElectricPotential::new::<millivolt>(f64::from(raw.vbus_mv.get())),
52 ibus: ElectricCurrent::new::<milliampere>(f64::from(raw.ibus_ma.get())),
53 cc1: ElectricPotential::new::<millivolt>(f64::from(raw.cc1_mv.get())),
54 cc2: ElectricPotential::new::<millivolt>(f64::from(raw.cc2_mv.get())),
55 }
56 }
57}
58
59#[cfg(feature = "python")]
60#[pyo3::pymethods]
61impl PdStatus {
62 #[getter]
63 fn type_id(&self) -> u8 {
64 self.type_id
65 }
66 #[getter]
67 fn timestamp(&self) -> u32 {
68 self.timestamp_ticks
69 }
70 #[getter]
71 fn vbus_v(&self) -> f64 {
72 self.vbus.get::<volt>()
73 }
74 #[getter]
75 fn ibus_a(&self) -> f64 {
76 self.ibus.get::<ampere>()
77 }
78 #[getter]
79 fn cc1_v(&self) -> f64 {
80 self.cc1.get::<volt>()
81 }
82 #[getter]
83 fn cc2_v(&self) -> f64 {
84 self.cc2.get::<volt>()
85 }
86
87 fn __repr__(&self) -> String {
88 format!(
89 "PdStatus(type_id={}, timestamp={}, vbus={:.3}V, ibus={:.3}A)",
90 self.type_id,
91 self.timestamp_ticks,
92 self.vbus.get::<volt>(),
93 self.ibus.get::<ampere>()
94 )
95 }
96
97 fn __str__(&self) -> String {
98 self.__repr__()
99 }
100}
101
102#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, KnownLayout, Immutable, Unaligned)]
104#[repr(C)]
105pub struct PdPreambleRaw {
106 pub timestamp32: U32,
107 pub vbus_mv: U16,
108 pub ibus_ma: I16, pub cc1_mv: U16,
110 pub cc2_mv: U16,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq)]
114#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
115#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
116pub struct PdPreamble {
117 pub timestamp: Time,
118 pub vbus: ElectricPotential,
119 pub ibus: ElectricCurrent,
120 pub cc1: ElectricPotential,
121 pub cc2: ElectricPotential,
122}
123
124impl From<PdPreambleRaw> for PdPreamble {
125 fn from(raw: PdPreambleRaw) -> Self {
126 Self {
127 timestamp: Time::new::<millisecond>(f64::from(raw.timestamp32.get())),
128 vbus: ElectricPotential::new::<millivolt>(f64::from(raw.vbus_mv.get())),
129 ibus: ElectricCurrent::new::<milliampere>(f64::from(raw.ibus_ma.get())),
130 cc1: ElectricPotential::new::<millivolt>(f64::from(raw.cc1_mv.get())),
131 cc2: ElectricPotential::new::<millivolt>(f64::from(raw.cc2_mv.get())),
132 }
133 }
134}
135
136#[cfg(feature = "python")]
137#[pyo3::pymethods]
138impl PdPreamble {
139 #[getter]
140 fn timestamp(&self) -> f64 {
141 self.timestamp.get::<millisecond>()
142 }
143 #[getter]
144 fn vbus_v(&self) -> f64 {
145 self.vbus.get::<volt>()
146 }
147 #[getter]
148 fn ibus_a(&self) -> f64 {
149 self.ibus.get::<ampere>()
150 }
151 #[getter]
152 fn cc1_v(&self) -> f64 {
153 self.cc1.get::<volt>()
154 }
155 #[getter]
156 fn cc2_v(&self) -> f64 {
157 self.cc2.get::<volt>()
158 }
159
160 fn __repr__(&self) -> String {
161 format!(
162 "PdPreamble(timestamp={}ms, vbus={:.3}V, ibus={:.3}A)",
163 self.timestamp.get::<millisecond>(),
164 self.vbus.get::<volt>(),
165 self.ibus.get::<ampere>()
166 )
167 }
168
169 fn __str__(&self) -> String {
170 self.__repr__()
171 }
172}
173
174#[derive(Debug, Clone, PartialEq)]
176#[cfg_attr(feature = "python", derive(pyo3::IntoPyObject))]
177pub enum PdEventData {
178 #[cfg_attr(feature = "python", pyo3(transparent))]
179 Connect(()),
180 #[cfg_attr(feature = "python", pyo3(transparent))]
181 Disconnect(()),
182 PdMessage {
183 sop: u8,
184 wire_data: Vec<u8>,
185 },
186}
187
188#[derive(Debug, Clone, PartialEq)]
190#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object, name = "PdEvent"))]
191pub struct PdEvent {
192 pub timestamp: Time,
193 pub data: PdEventData,
194}
195
196#[derive(Debug, Clone, PartialEq)]
198#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object, name = "PdEventStream"))]
199pub struct PdEventStream {
200 pub preamble: PdPreamble,
201 pub events: Vec<PdEvent>,
202}
203
204impl PdEventStream {
205 pub fn from_bytes(bytes: Bytes) -> Result<Self, KMError> {
208 if bytes.len() < PD_PREAMBLE_SIZE {
209 return Err(KMError::InvalidPacket(format!(
210 "PD event stream too short for preamble: need {}, got {}",
211 PD_PREAMBLE_SIZE,
212 bytes.len()
213 )));
214 }
215
216 let preamble_raw = PdPreambleRaw::ref_from_bytes(&bytes[..PD_PREAMBLE_SIZE])
218 .map_err(|_| KMError::InvalidPacket("Failed to parse PD preamble".to_string()))?;
219 let preamble = PdPreamble::from(*preamble_raw);
220
221 let mut events = Vec::new();
222 let mut offset = PD_PREAMBLE_SIZE;
223
224 while offset < bytes.len() {
226 if bytes.len() - offset < PD_EVENT_HEADER_SIZE {
227 return Err(KMError::InvalidPacket(format!(
228 "Incomplete PD event header at offset {}: need {}, got {}",
229 offset,
230 PD_EVENT_HEADER_SIZE,
231 bytes.len() - offset
232 )));
233 }
234
235 let size_flag = bytes[offset];
236
237 if size_flag == PD_EVENT_TYPE_CONNECTION {
239 let ts24 = u32::from_le_bytes([bytes[offset + 1], bytes[offset + 2], bytes[offset + 3], 0]);
240 let event_code = bytes[offset + 5];
241 let data = match event_code {
242 PD_CONNECTION_CONNECT | PD_CONNECTION_CONNECT_LEGACY => PdEventData::Connect(()),
243 PD_CONNECTION_DISCONNECT | PD_CONNECTION_DISCONNECT_LEGACY => PdEventData::Disconnect(()),
244 _ => PdEventData::PdMessage {
245 sop: event_code,
246 wire_data: Vec::new(),
247 },
248 };
249 events.push(PdEvent {
250 timestamp: Time::new::<millisecond>(f64::from(ts24)),
251 data,
252 });
253 offset += PD_EVENT_HEADER_SIZE;
254 continue;
255 }
256
257 let timestamp = u32::from_le_bytes([
258 bytes[offset + 1],
259 bytes[offset + 2],
260 bytes[offset + 3],
261 bytes[offset + 4],
262 ]);
263 let sop = bytes[offset + 5];
264
265 let encoded_size = size_flag & PD_EVENT_SIZE_MASK;
267 let wire_len = encoded_size.checked_sub(PD_EVENT_SIZE_OFFSET).ok_or_else(|| {
268 KMError::InvalidPacket(format!(
269 "Invalid PD event size at offset {}: encoded size {} is below {}",
270 offset, encoded_size, PD_EVENT_SIZE_OFFSET
271 ))
272 })? as usize;
273
274 offset += PD_EVENT_HEADER_SIZE;
275
276 if bytes.len() - offset < wire_len {
277 return Err(KMError::InvalidPacket(format!(
278 "Insufficient bytes for wire data: expected {}, got {}",
279 wire_len,
280 bytes.len() - offset
281 )));
282 }
283
284 let wire_data = if wire_len > 0 {
285 bytes.slice(offset..offset + wire_len).to_vec()
286 } else {
287 Vec::new()
288 };
289 offset += wire_len;
290
291 let data = PdEventData::PdMessage { sop, wire_data };
292
293 events.push(PdEvent {
294 timestamp: Time::new::<millisecond>(f64::from(timestamp)),
295 data,
296 });
297 }
298
299 Ok(Self { preamble, events })
300 }
301
302 pub fn pd_messages(&self) -> impl Iterator<Item = (&Time, u8, &Vec<u8>)> {
304 self.events.iter().filter_map(|e| match &e.data {
305 PdEventData::PdMessage { sop, wire_data } => Some((&e.timestamp, *sop, wire_data)),
306 _ => None,
307 })
308 }
309
310 pub fn connection_events(&self) -> impl Iterator<Item = (&Time, bool)> {
312 self.events.iter().filter_map(|e| match &e.data {
313 PdEventData::Connect(()) => Some((&e.timestamp, true)),
314 PdEventData::Disconnect(()) => Some((&e.timestamp, false)),
315 _ => None,
316 })
317 }
318}
319
320#[cfg(feature = "python")]
321#[pyo3::pymethods]
322impl PdEvent {
323 #[getter]
324 fn timestamp(&self) -> f64 {
325 self.timestamp.get::<millisecond>()
326 }
327
328 #[getter]
329 fn data(&self) -> PdEventData {
330 self.data.clone()
331 }
332
333 fn __repr__(&self) -> String {
334 match &self.data {
335 PdEventData::Connect(()) => format!(
336 "PdEvent(timestamp={}ms, type=connect)",
337 self.timestamp.get::<millisecond>()
338 ),
339 PdEventData::Disconnect(()) => format!(
340 "PdEvent(timestamp={}ms, type=disconnect)",
341 self.timestamp.get::<millisecond>()
342 ),
343 PdEventData::PdMessage { sop, wire_data } => format!(
344 "PdEvent(timestamp={}, type=pd_message, sop={}, {} bytes)",
345 self.timestamp.get::<millisecond>(),
346 sop,
347 wire_data.len()
348 ),
349 }
350 }
351
352 fn __str__(&self) -> String {
353 self.__repr__()
354 }
355}
356
357#[cfg(feature = "python")]
358#[pyo3::pymethods]
359impl PdEventStream {
360 #[getter]
361 fn preamble(&self) -> PdPreamble {
362 self.preamble
363 }
364
365 #[getter]
366 fn events(&self) -> Vec<PdEvent> {
367 self.events.clone()
368 }
369
370 fn __repr__(&self) -> String {
371 format!(
372 "PdEventStream(preamble={}, {} events)",
373 self.preamble.__repr__(),
374 self.events.len()
375 )
376 }
377
378 fn __str__(&self) -> String {
379 self.__repr__()
380 }
381}