summaryrefslogtreecommitdiff
path: root/ebus-rust
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
parentcaae83f445935c06cd6aef36f283a4688675278a (diff)
parent810721ceac0249e747cdf36f3edec22d829d4371 (diff)
downloadebus-d0c7100b706d70dfff342def2bac864693fe8544.tar.gz
ebus-d0c7100b706d70dfff342def2bac864693fe8544.zip
Merge branch 'ebus-rust'HEADmaster
Diffstat (limited to 'ebus-rust')
-rw-r--r--ebus-rust/.envrc1
-rw-r--r--ebus-rust/Cargo.lock1351
-rw-r--r--ebus-rust/Cargo.toml16
-rw-r--r--ebus-rust/README.md32
-rw-r--r--ebus-rust/src/build.rs0
-rw-r--r--ebus-rust/src/layer2/crc.rs23
-rw-r--r--ebus-rust/src/layer2/mod.rs263
-rw-r--r--ebus-rust/src/layer7/mod.rs129
-rw-r--r--ebus-rust/src/layer7/types.rs213
-rw-r--r--ebus-rust/src/main.rs403
-rw-r--r--ebus-rust/src/model/mod.rs126
11 files changed, 2557 insertions, 0 deletions
diff --git a/ebus-rust/.envrc b/ebus-rust/.envrc
new file mode 100644
index 0000000..3550a30
--- /dev/null
+++ b/ebus-rust/.envrc
@@ -0,0 +1 @@
+use flake
diff --git a/ebus-rust/Cargo.lock b/ebus-rust/Cargo.lock
new file mode 100644
index 0000000..90d391c
--- /dev/null
+++ b/ebus-rust/Cargo.lock
@@ -0,0 +1,1351 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "base64"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bstr"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
+dependencies = [
+ "lazy_static",
+ "memchr",
+ "regex-automata",
+ "serde",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "cc"
+version = "1.0.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time",
+ "winapi",
+]
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
+name = "csv"
+version = "1.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
+dependencies = [
+ "bstr",
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ebus"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "env_logger",
+ "log",
+ "nom",
+ "quick-xml",
+ "rinfluxdb",
+ "serde",
+ "structopt",
+ "url",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
+dependencies = [
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+dependencies = [
+ "matches",
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
+
+[[package]]
+name = "futures-io"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
+
+[[package]]
+name = "futures-sink"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
+
+[[package]]
+name = "futures-task"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
+
+[[package]]
+name = "futures-util"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
+dependencies = [
+ "autocfg",
+ "futures-core",
+ "futures-io",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "h2"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c06815895acec637cd6ed6e9662c935b866d20a106f8361892893a7d9234964"
+dependencies = [
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "http"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5"
+dependencies = [
+ "bytes",
+ "http",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
+
+[[package]]
+name = "httpdate"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "0.14.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+dependencies = [
+ "bytes",
+ "hyper",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+dependencies = [
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
+
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
+
+[[package]]
+name = "js-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b2f96d100e1cf1929e7719b7edb3b90ab5298072638fccd77be9ce942ecdfce"
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matches"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+
+[[package]]
+name = "minimal-lexical"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c64630dcdd71f1a64c435f54885086a0de5d6a12d104d69b165fb7d5286d677"
+
+[[package]]
+name = "mio"
+version = "0.7.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
+dependencies = [
+ "libc",
+ "log",
+ "miow",
+ "ntapi",
+ "winapi",
+]
+
+[[package]]
+name = "miow"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "nom"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1"
+dependencies = [
+ "memchr",
+ "minimal-lexical",
+ "version_check",
+]
+
+[[package]]
+name = "ntapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
+
+[[package]]
+name = "openssl"
+version = "0.10.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69df2d8dfc6ce3aaf44b40dec6f487d5a886516cf6879c49e98e0710f310a058"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c9b1041b4387893b91ee6746cddfc28516aff326a3519fb2adf820932c5e6cb"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b"
+dependencies = [
+ "memchr",
+ "serde",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280"
+dependencies = [
+ "base64",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-tls",
+ "ipnet",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "mime",
+ "native-tls",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-native-tls",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "winreg",
+]
+
+[[package]]
+name = "rinfluxdb"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d3e5a6fdf3b411012a7c211198da64ced3855dbb26f87d2eb5b4f4da70503aa"
+dependencies = [
+ "async-trait",
+ "chrono",
+ "csv",
+ "itertools",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "thiserror",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+
+[[package]]
+name = "schannel"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
+dependencies = [
+ "lazy_static",
+ "winapi",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
+dependencies = [
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.130"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.68"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
+
+[[package]]
+name = "socket2"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "structopt"
+version = "0.3.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40b9788f4202aa75c240ecc9c15c65185e6a39ccdeb0fd5d008b98825464c87c"
+dependencies = [
+ "clap",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+
+[[package]]
+name = "tokio"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc"
+dependencies = [
+ "autocfg",
+ "bytes",
+ "libc",
+ "memchr",
+ "mio",
+ "num_cpus",
+ "pin-project-lite",
+ "winapi",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d3725d3efa29485e87311c5b699de63cde14b00ed4d256b8318aa30ca452cd"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
+[[package]]
+name = "tracing"
+version = "0.1.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
+dependencies = [
+ "cfg-if",
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "url"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "matches",
+ "percent-encoding",
+ "serde",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
+dependencies = [
+ "cfg-if",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b"
+dependencies = [
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
+
+[[package]]
+name = "web-sys"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "winreg"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
+dependencies = [
+ "winapi",
+]
diff --git a/ebus-rust/Cargo.toml b/ebus-rust/Cargo.toml
new file mode 100644
index 0000000..545a225
--- /dev/null
+++ b/ebus-rust/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "ebus"
+version = "0.1.0"
+authors = ["Yves Fischer <yvesf+git@xapek.org>"]
+edition = "2018"
+
+[dependencies]
+serde = { version = "1.0", features = [ "derive" ] }
+quick-xml = { version = "0.22", features = [ "serialize" ] }
+nom = "7"
+structopt = "0.3"
+bytes = "1"
+env_logger = "0.9"
+log = "0.4"
+rinfluxdb = "0.1"
+url = "2" \ No newline at end of file
diff --git a/ebus-rust/README.md b/ebus-rust/README.md
new file mode 100644
index 0000000..b15a4d6
--- /dev/null
+++ b/ebus-rust/README.md
@@ -0,0 +1,32 @@
+# ebus-rust
+
+### Dumping sample to stdout
+```shell
+RUST_LOG=info cargo run parse-l7 < ../doc/sample_dump_2.bin
+```
+
+### Running against influxdb
+Start influxdb:
+```shell
+influxdb run
+```
+
+At first time:
+```shell
+influx -execute 'create database ebus'
+```
+(default data directory is `$HOME/.influxdb`).
+
+Insert dump:
+```shell
+RUST_LOG=info cargo run influxdb < ../doc/sample_dump_2.bin
+```
+
+Verify:
+```sql
+influx
+> show databases
+> use ebus
+> show measurements
+> SELECT * FROM ebus LIMIT 5;
+``` \ No newline at end of file
diff --git a/ebus-rust/src/build.rs b/ebus-rust/src/build.rs
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ebus-rust/src/build.rs
diff --git a/ebus-rust/src/layer2/crc.rs b/ebus-rust/src/layer2/crc.rs
new file mode 100644
index 0000000..6fb782d
--- /dev/null
+++ b/ebus-rust/src/layer2/crc.rs
@@ -0,0 +1,23 @@
+const CRC: [u8; 256] = [
+ 0x00, 0x9b, 0xad, 0x36, 0xc1, 0x5a, 0x6c, 0xf7, 0x19, 0x82, 0xb4, 0x2f, 0xd8, 0x43, 0x75, 0xee,
+ 0x32, 0xa9, 0x9f, 0x04, 0xf3, 0x68, 0x5e, 0xc5, 0x2b, 0xb0, 0x86, 0x1d, 0xea, 0x71, 0x47, 0xdc,
+ 0x64, 0xff, 0xc9, 0x52, 0xa5, 0x3e, 0x08, 0x93, 0x7d, 0xe6, 0xd0, 0x4b, 0xbc, 0x27, 0x11, 0x8a,
+ 0x56, 0xcd, 0xfb, 0x60, 0x97, 0x0c, 0x3a, 0xa1, 0x4f, 0xd4, 0xe2, 0x79, 0x8e, 0x15, 0x23, 0xb8,
+ 0xc8, 0x53, 0x65, 0xfe, 0x09, 0x92, 0xa4, 0x3f, 0xd1, 0x4a, 0x7c, 0xe7, 0x10, 0x8b, 0xbd, 0x26,
+ 0xfa, 0x61, 0x57, 0xcc, 0x3b, 0xa0, 0x96, 0x0d, 0xe3, 0x78, 0x4e, 0xd5, 0x22, 0xb9, 0x8f, 0x14,
+ 0xac, 0x37, 0x01, 0x9a, 0x6d, 0xf6, 0xc0, 0x5b, 0xb5, 0x2e, 0x18, 0x83, 0x74, 0xef, 0xd9, 0x42,
+ 0x9e, 0x05, 0x33, 0xa8, 0x5f, 0xc4, 0xf2, 0x69, 0x87, 0x1c, 0x2a, 0xb1, 0x46, 0xdd, 0xeb, 0x70,
+ 0x0b, 0x90, 0xa6, 0x3d, 0xca, 0x51, 0x67, 0xfc, 0x12, 0x89, 0xbf, 0x24, 0xd3, 0x48, 0x7e, 0xe5,
+ 0x39, 0xa2, 0x94, 0x0f, 0xf8, 0x63, 0x55, 0xce, 0x20, 0xbb, 0x8d, 0x16, 0xe1, 0x7a, 0x4c, 0xd7,
+ 0x6f, 0xf4, 0xc2, 0x59, 0xae, 0x35, 0x03, 0x98, 0x76, 0xed, 0xdb, 0x40, 0xb7, 0x2c, 0x1a, 0x81,
+ 0x5d, 0xc6, 0xf0, 0x6b, 0x9c, 0x07, 0x31, 0xaa, 0x44, 0xdf, 0xe9, 0x72, 0x85, 0x1e, 0x28, 0xb3,
+ 0xc3, 0x58, 0x6e, 0xf5, 0x02, 0x99, 0xaf, 0x34, 0xda, 0x41, 0x77, 0xec, 0x1b, 0x80, 0xb6, 0x2d,
+ 0xf1, 0x6a, 0x5c, 0xc7, 0x30, 0xab, 0x9d, 0x06, 0xe8, 0x73, 0x45, 0xde, 0x29, 0xb2, 0x84, 0x1f,
+ 0xa7, 0x3c, 0x0a, 0x91, 0x66, 0xfd, 0xcb, 0x50, 0xbe, 0x25, 0x13, 0x88, 0x7f, 0xe4, 0xd2, 0x49,
+ 0x95, 0x0e, 0x38, 0xa3, 0x54, 0xcf, 0xf9, 0x62, 0x8c, 0x17, 0x21, 0xba, 0x4d, 0xd6, 0xe0, 0x7b,
+];
+
+#[inline]
+pub fn crc(data: &[u8]) -> u8 {
+ data.iter().fold(0, |acc, x| CRC[acc as usize] ^ x)
+}
diff --git a/ebus-rust/src/layer2/mod.rs b/ebus-rust/src/layer2/mod.rs
new file mode 100644
index 0000000..ef07aad
--- /dev/null
+++ b/ebus-rust/src/layer2/mod.rs
@@ -0,0 +1,263 @@
+use std::fmt;
+
+mod crc;
+
+const EBUS_SYN: u8 = 0xaa;
+const EBUS_ESCAPE: u8 = 0xa9;
+const EBUS_ACKOK: u8 = 0x00;
+
+#[derive(Default)]
+pub struct Packet {
+ pub source: u8,
+ pub destination: u8,
+ pub primary: u8,
+ pub secondary: u8,
+ payload_length: u8,
+ payload: Vec<u8>,
+ pub crc: u8,
+ payload_slave_length: u8,
+ payload_slave: Vec<u8>,
+ crc_slave: u8,
+}
+
+impl Packet {
+ // payload returns the un-escaped payload
+ pub fn payload_decoded(&self) -> Vec<u8> {
+ let mut v = Vec::new();
+ let mut i = 0;
+
+ while i < self.payload.len() {
+ let c = self.payload[i];
+ if c == EBUS_ESCAPE && i + 1 < self.payload.len() {
+ i += 1;
+ let c = self.payload[i];
+ if c == 0x0 {
+ v.push(EBUS_ESCAPE);
+ } else if c == 0x1 {
+ v.push(EBUS_SYN);
+ } else {
+ v.push(c);
+ }
+ } else {
+ v.push(c);
+ }
+ i += 1;
+ }
+ v
+ }
+
+ pub fn calc_crc(&self) -> u8 {
+ let x = &[
+ self.source,
+ self.destination,
+ self.primary,
+ self.secondary,
+ self.payload_length,
+ ];
+ crc::crc(&[&x[..], self.payload.as_slice()].concat())
+ }
+}
+
+impl fmt::Debug for Packet {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if self.payload_slave_length > 0 {
+ f.debug_struct("Packet")
+ .field("source", &format!("{:#02x}", &self.source))
+ .field("destination", &format!("{:#02x}", &self.destination))
+ .field("primary", &format!("{:#02x}", &self.primary))
+ .field("secondary", &format!("{:#02x}", &self.secondary))
+ .field("payload", &format!("{:02x?}", &self.payload))
+ .field("payload_length", &self.payload_length)
+ .field("crc", &format!("{:#02x}", &self.crc))
+ .field(
+ "payload_slave_length",
+ &format!("{:#02x}", &self.payload_slave_length),
+ )
+ .field("payload_slave", &format!("{:02x?}", &self.payload_slave))
+ .field("crc_slave", &format!("{:#02x}", &self.crc_slave))
+ .finish()
+ } else {
+ f.debug_struct("Packet")
+ .field("source", &format!("{:#02x}", &self.source))
+ .field("destination", &format!("{:#02x}", &self.destination))
+ .field("primary", &format!("{:#02x}", &self.primary))
+ .field("secondary", &format!("{:#02x}", &self.secondary))
+ .field("payload", &format!("{:02x?}", &self.payload))
+ .field("payload_length", &self.payload_length)
+ .field("crc", &format!("{:#02x}", &self.crc))
+ .finish()
+ }
+ }
+}
+
+fn read_header(data: &[u8]) -> nom::IResult<&[u8], Packet> {
+ use nom::number::streaming::u8;
+ use nom::sequence::tuple;
+ let (input, (source, destination, primary, secondary, payload_length)) =
+ tuple((u8, u8, u8, u8, u8))(data)?;
+ Ok((
+ input,
+ Packet {
+ source,
+ destination,
+ primary,
+ secondary,
+ payload_length,
+ ..Default::default()
+ },
+ ))
+}
+
+fn read_packet(data: &[u8]) -> nom::IResult<&[u8], Packet> {
+ use nom::bytes::streaming::tag;
+ use nom::combinator::opt;
+ use nom::multi::count;
+ use nom::number::streaming::u8;
+ use nom::sequence::tuple;
+
+ let (input, mut packet) = read_header(data)?;
+
+ let (input2, (payload, crc, _, payload_slave_length)) = tuple((
+ count(u8, packet.payload_length as usize), // payload
+ u8, // crc
+ opt(tag([EBUS_ACKOK])), // ACK but non-ack is tolerated
+ u8, // SYN or payload slave length
+ ))(input)?;
+ packet.payload = payload;
+ packet.crc = crc;
+
+ if payload_slave_length != EBUS_SYN {
+ packet.payload_slave_length = payload_slave_length;
+ let (input3, (payload_slave, crc_slave)) =
+ tuple((count(u8, payload_slave_length as usize), u8))(input2)?;
+ packet.payload_slave = payload_slave;
+ packet.crc_slave = crc_slave;
+ Ok((input3, packet))
+ } else {
+ Ok((input2, packet))
+ }
+}
+
+pub fn parse(data: &[u8]) -> Result<Packet, nom::Err<nom::error::Error<&[u8]>>> {
+ use nom::bytes::streaming::take_while;
+ use nom::sequence::preceded;
+ fn take_syn(data: &[u8]) -> nom::IResult<&[u8], &[u8]> {
+ take_while(|chr| chr == EBUS_SYN)(data)
+ }
+
+ let mut parser = preceded(take_syn, read_packet);
+ let result = parser(data);
+ result.map(|r| r.1)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn teststructure_mastermaster() {
+ 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 = parse(&data).unwrap();
+ assert_eq!(res.source, 0003);
+ assert_eq!(res.destination, 241);
+ assert_eq!(res.primary, 008);
+ assert_eq!(res.secondary, 000);
+ assert_eq!(res.payload_length, 0008);
+ assert_eq!(res.crc, 128);
+ assert_eq!(res.payload, vec![128, 040, 230, 002, 000, 002, 000, 010]);
+ }
+
+ #[test]
+ fn testcrc() {
+ let packets = [
+ &[
+ 0x10, 0x03, 0x08, 0x00, 0x08, 0x00, 0x05, 0x00, 0x13, 0x80, 0x40, 0x00, 0x0a, 0x71,
+ 0x00, 0xaa,
+ ][..],
+ &[
+ 0x03, 0xf1, 0x08, 0x00, 0x08, 0x00, 0x14, 0x00, 0x13, 0x80, 0x00, 0x00, 0x0f, 0xc7,
+ 0x00, 0xaa,
+ ][..],
+ &[
+ 0x10, 0x03, 0x05, 0x07, 0x09, 0x00, 0x01, 0x50, 0x00, 0x01, 0x00, 0xff, 0x14, 0xff,
+ 0xa6, 0x00, 0xaa,
+ ][..],
+ &[
+ 0xf1, 0xfe, 0x05, 0x03, 0x08, 0x01, 0x00, 0x40, 0xff, 0x30, 0xff, 0x00, 0x13, 0xd8,
+ 0x00, 0xaa,
+ ][..],
+ &[
+ 0x03, 0xfe, 0x05, 0x03, 0x08, 0x01, 0x00, 0x00, 0x00, 0x30, 0x17, 0x33, 0x13, 0x82,
+ 0x00, 0xaa,
+ ][..],
+ &[
+ // 00000d60: aaaa 1003 0507 09bb 044b 0300 80ff 54ff .........K....T.
+ // 00000d70: 0400 aaaa aaaa aaaa f1fe 0503 0801 0110 ................
+ 0x10, 0x03, 0x05, 0x07, 0x09, 0xbb, 0x04, 0x4b, 0x03, 0x00, 0x80, 0xff, 0x54, 0xff,
+ 0x04, 0x00, 0xaa,
+ ][..],
+ ];
+ for d in &packets {
+ let p = parse(d).unwrap();
+ assert_eq!(p.crc, p.calc_crc(), "{:?}", p);
+ }
+ }
+
+ #[test]
+ fn test_escape() {
+ let data: Vec<u8> = vec![
+ 170, // Syn
+ 170, // Syn
+ 3, // Source
+ 241, // Destination
+ 8, // primaryCommand
+ 0, // secondaryCommand
+ 8, // payloadLength
+ 128, // p1
+ 40, // p2
+ EBUS_ESCAPE, // p3
+ 1, // p4
+ EBUS_ESCAPE, // p5
+ 0, // p6
+ 0, // p7
+ 10, // p8
+ 128, // CRC
+ 0, // ACK
+ 170, // SYN
+ 170,
+ ];
+
+ let res = parse(&data).unwrap();
+ assert_eq!(res.source, 0003);
+ assert_eq!(res.destination, 241);
+ assert_eq!(res.primary, 008);
+ assert_eq!(res.secondary, 000);
+ assert_eq!(res.payload_length, 0008);
+ assert_eq!(res.crc, 128);
+ assert_eq!(
+ res.payload_decoded(),
+ vec![128, 040, EBUS_SYN, EBUS_ESCAPE, 000, 010]
+ );
+ }
+}
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]));
+ }
+}
diff --git a/ebus-rust/src/main.rs b/ebus-rust/src/main.rs
new file mode 100644
index 0000000..9046ce6
--- /dev/null
+++ b/ebus-rust/src/main.rs
@@ -0,0 +1,403 @@
+extern crate bytes;
+extern crate env_logger;
+extern crate log;
+extern crate nom;
+extern crate quick_xml;
+extern crate serde;
+extern crate structopt;
+
+mod layer2;
+mod layer7;
+mod model;
+
+use bytes::{BufMut, BytesMut};
+use std::fs::File;
+use std::io;
+use std::io::Read;
+use std::path::Path;
+use std::thread;
+use std::time;
+use structopt::StructOpt;
+
+#[derive(Debug, StructOpt)]
+struct Opts {
+ #[structopt(
+ short,
+ long,
+ default_value = "",
+ help = "if not set internal copy will be used"
+ )]
+ config: String,
+ #[structopt(skip)]
+ ebus_xml: Vec<u8>,
+ #[structopt(short, long, help = "Use the adapter.ebusd.eu enhanced protocol")]
+ enhanced: bool,
+ #[structopt(subcommand)]
+ subcmd: Command,
+}
+
+#[derive(Debug, StructOpt)]
+enum Command {
+ Dump(Dump),
+ DumpL2(DumpL2),
+ ParseL2(ParseL2),
+ ParseL7(ParseL7),
+ Influxdb(Influxdb),
+}
+
+impl Command {
+ fn run(&self, opts: &Opts) {
+ match self {
+ Command::Dump(..) => dump(opts),
+ Command::DumpL2(..) => dump_l2(opts),
+ Command::ParseL2(..) => parse_l2(opts, &|p| println!("{:?}", p)),
+ Command::ParseL7(..) => parse_l7(opts, &|_, _, _, _| ()),
+ Command::Influxdb(cfg) => influxdb(opts, cfg),
+ }
+ }
+}
+
+#[derive(Debug, StructOpt)]
+#[structopt(about = "Dump the configuration from XML to stdout")]
+struct Dump {}
+
+#[derive(Debug, StructOpt)]
+#[structopt(about = "Dump raw data")]
+struct DumpL2 {}
+
+#[derive(Debug, StructOpt)]
+#[structopt(about = "Parse and dump L2 from stdin")]
+struct ParseL2 {}
+
+#[derive(Debug, StructOpt)]
+#[structopt(about = "Parse and dump L7 from stdin")]
+struct ParseL7 {}
+
+#[derive(Debug, StructOpt)]
+#[structopt(about = "Parse from stdin and write to influxdb")]
+struct Influxdb {
+ #[structopt(short, long, default_value = "http://localhost:8086")]
+ url: String,
+ #[structopt(short, long, default_value = "ebus")]
+ db: String,
+ #[structopt(short, long, default_value = "ebus")]
+ measurement: String,
+}
+
+fn main() {
+ env_logger::init();
+ let mut opts: Opts = Opts::from_args();
+
+ log::info!("Enhanced protocol is status: {}", opts.enhanced);
+
+ if opts.config.is_empty() {
+ opts.ebus_xml = Vec::from(*include_bytes!("../../ebus-xml/ebus.xml"));
+ } else {
+ let path = Path::new(&opts.config);
+ let mut file = File::open(&path)
+ .map_err(|e| format!("could not open file: {}", e))
+ .unwrap();
+ let mut xml = String::new();
+ file.read_to_string(&mut xml)
+ .map_err(|e| format!("could not read: {}", e))
+ .unwrap();
+ opts.ebus_xml = Vec::from(xml.as_bytes());
+ }
+
+ opts.subcmd.run(&opts);
+}
+
+fn dump(opts: &Opts) {
+ let conf = model::read_config(&opts.ebus_xml).unwrap();
+ for dev in conf.devices.devices {
+ println!("device: address={} name={}", dev.address, dev.name);
+ for d in dev.descriptions {
+ println!(" {}: {}", d.lang, d.text)
+ }
+ }
+ for p in conf.packets.packets {
+ println!(
+ "packet: primary={:02} secondary={:02} name={}",
+ p.primary, p.secondary, p.name
+ );
+ for d in p.descriptions {
+ println!(" {}: {}", d.lang, d.text);
+ }
+ for f in p.fields.fields.unwrap() {
+ match f {
+ model::PacketField::Byte { offset, name } => {
+ println!(" [{:02}] Byte: {}", offset, name);
+ }
+ model::PacketField::Data1b { offset, name } => {
+ println!(" [{:02}] Data1b: {}", offset, name)
+ }
+ model::PacketField::Data1c { offset, name } => {
+ println!(" [{:02}] Data1c: {}", offset, name)
+ }
+ model::PacketField::Data2b { offset, name } => {
+ println!(" [{:02}] Data2b: {}", offset, name)
+ }
+ model::PacketField::Data2c { offset, name } => {
+ println!(" [{:02}] Data1c: {}", offset, name)
+ }
+ model::PacketField::ByteEnum {
+ offset,
+ name,
+ options,
+ } => {
+ println!(" [{:02}] ByteEnum: {}", offset, name);
+ for o in options.0 {
+ println!(" [{:02}] {}", o.value, o.name);
+ }
+ }
+ model::PacketField::Bcd { offset, name } => {
+ println!(" [{:02}] Bcd: {}", offset, name)
+ }
+ model::PacketField::Bit { offset, name } => {
+ println!(" [{:02}] Bit: {}", offset, name)
+ }
+ model::PacketField::Word { offset, name } => {
+ println!(" [{:02}] Word: {}", offset, name)
+ }
+ model::PacketField::String {
+ offset,
+ name,
+ length,
+ } => {
+ println!(" [{:02}] String: {} / {}", offset, name, length)
+ }
+ }
+ }
+ }
+}
+
+fn dump_l2(opts: &Opts) {
+ let mut buf = BytesMut::with_capacity(1024);
+ let mut other: u8 = 0;
+ let mut sync = false;
+
+ for b in io::stdin().bytes() {
+ let mut b = b.unwrap();
+ if opts.enhanced && b >= 0x80 {
+ if other == 0 {
+ if b & 0xc0 == 0xc0 {
+ other = b;
+ }
+ continue;
+ } else {
+ // byte-1 byte-2
+ // 76543210 76543210
+ // 11ccccdd 10dddddd
+ // c: command (0x01 = receive)
+ // d: data (here b)
+ b = (other & 0x03) << 6 | b & 0x3f;
+ other = 0;
+ }
+ }
+ if !sync {
+ if b == 0xaa {
+ sync = true;
+ }
+ continue;
+ }
+ buf.put_u8(b);
+
+ if b == 0xaa {
+ if buf.len() > 1 {
+ println!(
+ "{:#02x?}",
+ buf.iter()
+ .map(|v| format!("0x{:02x}", v))
+ .collect::<Vec<String>>()
+ .join(", ")
+ );
+ }
+ buf.clear()
+ }
+ }
+}
+
+fn parse_l2(opts: &Opts, cb: &dyn Fn(layer2::Packet)) {
+ let mut buf = BytesMut::with_capacity(1024);
+ let mut other: u8 = 0;
+ let mut sync = false;
+ for b in io::stdin().bytes() {
+ let mut b = b.unwrap();
+ if opts.enhanced && b >= 0x80 {
+ if other == 0 {
+ if b & 0xc0 == 0xc0 {
+ other = b;
+ }
+ continue;
+ } else {
+ // byte-1 byte-2
+ // 76543210 76543210
+ // 11ccccdd 10dddddd
+ // c: command (0x01 = receive)
+ // d: data (here b)
+ b = (other & 0x03) << 6 | b & 0x3f;
+ other = 0;
+ }
+ }
+ if !sync {
+ if b == 0xaa {
+ sync = true;
+ }
+ continue;
+ }
+ if b == 0xaa {
+ if buf.len() > 1 {
+ buf.put_u8(0xaa);
+ let l2p = layer2::parse(&buf);
+ match l2p.ok() {
+ Some(p) => {
+ if p.calc_crc() == p.crc {
+ log::debug!(
+ "{:#02x?} from {:#02x?}",
+ p,
+ buf.iter()
+ .map(|v| format!("0x{:02x}", v))
+ .collect::<Vec<String>>()
+ .join(", ")
+ );
+ } else {
+ log::info!(
+ "CRC-Fail: [{}] should be crc={:#02x}",
+ buf.iter()
+ .map(|v| format!("0x{:02x}", v))
+ .collect::<Vec<String>>()
+ .join(", "),
+ p.calc_crc()
+ );
+ }
+ cb(p);
+ }
+ None => {
+ if buf.len() > 2 {
+ log::info!(
+ "Discard: [{}]",
+ buf.iter()
+ .map(|v| format!("0x{:02x}", v))
+ .collect::<Vec<String>>()
+ .join(", ")
+ );
+ }
+ }
+ }
+ buf.clear();
+ }
+ continue;
+ }
+ buf.put_u8(b);
+ }
+}
+
+fn parse_l7(opts: &Opts, cb: &dyn Fn(String, String, String, &Vec<layer7::DecodedField>)) {
+ let conf = model::read_config(&opts.ebus_xml).unwrap();
+
+ parse_l2(opts, &|p| {
+ let pack = conf.packets.get(p.primary, p.secondary);
+ if pack.is_none() {
+ log::info!("No definition: {:?}", p);
+ return;
+ }
+ let pack = pack.unwrap();
+ let values = pack
+ .fields
+ .fields
+ .as_ref()
+ .map(|fields| layer7::decode_fields(&p, fields));
+
+ let source = conf
+ .devices
+ .devices
+ .iter()
+ .find(|d| d.address == p.source)
+ .map(|d| d.name.clone())
+ .unwrap_or_else(|| format!("{:#02x}", p.source));
+ let destination = conf
+ .devices
+ .devices
+ .iter()
+ .find(|d| d.address == p.destination)
+ .map(|d| d.name.clone())
+ .unwrap_or_else(|| format!("{:#02x}", p.destination));
+
+ log::info!(
+ "{} -> {}: {} {}",
+ source,
+ destination,
+ pack.name,
+ values
+ .as_ref()
+ .unwrap_or(&Vec::new())
+ .iter()
+ .map(|field| format!(
+ "{}={}",
+ field.name.clone(),
+ field
+ .v
+ .as_ref()
+ .map(|v2| v2.convert())
+ .unwrap_or_else(|| String::from("<>"))
+ ))
+ .collect::<Vec<String>>()
+ .join(", ")
+ );
+ if let Some(values) = values {
+ cb(source, destination, pack.name.clone(), &values);
+ }
+ })
+}
+
+impl From<&layer7::types::Value> for rinfluxdb::line_protocol::FieldValue {
+ fn from(v: &layer7::types::Value) -> Self {
+ match v {
+ layer7::types::Value::Bool(v) => rinfluxdb::line_protocol::FieldValue::from(*v),
+ layer7::types::Value::I8(v) => rinfluxdb::line_protocol::FieldValue::from(*v as i64),
+ layer7::types::Value::U8(v) => rinfluxdb::line_protocol::FieldValue::from(*v as u64),
+ layer7::types::Value::U16(v) => rinfluxdb::line_protocol::FieldValue::from(*v as u64),
+ layer7::types::Value::F32(v) => rinfluxdb::line_protocol::FieldValue::from(*v as f64),
+ layer7::types::Value::String(v) => {
+ rinfluxdb::line_protocol::FieldValue::from(v.clone())
+ }
+ }
+ }
+}
+
+fn influxdb(opts: &Opts, cfg: &Influxdb) {
+ use rinfluxdb::line_protocol;
+ use rinfluxdb::line_protocol::blocking::Client;
+ use rinfluxdb::line_protocol::LineBuilder;
+ use url::Url;
+
+ let url = Url::parse(&cfg.url).unwrap();
+ let client = Client::new(url, Some(("", ""))).unwrap();
+
+ parse_l7(opts, &|source, destination, name, values| {
+ let lines = values
+ .iter()
+ .filter(|df| df.v.is_some()) // filter ersatzwert values
+ .map(|df| {
+ let l = LineBuilder::new(cfg.measurement.clone())
+ .insert_tag("source", source.clone())
+ .insert_tag("destination", destination.clone())
+ .insert_field(
+ format!("{}.{}", name.clone(), df.name.clone()),
+ df.v.as_ref().unwrap(),
+ )
+ .build();
+ log::debug!("{:?}", l);
+ l
+ })
+ .collect::<Vec<line_protocol::Line>>();
+
+ match client.send(&cfg.db, &lines) {
+ Ok(_) => (),
+ Err(err) => {
+ log::error!("Sleep 1s after error sending values to influxdb: {}", err);
+ thread::sleep(time::Duration::from_millis(1000));
+ }
+ };
+ });
+}
diff --git a/ebus-rust/src/model/mod.rs b/ebus-rust/src/model/mod.rs
new file mode 100644
index 0000000..fd738fa
--- /dev/null
+++ b/ebus-rust/src/model/mod.rs
@@ -0,0 +1,126 @@
+use serde::Deserialize;
+
+use quick_xml::de::{from_reader};
+
+pub fn read_config(xml: &[u8]) -> Result<Ebus, String> {
+ let ebus_configuration = from_reader(xml).map_err(|e| format!("failed to read xml: {}", e))?;
+ Ok(ebus_configuration)
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Description {
+ pub lang: String,
+ #[serde(rename = "$value")]
+ pub text: String,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+#[serde(rename_all = "lowercase")]
+pub enum DeviceType {
+ Master,
+ Slave,
+ Broadcast,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Device {
+ pub address: u8,
+ #[serde(rename = "type", default)]
+ pub device_type: Option<DeviceType>,
+ pub name: String,
+ #[serde(rename = "description", default)]
+ pub descriptions: Vec<Description>,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Devices {
+ #[serde(rename = "device", default)]
+ pub devices: Vec<Device>,
+}
+
+#[derive(Debug, Deserialize, PartialEq, Clone)]
+pub struct ByteEnumOption {
+ pub value: u8,
+ pub name: String,
+}
+
+#[derive(Debug, Deserialize, PartialEq, Clone)]
+pub struct ByteEnumList(pub Vec<ByteEnumOption>);
+
+impl ByteEnumList {
+ pub fn get(&self, value: u8) -> Option<String> {
+ self.0
+ .iter()
+ .find(|v| v.value == value)
+ .map(|v| v.name.clone())
+ }
+}
+
+#[derive(Debug, Deserialize, PartialEq, Clone)]
+pub enum PacketField {
+ #[serde(rename = "byte")]
+ Byte { offset: u8, name: String },
+ #[serde(rename = "data1b")]
+ Data1b { offset: u8, name: String },
+ #[serde(rename = "data1c")]
+ Data1c { offset: u8, name: String },
+ #[serde(rename = "data2b")]
+ Data2b { offset: u8, name: String },
+ #[serde(rename = "data2c")]
+ Data2c { offset: u8, name: String },
+ #[serde(rename = "byteEnum")]
+ ByteEnum {
+ offset: u8,
+ name: String,
+ #[serde(rename = "option")]
+ options: ByteEnumList,
+ },
+ #[serde(rename = "bcd")]
+ Bcd { offset: u8, name: String },
+ #[serde(rename = "bit")]
+ Bit { offset: u8, name: String },
+ #[serde(rename = "word")]
+ Word { offset: u8, name: String },
+ #[serde(rename = "string")]
+ String {
+ offset: u8,
+ name: String,
+ length: u8,
+ },
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct PacketFields {
+ #[serde(rename = "$value")]
+ pub fields: Option<Vec<PacketField>>,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Packet {
+ pub primary: u8,
+ pub secondary: u8,
+ pub name: String,
+ #[serde(rename = "description", default)]
+ pub descriptions: Vec<Description>,
+ pub fields: PacketFields,
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Packets {
+ #[serde(rename = "packet", default)]
+ pub packets: Vec<Packet>,
+}
+
+impl Packets {
+ pub fn get(&self, primary: u8, secondary: u8) -> Option<&Packet> {
+ self.packets
+ .iter()
+ .find(|p| p.primary == primary && p.secondary == secondary)
+ }
+}
+
+#[derive(Debug, Deserialize, PartialEq)]
+pub struct Ebus {
+ pub devices: Devices,
+ pub packets: Packets,
+}