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)]
23#[repr(C)]
24pub struct PdStatusRaw {
25 pub timestamp_ms: U32,
26 pub vbus_mv: U16,
27 pub ibus_ma: I16, pub cc1_mv: U16,
29 pub cc2_mv: U16,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq)]
33#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object))]
35pub struct PdStatus {
36 pub timestamp: Time,
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 Self {
46 timestamp: Time::new::<millisecond>(f64::from(raw.timestamp_ms.get())),
47 vbus: ElectricPotential::new::<millivolt>(f64::from(raw.vbus_mv.get())),
48 ibus: ElectricCurrent::new::<milliampere>(f64::from(raw.ibus_ma.get())),
49 cc1: ElectricPotential::new::<millivolt>(f64::from(raw.cc1_mv.get())),
50 cc2: ElectricPotential::new::<millivolt>(f64::from(raw.cc2_mv.get())),
51 }
52 }
53}
54
55impl From<PdStatus> for PdStatusRaw {
56 fn from(status: PdStatus) -> Self {
57 Self {
60 timestamp_ms: U32::new(status.timestamp.get::<millisecond>().round() as u32),
61 vbus_mv: U16::new(status.vbus.get::<millivolt>().round() as u16),
62 ibus_ma: I16::new(status.ibus.get::<milliampere>().round() as i16),
63 cc1_mv: U16::new(status.cc1.get::<millivolt>().round() as u16),
64 cc2_mv: U16::new(status.cc2.get::<millivolt>().round() as u16),
65 }
66 }
67}
68
69#[cfg(feature = "python")]
70#[pyo3::pymethods]
71impl PdStatus {
72 #[getter]
73 fn timestamp(&self) -> f64 {
74 self.timestamp.get::<millisecond>()
75 }
76 #[getter]
77 fn vbus_v(&self) -> f64 {
78 self.vbus.get::<volt>()
79 }
80 #[getter]
81 fn ibus_a(&self) -> f64 {
82 self.ibus.get::<ampere>()
83 }
84 #[getter]
85 fn cc1_v(&self) -> f64 {
86 self.cc1.get::<volt>()
87 }
88 #[getter]
89 fn cc2_v(&self) -> f64 {
90 self.cc2.get::<volt>()
91 }
92
93 fn __repr__(&self) -> String {
94 format!(
95 "PdStatus(timestamp={}ms, vbus={:.3}V, ibus={:.3}A)",
96 self.timestamp.get::<millisecond>(),
97 self.vbus.get::<volt>(),
98 self.ibus.get::<ampere>()
99 )
100 }
101
102 fn __str__(&self) -> String {
103 self.__repr__()
104 }
105}
106
107#[derive(Debug, Clone, PartialEq)]
109pub enum PdEventData {
110 Connect,
111 Disconnect,
112 PdMessage { sop: u8, wire_data: Vec<u8> },
113}
114
115#[cfg(feature = "python")]
116impl<'py> pyo3::IntoPyObject<'py> for PdEventData {
117 type Target = pyo3::types::PyDict;
118 type Output = pyo3::Bound<'py, Self::Target>;
119 type Error = pyo3::PyErr;
120
121 fn into_pyobject(self, py: pyo3::Python<'py>) -> Result<Self::Output, Self::Error> {
122 use pyo3::types::{PyDict, PyDictMethods};
123
124 let dict = PyDict::new(py);
125 match self {
126 Self::Connect => dict.set_item("Connect", py.None())?,
127 Self::Disconnect => dict.set_item("Disconnect", py.None())?,
128 Self::PdMessage { sop, wire_data } => {
129 let inner = PyDict::new(py);
130 inner.set_item("sop", sop)?;
131 inner.set_item("wire_data", wire_data)?;
132 dict.set_item("PdMessage", inner)?;
133 }
134 }
135 Ok(dict)
136 }
137}
138
139#[derive(Debug, Clone, PartialEq)]
141#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object, name = "PdEvent"))]
142pub struct PdEvent {
143 pub timestamp: Time,
144 pub data: PdEventData,
145}
146
147#[derive(Debug, Clone, PartialEq)]
149#[cfg_attr(feature = "python", pyo3::pyclass(skip_from_py_object, name = "PdEventStream"))]
150pub struct PdEventStream {
151 pub preamble: PdStatus,
153 pub events: Vec<PdEvent>,
154}
155
156impl PdEventStream {
157 pub fn from_bytes(bytes: Bytes) -> Result<Self, KMError> {
160 if bytes.len() < PD_STATUS_SIZE {
161 return Err(KMError::InvalidPacket(format!(
162 "PD event stream too short for preamble: need {}, got {}",
163 PD_STATUS_SIZE,
164 bytes.len()
165 )));
166 }
167
168 let preamble_raw = PdStatusRaw::ref_from_bytes(&bytes[..PD_STATUS_SIZE])
170 .map_err(|_| KMError::InvalidPacket("Failed to parse PD preamble".to_string()))?;
171 let preamble = PdStatus::from(*preamble_raw);
172
173 let mut events = Vec::new();
174 let mut offset = PD_STATUS_SIZE;
175
176 while offset < bytes.len() {
178 if bytes.len() - offset < PD_EVENT_HEADER_SIZE {
179 return Err(KMError::InvalidPacket(format!(
180 "Incomplete PD event header at offset {}: need {}, got {}",
181 offset,
182 PD_EVENT_HEADER_SIZE,
183 bytes.len() - offset
184 )));
185 }
186
187 let size_flag = bytes[offset];
188
189 if size_flag == PD_EVENT_TYPE_CONNECTION {
191 let ts24 = u32::from_le_bytes([bytes[offset + 1], bytes[offset + 2], bytes[offset + 3], 0]);
192 let event_code = bytes[offset + 5];
193 let data = match event_code {
194 PD_CONNECTION_CONNECT | PD_CONNECTION_CONNECT_LEGACY => PdEventData::Connect,
195 PD_CONNECTION_DISCONNECT | PD_CONNECTION_DISCONNECT_LEGACY => PdEventData::Disconnect,
196 _ => PdEventData::PdMessage {
197 sop: event_code,
198 wire_data: Vec::new(),
199 },
200 };
201 events.push(PdEvent {
202 timestamp: Time::new::<millisecond>(f64::from(ts24)),
203 data,
204 });
205 offset += PD_EVENT_HEADER_SIZE;
206 continue;
207 }
208
209 let timestamp = u32::from_le_bytes([
210 bytes[offset + 1],
211 bytes[offset + 2],
212 bytes[offset + 3],
213 bytes[offset + 4],
214 ]);
215 let sop = bytes[offset + 5];
216
217 let encoded_size = size_flag & PD_EVENT_SIZE_MASK;
219 let wire_len = encoded_size.checked_sub(PD_EVENT_SIZE_OFFSET).ok_or_else(|| {
220 KMError::InvalidPacket(format!(
221 "Invalid PD event size at offset {}: encoded size {} is below {}",
222 offset, encoded_size, PD_EVENT_SIZE_OFFSET
223 ))
224 })? as usize;
225
226 offset += PD_EVENT_HEADER_SIZE;
227
228 if bytes.len() - offset < wire_len {
229 return Err(KMError::InvalidPacket(format!(
230 "Insufficient bytes for wire data: expected {}, got {}",
231 wire_len,
232 bytes.len() - offset
233 )));
234 }
235
236 let wire_data = if wire_len > 0 {
237 bytes.slice(offset..offset + wire_len).to_vec()
238 } else {
239 Vec::new()
240 };
241 offset += wire_len;
242
243 let data = PdEventData::PdMessage { sop, wire_data };
244
245 events.push(PdEvent {
246 timestamp: Time::new::<millisecond>(f64::from(timestamp)),
247 data,
248 });
249 }
250
251 Ok(Self { preamble, events })
252 }
253
254 pub fn pd_messages(&self) -> impl Iterator<Item = (&Time, u8, &[u8])> {
256 self.events.iter().filter_map(|e| match &e.data {
257 PdEventData::PdMessage { sop, wire_data } => Some((&e.timestamp, *sop, wire_data.as_slice())),
258 _ => None,
259 })
260 }
261
262 pub fn connection_events(&self) -> impl Iterator<Item = (&Time, bool)> {
264 self.events.iter().filter_map(|e| match &e.data {
265 PdEventData::Connect => Some((&e.timestamp, true)),
266 PdEventData::Disconnect => Some((&e.timestamp, false)),
267 _ => None,
268 })
269 }
270}
271
272#[cfg(feature = "python")]
273#[pyo3::pymethods]
274impl PdEvent {
275 #[getter]
276 fn timestamp(&self) -> f64 {
277 self.timestamp.get::<millisecond>()
278 }
279
280 #[getter]
281 fn data(&self) -> PdEventData {
282 self.data.clone()
283 }
284
285 fn __repr__(&self) -> String {
286 match &self.data {
287 PdEventData::Connect => format!(
288 "PdEvent(timestamp={}ms, type=connect)",
289 self.timestamp.get::<millisecond>()
290 ),
291 PdEventData::Disconnect => format!(
292 "PdEvent(timestamp={}ms, type=disconnect)",
293 self.timestamp.get::<millisecond>()
294 ),
295 PdEventData::PdMessage { sop, wire_data } => format!(
296 "PdEvent(timestamp={}, type=pd_message, sop={}, {} bytes)",
297 self.timestamp.get::<millisecond>(),
298 sop,
299 wire_data.len()
300 ),
301 }
302 }
303
304 fn __str__(&self) -> String {
305 self.__repr__()
306 }
307}
308
309#[cfg(feature = "python")]
310#[pyo3::pymethods]
311impl PdEventStream {
312 #[getter]
313 fn preamble(&self) -> PdStatus {
314 self.preamble
315 }
316
317 #[getter]
318 fn events(&self) -> Vec<PdEvent> {
319 self.events.clone()
320 }
321
322 fn __repr__(&self) -> String {
323 format!(
324 "PdEventStream(preamble={}, {} events)",
325 self.preamble.__repr__(),
326 self.events.len()
327 )
328 }
329
330 fn __str__(&self) -> String {
331 self.__repr__()
332 }
333}