There are a few dimensions of my robot model that are subject to change at runtime, and so I would like to use the param server to set and get those values. It was easy enough to set the values, but now I am unsure as to the best way to pass them to xacro when uploading the robot description:

• Am I right that there isn't a way to query a parameter from the server within a launch file? Otherwise I would do something like:

<param name="robot_description" command="$(find xacro)/xacro.py$(arg model) my_var:=$(rosparam get my_var)"> • I can instead query the parameters within a python script, but how do I invoke xacro from within that script? Should I use xacro.main(['arg1', 'arg2', ...])? Edit: Scratch this as main() doesn't take any arguments. I'd prefer a solution that doesn't necessitate forking/modifying core code. • Presumably python would allow me to go the "hacky" route and just run the xacro command in a shell and get the result from stdout? Any suggestions welcome. Thanks! edit retag close merge delete ## Comments Btw: after seeing your own answer, the bit about "a few dimensions of my robot model that are subject to change at runtime" was rather confusing. "At runtime" typically means "at any point in time after a program has been started". That would not seem to be what your Python script allows. ( 2017-12-01 03:02:55 -0500 )edit ## 2 Answers Sort by » oldest newest most voted I ended up solving this problem by running xacro in a subprocess from within python. Here's the launch file: <?xml version="1.0"?> <launch> <rosparam command="load" file="$(find my_robot_bringup)/launch/dynamic_dimensions.yaml"/>
<arg name="namespace" default=""/>
<rosparam subst_value="True">
namespace: "$(arg namespace)" model_filepath: "$(find my_robot_description)/urdf/my_robot.urdf.xacro"
</rosparam>
</node>
</launch>

And the python:

#!/usr/bin/env python
import sys
import rospy
import xacro
import subprocess

my_robot_dynamic_dimension_1 = rospy.get_param(
'/my_robot_dynamic_dimension_1')
namespace = rospy.get_param('~namespace')
model_filepath = rospy.get_param('~model_filepath')

try:
command_string = "rosrun xacro xacro --inorder {} my_robot_dynamic_dimension_1:={}".format(model_filepath, my_robot_dynamic_dimension_1)
robot_description = subprocess.check_output(
command_string, shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as process_error:
rospy.logfatal('Failed to run xacro command with error: \n%s', process_error.output)
sys.exit(1)

rospy.set_param(namespace + "/robot_description", robot_description)

if __name__ == '__main__':
try:
except rospy.ROSInterruptException:
pass
more

As long as you understand that nodes are only started after parameters have been set on the server, and that there is no guaranteed order in which nodes are started.

This means that nodes that need robot_description might try loading it before your Python script is run.

( 2017-12-01 02:54:19 -0500 )edit

Using xacro (the library) directly is possible by calling xacro.main() yourself. It directly parses sys.argv, which you can override / append to just before you call the function.

( 2017-12-01 03:05:46 -0500 )edit

I have encountered that issue and will decide whether or not being able to set the xacro arguments from the param server is worth whatever amount of work it will take to make that happen. I like the idea of your last edit though (using a command within the param tag in the launch file).

( 2017-12-01 16:39:47 -0500 )edit

Thanks for the syntax help on calling main(). I'm not very familiar with Python.

( 2017-12-01 16:40:31 -0500 )edit

I'm pretty sure (but not entirely) it's not possible to use parameters while roslaunch is setting parameters (robot_description is just another parameter), but you could perhaps pass the 'dynamic dimensions' to xacro as args right when you ask it to convert the xacro to urdf for you.

<param name="robot_description" command="$(find xacro)/xacro.py$(arg model) myvar:=true" />

If with "a few dimensions of my robot model that are subject to change at runtime" you really mean: they need to change while my application is running, that is a different problem, and not something that is approachable with a universally accepted or supported solution (robot_description is essentially immutable, as almost all consumers are not written such that they consider the possibility of the parameter ever changing).

Edit:

The problem with the param tag in the code you posted is that I want to query the values to pass to xacro.py from the param server.

Well then I believe my first sentence applies:

I'm pretty sure (but not entirely) it's not possible to use parameters while roslaunch is setting parameters (robot_description is just another parameter), [..]

There are roughly two phases roslaunch goes through:

1. evaluating all launch files and setting the resulting values for the parameters on the parameter server
2. starting all the nodes (potentially based on those parameter values)

If I understand you correctly you're trying to do this in phase 1, which afaik is not possible.

You can use all the args that have been set in the launch file (and all the parents) at the point where you are setting the robot_description (ie: the \$(arg model)).

I remember some questions here on ROS Answers that have asked similar things, so perhaps a Google search (site:answers.ros.org) could turn something up.

What you could do perhaps is use param with a command which uses your Python script. That would ensure it's executed before nodes get started. You would just need to pass the script the parameter yaml that you already have. And instead of using rospy.get_param(..), you'd directly load it (with yaml.load(..)).

more

I don't think I was clear enough in my post. See my update (though I'm not sure how to get the XML to render properly as code...). The problem with the param tag in the code you posted is that I want to query the values to pass to xacro.py from the param server.

( 2017-11-29 17:24:46 -0500 )edit

Well then I guess we agree that bullet point 1 won't work. As far as bullet 2, I was hoping there was some C++ or Python API for calling xacro rather running the shell command and capturing stdout (bullet point 3). That's what I ended up doing, so I'll write that up as an answer.

( 2017-12-01 02:06:49 -0500 )edit