Skip to content

Commit f22e953

Browse files
authored
Merge pull request #10 from tu-darmstadt-ros-pkg/feature/safety_position_controller
Feature/safety position controller
2 parents fe293f2 + 2adb58b commit f22e953

File tree

10 files changed

+1450
-32
lines changed

10 files changed

+1450
-32
lines changed

README.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,16 @@
33
Simple controllers for the [ros2_control](https://control.ros.org/jazzy/index.html) framework.
44

55
## Controllers
6-
1. Safety Forward Controller: A simple forward controller that resets the command value to zero if no command is received within a specified timeout period.
7-
2. Self Collision Avoidance Controller: A controller that prevents self-collision by checking the robot's state and if a nearby collision is detected, it makes sure to prevent the robot from moving into that state.
6+
7+
1. Safety Forward Controller: A simple forward controller that resets the command value to zero if no command is
8+
received within a specified timeout period.
9+
2. Self Collision Avoidance Controller: A controller that prevents self-collision by checking the robot's state and if a
10+
nearby collision is detected, it makes sure to prevent the robot from moving into that state.
11+
3. Safety Position Controller: Unwraps joint values for continuous joint such that the command position is in (-inf, inf)
12+
and closest to the current value. Normal joint positions are clamped to their limits.
813

914
# hector_controller_spawner
10-
A lightweight ROS2 node that boots an entire *ros2_control* setup in a single shot, managing hardware interfaces and controllers efficiently.
15+
16+
A lightweight ROS2 node that boots an entire *ros2_control* setup in a single shot, managing hardware interfaces and
17+
controllers efficiently.
1118
See [README.md](hector_controller_spawner/README.md) for details.

hector_ros_controllers/CMakeLists.txt

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,22 @@ set(THIS_PACKAGE_INCLUDE_DEPENDS
2424
generate_parameter_library
2525
hardware_interface
2626
pluginlib
27+
hpp-fcl
28+
pinocchio
2729
rclcpp
2830
rclcpp_lifecycle
2931
realtime_tools
3032
std_msgs
33+
std_srvs
3134
fcl
3235
ad_kinematics)
3336

3437
foreach(dependency IN ITEMS ${THIS_PACKAGE_INCLUDE_DEPENDS})
3538
find_package(${dependency} REQUIRED)
3639
endforeach()
3740

41+
set(DEFS PINOCCHIO_WITH_HPP_FCL PINOCCHIO_URDFDOM_TYPEDEF_SHARED_PTR
42+
PINOCCHIO_URDFDOM_USE_STD_SHARED_PTR)
3843
# Add parameter_libraries here
3944

4045
generate_parameter_library(
@@ -48,14 +53,18 @@ generate_parameter_library(
4853
generate_parameter_library(safety_forward_controller_parameters
4954
params/safety_forward_controller_parameters.yaml)
5055

56+
generate_parameter_library(safety_position_controller_parameters
57+
params/safety_position_controller_parameters.yaml)
5158
# Add controllers here Define Controllers as Shared Libraries
5259

5360
add_library(
5461
controllers SHARED
5562
src/velocity_to_position_controllers_base.cpp
5663
src/safety_forward_controller.cpp
5764
src/velocity_to_position_command_controller.cpp
58-
src/self_collision_avoidance_controller.cpp)
65+
src/self_collision_avoidance_controller.cpp
66+
src/safety_position_controller.cpp
67+
src/collision_checker.cpp)
5968

6069
target_compile_features(controllers PUBLIC cxx_std_17)
6170

@@ -65,26 +74,27 @@ target_include_directories(
6574

6675
target_link_libraries(
6776
controllers
68-
PUBLIC fcl velocity_to_position_command_controller_parameters
77+
PUBLIC fcl
78+
velocity_to_position_command_controller_parameters
6979
self_collision_avoidance_controller_parameters
70-
safety_forward_controller_parameters)
80+
safety_forward_controller_parameters
81+
safety_position_controller_parameters)
7182

7283
target_link_libraries(controllers PUBLIC ad_kinematics::ad_kinematics)
7384
ament_target_dependencies(controllers PUBLIC ${THIS_PACKAGE_INCLUDE_DEPENDS})
7485

75-
# Causes the visibility macros to use dllexport rather than dllimport, which is
76-
# appropriate when building the dll but not consuming it.
77-
target_compile_definitions(
78-
controllers PRIVATE "VELOCITY_TO_POSITION_COMMAND_CONTROLLER_BUILDING_DLL")
86+
target_compile_definitions(controllers PUBLIC ${DEFS})
7987

8088
pluginlib_export_plugin_description_file(controller_interface
8189
hector_ros_controller_plugin.xml)
8290

8391
install(DIRECTORY include/ DESTINATION include/)
8492
install(
85-
TARGETS controllers velocity_to_position_command_controller_parameters
93+
TARGETS controllers
94+
velocity_to_position_command_controller_parameters
8695
self_collision_avoidance_controller_parameters
8796
safety_forward_controller_parameters
97+
safety_position_controller_parameters
8898
EXPORT export_hector_ros_controllers
8999
RUNTIME DESTINATION bin
90100
ARCHIVE DESTINATION lib

hector_ros_controllers/README.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Hector Ros Controllers
2+
3+
A set of ROS 2 controllers focused on **safety**, **self-collision avoidance**, and **safe position command handling**.
4+
All controllers are compatible with **ros2_control** and can be inserted as **chain controllers**.
5+
6+
---
7+
8+
## 1. Safety Forward Controller
9+
10+
Forwards incoming commands, but **resets the command to zero** if no message is received within a configured timeout.
11+
Useful for teleoperation and autonomy if the communication is unreliable or in case of node failures.
12+
13+
---
14+
15+
## 2. Self Collision Avoidance Controller
16+
17+
Monitors joint states and **prevents the robot from moving into a configuration close to self-collision**.
18+
Acts as a motion gatekeeper in real-time.
19+
20+
---
21+
22+
## 3. Safety Position Controller
23+
24+
A **chainable safety layer** for joint position commands.
25+
26+
### Features
27+
28+
* **Continuous joints unwrapped** to nearest equivalent angle.
29+
* **Joint limits enforced** directly from URDF.
30+
* **Target-only self-collision check** (Pinocchio + hpp-fcl).
31+
* **Block-if-too-far:** prevents large unsafe jumps and makes collision checking meaningful.
32+
* Optional **compliant / stiff current limiting** per joint.
33+
34+
### Collision Checking Limitation - target-only collision checking
35+
36+
If used **behind a trajectory controller**, target updates are **small steps**, so checking only the target pose is sufficient when using a **small collision padding**.
37+
38+
If the parent controller is a raw position controller (large jumps), **block-if-too-far** ensures targets stay close → prevents missing intermediate collisions.
39+
40+
**Future improvement:** continuous / path collision checking.
41+
42+
43+
## Parameters (Safety Position Controller)
44+
45+
| Parameter | Type | Default | Description |
46+
| ---------------------------------- | ---------- | ---------- | ---------------------------------------------------------------------------------------------------- |
47+
| `joints` | `string[]` | `[]` | Names of joints controlled. Must match URDF. *(read-only)* |
48+
| `unwrap_continuous_joints` | `bool` | `true` | Unwrap continuous joints to maintain continuity in commanded angle. |
49+
| `enforce_position_limits` | `bool` | `true` | Clamp joint commands to URDF limits. |
50+
| `check_self_collisions` | `bool` | `true` | Run self-collision check on the **target** pose before sending commands. |
51+
| `collision_padding` | `double` | `0.0` | Minimum allowed link-to-link distance [m] — distances ≤ padding count as collision. |
52+
| `collision_cache_epsilon` | `double` | `0.000001` | Threshold for reusing last collision check result (skip compute if target change is small). |
53+
| `block_if_too_far` | `bool` | `true` | Blocks commands that are too far from current state. Auto-enabled if `check_self_collisions = true`. |
54+
| `block_velocity_scaling` | `double` | `1.5` | Scales maximum per-cycle motion: blocked if ` |
55+
| `debug_visualize_collisions` | `bool` | `false` | Publishes collision debug markers for RViz. |
56+
| `set_current_limits` | `bool` | `false` | Enables per-joint current limit control. *(read-only)* |
57+
| `current_limits.*.compliant_limit` | `double` | `3.0` | Current limit in **compliant** mode [A]. |
58+
| `current_limits.*.stiff_limit` | `double` | `5.0` | Current limit in **stiff** mode [A]. |
59+
60+
> Note: When using dynamic reconfigure to change the parameters the controller must be deactivated and reactivated for changes to take effect!
61+
62+
63+
## Example Configuration
64+
65+
```yaml
66+
/**:
67+
controller_manager:
68+
ros__parameters:
69+
update_rate: 50
70+
71+
joint_state_broadcaster:
72+
type: joint_state_broadcaster/JointStateBroadcaster
73+
74+
arm_trajectory_controller:
75+
type: joint_trajectory_controller/JointTrajectoryController
76+
77+
arm_safety_position_controller:
78+
type: safety_position_controller/SafetyPositionController
79+
80+
arm_safety_position_controller:
81+
ros__parameters:
82+
joints: [arm_joint_1, arm_joint_2, arm_joint_3, arm_joint_4, arm_joint_5, arm_joint_6, arm_joint_7]
83+
unwrap_continuous_joints: true
84+
enforce_position_limits: true
85+
check_self_collisions: true
86+
collision_padding: 0.01
87+
debug_visualize_collisions: false
88+
block_velocity_scaling: 3.0
89+
set_current_limits: true
90+
current_limits:
91+
arm_joint_1: {compliant_limit: 3.0, stiff_limit: 9.0}
92+
arm_joint_2: {compliant_limit: 5.0, stiff_limit: 9.0}
93+
arm_joint_3: {compliant_limit: 5.0, stiff_limit: 9.0}
94+
arm_joint_4: {compliant_limit: 3.0, stiff_limit: 4.9}
95+
arm_joint_5: {compliant_limit: 2.5, stiff_limit: 5.5}
96+
arm_joint_6: {compliant_limit: 0.5, stiff_limit: 3.5}
97+
arm_joint_7: {compliant_limit: 0.5, stiff_limit: 3.5}
98+
99+
arm_trajectory_controller:
100+
ros__parameters:
101+
joints: [arm_joint_1, arm_joint_2, arm_joint_3, arm_joint_4, arm_joint_5, arm_joint_6, arm_joint_7]
102+
command_joints:
103+
- arm_safety_position_controller/arm_joint_1
104+
- arm_safety_position_controller/arm_joint_2
105+
- arm_safety_position_controller/arm_joint_3
106+
- arm_safety_position_controller/arm_joint_4
107+
- arm_safety_position_controller/arm_joint_5
108+
- arm_safety_position_controller/arm_joint_6
109+
- arm_safety_position_controller/arm_joint_7
110+
command_interfaces: [position]
111+
state_interfaces: [position, velocity]
112+
```
Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
<library path="libcontrollers">
2-
<class name="velocity_to_position_command_controller/VelocityToPositionCommandController"
3-
type="velocity_to_position_command_controller::VelocityToPositionCommandController"
4-
base_class_type="controller_interface::ChainableControllerInterface">
5-
<description>
6-
The velocity to position command controller provides a velocity input and converts it to position commands.
7-
</description>
8-
</class>
9-
<class name="self_collision_avoidance_controller/SelfCollisionAvoidanceController"
10-
type="self_collision_avoidance_controller::SelfCollisionAvoidanceController"
11-
base_class_type="controller_interface::ChainableControllerInterface">
12-
<description>
13-
A chainable controller that only forwards joint commands if the resulting configuration is collision free.
14-
</description>
15-
</class>
16-
<class name="safety_forward_controller/SafetyForwardController"
17-
type="safety_forward_controller::SafetyForwardController"
18-
base_class_type="controller_interface::ControllerInterface">
19-
<description>
20-
A high-level forward controller ensuring that all command interfaces are stopped after receiving now commands for a specified time, i.e. disconnect to operator station.
21-
</description>
22-
</class>
2+
<class name="velocity_to_position_command_controller/VelocityToPositionCommandController"
3+
type="velocity_to_position_command_controller::VelocityToPositionCommandController"
4+
base_class_type="controller_interface::ChainableControllerInterface">
5+
<description>
6+
The velocity to position command controller provides a velocity input and converts it to position commands.
7+
</description>
8+
</class>
9+
<class name="self_collision_avoidance_controller/SelfCollisionAvoidanceController"
10+
type="self_collision_avoidance_controller::SelfCollisionAvoidanceController"
11+
base_class_type="controller_interface::ChainableControllerInterface">
12+
<description>
13+
A chainable controller that only forwards joint commands if the resulting configuration is collision free.
14+
</description>
15+
</class>
16+
<class name="safety_forward_controller/SafetyForwardController"
17+
type="safety_forward_controller::SafetyForwardController"
18+
base_class_type="controller_interface::ControllerInterface">
19+
<description>
20+
A high-level forward controller ensuring that all command interfaces are stopped after receiving now
21+
commands for a specified time, i.e. disconnect to operator station.
22+
</description>
23+
</class>
24+
<class name="safety_position_controller/SafetyPositionController"
25+
type="safety_position_controller::SafetyPositionController"
26+
base_class_type="controller_interface::ChainableControllerInterface">
27+
<description>
28+
A chainable position controller ensuring that revolute joints are unwrapped and joint limits are satisfied.
29+
</description>
30+
</class>
2331
</library>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
// Created by aljoscha-schmidt on 10/20/25.
3+
//
4+
5+
#ifndef COLLISION_CHECKER_HPP
6+
#define COLLISION_CHECKER_HPP
7+
8+
#include <rclcpp/rclcpp.hpp>
9+
#include <rclcpp_lifecycle/lifecycle_node.hpp>
10+
#include <visualization_msgs/msg/marker_array.hpp>
11+
12+
#include <pinocchio/multibody/data.hpp>
13+
#include <pinocchio/multibody/geometry.hpp>
14+
#include <pinocchio/multibody/model.hpp>
15+
16+
#include <hpp/fcl/collision.h>
17+
#include <unordered_map>
18+
19+
// #define SAFETY_CC_ENABLE_TIMING
20+
21+
/// Self-collision checker using Pinocchio + hpp-fcl; optional RViz debug markers.
22+
class CollisionChecker
23+
{
24+
public:
25+
/**
26+
* @brief Ctor.
27+
* @param node lifecycle node (pub/log/time)
28+
* @param collision_padding min allowed distance [m]
29+
* @param collision_cache_epsilon cache threshold on max(q - q_last) [rad/m] -> reuse last distances
30+
* @param pub_debug_geometry publish MarkerArray on ~/debug_collision_geometry
31+
*/
32+
explicit CollisionChecker( const rclcpp_lifecycle::LifecycleNode::SharedPtr &node,
33+
double collision_padding = 0.0, double collision_cache_epsilon = 1e-4,
34+
bool pub_debug_geometry = false );
35+
36+
/**
37+
* @brief Init from URDF/SRDF XML.
38+
* - builds Model/Geometry(COLLISION)
39+
* - prunes pairs via SRDF + controlled_joints ancestry
40+
* @param urdf_xml URDF string
41+
* @param srdf_xml SRDF string (may be empty)
42+
* @param controlled_joints names used to keep relevant pairs (empty → keep all)
43+
* @param free_flyer root as free-flyer
44+
* @return true on success
45+
*/
46+
bool initFromXml( const std::string &urdf_xml, const std::string &srdf_xml,
47+
const std::vector<std::string> &controlled_joints, bool free_flyer = false );
48+
49+
/**
50+
* @brief Joint names excluding "universe".
51+
* @return names in model order
52+
*/
53+
std::vector<std::string> getJointNames() const;
54+
55+
/**
56+
* @brief Collision check from name→position map.
57+
* - nq==1: assign directly
58+
* - continuous revolute (nq=2,nv=1): [cos(θ), sin(θ)]
59+
* - others: left at neutral with warning
60+
* @param joint_positions rad (rev) / m (prismatic)
61+
* @return true if any distance ≤ padding
62+
*/
63+
bool checkCollision( const std::unordered_map<std::string, double> &joint_positions );
64+
65+
/**
66+
* @brief Collision check for full q.
67+
* - FK + update placements
68+
* - computeDistances() with nearest points + cached GJK
69+
* - cache: reuse if last collision result if q change ≤ epsilon
70+
* @param q size == model_.nq
71+
* @return true if any distance ≤ padding
72+
*/
73+
bool checkCollisionQ( const Eigen::VectorXd &q );
74+
75+
/**
76+
* @brief Set collision padding [m].
77+
* @param collision_padding new threshold
78+
*/
79+
void updateCollisionPadding( double collision_padding );
80+
81+
/**
82+
* @brief Toggle RViz debug publishing.
83+
* @param pub_debug_geometry on/off
84+
*/
85+
void updateDoDebugVisualization( bool pub_debug_geometry );
86+
87+
/**
88+
* @brief Set cache epsilon
89+
* @param epsilon new threshold
90+
*/
91+
void updateCollisionCacheEpsilon( double epsilon );
92+
93+
private:
94+
/**
95+
* @brief Publish geometry and nearest-point LINE_LIST markers.
96+
* - red if part of a pair with distance ≤ 0
97+
*/
98+
void publishMarkers() const;
99+
100+
/**
101+
* @brief Keep only pairs attached to controlled joints (and ancestors).
102+
* @param controlled_joints names defining relevance
103+
*/
104+
void filterCollisionPairs( const std::vector<std::string> &controlled_joints );
105+
106+
rclcpp_lifecycle::LifecycleNode::SharedPtr node_;
107+
rclcpp::Publisher<visualization_msgs::msg::MarkerArray>::SharedPtr markers_pub_;
108+
109+
pinocchio::Model model_;
110+
pinocchio::Data data_;
111+
pinocchio::GeometryModel geom_model_;
112+
pinocchio::GeometryData geom_data_;
113+
114+
Eigen::VectorXd q_default_;
115+
Eigen::VectorXd q_last_;
116+
bool last_collision_state_{ false };
117+
118+
double collision_padding_{ 0.0 };
119+
double collision_cache_epsilon_{ 1e-4 };
120+
bool pub_debug_geometry_{ false };
121+
122+
std::unordered_map<std::string, pinocchio::JointIndex> name_to_id_;
123+
124+
#ifdef SAFETY_CC_ENABLE_TIMING
125+
double sum_timings_{ 0.0 };
126+
int n_timings_{ 0 };
127+
#endif
128+
};
129+
130+
#endif // COLLISION_CHECKER_HPP

0 commit comments

Comments
 (0)