Skip to content

Commit fbc2e35

Browse files
authored
Allow setting payload inertia matrix via set_payload service backwards-compatible (backport #1811) (#1880)
Extend set_payload service with inertia matrix and transition_time
1 parent 1ecd8d5 commit fbc2e35

6 files changed

Lines changed: 162 additions & 13 deletions

File tree

ur_controllers/include/ur_controllers/gpio_controller.hpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ enum CommandInterfaces
8282
HAND_BACK_CONTROL_CMD = 33,
8383
HAND_BACK_CONTROL_ASYNC_SUCCESS = 34,
8484
ANALOG_OUTPUTS_DOMAIN = 35,
85+
PAYLOAD_INERTIA_IXX = 36,
86+
PAYLOAD_INERTIA_IYY = 37,
87+
PAYLOAD_INERTIA_IZZ = 38,
88+
PAYLOAD_INERTIA_IXY = 39,
89+
PAYLOAD_INERTIA_IXZ = 40,
90+
PAYLOAD_INERTIA_IYZ = 41,
91+
PAYLOAD_TRANSITION_TIME = 42,
8592
};
8693

8794
enum StateInterfaces
@@ -107,6 +114,12 @@ enum StateInterfaces
107114
PAYLOAD_STATE_COG_X = 72,
108115
PAYLOAD_STATE_COG_Y = 73,
109116
PAYLOAD_STATE_COG_Z = 74,
117+
PAYLOAD_STATE_INERTIA_IXX = 75,
118+
PAYLOAD_STATE_INERTIA_IYY = 76,
119+
PAYLOAD_STATE_INERTIA_IZZ = 77,
120+
PAYLOAD_STATE_INERTIA_IXY = 78,
121+
PAYLOAD_STATE_INERTIA_IXZ = 79,
122+
PAYLOAD_STATE_INERTIA_IYZ = 80,
110123
};
111124

112125
class GPIOController : public controller_interface::ControllerInterface
@@ -210,7 +223,8 @@ class GPIOController : public controller_interface::ControllerInterface
210223
*/
211224
bool waitForAsyncCommand(std::function<double(void)> get_value);
212225

213-
bool waitForPayloadRtdeMatch(double mass, double cx, double cy, double cz);
226+
bool waitForPayloadRtdeMatch(double mass, double cx, double cy, double cz, double ixx, double iyy, double izz,
227+
double ixy, double ixz, double iyz, double transition_time);
214228
};
215229
} // namespace ur_controllers
216230

ur_controllers/src/gpio_controller.cpp

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ controller_interface::InterfaceConfiguration GPIOController::command_interface_c
135135

136136
config.names.emplace_back(tf_prefix + "gpio/analog_output_domain_cmd");
137137

138+
config.names.emplace_back(tf_prefix + "payload/inertia.ixx");
139+
config.names.emplace_back(tf_prefix + "payload/inertia.iyy");
140+
config.names.emplace_back(tf_prefix + "payload/inertia.izz");
141+
config.names.emplace_back(tf_prefix + "payload/inertia.ixy");
142+
config.names.emplace_back(tf_prefix + "payload/inertia.ixz");
143+
config.names.emplace_back(tf_prefix + "payload/inertia.iyz");
144+
config.names.emplace_back(tf_prefix + "payload/transition_time");
145+
138146
return config;
139147
}
140148

@@ -200,6 +208,12 @@ controller_interface::InterfaceConfiguration ur_controllers::GPIOController::sta
200208
config.names.emplace_back(tf_prefix + "payload/cog.x");
201209
config.names.emplace_back(tf_prefix + "payload/cog.y");
202210
config.names.emplace_back(tf_prefix + "payload/cog.z");
211+
config.names.emplace_back(tf_prefix + "payload/inertia.ixx");
212+
config.names.emplace_back(tf_prefix + "payload/inertia.iyy");
213+
config.names.emplace_back(tf_prefix + "payload/inertia.izz");
214+
config.names.emplace_back(tf_prefix + "payload/inertia.ixy");
215+
config.names.emplace_back(tf_prefix + "payload/inertia.ixz");
216+
config.names.emplace_back(tf_prefix + "payload/inertia.iyz");
203217

