#![no_std] use embedded_hal::i2c::{I2c, Operation, SevenBitAddress}; use thiserror::Error; pub const BMS_I2C_ADDRESS: u8 = 0x55; pub trait BmsReadable { fn read_from_i2c(i2c_dev: &mut I) -> Result where Self: Sized, I: I2c; } pub trait BmsWriteable { fn write_to_device(&self, i2c_dev: &mut I) -> Result<(), BmsProtocolError> where I: I2c; } #[derive(Debug, Error)] pub enum BmsProtocolError { #[error("i2c communication failed")] I2cCommunicationError, } impl From for BmsProtocolError where E: embedded_hal::i2c::Error, { fn from(_value: E) -> Self { Self::I2cCommunicationError } } #[derive(Debug)] pub struct BmsSoftwareReset; impl BmsWriteable for BmsSoftwareReset { fn write_to_device(&self, i2c_dev: &mut I) -> Result<(), BmsProtocolError> where I: I2c, { i2c_dev.write( BMS_I2C_ADDRESS, &(BmsRegisterMap::Reset as u32).to_be_bytes(), )?; Ok(()) } } #[derive(Debug)] #[repr(transparent)] pub struct ProtocolVersion(pub u32); impl BmsReadable for ProtocolVersion { fn read_from_i2c(i2c_dev: &mut I) -> Result where Self: Sized, I: I2c, { let cmd = BmsRegisterMap::ProtocolVersion; let mut version: [u8; 4] = [0x00; 4]; i2c_dev.transaction( BMS_I2C_ADDRESS, &mut [ Operation::Write(&(cmd as u32).to_be_bytes()), Operation::Read(&mut version), ], )?; Ok(ProtocolVersion(u32::from_be_bytes(version))) } } #[derive(Debug)] #[repr(transparent)] pub struct FirmwareVersion(pub u32); impl BmsReadable for FirmwareVersion { fn read_from_i2c(i2c_dev: &mut I) -> Result where Self: Sized, I: I2c, { let cmd = BmsRegisterMap::FirmwareVersion; let mut version: [u8; 4] = [0x00; 4]; i2c_dev.transaction( BMS_I2C_ADDRESS, &mut [ Operation::Write(&(cmd as u32).to_be_bytes()), Operation::Read(&mut version), ], )?; Ok(FirmwareVersion(u32::from_be_bytes(version))) } } #[derive(Debug)] pub struct Config { pub capacity_mah: u32, pub v_full_mv: u32, pub v_empty_mv: u32, } impl BmsReadable for Config { fn read_from_i2c(i2c_dev: &mut I) -> Result where Self: Sized, I: I2c, { let cmd = BmsRegisterMap::ConfigCapacityMilliAmpereHours; let mut capacity_mah: [u8; 4] = [0x00; 4]; let mut v_full_mv: [u8; 4] = [0x00; 4]; let mut v_empty_mv: [u8; 4] = [0x00; 4]; i2c_dev.transaction( BMS_I2C_ADDRESS, &mut [ Operation::Write(&(cmd as u32).to_be_bytes()), Operation::Read(&mut capacity_mah), Operation::Read(&mut v_full_mv), Operation::Read(&mut v_empty_mv), ], )?; Ok(Config { capacity_mah: u32::from_be_bytes(capacity_mah), v_full_mv: u32::from_be_bytes(v_full_mv), v_empty_mv: u32::from_be_bytes(v_empty_mv), }) } } #[derive(Debug, Clone, Copy)] pub struct BatteryState { pub lifetime_capacity_mah: u32, pub remaining_capacity_mah: u32, pub current_mv: u32, pub temperature_celcius: i32, pub health_percent: u32, } impl BmsReadable for BatteryState { fn read_from_i2c(i2c_dev: &mut I) -> Result where Self: Sized, I: I2c, { let cmd = BmsRegisterMap::BatteryLifetimeCapacityMilliAmpereHours; let mut lifetime_capacity_mah: [u8; 4] = [0x00; 4]; let mut remaining_capacity_mah: [u8; 4] = [0x00; 4]; let mut current_mv: [u8; 4] = [0x00; 4]; let mut temperature_celcius: [u8; 4] = [0x00; 4]; let mut health_percent: [u8; 4] = [0x00; 4]; i2c_dev.transaction( BMS_I2C_ADDRESS, &mut [ Operation::Write(&(cmd as u32).to_be_bytes()), Operation::Read(&mut lifetime_capacity_mah), Operation::Read(&mut remaining_capacity_mah), Operation::Read(&mut current_mv), Operation::Read(&mut temperature_celcius), Operation::Read(&mut health_percent), ], )?; Ok(BatteryState { lifetime_capacity_mah: u32::from_be_bytes(lifetime_capacity_mah), remaining_capacity_mah: u32::from_be_bytes(remaining_capacity_mah), current_mv: u32::from_be_bytes(current_mv), temperature_celcius: i32::from_be_bytes(temperature_celcius), health_percent: u32::from_be_bytes(health_percent), }) } } #[derive(Debug, Clone)] pub struct ChargeInfoWindowSec(pub u32); impl BmsReadable for ChargeInfoWindowSec { fn read_from_i2c(i2c_dev: &mut I) -> Result where Self: Sized, I: I2c, { let cmd = BmsRegisterMap::ChargoInfoWindowTotalSecs; let mut charge_info_window_sec: [u8; 4] = [0x00; 4]; i2c_dev.transaction( BMS_I2C_ADDRESS, &mut [ Operation::Write(&(cmd as u32).to_be_bytes()), Operation::Read(&mut charge_info_window_sec), ], )?; Ok(ChargeInfoWindowSec(u32::from_be_bytes( charge_info_window_sec, ))) } } impl BmsWriteable for ChargeInfoWindowSec { fn write_to_device(&self, i2c_dev: &mut I) -> Result<(), BmsProtocolError> where I: I2c, { let cmd = BmsRegisterMap::ChargoInfoWindowTotalSecs; i2c_dev.transaction( BMS_I2C_ADDRESS, &mut [ Operation::Write(&(cmd as u32).to_be_bytes()), Operation::Write(&self.0.to_be_bytes()), ], )?; Ok(()) } } #[derive(Debug, Clone, Copy)] pub struct ChargeInfo { pub total_charge_ma: u32, pub total_discharge_ma: u32, pub max_charge_mw: u32, pub max_discharge_mw: u32, pub avg_voltage_mv: u32, } impl BmsReadable for ChargeInfo { fn read_from_i2c(i2c_dev: &mut I) -> Result where Self: Sized, I: I2c, { let cmd = BmsRegisterMap::ChargoInfoTotalChargeMilliAmpere; let mut total_charge_ma: [u8; 4] = [0x00; 4]; let mut total_discharge_ma: [u8; 4] = [0x00; 4]; let mut max_charge_mw: [u8; 4] = [0x00; 4]; let mut max_discharge_mw: [u8; 4] = [0x00; 4]; let mut avg_voltage_mv: [u8; 4] = [0x00; 4]; i2c_dev.transaction( BMS_I2C_ADDRESS, &mut [ Operation::Write(&(cmd as u32).to_be_bytes()), Operation::Read(&mut total_charge_ma), Operation::Read(&mut total_discharge_ma), Operation::Read(&mut max_charge_mw), Operation::Read(&mut max_discharge_mw), Operation::Read(&mut avg_voltage_mv), ], )?; Ok(ChargeInfo { total_charge_ma: u32::from_be_bytes(total_charge_ma), total_discharge_ma: u32::from_be_bytes(total_discharge_ma), max_charge_mw: u32::from_be_bytes(max_charge_mw), max_discharge_mw: u32::from_be_bytes(max_discharge_mw), avg_voltage_mv: u32::from_be_bytes(avg_voltage_mv), }) } } #[repr(u32)] #[non_exhaustive] pub enum BmsRegisterMap { // software reset register Reset = 0x00000000, // bms protocol revesion as u32 value ProtocolVersion = 0x00000001, // bms firmware revesion as u32 value FirmwareVersion = 0x00000002, // Configuration Values Start Here ConfigCapacityMilliAmpereHours = 0x00010000, ConfigFullMilliVolt = 0x00010004, ConfigEmptyMilliVolt = 0x00010008, // Battery State Values Start Here BatteryLifetimeCapacityMilliAmpereHours = 0x00020000, BatteryRemainingCapacityMilliAmpereHours = 0x00020004, BatteryCurrentVoltageMilliVolt = 0x00020008, BatteryTemperatureCelcius = 0x0002000c, BatteryHealthPercent = 0x00020010, // Charge Window Read/Write Address ChargoInfoWindowTotalSecs = 0x0003000, // Charge Info Value Start Here ChargoInfoTotalChargeMilliAmpere = 0x0003004, ChargoInfoTotalDischargeMilliAmpere = 0x0003008, ChargoInfoMaxChargeMilliWatt = 0x000300c, ChargoInfoMaxDischargeMilliWatt = 0x0003010, ChargoInfoAvgVoltageMilliVolt = 0x0003014, }