Skip to content

Commit 00d7a3d

Browse files
wentasahm-dahl
authored andcommitted
Derive ROS parameter description from field doc comments
With this change, adding doc comments to fields of structures used with `#[derive(RosParams)]` results in those comments being used as parameter description. See r2r/examples/parameters_derive.rs for how to use and test this feature. *BREAKING CHANGE* This commit changes r2r public API. Previously Node::params contained HashMap<String, ParameterValue>, now it contains HashMap<String, Parameter>. If you previously used the ParameterValue from this HashMap, now you can get the same by using the .value field of the Parameter structure.
1 parent c12a6fd commit 00d7a3d

File tree

6 files changed

+112
-42
lines changed

6 files changed

+112
-42
lines changed

r2r/examples/parameters.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
4747
loop {
4848
println!("node parameters");
4949
params.lock().unwrap().iter().for_each(|(k, v)| {
50-
println!("{} - {:?}", k, v);
50+
println!("{} - {:?}", k, v.value);
5151
});
5252
let _elapsed = timer.tick().await.expect("could not tick");
5353
}

r2r/examples/parameters_derive.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ use std::sync::{Arc, Mutex};
2323
// par1: 5.1
2424
// par2: 0
2525
//
26+
// ros2 param describe /demo/my_node par1 nested.nested2.par5
27+
// Prints:
28+
// Parameter name: par1
29+
// Type: double
30+
// Description: Parameter description
31+
// Constraints:
32+
// Parameter name: nested.nested2.par5
33+
// Type: integer
34+
// Description: Small parameter
35+
// Constraints:
36+
2637
// Error handling:
2738
// cargo run --example parameters_derive -- --ros-args -p nested.par4:=xxx
2839

@@ -31,7 +42,9 @@ use std::sync::{Arc, Mutex};
3142

