diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | README.md | 12 | ||||
-rw-r--r-- | doc/dsample-dump-2021-10-big.bin | bin | 0 -> 23265280 bytes | |||
-rw-r--r-- | ebus-rust/.envrc | 1 | ||||
-rw-r--r-- | ebus-rust/Cargo.lock | 1351 | ||||
-rw-r--r-- | ebus-rust/Cargo.toml | 16 | ||||
-rw-r--r-- | ebus-rust/README.md | 32 | ||||
-rw-r--r-- | ebus-rust/flake.lock | 42 | ||||
-rw-r--r-- | ebus-rust/flake.nix | 31 | ||||
-rw-r--r-- | ebus-rust/src/build.rs | 0 | ||||
-rw-r--r-- | ebus-rust/src/layer2/crc.rs | 23 | ||||
-rw-r--r-- | ebus-rust/src/layer2/mod.rs | 263 | ||||
-rw-r--r-- | ebus-rust/src/layer7/mod.rs | 129 | ||||
-rw-r--r-- | ebus-rust/src/layer7/types.rs | 213 | ||||
-rw-r--r-- | ebus-rust/src/main.rs | 403 | ||||
-rw-r--r-- | ebus-rust/src/model/mod.rs | 126 | ||||
-rw-r--r-- | ebus-xml/ebus-0.1.xsd | 9 | ||||
-rw-r--r-- | ebus-xml/ebus.xml | 49 |
18 files changed, 2690 insertions, 14 deletions
@@ -1,3 +1,5 @@ *~ /ebus-xml/build -/ebus-racket/**/compiled
\ No newline at end of file +/ebus-racket/**/compiled +/ebus-rust/target +.* @@ -1,9 +1,5 @@ -# Ebus Tools +# eBus -## ebus-racket - -Ebus protocol parser written in [racket](http://www.racket-lang.org). - -## ebus-xml - -Ebus protocol specification (devices, packets, fields) in xml.
\ No newline at end of file +- [ebus-racket](ebus-racket/) eBus protocol parser written in racket +- [ebus-rust](ebus-rust/) eBus protocol parser writtin in rust +- [ebus-xml](ebus-xml/) eBus protocol specification (devices, packets, fields) in xml. diff --git a/doc/dsample-dump-2021-10-big.bin b/doc/dsample-dump-2021-10-big.bin Binary files differnew file mode 100644 index 0000000..f6f1157 --- /dev/null +++ b/doc/dsample-dump-2021-10-big.bin 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/flake.lock b/ebus-rust/flake.lock new file mode 100644 index 0000000..1114d39 --- /dev/null +++ b/ebus-rust/flake.lock @@ -0,0 +1,42 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1631561581, + "narHash": "sha256-3VQMV5zvxaVLvqqUrNz3iJelLw30mIVSfZmAaauM3dA=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "7e5bf3925f6fbdfaf50a2a7ca0be2879c4261d19", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1633709683, + "narHash": "sha256-KVJEToVd6SuUqc2XFIpfSjns07orqEWzIq9AcF/SIf8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "ce7a1190a0fa4ba3465b5f5471b08567060ca14c", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "ref": "nixos-21.05", + "type": "indirect" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/ebus-rust/flake.nix b/ebus-rust/flake.nix new file mode 100644 index 0000000..b39b841 --- /dev/null +++ b/ebus-rust/flake.nix @@ -0,0 +1,31 @@ +{ + inputs.nixpkgs.url = "nixpkgs/nixos-21.05"; + inputs.flake-utils.url = "github:numtide/flake-utils"; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in + { + devShell = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + cargo + rustc + pkg-config + rustfmt + rls + clippy + bashInteractive + nixpkgs-fmt + gitFull + xxd + influxdb + ]; + buildInputs = with pkgs; [ + openssl + ]; + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; + }; + }); +} 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, +} diff --git a/ebus-xml/ebus-0.1.xsd b/ebus-xml/ebus-0.1.xsd index 4e96176..bc7d504 100644 --- a/ebus-xml/ebus-0.1.xsd +++ b/ebus-xml/ebus-0.1.xsd @@ -75,6 +75,7 @@ <element name="word" type="tns:FIELD_WORD"></element> <element name="bit" type="tns:FIELD_BIT"></element> <element name="byteEnum" type="tns:FIELD_BYTE_ENUM" /> + <element name="string" type="tns:FIELD_STRING" /> </choice> </complexType> @@ -155,4 +156,12 @@ </extension> </complexContent> </complexType> + + <complexType name="FIELD_STRING"> + <complexContent> + <extension base="tns:FIELD_BASE_TYPE"> + <attribute name="length" type="short" use="required" /> + </extension> + </complexContent> + </complexType> </schema> diff --git a/ebus-xml/ebus.xml b/ebus-xml/ebus.xml index a4552ba..bc7147e 100644 --- a/ebus-xml/ebus.xml +++ b/ebus-xml/ebus.xml @@ -1,7 +1,6 @@ <?xml version="1.0" standalone="yes"?> <!-- Ebus Paketspezifikation. https://xapek.org/ --> -<ebus xmlns="http://xapek.org/ebus/0.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://xapek.org/ebus/0.1 ebus-0.1.xsd"> +<ebus xmlns="http://xapek.org/ebus/0.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xapek.org/ebus/0.1 ebus-0.1.xsd"> <devices> <device address="0" type="master" name="pcModem"> <description lang="de">PC oder Modem</description> @@ -72,6 +71,9 @@ </device> </devices> <packets> + <packet primary="3" secondary="1" name="unknown-3h-1h"> + <fields /> + </packet> <packet primary="5" secondary="3" name="betriebsdatenRegler1"> <description lang="de">Betriebsdaten des Feuerungsautomaten an den Regler Block1</description> <fields> @@ -88,7 +90,7 @@ <description lang="de">Boiler Temperatur</description> </byte> <data1b offset="7" name="aussenTemperatur"> - <description lang="de">Aussentemperatur</description> + <description lang="de">Aussentemperatur</description> </data1b> </fields> </packet> @@ -164,7 +166,7 @@ <fields> <data2b offset="0" name="aussenTemperatur" /> <bcd offset="2" name="sekunden" /> - <bcd offset="3" name="minuten" /><!-- FEHLER ab hier beim offset??? --> + <bcd offset="3" name="minuten" /> <!-- FEHLER ab hier beim offset??? --> <bcd offset="4" name="stunden" /> <bcd offset="5" name="tag" /> <bcd offset="6" name="monat" /> @@ -173,6 +175,17 @@ </fields> </packet> + <packet primary="7" secondary="4" name="identifikation"> + <fields> + <byte offset="0" name="hersteller" /> + <string offset="1" name="device" length="5" /> + <bcd offset="7" name="softwareversion" /> + <bcd offset="8" name="softwarerevision" /> + <bcd offset="9" name="hardwareversion" /> + <bcd offset="10" name="hardwarerevision" /> + </fields> + </packet> + <packet primary="8" secondary="0" name="sollwertuebertragungRegler"> <description lang="de">Sollwertübertragung des Reglers an andere Regler</description> <fields> @@ -252,5 +265,31 @@ </word> </fields> </packet> + + <packet primary="80" secondary="18" name="unknown-50h-12h"> + <fields /> + </packet> + + <packet primary="80" secondary="22" name="unknown-50h-16h"> + <fields /> + </packet> + + <packet primary="80" secondary="34" name="unknown-50h-22h"> + <fields /> + </packet> + + <packet primary="80" secondary="35" name="unknown-50h-23h"> + <fields /> + </packet> + + <packet primary="80" secondary="80" name="unknown-50h-50h"> + <fields /> + </packet> + + <packet primary="254" secondary="1" name="unknown3"> + <fields> + <string offset="0" name="statusstring" length="10" /> + </fields> + </packet> </packets> -</ebus> +</ebus>
\ No newline at end of file |