Skip to content

Commit bade17f

Browse files
authored
Add autoconnect parameter to dashboard client (backport #1881) (#1889)
Defer primary version discovery and DashboardClient construction into connect() so the node can start without a reachable robot when autoconnect is false, then connect later via ~/connect.
1 parent 3651925 commit bade17f

6 files changed

Lines changed: 144 additions & 67 deletions

File tree

ur_robot_driver/doc/dashboard_client.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Close a safety popup on the teach pendant.
3636
connect (`std_srvs/Trigger <http://docs.ros.org/en/rolling/p/std_srvs/srv/Trigger.html>`_)
3737
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
3838

39-
Service to reconnect to the dashboard server
39+
Service to connect or reconnect to the dashboard server. Required after startup when ``autoconnect`` is ``false``.
4040

4141
download_program (`ur_dashboard_msgs/DownloadProgram <http://docs.ros.org/en/rolling/p/ur_dashboard_msgs/srv/DownloadProgram.html>`_)
4242
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -230,6 +230,14 @@ Defaults to saving to the programs folder
230230
Parameters
231231
----------
232232

233+
autoconnect (default: true)
234+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
235+
236+
If ``true`` (default), the dashboard client connects to the robot during startup and retries until
237+
the connection succeeds or ROS is shut down. If ``false``, the node starts without contacting the
238+
robot and only exposes the ``~/connect`` service until a connection succeeds; call ``~/connect``
239+
once the robot is available to register the remaining dashboard services.
240+
233241
receive_timeout (default: 20.0)
234242
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
235243

ur_robot_driver/include/ur_robot_driver/dashboard_client_ros.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,10 @@ class DashboardClientROS
200200
ur_dashboard_msgs::srv::GetPolyScopeVersion::Response::SharedPtr resp);
201201

202202
bool connect();
203+
void initServices(urcl::DashboardClient::ClientPolicy dashboard_policy);
203204

204205
std::shared_ptr<rclcpp::Node> node_;
206+
std::string robot_ip_;
205207
std::unique_ptr<urcl::DashboardClient> client_;
206208

207209
urcl::comm::INotifier notifier_;

ur_robot_driver/launch/ur_dashboard_client.launch.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,19 @@ def generate_launch_description():
4949
description="Timeout that the dashboard client will wait for a response from the robot.",
5050
)
5151
)
52+
declared_arguments.append(
53+
DeclareLaunchArgument(
54+
"autoconnect",
55+
default_value="true",
56+
description="Automatically connect to the robot dashboard server on startup. "
57+
"If false, call the ~/connect service after the robot is available.",
58+
)
59+
)
5260

5361
# Initialize Arguments
5462
robot_ip = LaunchConfiguration("robot_ip")
5563
dashboard_receive_timeout = LaunchConfiguration("dashboard_receive_timeout")
64+
autoconnect = LaunchConfiguration("autoconnect")
5665

5766
dashboard_client_node = Node(
5867
package="ur_robot_driver",
@@ -63,6 +72,7 @@ def generate_launch_description():
6372
parameters=[
6473
{"robot_ip": robot_ip},
6574
{"receive_timeout": dashboard_receive_timeout},
75+
{"autoconnect": autoconnect},
6676
],
6777
)
6878

ur_robot_driver/src/dashboard_client_ros.cpp

