Skip to main content

crazyflie_lib/subsystems/
param.rs

1//! # Parameter subsystem
2//!
3//! The Crazyflie exposes a param subsystem that allows to easily declare parameter
4//! variables in the Crazyflie and to discover, read and write them from the ground.
5//!
6//! Variables are defined in a table of content that is downloaded upon connection.
7//! Each param variable have a unique name composed from a group and a variable name.
8//! Functions that accesses variables, take a `name` parameter that accepts a string
9//! in the format "group.variable"
10//!
11//! During connection, the full param table of content is downloaded from the
12//! Crazyflie. Parameter values are loaded on-demand when first accessed via `get()`.
13//! Parameters can also be set without reading them first. If a variable value
14//! is modified by the Crazyflie during runtime, it sends a packet with the new
15//! value which updates the local value cache.
16
17use crate::crtp_utils::TocCache;
18use crate::{crtp_utils::WaitForPacket, Error, Result};
19use crate::{Value, ValueType};
20use crazyflie_link::Packet;
21use flume as channel;
22use futures::lock::Mutex;
23use serde::{Serialize, Deserialize};
24use std::{
25    collections::{BTreeMap, HashMap},
26    convert::{TryFrom, TryInto},
27    sync::Arc,
28};
29
30use crate::crazyflie::PARAM_PORT;
31
32/// State of a persistent parameter
33#[derive(Debug, Clone)]
34pub struct PersistentParamState {
35    /// True if a value is currently stored in persistent storage
36    pub is_stored: bool,
37    /// The firmware's default value for this parameter
38    pub default_value: Value,
39    /// The value stored in persistent storage (if is_stored is true)
40    pub stored_value: Option<Value>,
41}
42
43/// Cached state for a parameter's default value.
44#[derive(Debug, Clone, Copy)]
45enum DefaultValueCache {
46    /// Parameter has this default value
47    Value(Value),
48    /// Parameter doesn't support default value fetching
49    Unsupported,
50}
51
52#[derive(Debug, Serialize, Deserialize)]
53struct ParamItemInfo {
54    item_type: ValueType,
55    writable: bool,
56    has_extended_type: bool, // Bit 4: indicates extended type info exists
57}
58
59impl TryFrom<u8> for ParamItemInfo {
60    type Error = Error;
61
62    fn try_from(value: u8) -> Result<Self> {
63        Ok(Self {
64            item_type: match value & 0x0f {
65                0x08 => ValueType::U8,
66                0x09 => ValueType::U16,
67                0x0A => ValueType::U32,
68                0x0B => ValueType::U64,
69                0x00 => ValueType::I8,
70                0x01 => ValueType::I16,
71                0x02 => ValueType::I32,
72                0x03 => ValueType::I64,
73                0x05 => ValueType::F16,
74                0x06 => ValueType::F32,
75                0x07 => ValueType::F64,
76                _ => {
77                    return Err(Error::ParamError(format!(
78                        "Type error in TOC: type {} is unknown",
79                        value & 0x0f
80                    )))
81                }
82            },
83            writable: (value & (1 << 6)) == 0,
84            has_extended_type: (value & (1 << 4)) != 0,
85        })
86    }
87}
88
89type ParamChangeWatchers =
90    Arc<Mutex<Vec<futures::channel::mpsc::UnboundedSender<(String, Value)>>>>;
91
92async fn notify_watchers(watchers: &ParamChangeWatchers, name: String, value: Value) {
93    let mut to_remove = Vec::new();
94    let mut watchers = watchers.lock().await;
95
96    for (i, watcher) in watchers.iter().enumerate() {
97        if watcher.unbounded_send((name.clone(), value)).is_err() {
98            to_remove.push(i);
99        }
100    }
101
102    // Remove watchers that have dropped
103    for i in to_remove.into_iter().rev() {
104        watchers.remove(i);
105    }
106}
107
108/// # Access to the Crazyflie Param Subsystem
109///
110/// This struct provide methods to interact with the parameter subsystem. See the
111/// [param module documentation](crate::subsystems::param) for more context and information.
112#[derive(Debug)]
113pub struct Param {
114    uplink: channel::Sender<Packet>,
115    read_downlink: channel::Receiver<Packet>,
116    write_downlink: Mutex<channel::Receiver<Packet>>,
117    misc_downlink: Mutex<channel::Receiver<Packet>>,
118    toc: Arc<BTreeMap<String, (u16, ParamItemInfo)>>,
119    values: Arc<Mutex<HashMap<String, Option<Value>>>>,
120    default_values: Arc<Mutex<HashMap<String, DefaultValueCache>>>,
121    watchers: ParamChangeWatchers,
122}
123
124fn not_found(name: &str) -> Error {
125    Error::ParamError(format!("Parameter {} not found", name))
126}
127
128const READ_CHANNEL: u8 = 1;
129const _WRITE_CHANNEL: u8 = 2;
130const MISC_CHANNEL: u8 = 3;
131
132// MISC channel and commands for persistent parameters
133const _MISC_GET_EXTENDED_TYPE: u8 = 2; // V1 - deprecated, use V2
134const MISC_PERSISTENT_STORE: u8 = 3;
135const MISC_PERSISTENT_GET_STATE: u8 = 4;
136const MISC_PERSISTENT_CLEAR: u8 = 5;
137const _MISC_GET_DEFAULT_VALUE: u8 = 6; // V1 - deprecated, use V2
138const MISC_GET_EXTENDED_TYPE_V2: u8 = 7;
139const MISC_GET_DEFAULT_VALUE_V2: u8 = 8;
140
141// Firmware protocol status codes for persistent_get_state
142const PARAM_PERSISTENT_NOT_STORED: u8 = 0;
143const PARAM_PERSISTENT_STORED: u8 = 1;
144const PARAM_NOT_FOUND: u8 = 2;
145
146impl Param {
147    pub(crate) async fn new<T>(
148        downlink: channel::Receiver<Packet>,
149        uplink: channel::Sender<Packet>,
150        toc_cache: T,
151    ) -> Result<Self>
152    where
153        T: TocCache,
154    {
155        let (toc_downlink, read_downlink, write_downlink, misc_downlink) =
156            crate::crtp_utils::crtp_channel_dispatcher(downlink);
157
158        let toc = crate::crtp_utils::fetch_toc(PARAM_PORT, uplink.clone(), toc_downlink, toc_cache).await?;
159
160        // Create a channel for MISC commands (not param updates)
161        let (misc_cmd_tx, misc_cmd_rx) = channel::unbounded();
162
163        let mut param = Self {
164            uplink,
165            read_downlink,
166            write_downlink: Mutex::new(write_downlink),
167            misc_downlink: Mutex::new(misc_cmd_rx),
168            toc: Arc::new(toc),
169            values: Arc::new(Mutex::new(HashMap::new())),
170            default_values: Arc::new(Mutex::new(HashMap::new())),
171            watchers: Arc::default(),
172        };
173
174        param.initialize_values().await?;
175
176        param.spawn_misc_loop(misc_downlink, misc_cmd_tx).await;
177
178        Ok(param)
179    }
180
181    async fn initialize_values(&mut self) -> Result<()> {
182        for (name, (_param_id, _info)) in self.toc.as_ref() {
183            let mut values = self.values.lock().await;
184            values.insert(
185                name.into(),
186                None,
187            );
188        }
189
190        Ok(())
191    }
192
193    async fn read_value(&self, param_id: u16, param_type: ValueType) -> Result<Value> {
194        let request = Packet::new(PARAM_PORT, READ_CHANNEL, param_id.to_le_bytes().into());
195        self.uplink
196            .send_async(request.clone())
197            .await
198            .map_err(|_| Error::Disconnected)?;
199
200        let response = self
201            .read_downlink
202            .wait_packet(
203                request.get_port(),
204                request.get_channel(),
205                request.get_data(),
206            )
207            .await?;
208
209        Value::from_le_bytes(&response.get_data()[3..], param_type)
210    }
211
212    async fn spawn_misc_loop(&self, misc_downlink: channel::Receiver<Packet>, misc_cmd_tx: channel::Sender<Packet>) {
213        let values = self.values.clone();
214        let toc = self.toc.clone();
215        let watchers = self.watchers.clone();
216
217        tokio::spawn(async move {
218            while let Ok(pk) = misc_downlink.recv_async().await {
219                // Command byte 1 = parameter update notification
220                if pk.get_data().first() == Some(&1) {
221                    // The range sets the buffer to 2 bytes long so this unwrap cannot fail
222                    let param_id = u16::from_le_bytes(pk.get_data()[1..3].try_into().unwrap());
223                    if let Some((param, (_, item_info))) = toc.iter().find(|v| v.1 .0 == param_id) {
224                        if let Ok(value) =
225                            Value::from_le_bytes(&pk.get_data()[3..], item_info.item_type)
226                        {
227                            // The param is tested as being in the toc so this unwrap cannot fail.
228                            *values.lock().await.get_mut(param).unwrap() = Some(value);
229
230                            notify_watchers(&watchers, param.clone(), value).await;
231                        } else {
232                            println!("Error: Malformed param update");
233                            break;
234                        }
235                    } else {
236                        println!("Error: malformed param update");
237                        break;
238                    }
239                } else {
240                    // Other MISC commands - forward to misc_cmd_tx
241                    let _ = misc_cmd_tx.send_async(pk).await;
242                }
243            }
244            values.lock().await.clear();
245            watchers.lock().await.clear(); // Drops all tx senders, killing the stream
246        });
247    }
248
249    /// Get the names of all the parameters
250    ///
251    /// The names contain group and name of the parameter variable formatted as
252    /// "group.name".
253    pub fn names(&self) -> Vec<String> {
254        self.toc.keys().cloned().collect()
255    }
256
257    /// Return the type of a parameter variable or an Error if the parameter does not exist.
258    pub fn get_type(&self, name: &str) -> Result<ValueType> {
259        Ok(self
260            .toc
261            .get(name)
262            .ok_or_else(|| not_found(name))?
263            .1
264            .item_type)
265    }
266
267    /// Return true if he parameter variable is writable. False otherwise.
268    ///
269    /// Return an error if the parameter does not exist.
270    pub fn is_writable(&self, name: &str) -> Result<bool> {
271        Ok(self
272            .toc
273            .get(name)
274            .ok_or_else(|| not_found(name))?
275            .1
276            .writable)
277    }
278
279    /// Return true if the parameter has extended type information.
280    ///
281    /// Return an error if the parameter does not exist.
282    pub fn has_extended_type(&self, name: &str) -> Result<bool> {
283        Ok(self
284            .toc
285            .get(name)
286            .ok_or_else(|| not_found(name))?
287            .1
288            .has_extended_type)
289    }
290
291    /// Set a parameter value.
292    ///
293    /// This function will set the variable value and wait for confirmation from the
294    /// Crazyflie. If the set is successful `Ok(())` is returned, otherwise the
295    /// error code reported by the Crazyflie is returned in the error.
296    ///
297    /// This function accepts any primitive type as well as the [Value] type. The
298    /// type of the param variable is checked at runtime and must match the type
299    /// given to the function, either the direct primitive type or the type
300    /// contained in the `Value` enum. For example, to write a u16 value, both lines are valid:
301    ///
302    /// ```no_run
303    /// # use crazyflie_lib::{Crazyflie, Value, Error};
304    /// # use crazyflie_link::LinkContext;
305    /// # async fn example() -> Result<(), Error> {
306    /// # let context = LinkContext::new();
307    /// # let cf = Crazyflie::connect_from_uri(
308    /// #   &context,
309    /// #   "radio://0/60/2M/E7E7E7E7E7",
310    /// #   crazyflie_lib::NoTocCache
311    /// # ).await?;
312    /// cf.param.set("example.param", 42u16).await?;  // From primitive
313    /// cf.param.set("example.param", Value::U16(42)).await?;  // From Value
314    /// # Ok(())
315    /// # };
316    /// ```
317    ///
318    /// Return an error in case of type mismatch or if the variable does not exist.
319    pub async fn set<T: Into<Value>>(&self, param: &str, value: T) -> Result<()> {
320        let value: Value = value.into();
321        let (param_id, param_info) = self.toc.get(param).ok_or_else(|| not_found(param))?;
322
323        if param_info.item_type != value.into() {
324            return Err(Error::ParamError(format!(
325                "Parameter {} is type {:?}, cannot set with value {:?}",
326                param, param_info.item_type, value
327            )));
328        }
329
330        let downlink = self.write_downlink.lock().await;
331
332        let mut request_data = Vec::from(param_id.to_le_bytes());
333        request_data.append(&mut value.into());
334        let request = Packet::new(PARAM_PORT, _WRITE_CHANNEL, request_data);
335        self.uplink
336            .send_async(request)
337            .await
338            .map_err(|_| Error::Disconnected)?;
339
340        let answer = downlink
341            .wait_packet(PARAM_PORT, _WRITE_CHANNEL, &param_id.to_le_bytes())
342            .await?;
343
344        // Success response: firmware echoes back the written value
345        let expected_bytes: Vec<u8> = value.into();
346        let data = answer.get_data();
347        if data.len() < 2 {
348            return Err(Error::ProtocolError(
349                format!("Parameter write response too short: expected at least 2 bytes, got {}", data.len())
350            ));
351        }
352        let echoed_bytes = &data[2..];
353
354        if echoed_bytes == expected_bytes.as_slice() {
355            // The param is tested as being in the TOC so this unwrap cannot fail
356            *self.values.lock().await.get_mut(param).unwrap() = Some(value);
357            notify_watchers(&self.watchers, param.to_owned(), value).await;
358            Ok(())
359        } else {
360            // If echoed value doesn't match, it's likely a parameter error code
361            if echoed_bytes.is_empty() {
362                return Err(Error::ProtocolError(
363                    "Parameter write response invalid: no error code or echoed value".to_string()
364                ));
365            }
366            let error_code = echoed_bytes[0]; // For u8 params, single byte error code
367            Err(Error::ParamError(format!(
368                "Error setting parameter: parameter error code {}",
369                error_code
370            )))
371        }
372    }
373
374    /// Get param value
375    ///
376    /// Get value of a parameter. The first access will fetch the value from the
377    /// Crazyflie. Subsequent accesses are served from a local cache and are quick.
378    ///
379    /// Similarly to the `set` function above, the type of the param must match
380    /// the return parameter. For example to get a u16 param:
381    /// ```no_run
382    /// # use crazyflie_lib::{Crazyflie, Value, Error};
383    /// # use crazyflie_link::LinkContext;
384    /// # async fn example() -> Result<(), Error> {
385    /// # let context = LinkContext::new();
386    /// # let cf = Crazyflie::connect_from_uri(
387    /// #   &context,
388    /// #   "radio://0/60/2M/E7E7E7E7E7",
389    /// #   crazyflie_lib::NoTocCache
390    /// # ).await?;
391    /// let example: u16 = cf.param.get("example.param").await?;  // To primitive
392    /// dbg!(example);  // 42
393    /// let example: Value = cf.param.get("example.param").await?;  // To Value
394    /// dbg!(example);  // Value::U16(42)
395    /// # Ok(())
396    /// # };
397    /// ```
398    ///
399    /// Return an error in case of type mismatch or if the variable does not exist.
400    pub async fn get<T: TryFrom<Value>>(&self, name: &str) -> Result<T>
401    where
402        <T as TryFrom<Value>>::Error: std::fmt::Debug,
403    {
404        let mut values = self.values.lock().await;
405
406        let value = *values.get(name)
407            .ok_or_else(|| not_found(name))?;
408
409        // If the value is None it means it has never been read, read it now and update the value
410        let value = match value {
411            Some(v) => v,
412            None => {
413                let (param_id, param_info) = self
414                    .toc
415                    .get(name)
416                    .ok_or_else(|| not_found(name))?;
417                let v = self.read_value(*param_id, param_info.item_type).await?;
418                // Update the cache
419                *values.get_mut(name).unwrap() = Some(v.clone());
420                v
421            }
422        };
423
424        Ok(value
425            .try_into()
426            .map_err(|e| Error::ParamError(format!("Type error reading param: {:?}", e)))?)
427    }
428
429    /// Set a parameter from a f64 potentially loosing data
430    ///
431    /// This function is a forgiving version of the `set` function. It allows
432    /// to set any parameter of any type from a `f64` value. This allows to set
433    /// parameters without caring about the type and risking a type mismatch
434    /// runtime error. Since there is no type or value check, loss of information
435    /// can happen when using this function.
436    ///
437    /// Loss of information can happen in the following cases:
438    ///  - When setting an integer, the value is truncated to the number of bit of the parameter
439    ///    - Example: Setting `257` to a `u8` variable will set it to the value `1`
440    ///  - Similarly floating point precision will be truncated to the parameter precision. Rounding is undefined.
441    ///  - Setting a floating point outside the range of the parameter is undefined.
442    ///  - It is not possible to represent accurately a `u64` parameter in a `f64`.
443    ///
444    /// Returns an error if the param does not exists.
445    pub async fn set_lossy(&self, name: &str, value: f64) -> Result<()> {
446        let param_type = self
447            .toc
448            .get(name)
449            .ok_or_else(|| not_found(name))?
450            .1
451            .item_type;
452
453        let value = Value::from_f64_lossy(param_type, value);
454
455        self.set(name, value).await
456    }
457
458    /// Get a parameter as a `f64` independently of the parameter type
459    ///
460    /// This function is a forgiving version of the `get` function. It allows
461    /// to get any parameter of any type as a `f64` value. This allows to get
462    /// parameters without caring about the type and risking a type mismatch
463    /// runtime error. Since there is no type or value check, loss of information
464    /// can happen when using this function.
465    ///
466    /// Loss of information can happen in the following cases:
467    ///  - It is not possible to represent accurately a `u64` parameter in a `f64`.
468    ///
469    /// Returns an error if the param does not exists.
470    pub async fn get_lossy(&self, name: &str) -> Result<f64> {
471        let value: Value = self.get(name).await?;
472
473        Ok(value.to_f64_lossy())
474    }
475
476    /// Get notified for all parameter value change
477    ///
478    /// This function returns an async stream that will generate a tuple containing
479    /// the name of the variable that has changed (in the form of group.name)
480    /// and its new value.
481    ///
482    /// There can be two reasons for a parameter to change:
483    ///  - Either the parameter was changed by a call to [Param::set()]. The
484    ///    notification will be generated when the Crazyflie confirms the parameter
485    ///    has been set.
486    ///  - Or it can be a parameter change in the Crazyflie itself. The Crazyflie
487    ///    will send notification packet for every internal parameter change.
488    pub async fn watch_change(&self) -> Result<impl futures::Stream<Item = (String, Value)> + use<>> {
489        if self.uplink.is_disconnected() {
490            return Err(Error::Disconnected);
491        }
492        let (tx, rx) = futures::channel::mpsc::unbounded();
493
494        let mut watchers = self.watchers.lock().await;
495        watchers.push(tx);
496
497        Ok(rx)
498    }
499
500    /// Check if a parameter supports persistent storage
501    ///
502    /// Returns `true` if the parameter can be stored in persistent storage, `false` otherwise.
503    ///
504    /// Returns an error if the parameter does not exist.
505    pub async fn is_persistent(&self, name: &str) -> Result<bool> {
506        // Check if parameter has extended type flag (bit 4)
507        let (_, param_info) = self.toc.get(name).ok_or_else(|| not_found(name))?;
508
509        // If no extended type, it's not persistent
510        if !param_info.has_extended_type {
511            return Ok(false);
512        }
513
514        // Query the actual extended type flags
515        let extended_type = self.get_extended_type(name).await?;
516
517        // Check if PERSISTENT flag (bit 0) is set
518        Ok((extended_type & 0x01) != 0)
519    }
520
521    /// Get the extended type flags of a parameter from the firmware
522    ///
523    /// Returns a bitfield of extended type flags. Currently defined flags:
524    /// - `0x01`: PERSISTENT - parameter can be stored in persistent storage
525    ///
526    /// This queries the firmware directly. For most use cases, [`is_persistent()`](Self::is_persistent)
527    /// is more convenient.
528    ///
529    /// Returns an error if the parameter does not exist or does not have extended type information.
530    pub async fn get_extended_type(&self, name: &str) -> Result<u8> {
531        let (param_id, param_info) = self.toc.get(name).ok_or_else(|| not_found(name))?;
532
533        if !param_info.has_extended_type {
534            return Err(Error::ParamError(format!(
535                "Parameter '{}' does not have extended type info",
536                name
537            )));
538        }
539
540        // Send request: [CMD(1), ID(2)]
541        let request_data = vec![
542            MISC_GET_EXTENDED_TYPE_V2,
543            (param_id & 0xff) as u8,
544            (param_id >> 8) as u8,
545        ];
546        let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
547
548        // Lock before sending to prevent race conditions with concurrent requests
549        let misc_downlink = self.misc_downlink.lock().await;
550
551        self.uplink
552            .send_async(request)
553            .await
554            .map_err(|_| Error::Disconnected)?;
555
556        // Wait for response
557        // V2 success: [CMD(1), ID(2), STATUS(1), EXTENDED_TYPE(1)]
558        // Error: [CMD(1), ID(2), ERROR(1)]
559        let response = misc_downlink
560            .wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
561            .await?;
562
563        let data = response.get_data();
564
565        // Verify minimum response length
566        if data.len() < 4 {
567            return Err(Error::ProtocolError(format!(
568                "Response too short: expected at least 4 bytes, got {}",
569                data.len()
570            )));
571        }
572
573        // Check if this is an error response (exactly 4 bytes)
574        if data.len() == 4 {
575            let error_code = data[3];
576            if error_code == libc::ENOENT as u8 {
577                // Parameter ID invalid OR parameter doesn't have PARAM_EXTENDED flag
578                return Err(Error::ParamError(format!(
579                    "Parameter '{}' does not have extended type info (not marked as PARAM_EXTENDED in firmware)",
580                    name
581                )));
582            } else {
583                return Err(Error::ParamError(format!(
584                    "Failed to get extended type for '{}': error code {}",
585                    name, error_code
586                )));
587            }
588        }
589
590        // V2 success response: [CMD, ID_LOW, ID_HIGH, 0x00, EXTENDED_TYPE]
591        if data.len() < 5 {
592            return Err(Error::ProtocolError(format!(
593                "Response too short for V2 success: expected 5 bytes, got {}",
594                data.len()
595            )));
596        }
597
598        let status = data[3];
599        if status != 0x00 {
600            return Err(Error::ProtocolError(format!(
601                "Unexpected status byte in V2 response: expected 0x00, got 0x{:02x}",
602                status
603            )));
604        }
605
606        Ok(data[4])
607    }
608
609    /// Get the default value of a parameter as defined in the firmware
610    ///
611    /// This retrieves the default value that the parameter has in the firmware,
612    /// regardless of whether a different value has been stored in persistent storage.
613    ///
614    /// Returns an error if the parameter does not exist or does not support getting default values.
615    pub async fn get_default_value(&self, name: &str) -> Result<Value> {
616        // Check cache first
617        {
618            let cache = self.default_values.lock().await;
619            if let Some(cached) = cache.get(name) {
620                return match cached {
621                    DefaultValueCache::Value(v) => Ok(*v),
622                    DefaultValueCache::Unsupported => Err(Error::ParamError(format!(
623                        "Parameter '{}' does not support get_default_value (read-only or invalid)",
624                        name
625                    ))),
626                };
627            }
628        }
629
630        let (param_id, param_info) = self.toc.get(name).ok_or_else(|| not_found(name))?;
631
632        // Send request: [CMD(1), ID(2)]
633        let request_data = vec![
634            MISC_GET_DEFAULT_VALUE_V2,
635            (param_id & 0xff) as u8,
636            (param_id >> 8) as u8,
637        ];
638        let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
639
640        // Lock before sending to prevent race conditions with concurrent requests
641        let misc_downlink = self.misc_downlink.lock().await;
642
643        self.uplink
644            .send_async(request)
645            .await
646            .map_err(|_| Error::Disconnected)?;
647
648        // Wait for response
649        // V2 success: [CMD(1), ID(2), STATUS(1), VALUE(?)]
650        // Error: [CMD(1), ID(2), ERROR(1)]
651        let response = misc_downlink
652            .wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
653            .await?;
654
655        let data = response.get_data();
656
657        // Verify minimum response length
658        if data.len() < 4 {
659            return Err(Error::ProtocolError(format!(
660                "Response too short: expected at least 4 bytes, got {}",
661                data.len()
662            )));
663        }
664
665        // Check if this is an error response (exactly 4 bytes)
666        if data.len() == 4 {
667            let error_code = data[3];
668            if error_code == libc::ENOENT as u8 {
669                // Parameter ID invalid OR parameter is read-only
670                // (read-only params have no default value concept in firmware)
671                // Cache the unsupported state so we don't query again
672                let mut cache = self.default_values.lock().await;
673                cache.insert(name.to_owned(), DefaultValueCache::Unsupported);
674
675                return Err(Error::ParamError(format!(
676                    "Parameter '{}' does not support get_default_value (read-only or invalid)",
677                    name
678                )));
679            } else {
680                return Err(Error::ParamError(format!(
681                    "Failed to get default value for '{}': error code {}",
682                    name, error_code
683                )));
684            }
685        }
686
687        // V2 success response: [CMD, ID_LOW, ID_HIGH, 0x00, VALUE...]
688        let status = data[3];
689        if status != 0x00 {
690            return Err(Error::ProtocolError(format!(
691                "Unexpected status byte in V2 response: expected 0x00, got 0x{:02x}",
692                status
693            )));
694        }
695
696        // Parse value from data[4..] and cache it
697        let value = Value::from_le_bytes(&data[4..], param_info.item_type)?;
698
699        {
700            let mut cache = self.default_values.lock().await;
701            cache.insert(name.to_owned(), DefaultValueCache::Value(value));
702        }
703
704        Ok(value)
705    }
706
707    /// Get the complete state of a persistent parameter
708    ///
709    /// Returns the following information about a persistent parameter:
710    /// - Whether a value is currently stored in persistent storage
711    /// - The firmware's default value
712    /// - The stored value (if one exists)
713    ///
714    /// Returns an error if the parameter does not exist or is not persistent.
715    ///
716    /// # Example
717    ///
718    /// ```no_run
719    /// # async fn example(cf: &crazyflie_lib::Crazyflie) -> crazyflie_lib::Result<()> {
720    /// let state = cf.param.persistent_get_state("ring.effect").await?;
721    ///
722    /// println!("Default value: {:?}", state.default_value);
723    /// if state.is_stored {
724    ///     println!("Stored value: {:?}", state.stored_value.unwrap());
725    /// } else {
726    ///     println!("Using default (not stored)");
727    /// }
728    /// # Ok(())
729    /// # }
730    /// ```
731    pub async fn persistent_get_state(&self, name: &str) -> Result<PersistentParamState> {
732        let (param_id, param_info) = self.toc.get(name).ok_or_else(|| not_found(name))?;
733
734        if !self.is_persistent(name).await? {
735            return Err(Error::ParamError(format!(
736                "Parameter '{}' is not persistent",
737                name
738            )));
739        }
740
741        // Send request: [CMD(1), ID(2)]
742        let request_data = vec![
743            MISC_PERSISTENT_GET_STATE,
744            (param_id & 0xff) as u8,
745            (param_id >> 8) as u8,
746        ];
747        let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
748
749        // Lock before sending to prevent race conditions with concurrent requests
750        let misc_downlink = self.misc_downlink.lock().await;
751
752        self.uplink
753            .send_async(request)
754            .await
755            .map_err(|_| Error::Disconnected)?;
756
757        // Wait for response: [CMD(1), ID(2), STATUS(1), VALUE_DATA(?)]
758        let response = misc_downlink
759            .wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
760            .await?;
761
762        let data = response.get_data();
763
764        // Response format: [CMD(1), ID(2), STATUS(1), VALUE_DATA(?)]
765        // Verify minimum response length
766        if data.len() < 4 {
767            return Err(Error::ProtocolError(format!(
768                "Response too short: expected at least 4 bytes, got {}",
769                data.len()
770            )));
771        }
772
773        let status = data[3];
774
775        // Validate status code:
776        // PARAM_PERSISTENT_NOT_STORED = no value in persistent storage
777        // PARAM_PERSISTENT_STORED = value exists in persistent storage
778        // PARAM_NOT_FOUND = parameter ID doesn't exist in firmware
779        let is_stored = match status {
780            PARAM_PERSISTENT_NOT_STORED => false,
781            PARAM_PERSISTENT_STORED => true,
782            PARAM_NOT_FOUND => {
783                return Err(Error::ParamError(format!(
784                    "Parameter ID for '{}' is invalid or doesn't exist in firmware",
785                    name
786                )));
787            }
788            _ => {
789                return Err(Error::ProtocolError(format!(
790                    "Unexpected status code {} in persistent_get_state response for '{}'",
791                    status, name
792                )));
793            }
794        };
795        let value_size = param_info.item_type.byte_length();
796
797        // Parse values from data[4..]
798        if is_stored {
799            // Both default and stored values present
800            if data.len() < 4 + 2 * value_size {
801                return Err(Error::ProtocolError(format!(
802                    "Response too short for stored state: expected {} bytes, got {}",
803                    4 + 2 * value_size,
804                    data.len()
805                )));
806            }
807
808            let default_value = Value::from_le_bytes(&data[4..4 + value_size], param_info.item_type)?;
809            let stored_value = Value::from_le_bytes(&data[4 + value_size..4 + 2 * value_size], param_info.item_type)?;
810
811            Ok(PersistentParamState {
812                is_stored: true,
813                default_value,
814                stored_value: Some(stored_value),
815            })
816        } else {
817            // Only default value present
818            if data.len() < 4 + value_size {
819                return Err(Error::ProtocolError(format!(
820                    "Response too short for default value: expected {} bytes, got {}",
821                    4 + value_size,
822                    data.len()
823                )));
824            }
825
826            let default_value = Value::from_le_bytes(&data[4..4 + value_size], param_info.item_type)?;
827
828            Ok(PersistentParamState {
829                is_stored: false,
830                default_value,
831                stored_value: None,
832            })
833        }
834    }
835
836    /// Store the current value of a persistent parameter to persistent storage.
837    ///
838    /// When a value is stored, it will be used as the parameter's initial value
839    /// on every subsequent boot, instead of the firmware default. Note that
840    /// changing the parameter at runtime with [`set()`](Self::set) does not
841    /// update the stored value.
842    ///
843    /// # Example
844    /// ```no_run
845    /// # async fn example(cf: &crazyflie_lib::Crazyflie) -> crazyflie_lib::Result<()> {
846    /// // First set the value you want to persist
847    /// cf.param.set("ring.effect", 10u8).await?;
848    /// 
849    /// // Then store it to persistent storage
850    /// cf.param.persistent_store("ring.effect").await?;
851    /// # Ok(())
852    /// # }
853    /// ```
854    pub async fn persistent_store(&self, name: &str) -> Result<()> {
855        let (param_id, _) = self.toc.get(name).ok_or_else(|| not_found(name))?;
856
857        if !self.is_persistent(name).await? {
858            return Err(Error::ParamError(format!(
859                "Parameter '{}' is not persistent",
860                name
861            )));
862        }
863
864        // Send request: [CMD(1), ID(2)]
865        let request_data = vec![
866            MISC_PERSISTENT_STORE,
867            (param_id & 0xff) as u8,
868            (param_id >> 8) as u8,
869        ];
870        let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
871
872        // Lock before sending to prevent race conditions with concurrent requests
873        let misc_downlink = self.misc_downlink.lock().await;
874
875        self.uplink
876            .send_async(request)
877            .await
878            .map_err(|_| Error::Disconnected)?;
879
880        // Wait for response: [CMD(1), ID(2), STATUS(1)]
881        let response = misc_downlink
882            .wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
883            .await?;
884
885        let data = response.get_data();
886
887        // Verify response length
888        if data.len() < 4 {
889            return Err(Error::ProtocolError(format!(
890                "Response too short: expected 4 bytes, got {}",
891                data.len()
892            )));
893        }
894
895        let status = data[3];
896
897        match status {
898            0x00 => Ok(()),
899            x if x == libc::ENOENT as u8 => {
900                // Storage operation failed (couldn't write to persistent storage)
901                // or parameter ID invalid (shouldn't happen since we verified the ID)
902                Err(Error::ParamError(format!(
903                    "Failed to store parameter '{}' to persistent storage (storage write failed)",
904                    name
905                )))
906            }
907            _ => Err(Error::ProtocolError(format!(
908                "Unexpected status code {} in persistent_store response for '{}'",
909                status, name
910            ))),
911        }
912    }
913
914    /// Clear the stored value of a persistent parameter from persistent storage.
915    ///
916    /// When cleared, the parameter will revert to the firmware default on every
917    /// subsequent boot.
918    ///
919    /// # Example
920    /// ```no_run
921    /// # async fn example(cf: &crazyflie_lib::Crazyflie) -> crazyflie_lib::Result<()> {
922    /// // Clear the stored value, reverting to default
923    /// cf.param.persistent_clear("ring.effect").await?;
924    /// # Ok(())
925    /// # }
926    /// ```
927    pub async fn persistent_clear(&self, name: &str) -> Result<()> {
928        let (param_id, _) = self.toc.get(name).ok_or_else(|| not_found(name))?;
929
930        if !self.is_persistent(name).await? {
931            return Err(Error::ParamError(format!(
932                "Parameter '{}' is not persistent",
933                name
934            )));
935        }
936
937        // Send request: [CMD(1), ID(2)]
938        let request_data = vec![
939            MISC_PERSISTENT_CLEAR,
940            (param_id & 0xff) as u8,
941            (param_id >> 8) as u8,
942        ];
943        let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
944
945        // Lock before sending to prevent race conditions with concurrent requests
946        let misc_downlink = self.misc_downlink.lock().await;
947
948        self.uplink
949            .send_async(request)
950            .await
951            .map_err(|_| Error::Disconnected)?;
952
953        // Wait for response: [CMD(1), ID(2), STATUS(1)]
954        let response = misc_downlink
955            .wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
956            .await?;
957
958        let data = response.get_data();
959
960        // Verify response length
961        if data.len() < 4 {
962            return Err(Error::ProtocolError(format!(
963                "Response too short: expected 4 bytes, got {}",
964                data.len()
965            )));
966        }
967
968        let status = data[3];
969
970        match status {
971            0x00 => Ok(()),
972            x if x == libc::ENOENT as u8 => {
973                // Storage delete failed (couldn't delete from persistent storage)
974                // or parameter ID invalid (shouldn't happen since we verified the ID)
975                Err(Error::ParamError(format!(
976                    "Failed to clear parameter '{}' from persistent storage (storage delete failed)",
977                    name
978                )))
979            }
980            _ => Err(Error::ProtocolError(format!(
981                "Unexpected status code {} in persistent_clear response for '{}'",
982                status, name
983            ))),
984        }
985    }
986}
987
988#[cfg(test)]
989mod tests {
990    use super::*;
991
992    #[test]
993    fn param_toc_cache_format_stability() {
994        // This test pins the serialization format of ParamItemInfo.
995        // If it fails, the TOC cache format has changed. Bump TOC_CACHE_VERSION
996        // and update this test.
997        let info = ParamItemInfo { item_type: ValueType::U8, writable: true, has_extended_type: false };
998        let json = serde_json::to_string(&info).unwrap();
999        assert_eq!(json, r#"{"item_type":"U8","writable":true,"has_extended_type":false}"#);
1000    }
1001}