More convinent one time parameter access (#107)

* Implement TryInto for ParameterValue

* Add `get_parameter` method.

* Consolidate 'ParameterNotSet' into 'ParamterWrongType' error

* Add support for optional parameters

* Add example for get_parameter.
This commit is contained in:
James Carl 2024-10-13 04:51:47 -04:00 committed by GitHub
parent 09c8a8ae11
commit d8fe90b61d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 137 additions and 0 deletions

View File

@ -18,6 +18,24 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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::<String>("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<i64> = 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()?;

View File

@ -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 {

View File

@ -541,6 +541,29 @@ impl Node {
future::ready(())
}
/// Fetch a single ROS parameter.
pub fn get_parameter<T>(&self, name: &str) -> Result<T>
where
ParameterValue: TryInto<T, Error = WrongParameterType>,
{
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.

View File

@ -20,7 +20,94 @@ pub enum ParameterValue {
StringArray(Vec<String>),
}
#[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<bool>, "boolean array", ParameterValue::BoolArray(value) => value);
try_into_template!(Vec<u8>, "byte array", ParameterValue::ByteArray(value) => value);
try_into_template!(Vec<i64>, "integer array", ParameterValue::IntegerArray(value) => value);
try_into_template!(Vec<f64>, "double array", ParameterValue::DoubleArray(value) => value);
try_into_template!(Vec<String>, "string array", ParameterValue::StringArray(value) => value);
macro_rules! try_into_option_template {
($ty:ty, $expected_type_name:literal, $variant:pat => $result:expr) => {
impl TryInto<Option<$ty>> for ParameterValue {
type Error = WrongParameterType;
fn try_into(self) -> std::prelude::v1::Result<Option<$ty>, 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<bool>, "boolean array", ParameterValue::BoolArray(value) => value);
try_into_option_template!(Vec<u8>, "byte array", ParameterValue::ByteArray(value) => value);
try_into_option_template!(Vec<i64>, "integer array", ParameterValue::IntegerArray(value) => value);
try_into_option_template!(Vec<f64>, "double array", ParameterValue::DoubleArray(value) => value);
try_into_option_template!(Vec<String>, "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 })