1#![doc = include_str!("../README.md")]
5
6pub mod error;
7pub mod registers;
8
9use error::Error;
10use i2cdev::core::I2CDevice;
11use i2cdev::linux::LinuxI2CDevice;
12use registers::{
13 BATTERY_REG, CELL_VOLTAGE_REG, CHARGING_REG, COMMUNICATION_REG, ChargerActivity, ChargingState,
14 CommState, POWEROFF_REG, USBC_VBUS_REG, UsbCInputState, UsbCPowerDelivery,
15};
16use crate::registers::SOFTWARE_REV_REG;
17
18pub const DEFAULT_I2C_ADDRESS: u16 = 0x2d;
20
21pub const DEFAULT_I2C_DEV_PATH: &str = "/dev/i2c-1";
23
24pub const DEFAULT_CELL_LOW_VOLTAGE_THRESHOLD: u16 = 3400; pub const POWEROFF_VALUE: u8 = 0x55;
32
33#[derive(Debug)]
35pub struct PowerState {
36 pub charging_state: ChargingState,
37 pub charger_activity: ChargerActivity,
38 pub usbc_input_state: UsbCInputState,
39 pub usbc_power_delivery: UsbCPowerDelivery,
40}
41
42#[derive(Debug)]
45pub struct CommunicationState {
46 pub bq4050: CommState,
47 pub ip2368: CommState,
48}
49
50#[derive(Debug)]
58pub struct BatteryState {
59 pub millivolts: u16,
60 pub milliamps: i16,
61 pub remaining_percent: u16,
62 pub remaining_capacity_milliamphours: u16,
63 pub remaining_runtime_minutes: u16,
64 pub time_to_full_minutes: u16,
65}
66
67#[derive(Debug)]
69pub struct CellVoltage {
70 pub cell_1_millivolts: u16,
71 pub cell_2_millivolts: u16,
72 pub cell_3_millivolts: u16,
73 pub cell_4_millivolts: u16,
74}
75
76#[derive(Debug)]
78pub struct UsbCVBus {
79 pub millivolts: u16,
80 pub milliamps: u16,
81 pub milliwatts: u16,
82}
83
84pub struct UpsHatE {
90 i2c_bus: LinuxI2CDevice,
91}
92
93impl Default for UpsHatE {
94 fn default() -> Self {
97 let i2c = LinuxI2CDevice::new(DEFAULT_I2C_DEV_PATH, DEFAULT_I2C_ADDRESS)
98 .expect("Failed to open I2C device");
99
100 Self { i2c_bus: i2c }
101 }
102}
103
104impl UpsHatE {
105 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn from_i2c_device(i2c_bus: LinuxI2CDevice) -> Self {
114 Self { i2c_bus }
115 }
116
117 pub fn get_cell_voltage(&mut self) -> Result<CellVoltage, Error> {
118 let data = self.read_block(CELL_VOLTAGE_REG.id, CELL_VOLTAGE_REG.length)?;
119
120 let voltages = CellVoltage {
121 cell_1_millivolts: u16::from_le_bytes([data[0], data[1]]),
122 cell_2_millivolts: u16::from_le_bytes([data[2], data[3]]),
123 cell_3_millivolts: u16::from_le_bytes([data[4], data[5]]),
124 cell_4_millivolts: u16::from_le_bytes([data[6], data[7]]),
125 };
126
127 Ok(voltages)
128 }
129
130 pub fn get_usbc_vbus(&mut self) -> Result<UsbCVBus, Error> {
131 let data = self.read_block(USBC_VBUS_REG.id, USBC_VBUS_REG.length)?;
132
133 let vbus = UsbCVBus {
134 millivolts: u16::from_le_bytes([data[0], data[1]]),
135 milliamps: u16::from_le_bytes([data[2], data[3]]),
136 milliwatts: u16::from_le_bytes([data[4], data[5]]),
137 };
138
139 Ok(vbus)
140 }
141
142 pub fn get_battery_state(&mut self) -> Result<BatteryState, Error> {
143 let data = self.read_block(BATTERY_REG.id, BATTERY_REG.length)?;
144
145 let milliamps = i16::from_le_bytes([data[2], data[3]]);
146
147 let (remaining_runtime_minutes, time_to_full_minutes) = if milliamps < 0 {
148 (u16::from_le_bytes([data[8], data[9]]), 0)
150 } else {
151 (0, u16::from_le_bytes([data[10], data[11]]))
153 };
154
155 let state = BatteryState {
156 millivolts: u16::from_le_bytes([data[0], data[1]]),
157 milliamps,
158 remaining_percent: u16::from_le_bytes([data[4], data[5]]),
159 remaining_capacity_milliamphours: u16::from_le_bytes([data[6], data[7]]),
160 remaining_runtime_minutes,
161 time_to_full_minutes,
162 };
163
164 Ok(state)
165 }
166
167 pub fn get_power_state(&mut self) -> Result<PowerState, Error> {
168 let data = self.read_block(CHARGING_REG.id, CHARGING_REG.length)?;
169 let byte = data[0];
170
171 let charger_activity = ChargerActivity::try_from(byte & 0b111)?;
172 let usbc_input_state = UsbCInputState::from(byte & (1 << 5) != 0);
173 let usbc_power_delivery = UsbCPowerDelivery::from(byte & (1 << 6) != 0);
174 let charging_state = ChargingState::from(byte & (1 << 7) != 0);
175
176 Ok(PowerState {
177 charging_state,
178 charger_activity,
179 usbc_input_state,
180 usbc_power_delivery,
181 })
182 }
183
184 pub fn get_communication_state(&mut self) -> Result<CommunicationState, Error> {
185 let data = self.read_block(COMMUNICATION_REG.id, COMMUNICATION_REG.length)?;
186 let byte = data[0];
187
188 let ip2368 = CommState::from(byte & (1 << 0) != 0);
189 let bq4050 = CommState::from(byte & (1 << 1) != 0);
190
191 Ok(CommunicationState { bq4050, ip2368 })
192 }
193
194 pub fn get_software_revision(&mut self) -> Result<u8, Error> {
195 let data = self.read_block(SOFTWARE_REV_REG.id, SOFTWARE_REV_REG.length)?;
196 Ok(data[0])
197 }
198
199 #[allow(clippy::wrong_self_convention)]
204 pub fn is_battery_low(&mut self) -> Result<bool, Error> {
205 const CUTOFF: u32 = 4 * DEFAULT_CELL_LOW_VOLTAGE_THRESHOLD as u32;
206
207 let cell_voltages = self.get_cell_voltage()?;
208
209 let total_voltage: u32 = (cell_voltages.cell_1_millivolts
210 + cell_voltages.cell_2_millivolts
211 + cell_voltages.cell_3_millivolts
212 + cell_voltages.cell_4_millivolts) as u32;
213
214 Ok(total_voltage <= CUTOFF)
215 }
216
217 pub fn force_power_off(&mut self) -> Result<(), Error> {
221 self.i2c_bus
222 .smbus_write_byte_data(POWEROFF_REG.id, POWEROFF_VALUE)?;
223 Ok(())
224 }
225
226 #[allow(clippy::wrong_self_convention)]
228 pub fn is_power_off_pending(&mut self) -> Result<bool, Error> {
229 let data = self.read_block(POWEROFF_REG.id, POWEROFF_REG.length)?;
230 Ok(data[0] == POWEROFF_VALUE)
231 }
232
233 fn read_block(&mut self, register: u8, length: u8) -> Result<Vec<u8>, Error> {
234 let data = self.i2c_bus.smbus_read_i2c_block_data(register, length)?;
235
236 if data.len() != length as usize {
237 return Err(Error::InvalidDataLen(register, length as usize, data.len()));
238 }
239
240 Ok(data)
241 }
242}