diff --git a/Cargo.lock b/Cargo.lock
index 1dea7a6d50c5421632d57d200e752c9e8f720dd1..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"
@@ -2006,7 +2012,7 @@ dependencies = [
 [[package]]
 name = "mavlink-bindgen"
 version = "0.14.0"
-source = "git+https://git.skywarder.eu/avn/swd/mavlink/rust-mavlink.git?rev=b7446436b3c96ca4c40d28b54eeed346e7bf021e#b7446436b3c96ca4c40d28b54eeed346e7bf021e"
+source = "git+https://git.skywarder.eu/avn/swd/mavlink/rust-mavlink.git?rev=da4add3de8243d3b8194b9793677e4c950686ddc#da4add3de8243d3b8194b9793677e4c950686ddc"
 dependencies = [
  "crc-any",
  "lazy_static",
@@ -2123,7 +2129,7 @@ dependencies = [
  "spirv",
  "strum",
  "termcolor",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
  "unicode-xid",
 ]
 
@@ -2220,7 +2226,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -2250,7 +2256,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -2547,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",
 ]
@@ -2566,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]]
@@ -2609,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"
@@ -2636,7 +2642,7 @@ dependencies = [
  "concurrent-queue",
  "hermit-abi",
  "pin-project-lite",
- "rustix",
+ "rustix 0.38.44",
  "tracing",
  "windows-sys 0.59.0",
 ]
@@ -2658,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",
 ]
@@ -2681,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]]
@@ -2691,7 +2697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
 dependencies = [
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -2724,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",
 ]
@@ -2808,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",
 ]
@@ -2912,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"
@@ -2984,7 +3003,7 @@ dependencies = [
  "skyward_mavlink",
  "strum",
  "strum_macros",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
  "tokio",
  "tracing",
  "tracing-appender",
@@ -3018,7 +3037,7 @@ checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -3035,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]]
@@ -3149,7 +3168,7 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
 [[package]]
 name = "skyward_mavlink"
 version = "0.1.1"
-source = "git+https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git?branch=rust-strum#8f0700acc0def0cdb2d2ae490e0703fd6c820643"
+source = "git+https://git.skywarder.eu/avn/swd/mavlink/mavlink-skyward-lib.git?rev=03d37888f7b5a84b5032ca1af392a16da7f39df2#03d37888f7b5a84b5032ca1af392a16da7f39df2"
 dependencies = [
  "bitflags 2.9.0",
  "mavlink-bindgen",
@@ -3199,7 +3218,7 @@ dependencies = [
  "libc",
  "log",
  "memmap2",
- "rustix",
+ "rustix 0.38.44",
  "thiserror 1.0.69",
  "wayland-backend",
  "wayland-client",
@@ -3293,7 +3312,7 @@ dependencies = [
  "proc-macro2",
  "quote",
  "rustversion",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -3309,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",
@@ -3326,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",
 ]
 
@@ -3372,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]]
@@ -3387,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]]
@@ -3490,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",
@@ -3550,7 +3569,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -3613,7 +3632,7 @@ checksum = "0eaa1852afa96e0fe9e44caa53dc0bd2d9d05e0f2611ce09f97f8677af56e4ba"
 dependencies = [
  "tracing-core",
  "tracing-subscriber",
- "tracy-client",
+ "tracy-client 0.18.0",
 ]
 
 [[package]]
@@ -3627,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"
@@ -3686,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"
@@ -3790,7 +3820,7 @@ dependencies = [
  "log",
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
  "wasm-bindgen-shared",
 ]
 
@@ -3825,7 +3855,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
  "wasm-bindgen-backend",
  "wasm-bindgen-shared",
 ]
@@ -3847,7 +3877,7 @@ checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf"
 dependencies = [
  "cc",
  "downcast-rs",
- "rustix",
+ "rustix 0.38.44",
  "scoped-tls",
  "smallvec",
  "wayland-sys",
@@ -3860,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",
 ]
@@ -3882,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",
 ]
@@ -4037,7 +4067,7 @@ dependencies = [
  "raw-window-handle",
  "rustc-hash",
  "smallvec",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
  "wgpu-hal",
  "wgpu-types",
 ]
