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