use std::{ffi::CStr, sync::Arc};
use crate::{
parameter::{ParameterRange, ParameterRanges},
rcl_bindings::*,
vendor::rcl_interfaces::msg::rmw::{ParameterType, ParameterValue as RmwParameterValue},
ParameterValueError,
};
#[derive(Clone, Debug, PartialEq)]
pub enum ParameterValue {
Bool(bool),
Integer(i64),
Double(f64),
String(Arc<str>),
ByteArray(Arc<[u8]>),
BoolArray(Arc<[bool]>),
IntegerArray(Arc<[i64]>),
DoubleArray(Arc<[f64]>),
StringArray(Arc<[Arc<str>]>),
}
#[derive(Clone, Debug, PartialEq)]
pub enum ParameterKind {
Bool,
Integer,
Double,
String,
ByteArray,
BoolArray,
IntegerArray,
DoubleArray,
StringArray,
Dynamic,
}
impl From<bool> for ParameterValue {
fn from(value: bool) -> ParameterValue {
ParameterValue::Bool(value)
}
}
impl From<i64> for ParameterValue {
fn from(value: i64) -> ParameterValue {
ParameterValue::Integer(value)
}
}
impl From<f64> for ParameterValue {
fn from(value: f64) -> ParameterValue {
ParameterValue::Double(value)
}
}
impl From<Arc<str>> for ParameterValue {
fn from(value: Arc<str>) -> ParameterValue {
ParameterValue::String(value)
}
}
impl From<Arc<[u8]>> for ParameterValue {
fn from(value: Arc<[u8]>) -> ParameterValue {
ParameterValue::ByteArray(value)
}
}
impl From<Arc<[bool]>> for ParameterValue {
fn from(value: Arc<[bool]>) -> ParameterValue {
ParameterValue::BoolArray(value)
}
}
impl From<Arc<[i64]>> for ParameterValue {
fn from(value: Arc<[i64]>) -> ParameterValue {
ParameterValue::IntegerArray(value)
}
}
impl From<Arc<[f64]>> for ParameterValue {
fn from(value: Arc<[f64]>) -> ParameterValue {
ParameterValue::DoubleArray(value)
}
}
impl From<Arc<[Arc<str>]>> for ParameterValue {
fn from(value: Arc<[Arc<str>]>) -> ParameterValue {
ParameterValue::StringArray(value)
}
}
pub trait ParameterVariant: Into<ParameterValue> + Clone + TryFrom<ParameterValue> {
type Range: Into<ParameterRanges> + Default + Clone;
fn kind() -> ParameterKind;
}
impl TryFrom<ParameterValue> for bool {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::Bool(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for bool {
type Range = ();
fn kind() -> ParameterKind {
ParameterKind::Bool
}
}
impl TryFrom<ParameterValue> for i64 {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::Integer(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for i64 {
type Range = ParameterRange<i64>;
fn kind() -> ParameterKind {
ParameterKind::Integer
}
}
impl TryFrom<ParameterValue> for f64 {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::Double(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for f64 {
type Range = ParameterRange<f64>;
fn kind() -> ParameterKind {
ParameterKind::Double
}
}
impl TryFrom<ParameterValue> for Arc<str> {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::String(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for Arc<str> {
type Range = ();
fn kind() -> ParameterKind {
ParameterKind::String
}
}
impl TryFrom<ParameterValue> for Arc<[u8]> {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::ByteArray(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for Arc<[u8]> {
type Range = ();
fn kind() -> ParameterKind {
ParameterKind::ByteArray
}
}
impl TryFrom<ParameterValue> for Arc<[bool]> {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::BoolArray(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for Arc<[bool]> {
type Range = ();
fn kind() -> ParameterKind {
ParameterKind::BoolArray
}
}
impl TryFrom<ParameterValue> for Arc<[i64]> {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::IntegerArray(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for Arc<[i64]> {
type Range = ();
fn kind() -> ParameterKind {
ParameterKind::IntegerArray
}
}
impl TryFrom<ParameterValue> for Arc<[f64]> {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::DoubleArray(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for Arc<[f64]> {
type Range = ();
fn kind() -> ParameterKind {
ParameterKind::DoubleArray
}
}
impl TryFrom<ParameterValue> for Arc<[Arc<str>]> {
type Error = ParameterValueError;
fn try_from(value: ParameterValue) -> Result<Self, Self::Error> {
match value {
ParameterValue::StringArray(v) => Ok(v),
_ => Err(ParameterValueError::TypeMismatch),
}
}
}
impl ParameterVariant for Arc<[Arc<str>]> {
type Range = ();
fn kind() -> ParameterKind {
ParameterKind::StringArray
}
}
impl ParameterVariant for ParameterValue {
type Range = ParameterRanges;
fn kind() -> ParameterKind {
ParameterKind::Dynamic
}
}
impl From<ParameterValue> for RmwParameterValue {
fn from(value: ParameterValue) -> Self {
match value {
ParameterValue::Bool(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_BOOL,
bool_value: v,
..Default::default()
},
ParameterValue::Integer(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_INTEGER,
integer_value: v,
..Default::default()
},
ParameterValue::Double(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_DOUBLE,
double_value: v,
..Default::default()
},
ParameterValue::String(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_STRING,
string_value: v.into(),
..Default::default()
},
ParameterValue::ByteArray(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_BYTE_ARRAY,
byte_array_value: (*v).into(),
..Default::default()
},
ParameterValue::BoolArray(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_BOOL_ARRAY,
bool_array_value: (*v).into(),
..Default::default()
},
ParameterValue::IntegerArray(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_INTEGER_ARRAY,
integer_array_value: (*v).into(),
..Default::default()
},
ParameterValue::DoubleArray(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_DOUBLE_ARRAY,
double_array_value: (*v).into(),
..Default::default()
},
ParameterValue::StringArray(v) => RmwParameterValue {
type_: ParameterType::PARAMETER_STRING_ARRAY,
string_array_value: v.iter().map(|v| v.clone().into()).collect(),
..Default::default()
},
}
}
}
#[derive(Debug)]
pub enum RmwParameterConversionError {
InvalidParameterType,
}
impl std::fmt::Display for RmwParameterConversionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RmwParameterConversionError::InvalidParameterType => {
write!(f, "the parameter type was not valid")
}
}
}
}
impl std::error::Error for RmwParameterConversionError {}
impl TryFrom<RmwParameterValue> for ParameterValue {
type Error = RmwParameterConversionError;
fn try_from(param: RmwParameterValue) -> Result<Self, Self::Error> {
match param.type_ {
ParameterType::PARAMETER_BOOL => Ok(ParameterValue::Bool(param.bool_value)),
ParameterType::PARAMETER_INTEGER => Ok(ParameterValue::Integer(param.integer_value)),
ParameterType::PARAMETER_DOUBLE => Ok(ParameterValue::Double(param.double_value)),
ParameterType::PARAMETER_STRING => Ok(ParameterValue::String(
param.string_value.to_string().into(),
)),
ParameterType::PARAMETER_BYTE_ARRAY => {
Ok(ParameterValue::ByteArray((*param.byte_array_value).into()))
}
ParameterType::PARAMETER_BOOL_ARRAY => {
Ok(ParameterValue::BoolArray((*param.bool_array_value).into()))
}
ParameterType::PARAMETER_INTEGER_ARRAY => Ok(ParameterValue::IntegerArray(
(*param.integer_array_value).into(),
)),
ParameterType::PARAMETER_DOUBLE_ARRAY => Ok(ParameterValue::DoubleArray(
(*param.double_array_value).into(),
)),
ParameterType::PARAMETER_STRING_ARRAY => Ok(ParameterValue::StringArray(
param
.string_array_value
.iter()
.map(|s| s.to_string().into())
.collect::<Vec<_>>()
.into(),
)),
_ => Err(RmwParameterConversionError::InvalidParameterType),
}
}
}
impl ParameterValue {
pub(crate) unsafe fn from_rcl_variant(var: &rcl_variant_t) -> Self {
let num_active: u8 = [
!var.bool_value.is_null(),
!var.integer_value.is_null(),
!var.double_value.is_null(),
!var.string_value.is_null(),
!var.byte_array_value.is_null(),
!var.bool_array_value.is_null(),
!var.integer_array_value.is_null(),
!var.double_array_value.is_null(),
!var.string_array_value.is_null(),
]
.into_iter()
.map(u8::from)
.sum();
assert_eq!(num_active, 1);
if !var.bool_value.is_null() {
ParameterValue::Bool(*var.bool_value)
} else if !var.integer_value.is_null() {
ParameterValue::Integer(*var.integer_value)
} else if !var.double_value.is_null() {
ParameterValue::Double(*var.double_value)
} else if !var.string_value.is_null() {
let cstr = CStr::from_ptr(var.string_value);
let s = cstr.to_string_lossy().into_owned();
ParameterValue::String(s.into())
} else if !var.byte_array_value.is_null() {
let rcl_byte_array = &*var.byte_array_value;
let slice = rcl_from_raw_parts(rcl_byte_array.values, rcl_byte_array.size);
ParameterValue::ByteArray(slice.into())
} else if !var.bool_array_value.is_null() {
let rcl_bool_array = &*var.bool_array_value;
let slice = rcl_from_raw_parts(rcl_bool_array.values, rcl_bool_array.size);
ParameterValue::BoolArray(slice.into())
} else if !var.integer_array_value.is_null() {
let rcl_integer_array = &*var.integer_array_value;
let slice = rcl_from_raw_parts(rcl_integer_array.values, rcl_integer_array.size);
ParameterValue::IntegerArray(slice.into())
} else if !var.double_array_value.is_null() {
let rcl_double_array = &*var.double_array_value;
let slice = rcl_from_raw_parts(rcl_double_array.values, rcl_double_array.size);
ParameterValue::DoubleArray(slice.into())
} else if !var.string_array_value.is_null() {
let rcutils_string_array = &*var.string_array_value;
let slice = rcl_from_raw_parts(rcutils_string_array.data, rcutils_string_array.size);
let strings = slice
.iter()
.map(|&ptr| {
debug_assert!(!ptr.is_null());
let cstr = CStr::from_ptr(ptr);
Arc::from(cstr.to_string_lossy())
})
.collect::<Vec<_>>();
ParameterValue::StringArray(strings.into())
} else {
unreachable!()
}
}
pub(crate) fn rcl_parameter_type(&self) -> u8 {
match self {
ParameterValue::Bool(_) => ParameterType::PARAMETER_BOOL,
ParameterValue::Integer(_) => ParameterType::PARAMETER_INTEGER,
ParameterValue::Double(_) => ParameterType::PARAMETER_DOUBLE,
ParameterValue::String(_) => ParameterType::PARAMETER_STRING,
ParameterValue::ByteArray(_) => ParameterType::PARAMETER_BYTE_ARRAY,
ParameterValue::BoolArray(_) => ParameterType::PARAMETER_BOOL_ARRAY,
ParameterValue::IntegerArray(_) => ParameterType::PARAMETER_INTEGER_ARRAY,
ParameterValue::DoubleArray(_) => ParameterType::PARAMETER_DOUBLE_ARRAY,
ParameterValue::StringArray(_) => ParameterType::PARAMETER_STRING_ARRAY,
}
}
pub(crate) fn kind(&self) -> ParameterKind {
match self {
ParameterValue::Bool(_) => ParameterKind::Bool,
ParameterValue::Integer(_) => ParameterKind::Integer,
ParameterValue::Double(_) => ParameterKind::Double,
ParameterValue::String(_) => ParameterKind::String,
ParameterValue::ByteArray(_) => ParameterKind::ByteArray,
ParameterValue::BoolArray(_) => ParameterKind::BoolArray,
ParameterValue::IntegerArray(_) => ParameterKind::IntegerArray,
ParameterValue::DoubleArray(_) => ParameterKind::DoubleArray,
ParameterValue::StringArray(_) => ParameterKind::StringArray,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Context, InitOptions, RclrsError, ToResult};
#[test]
fn test_parameter_value() -> Result<(), RclrsError> {
let input_output_pairs = [
("true", ParameterValue::Bool(true)),
("1", ParameterValue::Integer(1)),
("1.0", ParameterValue::Double(1.0)),
("'1.0'", ParameterValue::String(Arc::from("1.0"))),
(
"[yes, no]",
ParameterValue::BoolArray(Arc::from([true, false])),
),
("[-3, 2]", ParameterValue::IntegerArray(Arc::from([-3, 2]))),
(
"[-3.0, 2.0]",
ParameterValue::DoubleArray(Arc::from([-3.0, 2.0])),
),
(
"['yes']",
ParameterValue::StringArray(Arc::from([Arc::from("yes")])),
),
];
for pair in input_output_pairs {
let ctx = Context::new(
[
String::from("--ros-args"),
String::from("-p"),
format!("foo:={}", pair.0),
],
InitOptions::default(),
)?;
let mut rcl_params = std::ptr::null_mut();
unsafe {
rcl_arguments_get_param_overrides(
&ctx.handle.rcl_context.lock().unwrap().global_arguments,
&mut rcl_params,
)
.ok()?;
}
assert!(!rcl_params.is_null());
assert_eq!(unsafe { (*rcl_params).num_nodes }, 1);
let rcl_node_params = unsafe { &(*(*rcl_params).params) };
assert_eq!(rcl_node_params.num_params, 1);
let rcl_variant = unsafe { &(*rcl_node_params.parameter_values) };
let param_value = unsafe { ParameterValue::from_rcl_variant(rcl_variant) };
assert_eq!(param_value, pair.1);
unsafe { rcl_yaml_node_struct_fini(rcl_params) };
}
Ok(())
}
}