Skip to content

Commit 9fb5a65

Browse files
authored
Allow setting the analog output domain when setting an analog output (backport of #1123)
1 parent f72fd62 commit 9fb5a65

File tree

5 files changed

+201
-2
lines changed

5 files changed

+201
-2
lines changed

ur_controllers/doc/index.rst

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
ur_controllers
2+
==============
3+
4+
This package contains controllers and hardware interface for ``ros2_controllers`` that are special to the UR
5+
robot family. Currently this contains:
6+
7+
8+
* A **speed_scaling_state_broadcaster** that publishes the current execution speed as reported by
9+
the robot to a topic interface. Values are floating points between 0 and 1.
10+
* A **scaled_joint_trajectory_controller** that is similar to the *joint_trajectory_controller*\ ,
11+
but it uses the speed scaling reported to align progress of the trajectory between the robot and controller.
12+
* A **io_and_status_controller** that allows setting I/O ports, controlling some UR-specific
13+
functionality and publishes status information about the robot.
14+
15+
About this package
16+
------------------
17+
18+
This package contains controllers not being available in the default ``ros2_controllers`` set. They are
19+
created to support more features offered by the UR robot family. Some of these controllers are
20+
example implementations for certain features and are intended to be generalized and merged
21+
into the default ``ros2_controllers`` controller set at some future point.
22+
23+
Controller description
24+
----------------------
25+
26+
This packages offers a couple of specific controllers that will be explained in the following
27+
sections.
28+
29+
.. _speed_scaling_state_broadcaster:
30+
31+
ur_controllers/SpeedScalingStateBroadcaster
32+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
33+
34+
This controller publishes the current actual execution speed as reported by the robot. Values are
35+
floating points between 0 and 1.
36+
37+
In the `ur_robot_driver
38+
<https://index.ros.org/p/ur_robot_driver/github-UniversalRobots-Universal_Robots_ROS2_Driver/>`_
39+
this is calculated by multiplying the two `RTDE
40+
<https://www.universal-robots.com/articles/ur/real-time-data-exchange-rtde-guide/>`_ data
41+
fields ``speed_scaling`` (which should be equal to the value shown by the speed slider position on the
42+
teach pendant) and ``target_speed_fraction`` (Which is the fraction to which execution gets slowed
43+
down by the controller).
44+
45+
.. _scaled_jtc:
46+
47+
ur_controlers/ScaledJointTrajectoryController
48+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49+
50+
These controllers work similar to the well-known
51+
`joint_trajectory_controller <https://control.ros.org/master/doc/ros2_controllers/joint_trajectory_controller/doc/userdoc.html>`_.
52+
53+
However, they are extended to handle the robot's execution speed specifically. Because the default
54+
``joint_trajectory_controller`` would interpolate the trajectory with the configured time constraints (ie: always assume maximum velocity and acceleration supported by the robot),
55+
this could lead to significant path deviation due to multiple reasons:
56+
57+
58+
* The speed slider on the robot might not be at 100%, so motion commands sent from ROS would
59+
effectively get scaled down resulting in a slower execution.
60+
* The robot could scale down motions based on configured safety limits resulting in a slower motion
61+
than expected and therefore not reaching the desired target in a control cycle.
62+
* Motions might not be executed at all, e.g. because the robot is E-stopped or in a protective stop
63+
* Motion commands sent to the robot might not be interpreted, e.g. because there is no
64+
`external_control <https://github.com/UniversalRobots/Universal_Robots_ROS_Driver#prepare-the-robot>`_
65+
program node running on the robot controller.
66+
* The program interpreting motion commands could be paused.
67+
68+
The following plot illustrates the problem:
69+
70+
.. image:: traj_without_speed_scaling.png
71+
:target: traj_without_speed_scaling.png
72+
:alt: Trajectory execution with default trajectory controller
73+
74+
75+
The graph shows a trajectory with one joint being moved to a target point and back to its starting
76+
point. As the joint's speed is limited to a very low setting on the teach pendant, speed scaling
77+
(black line) activates and limits the joint speed (green line). As a result, the target
78+
trajectory (light blue) doesn't get executed by the robot, but instead the pink trajectory is executed.
79+
The vertical distance between the light blue line and the pink line is the path error in each
80+
control cycle. We can see that the path deviation gets above 300 degrees at some point and the
81+
target point at -6 radians never gets reached.
82+
83+
All of the cases mentioned above are addressed by the scaled trajectory versions. Trajectory execution
84+
can be transparently scaled down using the speed slider on the teach pendant without leading to
85+
additional path deviations. Pausing the program or hitting the E-stop effectively leads to
86+
``speed_scaling`` being 0 meaning the trajectory will not be continued until the program is continued.
87+
This way, trajectory executions can be explicitly paused and continued.
88+
89+
With the scaled version of the trajectory controller the example motion shown in the previous diagram becomes:
90+
91+
.. image:: traj_with_speed_scaling.png
92+
:target: traj_with_speed_scaling.png
93+
:alt: Trajectory execution with scaled_joint_trajectory_controller
94+
95+
96+
The deviation between trajectory interpolation on the ROS side and actual robot execution stays minimal and the
97+
robot reaches the intermediate setpoint instead of returning "too early" as in the example above.
98+
99+
Under the hood this is implemented by proceeding the trajectory not by a full time step but only by
100+
the fraction determined by the current speed scaling. If speed scaling is currently at 50% then
101+
interpolation of the current control cycle will start half a time step after the beginning of the
102+
previous control cycle.
103+
104+
.. _io_and_status_controller:
105+
106+
ur_controllers/GPIOController
107+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
108+
109+
This controller allows setting I/O ports, controlling some UR-specific functionality and publishes
110+
status information about the robot.
111+
112+
Published topics
113+
""""""""""""""""
114+
115+
* ``~/io_states [ur_msgs/msg/IOStates]``: Status of all I/O ports
116+
* ``~/robot_mode [ur_dashboard_msgs/msg/RobotMode]``: The current robot mode (e.g. ``POWER_OFF``,
117+
``IDLE``, ``RUNNING``)
118+
* ``~/robot_program_running [std_msgs/msg/Bool]``: Publishes whether **the External Control
119+
program** is running or not. If this is ``false`` no commands can be sent to the robot.
120+
* ``~/safety_mode [ur_dashboard_msgs/msg/SafetyMode]``: The robot's current safety mode (e.g.
121+
``PROTECTIVE_STOP``, ``ROBOT_EMERGENCY_STOP``, ``NORMAL``)
122+
* ``~/tool_data [ur_msgs/msg/ToolDataMsg]``: Information about the robot's tool configuration
123+
124+
Advertised services
125+
"""""""""""""""""""
126+
127+
* ``~/hand_back_control [std_srvs/srv/Trigger]``: Calling this service will make the robot program
128+
exit the *External Control* program node and continue with the rest of the program.
129+
* ``~/resend_robot_program [std_srvs/srv/Trigger]``: When :ref:`headless_mode` is used, this
130+
service can be used to restart the *External Control* program on the robot.
131+
* ``~/set_io [ur_msgs/srv/SetIO]``: Set an output pin on the robot.
132+
* ``~/set_analog_output [ur_msgs/srv/SetAnalogOutput]``: Set an analog output on the robot. This
133+
also allows specifying the domain.
134+
* ``~/set_payload [ur_msgs/srv/SetPayload]``: Change the robot's payload on-the-fly.
135+
* ``~/set_speed_slider [ur_msgs/srv/SetSpeedSliderFraction]``: Set the value of the speed slider.
136+
* ``~/zero_ftsensor [std_srvs/srv/Trigger]``: Zeroes the reported wrench of the force torque
137+
sensor.

ur_controllers/include/ur_controllers/gpio_controller.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
#include "ur_dashboard_msgs/msg/robot_mode.hpp"
5252
#include "ur_dashboard_msgs/msg/safety_mode.hpp"
5353
#include "ur_msgs/srv/set_io.hpp"
54+
#include "ur_msgs/srv/set_analog_output.hpp"
5455
#include "ur_msgs/srv/set_speed_slider_fraction.hpp"
5556
#include "ur_msgs/srv/set_payload.hpp"
5657
#include "rclcpp/time.hpp"
@@ -79,6 +80,7 @@ enum CommandInterfaces
7980
ZERO_FTSENSOR_ASYNC_SUCCESS = 32,
8081
HAND_BACK_CONTROL_CMD = 33,
8182
HAND_BACK_CONTROL_ASYNC_SUCCESS = 34,
83+
ANALOG_OUTPUTS_DOMAIN = 35,
8284
};
8385

8486
enum StateInterfaces
@@ -122,6 +124,9 @@ class GPIOController : public controller_interface::ControllerInterface
122124
private:
123125
bool setIO(ur_msgs::srv::SetIO::Request::SharedPtr req, ur_msgs::srv::SetIO::Response::SharedPtr resp);
124126

127+
bool setAnalogOutput(ur_msgs::srv::SetAnalogOutput::Request::SharedPtr req,
128+
ur_msgs::srv::SetAnalogOutput::Response::SharedPtr resp);
129+
125130
bool setSpeedSlider(ur_msgs::srv::SetSpeedSliderFraction::Request::SharedPtr req,
126131
ur_msgs::srv::SetSpeedSliderFraction::Response::SharedPtr resp);
127132

@@ -161,6 +166,7 @@ class GPIOController : public controller_interface::ControllerInterface
161166
rclcpp::Service<std_srvs::srv::Trigger>::SharedPtr hand_back_control_srv_;
162167
rclcpp::Service<ur_msgs::srv::SetSpeedSliderFraction>::SharedPtr set_speed_slider_srv_;
163168
rclcpp::Service<ur_msgs::srv::SetIO>::SharedPtr set_io_srv_;
169+
rclcpp::Service<ur_msgs::srv::SetAnalogOutput>::SharedPtr set_analog_output_srv_;
164170
rclcpp::Service<ur_msgs::srv::SetPayload>::SharedPtr set_payload_srv_;
165171
rclcpp::Service<std_srvs::srv::Trigger>::SharedPtr tare_sensor_srv_;
166172

ur_controllers/src/gpio_controller.cpp

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ controller_interface::InterfaceConfiguration GPIOController::command_interface_c
9797
config.names.emplace_back(tf_prefix + "hand_back_control/hand_back_control_cmd");
9898
config.names.emplace_back(tf_prefix + "hand_back_control/hand_back_control_async_success");
9999

100+
config.names.emplace_back(tf_prefix + "gpio/analog_output_domain_cmd");
101+
100102
return config;
101103
}
102104

@@ -293,6 +295,9 @@ ur_controllers::GPIOController::on_activate(const rclcpp_lifecycle::State& /*pre
293295
program_state_pub_ = get_node()->create_publisher<std_msgs::msg::Bool>("~/robot_program_running", qos_latched);
294296
set_io_srv_ = get_node()->create_service<ur_msgs::srv::SetIO>(
295297
"~/set_io", std::bind(&GPIOController::setIO, this, std::placeholders::_1, std::placeholders::_2));
298+
set_analog_output_srv_ = get_node()->create_service<ur_msgs::srv::SetAnalogOutput>(
299+
"~/set_analog_output",
300+
std::bind(&GPIOController::setAnalogOutput, this, std::placeholders::_1, std::placeholders::_2));
296301

297302
set_speed_slider_srv_ = get_node()->create_service<ur_msgs::srv::SetSpeedSliderFraction>(
298303
"~/set_speed_slider",
@@ -357,7 +362,7 @@ bool GPIOController::setIO(ur_msgs::srv::SetIO::Request::SharedPtr req, ur_msgs:
357362
command_interfaces_[CommandInterfaces::IO_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
358363
command_interfaces_[CommandInterfaces::ANALOG_OUTPUTS_CMD + req->pin].set_value(static_cast<double>(req->state));
359364

360-
RCLCPP_INFO(get_node()->get_logger(), "Setting analog output '%d' to state: '%1.0f'.", req->pin, req->state);
365+
RCLCPP_INFO(get_node()->get_logger(), "Setting analog output '%d' to state: '%f'.", req->pin, req->state);
361366

362367
if (!waitForAsyncCommand([&]() { return command_interfaces_[CommandInterfaces::IO_ASYNC_SUCCESS].get_value(); })) {
363368
RCLCPP_WARN(get_node()->get_logger(), "Could not verify that io was set. (This might happen when using the "
@@ -385,6 +390,46 @@ bool GPIOController::setIO(ur_msgs::srv::SetIO::Request::SharedPtr req, ur_msgs:
385390
}
386391
}
387392

393+
bool GPIOController::setAnalogOutput(ur_msgs::srv::SetAnalogOutput::Request::SharedPtr req,
394+
ur_msgs::srv::SetAnalogOutput::Response::SharedPtr resp)
395+
{
396+
std::string domain_string = "UNKNOWN";
397+
switch (req->data.domain) {
398+
case ur_msgs::msg::Analog::CURRENT:
399+
domain_string = "CURRENT";
400+
break;
401+
case ur_msgs::msg::Analog::VOLTAGE:
402+
domain_string = "VOLTAGE";
403+
break;
404+
default:
405+
RCLCPP_ERROR(get_node()->get_logger(), "Domain must be either 0 (CURRENT) or 1 (VOLTAGE)");
406+
resp->success = false;
407+
return false;
408+
}
409+
410+
if (req->data.pin < 0 || req->data.pin > 1) {
411+
RCLCPP_ERROR(get_node()->get_logger(), "Invalid pin selected. Only pins 0 and 1 are allowed.");
412+
resp->success = false;
413+
return false;
414+
}
415+
416+
command_interfaces_[CommandInterfaces::IO_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
417+
command_interfaces_[CommandInterfaces::ANALOG_OUTPUTS_CMD + req->data.pin].set_value(
418+
static_cast<float>(req->data.state));
419+
command_interfaces_[CommandInterfaces::ANALOG_OUTPUTS_DOMAIN].set_value(static_cast<double>(req->data.domain));
420+
421+
RCLCPP_INFO(get_node()->get_logger(), "Setting analog output '%d' to state: '%f' in domain %s.", req->data.pin,
422+
req->data.state, domain_string.c_str());
423+
424+
if (!waitForAsyncCommand([&]() { return command_interfaces_[CommandInterfaces::IO_ASYNC_SUCCESS].get_value(); })) {
425+
RCLCPP_WARN(get_node()->get_logger(), "Could not verify that io was set. (This might happen when using the "
426+
"mocked interface)");
427+
}
428+
429+
resp->success = static_cast<bool>(command_interfaces_[IO_ASYNC_SUCCESS].get_value());
430+
return resp->success;
431+
}
432+
388433
bool GPIOController::setSpeedSlider(ur_msgs::srv::SetSpeedSliderFraction::Request::SharedPtr req,
389434
ur_msgs::srv::SetSpeedSliderFraction::Response::SharedPtr resp)
390435
{

ur_robot_driver/include/ur_robot_driver/hardware_interface.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ class URPositionHardwareInterface : public hardware_interface::SystemInterface
177177
// asynchronous commands
178178
std::array<double, 18> standard_dig_out_bits_cmd_;
179179
std::array<double, 2> standard_analog_output_cmd_;
180+
double analog_output_domain_cmd_;
180181
double tool_voltage_cmd_;
181182
double io_async_success_;
182183
double target_speed_fraction_cmd_;

ur_robot_driver/src/hardware_interface.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,8 @@ std::vector<hardware_interface::CommandInterface> URPositionHardwareInterface::e
301301
command_interfaces.emplace_back(hardware_interface::CommandInterface(
302302
tf_prefix + "gpio", "standard_analog_output_cmd_" + std::to_string(i), &standard_analog_output_cmd_[i]));
303303
}
304+
command_interfaces.emplace_back(
305+
hardware_interface::CommandInterface(tf_prefix + "gpio", "analog_output_domain_cmd", &analog_output_domain_cmd_));
304306

305307
command_interfaces.emplace_back(
306308
hardware_interface::CommandInterface(tf_prefix + "gpio", "tool_voltage_cmd", &tool_voltage_cmd_));
@@ -677,6 +679,8 @@ void URPositionHardwareInterface::initAsyncIO()
677679
standard_analog_output_cmd_[i] = NO_NEW_CMD_;
678680
}
679681

682+
analog_output_domain_cmd_ = NO_NEW_CMD_;
683+
680684
tool_voltage_cmd_ = NO_NEW_CMD_;
681685

682686
payload_mass_ = NO_NEW_CMD_;
@@ -706,7 +710,13 @@ void URPositionHardwareInterface::checkAsyncIO()
706710

707711
for (size_t i = 0; i < 2; ++i) {
708712
if (!std::isnan(standard_analog_output_cmd_[i]) && ur_driver_ != nullptr) {
709-
io_async_success_ = ur_driver_->getRTDEWriter().sendStandardAnalogOutput(i, standard_analog_output_cmd_[i]);
713+
urcl::AnalogOutputType domain = urcl::AnalogOutputType::SET_ON_TEACH_PENDANT;
714+
if (!std::isnan(analog_output_domain_cmd_) && ur_driver_ != nullptr) {
715+
domain = static_cast<urcl::AnalogOutputType>(analog_output_domain_cmd_);
716+
analog_output_domain_cmd_ = NO_NEW_CMD_;
717+
}
718+
io_async_success_ =
719+
ur_driver_->getRTDEWriter().sendStandardAnalogOutput(i, standard_analog_output_cmd_[i], domain);
710720
standard_analog_output_cmd_[i] = NO_NEW_CMD_;
711721
}
712722
}

0 commit comments

Comments
 (0)