umsh_ulcp/
battery_diagnostics.rs

1//! Optional battery properties, compact codecs, and one acquisition's results.
2use crate::{Status, battery::BatteryStatus, ids::prop, pui, sint, uint};
3
4pub const KEYS: [u32; 13] = [
5    prop::BATTERY_CURRENT,
6    prop::BATTERY_REMAINING_CAPACITY,
7    prop::BATTERY_FULL_CAPACITY,
8    prop::BATTERY_DESIGN_CAPACITY,
9    prop::BATTERY_EXT_POWER_PRESENT,
10    prop::BATTERY_PRESENT,
11    prop::BATTERY_GAUGE_FULL,
12    prop::BATTERY_GAUGE_INITIALIZED,
13    prop::BATTERY_GAUGE_SMOOTHING,
14    prop::BATTERY_CHARGE_VOLTAGE_REQUEST,
15    prop::BATTERY_GAUGE_FORMAT,
16    prop::BATTERY_GAUGE_STATUS,
17    prop::BATTERY_GAUGE_OPERATION_STATUS,
18];
19pub const BQ27220: u32 = 1;
20
21pub const fn unit(key: u32) -> Option<&'static str> {
22    match key {
23        prop::BATTERY_CURRENT => Some("mA"),
24        prop::BATTERY_REMAINING_CAPACITY..=prop::BATTERY_DESIGN_CAPACITY => Some("mAh"),
25        prop::BATTERY_CHARGE_VOLTAGE_REQUEST => Some("mV"),
26        _ => None,
27    }
28}
29
30impl core::fmt::Display for Value {
31    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32        match self {
33            Self::Current(v) => write!(f, "{v}"),
34            Self::Unsigned(v) | Self::Format(v) => write!(f, "{v}"),
35            Self::Bool(v) => write!(f, "{v}"),
36            Self::Voltage(VoltageRequest::Millivolts(v)) => write!(f, "{v}"),
37            Self::Voltage(VoltageRequest::Maximum) => f.write_str("maximum"),
38        }
39    }
40}
41
42pub const fn index(key: u32) -> Option<usize> {
43    if key >= prop::BATTERY_CURRENT && key <= prop::BATTERY_GAUGE_OPERATION_STATUS {
44        Some((key - prop::BATTERY_CURRENT) as usize)
45    } else {
46        None
47    }
48}
49
50/// Requested/supported fields. Bit zero is the existing battery snapshot.
51#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
52pub struct Fields(u16);
53impl Fields {
54    pub const NONE: Self = Self(0);
55    pub const SNAPSHOT: Self = Self(1);
56    pub const DIAGNOSTICS: Self = Self(0x3ffe);
57    pub const ALL: Self = Self(0x3fff);
58    /// Deliberately excluded from ALL/DIAGNOSTICS: requires explicit acquisition.
59    pub const GAUGE_CONFIG: Self = Self(0x4000);
60    /// Live chip-specific readings, acquired only when explicitly requested.
61    pub const GAUGE_TELEMETRY: Self = Self(0x8000);
62    pub const fn for_key(key: u32) -> Self {
63        if key == prop::BATTERY {
64            Self::SNAPSHOT
65        } else if key == prop::BATTERY_GAUGE_CONFIG {
66            Self::GAUGE_CONFIG
67        } else if key == prop::BATTERY_GAUGE_TELEMETRY {
68            Self::GAUGE_TELEMETRY
69        } else if let Some(index) = index(key) {
70            Self(1 << (index + 1))
71        } else {
72            Self::NONE
73        }
74    }
75    pub const fn contains(self, key: u32) -> bool {
76        self.0 & Self::for_key(key).0 != 0
77    }
78    pub const fn union(self, other: Self) -> Self {
79        Self(self.0 | other.0)
80    }
81    pub const fn intersection(self, other: Self) -> Self {
82        Self(self.0 & other.0)
83    }
84    pub const fn is_empty(self) -> bool {
85        self.0 == 0
86    }
87}
88
89#[derive(Clone, Copy, Debug, PartialEq, Eq)]
90pub enum VoltageRequest {
91    Millivolts(u32),
92    Maximum,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub enum Value {
97    Current(i32),
98    Unsigned(u32),
99    Bool(bool),
100    Voltage(VoltageRequest),
101    Format(u32),
102}
103
104impl Value {
105    /// Decode a property's nonempty value; empty means temporarily unavailable.
106    pub fn decode(key: u32, bytes: &[u8]) -> Result<Option<Self>, Status> {
107        if index(key).is_none() {
108            return Err(Status::PROP_NOT_FOUND);
109        }
110        if bytes.is_empty() {
111            return Ok(None);
112        }
113        let bad = || Status::PARSE_ERROR;
114        Ok(Some(match key {
115            prop::BATTERY_CURRENT => Self::Current(sint::decode(bytes).map_err(|_| bad())?),
116            prop::BATTERY_EXT_POWER_PRESENT..=prop::BATTERY_GAUGE_SMOOTHING => {
117                Self::Bool(match bytes {
118                    [0] => false,
119                    [1] => true,
120                    _ => return Err(bad()),
121                })
122            }
123            prop::BATTERY_CHARGE_VOLTAGE_REQUEST => {
124                let (tag, used) = pui::decode(bytes).map_err(|_| bad())?;
125                Self::Voltage(match (tag, &bytes[used..]) {
126                    (0, rest) => VoltageRequest::Millivolts(uint::decode(rest).map_err(|_| bad())?),
127                    (1, []) => VoltageRequest::Maximum,
128                    _ => return Err(bad()),
129                })
130            }
131            prop::BATTERY_GAUGE_FORMAT => {
132                let (format, used) = pui::decode(bytes).map_err(|_| bad())?;
133                if format == 0 || used != bytes.len() {
134                    return Err(bad());
135                }
136                Self::Format(format)
137            }
138            _ => Self::Unsigned(uint::decode(bytes).map_err(|_| bad())?),
139        }))
140    }
141
142    pub fn encode(self, key: u32, out: &mut [u8]) -> Result<usize, Status> {
143        let len = match self {
144            Self::Current(value) => sint::encode(value, out).map_err(|_| Status::NOMEM)?,
145            Self::Unsigned(value) => uint::encode(value, out).map_err(|_| Status::NOMEM)?,
146            Self::Bool(value) => {
147                *out.first_mut().ok_or(Status::NOMEM)? = u8::from(value);
148                1
149            }
150            Self::Format(value) => pui::encode(value, out).map_err(|_| Status::NOMEM)?,
151            Self::Voltage(value) => {
152                *out.first_mut().ok_or(Status::NOMEM)? = match value {
153                    VoltageRequest::Millivolts(_) => 0,
154                    VoltageRequest::Maximum => 1,
155                };
156                match value {
157                    VoltageRequest::Millivolts(mv) => {
158                        1 + uint::encode(mv, &mut out[1..]).map_err(|_| Status::NOMEM)?
159                    }
160                    VoltageRequest::Maximum => 1,
161                }
162            }
163        };
164        if Self::decode(key, &out[..len]) != Ok(Some(self)) {
165            return Err(Status::FAILURE);
166        }
167        Ok(len)
168    }
169}
170
171/// Per-property failures stay independent. Defaults fail rather than fabricate data.
172#[derive(Clone, Copy, Debug)]
173pub struct Sample {
174    pub snapshot: Result<BatteryStatus, ()>,
175    pub gauge_config: Result<crate::battery_gauge_config::Config, Status>,
176    pub gauge_telemetry: Result<crate::battery_gauge_telemetry::Telemetry, Status>,
177    values: [Result<Option<Value>, Status>; 13],
178}
179impl Default for Sample {
180    fn default() -> Self {
181        Self {
182            snapshot: Err(()),
183            gauge_config: Err(Status::FAILURE),
184            gauge_telemetry: Err(Status::FAILURE),
185            values: [Err(Status::FAILURE); 13],
186        }
187    }
188}
189impl Sample {
190    pub fn set(&mut self, key: u32, value: Result<Option<Value>, Status>) {
191        if let Some(i) = index(key) {
192            self.values[i] = value;
193        }
194    }
195    pub fn get(&self, key: u32) -> Result<Option<Value>, Status> {
196        self.values[index(key).ok_or(Status::PROP_NOT_FOUND)?]
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203    #[test]
204    fn complete_reply_matches_the_documented_compact_budget() {
205        let values = [
206            Value::Current(-100),
207            Value::Unsigned(1500),
208            Value::Unsigned(1500),
209            Value::Unsigned(1500),
210            Value::Bool(true),
211            Value::Bool(true),
212            Value::Bool(true),
213            Value::Bool(true),
214            Value::Bool(false),
215            Value::Voltage(VoltageRequest::Millivolts(4200)),
216            Value::Format(1),
217            Value::Unsigned(0x4268),
218            Value::Unsigned(0xa6),
219        ];
220        let mut bytes = [0; 100];
221        let mut reply = crate::frame::prop_are(&mut bytes, 1).unwrap();
222        reply
223            .write_entry(prop::BATTERY, &[7, 0x68, 0x10, 100, 0])
224            .unwrap();
225        for (key, value) in KEYS.into_iter().zip(values) {
226            let mut encoded = [0; 5];
227            let len = value.encode(key, &mut encoded).unwrap();
228            reply.write_entry(key, &encoded[..len]).unwrap();
229        }
230        assert_eq!(reply.finish(), 67);
231    }
232    #[test]
233    fn signed_and_tagged_values_use_the_shortest_encoding() {
234        for (value, expected) in [
235            (-100, &[0x9c][..]),
236            (-200, &[0x38, 0xff][..]),
237            (-128, &[0x80][..]),
238            (-129, &[0x7f, 0xff][..]),
239            (127, &[0x7f][..]),
240            (128, &[0x80, 0][..]),
241        ] {
242            let mut bytes = [0; 5];
243            let len = Value::Current(value)
244                .encode(prop::BATTERY_CURRENT, &mut bytes)
245                .unwrap();
246            assert_eq!(&bytes[..len], expected);
247            assert_eq!(
248                Value::decode(prop::BATTERY_CURRENT, expected),
249                Ok(Some(Value::Current(value)))
250            );
251        }
252        assert_eq!(
253            Value::decode(prop::BATTERY_CURRENT, &[0x9c, 0xff, 0xff, 0xff]),
254            Ok(Some(Value::Current(-100)))
255        );
256        let mut bytes = [0; 5];
257        let len = Value::Voltage(VoltageRequest::Millivolts(4200))
258            .encode(prop::BATTERY_CHARGE_VOLTAGE_REQUEST, &mut bytes)
259            .unwrap();
260        assert_eq!(&bytes[..len], &[0, 0x68, 0x10]);
261        assert_eq!(
262            Value::decode(prop::BATTERY_CHARGE_VOLTAGE_REQUEST, &[1]),
263            Ok(Some(Value::Voltage(VoltageRequest::Maximum)))
264        );
265        for bytes in [&[0][..], &[1, 0][..], &[2][..], &[0, 0, 0, 0, 0, 0][..]] {
266            assert!(Value::decode(prop::BATTERY_CHARGE_VOLTAGE_REQUEST, bytes).is_err());
267        }
268        assert_eq!(Value::decode(prop::BATTERY_CURRENT, &[]), Ok(None));
269        assert_eq!(
270            Value::decode(prop::BATTERY_PRESENT, &[0]),
271            Ok(Some(Value::Bool(false)))
272        );
273        assert!(Value::decode(prop::BATTERY_PRESENT, &[0, 0]).is_err());
274        assert!(Value::decode(prop::BATTERY_CURRENT, &[0; 5]).is_err());
275        assert!(Value::decode(prop::BATTERY_GAUGE_FORMAT, &[0]).is_err());
276        assert_eq!(
277            Value::decode(prop::BATTERY_GAUGE_FORMAT, &[99]),
278            Ok(Some(Value::Format(99)))
279        );
280    }
281}