@@ -4076,7 +4106,7 @@ dependencies = [
  "renderdoc-sys",
  "rustc-hash",
  "smallvec",
- "thiserror 2.0.11",
+ "thiserror 2.0.12",
  "wasm-bindgen",
  "web-sys",
  "wgpu-types",
@@ -4157,7 +4187,7 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -4168,7 +4198,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -4436,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",
@@ -4508,7 +4538,7 @@ dependencies = [
  "libc",
  "libloading",
  "once_cell",
- "rustix",
+ "rustix 0.38.44",
  "x11rb-protocol",
 ]
 
@@ -4579,7 +4609,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
  "synstructure",
 ]
 
@@ -4639,7 +4669,7 @@ checksum = "709ab20fc57cb22af85be7b360239563209258430bccf38d8b979c5a2ae3ecce"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
  "zbus-lockstep",
  "zbus_xml",
  "zvariant",
@@ -4654,7 +4684,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
  "zvariant_utils",
 ]
 
@@ -4709,7 +4739,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -4720,7 +4750,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -4740,7 +4770,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
  "synstructure",
 ]
 
@@ -4763,7 +4793,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
 ]
 
 [[package]]
@@ -4788,7 +4818,7 @@ dependencies = [
  "proc-macro-crate",
  "proc-macro2",
  "quote",
- "syn 2.0.98",
+ "syn 2.0.99",
  "zvariant_utils",
 ]
 
@@ -4800,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 d7507aa49ed77f86c2121a11b03fd0b0e2607599..0d667b73bd61dccb69a207164a57c19fe0034891 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,11 +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",
-] }
 serialport = "4.7.0"
 # ========= Persistency =========
 serde = { version = "1.0", features = ["derive"] }
@@ -42,9 +37,14 @@ 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 = "b7446436b3c96ca4c40d28b54eeed346e7bf021e"
+rev = "da4add3de8243d3b8194b9793677e4c950686ddc"
 features = ["serde"]
 
 [dev-dependencies]
diff --git a/src/communication/serial.rs b/src/communication/serial.rs
index b4d3897c8b563727e67dfb15e277cd9cae91dccd..a27efdbfdaece9ed755f0d0594032c2aaddda3f5 100644
--- a/src/communication/serial.rs
+++ b/src/communication/serial.rs
@@ -54,7 +54,7 @@ pub fn find_first_stm32_port() -> Result<Option<SerialPortInfo>, serialport::Err
 pub mod cached {
     use egui::Context;
 
-    use crate::ui::cache::CacheCall;
+    use crate::ui::cache::RecentCallCache;
 
     use super::*;
 
diff --git a/src/ui/cache.rs b/src/ui/cache.rs
index ec089b2e776d8574c08bfc2537ec7ec9739d242f..031a361506c55edec402f8fc099aae7810d1e494 100644
--- a/src/ui/cache.rs
+++ b/src/ui/cache.rs
@@ -7,6 +7,8 @@ use std::{
     time::{Duration, Instant},
 };
 
+use egui::{Context, Id, Ui};
+
 use crate::error::ErrInstrument;
 
 const SERIAL_PORT_REFRESH_INTERVAL: Duration = Duration::from_millis(500);
@@ -20,7 +22,7 @@ const INDEF_REFRESH_INTERVAL: Duration = Duration::MAX;
 /// * `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,
@@ -40,7 +42,7 @@ where
 }
 
 /// A trait to extend egui's Context with a caching function.