3243
#[derive(RosParams, Default, Debug)]
3344
struct Params {
45+
/// Parameter description
3446
par1: f64,
47+
/// Dummy parameter [m/s]
3548
par2: i32,
3649
nested: NestedParams,
3750
}
@@ -45,6 +58,7 @@ struct NestedParams {
4558

4659
#[derive(RosParams, Default, Debug)]
4760
struct NestedParams2 {
61+
/// Small parameter
4862
par5: i8,
4963
}
5064

r2r/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ mod context;
112112
pub use context::Context;
113113

114114
mod parameters;
115-
pub use parameters::{ParameterValue, RosParams};
115+
pub use parameters::{Parameter, ParameterValue, RosParams};
116116

117117
mod clocks;
118118
pub use clocks::{Clock, ClockType};

r2r/src/nodes.rs

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ use crate::subscribers::*;
3535
/// be called continously.
3636
pub struct Node {
3737
context: Context,
38-
/// ROS parameter values.
39-
pub params: Arc<Mutex<HashMap<String, ParameterValue>>>,
38+
/// ROS parameters.
39+
pub params: Arc<Mutex<HashMap<String, Parameter>>>,
4040
node_handle: Box<rcl_node_t>,
4141
// the node owns the subscribers
4242
subscribers: Vec<Box<dyn Subscriber_>>,
@@ -146,7 +146,7 @@ impl Node {
146146
let s = unsafe { CStr::from_ptr(*s) };
147147
let key = s.to_str().unwrap_or("");
148148
let val = ParameterValue::from_rcl(v);
149-
params.insert(key.to_owned(), val);
149+
params.insert(key.to_owned(), Parameter::new(val));
150150
}
151151
}
152152

@@ -243,7 +243,7 @@ impl Node {
243243
// register all parameters
244244
ps.lock()
245245
.unwrap()
246-
.register_parameters("", &mut self.params.lock().unwrap())?;
246+
.register_parameters("", None, &mut self.params.lock().unwrap())?;
247247
}
248248
let mut handlers: Vec<std::pin::Pin<Box<dyn Future<Output = ()> + Send>>> = Vec::new();
249249
let (mut event_tx, event_rx) = mpsc::channel::<(String, ParameterValue)>(10);
@@ -266,19 +266,31 @@ impl Node {
266266
.lock()
267267
.unwrap()
268268
.get(&p.name)
269-
.map(|v| v != &val)
269+
.map(|v| v.value != val)
270270
.unwrap_or(true); // changed=true if new
271271
let r = if let Some(ps) = &params_struct_clone {
272+
// Update parameter structure
272273
let result = ps.lock().unwrap().set_parameter(&p.name, &val);
273274
if result.is_ok() {
274-
params.lock().unwrap().insert(p.name.clone(), val.clone());
275+
// Also update Node::params
276+
params
277+
.lock()
278+
.unwrap()
279+
.entry(p.name.clone())
280+
.and_modify(|p| p.value = val.clone());
275281
}
276282
rcl_interfaces::msg::SetParametersResult {
277283
successful: result.is_ok(),
278284
reason: result.err().map_or("".into(), |e| e.to_string()),
279285
}
280286
} else {
281-
params.lock().unwrap().insert(p.name.clone(), val.clone());
287+
// No parameter structure - update only Node::params
288+
params
289+
.lock()
290+
.unwrap()
291+
.entry(p.name.clone())
292+
.and_modify(|p| p.value = val.clone())
293+
.or_insert(Parameter::new(val.clone()));
282294
rcl_interfaces::msg::SetParametersResult {
283295
successful: true,
284296
reason: "".into(),
@@ -316,17 +328,17 @@ impl Node {
316328
.names
317329
.iter()
318330
.map(|n| {
331+
// First try to get the parameter from the param structure
319332
if let Some(ps) = &params_struct_clone {
320-
ps.lock()
321-
.unwrap()
322-
.get_parameter(&n)
323-
.unwrap_or(ParameterValue::NotSet)
324-
} else {
325-
match params.get(n) {
326-
Some(v) => v.clone(),
327-
None => ParameterValue::NotSet,
333+
if let Ok(value) = ps.lock().unwrap().get_parameter(&n) {
334+
return value;
328335
}
329336
}
337+
// Otherwise get it from node HashMap
338+
match params.get(n) {
339+
Some(v) => v.value.clone(),
340+
None => ParameterValue::NotSet,
341+
}
330342
})
331343
.map(|v| v.into_parameter_value_msg())
332344
.collect::<Vec<rcl_interfaces::msg::ParameterValue>>();
@@ -384,7 +396,7 @@ impl Node {
384396
.names
385397
.iter()
386398
.map(|name| match params.get(name) {
387-
Some(pv) => pv.into_parameter_type(),
399+
Some(param) => param.value.into_parameter_type(),
388400
None => rcl_interfaces::msg::ParameterType::PARAMETER_NOT_SET as u8,
389401
})
390402
.collect();
@@ -402,7 +414,7 @@ impl Node {
402414

403415
fn handle_list_parameters(
404416
req: ServiceRequest<rcl_interfaces::srv::ListParameters::Service>,
405-
params: &Arc<Mutex<HashMap<String, ParameterValue>>>,
417+
params: &Arc<Mutex<HashMap<String, Parameter>>>,
406418
) -> future::Ready<()> {
407419
use rcl_interfaces::srv::ListParameters;
408420

@@ -445,26 +457,21 @@ impl Node {
445457

446458
fn handle_desc_parameters(
447459
req: ServiceRequest<rcl_interfaces::srv::DescribeParameters::Service>,
448-
params: &Arc<Mutex<HashMap<String, ParameterValue>>>,
460+
params: &Arc<Mutex<HashMap<String, Parameter>>>,
449461
) -> future::Ready<()> {
450462
use rcl_interfaces::msg::ParameterDescriptor;
451463
use rcl_interfaces::srv::DescribeParameters;
452464
let mut descriptors = Vec::<ParameterDescriptor>::new();
453465
let params = params.lock().unwrap();
454466
for name in &req.message.names {
455-
if let Some(pv) = params.get(name) {
456-
descriptors.push(ParameterDescriptor {
457-
name: name.clone(),
458-
type_: pv.into_parameter_type(),
459-
..Default::default()
460-
});
461-
} else {
462-
// parameter not found, but undeclared allowed, so return empty
463-
descriptors.push(ParameterDescriptor {
464-
name: name.clone(),
465-
..Default::default()
466-
});
467-
}
467+
let default = Parameter::empty();
468+
let param = params.get(name).unwrap_or(&default);
469+
descriptors.push(ParameterDescriptor {
470+
name: name.clone(),
471+
type_: param.value.into_parameter_type(),
472+
description: param.description.to_string(),
473+
..Default::default()
474+
});
468475
}
469476
req.respond(DescribeParameters::Response { descriptors })
470477
.expect("could not send reply to describe parameters request");

r2r/src/parameters.rs

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,37 @@ impl ParameterValue {
160160
}
161161
}
162162

163+
/// ROS parameter.
164+
pub struct Parameter {
165+
pub value: ParameterValue,
166+
pub description: &'static str,
167+
// TODO: Add other fields like min, max, step. Use field
168+
// attributes for defining them.
169+
}
170+
171+
impl Parameter {
172+
pub fn new(value: ParameterValue) -> Self {
173+
Self {
174+
value,
175+
description: "",
176+
}
177+
}
178+
pub fn empty() -> Self {
179+
Self {
180+
value: ParameterValue::NotSet,
181+
description: "",
182+
}
183+
}
184+
}
185+
163186
/// Trait for use it with
164187
/// [`Node::make_derived_parameter_handler()`](crate::Node::make_derived_parameter_handler()).
165188
///
166189
/// The trait is usually derived with `r2r_macros::RosParams`. See
167190
/// `parameters_derive.rs` example.
168191
pub trait RosParams {
169192
fn register_parameters(
170-
&mut self, prefix: &str, params: &mut HashMap<String, ParameterValue>,
193+
&mut self, prefix: &str, param: Option<Parameter>, params: &mut HashMap<String, Parameter>,
171194
) -> Result<()>;
172195
fn get_parameter(&mut self, param_name: &str) -> Result<ParameterValue>;
173196
fn set_parameter(&mut self, param_name: &str, param_val: &ParameterValue) -> Result<()>;
@@ -178,16 +201,18 @@ macro_rules! impl_ros_params {
178201
($type:path, $param_value_type:path, $to_param_conv:path, $from_param_conv:path) => {
179202
impl RosParams for $type {
180203
fn register_parameters(
181-
&mut self, prefix: &str, params: &mut HashMap<String, ParameterValue>,
204+
&mut self, prefix: &str, param: Option<Parameter>,
205+
params: &mut HashMap<String, Parameter>,
182206
) -> Result<()> {
183-
if let Some(param_val) = params.get(prefix) {
207+
if let Some(cli_param) = params.get(prefix) {
184208
// Apply parameter value if set from command line or launch file
185-
self.set_parameter("", param_val)
209+
self.set_parameter("", &cli_param.value)
186210
.map_err(|e| e.update_param_name(prefix))?;
187-
} else {
188-
// Insert missing parameter with its default value
189-
params.insert(prefix.to_owned(), $param_value_type($to_param_conv(self)?));
190211
}
212+
// Insert (or replace) the parameter with filled-in description etc.
213+
let mut param = param.unwrap();
214+
param.value = $param_value_type($to_param_conv(self)?);
215+
params.insert(prefix.to_owned(), param);
191216
Ok(())
192217
}
193218

r2r_macros/src/lib.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ pub fn derive_r2r_params(input: proc_macro::TokenStream) -> proc_macro::TokenStr
2626
fn register_parameters(
2727
&mut self,
2828
prefix: &str,
29-
params: &mut ::std::collections::hash_map::HashMap<String, ::r2r::ParameterValue>,
29+
desc: ::std::option::Option<::r2r::Parameter>,
30+
params: &mut ::std::collections::hash_map::HashMap<String, ::r2r::Parameter>,
3031
) -> ::r2r::Result<()> {
3132
let prefix = if prefix.is_empty() {
3233
String::from("")
@@ -79,9 +80,14 @@ fn get_register_calls(data: &Data) -> TokenStream {
7980
let field_matches = fields.named.iter().map(|f| {
8081
let name = &f.ident;
8182
let format_str = format!("{{prefix}}{}", name.as_ref().unwrap());
83+
let desc = get_field_doc(f);
8284
quote_spanned! {
8385
f.span() =>
84-
self.#name.register_parameters(&format!(#format_str), params)?;
86+
let param = ::r2r::Parameter {
87+
value: ::r2r::ParameterValue::NotSet, // will be set for leaf params by register_parameters() below
88+
description: #desc,
89+
};
90+
self.#name.register_parameters(&format!(#format_str), Some(param), params)?;
8591
}
8692
});
8793
quote! {
@@ -94,6 +100,24 @@ fn get_register_calls(data: &Data) -> TokenStream {
94100
}
95101
}
96102

103+
fn get_field_doc(f: &syn::Field) -> String {
104+
if let Some(doc) = f
105+
.attrs
106+
.iter()
107+
.find(|&attr| attr.path().get_ident().is_some_and(|id| id == "doc"))
108+
{
109+
match &doc.meta.require_name_value().unwrap().value {
110+
::syn::Expr::Lit(exprlit) => match &exprlit.lit {
111+
::syn::Lit::Str(s) => s.value().trim().to_owned(),
112+
_ => unimplemented!(),
113+
},
114+
_ => unimplemented!(),
115+
}
116+
} else {
117+
"".to_string()
118+
}
119+
}
120+
97121
// Generate match arms for RosParams::update_parameters()
98122
fn param_matches_for(call: TokenStream, data: &Data) -> TokenStream {
99123
match *data {

0 commit comments

Comments
 (0)