204218
return config;
205219
}
@@ -592,11 +606,19 @@ bool GPIOController::setPayload(const ur_msgs::srv::SetPayload::Request::SharedP
592606

593607
// reset success flag
594608
command_interfaces_[CommandInterfaces::PAYLOAD_ASYNC_SUCCESS].set_value(ASYNC_WAITING);
595-
596-
command_interfaces_[CommandInterfaces::PAYLOAD_MASS].set_value(req->mass);
609+
command_interfaces_[CommandInterfaces::PAYLOAD_MASS].set_value(static_cast<double>(req->mass));
597610
command_interfaces_[CommandInterfaces::PAYLOAD_COG_X].set_value(req->center_of_gravity.x);
598611
command_interfaces_[CommandInterfaces::PAYLOAD_COG_Y].set_value(req->center_of_gravity.y);
599612
command_interfaces_[CommandInterfaces::PAYLOAD_COG_Z].set_value(req->center_of_gravity.z);
613+
command_interfaces_[CommandInterfaces::PAYLOAD_INERTIA_IXX].set_value(req->ixx);
614+
command_interfaces_[CommandInterfaces::PAYLOAD_INERTIA_IYY].set_value(req->iyy);
615+
command_interfaces_[CommandInterfaces::PAYLOAD_INERTIA_IZZ].set_value(req->izz);
616+
command_interfaces_[CommandInterfaces::PAYLOAD_INERTIA_IXY].set_value(req->ixy);
617+
command_interfaces_[CommandInterfaces::PAYLOAD_INERTIA_IXZ].set_value(req->ixz);
618+
command_interfaces_[CommandInterfaces::PAYLOAD_INERTIA_IYZ].set_value(req->iyz);
619+
double transition_time_seconds =
620+
static_cast<double>(req->transition_time.sec) + (static_cast<double>(req->transition_time.nanosec) * 1e-9);
621+
command_interfaces_[CommandInterfaces::PAYLOAD_TRANSITION_TIME].set_value(transition_time_seconds);
600622

601623
if (!waitForAsyncCommand(
602624
[&]() { return command_interfaces_[CommandInterfaces::PAYLOAD_ASYNC_SUCCESS].get_value(); })) {
@@ -613,8 +635,10 @@ bool GPIOController::setPayload(const ur_msgs::srv::SetPayload::Request::SharedP
613635

614636
if (params_.verify_payload_on_set) {
615637
if (!waitForPayloadRtdeMatch(static_cast<double>(req->mass), req->center_of_gravity.x, req->center_of_gravity.y,
616-
req->center_of_gravity.z)) {
617-
RCLCPP_WARN(get_node()->get_logger(), "setPayload reported success but RTDE payload / payload_cog do not match "
638+
req->center_of_gravity.z, req->ixx, req->iyy, req->izz, req->ixy, req->ixz, req->iyz,
639+
transition_time_seconds)) {
640+
RCLCPP_WARN(get_node()->get_logger(), "setPayload reported success but RTDE payload / payload_cog / "
641+
"payload_inertia do not match "
618642
"the "
619643
"request yet. (This might "
620644
"happen when using the mocked interface.)");
@@ -680,19 +704,33 @@ bool GPIOController::waitForAsyncCommand(std::function<double(void)> get_value)
680704
return true;
681705
}
682706

683-
bool GPIOController::waitForPayloadRtdeMatch(double mass, double cx, double cy, double cz)
707+
bool GPIOController::waitForPayloadRtdeMatch(double mass, double cx, double cy, double cz, double ixx, double iyy,
708+
double izz, double ixy, double ixz, double iyz, double transition_time)
684709
{
685710
constexpr double tol_mass = 1e-3;
686711
constexpr double tol_cog = 1e-4;
712+
constexpr double tol_inertia = 1e-4;
687713
const auto maximum_retries = params_.check_io_successfull_retries;
688714

715+
if (transition_time > 0.0) {
716+
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(transition_time * 1000)));
717+
}
718+
689719
for (int retries = 0; retries <= maximum_retries; ++retries) {
690720
const auto m = state_interfaces_[StateInterfaces::PAYLOAD_STATE_MASS].get_value();
691721
const auto sx = state_interfaces_[StateInterfaces::PAYLOAD_STATE_COG_X].get_value();
692722
const auto sy = state_interfaces_[StateInterfaces::PAYLOAD_STATE_COG_Y].get_value();
693723
const auto sz = state_interfaces_[StateInterfaces::PAYLOAD_STATE_COG_Z].get_value();
724+
const auto sixx = state_interfaces_[StateInterfaces::PAYLOAD_STATE_INERTIA_IXX].get_value();
725+
const auto siyy = state_interfaces_[StateInterfaces::PAYLOAD_STATE_INERTIA_IYY].get_value();
726+
const auto sizz = state_interfaces_[StateInterfaces::PAYLOAD_STATE_INERTIA_IZZ].get_value();
727+
const auto sixy = state_interfaces_[StateInterfaces::PAYLOAD_STATE_INERTIA_IXY].get_value();
728+
const auto sixz = state_interfaces_[StateInterfaces::PAYLOAD_STATE_INERTIA_IXZ].get_value();
729+
const auto siyz = state_interfaces_[StateInterfaces::PAYLOAD_STATE_INERTIA_IYZ].get_value();
694730
if (std::abs(m - mass) <= tol_mass && std::abs(sx - cx) <= tol_cog && std::abs(sy - cy) <= tol_cog &&
695-
std::abs(sz - cz) <= tol_cog) {
731+
std::abs(sz - cz) <= tol_cog && std::abs(sixx - ixx) <= tol_inertia && std::abs(siyy - iyy) <= tol_inertia &&
732+
std::abs(sizz - izz) <= tol_inertia && std::abs(sixy - ixy) <= tol_inertia &&
733+
std::abs(sixz - ixz) <= tol_inertia && std::abs(siyz - iyz) <= tol_inertia) {
696734
return true;
697735
}
698736
std::this_thread::sleep_for(std::chrono::milliseconds(50));

ur_robot_driver/include/ur_robot_driver/hardware_interface.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,13 @@ class URPositionHardwareInterface : public hardware_interface::SystemInterface
262262

263263
// Payload stuff
264264
urcl::vector3d_t payload_center_of_gravity_;
265+
urcl::vector6d_t payload_inertia_;
265266
double payload_mass_;
267+
double payload_transition_time_;
266268
double payload_async_success_;
267269
double rtde_payload_mass_ = 0.0;
268270
urcl::vector3d_t rtde_payload_cog_{ 0.0, 0.0, 0.0 };
271+
urcl::vector6d_t rtde_payload_inertia_{ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
269272

270273
// Friction model parameters
271274
urcl::vector6d_t friction_model_viscous_;

ur_robot_driver/resources/rtde_output_recipe.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ actual_current
2929
tcp_offset
3030
payload
3131
payload_cog
32+
payload_inertia

ur_robot_driver/src/hardware_interface.cpp

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,18 @@ std::vector<hardware_interface::StateInterface> URPositionHardwareInterface::exp
350350
hardware_interface::StateInterface(tf_prefix + "payload", "cog.y", &rtde_payload_cog_[1]));
351351
state_interfaces.emplace_back(
352352
hardware_interface::StateInterface(tf_prefix + "payload", "cog.z", &rtde_payload_cog_[2]));
353+
state_interfaces.emplace_back(
354+
hardware_interface::StateInterface(tf_prefix + "payload", "inertia.ixx", &rtde_payload_inertia_[0]));
355+
state_interfaces.emplace_back(
356+
hardware_interface::StateInterface(tf_prefix + "payload", "inertia.iyy", &rtde_payload_inertia_[1]));
357+
state_interfaces.emplace_back(
358+
hardware_interface::StateInterface(tf_prefix + "payload", "inertia.izz", &rtde_payload_inertia_[2]));
359+
state_interfaces.emplace_back(
360+
hardware_interface::StateInterface(tf_prefix + "payload", "inertia.ixy", &rtde_payload_inertia_[3]));
361+
state_interfaces.emplace_back(
362+
hardware_interface::StateInterface(tf_prefix + "payload", "inertia.ixz", &rtde_payload_inertia_[4]));
363+
state_interfaces.emplace_back(
364+
hardware_interface::StateInterface(tf_prefix + "payload", "inertia.iyz", &rtde_payload_inertia_[5]));
353365

354366
return state_interfaces;
355367
}
@@ -407,6 +419,20 @@ std::vector<hardware_interface::CommandInterface> URPositionHardwareInterface::e
407419
hardware_interface::CommandInterface(tf_prefix + "payload", "cog.y", &payload_center_of_gravity_[1]));
408420
command_interfaces.emplace_back(
409421
hardware_interface::CommandInterface(tf_prefix + "payload", "cog.z", &payload_center_of_gravity_[2]));
422+
command_interfaces.emplace_back(
423+
hardware_interface::CommandInterface(tf_prefix + "payload", "inertia.ixx", &payload_inertia_[0]));
424+
command_interfaces.emplace_back(
425+
hardware_interface::CommandInterface(tf_prefix + "payload", "inertia.iyy", &payload_inertia_[1]));
426+
command_interfaces.emplace_back(
427+
hardware_interface::CommandInterface(tf_prefix + "payload", "inertia.izz", &payload_inertia_[2]));
428+
command_interfaces.emplace_back(
429+
hardware_interface::CommandInterface(tf_prefix + "payload", "inertia.ixy", &payload_inertia_[3]));
430+
command_interfaces.emplace_back(
431+
hardware_interface::CommandInterface(tf_prefix + "payload", "inertia.ixz", &payload_inertia_[4]));
432+
command_interfaces.emplace_back(
433+
hardware_interface::CommandInterface(tf_prefix + "payload", "inertia.iyz", &payload_inertia_[5]));
434+
command_interfaces.emplace_back(
435+
hardware_interface::CommandInterface(tf_prefix + "payload", "transition_time", &payload_transition_time_));
410436
command_interfaces.emplace_back(
411437
hardware_interface::CommandInterface(tf_prefix + "payload", "payload_async_success", &payload_async_success_));
412438

@@ -825,6 +851,7 @@ hardware_interface::return_type URPositionHardwareInterface::read(const rclcpp::
825851
readData(data_package_buffer_, "tcp_offset", tcp_offset_);
826852
readData(data_package_buffer_, "payload", rtde_payload_mass_);
827853
readData(data_package_buffer_, "payload_cog", rtde_payload_cog_);
854+
readData(data_package_buffer_, "payload_inertia", rtde_payload_inertia_);
828855

829856
// required transforms
830857
extractToolPose();
@@ -954,6 +981,8 @@ void URPositionHardwareInterface::initAsyncIO()
954981

955982
payload_mass_ = NO_NEW_CMD_;
956983
payload_center_of_gravity_ = { NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_ };
984+
payload_inertia_ = { NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_ };
985+
payload_transition_time_ = NO_NEW_CMD_;
957986

958987
friction_model_viscous_.fill(NO_NEW_CMD_);
959988
friction_model_coulomb_.fill(NO_NEW_CMD_);
@@ -1020,10 +1049,16 @@ void URPositionHardwareInterface::checkAsyncIO()
10201049

10211050
if (!std::isnan(payload_mass_) && !std::isnan(payload_center_of_gravity_[0]) &&
10221051
!std::isnan(payload_center_of_gravity_[1]) && !std::isnan(payload_center_of_gravity_[2]) &&
1023-
ur_driver_ != nullptr) {
1024-
payload_async_success_ = ur_driver_->setPayload(payload_mass_, payload_center_of_gravity_);
1052+
!std::isnan(payload_inertia_[0]) && !std::isnan(payload_inertia_[1]) && !std::isnan(payload_inertia_[2]) &&
1053+
!std::isnan(payload_inertia_[3]) && !std::isnan(payload_inertia_[4]) && !std::isnan(payload_inertia_[5]) &&
1054+
!std::isnan(payload_transition_time_) && ur_driver_ != nullptr) {
1055+
payload_async_success_ = ur_driver_->setTargetPayload(payload_mass_, payload_center_of_gravity_, payload_inertia_,
1056+
payload_transition_time_);
1057+
10251058
payload_mass_ = NO_NEW_CMD_;
10261059
payload_center_of_gravity_ = { NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_ };
1060+
payload_inertia_ = { NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_, NO_NEW_CMD_ };
1061+
payload_transition_time_ = NO_NEW_CMD_;
10271062
}
10281063

10291064
if (!std::isnan(friction_model_viscous_[0]) && ur_driver_ != nullptr) {

ur_robot_driver/test/integration_test_io_controller.py

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@
3333
import time
3434
import unittest
3535

36-
import launch_testing
3736
import pytest
3837
import rclpy
3938
from geometry_msgs.msg import Vector3
4039
from rclpy.node import Node
4140
from ur_msgs.msg import IOStates
4241

42+
from builtin_interfaces.msg import Duration as DurationMsg
43+
4344
sys.path.append(os.path.dirname(__file__))
4445
from test_common import ( # noqa: E402
4546
ControllerManagerInterface,
@@ -50,9 +51,8 @@
5051

5152

5253
@pytest.mark.launch_test
53-
@launch_testing.parametrize("tf_prefix", [(""), ("my_ur_")])
54-
def generate_test_description(tf_prefix):
55-
return generate_driver_test_description(tf_prefix=tf_prefix)
54+
def generate_test_description():
55+
return generate_driver_test_description()
5656

5757

5858
class IOControllerTest(unittest.TestCase):
@@ -159,3 +159,61 @@ def test_set_payload(self):
159159
mass=0.0, center_of_gravity=Vector3(x=0.0, y=0.0, z=0.0)
160160
)
161161
self.assertTrue(result.success, "Resetting payload via set_payload failed")
162+
163+
def test_set_payload_with_inertia(self):
164+
"""Setting mass, COG and full inertia matrix should succeed."""
165+
res = self._io_status_controller_interface.set_payload(
166+
mass=1.0,
167+
center_of_gravity=Vector3(x=0.1, y=0.0, z=0.2),
168+
ixx=0.01,
169+
iyy=0.01,
170+
izz=0.02,
171+
ixy=0.0,
172+
ixz=0.0,
173+
iyz=0.0,
174+
transition_time=DurationMsg(),
175+
)
176+
self.assertTrue(res.success)
177+
178+
def test_set_payload_with_transition_time(self):
179+
"""
180+
Setting payload with transition_time > 0 should succeed.
181+
182+
The service should wait for the transition to complete before verifying.
183+
"""
184+
res = self._io_status_controller_interface.set_payload(
185+
mass=1.0,
186+
center_of_gravity=Vector3(x=0.0, y=0.0, z=0.1),
187+
ixx=0.01,
188+
iyy=0.01,
189+
izz=0.02,
190+
transition_time=DurationMsg(sec=1, nanosec=0),
191+
)
192+
self.assertTrue(res.success)
193+
194+
def test_set_payload_updates_sequentially(self):
195+
"""Multiple sequential set_payload calls should all succeed."""
196+
payloads = [
197+
{"mass": 0.5, "cx": 0.0, "cy": 0.0, "cz": 0.1, "ixx": 0.005, "iyy": 0.005, "izz": 0.01},
198+
{"mass": 1.0, "cx": 0.1, "cy": 0.0, "cz": 0.2, "ixx": 0.01, "iyy": 0.01, "izz": 0.02},
199+
{
200+
"mass": 2.0,
201+
"cx": 0.05,
202+
"cy": 0.05,
203+
"cz": 0.15,
204+
"ixx": 0.02,
205+
"iyy": 0.02,
206+
"izz": 0.04,
207+
},
208+
{"mass": 0.0, "cx": 0.0, "cy": 0.0, "cz": 0.0, "ixx": 0.0, "iyy": 0.0, "izz": 0.0},
209+
]
210+
for p in payloads:
211+
res = self._io_status_controller_interface.set_payload(
212+
mass=p["mass"],
213+
center_of_gravity=Vector3(x=p["cx"], y=p["cy"], z=p["cz"]),
214+
ixx=p["ixx"],
215+
iyy=p["iyy"],
216+
izz=p["izz"],
217+
transition_time=DurationMsg(),
218+
)
219+
self.assertTrue(res.success, f"set_payload failed for m={p['mass']}")

0 commit comments

Comments
 (0)