-pub trait CacheCall {
+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.
@@ -49,7 +51,7 @@ pub trait CacheCall {
     /// * `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;
@@ -60,8 +62,8 @@ pub trait CacheCall {
         T: Clone + Send + Sync + 'static,
         H: Hash,
     {
-        let id = egui::Id::new(hashable);
-        self.call_cached(id, fun, SHORT_REFRESH_INTERVAL)
+        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
@@ -70,14 +72,14 @@ pub trait CacheCall {
         T: Clone + Send + Sync + 'static,
         H: Hash,
     {
-        let id = egui::Id::new(hashable);
-        self.call_cached(id, fun, INDEF_REFRESH_INTERVAL)
+        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,
@@ -86,6 +88,46 @@ impl CacheCall for egui::Context {
     }
 }
 
+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)
+    }
+}
+
+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.
 ///
 /// The `integrity_digest` field holds a 64-bit unsigned integer that represents
diff --git a/src/ui/panes/plot.rs b/src/ui/panes/plot.rs
index 01b58496e6829c9424f90bef3eb6bb8081bbca3d..7159a19c2a21f92c5c8e7943e9b100c07796b5eb 100644
--- a/src/ui/panes/plot.rs
+++ b/src/ui/panes/plot.rs
@@ -8,14 +8,18 @@ use crate::{
         MessageData, ROCKET_FLIGHT_TM_DATA, TimedMessage,
         reflection::{FieldLike, IndexedField},
     },
-    ui::{app::PaneResponse, cache::ChangeTracker},
+    ui::app::PaneResponse,
+    utils::units::UnitOfMeasure,
 };
 use egui::{Color32, Vec2b};
-use egui_plot::{Legend, Line, PlotPoint, PlotPoints};
+use egui_plot::{AxisHints, HPlacement, Legend, Line, PlotPoint, log_grid_spacer};
 use egui_tiles::TileId;
-use serde::{self, Deserialize, Serialize, ser::SerializeStruct};
+use serde::{self, Deserialize, Serialize};
 use source_window::sources_window;
-use std::{hash::Hash, iter::zip};
+use std::{
+    hash::{DefaultHasher, Hash, Hasher},
+    iter::zip,
+};
 
 #[derive(Clone, Default, Debug, Serialize, Deserialize)]
 pub struct Plot2DPane {
@@ -25,7 +29,6 @@ pub struct Plot2DPane {
     line_data: Vec<Vec<PlotPoint>>,
     #[serde(skip)]
     state_valid: bool,
-    // UI settings
     #[serde(skip)]
     settings_visible: bool,
     #[serde(skip)]
@@ -42,46 +45,132 @@ 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);
 
-        // plot last 100 messages
-        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)
             .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);
+
+        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), points) in zip(self.settings.plot_lines(), &self.line_data)
-                {
-                    plot_ui.line(
-                        Line::new(PlotPoints::from(
-                            &points[points.len().saturating_sub(100)..], // FIXME: don't show just the last 100 points
-                        ))
+            for ((field, settings), points) in zip(self.settings.plot_lines(), &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));
-            });
+                );
+            }
+            plot_ui
+                .response()
+                .context_menu(|ui| show_menu(ui, &mut self.settings_visible, &mut self.settings));
+        });
 
-        let settings_hash = ChangeTracker::record_initial_state(&self.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 self.settings));
 
-        if settings_hash.has_changed(&self.settings) {
+        if data_settings_digest != self.settings.data_digest() {
             self.state_valid = false;
         }
 
@@ -114,13 +203,7 @@ impl PaneBehavior for Plot2DPane {
             }
 
             for (line, y) in zip(&mut self.line_data, ys) {
-                let point = if x_field.field().name == "timestamp" {
-                    PlotPoint::new(x / 1e6, y)
-                } else {
-                    PlotPoint::new(x, y)
-                };
-
-                line.push(point);
+                line.push(PlotPoint::new(x, y));
             }
         }
 
@@ -136,13 +219,15 @@ impl PaneBehavior for Plot2DPane {
     }
 }
 
-fn show_menu(ui: &mut egui::Ui, settings_visible: &mut bool) {
+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("Settings…").clicked() {
+    if ui.button("Source Data Settings…").clicked() {
         *settings_visible = true;
         ui.close_menu();
     }
+
+    ui.checkbox(&mut settings.axes_visible, "Show Axes");
 }
 
 #[derive(Clone, Debug, PartialEq)]
@@ -150,6 +235,7 @@ struct PlotSettings {
     plot_message_id: u32,
     x_field: IndexedField,
     y_fields: Vec<(IndexedField, LineSettings)>,
+    axes_visible: bool,
 }
 
 impl PlotSettings {
@@ -200,6 +286,15 @@ impl PlotSettings {
             .log_unwrap();
         self.y_fields.clear();
     }
+
+    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);
+        }
+        hasher.finish()
+    }
 }
 
 impl Default for PlotSettings {
@@ -214,18 +309,11 @@ impl Default for PlotSettings {
             plot_message_id: msg_id,
             x_field,
             y_fields,
+            axes_visible: true,
         }
     }
 }
 
-impl Hash for PlotSettings {
-    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
-        self.plot_message_id.hash(state);
-        self.x_field.hash(state);
-        self.y_fields.hash(state);
-    }
-}
-
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 struct LineSettings {
     width: f32,
@@ -248,53 +336,69 @@ impl Hash for LineSettings {
     }
 }
 