Lines changed: 93 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,59 @@
5050
namespace ur_robot_driver
5151
{
5252
DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, const std::string& robot_ip)
53-
: node_(node), primary_client_(robot_ip, notifier_)
53+
: node_(node), robot_ip_(robot_ip), primary_client_(robot_ip, notifier_)
5454
{
5555
node_->declare_parameter<double>("receive_timeout", 20);
56+
node_->declare_parameter<bool>("autoconnect", true);
5657

5758
param_callback_handle_ = node_->add_on_set_parameters_callback(
5859
std::bind(&DashboardClientROS::parametersCallback, this, std::placeholders::_1));
5960

61+
reconnect_service_ = node_->create_service<std_srvs::srv::Trigger>(
62+
"~/connect",
63+
[&](const std_srvs::srv::Trigger::Request::SharedPtr /*req*/, std_srvs::srv::Trigger::Response::SharedPtr resp) {
64+
try {
65+
resp->success = connect();
66+
} catch (const urcl::UrException& e) {
67+
RCLCPP_ERROR(rclcpp::get_logger("Dashboard_Client"), "Service Call failed: '%s'", e.what());
68+
resp->message = e.what();
69+
resp->success = false;
70+
}
71+
return true;
72+
});
73+
74+
if (node_->get_parameter("autoconnect").as_bool()) {
75+
while (!connect()) {
76+
RCLCPP_ERROR(node_->get_logger(),
77+
"Failed to connect to Dashboard Server at %s. Please check the IP address and ensure the robot is "
78+
"powered on and has the dashboard server enabled. Retrying in 5 seconds.",
79+
robot_ip_.c_str());
80+
if (rclcpp::ok()) {
81+
rclcpp::sleep_for(std::chrono::seconds(5));
82+
} else {
83+
RCLCPP_ERROR(node_->get_logger(), "ROS is shutting down, exiting now.");
84+
return;
85+
}
86+
}
87+
} else {
88+
RCLCPP_INFO(node_->get_logger(),
89+
"Dashboard client started with autoconnect disabled. Call the ~/connect service to connect to %s.",
90+
robot_ip_.c_str());
91+
}
92+
}
93+
94+
bool DashboardClientROS::connect()
95+
{
96+
if (client_) {
97+
timeval tv;
98+
double time_buffer = 0;
99+
node_->get_parameter("receive_timeout", time_buffer);
100+
tv.tv_sec = time_buffer;
101+
tv.tv_usec = (time_buffer - static_cast<int>(time_buffer)) * 1e6;
102+
client_->setReceiveTimeout(tv);
103+
return client_->connect();
104+
}
105+
60106
primary_client_.start(10, std::chrono::seconds(10));
61107
auto robot_version = primary_client_.getRobotVersion();
62108
primary_client_.stop();
@@ -73,25 +119,36 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
73119
dashboard_policy = urcl::DashboardClient::ClientPolicy::POLYSCOPE_X;
74120
}
75121

76-
RCLCPP_INFO(node_->get_logger(), "Connecting to Dashboard Server at %s with policy %s", robot_ip.c_str(),
122+
RCLCPP_INFO(node_->get_logger(), "Connecting to Dashboard Server at %s with policy %s", robot_ip_.c_str(),
77123
dashboard_policy == urcl::DashboardClient::ClientPolicy::G5 ? "G5" : "Polyscope X");
78-
client_ = std::make_unique<urcl::DashboardClient>(robot_ip, dashboard_policy);
79-
80-
while (!connect()) {
81-
RCLCPP_ERROR(node_->get_logger(),
82-
"Failed to connect to Dashboard Server at %s. Please check the IP address and ensure the robot is "
83-
"powered on and has the dashboard server enabled. Retrying in 5 seconds.",
84-
robot_ip.c_str());
85-
if (rclcpp::ok()) {
86-
rclcpp::sleep_for(std::chrono::seconds(5));
87-
} else {
88-
RCLCPP_ERROR(node_->get_logger(), "ROS is shutting down, exiting now.");
89-
return;
90-
}
124+
client_ = std::make_unique<urcl::DashboardClient>(robot_ip_, dashboard_policy);
125+
126+
timeval tv;
127+
// Timeout after which a call to the dashboard server will be considered failure if no answer has been received.
128+
double time_buffer = 0;
129+
node_->get_parameter("receive_timeout", time_buffer);
130+
tv.tv_sec = time_buffer;
131+
tv.tv_usec = (time_buffer - static_cast<int>(time_buffer)) * 1e6;
132+
bool connected = false;
133+
try {
134+
client_->setReceiveTimeout(tv);
135+
connected = client_->connect();
136+
} catch (const urcl::UrException& e) {
137+
RCLCPP_ERROR(rclcpp::get_logger("Dashboard_Client"), "Connect failed: '%s'", e.what());
91138
}
139+
if (!connected) {
140+
client_.reset();
141+
return false;
142+
}
143+
92144
RCLCPP_INFO(node_->get_logger(), "Successfully connected to Dashboard Server at %s. Robot has version %s",
93-
robot_ip.c_str(), robot_version->toString().c_str());
145+
robot_ip_.c_str(), robot_version->toString().c_str());
146+
initServices(dashboard_policy);
147+
return true;
148+
}
94149

