|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright 2024, Universal Robots A/S |
| 3 | +# |
| 4 | +# Redistribution and use in source and binary forms, with or without |
| 5 | +# modification, are permitted provided that the following conditions are met: |
| 6 | +# |
| 7 | +# * Redistributions of source code must retain the above copyright |
| 8 | +# notice, this list of conditions and the following disclaimer. |
| 9 | +# |
| 10 | +# * Redistributions in binary form must reproduce the above copyright |
| 11 | +# notice, this list of conditions and the following disclaimer in the |
| 12 | +# documentation and/or other materials provided with the distribution. |
| 13 | +# |
| 14 | +# * Neither the name of the {copyright_holder} nor the names of its |
| 15 | +# contributors may be used to endorse or promote products derived from |
| 16 | +# this software without specific prior written permission. |
| 17 | +# |
| 18 | +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| 19 | +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 20 | +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| 21 | +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| 22 | +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| 23 | +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| 24 | +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| 25 | +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| 26 | +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| 27 | +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 28 | +# POSSIBILITY OF SUCH DAMAGE. |
| 29 | + |
| 30 | +import rclpy |
| 31 | +from builtin_interfaces.msg import Duration |
| 32 | +from control_msgs.action import FollowJointTrajectory |
| 33 | + |
| 34 | +from rclpy.action import ActionClient |
| 35 | +from rclpy.node import Node |
| 36 | +from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint |
| 37 | +from ur_msgs.srv import SetIO |
| 38 | +from controller_manager_msgs.srv import SwitchController |
| 39 | +from std_srvs.srv import Trigger |
| 40 | + |
| 41 | +TIMEOUT_WAIT_SERVICE = 10 |
| 42 | +TIMEOUT_WAIT_SERVICE_INITIAL = 60 |
| 43 | +TIMEOUT_WAIT_ACTION = 10 |
| 44 | + |
| 45 | +ROBOT_JOINTS = [ |
| 46 | + "shoulder_pan_joint", |
| 47 | + "shoulder_lift_joint", |
| 48 | + "elbow_joint", |
| 49 | + "wrist_1_joint", |
| 50 | + "wrist_2_joint", |
| 51 | + "wrist_3_joint", |
| 52 | +] |
| 53 | + |
| 54 | + |
| 55 | +# Helper functions |
| 56 | +def waitForService(node, srv_name, srv_type, timeout=TIMEOUT_WAIT_SERVICE): |
| 57 | + client = node.create_client(srv_type, srv_name) |
| 58 | + if client.wait_for_service(timeout) is False: |
| 59 | + raise Exception(f"Could not reach service '{srv_name}' within timeout of {timeout}") |
| 60 | + |
| 61 | + node.get_logger().info(f"Successfully connected to service '{srv_name}'") |
| 62 | + return client |
| 63 | + |
| 64 | + |
| 65 | +def waitForAction(node, action_name, action_type, timeout=TIMEOUT_WAIT_ACTION): |
| 66 | + client = ActionClient(node, action_type, action_name) |
| 67 | + if client.wait_for_server(timeout) is False: |
| 68 | + raise Exception( |
| 69 | + f"Could not reach action server '{action_name}' within timeout of {timeout}" |
| 70 | + ) |
| 71 | + |
| 72 | + node.get_logger().info(f"Successfully connected to action '{action_name}'") |
| 73 | + return client |
| 74 | + |
| 75 | + |
| 76 | +class Robot: |
| 77 | + def __init__(self, node): |
| 78 | + self.node = node |
| 79 | + self.service_interfaces = { |
| 80 | + "/io_and_status_controller/set_io": SetIO, |
| 81 | + "/dashboard_client/play": Trigger, |
| 82 | + "/controller_manager/switch_controller": SwitchController, |
| 83 | + } |
| 84 | + self.init_robot() |
| 85 | + |
| 86 | + def init_robot(self): |
| 87 | + self.service_clients = { |
| 88 | + srv_name: waitForService(self.node, srv_name, srv_type) |
| 89 | + for (srv_name, srv_type) in self.service_interfaces.items() |
| 90 | + } |
| 91 | + |
| 92 | + self.jtc_action_client = waitForAction( |
| 93 | + self.node, |
| 94 | + "/scaled_joint_trajectory_controller/follow_joint_trajectory", |
| 95 | + FollowJointTrajectory, |
| 96 | + ) |
| 97 | + |
| 98 | + def set_io(self, pin, value): |
| 99 | + """Test to set an IO.""" |
| 100 | + set_io_req = SetIO.Request() |
| 101 | + set_io_req.fun = 1 |
| 102 | + set_io_req.pin = pin |
| 103 | + set_io_req.state = value |
| 104 | + |
| 105 | + self.call_service("/io_and_status_controller/set_io", set_io_req) |
| 106 | + |
| 107 | + def send_trajectory(self, waypts, time_vec): |
| 108 | + """Send robot trajectory.""" |
| 109 | + if len(waypts) != len(time_vec): |
| 110 | + raise Exception("waypoints vector and time vec should be same length") |
| 111 | + |
| 112 | + # Construct test trajectory |
| 113 | + joint_trajectory = JointTrajectory() |
| 114 | + joint_trajectory.joint_names = ROBOT_JOINTS |
| 115 | + for i in range(len(waypts)): |
| 116 | + point = JointTrajectoryPoint() |
| 117 | + point.positions = waypts[i] |
| 118 | + point.time_from_start = time_vec[i] |
| 119 | + joint_trajectory.points.append(point) |
| 120 | + |
| 121 | + # Sending trajectory goal |
| 122 | + goal_response = self.call_action( |
| 123 | + self.jtc_action_client, FollowJointTrajectory.Goal(trajectory=joint_trajectory) |
| 124 | + ) |
| 125 | + if not goal_response.accepted: |
| 126 | + raise Exception("trajectory was not accepted") |
| 127 | + |
| 128 | + # Verify execution |
| 129 | + result = self.get_result(self.jtc_action_client, goal_response) |
| 130 | + return result.error_code == FollowJointTrajectory.Result.SUCCESSFUL |
| 131 | + |
| 132 | + def call_service(self, srv_name, request): |
| 133 | + future = self.service_clients[srv_name].call_async(request) |
| 134 | + rclpy.spin_until_future_complete(self.node, future) |
| 135 | + if future.result() is not None: |
| 136 | + return future.result() |
| 137 | + else: |
| 138 | + raise Exception(f"Exception while calling service: {future.exception()}") |
| 139 | + |
| 140 | + def call_action(self, ac_client, g): |
| 141 | + future = ac_client.send_goal_async(g) |
| 142 | + rclpy.spin_until_future_complete(self.node, future) |
| 143 | + |
| 144 | + if future.result() is not None: |
| 145 | + return future.result() |
| 146 | + else: |
| 147 | + raise Exception(f"Exception while calling action: {future.exception()}") |
| 148 | + |
| 149 | + def get_result(self, ac_client, goal_response): |
| 150 | + future_res = ac_client._get_result_async(goal_response) |
| 151 | + rclpy.spin_until_future_complete(self.node, future_res) |
| 152 | + if future_res.result() is not None: |
| 153 | + return future_res.result().result |
| 154 | + else: |
| 155 | + raise Exception(f"Exception while calling action: {future_res.exception()}") |
| 156 | + |
| 157 | + |
| 158 | +if __name__ == "__main__": |
| 159 | + rclpy.init() |
| 160 | + node = Node("robot_driver_test") |
| 161 | + robot = Robot(node) |
| 162 | + |
| 163 | + # The following list are arbitrary joint positions, change according to your own needs |
| 164 | + waypts = [ |
| 165 | + [-1.6006, -1.7272, -2.2030, -0.8079, 1.5951, -0.0311], |
| 166 | + [-1.2, -1.4, -1.9, -1.2, 1.5951, -0.0311], |
| 167 | + [-1.6006, -1.7272, -2.2030, -0.8079, 1.5951, -0.0311], |
| 168 | + ] |
| 169 | + time_vec = [Duration(sec=4, nanosec=0), Duration(sec=8, nanosec=0), Duration(sec=12, nanosec=0)] |
| 170 | + |
| 171 | + # Execute trajectory on robot, make sure that the robot is booted and the control script is running |
| 172 | + robot.send_trajectory(waypts, time_vec) |
| 173 | + |
| 174 | + # Set digital output 1 to true |
| 175 | + robot.set_io(1, 1.0) |
0 commit comments