-impl Serialize for PlotSettings {
-    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
-        let mut state = serializer.serialize_struct("PlotSettings", 3)?;
-        let y_fields: Vec<_> = self.y_fields.iter().map(|(f, s)| (f.id(), s)).collect();
-        state.serialize_field("msg_id", &self.plot_message_id)?;
-        state.serialize_field("x_field", &self.x_field.id())?;
-        state.serialize_field("y_fields", &y_fields)?;
-        state.end()
+mod plot_serde {
+    use serde::{Deserialize, Serialize};
+
+    use super::*;
+
+    #[derive(Serialize, Deserialize)]
+    struct FieldSettings {
+        field: usize,
+        settings: LineSettings,
     }
-}
 
-impl<'de> Deserialize<'de> for PlotSettings {
-    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
-        #[derive(Deserialize)]
-        struct FieldSettings {
-            field: usize,
-            settings: LineSettings,
-        }
+    #[derive(Serialize, Deserialize)]
+    struct PlotSettingsData {
+        msg_id: u32,
+        x_field: usize,
+        y_fields: Vec<FieldSettings>,
+        axes_visible: bool,
+    }
 
-        #[derive(Deserialize)]
-        struct PlotSettingsData {
-            msg_id: u32,
-            x_field: usize,
-            y_fields: Vec<FieldSettings>,
+    impl Serialize for PlotSettings {
+        fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+            let data = PlotSettingsData {
+                msg_id: self.plot_message_id,
+                x_field: self.x_field.id(),
+                y_fields: self
+                    .y_fields
+                    .iter()
+                    .map(|(field, settings)| FieldSettings {
+                        field: field.id(),
+                        settings: settings.clone(),
+                    })
+                    .collect(),
+                axes_visible: self.axes_visible,
+            };
+            data.serialize(serializer)
         }
+    }
 
-        let data = PlotSettingsData::deserialize(deserializer)?;
-        let x_field = data
-            .x_field
-            .to_mav_field(data.msg_id, &MAVLINK_PROFILE)
-            .log_unwrap();
-        let y_fields = data
-            .y_fields
-            .into_iter()
-            .map(|FieldSettings { field, settings }| {
-                (
-                    field
-                        .to_mav_field(data.msg_id, &MAVLINK_PROFILE)
-                        .log_unwrap(),
-                    settings,
-                )
+    impl<'de> Deserialize<'de> for PlotSettings {
+        fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
+            let data = PlotSettingsData::deserialize(deserializer)?;
+            let x_field = data
+                .x_field
+                .to_mav_field(data.msg_id, &MAVLINK_PROFILE)
+                .log_unwrap();
+            let y_fields = data
+                .y_fields
+                .into_iter()
+                .map(|FieldSettings { field, settings }| {
+                    (
+                        field
+                            .to_mav_field(data.msg_id, &MAVLINK_PROFILE)
+                            .log_unwrap(),
+                        settings,
+                    )
+                })
+                .collect();
+            Ok(Self {
+                plot_message_id: data.msg_id,
+                x_field,
+                y_fields,
+                axes_visible: data.axes_visible,
             })
-            .collect();
-        Ok(Self {
-            plot_message_id: data.msg_id,
-            x_field,
-            y_fields,
-        })
+        }
     }
 }
diff --git a/src/ui/panes/plot/source_window.rs b/src/ui/panes/plot/source_window.rs
index bf636a781c86fabd5b02aebd589d870eb24db89d..61ca3c7d22001a2fb32eb7e1db2e36fd2e6b815f 100644
--- a/src/ui/panes/plot/source_window.rs
+++ b/src/ui/panes/plot/source_window.rs
@@ -1,4 +1,4 @@
-use crate::{MAVLINK_PROFILE, ui::cache::ChangeTracker};
+use crate::MAVLINK_PROFILE;
 
 use crate::error::ErrInstrument;
 
@@ -6,7 +6,7 @@ use super::{LineSettings, PlotSettings};
 
 #[profiling::function]
 pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
-    let settings_hash = ChangeTracker::record_initial_state(&plot_settings);
+    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
@@ -24,7 +24,7 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
         });
 
     // reset fields if the message is changed
-    if settings_hash.has_changed(plot_settings) {
+    if data_settings_digest != plot_settings.data_digest() {
         plot_settings.clear_fields();
     }
 
@@ -86,8 +86,13 @@ pub fn sources_window(ui: &mut egui::Ui, plot_settings: &mut PlotSettings) {
                         }
                     });
                 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();
             }
         });
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/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"),
+        }
+    }
+}