Skip to content

Commit a45d546

Browse files
committed
Added documentation and started writing tests
Also fixed some little bugs
1 parent b8917af commit a45d546

File tree

7 files changed

+135
-17
lines changed

7 files changed

+135
-17
lines changed

ur_controllers/doc/index.rst

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ robot family. Currently this contains:
1111
but it uses the speed scaling reported to align progress of the trajectory between the robot and controller.
1212
* A **io_and_status_controller** that allows setting I/O ports, controlling some UR-specific
1313
functionality and publishes status information about the robot.
14+
* A **tool_contact_controller** that exposes an action to enable the tool contact function on the robot.
15+
* A **trajectory_until_node**. This is not a controller in itself, but allows for executing a trajectory with any of the motion controllers while having tool contact enabled.
1416

1517
About this package
1618
------------------
@@ -378,3 +380,63 @@ The controller provides the ``~/enable_freedrive_mode`` topic of type ``[std_msg
378380
* to deactivate freedrive mode is enough to publish a ``False`` msg on the indicated topic or
379381
to deactivate the controller or to stop publishing ``True`` on the enable topic and wait for the
380382
controller timeout.
383+
384+
.. _tool_contact_controller:
385+
386+
ur_controllers/ToolContactController
387+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
388+
This controller can enable tool contact on the robot. When tool contact is enabled,
389+
and the robot senses that the tool has made contact with something, it will stop all motion,
390+
and retract to where it first sensed the contact.
391+
This controller can be used with any of the motion controllers.
392+
This is not a complete interface of the URScript function ``tool_contact(direction)``, as it does not allow for choosing the direction.
393+
The direction of tool contact will always be the current TCP direction of movement.
394+
395+
Parameters
396+
""""""""""
397+
398+
+----------------------+--------+---------------+---------------------------------------------------------------------------------------+
399+
| Parameter name | Type | Default value | Description |
400+
| | | | |
401+
+----------------------+--------+---------------+---------------------------------------------------------------------------------------+
402+
| ``tf_prefix`` | string | <empty> | Urdf prefix of the corresponding arm |
403+
+----------------------+--------+---------------+---------------------------------------------------------------------------------------+
404+
405+
Action interface / usage
406+
""""""""""""""""""""""""
407+
The controller provides one action for enabling tool contact. For the controller to accept action goals it needs to be in ``active`` state.
408+
409+
* ``~/enable_tool_contact [ur_msgs/action/ToolContact]``
410+
411+
The goal section of ``ur_msgs/action/ToolContact`` has no fields, as a call to the action implicitly means that tool contact should be enabled.
412+
The result section has one field ``result``, which contains the result from the tool contact in the form of an integer.
413+
The action provides no feedback.
414+
415+
The action can be called from the command line using the following command, when the controller is active:
416+
.. code-block::
417+
418+
ros2 action send_goal /tool_contact_controller/enable_tool_contact ur_msgs/action/ToolContact
419+
420+
.. _trajectory_until_node:
421+
422+
ur_controllers/TrajectoryUntilNode
423+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
424+
This is not a controller in itself, but it allows the user to execute a trajectory with tool contact enabled without having to call 2 actions at the same time.
425+
This means that the trajectory will execute until either the trajectory is finished or tool contact has been triggered.
426+
Both scenarios will result in the trajectory being reported as successful.
427+
428+
Action interface / usage
429+
""""""""""""""""""""""""
430+
The node provides an action to execute a trajectory with tool contact enabled. For the node to accept action goals, both the motion controller and the tool contact controller need to be in ``active`` state.
431+
432+
* ``/trajectory_until_node/execute [ur_msgs/action/TrajectoryUntil]``
433+
434+
The action contains all the same fields as the ordinary `FollowJointTrajectory <http://docs.ros.org/en/noetic/api/control_msgs/html/action/FollowJointTrajectory.html>`_ action, but has two additional fields.
435+
One in the goal section called ``until_type``, which is used to choose between different conditions that can stop the trajectory. Currently only tool contact is available.
436+
The result section contains the other new field called ``until_condition_result``, which reports whether the chosen condition was triggered or not, and also error codes if something went wrong with the controller responsible for the until condition.
437+
438+
Implementation details
439+
""""""""""""""""""""""
440+
Upon instantiation of the node, the internal trajectory action client will connect to an action named ``motion_controller/follow_joint_trajectory``.
441+
This action does not exist, but upon launch of the driver, the node is remapped to connect to the ``initial_joint_controller``, default is ``scaled_joint_trajectory_controller``.
442+
If you wish to use the node with another motion controller use the launch argument ``initial_joint_controller:=<your_motion_controller>`` when launching the driver.

ur_controllers/src/tool_contact_controller_parameters.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
---
21
tool_contact_controller:
32
tf_prefix: {
43
type: string,

ur_controllers/src/trajectory_until_node.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ TrajectoryUntilNode::TrajectoryUntilNode(const rclcpp::NodeOptions& options)
7979

8080
// Create action server to advertise the "/trajectory_until/execute"
8181
action_server_ = rclcpp_action::create_server<TrajectoryUntil>(
82-
this, "trajectory_until/execute",
82+
this, std::string(this->get_name()) + "/execute",
8383
std::bind(&TrajectoryUntilNode::goal_received_callback, this, std::placeholders::_1, std::placeholders::_2),
8484
std::bind(&TrajectoryUntilNode::goal_cancelled_callback, this, std::placeholders::_1),
8585
std::bind(&TrajectoryUntilNode::goal_accepted_callback, this, std::placeholders::_1),

ur_robot_driver/src/hardware_interface.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,14 +1046,20 @@ hardware_interface::return_type URPositionHardwareInterface::prepare_command_mod
10461046
for (const auto& key : start_interfaces) {
10471047
for (auto i = 0u; i < info_.joints.size(); i++) {
10481048
if (key == info_.joints[i].name + "/" + hardware_interface::HW_IF_POSITION) {
1049-
if (!start_modes_[i].empty()) {
1049+
if (std::any_of(start_modes_[i].begin(), start_modes_[i].end(), [&](const std::string& item) {
1050+
return item == hardware_interface::HW_IF_VELOCITY || item == PASSTHROUGH_GPIO ||
1051+
item == FORCE_MODE_GPIO || item == FREEDRIVE_MODE_GPIO;
1052+
})) {
10501053
RCLCPP_ERROR(get_logger(), "Attempting to start position control while there is another control mode already "
10511054
"requested.");
10521055
return hardware_interface::return_type::ERROR;
10531056
}
10541057
start_modes_[i] = { hardware_interface::HW_IF_POSITION };
10551058
} else if (key == info_.joints[i].name + "/" + hardware_interface::HW_IF_VELOCITY) {
1056-
if (!start_modes_[i].empty()) {
1059+
if (std::any_of(start_modes_[i].begin(), start_modes_[i].end(), [&](const std::string& item) {
1060+
return item == hardware_interface::HW_IF_POSITION || item == PASSTHROUGH_GPIO ||
1061+
item == FORCE_MODE_GPIO || item == FREEDRIVE_MODE_GPIO;
1062+
})) {
10571063
RCLCPP_ERROR(get_logger(), "Attempting to start velocity control while there is another control mode already "
10581064
"requested.");
10591065
return hardware_interface::return_type::ERROR;

ur_robot_driver/test/integration_test_tool_contact.py

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@
3939
from rclpy.node import Node
4040

4141
from controller_manager_msgs.srv import SwitchController
42-
43-
from ur_msgs.action import ToolContact
42+
from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
43+
from builtin_interfaces.msg import Duration
44+
from ur_msgs.action import ToolContact, TrajectoryUntil
4445

4546
sys.path.append(os.path.dirname(__file__))
4647
from test_common import ( # noqa: E402
@@ -49,18 +50,21 @@
4950
IoStatusInterface,
5051
ActionInterface,
5152
generate_driver_test_description,
53+
ROBOT_JOINTS,
5254
)
5355

5456
TIMEOUT_EXECUTE_TRAJECTORY = 30
5557

5658

5759
@pytest.mark.launch_test
5860
@launch_testing.parametrize(
59-
"tf_prefix",
60-
[(""), ("my_ur_")],
61+
"tf_prefix, initial_joint_controller",
62+
[("", "scaled_joint_trajectory_controller"), ("my_ur_", "passthrough_trajectory_controller")],
6163
)
62-
def generate_test_description(tf_prefix):
63-
return generate_driver_test_description(tf_prefix=tf_prefix)
64+
def generate_test_description(tf_prefix, initial_joint_controller):
65+
return generate_driver_test_description(
66+
tf_prefix=tf_prefix, initial_joint_controller=initial_joint_controller
67+
)
6468

6569

6670
class RobotDriverTest(unittest.TestCase):
@@ -85,16 +89,58 @@ def init_robot(self):
8589
self._tool_contact_interface = ActionInterface(
8690
self.node, "/tool_contact_controller/enable_tool_contact", ToolContact
8791
)
92+
self._trajectory_until_interface = ActionInterface(
93+
self.node, "/trajectory_until/execute", TrajectoryUntil
94+
)
8895

8996
def setUp(self):
9097
self._dashboard_interface.start_robot()
9198
time.sleep(1)
9299
self.assertTrue(self._io_status_controller_interface.resend_robot_program().success)
93100

101+
#
102+
# Tests
103+
#
104+
94105
def test_start_tool_contact_controller(self):
95106
self.assertTrue(
96107
self._controller_manager_interface.switch_controller(
97108
strictness=SwitchController.Request.BEST_EFFORT,
98109
activate_controllers=["tool_contact_controller"],
99110
).ok
100111
)
112+
113+
def test_tool_contact(self):
114+
goal_handle = self._tool_contact_interface.send_goal()
115+
self.assertTrue(goal_handle.accepted)
116+
117+
cancel_res = self._tool_contact_interface.cancel_goal(goal_handle)
118+
self.assertEqual(cancel_res.return_code, 0)
119+
120+
def test_trajectory_until(self):
121+
L_pose_to_down = {
122+
"waypts": [[-0.45, -1.5, 1.5, -1.5, -1.5, -1.5], [-0.45, -1.2, 2.1, -2.4, -1.5, -1.5]],
123+
"time_vec": [Duration(sec=3, nanosec=0), Duration(sec=6, nanosec=0)],
124+
}
125+
126+
trajectory = JointTrajectory()
127+
trajectory.joint_names = ROBOT_JOINTS
128+
129+
trajectory.points = [
130+
JointTrajectoryPoint(
131+
positions=L_pose_to_down["waypts"][i], time_from_start=L_pose_to_down["time_vec"][i]
132+
)
133+
for i in range(len(L_pose_to_down["waypts"]))
134+
]
135+
136+
goal_handle = self._trajectory_until_interface.send_goal(
137+
trajectory=trajectory, until_type=TrajectoryUntil.Goal.TOOL_CONTACT
138+
)
139+
self.assertTrue(goal_handle.accepted)
140+
141+
result = self._trajectory_until_interface.get_result(
142+
goal_handle=goal_handle, timeout=TIMEOUT_EXECUTE_TRAJECTORY
143+
)
144+
145+
self.assertEqual(result.error_code, TrajectoryUntil.Result.SUCCESSFUL)
146+
self.assertGreaterEqual(result.until_condition_result, TrajectoryUntil.Result.NOT_TRIGGERED)

ur_robot_driver/test/robot_driver.py

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,6 @@ def test_trajectory_scaled(self, tf_prefix):
270270
)
271271
self.assertEqual(result.error_code, FollowJointTrajectory.Result.SUCCESSFUL)
272272

273-
def test_tool_contact(self, tf_prefix):
274-
start_result = self._io_status_controller_interface.start_tool_contact()
275-
self.assertEqual(start_result.success, True)
276-
277-
end_result = self._io_status_controller_interface.end_tool_contact()
278-
self.assertEqual(end_result.success, True)
279-
280273
def test_trajectory_scaled_aborts_on_violation(self, tf_prefix):
281274
"""Test that the robot correctly aborts the trajectory when the constraints are violated."""
282275
# Construct test trajectory

ur_robot_driver/test/test_common.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,18 @@ def get_result(self, goal_handle, timeout):
191191
f"Exception while calling action '{self.__action_name}': {future_res.exception()}"
192192
)
193193

194+
def cancel_goal(self, goal_handle, timeout=2):
195+
future_res = goal_handle.cancel_goal_async()
196+
logging.info("Canceling goal from '%s' with timeout %fs", self.__action_name, timeout)
197+
rclpy.spin_until_future_complete(self.__node, future_res, timeout_sec=timeout)
198+
if future_res.result() is not None:
199+
logging.info(" Received result: %s", future_res.result())
200+
return future_res.result()
201+
else:
202+
raise Exception(
203+
f"Exception while calling action '{self.__action_name}': {future_res.exception()}"
204+
)
205+
194206

195207
class DashboardInterface(
196208
_ServiceInterface,

0 commit comments

Comments
 (0)