diff --git a/include/ur_client_library/example_robot_wrapper.h b/include/ur_client_library/example_robot_wrapper.h index 1a24fe466..2053130d7 100644 --- a/include/ur_client_library/example_robot_wrapper.h +++ b/include/ur_client_library/example_robot_wrapper.h @@ -85,6 +85,8 @@ class ExampleRobotWrapper */ bool initializeRobotWithDashboard(); + bool initializeRobotWithPrimaryClient(); + /** * @brief Starts RTDE communication with the robot. * @@ -167,12 +169,20 @@ class ExampleRobotWrapper bool isHealthy() const; - std::shared_ptr dashboard_client_; /*!< Dashboard client to interact with the robot */ - std::shared_ptr ur_driver_; /*!< UR driver to interact with the robot */ + //! Dashboard client to interact with the robot + std::shared_ptr dashboard_client_; + + //! primary client to interact with the robot + std::shared_ptr primary_client_; + + //! UR driver to interact with the robot + std::shared_ptr ur_driver_; private: void handleRobotProgramState(bool program_running); + comm::INotifier notifier_; + std::atomic rtde_communication_started_ = false; std::atomic consume_rtde_packages_ = false; std::mutex read_package_mutex_; diff --git a/include/ur_client_library/primary/primary_client.h b/include/ur_client_library/primary/primary_client.h index 92322f598..b292dee88 100644 --- a/include/ur_client_library/primary/primary_client.h +++ b/include/ur_client_library/primary/primary_client.h @@ -171,6 +171,23 @@ class PrimaryClient return static_cast(consumer_->getRobotModeData()->robot_mode_); } + /*! + * \brief Get the robot's software version as Major.Minor.Bugfix + * + * This function by default blocks until a VersionMessage has been received and returns that + * version information. If there is an older version message that has been received, this is + * returned directly. + * + * \param blocking If true, the function will block until there is a valid version information + * received by the client or the timeout passed by. + * \param timeout The maximum time to wait for a valid version message. + * + * \throws urcl::TimeoutException if no message was received until the given timeout passed by. + * + */ + std::shared_ptr + getRobotVersion(bool wait_for_message = true, const std::chrono::milliseconds timeout = std::chrono::seconds(2)); + /*! * \brief Get the latest robot mode data. * diff --git a/include/ur_client_library/primary/primary_consumer.h b/include/ur_client_library/primary/primary_consumer.h index 44ccd5f3e..65445230c 100644 --- a/include/ur_client_library/primary/primary_consumer.h +++ b/include/ur_client_library/primary/primary_consumer.h @@ -31,6 +31,7 @@ #include "ur_client_library/primary/abstract_primary_consumer.h" #include "ur_client_library/primary/robot_state/robot_mode_data.h" #include "ur_client_library/ur/datatypes.h" +#include "ur_client_library/ur/version_information.h" #include #include @@ -87,6 +88,12 @@ class PrimaryConsumer : public AbstractPrimaryConsumer */ virtual bool consume(VersionMessage& pkg) override { + std::scoped_lock lock(version_information_mutex_); + version_information_ = std::make_shared(); + version_information_->major = pkg.major_version_; + version_information_->minor = pkg.minor_version_; + version_information_->bugfix = pkg.svn_version_; + version_information_->build = pkg.build_number_; return true; } @@ -196,11 +203,26 @@ class PrimaryConsumer : public AbstractPrimaryConsumer return robot_mode_; } + /*! + * \brief Get the latest version information. + * + * The version information will be updated in the background. This will always show the latest + * received version information independent of the time that has passed since receiving it. If no + * version information has been received yet, this will return a nullptr. + */ + std::shared_ptr getVersionInformation() + { + std::scoped_lock lock(version_information_mutex_); + return version_information_; + } + private: std::function error_code_message_callback_; std::shared_ptr kinematics_info_; std::mutex robot_mode_mutex_; std::shared_ptr robot_mode_; + std::mutex version_information_mutex_; + std::shared_ptr version_information_; }; } // namespace primary_interface diff --git a/include/ur_client_library/ur/ur_driver.h b/include/ur_client_library/ur/ur_driver.h index 3d06561fa..04ec58c28 100644 --- a/include/ur_client_library/ur/ur_driver.h +++ b/include/ur_client_library/ur/ur_driver.h @@ -890,6 +890,11 @@ class UrDriver return trajectory_interface_->isConnected(); } + std::shared_ptr getPrimaryClient() + { + return primary_client_; + } + private: void init(const UrDriverConfiguration& config); @@ -898,7 +903,7 @@ class UrDriver comm::INotifier notifier_; std::unique_ptr rtde_client_; - std::unique_ptr primary_client_; + std::shared_ptr primary_client_; std::unique_ptr reverse_interface_; std::unique_ptr trajectory_interface_; std::unique_ptr script_command_interface_; diff --git a/src/example_robot_wrapper.cpp b/src/example_robot_wrapper.cpp index 8f405c5d1..34353d201 100644 --- a/src/example_robot_wrapper.cpp +++ b/src/example_robot_wrapper.cpp @@ -32,6 +32,7 @@ #include #include "ur_client_library/exceptions.h" #include "ur_client_library/log.h" +#include "ur_client_library/ur/version_information.h" namespace urcl { @@ -40,24 +41,31 @@ ExampleRobotWrapper::ExampleRobotWrapper(const std::string& robot_ip, const std: const std::string& autostart_program, const std::string& script_file) : headless_mode_(headless_mode), autostart_program_(autostart_program) { - dashboard_client_ = std::make_shared(robot_ip); + primary_client_ = std::make_shared(robot_ip, notifier_); - // Connect the robot Dashboard - if (!dashboard_client_->connect()) + primary_client_->start(); + + auto robot_version = primary_client_->getRobotVersion(); + if (*robot_version < VersionInformation::fromString("10.0.0")) { - URCL_LOG_ERROR("Could not connect to dashboard"); - } + dashboard_client_ = std::make_shared(robot_ip); + // Connect the robot Dashboard + if (!dashboard_client_->connect()) + { + URCL_LOG_ERROR("Could not connect to dashboard"); + } - // In CI we the dashboard client times out for no obvious reason. Hence we increase the timeout - // here. - timeval tv; - tv.tv_sec = 10; - tv.tv_usec = 0; - dashboard_client_->setReceiveTimeout(tv); + // In CI we the dashboard client times out for no obvious reason. Hence we increase the timeout + // here. + timeval tv; + tv.tv_sec = 10; + tv.tv_usec = 0; + dashboard_client_->setReceiveTimeout(tv); + } - if (!initializeRobotWithDashboard()) + if (!initializeRobotWithPrimaryClient()) { - throw UrException("Could not initialize robot with dashboard"); + throw UrException("Could not initialize robot with primary client"); } UrDriverConfiguration driver_config; @@ -75,7 +83,7 @@ ExampleRobotWrapper::ExampleRobotWrapper(const std::string& robot_ip, const std: startRobotProgram(autostart_program); } - if (headless_mode | !std::empty(autostart_program)) + if (headless_mode || !std::empty(autostart_program)) { if (!waitForProgramRunning(500)) { @@ -94,20 +102,28 @@ ExampleRobotWrapper::~ExampleRobotWrapper() bool ExampleRobotWrapper::clearProtectiveStop() { - std::string safety_status; - dashboard_client_->commandSafetyStatus(safety_status); - bool is_protective_stopped = safety_status.find("PROTECTIVE_STOP") != std::string::npos; - if (is_protective_stopped) + if (primary_client_->isRobotProtectiveStopped()) { URCL_LOG_INFO("Robot is in protective stop, trying to release it"); - dashboard_client_->commandClosePopup(); - dashboard_client_->commandCloseSafetyPopup(); - if (!dashboard_client_->commandUnlockProtectiveStop()) + if (dashboard_client_ != nullptr) + { + dashboard_client_->commandClosePopup(); + dashboard_client_->commandCloseSafetyPopup(); + } + try + { + primary_client_->commandUnlockProtectiveStop(); + } + catch (const TimeoutException&) { std::this_thread::sleep_for(std::chrono::seconds(5)); - if (!dashboard_client_->commandUnlockProtectiveStop()) + try { - URCL_LOG_ERROR("Could not unlock protective stop"); + primary_client_->commandUnlockProtectiveStop(); + } + catch (const TimeoutException&) + { + URCL_LOG_ERROR("Robot could not unlock the protective stop"); return false; } } @@ -157,6 +173,36 @@ bool ExampleRobotWrapper::initializeRobotWithDashboard() return true; } +bool ExampleRobotWrapper::initializeRobotWithPrimaryClient() +{ + try + { + waitFor([&]() { return primary_client_->getRobotModeData() != nullptr; }, std::chrono::seconds(5)); + clearProtectiveStop(); + } + catch (const std::exception& exc) + { + URCL_LOG_ERROR("Could not clear protective stop (%s)", exc.what()); + return false; + } + + try + { + primary_client_->commandStop(); + primary_client_->commandBrakeRelease(); + } + catch (const TimeoutException& exc) + { + URCL_LOG_ERROR(exc.what()); + return false; + } + + // Now the robot is ready to receive a program + URCL_LOG_INFO("Robot ready to start a program"); + robot_initialized_ = true; + return true; +} + void ExampleRobotWrapper::handleRobotProgramState(bool program_running) { // Print the text in green so we see it better @@ -254,13 +300,20 @@ bool ExampleRobotWrapper::waitForProgramNotRunning(int milliseconds) bool ExampleRobotWrapper::startRobotProgram(const std::string& program_file_name) { - if (!dashboard_client_->commandLoadProgram(program_file_name)) + if (dashboard_client_ != nullptr) { - URCL_LOG_ERROR("Could not load program '%s'", program_file_name.c_str()); - return false; - } + if (!dashboard_client_->commandLoadProgram(program_file_name)) + { + URCL_LOG_ERROR("Could not load program '%s'", program_file_name.c_str()); + return false; + } - return dashboard_client_->commandPlay(); + return dashboard_client_->commandPlay(); + } + URCL_LOG_ERROR("Dashboard client is not initialized. If you are running a PolyScope X robot, the dashboard server is " + "not available. Loading and running polyscope programs isn't possible. Please use the headless mode " + "or the teach pendant instead."); + return false; } bool ExampleRobotWrapper::resendRobotProgram() { diff --git a/src/primary/primary_client.cpp b/src/primary/primary_client.cpp index ae034872e..9671eabf5 100644 --- a/src/primary/primary_client.cpp +++ b/src/primary/primary_client.cpp @@ -33,6 +33,7 @@ #include #include "ur_client_library/exceptions.h" #include +#include namespace urcl { namespace primary_interface @@ -267,6 +268,16 @@ void PrimaryClient::commandStop(const bool validate, const std::chrono::millisec } } } +std::shared_ptr PrimaryClient::getRobotVersion(bool blocking, + const std::chrono::milliseconds timeout) +{ + if (blocking) + { + waitFor([this]() { return consumer_->getVersionInformation() != nullptr; }, timeout); + } + + return consumer_->getVersionInformation(); +} } // namespace primary_interface } // namespace urcl diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp index edc35d716..ef76ce86b 100644 --- a/tests/test_dashboard_client.cpp +++ b/tests/test_dashboard_client.cpp @@ -32,6 +32,8 @@ #include #include #include +#include "gtest/gtest.h" +#include "test_utils.h" #include "ur_client_library/comm/tcp_socket.h" #include "ur_client_library/ur/version_information.h" #include @@ -61,6 +63,12 @@ class DashboardClientTest : public ::testing::Test protected: void SetUp() { + if (!robotVersionLessThan(g_ROBOT_IP, "10.0.0")) + { + GTEST_SKIP_("Running DashboardClient tests for PolyScope X is not supported as it doesn't have a dashboard " + "server."); + } + dashboard_client_.reset(new TestableDashboardClient(g_ROBOT_IP)); // In CI we the dashboard client times out for no obvious reason. Hence we increase the timeout // here. @@ -287,4 +295,4 @@ int main(int argc, char* argv[]) } return RUN_ALL_TESTS(); -} \ No newline at end of file +} diff --git a/tests/test_instruction_executor.cpp b/tests/test_instruction_executor.cpp index d8357ae37..225cffe32 100644 --- a/tests/test_instruction_executor.cpp +++ b/tests/test_instruction_executor.cpp @@ -35,6 +35,8 @@ #include "ur_client_library/ur/instruction_executor.h" #include "ur_client_library/control/motion_primitives.h" +#include "test_utils.h" + #include using namespace urcl; @@ -56,6 +58,10 @@ class InstructionExecutorTest : public ::testing::Test static void SetUpTestSuite() { + if (!(robotVersionLessThan(ROBOT_IP, "10.0.0") || g_HEADLESS)) + { + GTEST_SKIP_("Running URCap tests for PolyScope X is currently not supported."); + } // Setup driver g_my_robot = std::make_unique(ROBOT_IP, OUTPUT_RECIPE, INPUT_RECIPE, g_HEADLESS, "external_control.urp", SCRIPT_FILE); @@ -185,7 +191,7 @@ TEST_F(InstructionExecutorTest, execute_movel_success) TEST_F(InstructionExecutorTest, sending_commands_without_reverse_interface_connected_fails) { - g_my_robot->dashboard_client_->commandStop(); + g_my_robot->primary_client_->commandStop(); ASSERT_TRUE(g_my_robot->waitForProgramNotRunning(1000)); ASSERT_FALSE(executor_->moveJ({ -1.57, -1.6, 1.6, -0.7, 0.7, 0.2 })); @@ -213,7 +219,7 @@ TEST_F(InstructionExecutorTest, sending_commands_without_reverse_interface_conne auto motion_thread = std::thread([&]() { ASSERT_FALSE(executor_->moveJ({ -1.57, -1.6, 1.6, -0.7, 0.7, 0.2 })); }); std::this_thread::sleep_for(std::chrono::milliseconds(300)); - g_my_robot->dashboard_client_->commandStop(); + g_my_robot->primary_client_->commandStop(); ASSERT_TRUE(g_my_robot->waitForProgramNotRunning(1000)); motion_thread.join(); } @@ -243,6 +249,11 @@ TEST_F(InstructionExecutorTest, canceling_without_running_trajectory_returns_fal TEST(InstructionExecutorTestStandalone, canceling_without_receiving_answer_returns_false) { + if (!(robotVersionLessThan(ROBOT_IP, "10.0.0") || g_HEADLESS)) + { + GTEST_SKIP_("Running URCap tests for PolyScope X is currently not supported."); + } + std::ifstream in_file(SCRIPT_FILE); std::ofstream out_file; const std::string test_script_file = "test_script.urscript"; @@ -311,13 +322,12 @@ TEST_F(InstructionExecutorTest, unfeasible_movel_target_results_in_failure) std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now(); while (!is_protective_stopped || std::chrono::steady_clock::now() > start + std::chrono::seconds(5)) { - g_my_robot->dashboard_client_->commandSafetyStatus(safety_status); - is_protective_stopped = safety_status.find("PROTECTIVE_STOP") != std::string::npos; + is_protective_stopped = g_my_robot->primary_client_->isRobotProtectiveStopped(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } ASSERT_TRUE(is_protective_stopped); ASSERT_TRUE(g_my_robot->clearProtectiveStop()); - ASSERT_TRUE(g_my_robot->dashboard_client_->commandStop()); + ASSERT_NO_THROW(g_my_robot->primary_client_->commandStop()); move_thread.join(); } diff --git a/tests/test_spline_interpolation.cpp b/tests/test_spline_interpolation.cpp index 6a389491e..284da3f69 100644 --- a/tests/test_spline_interpolation.cpp +++ b/tests/test_spline_interpolation.cpp @@ -32,7 +32,6 @@ #include #include -#include #include #include @@ -43,6 +42,7 @@ #include #include #include +#include "test_utils.h" using namespace urcl; @@ -108,6 +108,11 @@ class SplineInterpolationTest : public ::testing::Test out_file << prog; out_file.close(); + if (!(robotVersionLessThan(g_ROBOT_IP, "10.0.0") || g_HEADLESS)) + { + GTEST_SKIP_("Running URCap tests for PolyScope X is currently not supported."); + } + // Setup driver g_my_robot = std::make_unique(g_ROBOT_IP, OUTPUT_RECIPE, INPUT_RECIPE, g_HEADLESS, "external_control.urp", SPLINE_SCRIPT_FILE); @@ -119,8 +124,11 @@ class SplineInterpolationTest : public ::testing::Test static void TearDownTestSuite() { - // Set target speed scaling to 100% as one test change this value - g_my_robot->ur_driver_->getRTDEWriter().sendSpeedSlider(1); + if (g_my_robot != nullptr) + { + // Set target speed scaling to 100% as one test change this value + g_my_robot->ur_driver_->getRTDEWriter().sendSpeedSlider(1); + } // Remove temporary file again std::remove(SPLINE_SCRIPT_FILE.c_str()); @@ -139,16 +147,17 @@ class SplineInterpolationTest : public ::testing::Test { step_time_ = 0.008; } - std::string safety_status; - g_my_robot->dashboard_client_->commandSafetyStatus(safety_status); - bool is_protective_stopped = safety_status.find("PROTECTIVE_STOP") != std::string::npos; - if (is_protective_stopped) + + if (g_my_robot->primary_client_->isRobotProtectiveStopped()) { // We forced a protective stop above. Some versions require waiting 5 seconds before releasing // the protective stop. + if (g_my_robot->dashboard_client_ != nullptr) + { + g_my_robot->dashboard_client_->commandClosePopup(); + } std::this_thread::sleep_for(std::chrono::seconds(5)); - g_my_robot->dashboard_client_->commandCloseSafetyPopup(); - ASSERT_TRUE(g_my_robot->dashboard_client_->commandUnlockProtectiveStop()); + ASSERT_NO_THROW(g_my_robot->primary_client_->commandUnlockProtectiveStop()); } ASSERT_TRUE(g_my_robot->isHealthy()); } @@ -1115,7 +1124,7 @@ TEST_F(SplineInterpolationTest, switching_control_mode_with_trajectory_produces_ std::unique_ptr data_pkg; g_my_robot->readDataPackage(data_pkg); - urcl::vector6d_t joint_positions_before; + urcl::vector6d_t joint_positions_before{ 0, 0, 0, 0, 0, 0 }; ASSERT_TRUE(data_pkg->getData("target_q", joint_positions_before)); // Start consuming rtde packages to avoid pipeline overflows while testing that the control script aborts correctly diff --git a/tests/test_ur_driver.cpp b/tests/test_ur_driver.cpp index dd07ce3ad..03a76015d 100644 --- a/tests/test_ur_driver.cpp +++ b/tests/test_ur_driver.cpp @@ -33,6 +33,7 @@ #include #include #include +#include "test_utils.h" using namespace urcl; @@ -50,6 +51,10 @@ class UrDriverTest : public ::testing::Test protected: static void SetUpTestSuite() { + if (!(robotVersionLessThan(g_ROBOT_IP, "10.0.0") || g_HEADLESS)) + { + GTEST_SKIP_("Running URCap tests for PolyScope X is currently not supported."); + } // Setup driver g_my_robot = std::make_unique(g_ROBOT_IP, OUTPUT_RECIPE, INPUT_RECIPE, g_HEADLESS, "external_control.urp", SCRIPT_FILE); @@ -279,8 +284,11 @@ TEST_F(UrDriverTest, read_error_code) // Wait for after PSTOP before clearing it std::this_thread::sleep_for(std::chrono::seconds(6)); - EXPECT_TRUE(g_my_robot->dashboard_client_->commandCloseSafetyPopup()); - EXPECT_TRUE(g_my_robot->dashboard_client_->commandUnlockProtectiveStop()); + if (g_my_robot->dashboard_client_ != nullptr) + { + EXPECT_TRUE(g_my_robot->dashboard_client_->commandCloseSafetyPopup()); + } + EXPECT_NO_THROW(g_my_robot->primary_client_->commandUnlockProtectiveStop()); } TEST(UrDriverInitTest, setting_connection_limits_works_correctly) diff --git a/tests/test_utils.h b/tests/test_utils.h new file mode 100644 index 000000000..7db5ca04d --- /dev/null +++ b/tests/test_utils.h @@ -0,0 +1,42 @@ +// -- BEGIN LICENSE BLOCK ---------------------------------------------- +// Copyright © 2025 Universal Robots A/S +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the {copyright_holder} nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// -- END LICENSE BLOCK ------------------------------------------------ + +#pragma once + +#include + +bool robotVersionLessThan(const std::string& robot_ip, const std::string& robot_version) +{ + urcl::comm::INotifier notifier; + urcl::primary_interface::PrimaryClient primary_client(robot_ip, notifier); + primary_client.start(); + auto version_information = primary_client.getRobotVersion(); + return *version_information < urcl::VersionInformation::fromString(robot_version); +}