#![no_std]
use core::{marker::PhantomData, time::Duration};
use device::Device;
use embedded_hal::i2c::I2c;
pub use device::ChipError;
pub mod device;
pub struct LC709203<I2C> {
address: u8,
_c: PhantomData<I2C>,
}
impl<I2C, E> LC709203<I2C>
where
I2C: I2c<Error = E>,
{
pub fn device<'a>(&self, i2c: &'a mut I2C) -> Device<'a, I2C> {
Device::new(self.address, i2c)
}
pub fn sleep(&self, i2c: &mut I2C) -> Result<(), ChipError<E>> {
self.device(i2c)
.write_power_mode(device::constants::IC_POWER_MODE_SLEEP)
}
pub fn wake_up(&self, i2c: &mut I2C) -> Result<(), ChipError<E>> {
self.device(i2c)
.write_power_mode(device::constants::IC_POWER_MODE_OPERATION)
}
pub fn is_discharging(&self, i2c: &mut I2C) -> Result<bool, ChipError<E>> {
let status = self.device(i2c).battery_status()?;
Ok((status & device::constants::BATTERY_STATUS_DISCHARGING) != 0)
}
pub fn is_charging(&self, i2c: &mut I2C) -> Result<bool, ChipError<E>> {
self.is_discharging(i2c).map(|discharging| !discharging)
}
pub fn rsoc(&self, i2c: &mut I2C) -> Result<u8, ChipError<E>> {
self.device(i2c).rsoc().map(|r| r as u8)
}
pub fn cell_percentage(&self, i2c: &mut I2C) -> Result<f32, ChipError<E>> {
self.device(i2c).ite().map(|r| (r as f32) * 0.1)
}
pub fn battery_health(&self, i2c: &mut I2C) -> Result<u8, ChipError<E>> {
self.device(i2c).battery_health().map(|h| h as u8)
}
pub fn cell_voltage_mv(&self, i2c: &mut I2C) -> Result<u16, ChipError<E>> {
self.device(i2c).cell_voltage()
}
pub fn cell_voltage_v(&self, i2c: &mut I2C) -> Result<f32, ChipError<E>> {
self.cell_voltage_mv(i2c).map(|mv| (mv as f32) / 1000.)
}
pub fn ic_version(&self, i2c: &mut I2C) -> Result<u16, ChipError<E>> {
self.device(i2c).ic_version()
}
pub fn time_to_empty(&self, i2c: &mut I2C) -> Result<Duration, ChipError<E>> {
self.device(i2c)
.time_to_empty()
.map(|t| Duration::from_secs((t as u64) * 60))
}
pub fn cell_temperature_mode(&self, i2c: &mut I2C) -> Result<TemperatureMode, ChipError<E>> {
let status = self.device(i2c).status_bit()?;
if (status & device::constants::STATUS_BIT_TSENSE1) == 0 {
Ok(TemperatureMode::Measure)
} else {
Ok(TemperatureMode::I2C)
}
}
pub fn set_cell_temperature_mode(
&self,
i2c: &mut I2C,
mode: TemperatureMode,
) -> Result<(), ChipError<E>> {
let mut dev = self.device(i2c);
let mut status = dev.status_bit()?;
status &= !device::constants::STATUS_BIT_TSENSE1;
if mode == TemperatureMode::I2C {
status |= device::constants::STATUS_BIT_TSENSE1;
}
dev.write_status_bit(status)
}
pub fn cell_temperature_thermistor_b(&self, i2c: &mut I2C) -> Result<u16, ChipError<E>> {
self.device(i2c).tsense1_thermistor_b()
}
pub fn set_cell_temperature_thermistor_b(
&self,
i2c: &mut I2C,
value: u16,
) -> Result<(), ChipError<E>> {
self.device(i2c).write_tsense1_thermistor_b(value)
}
pub fn cell_temperature(&self, i2c: &mut I2C) -> Result<f32, ChipError<E>> {
self.device(i2c)
.cell_temperature()
.map(|t| (t as f32) * 0.1 - 273.2)
}
pub fn set_cell_temperature(
&self,
i2c: &mut I2C,
temperature: f32,
) -> Result<(), ChipError<E>> {
let temp = ((temperature + 273.2) * 10.) as u16;
self.device(i2c).write_cell_temperature(temp)
}
pub fn ambient_temperature_mode(&self, i2c: &mut I2C) -> Result<TemperatureMode, ChipError<E>> {
let status = self.device(i2c).status_bit()?;
if (status & device::constants::STATUS_BIT_TSENSE2) == 0 {
Ok(TemperatureMode::Measure)
} else {
Ok(TemperatureMode::I2C)
}
}
pub fn set_ambient_temperature_mode(
&self,
i2c: &mut I2C,
mode: TemperatureMode,
) -> Result<(), ChipError<E>> {
let mut dev = self.device(i2c);
let mut status = dev.status_bit()?;
status &= !device::constants::STATUS_BIT_TSENSE2;
if mode == TemperatureMode::I2C {
status |= device::constants::STATUS_BIT_TSENSE2;
}
dev.write_status_bit(status)
}
pub fn ambient_temperature_thermistor_b(&self, i2c: &mut I2C) -> Result<u16, ChipError<E>> {
self.device(i2c).tsense2_thermistor_b()
}
pub fn set_ambient_temperature_thermistor_b(
&self,
i2c: &mut I2C,
value: u16,
) -> Result<(), ChipError<E>> {
self.device(i2c).write_tsense2_thermistor_b(value)
}
pub fn ambient_temperature(&self, i2c: &mut I2C) -> Result<f32, ChipError<E>> {
self.device(i2c)
.ambient_temperature()
.map(|t| (t as f32) * 0.1 - 273.2)
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum TemperatureMode {
Measure,
I2C,
}
#[derive(Debug)]
pub enum BuildError<E> {
I2CError(E),
MissingBatteryCapacity,
BatteryCapacityOutOfRange,
}
impl<E> From<E> for BuildError<E> {
fn from(e: E) -> Self {
Self::I2CError(e)
}
}
impl<E> From<ChipError<E>> for BuildError<E> {
fn from(e: ChipError<E>) -> Self {
match e {
ChipError::I2CError(e) => Self::I2CError(e),
ChipError::CrcMismatch(_) => {
unreachable!("");
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum InitTime {
PowerOn10ms,
PowerOn20ms,
PowerOn30ms,
PowerOn40ms,
Now,
}
#[derive(Debug, Clone, Copy)]
pub enum BatteryType {
Type01,
Type04,
Type05,
Type06,
Type07,
}
pub struct Builder {
address: u8,
init_time: InitTime,
battery_type: BatteryType,
battery_capacity: Option<u16>,
}
impl Default for Builder {
fn default() -> Self {
Self {
address: device::DEFAULT_I2C_ADDRESS,
init_time: InitTime::Now,
battery_type: BatteryType::Type01,
battery_capacity: None,
}
}
}
impl Builder {
pub fn build<I2C, E>(self, i2c: &mut I2C) -> Result<LC709203<I2C>, BuildError<E>>
where
I2C: I2c<Error = E>,
{
if self.battery_capacity.is_none() {
return Err(BuildError::MissingBatteryCapacity);
}
#[cfg(feature = "log")]
log::debug!("Initializing device");
let result = LC709203 {
address: self.address,
_c: PhantomData,
};
let mut device = result.device(i2c);
#[cfg(feature = "log")]
log::debug!("Waking up chip");
device.write_power_mode(device::constants::IC_POWER_MODE_OPERATION)?;
#[cfg(feature = "log")]
log::debug!("Writing battery configuration");
let apa = Self::calc_apa(self.battery_type, self.battery_capacity.unwrap())? as u16;
let apa = (apa << 8) | apa;
#[cfg(feature = "log")]
log::trace!("calculated battery APA: {:x}", apa);
device.write_apa(apa)?;
let type_value = match self.battery_type {
BatteryType::Type01 => device::constants::BATTERY_TYPE01,
BatteryType::Type04 => device::constants::BATTERY_TYPE04,
BatteryType::Type05 => device::constants::BATTERY_TYPE05,
BatteryType::Type06 => device::constants::BATTERY_TYPE06,
BatteryType::Type07 => device::constants::BATTERY_TYPE07,
};
device.write_battery_type(type_value)?;
device.write_battery_status(0)?;
match self.init_time {
InitTime::Now => device.write_init_rsoc(device::constants::INIT_RSOC)?,
InitTime::PowerOn10ms => {
device.write_before_rsoc(device::constants::BEFORE_RSOC_1ST_SAMPLE)?
}
InitTime::PowerOn20ms => {
device.write_before_rsoc(device::constants::BEFORE_RSOC_2ND_SAMPLE)?
}
InitTime::PowerOn30ms => {
device.write_before_rsoc(device::constants::BEFORE_RSOC_3RD_SAMPLE)?
}
InitTime::PowerOn40ms => {
device.write_before_rsoc(device::constants::BEFORE_RSOC_4TH_SAMPLE)?
}
}
#[cfg(feature = "log")]
log::info!("Device initialized");
Ok(result)
}
fn calc_apa<E>(battery_type: BatteryType, battery_capacity: u16) -> Result<u8, BuildError<E>> {
let apa_table = match (battery_type, battery_capacity) {
(BatteryType::Type04, 2600) => return Ok(0x10),
(BatteryType::Type05, 2600) => return Ok(0x06),
(BatteryType::Type01, cap) if cap >= 50 && cap < 6000 => {
[0x13, 0x15, 0x18, 0x21, 0x2d, 0x3a, 0x3f, 0x42, 0x44, 0x45]
}
(BatteryType::Type01, 6000) => return Ok(0x45),
(BatteryType::Type06, cap) if cap >= 50 && cap < 6000 => {
[0x0c, 0x0e, 0x11, 0x17, 0x1e, 0x28, 0x30, 0x34, 0x36, 0x37]
}
(BatteryType::Type06, 6000) => return Ok(0x37),
(BatteryType::Type07, cap) if cap >= 50 && cap < 3000 => {
[0x03, 0x05, 0x07, 0x02, 0x13, 0x19, 0x1c, 0x00, 0x00, 0x00]
}
(BatteryType::Type07, 3000) => return Ok(0x1c),
_ => return Err(BuildError::BatteryCapacityOutOfRange),
};
const CAP_TABLE: [u16; 10] = [50, 100, 200, 500, 1000, 2000, 3000, 4000, 5000, 6000];
for i in 0..apa_table.len() - 1 {
if CAP_TABLE[i] == battery_capacity {
return Ok(apa_table[i]);
}
if CAP_TABLE[i] < battery_capacity && CAP_TABLE[i + 1] > battery_capacity {
let apa0 = apa_table[i];
let apa1 = apa_table[i + 1];
let cap0 = CAP_TABLE[i];
let cap1 = CAP_TABLE[i + 1];
let apa = apa0 + (apa1 - apa0) * ((battery_capacity - cap0) / (cap1 - cap0)) as u8;
return Ok(apa);
}
}
unreachable!();
}
pub fn with_address(mut self, address: u8) -> Self {
self.address = address;
self
}
pub fn with_init_time(mut self, init_time: InitTime) -> Self {
self.init_time = init_time;
self
}
pub fn with_battery_type(mut self, battery_type: BatteryType) -> Self {
self.battery_type = battery_type;
self
}
pub fn with_battery_capacity(mut self, battery_capacity: u16) -> Self {
self.battery_capacity = Some(battery_capacity);
self
}
}