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#[derive(Debug, Serialize, Deserialize)]
33struct ParamItemInfo {
34    item_type: ValueType,
35    writable: bool,
36}
37
38impl TryFrom<u8> for ParamItemInfo {
39    type Error = Error;
40
41    fn try_from(value: u8) -> Result<Self> {
42        Ok(Self {
43            item_type: match value & 0x0f {
44                0x08 => ValueType::U8,
45                0x09 => ValueType::U16,
46                0x0A => ValueType::U32,
47                0x0B => ValueType::U64,
48                0x00 => ValueType::I8,
49                0x01 => ValueType::I16,
50                0x02 => ValueType::I32,
51                0x03 => ValueType::I64,
52                0x05 => ValueType::F16,
53                0x06 => ValueType::F32,
54                0x07 => ValueType::F64,
55                _ => {
56                    return Err(Error::ParamError(format!(
57                        "Type error in TOC: type {} is unknown",
58                        value & 0x0f
59                    )))
60                }
61            },
62            writable: (value & (1 << 6)) == 0,
63        })
64    }
65}
66
67type ParamChangeWatchers =
68    Arc<Mutex<Vec<futures::channel::mpsc::UnboundedSender<(String, Value)>>>>;
69
70/// # Access to the Crazyflie Param Subsystem
71///
72/// This struct provide methods to interact with the parameter subsystem. See the
73/// [param module documentation](crate::subsystems::param) for more context and information.
74#[derive(Debug)]
75pub struct Param {
76    uplink: channel::Sender<Packet>,
77    read_downlink: channel::Receiver<Packet>,
78    write_downlink: Mutex<channel::Receiver<Packet>>,
79    toc: Arc<BTreeMap<String, (u16, ParamItemInfo)>>,
80    values: Arc<Mutex<HashMap<String, Option<Value>>>>,
81    watchers: ParamChangeWatchers,
82}
83
84fn not_found(name: &str) -> Error {
85    Error::ParamError(format!("Parameter {} not found", name))
86}
87
88const READ_CHANNEL: u8 = 1;
89const _WRITE_CHANNEL: u8 = 2;
90const _MISC_CHANNEL: u8 = 3;
91
92impl Param {
93    pub(crate) async fn new<T>(
94        downlink: channel::Receiver<Packet>,
95        uplink: channel::Sender<Packet>,
96        toc_cache: T,
97    ) -> Result<Self>
98    where
99        T: TocCache,
100    {
101        let (toc_downlink, read_downlink, write_downlink, misc_downlink) =
102            crate::crtp_utils::crtp_channel_dispatcher(downlink);
103
104        let toc = crate::crtp_utils::fetch_toc(PARAM_PORT, uplink.clone(), toc_downlink, toc_cache).await?;
105
106        let mut param = Self {
107            uplink,
108            read_downlink,
109            write_downlink: Mutex::new(write_downlink),
110            toc: Arc::new(toc),
111            values: Arc::new(Mutex::new(HashMap::new())),
112            watchers: Arc::default(),
113        };
114
115        param.initialize_values().await?;
116
117        param.spawn_misc_loop(misc_downlink).await;
118
119        Ok(param)
120    }
121
122    async fn initialize_values(&mut self) -> Result<()> {
123        for (name, (_param_id, _info)) in self.toc.as_ref() {
124            let mut values = self.values.lock().await;
125            values.insert(
126                name.into(),
127                None,
128            );
129        }
130
131        Ok(())
132    }
133
134    async fn read_value(&self, param_id: u16, param_type: ValueType) -> Result<Value> {
135        let request = Packet::new(PARAM_PORT, READ_CHANNEL, param_id.to_le_bytes().into());
136        self.uplink
137            .send_async(request.clone())
138            .await
139            .map_err(|_| Error::Disconnected)?;
140
141        let response = self
142            .read_downlink
143            .wait_packet(
144                request.get_port(),
145                request.get_channel(),
146                request.get_data(),
147            )
148            .await?;
149
150        Value::from_le_bytes(&response.get_data()[3..], param_type)
151    }
152
153    async fn spawn_misc_loop(&self, misc_downlink: channel::Receiver<Packet>) {
154        let values = self.values.clone();
155        let toc = self.toc.clone();
156
157        tokio::spawn(async move {
158            while let Ok(pk) = misc_downlink.recv_async().await {
159                if pk.get_data()[0] != 1 {
160                    continue;
161                }
162
163                // The range sets the buffer to 2 bytes long so this unwrap cannot fail
164                let param_id = u16::from_le_bytes(pk.get_data()[1..3].try_into().unwrap());
165                if let Some((param, (_, item_info))) = toc.iter().find(|v| v.1 .0 == param_id) {
166                    if let Ok(value) =
167                        Value::from_le_bytes(&pk.get_data()[3..], item_info.item_type)
168                    {
169                        // The param is tested as being in the toc so this unwrap cannot fail.
170                        *values.lock().await.get_mut(param).unwrap() = Some(value);
171                    } else {
172                        println!("Error: Malformed param update");
173                        break;
174                    }
175                } else {
176                    println!("Error: malformed param update");
177                    break;
178                }
179            }
180            values.lock().await.clear();
181        });
182    }
183
184    /// Get the names of all the parameters
185    ///
186    /// The names contain group and name of the parameter variable formatted as
187    /// "group.name".
188    pub fn names(&self) -> Vec<String> {
189        self.toc.keys().cloned().collect()
190    }
191
192    /// Return the type of a parameter variable or an Error if the parameter does not exist.
193    pub fn get_type(&self, name: &str) -> Result<ValueType> {
194        Ok(self
195            .toc
196            .get(name)
197            .ok_or_else(|| not_found(name))?
198            .1
199            .item_type)
200    }
201
202    /// Return true if he parameter variable is writable. False otherwise.
203    ///
204    /// Return an error if the parameter does not exist.
205    pub fn is_writable(&self, name: &str) -> Result<bool> {
206        Ok(self
207            .toc
208            .get(name)
209            .ok_or_else(|| not_found(name))?
210            .1
211            .writable)
212    }
213
214    /// Set a parameter value.
215    ///
216    /// This function will set the variable value and wait for confirmation from the
217    /// Crazyflie. If the set is successful `Ok(())` is returned, otherwise the
218    /// error code reported by the Crazyflie is returned in the error.
219    ///
220    /// This function accepts any primitive type as well as the [Value](crate::Value) type. The
221    /// type of the param variable is checked at runtime and must match the type
222    /// given to the function, either the direct primitive type or the type
223    /// contained in the `Value` enum. For example, to write a u16 value, both lines are valid:
224    ///
225    /// ```no_run
226    /// # use crazyflie_lib::{Crazyflie, Value, Error};
227    /// # use crazyflie_link::LinkContext;
228    /// # async fn example() -> Result<(), Error> {
229    /// # let context = LinkContext::new();
230    /// # let cf = Crazyflie::connect_from_uri(
231    /// #   &context,
232    /// #   "radio://0/60/2M/E7E7E7E7E7",
233    /// #   crazyflie_lib::NoTocCache
234    /// # ).await?;
235    /// cf.param.set("example.param", 42u16).await?;  // From primitive
236    /// cf.param.set("example.param", Value::U16(42)).await?;  // From Value
237    /// # Ok(())
238    /// # };
239    /// ```
240    ///
241    /// Return an error in case of type mismatch or if the variable does not exist.
242    pub async fn set<T: Into<Value>>(&self, param: &str, value: T) -> Result<()> {
243        let value: Value = value.into();
244        let (param_id, param_info) = self.toc.get(param).ok_or_else(|| not_found(param))?;
245
246        if param_info.item_type != value.into() {
247            return Err(Error::ParamError(format!(
248                "Parameter {} is type {:?}, cannot set with value {:?}",
249                param, param_info.item_type, value
250            )));
251        }
252
253        let downlink = self.write_downlink.lock().await;
254
255        let mut request_data = Vec::from(param_id.to_le_bytes());
256        request_data.append(&mut value.into());
257        let request = Packet::new(PARAM_PORT, _WRITE_CHANNEL, request_data);
258        self.uplink
259            .send_async(request)
260            .await
261            .map_err(|_| Error::Disconnected)?;
262
263        let answer = downlink
264            .wait_packet(PARAM_PORT, _WRITE_CHANNEL, &param_id.to_le_bytes())
265            .await?;
266
267        // Success response: firmware echoes back the written value
268        let expected_bytes: Vec<u8> = value.into();
269        let data = answer.get_data();
270        if data.len() < 2 {
271            return Err(Error::ProtocolError(
272                format!("Parameter write response too short: expected at least 2 bytes, got {}", data.len())
273            ));
274        }
275        let echoed_bytes = &data[2..];
276
277        if echoed_bytes == expected_bytes.as_slice() {
278            // The param is tested as being in the TOC so this unwrap cannot fail
279            *self.values.lock().await.get_mut(param).unwrap() = Some(value);
280            self.notify_watchers(param, value).await;
281            Ok(())
282        } else {
283            // If echoed value doesn't match, it's likely a parameter error code
284            if echoed_bytes.is_empty() {
285                return Err(Error::ProtocolError(
286                    "Parameter write response invalid: no error code or echoed value".to_string()
287                ));
288            }
289            let error_code = echoed_bytes[0]; // For u8 params, single byte error code
290            Err(Error::ParamError(format!(
291                "Error setting parameter: parameter error code {}",
292                error_code
293            )))
294        }
295    }
296
297    /// Get param value
298    ///
299    /// Get value of a parameter. The first access will fetch the value from the
300    /// Crazyflie. Subsequent accesses are served from a local cache and are quick.
301    ///
302    /// Similarly to the `set` function above, the type of the param must match
303    /// the return parameter. For example to get a u16 param:
304    /// ```no_run
305    /// # use crazyflie_lib::{Crazyflie, Value, Error};
306    /// # use crazyflie_link::LinkContext;
307    /// # async fn example() -> Result<(), Error> {
308    /// # let context = LinkContext::new();
309    /// # let cf = Crazyflie::connect_from_uri(
310    /// #   &context,
311    /// #   "radio://0/60/2M/E7E7E7E7E7",
312    /// #   crazyflie_lib::NoTocCache
313    /// # ).await?;
314    /// let example: u16 = cf.param.get("example.param").await?;  // To primitive
315    /// dbg!(example);  // 42
316    /// let example: Value = cf.param.get("example.param").await?;  // To Value
317    /// dbg!(example);  // Value::U16(42)
318    /// # Ok(())
319    /// # };
320    /// ```
321    ///
322    /// Return an error in case of type mismatch or if the variable does not exist.
323    pub async fn get<T: TryFrom<Value>>(&self, name: &str) -> Result<T>
324    where
325        <T as TryFrom<Value>>::Error: std::fmt::Debug,
326    {
327        let mut values = self.values.lock().await;
328        
329        let value = *values.get(name)
330            .ok_or_else(|| not_found(name))?;
331
332        // If the value is None it means it has never been read, read it now and update the value
333        let value = match value {
334            Some(v) => v,
335            None => {
336                let (param_id, param_info) = self
337                    .toc
338                    .get(name)
339                    .ok_or_else(|| not_found(name))?;
340                let v = self.read_value(*param_id, param_info.item_type).await?;
341                // Update the cache
342                *values.get_mut(name).unwrap() = Some(v.clone());
343                v
344            }
345        };
346
347        Ok(value
348            .try_into()
349            .map_err(|e| Error::ParamError(format!("Type error reading param: {:?}", e)))?)
350    }
351
352    /// Set a parameter from a f64 potentially loosing data
353    ///
354    /// This function is a forgiving version of the `set` function. It allows
355    /// to set any parameter of any type from a `f64` value. This allows to set
356    /// parameters without caring about the type and risking a type mismatch
357    /// runtime error. Since there is no type or value check, loss of information
358    /// can happen when using this function.
359    ///
360    /// Loss of information can happen in the following cases:
361    ///  - When setting an integer, the value is truncated to the number of bit of the parameter
362    ///    - Example: Setting `257` to a `u8` variable will set it to the value `1`
363    ///  - Similarly floating point precision will be truncated to the parameter precision. Rounding is undefined.
364    ///  - Setting a floating point outside the range of the parameter is undefined.
365    ///  - It is not possible to represent accurately a `u64` parameter in a `f64`.
366    ///
367    /// Returns an error if the param does not exists.
368    pub async fn set_lossy(&self, name: &str, value: f64) -> Result<()> {
369        let param_type = self
370            .toc
371            .get(name)
372            .ok_or_else(|| not_found(name))?
373            .1
374            .item_type;
375
376        let value = Value::from_f64_lossy(param_type, value);
377
378        self.set(name, value).await
379    }
380
381    /// Get a parameter as a `f64` independently of the parameter type
382    ///
383    /// This function is a forgiving version of the `get` function. It allows
384    /// to get any parameter of any type as a `f64` value. This allows to get
385    /// parameters without caring about the type and risking a type mismatch
386    /// runtime error. Since there is no type or value check, loss of information
387    /// can happen when using this function.
388    ///
389    /// Loss of information can happen in the following cases:
390    ///  - It is not possible to represent accurately a `u64` parameter in a `f64`.
391    ///
392    /// Returns an error if the param does not exists.
393    pub async fn get_lossy(&self, name: &str) -> Result<f64> {
394        let value: Value = self.get(name).await?;
395
396        Ok(value.to_f64_lossy())
397    }
398
399    /// Get notified for all parameter value change
400    ///
401    /// This function returns an async stream that will generate a tuple containing
402    /// the name of the variable that has changed (in the form of group.name)
403    /// and its new value.
404    ///
405    /// There can be two reasons for a parameter to change:
406    ///  - Either the parameter was changed by a call to [Param::set()]. The
407    ///    notification will be generated when the Crazyflie confirms the parameter
408    ///    has been set.
409    ///  - Or it can be a parameter change in the Crazyflie itself. The Crazyflie
410    ///    will send notification packet for every internal parameter change.
411    pub async fn watch_change(&self) -> impl futures::Stream<Item = (String, Value)> + use<> {
412        let (tx, rx) = futures::channel::mpsc::unbounded();
413
414        let mut watchers = self.watchers.lock().await;
415        watchers.push(tx);
416
417        rx
418    }
419
420    async fn notify_watchers(&self, name: &str, value: Value) {
421        let mut to_remove = Vec::new();
422        let mut watchers = self.watchers.lock().await;
423
424        for (i, watcher) in watchers.iter().enumerate() {
425            if watcher.unbounded_send((name.to_owned(), value)).is_err() {
426                to_remove.push(i);
427            }
428        }
429
430        // Remove watchers that have dropped
431        for i in to_remove.into_iter().rev() {
432            watchers.remove(i);
433        }
434    }
435}