use crate::crtp_utils::TocCache;
use crate::{crtp_utils::WaitForPacket, Error, Result};
use crate::{Value, ValueType};
use crazyflie_link::Packet;
use flume as channel;
use futures::lock::Mutex;
use serde::{Serialize, Deserialize};
use std::{
collections::{BTreeMap, HashMap},
convert::{TryFrom, TryInto},
sync::Arc,
};
use crate::crazyflie::PARAM_PORT;
#[derive(Debug, Clone)]
pub struct PersistentParamState {
pub is_stored: bool,
pub default_value: Value,
pub stored_value: Option<Value>,
}
#[derive(Debug, Clone, Copy)]
enum DefaultValueCache {
Value(Value),
Unsupported,
}
#[derive(Debug, Serialize, Deserialize)]
struct ParamItemInfo {
item_type: ValueType,
writable: bool,
has_extended_type: bool, }
impl TryFrom<u8> for ParamItemInfo {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
Ok(Self {
item_type: match value & 0x0f {
0x08 => ValueType::U8,
0x09 => ValueType::U16,
0x0A => ValueType::U32,
0x0B => ValueType::U64,
0x00 => ValueType::I8,
0x01 => ValueType::I16,
0x02 => ValueType::I32,
0x03 => ValueType::I64,
0x05 => ValueType::F16,
0x06 => ValueType::F32,
0x07 => ValueType::F64,
_ => {
return Err(Error::ParamError(format!(
"Type error in TOC: type {} is unknown",
value & 0x0f
)))
}
},
writable: (value & (1 << 6)) == 0,
has_extended_type: (value & (1 << 4)) != 0,
})
}
}
type ParamChangeWatchers =
Arc<Mutex<Vec<futures::channel::mpsc::UnboundedSender<(String, Value)>>>>;
async fn notify_watchers(watchers: &ParamChangeWatchers, name: String, value: Value) {
let mut to_remove = Vec::new();
let mut watchers = watchers.lock().await;
for (i, watcher) in watchers.iter().enumerate() {
if watcher.unbounded_send((name.clone(), value)).is_err() {
to_remove.push(i);
}
}
for i in to_remove.into_iter().rev() {
watchers.remove(i);
}
}
#[derive(Debug)]
pub struct Param {
uplink: channel::Sender<Packet>,
read_downlink: channel::Receiver<Packet>,
write_downlink: Mutex<channel::Receiver<Packet>>,
misc_downlink: Mutex<channel::Receiver<Packet>>,
toc: Arc<BTreeMap<String, (u16, ParamItemInfo)>>,
values: Arc<Mutex<HashMap<String, Option<Value>>>>,
default_values: Arc<Mutex<HashMap<String, DefaultValueCache>>>,
watchers: ParamChangeWatchers,
}
fn not_found(name: &str) -> Error {
Error::ParamError(format!("Parameter {} not found", name))
}
const READ_CHANNEL: u8 = 1;
const _WRITE_CHANNEL: u8 = 2;
const MISC_CHANNEL: u8 = 3;
const _MISC_GET_EXTENDED_TYPE: u8 = 2; const MISC_PERSISTENT_STORE: u8 = 3;
const MISC_PERSISTENT_GET_STATE: u8 = 4;
const MISC_PERSISTENT_CLEAR: u8 = 5;
const _MISC_GET_DEFAULT_VALUE: u8 = 6; const MISC_GET_EXTENDED_TYPE_V2: u8 = 7;
const MISC_GET_DEFAULT_VALUE_V2: u8 = 8;
const PARAM_PERSISTENT_NOT_STORED: u8 = 0;
const PARAM_PERSISTENT_STORED: u8 = 1;
const PARAM_NOT_FOUND: u8 = 2;
impl Param {
pub(crate) async fn new<T>(
downlink: channel::Receiver<Packet>,
uplink: channel::Sender<Packet>,
toc_cache: T,
) -> Result<Self>
where
T: TocCache,
{
let (toc_downlink, read_downlink, write_downlink, misc_downlink) =
crate::crtp_utils::crtp_channel_dispatcher(downlink);
let toc = crate::crtp_utils::fetch_toc(PARAM_PORT, uplink.clone(), toc_downlink, toc_cache).await?;
let (misc_cmd_tx, misc_cmd_rx) = channel::unbounded();
let mut param = Self {
uplink,
read_downlink,
write_downlink: Mutex::new(write_downlink),
misc_downlink: Mutex::new(misc_cmd_rx),
toc: Arc::new(toc),
values: Arc::new(Mutex::new(HashMap::new())),
default_values: Arc::new(Mutex::new(HashMap::new())),
watchers: Arc::default(),
};
param.initialize_values().await?;
param.spawn_misc_loop(misc_downlink, misc_cmd_tx).await;
Ok(param)
}
async fn initialize_values(&mut self) -> Result<()> {
for (name, (_param_id, _info)) in self.toc.as_ref() {
let mut values = self.values.lock().await;
values.insert(
name.into(),
None,
);
}
Ok(())
}
async fn read_value(&self, param_id: u16, param_type: ValueType) -> Result<Value> {
let request = Packet::new(PARAM_PORT, READ_CHANNEL, param_id.to_le_bytes().into());
self.uplink
.send_async(request.clone())
.await
.map_err(|_| Error::Disconnected)?;
let response = self
.read_downlink
.wait_packet(
request.get_port(),
request.get_channel(),
request.get_data(),
)
.await?;
Value::from_le_bytes(&response.get_data()[3..], param_type)
}
async fn spawn_misc_loop(&self, misc_downlink: channel::Receiver<Packet>, misc_cmd_tx: channel::Sender<Packet>) {
let values = self.values.clone();
let toc = self.toc.clone();
let watchers = self.watchers.clone();
tokio::spawn(async move {
while let Ok(pk) = misc_downlink.recv_async().await {
if pk.get_data().first() == Some(&1) {
let param_id = u16::from_le_bytes(pk.get_data()[1..3].try_into().unwrap());
if let Some((param, (_, item_info))) = toc.iter().find(|v| v.1 .0 == param_id) {
if let Ok(value) =
Value::from_le_bytes(&pk.get_data()[3..], item_info.item_type)
{
*values.lock().await.get_mut(param).unwrap() = Some(value);
notify_watchers(&watchers, param.clone(), value).await;
} else {
println!("Error: Malformed param update");
break;
}
} else {
println!("Error: malformed param update");
break;
}
} else {
let _ = misc_cmd_tx.send_async(pk).await;
}
}
values.lock().await.clear();
});
}
pub fn names(&self) -> Vec<String> {
self.toc.keys().cloned().collect()
}
pub fn get_type(&self, name: &str) -> Result<ValueType> {
Ok(self
.toc
.get(name)
.ok_or_else(|| not_found(name))?
.1
.item_type)
}
pub fn is_writable(&self, name: &str) -> Result<bool> {
Ok(self
.toc
.get(name)
.ok_or_else(|| not_found(name))?
.1
.writable)
}
pub async fn set<T: Into<Value>>(&self, param: &str, value: T) -> Result<()> {
let value: Value = value.into();
let (param_id, param_info) = self.toc.get(param).ok_or_else(|| not_found(param))?;
if param_info.item_type != value.into() {
return Err(Error::ParamError(format!(
"Parameter {} is type {:?}, cannot set with value {:?}",
param, param_info.item_type, value
)));
}
let downlink = self.write_downlink.lock().await;
let mut request_data = Vec::from(param_id.to_le_bytes());
request_data.append(&mut value.into());
let request = Packet::new(PARAM_PORT, _WRITE_CHANNEL, request_data);
self.uplink
.send_async(request)
.await
.map_err(|_| Error::Disconnected)?;
let answer = downlink
.wait_packet(PARAM_PORT, _WRITE_CHANNEL, ¶m_id.to_le_bytes())
.await?;
let expected_bytes: Vec<u8> = value.into();
let data = answer.get_data();
if data.len() < 2 {
return Err(Error::ProtocolError(
format!("Parameter write response too short: expected at least 2 bytes, got {}", data.len())
));
}
let echoed_bytes = &data[2..];
if echoed_bytes == expected_bytes.as_slice() {
*self.values.lock().await.get_mut(param).unwrap() = Some(value);
notify_watchers(&self.watchers, param.to_owned(), value).await;
Ok(())
} else {
if echoed_bytes.is_empty() {
return Err(Error::ProtocolError(
"Parameter write response invalid: no error code or echoed value".to_string()
));
}
let error_code = echoed_bytes[0]; Err(Error::ParamError(format!(
"Error setting parameter: parameter error code {}",
error_code
)))
}
}
pub async fn get<T: TryFrom<Value>>(&self, name: &str) -> Result<T>
where
<T as TryFrom<Value>>::Error: std::fmt::Debug,
{
let mut values = self.values.lock().await;
let value = *values.get(name)
.ok_or_else(|| not_found(name))?;
let value = match value {
Some(v) => v,
None => {
let (param_id, param_info) = self
.toc
.get(name)
.ok_or_else(|| not_found(name))?;
let v = self.read_value(*param_id, param_info.item_type).await?;
*values.get_mut(name).unwrap() = Some(v.clone());
v
}
};
Ok(value
.try_into()
.map_err(|e| Error::ParamError(format!("Type error reading param: {:?}", e)))?)
}
pub async fn set_lossy(&self, name: &str, value: f64) -> Result<()> {
let param_type = self
.toc
.get(name)
.ok_or_else(|| not_found(name))?
.1
.item_type;
let value = Value::from_f64_lossy(param_type, value);
self.set(name, value).await
}
pub async fn get_lossy(&self, name: &str) -> Result<f64> {
let value: Value = self.get(name).await?;
Ok(value.to_f64_lossy())
}
pub async fn watch_change(&self) -> impl futures::Stream<Item = (String, Value)> + use<> {
let (tx, rx) = futures::channel::mpsc::unbounded();
let mut watchers = self.watchers.lock().await;
watchers.push(tx);
rx
}
pub async fn is_persistent(&self, name: &str) -> Result<bool> {
let (_, param_info) = self.toc.get(name).ok_or_else(|| not_found(name))?;
if !param_info.has_extended_type {
return Ok(false);
}
let extended_type = self.get_extended_type(name).await?;
Ok((extended_type & 0x01) != 0)
}
pub async fn get_extended_type(&self, name: &str) -> Result<u8> {
let (param_id, _) = self.toc.get(name).ok_or_else(|| not_found(name))?;
let request_data = vec![
MISC_GET_EXTENDED_TYPE_V2,
(param_id & 0xff) as u8,
(param_id >> 8) as u8,
];
let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
let misc_downlink = self.misc_downlink.lock().await;
self.uplink
.send_async(request)
.await
.map_err(|_| Error::Disconnected)?;
let response = misc_downlink
.wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
.await?;
let data = response.get_data();
if data.len() < 4 {
return Err(Error::ProtocolError(format!(
"Response too short: expected at least 4 bytes, got {}",
data.len()
)));
}
if data.len() == 4 {
let error_code = data[3];
if error_code == libc::ENOENT as u8 {
return Err(Error::ParamError(format!(
"Parameter '{}' does not have extended type info (not marked as PARAM_EXTENDED in firmware)",
name
)));
} else {
return Err(Error::ParamError(format!(
"Failed to get extended type for '{}': error code {}",
name, error_code
)));
}
}
if data.len() < 5 {
return Err(Error::ProtocolError(format!(
"Response too short for V2 success: expected 5 bytes, got {}",
data.len()
)));
}
let status = data[3];
if status != 0x00 {
return Err(Error::ProtocolError(format!(
"Unexpected status byte in V2 response: expected 0x00, got 0x{:02x}",
status
)));
}
Ok(data[4])
}
pub async fn get_default_value(&self, name: &str) -> Result<Value> {
{
let cache = self.default_values.lock().await;
if let Some(cached) = cache.get(name) {
return match cached {
DefaultValueCache::Value(v) => Ok(*v),
DefaultValueCache::Unsupported => Err(Error::ParamError(format!(
"Parameter '{}' does not support get_default_value (read-only or invalid)",
name
))),
};
}
}
let (param_id, param_info) = self.toc.get(name).ok_or_else(|| not_found(name))?;
let request_data = vec![
MISC_GET_DEFAULT_VALUE_V2,
(param_id & 0xff) as u8,
(param_id >> 8) as u8,
];
let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
let misc_downlink = self.misc_downlink.lock().await;
self.uplink
.send_async(request)
.await
.map_err(|_| Error::Disconnected)?;
let response = misc_downlink
.wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
.await?;
let data = response.get_data();
if data.len() < 4 {
return Err(Error::ProtocolError(format!(
"Response too short: expected at least 4 bytes, got {}",
data.len()
)));
}
if data.len() == 4 {
let error_code = data[3];
if error_code == libc::ENOENT as u8 {
let mut cache = self.default_values.lock().await;
cache.insert(name.to_owned(), DefaultValueCache::Unsupported);
return Err(Error::ParamError(format!(
"Parameter '{}' does not support get_default_value (read-only or invalid)",
name
)));
} else {
return Err(Error::ParamError(format!(
"Failed to get default value for '{}': error code {}",
name, error_code
)));
}
}
let status = data[3];
if status != 0x00 {
return Err(Error::ProtocolError(format!(
"Unexpected status byte in V2 response: expected 0x00, got 0x{:02x}",
status
)));
}
let value = Value::from_le_bytes(&data[4..], param_info.item_type)?;
{
let mut cache = self.default_values.lock().await;
cache.insert(name.to_owned(), DefaultValueCache::Value(value));
}
Ok(value)
}
pub async fn persistent_get_state(&self, name: &str) -> Result<PersistentParamState> {
let (param_id, param_info) = self.toc.get(name).ok_or_else(|| not_found(name))?;
if !self.is_persistent(name).await? {
return Err(Error::ParamError(format!(
"Parameter '{}' is not persistent",
name
)));
}
let request_data = vec![
MISC_PERSISTENT_GET_STATE,
(param_id & 0xff) as u8,
(param_id >> 8) as u8,
];
let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
let misc_downlink = self.misc_downlink.lock().await;
self.uplink
.send_async(request)
.await
.map_err(|_| Error::Disconnected)?;
let response = misc_downlink
.wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
.await?;
let data = response.get_data();
if data.len() < 4 {
return Err(Error::ProtocolError(format!(
"Response too short: expected at least 4 bytes, got {}",
data.len()
)));
}
let status = data[3];
let is_stored = match status {
PARAM_PERSISTENT_NOT_STORED => false,
PARAM_PERSISTENT_STORED => true,
PARAM_NOT_FOUND => {
return Err(Error::ParamError(format!(
"Parameter ID for '{}' is invalid or doesn't exist in firmware",
name
)));
}
_ => {
return Err(Error::ProtocolError(format!(
"Unexpected status code {} in persistent_get_state response for '{}'",
status, name
)));
}
};
let value_size = param_info.item_type.byte_length();
if is_stored {
if data.len() < 4 + 2 * value_size {
return Err(Error::ProtocolError(format!(
"Response too short for stored state: expected {} bytes, got {}",
4 + 2 * value_size,
data.len()
)));
}
let default_value = Value::from_le_bytes(&data[4..4 + value_size], param_info.item_type)?;
let stored_value = Value::from_le_bytes(&data[4 + value_size..4 + 2 * value_size], param_info.item_type)?;
Ok(PersistentParamState {
is_stored: true,
default_value,
stored_value: Some(stored_value),
})
} else {
if data.len() < 4 + value_size {
return Err(Error::ProtocolError(format!(
"Response too short for default value: expected {} bytes, got {}",
4 + value_size,
data.len()
)));
}
let default_value = Value::from_le_bytes(&data[4..4 + value_size], param_info.item_type)?;
Ok(PersistentParamState {
is_stored: false,
default_value,
stored_value: None,
})
}
}
pub async fn persistent_store(&self, name: &str) -> Result<()> {
let (param_id, _) = self.toc.get(name).ok_or_else(|| not_found(name))?;
if !self.is_persistent(name).await? {
return Err(Error::ParamError(format!(
"Parameter '{}' is not persistent",
name
)));
}
let request_data = vec![
MISC_PERSISTENT_STORE,
(param_id & 0xff) as u8,
(param_id >> 8) as u8,
];
let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
let misc_downlink = self.misc_downlink.lock().await;
self.uplink
.send_async(request)
.await
.map_err(|_| Error::Disconnected)?;
let response = misc_downlink
.wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
.await?;
let data = response.get_data();
if data.len() < 4 {
return Err(Error::ProtocolError(format!(
"Response too short: expected 4 bytes, got {}",
data.len()
)));
}
let status = data[3];
match status {
0x00 => Ok(()),
x if x == libc::ENOENT as u8 => {
Err(Error::ParamError(format!(
"Failed to store parameter '{}' to persistent storage (storage write failed)",
name
)))
}
_ => Err(Error::ProtocolError(format!(
"Unexpected status code {} in persistent_store response for '{}'",
status, name
))),
}
}
pub async fn persistent_clear(&self, name: &str) -> Result<()> {
let (param_id, _) = self.toc.get(name).ok_or_else(|| not_found(name))?;
if !self.is_persistent(name).await? {
return Err(Error::ParamError(format!(
"Parameter '{}' is not persistent",
name
)));
}
let request_data = vec![
MISC_PERSISTENT_CLEAR,
(param_id & 0xff) as u8,
(param_id >> 8) as u8,
];
let request = Packet::new(PARAM_PORT, MISC_CHANNEL, request_data.clone());
let misc_downlink = self.misc_downlink.lock().await;
self.uplink
.send_async(request)
.await
.map_err(|_| Error::Disconnected)?;
let response = misc_downlink
.wait_packet(PARAM_PORT, MISC_CHANNEL, &request_data)
.await?;
let data = response.get_data();
if data.len() < 4 {
return Err(Error::ProtocolError(format!(
"Response too short: expected 4 bytes, got {}",
data.len()
)));
}
let status = data[3];
match status {
0x00 => Ok(()),
x if x == libc::ENOENT as u8 => {
Err(Error::ParamError(format!(
"Failed to clear parameter '{}' from persistent storage (storage delete failed)",
name
)))
}
_ => Err(Error::ProtocolError(format!(
"Unexpected status code {} in persistent_clear response for '{}'",
status, name
))),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn param_toc_cache_format_stability() {
let info = ParamItemInfo { item_type: ValueType::U8, writable: true, has_extended_type: false };
let json = serde_json::to_string(&info).unwrap();
assert_eq!(json, r#"{"item_type":"U8","writable":true,"has_extended_type":false}"#);
}
}