Repository: bluerobotics/mavlink-server
Pull request #128: Parameters handle 
Author: login: patrickelectric
Created: 2025-03-11T17:42:46Z | Merged: 2025-03-12T15:53:54Z

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>


Diff:
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 00000000..04e51824
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/lib/drivers/rest/autopilot/parameters/ardupilot_parameters"]
+	path = src/lib/drivers/rest/autopilot/parameters/ardupilot_parameters
+	url = https://github.com/ArduPilot/ParameterRepository
diff --git a/Cargo.lock b/Cargo.lock
index fc14a148..d811bc69 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -298,9 +298,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitflags"
-version = "2.6.0"
+version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
 dependencies = [
  "serde",
 ]
@@ -352,6 +352,39 @@ version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21"
 
+[[package]]
+name = "cached"
+version = "0.55.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0839c297f8783316fcca9d90344424e968395413f0662a5481f79c6648bbc14"
+dependencies = [
+ "ahash",
+ "cached_proc_macro",
+ "cached_proc_macro_types",
+ "hashbrown 0.14.5",
+ "once_cell",
+ "thiserror 2.0.9",
+ "web-time",
+]
+
+[[package]]
+name = "cached_proc_macro"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "673992d934f0711b68ebb3e1b79cdc4be31634b37c98f26867ced0438ca5c603"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.87",
+]
+
+[[package]]
+name = "cached_proc_macro_types"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
+
 [[package]]
 name = "camino"
 version = "1.1.9"
@@ -1216,7 +1249,7 @@ version = "0.14.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f3de3fdca9c75fa4b83a76583d265fa49b1de6b088ebcd210749c24ceeb74660"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "bstr",
  "gix-path",
  "libc",
@@ -1299,7 +1332,7 @@ version = "0.16.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "bstr",
  "gix-features",
  "gix-path",
@@ -1332,7 +1365,7 @@ version = "0.35.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0cd4203244444017682176e65fd0180be9298e58ed90bd4a8489a357795ed22d"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "bstr",
  "filetime",
  "fnv",
@@ -1518,7 +1551,7 @@ version = "0.10.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2007538eda296445c07949cf04f4a767307d887184d6b3e83e2d636533ddc6e"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "gix-path",
  "libc",
  "windows-sys 0.52.0",
@@ -1551,7 +1584,7 @@ version = "0.41.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "030da39af94e4df35472e9318228f36530989327906f38e27807df305fccb780"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "gix-commitgraph",
  "gix-date",
  "gix-hash",
@@ -2132,7 +2165,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "libc",
  "redox_syscall",
 ]
