From ba2161356a93e3d179e1d69fd47d6023fe95a9ac Mon Sep 17 00:00:00 2001 From: James Carl Date: Tue, 8 Oct 2024 21:45:56 -0400 Subject: [PATCH 1/5] Implement TryInto for ParameterValue --- r2r/src/parameters.rs | 58 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/r2r/src/parameters.rs b/r2r/src/parameters.rs index 5648f1ed1..fa9cb0484 100644 --- a/r2r/src/parameters.rs +++ b/r2r/src/parameters.rs @@ -20,7 +20,65 @@ pub enum ParameterValue { StringArray(Vec), } +#[derive(Debug)] +pub struct WrongParameterType { + expected_type_name: &'static str, + actual_type_name: &'static str, +} + +impl std::error::Error for WrongParameterType {} + +impl std::fmt::Display for WrongParameterType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "got `{}`, expected `{}`", self.actual_type_name, self.expected_type_name) + } +} + +macro_rules! try_into_template { + ($ty:ty, $expected_type_name:literal, $variant:pat => $result:expr) => { + impl TryInto<$ty> for ParameterValue { + type Error = WrongParameterType; + + fn try_into(self) -> std::prelude::v1::Result<$ty, Self::Error> { + match self { + $variant => Ok($result), + _ => Err(WrongParameterType { + expected_type_name: $expected_type_name, + actual_type_name: self.type_name(), + }), + } + } + } + }; +} + +try_into_template!((), "not set", ParameterValue::NotSet => ()); +try_into_template!(bool, "boolean", ParameterValue::Bool(value) => value); +try_into_template!(i64, "integer", ParameterValue::Integer(value) => value); +try_into_template!(f64, "double", ParameterValue::Double(value) => value); +try_into_template!(String, "string", ParameterValue::String(value) => value); +try_into_template!(Vec, "boolean array", ParameterValue::BoolArray(value) => value); +try_into_template!(Vec, "byte array", ParameterValue::ByteArray(value) => value); +try_into_template!(Vec, "integer array", ParameterValue::IntegerArray(value) => value); +try_into_template!(Vec, "double array", ParameterValue::DoubleArray(value) => value); +try_into_template!(Vec, "string array", ParameterValue::StringArray(value) => value); + impl ParameterValue { + pub fn type_name(&self) -> &'static str { + match self { + ParameterValue::NotSet => "not set", + ParameterValue::Bool(_) => "boolean", + ParameterValue::Integer(_) => "integer", + ParameterValue::Double(_) => "double", + ParameterValue::String(_) => "string", + ParameterValue::BoolArray(_) => "boolean array", + ParameterValue::ByteArray(_) => "byte array", + ParameterValue::IntegerArray(_) => "integer array", + ParameterValue::DoubleArray(_) => "double array", + ParameterValue::StringArray(_) => "string array", + } + } + pub(crate) fn from_rcl(v: &rcl_variant_t) -> Self { if !v.bool_value.is_null() { ParameterValue::Bool(unsafe { *v.bool_value }) From a7093a8220493f9d9dbc9b99d9b53833bbf8c203 Mon Sep 17 00:00:00 2001 From: James Carl Date: Tue, 8 Oct 2024 22:25:41 -0400 Subject: [PATCH 2/5] Add `get_parameter` method. --- r2r/src/error.rs | 12 ++++++++++++ r2r/src/nodes.rs | 24 ++++++++++++++++++++++++ r2r/src/parameters.rs | 4 ++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/r2r/src/error.rs b/r2r/src/error.rs index 5ad59ac0c..389653777 100644 --- a/r2r/src/error.rs +++ b/r2r/src/error.rs @@ -127,6 +127,18 @@ pub enum Error { #[error("Parameter {name} conversion failed: {msg}")] ParameterValueConv { name: String, msg: String }, + + #[error("Expected parameter {name} is not set")] + ParameterNotSet { name: String }, + + #[error( + "Parameter {name} was expected to be of type {expected_type} but was of type {actual_type}" + )] + ParameterWrongType { + name: String, + expected_type: &'static str, + actual_type: &'static str, + }, } impl Error { diff --git a/r2r/src/nodes.rs b/r2r/src/nodes.rs index 7610535fd..cde555bf5 100644 --- a/r2r/src/nodes.rs +++ b/r2r/src/nodes.rs @@ -541,6 +541,30 @@ impl Node { future::ready(()) } + /// Fetch a single ROS parameter. + pub fn get_parameter(&self, name: &str) -> Result + where + ParameterValue: TryInto, + { + let params = self.params.lock().unwrap(); + let parameter = params.get(name).ok_or(Error::ParameterNotSet { + name: name.to_string(), + })?; + + let value: T = + parameter + .value + .clone() + .try_into() + .map_err(|error: WrongParameterType| Error::ParameterWrongType { + name: name.to_string(), + expected_type: error.expected_type_name, + actual_type: error.actual_type_name, + })?; + + Ok(value) + } + /// Subscribe to a ROS topic. /// /// This function returns a `Stream` of ros messages. diff --git a/r2r/src/parameters.rs b/r2r/src/parameters.rs index fa9cb0484..8c56b06c7 100644 --- a/r2r/src/parameters.rs +++ b/r2r/src/parameters.rs @@ -22,8 +22,8 @@ pub enum ParameterValue { #[derive(Debug)] pub struct WrongParameterType { - expected_type_name: &'static str, - actual_type_name: &'static str, + pub expected_type_name: &'static str, + pub actual_type_name: &'static str, } impl std::error::Error for WrongParameterType {} From e2b68c9903d3ab2b744980e9094d348f4499b3ae Mon Sep 17 00:00:00 2001 From: James Carl Date: Wed, 9 Oct 2024 17:19:51 -0400 Subject: [PATCH 3/5] Consolidate 'ParameterNotSet' into 'ParamterWrongType' error --- r2r/src/error.rs | 3 --- r2r/src/nodes.rs | 11 +++++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/r2r/src/error.rs b/r2r/src/error.rs index 389653777..6518da77e 100644 --- a/r2r/src/error.rs +++ b/r2r/src/error.rs @@ -128,9 +128,6 @@ pub enum Error { #[error("Parameter {name} conversion failed: {msg}")] ParameterValueConv { name: String, msg: String }, - #[error("Expected parameter {name} is not set")] - ParameterNotSet { name: String }, - #[error( "Parameter {name} was expected to be of type {expected_type} but was of type {actual_type}" )] diff --git a/r2r/src/nodes.rs b/r2r/src/nodes.rs index cde555bf5..e119f2353 100644 --- a/r2r/src/nodes.rs +++ b/r2r/src/nodes.rs @@ -547,14 +547,13 @@ impl Node { ParameterValue: TryInto, { let params = self.params.lock().unwrap(); - let parameter = params.get(name).ok_or(Error::ParameterNotSet { - name: name.to_string(), - })?; + let value = params + .get(name) + .map(|parameter| parameter.value.clone()) + .unwrap_or(ParameterValue::NotSet); let value: T = - parameter - .value - .clone() + value .try_into() .map_err(|error: WrongParameterType| Error::ParameterWrongType { name: name.to_string(), From a41dc40f72130d076cff088937c124f9aa44cfdc Mon Sep 17 00:00:00 2001 From: James Carl Date: Wed, 9 Oct 2024 17:26:24 -0400 Subject: [PATCH 4/5] Add support for optional parameters --- r2r/src/parameters.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/r2r/src/parameters.rs b/r2r/src/parameters.rs index 8c56b06c7..1497af201 100644 --- a/r2r/src/parameters.rs +++ b/r2r/src/parameters.rs @@ -63,6 +63,35 @@ try_into_template!(Vec, "integer array", ParameterValue::IntegerArray(value try_into_template!(Vec, "double array", ParameterValue::DoubleArray(value) => value); try_into_template!(Vec, "string array", ParameterValue::StringArray(value) => value); +macro_rules! try_into_option_template { + ($ty:ty, $expected_type_name:literal, $variant:pat => $result:expr) => { + impl TryInto> for ParameterValue { + type Error = WrongParameterType; + + fn try_into(self) -> std::prelude::v1::Result, Self::Error> { + match self { + $variant => Ok(Some($result)), + ParameterValue::NotSet => Ok(None), + _ => Err(WrongParameterType { + expected_type_name: $expected_type_name, + actual_type_name: self.type_name(), + }), + } + } + } + }; +} + +try_into_option_template!(bool, "boolean", ParameterValue::Bool(value) => value); +try_into_option_template!(i64, "integer", ParameterValue::Integer(value) => value); +try_into_option_template!(f64, "double", ParameterValue::Double(value) => value); +try_into_option_template!(String, "string", ParameterValue::String(value) => value); +try_into_option_template!(Vec, "boolean array", ParameterValue::BoolArray(value) => value); +try_into_option_template!(Vec, "byte array", ParameterValue::ByteArray(value) => value); +try_into_option_template!(Vec, "integer array", ParameterValue::IntegerArray(value) => value); +try_into_option_template!(Vec, "double array", ParameterValue::DoubleArray(value) => value); +try_into_option_template!(Vec, "string array", ParameterValue::StringArray(value) => value); + impl ParameterValue { pub fn type_name(&self) -> &'static str { match self { From 0577a2e011bed8990b9b6bc13972e2fd234fcf7b Mon Sep 17 00:00:00 2001 From: James Carl Date: Sat, 12 Oct 2024 17:47:57 -0400 Subject: [PATCH 5/5] Add example for get_parameter. --- r2r/examples/parameters.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/r2r/examples/parameters.rs b/r2r/examples/parameters.rs index 1af41bda5..a3ffb8e7a 100644 --- a/r2r/examples/parameters.rs +++ b/r2r/examples/parameters.rs @@ -18,6 +18,24 @@ fn main() -> Result<(), Box> { let ctx = r2r::Context::create()?; let mut node = r2r::Node::create(ctx, "to_be_replaced", "to_be_replaced")?; + // if you only need to load a parameter once at startup, it can be done like this. + // errors can be propigated with the ? operator and enhanced with the `thiserror` and `anyhow` crates. + // we do not use the ? operator here because we want the program to continue, even if the value is not set. + let serial_interface_path = node.get_parameter::("serial_interface"); + match serial_interface_path { + Ok(serial_interface) => println!("Serial interface: {serial_interface}"), + Err(error) => println!("Failed to get name of serial interface: {error}"), + } + + // you can also get parameters as optional types. + // this will be None if the parameter is not set. If the parameter is set but to the wrong type, this will + // will produce an error. + let baud_rate: Option = node.get_parameter("baud_rate")?; + + // because the baud_rate is an optional type, we can use `unwrap_or` to provide a default value. + let baud_rate = baud_rate.unwrap_or(115200); + println!("Baud rate: {baud_rate}"); + // make a parameter handler (once per node). // the parameter handler is optional, only spawn one if you need it. let (paramater_handler, parameter_events) = node.make_parameter_handler()?;