From 636864d3bee25c636ea49d3d09220555f80b9fb9 Mon Sep 17 00:00:00 2001 From: Peyton McKee Date: Tue, 16 Jan 2024 14:44:14 -0500 Subject: [PATCH 01/14] 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 From 9037843d02781f4dd202b51cc8d332e1977a3353 Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Tue, 16 Jan 2024 14:49:12 -0500 Subject: [PATCH 02/14] Update Status Checks --- .github/workflows/rust-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index c1f2f67..2726935 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -4,9 +4,11 @@ on: push: branches: - main + - Develop pull_request: branches: - main + - Develop jobs: build: From 684379919866ee2c15e824486305b916ecd4d344 Mon Sep 17 00:00:00 2001 From: Peyton McKee Date: Tue, 30 Jan 2024 15:57:24 -0500 Subject: [PATCH 03/14] #31 Protobuf (#32) * #31 Protobuf * #31 Build * #31 List of Strings + Network Encoding --- Cargo.lock | 287 ++++++++++++++++++++- Cargo.toml | 2 + README.md | 15 ++ oxy/RustSynth.py | 25 +- oxy/YAMLParser.py | 4 +- oxy/can-messages/bms.yaml | 446 +++++++++++++++++---------------- oxy/can-messages/mpu.yaml | 88 ++++--- oxy/can-messages/wheel.yaml | 20 +- oxy/structs/CANField.py | 4 +- oxy/structs/CANMsg.py | 7 +- oxy/structs/NetworkEncoding.py | 23 ++ src/data.rs | 8 +- src/decode_data.rs | 184 +++++++++++--- src/lib.rs | 3 +- src/mqtt.rs | 11 +- src/proto/serverdata.proto | 8 + src/serverdata.rs | 209 +++++++++++++++ 17 files changed, 1018 insertions(+), 326 deletions(-) create mode 100644 oxy/structs/NetworkEncoding.py create mode 100644 src/proto/serverdata.proto create mode 100644 src/serverdata.rs diff --git a/Cargo.lock b/Cargo.lock index 1d655d7..c274e6f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" + [[package]] name = "async-channel" version = "1.9.0" @@ -25,11 +40,25 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "calypso" version = "0.1.0" dependencies = [ "paho-mqtt", + "protobuf", + "protobuf-codegen", "socketcan", ] @@ -85,12 +114,34 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "event-listener" version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "futures" version = "0.3.29" @@ -186,12 +237,37 @@ dependencies = [ "slab", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + [[package]] name = "hex" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6a22814455d41612f41161581c2883c0c6a1c41852729b17d5ed88f01e153aa" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "itertools" version = "0.4.19" @@ -200,9 +276,15 @@ checksum = "c4a9b56eb56058f43dc66e58f40a214b2ccbc9f3df51861b63d51dec7b65bc3f" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "log" @@ -222,10 +304,16 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" dependencies = [ - "bitflags", + "bitflags 0.4.0", "libc", ] +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "openssl-sys" version = "0.9.96" @@ -291,6 +379,57 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "protobuf" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" +dependencies = [ + "once_cell", + "protobuf-support", + "thiserror", +] + +[[package]] +name = "protobuf-codegen" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e85514a216b1c73111d9032e26cc7a5ecb1bb3d4d9539e91fb72a4395060f78" +dependencies = [ + "anyhow", + "once_cell", + "protobuf", + "protobuf-parse", + "regex", + "tempfile", + "thiserror", +] + +[[package]] +name = "protobuf-parse" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77d6fbd6697c9e531873e81cec565a85e226b99a0f10e1acc079be057fe2fcba" +dependencies = [ + "anyhow", + "indexmap", + "log", + "protobuf", + "protobuf-support", + "tempfile", + "thiserror", + "which", +] + +[[package]] +name = "protobuf-support" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" +dependencies = [ + "thiserror", +] + [[package]] name = "quote" version = "1.0.33" @@ -300,6 +439,57 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7fa1134405e2ec9353fd416b17f8dacd46c473d7d3fd1cf202706a14eb792a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustix" +version = "0.38.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + [[package]] name = "slab" version = "0.4.9" @@ -333,6 +523,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "thiserror" version = "1.0.50" @@ -370,3 +573,81 @@ name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +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.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" diff --git a/Cargo.toml b/Cargo.toml index 4aa1040..5fb2ade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,5 @@ edition = "2021" [dependencies] socketcan = "1.7.0" paho-mqtt = "0.12.3" +protobuf-codegen = "3.3.0" +protobuf = "3.3.0" \ No newline at end of file diff --git a/README.md b/README.md index 17995cc..73a6f7d 100644 --- a/README.md +++ b/README.md @@ -32,3 +32,18 @@ run ```/home/ner/Desktop/Calypso/target/release/calypso ipc /tmp/ipc.sock``` 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``` +### Generate code + +#### linux +`apt-get install protobuf-compiler -y` + +#### mac +`brew install protobuf` + +`cargo install protobuf-codegen` + +`PATH="$HOME/.cargo/bin:$PATH"` + +`protoc --rust_out ./src ./src/proto/serverdata.proto` + +delete the `mod.rs` file \ No newline at end of file diff --git a/oxy/RustSynth.py b/oxy/RustSynth.py index 6b93ad5..eb83664 100644 --- a/oxy/RustSynth.py +++ b/oxy/RustSynth.py @@ -2,6 +2,7 @@ from structs.CANMsg import CANMsg from structs.Messages import Messages from structs.Result import Result +from structs.NetworkEncoding import CSV, SinglePoint class RustSynth: ''' @@ -18,7 +19,7 @@ class RustSynth: decode_mock: str = """ pub fn decode_mock(_data: &[u8]) -> Vec:: { let result = vec![ - Data::new(0.0, "Mock", "") + Data::new(vec![0.0], "Mock", "") ]; result } @@ -71,11 +72,29 @@ 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.name, field.unit, f"{self.format_data(field, self.parse_decoders(field))}")) + generated_lines += self.parse_network_encoding(msg) total_list: list[str] = [signature, self.decode_return_value] + generated_lines + [self.decode_close] return "\n".join(total_list) + def parse_network_encoding(self, msg: CANMsg) -> list[str]: # Change return type to list[str] + result = [] + networkEncoding = msg.networkEncoding[0] + if networkEncoding.id == "csv": + result.append(f" {networkEncoding.start}") + result.append(f" {','.join(self.decode_field_value(field) for field in networkEncoding.fields)}") + result.append(f" {networkEncoding.closing}") + result.append(f" , \"{networkEncoding.topic}\", \"{networkEncoding.unit}\")") + elif networkEncoding.id == "single_point": + for field in networkEncoding.fields: + result.append(f" {networkEncoding.start}") + result.append(f" {self.decode_field_value(field)}") + result.append(f" {networkEncoding.closing}") + result.append(f" , \"{networkEncoding.topic}\", \"{networkEncoding.unit}\"), ") + return result + + def decode_field_value(self, field: CANField) -> str: + return f"{self.format_data(field, self.parse_decoders(field))}" + # 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()}" diff --git a/oxy/YAMLParser.py b/oxy/YAMLParser.py index c6dae1f..49d2363 100644 --- a/oxy/YAMLParser.py +++ b/oxy/YAMLParser.py @@ -4,6 +4,7 @@ from structs.Format import Format from structs.Decoding import Decoding from structs.Messages import Messages +from structs.NetworkEncoding import NetworkEncoding class YAMLParser: ''' @@ -16,9 +17,10 @@ def __init__(self): self.yaml.register_class(Messages) self.yaml.register_class(CANMsg) self.yaml.register_class(CANField) + for encoding in NetworkEncoding.__subclasses__(): + self.yaml.register_class(encoding) for decoding in Decoding.__subclasses__(): self.yaml.register_class(decoding) - 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 index cb95642..b906a3c 100644 --- a/oxy/can-messages/bms.yaml +++ b/oxy/can-messages/bms.yaml @@ -4,240 +4,252 @@ msgs: - !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 + networkEncoding: + - !SinglePoint + 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: "" + networkEncoding: + - !SinglePoint + 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: "" + networkEncoding: + - !SinglePoint + 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" + networkEncoding: + - !SinglePoint + 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 + networkEncoding: + - !SinglePoint + 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 + networkEncoding: + - !SinglePoint + 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 index f017731..b998c1e 100644 --- a/oxy/can-messages/mpu.yaml +++ b/oxy/can-messages/mpu.yaml @@ -3,49 +3,55 @@ msgs: - !CANMsg id: "0x500" desc: "MPU Acceleromter" - fields: - - !CANField - name: "MPU/Accel/X" + networkEncoding: + - !CSV + topic: "MPU/Accel" 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" + 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 + networkEncoding: + - !SinglePoint + 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 index 3f92646..0ccb60e 100644 --- a/oxy/can-messages/wheel.yaml +++ b/oxy/can-messages/wheel.yaml @@ -3,12 +3,14 @@ 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 + networkEncoding: + - !SinglePoint + 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/structs/CANField.py b/oxy/structs/CANField.py index 6ca53d1..dc6bdbc 100644 --- a/oxy/structs/CANField.py +++ b/oxy/structs/CANField.py @@ -2,16 +2,14 @@ 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, a unit, a size, - and an optional CorrectingFactor and Decodings. Also knows its own + and an optional Format and Decodings. Also knows its own index within its parent CANMsg, which is assigned at load from YAML. ''' - id: int name: str unit: str size: int diff --git a/oxy/structs/CANMsg.py b/oxy/structs/CANMsg.py index bdef91e..b199428 100644 --- a/oxy/structs/CANMsg.py +++ b/oxy/structs/CANMsg.py @@ -1,6 +1,8 @@ from __future__ import annotations from .CANField import CANField from dataclasses import dataclass +from .NetworkEncoding import NetworkEncoding +from ruamel.yaml import Optional @dataclass class CANMsg: @@ -9,15 +11,14 @@ class CANMsg: ''' id: str desc: str - fields: list[CANField] + networkEncoding: list[NetworkEncoding] def __post_init__(self) -> None: idx: int = 0 - for field in self.fields: + for field in self.networkEncoding[0].fields: if (field.index is not None): field.index = idx idx += field.size - def __setstate__(self, state): self.__init__(**state) diff --git a/oxy/structs/NetworkEncoding.py b/oxy/structs/NetworkEncoding.py new file mode 100644 index 0000000..e7ea217 --- /dev/null +++ b/oxy/structs/NetworkEncoding.py @@ -0,0 +1,23 @@ +from dataclasses import dataclass +from .CANField import CANField +from ruamel.yaml import Optional + +@dataclass +class NetworkEncoding: + ''' + Determines the format of the data to be sent over the network. + ''' + id: str + fields: list[CANField] + topic: Optional[str] = None + unit: Optional[str] = None + start: str = "Data::new(vec![" + closing: str = "]" + +@dataclass +class CSV(NetworkEncoding): + id = "csv" + +@dataclass +class SinglePoint(NetworkEncoding): + id = "single_point" diff --git a/src/data.rs b/src/data.rs index 058b524..a63d84f 100644 --- a/src/data.rs +++ b/src/data.rs @@ -4,7 +4,7 @@ use std::fmt; * Wrapper Class for Data coming off the car */ pub struct Data { - pub value: f32, + pub value: Vec, pub topic: String, pub unit: String, } @@ -18,7 +18,7 @@ impl fmt::Display for Data { write!( f, - "topic: {}, value: {}, unit: {}", + "topic: {}, value: {:#?}, unit: {}", self.topic, self.value, self.unit ) } @@ -34,7 +34,7 @@ impl 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 { + pub fn new(value: Vec, topic: &str, unit: &str) -> Self { Self { value, topic: topic.to_string(), @@ -43,7 +43,7 @@ impl Data { } pub fn to_json(&self) -> String { - format!("{{\"value\": {}, \"unit\": \"{}\"}}", self.value, self.unit) + format!("{{\"value\": {:#?}, \"unit\": \"{}\"}}", self.value, self.unit) } } diff --git a/src/decode_data.rs b/src/decode_data.rs index b283149..fe0760d 100644 --- a/src/decode_data.rs +++ b/src/decode_data.rs @@ -3,98 +3,204 @@ use super::data::{Data,FormatData as fd, ProcessData as pd}; pub fn decode_mock(_data: &[u8]) -> Vec:: { let result = vec![ - Data::new(0.0, "Mock", "") + Data::new(vec![0.0], "Mock", "") ]; result } 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", "%"), + Data::new(vec![ + fd::high_voltage(pd::big_endian(&data[0..2] as &[u8], 8) as f32) + ] + , "None", "None"), + Data::new(vec![ + fd::current(pd::twos_comp(pd::big_endian(&data[2..4] as &[u8], 8) as u32, 16) as f32) + ] + , "None", "None"), + Data::new(vec![ + pd::big_endian(&data[4..6] as &[u8], 8) as f32 + ] + , "None", "None"), + Data::new(vec![ + data[6] as f32 + ] + , "None", "None"), + Data::new(vec![ + data[7] as f32 + ] + , "None", "None"), ]; result } 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", ""), + Data::new(vec![ + data[0] as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::little_endian(&data[1..5] as &[u8], 8) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::twos_comp(data[5] as u32, 8) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::twos_comp(data[6] as u32, 8) as f32 + ] + , "None", "None"), + Data::new(vec![ + data[7] as f32 + ] + , "None", "None"), ]; result } pub fn decode_shutdown_control(data: &[u8]) -> Vec:: { let result = vec![ - Data::new(data[0] as f32, "BMS/Shutdown/MPE", ""), + Data::new(vec![ + data[0] as f32 + ] + , "None", "None"), ]; result } 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"), + Data::new(vec![ + fd::cell_voltage(pd::little_endian(&data[0..2] as &[u8], 8) as f32) + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[2] as u8, 4) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[3] as u8, 0) as f32 + ] + , "None", "None"), + Data::new(vec![ + fd::cell_voltage(pd::little_endian(&data[4..6] as &[u8], 8) as f32) + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[6] as u8, 4) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[7] as u8, 0) as f32 + ] + , "None", "None"), + Data::new(vec![ + fd::cell_voltage(pd::little_endian(&data[8..10] as &[u8], 8) as f32) + ] + , "None", "None"), ]; result } 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"), + Data::new(vec![ + pd::twos_comp(pd::little_endian(&data[0..2] as &[u8], 8) as u32, 16) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[2] as u8, 4) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[3] as u8, 0) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::twos_comp(pd::little_endian(&data[4..6] as &[u8], 8) as u32, 16) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[6] as u8, 4) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::half(data[7] as u8, 0) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::twos_comp(pd::little_endian(&data[8..10] as &[u8], 8) as u32, 16) as f32 + ] + , "None", "None"), ]; result } 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"), + Data::new(vec![ + pd::twos_comp(data[0] as u32, 8) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::twos_comp(data[1] as u32, 8) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::twos_comp(data[2] as u32, 8) as f32 + ] + , "None", "None"), + Data::new(vec![ + pd::twos_comp(data[3] as u32, 8) as f32 + ] + , "None", "None"), ]; result } 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"), + Data::new(vec![ + fd::acceleration(pd::big_endian(&data[0..2] as &[u8], 8) as f32),fd::acceleration(pd::big_endian(&data[2..4] as &[u8], 8) as f32),fd::acceleration(pd::big_endian(&data[4..6] as &[u8], 8) as f32) + ] + , "MPU/Accel", "g") ]; result } 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", ""), + Data::new(vec![ + data[0] as f32 + ] + , "None", "None"), + Data::new(vec![ + data[1] as f32 + ] + , "None", "None"), + Data::new(vec![ + data[2] as f32 + ] + , "None", "None"), + Data::new(vec![ + data[3] as f32 + ] + , "None", "None"), ]; result } 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", ""), + Data::new(vec![ + data[0] as f32 + ] + , "None", "None"), + Data::new(vec![ + data[1] as f32 + ] + , "None", "None"), ]; result } diff --git a/src/lib.rs b/src/lib.rs index 455565d..6c1f8a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,4 +3,5 @@ pub mod data; pub mod decode_data; pub mod message; pub mod mqtt; -pub mod master_mapping; \ No newline at end of file +pub mod master_mapping; +pub mod serverdata; \ No newline at end of file diff --git a/src/mqtt.rs b/src/mqtt.rs index 7ebe686..fc05f95 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -1,10 +1,12 @@ extern crate paho_mqtt as mqtt; use mqtt::ServerResponse; +use protobuf::Message; use std::time::Duration; use std::{process, thread}; use crate::client::Client; use crate::data::Data; +use crate::serverdata; pub const DFLT_BROKER: &str = "mqtt://localhost:1883"; const DFLT_CLIENT: &str = "calypso"; @@ -26,13 +28,18 @@ impl Client for MqttClient { */ fn publish(&mut self, data: &Data) { let topic = data.topic.to_string(); - let payload = data.to_json(); + let mut payload = serverdata::ServerData::new(); + payload.unit = data.unit.to_string(); + payload.value = data.value + .iter() + .map(|x| x.to_string()) + .collect(); /* If the client is initialized, publish the data. */ if let Some(client) = &self.client { let msg = mqtt::MessageBuilder::new() .topic(topic) - .payload(payload) + .payload(payload.write_to_bytes().unwrap()) .finalize(); match { client.publish(msg) } { diff --git a/src/proto/serverdata.proto b/src/proto/serverdata.proto new file mode 100644 index 0000000..98f812f --- /dev/null +++ b/src/proto/serverdata.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package serverdata.v1; + +message ServerData { + repeated string value = 1; + string unit = 2; +} diff --git a/src/serverdata.rs b/src/serverdata.rs new file mode 100644 index 0000000..30ca6a7 --- /dev/null +++ b/src/serverdata.rs @@ -0,0 +1,209 @@ +// This file is generated by rust-protobuf 3.3.0. Do not edit +// .proto file is parsed by protoc --rust-out=... +// @generated + +// https://github.com/rust-lang/rust-clippy/issues/702 +#![allow(unknown_lints)] +#![allow(clippy::all)] + +#![allow(unused_attributes)] +#![cfg_attr(rustfmt, rustfmt::skip)] + +#![allow(box_pointers)] +#![allow(dead_code)] +#![allow(missing_docs)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(trivial_casts)] +#![allow(unused_results)] +#![allow(unused_mut)] + +//! Generated file from `src/proto/serverdata.proto` + +/// Generated files are compatible only with the same version +/// of protobuf runtime. +const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_3_0; + +// @@protoc_insertion_point(message:serverdata.v1.ServerData) +#[derive(PartialEq,Clone,Default,Debug)] +pub struct ServerData { + // message fields + // @@protoc_insertion_point(field:serverdata.v1.ServerData.value) + pub value: ::std::vec::Vec<::std::string::String>, + // @@protoc_insertion_point(field:serverdata.v1.ServerData.unit) + pub unit: ::std::string::String, + // special fields + // @@protoc_insertion_point(special_field:serverdata.v1.ServerData.special_fields) + pub special_fields: ::protobuf::SpecialFields, +} + +impl<'a> ::std::default::Default for &'a ServerData { + fn default() -> &'a ServerData { + ::default_instance() + } +} + +impl ServerData { + pub fn new() -> ServerData { + ::std::default::Default::default() + } + + fn generated_message_descriptor_data() -> ::protobuf::reflect::GeneratedMessageDescriptorData { + let mut fields = ::std::vec::Vec::with_capacity(2); + let mut oneofs = ::std::vec::Vec::with_capacity(0); + fields.push(::protobuf::reflect::rt::v2::make_vec_simpler_accessor::<_, _>( + "value", + |m: &ServerData| { &m.value }, + |m: &mut ServerData| { &mut m.value }, + )); + fields.push(::protobuf::reflect::rt::v2::make_simpler_field_accessor::<_, _>( + "unit", + |m: &ServerData| { &m.unit }, + |m: &mut ServerData| { &mut m.unit }, + )); + ::protobuf::reflect::GeneratedMessageDescriptorData::new_2::( + "ServerData", + fields, + oneofs, + ) + } +} + +impl ::protobuf::Message for ServerData { + const NAME: &'static str = "ServerData"; + + fn is_initialized(&self) -> bool { + true + } + + fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream<'_>) -> ::protobuf::Result<()> { + while let Some(tag) = is.read_raw_tag_or_eof()? { + match tag { + 10 => { + self.value.push(is.read_string()?); + }, + 18 => { + self.unit = is.read_string()?; + }, + tag => { + ::protobuf::rt::read_unknown_or_skip_group(tag, is, self.special_fields.mut_unknown_fields())?; + }, + }; + } + ::std::result::Result::Ok(()) + } + + // Compute sizes of nested messages + #[allow(unused_variables)] + fn compute_size(&self) -> u64 { + let mut my_size = 0; + for value in &self.value { + my_size += ::protobuf::rt::string_size(1, &value); + }; + if !self.unit.is_empty() { + my_size += ::protobuf::rt::string_size(2, &self.unit); + } + my_size += ::protobuf::rt::unknown_fields_size(self.special_fields.unknown_fields()); + self.special_fields.cached_size().set(my_size as u32); + my_size + } + + fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::Result<()> { + for v in &self.value { + os.write_string(1, &v)?; + }; + if !self.unit.is_empty() { + os.write_string(2, &self.unit)?; + } + os.write_unknown_fields(self.special_fields.unknown_fields())?; + ::std::result::Result::Ok(()) + } + + fn special_fields(&self) -> &::protobuf::SpecialFields { + &self.special_fields + } + + fn mut_special_fields(&mut self) -> &mut ::protobuf::SpecialFields { + &mut self.special_fields + } + + fn new() -> ServerData { + ServerData::new() + } + + fn clear(&mut self) { + self.value.clear(); + self.unit.clear(); + self.special_fields.clear(); + } + + fn default_instance() -> &'static ServerData { + static instance: ServerData = ServerData { + value: ::std::vec::Vec::new(), + unit: ::std::string::String::new(), + special_fields: ::protobuf::SpecialFields::new(), + }; + &instance + } +} + +impl ::protobuf::MessageFull for ServerData { + fn descriptor() -> ::protobuf::reflect::MessageDescriptor { + static descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::Lazy::new(); + descriptor.get(|| file_descriptor().message_by_package_relative_name("ServerData").unwrap()).clone() + } +} + +impl ::std::fmt::Display for ServerData { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + ::protobuf::text_format::fmt(self, f) + } +} + +impl ::protobuf::reflect::ProtobufValue for ServerData { + type RuntimeType = ::protobuf::reflect::rt::RuntimeTypeMessage; +} + +static file_descriptor_proto_data: &'static [u8] = b"\ + \n\x1asrc/proto/serverdata.proto\x12\rserverdata.v1\"6\n\nServerData\x12\ + \x14\n\x05value\x18\x01\x20\x03(\tR\x05value\x12\x12\n\x04unit\x18\x02\ + \x20\x01(\tR\x04unitJ\xb0\x01\n\x06\x12\x04\0\0\x07\x01\n\x08\n\x01\x0c\ + \x12\x03\0\0\x12\n\x08\n\x01\x02\x12\x03\x02\0\x16\n\n\n\x02\x04\0\x12\ + \x04\x04\0\x07\x01\n\n\n\x03\x04\0\x01\x12\x03\x04\x08\x12\n\x0b\n\x04\ + \x04\0\x02\0\x12\x03\x05\x03\x1d\n\x0c\n\x05\x04\0\x02\0\x04\x12\x03\x05\ + \x03\x0b\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x05\x0c\x12\n\x0c\n\x05\x04\ + \0\x02\0\x01\x12\x03\x05\x13\x18\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x05\ + \x1b\x1c\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x06\x03\x13\n\x0c\n\x05\x04\0\ + \x02\x01\x05\x12\x03\x06\x03\t\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x06\ + \n\x0e\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x06\x11\x12b\x06proto3\ +"; + +/// `FileDescriptorProto` object which was a source for this generated file +fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto { + static file_descriptor_proto_lazy: ::protobuf::rt::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::Lazy::new(); + file_descriptor_proto_lazy.get(|| { + ::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap() + }) +} + +/// `FileDescriptor` object which allows dynamic access to files +pub fn file_descriptor() -> &'static ::protobuf::reflect::FileDescriptor { + static generated_file_descriptor_lazy: ::protobuf::rt::Lazy<::protobuf::reflect::GeneratedFileDescriptor> = ::protobuf::rt::Lazy::new(); + static file_descriptor: ::protobuf::rt::Lazy<::protobuf::reflect::FileDescriptor> = ::protobuf::rt::Lazy::new(); + file_descriptor.get(|| { + let generated_file_descriptor = generated_file_descriptor_lazy.get(|| { + let mut deps = ::std::vec::Vec::with_capacity(0); + let mut messages = ::std::vec::Vec::with_capacity(1); + messages.push(ServerData::generated_message_descriptor_data()); + let mut enums = ::std::vec::Vec::with_capacity(0); + ::protobuf::reflect::GeneratedFileDescriptor::new_generated( + file_descriptor_proto(), + deps, + messages, + enums, + ) + }); + ::protobuf::reflect::FileDescriptor::new_generated_2(generated_file_descriptor) + }) +} From 470a40b89d9d735fb156b00c8c8ceee19de6008d Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Thu, 22 Feb 2024 23:08:59 -0500 Subject: [PATCH 04/14] Fix Topic Synthesizing --- README.md | 7 ++++- oxy/RustSynth.py | 5 ++-- src/decode_data.rs | 70 +++++++++++++++++++++++----------------------- 3 files changed, 44 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 73a6f7d..baf331b 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,12 @@ run ```/home/ner/Desktop/Calypso/target/release/calypso ipc /tmp/ipc.sock``` 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``` -### Generate code +### Synthesize Rust +`cd oxy` + +`python3 typedpoc.py` + +### Generate Proto #### linux `apt-get install protobuf-compiler -y` diff --git a/oxy/RustSynth.py b/oxy/RustSynth.py index eb83664..c7a2d40 100644 --- a/oxy/RustSynth.py +++ b/oxy/RustSynth.py @@ -3,6 +3,7 @@ from structs.Messages import Messages from structs.Result import Result from structs.NetworkEncoding import CSV, SinglePoint +from typing import List class RustSynth: ''' @@ -46,7 +47,7 @@ class RustSynth: """ # 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: + def parse_messages(self, msgs: List[CANMsg]) -> Result: result = Result("", "") result.decode_data += self.ignore_clippy result.decode_data += self.decode_data_import @@ -89,7 +90,7 @@ def parse_network_encoding(self, msg: CANMsg) -> list[str]: # Change return typ result.append(f" {networkEncoding.start}") result.append(f" {self.decode_field_value(field)}") result.append(f" {networkEncoding.closing}") - result.append(f" , \"{networkEncoding.topic}\", \"{networkEncoding.unit}\"), ") + result.append(f" , \"{field.name}\", \"{field.unit}\"), ") return result def decode_field_value(self, field: CANField) -> str: diff --git a/src/decode_data.rs b/src/decode_data.rs index fe0760d..6612274 100644 --- a/src/decode_data.rs +++ b/src/decode_data.rs @@ -12,23 +12,23 @@ pub fn decode_accumulator_status(data: &[u8]) -> Vec:: { Data::new(vec![ fd::high_voltage(pd::big_endian(&data[0..2] as &[u8], 8) as f32) ] - , "None", "None"), + , "BMS/Pack/Voltage", "V"), Data::new(vec![ fd::current(pd::twos_comp(pd::big_endian(&data[2..4] as &[u8], 8) as u32, 16) as f32) ] - , "None", "None"), + , "BMS/Pack/Current", "A"), Data::new(vec![ pd::big_endian(&data[4..6] as &[u8], 8) as f32 ] - , "None", "None"), + , "BMS/Pack/Amp-hours", "Ah"), Data::new(vec![ data[6] as f32 ] - , "None", "None"), + , "BMS/Pack/SOC", "%"), Data::new(vec![ data[7] as f32 ] - , "None", "None"), + , "BMS/Pack/Health", "%"), ]; result } @@ -38,23 +38,23 @@ pub fn decode_bms_status(data: &[u8]) -> Vec:: { Data::new(vec![ data[0] as f32 ] - , "None", "None"), + , "BMS/State", ""), Data::new(vec![ pd::little_endian(&data[1..5] as &[u8], 8) as f32 ] - , "None", "None"), + , "BMS/Faults", ""), Data::new(vec![ pd::twos_comp(data[5] as u32, 8) as f32 ] - , "None", "None"), + , "BMS/Temps/Average", "C"), Data::new(vec![ pd::twos_comp(data[6] as u32, 8) as f32 ] - , "None", "None"), + , "BMS/Temps/Internal", "C"), Data::new(vec![ data[7] as f32 ] - , "None", "None"), + , "BMS/Cells/BurningStatus", ""), ]; result } @@ -64,7 +64,7 @@ pub fn decode_shutdown_control(data: &[u8]) -> Vec:: { Data::new(vec![ data[0] as f32 ] - , "None", "None"), + , "BMS/Shutdown/MPE", ""), ]; result } @@ -74,31 +74,31 @@ pub fn decode_cell_data(data: &[u8]) -> Vec:: { Data::new(vec![ fd::cell_voltage(pd::little_endian(&data[0..2] as &[u8], 8) as f32) ] - , "None", "None"), + , "BMS/Cells/Volts/High/Value", "V"), Data::new(vec![ pd::half(data[2] as u8, 4) as f32 ] - , "None", "None"), + , "BMS/Cells/Volts/High/Chip", ""), Data::new(vec![ pd::half(data[3] as u8, 0) as f32 ] - , "None", "None"), + , "BMS/Cells/Volts/High/Cell", ""), Data::new(vec![ fd::cell_voltage(pd::little_endian(&data[4..6] as &[u8], 8) as f32) ] - , "None", "None"), + , "BMS/Cells/Volts/Low/Value", "V"), Data::new(vec![ pd::half(data[6] as u8, 4) as f32 ] - , "None", "None"), + , "BMS/Cells/Volts/Low/Chip", ""), Data::new(vec![ pd::half(data[7] as u8, 0) as f32 ] - , "None", "None"), + , "BMS/Cells/Volts/Low/Cell", ""), Data::new(vec![ fd::cell_voltage(pd::little_endian(&data[8..10] as &[u8], 8) as f32) ] - , "None", "None"), + , "BMS/Cells/Volts/Avg/Value", "V"), ]; result } @@ -108,31 +108,31 @@ pub fn decode_cell_temperatures(data: &[u8]) -> Vec:: { Data::new(vec![ pd::twos_comp(pd::little_endian(&data[0..2] as &[u8], 8) as u32, 16) as f32 ] - , "None", "None"), + , "BMS/Cells/Temp/High/Value", "C"), Data::new(vec![ pd::half(data[2] as u8, 4) as f32 ] - , "None", "None"), + , "BMS/Cells/Temp/High/Cell", ""), Data::new(vec![ pd::half(data[3] as u8, 0) as f32 ] - , "None", "None"), + , "BMS/Cells/Temp/High/Chip", ""), Data::new(vec![ pd::twos_comp(pd::little_endian(&data[4..6] as &[u8], 8) as u32, 16) as f32 ] - , "None", "None"), + , "BMS/Cells/Temp/Low/Value", "C"), Data::new(vec![ pd::half(data[6] as u8, 4) as f32 ] - , "None", "None"), + , "BMS/Cells/Temp/Low/Cell", ""), Data::new(vec![ pd::half(data[7] as u8, 0) as f32 ] - , "None", "None"), + , "BMS/Cells/Temp/Low/Chip", ""), Data::new(vec![ pd::twos_comp(pd::little_endian(&data[8..10] as &[u8], 8) as u32, 16) as f32 ] - , "None", "None"), + , "BMS/Cells/Temp/Avg/Value", "C"), ]; result } @@ -142,19 +142,19 @@ pub fn decode_segment_temperatures(data: &[u8]) -> Vec:: { Data::new(vec![ pd::twos_comp(data[0] as u32, 8) as f32 ] - , "None", "None"), + , "BMS/Segment/Temp/1", "C"), Data::new(vec![ pd::twos_comp(data[1] as u32, 8) as f32 ] - , "None", "None"), + , "BMS/Segment/Temp/2", "C"), Data::new(vec![ pd::twos_comp(data[2] as u32, 8) as f32 ] - , "None", "None"), + , "BMS/Segment/Temp/3", "C"), Data::new(vec![ pd::twos_comp(data[3] as u32, 8) as f32 ] - , "None", "None"), + , "BMS/Segment/Temp/4", "C"), ]; result } @@ -174,19 +174,19 @@ pub fn decode_mpu_status(data: &[u8]) -> Vec:: { Data::new(vec![ data[0] as f32 ] - , "None", "None"), + , "MPU/State/Mode", ""), Data::new(vec![ data[1] as f32 ] - , "None", "None"), + , "MPU/State/Torque_Limit_Percentage", ""), Data::new(vec![ data[2] as f32 ] - , "None", "None"), + , "MPU/State/Regen_Strength", ""), Data::new(vec![ data[3] as f32 ] - , "None", "None"), + , "MPU/State/Traction_Control", ""), ]; result } @@ -196,11 +196,11 @@ pub fn decode_wheel_state(data: &[u8]) -> Vec:: { Data::new(vec![ data[0] as f32 ] - , "None", "None"), + , "WHEEL/Buttons/1", ""), Data::new(vec![ data[1] as f32 ] - , "None", "None"), + , "WHEEL/Buttons/2", ""), ]; result } From 52749af6421597b5c3683519d9ec8c2bfbeb6066 Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Thu, 22 Feb 2024 23:11:36 -0500 Subject: [PATCH 05/14] prettier --- src/mqtt.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/mqtt.rs b/src/mqtt.rs index fc05f95..eb7b5fa 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -30,10 +30,7 @@ impl Client for MqttClient { let topic = data.topic.to_string(); let mut payload = serverdata::ServerData::new(); payload.unit = data.unit.to_string(); - payload.value = data.value - .iter() - .map(|x| x.to_string()) - .collect(); + payload.value = data.value.iter().map(|x| x.to_string()).collect(); /* If the client is initialized, publish the data. */ if let Some(client) = &self.client { From e52872f2ff6e429e5190c6e57aec05e7cdf238cf Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Thu, 22 Feb 2024 23:14:12 -0500 Subject: [PATCH 06/14] Clippy --- src/mqtt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqtt.rs b/src/mqtt.rs index eb7b5fa..042e4b4 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -39,7 +39,7 @@ impl Client for MqttClient { .payload(payload.write_to_bytes().unwrap()) .finalize(); - match { client.publish(msg) } { + match client.publish(msg) { Ok(_) => (), Err(e) => println!("Error sending message: {:?}", e), } @@ -88,7 +88,7 @@ impl MqttClient { .server_uri(host_name) .client_id(DFLT_CLIENT.to_string()) .finalize(); - self.client = Some(match { mqtt::Client::new(create_options) } { + self.client = Some(match mqtt::Client::new(create_options) { Ok(client) => client, Err(e) => { println!("Error creating the client: {:?}", e); From 3f3a12834838448458cc47a21b904eca80daf3d4 Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Thu, 22 Feb 2024 23:24:07 -0500 Subject: [PATCH 07/14] Enforce Protobuf Measuring --- src/mqtt.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mqtt.rs b/src/mqtt.rs index 042e4b4..5f4ab31 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -36,7 +36,10 @@ impl Client for MqttClient { if let Some(client) = &self.client { let msg = mqtt::MessageBuilder::new() .topic(topic) - .payload(payload.write_to_bytes().unwrap()) + .payload( + protobuf::Message::write_to_bytes(&payload) + .unwrap_or("failed to serialize".as_bytes().to_vec()), + ) .finalize(); match client.publish(msg) { From 6768b1ee315870f86503d407a3768830aafc7ced Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Thu, 22 Feb 2024 23:26:25 -0500 Subject: [PATCH 08/14] Remove Unused Import --- src/mqtt.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/mqtt.rs b/src/mqtt.rs index 5f4ab31..e8aee09 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -1,6 +1,5 @@ extern crate paho_mqtt as mqtt; use mqtt::ServerResponse; -use protobuf::Message; use std::time::Duration; use std::{process, thread}; From d54ed4bd974e7443f67b222191d91fbdfa2befd9 Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Sun, 25 Feb 2024 20:27:03 -0500 Subject: [PATCH 09/14] Add Length Checker --- oxy/RustSynth.py | 78 ++++++--- oxy/structs/CANMsg.py | 2 - src/decode_data.rs | 370 +++++++++++++++++++++++------------------- src/mqtt.rs | 1 - 4 files changed, 253 insertions(+), 198 deletions(-) diff --git a/oxy/RustSynth.py b/oxy/RustSynth.py index c7a2d40..227eb81 100644 --- a/oxy/RustSynth.py +++ b/oxy/RustSynth.py @@ -1,21 +1,28 @@ from structs.CANField import CANField from structs.CANMsg import CANMsg -from structs.Messages import Messages from structs.Result import Result -from structs.NetworkEncoding import CSV, SinglePoint from typing import List + class RustSynth: - ''' + """ A class to synthesize Rust from a given CANMsg spec. - ''' - - 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 + """ + + 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:: { @@ -24,13 +31,19 @@ class RustSynth: ]; result } -""" # A mock decode function that is used for messages that don't have a decode function +""" # 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_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_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 + 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 { @@ -44,7 +57,7 @@ class RustSynth: } } } -""" # The MessageInfo struct that is used to store the decode function for a given message +""" # 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: List[CANMsg]) -> Result: @@ -66,33 +79,48 @@ def parse_messages(self, msgs: List[CANMsg]) -> 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" + 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) + length_check: str = self.add_length_check(msg.networkEncoding[0].fields) generated_lines: list[str] = [] # Generate a line for each field in the message generated_lines += self.parse_network_encoding(msg) - total_list: list[str] = [signature, self.decode_return_value] + generated_lines + [self.decode_close] + total_list: list[str] = ( + [signature, length_check, self.decode_return_value] + + generated_lines + + [self.decode_close] + ) return "\n".join(total_list) - def parse_network_encoding(self, msg: CANMsg) -> list[str]: # Change return type to list[str] + def parse_network_encoding( + self, msg: CANMsg + ) -> list[str]: # Change return type to list[str] result = [] networkEncoding = msg.networkEncoding[0] if networkEncoding.id == "csv": result.append(f" {networkEncoding.start}") - result.append(f" {','.join(self.decode_field_value(field) for field in networkEncoding.fields)}") + result.append( + f" {','.join(self.decode_field_value(field) for field in networkEncoding.fields)}" + ) result.append(f" {networkEncoding.closing}") - result.append(f" , \"{networkEncoding.topic}\", \"{networkEncoding.unit}\")") + result.append( + f' , "{networkEncoding.topic}", "{networkEncoding.unit}")' + ) elif networkEncoding.id == "single_point": for field in networkEncoding.fields: result.append(f" {networkEncoding.start}") result.append(f" {self.decode_field_value(field)}") result.append(f" {networkEncoding.closing}") - result.append(f" , \"{field.name}\", \"{field.unit}\"), ") + result.append(f' , "{field.name}", "{field.unit}"), ') return result + def add_length_check(self, fields: List[CANField]) -> str: + fieldSize = sum(field.size for field in fields) + return f"if data.len() < {fieldSize} {{ return vec![]; }}" + def decode_field_value(self, field: CANField) -> str: return f"{self.format_data(field, self.parse_decoders(field))}" @@ -106,7 +134,7 @@ def signature(self, desc: str) -> str: # 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}\")," + 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: @@ -116,14 +144,14 @@ def parse_decoders(self, field: CANField) -> str: base: str if field.size == 1: base = f"data[{field.index}]" - else : + else: base = f"&data[{field.index}..{field.index + field.size}]" for decoder in field.decodings: base = f"pd::{decoder.repr}({base} as {decoder.entry_type}, {decoder.bits})" return f"{base} as {field.final_type}" # 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: + def format_data(self, field: CANField, decoded_data: str) -> str: cf = decoded_data if field.format: cf = f"fd::{field.format}({decoded_data})" diff --git a/oxy/structs/CANMsg.py b/oxy/structs/CANMsg.py index b199428..ac50c8b 100644 --- a/oxy/structs/CANMsg.py +++ b/oxy/structs/CANMsg.py @@ -1,8 +1,6 @@ from __future__ import annotations -from .CANField import CANField from dataclasses import dataclass from .NetworkEncoding import NetworkEncoding -from ruamel.yaml import Optional @dataclass class CANMsg: diff --git a/src/decode_data.rs b/src/decode_data.rs index 6612274..2f9ac86 100644 --- a/src/decode_data.rs +++ b/src/decode_data.rs @@ -1,207 +1,237 @@ #![allow(clippy::all)] -use super::data::{Data,FormatData as fd, ProcessData as pd}; +use super::data::{Data, FormatData as fd, ProcessData as pd}; -pub fn decode_mock(_data: &[u8]) -> Vec:: { - let result = vec![ - Data::new(vec![0.0], "Mock", "") - ]; +pub fn decode_mock(_data: &[u8]) -> Vec { + let result = vec![Data::new(vec![0.0], "Mock", "")]; result } -pub fn decode_accumulator_status(data: &[u8]) -> Vec:: { +pub fn decode_accumulator_status(data: &[u8]) -> Vec { + if data.len() < 8 { + return vec![]; + } let result = vec![ - Data::new(vec![ - fd::high_voltage(pd::big_endian(&data[0..2] as &[u8], 8) as f32) - ] - , "BMS/Pack/Voltage", "V"), - Data::new(vec![ - 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(vec![ - pd::big_endian(&data[4..6] as &[u8], 8) as f32 - ] - , "BMS/Pack/Amp-hours", "Ah"), - Data::new(vec![ - data[6] as f32 - ] - , "BMS/Pack/SOC", "%"), - Data::new(vec![ - data[7] as f32 - ] - , "BMS/Pack/Health", "%"), - ]; + Data::new( + vec![fd::high_voltage( + pd::big_endian(&data[0..2] as &[u8], 8) as f32 + )], + "BMS/Pack/Voltage", + "V", + ), + Data::new( + vec![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( + vec![pd::big_endian(&data[4..6] as &[u8], 8) as f32], + "BMS/Pack/Amp-hours", + "Ah", + ), + Data::new(vec![data[6] as f32], "BMS/Pack/SOC", "%"), + Data::new(vec![data[7] as f32], "BMS/Pack/Health", "%"), + ]; result } -pub fn decode_bms_status(data: &[u8]) -> Vec:: { +pub fn decode_bms_status(data: &[u8]) -> Vec { + if data.len() < 8 { + return vec![]; + } let result = vec![ - Data::new(vec![ - data[0] as f32 - ] - , "BMS/State", ""), - Data::new(vec![ - pd::little_endian(&data[1..5] as &[u8], 8) as f32 - ] - , "BMS/Faults", ""), - Data::new(vec![ - pd::twos_comp(data[5] as u32, 8) as f32 - ] - , "BMS/Temps/Average", "C"), - Data::new(vec![ - pd::twos_comp(data[6] as u32, 8) as f32 - ] - , "BMS/Temps/Internal", "C"), - Data::new(vec![ - data[7] as f32 - ] - , "BMS/Cells/BurningStatus", ""), - ]; + Data::new(vec![data[0] as f32], "BMS/State", ""), + Data::new( + vec![pd::little_endian(&data[1..5] as &[u8], 8) as f32], + "BMS/Faults", + "", + ), + Data::new( + vec![pd::twos_comp(data[5] as u32, 8) as f32], + "BMS/Temps/Average", + "C", + ), + Data::new( + vec![pd::twos_comp(data[6] as u32, 8) as f32], + "BMS/Temps/Internal", + "C", + ), + Data::new(vec![data[7] as f32], "BMS/Cells/BurningStatus", ""), + ]; result } -pub fn decode_shutdown_control(data: &[u8]) -> Vec:: { - let result = vec![ - Data::new(vec![ - data[0] as f32 - ] - , "BMS/Shutdown/MPE", ""), - ]; +pub fn decode_shutdown_control(data: &[u8]) -> Vec { + if data.len() < 1 { + return vec![]; + } + let result = vec![Data::new(vec![data[0] as f32], "BMS/Shutdown/MPE", "")]; result } -pub fn decode_cell_data(data: &[u8]) -> Vec:: { +pub fn decode_cell_data(data: &[u8]) -> Vec { + if data.len() < 10 { + return vec![]; + } let result = vec![ - Data::new(vec![ - fd::cell_voltage(pd::little_endian(&data[0..2] as &[u8], 8) as f32) - ] - , "BMS/Cells/Volts/High/Value", "V"), - Data::new(vec![ - pd::half(data[2] as u8, 4) as f32 - ] - , "BMS/Cells/Volts/High/Chip", ""), - Data::new(vec![ - pd::half(data[3] as u8, 0) as f32 - ] - , "BMS/Cells/Volts/High/Cell", ""), - Data::new(vec![ - fd::cell_voltage(pd::little_endian(&data[4..6] as &[u8], 8) as f32) - ] - , "BMS/Cells/Volts/Low/Value", "V"), - Data::new(vec![ - pd::half(data[6] as u8, 4) as f32 - ] - , "BMS/Cells/Volts/Low/Chip", ""), - Data::new(vec![ - pd::half(data[7] as u8, 0) as f32 - ] - , "BMS/Cells/Volts/Low/Cell", ""), - Data::new(vec![ - fd::cell_voltage(pd::little_endian(&data[8..10] as &[u8], 8) as f32) - ] - , "BMS/Cells/Volts/Avg/Value", "V"), - ]; + Data::new( + vec![fd::cell_voltage( + pd::little_endian(&data[0..2] as &[u8], 8) as f32 + )], + "BMS/Cells/Volts/High/Value", + "V", + ), + Data::new( + vec![pd::half(data[2] as u8, 4) as f32], + "BMS/Cells/Volts/High/Chip", + "", + ), + Data::new( + vec![pd::half(data[3] as u8, 0) as f32], + "BMS/Cells/Volts/High/Cell", + "", + ), + Data::new( + vec![fd::cell_voltage( + pd::little_endian(&data[4..6] as &[u8], 8) as f32 + )], + "BMS/Cells/Volts/Low/Value", + "V", + ), + Data::new( + vec![pd::half(data[6] as u8, 4) as f32], + "BMS/Cells/Volts/Low/Chip", + "", + ), + Data::new( + vec![pd::half(data[7] as u8, 0) as f32], + "BMS/Cells/Volts/Low/Cell", + "", + ), + Data::new( + vec![fd::cell_voltage( + pd::little_endian(&data[8..10] as &[u8], 8) as f32, + )], + "BMS/Cells/Volts/Avg/Value", + "V", + ), + ]; result } -pub fn decode_cell_temperatures(data: &[u8]) -> Vec:: { +pub fn decode_cell_temperatures(data: &[u8]) -> Vec { + if data.len() < 10 { + return vec![]; + } let result = vec![ - Data::new(vec![ - 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(vec![ - pd::half(data[2] as u8, 4) as f32 - ] - , "BMS/Cells/Temp/High/Cell", ""), - Data::new(vec![ - pd::half(data[3] as u8, 0) as f32 - ] - , "BMS/Cells/Temp/High/Chip", ""), - Data::new(vec![ - 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(vec![ - pd::half(data[6] as u8, 4) as f32 - ] - , "BMS/Cells/Temp/Low/Cell", ""), - Data::new(vec![ - pd::half(data[7] as u8, 0) as f32 - ] - , "BMS/Cells/Temp/Low/Chip", ""), - Data::new(vec![ - pd::twos_comp(pd::little_endian(&data[8..10] as &[u8], 8) as u32, 16) as f32 - ] - , "BMS/Cells/Temp/Avg/Value", "C"), - ]; + Data::new( + vec![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( + vec![pd::half(data[2] as u8, 4) as f32], + "BMS/Cells/Temp/High/Cell", + "", + ), + Data::new( + vec![pd::half(data[3] as u8, 0) as f32], + "BMS/Cells/Temp/High/Chip", + "", + ), + Data::new( + vec![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( + vec![pd::half(data[6] as u8, 4) as f32], + "BMS/Cells/Temp/Low/Cell", + "", + ), + Data::new( + vec![pd::half(data[7] as u8, 0) as f32], + "BMS/Cells/Temp/Low/Chip", + "", + ), + Data::new( + vec![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_segment_temperatures(data: &[u8]) -> Vec:: { +pub fn decode_segment_temperatures(data: &[u8]) -> Vec { + if data.len() < 4 { + return vec![]; + } let result = vec![ - Data::new(vec![ - pd::twos_comp(data[0] as u32, 8) as f32 - ] - , "BMS/Segment/Temp/1", "C"), - Data::new(vec![ - pd::twos_comp(data[1] as u32, 8) as f32 - ] - , "BMS/Segment/Temp/2", "C"), - Data::new(vec![ - pd::twos_comp(data[2] as u32, 8) as f32 - ] - , "BMS/Segment/Temp/3", "C"), - Data::new(vec![ - pd::twos_comp(data[3] as u32, 8) as f32 - ] - , "BMS/Segment/Temp/4", "C"), - ]; + Data::new( + vec![pd::twos_comp(data[0] as u32, 8) as f32], + "BMS/Segment/Temp/1", + "C", + ), + Data::new( + vec![pd::twos_comp(data[1] as u32, 8) as f32], + "BMS/Segment/Temp/2", + "C", + ), + Data::new( + vec![pd::twos_comp(data[2] as u32, 8) as f32], + "BMS/Segment/Temp/3", + "C", + ), + Data::new( + vec![pd::twos_comp(data[3] as u32, 8) as f32], + "BMS/Segment/Temp/4", + "C", + ), + ]; result } -pub fn decode_mpu_acceleromter(data: &[u8]) -> Vec:: { - let result = vec![ - Data::new(vec![ - fd::acceleration(pd::big_endian(&data[0..2] as &[u8], 8) as f32),fd::acceleration(pd::big_endian(&data[2..4] as &[u8], 8) as f32),fd::acceleration(pd::big_endian(&data[4..6] as &[u8], 8) as f32) - ] - , "MPU/Accel", "g") - ]; +pub fn decode_mpu_acceleromter(data: &[u8]) -> Vec { + if data.len() < 6 { + return vec![]; + } + let result = vec![Data::new( + vec![ + fd::acceleration(pd::big_endian(&data[0..2] as &[u8], 8) as f32), + fd::acceleration(pd::big_endian(&data[2..4] as &[u8], 8) as f32), + fd::acceleration(pd::big_endian(&data[4..6] as &[u8], 8) as f32), + ], + "MPU/Accel", + "g", + )]; result } -pub fn decode_mpu_status(data: &[u8]) -> Vec:: { +pub fn decode_mpu_status(data: &[u8]) -> Vec { + if data.len() < 4 { + return vec![]; + } let result = vec![ - Data::new(vec![ - data[0] as f32 - ] - , "MPU/State/Mode", ""), - Data::new(vec![ - data[1] as f32 - ] - , "MPU/State/Torque_Limit_Percentage", ""), - Data::new(vec![ - data[2] as f32 - ] - , "MPU/State/Regen_Strength", ""), - Data::new(vec![ - data[3] as f32 - ] - , "MPU/State/Traction_Control", ""), - ]; + Data::new(vec![data[0] as f32], "MPU/State/Mode", ""), + Data::new( + vec![data[1] as f32], + "MPU/State/Torque_Limit_Percentage", + "", + ), + Data::new(vec![data[2] as f32], "MPU/State/Regen_Strength", ""), + Data::new(vec![data[3] as f32], "MPU/State/Traction_Control", ""), + ]; result } -pub fn decode_wheel_state(data: &[u8]) -> Vec:: { +pub fn decode_wheel_state(data: &[u8]) -> Vec { + if data.len() < 2 { + return vec![]; + } let result = vec![ - Data::new(vec![ - data[0] as f32 - ] - , "WHEEL/Buttons/1", ""), - Data::new(vec![ - data[1] as f32 - ] - , "WHEEL/Buttons/2", ""), - ]; + Data::new(vec![data[0] as f32], "WHEEL/Buttons/1", ""), + Data::new(vec![data[1] as f32], "WHEEL/Buttons/2", ""), + ]; result } - diff --git a/src/mqtt.rs b/src/mqtt.rs index e8aee09..38d2266 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -116,7 +116,6 @@ impl MqttClient { .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"); From 3c300d4d53785eb04816b9b0065c38ccd8952599 Mon Sep 17 00:00:00 2001 From: Nicholas DePatie <80368116+nwdepatie@users.noreply.github.com> Date: Thu, 29 Feb 2024 23:08:25 -0500 Subject: [PATCH 10/14] Got Rust autogeneration working with Rust prebuild hook! (#33) * Got Rust autogeneration working with Rust prebuild hook! * Adding in git admin for adding in autogen * Adding in a more verbose failure for autogen * Add automatic reconnect --------- Co-authored-by: Peyton-McKee --- .github/workflows/rust-ci.yml | 8 +- .gitignore | 2 + .gitmodules | 3 + Embedded-Base | 1 + build.rs | 20 +++ calypsogen.py | 35 +++++ oxy/RustSynth.py | 158 -------------------- oxy/YAMLParser.py | 26 ---- oxy/can-messages/bms.yaml | 255 --------------------------------- oxy/can-messages/mpu.yaml | 57 -------- oxy/can-messages/wheel.yaml | 16 --- oxy/structs/CANField.py | 19 --- oxy/structs/CANMsg.py | 22 --- oxy/structs/Decoding.py | 32 ----- oxy/structs/Format.py | 56 -------- oxy/structs/Messages.py | 9 -- oxy/structs/NetworkEncoding.py | 23 --- oxy/structs/Result.py | 13 -- oxy/typedpoc.py | 21 --- src/decode_data.rs | 237 ------------------------------ src/main.rs | 2 +- src/master_mapping.rs | 27 ---- src/mqtt.rs | 1 + 23 files changed, 70 insertions(+), 973 deletions(-) create mode 100644 .gitmodules create mode 160000 Embedded-Base create mode 100644 build.rs create mode 100644 calypsogen.py delete mode 100644 oxy/RustSynth.py delete mode 100644 oxy/YAMLParser.py delete mode 100644 oxy/can-messages/bms.yaml delete mode 100644 oxy/can-messages/mpu.yaml delete mode 100644 oxy/can-messages/wheel.yaml delete mode 100644 oxy/structs/CANField.py delete mode 100644 oxy/structs/CANMsg.py delete mode 100644 oxy/structs/Decoding.py delete mode 100644 oxy/structs/Format.py delete mode 100644 oxy/structs/Messages.py delete mode 100644 oxy/structs/NetworkEncoding.py delete mode 100644 oxy/structs/Result.py delete mode 100644 oxy/typedpoc.py delete mode 100644 src/decode_data.rs delete mode 100644 src/master_mapping.rs diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 2726935..2308ffd 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -16,7 +16,13 @@ jobs: steps: - name: Setup Rust - uses: actions/checkout@v2 + uses: actions/checkout@v3 + with: + submodules: recursive + - name: Install Python and Pip + run: sudo apt-get install python3 python3-pip + - name: Install ruamel.yaml + run: pip install "ruamel.yaml<0.18.0" - name: Install cargo-audit run: cargo install cargo-audit - name: Build diff --git a/.gitignore b/.gitignore index e22ff85..1fcceda 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ __pycache__/ build/ dist/ logs/ +master_mapping.rs +decode_data.rs diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..672744f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "Embedded-Base"] + path = Embedded-Base + url = git@github.com:Northeastern-Electric-Racing/Embedded-Base.git diff --git a/Embedded-Base b/Embedded-Base new file mode 160000 index 0000000..0c828dc --- /dev/null +++ b/Embedded-Base @@ -0,0 +1 @@ +Subproject commit 0c828dc580b36d8a8eb5f881307b1cfef78b4f9e diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..588a383 --- /dev/null +++ b/build.rs @@ -0,0 +1,20 @@ +use std::process::Command; + +/* Prebuild script */ +fn main() { + println!("cargo:rerun-if-env-changed=ALWAYS_RUN"); + + match Command::new("python3").arg("./calypsogen.py").status() { + Ok(status) if status.success() => { + println!("Python script executed successfully"); + }, + Ok(status) => { + eprintln!("Python script exited with status: {}", status); + std::process::exit(1); + }, + Err(e) => { + eprintln!("Failed to execute Python script: {}", e); + std::process::exit(1); + }, + } +} \ No newline at end of file diff --git a/calypsogen.py b/calypsogen.py new file mode 100644 index 0000000..07d6c09 --- /dev/null +++ b/calypsogen.py @@ -0,0 +1,35 @@ +import importlib.util +import sys + +# Full path to the directory containing the cangen module +EMBEDDED_BASE_PATH = "./Embedded-Base" +module_name = "cangen" + +# Full path to the cangen module file +module_path = f"{EMBEDDED_BASE_PATH}/{module_name}/__init__.py" + +# Add the cangen directory to the system's path +sys.path.append(EMBEDDED_BASE_PATH) + +# Load the module +spec = importlib.util.spec_from_file_location(module_name, module_path) +cangen = importlib.util.module_from_spec(spec) +spec.loader.exec_module(cangen) + +decode_data = open("./src/decode_data.rs", "w") +master_mapping = open("./src/master_mapping.rs", "w") + +bms_messages = cangen.YAMLParser().parse(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/bms.yaml", "r")) +mpu_messages = cangen.YAMLParser().parse(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/mpu.yaml", "r")) +wheel_messages = cangen.YAMLParser().parse(open(f"{EMBEDDED_BASE_PATH}/{module_name}/can-messages/wheel.yaml", "r")) + +bms_messages.msgs.extend(mpu_messages.msgs) +bms_messages.msgs.extend(wheel_messages.msgs) + +result = cangen.RustSynth().parse_messages(bms_messages.msgs) + +decode_data.write(result.decode_data) +decode_data.close() + +master_mapping.write(result.master_mapping) +master_mapping.close() diff --git a/oxy/RustSynth.py b/oxy/RustSynth.py deleted file mode 100644 index 227eb81..0000000 --- a/oxy/RustSynth.py +++ /dev/null @@ -1,158 +0,0 @@ -from structs.CANField import CANField -from structs.CANMsg import CANMsg -from structs.Result import Result -from typing import List - - -class RustSynth: - """ - A class to synthesize Rust from a given CANMsg spec. - """ - - 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(vec![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: List[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) - length_check: str = self.add_length_check(msg.networkEncoding[0].fields) - generated_lines: list[str] = [] - # Generate a line for each field in the message - generated_lines += self.parse_network_encoding(msg) - total_list: list[str] = ( - [signature, length_check, self.decode_return_value] - + generated_lines - + [self.decode_close] - ) - return "\n".join(total_list) - - def parse_network_encoding( - self, msg: CANMsg - ) -> list[str]: # Change return type to list[str] - result = [] - networkEncoding = msg.networkEncoding[0] - if networkEncoding.id == "csv": - result.append(f" {networkEncoding.start}") - result.append( - f" {','.join(self.decode_field_value(field) for field in networkEncoding.fields)}" - ) - result.append(f" {networkEncoding.closing}") - result.append( - f' , "{networkEncoding.topic}", "{networkEncoding.unit}")' - ) - elif networkEncoding.id == "single_point": - for field in networkEncoding.fields: - result.append(f" {networkEncoding.start}") - result.append(f" {self.decode_field_value(field)}") - result.append(f" {networkEncoding.closing}") - result.append(f' , "{field.name}", "{field.unit}"), ') - return result - - def add_length_check(self, fields: List[CANField]) -> str: - fieldSize = sum(field.size for field in fields) - return f"if data.len() < {fieldSize} {{ return vec![]; }}" - - def decode_field_value(self, field: CANField) -> str: - return f"{self.format_data(field, self.parse_decoders(field))}" - - # 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} {{" - - # 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 {field.final_type}" - else: - 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} as {decoder.entry_type}, {decoder.bits})" - return f"{base} as {field.final_type}" - - # 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 deleted file mode 100644 index 49d2363..0000000 --- a/oxy/YAMLParser.py +++ /dev/null @@ -1,26 +0,0 @@ -from ruamel.yaml import YAML, Any -from structs.CANMsg import CANMsg -from structs.CANField import CANField -from structs.Format import Format -from structs.Decoding import Decoding -from structs.Messages import Messages -from structs.NetworkEncoding import NetworkEncoding - -class YAMLParser: - ''' - A class to parse a given YAML string or file. Most of the heavy lifting - is done by the internals of ruamel.yaml. - ''' - - def __init__(self): - self.yaml = YAML() - self.yaml.register_class(Messages) - self.yaml.register_class(CANMsg) - self.yaml.register_class(CANField) - for encoding in NetworkEncoding.__subclasses__(): - self.yaml.register_class(encoding) - for decoding in Decoding.__subclasses__(): - self.yaml.register_class(decoding) - - 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 deleted file mode 100644 index b906a3c..0000000 --- a/oxy/can-messages/bms.yaml +++ /dev/null @@ -1,255 +0,0 @@ -!Messages -msgs: -#BMS BROADCAST -- !CANMsg - id: "0x80" - desc: "accumulator status" - networkEncoding: - - !SinglePoint - 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" - networkEncoding: - - !SinglePoint - 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" - networkEncoding: - - !SinglePoint - fields: - - !CANField - name: "BMS/Shutdown/MPE" - size: 1 - unit: "" - -- !CANMsg - id: "0x83" - desc: "Cell Data" - networkEncoding: - - !SinglePoint - 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" - networkEncoding: - - !SinglePoint - 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" - networkEncoding: - - !SinglePoint - 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 deleted file mode 100644 index b998c1e..0000000 --- a/oxy/can-messages/mpu.yaml +++ /dev/null @@ -1,57 +0,0 @@ -!Messages -msgs: -- !CANMsg - id: "0x500" - desc: "MPU Acceleromter" - networkEncoding: - - !CSV - topic: "MPU/Accel" - unit: "g" - 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" - networkEncoding: - - !SinglePoint - 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 deleted file mode 100644 index 0ccb60e..0000000 --- a/oxy/can-messages/wheel.yaml +++ /dev/null @@ -1,16 +0,0 @@ -!Messages -msgs: -- !CANMsg - id: "0x680" - desc: "Wheel State" - networkEncoding: - - !SinglePoint - 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/structs/CANField.py b/oxy/structs/CANField.py deleted file mode 100644 index dc6bdbc..0000000 --- a/oxy/structs/CANField.py +++ /dev/null @@ -1,19 +0,0 @@ -from __future__ import annotations -from .Decoding import * -from ruamel.yaml import Optional -from dataclasses import dataclass - -@dataclass -class CANField: - ''' - Represents a field in a CAN message. Has an id, a name, a unit, a size, - and an optional Format and Decodings. Also knows its own - index within its parent CANMsg, which is assigned at load from YAML. - ''' - name: str - unit: str - size: int - index: int = -1 - 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 deleted file mode 100644 index ac50c8b..0000000 --- a/oxy/structs/CANMsg.py +++ /dev/null @@ -1,22 +0,0 @@ -from __future__ import annotations -from dataclasses import dataclass -from .NetworkEncoding import NetworkEncoding - -@dataclass -class CANMsg: - ''' - Represents a CAN message. Has an id, a description, and a number of individual fields. - ''' - id: str - desc: str - networkEncoding: list[NetworkEncoding] - - def __post_init__(self) -> None: - idx: int = 0 - for field in self.networkEncoding[0].fields: - if (field.index is not None): - field.index = idx - idx += field.size - - def __setstate__(self, state): - self.__init__(**state) diff --git a/oxy/structs/Decoding.py b/oxy/structs/Decoding.py deleted file mode 100644 index bf05872..0000000 --- a/oxy/structs/Decoding.py +++ /dev/null @@ -1,32 +0,0 @@ -from dataclasses import dataclass - -@dataclass -class Decoding: - ''' - This is an abstract class (well, what passes for one in Python) - that represents a decoding to be applied to a slice of data. - ''' - bits: int - 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 deleted file mode 100644 index bb923dd..0000000 --- a/oxy/structs/Format.py +++ /dev/null @@ -1,56 +0,0 @@ -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 deleted file mode 100644 index eeac46e..0000000 --- a/oxy/structs/Messages.py +++ /dev/null @@ -1,9 +0,0 @@ -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/NetworkEncoding.py b/oxy/structs/NetworkEncoding.py deleted file mode 100644 index e7ea217..0000000 --- a/oxy/structs/NetworkEncoding.py +++ /dev/null @@ -1,23 +0,0 @@ -from dataclasses import dataclass -from .CANField import CANField -from ruamel.yaml import Optional - -@dataclass -class NetworkEncoding: - ''' - Determines the format of the data to be sent over the network. - ''' - id: str - fields: list[CANField] - topic: Optional[str] = None - unit: Optional[str] = None - start: str = "Data::new(vec![" - closing: str = "]" - -@dataclass -class CSV(NetworkEncoding): - id = "csv" - -@dataclass -class SinglePoint(NetworkEncoding): - id = "single_point" diff --git a/oxy/structs/Result.py b/oxy/structs/Result.py deleted file mode 100644 index 9b1d317..0000000 --- a/oxy/structs/Result.py +++ /dev/null @@ -1,13 +0,0 @@ - -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 deleted file mode 100644 index 052ab37..0000000 --- a/oxy/typedpoc.py +++ /dev/null @@ -1,21 +0,0 @@ -from YAMLParser import YAMLParser -from RustSynth import RustSynth - -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/src/decode_data.rs b/src/decode_data.rs deleted file mode 100644 index 2f9ac86..0000000 --- a/src/decode_data.rs +++ /dev/null @@ -1,237 +0,0 @@ -#![allow(clippy::all)] -use super::data::{Data, FormatData as fd, ProcessData as pd}; - -pub fn decode_mock(_data: &[u8]) -> Vec { - let result = vec![Data::new(vec![0.0], "Mock", "")]; - result -} -pub fn decode_accumulator_status(data: &[u8]) -> Vec { - if data.len() < 8 { - return vec![]; - } - let result = vec![ - Data::new( - vec![fd::high_voltage( - pd::big_endian(&data[0..2] as &[u8], 8) as f32 - )], - "BMS/Pack/Voltage", - "V", - ), - Data::new( - vec![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( - vec![pd::big_endian(&data[4..6] as &[u8], 8) as f32], - "BMS/Pack/Amp-hours", - "Ah", - ), - Data::new(vec![data[6] as f32], "BMS/Pack/SOC", "%"), - Data::new(vec![data[7] as f32], "BMS/Pack/Health", "%"), - ]; - result -} - -pub fn decode_bms_status(data: &[u8]) -> Vec { - if data.len() < 8 { - return vec![]; - } - let result = vec![ - Data::new(vec![data[0] as f32], "BMS/State", ""), - Data::new( - vec![pd::little_endian(&data[1..5] as &[u8], 8) as f32], - "BMS/Faults", - "", - ), - Data::new( - vec![pd::twos_comp(data[5] as u32, 8) as f32], - "BMS/Temps/Average", - "C", - ), - Data::new( - vec![pd::twos_comp(data[6] as u32, 8) as f32], - "BMS/Temps/Internal", - "C", - ), - Data::new(vec![data[7] as f32], "BMS/Cells/BurningStatus", ""), - ]; - result -} - -pub fn decode_shutdown_control(data: &[u8]) -> Vec { - if data.len() < 1 { - return vec![]; - } - let result = vec![Data::new(vec![data[0] as f32], "BMS/Shutdown/MPE", "")]; - result -} - -pub fn decode_cell_data(data: &[u8]) -> Vec { - if data.len() < 10 { - return vec![]; - } - let result = vec![ - Data::new( - vec![fd::cell_voltage( - pd::little_endian(&data[0..2] as &[u8], 8) as f32 - )], - "BMS/Cells/Volts/High/Value", - "V", - ), - Data::new( - vec![pd::half(data[2] as u8, 4) as f32], - "BMS/Cells/Volts/High/Chip", - "", - ), - Data::new( - vec![pd::half(data[3] as u8, 0) as f32], - "BMS/Cells/Volts/High/Cell", - "", - ), - Data::new( - vec![fd::cell_voltage( - pd::little_endian(&data[4..6] as &[u8], 8) as f32 - )], - "BMS/Cells/Volts/Low/Value", - "V", - ), - Data::new( - vec![pd::half(data[6] as u8, 4) as f32], - "BMS/Cells/Volts/Low/Chip", - "", - ), - Data::new( - vec![pd::half(data[7] as u8, 0) as f32], - "BMS/Cells/Volts/Low/Cell", - "", - ), - Data::new( - vec![fd::cell_voltage( - pd::little_endian(&data[8..10] as &[u8], 8) as f32, - )], - "BMS/Cells/Volts/Avg/Value", - "V", - ), - ]; - result -} - -pub fn decode_cell_temperatures(data: &[u8]) -> Vec { - if data.len() < 10 { - return vec![]; - } - let result = vec![ - Data::new( - vec![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( - vec![pd::half(data[2] as u8, 4) as f32], - "BMS/Cells/Temp/High/Cell", - "", - ), - Data::new( - vec![pd::half(data[3] as u8, 0) as f32], - "BMS/Cells/Temp/High/Chip", - "", - ), - Data::new( - vec![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( - vec![pd::half(data[6] as u8, 4) as f32], - "BMS/Cells/Temp/Low/Cell", - "", - ), - Data::new( - vec![pd::half(data[7] as u8, 0) as f32], - "BMS/Cells/Temp/Low/Chip", - "", - ), - Data::new( - vec![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_segment_temperatures(data: &[u8]) -> Vec { - if data.len() < 4 { - return vec![]; - } - let result = vec![ - Data::new( - vec![pd::twos_comp(data[0] as u32, 8) as f32], - "BMS/Segment/Temp/1", - "C", - ), - Data::new( - vec![pd::twos_comp(data[1] as u32, 8) as f32], - "BMS/Segment/Temp/2", - "C", - ), - Data::new( - vec![pd::twos_comp(data[2] as u32, 8) as f32], - "BMS/Segment/Temp/3", - "C", - ), - Data::new( - vec![pd::twos_comp(data[3] as u32, 8) as f32], - "BMS/Segment/Temp/4", - "C", - ), - ]; - result -} - -pub fn decode_mpu_acceleromter(data: &[u8]) -> Vec { - if data.len() < 6 { - return vec![]; - } - let result = vec![Data::new( - vec![ - fd::acceleration(pd::big_endian(&data[0..2] as &[u8], 8) as f32), - fd::acceleration(pd::big_endian(&data[2..4] as &[u8], 8) as f32), - fd::acceleration(pd::big_endian(&data[4..6] as &[u8], 8) as f32), - ], - "MPU/Accel", - "g", - )]; - result -} - -pub fn decode_mpu_status(data: &[u8]) -> Vec { - if data.len() < 4 { - return vec![]; - } - let result = vec![ - Data::new(vec![data[0] as f32], "MPU/State/Mode", ""), - Data::new( - vec![data[1] as f32], - "MPU/State/Torque_Limit_Percentage", - "", - ), - Data::new(vec![data[2] as f32], "MPU/State/Regen_Strength", ""), - Data::new(vec![data[3] as f32], "MPU/State/Traction_Control", ""), - ]; - result -} - -pub fn decode_wheel_state(data: &[u8]) -> Vec { - if data.len() < 2 { - return vec![]; - } - let result = vec![ - Data::new(vec![data[0] as f32], "WHEEL/Buttons/1", ""), - Data::new(vec![data[1] as f32], "WHEEL/Buttons/2", ""), - ]; - result -} diff --git a/src/main.rs b/src/main.rs index d776278..1322d74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ fn read_can(mut publisher: Box, can_interface: &str) { } }; loop { - let msg = match { socket.read_frame() } { + let msg = match socket.read_frame() { Ok(msg) => msg, Err(err) => { println!("Failed to read CAN frame: {}", err); diff --git a/src/master_mapping.rs b/src/master_mapping.rs deleted file mode 100644 index 8cb2016..0000000 --- a/src/master_mapping.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::decode_data::*; -use super::data::Data; - -pub struct MessageInfo { - pub decoder: fn(data: &[u8]) -> Vec, -} - -impl MessageInfo { - pub fn new(decoder: fn(data: &[u8]) -> Vec) -> Self { - Self { - decoder - } - } -} -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/mqtt.rs b/src/mqtt.rs index 38d2266..b20be6f 100644 --- a/src/mqtt.rs +++ b/src/mqtt.rs @@ -113,6 +113,7 @@ impl MqttClient { .keep_alive_interval(Duration::from_secs(20)) .clean_session(false) .will_message(lastwilltestatment) + .automatic_reconnect(Duration::from_secs(1), Duration::from_secs(30)) .finalize(); if let Err(e) = client.connect(conn_opts) { println!("Unable to connect:\n\t{:?}", e); From f79e731a4982b81631ca0bc4deca615fc200a164 Mon Sep 17 00:00:00 2001 From: Jack Rubacha Date: Mon, 4 Mar 2024 16:31:17 -0500 Subject: [PATCH 11/14] switch to https submodule --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 672744f..5ac4a66 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "Embedded-Base"] path = Embedded-Base - url = git@github.com:Northeastern-Electric-Racing/Embedded-Base.git + url = https://github.com/Northeastern-Electric-Racing/Embedded-Base.git From 721feb2d18af5addda598678b7ddca48c09c9a50 Mon Sep 17 00:00:00 2001 From: Peyton-McKee Date: Mon, 1 Apr 2024 14:08:35 -0400 Subject: [PATCH 12/14] Update Submodule --- Embedded-Base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Embedded-Base b/Embedded-Base index 0c828dc..d9062de 160000 --- a/Embedded-Base +++ b/Embedded-Base @@ -1 +1 @@ -Subproject commit 0c828dc580b36d8a8eb5f881307b1cfef78b4f9e +Subproject commit d9062debb8451a0acb4aa341d3930e2cebf200f2 From c1b51e29af9e568ec23f8ff2485cfbe7221b5854 Mon Sep 17 00:00:00 2001 From: msereeyothin Date: Thu, 4 Apr 2024 14:54:17 -0400 Subject: [PATCH 13/14] added dockerfile and changed some dependencies --- Cargo.toml | 4 ++-- Dockerfile | 16 ++++++++++++++++ src/serverdata.rs | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 Dockerfile diff --git a/Cargo.toml b/Cargo.toml index 5fb2ade..09b8664 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,5 @@ edition = "2021" [dependencies] socketcan = "1.7.0" paho-mqtt = "0.12.3" -protobuf-codegen = "3.3.0" -protobuf = "3.3.0" \ No newline at end of file +protobuf-codegen = "3.4.0" +protobuf = "3.4.0" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2704b93 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM rust:1.70 + +WORKDIR /usr/src/calypso + +COPY . . + +# install dependenices +RUN apt-get update && apt-get install -y python3-pip cmake +RUN pip install ruamel.yaml==0.18.5 +RUN cargo install --path . + + +# build the project +RUN cargo build --release + +CMD [ "calypso", "mqtt", "localhost:1883", "vcan0", "skip_can_configure" ] \ No newline at end of file diff --git a/src/serverdata.rs b/src/serverdata.rs index 30ca6a7..8f740b3 100644 --- a/src/serverdata.rs +++ b/src/serverdata.rs @@ -23,7 +23,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_3_0; +const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_4_0; // @@protoc_insertion_point(message:serverdata.v1.ServerData) #[derive(PartialEq,Clone,Default,Debug)] From b14f75b629e0ad3328c19242dc739fa057b0dd84 Mon Sep 17 00:00:00 2001 From: msereeyothin Date: Tue, 9 Apr 2024 13:18:36 -0400 Subject: [PATCH 14/14] reverted protobuf version and changed dockerfile --- Cargo.toml | 4 ++-- Dockerfile | 6 +++--- src/serverdata.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 09b8664..5fb2ade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,5 +8,5 @@ edition = "2021" [dependencies] socketcan = "1.7.0" paho-mqtt = "0.12.3" -protobuf-codegen = "3.4.0" -protobuf = "3.4.0" \ No newline at end of file +protobuf-codegen = "3.3.0" +protobuf = "3.3.0" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2704b93..e87becc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,15 +2,15 @@ FROM rust:1.70 WORKDIR /usr/src/calypso -COPY . . + # install dependenices RUN apt-get update && apt-get install -y python3-pip cmake RUN pip install ruamel.yaml==0.18.5 -RUN cargo install --path . # build the project +COPY . . RUN cargo build --release -CMD [ "calypso", "mqtt", "localhost:1883", "vcan0", "skip_can_configure" ] \ No newline at end of file +CMD [ "./target/release/calypso", "mqtt", "localhost:1883", "vcan0", "skip_can_configure" ] \ No newline at end of file diff --git a/src/serverdata.rs b/src/serverdata.rs index 8f740b3..30ca6a7 100644 --- a/src/serverdata.rs +++ b/src/serverdata.rs @@ -23,7 +23,7 @@ /// Generated files are compatible only with the same version /// of protobuf runtime. -const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_4_0; +const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_3_3_0; // @@protoc_insertion_point(message:serverdata.v1.ServerData) #[derive(PartialEq,Clone,Default,Debug)]