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, ¶m_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}