diff --git a/telemetry/Pipfile b/telemetry/Pipfile new file mode 100644 index 00000000..3aafc382 --- /dev/null +++ b/telemetry/Pipfile @@ -0,0 +1,15 @@ +[[source]] +url = "https://pypi.org/simple" +verify_ssl = true +name = "pypi" + +[packages] +pyqt5 = "*" +pyqtgraph = "*" + +[dev-packages] +ipykernel = "*" + +[requires] +python_version = "3.12" +python_full_version = "3.12.6" diff --git a/telemetry/Pipfile.lock b/telemetry/Pipfile.lock new file mode 100644 index 00000000..9d816a3c --- /dev/null +++ b/telemetry/Pipfile.lock @@ -0,0 +1,527 @@ +{ + "_meta": { + "hash": { + "sha256": "7fa924cd207dad9831f7f32611f0d2989f14c804054c5ec22a3b414b39980c52" + }, + "pipfile-spec": 6, + "requires": { + "python_full_version": "3.12.6", + "python_version": "3.12" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "numpy": { + "hashes": [ + "sha256:0391ea3622f5c51a2e29708877d56e3d276827ac5447d7f45e9bc4ade8923c52", + "sha256:12c045f43b1d2915eca6b880a7f4a256f59d62df4f044788c8ba67709412128d", + "sha256:136553f123ee2951bfcfbc264acd34a2fc2f29d7cdf610ce7daf672b6fbaa693", + "sha256:1402da8e0f435991983d0a9708b779f95a8c98c6b18a171b9f1be09005e64d9d", + "sha256:16372619ee728ed67a2a606a614f56d3eabc5b86f8b615c79d01957062826ca8", + "sha256:1ad78ce7f18ce4e7df1b2ea4019b5817a2f6a8a16e34ff2775f646adce0a5027", + "sha256:1b416af7d0ed3271cad0f0a0d0bee0911ed7eba23e66f8424d9f3dfcdcae1304", + "sha256:1f45315b2dc58d8a3e7754fe4e38b6fce132dab284a92851e41b2b344f6441c5", + "sha256:2376e317111daa0a6739e50f7ee2a6353f768489102308b0d98fcf4a04f7f3b5", + "sha256:23c9f4edbf4c065fddb10a4f6e8b6a244342d95966a48820c614891e5059bb50", + "sha256:246535e2f7496b7ac85deffe932896a3577be7af8fb7eebe7146444680297e9a", + "sha256:2e8da03bd561504d9b20e7a12340870dfc206c64ea59b4cfee9fceb95070ee94", + "sha256:34c1b7e83f94f3b564b35f480f5652a47007dd91f7c839f404d03279cc8dd021", + "sha256:39261798d208c3095ae4f7bc8eaeb3481ea8c6e03dc48028057d3cbdbdb8937e", + "sha256:3b787adbf04b0db1967798dba8da1af07e387908ed1553a0d6e74c084d1ceafe", + "sha256:3c2ec8a0f51d60f1e9c0c5ab116b7fc104b165ada3f6c58abf881cb2eb16044d", + "sha256:435e7a933b9fda8126130b046975a968cc2d833b505475e588339e09f7672890", + "sha256:4d8335b5f1b6e2bce120d55fb17064b0262ff29b459e8493d1785c18ae2553b8", + "sha256:4d9828d25fb246bedd31e04c9e75714a4087211ac348cb39c8c5f99dbb6683fe", + "sha256:52659ad2534427dffcc36aac76bebdd02b67e3b7a619ac67543bc9bfe6b7cdb1", + "sha256:5266de33d4c3420973cf9ae3b98b54a2a6d53a559310e3236c4b2b06b9c07d4e", + "sha256:5521a06a3148686d9269c53b09f7d399a5725c47bbb5b35747e1cb76326b714b", + "sha256:596140185c7fa113563c67c2e894eabe0daea18cf8e33851738c19f70ce86aeb", + "sha256:5b732c8beef1d7bc2d9e476dbba20aaff6167bf205ad9aa8d30913859e82884b", + "sha256:5ebeb7ef54a7be11044c33a17b2624abe4307a75893c001a4800857956b41094", + "sha256:712a64103d97c404e87d4d7c47fb0c7ff9acccc625ca2002848e0d53288b90ea", + "sha256:7678556eeb0152cbd1522b684dcd215250885993dd00adb93679ec3c0e6e091c", + "sha256:77974aba6c1bc26e3c205c2214f0d5b4305bdc719268b93e768ddb17e3fdd636", + "sha256:783145835458e60fa97afac25d511d00a1eca94d4a8f3ace9fe2043003c678e4", + "sha256:7bfdb06b395385ea9b91bf55c1adf1b297c9fdb531552845ff1d3ea6e40d5aba", + "sha256:7c8dde0ca2f77828815fd1aedfdf52e59071a5bae30dac3b4da2a335c672149a", + "sha256:83807d445817326b4bcdaaaf8e8e9f1753da04341eceec705c001ff342002e5d", + "sha256:87eed225fd415bbae787f93a457af7f5990b92a334e346f72070bf569b9c9c95", + "sha256:8fb62fe3d206d72fe1cfe31c4a1106ad2b136fcc1606093aeab314f02930fdf2", + "sha256:95172a21038c9b423e68be78fd0be6e1b97674cde269b76fe269a5dfa6fadf0b", + "sha256:9f48ba6f6c13e5e49f3d3efb1b51c8193215c42ac82610a04624906a9270be6f", + "sha256:a0c03b6be48aaf92525cccf393265e02773be8fd9551a2f9adbe7db1fa2b60f1", + "sha256:a5ae282abe60a2db0fd407072aff4599c279bcd6e9a2475500fc35b00a57c532", + "sha256:aee2512827ceb6d7f517c8b85aa5d3923afe8fc7a57d028cffcd522f1c6fd082", + "sha256:c8b0451d2ec95010d1db8ca733afc41f659f425b7f608af569711097fd6014e2", + "sha256:c9aa4496fd0e17e3843399f533d62857cef5900facf93e735ef65aa4bbc90ef0", + "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71", + "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787", + "sha256:cf802eef1f0134afb81fef94020351be4fe1d6681aadf9c5e862af6602af64ef", + "sha256:d42f9c36d06440e34226e8bd65ff065ca0963aeecada587b937011efa02cdc9d", + "sha256:d5b47c440210c5d1d67e1cf434124e0b5c395eee1f5806fdd89b553ed1acd0a3", + "sha256:d9b4a8148c57ecac25a16b0e11798cbe88edf5237b0df99973687dd866f05e1b", + "sha256:daf43a3d1ea699402c5a850e5313680ac355b4adc9770cd5cfc2940e7861f1bf", + "sha256:dbdc15f0c81611925f382dfa97b3bd0bc2c1ce19d4fe50482cb0ddc12ba30020", + "sha256:deaa09cd492e24fd9b15296844c0ad1b3c976da7907e1c1ed3a0ad21dded6f76", + "sha256:e37242f5324ffd9f7ba5acf96d774f9276aa62a966c0bad8dae692deebec7716", + "sha256:ed2cf9ed4e8ebc3b754d398cba12f24359f018b416c380f577bbae112ca52fc9", + "sha256:f2712c5179f40af9ddc8f6727f2bd910ea0eb50206daea75f58ddd9fa3f715bb", + "sha256:f4ca91d61a4bf61b0f2228f24bbfa6a9facd5f8af03759fe2a655c50ae2c6610", + "sha256:f6b3dfc7661f8842babd8ea07e9897fe3d9b69a1d7e5fbb743e4160f9387833b" + ], + "markers": "python_version >= '3.10'", + "version": "==2.2.3" + }, + "pyqt5": { + "hashes": [ + "sha256:6cd75628f6e732b1ffcfe709ab833a0716c0445d7aec8046a48d5843352becb6", + "sha256:76be0322ceda5deecd1708a8d628e698089a1cea80d1a49d242a6d579a40babd", + "sha256:bdde598a3bb95022131a5c9ea62e0a96bd6fb28932cc1619fd7ba211531b7517", + "sha256:c8b03dd9380bb13c804f0bdb0f4956067f281785b5e12303d529f0462f9afdc2", + "sha256:cd672a6738d1ae33ef7d9efa8e6cb0a1525ecf53ec86da80a9e1b6ec38c8d0f1", + "sha256:fda45743ebb4a27b4b1a51c6d8ef455c4c1b5d610c90d2934c7802b5c1557c52" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==5.15.11" + }, + "pyqt5-qt5": { + "hashes": [ + "sha256:18b6fec012de60921fcb131cf2a21368171dc29050d43e4b81a64be407a36105", + "sha256:2cfa8f50dd29618ef98f29355f83d8a5f3e41003be22128e9b5d94d214b6b468", + "sha256:5ee1754a6460849cba76c0f0c490c0ccc3b514abc780b141cf772db22b76b54b", + "sha256:e1a0e7ae35a7615c74a293705204579650930486a89af23082462f429dae504a" + ], + "version": "==5.15.16" + }, + "pyqt5-sip": { + "hashes": [ + "sha256:023466ae96f72fbb8419b44c3f97475de6642fa5632520d0f50fc1a52a3e8200", + "sha256:0c75d28b8282be3c1d7dbc76950d6e6eba1e334783224e9b9835ce1a9c64f482", + "sha256:2c912807dd638644168ea8c7a447bfd9d85a19471b98c2c588c4d2e911c09b0a", + "sha256:2f2a8dcc7626fe0da73a0918e05ce2460c7a14ddc946049310e6e35052105434", + "sha256:32b03e7e77ecd7b4119eba486b0706fa59b490bcceb585f9b6ddec8a582082db", + "sha256:351beab964a19f5671b2a3e816ecf4d3543a99a7e0650f88a947fea251a7589f", + "sha256:419b9027e92b0b707632c370cfc6dc1f3b43c6313242fc4db57a537029bd179c", + "sha256:4a92478d6808040fbe614bb61500fbb3f19f72714b99369ec28d26a7e3494115", + "sha256:54c31de7706d8a9a8c0fc3ea2c70468aba54b027d4974803f8eace9c22aad41c", + "sha256:5b6c734f4ad28f3defac4890ed747d391d246af279200935d49953bc7d915b8c", + "sha256:672c209d05661fab8e17607c193bf43991d268a1eefbc2c4551fbf30fd8bb2ca", + "sha256:682dadcdbd2239af9fdc0c0628e2776b820e128bec88b49b8d692fe682f90b4f", + "sha256:71514a7d43b44faa1d65a74ad2c5da92c03a251bdc749f009c313f06cceacc9a", + "sha256:855e8f5787d57e26a48d8c3de1220a8e92ab83be8d73966deac62fdae03ea2f9", + "sha256:8c4bc535bae0dfa764e8534e893619fe843ce5a2e25f901c439bcb960114f686", + "sha256:b0ff280b28813e9bfd3a4de99490739fc29b776dc48f1c849caca7239a10fc8b", + "sha256:c7a7ff355e369616b6bcb41d45b742327c104b2bf1674ec79b8d67f8f2fa9543", + "sha256:d65a9c1b4cbbd8e856254609f56e897d2cb5c903f77b75fb720cb3a32c76b92b", + "sha256:ea08341c8a5da00c81df0d689ecd4ee47a95e1ecad9e362581c92513f2068005", + "sha256:ec47914cc751608e587c1c2fdabeaf4af7fdc28b9f62796c583bea01c1a1aa3e", + "sha256:fb565469d08dcb0a427def0c45e722323beb62db79454260482b6948bfd52d47" + ], + "markers": "python_version >= '3.9'", + "version": "==12.17.0" + }, + "pyqtgraph": { + "hashes": [ + "sha256:64f84f1935c6996d0e09b1ee66fe478a7771e3ca6f3aaa05f00f6e068321d9e3", + "sha256:7754edbefb6c367fa0dfb176e2d0610da3ada20aa7a5318516c74af5fb72bf7a" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==0.13.7" + } + }, + "develop": { + "appnope": { + "hashes": [ + "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", + "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c" + ], + "markers": "platform_system == 'Darwin'", + "version": "==0.1.4" + }, + "asttokens": { + "hashes": [ + "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", + "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, + "comm": { + "hashes": [ + "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", + "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3" + ], + "markers": "python_version >= '3.8'", + "version": "==0.2.2" + }, + "debugpy": { + "hashes": [ + "sha256:086b32e233e89a2740c1615c2f775c34ae951508b28b308681dbbb87bba97d06", + "sha256:22a11c493c70413a01ed03f01c3c3a2fc4478fc6ee186e340487b2edcd6f4180", + "sha256:274b6a2040349b5c9864e475284bce5bb062e63dce368a394b8cc865ae3b00c6", + "sha256:2ae5df899732a6051b49ea2632a9ea67f929604fd2b036613a9f12bc3163b92d", + "sha256:36f4829839ef0afdfdd208bb54f4c3d0eea86106d719811681a8627ae2e53dd5", + "sha256:39dfbb6fa09f12fae32639e3286112fc35ae976114f1f3d37375f3130a820969", + "sha256:4703575b78dd697b294f8c65588dc86874ed787b7348c65da70cfc885efdf1e1", + "sha256:4ad9a94d8f5c9b954e0e3b137cc64ef3f579d0df3c3698fe9c3734ee397e4abb", + "sha256:557cc55b51ab2f3371e238804ffc8510b6ef087673303890f57a24195d096e61", + "sha256:5cc45235fefac57f52680902b7d197fb2f3650112379a6fa9aa1b1c1d3ed3f02", + "sha256:646530b04f45c830ceae8e491ca1c9320a2d2f0efea3141487c82130aba70dce", + "sha256:696d8ae4dff4cbd06bf6b10d671e088b66669f110c7c4e18a44c43cf75ce966f", + "sha256:7e94b643b19e8feb5215fa508aee531387494bf668b2eca27fa769ea11d9f498", + "sha256:88a77f422f31f170c4b7e9ca58eae2a6c8e04da54121900651dfa8e66c29901a", + "sha256:898fba72b81a654e74412a67c7e0a81e89723cfe2a3ea6fcd3feaa3395138ca9", + "sha256:9649eced17a98ce816756ce50433b2dd85dfa7bc92ceb60579d68c053f98dff9", + "sha256:9af40506a59450f1315168d47a970db1a65aaab5df3833ac389d2899a5d63b3f", + "sha256:a28ed481d530e3138553be60991d2d61103ce6da254e51547b79549675f539b7", + "sha256:a2ba7ffe58efeae5b8fad1165357edfe01464f9aef25e814e891ec690e7dd82a", + "sha256:a4042edef80364239f5b7b5764e55fd3ffd40c32cf6753da9bda4ff0ac466018", + "sha256:b0232cd42506d0c94f9328aaf0d1d0785f90f87ae72d9759df7e5051be039738", + "sha256:b202f591204023b3ce62ff9a47baa555dc00bb092219abf5caf0e3718ac20e7c", + "sha256:b5c6c967d02fee30e157ab5227706f965d5c37679c687b1e7bbc5d9e7128bd41", + "sha256:cbbd4149c4fc5e7d508ece083e78c17442ee13b0e69bfa6bd63003e486770f45", + "sha256:f30b03b0f27608a0b26c75f0bb8a880c752c0e0b01090551b9d87c7d783e2069", + "sha256:fdb3c6d342825ea10b90e43d7f20f01535a72b3a1997850c0c3cefa5c27a4a2c" + ], + "markers": "python_version >= '3.8'", + "version": "==1.8.12" + }, + "decorator": { + "hashes": [ + "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", + "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a" + ], + "markers": "python_version >= '3.8'", + "version": "==5.2.1" + }, + "executing": { + "hashes": [ + "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", + "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755" + ], + "markers": "python_version >= '3.8'", + "version": "==2.2.0" + }, + "ipykernel": { + "hashes": [ + "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", + "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.29.5" + }, + "ipython": { + "hashes": [ + "sha256:377ea91c8226b48dc9021ac9846a64761abc7ddf74c5efe38e6eb06f6e052f3a", + "sha256:3e878273824b52e0a2280ed84f8193aba8c4ba9a6f45a438348a3d5ef1a34bd0" + ], + "markers": "python_version >= '3.11'", + "version": "==9.0.1" + }, + "ipython-pygments-lexers": { + "hashes": [ + "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", + "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.1" + }, + "jedi": { + "hashes": [ + "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", + "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9" + ], + "markers": "python_version >= '3.6'", + "version": "==0.19.2" + }, + "jupyter-client": { + "hashes": [ + "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", + "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f" + ], + "markers": "python_version >= '3.8'", + "version": "==8.6.3" + }, + "jupyter-core": { + "hashes": [ + "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", + "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9" + ], + "markers": "python_version >= '3.8'", + "version": "==5.7.2" + }, + "matplotlib-inline": { + "hashes": [ + "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", + "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca" + ], + "markers": "python_version >= '3.8'", + "version": "==0.1.7" + }, + "nest-asyncio": { + "hashes": [ + "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", + "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c" + ], + "markers": "python_version >= '3.5'", + "version": "==1.6.0" + }, + "packaging": { + "hashes": [ + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" + ], + "markers": "python_version >= '3.8'", + "version": "==24.2" + }, + "parso": { + "hashes": [ + "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", + "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d" + ], + "markers": "python_version >= '3.6'", + "version": "==0.8.4" + }, + "pexpect": { + "hashes": [ + "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", + "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f" + ], + "markers": "sys_platform != 'win32' and sys_platform != 'emscripten'", + "version": "==4.9.0" + }, + "platformdirs": { + "hashes": [ + "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", + "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb" + ], + "markers": "python_version >= '3.8'", + "version": "==4.3.6" + }, + "prompt-toolkit": { + "hashes": [ + "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", + "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198" + ], + "markers": "python_full_version >= '3.8.0'", + "version": "==3.0.50" + }, + "psutil": { + "hashes": [ + "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", + "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e", + "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", + "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", + "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", + "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", + "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", + "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17", + "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", + "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99" + ], + "markers": "python_version >= '3.6'", + "version": "==7.0.0" + }, + "ptyprocess": { + "hashes": [ + "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", + "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220" + ], + "version": "==0.7.0" + }, + "pure-eval": { + "hashes": [ + "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", + "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42" + ], + "version": "==0.2.3" + }, + "pygments": { + "hashes": [ + "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", + "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==2.9.0.post0" + }, + "pyzmq": { + "hashes": [ + "sha256:000760e374d6f9d1a3478a42ed0c98604de68c9e94507e5452951e598ebecfba", + "sha256:004837cb958988c75d8042f5dac19a881f3d9b3b75b2f574055e22573745f841", + "sha256:0250c94561f388db51fd0213cdccbd0b9ef50fd3c57ce1ac937bf3034d92d72e", + "sha256:03719e424150c6395b9513f53a5faadcc1ce4b92abdf68987f55900462ac7eec", + "sha256:0995fd3530f2e89d6b69a2202e340bbada3191014352af978fa795cb7a446331", + "sha256:099b56ef464bc355b14381f13355542e452619abb4c1e57a534b15a106bf8e23", + "sha256:09dac387ce62d69bec3f06d51610ca1d660e7849eb45f68e38e7f5cf1f49cbcb", + "sha256:0b2007f28ce1b8acebdf4812c1aab997a22e57d6a73b5f318b708ef9bcabbe95", + "sha256:0b6a93d684278ad865fc0b9e89fe33f6ea72d36da0e842143891278ff7fd89c3", + "sha256:0f50db737d688e96ad2a083ad2b453e22865e7e19c7f17d17df416e91ddf67eb", + "sha256:100a826a029c8ef3d77a1d4c97cbd6e867057b5806a7276f2bac1179f893d3bf", + "sha256:1238c2448c58b9c8d6565579393148414a42488a5f916b3f322742e561f6ae0d", + "sha256:160194d1034902937359c26ccfa4e276abffc94937e73add99d9471e9f555dd6", + "sha256:17d72a74e5e9ff3829deb72897a175333d3ef5b5413948cae3cf7ebf0b02ecca", + "sha256:17f88622b848805d3f6427ce1ad5a2aa3cf61f12a97e684dab2979802024d460", + "sha256:1c6ae0e95d0a4b0cfe30f648a18e764352d5415279bdf34424decb33e79935b8", + "sha256:1c84c1297ff9f1cd2440da4d57237cb74be21fdfe7d01a10810acba04e79371a", + "sha256:1fd4b3efc6f62199886440d5e27dd3ccbcb98dfddf330e7396f1ff421bfbb3c2", + "sha256:25e720dba5b3a3bb2ad0ad5d33440babd1b03438a7a5220511d0c8fa677e102e", + "sha256:269c14904da971cb5f013100d1aaedb27c0a246728c341d5d61ddd03f463f2f3", + "sha256:290c96f479504439b6129a94cefd67a174b68ace8a8e3f551b2239a64cfa131a", + "sha256:2d88ba221a07fc2c5581565f1d0fe8038c15711ae79b80d9462e080a1ac30435", + "sha256:2e1eb9d2bfdf5b4e21165b553a81b2c3bd5be06eeddcc4e08e9692156d21f1f6", + "sha256:31fff709fef3b991cfe7189d2cfe0c413a1d0e82800a182cfa0c2e3668cd450f", + "sha256:361edfa350e3be1f987e592e834594422338d7174364763b7d3de5b0995b16f3", + "sha256:36d4e7307db7c847fe37413f333027d31c11d5e6b3bacbb5022661ac635942ba", + "sha256:36ee4297d9e4b34b5dc1dd7ab5d5ea2cbba8511517ef44104d2915a917a56dc8", + "sha256:380816d298aed32b1a97b4973a4865ef3be402a2e760204509b52b6de79d755d", + "sha256:3ef584f13820d2629326fe20cc04069c21c5557d84c26e277cfa6235e523b10f", + "sha256:3fe6e28a8856aea808715f7a4fc11f682b9d29cac5d6262dd8fe4f98edc12d53", + "sha256:44dba28c34ce527cf687156c81f82bf1e51f047838d5964f6840fd87dfecf9fe", + "sha256:45fad32448fd214fbe60030aa92f97e64a7140b624290834cc9b27b3a11f9473", + "sha256:46d4ebafc27081a7f73a0f151d0c38d4291656aa134344ec1f3d0199ebfbb6d4", + "sha256:49135bb327fca159262d8fd14aa1f4a919fe071b04ed08db4c7c37d2f0647162", + "sha256:4a98898fdce380c51cc3e38ebc9aa33ae1e078193f4dc641c047f88b8c690c9a", + "sha256:4eb3197f694dfb0ee6af29ef14a35f30ae94ff67c02076eef8125e2d98963cd0", + "sha256:51431f6b2750eb9b9d2b2952d3cc9b15d0215e1b8f37b7a3239744d9b487325d", + "sha256:574b285150afdbf0a0424dddf7ef9a0d183988eb8d22feacb7160f7515e032cb", + "sha256:57dd4d91b38fa4348e237a9388b4423b24ce9c1695bbd4ba5a3eada491e09399", + "sha256:59660e15c797a3b7a571c39f8e0b62a1f385f98ae277dfe95ca7eaf05b5a0f12", + "sha256:5b4fc44f5360784cc02392f14235049665caaf7c0fe0b04d313e763d3338e463", + "sha256:632a09c6d8af17b678d84df442e9c3ad8e4949c109e48a72f805b22506c4afa7", + "sha256:637536c07d2fb6a354988b2dd1d00d02eb5dd443f4bbee021ba30881af1c28aa", + "sha256:651726f37fcbce9f8dd2a6dab0f024807929780621890a4dc0c75432636871be", + "sha256:6991ee6c43e0480deb1b45d0c7c2bac124a6540cba7db4c36345e8e092da47ce", + "sha256:6d75fcb00a1537f8b0c0bb05322bc7e35966148ffc3e0362f0369e44a4a1de99", + "sha256:70b3a46ecd9296e725ccafc17d732bfc3cdab850b54bd913f843a0a54dfb2c04", + "sha256:786dd8a81b969c2081b31b17b326d3a499ddd1856e06d6d79ad41011a25148da", + "sha256:7c6160fe513654e65665332740f63de29ce0d165e053c0c14a161fa60dd0da01", + "sha256:7ebdd96bd637fd426d60e86a29ec14b8c1ab64b8d972f6a020baf08a30d1cf46", + "sha256:7f18ce33f422d119b13c1363ed4cce245b342b2c5cbbb76753eabf6aa6f69c7d", + "sha256:80a00370a2ef2159c310e662c7c0f2d030f437f35f478bb8b2f70abd07e26b24", + "sha256:817fcd3344d2a0b28622722b98500ae9c8bfee0f825b8450932ff19c0b15bebd", + "sha256:8531ed35dfd1dd2af95f5d02afd6545e8650eedbf8c3d244a554cf47d8924459", + "sha256:866c12b7c90dd3a86983df7855c6f12f9407c8684db6aa3890fc8027462bda82", + "sha256:88812b3b257f80444a986b3596e5ea5c4d4ed4276d2b85c153a6fbc5ca457ae7", + "sha256:8b0f5bab40a16e708e78a0c6ee2425d27e1a5d8135c7a203b4e977cee37eb4aa", + "sha256:8bacc1a10c150d58e8a9ee2b2037a70f8d903107e0f0b6e079bf494f2d09c091", + "sha256:8ec8e3aea6146b761d6c57fcf8f81fcb19f187afecc19bf1701a48db9617a217", + "sha256:8eddb3784aed95d07065bcf94d07e8c04024fdb6b2386f08c197dfe6b3528fda", + "sha256:9027a7fcf690f1a3635dc9e55e38a0d6602dbbc0548935d08d46d2e7ec91f454", + "sha256:90dc731d8e3e91bcd456aa7407d2eba7ac6f7860e89f3766baabb521f2c1de4a", + "sha256:91e2bfb8e9a29f709d51b208dd5f441dc98eb412c8fe75c24ea464734ccdb48e", + "sha256:95f5728b367a042df146cec4340d75359ec6237beebf4a8f5cf74657c65b9257", + "sha256:95f7b01b3f275504011cf4cf21c6b885c8d627ce0867a7e83af1382ebab7b3ff", + "sha256:97cbb368fd0debdbeb6ba5966aa28e9a1ae3396c7386d15569a6ca4be4572b99", + "sha256:9ec6abfb701437142ce9544bd6a236addaf803a32628d2260eb3dbd9a60e2891", + "sha256:9fbdb90b85c7624c304f72ec7854659a3bd901e1c0ffb2363163779181edeb68", + "sha256:a003200b6cd64e89b5725ff7e284a93ab24fd54bbac8b4fa46b1ed57be693c27", + "sha256:a0741edbd0adfe5f30bba6c5223b78c131b5aa4a00a223d631e5ef36e26e6d13", + "sha256:a23948554c692df95daed595fdd3b76b420a4939d7a8a28d6d7dea9711878641", + "sha256:a4bffcadfd40660f26d1b3315a6029fd4f8f5bf31a74160b151f5c577b2dc81b", + "sha256:a6549ecb0041dafa55b5932dcbb6c68293e0bd5980b5b99f5ebb05f9a3b8a8f3", + "sha256:a7ad34a2921e8f76716dc7205c9bf46a53817e22b9eec2e8a3e08ee4f4a72468", + "sha256:abf7b5942c6b0dafcc2823ddd9154f419147e24f8df5b41ca8ea40a6db90615c", + "sha256:b314268e716487bfb86fcd6f84ebbe3e5bec5fac75fdf42bc7d90fdb33f618ad", + "sha256:baa1da72aecf6a490b51fba7a51f1ce298a1e0e86d0daef8265c8f8f9848eb77", + "sha256:bd8fdee945b877aa3bffc6a5a8816deb048dab0544f9df3731ecd0e54d8c84c9", + "sha256:bdbc78ae2065042de48a65f1421b8af6b76a0386bb487b41955818c3c1ce7bed", + "sha256:c059883840e634a21c5b31d9b9a0e2b48f991b94d60a811092bc37992715146a", + "sha256:c1bb37849e2294d519117dd99b613c5177934e5c04a5bb05dd573fa42026567e", + "sha256:c2a9cb17fd83b7a3a3009901aca828feaf20aa2451a8a487b035455a86549c09", + "sha256:c7154d228502e18f30f150b7ce94f0789d6b689f75261b623f0fdc1eec642aab", + "sha256:cdb69710e462a38e6039cf17259d328f86383a06c20482cc154327968712273c", + "sha256:ceb0d78b7ef106708a7e2c2914afe68efffc0051dc6a731b0dbacd8b4aee6d68", + "sha256:d14f50d61a89b0925e4d97a0beba6053eb98c426c5815d949a43544f05a0c7ec", + "sha256:d51a7bfe01a48e1064131f3416a5439872c533d756396be2b39e3977b41430f9", + "sha256:d9da0289d8201c8a29fd158aaa0dfe2f2e14a181fd45e2dc1fbf969a62c1d594", + "sha256:e5e33b1491555843ba98d5209439500556ef55b6ab635f3a01148545498355e5", + "sha256:e76ad4729c2f1cf74b6eb1bdd05f6aba6175999340bd51e6caee49a435a13bf5", + "sha256:e7eeaef81530d0b74ad0d29eec9997f1c9230c2f27242b8d17e0ee67662c8f6e", + "sha256:e8e47050412f0ad3a9b2287779758073cbf10e460d9f345002d4779e43bb0136", + "sha256:ed038a921df836d2f538e509a59cb638df3e70ca0fcd70d0bf389dfcdf784d2a", + "sha256:edb550616f567cd5603b53bb52a5f842c0171b78852e6fc7e392b02c2a1504bb", + "sha256:ee7152f32c88e0e1b5b17beb9f0e2b14454235795ef68c0c120b6d3d23d12833", + "sha256:eeb37f65350d5c5870517f02f8bbb2ac0fbec7b416c0f4875219fef305a89a45", + "sha256:ef29630fde6022471d287c15c0a2484aba188adbfb978702624ba7a54ddfa6c1", + "sha256:ef5479fac31df4b304e96400fc67ff08231873ee3537544aa08c30f9d22fce38", + "sha256:f0019cc804ac667fb8c8eaecdb66e6d4a68acf2e155d5c7d6381a5645bd93ae4", + "sha256:f0f19c2097fffb1d5b07893d75c9ee693e9cbc809235cf3f2267f0ef6b015f24", + "sha256:f19dae58b616ac56b96f2e2290f2d18730a898a171f447f491cc059b073ca1fa", + "sha256:f1f31661a80cc46aba381bed475a9135b213ba23ca7ff6797251af31510920ce", + "sha256:f2c307fbe86e18ab3c885b7e01de942145f539165c3360e2af0f094dd440acd9", + "sha256:f32718ee37c07932cc336096dc7403525301fd626349b6eff8470fe0f996d8d7", + "sha256:f39d1227e8256d19899d953e6e19ed2ccb689102e6d85e024da5acf410f301eb", + "sha256:f5eeeb82feec1fc5cbafa5ee9022e87ffdb3a8c48afa035b356fcd20fc7f533f", + "sha256:f92a002462154c176dac63a8f1f6582ab56eb394ef4914d65a9417f5d9fde218", + "sha256:f9ba5def063243793dec6603ad1392f735255cbc7202a3a484c14f99ec290705", + "sha256:fc409c18884eaf9ddde516d53af4f2db64a8bc7d81b1a0c274b8aa4e929958e8" + ], + "markers": "python_version >= '3.7'", + "version": "==26.2.1" + }, + "six": { + "hashes": [ + "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", + "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "version": "==1.17.0" + }, + "stack-data": { + "hashes": [ + "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", + "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695" + ], + "version": "==0.6.3" + }, + "tornado": { + "hashes": [ + "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", + "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", + "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", + "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", + "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", + "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", + "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", + "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", + "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", + "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", + "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1" + ], + "markers": "python_version >= '3.8'", + "version": "==6.4.2" + }, + "traitlets": { + "hashes": [ + "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", + "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f" + ], + "markers": "python_version >= '3.8'", + "version": "==5.14.3" + }, + "wcwidth": { + "hashes": [ + "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", + "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5" + ], + "version": "==0.2.13" + } + } +} diff --git a/telemetry/README.md b/telemetry/README.md new file mode 100644 index 00000000..e3b27504 --- /dev/null +++ b/telemetry/README.md @@ -0,0 +1,89 @@ +# Q25FSAE Telemetry System + +## Overview + +This telemetry system is designed for the Q25FSAE race car, utilizing PyQt5 for a graphical interface and a simulated data generator. The goal of the system is to provide a live look at the state of the car during testing, for development and optimization purposes. Right now, the system is a demonstration consisting of three main components: + +- **`gui.py`**: Provides the graphical user interface (GUI) for displaying telemetry data. +- **`main.py`**: Initializes and runs the telemetry system. +- **`demo_data.py`**: Generates simulated telemetry data for testing. + +## Running the Project + +This project uses `pipenv` for dependency management. To set up and run the telemetry system, follow these steps: + +### 1. Install `pipenv` +If you don’t already have `pipenv` installed, you can install it using: +```sh +pip install pipenv +``` +### 2. Set Up the Virtual Environment + +Navigate to the project directory and install dependencies using pipenv: + +```sh +pipenv install +``` +This will create a virtual environment and install all necessary dependencies as specified in Pipfile and Pipfile.lock. + +### 3. Activate the Virtual Environment +To enter the virtual environment, run: +```sh +pipenv shell +``` +### 4. Run the Telemetry System +Once inside the virtual environment, start the telemetry system with: + +```sh +python telemetry/main.py +``` +To run the system with simulated telemetry data, use: + +```sh +python telemetry/main.py --demo +``` +This will launch the GUI and display real-time demo data. + + +## Structure and Functionality + +### `gui.py` +This module defines `TelemetryWindow`, the main GUI for displaying telemetry data. It includes: + +- **PyQt5-based graphical layout** for real-time data visualization. +- **Dial-based and graphical telemetry displays**. +- **CSV logging capability**: Allows data logging for later analysis. +- **Warning sounds** for critical values. + +### `demo_data.py` +This module defines `DemoDataThread`, a background thread that generates fake telemetry data for testing purposes. + +- **QThread for asynchronous data generation**: Avoids blocking the GUI while generating telemetry data. +- **Simulated telemetry values**: + - State of Charge (SOC) + - Voltage, Current, Power + - Temperature readings +- **Data emission via PyQt5 signals**: Updates the GUI in real time. + +### `main.py` +This module serves as the entry point for the telemetry system. It: + +- Initializes the PyQt5 application. +- Loads the `TelemetryWindow` GUI. +- Starts `DemoDataThread` when the `--demo` flag is provided. +- Ensures smooth interaction between the GUI and data processing threads. + +## Future Improvements + +The system currently relies on simulated data. To integrate real-time data from the Q25FSAE race car: + +1. We should implement a **server-client architecture** using sockets. This will simulate the data transmission that will be happening on the car, since we will be connecting this GUI to a WIFI hotspot on the car, and communictaing over sockets. + +2. Create a **CAN data translation module** to translate CAN data sent from the car over WIFI to the GUI's client socket. This will parse real CAN messages to extract telemetry data, and implement **data filtering** to remove noise and excess data. + +### 3. **Performance Enhancements** +Currently, the system relies on **single-threaded GUI updates**. The following optimizations are recommended: + +- **Optimize PyQt5 threading** to prevent lag in high-frequency telemetry updates. +- **Implement an efficient data buffer** for handling incoming messages. +- **Reduce GUI overhead** by updating only changed significantly changed values instead of refreshing the entire display with new data every 50ms. diff --git a/telemetry/assets/lowBattery.wav b/telemetry/assets/lowBattery.wav new file mode 100644 index 00000000..9eb4c83f Binary files /dev/null and b/telemetry/assets/lowBattery.wav differ diff --git a/telemetry/demo_data.py b/telemetry/demo_data.py new file mode 100644 index 00000000..8a55becb --- /dev/null +++ b/telemetry/demo_data.py @@ -0,0 +1,56 @@ +import socket +import json +import time +import math +import random + +class DemoDataThread: + def __init__(self, server_ip="127.0.0.1", server_port=9999): + self.server_ip = server_ip + self.server_port = server_port + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.time_counter = 0.0 + self.soc_value = 100.0 + + def generate_data_dict(self): + # Sine wave for throttle (0 to 100) + throttle = int((math.sin(self.time_counter * 0.5) + 1) * 50) + + # Cosine wave for brake (0 to 100) + brake = int((math.cos(self.time_counter * 0.5) + 1) * 50) + + # Cell temperatures: ~30 +/- 5 using sine waves + cell_temps = [30 + 5 * math.sin(self.time_counter + i) for i in range(4)] + + # Decrement SOC slowly but never below 0 + self.soc_value = max(0, self.soc_value - 0.02) + + # Wheel speed: cyclical in range ~0 to 100 + wheel_speed = abs(100 * math.sin(self.time_counter * 0.3)) + + # Speed as a simple function of throttle + speed = int(throttle * 0.5) + + # Fault: 1% random chance + fault = 1 if random.random() < 0.01 else 0 + + return { + "time": self.time_counter, + "cell_temps": cell_temps, + "soc": self.soc_value, + "wheel_speed": wheel_speed, + "throttle": throttle, + "brake": brake, + "speed": speed, + "fault": fault + } + + def start(self): + while True: + data = self.generate_data_dict() + self.socket.sendto(json.dumps(data).encode("utf-8"), (self.server_ip, self.server_port)) + time.sleep(0.05) + self.time_counter += 0.05 + +if __name__ == "__main__": + DemoDataThread().start() \ No newline at end of file diff --git a/telemetry/gui.py b/telemetry/gui.py new file mode 100644 index 00000000..7fd532b2 --- /dev/null +++ b/telemetry/gui.py @@ -0,0 +1,340 @@ +import csv +from datetime import datetime + +from PyQt5.QtCore import QTimer, Qt, pyqtSlot, QUrl +from PyQt5.QtMultimedia import QSoundEffect +from PyQt5.QtWidgets import ( + QMainWindow, QWidget, QLabel, QVBoxLayout, QHBoxLayout, QDial, + QFrame, QPushButton, QApplication +) +import pyqtgraph as pg + +num_BMS_sensors = 4 + +class TelemetryWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("QFSAE 25 Telemetry") + self.resize(2400, 1800) # Set size of window + + # Data storage for current plots + self.time_data = [] + self.cell_temp_data = [[] for _ in range(num_BMS_sensors)] + self.soc_data = [] + self.wheel_speed_data = [] + self.throttle_data = [] + self.brake_data = [] + + # Time offset to make the next incoming timestamp zero after reset. + self.time_offset = 0.0 + self.last_raw_time = 0.0 + + # Start the timer for the data. + self.startTime = 0.0 + + # Initialize the UI + self.initUI() + + # Timer to refresh the plots every 50 ms + self.timer = QTimer() + self.timer.timeout.connect(self.updatePlots) + self.timer.start(50) + + # Elapsed time timer (update every 50 ms) + self.elapsedTimer = QTimer() + self.elapsedTimer.timeout.connect(self.updateElapsedTime) + self.elapsedTimer.start(50) + + # Low battery sound + self.beepSound = QSoundEffect() + self.beepSound.setSource(QUrl.fromLocalFile("assets/lowBattery.wav")) + + def initUI(self): + # The main widget is a box in the middle + centralWidget = QWidget() + self.setCentralWidget(centralWidget) + self.mainLayout = QVBoxLayout() + centralWidget.setLayout(self.mainLayout) + + # Elapsed time label at top-right + headerLayout = QHBoxLayout() + headerLayout.addStretch() + self.elapsedTimeLabel = QLabel("Elapsed: 0.0 s") + headerLayout.addWidget(self.elapsedTimeLabel) + self.mainLayout.addLayout(headerLayout) + + # Row 1: Cell Temperatures and Wheel Speed (each plot with a statistic label) + row1 = QHBoxLayout() + self.cellTempPlot, self.cellTempStat = self.createPlotWithStat("Accumulator Cell Temperatures") + row1.addWidget(self.wrapPlotAndStat(self.cellTempPlot, self.cellTempStat)) + self.wheelSpeedPlot, self.wheelSpeedStat = self.createPlotWithStat("Wheel Speed") + row1.addWidget(self.wrapPlotAndStat(self.wheelSpeedPlot, self.wheelSpeedStat)) + self.mainLayout.addLayout(row1) + + # Row 2: SOC Plot (y-axis fixed to 0-100, full timeline) + self.socPlot, self.socStat = self.createPlotWithStat("BMS State of Charge (%)") + self.socPlot.setYRange(0, 100) + self.mainLayout.addWidget(self.wrapPlotAndStat(self.socPlot, self.socStat)) + + # Row 3: Throttle and Brake Plots side-by-side (both 0-100 on y-axis) + row3 = QHBoxLayout() + self.throttlePlot, self.throttleStat = self.createPlotWithStat("Throttle (%) Over Time") + self.throttlePlot.setYRange(0, 100) + row3.addWidget(self.wrapPlotAndStat(self.throttlePlot, self.throttleStat)) + self.brakePlot, self.brakeStat = self.createPlotWithStat("Brake (%) Over Time") + self.brakePlot.setYRange(0, 100) + row3.addWidget(self.wrapPlotAndStat(self.brakePlot, self.brakeStat)) + self.mainLayout.addLayout(row3) + + # Row 4: Bottom Row: Dials, Speed, Fault, and Battery Warning + bottomLayout = QHBoxLayout() + # Throttle Dial + throttleLayout = QVBoxLayout() + throttleLabel = QLabel("Throttle (%)") + throttleLabel.setAlignment(Qt.AlignCenter) + self.throttleDial = QDial() + self.throttleDial.setRange(0, 100) + self.throttleDial.setNotchesVisible(True) + self.throttleDial.setEnabled(False) + throttleLayout.addWidget(throttleLabel) + throttleLayout.addWidget(self.throttleDial) + bottomLayout.addLayout(throttleLayout) + + # Brake Dial + brakeLayout = QVBoxLayout() + brakeLabel = QLabel("Brake (%)") + brakeLabel.setAlignment(Qt.AlignCenter) + self.brakeDial = QDial() + self.brakeDial.setRange(0, 100) + self.brakeDial.setNotchesVisible(True) + self.brakeDial.setEnabled(False) + brakeLayout.addWidget(brakeLabel) + brakeLayout.addWidget(self.brakeDial) + bottomLayout.addLayout(brakeLayout) + + # Speed Display + speedLayout = QVBoxLayout() + speedLabel = QLabel("Speed") + speedLabel.setAlignment(Qt.AlignCenter) + self.speedValueLabel = QLabel("0") + self.speedValueLabel.setAlignment(Qt.AlignCenter) + self.speedValueLabel.setFrameStyle(QFrame.Panel | QFrame.Sunken) + speedLayout.addWidget(speedLabel) + speedLayout.addWidget(self.speedValueLabel) + bottomLayout.addLayout(speedLayout) + + # Fault Indicator + faultLayout = QVBoxLayout() + faultLabel = QLabel("Fault Indicator") + faultLabel.setAlignment(Qt.AlignCenter) + self.faultStatus = QLabel("OK") + self.faultStatus.setFrameStyle(QFrame.Panel | QFrame.Sunken) + self.faultStatus.setAlignment(Qt.AlignCenter) + faultLayout.addWidget(faultLabel) + faultLayout.addWidget(self.faultStatus) + bottomLayout.addLayout(faultLayout) + + # Battery Warning Light (hidden by default) + warningLayout = QVBoxLayout() + warningLabel = QLabel("Battery Low!") + warningLabel.setAlignment(Qt.AlignCenter) + warningLabel.setStyleSheet("background-color: orange; color: black;") + warningLabel.hide() # start hidden + self.warningLight = warningLabel + warningLayout.addWidget(warningLabel) + bottomLayout.addLayout(warningLayout) + + self.mainLayout.addLayout(bottomLayout) + + # Row 5: Reset and Export Buttons + buttonLayout = QHBoxLayout() + self.resetButton = QPushButton("Reset Data") + self.resetButton.clicked.connect(self.resetData) + self.exportButton = QPushButton("Export to CSV") + self.exportButton.clicked.connect(self.exportData) + buttonLayout.addWidget(self.resetButton) + buttonLayout.addWidget(self.exportButton) + self.mainLayout.addLayout(buttonLayout) + + # Initialize plots' curves + self.cellTemp_curves = [] + colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'][:num_BMS_sensors] + for i in range(num_BMS_sensors): + curve = self.cellTempPlot.plot([], [], pen=colors[i], name=f"Cell {i+1}") + self.cellTemp_curves.append(curve) + self.wheelSpeedCurve = self.wheelSpeedPlot.plot([], [], pen='w') + self.socCurve = self.socPlot.plot([], [], pen='c') + self.throttleCurve = self.throttlePlot.plot([], [], pen='g') + self.brakeCurve = self.brakePlot.plot([], [], pen='r') + + def createPlotWithStat(self, title): + """Creates a plot widget and a corresponding label for average stats.""" + plot = pg.PlotWidget(title=title) + statLabel = QLabel("Avg: N/A") + statLabel.setAlignment(Qt.AlignCenter) + return plot, statLabel + + def wrapPlotAndStat(self, plot, statLabel): + """Wraps a plot and a stat label in a vertical layout widget.""" + container = QWidget() + layout = QVBoxLayout() + layout.addWidget(plot) + layout.addWidget(statLabel) + container.setLayout(layout) + return container + + @pyqtSlot(dict) + def updateTelemetry(self, data): + # Get the raw timestamp from the incoming data. + raw_timestamp = data.get("time", len(self.time_data)) + # Compute effective time relative to the current offset. + effective_timestamp = raw_timestamp - self.time_offset + self.time_data.append(effective_timestamp) + # Store the last raw time for reset purposes. + self.last_raw_time = raw_timestamp + if len(self.time_data) == 1: + self.startTime = effective_timestamp + + # Cell temperatures + cell_temps = data.get("cell_temps", [0 for _ in range(num_BMS_sensors)]) + for i in range(len(cell_temps)): + self.cell_temp_data[i].append(cell_temps[i]) + + # SOC + soc = data.get("soc", 0) + self.soc_data.append(soc) + if soc < 10: + self.warningLight.show() + self.beepSound.play() + else: + self.warningLight.hide() + + # Wheel speed + wheel_speed = data.get("wheel_speed", 0) + self.wheel_speed_data.append(wheel_speed) + + # Throttle + throttle = data.get("throttle", 0) + self.throttle_data.append(throttle) + + # Brake + brake = data.get("brake", 0) + self.brake_data.append(brake) + + # Speed (numeric display) + speed = data.get("speed", 0) + self.speedValueLabel.setText(f"{speed:.1f}") + + # Fault + fault = data.get("fault", 0) + if fault: + self.faultStatus.setText("FAULT!") + self.faultStatus.setStyleSheet("background-color: red; color: white;") + else: + self.faultStatus.setText("OK") + self.faultStatus.setStyleSheet("background-color: green; color: white;") + + def updatePlots(self): + if not self.time_data: + return + + current_time = self.time_data[-1] + xmin = max(current_time - 60, 0) + + # Update cell temperature curves and average + for i, curve in enumerate(self.cellTemp_curves): + curve.setData(self.time_data, self.cell_temp_data[i]) + self.cellTempPlot.setXRange(xmin, current_time) + avgTemps = [] + for i in range(num_BMS_sensors): + temps_in_window = [temp for j, temp in enumerate(self.cell_temp_data[i]) if self.time_data[j] >= xmin] + if temps_in_window: + avgTemps.append(sum(temps_in_window) / len(temps_in_window)) + avgTemp = sum(avgTemps) / len(avgTemps) if avgTemps else 0 + self.cellTempStat.setText(f"Avg: {avgTemp:.1f}") + + # Update wheel speed + self.wheelSpeedCurve.setData(self.time_data, self.wheel_speed_data) + self.wheelSpeedPlot.setXRange(xmin, current_time) + speeds_in_window = [speed for j, speed in enumerate(self.wheel_speed_data) if self.time_data[j] >= xmin] + avgSpeed = sum(speeds_in_window) / len(speeds_in_window) if speeds_in_window else 0 + self.wheelSpeedStat.setText(f"Avg: {avgSpeed:.1f}") + + # SOC plot: full timeline + self.socCurve.setData(self.time_data, self.soc_data) + avgSOC = sum(self.soc_data) / len(self.soc_data) if self.soc_data else 0 + self.socStat.setText(f"Avg: {avgSOC:.1f}") + + # Update throttle + self.throttleCurve.setData(self.time_data, self.throttle_data) + self.throttlePlot.setXRange(xmin, current_time) + throttles_in_window = [val for j, val in enumerate(self.throttle_data) if self.time_data[j] >= xmin] + avgThrottle = sum(throttles_in_window) / len(throttles_in_window) if throttles_in_window else 0 + self.throttleStat.setText(f"Avg: {avgThrottle:.1f}") + + # Update brake + self.brakeCurve.setData(self.time_data, self.brake_data) + self.brakePlot.setXRange(xmin, current_time) + brakes_in_window = [val for j, val in enumerate(self.brake_data) if self.time_data[j] >= xmin] + avgBrake = sum(brakes_in_window) / len(brakes_in_window) if brakes_in_window else 0 + self.brakeStat.setText(f"Avg: {avgBrake:.1f}") + + def updateElapsedTime(self): + if self.time_data: + elapsed = self.time_data[-1] - self.startTime + self.elapsedTimeLabel.setText(f"Elapsed: {elapsed:.1f} s") + + def resetData(self): + # Set the time_offset to the last received raw time so that the next incoming + # data point's effective time will be zero. + self.time_offset = self.last_raw_time if hasattr(self, "last_raw_time") else 0.0 + + # Use UDP-based reset if available + if hasattr(self, "sendResetCommand"): + self.sendResetCommand() + + # Clear data storage + self.time_data.clear() + self.cell_temp_data = [[] for _ in range(num_BMS_sensors)] + self.soc_data.clear() + self.wheel_speed_data.clear() + self.throttle_data.clear() + self.brake_data.clear() + self.startTime = 0.0 + + # Clear plots + self.cellTempPlot.clear() + self.wheelSpeedPlot.clear() + self.socPlot.clear() + self.throttlePlot.clear() + self.brakePlot.clear() + + # Recreate curves after clearing plots + self.cellTemp_curves = [] + colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w'][:num_BMS_sensors] + for i in range(num_BMS_sensors): + curve = self.cellTempPlot.plot([], [], pen=colors[i], name=f"Cell {i+1}") + self.cellTemp_curves.append(curve) + self.wheelSpeedCurve = self.wheelSpeedPlot.plot([], [], pen='w') + self.socCurve = self.socPlot.plot([], [], pen='c') + self.throttleCurve = self.throttlePlot.plot([], [], pen='g') + self.brakeCurve = self.brakePlot.plot([], [], pen='r') + + + def exportData(self): + filename = datetime.now().strftime("telemetry_%Y%m%d_%H%M%S.csv") + with open(filename, mode="w", newline="") as csv_file: + writer = csv.writer(csv_file) + # Write header + writer.writerow(["Time", "BMS", "SOC", "Wheel Speed", "Throttle%", "Brake%"]) + for i, t in enumerate(self.time_data): + cell_temps = [self.cell_temp_data[j][i] if i < len(self.cell_temp_data[j]) else "" for j in range(num_BMS_sensors)] + writer.writerow([ + t, + cell_temps, + self.soc_data[i] if i < len(self.soc_data) else "", + self.wheel_speed_data[i] if i < len(self.wheel_speed_data) else "", + self.throttle_data[i] if i < len(self.throttle_data) else "", + self.brake_data[i] if i < len(self.brake_data) else "" + ]) + print(f"Exported data to {filename}") diff --git a/telemetry/main.py b/telemetry/main.py new file mode 100644 index 00000000..6e66fd31 --- /dev/null +++ b/telemetry/main.py @@ -0,0 +1,40 @@ +import socket +import sys +import json +from PyQt5.QtWidgets import QApplication +from PyQt5.QtCore import QThread, pyqtSignal, QObject +from gui import TelemetryWindow + +class SignalEmitter(QObject): + newData = pyqtSignal(dict) + +def main(): + app = QApplication(sys.argv) + window = TelemetryWindow() + window.show() + + emitter = SignalEmitter() + emitter.newData.connect(window.updateTelemetry) + + host = "0.0.0.0" + port = 9999 + running = True + + def udp_listner(): + server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + server_socket.bind((host, port)) + while running: + data, _ = server_socket.recvfrom(2048) + telemetry_data = json.loads(data.decode("utf-8")) + emitter.newData.emit(telemetry_data) + + udp_thread = QThread() + udp_thread.run = udp_listner + udp_thread.start() + + window.serverThread = udp_thread + + sys.exit(app.exec_()) + +if __name__ == '__main__': + main() \ No newline at end of file