150+
void DashboardClientROS::initServices(urcl::DashboardClient::ClientPolicy dashboard_policy)
151+
{
95152
// Service to release the brakes. If the robot is currently powered off, it will get powered on on the fly.
96153
brake_release_service_ = createDashboardTriggerSrv(
97154
"~/brake_release", std::bind(&urcl::DashboardClient::commandBrakeReleaseWithResponse, client_.get()));
@@ -183,7 +240,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
183240
});
184241

185242
// Load a robot program from a file
186-
load_program_service_ = node->create_service<ur_dashboard_msgs::srv::Load>(
243+
load_program_service_ = node_->create_service<ur_dashboard_msgs::srv::Load>(
187244
"~/load_program", [&](const ur_dashboard_msgs::srv::Load::Request::SharedPtr req,
188245
ur_dashboard_msgs::srv::Load::Response::SharedPtr resp) {
189246
auto dashboard_response = dashboardCallWithChecks(
@@ -231,7 +288,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
231288
});
232289

233290
// Service to get serial number of the robot
234-
get_serial_number_service_ = node->create_service<ur_dashboard_msgs::srv::GetSerialNumber>(
291+
get_serial_number_service_ = node_->create_service<ur_dashboard_msgs::srv::GetSerialNumber>(
235292
"~/get_serial_number", [&](const ur_dashboard_msgs::srv::GetSerialNumber::Request::SharedPtr /*unused*/,
236293
ur_dashboard_msgs::srv::GetSerialNumber::Response::SharedPtr resp) {
237294
auto dashboard_response =
@@ -258,14 +315,14 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
258315
std::bind(&DashboardClientROS::handleRobotModeQuery, this, std::placeholders::_1, std::placeholders::_2));
259316

260317
// Service to add a message to the robot's log
261-
add_to_log_service_ = node->create_service<ur_dashboard_msgs::srv::AddToLog>(
318+
add_to_log_service_ = node_->create_service<ur_dashboard_msgs::srv::AddToLog>(
262319
"~/add_to_log", [&](const ur_dashboard_msgs::srv::AddToLog::Request::SharedPtr req,
263320
ur_dashboard_msgs::srv::AddToLog::Response::SharedPtr resp) {
264321
dashboardCallWithChecks([this, req]() { return client_->commandAddToLogWithResponse(req->message); }, resp);
265322
return true;
266323
});
267324
// Service to get the polyscope version running on the robot
268-
get_polyscope_version_service_ = node->create_service<ur_dashboard_msgs::srv::GetPolyScopeVersion>(
325+
get_polyscope_version_service_ = node_->create_service<ur_dashboard_msgs::srv::GetPolyScopeVersion>(
269326
"~/get_polyscope_version", std::bind(&DashboardClientROS::handleGetPolyScopeVersionQuery, this,
270327
std::placeholders::_1, std::placeholders::_2));
271328

@@ -282,20 +339,6 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
282339
return true;
283340
});
284341

285-
// Service to reconnect to the dashboard server
286-
reconnect_service_ = node_->create_service<std_srvs::srv::Trigger>(
287-
"~/connect",
288-
[&](const std_srvs::srv::Trigger::Request::SharedPtr req, std_srvs::srv::Trigger::Response::SharedPtr resp) {
289-
try {
290-
resp->success = connect();
291-
} catch (const urcl::UrException& e) {
292-
RCLCPP_ERROR(rclcpp::get_logger("Dashboard_Client"), "Service Call failed: '%s'", e.what());
293-
resp->message = e.what();
294-
resp->success = false;
295-
}
296-
return true;
297-
});
298-
299342
// Disconnect from the dashboard service.
300343
quit_service_ =
301344
createDashboardTriggerSrv("~/quit", std::bind(&urcl::DashboardClient::commandQuitWithResponse, client_.get()));
@@ -370,7 +413,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
370413
});
371414

372415
// Service to set the user role on the robot (Only valid for CB3 robots)
373-
set_user_role_service_ = node->create_service<ur_dashboard_msgs::srv::SetUserRole>(
416+
set_user_role_service_ = node_->create_service<ur_dashboard_msgs::srv::SetUserRole>(
374417
"~/set_user_role", [&](const ur_dashboard_msgs::srv::SetUserRole::Request::SharedPtr req,
375418
ur_dashboard_msgs::srv::SetUserRole::Response::SharedPtr resp) {
376419
dashboardCallWithChecks([this, req]() { return client_->commandSetUserRoleWithResponse(req->user_role.role); },
@@ -379,7 +422,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
379422
});
380423

381424
// Service to get the current user role (Only valid for CB3)
382-
get_user_role_service_ = node->create_service<ur_dashboard_msgs::srv::GetUserRole>(
425+
get_user_role_service_ = node_->create_service<ur_dashboard_msgs::srv::GetUserRole>(
383426
"~/get_user_role", [&](const ur_dashboard_msgs::srv::GetUserRole::Request::SharedPtr /*unused*/,
384427
ur_dashboard_msgs::srv::GetUserRole::Response::SharedPtr resp) {
385428
auto dashboard_response =
@@ -395,7 +438,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
395438
});
396439

397440
// Service to set the operational mode. (e-series only)
398-
set_operational_mode_service_ = node->create_service<ur_dashboard_msgs::srv::SetOperationalMode>(
441+
set_operational_mode_service_ = node_->create_service<ur_dashboard_msgs::srv::SetOperationalMode>(
399442
"~/set_operational_mode", [&](const ur_dashboard_msgs::srv::SetOperationalMode::Request::SharedPtr req,
400443
ur_dashboard_msgs::srv::SetOperationalMode::Response::SharedPtr resp) {
401444
dashboardCallWithChecks(
@@ -404,7 +447,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
404447
});
405448

406449
// Service to get the current operational mode (e-series only)
407-
get_operational_mode_service_ = node->create_service<ur_dashboard_msgs::srv::GetOperationalMode>(
450+
get_operational_mode_service_ = node_->create_service<ur_dashboard_msgs::srv::GetOperationalMode>(
408451
"~/get_operational_mode", [&](const ur_dashboard_msgs::srv::GetOperationalMode::Request::SharedPtr /*unused*/,
409452
ur_dashboard_msgs::srv::GetOperationalMode::Response::SharedPtr resp) {
410453
auto dashboard_response =
@@ -420,7 +463,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
420463
});
421464

422465
// Service to get the robot model as a string
423-
get_robot_model_service_ = node->create_service<ur_dashboard_msgs::srv::GetRobotModel>(
466+
get_robot_model_service_ = node_->create_service<ur_dashboard_msgs::srv::GetRobotModel>(
424467
"~/get_robot_model", [&](const ur_dashboard_msgs::srv::GetRobotModel::Request::SharedPtr /*unused*/,
425468
ur_dashboard_msgs::srv::GetRobotModel::Response::SharedPtr resp) {
426469
auto dashboard_response =
@@ -436,12 +479,12 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
436479
});
437480

438481
// Service to get robot safety status as a string
439-
get_safety_status_service_ = node->create_service<ur_dashboard_msgs::srv::GetSafetyStatus>(
482+
get_safety_status_service_ = node_->create_service<ur_dashboard_msgs::srv::GetSafetyStatus>(
440483
"~/get_safety_status",
441484
std::bind(&DashboardClientROS::handleSafetyStatusQuery, this, std::placeholders::_1, std::placeholders::_2));
442485

443486
// Service to generate flight report, defaults to system type
444-
generate_flight_report_service_ = node->create_service<ur_dashboard_msgs::srv::GenerateFlightReport>(
487+
generate_flight_report_service_ = node_->create_service<ur_dashboard_msgs::srv::GenerateFlightReport>(
445488
"~/generate_flight_report", [&](const ur_dashboard_msgs::srv::GenerateFlightReport::Request::SharedPtr req,
446489
ur_dashboard_msgs::srv::GenerateFlightReport::Response::SharedPtr resp) {
447490
auto dashboard_response = dashboardCallWithChecks(
@@ -462,7 +505,7 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
462505
});
463506

464507
// Service to generate support file, defaults to saving the file in /programs
465-
generate_support_file_service_ = node->create_service<ur_dashboard_msgs::srv::GenerateSupportFile>(
508+
generate_support_file_service_ = node_->create_service<ur_dashboard_msgs::srv::GenerateSupportFile>(
466509
"~/generate_support_file", [&](const ur_dashboard_msgs::srv::GenerateSupportFile::Request::SharedPtr req,
467510
ur_dashboard_msgs::srv::GenerateSupportFile::Response::SharedPtr resp) {
468511
auto dashboard_response = dashboardCallWithChecks(
@@ -483,18 +526,6 @@ DashboardClientROS::DashboardClientROS(const rclcpp::Node::SharedPtr& node, cons
483526
});
484527
}
485528

486-
bool DashboardClientROS::connect()
487-
{
488-
timeval tv;
489-
// Timeout after which a call to the dashboard server will be considered failure if no answer has been received.
490-
double time_buffer = 0;
491-
node_->get_parameter("receive_timeout", time_buffer);
492-
tv.tv_sec = time_buffer;
493-
tv.tv_usec = (time_buffer - static_cast<int>(time_buffer)) * 1e6;
494-
client_->setReceiveTimeout(tv);
495-
return client_->connect();
496-
}
497-
498529
bool DashboardClientROS::handleRunningQuery(const ur_dashboard_msgs::srv::IsProgramRunning::Request::SharedPtr req,
499530
ur_dashboard_msgs::srv::IsProgramRunning::Response::SharedPtr resp)
500531
{
@@ -720,12 +751,14 @@ DashboardClientROS::parametersCallback(const std::vector<rclcpp::Parameter>& par
720751

721752
for (const auto& parameter : parameters) {
722753
if (parameter.get_name() == "receive_timeout") {
723-
timeval tv;
724-
double time_buffer = parameter.as_double();
725-
tv.tv_sec = time_buffer;
726-
tv.tv_usec = (time_buffer - static_cast<int>(time_buffer)) * 1e6;
727-
client_->setReceiveTimeout(tv);
728-
RCLCPP_INFO(node_->get_logger(), "Set receive_timeout to %f seconds", time_buffer);
754+
if (client_) {
755+
timeval tv;
756+
double time_buffer = parameter.as_double();
757+
tv.tv_sec = time_buffer;
758+
tv.tv_usec = (time_buffer - static_cast<int>(time_buffer)) * 1e6;
759+
client_->setReceiveTimeout(tv);
760+
}
761+
RCLCPP_INFO(node_->get_logger(), "Set receive_timeout to %f seconds", parameter.as_double());
729762
} else {
730763
result.successful = false;
731764
result.reason = "Requested to change parameter '" + parameter.get_name() +

ur_robot_driver/test/dashboard_client.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,25 +40,32 @@
4040
sys.path.append(os.path.dirname(__file__))
4141
from test_common import ( # noqa: E402
4242
DashboardInterface,
43+
connect_dashboard_client,
4344
generate_dashboard_test_description,
4445
)
4546

4647

4748
@pytest.mark.launch_test
4849
@launch_testing.parametrize(
49-
"ursim_version, ur_type",
50-
[("latest", "ur30"), ("10.12.0", "ur15"), ("3.15.8", "ur10")],
50+
"ursim_version, ur_type, autoconnect",
51+
[
52+
("latest", "ur30", "false"),
53+
("10.12.0", "ur15", "true"),
54+
("3.15.8", "ur10", "true"),
55+
],
5156
)
52-
def generate_test_description(ursim_version, ur_type):
53-
return generate_dashboard_test_description(ursim_version, ur_type)
57+
def generate_test_description(ursim_version, ur_type, autoconnect):
58+
return generate_dashboard_test_description(ursim_version, ur_type, autoconnect)
5459

5560

5661
class DashboardClientTest(unittest.TestCase):
5762
@classmethod
58-
def setUpClass(cls, ursim_version):
63+
def setUpClass(cls, ursim_version, autoconnect):
5964
# Initialize the ROS context
6065
rclpy.init()
6166
cls.node = Node("dashboard_client_test")
67+
if autoconnect == "false":
68+
connect_dashboard_client(cls.node)
6269
cls.init_robot(cls, ursim_version)
6370

6471
@classmethod

0 commit comments

Comments
 (0)