From 7db100a10fa9091c79cb3087ca8938864e6f958f Mon Sep 17 00:00:00 2001 From: Peyton McKee Date: Tue, 16 Jan 2024 14:44:14 -0500 Subject: [PATCH] Mqtt+yaml integration/refactoring (#30) * Add files via upload pub and sub mqtt client * #22 Refactoring of IPC and Mqtt * #22 Privatize Functions * #22 More Documentation * #22 Even more documentation * #22 And the debugging begins * #22 Debug Update * #22 Added Debug Statement * #22 Remove Thread * #22 Unwrap or Debug * #22 Edit Error Message * #22 Use Stream Instead of Channel * #22 Format Without New Lines * #22 Remove Debug Statements * #22 Date.now * #22 Mqtt Debug Statement * #22 Change publishing topic * #22 Change Topic * #22 Add / * #22 Add 50ms throttle after publish * #22 10ms buffer * #22 1ms throttle * #22 Remove Throttle * #22 Add 1ms throttle back * #22 Lots of General Code Cleanliness And Refactoring * #22 Remove Unused Dependencies * #22 Add Chrono Back * #22 Slight Cleanup * #20 finalize necessary can messages * Filled out Relevant CAN Fields * #20 Write Decode Data to File * #20 Compile * #20 Cleanup Some Linting errors * #20 Disable Clippy * #20 Actually dont allow clippy * #20 Clippy * #20 Delete Unused Files * #20 Seperate Yaml Files * #20 Add Documentation, remove unused dependencies * #20 Adjust can message topic name * Add can_interface specifier and can configure skip (#27) * add can_interface specifier and can configure skip * Update readme with can interface * use clippy suggestions * update minimum argument number checking * make can0 optional --------- Co-authored-by: ihong4 <92641118+ihong4@users.noreply.github.com> Co-authored-by: Jack Rubacha --- .github/workflows/rust-ci.yml | 27 + .gitignore | 1 + .vscode/settings.json | 6 + Cargo.lock | 734 ++++++-------------------- Cargo.toml | 10 +- README.md | 30 +- oxy/RustSynth.py | 106 +++- oxy/YAMLParser.py | 13 +- oxy/can-messages/bms.yaml | 243 +++++++++ oxy/can-messages/mpu.yaml | 51 ++ oxy/can-messages/wheel.yaml | 14 + oxy/mapping.yaml | 54 -- oxy/poc_translator.py | 48 -- oxy/structs/CANField.py | 9 +- oxy/structs/{CANmsg.py => CANMsg.py} | 9 +- oxy/structs/CorrectingFactor.py | 9 - oxy/structs/Decoding.py | 11 +- oxy/structs/Format.py | 56 ++ oxy/structs/Messages.py | 9 + oxy/structs/Result.py | 13 + oxy/typedpoc.py | 19 +- python/__init__.py | 0 python/__main__.py | 148 ------ python/data.py | 123 ----- python/decode_data.py | 338 ------------ python/decode_files.py | 77 --- python/decode_statuses.py | 188 ------- python/master_mapping.py | 763 --------------------------- python/message.py | 49 -- python/thread.py | 9 - src/client.rs | 16 + src/data.rs | 152 +++--- src/decode_data.rs | 490 +++-------------- src/decode_files.rs | 93 ---- src/decode_statuses.rs | 163 ------ src/lib.rs | 6 + src/main.rs | 138 +++-- src/master_mapping.rs | 256 +-------- src/message.rs | 55 +- src/mqtt.rs | 157 ++++++ src/telem_main.rs | 98 ---- src/thread.rs | 9 - 42 files changed, 1193 insertions(+), 3607 deletions(-) create mode 100644 .github/workflows/rust-ci.yml create mode 100644 .vscode/settings.json create mode 100644 oxy/can-messages/bms.yaml create mode 100644 oxy/can-messages/mpu.yaml create mode 100644 oxy/can-messages/wheel.yaml delete mode 100644 oxy/mapping.yaml delete mode 100644 oxy/poc_translator.py rename oxy/structs/{CANmsg.py => CANMsg.py} (71%) delete mode 100644 oxy/structs/CorrectingFactor.py create mode 100644 oxy/structs/Format.py create mode 100644 oxy/structs/Messages.py create mode 100644 oxy/structs/Result.py delete mode 100644 python/__init__.py delete mode 100644 python/__main__.py delete mode 100644 python/data.py delete mode 100644 python/decode_data.py delete mode 100644 python/decode_files.py delete mode 100644 python/decode_statuses.py delete mode 100644 python/master_mapping.py delete mode 100644 python/message.py delete mode 100644 python/thread.py create mode 100644 src/client.rs delete mode 100644 src/decode_files.rs delete mode 100644 src/decode_statuses.rs create mode 100644 src/lib.rs create mode 100644 src/mqtt.rs delete mode 100644 src/telem_main.rs delete mode 100644 src/thread.rs diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml new file mode 100644 index 0000000..c1f2f67 --- /dev/null +++ b/.github/workflows/rust-ci.yml @@ -0,0 +1,27 @@ +name: Rust CI + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Setup Rust + uses: actions/checkout@v2 + - name: Install cargo-audit + run: cargo install cargo-audit + - name: Build + run: cargo build --verbose + - name: Test + run: cargo test --verbose + - name: Clippy + run: cargo clippy --verbose -- -D warnings + - name: Audit + run: cargo audit diff --git a/.gitignore b/.gitignore index 15f2f6f..e22ff85 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # misc things .DS_Store .gitignore +*.nix # python things pyrightconfig.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e397228 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "rust-analyzer.linkedProjects": [ + "./Cargo.toml" + ] +} diff --git a/Cargo.lock b/Cargo.lock index 19d32cf..1d655d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,21 +3,14 @@ version = 3 [[package]] -name = "android_system_properties" -version = "0.1.5" +name = "async-channel" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" dependencies = [ - "libc", -] - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", + "concurrent-queue", + "event-listener", + "futures-core", ] [[package]] @@ -33,28 +26,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" [[package]] -name = "bumpalo" -version = "3.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "bytesize" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38fcc2979eff34a4b84e1cf9a1e3da42a7d44b3b690a40cdcb23e3d556cfb2e5" +name = "calypso" +version = "0.1.0" +dependencies = [ + "paho-mqtt", + "socketcan", +] [[package]] name = "cc" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -63,36 +49,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.24" +name = "cmake" +version = "0.1.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" dependencies = [ - "iana-time-zone", - "js-sys", - "num-integer", - "num-traits", - "time 0.1.45", - "wasm-bindgen", - "winapi", + "cc", ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "concurrent-queue" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ - "termcolor", - "unicode-width", + "crossbeam-utils", ] -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -104,126 +77,120 @@ dependencies = [ ] [[package]] -name = "crossbeam-deque" -version = "0.8.3" +name = "crossbeam-utils" +version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", ] [[package]] -name = "crossbeam-epoch" -version = "0.9.14" +name = "event-listener" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] -name = "crossbeam-utils" -version = "0.8.15" +name = "futures" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ - "cfg-if", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "cxx" -version = "1.0.94" +name = "futures-channel" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "futures-core", + "futures-sink", ] [[package]] -name = "cxx-build" -version = "1.0.94" +name = "futures-core" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", - "proc-macro2", - "quote", - "scratch", - "syn 2.0.15", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.94" +name = "futures-io" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] -name = "cxxbridge-macro" -version = "1.0.94" +name = "futures-macro" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn", ] [[package]] -name = "either" -version = "1.8.1" +name = "futures-sink" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] -name = "hermit-abi" -version = "0.2.6" +name = "futures-task" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] -name = "hex" -version = "0.2.0" +name = "futures-timer" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] -name = "iana-time-zone" -version = "0.1.56" +name = "futures-util" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" +name = "hex" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" -dependencies = [ - "cxx", - "cxx-build", -] +checksum = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" [[package]] name = "itertools" @@ -231,114 +198,23 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f" -[[package]] -name = "js-sys" -version = "0.3.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" -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.142" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" - -[[package]] -name = "link-cplusplus" -version = "1.0.8" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" -dependencies = [ - "cc", -] +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matrixmultiply" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb99c395ae250e1bf9133673f03ca9f97b7e71b705436bf8f089453445d1e9fe" -dependencies = [ - "rawpointer", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "nalgebra" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d47bba83f9e2006d117a9a33af1524e655516b8919caac694427a6fb1e511" -dependencies = [ - "approx", - "matrixmultiply", - "nalgebra-macros", - "num-complex", - "num-rational", - "num-traits", - "simba", - "typenum", -] - -[[package]] -name = "nalgebra-macros" -version = "0.2.0" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232c68884c0c99810a5a4d333ef7e47689cfd0edc85efc9e54e1e6bf5212766" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ner_processing" -version = "0.1.0" -dependencies = [ - "chrono", - "matrixmultiply", - "nalgebra", - "rayon", - "socketcan", - "systemstat", - "transpose", -] +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "nix" @@ -351,160 +227,86 @@ dependencies = [ ] [[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-complex" -version = "0.4.3" +name = "openssl-sys" +version = "0.9.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" +checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", + "cc", + "libc", + "pkg-config", + "vcpkg", ] [[package]] -name = "num-rational" -version = "0.4.1" +name = "paho-mqtt" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "19e405de34b835fb6457d8b0169eda21949f855472b3e346556af9e29fac6eb2" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "async-channel", + "crossbeam-channel", + "futures", + "futures-timer", + "libc", + "log", + "paho-mqtt-sys", + "thiserror", ] [[package]] -name = "num-traits" -version = "0.2.15" +name = "paho-mqtt-sys" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "5e482419d847af4ec43c07eed70f5f94f87dc712d267aecc91ab940944ab6bf4" dependencies = [ - "autocfg", + "cmake", + "openssl-sys", ] [[package]] -name = "num_cpus" -version = "1.15.0" +name = "pin-project-lite" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi", - "libc", -] +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] -name = "once_cell" -version = "1.17.1" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "paste" -version = "1.0.12" +name = "pkg-config" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "safe_arch" -version = "0.6.0" +name = "slab" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ - "bytemuck", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" - -[[package]] -name = "serde" -version = "1.0.160" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" - -[[package]] -name = "simba" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" -dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", - "wide", + "autocfg", ] [[package]] @@ -520,28 +322,11 @@ dependencies = [ "try_from", ] -[[package]] -name = "strength_reduce" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" -version = "2.0.15" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -549,63 +334,23 @@ dependencies = [ ] [[package]] -name = "systemstat" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24aec24a9312c83999a28e3ef9db7e2afd5c64bf47725b758cdc1cafd5b0bd2" -dependencies = [ - "bytesize", - "lazy_static", - "libc", - "nom", - "time 0.3.20", - "winapi", -] - -[[package]] -name = "termcolor" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "time" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" -dependencies = [ - "libc", - "wasi", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.20" +name = "thiserror" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ - "serde", - "time-core", + "thiserror-impl", ] [[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "transpose" -version = "0.2.2" +name = "thiserror-impl" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6522d49d03727ffb138ae4cbc1283d3774f0d10aa7f9bf52e6784c45daf9b23" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ - "num-integer", - "strength_reduce", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -614,187 +359,14 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "923a7ee3e97dbfe8685261beb4511cc9620a1252405d02693d43169729570111" -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - [[package]] name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasm-bindgen" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.84" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" - -[[package]] -name = "wide" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b689b6c49d6549434bf944e6b0f39238cf63693cb7a147e9d887507fffa3b223" -dependencies = [ - "bytemuck", - "safe_arch", -] - -[[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 = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" diff --git a/Cargo.toml b/Cargo.toml index 36b9d1b..4aa1040 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,10 @@ [package] -name = "ner_processing" +name = "calypso" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] - -rayon = "1.5.1" -chrono = "0.4.19" -systemstat = "0.2.1" socketcan = "1.7.0" -transpose = "0.2.2" -matrixmultiply = "0.3.3" -nalgebra = "0.32.2" +paho-mqtt = "0.12.3" diff --git a/README.md b/README.md index 2e85bac..17995cc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,34 @@ # Calypso Custom CAN Decoder for all the data being streamed around the car -### NERO Config +### Recommended Extensions +View https://www.youtube.com/watch?v=BU1LYFkpJuk for more information + +- rust-analyzer +- CodeLLDB +- Even Better TOML +- Error Lens +- Todo Tree +- crates + +#### Go to Settings in VSCode +search Rust-analyzer check and set the command from check -> clippy + +#### Open Settings.json +add following information: +``` +"[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer", + "editor.formatOnSave": true +} +``` + +### NERO 1.0 Config Utilizes a linux IPC to stream data to the NERO frontend -### SIREN Config +run ```/home/ner/Desktop/Calypso/target/release/calypso ipc /tmp/ipc.sock``` + +### SIREN and NERO 2.0 Config Utilizes MQTT Web Socket to offload data from the car for our telemetry system +run ```/home/ner/Desktop/Calypso/target/release/calypso mqtt localhost:1883``` + diff --git a/oxy/RustSynth.py b/oxy/RustSynth.py index c332f4c..6b93ad5 100644 --- a/oxy/RustSynth.py +++ b/oxy/RustSynth.py @@ -1,42 +1,110 @@ from structs.CANField import CANField from structs.CANMsg import CANMsg -from structs.Decoding import Decoding - -from typing import Optional +from structs.Messages import Messages +from structs.Result import Result class RustSynth: ''' A class to synthesize Rust from a given CANMsg spec. ''' - inst_hashmap: str = " let mut result = HashMap::new();" - closing: str = " result\n}" + ignore_clippy: str = "#![allow(clippy::all)]\n" # Ignoring clippy for decode_data because it's autogenerated and has some unnecessary type casting to ensure correct types + decode_data_import: str = "use super::data::{Data,FormatData as fd, ProcessData as pd}; \n" # Importing the Data struct and the FormatData and ProcessData traits + + decode_return_type: str = "Vec::" # The return type of any decode function + decode_return_value: str = f" let result = vec![" # Initializing the result vector + decode_close: str = " ]; \n result\n}\n" # Returning the result vector and closing the function + + decode_mock: str = """ +pub fn decode_mock(_data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(0.0, "Mock", "") + ]; + result +} +""" # A mock decode function that is used for messages that don't have a decode function + + master_mapping_import: str = "use super::decode_data::*; \nuse super::data::Data; \n" # Importing all the functions in decode_data.rs file and the Data struct + + master_mapping_signature: str = "pub fn get_message_info(id: &u32) -> MessageInfo { \n match id {" # The signature of the master_mapping function + + master_mapping_closing: str = " _ => MessageInfo::new(decode_mock), \n }\n}" # The closing of the master_mapping function and the default case for the match statement that returns the mock decode function + + message_info = """ +pub struct MessageInfo { + pub decoder: fn(data: &[u8]) -> Vec, +} +impl MessageInfo { + pub fn new(decoder: fn(data: &[u8]) -> Vec) -> Self { + Self { + decoder + } + } +} +""" # The MessageInfo struct that is used to store the decode function for a given message + + # The main function of the RustSynth class. Takes a list of CANMsgs and returns a Result object that contains the synthesized Rust code for the decode_data.rs and master_mapping.rs files + def parse_messages(self, msgs: [CANMsg]) -> Result: + result = Result("", "") + result.decode_data += self.ignore_clippy + result.decode_data += self.decode_data_import + result.decode_data += self.decode_mock + + result.master_mapping += self.master_mapping_import + result.master_mapping += self.message_info + result.master_mapping += self.master_mapping_signature + + for msg in msgs: + result.decode_data += self.synthesize(msg) + "\n" + result.master_mapping += self.map_msg_to_decoder(msg) + + result.master_mapping += self.master_mapping_closing + return result + + # Helper function that maps a given CANMsg to its decode function + def map_msg_to_decoder(self, msg: CANMsg) -> str: + return f" {msg.id} => MessageInfo::new({self.function_name(msg.desc)}),\n" + + # Helper function that synthesizes the decode function for a given CANMsg def synthesize(self, msg: CANMsg) -> str: signature: str = self.signature(msg.desc) generated_lines: list[str] = [] + # Generate a line for each field in the message for field in msg.fields: - generated_lines.append(self.finalize_line(field.id, f"({self.parse_decoders(field)}){self.correcting_factor(field)}")) - total_list: list[str] = [signature, self.inst_hashmap] + generated_lines + [self.closing] + generated_lines.append(self.finalize_line(field.name, field.unit, f"{self.format_data(field, self.parse_decoders(field))}")) + total_list: list[str] = [signature, self.decode_return_value] + generated_lines + [self.decode_close] return "\n".join(total_list) - def signature(self, to_decode: str) -> str: - return f"pub fn decode_{to_decode.replace(' ', '_')}(data: &[u8]) -> HashMap {{" + # Helper function that generates the name of a decode function for a given CANMsg based off the can message description + def function_name(self, desc: str) -> str: + return f"decode_{desc.replace(' ', '_').lower()}" + + # Helper function that generates the signature of a decode function for a given CANMsg based off the can message description + def signature(self, desc: str) -> str: + return f"pub fn {self.function_name(desc)}(data: &[u8]) -> {self.decode_return_type} {{" - def finalize_line(self, id: int, val: str) -> str: - return f" result.insert({id}, {val});" + # Helper function that generates a line the data struct for a given CANField value + def finalize_line(self, topic: str, unit: str, val: str) -> str: + return f" Data::new({val}, \"{topic}\", \"{unit}\")," + # Helper function that parses the decoders for a given CANField by applying the decoders to the data and casting the result to the final type of the CANField. def parse_decoders(self, field: CANField) -> str: if isinstance(field.decodings, type(None)): - return f"data[{field.index}] as f32" + return f"data[{field.index}] as {field.final_type}" else: - base: str = f"&data[{field.index}..{field.index + field.size}]" + base: str + if field.size == 1: + base = f"data[{field.index}]" + else : + base = f"&data[{field.index}..{field.index + field.size}]" for decoder in field.decodings: - base = f"pd::{decoder.repr}({base}, {decoder.bits}) as {decoder.final_type}" - return base + base = f"pd::{decoder.repr}({base} as {decoder.entry_type}, {decoder.bits})" + return f"{base} as {field.final_type}" - def correcting_factor(self, field:CANField) -> str: - cf: str = "" - if field.correcting_factor: - cf = f" {field.correcting_factor.op} {field.correcting_factor.const}" + # Helper function that formats the data for a given CANField based off the format of the CANField if it exists, returns the decoded data otherwise + def format_data(self, field:CANField, decoded_data: str) -> str: + cf = decoded_data + if field.format: + cf = f"fd::{field.format}({decoded_data})" return cf diff --git a/oxy/YAMLParser.py b/oxy/YAMLParser.py index 72a935f..c6dae1f 100644 --- a/oxy/YAMLParser.py +++ b/oxy/YAMLParser.py @@ -1,10 +1,9 @@ -from io import TextIOWrapper from ruamel.yaml import YAML, Any - from structs.CANMsg import CANMsg from structs.CANField import CANField -from structs.CorrectingFactor import CorrectingFactor -import structs.Decoding +from structs.Format import Format +from structs.Decoding import Decoding +from structs.Messages import Messages class YAMLParser: ''' @@ -14,12 +13,12 @@ class YAMLParser: def __init__(self): self.yaml = YAML() + self.yaml.register_class(Messages) self.yaml.register_class(CANMsg) self.yaml.register_class(CANField) - self.yaml.register_class(CorrectingFactor) - for decoding in structs.Decoding.Decoding.__subclasses__(): + for decoding in Decoding.__subclasses__(): self.yaml.register_class(decoding) - def parse(self, file: Any) -> CANMsg: + def parse(self, file: Any) -> Messages: return self.yaml.load(file) diff --git a/oxy/can-messages/bms.yaml b/oxy/can-messages/bms.yaml new file mode 100644 index 0000000..cb95642 --- /dev/null +++ b/oxy/can-messages/bms.yaml @@ -0,0 +1,243 @@ +!Messages +msgs: +#BMS BROADCAST +- !CANMsg + id: "0x80" + desc: "accumulator status" + fields: + - !CANField + name: "BMS/Pack/Voltage" + unit: "V" + size: 2 + decodings: + - !BigEndian + bits: 8 + format: "high_voltage" + - !CANField + name: "BMS/Pack/Current" + unit: "A" + size: 2 + decodings: + - !BigEndian + bits: 8 + - !TwosComplement + bits: 16 + format: "current" + - !CANField + name: "BMS/Pack/Amp-hours" + unit: "Ah" + size: 2 + decodings: + - !BigEndian + bits: 8 + - !CANField + name: "BMS/Pack/SOC" + unit: "%" + size: 1 + - !CANField + name: "BMS/Pack/Health" + unit: "%" + size: 1 + +- !CANMsg + id: "0x81" + desc: "BMS Status" + fields: + - !CANField + name: "BMS/State" + unit: "" + size: 1 + - !CANField + name: "BMS/Faults" + unit: "" + size: 4 + decodings: + - !LittleEndian + bits: 8 + - !CANField + name: "BMS/Temps/Average" + unit: "C" + size: 1 + decodings: + - !TwosComplement + bits: 8 + - !CANField + name: "BMS/Temps/Internal" + size: 1 + unit: "C" + decodings: + - !TwosComplement + bits: 8 + - !CANField + name: "BMS/Cells/BurningStatus" + size: 1 + unit: "" + +- !CANMsg + id: "0x82" + desc: "Shutdown Control" + fields: + - !CANField + name: "BMS/Shutdown/MPE" + size: 1 + unit: "" + +- !CANMsg + id: "0x83" + desc: "Cell Data" + fields: + - !CANField + name: "BMS/Cells/Volts/High/Value" + size: 2 + unit: "V" + decodings: + - !LittleEndian + bits: 8 + format: "cell_voltage" + - !CANField + name: "BMS/Cells/Volts/High/Chip" + size: 1 + unit: "" + decodings: + - !Half + bits: 4 + - !CANField + name: "BMS/Cells/Volts/High/Cell" + index: 2 + size: 1 + unit: "" + decodings: + - !Half + bits: 0 + - !CANField + name: "BMS/Cells/Volts/Low/Value" + size: 2 + index: 3 + unit: "V" + decodings: + - !LittleEndian + bits: 8 + format: "cell_voltage" + - !CANField + name: "BMS/Cells/Volts/Low/Chip" + index: 5 + size: 1 + unit: "" + decodings: + - !Half + bits: 4 + - !CANField + name: "BMS/Cells/Volts/Low/Cell" + index: 5 + size: 1 + unit: "" + decodings: + - !Half + bits: 0 + - !CANField + name: "BMS/Cells/Volts/Avg/Value" + size: 2 + index: 6 + unit: "V" + decodings: + - !LittleEndian + bits: 8 + format: "cell_voltage" + +- !CANMsg + id: "0x84" + desc: "Cell Temperatures" + fields: + - !CANField + name: "BMS/Cells/Temp/High/Value" + unit: "C" + size: 2 + decodings: + - !LittleEndian + bits: 8 + - !TwosComplement + bits: 16 + - !CANField + name: "BMS/Cells/Temp/High/Cell" + unit: "" + size: 1 + decodings: + - !Half + bits: 4 + - !CANField + name: "BMS/Cells/Temp/High/Chip" + unit: "" + size: 1 + index: 2 + decodings: + - !Half + bits: 0 + - !CANField + name: "BMS/Cells/Temp/Low/Value" + unit: "C" + size: 2 + index: 3 + decodings: + - !LittleEndian + bits: 8 + - !TwosComplement + bits: 16 + - !CANField + name: "BMS/Cells/Temp/Low/Cell" + unit: "" + size: 1 + index: 5 + decodings: + - !Half + bits: 4 + - !CANField + name: "BMS/Cells/Temp/Low/Chip" + unit: "" + size: 1 + index: 5 + decodings: + - !Half + bits: 0 + - !CANField + name: "BMS/Cells/Temp/Avg/Value" + unit: "C" + size: 2 + index: 6 + decodings: + - !LittleEndian + bits: 8 + - !TwosComplement + bits: 16 + +- !CANMsg + id: "0x85" + desc: "Segment Temperatures" + fields: + - !CANField + name: "BMS/Segment/Temp/1" + unit: "C" + size: 1 + decodings: + - !TwosComplement + bits: 8 + - !CANField + name: "BMS/Segment/Temp/2" + unit: "C" + size: 1 + decodings: + - !TwosComplement + bits: 8 + - !CANField + name: "BMS/Segment/Temp/3" + unit: "C" + size: 1 + decodings: + - !TwosComplement + bits: 8 + - !CANField + name: "BMS/Segment/Temp/4" + unit: "C" + size: 1 + decodings: + - !TwosComplement + bits: 8 diff --git a/oxy/can-messages/mpu.yaml b/oxy/can-messages/mpu.yaml new file mode 100644 index 0000000..f017731 --- /dev/null +++ b/oxy/can-messages/mpu.yaml @@ -0,0 +1,51 @@ +!Messages +msgs: +- !CANMsg + id: "0x500" + desc: "MPU Acceleromter" + fields: + - !CANField + name: "MPU/Accel/X" + unit: "g" + size: 2 + decodings: + - !BigEndian + bits: 8 + format: "acceleration" + - !CANField + name: "MPU/Accel/Y" + unit: "g" + size: 2 + decodings: + - !BigEndian + bits: 8 + format: "acceleration" + - !CANField + name: "MPU/Accel/Z" + unit: "g" + size: 2 + decodings: + - !BigEndian + bits: 8 + format: "acceleration" + +- !CANMsg + id: "0x501" + desc: "MPU Status" + fields: + - !CANField + name: "MPU/State/Mode" + unit: "" + size: 1 + - !CANField + name: "MPU/State/Torque_Limit_Percentage" + unit: "" + size: 1 + - !CANField + name: "MPU/State/Regen_Strength" + unit: "" + size: 1 + - !CANField + name: "MPU/State/Traction_Control" + unit: "" + size: 1 \ No newline at end of file diff --git a/oxy/can-messages/wheel.yaml b/oxy/can-messages/wheel.yaml new file mode 100644 index 0000000..3f92646 --- /dev/null +++ b/oxy/can-messages/wheel.yaml @@ -0,0 +1,14 @@ +!Messages +msgs: +- !CANMsg + id: "0x680" + desc: "Wheel State" + fields: + - !CANField + name: "WHEEL/Buttons/1" + unit: "" + size: 1 + - !CANField + name: "WHEEL/Buttons/2" + unit: "" + size: 1 \ No newline at end of file diff --git a/oxy/mapping.yaml b/oxy/mapping.yaml deleted file mode 100644 index 7182e03..0000000 --- a/oxy/mapping.yaml +++ /dev/null @@ -1,54 +0,0 @@ -!CANMsg -id: 1 -desc: "accumulator status" -fields: -- !CANField - id: 1 - name: Pack Inst Voltage - units: "V" - size: 2 - decodings: - - !BigEndian - bits: 8 - final_type: "f32" - correcting_factor: - !CorrectingFactor - const: 10.0 - op: "/" -- !CANField - id: 2 - name: "Pack Current" - units: "A" - size: 2 - decodings: - - !BigEndian - bits: 8 - final_type: "u32" - - !TwosComplement - bits: 16 - final_type: "f32" - correcting_factor: - !CorrectingFactor - const: 10.0 - op: "/" -- !CANField - id: 3 - name: "Pack Amp-hours" - units: "Ah" - size: 2 - decodings: - - !BigEndian - bits: 8 - final_type: "f32" -- !CANField - id: 4 - name: "Pack SOC" - units: "%" - size: 1 - final_type: "f32" -- !CANField - id: 5 - name: "Pack Health" - units: "%" - size: 1 - final_type: "f32" diff --git a/oxy/poc_translator.py b/oxy/poc_translator.py deleted file mode 100644 index 51c2db1..0000000 --- a/oxy/poc_translator.py +++ /dev/null @@ -1,48 +0,0 @@ -from ruamel.yaml import YAML, Any -from functools import reduce - - -yaml: YAML = YAML(typ="safe") - -out_string: str = "" -data: dict[str, Any] = yaml.load(open("mapping.yaml")) -print(data) -print(type(data)) - -function_name: str = "decode" + "_" + "_".join(data['string'].split(" ")) -args: str = "(data: &[u8])" -returnVal: str= " -> HashMap" - -signature: str = "pub fn " + function_name + args + returnVal + " {" -instantiate_hash_map: str = " let mut result = HashMap::new();" -conclusion: str = " result\n}" - -decodings: list[str] = [] -accumulated_size: int = 0 -for field in data["fields"]: # result.insert(1, (pd::big_endian(&data[0..2], 8) as f32) / 10.0); - field: dict - decoded: str - id = field["field_id"] - if field["size"] > 1: # we need to do some decoding, then - to_decode: str = f"&data[{accumulated_size}..{accumulated_size+field['size']}]" - _cf: str = field.get("correcting_factor", "") - correcting_factor: str = f"{' ' + ('/' if '/' in _cf else '*') + ' ' if 'correcting_factor' in field.keys() else ''}{_cf.split('/')[-1]}" - for decodingsetup in field["decoding"]: - decodingsetup: dict[str, dict[str, str]] = {k: reduce(lambda x,y: x|y, v, {}) for k,v in decodingsetup.items()} - for decoder, params in decodingsetup.items(): - match decoder: - case "big_endian": - to_decode = f"pd::big_endian({to_decode}, {params['bits']}) as {params['final_type']}" - case "twos_complement": - to_decode = f"pd::twos_comp({to_decode}, {params['bits']}) as {params['final_type']}" - decoded = f"{id}, {to_decode}{correcting_factor}" - else: # no decoding required! - decoded = f"{id}, data[{accumulated_size}] as {field['final_type']}" - - decodings.append(decoded) - accumulated_size += field["size"] - -formatted_decodings = [f" result.insert({i});" for i in decodings] - -finals: list[str] = [signature, instantiate_hash_map] + formatted_decodings + [conclusion] -print("\n".join(finals)) diff --git a/oxy/structs/CANField.py b/oxy/structs/CANField.py index bf32677..6ca53d1 100644 --- a/oxy/structs/CANField.py +++ b/oxy/structs/CANField.py @@ -1,20 +1,21 @@ from __future__ import annotations -from .CorrectingFactor import CorrectingFactor from .Decoding import * from ruamel.yaml import Optional from dataclasses import dataclass +from .Format import Format @dataclass class CANField: ''' - Represents a field in a CAN message. Has an id, a name, units, a size, + Represents a field in a CAN message. Has an id, a name, a unit, a size, and an optional CorrectingFactor and Decodings. Also knows its own index within its parent CANMsg, which is assigned at load from YAML. ''' id: int name: str - units: str + unit: str size: int index: int = -1 - correcting_factor: Optional[CorrectingFactor] = None + final_type: str = "f32" decodings: Optional[list[Decoding]] = None + format: Optional[str] = None diff --git a/oxy/structs/CANmsg.py b/oxy/structs/CANMsg.py similarity index 71% rename from oxy/structs/CANmsg.py rename to oxy/structs/CANMsg.py index d35664e..bdef91e 100644 --- a/oxy/structs/CANmsg.py +++ b/oxy/structs/CANMsg.py @@ -1,6 +1,4 @@ from __future__ import annotations -from ruamel.yaml import Optional, MappingNode -from structs.CorrectingFactor import CorrectingFactor from .CANField import CANField from dataclasses import dataclass @@ -9,15 +7,16 @@ class CANMsg: ''' Represents a CAN message. Has an id, a description, and a number of individual fields. ''' - id: int + id: str desc: str fields: list[CANField] def __post_init__(self) -> None: idx: int = 0 for field in self.fields: - field.index = idx - idx += field.size + if (field.index is not None): + field.index = idx + idx += field.size def __setstate__(self, state): diff --git a/oxy/structs/CorrectingFactor.py b/oxy/structs/CorrectingFactor.py deleted file mode 100644 index 530354c..0000000 --- a/oxy/structs/CorrectingFactor.py +++ /dev/null @@ -1,9 +0,0 @@ -from dataclasses import dataclass - -@dataclass -class CorrectingFactor: - ''' - Represents a correcting factor to be applied to data after decoding. - ''' - const: float - op: str diff --git a/oxy/structs/Decoding.py b/oxy/structs/Decoding.py index e461fbe..bf05872 100644 --- a/oxy/structs/Decoding.py +++ b/oxy/structs/Decoding.py @@ -7,17 +7,26 @@ class Decoding: that represents a decoding to be applied to a slice of data. ''' bits: int - final_type: str + entry_type: str repr: str = "*"*42 @dataclass class BigEndian(Decoding): repr: str = "big_endian" + entry_type = "&[u8]" @dataclass class LittleEndian(Decoding): repr: str = "little_endian" + entry_type = "&[u8]" @dataclass class TwosComplement(Decoding): repr: str = "twos_comp" + entry_type = "u32" + + +@dataclass +class Half(Decoding): + repr: str = "half" + entry_type = "u8" diff --git a/oxy/structs/Format.py b/oxy/structs/Format.py new file mode 100644 index 0000000..bb923dd --- /dev/null +++ b/oxy/structs/Format.py @@ -0,0 +1,56 @@ +from dataclasses import dataclass + +@dataclass +class Format: + ''' + Represents a format to be applied to data after decoding. + ''' + repr: str = "" + +@dataclass +class Temperature(Format): + repr: str = "temperature" + +@dataclass +class LowVoltage(Format): + repr: str = "low_voltage" + +@dataclass +class Torque(Format): + repr: str = "torque" + +@dataclass +class HighVoltage(Format): + repr: str = "high_voltage" + +@dataclass +class Current(Format): + repr: str = "current" + +@dataclass +class Angle(Format): + repr: str = "angle" + +@dataclass +class AngularVelocity(Format): + repr: str = "angular_velocity" + +@dataclass +class Frequency(Format): + repr: str = "frequency" + +@dataclass +class Power(Format): + repr: str = "power" + +@dataclass +class Timer(Format): + repr: str = "timer" + +@dataclass +class Flux(Format): + repr: str = "flux" + +@dataclass +class CellVoltage(Format): + repr: str = "cell_voltage" diff --git a/oxy/structs/Messages.py b/oxy/structs/Messages.py new file mode 100644 index 0000000..eeac46e --- /dev/null +++ b/oxy/structs/Messages.py @@ -0,0 +1,9 @@ +from dataclasses import dataclass +from .CANMsg import CANMsg + +@dataclass +class Messages: + ''' + Represents a list of CAN messages. Has a list of CANMsgs. + ''' + msgs: list[CANMsg] diff --git a/oxy/structs/Result.py b/oxy/structs/Result.py new file mode 100644 index 0000000..9b1d317 --- /dev/null +++ b/oxy/structs/Result.py @@ -0,0 +1,13 @@ + +class Result: + """ + This class is used to store the results of the RustSynth.py script. + decode_data is the synthesized Rust code for the decode_data.rs file. + master_mapping is the synthesized Rust code for the master_mapping.rs file. + """ + decode_data: str + master_mapping: str + + def __init__(self, decode_data: str, master_mapping: str): + self.decode_data = decode_data + self.master_mapping = master_mapping diff --git a/oxy/typedpoc.py b/oxy/typedpoc.py index 89abd50..052ab37 100644 --- a/oxy/typedpoc.py +++ b/oxy/typedpoc.py @@ -1,4 +1,21 @@ from YAMLParser import YAMLParser from RustSynth import RustSynth -print(RustSynth().synthesize(YAMLParser().parse(open("mapping.yaml", "r")))) +decode_data = open("../src/decode_data.rs", "w") +master_mapping = open("../src/master_mapping.rs", "w") + +bms_messages = YAMLParser().parse(open("can-messages/bms.yaml", "r")) +mpu_messages = YAMLParser().parse(open("can-messages/mpu.yaml", "r")) +wheel_messages = YAMLParser().parse(open("can-messages/wheel.yaml", "r")) + + +bms_messages.msgs.extend(mpu_messages.msgs) +bms_messages.msgs.extend(wheel_messages.msgs) + +result = RustSynth().parse_messages(bms_messages.msgs) + +decode_data.write(result.decode_data) +decode_data.close() + +master_mapping.write(result.master_mapping) +master_mapping.close() \ No newline at end of file diff --git a/python/__init__.py b/python/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/python/__main__.py b/python/__main__.py deleted file mode 100644 index d65d8e4..0000000 --- a/python/__main__.py +++ /dev/null @@ -1,148 +0,0 @@ -import multiprocessing -from datetime import datetime - -from os import listdir, path -import csv -import sys -from typing import List - -from python.master_mapping import DATA_IDS -from python.thread import thread - -DEFAULT_LOGS_DIRECTORY = "./logs/" -DEFAULT_OUTPUT_PATH = "./output.csv" -PROCESSORS = 8 -PROCESS_CHUNK_SIZE = 85000 # Processes data in chunks, specified by this variable - - -def getLineCount(filepaths: List[str]) -> int: - """ - Gets the total line count of all the files in the list. - - There is no native way to get line counts of files without looping, so - this function gets the total size and estimates the line count based - on a subset of N lines. - """ - if len(filepaths) == 0: - return 0 - - N = 20 - tested_lines = 0 - tested_size = 0 - total_size = sum(path.getsize(fp) for fp in filepaths) - - for fp in filepaths: - with open(fp) as file: - for line in file: - tested_lines += 1 - tested_size += len(line) - if tested_lines >= N: - return int(total_size / (tested_size / tested_lines)) - return int(total_size / (tested_size / tested_lines)) - -def find_time(start, finish): - """ - Prints the difference between the two times provided - Both inputs are lists of strings: - - minutes being the zeroth index of the list - - seconds being the first index of the list - - microseconds being the second index of the list - """ - - minutes = int(finish[0]) - int(start[0]) - seconds = int(finish[1]) - int(start[1]) - microseconds = int(finish[2]) - int(start[2]) - - if microseconds < 0: - seconds -= 1 - microseconds += 1000000 - - if seconds < 0: - minutes -= 1 - seconds += 60 - - print("Time to process (Minutes:Seconds.Microseconds): " + str(minutes) + ":" + str(seconds) + "." + str( - microseconds)) - - -def process_lines(lines, writer): - """ - Processes a chunk of lines and writes the results to the CSV. - """ - with multiprocessing.Pool(PROCESSORS) as p: - out = p.map(thread, lines) - lines.clear() - for data in out: - for sub_data in data: - str_time = sub_data.timestamp.strftime("%Y-%m-%dT%H:%M:%S.%fZ") - writer.writerow([str_time, sub_data.id, DATA_IDS[sub_data.id]["name"], sub_data.value]) - - -if __name__ == "__main__": - """ - Processes the log files in the log folder and puts them in the output.csv file. - Command line args: - - arg 1 = output directory of the csv file - - Must be a directory name - - A file called 'output.csv' is created here - - args 2... = space separated list of file paths to process - - Each path must be a text file - Default file paths are all those in "./logs/" - Default output directory is the current location - """ - - start_time = datetime.now().strftime("%M:%S:%f").split(":") - output_path = "" - paths_to_process = [] - - # If manually specifying the paths - if len(sys.argv) > 1: - # Formats the input file path strings correctly - for i in range(1, len(sys.argv)): - sys.argv[i] = sys.argv[i].replace("\\","/") - - output_path = sys.argv[1] + "/output.csv" - paths_to_process = sys.argv[2:] - else: - output_path = DEFAULT_OUTPUT_PATH - paths_to_process = [DEFAULT_LOGS_DIRECTORY + name for name in listdir(DEFAULT_LOGS_DIRECTORY)] - - line_count = getLineCount(paths_to_process) - print(f"Processing a total of {line_count} lines") - - current_line = 0 - manager = multiprocessing.Manager() - return_dict = manager.dict() - - print(f"Writing to the CSV") - header = ["time", "data_id", "description", "value"] - - with open(output_path, "w", encoding="UTF8", newline="") as f: - writer = csv.writer(f) - writer.writerow(header) - - for fp in paths_to_process: - with open(fp) as file: - line_num = 0 - lines = [] - for line in file: - line_num += 1 - current_line += 1 - if current_line % 5000 == 0: - print(f"Line {line_num}") - - lines.append(line) - try: - if line_num % PROCESS_CHUNK_SIZE == 0: - # When stored data reaches specified amount, use threads to decode data faster - process_lines(lines, writer) - except: - print(f"Error with line {line_num} in file {file.name}") - pass - - if lines: - # Handles leftover stored lines when the loop ends - process_lines(lines, writer) - - finish_time = datetime.now().strftime("%M:%S:%f").split(":") - find_time(start_time, finish_time) diff --git a/python/data.py b/python/data.py deleted file mode 100644 index b04c194..0000000 --- a/python/data.py +++ /dev/null @@ -1,123 +0,0 @@ -from typing import List, Any -from datetime import datetime - - -class Data: - """ - Wrapper class for an individual piece of data. - """ - - def __init__(self, timestamp: datetime, id: int, value: Any): - self.timestamp = timestamp - self.id = id - self.value = value - - def __str__(self): - """ - Overrides the string representation of the class. - """ - return f"ID {self.id} - {self.timestamp} - {self.value}" - - -class ProcessData: - """ - Utility functions to process message data. - """ - - @staticmethod - def groupBytes(data_bytes: List[int], group_length: int = 2) -> List[List[int]]: - """ - Splits the given data bytes into lists of specified length. - """ - return [data_bytes[i : i + group_length] for i in range(0, len(data_bytes), group_length)] - - @staticmethod - def twosComp(val: int, bits: int = 16) -> int: - """ - Computes the twos complement of the given value. - """ - if (val & (1 << (bits - 1))) != 0: - val = val - (1 << bits) - return val - - @staticmethod - def littleEndian(data_bytes: List[int], bits: int = 8) -> int: - """ - Transforms the given data bytes into a value in little endian. - Little Endian byte order stores low order bytes first. - """ - result = 0 - for i in range(len(data_bytes)): - result |= data_bytes[i] << (bits * i) - return result - - @staticmethod - def bigEndian(data_bytes: List[int], bits: int = 8) -> int: - """ - Transforms the given data bytes into a value in big endian. - Big Endian byte order stores low order bytes last. - """ - result = 0 - for i in range(len(data_bytes)): - result |= data_bytes[i] << (bits * (len(data_bytes) - i - 1)) - return result - - @staticmethod - def defaultDecode(byte_vals: List[int]) -> List[int]: - """ - Default decode structure seen by a majority of the messages. - """ - grouped_vals = ProcessData.groupBytes(byte_vals) - parsed_vals = [ProcessData.littleEndian(val) for val in grouped_vals] - decoded_vals = [ProcessData.twosComp(val) for val in parsed_vals] - return decoded_vals - - -class FormatData: - """ - Utility functions to scale data values of a specific type. - """ - - @staticmethod - def temperature(value): - return value / 10 - - @staticmethod - def lowVoltage(value): - return value / 100 - - @staticmethod - def torque(value): - return value / 10 - - @staticmethod - def highVoltage(value): - return value / 10 - - @staticmethod - def current(value): - return value / 10 - - @staticmethod - def angle(value): - return value / 10 - - @staticmethod - def angularVelocity(value): - return -value - - @staticmethod - def frequency(value): - return value / 10 - - @staticmethod - def power(value): - return value / 10 - - @staticmethod - def timer(value): - return value * 0.003 - - @staticmethod - def flux(value): - return value / 1000 diff --git a/python/decode_data.py b/python/decode_data.py deleted file mode 100644 index 29aba85..0000000 --- a/python/decode_data.py +++ /dev/null @@ -1,338 +0,0 @@ -""" -This file specifies methods to decode messages into the many pieces of data they contain. -""" - -from typing import Any, Dict, List -import numpy as np -from data import ( - ProcessData as pd, - FormatData as fd, -) - - -def decodeMock(data: List[int]) -> Dict[int, Any]: - return { - 0: 0 - } - -def decodeAccumulatorStatus(data: List[int]) -> Dict[int, Any]: - return { - 1: pd.bigEndian(data[0:2]), - 2: pd.twosComp(pd.bigEndian(data[2:4])) / 10, - 3: pd.bigEndian(data[4:6]), - 4: data[6], - 5: data[7] - } - -def decodeBMSStatus(data: List[int]) -> Dict[int, Any]: - return { - 106: data[0], - 107: pd.littleEndian(data[1:5]), - 10: pd.twosComp(data[5], 8), - 11: pd.twosComp(data[6], 8) - } - -def decode3(data: List[int]) -> Dict[int, Any]: - return { - 12: data[0] - } - -def decodeCellVoltages(data: List[int]) -> Dict[int, Any]: - high_cell_volt_chip_number = (data[2] >> 4) & 15 - high_cell_volt_cell_number = (data[2] >> 0) & 15 - low_cell_volt_chip_number = (data[5] >> 4) & 15 - low_cell_volt_cell_number = (data[5] >> 0) & 15 - return { - 13: pd.littleEndian(data[0:2]), - 121: high_cell_volt_chip_number, - 122: high_cell_volt_cell_number, - 15: pd.littleEndian(data[3:5]), - 123: low_cell_volt_chip_number, - 124: low_cell_volt_cell_number, - 17: pd.littleEndian(data[6:8]) - } - -def decode5(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - final_data = [fd.temperature(d) for d in decoded_data] - return { - 18: final_data[0], - 19: final_data[1], - 20: final_data[2], - 21: final_data[3] - } - -def decode6(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - final_data = [fd.temperature(d) for d in decoded_data] - return { - 22: final_data[0], - 23: final_data[1], - 24: final_data[2], - 25: final_data[3] - } - -def decode7(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - final_data = [fd.temperature(d) for d in decoded_data[:3]] - return { - 26: final_data[0], - 27: final_data[1], - 28: final_data[2], - 29: fd.torque(decoded_data[3]) - } - -# TODO: Fill this method out (complicated with bit shifts) -def decode8(data: List[int]) -> Dict[int, Any]: - return { - 30: 0, - 31: 0, - 32: 0, - 33: 0, - 34: 0, - 35: 0 - } - -def decode9(data: List[int]) -> Dict[int, Any]: - return { - 36: data[0], - 37: data[1], - 38: data[2], - 39: data[3], - 40: data[4], - 41: data[5], - 42: data[6], - 43: data[7] - } - -def decode10(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - motor_speed = fd.angularVelocity(decoded_data[1]) - vehicle_speed = motor_speed * 0.013048225 - return { - 44: fd.angle(decoded_data[0]), - 45: motor_speed, - 46: fd.frequency(decoded_data[2]), - 47: fd.angle(decoded_data[3]), - 101: vehicle_speed - } - -def decode11(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - final_data = [fd.current(d) for d in decoded_data] - return { - 48: final_data[0], - 49: final_data[1], - 50: final_data[2], - 51: final_data[3] - } - -def decode12(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - final_data = [fd.highVoltage(d) for d in decoded_data] - return { - 52: final_data[0], - 53: final_data[1], - 54: final_data[2], - 55: final_data[3] - } - -def decode13(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - return { - 56: fd.flux(decoded_data[0]), - 57: fd.flux(decoded_data[1]), - 58: fd.current(decoded_data[2]), - 59: fd.current(decoded_data[3]) - } - -def decode14(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - final_data = [fd.lowVoltage(d) for d in decoded_data] - return { - 60: final_data[0], - 61: final_data[1], - 62: final_data[2], - 63: final_data[3] - } - -def decode15(data: List[int]) -> Dict[int, Any]: - return { - 64: pd.littleEndian(data[0:2]), - 65: data[2], - 66: data[3], - 67: data[4] & 1, - 68: (data[4] >> 5) & 7, - 69: data[5] & 1, - 70: data[6] & 1, - 71: (data[6] >> 7) & 1, - 72: data[7] & 1, - 73: (data[7] >> 1) & 1, - 74: (data[7] >> 2) & 1 - } - -def decode16(data: List[int]) -> Dict[int, Any]: - grouped_data = [pd.littleEndian(d) for d in pd.groupBytes(data)] - return { - 75: grouped_data[0], - 76: grouped_data[1], - 77: grouped_data[2], - 78: grouped_data[3] - } - -def decode17(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data[:4]) - timer_data = pd.littleEndian(data[4:]) - return { - 79: fd.torque(decoded_data[0]), - 80: fd.torque(decoded_data[1]), - 81: fd.timer(timer_data) - } - -def decode18(data: List[int]) -> Dict[int, Any]: - decoded_data = pd.defaultDecode(data) - return { - 82: fd.torque(decoded_data[0]), - 83: fd.angularVelocity(decoded_data[1]), - 84: data[4], - 85: data[5] & 1, - 86: (data[5] >> 1) & 1, - 87: (data[5] >> 2) & 1, - 88: fd.torque(decoded_data[3]) - } - -def decode19(data: List[int]) -> Dict[int, Any]: - return { - 89: pd.littleEndian(data[0:2]), - 90: pd.littleEndian(data[2:4]) - } - -def decodeAcceleromterData(data: List[int]) -> Dict[int, Any]: - # The accelerometer is mounted at a 70 degree angle to the horizontal, we need to rotate the data to account for this - decoded_data = pd.defaultDecode(data) - converted_data = [val*0.0029 for val in decoded_data] - matrixData = np.matrix.transpose(np.mat(converted_data[0:3])) - transformMatrix = np.mat([[1, 0, 0], [0, np.cos(np.deg2rad(70)), np.sin(np.deg2rad(70))], [0, -np.sin(np.deg2rad(70)), np.cos(np.deg2rad(70))]]) - transformedData = transformMatrix * matrixData - return { - 91: float(transformedData[0][0]), - 92: float(transformedData[1][0]), - 93: float(transformedData[2][0]) - } - -def decode21(data: List[int]) -> Dict[int, Any]: - temp = pd.littleEndian(data[0:2]) - humid = pd.littleEndian(data[2:4]) - tempF = -49 + (315 * temp / 65535.0) - tempC = -45 + (175 * temp / 65535.0) - relHumid = 100 * humid / 65535.0 - return { - 94: tempC, - 95: tempF, - 96: relHumid - } - -def decode22(data: List[int]) -> Dict[int, Any]: - cell_id = data[0] - instant_voltage = pd.bigEndian(data[1:3]) - internal_resistance = pd.bigEndian(data[3:5]) & 32767 # clear last bit - shunted = (data[3] >> 7) & 1 # get last bit - open_voltage = pd.bigEndian(data[5:7]) - return { - 97: f"{cell_id} {instant_voltage} {open_voltage} {internal_resistance} {shunted}" - } - -def decode29(data: List[int]) -> Dict[int, Any]: - glv_current = pd.twosComp(pd.littleEndian(data), 32) - return { - 98: glv_current / 1000000.0 # undo 10^6 scale factor from car - } - -def decode34(data: List[int]) -> Dict[int, Any]: - voltage1 = pd.twosComp(pd.littleEndian(data[0:4]), 32) - voltage2 = pd.twosComp(pd.littleEndian(data[4:]), 32) - return { - 99: voltage1 / 1000000.0, # undo 10^6 scale factor from car - 100: voltage2 / 1000000.0 - } - -def decode35(data: List[int]) -> Dict[int, Any]: - return { - 102: pd.bigEndian(data[0:2]), - 103: pd.bigEndian(data[2:4]), - 104: data[4] - } - -def decodeMPUDashboardInfo(data: List[int]) -> Dict[int, Any]: - return { - 105: data[0], - 130: data[1], - 131: data[2], - 132: data[3], - 133: data[4], - } - - -def decodeGPS1(data: List[int]) -> Dict[int, Any]: - return { - 108: pd.twosComp(pd.littleEndian(data[0:4]), 32) / 10000000, # Longitude in degrees * 1e-7 (Get rid of multiplier) - 109: pd.twosComp(pd.littleEndian(data[4:8]), 32) / 10000000 # Latitude in degrees * 1e-7 (Get rid of multiplier) - } - -def decodeGPS2(data: List[int]) -> Dict[int, Any]: - return { - 110: pd.twosComp(pd.littleEndian(data[0:4]), 32), - 111: pd.twosComp(pd.littleEndian(data[4:8]), 32) / 1000 # Altitude in mm (transform to m) - } - -def decodeGPS3(data: List[int]) -> Dict[int, Any]: - return { - 112: pd.twosComp(pd.littleEndian(data[0:4]), 32) / 1000, # Ground speed in mm/sec (transform to m/s) - 113: pd.twosComp(pd.littleEndian(data[4:8]), 32) / 100000 # Heading in degrees * 1e-5 (Get rid of multiplier) - } - -def decodeCellTemps(data: List[int]) -> Dict[int, Any]: - high_cell_temp_chip_number = (data[2] >> 4) & 15 - high_cell_temp_cell_number = (data[2] >> 0) & 15 - low_cell_temp_chip_number = (data[5] >> 4) & 15 - low_cell_temp_cell_number = (data[5] >> 0) & 15 - - return { - 114: pd.twosComp(pd.littleEndian(data[0:2]), 16), - 115: high_cell_temp_chip_number, - 116: high_cell_temp_cell_number, - 117: pd.twosComp(pd.littleEndian(data[3:5]), 16), - 118: low_cell_temp_chip_number, - 119: low_cell_temp_cell_number, - 120: pd.twosComp(pd.littleEndian(data[6:8]), 16), - } - -def decodeSegmentTemps(data: List[int]) -> Dict[int, Any]: - return { - 125: pd.twosComp(data[0], 8), - 126: pd.twosComp(data[1], 8), - 127: pd.twosComp(data[2], 8), - 128: pd.twosComp(data[3], 8), - } - -def decodeLoggingStatus(data: List[int]) -> Dict[int, Any]: - return { - 129: data[0] - } - -def decodeLVBattery1(data: List[int]) -> Dict[int, Any]: - return { - 134: pd.littleEndian(data[0:2]), - 135: data[2], - 136: pd.littleEndian(data[3:5]), - 137: data[5], - 138: pd.twosComp(pd.littleEndian(data[6:8]), 16) - } - -def decodeLVBattery2(data: List[int]) -> Dict[int, Any]: - return { - 139: pd.twosComp(pd.littleEndian(data[0:2]), 16) * (192.264 / 1000000) * 4, - 140: pd.twosComp(pd.littleEndian(data[2:4]), 16) * (1.648 / 1000), - 141: pd.twosComp(pd.littleEndian(data[4:6]), 16) * (1.648 / 1000), - 142: pd.twosComp(pd.littleEndian(data[6:8]), 16) * (1.46487 / 1000000) / (0.5 / 1000) - } \ No newline at end of file diff --git a/python/decode_files.py b/python/decode_files.py deleted file mode 100644 index 8205b8c..0000000 --- a/python/decode_files.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -This file specifies methods to decode message fields (timestamp, id, data bytes) from -a line in a log file. -""" - -from enum import Enum -from datetime import datetime - -from python.message import Message - - -class LogFormat(Enum): - TEXTUAL1 = 1 - TEXTUAL1_LEGACY = 2 - TEXTUAL2 = 3 - BINARY = 4 - - -def processLine(line: str, format: LogFormat) -> Message: - """ - Processes a line of textual data according to a given format. - """ - if format == LogFormat.TEXTUAL1: - return _processTextual1(line) - if format == LogFormat.TEXTUAL1_LEGACY: - return _processTextual1Legacy(line) - elif format == LogFormat.TEXTUAL2: - return _processTextual2(line) - elif format == LogFormat.BINARY: - return _processBinary(line) - else: - raise ValueError("Invalid file format.") - - -def _processTextual1(line: str) -> Message: - """ - Processes a line of data in the format "Timestamp id length [data1,data2,...]" - Example line format: 1679511802367 514 8 [54,0,10,0,0,0,0,0] - """ - fields = line.strip().split(" ") - timestamp = datetime.fromtimestamp(float(fields[0]) / 1000) - id = int(fields[1]) - length = int(fields[2]) - data = fields[3][1:-1].split(",") # remove commas and brackets at start and end - int_data = [int(x) for x in data] - return Message(timestamp, id, int_data) - - -def _processTextual1Legacy(line: str) -> Message: - """ - Processes a line of data in the format "Timestamp id length [data1,data2,...]" - Example line format: 2021-01-01T00:00:00.003Z 514 8 [54,0,10,0,0,0,0,0] - """ - fields = line.strip().split(" ") - timestamp = datetime.strptime(fields[0], "%Y-%m-%dT%H:%M:%S.%fZ") - id = int(fields[1]) - length = int(fields[2]) - data = fields[3][1:-1].split(",") # remove commas and brackets at start and end - int_data = [int(x) for x in data] - return Message(timestamp, id, int_data) - - -def _processTextual2(line: str) -> Message: - """ - Processes a line of data in the format "Timestamp id length data1 data2 ..." - Example line format: 1659901910.121 514 8 54 0 10 0 0 0 0 0 - """ - fields = line.strip().split(" ") - timestamp = datetime.fromtimestamp(float(fields[0])) - id = int(fields[1]) - length = int(fields[2]) - data = [int(x) for x in fields[3:3+length]] - return Message(timestamp, id, data) - - -def _processBinary(line: str) -> Message: - raise RuntimeError("Binary files not currently supported.") diff --git a/python/decode_statuses.py b/python/decode_statuses.py deleted file mode 100644 index 7879511..0000000 --- a/python/decode_statuses.py +++ /dev/null @@ -1,188 +0,0 @@ -from typing import Any - -from python.data import Data - - -# Mapping from data IDs to the status bits they encode -# Each data ID contains a dict with keys that are bit names and values that are the indexes -STATUS_MAP = { - # 6: { # Failsafe Statuses - # }, - # 7: { # DTC Status 1 - # }, - # 8: { # DTC Status 2 - # }, - # 9: { # Current Limits Status - # }, - # 12: { # MPE State - # }, - 64: { # VSM State - "VSM Start State": 0, - "Pre-charge Init State": 1, - "Pre-charge Active State": 2, - "Pre-charge Complete State": 3, - "VSM Wait State": 4, - "VSM Ready State": 5, - "Motor Running State": 6, - "Blink Fault Code State": 7 - }, - 65: { # Inverter State - "Power on State": 0, - "Stop State": 1, - "Open Loop State": 2, - "Closed Loop State": 3, - "Wait State": 4, - "Idle Run State": 8, - "Idle Stop State": 9 - }, - 66: { # Relay State - "Relay 1 Status": 0, - "Relay 2 Status": 1, - "Relay 3 Status": 2, - "Relay 4 Status": 3, - "Relay 5 Status": 4, - "Relay 6 Status": 5, - }, - 67: { # Inverter Run Mode - "Inverter Run Mode": 0 - }, - 69: { # Inverter Command Mode - "Inverter Command Mode": 0 - }, - 70: { # Inverter Enable State - "Inverter Enable State": 0 - }, - 71: { # Inverter Enable Lockout - "Inverter Enable Lockout": 0 - }, - 72: { # Direction Command - "Direction Command": 0 - }, - 73: { # BMS Active - "BMS Active": 0 - }, - 74: { # BMS Limiting Torque - "BMS Limiting Torque": 0 - }, - 75: { # POST Fault Lo - "Hardware Gate/Desaturation Fault": 0, - "HW Over-current Fault": 1, - "Accelerator Shorted": 2, - "Accelerator Open": 3, - "Current Sensor Low": 4, - "Current Sensor High": 5, - "Module Temperature Low": 6, - "Module Temperature High": 7, - "Control PCB Temperature Low": 8, - "Control PCB Temperature High": 9, - "Gate Drive PCB Temperature Low": 10, - "Gate Drive PCB Temperature High": 11, - "5V Sense Voltage Low": 12, - "5V Sense Voltage High": 13, - "12V Sense Voltage Low": 14, - "12V Sense Voltage High": 15 - }, - 76: { # POST Fault Hi - "2.5V Sense Voltage Low": 0, - "2.5V Sense Voltage High": 1, - "1.5V Sense Voltage Low": 2, - "1.5V Sense Voltage High": 3, - "DC Bus Voltage High": 4, - "DC Bus Voltage Low": 5, - "Pre-charge Timeout": 6, - "Pre-charge Voltage Failure": 7, - "Brake Shorted": 14, - "Brake Open": 15 - }, - 77: { # Run Fault Lo - "Motor Over-speed Fault": 0, - "Over-current Fault": 1, - "Over-voltage Fault": 2, - "Inverter Over-temperature Fault": 3, - "Accelerator Input Shorted Fault": 4, - "Accelerator Input Open Fault": 5, - "Direction Command Fault": 6, - "Inverter Response Time-out Fault": 7, - "Hardware Gate/Desaturation Fault": 8, - "Hardware Over-current Fault": 9, - "Under-voltage Fault": 10, - "CAN Command Message Lost Fault": 11, - "Motor Over-temperature Fault": 12 - }, - 78: { # Run Fault Hi - "Brake Input Shorted Fault": 0, - "Brake Input Open Fault": 1, - "Module A Over-temperature Fault": 2, - "Module B Over temperature Fault": 3, - "Module C Over-temperature Fault": 4, - "PCB Over-temperature Fault": 5, - "Gate Drive Board 1 Over-temperature Fault": 6, - "Gate Drive Board 2 Over-temperature Fault": 7, - "Gate Drive Board 3 Over-temperature Fault": 8, - "Current Sensor Fault": 9, - "Hardware Over-Voltage Fault": 11, - "Resolver Not Connected": 14, - "Inverter Discharge Active": 15 - }, - 84: { # Direction Command - "Direction Command": 0 - }, - 85: { # Inverter Enable - "Inverter Enable": 0 - }, - 86: { # Inverter Discharge - "Inverter Discharge": 0 - }, - 87: { # Speed Mode Enable - "Speed Mode Enable": 0 - }, - # 97: { # Cell Voltage Info - # } - 107: { # BMS Faults - "Cells Not Balancing": 0, - "Cell Voltage Too High": 1, - "Cell Voltage Too Low": 2, - "Pack Too High": 3, - "Open Wiring Fault": 4, - "Internal Software Fault": 5, - "Internal Thermal Fault": 6, - "Internal Cell Comm Fault": 7, - "Current Sensor Fault": 8, - "Charge Reading Mismatch": 9, - "Low Cell Voltage": 10, - "Weak Pack Fault": 11, - "External Can Fault": 12, - "Discharge Limit Enforcement Fault": 13, - "Charger Safety Relay": 14, - "Battery Can Fault": 15, - "Charger Can Fault": 16, - "Charge Limit Enforcement Fault": 17 - }, -} - - -def getStatus(data_id: int, data_value: Any, name: str) -> int: - """ - Gets the specified status of the given data piece. Returns a bit value. - """ - if data_id not in STATUS_MAP: - raise KeyError("Data ID has no associated status mapping") - bitmap = STATUS_MAP[data_id] - - if name not in bitmap: - raise KeyError("Status name could not be found in the given data point") - index = bitmap[name] - - return (data_value >> index) & 1 - - -def getStatuses(data_id: int, data_value: Any) -> Any: - """ - Gets all the statuses for the given data piece. - """ - if data_id not in STATUS_MAP: - raise KeyError("Data ID has no associated status mapping") - bitmap = STATUS_MAP[data_id] - - # Convert each dict value to the bit value at the index - return {name:(data_value >> index) & 1 for (name, index) in bitmap.items()} \ No newline at end of file diff --git a/python/master_mapping.py b/python/master_mapping.py deleted file mode 100644 index 0090e01..0000000 --- a/python/master_mapping.py +++ /dev/null @@ -1,763 +0,0 @@ -""" -This file specifes the CAN and data ID mappings. IDS: - - External Message ID (actual CAN message id) - - Data ID (id for individual data values contained in the messages) -""" - -from python.decode_data import * - -# Mapping from external message ID to decoding information -MESSAGE_IDS = { - 1: { - "description": "accumulator status", - "decoder": decodeAccumulatorStatus - }, - 2: { - "description": "BMS status", - "decoder": decodeBMSStatus - }, - 3: { - "description": "shutdown control", - "decoder": decode3 - }, - 4: { - "description": "cell data", - "decoder": decodeCellVoltages - }, - 160: { - "description": "temperatures (igbt modules, gate driver board)", - "decoder": decode5 - }, - 161: { - "description": "temperatures (control board)", - "decoder": decode6, - }, - 162: { - "description": "temperatures (motor)", - "decoder": decode7, - }, - 163: { - "description": "analog input voltages", - "decoder": decode8, - }, - 164: { - "description": "digital input status", - "decoder": decode9, - }, - 165: { - "description": "motor position information", - "decoder": decode10, - }, - 166: { - "description": "current information", - "decoder": decode11, - }, - 167: { - "description": "voltage information", - "decoder": decode12, - }, - 168: { - "description": "flux information", - "decoder": decode13, - }, - 169: { - "description": "internal voltages", - "decoder": decode14, - }, - 170: { - "description": "internal states", - "decoder": decode15, - }, - 171: { - "description": "fault codes", - "decoder": decode16, - }, - 172: { - "description": "torque and timer", - "decoder": decode17, - }, - 192: { - "description": "commanded data", - "decoder": decode18, - }, - 514: { - "description": "current limits", - "decoder": decode19, - }, - 768: { - "description": "nerduino accelerometer", - "decoder": decodeAcceleromterData, - }, - 769: { - "description": "nerduino humidity", - "decoder": decode21, - }, - 7: { - "description": "cell voltages", - "decoder": decode22, - }, - 193: { - "description": "unknown 1", - "decoder": decodeMock, - }, - 6: { - "description": "unknown 2", - "decoder": decodeMock, - }, - 194: { - "description": "unknown 3", - "decoder": decodeMock, - }, - 1744: { - "description": "unknown 4", - "decoder": decodeMock, - }, - 1745: { - "description": "unknown 5", - "decoder": decodeMock, - }, - 175: { - "description": "unknown 6", - "decoder": decodeMock, - }, - 770: { - "description": "GLV current", - "decoder": decode29, - }, - 2015: { - "description": "unknown 2015", - "decoder": decodeMock, - }, - 2027: { - "description": "unknown 2027", - "decoder": decodeMock, - }, - 2019: { - "description": "unknown 2019", - "decoder": decodeMock, - }, - 771: { - "description": "strain gauge", - "decoder": decode34, - }, - 1024: { - "description": "wheel state", - "decoder": decode35, - }, - 10: { - "description": "MPU States", - "decoder": decodeMPUDashboardInfo, - }, - 772: { - "description": "GPS Data 1", - "decoder": decodeGPS1, - }, - 773: { - "description": "GPS Data 2", - "decoder": decodeGPS2, - }, - 774: { - "description": "GPS Data 3", - "decoder": decodeGPS3, - }, - 8: { - "description": "Cell Temperatures", - "decoder": decodeCellTemps, - }, - 9: { - "description": "Segment Temperatures", - "decoder": decodeSegmentTemps, - }, - 775: { - "description": "Logging Status", - "decoder": decodeLoggingStatus, - }, - 177: { - "description": "unknown 177", - "decoder": decodeMock - }, - 1025: { - "description": "LV Battery 1", - "decoder": decodeLVBattery1, - }, - 1026: { - "description": "LV Battery 2", - "decoder": decodeLVBattery2, - } -} - -# Mapping from data ids to their description (potentially add format information) -DATA_IDS = { - 0: { - "name": "Mock Data", - "units": "", - }, - 1: { - "name": "Pack Inst Voltage", - "units": "V", - }, - 2: { - "name": "Pack Current", - "units": "A", - }, - 3: { - "name": "Pack Amphours", - "units": "Ah", - }, - 4: { - "name": "Pack SOC", - "units": "%", - }, - 5: { - "name": "Pack Health", - "units": "%", - }, - 6: { - "name": "Failsafe Statuses", - "units": "HEX", - }, - 7: { - "name": "DTC Status 1", - "units": "HEX", - }, - 8: { - "name": "DTC Status 2", - "units": "HEX", - }, - 9: { - "name": "Current Limits Status", - "units": "", - }, - 10: { - "name": "Average Temp", - "units": "C", - }, - 11: { - "name": "Internal Temp", - "units": "C", - }, - 12: { - "name": "MPE State", - "units": "BIN", - }, - 13: { - "name": "High Cell Voltage", - "units": "V", - }, - 14: { - "name": "High Cell Voltage ID", - "units": "", - }, - 15: { - "name": "Low Cell Voltage", - "units": "V", - }, - 16: { - "name": "Low Cell Voltage ID", - "units": "", - }, - 17: { - "name": "Average Cell Voltage", - "units": "V", - }, - 18: { - "name": "Module A Temperature", - "units": "C", - }, - 19: { - "name": "Module B Temperature", - "units": "C", - }, - 20: { - "name": "Module C Temperature", - "units": "C", - }, - 21: { - "name": "Gate Driver Board Temperature", - "units": "C", - }, - 22: { - "name": "Control Board Temperature", - "units": "C", - }, - 23: { - "name": "RTD #1 Temperature", - "units": "C", - }, - 24: { - "name": "RTD #2 Temperature", - "units": "C", - }, - 25: { - "name": "RTD #3 Temperature", - "units": "C", - }, - 26: { - "name": "RTD #4 Temperature", - "units": "C", - }, - 27: { - "name": "RTD #5 Temperature", - "units": "C", - }, - 28: { - "name": "Motor Temperature", - "units": "C", - }, - 29: { - "name": "Torque Shudder", - "units": "N-m", - }, - 30: { - "name": "Analog Input 1", - "units": "V", - }, - 31: { - "name": "Analog Input 2", - "units": "V", - }, - 32: { - "name": "Analog Input 3", - "units": "V", - }, - 33: { - "name": "Analog Input 4", - "units": "V", - }, - 34: { - "name": "Analog Input 5", - "units": "V", - }, - 35: { - "name": "Analog Input 6", - "units": "V", - }, - 36: { - "name": "Digital Input 1", - "units": "BIN", - }, - 37: { - "name": "Digital Input 2", - "units": "BIN", - }, - 38: { - "name": "Digital Input 3", - "units": "BIN", - }, - 39: { - "name": "Digital Input 4", - "units": "BIN", - }, - 40: { - "name": "Digital Input 5", - "units": "BIN", - }, - 41: { - "name": "Digital Input 6", - "units": "BIN", - }, - 42: { - "name": "Digital Input 7", - "units": "BIN", - }, - 43: { - "name": "Digital Input 8", - "units": "BIN", - }, - 44: { - "name": "Motor Angle (Electrical)", - "units": "Deg", - }, - 45: { - "name": "Motor Speed", - "units": "RPM", - }, - 46: { - "name": "Electrical Output Frequency", - "units": "Hz", - }, - 47: { - "name": "Delta Resolver Filtered", - "units": "Deg", - }, - 48: { - "name": "Phase A Current", - "units": "A", - }, - 49: { - "name": "Phase B Current", - "units": "A", - }, - 50: { - "name": "Phase C Current", - "units": "A", - }, - 51: { - "name": "DC Bus Current", - "units": "A", - }, - 52: { - "name": "DC Bus Voltage", - "units": "V", - }, - 53: { - "name": "Output Voltage", - "units": "V", - }, - 54: { - "name": "VAB_Vd Voltage", - "units": "V", - }, - 55: { - "name": "VBC_Vq Voltage", - "units": "V", - }, - 56: { - "name": "Flux Command", - "units": "Wb", - }, - 57: { - "name": "Flux Feedback", - "units": "Wb", - }, - 58: { - "name": "Id Feedback", - "units": "A", - }, - 59: { - "name": "Iq Feedback", - "units": "A", - }, - 60: { - "name": "1.5V Reference Voltage", - "units": "V", - }, - 61: { - "name": "2.5V Reference Voltage", - "units": "V", - }, - 62: { - "name": "5.0V Reference Voltage", - "units": "V", - }, - 63: { - "name": "12V System Voltage", - "units": "V", - }, - 64: { - "name": "VSM State", - "units": "", - }, - 65: { - "name": "Inverter State", - "units": "", - }, - 66: { - "name": "Relay State", - "units": "BIN", - }, - 67: { - "name": "Inverter Run Mode", - "units": "BIN", - }, - 68: { - "name": "Inverter Active Discharge State", - "units": "BIN", - }, - 69: { - "name": "Inverter Command Mode", - "units": "BIN", - }, - 70: { - "name": "Inverter Enable State", - "units": "BIN", - }, - 71: { - "name": "Inverter Enable Lockout", - "units": "BIN", - }, - 72: { - "name": "Direction Command", - "units": "BIN" - }, - 73: { - "name": "BMS Active", - "units": "BIN", - }, - 74: { - "name": "BMS Limiting Torque", - "units": "BIN", - }, - 75: { - "name": "POST Fault Lo", - "units": "BIN", - }, - 76: { - "name": "POST Fault Hi", - "units": "BIN", - }, - 77: { - "name": "Run Fault Lo", - "units": "BIN", - }, - 78: { - "name": "Run Fault Hi", - "units": "BIN", - }, - 79: { - "name": "Commanded Torque", - "units": "N-m", - }, - 80: { - "name": "Torque Feedback", - "units": "N-m", - }, - 81: { - "name": "Power on Timer", - "units": "s", - }, - 82: { - "name": "Torque Command", - "units": "N-m", - }, - 83: { - "name": "Speed Command", - "units": "RPM", - }, - 84: { - "name": "Direction Command", - "units": "BIN", - }, - 85: { - "name": "Inverter Enable", - "units": "BIN", - }, - 86: { - "name": "Inverter Discharge", - "units": "BIN", - }, - 87: { - "name": "Speed Mode Enable", - "units": "BIN", - }, - 88: { - "name": "Commanded Torque Limit", - "units": "N-m", - }, - 89: { - "name": "Pack DCL", - "units": "A", - }, - 90: { - "name": "Pack CCL", - "units": "A", - }, - 91: { - "name": "TCU X-Axis Acceleration", - "units": "g", - }, - 92: { - "name": "TCU Y-Axis Acceleration", - "units": "g", - }, - 93: { - "name": "TCU Z-Axis Acceleration", - "units": "g", - }, - 94: { - "name": "TCU Temperature C", - "units": "C", - }, - 95: { - "name": "TCU Temperature F", - "units": "F", - }, - 96: { - "name": "Relative Humidity", - "units": "%", - }, - 97: { - "name": "Cell Voltage Info", - "units": "", - }, - 98: { - "name": "GLV Current", - "units": "A", - }, - 99: { - "name": "Strain Gauge Voltage 1", - "units": "V", - }, - 100: { - "name": "Strain Gauge Voltage 2", - "units": "V", - }, - 101: { - "name": "Vehicle Speed", - "units": "MPH", - }, - 102: { - "name": "Wheel Knob 1", - "units": "", - }, - 103: { - "name": "Wheel Knob 2", - "units": "", - }, - 104: { - "name": "Wheel Buttons", - "units": "BIN", - }, - 105: { - "name": "MPU Mode State", - "units": "" - }, - 106: { - "name": "BMS State", - "units": "" - }, - 107: { - "name": "BMS Faults", - "units": "HEX" - }, - 108: { - "name": "Latitude", - "units": "Deg" - }, - 109: { - "name": "Longitude", - "units": "Deg" - }, - 110: { - "name": "GPS Fix Status", - "units": "" - }, - 111: { - "name": "Altitude", - "units": "m" - }, - 112: { - "name": "Ground Speed", - "units": "m/s" - }, - 113: { - "name": "Heading Direction", - "units": "Deg" - }, - 114: { - "name": "High Cell Temp", - "units": "C" - }, - 115: { - "name": "High Cell Temp Chip Number", - "units": "" - }, - 116: { - "name": "High Cell Temp Cell Number", - "units": "" - }, - 117: { - "name": "Low Cell Temp", - "units": "C" - }, - 118: { - "name": "Low Cell Temp Chip Number", - "units": "" - }, - 119: { - "name": "Low Cell Temp Cell Number", - "units": "" - }, - 120: { - "name": "Average Cell Temp", - "units": "C" - }, - 121: { - "name": "High Cell Voltage Chip Number", - "units": "" - }, - 122: { - "name": "High Cell Voltage Cell Number", - "units": "" - }, - 123: { - "name": "Low Cell Voltage Chip Number", - "units": "" - }, - 124: { - "name": "Low Cell Voltage Cell Number", - "units": "" - }, - 125: { - "name": "Segment 1 Average Temperature", - "units": "C" - }, - 126: { - "name": "Segment 2 Average Temperature", - "units": "C" - }, - 127: { - "name": "Segment 3 Average Temperature", - "units": "C" - }, - 128: { - "name": "Segment 4 Average Temperature", - "units": "C" - }, - 129: { - "name": "Logging Status", - "units": "" - }, - 130: { - "name": "Accumulator Fan Percentage", - "units": "%" - }, - 131: { - "name": "Motor Fan Percentage", - "units": "%" - }, - 132: { - "name": "Torque Limit Percentage", - "units": "%" - }, - 133: { - "name": "Regen Strength Value", - "units": "" - }, - 134: { - "name": "Charger State", - "units": "" - }, - 135: { - "name": "Measurement System Valid", - "units": "" - }, - 136: { - "name": "System Status", - "units": "" - }, - 137: { - "name": "Charge Status", - "units": "" - }, - 138: { - "name": "ibat", - "units": "A" - }, - 139: { - "name": "vbat", - "units": "V" - }, - 140: { - "name": "vin", - "units": "V" - }, - 141: { - "name": "vsys", - "units": "V" - }, - 142: { - "name": "iin", - "units": "A" - } -} diff --git a/python/message.py b/python/message.py deleted file mode 100644 index 081d8e8..0000000 --- a/python/message.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import List, Dict, Any -from datetime import datetime - -from python.data import Data -from python.master_mapping import MESSAGE_IDS - - -class MessageFormatException(Exception): - """ - A class to represent exceptions related to invalid message formats. - """ - - def __init__(self, message: str): - self.message = message - - -class Message: - """ - Wrapper class for an individual message. - """ - - def __init__(self, timestamp: datetime, id: int, data: List[int]): - self.timestamp = timestamp - self.id = id - self.data = data - - def __str__(self): - """ - Overrides the string representation of the class. - """ - return f"[{self.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ')}] {self.id} - {self.data}" - - def decode(self) -> List[Data]: - """ - Processes this message's data into a list of data points. - """ - return self.decodeMessage(self.timestamp, self.id, self.data) - - @staticmethod - def decodeMessage(timestamp: datetime, id: int, data: List[int]) -> List[Data]: - """ - Decodes the given message fields into their data points - """ - try: - decoded_data: Dict[int, Any] = MESSAGE_IDS[id]["decoder"](data) - except: - raise MessageFormatException(f"Invalid data format for can id {id}") - - return [Data(timestamp, data_id, decoded_data[data_id]) for data_id in decoded_data] \ No newline at end of file diff --git a/python/thread.py b/python/thread.py deleted file mode 100644 index c0d61aa..0000000 --- a/python/thread.py +++ /dev/null @@ -1,9 +0,0 @@ -from python.decode_files import LogFormat, processLine -from python.message import Message - -FORMAT = LogFormat.TEXTUAL1 - - -def thread(line): - message: Message = processLine(line, FORMAT) - return message.decode() \ No newline at end of file diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..41a4c85 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,16 @@ +use crate::data::Data; + +/** + * Cummulative trait for structs that are able to connect to a server and publish data to it. + */ +pub trait Client { + /** + * Connects to the server at the given path. + */ + fn connect(&mut self, path: &str); + + /** + * Publishes the given data to the server. + */ + fn publish(&mut self, data: &Data); +} diff --git a/src/data.rs b/src/data.rs index e1b83cc..058b524 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,142 +1,134 @@ -use chrono::prelude::*; use std::fmt; +/** + * Wrapper Class for Data coming off the car + */ pub struct Data { - // Wrapper class for an individual piece of data. - pub(crate) timestamp: DateTime, - pub id: u8, pub value: f32, + pub topic: String, + pub unit: String, } +/** + * Implementation for the format of the data for debugging purposes + */ impl fmt::Display for Data { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Overrides the string representation of the class. - write!(f, "ID {} - {} - {}", self.id, self.timestamp, self.value) + write!( + f, + "topic: {}, value: {}, unit: {}", + self.topic, self.value, self.unit + ) } } +/** + * Implementation fo the Data Structs' methods + */ impl Data { - pub fn new(timestamp: DateTime, id: u8, value: f32) -> Self { + /** + * Constructor + * @param id: the id of the data + * @param value: the value of the data + * @param topic: the topic of the data + */ + pub fn new(value: f32, topic: &str, unit: &str) -> Self { Self { - timestamp, - id, value, + topic: topic.to_string(), + unit: unit.to_string(), } } -} -pub struct ProcessData { - // Utility functions to process message data. + pub fn to_json(&self) -> String { + format!("{{\"value\": {}, \"unit\": \"{}\"}}", self.value, self.unit) + } } -impl ProcessData { - pub fn group_bytes(data_bytes: &[u8], group_length: usize) -> Vec> { - // Splits the given data bytes into lists of specified length. - data_bytes - .chunks(group_length) - .map(|chunk| chunk.to_vec()) - .collect() - } +/** + * Class to contain the data processing functions + */ +pub struct ProcessData {} +impl ProcessData { + /** + * Computes the twos complement of the given value. + */ pub fn twos_comp(val: u32, bits: usize) -> i64 { - // Computes the twos complement of the given value. if (val & (1 << (bits - 1))) != 0 { - (val as i64) - (1 << bits) + (val as i64) - ((1 << bits) as i64) } else { val as i64 } } + /** + * Transforms the given data bytes into a value in little endian. + * Little Endian byte order stores low order bytes first. + */ pub fn little_endian(data_bytes: &[u8], bits: usize) -> u32 { - // Transforms the given data bytes into a value in little endian. - // Little Endian byte order stores low order bytes first. let mut result: u32 = 0; for (i, byte) in data_bytes.iter().enumerate() { - // println!("Little End Byte: {}", byte); result |= (*byte as u32) << (bits * i); - // println!("Little End Result: {}", result) } result } + /** + * Transforms the given data bytes into a value in big endian. + * Big Endian byte order stores low order bytes last. + */ pub fn big_endian(bytes: &[u8], bits: usize) -> u32 { - // Transforms the given data bytes into a value in big endian. - // Big Endian byte order stores low order bytes last. let mut result: u32 = 0; for (i, byte) in bytes.iter().enumerate() { - // println!("Big End Byte: {}", byte); result |= (*byte as u32) << (bits * (bytes.len() - i - 1)); - // println!("Big End Result: {}", result); } result } - pub fn default_decode(byte_vals: &[u8]) -> Vec { - // Default decode structure seen by a majority of the messages. - - let grouped_vals = ProcessData::group_bytes(byte_vals, 2); - println!("CUCKED GROUP BYTES"); - let parsed_vals: Vec = grouped_vals - .iter() - .map(|val| ProcessData::little_endian(val, 8)) - .collect(); - println!("CUCKED LITTLE ENDIAN"); - let decoded_vals: Vec = parsed_vals - .iter() - .map(|val| ProcessData::twos_comp(*val, 16)) - .collect(); - println!("CUCKED TWOS COMP"); - decoded_vals + /** + * Decodes the given byte by taking the top four bits after shifting it by the given number of bits. + */ + pub fn half(byte: u8, bits: u8) -> u32 { + (byte >> bits & 15) as u32 } } -pub struct FormatData { - // Utility functions to scale data values of a specific type. -} +/** + * Class to contain the data formatting functions + */ +pub struct FormatData {} impl FormatData { - pub fn temperature(value: i64) -> f32 { - value as f32 / 10.0 - } - - pub fn low_voltage(value: i64) -> f32 { - value as f32 / 100.0 - } - - pub fn torque(value: i64) -> f32 { - value as f32 / 10.0 - } - - pub fn high_voltage(value: i64) -> f32 { - value as f32 / 10.0 - } - - pub fn current(value: i64) -> f32 { - value as f32 / 10.0 - } - - pub fn angle(value: i64) -> f32 { - value as f32 / 10.0 + /* Temperatures are divided by 10 for 1 decimal point precision in C */ + pub fn temperature(value: f32) -> f32 { + value / 10.0 } - pub fn angular_velocity(value: i64) -> i64 { - -value + /* Torque values are divided by 10 for one decimal point precision in N-m */ + pub fn torque(value: f32) -> f32 { + value / 10.0 } - pub fn frequency(value: i64) -> f32 { - value as f32 / 10.0 + /* Current values are divided by 10 for one decimal point precision in A */ + pub fn current(value: f32) -> f32 { + value / 10.0 } - pub fn power(value: i32) -> f32 { - value as f32 / 10.0 + /* Cell Voltages are recorded on a 10000x multiplier for V, must be divided by 10000 to get accurate number */ + pub fn cell_voltage(value: f32) -> f32 { + value / 10000.0 } - pub fn timer(value: i32) -> f32 { - value as f32 * 0.003 + /* Acceleration values must be offset by 0.0029 according to datasheet */ + pub fn acceleration(value: f32) -> f32 { + value * 0.0029 } - pub fn flux(value: i64) -> f32 { - value as f32 / 1000.0 + /* High Voltage values are divided by 100 for one decimal point precision in V, high voltage is in regards to average voltage from the accumulator pack */ + pub fn high_voltage(value: f32) -> f32 { + value / 100.0 } } diff --git a/src/decode_data.rs b/src/decode_data.rs index 998eb5e..b283149 100644 --- a/src/decode_data.rs +++ b/src/decode_data.rs @@ -1,449 +1,101 @@ -// This file specifies methods to decode messages into the many pieces of data they contain. +#![allow(clippy::all)] +use super::data::{Data,FormatData as fd, ProcessData as pd}; -use nalgebra::convert; -use nalgebra::{Matrix3, Vector3}; -use std::collections::HashMap; - -use super::data::FormatData as fd; -use super::data::ProcessData as pd; - -pub fn decode_mock(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(0, 0.0); - result -} - -pub fn decode_accumulator_status(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(1, (pd::big_endian(&data[0..2], 8) as f32) / 10.0); - result.insert( - 2, - pd::twos_comp(pd::big_endian(&data[2..4], 8) as u32, 16) as f32 / 10.0, - ); - result.insert(3, pd::big_endian(&data[4..6], 8) as f32); - result.insert(4, data[6] as f32); - result.insert(5, data[7] as f32); - result -} - -pub fn decode_bms_status(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(106, data[0] as f32); - result.insert(107, pd::little_endian(&data[1..5], 8) as f32); - result.insert(10, pd::twos_comp(data[5] as u32, 8) as f32); - result.insert(11, pd::twos_comp(data[6] as u32, 8) as f32); - result.insert(143, data[7] as f32); - result -} - -pub fn decode3(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(12, data[0] as f32); - result -} - -pub fn decode_cell_voltages(data: &[u8]) -> HashMap { - let high_cell_volt_chip_number = (data[2] >> 4) & 15; - let high_cell_volt_cell_number = (data[2] >> 0) & 15; - let low_cell_volt_chip_number = (data[5] >> 4) & 15; - let low_cell_volt_cell_number = (data[5] >> 0) & 15; - let mut result = HashMap::new(); - result.insert(13, (pd::little_endian(&data[0..2], 8) as f32) / 10000.0); - result.insert(121, high_cell_volt_chip_number as f32); - result.insert(122, high_cell_volt_cell_number as f32); - result.insert(15, (pd::little_endian(&data[3..5], 8) as f32) / 10000.0); - result.insert(123, low_cell_volt_chip_number as f32); - result.insert(124, low_cell_volt_cell_number as f32); - result.insert(17, (pd::little_endian(&data[6..8], 8) as f32) / 10000.0); - result -} - -pub fn decode5(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(data); - let final_data = decoded_data - .iter() - .map(|d| fd::temperature(*d)) - .collect::>(); - let mut result = HashMap::new(); - result.insert(18, final_data[0]); - result.insert(19, final_data[1]); - result.insert(20, final_data[2]); - result.insert(21, final_data[3]); - result -} - -pub fn decode6(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(data); - let final_data = decoded_data - .iter() - .map(|d| fd::temperature(*d)) - .collect::>(); - let mut result = HashMap::new(); - result.insert(22, final_data[0]); - result.insert(23, final_data[1]); - result.insert(24, final_data[2]); - result.insert(25, final_data[3]); - result -} - -pub fn decode7(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(data); - let final_data = decoded_data[..3] - .iter() - .map(|d| fd::temperature(*d)) - .collect::>(); - let mut result = HashMap::new(); - result.insert(26, final_data[0]); - result.insert(27, final_data[1]); - result.insert(28, final_data[2]); - result.insert(29, fd::torque(decoded_data[3])); - result -} - -// TODO: Fill this method out (complicated with bit shifts) -pub fn decode8(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(30, 0.0); - result.insert(31, 0.0); - result.insert(32, 0.0); - result.insert(33, 0.0); - result.insert(34, 0.0); - result.insert(35, 0.0); - result -} - -pub fn decode9(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(36, data[0] as f32); - result.insert(37, data[1] as f32); - result.insert(38, data[2] as f32); - result.insert(39, data[3] as f32); - result.insert(40, data[4] as f32); - result.insert(41, data[5] as f32); - result.insert(42, data[6] as f32); - result.insert(43, data[7] as f32); - result -} - -pub fn decode10(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(data); - let motor_speed: f32 = fd::angular_velocity(decoded_data[1]) as f32; - let vehicle_speed = motor_speed * 0.013048225; - let mut result = HashMap::new(); - result.insert(44, fd::angle(decoded_data[0])); - result.insert(45, motor_speed); - result.insert(46, fd::frequency(decoded_data[2])); - result.insert(47, fd::angle(decoded_data[3])); - result.insert(101, vehicle_speed); - result -} - -pub fn decode11(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(data); - let final_data = decoded_data - .iter() - .map(|d| fd::current(*d)) - .collect::>(); - let mut result = HashMap::new(); - result.insert(48, final_data[0]); - result.insert(49, final_data[1]); - result.insert(50, final_data[2]); - result.insert(51, final_data[3]); - result -} - -pub fn decode12(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(&data); - let final_data: Vec = decoded_data.iter().map(|d| fd::high_voltage(*d)).collect(); - let mut result = HashMap::new(); - result.insert(52, final_data[0]); - result.insert(53, final_data[1]); - result.insert(54, final_data[2]); - result.insert(55, final_data[3]); - result -} - -pub fn decode13(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(&data); - let mut result = HashMap::new(); - result.insert(56, fd::flux(decoded_data[0])); - result.insert(57, fd::flux(decoded_data[1])); - result.insert(58, fd::current(decoded_data[2])); - result.insert(59, fd::current(decoded_data[3])); - result -} - -pub fn decode14(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(&data); - let final_data: Vec = decoded_data.iter().map(|d| fd::low_voltage(*d)).collect(); - let mut result = HashMap::new(); - result.insert(60, final_data[0]); - result.insert(61, final_data[1]); - result.insert(62, final_data[2]); - result.insert(63, final_data[3]); - result -} - -pub fn decode15(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(64, pd::little_endian(&data[0..2], 8) as f32); - result.insert(65, data[2] as f32); - result.insert(66, data[3] as f32); - result.insert(67, (data[4] & 1) as f32); - result.insert(68, ((data[4] >> 5) & 7) as f32); - result.insert(69, (data[5] & 1) as f32); - result.insert(70, (data[6] & 1) as f32); - result.insert(71, ((data[6] >> 7) & 1) as f32); - result.insert(72, (data[7] & 1) as f32); - result.insert(73, ((data[7] >> 1) & 1) as f32); - result.insert(74, ((data[7] >> 2) & 1) as f32); - result -} - -pub fn decode16(data: &[u8]) -> HashMap { - let binding = pd::group_bytes(&data, 2); - let data = binding.iter().map(|d| pd::little_endian(d, 8) as f32); - let grouped_data = data.collect::>(); - let mut result = HashMap::new(); - result.insert(75, grouped_data[0]); - result.insert(76, grouped_data[1]); - result.insert(77, grouped_data[2]); - result.insert(78, grouped_data[3]); - result -} - -pub fn decode17(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(&data[0..4]); - let timer_data = pd::little_endian(&data[4..], 8) as i32; - let mut result = HashMap::new(); - result.insert(79, fd::torque(decoded_data[0])); - result.insert(80, fd::torque(decoded_data[1])); - result.insert(81, fd::timer(timer_data)); - result -} - -pub fn decode18(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(&data); - let mut result = HashMap::new(); - result.insert(82, fd::torque(decoded_data[0])); - result.insert(83, fd::angular_velocity(decoded_data[1]) as f32); - result.insert(84, data[4] as f32); - result.insert(85, (data[5] & 1) as f32); - result.insert(86, ((data[5] >> 1) & 1) as f32); - result.insert(87, ((data[5] >> 2) & 1) as f32); - result.insert(88, fd::torque(decoded_data[3])); - result -} - -pub fn decode19(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(89, pd::little_endian(&data[0..2], 8) as f32); - result.insert(90, pd::little_endian(&data[2..4], 8) as f32); - result -} - -pub fn decode_accelerometer_data(data: &[u8]) -> HashMap { - let decoded_data = pd::default_decode(&data); - let converted_data = decoded_data - .iter() - .map(|val| *val as f32 * 0.0029) - .collect::>(); - let matrix_data: Vector3 = - Vector3::new(converted_data[0], converted_data[1], converted_data[2]); - let transform_matrix = Matrix3::new( - 1.0, - 0.0, - 0.0, - 0.0, - f32::cos(f32::to_radians(70.0)), - f32::sin(f32::to_radians(70.0)), - 0.0, - -f32::sin(f32::to_radians(70.0)), - f32::cos(f32::to_radians(70.0)), - ); - let transformed_data = transform_matrix * matrix_data; - let mut result = HashMap::new(); - result.insert(91, transformed_data[0]); - result.insert(92, transformed_data[1]); - result.insert(93, transformed_data[2]); - result -} - -pub fn decode21(data: &[u8]) -> HashMap { - let temp = pd::little_endian(&data[0..2], 8) as f32; - let humid = pd::little_endian(&data[2..4], 8) as f32; - let temp_f = -49.0 + (315.0 * temp / 65535.0); - let temp_c = -45.0 + (175.0 * temp / 65535.0); - let rel_humid = 100.0 * humid / 65535.0; - let mut result = HashMap::new(); - result.insert(94, temp_c); - result.insert(95, temp_f); - result.insert(96, rel_humid); - result -} - -// fn decode22(data: &[u8]) -> HashMap { -// let cell_id = data[0] as u32; -// let instant_voltage = pd::big_endian(&data[1..3], 8); -// let internal_resistance = (pd::big_endian(&data[3..5], 8) & 32767) as u32; -// let shunted = ((data[3] >> 7) & 1) as u32; -// let open_voltage = pd::big_endian(&data[5..7], 8); -// let mut result = HashMap::new(); -// result.insert( -// 97, -// "Cell ID: ".to_string() -// + &cell_id.to_string() -// + ", Instant Voltage: " -// + &instant_voltage.to_string() -// + ", Internal Resistance: " -// + &internal_resistance.to_string() -// + ", Shunted: " -// + &shunted.to_string() -// + ", Open Voltage: " -// + &open_voltage.to_string(), -// ); - -// result -// } - -pub fn decode29(data: &[u8]) -> HashMap { - let glv_current = pd::twos_comp(pd::little_endian(data, 8), 32) as f32; - let mut result = HashMap::new(); - result.insert(98, glv_current / 1000000.0); +pub fn decode_mock(_data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(0.0, "Mock", "") + ]; result } - -pub fn decode34(data: &[u8]) -> HashMap { - let voltage1 = pd::twos_comp(pd::little_endian(&data[0..4], 8), 32) as f32; - let voltage2 = pd::twos_comp(pd::little_endian(&data[4..], 8), 32) as f32; - let mut result = HashMap::new(); - result.insert(99, voltage1 / 1000000.0); - result.insert(100, voltage2 / 1000000.0); +pub fn decode_accumulator_status(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(fd::high_voltage(pd::big_endian(&data[0..2] as &[u8], 8) as f32), "BMS/Pack/Voltage", "V"), + Data::new(fd::current(pd::twos_comp(pd::big_endian(&data[2..4] as &[u8], 8) as u32, 16) as f32), "BMS/Pack/Current", "A"), + Data::new(pd::big_endian(&data[4..6] as &[u8], 8) as f32, "BMS/Pack/Amp-hours", "Ah"), + Data::new(data[6] as f32, "BMS/Pack/SOC", "%"), + Data::new(data[7] as f32, "BMS/Pack/Health", "%"), + ]; result } -pub fn decode35(data: &[u8]) -> HashMap { - let mut result: HashMap = HashMap::new(); - result.insert(102, pd::big_endian(&data[0..2], 8) as f32); - result.insert(103, pd::big_endian(&data[2..4], 8) as f32); - result.insert(104, data[4] as f32); +pub fn decode_bms_status(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(data[0] as f32, "BMS/State", ""), + Data::new(pd::little_endian(&data[1..5] as &[u8], 8) as f32, "BMS/Faults", ""), + Data::new(pd::twos_comp(data[5] as u32, 8) as f32, "BMS/Temps/Average", "C"), + Data::new(pd::twos_comp(data[6] as u32, 8) as f32, "BMS/Temps/Internal", "C"), + Data::new(data[7] as f32, "BMS/Cells/BurningStatus", ""), + ]; result } -pub fn decode_mpu_dashboard_info(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(105, data[0] as f32); - result.insert(130, data[1] as f32); - result.insert(131, data[2] as f32); - result.insert(132, data[3] as f32); - result.insert(133, data[4] as f32); - result.insert(144, data[5] as f32); - result.insert(145, data[6] as f32); - result.insert(146, data[7] as f32); +pub fn decode_shutdown_control(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(data[0] as f32, "BMS/Shutdown/MPE", ""), + ]; result } -pub fn decode_gps_1(data: &[u8]) -> HashMap { - let longitude = pd::twos_comp(pd::little_endian(&data[0..4], 8), 32) as f32 / 10000000.0; - let latitude = pd::twos_comp(pd::little_endian(&data[4..8], 8), 32) as f32 / 10000000.0; - let mut result = HashMap::new(); - result.insert(108, longitude); - result.insert(109, latitude); +pub fn decode_cell_data(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(fd::cell_voltage(pd::little_endian(&data[0..2] as &[u8], 8) as f32), "BMS/Cells/Volts/High/Value", "V"), + Data::new(pd::half(data[2] as u8, 4) as f32, "BMS/Cells/Volts/High/Chip", ""), + Data::new(pd::half(data[3] as u8, 0) as f32, "BMS/Cells/Volts/High/Cell", ""), + Data::new(fd::cell_voltage(pd::little_endian(&data[4..6] as &[u8], 8) as f32), "BMS/Cells/Volts/Low/Value", "V"), + Data::new(pd::half(data[6] as u8, 4) as f32, "BMS/Cells/Volts/Low/Chip", ""), + Data::new(pd::half(data[7] as u8, 0) as f32, "BMS/Cells/Volts/Low/Cell", ""), + Data::new(fd::cell_voltage(pd::little_endian(&data[8..10] as &[u8], 8) as f32), "BMS/Cells/Volts/avg/Value", "V"), + ]; result } -pub fn decode_gps_2(data: &[u8]) -> HashMap { - let altitude = pd::twos_comp(pd::little_endian(&data[4..8], 8), 32) as f32 / 1000.0; - let mut result: HashMap = HashMap::new(); - result.insert( - 110, - pd::twos_comp(pd::little_endian(&data[0..4], 8), 32) as f32, - ); - result.insert(111, altitude); +pub fn decode_cell_temperatures(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(pd::twos_comp(pd::little_endian(&data[0..2] as &[u8], 8) as u32, 16) as f32, "BMS/Cells/Temp/High/Value", "C"), + Data::new(pd::half(data[2] as u8, 4) as f32, "BMS/Cells/Temp/High/Cell", ""), + Data::new(pd::half(data[3] as u8, 0) as f32, "BMS/Cells/Temp/High/Chip", ""), + Data::new(pd::twos_comp(pd::little_endian(&data[4..6] as &[u8], 8) as u32, 16) as f32, "BMS/Cells/Temp/Low/Value", "C"), + Data::new(pd::half(data[6] as u8, 4) as f32, "BMS/Cells/Temp/Low/Cell", ""), + Data::new(pd::half(data[7] as u8, 0) as f32, "BMS/Cells/Temp/Low/Chip", ""), + Data::new(pd::twos_comp(pd::little_endian(&data[8..10] as &[u8], 8) as u32, 16) as f32, "BMS/Cells/Temp/avg/Value", "C"), + ]; result } -pub fn decode_gps_3(data: &[u8]) -> HashMap { - let ground_speed = pd::twos_comp(pd::little_endian(&data[0..4], 8), 32) as f32 / 1000.0; - let heading = pd::twos_comp(pd::little_endian(&data[4..8], 8), 32) as f32 / 100000.0; - let mut result = HashMap::new(); - result.insert(112, ground_speed); - result.insert(113, heading); - result -} - -pub fn decode_cell_temps(data: &[u8]) -> HashMap { - let high_cell_temp_chip_number = (data[2] >> 4) & 15; - let high_cell_temp_cell_number = (data[2] >> 0) & 15; - let low_cell_temp_chip_number = (data[5] >> 4) & 15; - let low_cell_temp_cell_number = (data[5] >> 0) & 15; - - let mut result = HashMap::new(); - result.insert( - 114, - pd::twos_comp(pd::little_endian(&data[0..2], 8), 16) as f32, - ); - result.insert(115, high_cell_temp_chip_number as f32); - result.insert(116, high_cell_temp_cell_number as f32); - result.insert( - 117, - pd::twos_comp(pd::little_endian(&data[3..5], 8), 16) as f32, - ); - result.insert(118, low_cell_temp_chip_number as f32); - result.insert(119, low_cell_temp_cell_number as f32); - result.insert( - 120, - pd::twos_comp(pd::little_endian(&data[6..8], 8), 16) as f32, - ); - +pub fn decode_segment_temperatures(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(pd::twos_comp(data[0] as u32, 8) as f32, "BMS/Segment/Temp/1", "C"), + Data::new(pd::twos_comp(data[1] as u32, 8) as f32, "BMS/Segment/Temp/2", "C"), + Data::new(pd::twos_comp(data[2] as u32, 8) as f32, "BMS/Segment/Temp/3", "C"), + Data::new(pd::twos_comp(data[3] as u32, 8) as f32, "BMS/Segment/Temp/4", "C"), + ]; result } -pub fn decode_segment_temps(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(125, pd::twos_comp(data[0] as u32, 8) as f32); - result.insert(126, pd::twos_comp(data[1] as u32, 8) as f32); - result.insert(127, pd::twos_comp(data[2] as u32, 8) as f32); - result.insert(128, pd::twos_comp(data[3] as u32, 8) as f32); - +pub fn decode_mpu_acceleromter(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(fd::acceleration(pd::big_endian(&data[0..2] as &[u8], 8) as f32), "MPU/Accel/X", "g"), + Data::new(fd::acceleration(pd::big_endian(&data[2..4] as &[u8], 8) as f32), "MPU/Accel/Y", "g"), + Data::new(fd::acceleration(pd::big_endian(&data[4..6] as &[u8], 8) as f32), "MPU/Accel/Z", "g"), + ]; result } -pub fn decode_logging_status(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(129, data[0] as f32); - +pub fn decode_mpu_status(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(data[0] as f32, "MPU/State/Mode", ""), + Data::new(data[1] as f32, "MPU/State/Torque_Limit_Percentage", ""), + Data::new(data[2] as f32, "MPU/State/Regen_Strength", ""), + Data::new(data[3] as f32, "MPU/State/Traction_Control", ""), + ]; result } -pub fn decode_lv_battery_1(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert(134, (pd::little_endian(&data[0..2], 8)) as f32); - result.insert(135, data[2] as f32); - result.insert(136, (pd::little_endian(&data[3..5], 8)) as f32); - result.insert(137, data[5] as f32); - result.insert(138, (pd::little_endian(&data[6..8], 8)) as f32); +pub fn decode_wheel_state(data: &[u8]) -> Vec:: { + let result = vec![ + Data::new(data[0] as f32, "WHEEL/Buttons/1", ""), + Data::new(data[1] as f32, "WHEEL/Buttons/2", ""), + ]; result } -pub fn decode_lv_battery_2(data: &[u8]) -> HashMap { - let mut result = HashMap::new(); - result.insert( - 139, - (pd::twos_comp(pd::little_endian(&data[0..2], 8), 16) as f32) * (192.264 / 1000000.0) * 4.0, - ); - result.insert( - 140, - (pd::twos_comp(pd::little_endian(&data[2..4], 8), 16) as f32) * (1.648 / 1000.0), - ); - result.insert( - 141, - (pd::twos_comp(pd::little_endian(&data[4..6], 8), 16) as f32) * (1.648 / 1000.0), - ); - result.insert( - 142, - (pd::twos_comp(pd::little_endian(&data[6..8], 8), 16) as f32) * (1.46487 / 1000000.0) - / (0.5 / 1000.0), - ); - result -} diff --git a/src/decode_files.rs b/src/decode_files.rs deleted file mode 100644 index a25629b..0000000 --- a/src/decode_files.rs +++ /dev/null @@ -1,93 +0,0 @@ - -// This file specifies methods to decode message fields (timestamp, id, data bytes) from -// a line in a log file. - - -use chrono::prelude::*; -use std::convert::TryInto; - -use message::Message; - -mod message; - -enum LogFormat { - Textual1, - Textual1Legacy, - Textual2, - Binary, -} - -fn process_line(line: &str, format: LogFormat) -> Message { - - // Processes a line of textual data according to a given format. - - match format { - LogFormat::Textual1 => _process_textual1(line), - LogFormat::Textual1Legacy => _process_textual1_legacy(line), - LogFormat::Textual2 => _process_textual2(line), - LogFormat::Binary => _process_binary(line), - } -} - -fn _process_textual1(line: &str) -> Message { - - // Processes a line of data in the format "Timestamp id length [data1,data2,...]" - // Example line format: 1679511802367 514 8 [54,0,10,0,0,0,0,0] - - let fields: Vec<&str> = line.trim().split(' ').collect(); - let timestamp = NaiveDateTime::from_timestamp(fields[0].parse::().unwrap() / 1000, 0); - let id = fields[1].parse::().unwrap(); - let length = fields[2].parse::().unwrap(); - let data: Vec = fields[3][1..fields[3].len() - 1] - .split(',') - .map(|x| x.parse().unwrap()) - .collect(); - // remove commas and brackets at start and end - let int_data: Vec = data - .chunks_exact(2) - .map(|x| i16::from_le_bytes(x.try_into().unwrap())) - .collect(); - Message::new(timestamp, id, int_data) -} - -fn _process_textual1_legacy(line: &str) -> Message { - - // Processes a line of data in the format: Timestamp id length [data1,data2,...] - // Example line format: 2021-01-01T00:00:00.003Z 514 8 [54,0,10,0,0,0,0,0] - - let fields: Vec<&str> = line.trim().split(' ').collect(); - let timestamp = NaiveDateTime::parse_from_str(fields[0], "%Y-%m-%dT%H:%M:%S.%fZ") - .unwrap() - .into(); - let id = fields[1].parse::().unwrap(); - let length = fields[2].parse::().unwrap(); - let data: Vec = fields[3][1..fields[3].len() - 1] - .split(',') - .map(|x| x.parse().unwrap()) - .collect(); - let int_data: Vec = data - .chunks_exact(2) - .map(|x| i16::from_le_bytes(x.try_into().unwrap())) - .collect(); - Message::new(timestamp, id, int_data) -} - -fn _process_textual2(line: &str) -> Message { - - // Processes a line of data in the format "Timestamp id length data1 data2 ..." - // Example line format: 1659901910.121 514 8 54 0 10 0 0 0 0 0 - - let fields: Vec<&str> = line.trim().split(' ').collect(); - let timestamp = NaiveDateTime::from_timestamp(fields[0].parse::().unwrap(), 0); - let id = fields[1].parse::().unwrap(); - let length = fields[2].parse::().unwrap(); - let data: Vec = fields[3..3 + length] - .iter() - .map(|x| x.parse().unwrap()) - .collect(); - Message::new(timestamp, id, data) -} - -fn _process_binary(line: &str) -> Message { - panic!("Binary files not currently supported.") -} \ No newline at end of file diff --git a/src/decode_statuses.rs b/src/decode_statuses.rs deleted file mode 100644 index de2a88f..0000000 --- a/src/decode_statuses.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::collections::HashMap; - -// Mapping from data IDs to the status bits they encode -// Each data ID contains a hash map with keys that are bit names and values that are the indexes -const STATUS_MAP: HashMap> = [(6, HashMap::new()), // Failsafe Statuses -(7, HashMap::new()), // DTC Status 1 -(8, HashMap::new()), // DTC Status 2 -(9, HashMap::new()), // Current Limits Status -(12, HashMap::new()), // MPE State -( // VSM State - 64, - hashmap![ - "VSM Start State" => 0, - "Pre-charge Init State" => 1, - "Pre-charge Active State" => 2, - "Pre-charge Complete State" => 3, - "VSM Wait State" => 4, - "VSM Ready State" => 5, - "Motor Running State" => 6, - "Blink Fault Code State" => 7 - ] -), -( // Inverter State - 65, - hashmap![ - "Power on State" => 0, - "Stop State" => 1, - "Open Loop State" => 2, - "Closed Loop State" => 3, - "Wait State" => 4, - "Idle Run State" => 8, - "Idle Stop State" => 9 - ] -), -( // Relay State - 66, - hashmap![ - "Relay 1 Status" => 0, - "Relay 2 Status" => 1, - "Relay 3 Status" => 2, - "Relay 4 Status" => 3, - "Relay 5 Status" => 4, - "Relay 6 Status" => 5, - ] -), -(// Inverter Run Mode - 67, hashmap!["Inverter Run Mode" => 0]), -(// Inverter Command Mode - 69, hashmap!["Inverter Command Mode" => 0]), -(// Inverter Enable State - 70, hashmap!["Inverter Enable State" => 0]), -(// Inverter Enable Lockout - 71, hashmap!["Inverter Enable Lockout" => 0]), -(// Direction Command - 72, hashmap!["Direction Command" => 0]), -(// BMS Active - 73, hashmap!["BMS Active" => 0]), -(// BMS Limiting Torque - 74, hashmap!["BMS Limiting Torque" => 0]), -(// POST Fault Lo - 75, - hashmap![ - "Hardware Gate/Desaturation Fault" => 0, - "HW Over-current Fault" => 1, - "Accelerator Shorted" => 2, - "Accelerator Open" => 3, - "Current Sensor Low" => 4, - "Current Sensor High" => 5, - "Module Temperature Low" => 6, - "Module Temperature High" => 7, - "Control PCB Temperature Low" => 8, - "Control PCB Temperature High" => 9, - "Gate Drive PCB Temperature Low" => 10, - "Gate Drive PCB Temperature High" => 11, - "5V Sense Voltage Low" => 12, - "5V Sense Voltage High" => 13, - "12V Sense Voltage Low" => 14, - "12V Sense Voltage High" => 15 - ]), -( - 76, - hashmap![ // POST Fault Hi - "2.5V Sense Voltage Low" => 0, - "2.5V Sense Voltage High" => 1, - "1.5V Sense Voltage Low" => 2, - "1.5V Sense Voltage High" => 3, - "DC Bus Voltage High" => 4, - "DC Bus Voltage Low" => 5, - "Pre-charge Timeout" => 6, - "Pre-charge Voltage Failure" => 7, - "Brake Shorted" => 14, - "Brake Open" => 15 - ]), -( - 77, - hashmap![ // Run Fault Lo - "Motor Over-speed Fault" => 0, - "Over-current Fault" => 1, - "Over-voltage Fault" => 2, - "Inverter Over-temperature Fault" => 3, - "Accelerator Input Shorted Fault" => 4, - "Accelerator Input Open Fault" => 5, - "Direction Command Fault" => 6, - "Inverter Response Time-out Fault" => 7, - "Hardware Gate/Desaturation Fault" => 8, - "Hardware Over-current Fault" => 9, - "Under-voltage Fault" => 10, - "CAN Command Message Lost Fault" => 11, - "Motor Over-temperature Fault" => 12 - ]), -( - 78, - hashmap![ // Run Fault Hi - "Brake Input Shorted Fault" => 0, - "Brake Input Open Fault" => 1, - "Module A Over-temperature Fault" => 2, - "Module B Over temperature Fault" => 3, - "Module C Over-temperature Fault" => 4, - "PCB Over-temperature Fault" => 5, - "Gate Drive Board 1 Over-temperature Fault" => 6, - "Gate Drive Board 2 Over-temperature Fault" => 7, - "Gate Drive Board 3 Over-temperature Fault" => 8, - "Current Sensor Fault" => 9, - "Hardware Over-Voltage Fault" => 11, - "Resolver Not Connected" => 14, - "Inverter Discharge Active" => 15 - ]), -(// Direction Command - 84, hashmap!["Direction Command" => 0]), -(// Inverter Enable - 85, hashmap!["Inverter Enable" => 0]), -(// Inverter Discharge - 86, hashmap!["Inverter Discharge" => 0]), -(// Speed Mode Enable - 87, hashmap!["Speed Mode Enable" => 0]), -(// Cell Voltage Info -)]; - - -fn get_status(data: &Data, name: &str) -> i32 { - if !STATUS_MAP.contains_key(&data.id) { - panic!("Data ID has no associated status mapping"); - } - let bitmap = &STATUS_MAP[&data.id]; - - if !bitmap.contains_key(name) { - panic!("Status name could not be found in the given data point"); - } - let index = bitmap[name]; - - return (data.value >> index) & 1; -} - -fn get_statuses(data: &Data) -> HashMap<&str, i32> { - if !STATUS_MAP.contains_key(&data.id) { - panic!("Data ID has no associated status mapping"); - } - let bitmap = &STATUS_MAP[&data.id]; - - // Convert each dict value to the bit value at the index - return bitmap.iter().map(|(name, index)| (*name, (data.value >> index) & 1)).collect(); -} - diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..455565d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub mod client; +pub mod data; +pub mod decode_data; +pub mod message; +pub mod mqtt; +pub mod master_mapping; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b3ca34b..d776278 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,62 +1,52 @@ -extern crate systemstat; -use chrono::DateTime; -use chrono::TimeZone; -use chrono::Utc; -use socketcan::*; -use std::env; -use std::io::Write; -use std::os::unix::net::UnixStream; -use std::process::Command; -use std::sync::mpsc::channel; -use std::thread; -mod data; -mod decode_data; -mod master_mapping; -mod message; +use std::{ + env, + process::{self, Command}, +}; -fn main() { - let args: Vec = env::args().collect(); - let default = "tmp/ipc.sock".to_owned(); - let ipc_path = args.get(0).unwrap_or(&default); +use calypso::{client::Client, message::Message, mqtt::MqttClient}; +use socketcan::CANSocket; +fn configure_can(can_interface: &str) { let mut down_command = Command::new("sudo") .arg("ifconfig") - .arg("can0") + .arg(can_interface) .arg("down") .spawn() .expect("down command did not work"); - down_command + down_command // Takes down any current can networks .wait() .expect("Fail while waiting for down command"); let mut bit_rate_commmand = Command::new("sudo") .arg("ip") .arg("link") .arg("set") - .arg("can0") + .arg(can_interface) .arg("type") .arg("can") .arg("bitrate") .arg("1000000") .spawn() .expect("bit rate command did not work"); - bit_rate_commmand + bit_rate_commmand //sets the bit rate of the can network .wait() .expect("Fail while waiting for bit rate"); let mut up_command = Command::new("sudo") .arg("ifconfig") - .arg("can0") + .arg(can_interface) .arg("up") .spawn() .expect("up command did nto work"); - up_command + up_command // Brings up the new can network .wait() .expect("Fail while waiting for up command"); +} - let mut stream = UnixStream::connect(ipc_path).unwrap(); - let (tx, rx) = channel(); - //open can socket channel at name can0 - const CAN_CHANNEL: &str = "can0"; - let socket = CANSocket::open(&CAN_CHANNEL); +/** + * Reads the can socket and publishes the data to the given client. + */ +fn read_can(mut publisher: Box, can_interface: &str) { + //open can socket channel at name can_interface + let socket = CANSocket::open(can_interface); let socket = match socket { Ok(socket) => socket, Err(err) => { @@ -64,28 +54,76 @@ fn main() { return; } }; - thread::spawn(move || loop { - let msg = socket.read_frame().unwrap(); - let date: DateTime = Utc.with_ymd_and_hms(2020, 1, 1, 0, 0, 0).unwrap(); + loop { + let msg = match { socket.read_frame() } { + Ok(msg) => msg, + Err(err) => { + println!("Failed to read CAN frame: {}", err); + continue; + } + }; let data = msg.data(); - let message = message::Message::new(&date, &msg.id(), &data); + let message = Message::new(msg.id(), data.to_vec()); let decoded_data = message.decode(); - for (_i, data) in decoded_data.iter().enumerate() { - let message = format!( - "{{ - index:{}, - value:{} - }}", - data.id.to_string(), - data.value.to_string() - ); - println!("Sending message: {}", message); - tx.send(message).unwrap(); + for data in decoded_data.iter() { + publisher.publish(data) } - }); - loop { - let _ = rx - .try_recv() - .map(|reply| stream.write_all(reply.as_bytes())); } } + +/** + * Parses the command line arguments. + * Returns the client type and the path to connect to. + */ +fn parse_args() -> (String, Box, String, bool) { + let args: Vec = env::args().collect(); + println!("{:?}", args); + if args.len() < 3 { + println!("Not enough arguments!"); + println!("Client type and client path are required."); + process::exit(1); + } + let client_type = &args[1]; + let path = &args[2]; + let can_interface = if args.len() > 3 { &args[3] } else { "can0" }; + + let skip_can_config = args.len() > 4 && args[4] == "skip_can_configure"; + + println!("Client type: {}", client_type); + println!("Path: {}", path); + println!("Can interface: {}", can_interface); + println!("Skip can configuration: {}", skip_can_config); + + match client_type.as_str() { + "mqtt" => ( + String::from(path), + Box::new(MqttClient::new()) as Box, + String::from(can_interface), + skip_can_config, + ), + _ => { + println!("Please provide a valid client type"); + process::exit(1); + } + } +} + +/** + * Main Function + * Configures the can network, retrieves the client based on the command line arguments, + * connects the client and then reads the can socket from specified interface. + * Sample Calls for IPC "/home/ner/Desktop/Calypso/target/release/calypso ipc /tmp/ipc.sock &" + * Sample Call for Mqtt "/home/ner/Desktop/Calypso/target/release/calypso mqtt localhost:1883 &" + * + * 3rd argument: if a can interface is passed the program will use it instead of the default can0 + * 4th argument: if skip_can_configure is passed the program will not call can interface setup commands + * Ex: "/home/ner/Desktop/Calypso/target/release/calypso mqtt localhost:1883 can0 skip_can_configure &" + */ +fn main() { + let (path, mut client, can_interface, skip_can_configure) = parse_args(); + if !skip_can_configure { + configure_can(&can_interface) + } + client.connect(&path); + read_can(client, &can_interface); +} diff --git a/src/master_mapping.rs b/src/master_mapping.rs index d5d7fa1..8cb2016 100644 --- a/src/master_mapping.rs +++ b/src/master_mapping.rs @@ -1,247 +1,27 @@ -use super::decode_data::*; -use std::collections::HashMap; +use super::decode_data::*; +use super::data::Data; -#[derive(Clone)] pub struct MessageInfo { - pub description: String, - pub decoder: fn(data: &[u8]) -> HashMap, -} + pub decoder: fn(data: &[u8]) -> Vec, +} impl MessageInfo { - pub fn new(description: String, decoder: fn(data: &[u8]) -> HashMap) -> Self { + pub fn new(decoder: fn(data: &[u8]) -> Vec) -> Self { Self { - description, - decoder, + decoder } } } - -// Mapping from external message ID to decoding information -pub fn get_message_info(id: &u32) -> MessageInfo { - match id { - 1 => return MessageInfo::new("accumulator status".to_string(), decode_accumulator_status), - 2 => return MessageInfo::new("BMS status".to_string(), decode_bms_status), - 3 => return MessageInfo::new("shutdown control".to_string(), decode3), - 4 => return MessageInfo::new("cell data".to_string(), decode_cell_voltages), - 160 => { - return MessageInfo::new( - "temperatures (igbt modules, gate driver board)".to_string(), - decode5, - ) - } - 161 => return MessageInfo::new("temperatures (control board)".to_string(), decode6), - 162 => return MessageInfo::new("temperatures (motor)".to_string(), decode7), - 163 => return MessageInfo::new("analog input voltages".to_string(), decode8), - 164 => return MessageInfo::new("digital input status".to_string(), decode9), - 165 => return MessageInfo::new("motor position information".to_string(), decode10), - 166 => return MessageInfo::new("Current information".to_string(), decode11), - 167 => return MessageInfo::new("Voltage Information".to_string(), decode12), - 168 => return MessageInfo::new("Flux Information".to_string(), decode13), - 169 => return MessageInfo::new("Internal Voltages".to_string(), decode14), - 170 => return MessageInfo::new("Internal States".to_string(), decode15), - 171 => return MessageInfo::new("Fault Codes".to_string(), decode16), - 172 => return MessageInfo::new("Torque and Timer Decoder".to_string(), decode17), - 192 => return MessageInfo::new("Command Data".to_string(), decode18), - 514 => return MessageInfo::new("Current Limits".to_string(), decode19), - 768 => { - return MessageInfo::new( - "NERduino Accelerometer".to_string(), - decode_accelerometer_data, - ) - } - 769 => return MessageInfo::new("NERduino Humidity".to_string(), decode21), - 7 => return MessageInfo::new("Cell Voltages".to_string(), decode_mock), - 193 => return MessageInfo::new("Unknown 1".to_string(), decode_mock), - 6 => return MessageInfo::new("Unknown 2".to_string(), decode_mock), - 194 => return MessageInfo::new("Unknown 3".to_string(), decode_mock), - 1744 => return MessageInfo::new("Unknown 4".to_string(), decode_mock), - 1745 => return MessageInfo::new("Unknown 5".to_string(), decode_mock), - 175 => return MessageInfo::new("Unknown 6".to_string(), decode_mock), - 770 => return MessageInfo::new("GLV Current".to_string(), decode29), - 2015 => return MessageInfo::new("Unknown 2015".to_string(), decode_mock), - 2027 => return MessageInfo::new("Unknown 2027".to_string(), decode_mock), - 2019 => return MessageInfo::new("Unknown 2019".to_string(), decode_mock), - 771 => return MessageInfo::new("Strain Gauge".to_string(), decode34), - 1024 => return MessageInfo::new("Wheel State".to_string(), decode35), - 10 => return MessageInfo::new("MPU States".to_string(), decode_mpu_dashboard_info), - 772 => return MessageInfo::new("GPS Data 1".to_string(), decode_gps_1), - 773 => return MessageInfo::new("GPS Data 2".to_string(), decode_gps_2), - 774 => return MessageInfo::new("GPS Data 3".to_string(), decode_gps_3), - 8 => return MessageInfo::new("Cell Temperatures".to_string(), decode_cell_temps), - 9 => return MessageInfo::new("Segment Temperatures".to_string(), decode_segment_temps), - 775 => return MessageInfo::new("Logging Status".to_string(), decode_logging_status), - 1025 => return MessageInfo::new("LV Battery 1".to_string(), decode_lv_battery_1), - 1026 => return MessageInfo::new("LV Battery 2".to_string(), decode_lv_battery_2), - _ => return MessageInfo::new("Unknown".to_string(), decode_mock), - } -} - -#[derive(Clone)] -pub struct DataInfo { - name: String, - units: String, -} - -impl DataInfo { - pub fn new(name: String, units: String) -> Self { - Self { name, units } - } -} - -// maps from data id to DataInfo containing the name of the data and its units -pub fn get_data_info(id: u8) -> DataInfo { - match id { - 0 => return DataInfo::new("Mock Data".to_string(), "".to_string()), - 1 => return DataInfo::new("Pack Inst Voltage".to_string(), "V".to_string()), - 2 => return DataInfo::new("Pack Current".to_string(), "A".to_string()), - 3 => return DataInfo::new("Pack Amphours".to_string(), "Ah".to_string()), - 4 => return DataInfo::new("Pack SOC".to_string(), "%".to_string()), - 5 => return DataInfo::new("Pack Health".to_string(), "%".to_string()), - 6 => return DataInfo::new("Failsafe Statuses".to_string(), "HEX".to_string()), - 7 => return DataInfo::new("DTC Status 1".to_string(), "HEX".to_string()), - 8 => return DataInfo::new("DTC Status 2".to_string(), "HEX".to_string()), - 9 => return DataInfo::new("Current Limits Status".to_string(), "".to_string()), - 10 => return DataInfo::new("Average Temp".to_string(), "C".to_string()), - 11 => return DataInfo::new("Internal Temp".to_string(), "C".to_string()), - 12 => return DataInfo::new("MPE State".to_string(), "BIN".to_string()), - 13 => return DataInfo::new("High Cell Voltage".to_string(), "V".to_string()), - 14 => return DataInfo::new("High Cell Voltage ID".to_string(), "".to_string()), - 15 => return DataInfo::new("Low Cell Voltage".to_string(), "V".to_string()), - 16 => return DataInfo::new("Low Cell Voltage ID".to_string(), "".to_string()), - 17 => return DataInfo::new("Average Cell Voltage".to_string(), "V".to_string()), - 18 => return DataInfo::new("Module A Temperature".to_string(), "C".to_string()), - 19 => return DataInfo::new("Module B Temperature".to_string(), "C".to_string()), - 20 => return DataInfo::new("Module C Temperature".to_string(), "C".to_string()), - 21 => return DataInfo::new("Gate Driver Board Temperature".to_string(), "C".to_string()), - 22 => return DataInfo::new("Control Board Temperature".to_string(), "C".to_string()), - 23 => return DataInfo::new("RTD #1 Temperature".to_string(), "C".to_string()), - 24 => return DataInfo::new("RTD #2 Temperature".to_string(), "C".to_string()), - 25 => return DataInfo::new("RTD #3 Temperature".to_string(), "C".to_string()), - 26 => return DataInfo::new("RTD #4 Temperature".to_string(), "C".to_string()), - 27 => return DataInfo::new("RTD #5 Temperature".to_string(), "C".to_string()), - 28 => return DataInfo::new("Motor Temperature".to_string(), "C".to_string()), - 29 => return DataInfo::new("Torque Shudder".to_string(), "N-m".to_string()), - 30 => return DataInfo::new("Analog Input 1".to_string(), "V".to_string()), - 31 => return DataInfo::new("Analog Input 2".to_string(), "V".to_string()), - 32 => return DataInfo::new("Analog Input 3".to_string(), "V".to_string()), - 33 => return DataInfo::new("Analog Input 4".to_string(), "V".to_string()), - 34 => return DataInfo::new("Analog Input 5".to_string(), "V".to_string()), - 35 => return DataInfo::new("Analog Input 6".to_string(), "V".to_string()), - 36 => return DataInfo::new("Digital Input 1".to_string(), "BIN".to_string()), - 37 => return DataInfo::new("Digital Input 2".to_string(), "BIN".to_string()), - 38 => return DataInfo::new("Digital Input 3".to_string(), "BIN".to_string()), - 39 => return DataInfo::new("Digital Input 4".to_string(), "BIN".to_string()), - 40 => return DataInfo::new("Digital Input 5".to_string(), "BIN".to_string()), - 41 => return DataInfo::new("Digital Input 6".to_string(), "BIN".to_string()), - 42 => return DataInfo::new("Digital Input 7".to_string(), "BIN".to_string()), - 43 => return DataInfo::new("Digital Input 8".to_string(), "BIN".to_string()), - 44 => return DataInfo::new("Motor Angle Electrical".to_string(), "Deg".to_string()), - 45 => return DataInfo::new("Motor Speed".to_string(), "RPM".to_string()), - 46 => return DataInfo::new("Electrical Output Frequency".to_string(), "Hz".to_string()), - 48 => return DataInfo::new("Phase A Current".to_string(), "A".to_string()), - 49 => return DataInfo::new("Phase B Current".to_string(), "A".to_string()), - 50 => return DataInfo::new("Phase C Current".to_string(), "A".to_string()), - 51 => return DataInfo::new("DC Bus Current".to_string(), "A".to_string()), - 52 => return DataInfo::new("DC Bus Voltage".to_string(), "V".to_string()), - 53 => return DataInfo::new("Output Voltage".to_string(), "V".to_string()), - 54 => return DataInfo::new("VAB_Vd Voltage".to_string(), "V".to_string()), - 55 => return DataInfo::new("VBC_Vq Voltage".to_string(), "V".to_string()), - 56 => return DataInfo::new("Flux Command".to_string(), "Wb".to_string()), - 57 => return DataInfo::new("Flux Feedback".to_string(), "wb".to_string()), - 58 => return DataInfo::new("Id Feedback".to_string(), "A".to_string()), - 59 => return DataInfo::new("Iq Feedback".to_string(), "A".to_string()), - 60 => return DataInfo::new("1.5V Reference Voltage".to_string(), "V".to_string()), - 61 => return DataInfo::new("2.5V Reference Voltage".to_string(), "V".to_string()), - 62 => return DataInfo::new("5.0V Reference Voltage".to_string(), "V".to_string()), - 63 => return DataInfo::new("12V System Voltage".to_string(), "V".to_string()), - 64 => return DataInfo::new("VSM State".to_string(), "".to_string()), - 65 => return DataInfo::new("Inverter State".to_string(), "".to_string()), - 66 => return DataInfo::new("Relay State".to_string(), "BIN".to_string()), - 67 => return DataInfo::new("Inverter Run Mode".to_string(), "BIN".to_string()), - 68 => { - return DataInfo::new( - "Inverter Active Discharge State".to_string(), - "BIN".to_string(), - ) - } - 69 => return DataInfo::new("Inverter Command Mode".to_string(), "BIN".to_string()), - 70 => return DataInfo::new("Inverter Enable State".to_string(), "BIN".to_string()), - 71 => return DataInfo::new("Inverter Enable Lockout".to_string(), "BIN".to_string()), - 72 => return DataInfo::new("Direction Command".to_string(), "BIN".to_string()), - 73 => return DataInfo::new("BMS Active".to_string(), "BIN".to_string()), - 74 => return DataInfo::new("BMS Limiting Torque".to_string(), "BIN".to_string()), - 75 => return DataInfo::new("POST Fault Lo".to_string(), "BIN".to_string()), - 76 => return DataInfo::new("POST Fault Hi".to_string(), "BIN".to_string()), - 77 => return DataInfo::new("Run Fault Lo".to_string(), "BIN".to_string()), - 78 => return DataInfo::new("Run Fault Hi".to_string(), "BIN".to_string()), - 79 => return DataInfo::new("Commanded Torque".to_string(), "N-m".to_string()), - 80 => return DataInfo::new("Torque Feedback".to_string(), "N-m".to_string()), - 81 => return DataInfo::new("Power on Timer".to_string(), "s".to_string()), - 82 => return DataInfo::new("Torque Command".to_string(), "N-m".to_string()), - 83 => return DataInfo::new("Speed Command".to_string(), "RPM".to_string()), - 84 => return DataInfo::new("Direction Command".to_string(), "BIN".to_string()), - 85 => return DataInfo::new("Inverter Enable".to_string(), "BIN".to_string()), - 86 => return DataInfo::new("Inverter Discharge".to_string(), "BIN".to_string()), - 87 => return DataInfo::new("Speed Mode Enable".to_string(), "BIN".to_string()), - 88 => return DataInfo::new("Commanded Torque Limit".to_string(), "N-m".to_string()), - 89 => return DataInfo::new("Pack DCL".to_string(), "A".to_string()), - 90 => return DataInfo::new("Pack CCL".to_string(), "A".to_string()), - 91 => return DataInfo::new("TCU X-Axis Acceleration".to_string(), "g".to_string()), - 92 => return DataInfo::new("TCU Y-Axis Acceleration".to_string(), "g".to_string()), - 93 => return DataInfo::new("TCU Z-Axis Acceleration".to_string(), "g".to_string()), - 94 => return DataInfo::new("TCU Temperature C".to_string(), "C".to_string()), - 95 => return DataInfo::new("TCU Temperature F".to_string(), "F".to_string()), - 96 => return DataInfo::new("Relative Humidity".to_string(), "%".to_string()), - 97 => return DataInfo::new("Cell Voltage Info".to_string(), "".to_string()), - 98 => return DataInfo::new("GLV Current".to_string(), "A".to_string()), - 99 => return DataInfo::new("Strain Gauge Voltage 1".to_string(), "V".to_string()), - 100 => return DataInfo::new("Strain Gauge Voltage 2".to_string(), "V".to_string()), - 101 => return DataInfo::new("Vehicle Speed".to_string(), "MPH".to_string()), - 102 => return DataInfo::new("Wheel Knob 1".to_string(), "".to_string()), - 103 => return DataInfo::new("Wheel Knob 2".to_string(), "".to_string()), - 104 => return DataInfo::new("Wheel Buttons".to_string(), "".to_string()), - 105 => return DataInfo::new("MPU Mode State".to_string(), "".to_string()), - 106 => return DataInfo::new("BMS State".to_string(), "".to_string()), - 107 => return DataInfo::new("BMS Faults".to_string(), "HEX".to_string()), - 108 => return DataInfo::new("Latitude".to_string(), "Deg".to_string()), - 109 => return DataInfo::new("Longitude".to_string(), "Deg".to_string()), - 110 => return DataInfo::new("GPS Fix Status".to_string(), "".to_string()), - 111 => return DataInfo::new("Altitude".to_string(), "m".to_string()), - 112 => return DataInfo::new("Ground Speed".to_string(), "m/s".to_string()), - 113 => return DataInfo::new("Heading Direction".to_string(), "Deg".to_string()), - 114 => return DataInfo::new("High Cell Temp".to_string(), "C".to_string()), - 115 => return DataInfo::new("High Cell Temp Chip Number".to_string(), "".to_string()), - 116 => return DataInfo::new("High Cell Temp Cell Number".to_string(), "".to_string()), - 117 => return DataInfo::new("Low Cell Temp".to_string(), "C".to_string()), - 118 => return DataInfo::new("Low Cell Temp Chip Number".to_string(), "".to_string()), - 119 => return DataInfo::new("Low Cell temp Cell Number".to_string(), "".to_string()), - 120 => return DataInfo::new("Average Cell Temp".to_string(), "C".to_string()), - 121 => return DataInfo::new("High Cell Voltage Chip Number".to_string(), "".to_string()), - 122 => return DataInfo::new("High Cell Voltage Cell Number".to_string(), "".to_string()), - 123 => return DataInfo::new("Low Cell Voltage Chip Number".to_string(), "".to_string()), - 124 => return DataInfo::new("Low Cell Voltage Cell Number".to_string(), "".to_string()), - 125 => return DataInfo::new("Segment 1 Average Temperature".to_string(), "C".to_string()), - 126 => return DataInfo::new("Segment 2 Average Temperature".to_string(), "C".to_string()), - 127 => return DataInfo::new("Segment 3 Average Temperature".to_string(), "C".to_string()), - 128 => return DataInfo::new("Segment 4 Average Temperature".to_string(), "C".to_string()), - 129 => return DataInfo::new("Logging Status".to_string(), "".to_string()), - 130 => return DataInfo::new("Accumulator Fan Percentage".to_string(), "%".to_string()), - 131 => return DataInfo::new("Motor Fan Percentage".to_string(), "%".to_string()), - 132 => return DataInfo::new("Torque Limit Percentage".to_string(), "%".to_string()), - 133 => return DataInfo::new("Regen Strength value".to_string(), "".to_string()), - 134 => return DataInfo::new("Carger State".to_string(), "".to_string()), - 135 => return DataInfo::new("Measurement System Valid".to_string(), "".to_string()), - 136 => return DataInfo::new("System Status".to_string(), "".to_string()), - 137 => return DataInfo::new("Charge Status".to_string(), "".to_string()), - 138 => return DataInfo::new("ibat".to_string(), "A".to_string()), - 139 => return DataInfo::new("vbat".to_string(), "V".to_string()), - 140 => return DataInfo::new("vin".to_string(), "V".to_string()), - 141 => return DataInfo::new("vsys".to_string(), "V".to_string()), - 142 => return DataInfo::new("iin".to_string(), "A".to_string()), - 143 => return DataInfo::new("Cell Burning Status".to_string(), "".to_string()), - 144 => return DataInfo::new("Traction Control On".to_string(), "".to_string()), - 145 => return DataInfo::new("Precharge State".to_string(), "".to_string()), - 146 => return DataInfo::new("BMS Prefault Status".to_string(), "".to_string()), - _ => return DataInfo::new("".to_string(), "".to_string()), +pub fn get_message_info(id: &u32) -> MessageInfo { + match id { 0x80 => MessageInfo::new(decode_accumulator_status), + 0x81 => MessageInfo::new(decode_bms_status), + 0x82 => MessageInfo::new(decode_shutdown_control), + 0x83 => MessageInfo::new(decode_cell_data), + 0x84 => MessageInfo::new(decode_cell_temperatures), + 0x85 => MessageInfo::new(decode_segment_temperatures), + 0x500 => MessageInfo::new(decode_mpu_acceleromter), + 0x501 => MessageInfo::new(decode_mpu_status), + 0x680 => MessageInfo::new(decode_wheel_state), + _ => MessageInfo::new(decode_mock), } -} +} \ No newline at end of file diff --git a/src/message.rs b/src/message.rs index ecbfdb4..0a8c9be 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,41 +1,50 @@ - -use chrono::prelude::*; - use super::data::Data; -use super::master_mapping::get_message_info; -pub struct Message<'a> { - // Wrapper class for an individual message. - timestamp: DateTime, +use super::master_mapping::get_message_info; +/** + * Wrapper class for an individual message. + */ +pub struct Message { id: u32, - data: &'a [u8], + data: Vec, } -impl<'a> Message<'a> { - pub fn new(timestamp: &DateTime, id: &u32, data: &'a [u8]) -> Self { +/** + * Implementation of Message. + */ +impl Message { + /** + * Creates a new message with the given timestamp, id, and data. + */ + pub fn new(id: u32, data: Vec) -> Self { Self { - timestamp: *timestamp, - id: *id, + id, data, } } + + /** + * Decodes the message and returns a vector of Data objects. + */ pub fn decode(&self) -> Vec { - self.decode_message(&self.timestamp, &self.id, &self.data) + Message::decode_message(&self.id, &self.data) } - fn decode_message( - &self, - timestamp: &DateTime, - id: &u32, - data: &[u8], - ) -> Vec { + /** + * Decodes the message and returns a vector of Data objects. + * Achieves this by calling the decoder function associated with the message id. + * param timestamp: The timestamp of the message. + * param id: The id of the message. + * param data: The data of the message. + * return: A vector of Data objects. + */ + fn decode_message(id: &u32, data: &[u8]) -> Vec { let decoder = get_message_info(id).decoder; - println!("ATTEMPTING TO CUCK: {}", id); let mut decoded_data: Vec = Vec::new(); let result = decoder(data); - for (data_id, value) in result { - decoded_data.push(Data::new(*timestamp, data_id, value)); + for data in result { + decoded_data.push(data); } - return decoded_data; + decoded_data } } diff --git a/src/mqtt.rs b/src/mqtt.rs new file mode 100644 index 0000000..7ebe686 --- /dev/null +++ b/src/mqtt.rs @@ -0,0 +1,157 @@ +extern crate paho_mqtt as mqtt; +use mqtt::ServerResponse; +use std::time::Duration; +use std::{process, thread}; + +use crate::client::Client; +use crate::data::Data; + +pub const DFLT_BROKER: &str = "mqtt://localhost:1883"; +const DFLT_CLIENT: &str = "calypso"; + +/** + * MqttClient is a wrapper around the paho_mqtt::Client. + */ +pub struct MqttClient { + client: Option, +} + +/** + * Implement the Publish trait for MqttClient. + */ +impl Client for MqttClient { + /** + * Publishes the given data to the broker. + * param data: The data object to format and send. + */ + fn publish(&mut self, data: &Data) { + let topic = data.topic.to_string(); + let payload = data.to_json(); + + /* If the client is initialized, publish the data. */ + if let Some(client) = &self.client { + let msg = mqtt::MessageBuilder::new() + .topic(topic) + .payload(payload) + .finalize(); + + match { client.publish(msg) } { + Ok(_) => (), + Err(e) => println!("Error sending message: {:?}", e), + } + thread::sleep(Duration::from_millis(1)); + } else { + println!("Client not initialized, please set host first and connect") + } + } + + /** + * Connects to the broker. + * Sets the host and then connects + */ + fn connect(&mut self, host: &str) { + self.set_host(&host.to_string()); + self.connect(); + } +} + +impl Default for MqttClient { + /** + * Creates a new MqttClient. + */ + fn default() -> Self { + Self::new() + } +} + +/** + * Implementation of the MqttClient struct. + */ +impl MqttClient { + /** + * Creates a new MqttClient. + */ + pub fn new() -> MqttClient { + MqttClient { client: None } + } + + /** + * Creates a new MqttClient with the given host name. + * param host_name: The host name of the broker. + */ + fn set_host(&mut self, host_name: &String) { + let create_options = mqtt::CreateOptionsBuilder::new() + .server_uri(host_name) + .client_id(DFLT_CLIENT.to_string()) + .finalize(); + self.client = Some(match { mqtt::Client::new(create_options) } { + Ok(client) => client, + Err(e) => { + println!("Error creating the client: {:?}", e); + process::exit(1); + } + }); + } + + /** + * Connects to the broker. + * Sets the last will and testament. + */ + fn connect(&mut self) { + if let Some(client) = &self.client { + let lastwilltestatment = mqtt::MessageBuilder::new() + .topic("Calypso/Status") + .payload("Calypso is offline") + .finalize(); + let conn_opts = mqtt::ConnectOptionsBuilder::new() + .keep_alive_interval(Duration::from_secs(20)) + .clean_session(false) + .will_message(lastwilltestatment) + .finalize(); + if let Err(e) = client.connect(conn_opts) { + println!("Unable to connect:\n\t{:?}", e); + process::exit(1); + } + } else { + println!("Client not initialized, please set host first"); + } + } + + /** + * Check if the client is connected to the broker. + */ + fn _is_connected(&self) -> bool { + if let Some(client) = &self.client { + client.is_connected() + } else { + println!("Client not initialized, please set host first"); + false + } + } + + /** + * Reconnect to the broker. + */ + fn _reconnect(&mut self) -> Result { + if let Some(client) = &self.client { + client.reconnect() + } else { + Err(mqtt::Error::General( + "Client not initialized, please set host first", + )) + } + } + + /** + * Disconnect from the broker. + */ + fn _disconnect(&mut self) -> Result<(), mqtt::Error> { + if let Some(client) = &self.client { + client.disconnect(None) + } else { + Err(mqtt::Error::General( + "Client not initialized, please set host first", + )) + } + } +} diff --git a/src/telem_main.rs b/src/telem_main.rs deleted file mode 100644 index 3d3de3d..0000000 --- a/src/telem_main.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::fs::File; -use std::io::BufRead; -use std::io::BufReader; -use std::io::Write; -use rayon::prelude::*; -use chrono::prelude::*; - -use master_mapping::DATA_IDS; -use master_mapping::MESSAGE_IDS; - -mod master_mapping; - -const DEFAULT_LOGS_DIRECTORY: &str = "./logs/"; -const DEFAULT_OUTPUT_PATH: &str = "./output/"; -const PROCESSORS: u8 = 8; -const PROCESS_CHUNK_SIZE: u32 = 85000; //Processes data in chunks, specified by this variable - -fn get_line_count(filepaths: &[String]) -> usize { - /* - * Gets the total line count of all the files in the list. - - There is no native way to get line counts of files without looping, so - this function gets the total size and estimates the line count based - on a subset of N lines. - */ - if filepaths.is_empty() { - return 0; - } - - const n: u8 = 20; - let tested_lines: u8 = 0; - let tested_size: u8 = 0; - let total_size: u64 = filepaths.iter().map(|filepath| fs::metadata(filepath).unwrap().len()).sum(); - - for filepath in filepaths { - let file = File::open(filepath).unwrap(); - let reader = BufReader::new(file); - - for line in reader.lines() { - let line = line.unwrap(); - total_size += line.len(); - tested_lines += 1; - - if tested_lines >= n { - return (total_size / tested_lines) * (total_size / tested_lines); - } - } - } - return total_size / (tested_size / tested_lines) -} - -fn find_time(start: &[&str], finish: &[&str]) { - /* - * Prints the difference between the two times provided - Both inputs are lists of strings: - - minutes being the zeroth index of the list - - seconds being the first index of the list - - microseconds being the second index of the list - */ - - let start_minutes = start[0].parse::().unwrap(); - let start_seconds = start[1].parse::().unwrap(); - let start_microseconds = start[2].parse::().unwrap(); - - let finish_minutes = finish[0].parse::().unwrap(); - let finish_seconds = finish[1].parse::().unwrap(); - let finish_microseconds = finish[2].parse::().unwrap(); - - let mut minutes = finish_minutes - start_minutes; - let mut seconds = finish_seconds - start_seconds; - let mut microseconds = finish_microseconds - start_microseconds; - - if microseconds < 0 { - seconds -= 1; - microseconds += 1_000_000; - } - - if seconds < 0 { - minutes -= 1; - seconds += 60; - } - - println!("Time to process (Minutes:Seconds.Microseconds): {}:{}.{:06}", minutes, seconds, microseconds); -} - -fn process_lines(lines: &mut Vec, writer: &mut csv::Writer<&mut dyn Write>) { - /* - * Processes a chunk of lines and writes the results to the CSV. - */ - let out: Vec> = lines.par_iter().map(|line| thread(line)).collect(); - lines.clear(); - for data in out { - for sub_data in data { - let str_time = sub_data.timestamp.format("%Y-%m-%dT%H:%M:%S.%fZ").to_string(); - writer.write_record(&[&str_time, &sub_data.id.to_string(), &DATA_IDS[sub_data.id]["name"], &sub_data.value.to_string()]).unwrap(); - } - } -} diff --git a/src/thread.rs b/src/thread.rs deleted file mode 100644 index c2e2362..0000000 --- a/src/thread.rs +++ /dev/null @@ -1,9 +0,0 @@ -use decode_files::{LogFormat, processLine}; -use message::Message; - -const FORMAT: LogFormat = LogFormat::Textual1; - -fn thread(line: &str) -> Result, MessageFormatException> { - let message = processLine(line, FORMAT)?; - message.decode() -} \ No newline at end of file