@@ -2258,8 +2291,10 @@ dependencies = [
  "arc-swap",
  "async-trait",
  "axum",
+ "bitflags 2.9.0",
  "byteorder",
  "bytes",
+ "cached",
  "chrono",
  "clap",
  "criterion",
@@ -2274,6 +2309,7 @@ dependencies = [
  "once_cell",
  "regex",
  "ringbuffer",
+ "semver",
  "serde",
  "serde_derive",
  "serde_json",
@@ -2416,7 +2452,7 @@ version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "cfg-if",
  "cfg_aliases",
  "libc",
@@ -2983,7 +3019,7 @@ version = "0.5.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
 ]
 
 [[package]]
@@ -3079,7 +3115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94"
 dependencies = [
  "base64 0.21.7",
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "serde",
  "serde_derive",
 ]
@@ -3140,7 +3176,7 @@ version = "0.38.39"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "375116bee2be9ed569afe2154ea6a99dfdffd257f533f187498c2a8f5feaf4ee"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "errno",
  "libc",
  "linux-raw-sys",
@@ -3305,7 +3341,7 @@ version = "2.11.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "core-foundation 0.9.4",
  "core-foundation-sys",
  "libc",
@@ -3325,9 +3361,9 @@ dependencies = [
 
 [[package]]
 name = "semver"
-version = "1.0.23"
+version = "1.0.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
+checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
 dependencies = [
  "serde",
 ]
@@ -3434,7 +3470,7 @@ version = "4.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7331eefcaafaa382c0df95bcd84068f0b3e3c215c300750dde2316e9b8806ed5"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "cfg-if",
  "core-foundation 0.10.0",
  "core-foundation-sys",
@@ -3970,7 +4006,7 @@ version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
 dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.9.0",
  "bytes",
  "http",
  "http-body",
@@ -4425,6 +4461,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "webpki-roots"
 version = "0.26.6"
diff --git a/Cargo.toml b/Cargo.toml
index df8e81bc..f01c77ad 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -61,6 +61,9 @@ zenoh = "1.0.1"
 ringbuffer = "0.15.0"
 strum = "0.26"
 strum_macros = "0.26"
+bitflags = { version = "2.9.0", features = ["serde"] }
+semver = { version = "1.0.26", features = ["serde"] }
+cached = "0.55.1"
 
 [dev-dependencies]
 criterion = "0.5"
diff --git a/src/lib/drivers/rest/autopilot/ardupilot.rs b/src/lib/drivers/rest/autopilot/ardupilot.rs
index facf4514..99bf9a07 100644
--- a/src/lib/drivers/rest/autopilot/ardupilot.rs
+++ b/src/lib/drivers/rest/autopilot/ardupilot.rs
@@ -1,4 +1,17 @@
 use super::{arducopter, arduplane, ardurover, ardusub, VehicleType};
+use serde::Serialize;
+
+#[derive(Debug, PartialEq, strum_macros::EnumString, strum_macros::Display)]
+pub enum FirmwareType {
+    #[strum(serialize = "Copter")]
+    Copter,
+    #[strum(serialize = "Plane")]
+    Plane,
+    #[strum(serialize = "Rover")]
+    Rover,
+    #[strum(serialize = "Sub")]
+    Submarine,
+}
 
 pub fn flight_mode(
     vehicle_type: VehicleType,
@@ -8,30 +21,85 @@ pub fn flight_mode(
     if base_mode.bits() == 0 {
         "pre-flight".to_string()
     } else {
-        match vehicle_type {
-            // ArduSub
-            VehicleType::Submarine => ardusub::CustomMode::from_u32(custom_mode).to_string(),
-            // ArduRover
-            VehicleType::GroundRover | VehicleType::SurfaceBoat => {
-                ardurover::CustomMode::from_u32(custom_mode).to_string()
-            }
-            // ArduPlane
-            VehicleType::FlappingWing
-            | VehicleType::VtolTiltrotor
-            | VehicleType::VtolTailsitterQuadrotor
-            | VehicleType::VtolTailsitterDuorotor
-            | VehicleType::FixedWing => arduplane::CustomMode::from_u32(custom_mode).to_string(),
-            // ArduCopter
-            VehicleType::Tricopter
-            | VehicleType::Coaxial
-            | VehicleType::Hexarotor
-            | VehicleType::Helicopter
-            | VehicleType::Octorotor
-            | VehicleType::Dodecarotor
-            | VehicleType::Quadrotor => arducopter::CustomMode::from_u32(custom_mode).to_string(),
-            _ => {
-                format!("TODO: {vehicle_type:?} {base_mode:?} {custom_mode:?}")
-            }
+        match firmware_type(vehicle_type) {
+            FirmwareType::Copter => arducopter::CustomMode::from_u32(custom_mode).to_string(),
+            FirmwareType::Plane => arduplane::CustomMode::from_u32(custom_mode).to_string(),
+            FirmwareType::Rover => ardurover::CustomMode::from_u32(custom_mode).to_string(),
+            FirmwareType::Submarine => ardusub::CustomMode::from_u32(custom_mode).to_string(),
         }
     }
 }
+
+pub fn firmware_type(vehicle_type: VehicleType) -> FirmwareType {
+    match vehicle_type {
+        // ArduSub
+        VehicleType::Submarine => FirmwareType::Submarine,
+        // ArduRover
+        VehicleType::GroundRover | VehicleType::SurfaceBoat => FirmwareType::Rover,
+        // ArduPlane
+        VehicleType::FlappingWing
+        | VehicleType::VtolTiltrotor
+        | VehicleType::VtolTailsitterQuadrotor
+        | VehicleType::VtolTailsitterDuorotor
+        | VehicleType::FixedWing => FirmwareType::Plane,
+        // ArduCopter
+        VehicleType::Tricopter
+        | VehicleType::Coaxial
+        | VehicleType::Hexarotor
+        | VehicleType::Helicopter
+        | VehicleType::Octorotor
+        | VehicleType::Dodecarotor
+        | VehicleType::Quadrotor => FirmwareType::Copter,
+        _ => {
+            todo!("TODO: {vehicle_type:?}")
+        }
+    }
+}
+
+bitflags::bitflags! {
+    #[derive(Serialize, Debug, Clone)]
+    #[serde(transparent)]
+    /// (Bitmask) Bitmask of (optional) autopilot capabilities (64 bit). If a bit is set, the autopilot supports this capability.
+    pub struct Capabilities: u64 {
+        /// Autopilot supports the MISSION_ITEM float message type. Note that MISSION_ITEM is deprecated, and autopilots should use MISSION_INT instead.
+        const MISSION_FLOAT = 1;
+        /// Autopilot supports the new param float message type. DEPRECATED: Replaced By MAV_PROTOCOL_CAPABILITY_PARAM_ENCODE_C_CAST (2022-03)
+        const PARAM_FLOAT = 2;
+        /// Autopilot supports MISSION_ITEM_INT scaled integer message type. Note that this flag must always be set if missions are supported, because missions must always use MISSION_ITEM_INT (rather than MISSION_ITEM, which is deprecated).
+        const MISSION_INT = 4;
+        /// Autopilot supports COMMAND_INT scaled integer message type.
+        const COMMAND_INT = 8;
+        /// Parameter protocol uses byte-wise encoding of parameter values into param_value (float) fields: https://mavlink.io/en/services/parameter.html#parameter-encoding. Note that either this flag or MAV_PROTOCOL_CAPABILITY_PARAM_ENCODE_C_CAST should be set if the parameter protocol is supported.
+        const PARAM_ENCODE_BYTEWISE = 16;
+        /// Autopilot supports the File Transfer Protocol v1: https://mavlink.io/en/services/ftp.html.
+        const FTP = 32;
+        /// Autopilot supports commanding attitude offboard.
+        const SET_ATTITUDE_TARGET = 64;
+        /// Autopilot supports commanding position and velocity targets in local NED frame.
+        const SET_POSITION_TARGET_LOCAL_NED = 128;
+        /// Autopilot supports commanding position and velocity targets in global scaled integers.
+        const SET_POSITION_TARGET_GLOBAL_INT = 256;
+        /// Autopilot supports terrain protocol / data handling.
+        const TERRAIN = 512;
+        /// Reserved for future use.
+        const RESERVED3 = 1024;
+        /// Autopilot supports the MAV_CMD_DO_FLIGHTTERMINATION command (flight termination).
+        const FLIGHT_TERMINATION = 2048;
+        /// Autopilot supports onboard compass calibration.
+        const COMPASS_CALIBRATION = 4096;
+        /// Autopilot supports MAVLink version 2.
+        const MAVLINK2 = 8192;
+        /// Autopilot supports mission fence protocol.
+        const MISSION_FENCE = 16384;
+        /// Autopilot supports mission rally point protocol.
+        const MISSION_RALLY = 32768;
+        /// Reserved for future use.
+        const RESERVED2 = 65536;
+        /// Parameter protocol uses C-cast of parameter values to set the param_value (float) fields: https://mavlink.io/en/services/parameter.html#parameter-encoding. Note that either this flag or MAV_PROTOCOL_CAPABILITY_PARAM_ENCODE_BYTEWISE should be set if the parameter protocol is supported.
+        const PARAM_ENCODE_C_CAST = 131072;
+        /// This component implements/is a gimbal manager. This means the GIMBAL_MANAGER_INFORMATION, and other messages can be requested.
+        const COMPONENT_IMPLEMENTS_GIMBAL_MANAGER = 262144;
+        /// Component supports locking control to a particular GCS independent of its system (via MAV_CMD_REQUEST_OPERATOR_CONTROL). WORK IN PROGRESS: Do not use in stable production environments (it may change).
+        const COMPONENT_ACCEPTS_GCS_CONTROL = 524288;
+    }
+}
diff --git a/src/lib/drivers/rest/autopilot/mod.rs b/src/lib/drivers/rest/autopilot/mod.rs
index 13fef21d..b6fecc1e 100644
--- a/src/lib/drivers/rest/autopilot/mod.rs
+++ b/src/lib/drivers/rest/autopilot/mod.rs
@@ -5,6 +5,7 @@ pub mod ardupilot;
 pub mod arduplane;
 pub mod ardurover;
 pub mod ardusub;
+pub mod parameters;
 
 #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)]
 #[repr(u8)]
diff --git a/src/lib/drivers/rest/autopilot/parameters/ardupilot_parameters b/src/lib/drivers/rest/autopilot/parameters/ardupilot_parameters
new file mode 160000
index 00000000..a2cf1db2
--- /dev/null
+++ b/src/lib/drivers/rest/autopilot/parameters/ardupilot_parameters
@@ -0,0 +1 @@
+Subproject commit a2cf1db273714dc2e388f41ca36816e3265b1652
diff --git a/src/lib/drivers/rest/autopilot/parameters/mod.rs b/src/lib/drivers/rest/autopilot/parameters/mod.rs
new file mode 100644
index 00000000..1bf153c1
--- /dev/null
+++ b/src/lib/drivers/rest/autopilot/parameters/mod.rs
@@ -0,0 +1,124 @@
+use include_dir::{include_dir, Dir};
+use serde::Deserialize;
+use std::collections::HashMap;
+
+static ASSETS_DIR: Dir = include_dir!(
+    "$CARGO_MANIFEST_DIR/src/lib/drivers/rest/autopilot/parameters/ardupilot_parameters"
+);
+
+// A function that caches it return value, should return a map of vehicle type by version
+pub fn parameters() -> HashMap<String, Vec<String>> {
+    let mut parameters: HashMap<String, Vec<String>> = HashMap::new();
+    for dir in ASSETS_DIR.dirs() {
+        let directory = dir.path().file_name().unwrap().to_str().unwrap();
+
+        if !directory.contains("-") {
+            continue;
+        }
+
+        let vehicle_type = directory.split("-").next().unwrap();
+        let version = directory.split("-").nth(1).unwrap();
+
+        if parameters.contains_key(vehicle_type) {
+            parameters
+                .get_mut(vehicle_type)
+                .unwrap()
+                .push(version.to_string());
+        } else {
+            parameters.insert(vehicle_type.to_string(), vec![version.to_string()]);
+        }
+    }
+    parameters
+}
+
+#[cached::proc_macro::cached]
+pub fn get_parameters(vehicle_type: String, version: String) -> HashMap<String, Parameter> {
+    let file = ASSETS_DIR
+        .get_dir(format!("{}-{}", vehicle_type, version))
+        .unwrap()
+        .files()
+        .filter(|file| file.path().extension().unwrap_or_default() == "json")
+        .next();
+
+    if let Some(file) = file {
+        let content = file.contents_utf8().unwrap();
+        let mut json_value: serde_json::Value = serde_json::from_str(content).unwrap();
+
+        // TODO: Deal with the version number
+        if let Some(obj) = json_value.as_object_mut() {
+            obj.remove("json");
+        }
+
+        let full: HashMap<String, HashMap<String, Parameter>> =
+            serde_json::from_value(json_value).unwrap();
+        let mut parameters: HashMap<String, Parameter> = HashMap::new();
+        for (_key, value) in full {
+            for (key2, value2) in value {
+                parameters.insert(key2, value2);
+            }
+        }
+        parameters
+    } else {
+        Default::default()
+    }
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Parameter {
+    #[serde(rename = "Description")]
+    pub description: String,
+    #[serde(rename = "DisplayName")]
+    pub display_name: String,
+    #[serde(rename = "Increment", default, deserialize_with = "str_to_f64_opt")]
+    pub increment: Option<f64>,
+    #[serde(rename = "Range", default)]
+    pub range: Option<Range>,
+    #[serde(rename = "Units", default)]
+    pub units: Option<String>,
+    #[serde(rename = "User", default)]
+    pub user_level: String,
+    #[serde(rename = "Values", default)]
+    pub values: Option<HashMap<String, String>>,
+    #[serde(rename = "Bitmask", default)]
+    pub bitmask: Option<HashMap<String, String>>,
+}
+
+#[derive(Clone, Debug, Deserialize)]
+pub struct Range {
+    #[serde(deserialize_with = "str_to_f64")]
+    pub high: f64,
+    #[serde(deserialize_with = "str_to_f64")]
+    pub low: f64,
+}
+
+fn str_to_f64<'de, D>(deserializer: D) -> Result<f64, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    match serde_json::Value::deserialize(deserializer)? {
+        serde_json::Value::String(s) => s.parse().map_err(serde::de::Error::custom),
+        serde_json::Value::Number(num) => num
+            .as_f64()
+            .ok_or_else(|| serde::de::Error::custom("Invalid number")),
+        other => Err(serde::de::Error::custom(format!(
+            "Unexpected type: {:?}",
+            other
+        ))),
+    }
+}
+
+fn str_to_f64_opt<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    match serde_json::Value::deserialize(deserializer)? {
+        serde_json::Value::String(s) => s.parse().map(Some).map_err(serde::de::Error::custom),
+        serde_json::Value::Number(n) => Ok(n.as_f64()),
+        _ => Ok(None),
+    }
+}
+
+// Use it:
+fn parse_parameters(json_content: &str) -> HashMap<String, HashMap<String, Parameter>> {
+    serde_json::from_str(json_content).unwrap()
+}
diff --git a/src/lib/drivers/rest/control.rs b/src/lib/drivers/rest/control.rs
index 4ce8d8f8..775e4b25 100644
--- a/src/lib/drivers/rest/control.rs
+++ b/src/lib/drivers/rest/control.rs
@@ -1,7 +1,7 @@
 use std::collections::HashMap;
 use std::sync::Arc;
 
-use super::autopilot::{self, AutoPilotType};
+use super::autopilot::{self, ardupilot::Capabilities, AutoPilotType};
 
 use anyhow::Result;
 use lazy_static::lazy_static;
@@ -21,6 +21,11 @@ lazy_static! {
         broadcast::channel(16).0;
 }
 
+lazy_static! {
+    static ref BROADCAST_INNER: broadcast::Sender<(mavlink::MavHeader, mavlink::ardupilotmega::MavMessage)> =
+        broadcast::channel(16).0;
+}
+
 lazy_static! {
     static ref BROADCAST_VEHICLES: broadcast::Sender<Vehicle> = broadcast::channel(16).0;
 }
@@ -44,6 +49,14 @@ pub struct Vehicle {
     mode: String,
     attitude: Attitude,
     position: Position,
+    version: Option<Version>,
+    parameters: HashMap<String, String>,
+}
+
+#[derive(Clone, Debug, Serialize)]
+pub struct Version {
+    capabilities: autopilot::ardupilot::Capabilities,
+    version: semver::Version,
 }
 
 #[derive(Clone, Debug, Default, Serialize)]
@@ -109,6 +122,36 @@ impl Vehicle {
                     altitude: global_position.alt as f32 / 1e3,  //mm
                 };
             }
+            mavlink::ardupilotmega::MavMessage::AUTOPILOT_VERSION(autopilot_version) => {
+                let major = ((autopilot_version.flight_sw_version >> 24) & 0xff) as u64;
+                let minor = ((autopilot_version.flight_sw_version >> 16) & 0xff) as u64;
+                let patch = ((autopilot_version.flight_sw_version >> 8) & 0xff) as u64;
+                self.version = Some(Version {
+                    capabilities: Capabilities::from_bits_truncate(
+                        autopilot_version.capabilities.bits() as u64,
+                    ),
+                    version: semver::Version::new(major, minor, patch),
+                });
+
+                request_parameters(self.vehicle_id, header.component_id);
+                let firmware_type = autopilot::ardupilot::firmware_type(
+                    self.vehicle_type.expect("Should have vehicle type already"),
+                );
+                let version_major_minor = format!("{}.{}", major, minor);
+                let parameters = autopilot::parameters::get_parameters(
+                    firmware_type.to_string(),
+                    version_major_minor,
+                );
+            }
+            mavlink::ardupilotmega::MavMessage::PARAM_VALUE(param_value) => {
+                self.parameters.insert(
+                    String::from_utf8_lossy(&param_value.param_id)
+                        .to_string()
+                        .trim_end_matches(|c| c == '\0')
+                        .to_string(),
+                    param_value.param_value.to_string(),
+                );
+            }
             _ => {
                 vehicle_updated = false;
             }
@@ -120,6 +163,75 @@ impl Vehicle {
     }
 }
 
