From bf2ad52e0f91b39f87b478ad6e15dd9078890c5d Mon Sep 17 00:00:00 2001 From: ju6ge Date: Wed, 25 Mar 2026 15:39:42 +0100 Subject: [PATCH] Fix Transit_I26 implementation and complete protocol conversion --- lib-bms-protocol/src/lib.rs | 3 +- lib-bms-protocol/src/types.rs | 113 ++++++++++++++++++++++++++++++++-- 2 files changed, 109 insertions(+), 7 deletions(-) diff --git a/lib-bms-protocol/src/lib.rs b/lib-bms-protocol/src/lib.rs index d696386..a091787 100644 --- a/lib-bms-protocol/src/lib.rs +++ b/lib-bms-protocol/src/lib.rs @@ -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(), }) } diff --git a/lib-bms-protocol/src/types.rs b/lib-bms-protocol/src/types.rs index e034e52..1012c46 100644 --- a/lib-bms-protocol/src/types.rs +++ b/lib-bms-protocol/src/types.rs @@ -165,7 +165,7 @@ impl Transit_U26 { impl From 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 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 { + // 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 { + 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 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 for Transit_I26 { + type Error = U26Error; + + fn try_from(value: i32) -> Result { + 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()); + } }