Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable generate_parameter_module through ament_cmake_python #161

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions example_cmake_python/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Changelog for package cmake_generate_parameter_module_example
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 changes: 15 additions & 0 deletions example_cmake_python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.16)
project(cmake_generate_parameter_module_example)

find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)
find_package(generate_parameter_library REQUIRED)

generate_parameter_module(admittance_parameters
cmake_generate_parameter_module_example/parameters.yaml
cmake_generate_parameter_module_example.custom_validation
)

ament_python_install_package(${PROJECT_NAME})

ament_package()
156 changes: 156 additions & 0 deletions example_cmake_python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Example:

## Build the node

```
mkdir -p colcon_ws/src
cd colcon_ws/src
git clone https://github.com/picknikrobotics/generate_parameter_library.git
cd ..
colcon build
```

## Run the Python node

```
source install/setup.bash
python3 src/generate_parameter_library/example_cmake_python/cmake_generate_parameter_module_example/minimal_publisher.py --ros-args --params-file src/generate_parameter_library/example_python/config/implementation.yaml
```

You should see an output like this:
`[INFO] [1656018676.015816509] [admittance_controller]: Initial control frame parameter is: 'ee_link'`


## ROS 2 CLI

Run the following:

`ros2 param list`

You should see:

```
/admittance_controller:
admittance.damping_ratio
admittance.mass
admittance.selected_axes
admittance.stiffness
chainable_command_interfaces
command_interfaces
control.frame.external
control.frame.id
enable_parameter_update_without_reactivation
fixed_array
fixed_string
fixed_string_no_default
fixed_world_frame.frame.external
fixed_world_frame.frame.id
ft_sensor.filter_coefficient
ft_sensor.frame.external
ft_sensor.frame.id
ft_sensor.name
gravity_compensation.CoG.force
gravity_compensation.CoG.pos
gravity_compensation.frame.external
gravity_compensation.frame.id
interpolation_mode
joints
kinematics.alpha
kinematics.base
kinematics.group_name
kinematics.plugin_name
kinematics.plugin_package
kinematics.tip
one_number
pid.elbow_joint.d
pid.elbow_joint.i
pid.elbow_joint.p
pid.rate
pid.shoulder_lift_joint.d
pid.shoulder_lift_joint.i
pid.shoulder_lift_joint.p
pid.shoulder_pan_joint.d
pid.shoulder_pan_joint.i
pid.shoulder_pan_joint.p
pid.wrist_1_joint.d
pid.wrist_1_joint.i
pid.wrist_1_joint.p
pid.wrist_2_joint.d
pid.wrist_2_joint.i
pid.wrist_2_joint.p
pid.wrist_3_joint.d
pid.wrist_3_joint.i
pid.wrist_3_joint.p
qos_overrides./parameter_events.publisher.depth
qos_overrides./parameter_events.publisher.durability
qos_overrides./parameter_events.publisher.history
qos_overrides./parameter_events.publisher.reliability
scientific_notation_num
state_interfaces
three_numbers
three_numbers_of_five
use_feedforward_commanded_input
use_sim_time
```

All parameter are automatically declared and callbacks are setup by default. You can set a parameter by typing:

`ros2 param set /admittance_controller control.frame.id new_frame`

You should see:

`[INFO] [1656019001.515820371] [admittance_controller]: New control frame parameter is: 'new_frame'`

Congratulations, you updated the parameter!

If you try to set a parameter that is read only, you will get an error. Running the following

`ros2 param set /admittance_controller command_interfaces ["velocity"]`

will result in the error

`Setting parameter failed: Trying to set a read-only parameter: command_interfaces.`

Running the following

`ros2 param describe /admittance_controller admittance.damping_ratio`

will show a parameter's description

```
Parameter name: admittance.damping_ratio
Type: double array
Description: specifies damping ratio values for x, y, z, rx, ry, and rz used in the admittance calculation. The values are calculated as damping can be used instead: zeta = D / (2 * sqrt( M * S ))
Constraints:
Min value: 0.1
Max value: 10.0
```

If you try to set a value out of the specified bounds,

`ros2 param set /admittance_controller admittance.damping_ratio [-10.0,-10.0,-10.0,-10.0,-10.0,-10.0]`

you will get the error

`Setting parameter failed: Value array('d', [-10.0, -10.0, -10.0, -10.0, -10.0, -10.0]) in parameter 'admittance.damping_ratio' must be within bounds [0.1, 10.0]`

If you try to set a vector parameter with the wrong length,

`ros2 param set /admittance_controller admittance.damping_ratio [1.0,1.0,1.0]`

you will get the error

`Setting parameter failed: Length of parameter 'admittance.damping_ratio' is '3' but must be equal to 6`

If you try to load a yaml file with missing required parameters

`python3 src/generate_parameter_library/example_cmake_python/cmake_generate_parameter_module_example/minimal_publisher.py --ros-args --params-file src/generate_parameter_library/example_python/config/missing_required.yaml`

you will get the error

```
Traceback (most recent call last):
[...]
rclpy.exceptions.ParameterUninitializedException: The parameter 'fixed_string_no_default' is not initialized
[ros2run]: Process exited with failure 1
```
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
# Copyright 2023 PickNik Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the PickNik Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


def no_args_validator(param):
return ''


def validate_double_array_custom_func(param, arg1, arg2):
return ''
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
# Copyright 2023 PickNik Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# * Neither the name of the PickNik Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import rclpy
import rclpy.node

from cmake_generate_parameter_module_example.admittance_parameters import (
admittance_controller,
)


class MinimalParam(rclpy.node.Node):
def __init__(self):
super().__init__('admittance_controller')
self.timer = self.create_timer(1, self.timer_callback)

self.param_listener = admittance_controller.ParamListener(self)
self.params = self.param_listener.get_params()
self.get_logger().info(
"Initial control frame parameter is: '%s'" % self.params.control.frame.id
)
self.get_logger().info("fixed string is: '%s'" % self.params.fixed_string)

self.get_logger().info(
"Original joints parameter is: '%s'" % str(self.params.joints)
)
for d in self.params.fixed_array:
self.get_logger().info("value: '%s'" % str(d))

def timer_callback(self):
if self.param_listener.is_old(self.params):
self.param_listener.refresh_dynamic_parameters()
self.params = self.param_listener.get_params()
self.get_logger().info(
"New control frame parameter is: '%s'" % self.params.control.frame.id
)
self.get_logger().info("fixed string is: '%s'" % self.params.fixed_string)
for d in self.params.fixed_array:
self.get_logger().info("value: '%s'" % str(d))


def main(args=None):
rclpy.init(args=args)
node = MinimalParam()
rclpy.spin(node)


if __name__ == '__main__':
main()
Loading
Loading