use crate::database::{get_spn_def, get_spns_for_pgn};
use crate::frame::{extract_pgn, extract_source_address};
use crate::types::{DecodedSpn, SpnDataType, SpnDef};
pub fn decode_spn(data: &[u8], spn_def: &SpnDef) -> Option<f64> {
if data.len() <= spn_def.start_byte as usize {
return None;
}
let raw_value = extract_raw_value(data, spn_def)?;
let max_value = (1u64 << spn_def.bit_length) - 1;
if raw_value >= max_value - 1 {
return None;
}
let value = (raw_value as f64) * spn_def.scale + spn_def.offset;
Some(value)
}
pub fn decode_spn_full(data: &[u8], spn_def: &SpnDef) -> Option<DecodedSpn> {
if data.len() <= spn_def.start_byte as usize {
return None;
}
let raw_value = extract_raw_value(data, spn_def)?;
let max_value = (1u64 << spn_def.bit_length) - 1;
if raw_value >= max_value - 1 {
return None;
}
let value = (raw_value as f64) * spn_def.scale + spn_def.offset;
Some(DecodedSpn {
spn: spn_def.spn,
name: spn_def.name,
value,
unit: spn_def.unit,
raw_value,
})
}
pub fn decode_frame(can_id: u32, data: &[u8]) -> Vec<DecodedSpn> {
let pgn = extract_pgn(can_id);
let Some(spn_defs) = get_spns_for_pgn(pgn) else {
return Vec::new();
};
let mut results = Vec::with_capacity(spn_defs.len());
for spn_def in spn_defs {
if let Some(decoded) = decode_spn_full(data, spn_def) {
results.push(decoded);
}
}
results
}
pub fn decode_spn_by_number(spn: u32, data: &[u8]) -> Option<f64> {
let spn_def = get_spn_def(spn)?;
decode_spn(data, spn_def)
}
pub fn get_source_address(can_id: u32) -> u8 {
extract_source_address(can_id)
}
fn extract_raw_value(data: &[u8], spn_def: &SpnDef) -> Option<u64> {
let start = spn_def.start_byte as usize;
match spn_def.data_type {
SpnDataType::Uint8 => {
if start >= data.len() {
return None;
}
if spn_def.bit_length == 8 && spn_def.start_bit == 0 {
Some(data[start] as u64)
} else {
let byte = data[start];
let mask = (1u8 << spn_def.bit_length) - 1;
Some(((byte >> spn_def.start_bit) & mask) as u64)
}
}
SpnDataType::Uint16 => {
if start + 1 >= data.len() {
return None;
}
Some(u16::from_le_bytes([data[start], data[start + 1]]) as u64)
}
SpnDataType::Uint32 => {
if start + 3 >= data.len() {
return None;
}
Some(u32::from_le_bytes([
data[start],
data[start + 1],
data[start + 2],
data[start + 3],
]) as u64)
}
SpnDataType::Int8 => {
if start >= data.len() {
return None;
}
Some(data[start] as i8 as i64 as u64)
}
SpnDataType::Int16 => {
if start + 1 >= data.len() {
return None;
}
let val = i16::from_le_bytes([data[start], data[start + 1]]);
Some(val as i64 as u64)
}
SpnDataType::Int32 => {
if start + 3 >= data.len() {
return None;
}
let val = i32::from_le_bytes([
data[start],
data[start + 1],
data[start + 2],
data[start + 3],
]);
Some(val as i64 as u64)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_coolant_temp() {
let spn_def = get_spn_def(110).unwrap();
let data = [130u8, 0, 0, 0, 0, 0, 0, 0];
let value = decode_spn(&data, spn_def);
assert_eq!(value, Some(90.0));
}
#[test]
fn test_decode_engine_speed() {
let spn_def = get_spn_def(190).unwrap();
let data = [0, 0, 0, 0x20, 0x4E, 0, 0, 0]; let value = decode_spn(&data, spn_def);
assert_eq!(value, Some(2500.0));
}
#[test]
fn test_decode_not_available() {
let spn_def = get_spn_def(110).unwrap();
let data = [0xFF, 0, 0, 0, 0, 0, 0, 0];
let value = decode_spn(&data, spn_def);
assert!(value.is_none());
}
#[test]
fn test_decode_frame_eec1() {
let can_id = 0x0CF00400;
let data = [0x00, 0x00, 0x00, 0x20, 0x4E, 0x00, 0x00, 0x00];
let decoded = decode_frame(can_id, &data);
assert!(!decoded.is_empty());
let engine_speed = decoded.iter().find(|d| d.spn == 190);
assert!(engine_speed.is_some());
assert_eq!(engine_speed.unwrap().value, 2500.0);
}
#[test]
fn test_decode_spn_by_number() {
let data = [130u8, 0, 0, 0, 0, 0, 0, 0];
let value = decode_spn_by_number(110, &data);
assert_eq!(value, Some(90.0));
let value = decode_spn_by_number(99999, &data);
assert!(value.is_none());
}
#[test]
fn test_bit_field_extraction() {
let spn_def = get_spn_def(899).unwrap();
let data = [0x05, 0, 0, 0, 0, 0, 0, 0];
let value = decode_spn(&data, spn_def);
assert_eq!(value, Some(5.0));
let data = [0x0A, 0, 0, 0, 0, 0, 0, 0];
let value = decode_spn(&data, spn_def);
assert_eq!(value, Some(10.0));
}
}