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()?; diff --git a/r2r/src/error.rs b/r2r/src/error.rs index 5ad59ac0c..6518da77e 100644 --- a/r2r/src/error.rs +++ b/r2r/src/error.rs @@ -127,6 +127,15 @@ pub enum Error { #[error("Parameter {name} conversion failed: {msg}")] ParameterValueConv { name: String, msg: 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..e119f2353 100644 --- a/r2r/src/nodes.rs +++ b/r2r/src/nodes.rs @@ -541,6 +541,29 @@ 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 value = params + .get(name) + .map(|parameter| parameter.value.clone()) + .unwrap_or(ParameterValue::NotSet); + + let value: T = + value + .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 5648f1ed1..1497af201 100644 --- a/r2r/src/parameters.rs +++ b/r2r/src/parameters.rs @@ -20,7 +20,94 @@ pub enum ParameterValue { StringArray(Vec), } +#[derive(Debug)] +pub struct WrongParameterType { + pub expected_type_name: &'static str, + pub 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); + +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 { + 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 })