+fn request_parameters(vehicle_id: u8, component_id: u8) {
+    let message = mavlink::ardupilotmega::MavMessage::PARAM_REQUEST_LIST(
+        mavlink::ardupilotmega::PARAM_REQUEST_LIST_DATA {
+            target_system: vehicle_id,
+            target_component: component_id,
+        },
+    );
+
+    send_mavlink_message(message);
+}
+
+pub async fn version(
+    vehicle_id: Option<u8>,
+    component_id: Option<u8>,
+) -> Result<mavlink::ardupilotmega::MavMessage> {
+    let vehicle_id = vehicle_id.unwrap_or(1); // default system_id
+    let component_id =
+        component_id.unwrap_or(mavlink::ardupilotmega::MavComponent::MAV_COMP_ID_AUTOPILOT1 as u8);
+
+    let message = mavlink::ardupilotmega::MavMessage::COMMAND_LONG(
+        mavlink::ardupilotmega::COMMAND_LONG_DATA {
+            param1: 148.0, // AUTOPILOT_VERSION
+            param2: 0.0,
+            param3: 0.0,
+            param4: 0.0,
+            param5: 0.0,
+            param6: 0.0,
+            param7: 0.0,
+            command: mavlink::ardupilotmega::MavCmd::MAV_CMD_REQUEST_MESSAGE,
+            target_system: vehicle_id,
+            target_component: component_id,
+            confirmation: 0,
+        },
+    );
+
+    send_mavlink_message(message);
+    wait_for_message(vehicle_id, component_id, |message| {
+        matches!(
+            message,
+            mavlink::ardupilotmega::MavMessage::AUTOPILOT_VERSION(_)
+        )
+    })
+    .await
+}
+
+pub async fn wait_for_message<F>(
+    vehicle_id: u8,
+    component_id: u8,
+    condition: F,
+) -> Result<mavlink::ardupilotmega::MavMessage>
+where
+    F: Fn(&mavlink::ardupilotmega::MavMessage) -> bool,
+{
+    let mut receiver = BROADCAST_INNER.subscribe();
+    let receive = async {
+        loop {
+            if let Ok((header, message)) = receiver.recv().await {
+                if (header.system_id == vehicle_id && header.component_id == component_id)
+                    && condition(&message)
+                {
+                    return Ok(message);
+                }
+            }
+        }
+    };
+
+    tokio::time::timeout(std::time::Duration::from_secs(3), receive).await?
+}
+
 pub fn arm(vehicle_id: Option<u8>, component_id: Option<u8>, force: Option<bool>) -> Result<()> {
     generic_arming(vehicle_id, component_id, force, true)
 }
