#![doc = include_str!("../README.md")]
pub mod error;
pub mod registers;
use error::Error;
use i2cdev::core::I2CDevice;
use i2cdev::linux::LinuxI2CDevice;
use registers::{
BATTERY_REG, CELL_VOLTAGE_REG, CHARGING_REG, COMMUNICATION_REG, ChargerActivity, ChargingState,
CommState, POWEROFF_REG, USBC_VBUS_REG, UsbCInputState, UsbCPowerDelivery,
};
use crate::registers::SOFTWARE_REV_REG;
pub const DEFAULT_I2C_ADDRESS: u16 = 0x2d;
pub const DEFAULT_I2C_DEV_PATH: &str = "/dev/i2c-1";
pub const DEFAULT_CELL_LOW_VOLTAGE_THRESHOLD: u16 = 3400;
pub const POWEROFF_VALUE: u8 = 0x55;
#[derive(Debug)]
pub struct PowerState {
pub charging_state: ChargingState,
pub charger_activity: ChargerActivity,
pub usbc_input_state: UsbCInputState,
pub usbc_power_delivery: UsbCPowerDelivery,
}
#[derive(Debug)]
pub struct CommunicationState {
pub bq4050: CommState,
pub ip2368: CommState,
}
#[derive(Debug)]
pub struct BatteryState {
pub millivolts: u16,
pub milliamps: i16,
pub remaining_percent: u16,
pub remaining_capacity_milliamphours: u16,
pub remaining_runtime_minutes: u16,
pub time_to_full_minutes: u16,
}
#[derive(Debug)]
pub struct CellVoltage {
pub cell_1_millivolts: u16,
pub cell_2_millivolts: u16,
pub cell_3_millivolts: u16,
pub cell_4_millivolts: u16,
}
#[derive(Debug)]
pub struct UsbCVBus {
pub millivolts: u16,
pub milliamps: u16,
pub milliwatts: u16,
}
pub struct UpsHatE {
i2c_bus: LinuxI2CDevice,
}
impl Default for UpsHatE {
fn default() -> Self {
let i2c = LinuxI2CDevice::new(DEFAULT_I2C_DEV_PATH, DEFAULT_I2C_ADDRESS)
.expect("Failed to open I2C device");
Self { i2c_bus: i2c }
}
}
impl UpsHatE {
pub fn new() -> Self {
Self::default()
}
pub fn from_i2c_device(i2c_bus: LinuxI2CDevice) -> Self {
Self { i2c_bus }
}
pub fn get_cell_voltage(&mut self) -> Result<CellVoltage, Error> {
let data = self.read_block(CELL_VOLTAGE_REG.id, CELL_VOLTAGE_REG.length)?;
let voltages = CellVoltage {
cell_1_millivolts: u16::from_le_bytes([data[0], data[1]]),
cell_2_millivolts: u16::from_le_bytes([data[2], data[3]]),
cell_3_millivolts: u16::from_le_bytes([data[4], data[5]]),
cell_4_millivolts: u16::from_le_bytes([data[6], data[7]]),
};
Ok(voltages)
}
pub fn get_usbc_vbus(&mut self) -> Result<UsbCVBus, Error> {
let data = self.read_block(USBC_VBUS_REG.id, USBC_VBUS_REG.length)?;
let vbus = UsbCVBus {
millivolts: u16::from_le_bytes([data[0], data[1]]),
milliamps: u16::from_le_bytes([data[2], data[3]]),
milliwatts: u16::from_le_bytes([data[4], data[5]]),
};
Ok(vbus)
}
pub fn get_battery_state(&mut self) -> Result<BatteryState, Error> {
let data = self.read_block(BATTERY_REG.id, BATTERY_REG.length)?;
let milliamps = i16::from_le_bytes([data[2], data[3]]);
let (remaining_runtime_minutes, time_to_full_minutes) = if milliamps < 0 {
(u16::from_le_bytes([data[8], data[9]]), 0)
} else {
(0, u16::from_le_bytes([data[10], data[11]]))
};
let state = BatteryState {
millivolts: u16::from_le_bytes([data[0], data[1]]),
milliamps,
remaining_percent: u16::from_le_bytes([data[4], data[5]]),
remaining_capacity_milliamphours: u16::from_le_bytes([data[6], data[7]]),
remaining_runtime_minutes,
time_to_full_minutes,
};
Ok(state)
}
pub fn get_power_state(&mut self) -> Result<PowerState, Error> {
let data = self.read_block(CHARGING_REG.id, CHARGING_REG.length)?;
let byte = data[0];
let charger_activity = ChargerActivity::try_from(byte & 0b111)?;
let usbc_input_state = UsbCInputState::from(byte & (1 << 5) != 0);
let usbc_power_delivery = UsbCPowerDelivery::from(byte & (1 << 6) != 0);
let charging_state = ChargingState::from(byte & (1 << 7) != 0);
Ok(PowerState {
charging_state,
charger_activity,
usbc_input_state,
usbc_power_delivery,
})
}
pub fn get_communication_state(&mut self) -> Result<CommunicationState, Error> {
let data = self.read_block(COMMUNICATION_REG.id, COMMUNICATION_REG.length)?;
let byte = data[0];
let ip2368 = CommState::from(byte & (1 << 0) != 0);
let bq4050 = CommState::from(byte & (1 << 1) != 0);
Ok(CommunicationState { bq4050, ip2368 })
}
pub fn get_software_revision(&mut self) -> Result<u8, Error> {
let data = self.read_block(SOFTWARE_REV_REG.id, SOFTWARE_REV_REG.length)?;
Ok(data[0])
}
#[allow(clippy::wrong_self_convention)]
pub fn is_battery_low(&mut self) -> Result<bool, Error> {
const CUTOFF: u32 = 4 * DEFAULT_CELL_LOW_VOLTAGE_THRESHOLD as u32;
let cell_voltages = self.get_cell_voltage()?;
let total_voltage: u32 = (cell_voltages.cell_1_millivolts
+ cell_voltages.cell_2_millivolts
+ cell_voltages.cell_3_millivolts
+ cell_voltages.cell_4_millivolts) as u32;
Ok(total_voltage <= CUTOFF)
}
pub fn force_power_off(&mut self) -> Result<(), Error> {
self.i2c_bus
.smbus_write_byte_data(POWEROFF_REG.id, POWEROFF_VALUE)?;
Ok(())
}
#[allow(clippy::wrong_self_convention)]
pub fn is_power_off_pending(&mut self) -> Result<bool, Error> {
let data = self.read_block(POWEROFF_REG.id, POWEROFF_REG.length)?;
Ok(data[0] == POWEROFF_VALUE)
}
fn read_block(&mut self, register: u8, length: u8) -> Result<Vec<u8>, Error> {
let data = self.i2c_bus.smbus_read_i2c_block_data(register, length)?;
if data.len() != length as usize {
return Err(Error::InvalidDataLen(register, length as usize, data.len()));
}
Ok(data)
}
}