diff --git a/Cargo.lock b/Cargo.lock index 9ba641ebdda989d53134b402ded2883bdfdedcce..14c6a43485ce14feaf75e20ba9bc0e9d0cc45445 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -190,9 +190,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.96" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "arboard" @@ -300,7 +300,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -332,7 +332,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 0.38.44", "tracing", ] @@ -344,7 +344,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -359,7 +359,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -373,13 +373,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.86" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -541,9 +541,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" dependencies = [ "bytemuck_derive", ] @@ -556,7 +556,7 @@ checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -573,9 +573,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.10.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "calloop" @@ -586,7 +586,7 @@ dependencies = [ "bitflags 2.9.0", "log", "polling", - "rustix", + "rustix 0.38.44", "slab", "thiserror 1.0.69", ] @@ -598,7 +598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", - "rustix", + "rustix 0.38.44", "wayland-backend", "wayland-client", ] @@ -837,7 +837,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -872,15 +872,15 @@ checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" [[package]] name = "dyn-clone" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feeef44e73baff3a26d371801df019877a9866a8c493d315ab00177843314f35" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[package]] name = "ecolor" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "878e9005799dd739e5d5d89ff7480491c12d0af571d44399bcaefa1ee172dd76" +checksum = "bc4feb366740ded31a004a0e4452fbf84e80ef432ecf8314c485210229672fd1" dependencies = [ "bytemuck", "emath", @@ -889,9 +889,9 @@ dependencies = [ [[package]] name = "eframe" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eba4c50d905804fe9ec4e159fde06b9d38f9440228617ab64a03d7a2091ece63" +checksum = "d0dfe0859f3fb1bc6424c57d41e10e9093fe938f426b691e42272c2f336d915c" dependencies = [ "ahash", "bytemuck", @@ -928,9 +928,9 @@ dependencies = [ [[package]] name = "egui" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d2768eaa6d5c80a6e2a008da1f0e062dff3c83eb2b28605ea2d0732d46e74d6" +checksum = "25dd34cec49ab55d85ebf70139cb1ccd29c977ef6b6ba4fe85489d6877ee9ef3" dependencies = [ "accesskit", "ahash", @@ -946,9 +946,9 @@ dependencies = [ [[package]] name = "egui-wgpu" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d8151704bcef6271bec1806c51544d70e79ef20e8616e5eac01facfd9c8c54a" +checksum = "d319dfef570f699b6e9114e235e862a2ddcf75f0d1a061de9e1328d92146d820" dependencies = [ "ahash", "bytemuck", @@ -966,9 +966,9 @@ dependencies = [ [[package]] name = "egui-winit" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace791b367c1f63e6044aef2f3834904509d1d1a6912fd23ebf3f6a9af92cd84" +checksum = "7d9dfbb78fe4eb9c3a39ad528b90ee5915c252e77bbab9d4ebc576541ab67e13" dependencies = [ "accesskit_winit", "ahash", @@ -987,9 +987,9 @@ dependencies = [ [[package]] name = "egui_extras" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b5cf69510eb3d19211fc0c062fb90524f43fe8e2c012967dcf0e2d81cb040f" +checksum = "624659a2e972a46f4d5f646557906c55f1cd5a0836eddbe610fdf1afba1b4226" dependencies = [ "ahash", "egui", @@ -1011,9 +1011,9 @@ dependencies = [ [[package]] name = "egui_glow" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53e2374a964c3c793cb0b8ead81bca631f24974bc0b747d1a5622f4e39fdd0" +checksum = "910906e3f042ea6d2378ec12a6fd07698e14ddae68aed2d819ffe944a73aab9e" dependencies = [ "ahash", "bytemuck", @@ -1053,15 +1053,15 @@ dependencies = [ [[package]] name = "either" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "emath" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b7b6be5ad1d247f11738b0e4699d9c20005ed366f2c29f5ec1f8e1de180bc2" +checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b" dependencies = [ "bytemuck", "serde", @@ -1091,7 +1091,7 @@ checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1103,7 +1103,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1124,7 +1124,7 @@ checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1135,14 +1135,14 @@ checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "epaint" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "275b665a7b9611d8317485187e5458750850f9e64604d3c58434bb3fc1d22915" +checksum = "41fcc0f5a7c613afd2dee5e4b30c3e6acafb8ad6f0edb06068811f708a67c562" dependencies = [ "ab_glyph", "ahash", @@ -1159,9 +1159,9 @@ dependencies = [ [[package]] name = "epaint_default_fonts" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343d356d7cac894dacafc161b4654e0881301097bdf32a122ed503d97cb94b6" +checksum = "fc7e7a64c02cf7a5b51e745a9e45f60660a286f151c238b9d397b3e923f5082f" [[package]] name = "equivalent" @@ -1255,7 +1255,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1342,7 +1342,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1722,7 +1722,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -1808,9 +1808,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jni" @@ -1906,7 +1906,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", - "redox_syscall 0.5.9", + "redox_syscall 0.5.10", ] [[package]] @@ -1935,6 +1935,12 @@ version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" + [[package]] name = "litemap" version = "0.7.5" @@ -2005,14 +2011,13 @@ dependencies = [ [[package]] name = "mavlink-bindgen" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83b15a4ad504e29cabfb03fdc97250a22d2354a5404d80fc48dbf02e06acf5f" +version = "0.14.0" +source = "git+https://git.skywarder.eu/avn/swd/mavlink/rust-mavlink.git?rev=da4add3de8243d3b8194b9793677e4c950686ddc#da4add3de8243d3b8194b9793677e4c950686ddc" dependencies = [ "crc-any", "lazy_static", "proc-macro2", - "quick-xml 0.26.0", + "quick-xml 0.36.2", "quote", "serde", "thiserror 1.0.69", @@ -2020,9 +2025,8 @@ dependencies = [ [[package]] name = "mavlink-core" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e64d975ca3cf0ad8a7c278553f91d77de15fcde9b79bf6bc542e209dd0c7dee" +version = "0.14.0" +source = "git+https://git.skywarder.eu/avn/swd/mavlink/rust-mavlink.git?rev=b7446436b3c96ca4c40d28b54eeed346e7bf021e#b7446436b3c96ca4c40d28b54eeed346e7bf021e" dependencies = [ "byteorder", "crc-any", @@ -2125,7 +2129,7 @@ dependencies = [ "spirv", "strum", "termcolor", - "thiserror 2.0.11", + "thiserror 2.0.12", "unicode-xid", ] @@ -2222,7 +2226,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2252,7 +2256,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2549,7 +2553,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.9", + "redox_syscall 0.5.10", "smallvec", "windows-targets 0.52.6", ] @@ -2568,22 +2572,22 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfe2e71e1471fe07709406bf725f710b02927c9c54b2b5b2ec0e8087d97c327d" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6e859e6e5bd50440ab63c47e3ebabc90f26251f7c73c3d3e837b74a1cc3fa67" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -2611,9 +2615,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "png" @@ -2638,7 +2642,7 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -2660,18 +2664,18 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] @@ -2683,7 +2687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" dependencies = [ "profiling-procmacros", - "tracy-client", + "tracy-client 0.17.6", ] [[package]] @@ -2693,26 +2697,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "quick-xml" -version = "0.26.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" dependencies = [ "memchr", + "serde", ] [[package]] name = "quick-xml" -version = "0.30.0" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956" +checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", - "serde", ] [[package]] @@ -2726,9 +2730,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] @@ -2752,7 +2756,7 @@ checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.21", + "zerocopy 0.8.23", ] [[package]] @@ -2810,9 +2814,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags 2.9.0", ] @@ -2914,21 +2918,34 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.2", "windows-sys 0.59.0", ] [[package]] name = "rustversion" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -2986,7 +3003,7 @@ dependencies = [ "skyward_mavlink", "strum", "strum_macros", - "thiserror 2.0.11", + "thiserror 2.0.12", "tokio", "tracing", "tracing-appender", @@ -3020,14 +3037,14 @@ checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "serde_json" -version = "1.0.139" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -3037,13 +3054,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -3150,8 +3167,8 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "skyward_mavlink" -version = "0.1.0" -source = "git+https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git?branch=rust-strum#0a67d0afc508c38faecc611a58819479686bee27" +version = "0.1.1" +source = "git+https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git?rev=03d37888f7b5a84b5032ca1af392a16da7f39df2#03d37888f7b5a84b5032ca1af392a16da7f39df2" dependencies = [ "bitflags 2.9.0", "mavlink-bindgen", @@ -3201,7 +3218,7 @@ dependencies = [ "libc", "log", "memmap2", - "rustix", + "rustix 0.38.44", "thiserror 1.0.69", "wayland-backend", "wayland-client", @@ -3295,7 +3312,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -3311,9 +3328,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.98" +version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", @@ -3328,20 +3345,20 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "tempfile" -version = "3.17.1" +version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e5a0acb1f3f55f65cc4a866c361b2fb2a0ff6366785ae6fbb5f85df07ba230" +checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", - "rustix", + "rustix 1.0.1", "windows-sys 0.59.0", ] @@ -3374,11 +3391,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.11", + "thiserror-impl 2.0.12", ] [[package]] @@ -3389,18 +3406,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "thiserror-impl" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -3492,9 +3509,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.43.0" +version = "1.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e" +checksum = "9975ea0f48b5aa3972bf2d888c238182458437cc2a19374b81b25cdf1023fb3a" dependencies = [ "backtrace", "libc", @@ -3552,7 +3569,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -3615,7 +3632,7 @@ checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba" dependencies = [ "tracing-core", "tracing-subscriber", - "tracy-client", + "tracy-client 0.18.0", ] [[package]] @@ -3629,6 +3646,17 @@ dependencies = [ "tracy-client-sys", ] +[[package]] +name = "tracy-client" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d90a2c01305b02b76fdd89ac8608bae27e173c829a35f7d76a345ab5d33836db" +dependencies = [ + "loom", + "once_cell", + "tracy-client-sys", +] + [[package]] name = "tracy-client-sys" version = "0.24.3" @@ -3688,9 +3716,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-segmentation" @@ -3792,7 +3820,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "wasm-bindgen-shared", ] @@ -3827,7 +3855,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3849,7 +3877,7 @@ checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" dependencies = [ "cc", "downcast-rs", - "rustix", + "rustix 0.38.44", "scoped-tls", "smallvec", "wayland-sys", @@ -3862,7 +3890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" dependencies = [ "bitflags 2.9.0", - "rustix", + "rustix 0.38.44", "wayland-backend", "wayland-scanner", ] @@ -3884,7 +3912,7 @@ version = "0.31.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" dependencies = [ - "rustix", + "rustix 0.38.44", "wayland-client", "xcursor", ] @@ -4039,7 +4067,7 @@ dependencies = [ "raw-window-handle", "rustc-hash", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", "wgpu-hal", "wgpu-types", ] @@ -4078,7 +4106,7 @@ dependencies = [ "renderdoc-sys", "rustc-hash", "smallvec", - "thiserror 2.0.11", + "thiserror 2.0.12", "wasm-bindgen", "web-sys", "wgpu-types", @@ -4159,7 +4187,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -4170,7 +4198,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -4438,7 +4466,7 @@ dependencies = [ "pin-project", "raw-window-handle", "redox_syscall 0.4.1", - "rustix", + "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", "smol_str", @@ -4510,7 +4538,7 @@ dependencies = [ "libc", "libloading", "once_cell", - "rustix", + "rustix 0.38.44", "x11rb-protocol", ] @@ -4581,7 +4609,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "synstructure", ] @@ -4641,7 +4669,7 @@ checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "zbus-lockstep", "zbus_xml", "zvariant", @@ -4656,7 +4684,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "zvariant_utils", ] @@ -4696,11 +4724,11 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf01143b2dd5d134f11f545cf9f1431b13b749695cb33bcce051e7568f99478" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ - "zerocopy-derive 0.8.21", + "zerocopy-derive 0.8.23", ] [[package]] @@ -4711,18 +4739,18 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] name = "zerocopy-derive" -version = "0.8.21" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712c8386f4f4299382c9abee219bee7084f78fb939d88b6840fcc1320d5f6da2" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -4742,7 +4770,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "synstructure", ] @@ -4765,7 +4793,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] [[package]] @@ -4790,7 +4818,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", "zvariant_utils", ] @@ -4802,5 +4830,5 @@ checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" dependencies = [ "proc-macro2", "quote", - "syn 2.0.98", + "syn 2.0.99", ] diff --git a/Cargo.toml b/Cargo.toml index 03ca6287fe321ae6cf764427956b606d6cdd3e9e..0d667b73bd61dccb69a207164a57c19fe0034891 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,6 @@ egui_file = "0.22" # =========== Asynchronous =========== tokio = { version = "1.41", features = ["rt-multi-thread", "net", "sync"] } # =========== Mavlink =========== -skyward_mavlink = { git = "https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git", branch = "rust-strum", features = [ - "reflection", - "orion", - "serde", -] } -mavlink-bindgen = { version = "0.13.1", features = ["serde"] } serialport = "4.7.0" # ========= Persistency ========= serde = { version = "1.0", features = ["derive"] } @@ -43,5 +37,15 @@ anyhow = "1.0" ring-channel = "0.12.0" thiserror = "2.0.7" +[dependencies.skyward_mavlink] +git = "https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git" +rev = "03d37888f7b5a84b5032ca1af392a16da7f39df2" +features = ["reflection", "orion", "serde"] + +[dependencies.mavlink-bindgen] +git = "https://git.skywarder.eu/avn/swd/mavlink/rust-mavlink.git" +rev = "da4add3de8243d3b8194b9793677e4c950686ddc" +features = ["serde"] + [dev-dependencies] rand = "0.9.0" diff --git a/src/communication/error.rs b/src/communication/error.rs index 42b0f1c36326ec31e6f4f39cd1d78b6885142aae..d688e8650fc77e53e4117eabb53b6156382751e0 100644 --- a/src/communication/error.rs +++ b/src/communication/error.rs @@ -21,8 +21,6 @@ pub enum ConnectionError { WrongConfiguration(String), #[error("IO error: {0}")] Io(#[from] std::io::Error), - #[error("Unknown error")] - Unknown(String), } impl From<MessageWriteError> for CommunicationError { diff --git a/src/communication/ethernet.rs b/src/communication/ethernet.rs index 629887cd034a5f8a5553d70267de702861f23bd2..00331ed7ab0ab31816d316be2d668a03bbcc8fea 100644 --- a/src/communication/ethernet.rs +++ b/src/communication/ethernet.rs @@ -29,7 +29,7 @@ impl Connectable for EthernetConfiguration { #[profiling::function] fn connect(&self) -> Result<Self::Connected, ConnectionError> { let incoming_addr = format!("udpin:0.0.0.0:{}", self.port); - let outgoing_addr = format!("udpbcast:255.255.255.255:{}", self.port); + let outgoing_addr = format!("udpcast:255.255.255.255:{}", self.port); let mut incoming_conn: BoxedConnection = mavlink::connect(&incoming_addr)?; let mut outgoing_conn: BoxedConnection = mavlink::connect(&outgoing_addr)?; incoming_conn.set_protocol_version(MavlinkVersion::V1); diff --git a/src/communication/serial.rs b/src/communication/serial.rs index 8f1c6fa73b6c4c924357f0050857d371f02d4762..a27efdbfdaece9ed755f0d0594032c2aaddda3f5 100644 --- a/src/communication/serial.rs +++ b/src/communication/serial.rs @@ -51,6 +51,40 @@ pub fn find_first_stm32_port() -> Result<Option<SerialPortInfo>, serialport::Err Ok(None) } +pub mod cached { + use egui::Context; + + use crate::ui::cache::RecentCallCache; + + use super::*; + + /// Returns a cached list of all available USB ports. + /// + /// # Arguments + /// * `ctx` - The egui context used for caching. + /// + /// # Returns + /// * A Result containing a vector of `SerialPortInfo` or a `serialport::Error`. + pub fn cached_list_all_usb_ports( + ctx: &Context, + ) -> Result<Vec<SerialPortInfo>, serialport::Error> { + ctx.call_cached_short(&"list_usb_ports", list_all_usb_ports) + } + + /// Returns the first cached STM32 port found, if any. + /// + /// # Arguments + /// * `ctx` - The egui context used for caching. + /// + /// # Returns + /// * A Result containing an Option of `SerialPortInfo` or a `serialport::Error`. + pub fn cached_first_stm32_port( + ctx: &Context, + ) -> Result<Option<SerialPortInfo>, serialport::Error> { + ctx.call_cached_short(&"find_first_stm32_port", find_first_stm32_port) + } +} + /// Configuration for a serial connection. #[derive(Debug, Clone)] pub struct SerialConfiguration { diff --git a/src/main.rs b/src/main.rs index 9093f877687fd8cc2c26b394476e95f6fad98fce..87495b96d8d51d8b6e7ece615f396ab1cef08fbb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use tokio::runtime::Runtime; use tracing_subscriber::{EnvFilter, Layer, layer::SubscriberExt, util::SubscriberInitExt}; use error::ErrInstrument; -use mavlink::ReflectionContext; +use mavlink::reflection::ReflectionContext; use ui::App; /// ReflectionContext singleton, used to get access to the Mavlink message definitions diff --git a/src/mavlink.rs b/src/mavlink.rs index 91193466aa0254f7dbe1e0415be3421d18e4ab09..f1517ccf525029cb9782ba848669ab65611fb369 100644 --- a/src/mavlink.rs +++ b/src/mavlink.rs @@ -3,13 +3,35 @@ //! It serves also as an abstraction wrapper around the `skyward_mavlink` crate, facilitating //! rapid switching between different mavlink versions and profiles (_dialects_). -mod base; mod error; -mod reflection; +pub mod reflection; -// Export all the types from the base module as if they were defined in this module -pub use base::*; -pub use reflection::ReflectionContext; +use std::time::Instant; + +// Re-export from the mavlink crate +pub use skyward_mavlink::{ + mavlink::*, orion::*, + reflection::ORION_MAVLINK_PROFILE_SERIALIZED as MAVLINK_PROFILE_SERIALIZED, +}; /// Default port for the Ethernet connection pub const DEFAULT_ETHERNET_PORT: u16 = 42069; + +/// A wrapper around the `MavMessage` struct, adding a received time field. +#[derive(Debug, Clone)] +pub struct TimedMessage { + /// The underlying mavlink message + pub message: MavMessage, + /// The time instant at which the message was received + pub time: Instant, +} + +impl TimedMessage { + /// Create a new `TimedMessage` instance with the given message and the current time + pub fn just_received(message: MavMessage) -> Self { + Self { + message, + time: Instant::now(), + } + } +} diff --git a/src/mavlink/base.rs b/src/mavlink/base.rs deleted file mode 100644 index fb64c4cca761dc752c63498d8f5464cc5c475042..0000000000000000000000000000000000000000 --- a/src/mavlink/base.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! Wrapper around the `skyward_mavlink` crate -//! -//! This facilitates rapid switching between different mavlink versions and profiles. -//! -//! In addition, it provides few utility functions to work with mavlink messages. - -use std::time::Instant; - -// Re-export from the mavlink crate -pub use skyward_mavlink::{ - mavlink::*, orion::*, - reflection::ORION_MAVLINK_PROFILE_SERIALIZED as MAVLINK_PROFILE_SERIALIZED, -}; - -use crate::error::ErrInstrument; - -use super::error::{MavlinkError, Result}; - -/// A wrapper around the `MavMessage` struct, adding a received time field. -#[derive(Debug, Clone)] -pub struct TimedMessage { - /// The underlying mavlink message - pub message: MavMessage, - /// The time instant at which the message was received - pub time: Instant, -} - -impl TimedMessage { - /// Create a new `TimedMessage` instance with the given message and the current time - pub fn just_received(message: MavMessage) -> Self { - Self { - message, - time: Instant::now(), - } - } -} - -/// Extract fields from a MavLink message using string keys -#[profiling::function] -pub fn extract_from_message<K, T>( - message: &MavMessage, - fields: impl IntoIterator<Item = K>, -) -> Result<Vec<T>> -where - K: AsRef<str>, - T: serde::de::DeserializeOwned + Default, -{ - let value: serde_json::Value = - serde_json::to_value(message).log_expect("MavMessage should be serializable"); - Ok(fields - .into_iter() - .flat_map(|field| { - let field = field.as_ref(); - let value = value - .get(field) - .ok_or(MavlinkError::UnknownField(field.to_string()))?; - serde_json::from_value::<T>(value.clone()).map_err(MavlinkError::from) - }) - .collect()) -} diff --git a/src/mavlink/error.rs b/src/mavlink/error.rs index 23975eb59b7384774098dacb2e31f5c5b7f1c878..b0aca6e4114deca47fb0c777683cc36b826924e9 100644 --- a/src/mavlink/error.rs +++ b/src/mavlink/error.rs @@ -1,11 +1,7 @@ use thiserror::Error; -pub type Result<T> = std::result::Result<T, MavlinkError>; - #[derive(Debug, Error)] pub enum MavlinkError { - #[error("Error parsing field: {0}")] - UnknownField(String), #[error("Error parsing message: {0}")] ParseError(#[from] serde_json::Error), } diff --git a/src/mavlink/reflection.rs b/src/mavlink/reflection.rs index 528ffde5bb9979465bb760f8da74e80d5f620c3c..e3ed70e723913efad04c48e64e1bf43777ec1ad0 100644 --- a/src/mavlink/reflection.rs +++ b/src/mavlink/reflection.rs @@ -6,19 +6,22 @@ use std::collections::HashMap; -use anyhow::anyhow; use mavlink_bindgen::parser::{MavProfile, MavType}; +use serde::ser::SerializeStruct; +use skyward_mavlink::mavlink::Message; -use crate::error::ErrInstrument; +use crate::{MAVLINK_PROFILE, error::ErrInstrument}; use super::MAVLINK_PROFILE_SERIALIZED; +pub use mavlink_bindgen::parser::{MavField, MavMessage}; + /// Reflection context for MAVLink messages. /// /// This struct provides methods to query information about MAVLink messages and their fields. pub struct ReflectionContext { mavlink_profile: MavProfile, - id_name_map: HashMap<u32, String>, + id_msg_map: HashMap<u32, MavMessage>, } impl ReflectionContext { @@ -26,57 +29,56 @@ impl ReflectionContext { pub fn new() -> Self { let profile: MavProfile = serde_json::from_str(MAVLINK_PROFILE_SERIALIZED) .log_expect("Failed to deserialize MavProfile"); - let id_name_map = profile + let id_msg_map = profile .messages - .iter() - .map(|(name, m)| (m.id, name.clone())) + .values() + .map(|m| (m.id, m.clone())) .collect(); Self { mavlink_profile: profile, - id_name_map, + id_msg_map, } } /// Get the name of a message by its ID. - pub fn get_name_from_id(&self, message_id: u32) -> Option<&str> { - self.id_name_map.get(&message_id).map(|s| s.as_str()) + pub fn get_msg(&'static self, msg: impl MessageLike) -> Option<&'static MavMessage> { + msg.to_mav_message(self).ok() } - /// Get all message names in a sorted vector. - pub fn sorted_messages(&self) -> Vec<&str> { - let mut msgs: Vec<&str> = self - .mavlink_profile - .messages - .keys() - .map(|s| s.as_str()) - .collect(); - msgs.sort(); - msgs + /// Get all field names for a message by its ID. + pub fn get_fields(&'static self, message_id: impl MessageLike) -> Option<Vec<IndexedField>> { + message_id.to_mav_message(self).ok().map(|msg| { + msg.fields + .iter() + .enumerate() + .map(|(i, f)| IndexedField { + id: i, + msg, + field: f, + }) + .collect() + }) } - /// Get all field names for a message by its ID. - pub fn get_fields_by_id(&self, message_id: u32) -> anyhow::Result<Vec<&str>> { - Ok(self + /// Get all message names in a sorted vector. + pub fn get_sorted_msgs(&self) -> Vec<&MavMessage> { + let mut msgs: Vec<(&str, &MavMessage)> = self .mavlink_profile .messages .iter() - .find(|(_, m)| m.id == message_id) - .map(|(_, m)| &m.fields) - .ok_or(anyhow!("Message ID {} not found in profile", message_id))? - .iter() - .map(|f| f.name.as_str()) - .collect()) + .map(|(k, m)| (k.as_str(), m)) + .collect(); + msgs.sort_by_cached_key(|(k, _)| *k); + msgs.into_iter().map(|(_, m)| m).collect() } /// Get all plottable field names for a message by its ID. - pub fn get_plottable_fields_by_id(&self, message_id: u32) -> anyhow::Result<Vec<&str>> { - Ok(self - .mavlink_profile - .messages - .iter() - .find(|(_, m)| m.id == message_id) - .map(|(_, m)| &m.fields) - .ok_or(anyhow!("Message ID {} not found in profile", message_id))? + pub fn get_plottable_fields( + &'static self, + message_id: impl MessageLike, + ) -> Option<Vec<IndexedField>> { + let msg = message_id.to_mav_message(self).ok()?; + msg.fields .iter() .filter(|f| { matches!( @@ -93,21 +95,309 @@ impl ReflectionContext { | MavType::Double ) }) - .map(|f| f.name.as_str()) - .collect()) + .map(|f| f.to_mav_field(msg.id, self).ok()) + .collect() } +} - /// Get all field names for a message by its name. - pub fn get_fields_by_name(&self, message_name: &str) -> anyhow::Result<Vec<&str>> { - Ok(self - .mavlink_profile +#[derive(Debug, Clone)] +pub struct IndexedField { + id: usize, + msg: &'static MavMessage, + field: &'static MavField, +} + +macro_rules! extract_as_type { + ($as_type: ty, $func: ident, $($mav_ty: ident, $rust_ty: ty),+) => { + pub fn $func(&self, message: &impl Message) -> Result<$as_type, String> { + macro_rules! downcast { + ($value: expr, $type: ty) => { + Ok(*$value + .downcast::<$type>() + .map_err(|_| "Type mismatch".to_string())? as $as_type) + }; + } + + let value = message + .get_field(self.id) + .ok_or("Field not found".to_string())?; + match self.field.mavtype { + $(MavType::$mav_ty => downcast!(value, $rust_ty),)+ + _ => Err("Field type not supported".to_string()), + } + } + }; +} + +impl IndexedField { + pub fn msg(&self) -> &MavMessage { + self.msg + } + + pub fn msg_id(&self) -> u32 { + self.msg.id + } + + pub fn id(&self) -> usize { + self.id + } + + pub fn field(&self) -> &MavField { + self.field + } + + pub fn name(&self) -> &str { + &self.field.name + } +} + +/// ### Extractors +/// These methods allow to extract the value of a field from a message, casting +/// it to the desired type. +impl IndexedField { + #[rustfmt::skip] + extract_as_type!(f32, extract_as_f32, + UInt8, u8, + UInt16, u16, + UInt32, u32, + UInt64, u64, + Int8, i8, + Int16, i16, + Int32, i32, + Int64, i64, + Float, f32, + Double, f64 + ); + + #[rustfmt::skip] + extract_as_type!(f64, extract_as_f64, + UInt8, u8, + UInt16, u16, + UInt32, u32, + UInt64, u64, + Int8, i8, + Int16, i16, + Int32, i32, + Int64, i64, + Float, f32, + Double, f64 + ); + + #[rustfmt::skip] + extract_as_type!(u8, extract_as_u8, + UInt8, u8, + Char, char + ); + + #[rustfmt::skip] + extract_as_type!(u16, extract_as_u16, + UInt8, u8, + Int8, i8, + UInt16, u16 + ); + + #[rustfmt::skip] + extract_as_type!(u32, extract_as_u32, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + UInt32, u32 + ); + + #[rustfmt::skip] + extract_as_type!(u64, extract_as_u64, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + UInt32, u32, + Int32, i32, + UInt64, u64 + ); + + #[rustfmt::skip] + extract_as_type!(i8, extract_as_i8, + Int8, i8 + ); + + #[rustfmt::skip] + extract_as_type!(i16, extract_as_i16, + UInt8, u8, + Int8, i8, + Int16, i16 + ); + + #[rustfmt::skip] + extract_as_type!(i32, extract_as_i32, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + Int32, i32 + ); + + #[rustfmt::skip] + extract_as_type!(i64, extract_as_i64, + UInt8, u8, + Int8, i8, + UInt16, u16, + Int16, i16, + UInt32, u32, + Int32, i32, + Int64, i64 + ); + + #[rustfmt::skip] + extract_as_type!(char, extract_as_char, + UInt8, u8, + Char, char + ); +} + +impl std::hash::Hash for IndexedField { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.id.hash(state); + self.msg.id.hash(state); + } +} + +impl PartialEq for IndexedField { + fn eq(&self, other: &Self) -> bool { + self.id == other.id && self.msg.id == other.msg.id + } +} + +impl serde::Serialize for IndexedField { + fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { + let mut state = serializer.serialize_struct("IndexedField", 3)?; + state.serialize_field("id", &self.id)?; + state.serialize_field("msg_id", &self.msg.id)?; + state.end() + } +} + +impl<'de> serde::Deserialize<'de> for IndexedField { + fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> { + #[derive(serde::Deserialize)] + struct IndexedFieldDe { + id: usize, + msg_id: u32, + } + + let de = IndexedFieldDe::deserialize(deserializer)?; + let field = de + .id + .to_mav_field(de.msg_id, &MAVLINK_PROFILE) + .map_err(|u| serde::de::Error::custom(format!("Invalid field: {}", u)))?; + Ok(field) + } +} + +pub trait MessageLike { + fn to_mav_message( + &self, + ctx: &'static ReflectionContext, + ) -> Result<&'static MavMessage, String>; +} + +pub trait FieldLike { + fn to_mav_field( + &self, + msg_id: u32, + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String>; +} + +impl MessageLike for u32 { + fn to_mav_message<'b>(&self, ctx: &'b ReflectionContext) -> Result<&'b MavMessage, String> { + ctx.id_msg_map + .get(self) + .ok_or_else(|| format!("Message {} not found", self)) + } +} + +impl MessageLike for &str { + fn to_mav_message<'b>(&self, ctx: &'b ReflectionContext) -> Result<&'b MavMessage, String> { + ctx.mavlink_profile .messages .iter() - .find(|(_, m)| m.name == message_name) - .map(|(_, m)| &m.fields) - .ok_or(anyhow!("Message {} not found in profile", message_name))? - .iter() - .map(|f| f.name.as_str()) - .collect()) + .find(|(_, m)| m.name == *self) + .map(|(_, m)| m) + .ok_or_else(|| format!("Message {} not found", self)) + } +} + +impl FieldLike for &MavField { + fn to_mav_field( + &self, + msg_id: u32, + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String> { + ctx.id_msg_map + .get(&msg_id) + .and_then(|msg| { + msg.fields + .iter() + .enumerate() + .find(|(_, f)| f == self) + .map(|(i, f)| IndexedField { + id: i, + msg, + field: f, + }) + }) + .ok_or_else(|| format!("Field {} not found in message {}", self.name, msg_id)) + } +} + +impl FieldLike for IndexedField { + fn to_mav_field(&self, _msg_id: u32, _ctx: &ReflectionContext) -> Result<IndexedField, String> { + Ok(IndexedField { + id: self.id, + msg: self.msg, + field: self.field, + }) + } +} + +impl FieldLike for usize { + fn to_mav_field( + &self, + msg_id: u32, + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String> { + ctx.id_msg_map + .get(&msg_id) + .and_then(|msg| { + msg.fields.get(*self).map(|f| IndexedField { + id: *self, + msg, + field: f, + }) + }) + .ok_or_else(|| format!("Field {} not found in message {}", self, msg_id)) + } +} + +impl FieldLike for &str { + fn to_mav_field( + &self, + msg_id: u32, + ctx: &'static ReflectionContext, + ) -> Result<IndexedField, String> { + ctx.id_msg_map + .get(&msg_id) + .and_then(|msg| { + msg.fields + .iter() + .find(|f| f.name == *self) + .map(|f| IndexedField { + id: msg.fields.iter().position(|f2| f2 == f).log_unwrap(), + msg, + field: f, + }) + }) + .ok_or_else(|| format!("Field {} not found in message {}", self, msg_id)) } } diff --git a/src/ui.rs b/src/ui.rs index 383fabd62708460d45dd65f105faf3feb5f75975..077abdba17c67d09579d71aa56bf547fbe52910f 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,5 +1,5 @@ mod app; -mod cache; +pub mod cache; mod panes; mod persistency; mod shortcuts; diff --git a/src/ui/cache.rs b/src/ui/cache.rs index 46b29cc2f5199005f2eb42e4ee4a5c827d457801..031a361506c55edec402f8fc099aae7810d1e494 100644 --- a/src/ui/cache.rs +++ b/src/ui/cache.rs @@ -1,14 +1,19 @@ //! Module for caching expensive UI calls using egui's temporary memory storage. //! It provides utilities for caching the results of functions to avoid frequent recalculations. -use std::time::{Duration, Instant}; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + time::{Duration, Instant}, +}; -use egui::Context; -use serialport::SerialPortInfo; +use egui::{Context, Id, Ui}; -use crate::{communication, error::ErrInstrument}; +use crate::error::ErrInstrument; const SERIAL_PORT_REFRESH_INTERVAL: Duration = Duration::from_millis(500); +const SHORT_REFRESH_INTERVAL: Duration = Duration::from_millis(500); +const INDEF_REFRESH_INTERVAL: Duration = Duration::MAX; /// Internal helper function that caches the result of a given function call for a specified duration. /// @@ -17,7 +22,7 @@ const SERIAL_PORT_REFRESH_INTERVAL: Duration = Duration::from_millis(500); /// * `id` - The unique identifier for the cached item. /// * `fun` - The function whose return value is to be cached. /// * `expiration_duration` - The duration after which the cache should be refreshed. -fn call<T, F>(ctx: &egui::Context, id: egui::Id, fun: F, expiration_duration: Duration) -> T +fn call<T, F>(ctx: &Context, id: Id, fun: F, expiration_duration: Duration) -> T where F: Fn() -> T, T: Clone + Send + Sync + 'static, @@ -37,22 +42,44 @@ where } /// A trait to extend egui's Context with a caching function. -pub trait CacheCall { - /// Calls the provided function and caches its result. +pub trait RecentCallCache { + /// Calls the provided function and caches its result. Every time this + /// function is called, it will return the cached value if it is still + /// valid. /// /// # Arguments /// * `id` - A unique identifier for the cached value. /// * `fun` - The function to be cached. /// * `expiration_duration` - The cache expiration duration. - fn call_cached<F, T>(&self, id: egui::Id, fun: F, expiration_duration: Duration) -> T + fn cached_function_call_for<F, T>(&self, id: Id, fun: F, expiration_duration: Duration) -> T where F: Fn() -> T, T: Clone + Send + Sync + 'static; + + fn call_cached_short<F, T, H>(&self, hashable: &H, fun: F) -> T + where + F: Fn() -> T, + T: Clone + Send + Sync + 'static, + H: Hash, + { + let id = Id::new(hashable); + self.cached_function_call_for(id, fun, SHORT_REFRESH_INTERVAL) + } + + fn call_cached_indef<F, T, H>(&self, hashable: &H, fun: F) -> T + where + F: Fn() -> T, + T: Clone + Send + Sync + 'static, + H: Hash, + { + let id = Id::new(hashable); + self.cached_function_call_for(id, fun, INDEF_REFRESH_INTERVAL) + } } -impl CacheCall for egui::Context { +impl RecentCallCache for Context { /// Implements the caching call using the internal `call` function. - fn call_cached<F, T>(&self, id: egui::Id, fun: F, expiration_duration: Duration) -> T + fn cached_function_call_for<F, T>(&self, id: Id, fun: F, expiration_duration: Duration) -> T where F: Fn() -> T, T: Clone + Send + Sync + 'static, @@ -61,32 +88,108 @@ impl CacheCall for egui::Context { } } -/// Returns a cached list of all available USB ports. -/// -/// # Arguments -/// * `ctx` - The egui context used for caching. -/// -/// # Returns -/// * A Result containing a vector of `SerialPortInfo` or a `serialport::Error`. -pub fn cached_list_all_usb_ports(ctx: &Context) -> Result<Vec<SerialPortInfo>, serialport::Error> { - ctx.call_cached( - egui::Id::new("list_usb_ports"), - communication::serial::list_all_usb_ports, - SERIAL_PORT_REFRESH_INTERVAL, - ) +impl RecentCallCache for &Ui { + /// Implements the caching call using the context of the UI. + fn cached_function_call_for<F, T>(&self, id: Id, fun: F, expiration_duration: Duration) -> T + where + F: Fn() -> T, + T: Clone + Send + Sync + 'static, + { + call(self.ctx(), id, fun, expiration_duration) + } } -/// Returns the first cached STM32 port found, if any. -/// -/// # Arguments -/// * `ctx` - The egui context used for caching. +pub trait CacheWithCondition { + fn cache_result_if<F, T, H>(&self, hashable: H, condition: bool, fun: F) -> T + where + F: Fn() -> T, + T: Clone + Send + Sync + 'static, + H: Hash; +} + +impl CacheWithCondition for Ui { + fn cache_result_if<F, T, H>(&self, hashable: H, condition: bool, fun: F) -> T + where + F: Fn() -> T, + T: Clone + Send + Sync + 'static, + H: Hash, + { + let id = self.next_auto_id().with(hashable); + self.memory_mut(|m| { + let value = m.data.get_temp::<T>(id); + if !condition || value.is_none() { + let value = fun(); + m.data.insert_temp(id, value.clone()); + value + } else { + value.log_unwrap() + } + }) + } +} + +/// ChangeTracker manages the tracking of state changes using an integrity digest. /// -/// # Returns -/// * A Result containing an Option of `SerialPortInfo` or a `serialport::Error`. -pub fn cached_first_stm32_port(ctx: &Context) -> Result<Option<SerialPortInfo>, serialport::Error> { - ctx.call_cached( - egui::Id::new("list_usb_ports"), - communication::serial::find_first_stm32_port, - SERIAL_PORT_REFRESH_INTERVAL, - ) +/// The `integrity_digest` field holds a 64-bit unsigned integer that represents +/// a summary (or hash) of the current state. This can be used to verify that the +/// cached UI state remains consistent, and to quickly detect any modifications. +pub struct ChangeTracker { + integrity_digest: u64, +} + +impl ChangeTracker { + /// Records the initial state of a hashable value by computing its hash digest. + /// + /// This method takes a reference to any value that implements the `Hash` trait, + /// computes its hash using the default hasher, and stores the resulting digest in a + /// newly created `ChangeTracker` instance. This digest serves as a reference point + /// for future state comparisons. + /// + /// # Parameters + /// + /// - `state`: A reference to the value whose state is to be recorded. + /// + /// # Returns + /// + /// A `ChangeTracker` initialized with the computed hash digest. + /// + /// # Examples + /// + /// ``` + /// let initial_tracker = ChangeTracker::record_initial_state(&state); + /// ``` + pub fn record_initial_state<T: Hash>(state: &T) -> Self { + let mut hasher = DefaultHasher::new(); + state.hash(&mut hasher); + let integrity_digest = hasher.finish(); + Self { integrity_digest } + } + + /// Checks whether the hash of the current state differs from the initially recorded state. + /// + /// This method computes the hash digest of the current state (which must implement the + /// `Hash` trait) and compares it with the digest stored in the `ChangeTracker`. If the digests + /// differ, it indicates that the state has changed since the initial recording. + /// + /// # Parameters + /// + /// - `state`: A reference to the current state to be checked for changes. + /// + /// # Returns + /// + /// `true` if the current state's hash digest does not match the initially recorded digest, + /// indicating a change; `false` otherwise. + /// + /// # Examples + /// + /// ``` + /// if tracker.has_changed(&state) { + /// println!("The state has changed."); + /// } + /// ``` + pub fn has_changed<T: Hash>(&self, state: &T) -> bool { + let mut hasher = DefaultHasher::new(); + state.hash(&mut hasher); + self.integrity_digest != hasher.finish() + } } diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs index e83bd2faa2a011abd45ccaba4bdbc23e6733be51..4a98372c1fd099015a2774a335a285fef93b3e2b 100644 --- a/src/ui/panes/plot.rs +++ b/src/ui/panes/plot.rs @@ -2,38 +2,43 @@ mod source_window; use super::PaneBehavior; use crate::{ + MAVLINK_PROFILE, error::ErrInstrument, - mavlink::{MessageData, ROCKET_FLIGHT_TM_DATA, TimedMessage, extract_from_message}, + mavlink::{ + MessageData, ROCKET_FLIGHT_TM_DATA, TimedMessage, + reflection::{FieldLike, IndexedField}, + }, ui::app::PaneResponse, + utils::units::UnitOfMeasure, }; -use egui::{Color32, Vec2b}; -use egui_plot::{Legend, Line, PlotPoints}; +use egui::{Color32, Vec2, Vec2b}; +use egui_plot::{AxisHints, HPlacement, Legend, Line, PlotPoint, log_grid_spacer}; use egui_tiles::TileId; -use serde::{Deserialize, Serialize}; -use source_window::{SourceSettings, sources_window}; -use std::iter::zip; +use serde::{self, Deserialize, Serialize}; +use source_window::sources_window; +use std::{ + hash::{DefaultHasher, Hash, Hasher}, + iter::zip, + time::{Duration, Instant}, +}; #[derive(Clone, Default, Debug, Serialize, Deserialize)] pub struct Plot2DPane { + settings: PlotSettings, // UI settings #[serde(skip)] - pub contains_pointer: bool, + line_data: Vec<TimeAwarePlotPoints>, #[serde(skip)] - settings_visible: bool, - - line_settings: Vec<LineSettings>, + state_valid: bool, #[serde(skip)] - line_data: Vec<Vec<[f64; 2]>>, - - settings: MsgSources, - + settings_visible: bool, #[serde(skip)] - state_valid: bool, + pub contains_pointer: bool, } impl PartialEq for Plot2DPane { fn eq(&self, other: &Self) -> bool { - self.settings == other.settings && self.line_settings == other.line_settings + self.settings == other.settings } } @@ -41,43 +46,135 @@ impl PaneBehavior for Plot2DPane { #[profiling::function] fn ui(&mut self, ui: &mut egui::Ui, _: TileId) -> PaneResponse { let mut response = PaneResponse::default(); + let data_settings_digest = self.settings.data_digest(); let ctrl_pressed = ui.input(|i| i.modifiers.ctrl); - egui_plot::Plot::new("plot") + let x_unit = UnitOfMeasure::from( + &self + .settings + .x_field + .field() + .unit + .clone() + .unwrap_or_default(), + ); + let y_units = self + .settings + .y_fields + .iter() + .map(|(field, _)| field.field().unit.as_ref().map(UnitOfMeasure::from)) + .collect::<Vec<_>>(); + // define y_unit as the common unit of the y_fields if they are all the same + let y_unit = y_units + .iter() + .fold(y_units.first().log_unwrap(), |acc, unit| { + match (acc, unit) { + (Some(uom), Some(unit)) if uom == unit => acc, + _ => &None, + } + }); + let x_name = self.settings.x_field.field().name.clone(); + + let x_axis = match x_unit { + UnitOfMeasure::Time(ref time_unit) => { + AxisHints::new_x().label(&x_name).formatter(move |m, r| { + let scaling_factor_to_nanos = time_unit.scale() * 1e9; + let r_span_in_nanos = (r.end() - r.start()).abs() * scaling_factor_to_nanos; + let m_in_nanos = m.value * scaling_factor_to_nanos; + // all the following numbers are arbitrary + // they are chosen based on common sense + if r_span_in_nanos < 4e3 { + format!("{:.0}ns", m_in_nanos) + } else if r_span_in_nanos < 4e6 { + format!("{:.0}µs", m_in_nanos / 1e3) + } else if r_span_in_nanos < 4e9 { + format!("{:.0}ms", m_in_nanos / 1e6) + } else if r_span_in_nanos < 24e10 { + format!("{:.0}s", m_in_nanos / 1e9) + } else if r_span_in_nanos < 144e11 { + format!("{:.0}m{:.0}s", m_in_nanos / 60e9, (m_in_nanos % 60e9) / 1e9) + } else if r_span_in_nanos < 3456e11 { + format!( + "{:.0}h{:.0}m", + m_in_nanos / 3600e9, + (m_in_nanos % 3600e9) / 60e9 + ) + } else { + format!( + "{:.0}d{:.0}h", + m_in_nanos / 86400e9, + (m_in_nanos % 86400e9) / 3600e9 + ) + } + }) + } + _ => AxisHints::new_x().label(&x_name), + }; + let y_axis = AxisHints::new_y().placement(HPlacement::Right); + + let cursor_formatter = |name: &str, value: &PlotPoint| { + let x_unit = format!(" [{}]", x_unit); + let y_unit = y_unit + .as_ref() + .map(|unit| format!(" [{}]", unit)) + .unwrap_or_default(); + if name.is_empty() { + format!( + "{}: {:.2}{}\ny: {:.2}{}", + x_name, value.x, x_unit, value.y, y_unit + ) + } else { + format!( + "{}: {:.2}{}\n{}: {:.2}{}", + x_name, value.x, x_unit, name, value.y, y_unit + ) + } + }; + + let mut plot = egui_plot::Plot::new("plot") + .x_grid_spacer(log_grid_spacer(4)) // 4 was an arbitrary choice .auto_bounds(Vec2b::TRUE) + .set_margin_fraction(Vec2::splat(0.)) .legend(Legend::default()) - .label_formatter(|name, value| format!("{} - x:{:.2} y:{:.2}", name, value.x, value.y)) - .show(ui, |plot_ui| { - self.contains_pointer = plot_ui.response().contains_pointer(); - if plot_ui.response().dragged() && ctrl_pressed { - response.set_drag_started(); - } + .label_formatter(cursor_formatter); - for (settings, points) in zip(&self.line_settings, &mut self.line_data) { - plot_ui.line( - // TODO: remove clone when PlotPoints supports borrowing - Line::new(PlotPoints::from(points.clone())) - .color(settings.color) - .width(settings.width) - .name(&settings.field), - ); - } - plot_ui - .response() - .context_menu(|ui| show_menu(ui, &mut self.settings_visible)); - }); + if self.settings.axes_visible { + plot = plot.custom_x_axes(vec![x_axis]).custom_y_axes(vec![y_axis]); + } else { + plot = plot.show_axes(Vec2b::FALSE); + } + + plot.show(ui, |plot_ui| { + self.contains_pointer = plot_ui.response().contains_pointer(); + if plot_ui.response().dragged() && ctrl_pressed { + response.set_drag_started(); + } + + for ((field, settings), TimeAwarePlotPoints { points, .. }) in + zip(&self.settings.y_fields, &self.line_data) + { + plot_ui.line( + Line::new(&points[..]) + .color(settings.color) + .width(settings.width) + .name(&field.field().name), + ); + } + plot_ui + .response() + .context_menu(|ui| show_menu(ui, &mut self.settings_visible, &mut self.settings)); + }); - let mut settings = SourceSettings::new(&mut self.settings, &mut self.line_settings); egui::Window::new("Plot Settings") - .id(ui.auto_id_with("plot_settings")) // TODO: fix this issue with ids + .id(ui.auto_id_with("plot_settings")) .auto_sized() .collapsible(true) .movable(true) .open(&mut self.settings_visible) - .show(ui.ctx(), |ui| sources_window(ui, &mut settings)); + .show(ui.ctx(), |ui| sources_window(ui, &mut self.settings)); - if settings.are_sources_changed() { + if data_settings_digest != self.settings.data_digest() { self.state_valid = false; } @@ -94,34 +191,43 @@ impl PaneBehavior for Plot2DPane { self.line_data.clear(); } - let MsgSources { - x_field, y_fields, .. + let PlotSettings { + x_field, + y_fields, + points_lifespan, + .. } = &self.settings; - for msg in messages { - let x: f64 = extract_from_message(&msg.message, [x_field]).log_unwrap()[0]; - let ys: Vec<f64> = extract_from_message(&msg.message, y_fields).log_unwrap(); + // iter on filtered messages based on lifespan set + for msg in messages + .iter() + .filter(|msg| points_lifespan > &msg.time.elapsed()) + { + let x: f64 = x_field.extract_as_f64(&msg.message).log_unwrap(); + let ys: Vec<f64> = y_fields + .iter() + .map(|(field, _)| field.extract_as_f64(&msg.message).log_unwrap()) + .collect(); if self.line_data.len() < ys.len() { - self.line_data.resize(ys.len(), Vec::new()); + self.line_data.resize(ys.len(), TimeAwarePlotPoints::new()); } - for (line, y) in zip(&mut self.line_data, ys) { - let point = if x_field == "timestamp" { - [x / 1e6, y] - } else { - [x, y] - }; - - line.push(point); + for (points, y) in zip(&mut self.line_data, ys) { + points.push(msg.time, PlotPoint::new(x, y)); } } + // clear points older than lifespan set + for line in &mut self.line_data { + line.clear_older_than(*points_lifespan); + } + self.state_valid = true; } fn get_message_subscription(&self) -> Option<u32> { - Some(self.settings.msg_id) + Some(self.settings.plot_message_id) } fn should_send_message_history(&self) -> bool { @@ -129,34 +235,78 @@ impl PaneBehavior for Plot2DPane { } } -#[derive(Clone, Debug, Serialize, Deserialize)] -struct MsgSources { - msg_id: u32, - x_field: String, - y_fields: Vec<String>, +fn show_menu(ui: &mut egui::Ui, settings_visible: &mut bool, settings: &mut PlotSettings) { + ui.set_max_width(200.0); // To make sure we wrap long text + + if ui.button("Source Data Settings…").clicked() { + *settings_visible = true; + ui.close_menu(); + } + + ui.checkbox(&mut settings.axes_visible, "Show Axes"); } -impl Default for MsgSources { - fn default() -> Self { - Self { - msg_id: ROCKET_FLIGHT_TM_DATA::ID, - x_field: "timestamp".to_owned(), - y_fields: Vec::new(), +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +struct PlotSettings { + /// The message id to plot + pub(super) plot_message_id: u32, + /// The field to plot on the x-axis + pub(super) x_field: IndexedField, + /// The fields to plot, with their respective line settings + pub(super) y_fields: Vec<(IndexedField, LineSettings)>, + /// Whether to show the axes of the plot + pub(super) axes_visible: bool, + /// Points will be shown for this duration before being removed + pub(super) points_lifespan: Duration, +} + +impl PlotSettings { + fn add_field(&mut self, field: IndexedField) { + let line_settings = LineSettings::default(); + self.y_fields.push((field, line_settings)); + } + + fn clear_fields(&mut self) { + self.x_field = 0 + .to_mav_field(self.plot_message_id, &MAVLINK_PROFILE) + .log_unwrap(); + self.y_fields.clear(); + } + + /// Returns a digest of the data settings, used to check if the settings + /// have changed IMPORTANT: To trigger a redraw, hash the settings that need + /// to redraw the plot here + fn data_digest(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.x_field.hash(&mut hasher); + for (field, _) in &self.y_fields { + field.hash(&mut hasher); } + self.points_lifespan.as_secs().hash(&mut hasher); + hasher.finish() } } -impl PartialEq for MsgSources { - fn eq(&self, other: &Self) -> bool { - self.msg_id == other.msg_id - && self.x_field == other.x_field - && self.y_fields == other.y_fields +impl Default for PlotSettings { + fn default() -> Self { + let msg_id = ROCKET_FLIGHT_TM_DATA::ID; + let x_field = 0.to_mav_field(msg_id, &MAVLINK_PROFILE).log_unwrap(); + let y_fields = vec![( + 1.to_mav_field(msg_id, &MAVLINK_PROFILE).log_unwrap(), + LineSettings::default(), + )]; + Self { + plot_message_id: msg_id, + x_field, + y_fields, + axes_visible: true, + points_lifespan: Duration::from_secs(600), + } } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] struct LineSettings { - field: String, width: f32, color: Color32, } @@ -164,27 +314,46 @@ struct LineSettings { impl Default for LineSettings { fn default() -> Self { Self { - field: "".to_owned(), width: 1.0, color: Color32::BLUE, } } } -impl LineSettings { - fn new(field_y: String) -> Self { +impl Hash for LineSettings { + fn hash<H: std::hash::Hasher>(&self, state: &mut H) { + self.width.to_bits().hash(state); + self.color.hash(state); + } +} + +#[derive(Clone, Debug)] +struct TimeAwarePlotPoints { + times: Vec<Instant>, + points: Vec<PlotPoint>, +} + +impl TimeAwarePlotPoints { + fn new() -> Self { Self { - field: field_y, - ..Default::default() + times: Vec::new(), + points: Vec::new(), } } -} -fn show_menu(ui: &mut egui::Ui, settings_visible: &mut bool) { - ui.set_max_width(200.0); // To make sure we wrap long text + fn push(&mut self, time: Instant, point: PlotPoint) { + self.times.push(time); + self.points.push(point); + } - if ui.button("Settings…").clicked() { - *settings_visible = true; - ui.close_menu(); + fn clear_older_than(&mut self, lifespan: Duration) { + while let Some(time) = self.times.first().copied() { + if time.elapsed() > lifespan { + self.times.remove(0); + self.points.remove(0); + } else { + break; + } + } } } diff --git a/src/ui/panes/plot/source_window.rs b/src/ui/panes/plot/source_window.rs index f6def542663b70e9620103083c72a0b304d87db8..a62d1f9e41bcb3154c7bd4197327fc62c37a7306 100644 --- a/src/ui/panes/plot/source_window.rs +++ b/src/ui/panes/plot/source_window.rs @@ -1,47 +1,61 @@ -use crate::{ - MAVLINK_PROFILE, - mavlink::{MavMessage, Message}, -}; +use std::time::Duration; -use crate::error::ErrInstrument; +use crate::{MAVLINK_PROFILE, error::ErrInstrument}; -use super::{LineSettings, MsgSources}; +use super::{LineSettings, PlotSettings}; #[profiling::function] -pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut SourceSettings) { +pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) { + // select how many points are shown on the plot + let mut points_lifespan_sec = plot_settings.points_lifespan.as_secs(); + ui.horizontal(|ui| { + let res1 = ui.add(egui::Label::new("Points Lifespan: ")); + let res2 = ui.add( + egui::DragValue::new(&mut points_lifespan_sec) + .range(5..=1800) + .speed(1) + .suffix(" seconds"), + ); + res1.union(res2) + }) + .inner + .on_hover_text("How long the data is shown on the plot"); + plot_settings.points_lifespan = Duration::from_secs(points_lifespan_sec); + + ui.add_sized([250., 10.], egui::Separator::default()); + + let data_settings_digest = plot_settings.data_digest(); // extract the msg name from the id to show it in the combo box let msg_name = MAVLINK_PROFILE - .get_name_from_id(*plot_settings.get_msg_id()) + .get_msg(plot_settings.plot_message_id) + .map(|m| m.name.clone()) .unwrap_or_default(); // show the first combo box with the message name selection egui::ComboBox::from_label("Message Kind") .selected_text(msg_name) .show_ui(ui, |ui| { - for msg in MAVLINK_PROFILE.sorted_messages() { - ui.selectable_value( - plot_settings.get_mut_msg_id(), - MavMessage::message_id_from_name(msg).log_expect("Invalid message name"), - msg, - ); + for msg in MAVLINK_PROFILE.get_sorted_msgs() { + ui.selectable_value(&mut plot_settings.plot_message_id, msg.id, &msg.name); } }); // reset fields if the message is changed - if plot_settings.is_msg_id_changed() { + if data_settings_digest != plot_settings.data_digest() { plot_settings.clear_fields(); } // check fields and assign a default field_x and field_y once the msg is changed let fields = MAVLINK_PROFILE - .get_plottable_fields_by_id(*plot_settings.get_msg_id()) + .get_plottable_fields(plot_settings.plot_message_id) .log_expect("Invalid message id"); // get the first field that is in the list of fields or the previous if valid - let x_field = plot_settings.get_x_field(); + let x_field = &plot_settings.x_field; let new_field_x = fields - .contains(&x_field) + .iter() + .any(|f| f == x_field) .then(|| x_field.to_owned()) - .or(fields.first().map(|s| s.to_string())); + .or(fields.first().map(|s| s.to_owned())); // if there are no fields, reset the field_x and plot_lines let Some(new_field_x) = new_field_x else { @@ -49,55 +63,54 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut SourceSettings) { return; }; // update the field_x - plot_settings.set_x_field(new_field_x); - let x_field = plot_settings.get_mut_x_field(); + plot_settings.x_field = new_field_x; // if fields are valid, show the combo boxes for the x_axis + let x_field = &mut plot_settings.x_field; egui::ComboBox::from_label("X Axis") - .selected_text(x_field.as_str()) + .selected_text(&x_field.field().name) .show_ui(ui, |ui| { for msg in fields.iter() { - ui.selectable_value(x_field, (*msg).to_owned(), *msg); + ui.selectable_value(x_field, msg.to_owned(), &msg.field().name); } }); // populate the plot_lines with the first field if it is empty and there are more than 1 fields - if plot_settings.fields_empty() && fields.len() > 1 { - plot_settings.add_field(fields[1].to_string()); + if plot_settings.y_fields.is_empty() && fields.len() > 1 { + plot_settings.add_field(fields[1].to_owned()); } // check how many fields are left and how many are selected - let plot_lines_len = plot_settings.fields_len(); + let plot_lines_len = plot_settings.y_fields.len(); egui::Grid::new(ui.auto_id_with("y_axis")) .num_columns(3) .spacing([10.0, 2.5]) .show(ui, |ui| { - for (i, line_settings) in plot_settings.line_settings.iter_mut().enumerate() { - let LineSettings { - field, - width, - color, - } = line_settings; + for (i, (field, line_settings)) in plot_settings.y_fields[..].iter_mut().enumerate() { + let LineSettings { width, color } = line_settings; let widget_label = if plot_lines_len > 1 { format!("Y Axis {}", i + 1) } else { "Y Axis".to_owned() }; egui::ComboBox::from_label(widget_label) - .selected_text(field.as_str()) + .selected_text(&field.field().name) .show_ui(ui, |ui| { for msg in fields.iter() { - ui.selectable_value(field, (*msg).to_owned(), *msg); + ui.selectable_value(field, msg.to_owned(), &msg.field().name); } }); ui.color_edit_button_srgba(color); - ui.add(egui::DragValue::new(width).speed(0.1).suffix(" pt")) - .on_hover_text("Width of the line in points"); + ui.add( + egui::DragValue::new(width) + .range(0.0..=10.0) + .speed(0.02) + .suffix(" pt"), + ) + .on_hover_text("Width of the line in points"); ui.end_row(); } }); - // Sync changes applied to line_settings with msg_sources - plot_settings.sync_fields_with_lines(); // if we have fields left, show the add button if fields.len().saturating_sub(plot_lines_len + 1) > 0 @@ -106,85 +119,11 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut SourceSettings) { .on_hover_text("Add another Y axis") .clicked() { + // get the first field that is not in the plot_lines let next_field = fields .iter() - .find(|f| !plot_settings.contains_field(f)) + .find(|field| !plot_settings.y_fields.iter().any(|(f, _)| f == *field)) .log_unwrap(); - plot_settings.add_field(next_field.to_string()); - } -} - -pub struct SourceSettings<'a> { - msg_sources: &'a mut MsgSources, - old_msg_sources: MsgSources, - line_settings: &'a mut Vec<LineSettings>, -} - -impl<'a> SourceSettings<'a> { - pub fn new(msg_sources: &'a mut MsgSources, line_settings: &'a mut Vec<LineSettings>) -> Self { - Self { - old_msg_sources: msg_sources.clone(), - msg_sources, - line_settings, - } - } - - pub fn are_sources_changed(&self) -> bool { - self.msg_sources != &self.old_msg_sources - } - - pub fn fields_empty(&self) -> bool { - self.msg_sources.y_fields.is_empty() - } - - fn get_msg_id(&self) -> &u32 { - &self.msg_sources.msg_id - } - - fn get_x_field(&self) -> &str { - &self.msg_sources.x_field - } - - fn get_mut_msg_id(&mut self) -> &mut u32 { - &mut self.msg_sources.msg_id - } - - fn get_mut_x_field(&mut self) -> &mut String { - &mut self.msg_sources.x_field - } - - fn set_x_field(&mut self, field: String) { - self.msg_sources.x_field = field; - } - - fn fields_len(&self) -> usize { - self.msg_sources.y_fields.len() - } - - fn is_msg_id_changed(&self) -> bool { - self.msg_sources.msg_id != self.old_msg_sources.msg_id - } - - fn contains_field(&self, field: &str) -> bool { - self.msg_sources.y_fields.contains(&field.to_owned()) - } - - fn sync_fields_with_lines(&mut self) { - self.msg_sources.y_fields = self - .line_settings - .iter() - .map(|ls| ls.field.clone()) - .collect(); - } - - fn add_field(&mut self, field: String) { - self.line_settings.push(LineSettings::new(field.clone())); - self.msg_sources.y_fields.push(field); - } - - fn clear_fields(&mut self) { - self.msg_sources.y_fields.clear(); - self.line_settings.clear(); - self.msg_sources.x_field = "".to_owned(); + plot_settings.add_field(next_field.to_owned()); } } diff --git a/src/ui/windows/connections.rs b/src/ui/windows/connections.rs index ebc34c32fcfd24d57cba0a17ff7f126d79b48e0b..30ffe5eae5606c493f4a37aae420f1b3d3254b2f 100644 --- a/src/ui/windows/connections.rs +++ b/src/ui/windows/connections.rs @@ -4,12 +4,15 @@ use tracing::{error, warn}; use crate::{ communication::{ - ConnectionError, EthernetConfiguration, SerialConfiguration, serial::DEFAULT_BAUD_RATE, + ConnectionError, EthernetConfiguration, SerialConfiguration, + serial::{ + DEFAULT_BAUD_RATE, + cached::{cached_first_stm32_port, cached_list_all_usb_ports}, + }, }, error::ErrInstrument, mavlink::DEFAULT_ETHERNET_PORT, message_broker::MessageBroker, - ui::cache::{cached_first_stm32_port, cached_list_all_usb_ports}, }; #[derive(Default)] diff --git a/src/utils.rs b/src/utils.rs index 7e22ff58e02d0b7d8c76696d474d7624e4ac3f77..503a473f5b5c5a5bf2302a8ccad4bc54e89619b0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,2 +1,2 @@ mod ring_buffer; - +pub mod units; diff --git a/src/utils/ring_buffer.rs b/src/utils/ring_buffer.rs index c25df112697f5d3ff6737fe0b219b25c72d27010..5f6ee5732da733f864f492c3e6b8a86a8b4a0943 100644 --- a/src/utils/ring_buffer.rs +++ b/src/utils/ring_buffer.rs @@ -1,5 +1,3 @@ - - #[derive(Debug)] pub struct RingBuffer<const G: usize> { buffer: Box<[u8; G]>, diff --git a/src/utils/units.rs b/src/utils/units.rs new file mode 100644 index 0000000000000000000000000000000000000000..98267d98cf45a12f542c22d5d7d2d8366c315867 --- /dev/null +++ b/src/utils/units.rs @@ -0,0 +1,70 @@ +use std::{fmt::Display, str::FromStr}; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum UnitOfMeasure { + Time(TimeUnits), + Other(String), +} + +impl<T: AsRef<str>> From<T> for UnitOfMeasure { + fn from(s: T) -> Self { + if let Ok(unit) = TimeUnits::from_str(s.as_ref()) { + UnitOfMeasure::Time(unit) + } else { + UnitOfMeasure::Other(s.as_ref().to_string()) + } + } +} + +impl Display for UnitOfMeasure { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UnitOfMeasure::Time(unit) => write!(f, "{}", unit), + UnitOfMeasure::Other(unit) => write!(f, "{}", unit), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TimeUnits { + Second, // s + Millisecond, // ms + Microsecond, // us + Nanosecond, // ns +} + +impl TimeUnits { + pub fn scale(&self) -> f64 { + match self { + TimeUnits::Second => 1.0, + TimeUnits::Millisecond => 1e-3, + TimeUnits::Microsecond => 1e-6, + TimeUnits::Nanosecond => 1e-9, + } + } +} + +impl FromStr for TimeUnits { + type Err = (); + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "s" => Ok(TimeUnits::Second), + "ms" => Ok(TimeUnits::Millisecond), + "us" => Ok(TimeUnits::Microsecond), + "ns" => Ok(TimeUnits::Nanosecond), + _ => Err(()), + } + } +} + +impl Display for TimeUnits { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TimeUnits::Second => write!(f, "s"), + TimeUnits::Millisecond => write!(f, "ms"), + TimeUnits::Microsecond => write!(f, "µs"), + TimeUnits::Nanosecond => write!(f, "ns"), + } + } +}