Merge branch 'feature/protocol-use-transit-u26' into feature/base-type-error-correcting
This commit is contained in:
+36
-19
@@ -8,6 +8,8 @@ mod types;
|
|||||||
use embedded_hal::i2c::{I2c, Operation, SevenBitAddress};
|
use embedded_hal::i2c::{I2c, Operation, SevenBitAddress};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::types::Transit_U26;
|
||||||
|
|
||||||
pub const BMS_I2C_ADDRESS: u8 = 0x55;
|
pub const BMS_I2C_ADDRESS: u8 = 0x55;
|
||||||
|
|
||||||
pub trait BmsReadable {
|
pub trait BmsReadable {
|
||||||
@@ -27,6 +29,8 @@ pub trait BmsWriteable {
|
|||||||
pub enum BmsProtocolError {
|
pub enum BmsProtocolError {
|
||||||
#[error("i2c communication failed")]
|
#[error("i2c communication failed")]
|
||||||
I2cCommunicationError,
|
I2cCommunicationError,
|
||||||
|
#[error("checksum error in received data")]
|
||||||
|
ChecksumError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> From<E> for BmsProtocolError
|
impl<E> From<E> for BmsProtocolError
|
||||||
@@ -38,6 +42,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::types::U26Error> for BmsProtocolError {
|
||||||
|
fn from(error: crate::types::U26Error) -> Self {
|
||||||
|
match error {
|
||||||
|
crate::types::U26Error::ChecksumError => Self::ChecksumError,
|
||||||
|
_ => Self::I2cCommunicationError,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct BmsSoftwareReset;
|
pub struct BmsSoftwareReset;
|
||||||
|
|
||||||
@@ -73,7 +86,8 @@ impl BmsReadable for ProtocolVersion {
|
|||||||
Operation::Read(&mut version),
|
Operation::Read(&mut version),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(ProtocolVersion(u32::from_be_bytes(version)))
|
let transit = Transit_U26::from_be_bytes(version)?;
|
||||||
|
Ok(ProtocolVersion(transit.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +110,8 @@ impl BmsReadable for FirmwareVersion {
|
|||||||
Operation::Read(&mut version),
|
Operation::Read(&mut version),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(FirmwareVersion(u32::from_be_bytes(version)))
|
let transit = Transit_U26::from_be_bytes(version)?;
|
||||||
|
Ok(FirmwareVersion(transit.into()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,9 +142,9 @@ impl BmsReadable for Config {
|
|||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(Config {
|
Ok(Config {
|
||||||
capacity_mah: u32::from_be_bytes(capacity_mah),
|
capacity_mah: Transit_U26::from_be_bytes(capacity_mah)?.into(),
|
||||||
v_full_mv: u32::from_be_bytes(v_full_mv),
|
v_full_mv: Transit_U26::from_be_bytes(v_full_mv)?.into(),
|
||||||
v_empty_mv: u32::from_be_bytes(v_empty_mv),
|
v_empty_mv: Transit_U26::from_be_bytes(v_empty_mv)?.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,11 +182,12 @@ impl BmsReadable for BatteryState {
|
|||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(BatteryState {
|
Ok(BatteryState {
|
||||||
lifetime_capacity_mah: u32::from_be_bytes(lifetime_capacity_mah),
|
lifetime_capacity_mah: Transit_U26::from_be_bytes(lifetime_capacity_mah)?.into(),
|
||||||
remaining_capacity_mah: u32::from_be_bytes(remaining_capacity_mah),
|
remaining_capacity_mah: Transit_U26::from_be_bytes(remaining_capacity_mah)?.into(),
|
||||||
current_mv: u32::from_be_bytes(current_mv),
|
current_mv: Transit_U26::from_be_bytes(current_mv)?.into(),
|
||||||
temperature_celcius: i32::from_be_bytes(temperature_celcius),
|
temperature_celcius: crate::types::Transit_I26::from_be_bytes(temperature_celcius)?
|
||||||
health_percent: u32::from_be_bytes(health_percent),
|
.into(),
|
||||||
|
health_percent: Transit_U26::from_be_bytes(health_percent)?.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,9 +210,9 @@ impl BmsReadable for ChargeInfoWindowSec {
|
|||||||
Operation::Read(&mut charge_info_window_sec),
|
Operation::Read(&mut charge_info_window_sec),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(ChargeInfoWindowSec(u32::from_be_bytes(
|
Ok(ChargeInfoWindowSec(
|
||||||
charge_info_window_sec,
|
Transit_U26::from_be_bytes(charge_info_window_sec)?.into(),
|
||||||
)))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,11 +222,12 @@ impl BmsWriteable for ChargeInfoWindowSec {
|
|||||||
I: I2c<SevenBitAddress>,
|
I: I2c<SevenBitAddress>,
|
||||||
{
|
{
|
||||||
let cmd = BmsRegisterMap::ChargoInfoWindowTotalSecs;
|
let cmd = BmsRegisterMap::ChargoInfoWindowTotalSecs;
|
||||||
|
let transit = Transit_U26::try_from(self.0)?;
|
||||||
i2c_dev.transaction(
|
i2c_dev.transaction(
|
||||||
BMS_I2C_ADDRESS,
|
BMS_I2C_ADDRESS,
|
||||||
&mut [
|
&mut [
|
||||||
Operation::Write(&(cmd as u32).to_be_bytes()),
|
Operation::Write(&(cmd as u32).to_be_bytes()),
|
||||||
Operation::Write(&self.0.to_be_bytes()),
|
Operation::Write(&transit.to_be_bytes()),
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -250,11 +267,11 @@ impl BmsReadable for ChargeInfo {
|
|||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(ChargeInfo {
|
Ok(ChargeInfo {
|
||||||
total_charge_ma: u32::from_be_bytes(total_charge_ma),
|
total_charge_ma: Transit_U26::from_be_bytes(total_charge_ma)?.into(),
|
||||||
total_discharge_ma: u32::from_be_bytes(total_discharge_ma),
|
total_discharge_ma: Transit_U26::from_be_bytes(total_discharge_ma)?.into(),
|
||||||
max_charge_mw: u32::from_be_bytes(max_charge_mw),
|
max_charge_mw: Transit_U26::from_be_bytes(max_charge_mw)?.into(),
|
||||||
max_discharge_mw: u32::from_be_bytes(max_discharge_mw),
|
max_discharge_mw: Transit_U26::from_be_bytes(max_discharge_mw)?.into(),
|
||||||
avg_voltage_mv: u32::from_be_bytes(avg_voltage_mv),
|
avg_voltage_mv: Transit_U26::from_be_bytes(avg_voltage_mv)?.into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ impl Transit_U26 {
|
|||||||
|
|
||||||
impl From<Transit_U26> for u32 {
|
impl From<Transit_U26> for u32 {
|
||||||
fn from(value: Transit_U26) -> Self {
|
fn from(value: Transit_U26) -> Self {
|
||||||
value.0 & U26_VALUE_MASK >> 6
|
(value.0 & U26_VALUE_MASK) >> 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +181,58 @@ impl TryFrom<u32> for Transit_U26 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
/// this type is used for transmitting signed 26-bit integer data
|
||||||
|
/// It wraps the Transit_U26 type to provide i32 support
|
||||||
|
/// The i32 value is converted to u32 by casting, preserving the bit pattern
|
||||||
|
/// This ensures proper sign extension when converting between signed and unsigned
|
||||||
|
/// Valid range: -33554432 to 33554431 (26-bit signed)
|
||||||
|
pub struct Transit_I26(Transit_U26);
|
||||||
|
|
||||||
|
impl Transit_I26 {
|
||||||
|
pub fn new(value: i32) -> Result<Self, U26Error> {
|
||||||
|
// Check if value is within 26-bit signed range
|
||||||
|
if value < -0x02000000 || value > 0x01FFFFFF {
|
||||||
|
return Err(U26Error::Overflow);
|
||||||
|
}
|
||||||
|
// Extract only the lower 26 bits
|
||||||
|
let u26_val = (value as u32) & 0x03FFFFFF;
|
||||||
|
let transit_u26 = Transit_U26::try_from(u26_val)?;
|
||||||
|
Ok(Self(transit_u26))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_be_bytes(bytes: [u8; 4]) -> Result<Self, U26Error> {
|
||||||
|
let transit_u26 = Transit_U26::from_be_bytes(bytes)?;
|
||||||
|
Ok(Self(transit_u26))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_be_bytes(&self) -> [u8; 4] {
|
||||||
|
self.0.to_be_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Transit_I26> for i32 {
|
||||||
|
fn from(value: Transit_I26) -> Self {
|
||||||
|
// Convert u32 back to i32 by casting, preserving the exact bit pattern
|
||||||
|
let u32_val: u32 = value.0.into();
|
||||||
|
// Interpret as signed 26-bit integer
|
||||||
|
if u32_val & 0x02000000 != 0 {
|
||||||
|
// Sign-extend to 32 bits
|
||||||
|
(u32_val as i32) | 0xFC000000u32 as i32
|
||||||
|
} else {
|
||||||
|
u32_val as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<i32> for Transit_I26 {
|
||||||
|
type Error = U26Error;
|
||||||
|
|
||||||
|
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||||
|
Self::new(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// only calc hemming code for the first 24 data bits including the 5 hemming bits
|
// only calc hemming code for the first 24 data bits including the 5 hemming bits
|
||||||
// pos | | p1 | p2 | p3 | p4 | p5 |
|
// pos | | p1 | p2 | p3 | p4 | p5 |
|
||||||
// 00001 | p1 | o | | | | |
|
// 00001 | p1 | o | | | | |
|
||||||
@@ -358,9 +410,9 @@ pub fn check_hemming(data: u32, hemming: u8) -> Result<(), HemmingCorrectionValu
|
|||||||
/// - All double-bit errors
|
/// - All double-bit errors
|
||||||
/// - Random errors with a probability of 2^-6 = 0.015625 of going undetected
|
/// - Random errors with a probability of 2^-6 = 0.015625 of going undetected
|
||||||
pub fn calc_crc6(data: u32) -> u8 {
|
pub fn calc_crc6(data: u32) -> u8 {
|
||||||
let mut crc: u8 = 0x00;
|
let mut crc: u8 = 0x00;
|
||||||
for pos in 0..32 {
|
for pos in 0..32 {
|
||||||
if ((data >> (31-pos)) & 0x1) ^ ((crc >> 5) & 0x1) as u32 != 0 {
|
if ((data >> (31 - pos)) & 0x1) ^ ((crc >> 5) & 0x1) as u32 != 0 {
|
||||||
crc = ((crc << 1) ^ 0x03) & 0x3F;
|
crc = ((crc << 1) ^ 0x03) & 0x3F;
|
||||||
} else {
|
} else {
|
||||||
crc = (crc << 1) & 0x3F;
|
crc = (crc << 1) & 0x3F;
|
||||||
@@ -374,9 +426,9 @@ pub fn calc_crc6(data: u32) -> u8 {
|
|||||||
/// This function is the mirror piece to the crc6 calculation, it checks a crc value against
|
/// This function is the mirror piece to the crc6 calculation, it checks a crc value against
|
||||||
/// the data to determine if the crc is valid.
|
/// the data to determine if the crc is valid.
|
||||||
pub fn check_crc6(data: u32, crc: u8) -> bool {
|
pub fn check_crc6(data: u32, crc: u8) -> bool {
|
||||||
let mut check_crc: u8 = 0x00;
|
let mut check_crc: u8 = 0x00;
|
||||||
for pos in 0..32 {
|
for pos in 0..32 {
|
||||||
if ((data >> (31-pos)) & 0x1) ^ ((check_crc >> 5) & 0x1) as u32 != 0 {
|
if ((data >> (31 - pos)) & 0x1) ^ ((check_crc >> 5) & 0x1) as u32 != 0 {
|
||||||
check_crc = ((check_crc << 1) ^ 0x03) & 0x3F;
|
check_crc = ((check_crc << 1) ^ 0x03) & 0x3F;
|
||||||
} else {
|
} else {
|
||||||
check_crc = (check_crc << 1) & 0x3F;
|
check_crc = (check_crc << 1) & 0x3F;
|
||||||
@@ -388,7 +440,8 @@ pub fn check_crc6(data: u32, crc: u8) -> bool {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod base_type_tests {
|
mod base_type_tests {
|
||||||
use super::{
|
use super::{
|
||||||
Stored_U26, Transit_U26, U26_MAX_VALUE, calc_crc6, calc_hemming, check_crc6, check_hemming,
|
calc_crc6, calc_hemming, check_crc6, check_hemming, Stored_U26, Transit_I26, Transit_U26,
|
||||||
|
U26_MAX_VALUE,
|
||||||
};
|
};
|
||||||
use crate::types::U26Error;
|
use crate::types::U26Error;
|
||||||
|
|
||||||
@@ -515,4 +568,52 @@ mod base_type_tests {
|
|||||||
assert!(!check_crc6(corrupted_data, original_crc));
|
assert!(!check_crc6(corrupted_data, original_crc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transit_i26_positive_values() {
|
||||||
|
let data = Transit_I26::new(12345).unwrap();
|
||||||
|
let bytes = data.to_be_bytes();
|
||||||
|
let decoded = Transit_I26::from_be_bytes(bytes).unwrap();
|
||||||
|
assert_eq!(i32::from(decoded), 12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transit_i26_negative_values() {
|
||||||
|
let data = Transit_I26::new(-12345).unwrap();
|
||||||
|
let bytes = data.to_be_bytes();
|
||||||
|
let decoded = Transit_I26::from_be_bytes(bytes).unwrap();
|
||||||
|
assert_eq!(i32::from(decoded), -12345);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transit_i26_zero() {
|
||||||
|
let data = Transit_I26::new(0).unwrap();
|
||||||
|
let bytes = data.to_be_bytes();
|
||||||
|
let decoded = Transit_I26::from_be_bytes(bytes).unwrap();
|
||||||
|
assert_eq!(i32::from(decoded), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transit_i26_max_positive() {
|
||||||
|
let data = Transit_I26::new(0x01FFFFFF).unwrap();
|
||||||
|
let bytes = data.to_be_bytes();
|
||||||
|
let decoded = Transit_I26::from_be_bytes(bytes).unwrap();
|
||||||
|
assert_eq!(i32::from(decoded), 0x01FFFFFF as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transit_i26_max_negative() {
|
||||||
|
let data = Transit_I26::new(-0x01FFFFFF).unwrap();
|
||||||
|
let bytes = data.to_be_bytes();
|
||||||
|
let decoded = Transit_I26::from_be_bytes(bytes).unwrap();
|
||||||
|
assert_eq!(i32::from(decoded), -0x01FFFFFF as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transit_i26_checksum_error() {
|
||||||
|
let data = Transit_I26::new(12345).unwrap();
|
||||||
|
let mut bytes = data.to_be_bytes();
|
||||||
|
bytes[1] ^= 0x01; // Corrupt one byte
|
||||||
|
assert!(Transit_I26::from_be_bytes(bytes).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user