From 76f8611ee3345268e2091a9d2e81b53a8b05889c Mon Sep 17 00:00:00 2001 From: Yves Fischer Date: Wed, 13 Oct 2021 23:22:16 +0200 Subject: ebus parser in rust --- ebus-rust/src/layer7/mod.rs | 129 +++++++++++++++++++++++++ ebus-rust/src/layer7/types.rs | 213 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 ebus-rust/src/layer7/mod.rs create mode 100644 ebus-rust/src/layer7/types.rs (limited to 'ebus-rust/src/layer7') diff --git a/ebus-rust/src/layer7/mod.rs b/ebus-rust/src/layer7/mod.rs new file mode 100644 index 0000000..7f22478 --- /dev/null +++ b/ebus-rust/src/layer7/mod.rs @@ -0,0 +1,129 @@ +pub mod types; +use crate::layer2; +use crate::model; + +pub struct DecodedField { + pub v: Option, + pub name: String, + pub field: model::PacketField, +} + +pub fn decode_fields(p: &layer2::Packet, fields: &[model::PacketField]) -> Vec { + fields + .iter() + .map(|f| { + let r = |decoder: &dyn Fn(&[u8]) -> Option, + offset: &u8, + name: &String| DecodedField { + v: decoder( + p.payload_decoded() + .get((*offset as usize)..) + .unwrap_or(&[0; 0]), + ), + name: name.clone(), + field: f.clone(), + }; + match f { + model::PacketField::Byte { offset, name } => r(&types::byte, offset, name), + model::PacketField::Data1b { offset, name } => r(&types::data1b, offset, name), + model::PacketField::Data1c { offset, name } => r(&types::data1c, offset, name), + model::PacketField::Data2b { offset, name } => r(&types::data2b, offset, name), + model::PacketField::Data2c { offset, name } => r(&types::data2c, offset, name), + model::PacketField::ByteEnum { + offset, + name, + options, + } => r(&(move |data| types::byteenum(data, options)), offset, name), + model::PacketField::Bcd { offset, name } => r(&types::bcd, offset, name), + model::PacketField::Bit { offset, name } => r(&types::bit, offset, name), + model::PacketField::Word { offset, name } => r(&types::word, offset, name), + model::PacketField::String { + offset, + name, + length, + } => r( + &(move |data| types::string(data, *length as usize)), + offset, + name, + ), + } + }) + .collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + static EBUS_XML: &[u8] = include_bytes!("../../../ebus-xml/ebus.xml"); + + fn tostring(values: Vec) -> String { + values + .iter() + .map(|df| { + df.name.clone() + + "=" + + &df + .v + .as_ref() + .map(|v2| v2.convert()) + .unwrap_or(String::from("")) + }) + .collect::>() + .join(",") + } + + #[test] + fn test1() { + let conf = model::read_config(EBUS_XML).unwrap(); + let data = [ + 0xf1, 0xfe, 0x5, 0x3, 0x08, 0x01, 0x01, 0x10, 0xff, 0x4e, 0xff, 0x35, 0x0b, 0x92, 0x00, + 0xaa, + ]; + + let l2p = layer2::parse(&data[..]).unwrap(); + let pack = conf.packets.get(l2p.primary, l2p.secondary).unwrap(); + let values = decode_fields(&l2p, pack.fields.fields.as_ref().unwrap()); + assert_eq!("stellgradKesselleistung=,kesselTemperatur=39,ruecklaufTemperatur=,boilerTemperatur=53,aussenTemperatur=11", tostring(values)); + } + + #[test] + fn test() { + let conf = model::read_config(EBUS_XML).unwrap(); + + let data: Vec = vec![ + 170, // Syn + 170, // Syn + 003, // Source + 241, // Destination + 008, // primaryCommand + 000, // secondaryCommand + 008, // payloadLength + 128, // p1 + 040, // p2 + 230, // p3 + 002, // p4 + 000, // p5 + 002, // p6 + 000, // p7 + 010, // p8 + 128, // CRC + 000, // ACK + 170, // SYN + 170, + ]; + + let res = layer2::parse(&data).unwrap(); + assert_eq!( + res.payload_decoded(), + vec![128, 040, 230, 002, 000, 002, 000, 010] + ); + + let pack = conf.packets.get(res.primary, res.secondary).unwrap(); + let values = decode_fields(&res, pack.fields.fields.as_ref().unwrap()); + assert_eq!( + "TK_soll=40.5,TA_ist=2.8984375,L_zwang=0,Status=false,TB_soll=10", + tostring(values) + ); + } +} diff --git a/ebus-rust/src/layer7/types.rs b/ebus-rust/src/layer7/types.rs new file mode 100644 index 0000000..c119600 --- /dev/null +++ b/ebus-rust/src/layer7/types.rs @@ -0,0 +1,213 @@ +use crate::model; +use std::convert::TryFrom; +use std::ops::Neg; + +#[derive(PartialEq, Debug)] +pub enum Value { + Bool(bool), + I8(i8), + U8(u8), + U16(u16), + F32(f32), + String(String), +} + +impl Value { + pub fn convert(&self) -> String { + match self { + Value::Bool(v) => format!("{}", v), + Value::I8(v) => format!("{}", v), + Value::U8(v) => format!("{}", v), + Value::U16(v) => format!("{}", v), + Value::F32(v) => format!("{}", v), + Value::String(v) => v.clone(), + } + } +} + +#[inline] +fn low_nibble(v: u8) -> u8 { + 0x0f & v +} + +#[inline] +fn high_nibble(v: u8) -> u8 { + v >> 4 +} + +pub fn bcd(data: &[u8]) -> Option { + data.get(0).and_then(|value| { + if *value == 0xff { + None // Ersatzwert + } else { + Some(Value::U8(((*value & 0xf0) >> 4) * 10 + (*value & 0x0f))) + } + }) +} + +pub fn word(data: &[u8]) -> Option { + data.get(0..2) + .and_then(|data| <[u8; 2]>::try_from(data).ok()) + .and_then(|[low, high]| { + if low == 0xff && high == 0xff { + None // Ersatzwert + } else { + Some(Value::U16(low as u16 | (high as u16) << 8)) + } + }) +} + +pub fn byteenum(data: &[u8], options: &model::ByteEnumList) -> Option { + data.get(0).and_then(|v| options.get(*v)).map(Value::String) +} + +pub fn data2c(data: &[u8]) -> Option { + data.get(0..2) + .and_then(|data| <[u8; 2]>::try_from(data).ok()) + .and_then(|[low, high]| { + if high == 0x80 && low == 0x00 { + None + } else if high & 0x80 == 0x80 { + Some(Value::F32( + ((((!high as u16) * 16) + (high_nibble(!low) as u16)) as f32 + + ((low_nibble(!low) + 1) as f32 / 16.0)) + .neg(), + )) + } else { + Some(Value::F32( + ((high as u16) * 16 + high_nibble(low) as u16) as f32 + + low_nibble(low) as f32 / 16.0, + )) + } + }) +} + +pub fn data2b(data: &[u8]) -> Option { + data.get(0..2) + .and_then(|data| <[u8; 2]>::try_from(data).ok()) + .and_then(|[low, high]| { + if high == 0x80 && low == 0x00 { + None + } else if high & 0x80 == 0x80 { + Some(Value::F32( + ((!high as f32) + ((!low as f32 + 1.0) / 256.0_f32)).neg(), + )) + } else { + Some(Value::F32(high as f32 + (low as f32 / 256.0))) + } + }) +} + +pub fn bit(data: &[u8]) -> Option { + data.get(0).map(|v| *v == 1).map(Value::Bool) +} + +pub fn byte(data: &[u8]) -> Option { + data.get(0).and_then(|value| { + if *value == 0xff { + None + } else { + Some(Value::U8(*value)) + } + }) +} + +pub fn data1b(data: &[u8]) -> Option { + data.get(0).and_then(|value| { + if *value == 0x80 { + None + } else if *value >> 7 == 1 { + Some(Value::I8(((1 + !(*value)) as i8).neg())) + } else { + Some(Value::I8(*value as i8)) + } + }) +} + +pub fn data1c(data: &[u8]) -> Option { + data.get(0).and_then(|value| { + if *value == 0xff { + None + } else { + Some(Value::F32(*value as f32 / 2.0)) + } + }) +} + +pub fn string(data: &[u8], length: usize) -> Option { + data.get(0..length) + .map(|data| { + data.iter() + .map(|p| *p as char) + .filter(|p| { + *p >= 'a' && *p <= 'z' || *p >= 'A' && *p <= 'Z' || *p >= ' ' && *p <= '9' + }) + .map(String::from) + .collect::>() + .join("") + }) + .map(Value::String) +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! assert_f32_eq_approx { + ($x:expr, $y:expr, $Δ:expr) => { + match $y { + Some(Value::F32(v)) => { + if !($x - v < $Δ && v - $x < $Δ) { + panic!("Left({}) != Right({}) Δ={}", $x, v, $Δ); + } + } + _ => panic!("Left({}) != Right(None)", $x), + } + }; + } + + #[test] + fn test_decoders() { + // Bei allen 16-Bit Typen (2 Byte), wird das Low-Byte immer zuerst übertragen. + assert_eq!(None, bcd(&[0xff])); + assert_eq!(Some(Value::U8(0)), bcd(&[0x00])); + assert_eq!(Some(Value::U8(1)), bcd(&[0x01])); + assert_eq!(Some(Value::U8(2)), bcd(&[0x02])); + assert_eq!(Some(Value::U8(10)), bcd(&[0x10])); + assert_eq!(Some(Value::U8(11)), bcd(&[0x11])); + assert_eq!(Some(Value::U8(99)), bcd(&[0x99])); + + assert_eq!(Some(Value::I8(0)), data1b(&[0x00])); + assert_eq!(Some(Value::I8(1)), data1b(&[0x01])); + assert_eq!(Some(Value::I8(127)), data1b(&[0x7f])); + assert_eq!(Some(Value::I8(-127)), data1b(&[0x81])); + assert_eq!(None, data1b(&[0x80])); + + assert_eq!(Some(Value::F32(0.0)), data1c(&[0])); + assert_eq!(Some(Value::F32(50.0)), data1c(&[0x64])); + assert_eq!(Some(Value::F32(100.0)), data1c(&[0xc8])); + + assert_eq!(Some(Value::F32(0.0)), data2b(&[0x00, 0x00])); + assert_eq!(Some(Value::F32(1.0 / 256.0)), data2b(&[0x01, 0x00,])); + assert_eq!(Some(Value::F32(-1.0 / 256.0)), data2b(&[0xff, 0xff])); + assert_eq!(Some(Value::F32(-1.0)), data2b(&[0x00, 0xff])); + assert_eq!(None, data2b(&[0x00, 0x80])); + assert_f32_eq_approx!(-127.996, data2b(&[0x01, 0x80]), 0.001); + assert_f32_eq_approx!(127.99609, data2b(&[0xff, 0x7f]), 0.001); + assert_f32_eq_approx!(52.7, data2b(&[0xb3, 0x34]), 0.1); + + assert_f32_eq_approx!(0.0, data2c(&[0x00, 0x00]), f32::EPSILON); + assert_f32_eq_approx!((1.0 / 16.0), data2c(&[0x01, 0x00]), f32::EPSILON); + assert_f32_eq_approx!((-1.0 / 16.0), data2c(&[0xff, 0xff]), f32::EPSILON); + assert_f32_eq_approx!(-1.0, data2c(&[0xf0, 0xff]), f32::EPSILON); + assert_eq!(None, data2c(&[0x00, 0x80])); + assert_f32_eq_approx!(-2047.9, data2c(&[0x01, 0x80]), 0.1); + assert_f32_eq_approx!(2047.9, data2c(&[0xff, 0x7f]), 0.1); + + assert_eq!(None, word(&[0xff, 0xff])); + assert_eq!(Some(Value::U16(65279)), word(&[0xff, 0xfe])); + assert_eq!(Some(Value::U16(256)), word(&[0x00, 0x01])); + assert_eq!(Some(Value::U16(1)), word(&[0x01, 0x00])); + assert_eq!(Some(Value::U16(0)), word(&[0x00, 0x00])); + } +} -- cgit v1.2.1