usbpd/protocol_layer/message/
request.rs

1//! Definitions of request message content.
2use byteorder::{ByteOrder, LittleEndian};
3use proc_bitfield::bitfield;
4use uom::si::electric_current::{self, centiampere};
5use uom::si::{self};
6
7use super::_20millivolts_mod::_20millivolts;
8use super::_50milliamperes_mod::_50milliamperes;
9use super::_250milliwatts_mod::_250milliwatts;
10use super::pdo;
11use super::units::{ElectricCurrent, ElectricPotential};
12
13bitfield! {
14    #[derive(Clone, Copy, PartialEq, Eq)]
15    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
16    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17    pub struct RawDataObject(pub u32): Debug, FromStorage, IntoStorage {
18        /// Valid range 1..=14
19        pub object_position: u8 @ 28..=31,
20    }
21}
22
23bitfield! {
24    #[derive(Clone, Copy, PartialEq, Eq)]
25    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
26    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27    pub struct FixedVariableSupply(pub u32): Debug, FromStorage, IntoStorage {
28        /// Valid range 1..=14
29        pub object_position: u8 @ 28..=31,
30        pub giveback_flag: bool @ 27,
31        pub capability_mismatch: bool @ 26,
32        pub usb_communications_capable: bool @ 25,
33        pub no_usb_suspend: bool @ 24,
34        pub unchunked_extended_messages_supported: bool @ 23,
35        pub epr_mode_capable: bool @ 22,
36        pub raw_operating_current: u16 @ 10..=19,
37        pub raw_max_operating_current: u16 @ 0..=9,
38    }
39}
40
41impl FixedVariableSupply {
42    pub fn to_bytes(self, buf: &mut [u8]) -> usize {
43        LittleEndian::write_u32(buf, self.0);
44        4
45    }
46
47    pub fn operating_current(&self) -> ElectricCurrent {
48        ElectricCurrent::new::<centiampere>(self.raw_operating_current().into())
49    }
50
51    pub fn max_operating_current(&self) -> ElectricCurrent {
52        ElectricCurrent::new::<centiampere>(self.raw_max_operating_current().into())
53    }
54}
55
56bitfield! {
57    #[derive(Clone, Copy, PartialEq, Eq)]
58    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
59    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
60    pub struct Battery(pub u32): Debug, FromStorage, IntoStorage {
61        /// Object position (0000b and 1110b…1111b are Reserved and Shall Not be used)
62        pub object_position: u8 @ 28..=31,
63        /// GiveBackFlag = 0
64        pub giveback_flag: bool @ 27,
65        /// Capability mismatch
66        pub capability_mismatch: bool @ 26,
67        /// USB communications capable
68        pub usb_communications_capable: bool @ 25,
69        /// No USB Suspend
70        pub no_usb_suspend: bool @ 24,
71        /// Unchunked extended messages supported
72        pub unchunked_extended_messages_supported: bool @ 23,
73        /// EPR mode capable
74        pub epr_mode_capable: bool @ 22,
75        /// Operating power in 250mW units
76        pub raw_operating_power: u16 @ 10..=19,
77        /// Maximum operating power in 250mW units
78        pub raw_max_operating_power: u16 @ 0..=9,
79    }
80}
81
82impl Battery {
83    pub fn to_bytes(self, buf: &mut [u8]) {
84        LittleEndian::write_u32(buf, self.0);
85    }
86
87    pub fn operating_power(&self) -> si::u32::Power {
88        si::u32::Power::new::<_250milliwatts>(self.raw_operating_power().into())
89    }
90
91    pub fn max_operating_power(&self) -> si::u32::Power {
92        si::u32::Power::new::<_250milliwatts>(self.raw_max_operating_power().into())
93    }
94}
95
96bitfield!(
97    #[derive(Clone, Copy, PartialEq, Eq)]
98    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
99    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100    pub struct Pps(pub u32): Debug, FromStorage, IntoStorage {
101        /// Object position (0000b and 1110b…1111b are Reserved and Shall Not be used)
102        pub object_position: u8 @ 28..=31,
103        /// Capability mismatch
104        pub capability_mismatch: bool @ 26,
105        /// USB communications capable
106        pub usb_communications_capable: bool @ 25,
107        /// No USB Suspend
108        pub no_usb_suspend: bool @ 24,
109        /// Unchunked extended messages supported
110        pub unchunked_extended_messages_supported: bool @ 23,
111        /// EPR mode capable
112        pub epr_mode_capable: bool @ 22,
113        /// Output voltage in 20mV units
114        pub raw_output_voltage: u16 @ 9..=20,
115        /// Operating current in 50mA units
116        pub raw_operating_current: u16 @ 0..=6,
117    }
118);
119
120impl Pps {
121    pub fn to_bytes(self, buf: &mut [u8]) -> usize {
122        LittleEndian::write_u32(buf, self.0);
123        4
124    }
125
126    pub fn output_voltage(&self) -> ElectricPotential {
127        ElectricPotential::new::<_20millivolts>(self.raw_output_voltage().into())
128    }
129
130    pub fn operating_current(&self) -> ElectricCurrent {
131        ElectricCurrent::new::<_50milliamperes>(self.raw_operating_current().into())
132    }
133}
134
135bitfield!(
136    #[derive(Clone, Copy, PartialEq, Eq)]
137    #[cfg_attr(feature = "defmt", derive(defmt::Format))]
138    #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
139    pub struct Avs(pub u32): Debug, FromStorage, IntoStorage {
140        /// Object position (0000b and 1110b…1111b are Reserved and Shall Not be used)
141        pub object_position: u8 @ 28..=31,
142        /// Capability mismatch
143        pub capability_mismatch: bool @ 26,
144        /// USB communications capable
145        pub usb_communications_capable: bool @ 25,
146        /// No USB Suspend
147        pub no_usb_suspend: bool @ 24,
148        /// Unchunked extended messages supported
149        pub unchunked_extended_messages_supported: bool @ 23,
150        /// EPR mode capable
151        pub epr_mode_capable: bool @ 22,
152        /// Output voltage in 20mV units
153        pub raw_output_voltage: u16 @ 9..=20,
154        /// Operating current in 50mA units
155        pub raw_operating_current: u16 @ 0..=6,
156    }
157);
158
159impl Avs {
160    pub fn to_bytes(self, buf: &mut [u8]) {
161        LittleEndian::write_u32(buf, self.0);
162    }
163
164    pub fn output_voltage(&self) -> ElectricPotential {
165        ElectricPotential::new::<_20millivolts>(self.raw_output_voltage().into())
166    }
167
168    pub fn operating_current(&self) -> ElectricCurrent {
169        ElectricCurrent::new::<_50milliamperes>(self.raw_operating_current().into())
170    }
171}
172
173/// Power requests towards the source.
174#[derive(Debug, Clone, Copy)]
175#[cfg_attr(feature = "defmt", derive(defmt::Format))]
176#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
177#[allow(unused)] // FIXME: Implement missing request types.
178pub enum PowerSource {
179    FixedVariableSupply(FixedVariableSupply),
180    Battery(Battery),
181    Pps(Pps),
182    Avs(Avs),
183    Unknown(RawDataObject),
184}
185
186/// Errors that can occur during sink requests towards the source.
187#[derive(Debug)]
188#[non_exhaustive]
189pub enum Error {
190    /// A requested (specific) voltage does not exist in the PDOs.
191    VoltageMismatch,
192}
193
194/// Requestable voltage levels.
195#[derive(Debug)]
196pub enum VoltageRequest {
197    /// The safe 5 V supply.
198    Safe5V,
199    /// The highest voltage that the source can supply.
200    Highest,
201    /// A specific voltage.
202    Specific(ElectricPotential),
203}
204
205/// Requestable currents.
206#[derive(Debug)]
207pub enum CurrentRequest {
208    /// The highest current that the source can supply.
209    Highest,
210    /// A specific current.
211    Specific(ElectricCurrent),
212}
213
214/// A fixed supply PDO, alongside its index in the PDO table.
215pub struct IndexedFixedSupply<'d>(pub &'d pdo::FixedSupply, usize);
216
217/// An augmented PDO, alongside its index in the PDO table.
218pub struct IndexedAugmented<'d>(pub &'d pdo::Augmented, usize);
219
220impl PowerSource {
221    pub fn object_position(&self) -> u8 {
222        match self {
223            PowerSource::FixedVariableSupply(p) => p.object_position(),
224            PowerSource::Battery(p) => p.object_position(),
225            PowerSource::Pps(p) => p.object_position(),
226            PowerSource::Avs(p) => p.object_position(),
227            PowerSource::Unknown(p) => p.object_position(),
228        }
229    }
230
231    /// Find the highest fixed voltage that can be found in the source capabilities.
232    ///
233    /// Reports the index of the found PDO, and the fixed supply instance, or `None` if there is no fixed supply PDO.
234    pub fn find_highest_fixed_voltage(source_capabilities: &pdo::SourceCapabilities) -> Option<IndexedFixedSupply<'_>> {
235        let mut selected_pdo = None;
236
237        for (index, cap) in source_capabilities.pdos().iter().enumerate() {
238            if let pdo::PowerDataObject::FixedSupply(fixed_supply) = cap {
239                selected_pdo = match selected_pdo {
240                    None => Some(IndexedFixedSupply(fixed_supply, index)),
241                    Some(ref x) => {
242                        if fixed_supply.voltage() > x.0.voltage() {
243                            Some(IndexedFixedSupply(fixed_supply, index))
244                        } else {
245                            selected_pdo
246                        }
247                    }
248                };
249            }
250        }
251
252        selected_pdo
253    }
254
255    /// Find a specific fixed voltage within the source capabilities.
256    ///
257    /// Reports the index of the found PDO, and the fixed supply instance, or `None` if there is no match to the request.
258    pub fn find_specific_fixed_voltage(
259        source_capabilities: &pdo::SourceCapabilities,
260        voltage: ElectricPotential,
261    ) -> Option<IndexedFixedSupply<'_>> {
262        for (index, cap) in source_capabilities.pdos().iter().enumerate() {
263            if let pdo::PowerDataObject::FixedSupply(fixed_supply) = cap
264                && (fixed_supply.voltage() == voltage)
265            {
266                return Some(IndexedFixedSupply(fixed_supply, index));
267            }
268        }
269
270        None
271    }
272
273    /// Find a suitable PDO for a Programmable Power Supply (PPS) by evaluating the provided voltage
274    /// request against the source capabilities.
275    ///
276    /// Reports the index of the found PDO, and the augmented supply instance, or `None` if there is no match to the request.
277    pub fn find_pps_voltage(
278        source_capabilities: &pdo::SourceCapabilities,
279        voltage: ElectricPotential,
280    ) -> Option<IndexedAugmented<'_>> {
281        for (index, cap) in source_capabilities.pdos().iter().enumerate() {
282            let pdo::PowerDataObject::Augmented(augmented) = cap else {
283                trace!("Skip non-augmented PDO {:?}", cap);
284                continue;
285            };
286
287            // Handle EPR when supported.
288            match augmented {
289                pdo::Augmented::Spr(spr) => {
290                    if spr.min_voltage() <= voltage && spr.max_voltage() >= voltage {
291                        return Some(IndexedAugmented(augmented, index));
292                    } else {
293                        trace!("Skip PDO, voltage out of range. {:?}", augmented);
294                    }
295                }
296                _ => trace!("Skip PDO, only SPR is supported. {:?}", augmented),
297            };
298        }
299
300        trace!("Could not find suitable PPS voltage");
301        None
302    }
303
304    /// Create a new, specific power source request for a fixed supply.
305    ///
306    /// # Arguments
307    ///
308    /// * `supply` - The combination of fixed supply PDO and its index in the PDO table.
309    /// * `current_request` - The desired current level.
310    pub fn new_fixed_specific(supply: IndexedFixedSupply, current_request: CurrentRequest) -> Result<Self, Error> {
311        let IndexedFixedSupply(pdo, index) = supply;
312
313        let (current, mismatch) = match current_request {
314            CurrentRequest::Highest => (pdo.max_current(), false),
315            CurrentRequest::Specific(x) => (x, x > pdo.max_current()),
316        };
317
318        let mut raw_current = current.get::<electric_current::centiampere>() as u16;
319
320        if raw_current > 0x3ff {
321            error!("Clamping invalid current: {} mA", 10 * raw_current);
322            raw_current = 0x3ff;
323        }
324
325        let object_position = index + 1;
326        assert!(object_position > 0b0000 && object_position <= 0b1110);
327
328        Ok(Self::FixedVariableSupply(
329            FixedVariableSupply(0)
330                .with_raw_operating_current(raw_current)
331                .with_raw_max_operating_current(raw_current)
332                .with_object_position(object_position as u8)
333                .with_capability_mismatch(mismatch)
334                .with_no_usb_suspend(true)
335                .with_usb_communications_capable(true), // FIXME: Make adjustable?
336        ))
337    }
338
339    /// Create a new power source request for a fixed supply.
340    ///
341    /// Finds a suitable PDO by evaluating the provided current and voltage requests against the source capabilities.
342    pub fn new_fixed(
343        current_request: CurrentRequest,
344        voltage_request: VoltageRequest,
345        source_capabilities: &pdo::SourceCapabilities,
346    ) -> Result<Self, Error> {
347        let selected = match voltage_request {
348            VoltageRequest::Safe5V => source_capabilities
349                .vsafe_5v()
350                .map(|supply| IndexedFixedSupply(supply, 0)),
351            VoltageRequest::Highest => Self::find_highest_fixed_voltage(source_capabilities),
352            VoltageRequest::Specific(x) => Self::find_specific_fixed_voltage(source_capabilities, x),
353        };
354
355        if selected.is_none() {
356            return Err(Error::VoltageMismatch);
357        }
358
359        Self::new_fixed_specific(selected.unwrap(), current_request)
360    }
361
362    /// Create a new power source request for a programmable power supply (PPS).
363    ///
364    /// Finds a suitable PDO by evaluating the provided current and voltage requests against the source capabilities.
365    /// If no PDO is found that matches the request, an error is returned.
366    pub fn new_pps(
367        current_request: CurrentRequest,
368        voltage: ElectricPotential,
369        source_capabilities: &pdo::SourceCapabilities,
370    ) -> Result<Self, Error> {
371        let selected = Self::find_pps_voltage(source_capabilities, voltage);
372
373        if selected.is_none() {
374            return Err(Error::VoltageMismatch);
375        }
376
377        let IndexedAugmented(pdo, index) = selected.unwrap();
378        let max_current = match pdo {
379            pdo::Augmented::Spr(spr) => spr.max_current(),
380            _ => unreachable!(),
381        };
382
383        let (current, mismatch) = match current_request {
384            CurrentRequest::Highest => (max_current, false),
385            CurrentRequest::Specific(x) => (x, x > max_current),
386        };
387
388        let mut raw_current = current.get::<_50milliamperes>() as u16;
389
390        if raw_current > 0x3ff {
391            error!("Clamping invalid current: {} mA", 10 * raw_current);
392            raw_current = 0x3ff;
393        }
394
395        let raw_voltage = voltage.get::<_20millivolts>() as u16;
396
397        let object_position = index + 1;
398        assert!(object_position > 0b0000 && object_position <= 0b1110);
399
400        Ok(Self::Pps(
401            Pps(0)
402                .with_raw_output_voltage(raw_voltage)
403                .with_raw_operating_current(raw_current)
404                .with_object_position(object_position as u8)
405                .with_capability_mismatch(mismatch)
406                .with_no_usb_suspend(true)
407                .with_usb_communications_capable(true),
408        ))
409    }
410}