Fix Transit_I26 implementation and complete protocol conversion

This commit is contained in:
2026-03-26 22:26:59 +01:00
parent 2332b507b2
commit bf2ad52e0f
2 changed files with 109 additions and 7 deletions
+2 -1
View File
@@ -185,7 +185,8 @@ impl BmsReadable for BatteryState {
lifetime_capacity_mah: Transit_U26::from_be_bytes(lifetime_capacity_mah)?.into(),
remaining_capacity_mah: Transit_U26::from_be_bytes(remaining_capacity_mah)?.into(),
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)?
.into(),
health_percent: Transit_U26::from_be_bytes(health_percent)?.into(),
})
}
+107 -6
View File
@@ -165,7 +165,7 @@ impl Transit_U26 {
impl From<Transit_U26> for u32 {
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
// pos | | p1 | p2 | p3 | p4 | p5 |
// 00001 | p1 | o | | | | |
@@ -358,9 +410,9 @@ pub fn check_hemming(data: u32, hemming: u8) -> Result<(), HemmingCorrectionValu
/// - All double-bit errors
/// - Random errors with a probability of 2^-6 = 0.015625 of going undetected
pub fn calc_crc6(data: u32) -> u8 {
let mut crc: u8 = 0x00;
let mut crc: u8 = 0x00;
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;
} else {
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
/// the data to determine if the crc is valid.
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 {
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;
} else {
check_crc = (check_crc << 1) & 0x3F;
@@ -388,7 +440,8 @@ pub fn check_crc6(data: u32, crc: u8) -> bool {
#[cfg(test)]
mod base_type_tests {
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;
@@ -515,4 +568,52 @@ mod base_type_tests {
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());
}
}