summaryrefslogtreecommitdiff
path: root/ebus-rust/src/layer7
diff options
context:
space:
mode:
authorYves Fischer <yvesf+git@xapek.org>2021-12-31 23:57:29 +0100
committerYves Fischer <yvesf+git@xapek.org>2021-12-31 23:57:29 +0100
commitd0c7100b706d70dfff342def2bac864693fe8544 (patch)
tree333268ca977222e2f5a41eff68607b36721f7694 /ebus-rust/src/layer7
parentcaae83f445935c06cd6aef36f283a4688675278a (diff)
parent810721ceac0249e747cdf36f3edec22d829d4371 (diff)
downloadebus-d0c7100b706d70dfff342def2bac864693fe8544.tar.gz
ebus-d0c7100b706d70dfff342def2bac864693fe8544.zip
Merge branch 'ebus-rust'HEADmaster
Diffstat (limited to 'ebus-rust/src/layer7')
-rw-r--r--ebus-rust/src/layer7/mod.rs129
-rw-r--r--ebus-rust/src/layer7/types.rs213
2 files changed, 342 insertions, 0 deletions
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<types::Value>,
+ pub name: String,
+ pub field: model::PacketField,
+}
+
+pub fn decode_fields(p: &layer2::Packet, fields: &[model::PacketField]) -> Vec<DecodedField> {
+ fields
+ .iter()
+ .map(|f| {
+ let r = |decoder: &dyn Fn(&[u8]) -> Option<types::Value>,
+ 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<DecodedField>) -> String {
+ values
+ .iter()
+ .map(|df| {
+ df.name.clone()
+ + "="
+ + &df
+ .v
+ .as_ref()
+ .map(|v2| v2.convert())
+ .unwrap_or(String::from("<None>"))
+ })
+ .collect::<Vec<String>>()
+ .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=<None>,kesselTemperatur=39,ruecklaufTemperatur=<None>,boilerTemperatur=53,aussenTemperatur=11", tostring(values));
+ }
+
+ #[test]
+ fn test() {
+ let conf = model::read_config(EBUS_XML).unwrap();
+
+ let data: Vec<u8> = 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<Value> {
+ 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<Value> {
+ 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<Value> {
+ data.get(0).and_then(|v| options.get(*v)).map(Value::String)
+}
+
+pub fn data2c(data: &[u8]) -> Option<Value> {
+ 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<Value> {
+ 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<Value> {
+ data.get(0).map(|v| *v == 1).map(Value::Bool)
+}
+
+pub fn byte(data: &[u8]) -> Option<Value> {
+ data.get(0).and_then(|value| {
+ if *value == 0xff {
+ None
+ } else {
+ Some(Value::U8(*value))
+ }
+ })
+}
+
+pub fn data1b(data: &[u8]) -> Option<Value> {
+ 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<Value> {
+ 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<Value> {
+ 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::<Vec<String>>()
+ .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]));
+ }
+}