@@ -184,7 +296,8 @@ pub async fn update((header, message): (mavlink::MavHeader, mavlink::ardupilotme
             vehicle_id: header.system_id,
             ..Default::default()
         });
-    vehicle.update(header, message);
+    vehicle.update(header, message.clone());
+    let _ = BROADCAST_INNER.send((header, message));
 }
 
 pub async fn vehicles() -> Vec<Vehicle> {
diff --git a/src/lib/web/routes/v1/rest/mod.rs b/src/lib/web/routes/v1/rest/mod.rs
index 7f92d931..e0906b5e 100644
--- a/src/lib/web/routes/v1/rest/mod.rs
+++ b/src/lib/web/routes/v1/rest/mod.rs
@@ -26,4 +26,6 @@ pub fn router() -> Router {
         .route("/vehicles/ws", get(vehicles::websocket_handler))
         .route("/vehicles/arm", post(vehicles::arm))
         .route("/vehicles/disarm", post(vehicles::disarm))
+        .route("/vehicles/version", get(vehicles::version))
+        .route("/vehicles/available_parameters", get(vehicles::parameters))
 }
diff --git a/src/lib/web/routes/v1/rest/vehicles.rs b/src/lib/web/routes/v1/rest/vehicles.rs
index cebf707b..7cf83e9a 100644
--- a/src/lib/web/routes/v1/rest/vehicles.rs
+++ b/src/lib/web/routes/v1/rest/vehicles.rs
@@ -1,10 +1,10 @@
 use std::net::SocketAddr;
 
-use crate::drivers::rest::control;
+use crate::drivers::rest::{autopilot, control};
 use axum::{
     extract::{
         ws::{self, WebSocket},
-        ConnectInfo, Json, WebSocketUpgrade,
+        ConnectInfo, Json, Query, WebSocketUpgrade,
     },
     http::header,
     response::IntoResponse,
@@ -22,6 +22,18 @@ pub struct ArmPayload {
     force: Option<bool>,
 }
 
+#[derive(Debug, Default, Deserialize)]
+pub struct ThisVehiclePayload {
+    vehicle_id: Option<u8>,
+    component_id: Option<u8>,
+}
+
+pub(crate) async fn parameters() -> impl IntoResponse {
+    let parameters = autopilot::parameters::parameters();
+    let json = serde_json::to_string_pretty(&parameters).unwrap();
+    ([(header::CONTENT_TYPE, "application/json")], json).into_response()
+}
+
 pub(crate) async fn vehicles() -> impl IntoResponse {
     let vehicles = control::vehicles().await;
     let json = serde_json::to_string_pretty(&vehicles).unwrap();
@@ -38,6 +50,19 @@ pub(crate) async fn disarm(Json(payload): Json<ArmPayload>) -> impl IntoResponse
     let _ = control::disarm(payload.vehicle_id, payload.component_id, payload.force);
 }
 
+//TODO: Needs to deal with optional arguments for system and component id
+pub(crate) async fn version(Query(payload): Query<ThisVehiclePayload>) -> impl IntoResponse {
+    let version = control::version(payload.vehicle_id, payload.component_id).await;
+    dbg!(&version);
+    let answer = match version {
+        Ok(version) => serde_json::to_string_pretty(&version).unwrap(),
+        Err(err) => {
+            format!("{{\"error\": \"{}\"}}", err)
+        }
+    };
+    ([(header::CONTENT_TYPE, "application/json")], answer).into_response()
+}
+
 #[instrument(level = "debug", skip_all)]
 pub(crate) async fn websocket_handler(
     ws: WebSocketUpgrade,


Reviews:
user: login: joaoantoniocardoso
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T00:50:47Z
user: login: joaoantoniocardoso
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T01:06:03Z
user: login: joaoantoniocardoso
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-12T15:53:48Z

Review comments:
diff_hunk: @@ -120,6 +163,81 @@ impl Vehicle {
     }
 }
 
+fn request_parameters(vehicle_id: u8, component_id: u8) {
+    let message = mavlink::ardupilotmega::MavMessage::PARAM_REQUEST_LIST(
+        mavlink::ardupilotmega::PARAM_REQUEST_LIST_DATA {
+            target_system: vehicle_id,
+            target_component: component_id,
+        },
+    );
+
+    send_mavlink_message(message);
+}
+
+pub async fn version(
+    vehicle_id: Option<u8>,
+    component_id: Option<u8>,
+) -> Result<mavlink::ardupilotmega::MavMessage> {
+    let vehicle_id = vehicle_id.unwrap_or(1); // default system_id
+    let component_id =
+        component_id.unwrap_or(mavlink::ardupilotmega::MavComponent::MAV_COMP_ID_AUTOPILOT1 as u8);
+
+    let message = mavlink::ardupilotmega::MavMessage::COMMAND_LONG(
+        mavlink::ardupilotmega::COMMAND_LONG_DATA {
+            param1: 148.0, // AUTOPILOT_VERSION
+            param2: 0.0,
+            param3: 0.0,
+            param4: 0.0,
+            param5: 0.0,
+            param6: 0.0,
+            param7: 0.0,
+            command: mavlink::ardupilotmega::MavCmd::MAV_CMD_REQUEST_MESSAGE,
+            target_system: vehicle_id,
+            target_component: component_id,
+            confirmation: 0,
+        },
+    );
+
+    send_mavlink_message(message);
+    wait_for_message(vehicle_id, component_id, |message| {
+        matches!(
+            message,
+            mavlink::ardupilotmega::MavMessage::AUTOPILOT_VERSION(_)
+        )
+    })
+    .await
+}
+
+pub async fn wait_for_message<F>(
+    vehicle_id: u8,
+    component_id: u8,
+    condition: F,
+) -> Result<mavlink::ardupilotmega::MavMessage>
+where
+    F: Fn(&mavlink::ardupilotmega::MavMessage) -> bool,
+{
+    let mut receiver = BROADCAST_INNER.subscribe();
+    let timeout = tokio::time::sleep(std::time::Duration::from_secs(3));
+    tokio::pin!(timeout);
+
+    loop {
+        tokio::select! {
+            result = receiver.recv() => {
+                if let Ok((header, message)) = result {
+                    if header.system_id == vehicle_id && header.component_id == component_id {
+                        if condition(&message) {
+                            return Ok(message);
+                        }
+                    }
+                }
+            }
+            _ = &mut timeout => {
+                return Err(anyhow::anyhow!("Timeout waiting for message"));
+            }
+        }
+    }
+}
+
path: src/lib/drivers/rest/control.rs
user: login: joaoantoniocardoso
body: Unless you are thinking of adding more futures to this select, tokio offers a simpler way of doing that for a single future:

```rust
pub async fn wait_for_message_C<F>(
    vehicle_id: u8,
    component_id: u8,
    condition: F,
) -> Result<mavlink::ardupilotmega::MavMessage>
where
    F: Fn(&mavlink::ardupilotmega::MavMessage) -> bool,
{
    let mut receiver = BROADCAST_INNER.subscribe();

    let receive = async {
        loop {
            if let Ok((header, message)) = receiver.recv().await {
                if (header.system_id == vehicle_id && header.component_id == component_id)
                    && condition(&message)
                {
                    return Ok(message);
                }
            }
        }
    };

    tokio::time::timeout(std::time::Duration::from_secs(3), receive).await?
}
```
created_at: 2025-03-12T00:50:47Z
author_association: MEMBER
diff_hunk: @@ -61,6 +61,9 @@ zenoh = "1.0.1"
 ringbuffer = "0.15.0"
 strum = "0.26"
 strum_macros = "0.26"
+bitflags = { version = "2.9.0", features = ["serde"] }
+semver = { version = "1.0.26", features = ["serde"] }
+cached = "0.55.1"
path: Cargo.toml
user: login: joaoantoniocardoso
body: could you break this into a new commit `cargo: add cached, bitflags, semver crates` or something similar?
created_at: 2025-03-12T01:06:02Z
author_association: MEMBER

PR comments:
user: login: joaoantoniocardoso
created_at: 2025-03-11T20:09:44Z
body: BTW, instead of bitflags, there is [this bitfield](https://github.com/ZeniteSolar/can_monitor/blob/3c275e864397c51181fbbe0a3a7871ab8ba22bdb/src/can_types.rs#L87), I found it better when serializing/deserializing binary protocol.
author_association: MEMBER
user: login: patrickelectric
created_at: 2025-03-11T20:20:42Z
body: > BTW, instead of bitflags, there is [this bitfield](https://github.com/ZeniteSolar/can_monitor/blob/3c275e864397c51181fbbe0a3a7871ab8ba22bdb/src/can_types.rs#L87), I found it better when serializing/deserializing binary protocol.

I tried it but I failed to transform a u64 into a bitflag.  
author_association: MEMBER