1use crate::{Status, pui};
4
5pub const FORMAT: u32 = 1;
6pub const VERSION: u32 = 1;
7
8pub struct Field {
9 pub name: &'static str,
10 pub address: u16,
11 pub width: usize,
12 pub unit: &'static str,
13}
14
15macro_rules! fields {
16 ($(($name:literal, $address:literal, $width:literal, $unit:literal)),* $(,)?) => {
17 pub const FIELDS: &[Field] = &[$(Field { name: $name, address: $address, width: $width, unit: $unit }),*];
18 };
19}
20
21fields![
23 ("battery_id", 0x929a, 1, "bits"),
24 ("operation_config_a", 0x9206, 2, "bits"),
25 ("operation_config_b", 0x9208, 2, "bits"),
26 ("cedv_config", 0x929b, 2, "bits"),
27 ("full_capacity", 0x929d, 2, "mAh"),
28 ("design_capacity", 0x929f, 2, "mAh"),
29 ("design_voltage", 0x92a3, 2, "mV"),
30 ("termination_voltage_margin", 0x92a5, 2, "mV"),
31 ("charging_voltage", 0x91fd, 2, "mV"),
32 ("taper_current", 0x9201, 2, "mA"),
33 ("discharge_detection", 0x9228, 2, "mA"),
34 ("charge_detection", 0x922a, 2, "mA"),
35 ("quit_current", 0x922c, 2, "mA"),
36 ("battery_low", 0x9251, 2, "0.01%"),
37 ("learning_low_temp", 0x925b, 1, "raw"),
38 ("overload_current", 0x9264, 2, "mA"),
39 ("near_full", 0x926b, 2, "mAh"),
40 ("reserve_capacity", 0x926d, 2, "mAh"),
41 ("charge_efficiency", 0x926f, 1, "%"),
42 ("discharge_efficiency", 0x9270, 1, "%"),
43 ("fixed_edv0", 0x92b4, 2, "mV"),
44 ("edv0_hold", 0x92b6, 1, "s"),
45 ("fixed_edv1", 0x92b7, 2, "mV"),
46 ("edv1_hold", 0x92b9, 1, "s"),
47 ("fixed_edv2", 0x92ba, 2, "mV"),
48 ("edv2_hold", 0x92bc, 1, "s"),
49 ("emf", 0x92a7, 2, "raw"),
50 ("c0", 0x92a9, 2, "raw"),
51 ("r0", 0x92ab, 2, "raw"),
52 ("t0", 0x92ad, 2, "raw"),
53 ("r1", 0x92af, 2, "raw"),
54 ("tc", 0x92b1, 1, "raw"),
55 ("c1", 0x92b2, 1, "raw"),
56 ("age_factor", 0x92b3, 1, "raw"),
57 ("flag_config_a", 0x927f, 2, "bits"),
58 ("flag_config_b", 0x9281, 1, "bits"),
59 ("full_set_voltage", 0x9288, 2, "mV"),
60 ("full_clear_voltage", 0x928a, 2, "mV"),
61 ("full_set_soc", 0x928c, 1, "%"),
62 ("full_clear_soc", 0x928d, 1, "%"),
63 ("smoothing_config", 0x9271, 1, "bits"),
64 ("cc_gain", 0x9184, 4, "F4 bits"),
65 ("cc_delta", 0x9188, 4, "F4 bits"),
66 ("current_deadband", 0x91de, 1, "mA"),
67 ("cc_deadband", 0x91df, 1, "294 nV"),
68 ("voltage_0_dod", 0x92bd, 2, "mV"),
69 ("voltage_50_dod", 0x92c7, 2, "mV"),
70 ("voltage_100_dod", 0x92d1, 2, "mV"),
71];
72
73pub const MAX_ENCODED: usize = 2 + FIELDS.len() * 3 + 2;
74
75const fn offset(index: usize) -> usize {
76 let mut result = 0;
77 let mut i = 0;
78 while i < index {
79 result += FIELDS[i].width;
80 i += 1;
81 }
82 result
83}
84pub const RAW_LEN: usize = offset(FIELDS.len());
85
86#[derive(Clone, Copy, Debug, PartialEq, Eq)]
87pub struct Config {
88 data: [u8; RAW_LEN],
91}
92
93impl Default for Config {
94 fn default() -> Self {
95 Self { data: [0; RAW_LEN] }
96 }
97}
98
99impl Config {
100 pub fn taper_current_ma(&self) -> u16 {
101 self.value(9) as u16
102 }
103
104 pub fn value(&self, index: usize) -> u32 {
105 let start = offset(index);
106 self.data[start..start + FIELDS[index].width]
107 .iter()
108 .fold(0u32, |v, b| (v << 8) | u32::from(*b))
109 }
110
111 pub fn values(&self) -> impl Iterator<Item = u32> + '_ {
112 (0..FIELDS.len()).map(|i| self.value(i))
113 }
114
115 pub fn set(&mut self, index: usize, value: u32) -> Result<(), Status> {
116 let field = FIELDS.get(index).ok_or(Status::INVALID_ARGUMENT)?;
117 if field.width < 4 && value >= 1 << (8 * field.width) {
118 return Err(Status::INVALID_ARGUMENT);
119 }
120 let start = offset(index);
121 self.data[start..start + field.width]
122 .copy_from_slice(&value.to_be_bytes()[4 - field.width..]);
123 Ok(())
124 }
125
126 pub fn encode(&self, out: &mut [u8]) -> Result<usize, Status> {
127 let mut used = pui::encode(FORMAT, out).map_err(|_| Status::NOMEM)?;
128 used += pui::encode(VERSION, &mut out[used..]).map_err(|_| Status::NOMEM)?;
129 for (field, value) in FIELDS.iter().zip(self.values()) {
130 if field.width == 4 {
131 out.get_mut(used..used + 4)
132 .ok_or(Status::NOMEM)?
133 .copy_from_slice(&value.to_le_bytes());
134 used += 4;
135 } else {
136 if value >= 1 << (8 * field.width) {
137 return Err(Status::INVALID_ARGUMENT);
138 }
139 used += pui::encode(value, &mut out[used..]).map_err(|_| Status::NOMEM)?;
140 }
141 }
142 Ok(used)
143 }
144
145 pub fn decode(mut bytes: &[u8]) -> Result<Self, Status> {
146 fn next(bytes: &mut &[u8]) -> Result<u32, Status> {
147 let (value, used) = pui::decode(bytes).map_err(|_| Status::PARSE_ERROR)?;
148 *bytes = &bytes[used..];
149 Ok(value)
150 }
151 if next(&mut bytes)? != FORMAT || next(&mut bytes)? != VERSION {
152 return Err(Status::UNIMPLEMENTED);
153 }
154 let mut result = Self::default();
155 for (index, field) in FIELDS.iter().enumerate() {
156 let value = if field.width == 4 {
157 let value = u32::from_le_bytes(
158 bytes
159 .get(..4)
160 .ok_or(Status::PARSE_ERROR)?
161 .try_into()
162 .unwrap(),
163 );
164 bytes = &bytes[4..];
165 value
166 } else {
167 next(&mut bytes)?
168 };
169 result.set(index, value).map_err(|_| Status::PARSE_ERROR)?;
170 }
171 if !bytes.is_empty() {
172 return Err(Status::PARSE_ERROR);
173 }
174 Ok(result)
175 }
176
177 pub fn cedv_config(&self) -> u32 {
178 self.value(3)
179 }
180}
181
182impl core::fmt::Display for Config {
183 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
184 writeln!(f, "BQ27220 configuration v1")?;
185 for (field, value) in FIELDS.iter().zip(self.values()) {
186 if field.unit.contains("bits") {
187 writeln!(f, " {}: {value:#x} ({})", field.name, field.unit)?;
188 } else {
189 writeln!(f, " {}: {value} {}", field.name, field.unit)?;
190 }
191 }
192 write!(
193 f,
194 " fcc_limit: {}; independent_charger: {}; edv_compensation: {}",
195 self.cedv_config() & 0x100 != 0,
196 self.cedv_config() & 0x10 != 0,
197 self.cedv_config() & 8 != 0
198 )
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 #[test]
206 fn compact_snapshot_rejects_truncation_unknown_versions_and_trailing_data() {
207 let mut value = Config::default();
208 value.set(3, 0x102a).unwrap();
209 value.set(4, 1372).unwrap();
210 value.set(5, 1500).unwrap();
211 value.set(41, 0x3f2c3d70).unwrap();
212 let mut bytes = [0; MAX_ENCODED];
213 let len = value.encode(&mut bytes).unwrap();
214 assert_eq!(&bytes[..8], &[1, 1, 0, 0, 0, 0xaa, 0x20, 0xdc]);
215 assert_eq!(Config::decode(&bytes[..len]), Ok(value));
216 for end in 0..len {
217 assert!(Config::decode(&bytes[..end]).is_err());
218 }
219 assert!(Config::decode(&bytes[..len + 1]).is_err());
220 bytes[1] = 2;
221 assert_eq!(Config::decode(&bytes[..len]), Err(Status::UNIMPLEMENTED));
222 }
223}