1use crate::pui;
10
11pub const FLAG_VOLTAGE: u8 = 1 << 0;
13pub const FLAG_LEVEL: u8 = 1 << 1;
15pub const FLAG_CHARGE_STATE: u8 = 1 << 2;
17
18const FLAGS_RESERVED: u8 = !(FLAG_VOLTAGE | FLAG_LEVEL | FLAG_CHARGE_STATE);
19
20pub const MAX_ENCODED_LEN: usize = 5;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum BatteryChargeState {
27 Discharging = 0,
29 Charging = 1,
31 Charged = 2,
33}
34
35impl BatteryChargeState {
36 pub const fn code(self) -> u32 {
38 self as u32
39 }
40
41 pub const fn from_code(code: u32) -> Option<Self> {
43 match code {
44 0 => Some(Self::Discharging),
45 1 => Some(Self::Charging),
46 2 => Some(Self::Charged),
47 _ => None,
48 }
49 }
50}
51
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum BatteryError {
55 Malformed,
58 BufferTooSmall,
60}
61
62#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
67pub struct BatteryStatus {
68 pub voltage_mv: Option<u16>,
70 pub level_percent: Option<u8>,
72 pub charge_state: Option<BatteryChargeState>,
74}
75
76impl BatteryStatus {
77 pub const fn is_empty(&self) -> bool {
79 self.voltage_mv.is_none() && self.level_percent.is_none() && self.charge_state.is_none()
80 }
81
82 pub fn flags(&self) -> u8 {
84 let mut flags = 0;
85 if self.voltage_mv.is_some() {
86 flags |= FLAG_VOLTAGE;
87 }
88 if self.level_percent.is_some() {
89 flags |= FLAG_LEVEL;
90 }
91 if self.charge_state.is_some() {
92 flags |= FLAG_CHARGE_STATE;
93 }
94 flags
95 }
96
97 pub fn encode(&self, out: &mut [u8]) -> Result<usize, BatteryError> {
102 if self.is_empty() {
103 return Ok(0);
104 }
105 let mut len = 0;
106 let mut push = |byte: u8| -> Result<(), BatteryError> {
107 *out.get_mut(len).ok_or(BatteryError::BufferTooSmall)? = byte;
108 len += 1;
109 Ok(())
110 };
111 push(self.flags())?;
112 if let Some(mv) = self.voltage_mv {
113 let [low, high] = mv.to_le_bytes();
114 push(low)?;
115 push(high)?;
116 }
117 if let Some(percent) = self.level_percent {
118 if percent > 100 {
119 return Err(BatteryError::Malformed);
120 }
121 push(percent)?;
122 }
123 if let Some(state) = self.charge_state {
124 let mut pui_buf = [0u8; pui::MAX_LEN];
125 let pui_len = pui::encode(state.code(), &mut pui_buf)
126 .map_err(|_| BatteryError::BufferTooSmall)?;
127 for &byte in &pui_buf[..pui_len] {
128 push(byte)?;
129 }
130 }
131 Ok(len)
132 }
133
134 pub fn decode(value: &[u8]) -> Result<Self, BatteryError> {
141 let Some((&flags, mut rest)) = value.split_first() else {
142 return Ok(Self::default());
143 };
144 if flags == 0 || flags & FLAGS_RESERVED != 0 {
145 return Err(BatteryError::Malformed);
146 }
147 let mut take = |count: usize| -> Result<&[u8], BatteryError> {
148 if rest.len() < count {
149 return Err(BatteryError::Malformed);
150 }
151 let (field, remaining) = rest.split_at(count);
152 rest = remaining;
153 Ok(field)
154 };
155 let voltage_mv = if flags & FLAG_VOLTAGE != 0 {
156 let field = take(2)?;
157 Some(u16::from_le_bytes([field[0], field[1]]))
158 } else {
159 None
160 };
161 let level_percent = if flags & FLAG_LEVEL != 0 {
162 let percent = take(1)?[0];
163 if percent > 100 {
164 return Err(BatteryError::Malformed);
165 }
166 Some(percent)
167 } else {
168 None
169 };
170 let charge_state = if flags & FLAG_CHARGE_STATE != 0 {
171 let (code, consumed) = pui::decode(rest).map_err(|_| BatteryError::Malformed)?;
172 rest = &rest[consumed..];
173 Some(BatteryChargeState::from_code(code).ok_or(BatteryError::Malformed)?)
174 } else {
175 None
176 };
177 if !rest.is_empty() {
178 return Err(BatteryError::Malformed);
179 }
180 Ok(Self {
181 voltage_mv,
182 level_percent,
183 charge_state,
184 })
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[track_caller]
193 fn round_trip(status: BatteryStatus, expected: &[u8]) {
194 let mut buf = [0u8; MAX_ENCODED_LEN];
195 let len = status.encode(&mut buf).unwrap();
196 assert_eq!(&buf[..len], expected, "encoding of {status:?}");
197 assert_eq!(BatteryStatus::decode(expected).unwrap(), status);
198 }
199
200 #[test]
201 fn every_field_combination_round_trips() {
202 round_trip(BatteryStatus::default(), &[]);
203 round_trip(
204 BatteryStatus {
205 voltage_mv: Some(3987),
206 ..Default::default()
207 },
208 &[0b001, 0x93, 0x0F],
209 );
210 round_trip(
211 BatteryStatus {
212 level_percent: Some(100),
213 ..Default::default()
214 },
215 &[0b010, 100],
216 );
217 round_trip(
218 BatteryStatus {
219 charge_state: Some(BatteryChargeState::Charged),
220 ..Default::default()
221 },
222 &[0b100, 2],
223 );
224 round_trip(
225 BatteryStatus {
226 voltage_mv: Some(4200),
227 level_percent: Some(87),
228 ..Default::default()
229 },
230 &[0b011, 0x68, 0x10, 87],
231 );
232 round_trip(
233 BatteryStatus {
234 voltage_mv: Some(3700),
235 charge_state: Some(BatteryChargeState::Discharging),
236 ..Default::default()
237 },
238 &[0b101, 0x74, 0x0E, 0],
239 );
240 round_trip(
241 BatteryStatus {
242 level_percent: Some(0),
243 charge_state: Some(BatteryChargeState::Charging),
244 ..Default::default()
245 },
246 &[0b110, 0, 1],
247 );
248 round_trip(
249 BatteryStatus {
250 voltage_mv: Some(4180),
251 level_percent: Some(99),
252 charge_state: Some(BatteryChargeState::Charging),
253 },
254 &[0b111, 0x54, 0x10, 99, 1],
255 );
256 }
257
258 #[test]
259 fn rejects_malformed_values() {
260 assert_eq!(BatteryStatus::decode(&[0]), Err(BatteryError::Malformed));
262 assert_eq!(
264 BatteryStatus::decode(&[0b1000, 1]),
265 Err(BatteryError::Malformed)
266 );
267 assert_eq!(
269 BatteryStatus::decode(&[0b001, 0x93]),
270 Err(BatteryError::Malformed)
271 );
272 assert_eq!(
274 BatteryStatus::decode(&[0b010, 50, 0]),
275 Err(BatteryError::Malformed)
276 );
277 assert_eq!(
279 BatteryStatus::decode(&[0b010, 101]),
280 Err(BatteryError::Malformed)
281 );
282 assert_eq!(
284 BatteryStatus::decode(&[0b100, 3]),
285 Err(BatteryError::Malformed)
286 );
287 assert_eq!(
289 BatteryStatus::decode(&[0b100, 0x80]),
290 Err(BatteryError::Malformed)
291 );
292 }
293
294 #[test]
295 fn encode_rejects_out_of_range_level() {
296 let status = BatteryStatus {
297 level_percent: Some(101),
298 ..Default::default()
299 };
300 let mut buf = [0u8; MAX_ENCODED_LEN];
301 assert_eq!(status.encode(&mut buf), Err(BatteryError::Malformed));
302 }
303
304 #[test]
305 fn encode_reports_short_buffers() {
306 let status = BatteryStatus {
307 voltage_mv: Some(4000),
308 ..Default::default()
309 };
310 let mut buf = [0u8; 2];
311 assert_eq!(status.encode(&mut buf), Err(BatteryError::BufferTooSmall));
312 }
313
314 #[test]
315 fn charge_state_codes_are_strict() {
316 assert_eq!(
317 BatteryChargeState::from_code(0),
318 Some(BatteryChargeState::Discharging)
319 );
320 assert_eq!(
321 BatteryChargeState::from_code(1),
322 Some(BatteryChargeState::Charging)
323 );
324 assert_eq!(
325 BatteryChargeState::from_code(2),
326 Some(BatteryChargeState::Charged)
327 );
328 assert_eq!(BatteryChargeState::from_code(3), None);
329 }
330}