From ff1ef391a244807d81bd9d2e624edb41a24015cb Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 15 May 2026 10:51:48 +0200 Subject: [PATCH 01/46] Implement assertHasCommand for DashboardClientImplX --- .../ur/dashboard_client_implementation_x.h | 10 ++ src/ur/dashboard_client_implementation_x.cpp | 99 ++++++++++--------- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client_implementation_x.h b/include/ur_client_library/ur/dashboard_client_implementation_x.h index b257e1726..ea6a38c09 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_x.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_x.h @@ -42,6 +42,13 @@ class Result; namespace urcl { +struct RobotAPICommand +{ + std::string endpoint; + VersionInformation robotAPIVersion; + VersionInformation marketingVersion; +}; + class DashboardClientImplX : public DashboardClientImpl { public: @@ -186,8 +193,11 @@ class DashboardClientImplX : public DashboardClientImpl std::unique_ptr cli_; VersionInformation robot_api_version_; + timeval recv_timeout_ = { 10, 0 }; timeval send_timeout_ = { 10, 0 }; + + static std::unordered_map g_command_list; }; } // namespace urcl diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index d728d91d5..066064362 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -43,6 +43,37 @@ using namespace std::chrono_literals; namespace urcl { +std::unordered_map DashboardClientImplX::g_command_list = { + { "get_loaded_program", + { "/program/v1/loaded", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") } }, + { "get_program_list", + { "/programs/v1", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") } }, + { "upload_program", + { "/programs/v1", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") } }, + { "update_program", + { "/programs/v1", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") } }, + { "download_program", + { "/programs/v1/", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") } }, + { "robot_mode", + { "/robotstate/v1/robotmode", VersionInformation::fromString("3.1.4"), + VersionInformation::fromString("10.12.0") } }, + { "safety_mode", + { "/robotstate/v1/safetymode", VersionInformation::fromString("3.1.4"), + VersionInformation::fromString("10.12.0") } }, + { "get_operational_mode", + { "/system/v1/operationalmode", VersionInformation::fromString("3.1.4"), + VersionInformation::fromString("10.12.0") } }, + { "popup", { "/popup/v1", VersionInformation::fromString("3.3.3"), VersionInformation::fromString("10.14.0") } }, + { "close_popup", + { "/popup/v1", VersionInformation::fromString("3.3.3"), VersionInformation::fromString("10.14.0") } }, + { "close_safety_popup", + { "/popup/v1/safety", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { + "is_in_remote_control", + { "/system/v1/controlmode", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") }, + } +}; + DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) { cli_ = std::make_unique("http://" + host); @@ -137,11 +168,17 @@ VersionInformation DashboardClientImplX::queryPolyScopeVersion() throw NotImplementedException("queryPolyScopeVersion is not implemented for DashboardClientImplX."); } -void DashboardClientImplX::assertHasCommand([[maybe_unused]] const std::string& command) const +void DashboardClientImplX::assertHasCommand(const std::string& command) const { - // Currently, there is only one set of implemented commands. Once the first software release has - // been made with a Dashboard Server, following versions will support more commands, which is - // when we might have to deal with that here. + if (robot_api_version_ < g_command_list.at(command).robotAPIVersion) + { + std::stringstream ss; + ss << "The command '" << command << "' requires Robot API version " << g_command_list.at(command).robotAPIVersion + << " or higher. The connected robot has Robot API version " << robot_api_version_ + << ". Please upgrade the robot to PolyScope " << g_command_list.at(command).marketingVersion + << " or higher to use this command."; + throw NotImplementedException(ss.str()); + } } bool DashboardClientImplX::sendRequest([[maybe_unused]] const std::string& command_str, @@ -297,11 +334,7 @@ DashboardResponse DashboardClientImplX::commandIsProgramSaved() DashboardResponse DashboardClientImplX::commandIsInRemoteControl() { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandIsInRemoteControl is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("is_in_remote_control"); auto response = get("/system/v1/controlmode"); auto json_data = json::parse(response.message); if (response.ok) @@ -346,11 +379,7 @@ DashboardResponse DashboardClientImplX::commandGetSerialNumber() DashboardResponse DashboardClientImplX::commandRobotMode() { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandRobotMode is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("robot_mode"); auto response = get("/robotstate/v1/robotmode"); auto json_data = json::parse(response.message); if (response.ok) @@ -362,11 +391,7 @@ DashboardResponse DashboardClientImplX::commandRobotMode() DashboardResponse DashboardClientImplX::commandGetLoadedProgram() { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandGetLoadedProgram is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("get_loaded_program"); auto response = get("/program/v1/loaded"); auto json_data = json::parse(response.message); if (response.ok) @@ -378,11 +403,7 @@ DashboardResponse DashboardClientImplX::commandGetLoadedProgram() DashboardResponse DashboardClientImplX::commandSafetyMode() { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandSafetyMode is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("safety_mode"); auto response = get("/robotstate/v1/safetymode"); auto json_data = json::parse(response.message); if (response.ok) @@ -410,11 +431,7 @@ DashboardResponse DashboardClientImplX::commandProgramState() DashboardResponse DashboardClientImplX::commandGetOperationalMode() { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandGetOperationalMode is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("get_operational_mode"); auto response = get("/system/v1/operationalmode"); auto json_data = json::parse(response.message); if (response.ok) @@ -461,11 +478,7 @@ DashboardResponse DashboardClientImplX::commandSaveLog() DashboardResponse DashboardClientImplX::commandGetProgramList() { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandGetProgramList is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("get_program_list"); auto response = get("/programs/v1/"); auto json_data = json::parse(response.message); if (response.ok) @@ -526,11 +539,7 @@ DashboardResponse DashboardClientImplX::performProgramUpload( DashboardResponse DashboardClientImplX::commandUploadProgram(const std::string& file_path) { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandUploadProgram is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("upload_program"); URCL_LOG_INFO("Uploading program from file: %s", file_path.c_str()); return performProgramUpload( file_path, [this](const std::string& e, const httplib::UploadFormDataItems& f) { return post(e, f, true); }); @@ -538,11 +547,7 @@ DashboardResponse DashboardClientImplX::commandUploadProgram(const std::string& DashboardResponse DashboardClientImplX::commandUpdateProgram(const std::string& file_path) { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandUpdateProgram is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("update_program"); return performProgramUpload( file_path, [this](const std::string& e, const httplib::UploadFormDataItems& f) { return put(e, f); }); } @@ -550,11 +555,7 @@ DashboardResponse DashboardClientImplX::commandUpdateProgram(const std::string& DashboardResponse DashboardClientImplX::commandDownloadProgram(const std::string& program_name, const std::string& save_path) { - if (robot_api_version_ < VersionInformation::fromString("3.1.4")) - { - throw NotImplementedException("commandDownloadProgram is not implemented for Robot API version < 3.1.4. Please " - "upgrade the robot to PolyScope 10.12.0 or higher to use this command."); - } + assertHasCommand("download_program"); if (program_name.size() == 0 || save_path.size() == 0) { std::string error = "Both program_name and save_path parameters should be populated."; From 153218ee03177d6e64163d9e7036996fea67a46b Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 15 May 2026 10:53:15 +0200 Subject: [PATCH 02/46] Add popup and closePopup for DashboardClientImplX --- .../ur_client_library/ur/dashboard_client.h | 4 +- .../ur/dashboard_client_implementation.h | 2 +- .../ur/dashboard_client_implementation_g5.h | 2 +- .../ur/dashboard_client_implementation_x.h | 5 ++- src/ur/dashboard_client.cpp | 9 ++-- src/ur/dashboard_client_implementation_g5.cpp | 3 +- src/ur/dashboard_client_implementation_x.cpp | 39 +++++++++++++--- tests/test_dashboard_client.cpp | 6 +-- tests/test_dashboard_client_x.cpp | 45 +++++++++++++++++++ 9 files changed, 97 insertions(+), 18 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 7613d70d6..0d89b3a46 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -405,12 +405,12 @@ class DashboardClient * * \return True succeeded */ - bool commandPopup(const std::string& popup_text); + bool commandPopup(const std::string& popup_text, const std::string& popup_title = ""); /*! * \brief Send popup command */ - DashboardResponse commandPopupWithResponse(const std::string& popup_text); + DashboardResponse commandPopupWithResponse(const std::string& popup_text, const std::string& popup_title = ""); /*! * \brief Send text to log diff --git a/include/ur_client_library/ur/dashboard_client_implementation.h b/include/ur_client_library/ur/dashboard_client_implementation.h index 6fda79375..39ebc76ac 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation.h +++ b/include/ur_client_library/ur/dashboard_client_implementation.h @@ -323,7 +323,7 @@ class DashboardClientImpl * * \throws an NotImplementedException when called on PolyScope X robots */ - virtual DashboardResponse commandPopup(const std::string& popup_text) = 0; + virtual DashboardResponse commandPopup(const std::string& popup_text, const std::string& popup_title = "") = 0; /*! * \brief Send text to log diff --git a/include/ur_client_library/ur/dashboard_client_implementation_g5.h b/include/ur_client_library/ur/dashboard_client_implementation_g5.h index fa66589da..fdf0ac897 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_g5.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_g5.h @@ -146,7 +146,7 @@ class DashboardClientImplG5 : public DashboardClientImpl, comm::TCPSocket DashboardResponse commandResume() override; DashboardResponse commandPlay() override; DashboardResponse commandPolyscopeVersion() override; - DashboardResponse commandPopup(const std::string& popup_text) override; + DashboardResponse commandPopup(const std::string& popup_text, const std::string& popup_title = "") override; DashboardResponse commandPowerOff() override; DashboardResponse commandPowerOn(const std::chrono::duration timeout = std::chrono::seconds(300)) override; DashboardResponse commandProgramState() override; diff --git a/include/ur_client_library/ur/dashboard_client_implementation_x.h b/include/ur_client_library/ur/dashboard_client_implementation_x.h index ea6a38c09..09d4d2bdc 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_x.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_x.h @@ -148,7 +148,7 @@ class DashboardClientImplX : public DashboardClientImpl DashboardResponse commandResume() override; DashboardResponse commandPlay() override; DashboardResponse commandPolyscopeVersion() override; - DashboardResponse commandPopup(const std::string& popup_text) override; + DashboardResponse commandPopup(const std::string& popup_text, const std::string& popup_title = "") override; DashboardResponse commandPowerOff() override; DashboardResponse commandPowerOn(const std::chrono::duration timeout = std::chrono::seconds(300)) override; DashboardResponse commandProgramState() override; @@ -186,6 +186,9 @@ class DashboardClientImplX : public DashboardClientImpl DashboardResponse put(const std::string& endpoint, const httplib::UploadFormDataItems& form_data, const bool debug = true); DashboardResponse get(const std::string& endpoint, const bool debug = true); + + DashboardResponse del(const std::string& endpoint, const bool debug = true); + virtual VersionInformation queryPolyScopeVersion(); void assertHasCommand(const std::string& command) const override; diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index 080936390..612e05fb5 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -318,14 +318,15 @@ DashboardResponse DashboardClient::commandIsInRemoteControlWithResponse() return impl_->commandIsInRemoteControl(); } -bool DashboardClient::commandPopup(const std::string& popup_text) +bool DashboardClient::commandPopup(const std::string& popup_text, const std::string& popup_title) { - return commandPopupWithResponse(popup_text).ok; + return commandPopupWithResponse(popup_text, popup_title).ok; } -DashboardResponse DashboardClient::commandPopupWithResponse(const std::string& popup_text) +DashboardResponse DashboardClient::commandPopupWithResponse(const std::string& popup_text, + const std::string& popup_title) { - return impl_->commandPopup(popup_text); + return impl_->commandPopup(popup_text, popup_title); } bool DashboardClient::commandAddToLog(const std::string& log_text) diff --git a/src/ur/dashboard_client_implementation_g5.cpp b/src/ur/dashboard_client_implementation_g5.cpp index c923d396c..fd8f86728 100644 --- a/src/ur/dashboard_client_implementation_g5.cpp +++ b/src/ur/dashboard_client_implementation_g5.cpp @@ -776,7 +776,8 @@ DashboardResponse DashboardClientImplG5::commandIsInRemoteControl() return response; } -DashboardResponse DashboardClientImplG5::commandPopup(const std::string& popup_text) +DashboardResponse DashboardClientImplG5::commandPopup(const std::string& popup_text, + [[maybe_unused]] const std::string& popup_title) { DashboardResponse response; try diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 066064362..619ff411d 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -293,12 +293,14 @@ DashboardResponse DashboardClientImplX::commandStop() DashboardResponse DashboardClientImplX::commandClosePopup() { - throw NotImplementedException("commandClosePopup is not implemented for DashboardClientImplX."); + assertHasCommand("close_popup"); + return del("/popup/v1"); } DashboardResponse DashboardClientImplX::commandCloseSafetyPopup() { - throw NotImplementedException("commandCloseSafetyPopup is not implemented for DashboardClientImplX."); + assertHasCommand("close_safety_popup"); + return del("/popup/v1/safety"); } DashboardResponse DashboardClientImplX::commandRestartSafety() @@ -352,9 +354,10 @@ DashboardResponse DashboardClientImplX::commandIsInRemoteControl() return response; } -DashboardResponse DashboardClientImplX::commandPopup([[maybe_unused]] const std::string& popup_text) +DashboardResponse DashboardClientImplX::commandPopup(const std::string& popup_text, const std::string& title) { - throw NotImplementedException("commandPopup is not implemented for DashboardClientImplX."); + assertHasCommand("popup"); + return post("/popup/v1", R"({"title": "TITLE )" + title + R"(", "message": ")" + popup_text + R"("})"); } DashboardResponse DashboardClientImplX::commandAddToLog([[maybe_unused]] const std::string& log_text) @@ -599,7 +602,15 @@ DashboardResponse DashboardClientImplX::handleHttpResult(const httplib::Result& } response.message = res->body; response.data["status_code"] = res->status; - response.ok = res->status == 200; + if (res->status >= 200 && res->status < 300) + { + response.ok = true; + } + else + { + response.ok = false; + } + return response; } @@ -696,6 +707,24 @@ DashboardResponse DashboardClientImplX::get(const std::string& endpoint, const b return response; } +DashboardResponse DashboardClientImplX::del(const std::string& endpoint, const bool debug) +{ + if (robot_api_version_.isEmpty()) + { + connect(); + } + DashboardResponse response; + if (auto res = cli_->Delete(base_url_ + endpoint)) + { + response = handleHttpResult(res, debug); + } + else + { + throw UrException("Error code: " + to_string(res.error())); + } + return response; +} + DashboardClientImplX::~DashboardClientImplX() { // We need to keep the implementation in the cpp file due to the unique_ptr of the incomplete diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp index c449c140c..ee7c6f70d 100644 --- a/tests/test_dashboard_client.cpp +++ b/tests/test_dashboard_client.cpp @@ -77,7 +77,7 @@ class MockDashboardClientImpl : public DashboardClientImplG5 MOCK_METHOD(DashboardResponse, commandPause, (), (override)); MOCK_METHOD(DashboardResponse, commandPlay, (), (override)); MOCK_METHOD(DashboardResponse, commandPolyscopeVersion, (), (override)); - MOCK_METHOD(DashboardResponse, commandPopup, (const std::string&), (override)); + MOCK_METHOD(DashboardResponse, commandPopup, (const std::string&, const std::string&), (override)); MOCK_METHOD(DashboardResponse, commandPowerOff, (), (override)); MOCK_METHOD(DashboardResponse, commandPowerOn, (const std::chrono::duration timeout), (override)); MOCK_METHOD(DashboardResponse, commandProgramState, (), (override)); @@ -204,9 +204,9 @@ TEST_F(DashboardClientTest, popup) { EXPECT_TRUE(dashboard_client_->connect()); const auto impl = dashboard_client_->getImplPtr(); - EXPECT_CALL(*impl, commandPopup("Test Popup")).WillOnce(testing::Return(SUCCESS_RESPONSE)); + EXPECT_CALL(*impl, commandPopup("Test Popup", "Test")).WillOnce(testing::Return(SUCCESS_RESPONSE)); EXPECT_CALL(*impl, commandClosePopup()).WillOnce(testing::Return(SUCCESS_RESPONSE)); - EXPECT_TRUE(dashboard_client_->commandPopup("Test Popup")); + EXPECT_TRUE(dashboard_client_->commandPopup("Test Popup", "Test")); EXPECT_TRUE(dashboard_client_->commandClosePopup()); } diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 83432fd70..07f5634e0 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -507,6 +507,51 @@ TEST_F(DashboardClientTestX, microsecond_receive_timeout_makes_connect_fail) EXPECT_FALSE(dashboard_client_->connect()); } +TEST_F(DashboardClientTestX, open_and_close_popups) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandPopup(""), NotImplementedException); + ASSERT_THROW(dashboard_client_->commandClosePopup(), NotImplementedException); + ASSERT_THROW(dashboard_client_->commandCloseSafetyPopup(), NotImplementedException); + } + else + { + // Can only be tested in remote mode, so just check that we get the correct error message + // Then we know the endpoint exists and the client is sending the correct message + if (skip_remote_control_tests) + { + auto response = dashboard_client_->commandClosePopup(); + ASSERT_FALSE(response.ok); + ASSERT_TRUE(response.message.find("Forbidden") != response.message.npos); + response = dashboard_client_->commandPopup("Test popup", "Test"); + ASSERT_FALSE(response.ok); + ASSERT_TRUE(response.message.find("Forbidden") != response.message.npos); + response = dashboard_client_->commandClosePopup(); + ASSERT_FALSE(response.ok); + ASSERT_TRUE(response.message.find("Forbidden") != response.message.npos); + response = dashboard_client_->commandCloseSafetyPopup(); + ASSERT_FALSE(response.ok); + ASSERT_TRUE(response.message.find("Forbidden") != response.message.npos); + } + else + { + auto response = dashboard_client_->commandClosePopup(); + ASSERT_FALSE(response.ok); + ASSERT_TRUE(response.message.find("Failed to close system dialog") != response.message.npos); + response = dashboard_client_->commandPopup("Test popup", "Test"); + ASSERT_TRUE(response.ok); + ASSERT_TRUE(response.message.find("Popup opened successfully") != response.message.npos); + response = dashboard_client_->commandClosePopup(); + ASSERT_TRUE(response.ok); + response = dashboard_client_->commandCloseSafetyPopup(); + ASSERT_FALSE(response.ok); + ASSERT_TRUE(response.message.find("Failed to close safety popup") != response.message.npos); + } + } +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); From d68d1b1accde461e0359612310156a5df2991a20 Mon Sep 17 00:00:00 2001 From: Jacob Larsen Date: Mon, 18 May 2026 09:31:41 +0000 Subject: [PATCH 03/46] Create table for polyscope X compatibility --- doc/polyscope_compatibility.rst | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index 23366f1ca..1446fabff 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -42,10 +42,35 @@ table below or checkout the latest tag before the breaking changes were introduc |polyscope| X doesn't support all features supported by this library for |polyscope| 5. Currently, the following components are known not to be supported: - - Dashboard client -- |polyscope| X received the first implementation of the Robot API - replacing the Dashboard Server in version 10.11.0. It covers robot state control and loading - and playing programs. - From version 10.12, it also supports uploading programs to the robot and downloading programs from the robot, as well as listing existing programs on the robot. + - Dashboard client -- |polyscope| X does not have a Dashboard server, but in version 10.11.0 introduced the Robot API, that fulfills some of the same purposes. The robot API has not yet reached feature parity with the Dashboard server. + + - Implemented features, and their version requirements: + + .. list-table:: + :header-rows: 1 + + * - Introduced in |polyscope| version + - Functionality group + - examples + * - 10.11.0 + - Robot state control + - power on, power off, brake release, etc + * - 10.11.0 + - Load and play programs + - load program, play, pause, etc + * - 10.12.0 + - Robot Program interactions + - Upload/update program, download program, list programs, etc + * - 10.12.0 + - Get robot modes + - Robot mode, safety mode, operational mode, remote control + * - 10.14.0 + - Popup interactions + - open, close, close safety popup + + - Using external control on |polyscope| X requires another URCapX for making external control + work. This is currently in the process of being created. + See `Universal Robots External Control URCapX `_ .. |polyscope| replace:: PolyScope From 97ec526ee4cd13990390ea3e8fd1b3dcc315dec6 Mon Sep 17 00:00:00 2001 From: Jacob Larsen Date: Mon, 18 May 2026 09:39:24 +0000 Subject: [PATCH 04/46] Implement commandPolyscopeVersion also queryPolScopeVersion --- doc/polyscope_compatibility.rst | 3 +++ src/ur/dashboard_client_implementation_x.cpp | 18 +++++++++++++++--- tests/test_dashboard_client_x.cpp | 16 ++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index 1446fabff..cd5ec5aa7 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -67,6 +67,9 @@ table below or checkout the latest tag before the breaking changes were introduc * - 10.14.0 - Popup interactions - open, close, close safety popup + * - 10.14.0 + - Get robot information + - Polyscope version - Using external control on |polyscope| X requires another URCapX for making external control work. This is currently in the process of being created. diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 619ff411d..596a6567a 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -71,7 +71,9 @@ std::unordered_map DashboardClientImplX::g_command { "is_in_remote_control", { "/system/v1/controlmode", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") }, - } + }, + { "PolyscopeVersion", + { "/versions/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, }; DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) @@ -165,7 +167,9 @@ timeval DashboardClientImplX::getConfiguredSendTimeout() const VersionInformation DashboardClientImplX::queryPolyScopeVersion() { - throw NotImplementedException("queryPolyScopeVersion is not implemented for DashboardClientImplX."); + DashboardResponse response = commandPolyscopeVersion(); + std::string version_string = std::get(response.data["polyscope_version"]); + return VersionInformation::fromString(version_string); } void DashboardClientImplX::assertHasCommand(const std::string& command) const @@ -367,7 +371,15 @@ DashboardResponse DashboardClientImplX::commandAddToLog([[maybe_unused]] const s DashboardResponse DashboardClientImplX::commandPolyscopeVersion() { - throw NotImplementedException("commandPolyscopeVersion is not implemented for DashboardClientImplX."); + assertHasCommand("PolyscopeVersion"); + const std::string endpoint = g_command_list["PolyscopeVersion"].endpoint; + auto response = get(endpoint); // This returns both marketing version and baseline version + auto json_data = json::parse(response.message); + if (response.ok) + { + response.data["polyscope_version"] = std::string(json_data["marketingVersion"]); + } + return response; } DashboardResponse DashboardClientImplX::commandGetRobotModel() diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 07f5634e0..6b6d1e6d9 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -552,6 +552,22 @@ TEST_F(DashboardClientTestX, open_and_close_popups) } } +TEST_F(DashboardClientTestX, get_polyscope_version) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandPolyscopeVersion(), NotImplementedException); + } + else + { + auto response = dashboard_client_->commandPolyscopeVersion(); + ASSERT_TRUE(response.ok); + std::string version_string = std::get(response.data["polyscope_version"]); + EXPECT_EQ(*polyscope_version_, VersionInformation::fromString(version_string)); + } +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); From 25716e158c72475ba968d1d66b4b19311b0527c1 Mon Sep 17 00:00:00 2001 From: Jacob Larsen Date: Mon, 18 May 2026 11:08:02 +0000 Subject: [PATCH 05/46] Implement getRobotModel and getSerialNumber They use the same endpoint, so same commit --- doc/polyscope_compatibility.rst | 2 +- src/ur/dashboard_client_implementation_x.cpp | 24 +++++++++++-- tests/test_dashboard_client_x.cpp | 37 ++++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index cd5ec5aa7..067dc3067 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -69,7 +69,7 @@ table below or checkout the latest tag before the breaking changes were introduc - open, close, close safety popup * - 10.14.0 - Get robot information - - Polyscope version + - Polyscope version, robot model, serial number - Using external control on |polyscope| X requires another URCapX for making external control work. This is currently in the process of being created. diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 596a6567a..b8118eb68 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -74,6 +74,10 @@ std::unordered_map DashboardClientImplX::g_command }, { "PolyscopeVersion", { "/versions/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "get_robot_model", + { "/system/v1/information", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "get_serial_number", + { "/system/v1/information", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, }; DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) @@ -384,12 +388,28 @@ DashboardResponse DashboardClientImplX::commandPolyscopeVersion() DashboardResponse DashboardClientImplX::commandGetRobotModel() { - throw NotImplementedException("commandGetRobotModel is not implemented for DashboardClientImplX."); + assertHasCommand("get_robot_model"); + const std::string endpoint = g_command_list["get_robot_model"].endpoint; + auto response = get(endpoint); + auto json_data = json::parse(response.message); + if (response.ok) + { + response.data["robot_model"] = std::string(json_data["robotType"]); + } + return response; } DashboardResponse DashboardClientImplX::commandGetSerialNumber() { - throw NotImplementedException("commandGetSerialNumber is not implemented for DashboardClientImplX."); + assertHasCommand("get_serial_number"); + const std::string endpoint = g_command_list["get_serial_number"].endpoint; + auto response = get(endpoint); + auto json_data = json::parse(response.message); + if (response.ok) + { + response.data["serial_number"] = std::string(json_data["serialNumber"]); + } + return response; } DashboardResponse DashboardClientImplX::commandRobotMode() diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 6b6d1e6d9..fedc63d4b 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -568,6 +568,43 @@ TEST_F(DashboardClientTestX, get_polyscope_version) } } +TEST_F(DashboardClientTestX, get_robot_model) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandGetRobotModel(), NotImplementedException); + } + else + { + auto response = dashboard_client_->commandGetRobotModel(); + ASSERT_TRUE(response.ok); + const std::string model_string = std::get(response.data["robot_model"]); + + waitFor([this]() { return primary_client_->getRobotType() != urcl::RobotType::UNDEFINED; }, + std::chrono::milliseconds(1000)); + + const std::string true_robot = robotTypeString(primary_client_->getRobotType()); + EXPECT_EQ(model_string, true_robot); + } +} + +TEST_F(DashboardClientTestX, get_serial_number) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandGetSerialNumber(), NotImplementedException); + } + else + { + auto response = dashboard_client_->commandGetSerialNumber(); + ASSERT_TRUE(response.ok); + const std::string serial_number = std::get(response.data["serial_number"]); + EXPECT_FALSE(serial_number.empty()); // Dont know what to check for here otherwise + } +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); From c894239ea724e331c7e8f94df48c265289a1af53 Mon Sep 17 00:00:00 2001 From: Jacob Larsen Date: Mon, 18 May 2026 12:56:15 +0000 Subject: [PATCH 06/46] Implement commandShutdown --- doc/polyscope_compatibility.rst | 3 +++ src/ur/dashboard_client_implementation_x.cpp | 6 ++++- tests/test_dashboard_client_x.cpp | 23 ++++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index 067dc3067..321b19fd9 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -70,6 +70,9 @@ table below or checkout the latest tag before the breaking changes were introduc * - 10.14.0 - Get robot information - Polyscope version, robot model, serial number + * - 10.14.0 + - System control + - Shut down robot - Using external control on |polyscope| X requires another URCapX for making external control work. This is currently in the process of being created. diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index b8118eb68..8527fc545 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -78,6 +78,8 @@ std::unordered_map DashboardClientImplX::g_command { "/system/v1/information", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, { "get_serial_number", { "/system/v1/information", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "shutdown", + { "/system/v1/shutdown", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, }; DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) @@ -323,7 +325,9 @@ DashboardResponse DashboardClientImplX::commandUnlockProtectiveStop() DashboardResponse DashboardClientImplX::commandShutdown() { - throw NotImplementedException("commandShutdown is not implemented for DashboardClientImplX."); + assertHasCommand("shutdown"); + const std::string endpoint = g_command_list["shutdown"].endpoint; + return put(endpoint, ""); } DashboardResponse DashboardClientImplX::commandQuit() diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index fedc63d4b..71902ef60 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -605,6 +605,29 @@ TEST_F(DashboardClientTestX, get_serial_number) } } +TEST_F(DashboardClientTestX, shutdown_robot) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandShutdown(), NotImplementedException); + } + else + { + if (skip_remote_control_tests) + { + auto response = dashboard_client_->commandShutdown(); + ASSERT_FALSE(response.ok); + EXPECT_TRUE(response.message.find("Forbidden") != response.message.npos); + } + else + { + auto response = dashboard_client_->commandShutdown(); + ASSERT_TRUE(response.ok); + } + } +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); From 59c411a5dfbffe509ca15dd65fe1c4b1b98c27a1 Mon Sep 17 00:00:00 2001 From: Jacob Larsen Date: Mon, 18 May 2026 13:24:55 +0000 Subject: [PATCH 07/46] Implement commandAddToLog --- doc/polyscope_compatibility.rst | 3 +++ src/ur/dashboard_client_implementation_x.cpp | 2 ++ tests/test_dashboard_client_x.cpp | 24 ++++++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index 321b19fd9..5f4f5cbbf 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -73,6 +73,9 @@ table below or checkout the latest tag before the breaking changes were introduc * - 10.14.0 - System control - Shut down robot + * - 10.14.0 + - Logging + - Add entry to system log - Using external control on |polyscope| X requires another URCapX for making external control work. This is currently in the process of being created. diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 8527fc545..92f8a6d96 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -80,6 +80,8 @@ std::unordered_map DashboardClientImplX::g_command { "/system/v1/information", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, { "shutdown", { "/system/v1/shutdown", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "add_to_log", + { "/system/v1/log", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, }; DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 71902ef60..30191573b 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -628,6 +628,30 @@ TEST_F(DashboardClientTestX, shutdown_robot) } } +TEST_F(DashboardClientTestX, add_to_log) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandAddToLog(""), NotImplementedException); + } + else + { + if (skip_remote_control_tests) + { + auto response = dashboard_client_->commandAddToLog("Test log"); + ASSERT_FALSE(response.ok); + EXPECT_TRUE(response.message.find("Forbidden") != response.message.npos); + } + else + { + auto response = dashboard_client_->commandAddToLog("Test log"); + ASSERT_TRUE(response.ok); + EXPECT_TRUE(response.message.find("Log entry added") != response.message.npos); + } + } +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); From 361b7243e528c767e244add13afbc64b12b1ecb6 Mon Sep 17 00:00:00 2001 From: Jacob Larsen Date: Mon, 18 May 2026 13:26:08 +0000 Subject: [PATCH 08/46] Add remaining endpoints to g_command_list These are not implemented --- src/ur/dashboard_client_implementation_x.cpp | 23 ++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 92f8a6d96..25c5c49a2 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -82,6 +82,14 @@ std::unordered_map DashboardClientImplX::g_command { "/system/v1/shutdown", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, { "add_to_log", { "/system/v1/log", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "generate_flight_report", + { "/supportfiles/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "download_flight_reports", + { "/supportfiles/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "set_operational_mode", + { "/operational-mode/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "clear_operational_mode", + { "/operational-mode/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } } }; DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) @@ -376,7 +384,11 @@ DashboardResponse DashboardClientImplX::commandPopup(const std::string& popup_te DashboardResponse DashboardClientImplX::commandAddToLog([[maybe_unused]] const std::string& log_text) { - throw NotImplementedException("commandAddToLog is not implemented for DashboardClientImplX."); + assertHasCommand("add_to_log"); + const std::string endpoint = g_command_list["add_to_log"].endpoint; + const std::string message = R"({"message": ")" + log_text + R"("})"; + std::cout << message << std::endl; + return post(endpoint, message); } DashboardResponse DashboardClientImplX::commandPolyscopeVersion() @@ -504,7 +516,14 @@ DashboardResponse DashboardClientImplX::commandGetUserRole() DashboardResponse DashboardClientImplX::commandGenerateFlightReport([[maybe_unused]] const std::string& report_type) { - throw NotImplementedException("commandGenerateFlightReport is not implemented for DashboardClientImplX."); + assertHasCommand("generate_flight_report"); + const std::string endpoint = g_command_list["generate_flight_report"].endpoint; + auto response = post(endpoint, "", "application/json"); + auto json = json::parse(response.message); + std::cout << json << std::endl; + // std::cout << response << std::endl; + return response; + // throw NotImplementedException("commandGenerateFlightReport is not implemented for DashboardClientImplX."); } DashboardResponse DashboardClientImplX::commandGenerateSupportFile([[maybe_unused]] const std::string& dir_path) From 6959ca1f0f31bc4369f4693ca1b8d27f90f888da Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 17 Aug 2026 12:15:02 +0200 Subject: [PATCH 09/46] Update tests --- tests/resources/upload_prog.urpx | 2300 +---------------------------- tests/test_dashboard_client_x.cpp | 19 +- 2 files changed, 15 insertions(+), 2304 deletions(-) diff --git a/tests/resources/upload_prog.urpx b/tests/resources/upload_prog.urpx index 17dc71464..c791f4a3c 100644 --- a/tests/resources/upload_prog.urpx +++ b/tests/resources/upload_prog.urpx @@ -1,2299 +1 @@ -{ - "application": { - "id": "5", - "applicationInfo": { - "name": "application" - }, - "applicationContent": { - "applicationContributions": { - "ur-mounting": { - "type": "ur-mounting", - "version": "0.0.1", - "mounting": { - "baseAngle": { - "value": 0, - "unit": "deg" - }, - "tiltAngle": { - "value": 0, - "unit": "deg" - } - } - }, - "ur-frames": { - "type": "ur-frames", - "version": "0.0.7", - "framesList": [ - { - "name": "base", - "nameVariable": { - "name": "base", - "reference": false, - "type": "$$Variable", - "valueType": "frame", - "id": "4aa883ca-3eba-49ec-bbcc-ee1ee65176ed", - "_IDENTIFIER": "VariableDeclaration" - }, - "parent": "world", - "pose": { - "position": [ - 0, - 0, - 0 - ], - "orientation": [ - 0, - 0, - 0 - ] - } - }, - { - "name": "tcp", - "nameVariable": { - "name": "tcp", - "reference": false, - "type": "$$Variable", - "valueType": "frame", - "id": "551721e6-e865-fe61-5a00-85c8f3fcb7ce", - "_IDENTIFIER": "VariableDeclaration" - }, - "parent": "flange", - "pose": { - "position": [ - 0, - 0, - 0 - ], - "orientation": [ - 0, - 0, - 0 - ] - } - }, - { - "name": "world", - "nameVariable": { - "name": "world", - "reference": false, - "type": "$$Variable", - "valueType": "frame", - "id": "15d05e2b-ad37-603f-cfbd-3b344fdee9d4", - "_IDENTIFIER": "VariableDeclaration" - }, - "pose": { - "position": [ - 0, - 0, - 0 - ], - "orientation": [ - 0, - 0, - 0 - ] - } - }, - { - "name": "flange", - "nameVariable": { - "name": "flange", - "reference": false, - "type": "$$Variable", - "valueType": "frame", - "id": "af9509cc-f84c-71b2-3578-10a8172db29b", - "_IDENTIFIER": "VariableDeclaration" - }, - "parent": "base", - "pose": { - "position": [ - 0, - 0, - 0 - ], - "orientation": [ - 0, - 0, - 0 - ] - } - } - ] - }, - "ur-grid-pattern": { - "type": "ur-grid-pattern", - "version": "0.0.3", - "grids": [ - { - "grid": { - "name": "grid", - "reference": false, - "type": "$$Variable", - "valueType": "grid", - "id": "17606307-89d9-eb68-6cfb-918c0ddbed4c", - "_IDENTIFIER": "VariableDeclaration" - }, - "waypoint": { - "name": "grid_iterator", - "reference": false, - "type": "$$Variable", - "valueType": "waypoint", - "id": "74c6d3f7-b0ad-09c5-4bf6-2acc5848f9d5", - "_IDENTIFIER": "VariableDeclaration" - }, - "corners": [ - null, - null, - null, - null - ], - "numRows": 4, - "numColumns": 5 - } - ] - }, - "ur-end-effector": { - "type": "ur-end-effector", - "version": "0.0.2", - "endEffectors": [ - { - "id": "d2a7e55f-5c8b-3fb9-d898-93ce3c841a6c", - "name": "Robot", - "payload": { - "weight": { - "value": 0, - "unit": "kg" - } - }, - "cog": { - "cx": { - "value": 0, - "unit": "m" - }, - "cy": { - "value": 0, - "unit": "m" - }, - "cz": { - "value": 0, - "unit": "m" - } - }, - "inertia": { - "Ixx": { - "value": 0, - "unit": "kg*m^2" - }, - "Iyy": { - "value": 0, - "unit": "kg*m^2" - }, - "Izz": { - "value": 0, - "unit": "kg*m^2" - }, - "Ixy": { - "value": 0, - "unit": "kg*m^2" - }, - "Ixz": { - "value": 0, - "unit": "kg*m^2" - }, - "Iyz": { - "value": 0, - "unit": "kg*m^2" - } - }, - "useCustomInertia": false, - "tcps": [ - { - "id": "4076e4c7-d851-a441-2776-902faa191f9a", - "name": "Tool_flange", - "x": { - "value": 0, - "unit": "m" - }, - "y": { - "value": 0, - "unit": "m" - }, - "z": { - "value": 0, - "unit": "m" - }, - "rx": { - "value": 0, - "unit": "rad" - }, - "ry": { - "value": 0, - "unit": "rad" - }, - "rz": { - "value": 0, - "unit": "rad" - } - } - ] - } - ], - "defaultTcp": { - "endEffectorId": "d2a7e55f-5c8b-3fb9-d898-93ce3c841a6c", - "tcpId": "4076e4c7-d851-a441-2776-902faa191f9a" - } - }, - "ur-motion-profiles": { - "type": "ur-motion-profiles", - "version": "0.0.1", - "moveProfiles": { - "joint": [ - { - "isDefault": false, - "profile": { - "name": "Joint_fast", - "reference": false, - "type": "$$Variable", - "valueType": "profile", - "id": "9cadc383-da1d-d089-1320-9760cfb9fe0e", - "_IDENTIFIER": "VariableDeclaration" - }, - "parameters": { - "speedType": "OptiMove", - "speed": { - "entity": { - "value": 1.0471975511965976, - "unit": "rad/s" - }, - "selectedType": "VALUE", - "value": 1.0471975511965976 - }, - "acceleration": { - "entity": { - "value": 1.3962634015954636, - "unit": "rad/s^2" - }, - "selectedType": "VALUE", - "value": 1.3962634015954636 - }, - "optiMoveSpeed": { - "entity": { - "value": 50, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 50 - }, - "optiMoveAcceleration": { - "entity": { - "value": 25, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 25 - } - } - }, - { - "isDefault": true, - "profile": { - "name": "Joint_slow", - "reference": false, - "type": "$$Variable", - "valueType": "profile", - "id": "9f7cd9c1-6b40-8ecf-cc23-dd5e94791815", - "_IDENTIFIER": "VariableDeclaration" - }, - "parameters": { - "speedType": "OptiMove", - "speed": { - "entity": { - "value": 1.0471975511965976, - "unit": "rad/s" - }, - "selectedType": "VALUE", - "value": 1.0471975511965976 - }, - "acceleration": { - "entity": { - "value": 1.3962634015954636, - "unit": "rad/s^2" - }, - "selectedType": "VALUE", - "value": 1.3962634015954636 - }, - "optiMoveSpeed": { - "entity": { - "value": 20, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 20 - }, - "optiMoveAcceleration": { - "entity": { - "value": 4, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 4 - } - } - } - ], - "linear": [ - { - "isDefault": false, - "profile": { - "name": "Linear_fast", - "reference": false, - "type": "$$Variable", - "valueType": "profile", - "id": "bbc53484-5136-9422-baed-131c092effdc", - "_IDENTIFIER": "VariableDeclaration" - }, - "parameters": { - "speedType": "OptiMove", - "speed": { - "entity": { - "value": 0.25, - "unit": "m/s" - }, - "selectedType": "VALUE", - "value": 0.25 - }, - "acceleration": { - "entity": { - "value": 1.2, - "unit": "m/s^2" - }, - "selectedType": "VALUE", - "value": 1.2 - }, - "optiMoveSpeed": { - "entity": { - "value": 50, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 50 - }, - "optiMoveAcceleration": { - "entity": { - "value": 25, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 25 - } - } - }, - { - "isDefault": true, - "profile": { - "name": "Linear_slow", - "reference": false, - "type": "$$Variable", - "valueType": "profile", - "id": "a4ce2084-6f47-6b6b-1e85-cb174d0397b3", - "_IDENTIFIER": "VariableDeclaration" - }, - "parameters": { - "speedType": "OptiMove", - "speed": { - "entity": { - "value": 0.25, - "unit": "m/s" - }, - "selectedType": "VALUE", - "value": 0.25 - }, - "acceleration": { - "entity": { - "value": 1.2, - "unit": "m/s^2" - }, - "selectedType": "VALUE", - "value": 1.2 - }, - "optiMoveSpeed": { - "entity": { - "value": 20, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 20 - }, - "optiMoveAcceleration": { - "entity": { - "value": 4, - "unit": "%" - }, - "selectedType": "VALUE", - "value": 4 - } - } - } - ], - "process": [ - { - "isDefault": true, - "profile": { - "name": "Process", - "reference": false, - "type": "$$Variable", - "valueType": "profile", - "id": "9e716d40-7af5-7806-2a39-f1d98dce92dd", - "_IDENTIFIER": "VariableDeclaration" - }, - "parameters": { - "speedType": "Classic", - "speed": { - "entity": { - "value": 0.25, - "unit": "m/s" - }, - "selectedType": "VALUE", - "value": 0.25 - }, - "acceleration": { - "entity": { - "value": 1.2, - "unit": "m/s^2" - }, - "selectedType": "VALUE", - "value": 1.2 - } - } - } - ] - } - }, - "ur-smart-skills": { - "type": "ur-smart-skills", - "version": "0.0.3", - "preamble": "# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper" - }, - "ur-application-variables": { - "type": "ur-application-variables", - "version": "0.0.1", - "variables": {} - }, - "universal-robots-external-control-external-control-application": { - "type": "universal-robots-external-control-external-control-application", - "version": "1.0.0", - "port": 50002, - "robotIP": "192.168.56.1" - } - }, - "sourceConfig": { - "labelMap": {}, - "analogDomainMap": {}, - "presets": {} - }, - "sourcesNodes": { - "robot": { - "groupId": "robot", - "version": "1.0.0.", - "sources": [ - { - "sourceID": "ur-wired-io", - "signals": [ - { - "signalID": "DI 0", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 1", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 2", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 3", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 4", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 5", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 6", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 7", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 0", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 1", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 2", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 3", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 4", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 5", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 6", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 7", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 0", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 1", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 2", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 3", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 4", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 5", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 6", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CI 7", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 0", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 1", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 2", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 3", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 4", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 5", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 6", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "CO 7", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "AI 0", - "direction": "IN", - "valueType": "FLOAT" - }, - { - "signalID": "AI 1", - "direction": "IN", - "valueType": "FLOAT" - }, - { - "signalID": "AO 0", - "direction": "OUT", - "valueType": "FLOAT" - }, - { - "signalID": "AO 1", - "direction": "OUT", - "valueType": "FLOAT" - } - ], - "webSocketURL": "/sources/wired-io" - }, - { - "sourceID": "ur-tool-io", - "signals": [ - { - "signalID": "DI 0", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DI 1", - "direction": "IN", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 0", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "DO 1", - "direction": "OUT", - "valueType": "BOOLEAN" - }, - { - "signalID": "AI 0", - "direction": "IN", - "valueType": "FLOAT" - }, - { - "signalID": "AI 1", - "direction": "IN", - "valueType": "FLOAT" - } - ], - "webSocketURL": "/sources/tool-io" - } - ], - "isDynamic": false - }, - "ur-modbus": { - "groupId": "ur-modbus", - "isDynamic": true, - "version": "1.0.0", - "sources": [] - }, - "ur-robot-io": { - "type": "ur-robot-io", - "groupId": "ur-robot-io", - "isDynamic": false, - "version": "1.0.2", - "sources": [ - { - "sourceID": "ur-robot-wired-io", - "name": "Wired I/O", - "signals": [ - { - "direction": "IN", - "signalID": "DI 0", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 1", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 2", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 3", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 4", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 5", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 6", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 7", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 0", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 1", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 2", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 3", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 4", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 5", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 6", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 7", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 0", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 1", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 2", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 3", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 4", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 5", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 6", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "CI 7", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 0", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 1", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 2", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 3", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 4", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 5", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 6", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "CO 7", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "AI 0", - "valueType": "FLOAT" - }, - { - "direction": "IN", - "signalID": "AI 1", - "valueType": "FLOAT" - }, - { - "direction": "OUT", - "signalID": "AO 0", - "valueType": "FLOAT" - }, - { - "direction": "OUT", - "signalID": "AO 1", - "valueType": "FLOAT" - } - ] - }, - { - "sourceID": "ur-robot-tool-io", - "name": "Tool I/O", - "signals": [ - { - "direction": "IN", - "signalID": "DI 0", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "DI 1", - "valueType": "BOOLEAN" - }, - { - "direction": "IN", - "signalID": "AI 0", - "valueType": "FLOAT" - }, - { - "direction": "IN", - "signalID": "AI 1", - "valueType": "FLOAT" - }, - { - "direction": "OUT", - "signalID": "DO 0", - "valueType": "BOOLEAN" - }, - { - "direction": "OUT", - "signalID": "DO 1", - "valueType": "BOOLEAN" - } - ] - } - ], - "parameters": { - "sourceConfig": { - "labelMap": {}, - "analogDomainMap": {}, - "presets": {}, - "toolOutput": { - "dualPinPower": false, - "voltage": { - "value": 0, - "unit": "V" - }, - "powerOutput": { - "DO 0": 1, - "DO 1": 1 - } - } - }, - "migrateSourceConfigDone": true - } - } - }, - "safety": { - "settings": { - "io": { - "automaticModeSafeguardResetInput": { - "name": "automaticModeSafeguardResetInput", - "valueA": 255, - "valueB": 255 - }, - "automaticModeSafeguardStopInput": { - "name": "automaticModeSafeguardStopInput", - "valueA": 255, - "valueB": 255 - }, - "emergencyStopInput": { - "name": "emergencyStopInput", - "valueA": 255, - "valueB": 255 - }, - "notReducedModeOutput": { - "name": "notReducedModeOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - }, - "operationalModeInput": { - "name": "operationalModeInput", - "valueA": 255, - "valueB": 255 - }, - "reducedModeInput": { - "name": "reducedModeInput", - "valueA": 255, - "valueB": 255 - }, - "reducedModeOutput": { - "name": "reducedModeOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - }, - "robotMovingOutput": { - "name": "robotMovingOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - }, - "robotNotStoppingOutput": { - "name": "robotNotStoppingOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - }, - "safeHomeOutput": { - "name": "safeHomeOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - }, - "safeguardResetInput": { - "name": "safeguardResetInput", - "valueA": 0, - "valueB": 1 - }, - "systemEmergencyStoppedOutput": { - "name": "systemEmergencyStoppedOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - }, - "threePositionSwitchInput": { - "name": "threePositionSwitchInput", - "valueA": 255, - "valueB": 255 - }, - "freedriveEnabledInput": { - "name": "freedriveEnabledInput", - "valueA": 255, - "valueB": 255 - }, - "threePositionEnablingStopOutput": { - "name": "threePositionEnablingStopOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - }, - "notThreePositionEnablingStopOutput": { - "name": "notThreePositionEnablingStopOutput", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "major": 5, - "minor": 13, - "normalJointPositions": { - "base": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "elbow": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "shoulder": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "wrist1": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "wrist2": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "wrist3": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - } - }, - "normalJointSpeeds": { - "base": 3.3415926, - "shoulder": 3.3415926, - "elbow": 3.3415926, - "wrist1": 3.3415926, - "wrist2": 3.3415926, - "wrist3": 3.3415926 - }, - "normalRobotLimits": { - "elbowForce": 150, - "elbowSpeed": 1.5, - "momentum": 25, - "power": 300, - "stoppingDistance": 0.5, - "stoppingTime": 0.4, - "toolForce": 150, - "toolSpeed": 1.5 - }, - "reducedJointPositions": { - "base": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "elbow": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "shoulder": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "wrist1": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "wrist2": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - }, - "wrist3": { - "maximum": 6.33555, - "maximumJointPosition": 0.05235988, - "maximumRevolutionCounter": 1, - "minimum": -6.33555, - "minimumJointPosition": 6.2308254, - "minimumRevolutionCounter": -2, - "unlimited": false - } - }, - "reducedJointSpeeds": { - "base": 3.3415926, - "shoulder": 3.3415926, - "elbow": 3.3415926, - "wrist1": 3.3415926, - "wrist2": 3.3415926, - "wrist3": 3.3415926 - }, - "reducedRobotLimits": { - "elbowForce": 120, - "elbowSpeed": 0.75, - "momentum": 10, - "power": 200, - "stoppingDistance": 0.3, - "stoppingTime": 0.3, - "toolForce": 120, - "toolSpeed": 0.75 - }, - "safetyHardware": { - "injectionMoldingMachineInterface": "NONE", - "teachPendant": "NORMAL" - }, - "safetyPlanes": { - "planes": [ - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - }, - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - }, - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - }, - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - }, - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - }, - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - }, - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - }, - { - "name": "UNDEFINED", - "safetyPlane": { - "normalModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModePlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "reducedModeTriggerPlane": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "restriction": "disabled" - } - ], - "ioSafetyPlanes": [ - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - }, - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - }, - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - }, - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - }, - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - }, - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - }, - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - }, - { - "name": "UNDEFINED", - "ioSafetyPlane": { - "triggerOutput": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "triggerSafeguard": { - "distance": 0, - "vector": { - "x": 0, - "y": 0, - "z": 0 - } - }, - "restrictsElbow": false, - "restrictsToolFlange": true, - "inputConfiguration": { - "name": "UNDEFINED", - "valueA": 255, - "valueB": 255 - }, - "outputConfiguration": { - "name": "UNDEFINED", - "ossdEnabled": false, - "valueA": 255, - "valueB": 255 - } - }, - "tilt": 0, - "offset": 0, - "rotation": 0, - "trigger": "disabled" - } - ] - }, - "safetySafeHome": { - "base": -1, - "elbow": -1, - "shoulder": -1, - "wrist1": -1, - "wrist2": -1, - "wrist3": -1, - "enabled": false - }, - "safetyAPIParameters": { - "numberOfClients": 0, - "clients": [] - }, - "safetyFieldbusses": { - "enablePROFIsafe": false, - "sourceAddressPROFIsafe": 0, - "destAddressPROFIsafe": 0, - "modeControlPROFIsafe": false - }, - "threePosition": { - "allowManualHighSpeed": true, - "useTeachPendantAs3PE": false - }, - "toolDirection": { - "limitDeviation": 6.2831855, - "limitDirection": { - "x": 0, - "y": 0, - "z": 1 - }, - "limitRestriction": "DISABLED", - "toolPan": 0, - "toolTilt": 0 - }, - "toolPositions": { - "toolPositions": [ - { - "name": "Tool Flange", - "center": { - "x": 0, - "y": 0, - "z": 0 - }, - "radius": 0, - "definition": 2 - }, - { - "name": "UNDEFINED", - "center": { - "x": 0, - "y": 0, - "z": 0 - }, - "radius": 0, - "definition": 0 - }, - { - "name": "UNDEFINED", - "center": { - "x": 0, - "y": 0, - "z": 0 - }, - "radius": 0, - "definition": 0 - } - ] - }, - "normalWristClamp": { - "enableWristClampPosition": "LIMIT_ENABLED", - "enableWristClampTorque": "LIMIT_ENABLED" - }, - "reducedWristClamp": { - "enableWristClampPosition": "LIMIT_ENABLED", - "enableWristClampTorque": "LIMIT_ENABLED" - } - }, - "crc": "633835311" - }, - "operatorScreens": [ - { - "type": "ur-operator-screen-default", - "version": "0.0.2", - "parameters": { - "status": [], - "configuration": [] - } - } - ], - "sidebarItems": [ - { - "type": "ur-global-variables", - "version": "1.0.0", - "disabled": { - "master": false, - "automaticMode": false, - "remoteMode": true - } - }, - { - "type": "ur-log-messages-sidebar", - "version": "0.0.1", - "disabled": { - "master": true, - "automaticMode": true, - "remoteMode": true - } - } - ], - "smartSkills": [ - { - "name": "Align to Plane", - "enabled": true, - "type": "ur-align-to-plane", - "parameters": { - "radius": 0.05, - "push_force": 20, - "n_plane_points": 3, - "max_distance": 0.25, - "velocity_slow": 0.001, - "velocity_search": 0.035, - "velocity_move": 0.1, - "acceleration": 0.1 - } - }, - { - "name": "Align Z to Nearest Axis", - "enabled": true, - "type": "ur-align-z-to-nearest-axis" - }, - { - "name": "Center", - "enabled": true, - "type": "ur-center", - "parameters": { - "push_force": 10, - "velocity_move": 0.05, - "acc_move": 0.2, - "max_radius_search": 0.05, - "num_fingers": 3 - } - }, - { - "name": "Freedrive", - "enabled": true, - "type": "ur-freedrive", - "version": "1.0.0", - "recordingFrequency": 50, - "recordingSignals": [ - "timestamp", - "target_q", - "actual_TCP_pose", - "tcp_offset" - ] - }, - { - "name": "Move into Contact", - "enabled": true, - "type": "ur-move-into-contact", - "parameters": { - "force": 10, - "velocity": 0.05, - "acceleration": 0.2, - "max_distance": 0.25, - "retract": 0 - } - }, - { - "name": "Retract", - "enabled": true, - "type": "ur-retract", - "parameters": { - "distance": -0.1, - "acceleration": 0.4, - "velocity": 0.1 - } - }, - { - "name": "Put into Box", - "enabled": false, - "type": "ur-put-in-box", - "version": "1.0.0" - }, - { - "name": "Custom", - "enabled": false, - "type": "ur-custom-smart-skill", - "parameters": { - "includePreamble": true, - "includeModules": false - }, - "version": "1.0.0" - }, - { - "name": "Home", - "enabled": true, - "type": "ur-position", - "version": "1.1.2", - "parameters": { - "actualWaypoint": { - "frame": "base", - "pose": { - "position": [ - -1.8246917738038495E-9, - -0.2329000001676105, - 1.0793999999522315 - ], - "orientation": [ - 3.987257497300885E-9, - 2.2214414675120993, - -2.221441467056474 - ] - }, - "qNear": { - "base": 0, - "shoulder": -1.5707963249999999, - "elbow": 0, - "wrist1": -1.5707963249999999, - "wrist2": 0, - "wrist3": 0 - } - }, - "variable": { - "name": "Home", - "reference": false, - "type": "$$Variable", - "valueType": "waypoint", - "id": "038b8cab-70ae-6958-23c8-174fa0b397d1", - "_IDENTIFIER": "VariableDeclaration" - } - } - } - ] - }, - "urscript": { - "script": "set_safety_mode_transition_hardness(1)\nreset_world_model()\nset_input_actions_to_default()\nset_analog_outputdomain(0,0)\nset_analog_outputdomain(1,0)\nset_standard_analog_input_domain(0,0)\nset_standard_analog_input_domain(1,0)\nset_tool_output_mode(0)\nset_tool_voltage(0)\nset_tool_digital_output_mode(0,1)\nset_tool_digital_output_mode(1,1)\nset_tool_analog_input_domain(0,0)\nset_tool_analog_input_domain(1,0)\nset_gravity([0, 0, 9.82])\nlocal existingBaseParent = get_frame_parent(\"base\")\nlocal basePose = get_pose(\"base\", existingBaseParent)\nbasePose[3] = 0\nbasePose[4] = 0\nbasePose[5] = 0\nmove_frame(\"base\", basePose, existingBaseParent)\nglobal base = \"base\"\nglobal tcp = \"tcp\"\nglobal world = \"world\"\nglobal flange = \"flange\"\nset_target_payload(0, [0, 0, 0], [0, 0, 0, 0, 0, 0])\nset_tcp(p[0, 0, 0, 0, 0, 0], \"Tool_flange\")\n# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper\n# Waypoint variable for Home smart skill\nglobal Home = struct(p=p[-1.8246917738038495e-9, -0.2329000001676105, 1.0793999999522315, 3.987257497300885e-9, 2.2214414675120993, -2.221441467056474], frame=\"base\", q=[0, -1.5707963249999999, 0, -1.5707963249999999, 0, 0])\n# Start of Align to Plane\n###\n# Align to plane will touch up a plane by moving the robot into contact with the table or part in several locations to determine its orientation. Afterwards the robot will orient its tool to the plane.\n# @param radius number Radius [m] of the circle within the plane will be touched up\n# @param push_force number How hard to robot pushed against the plane\n# @param n_plane_points number Number of points that the robot uses to compute the plane\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_slow number Velocity when pressing downwards\n# @param velocity_search number Velocity used when approaching the touch up point\n# @param velocity_move number Velocity used in freespace\n# @param acceleration number Acceleration of the robot\n# @param direction array 3D vector determining the direction of the TCP for touching up the plane\n###\ndef ur_align_to_plane(radius = 0.05, push_force = 20, n_plane_points = 3, max_distance = 0.25, velocity_slow = 0.001, velocity_search = 0.035, velocity_move = 0.10, acceleration = 0.1, direction = [0, 0, 1]):\n local angle = 2 * PI / n_plane_points\n local start_pos = get_target_tcp_pose()\n local retract_distance = -0.015\n ur_move_tcp_direction(retract_distance, direction, velocity_move, acceleration, 0)\n sleep(0.25)\n zero_ftsensor()\n local cnt = 0\n local t_base_target = get_target_tcp_pose()\n local mean_point = [0.0, 0.0, 0.0]\n local A = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]\n local b = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\n while cnt < n_plane_points:\n local new_pos = pose_trans(t_base_target, p[cos(angle * cnt) * radius, sin(angle * cnt) * radius, 0.0, 0.0, 0.0, 0.0])\n local blend_radius = norm(point_dist(get_actual_tcp_pose(), new_pos))/5\n movel(new_pos, a = acceleration, v = velocity_move, r = blend_radius)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_search, acceleration, push_force)\n local movement = normalize(direction * -1) * 0.0005\n local target_pose = pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose)\n sleep(0.2)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_slow, acceleration, push_force)\n sleep(0.2)\n while (not is_steady()):\n sync()\n end\n local poked_point = get_target_tcp_pose()\n poked_point = pose_trans(inv(t_base_target), poked_point)\n A[cnt, 0] = poked_point[0]\n A[cnt, 1] = poked_point[1]\n A[cnt, 2] = 1.0\n b[cnt] = poked_point[2]\n mean_point = mean_point + [poked_point[0], poked_point[1], poked_point[2]]\n movel(new_pos, a = 0.2, v = velocity_move, r = blend_radius)\n cnt = cnt + 1\n end\n mean_point = mean_point / n_plane_points\n cnt = 0\n while cnt < n_plane_points:\n local cntj = 0\n while cntj < 2:\n A[cnt, cntj] = A[cnt, cntj] - mean_point[cntj]\n cntj = cntj + 1\n end\n b[cnt] = b[cnt] - mean_point[2]\n cnt = cnt + 1\n end\n local x1 = inv(transpose(A) * A) * transpose(A) * b\n local x = normalize([x1[0], x1[1], -1])\n local d = dot(mean_point, x)\n local dval = dot(direction, x)\n if dval < 0:\n x = -x\n dval = -dval\n end\n local eaa = [0.0, 0.0, 0.0]\n local EPSILON = 1e-10\n if norm(dval - 1) < EPSILON:\n # if the projection is close to 1 then the angle between the vectors are almost 0 and we cannot\n # reliably determine the perpendicular axis.\n # A good approximation is therefore just to set the EAA equal to 0.\n eaa = [0.0, 0.0, 0.0]\n else:\n local axis = cross(direction, x)\n local eaa = normalize(axis) * acos(dval)\n end\n local t_base_target_aligned = pose_trans(t_base_target, p[0, 0, 0, eaa[0], eaa[1], eaa[2]])\n movel(t_base_target_aligned, a = 0.2, v = velocity_move)\nend\n# End of Align to Plane\n# Start of Align Z to Nearest Axis\n###\n# Aligns the TCP Z axis to the nearest axis of the given frame\n# @param frame_id string frame_id to lookup frame\n###\ndef ur_align_z_to_nearest_axis(frame_id = \"world\"):\n ###\n # Given a reference frame as input this function returns a struct with the nearest\n # pose which aligns the z-axis of the robot TCP with the z-axis of the given reference frame.\n # The pose is in the reference of the given frame.\n # @param frame bool frame\n # @returns struct pose, distance, referencePose\n ###\n def get_aligned_z_pose(frame):\n local actualPose = get_actual_tcp_pose()\n local actualPoseInFrame = pose_trans(pose_inv(frame), actualPose)\n # Create rotation vector and convert that to RPY representation\n local actualRotInFrame = [actualPoseInFrame[3], actualPoseInFrame[4], actualPoseInFrame[5]]\n local actRPY = rotvec2rpy(actualRotInFrame)\n # Set RX and RY to 0 and convert back to rotation vector\n local alignedRot = rpy2rotvec([0, 0, actRPY[2]])\n local alignedRotFlipped = rpy2rotvec([PI, 0, actRPY[2]])\n local zUpPose = actualPoseInFrame\n zUpPose[3] = alignedRot[0]\n zUpPose[4] = alignedRot[1]\n zUpPose[5] = alignedRot[2]\n zUpStruct = struct(pose = zUpPose, distance=pose_dist(actualPoseInFrame, zUpPose), referencePose=frame)\n local zDownPose = actualPoseInFrame\n zDownPose[3] = alignedRotFlipped[0]\n zDownPose[4] = alignedRotFlipped[1]\n zDownPose[5] = alignedRotFlipped[2]\n local zDownStruct = struct(pose = zDownPose, distance=pose_dist(actualPoseInFrame, zDownPose), referencePose=frame)\n # Return the solution which is closer to the current robot pose\n if (zDownStruct.distance > zUpStruct.distance):\n return zUpStruct\n else:\n return zDownStruct\n end\n end\n local frame = get_pose(frame_id)\n # Rotate the given frame so that Z can be align to X-Y-Z respectively \n local rotZtoX = rpy2rotvec([0,0.5*PI,0])\n local rotZtoY = rpy2rotvec([0.5*PI,0,0])\n local rotZtoZ = rpy2rotvec([0,0,0])\n # Get aligned poses for each of the rotated frames\n local structAlignedToX = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoX[0],rotZtoX[1],rotZtoX[2]]))\n structAlignedToY = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoY[0],rotZtoY[1],rotZtoY[2]]))\n structAlignedToZ = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoZ[0],rotZtoZ[1],rotZtoZ[2]]))\n # Find the nearest alignement\n local structAligned = structAlignedToZ\n if(structAligned.distance > structAlignedToX.distance):\n structAligned = structAlignedToX \n end\n if(structAligned.distance > structAlignedToY.distance):\n structAligned = structAlignedToY \n end\n # Move the robot to the aligned pose\n movel(pose_trans(get_actual_tcp_pose(), p[0,0,0.00001,0,0,0]), v = 0.1)\n movel(pose_trans(structAligned.referencePose, structAligned.pose ), v = 0.1)\nend\n# End of Align Z to Nearest Axis\n# Start of Center to Object\n###\n# Centers to an object by touching the externals of it. It works well for fixtured or heavy parts.\n# @param push_force number Force the robot uses to determine if a contact has been achieved\n# @param velocity_move number Velocity in freespace\n# @param velocity_search number First move is used then search\n# @param acc_move number Acceleration in freespace\n# @param max_radius_search number Maximum search radius\n# @param num_fingers number Number of fingers that the gripper has\n###\ndef ur_center_to_object(push_force = 10, velocity_move = 0.10, velocity_search = 0.01, acc_move = 0.2, max_radius_search = 0.05, num_fingers = 3):\n def compute_circle_center(p_list):\n # Compute the circle center by circular regression\n # Source: https://math.stackexchange.com/questions/2898295/how-to-quickly-fit-a-circle-by-given-random-arc-points\n local itr = 0\n local x = 0\n local y = 1\n \n local m1 = [[0,0,0],[0,0,0],[0,0,0]]\n local m2 = [[0,0],[0,0],[0,0]]\n local m3 = [[0],[0],[0]]\n \n while(itr < get_list_length(p_list)):\n local p = p_list[itr]\n \n if(p_list[itr] == p[0,0,0,0,0,0]):\n break\n end\n \n m1[0,0] = m1[0,0] + (p[x]*p[x])\n m1[0,1] = m1[0,1] + (p[x]*p[y])\n m1[0,2] = m1[0,2] + (p[x])\n \n m1[1,0] = m1[1,0] + (p[x]*p[y])\n m1[1,1] = m1[1,1] + (p[y]*p[y])\n m1[1,2] = m1[1,2] + (p[y])\n \n m1[2,0] = m1[2,0] + (p[x])\n m1[2,1] = m1[2,1] + (p[y])\n \n m2[0,0] = m2[0,0] + (pow(p[x], 3))\n m2[0,1] = m2[0,1] + (p[x] * pow(p[y], 2))\n \n m2[1,0] = m2[1,0] + (pow(p[y], 3))\n m2[1,1] = m2[1,1] + (pow(p[x], 2) * p[y])\n \n m2[2,0] = m2[2,0] + (pow(p[x], 2))\n m2[2,1] = m2[2,1] + (pow(p[y], 2))\n \n itr = itr +1\n end\n \n if(itr < 2):\n return p[0,0,0,0,0,0]\n elif(itr > get_list_length(p_list)):\n return p[0,0,0,0,0,0]\n end\n \n m1[0,0] = 2 * m1[0,0]\n m1[0,1] = 2 * m1[0,1]\n m1[1,0] = 2 * m1[1,0]\n m1[1,1] = 2 * m1[1,1]\n m1[2,0] = 2 * m1[2,0]\n m1[2,1] = 2 * m1[2,1]\n m1[2,2] = itr\n m3[0,0] = m2[0,0] + m2[0,1]\n m3[1,0] = m2[1,0] + m2[1,1]\n m3[2,0] = m2[2,0] + m2[2,1]\n \n local center = inv(m1) * m3\n \n return p[center[0,0], center[1,0],0,0,0,0]\n end\n \n def sanity_checked_move(p_org, p_new, max_diff, acc, vel):\n if (pose_dist(p_org, p_new) > max_diff):\n movel(p_org, a = acc, v = vel)\n popup(\"New pose is too far away from original. Returning to original\", title = \"Failed\", warning = False, error = True, blocking = True)\n else:\n movel(p_new, a = acc, v = vel)\n end\n end\n # Start by zeroing the FT sensor\n sleep(0.25)\n zero_ftsensor()\n local p_start = get_actual_tcp_pose()\n local p0 = p[0,0,0,0,0,0]\n local DIR_X = [1, 0, 0]\n if (num_fingers == 2):\n local dir_list = [DIR_X, -DIR_X, DIR_X, -DIR_X]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0]\n elif (num_fingers == 3):\n local DIR_P1 = DIR_X\n local DIR_P2 = [-1 / 2, sqrt(3.0) / 2.0, 0]\n local DIR_P3 = [-1 / 2, -sqrt(3.0) / 2.0, 0]\n local dir_list = [DIR_P1, DIR_P2, DIR_P3, DIR_P1, DIR_P2, DIR_P3]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0, p0, p0]\n else:\n popup(\"Number of fingers not supported\")\n halt\n end\n # Loop through directions\n local it = 0\n local dir_list_size = size(dir_list)\n local dir_list_length = dir_list_size[0]\n while(it < dir_list_length):\n # Move to starting position if more than 3 positions is stored then calculate a new starting position\n if(it < 3):\n movel(pose_trans(p_start, start_offset[it]), a = acc_move, v = velocity_move)\n else:\n local p_start_temp = pose_trans(pose_trans(p_start, compute_circle_center(p_list)), start_offset[it])\n local p_start_w_offset = pose_trans(p_start, start_offset[it])\n sanity_checked_move(p_start_w_offset, p_start_temp, max_radius_search, acc_move, velocity_move)\n end\n local p_start_temp = get_actual_tcp_pose()\n # Move into contact and store contact point\n sleep(0.1)\n local contact_point = ur_move_until_force(distance = max_radius_search, direction = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]], velocity = velocity_search, acceleration = acc_move, stop_force = push_force)\n \n local dir = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]]\n dir = normalize(dir) * 0.05\n contact_point = pose_trans(contact_point, p[dir[0], dir[1], dir[2], 0, 0, 0])\n p_list[it] = pose_trans(pose_inv(p_start), contact_point)\n # Move out of contact\n movel(p_start_temp, a = acc_move, v = velocity_move)\n it = it + 1\n end\n # Find circle center based on n stored points\n local center_offset_xy = compute_circle_center(p_list)\n local p_center = pose_trans(p_start, center_offset_xy)\n \n # Move the robot to the center if it can\n sanity_checked_move(p_start, p_center, max_radius_search, acc_move, velocity_move)\nend\n# End of Center to Object\n# Start of Move Into Contact\n###\n# Moves the robot into contact in the TCP direction set\n# @param force number Force that determines when a contact has been achieved\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_search number velocity_search\n# @param retract number Retract distance after a contact has been found\n# @param move_tcp_dir array TCP direction (3D vector)\n# @param zero_ft_on_start bool Determines if the force-torque sensor should be zeroed on start\n###\ndef ur_move_into_contact(force = 10, velocity = 0.05, acceleration = 0.1, max_distance = 0.25, retract = 0.0, move_tcp_dir = [0, 0, 1], zero_ft_on_start = True):\n # Zero the force torque sensor\n if (zero_ft_on_start):\n sleep(0.25)\n zero_ftsensor()\n end\n # Move the robot\n ur_move_until_force(max_distance, move_tcp_dir, velocity, acceleration, force)\n # If a retract distance is set, move the robot back to that position\n if (retract != 0):\n # Compute position offset from TCP direction and retract distance\n local position = normalize(move_tcp_dir) * retract\n movel(pose_trans(get_actual_tcp_pose(), p[position[0], position[1], position[2], 0, 0, 0]))\n end\nend\n# End of Move Into Contact\n# Start of Retract\n###\n# Retract in the TCP direction set\n# @param distance number Retraction distance\n# @param direction array TCP direction to move in (3D vector)\n# @param acceleration number Acceleration used by the robot\n# @param velocity number Velocity used by the robot\n###\ndef ur_retract(distance = -0.1, direction = [0, 0, 1], acceleration = 0.4, velocity = 0.1):\n local movement = normalize(direction) * distance\n movel(pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]), a = acceleration, v = velocity)\nend\n# End of Retract", - "nodeIDList": [] - } - }, - "program": { - "id": "5", - "programContent": { - "children": [ - { - "children": [], - "contributedNode": { - "type": "ur-modules", - "version": "0.0.1", - "allowsChildren": true, - "lockChildren": false - }, - "guid": "e98f5594-3223-dccd-3a54-e4a0ebb448ae", - "parentId": "563d78e2-b8b7-818d-6886-d3d169c13afb" - }, - { - "children": [], - "contributedNode": { - "type": "ur-functions", - "version": "0.0.1", - "allowsChildren": true, - "lockChildren": false - }, - "guid": "f486e2c5-15bd-418e-bbce-fdb9b1bf0b36", - "parentId": "563d78e2-b8b7-818d-6886-d3d169c13afb" - }, - { - "children": [], - "contributedNode": { - "type": "ur-before-start", - "version": "0.0.1", - "allowsChildren": true - }, - "guid": "f37a4466-36c4-fecd-db39-70c813c3e4cf", - "parentId": "563d78e2-b8b7-818d-6886-d3d169c13afb" - }, - { - "children": [], - "contributedNode": { - "type": "ur-configuration", - "version": "0.0.1", - "allowsChildren": true, - "parameters": {} - }, - "guid": "afe54a28-c4e9-e549-5537-623168b12932", - "parentId": "563d78e2-b8b7-818d-6886-d3d169c13afb" - }, - { - "children": [], - "contributedNode": { - "type": "ur-status", - "version": "0.0.1", - "allowsChildren": true, - "parameters": {} - }, - "guid": "9560e234-e99c-194a-61ab-6d9086f9cc3b", - "parentId": "563d78e2-b8b7-818d-6886-d3d169c13afb" - }, - { - "children": [ - { - "children": [], - "contributedNode": { - "type": "ur-wait", - "version": "0.0.3", - "parameters": { - "type": "time", - "time": { - "entity": { - "value": 23, - "unit": "s" - }, - "selectedType": "VALUE", - "value": "23" - } - } - }, - "guid": "fb05e537-a239-564a-9f4a-8241657bb343", - "parentId": "6a3b8974-6b6b-a47d-0e37-f636c0bec306", - "programLabel": [ - { - "type": "secondary", - "value": "23.00 s" - } - ] - } - ], - "contributedNode": { - "type": "ur-code", - "version": "0.0.1", - "allowsChildren": true, - "lockChildren": false, - "parameters": { - "loopForever": false - } - }, - "guid": "6a3b8974-6b6b-a47d-0e37-f636c0bec306", - "parentId": "563d78e2-b8b7-818d-6886-d3d169c13afb" - } - ], - "contributedNode": { - "type": "ur-program", - "version": "0.0.2", - "allowsChildren": true, - "lockChildren": true, - "parameters": { - "name": "Default program", - "symbolHistory": { - "variables": [ - { - "name": "base", - "valueType": "frame", - "reference": { - "id": "4aa883ca-3eba-49ec-bbcc-ee1ee65176ed", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "tcp", - "valueType": "frame", - "reference": { - "id": "551721e6-e865-fe61-5a00-85c8f3fcb7ce", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "world", - "valueType": "frame", - "reference": { - "id": "15d05e2b-ad37-603f-cfbd-3b344fdee9d4", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "flange", - "valueType": "frame", - "reference": { - "id": "af9509cc-f84c-71b2-3578-10a8172db29b", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "grid", - "valueType": "grid", - "reference": { - "id": "17606307-89d9-eb68-6cfb-918c0ddbed4c", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "grid_iterator", - "valueType": "waypoint", - "reference": { - "id": "74c6d3f7-b0ad-09c5-4bf6-2acc5848f9d5", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "Joint_fast", - "valueType": "profile", - "reference": { - "id": "9cadc383-da1d-d089-1320-9760cfb9fe0e", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "Joint_slow", - "valueType": "profile", - "reference": { - "id": "9f7cd9c1-6b40-8ecf-cc23-dd5e94791815", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "Linear_fast", - "valueType": "profile", - "reference": { - "id": "bbc53484-5136-9422-baed-131c092effdc", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "Linear_slow", - "valueType": "profile", - "reference": { - "id": "a4ce2084-6f47-6b6b-1e85-cb174d0397b3", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "Process", - "valueType": "profile", - "reference": { - "id": "9e716d40-7af5-7806-2a39-f1d98dce92dd", - "_IDENTIFIER": "VariableReference" - } - }, - { - "name": "Home", - "valueType": "waypoint", - "reference": { - "id": "038b8cab-70ae-6958-23c8-174fa0b397d1", - "_IDENTIFIER": "VariableReference" - } - } - ], - "functions": [], - "modules": [ - { - "name": "application", - "reference": { - "id": "application-module-id", - "_IDENTIFIER": "ModuleReference" - } - } - ] - } - } - }, - "guid": "563d78e2-b8b7-818d-6886-d3d169c13afb" - }, - "programInformation": { - "name": "test upload", - "description": "", - "createdDate": 1771239851763, - "lastSavedDate": 1771242227312, - "lastModifiedDate": null, - "programState": "FINAL", - "functionsBlockShown": false - }, - "urscript": { - "script": "$ 1 \"ur-program\"\n$ 2 \"ur-modules\"\n$ 3 \"ur-functions\"\n$ 4 \"ur-before-start\"\n$ 5 \"ur-code\"\n$ 6 \"ur-wait\"\nsleep(23)", - "nodeIDList": [ - "00000000-0000-0000-0000-000000000000", - "563d78e2-b8b7-818d-6886-d3d169c13afb", - "e98f5594-3223-dccd-3a54-e4a0ebb448ae", - "f486e2c5-15bd-418e-bbce-fdb9b1bf0b36", - "f37a4466-36c4-fecd-db39-70c813c3e4cf", - "6a3b8974-6b6b-a47d-0e37-f636c0bec306", - "fb05e537-a239-564a-9f4a-8241657bb343" - ] - } - } -} +{"application":{"id":"3","applicationInfo":{"name":"upload_application","robotType":"UR5","createdDate":1786960428682,"lastModifiedDate":1786960455907,"defaultProgramId":null},"applicationContent":{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{}},"activeOperatorScreen":"ur-operator-screen-default","smartSkills":[{"name":"Align to Plane","enabled":true,"type":"ur-align-to-plane","parameters":{"radius":0.05,"push_force":20,"n_plane_points":3,"max_distance":0.25,"velocity_slow":0.001,"velocity_search":0.035,"velocity_move":0.1,"acceleration":0.1}},{"name":"Align Z to Nearest Axis","enabled":true,"type":"ur-align-z-to-nearest-axis"},{"name":"Center","enabled":true,"type":"ur-center","parameters":{"push_force":10,"velocity_move":0.05,"acc_move":0.2,"max_radius_search":0.05,"num_fingers":3}},{"name":"Freedrive","enabled":true,"type":"ur-freedrive","version":"1.0.0","recordingFrequency":50,"recordingSignals":["timestamp","target_q","actual_TCP_pose","tcp_offset"]},{"name":"Move into Contact","enabled":true,"type":"ur-move-into-contact","parameters":{"force":10,"velocity":0.05,"acceleration":0.2,"max_distance":0.25,"retract":0},"version":"1.0.0"},{"name":"Retract","enabled":true,"type":"ur-retract","parameters":{"distance":0.1,"acceleration":0.4,"velocity":0.1},"version":"1.0.0"},{"name":"Put into Box","enabled":false,"type":"ur-put-in-box","version":"1.0.0"},{"name":"Custom","enabled":false,"type":"ur-custom-smart-skill","parameters":{"includePreamble":true,"includeModules":false},"version":"1.0.0"},{"name":"Home","enabled":true,"type":"ur-position","version":"1.1.2","parameters":{"actualWaypoint":{"frame":"base","pose":{"position":[-1.8246917738038495e-9,-0.2329000001676105,1.0793999999522315],"orientation":[3.987257497300885e-9,2.2214414675120993,-2.221441467056474]},"qNear":{"base":0,"shoulder":-1.5707963249999999,"elbow":0,"wrist1":-1.5707963249999999,"wrist2":0,"wrist3":0}},"variable":{"name":"Home","reference":false,"type":"$$Variable","valueType":"waypoint","id":"038b8cab-70ae-6958-23c8-174fa0b397d1","_IDENTIFIER":"VariableDeclaration"}}}],"safety":{"settings":{"io":{"automaticModeSafeguardResetInput":{"name":"automaticModeSafeguardResetInput","valueA":255,"valueB":255},"automaticModeSafeguardStopInput":{"name":"automaticModeSafeguardStopInput","valueA":255,"valueB":255},"emergencyStopInput":{"name":"emergencyStopInput","valueA":255,"valueB":255},"notReducedModeOutput":{"name":"notReducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"operationalModeInput":{"name":"operationalModeInput","valueA":255,"valueB":255},"reducedModeInput":{"name":"reducedModeInput","valueA":255,"valueB":255},"reducedModeOutput":{"name":"reducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotMovingOutput":{"name":"robotMovingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotNotStoppingOutput":{"name":"robotNotStoppingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeHomeOutput":{"name":"safeHomeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeguardResetInput":{"name":"safeguardResetInput","valueA":0,"valueB":1},"systemEmergencyStoppedOutput":{"name":"systemEmergencyStoppedOutput","ossdEnabled":false,"valueA":255,"valueB":255},"threePositionSwitchInput":{"name":"threePositionSwitchInput","valueA":255,"valueB":255},"freedriveEnabledInput":{"name":"freedriveEnabledInput","valueA":255,"valueB":255},"threePositionEnablingStopOutput":{"name":"threePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255},"notThreePositionEnablingStopOutput":{"name":"notThreePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255}},"major":5,"minor":13,"normalJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"normalJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"normalRobotLimits":{"elbowForce":150,"elbowSpeed":1.5,"momentum":25,"power":300,"stoppingDistance":0.5,"stoppingTime":0.4,"toolForce":150,"toolSpeed":1.5},"reducedJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"reducedJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"reducedRobotLimits":{"elbowForce":120,"elbowSpeed":0.75,"momentum":10,"power":200,"stoppingDistance":0.3,"stoppingTime":0.3,"toolForce":120,"toolSpeed":0.75},"safetyHardware":{"injectionMoldingMachineInterface":"NONE","teachPendant":"NORMAL"},"safetyPlanes":{"planes":[{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"}],"ioSafetyPlanes":[{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"}]},"safetySafeHome":{"base":-1,"elbow":-1,"shoulder":-1,"wrist1":-1,"wrist2":-1,"wrist3":-1,"enabled":false},"safetyAPIParameters":{"numberOfClients":0,"clients":[]},"safetyFieldbusses":{"enablePROFIsafe":false,"sourceAddressPROFIsafe":0,"destAddressPROFIsafe":0,"modeControlPROFIsafe":false},"threePosition":{"allowManualHighSpeed":true,"useTeachPendantAs3PE":false},"toolDirection":{"limitDeviation":6.2831855,"limitDirection":{"x":0,"y":0,"z":1},"limitRestriction":"DISABLED","toolPan":0,"toolTilt":0},"toolPositions":{"toolPositions":[{"name":"Tool Flange","center":{"x":0,"y":0,"z":0},"radius":0,"definition":2},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0}]},"normalWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"},"reducedWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"}},"crc":"633835311","confirmed":true},"operatorScreens":[{"type":"ur-operator-screen-default","version":"0.0.2","parameters":{"status":[],"configuration":[]}}],"applicationContributions":{"ur-mounting":{"type":"ur-mounting","version":"0.0.1","mounting":{"baseAngle":{"value":0,"unit":"deg"},"tiltAngle":{"value":0,"unit":"deg"}}},"ur-frames":{"type":"ur-frames","version":"0.0.7","framesList":[{"name":"base","nameVariable":{"name":"base","reference":false,"type":"$$Variable","valueType":"frame","id":"4aa883ca-3eba-49ec-bbcc-ee1ee65176ed","_IDENTIFIER":"VariableDeclaration"},"parent":"world","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"tcp","nameVariable":{"name":"tcp","reference":false,"type":"$$Variable","valueType":"frame","id":"551721e6-e865-fe61-5a00-85c8f3fcb7ce","_IDENTIFIER":"VariableDeclaration"},"parent":"flange","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"world","nameVariable":{"name":"world","reference":false,"type":"$$Variable","valueType":"frame","id":"15d05e2b-ad37-603f-cfbd-3b344fdee9d4","_IDENTIFIER":"VariableDeclaration"},"pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"flange","nameVariable":{"name":"flange","reference":false,"type":"$$Variable","valueType":"frame","id":"af9509cc-f84c-71b2-3578-10a8172db29b","_IDENTIFIER":"VariableDeclaration"},"parent":"base","pose":{"position":[0,0,0],"orientation":[0,0,0]}}]},"ur-grid-pattern":{"type":"ur-grid-pattern","version":"0.0.3","grids":[{"grid":{"name":"grid","reference":false,"type":"$$Variable","valueType":"grid","id":"17606307-89d9-eb68-6cfb-918c0ddbed4c","_IDENTIFIER":"VariableDeclaration"},"waypoint":{"name":"grid_iterator","reference":false,"type":"$$Variable","valueType":"waypoint","id":"74c6d3f7-b0ad-09c5-4bf6-2acc5848f9d5","_IDENTIFIER":"VariableDeclaration"},"corners":[null,null,null,null],"numRows":4,"numColumns":5}]},"ur-end-effector":{"type":"ur-end-effector","version":"0.0.2","endEffectors":[{"id":"d2a7e55f-5c8b-3fb9-d898-93ce3c841a6c","name":"Robot","payload":{"weight":{"value":0,"unit":"kg"}},"cog":{"cx":{"value":0,"unit":"m"},"cy":{"value":0,"unit":"m"},"cz":{"value":0,"unit":"m"}},"inertia":{"Ixx":{"value":0,"unit":"kg*m^2"},"Iyy":{"value":0,"unit":"kg*m^2"},"Izz":{"value":0,"unit":"kg*m^2"},"Ixy":{"value":0,"unit":"kg*m^2"},"Ixz":{"value":0,"unit":"kg*m^2"},"Iyz":{"value":0,"unit":"kg*m^2"}},"useCustomInertia":false,"tcps":[{"id":"4076e4c7-d851-a441-2776-902faa191f9a","name":"Tool_flange","x":{"value":0,"unit":"m"},"y":{"value":0,"unit":"m"},"z":{"value":0,"unit":"m"},"rx":{"value":0,"unit":"rad"},"ry":{"value":0,"unit":"rad"},"rz":{"value":0,"unit":"rad"}}]}],"defaultTcp":{"endEffectorId":"d2a7e55f-5c8b-3fb9-d898-93ce3c841a6c","tcpId":"4076e4c7-d851-a441-2776-902faa191f9a"}},"ur-motion-profiles":{"type":"ur-motion-profiles","version":"0.0.1","moveProfiles":{"joint":[{"isDefault":false,"profile":{"name":"Joint_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"9cadc383-da1d-d089-1320-9760cfb9fe0e","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Joint_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"9f7cd9c1-6b40-8ecf-cc23-dd5e94791815","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"linear":[{"isDefault":false,"profile":{"name":"Linear_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"bbc53484-5136-9422-baed-131c092effdc","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Linear_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"a4ce2084-6f47-6b6b-1e85-cb174d0397b3","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"process":[{"isDefault":true,"profile":{"name":"Process","reference":false,"type":"$$Variable","valueType":"profile","id":"9e716d40-7af5-7806-2a39-f1d98dce92dd","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"Classic","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2}}}]}},"ur-smart-skills":{"type":"ur-smart-skills","version":"0.0.3","preamble":"# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper"},"ur-application-variables":{"type":"ur-application-variables","version":"0.0.1","variables":{}},"universal-robots-external-control-external-control-application":{"type":"universal-robots-external-control-external-control-application","version":"1.0.0","port":50002,"robotIP":"192.168.56.1"}},"sidebarItems":[{"type":"ur-global-variables","version":"1.0.0","disabled":{"master":false,"automaticMode":false,"remoteMode":true}},{"type":"ur-log-messages-sidebar","version":"0.0.1","disabled":{"master":true,"automaticMode":true,"remoteMode":true}}],"logicPrograms":{"5856afd9-121e-dbbc-c950-18fdf9809bc7":{"id":"5856afd9-121e-dbbc-c950-18fdf9809bc7","programContent":{"children":[{"children":[],"contributedNode":{"type":"ur-modules","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"39a97984-ceea-e4e0-8a72-cdc82fe42f3f","parentId":"dd58a79d-5479-66fb-b452-eed822a2fd43","programType":"logic"},{"children":[],"contributedNode":{"type":"ur-functions","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"c4914329-7f3a-67f3-f478-38683f4b4acb","parentId":"dd58a79d-5479-66fb-b452-eed822a2fd43","programType":"logic"},{"children":[],"contributedNode":{"type":"ur-before-start","version":"0.0.1","allowsChildren":true},"guid":"f5049caf-a2a2-9215-7ff7-9813b8057bb8","parentId":"dd58a79d-5479-66fb-b452-eed822a2fd43","programType":"logic"},{"children":[],"contributedNode":{"type":"ur-logic-program","version":"0.0.1","allowsChildren":true,"parameters":{"logicProgram":{"name":"Logic_Program","reference":false,"type":"$$LogicProgram"}}},"guid":"359b1ea1-1cad-1d29-d0df-3f99a18df227","parentId":"dd58a79d-5479-66fb-b452-eed822a2fd43","programType":"logic"}],"contributedNode":{"type":"ur-logic-programs","version":"0.0.1","allowsChildren":true,"lockChildren":true,"parameters":{"name":""}},"guid":"dd58a79d-5479-66fb-b452-eed822a2fd43","programType":"logic"},"programInformation":{"name":"Logic_Program","description":"","programState":"FINAL","functionsBlockShown":false,"createdDate":0,"lastSavedDate":0,"lastModifiedDate":0},"urscript":{"script":"$ 1 \"ur-logic-programs\"\n$ 2 \"ur-modules\"\n$ 3 \"ur-functions\"\n$ 4 \"ur-before-start\"\n$ 5 \"ur-logic-program\"\nwhile (True):\nend","nodeIDList":["00000000-0000-0000-0000-000000000000","dd58a79d-5479-66fb-b452-eed822a2fd43","39a97984-ceea-e4e0-8a72-cdc82fe42f3f","c4914329-7f3a-67f3-f478-38683f4b4acb","f5049caf-a2a2-9215-7ff7-9813b8057bb8","359b1ea1-1cad-1d29-d0df-3f99a18df227"]}}},"sourcesNodes":{"robot":{"groupId":"robot","version":"1.0.0.","sources":[{"sourceID":"ur-wired-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"},{"signalID":"AO 0","direction":"OUT","valueType":"FLOAT"},{"signalID":"AO 1","direction":"OUT","valueType":"FLOAT"}],"webSocketURL":"/sources/wired-io"},{"sourceID":"ur-tool-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"}],"webSocketURL":"/sources/tool-io"}],"isDynamic":false},"ur-modbus":{"groupId":"ur-modbus","isDynamic":true,"version":"1.0.0","sources":[]},"ur-robot-io":{"type":"ur-robot-io","groupId":"ur-robot-io","isDynamic":false,"version":"1.0.3","sources":[{"sourceID":"ur-robot-wired-io","name":"Wired I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 0","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 1","valueType":"FLOAT"}]},{"sourceID":"ur-robot-tool-io","name":"Tool I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"}]}],"parameters":{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{},"toolOutput":{"dualPinPower":false,"voltage":{"value":0,"unit":"V"},"powerOutput":{"DO 0":1,"DO 1":1}}},"migrateSourceConfigDone":true}}}},"urscript":{"script":"set_safety_mode_transition_hardness(1)\nreset_world_model()\nset_input_actions_to_default()\nset_analog_outputdomain(0,0)\nset_analog_outputdomain(1,0)\nset_standard_analog_input_domain(0,0)\nset_standard_analog_input_domain(1,0)\nset_tool_output_mode(0)\nset_tool_voltage(0)\nset_tool_digital_output_mode(0,1)\nset_tool_digital_output_mode(1,1)\nset_tool_analog_input_domain(0,0)\nset_tool_analog_input_domain(1,0)\nset_gravity([0, 0, 9.82])\nlocal existingBaseParent = get_frame_parent(\"base\")\nlocal basePose = get_pose(\"base\", existingBaseParent)\nbasePose[3] = 0\nbasePose[4] = 0\nbasePose[5] = 0\nmove_frame(\"base\", basePose, existingBaseParent)\nglobal base = \"base\"\nglobal tcp = \"tcp\"\nglobal world = \"world\"\nglobal flange = \"flange\"\nset_target_payload(0, [0, 0, 0], [0, 0, 0, 0, 0, 0])\nset_tcp(p[0, 0, 0, 0, 0, 0], \"Tool_flange\")\n# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper\n# Waypoint variable for Home smart skill\nglobal Home = struct(p=p[-1.8246917738038495e-9, -0.2329000001676105, 1.0793999999522315, 3.987257497300885e-9, 2.2214414675120993, -2.221441467056474], frame=\"base\", q=[0, -1.5707963249999999, 0, -1.5707963249999999, 0, 0])\n# Start of Align to Plane\n###\n# Align to plane will touch up a plane by moving the robot into contact with the table or part in several locations to determine its orientation. Afterwards the robot will orient its tool to the plane.\n# @param radius number Radius [m] of the circle within the plane will be touched up\n# @param push_force number How hard to robot pushed against the plane\n# @param n_plane_points number Number of points that the robot uses to compute the plane\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_slow number Velocity when pressing downwards\n# @param velocity_search number Velocity used when approaching the touch up point\n# @param velocity_move number Velocity used in freespace\n# @param acceleration number Acceleration of the robot\n# @param direction array 3D vector determining the direction of the TCP for touching up the plane\n###\ndef ur_align_to_plane(radius = 0.05, push_force = 20, n_plane_points = 3, max_distance = 0.25, velocity_slow = 0.001, velocity_search = 0.035, velocity_move = 0.10, acceleration = 0.1, direction = [0, 0, 1]):\n local angle = 2 * PI / n_plane_points\n local start_pos = get_target_tcp_pose()\n local retract_distance = -0.015\n ur_move_tcp_direction(retract_distance, direction, velocity_move, acceleration, 0)\n sleep(0.25)\n zero_ftsensor()\n local cnt = 0\n local t_base_target = get_target_tcp_pose()\n local mean_point = [0.0, 0.0, 0.0]\n local A = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]\n local b = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\n while cnt < n_plane_points:\n local new_pos = pose_trans(t_base_target, p[cos(angle * cnt) * radius, sin(angle * cnt) * radius, 0.0, 0.0, 0.0, 0.0])\n local blend_radius = norm(point_dist(get_actual_tcp_pose(), new_pos))/5\n movel(new_pos, a = acceleration, v = velocity_move, r = blend_radius)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_search, acceleration, push_force)\n local movement = normalize(direction * -1) * 0.0005\n local target_pose = pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose)\n sleep(0.2)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_slow, acceleration, push_force)\n sleep(0.2)\n while (not is_steady()):\n sync()\n end\n local poked_point = get_target_tcp_pose()\n poked_point = pose_trans(inv(t_base_target), poked_point)\n A[cnt, 0] = poked_point[0]\n A[cnt, 1] = poked_point[1]\n A[cnt, 2] = 1.0\n b[cnt] = poked_point[2]\n mean_point = mean_point + [poked_point[0], poked_point[1], poked_point[2]]\n movel(new_pos, a = 0.2, v = velocity_move, r = blend_radius)\n cnt = cnt + 1\n end\n mean_point = mean_point / n_plane_points\n cnt = 0\n while cnt < n_plane_points:\n local cntj = 0\n while cntj < 2:\n A[cnt, cntj] = A[cnt, cntj] - mean_point[cntj]\n cntj = cntj + 1\n end\n b[cnt] = b[cnt] - mean_point[2]\n cnt = cnt + 1\n end\n local x1 = inv(transpose(A) * A) * transpose(A) * b\n local x = normalize([x1[0], x1[1], -1])\n local d = dot(mean_point, x)\n local dval = dot(direction, x)\n if dval < 0:\n x = -x\n dval = -dval\n end\n local eaa = [0.0, 0.0, 0.0]\n local EPSILON = 1e-10\n if norm(dval - 1) < EPSILON:\n # if the projection is close to 1 then the angle between the vectors are almost 0 and we cannot\n # reliably determine the perpendicular axis.\n # A good approximation is therefore just to set the EAA equal to 0.\n eaa = [0.0, 0.0, 0.0]\n else:\n local axis = cross(direction, x)\n local eaa = normalize(axis) * acos(dval)\n end\n local t_base_target_aligned = pose_trans(t_base_target, p[0, 0, 0, eaa[0], eaa[1], eaa[2]])\n movel(t_base_target_aligned, a = 0.2, v = velocity_move)\nend\n# End of Align to Plane\n# Start of Align Z to Nearest Axis\n###\n# Aligns the TCP Z axis to the nearest axis of the given frame\n# @param frame_id string frame_id to lookup frame\n###\ndef ur_align_z_to_nearest_axis(frame_id = \"world\"):\n ###\n # Given a reference frame as input this function returns a struct with the nearest\n # pose which aligns the z-axis of the robot TCP with the z-axis of the given reference frame.\n # The pose is in the reference of the given frame.\n # @param frame bool frame\n # @returns struct pose, distance, referencePose\n ###\n def get_aligned_z_pose(frame):\n local actualPose = get_actual_tcp_pose()\n local actualPoseInFrame = pose_trans(pose_inv(frame), actualPose)\n # Create rotation vector and convert that to RPY representation\n local actualRotInFrame = [actualPoseInFrame[3], actualPoseInFrame[4], actualPoseInFrame[5]]\n local actRPY = rotvec2rpy(actualRotInFrame)\n # Set RX and RY to 0 and convert back to rotation vector\n local alignedRot = rpy2rotvec([0, 0, actRPY[2]])\n local alignedRotFlipped = rpy2rotvec([PI, 0, actRPY[2]])\n local zUpPose = actualPoseInFrame\n zUpPose[3] = alignedRot[0]\n zUpPose[4] = alignedRot[1]\n zUpPose[5] = alignedRot[2]\n zUpStruct = struct(pose = zUpPose, distance=pose_dist(actualPoseInFrame, zUpPose), referencePose=frame)\n local zDownPose = actualPoseInFrame\n zDownPose[3] = alignedRotFlipped[0]\n zDownPose[4] = alignedRotFlipped[1]\n zDownPose[5] = alignedRotFlipped[2]\n local zDownStruct = struct(pose = zDownPose, distance=pose_dist(actualPoseInFrame, zDownPose), referencePose=frame)\n # Return the solution which is closer to the current robot pose\n if (zDownStruct.distance > zUpStruct.distance):\n return zUpStruct\n else:\n return zDownStruct\n end\n end\n local frame = get_pose(frame_id)\n # Rotate the given frame so that Z can be align to X-Y-Z respectively \n local rotZtoX = rpy2rotvec([0,0.5*PI,0])\n local rotZtoY = rpy2rotvec([0.5*PI,0,0])\n local rotZtoZ = rpy2rotvec([0,0,0])\n # Get aligned poses for each of the rotated frames\n local structAlignedToX = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoX[0],rotZtoX[1],rotZtoX[2]]))\n structAlignedToY = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoY[0],rotZtoY[1],rotZtoY[2]]))\n structAlignedToZ = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoZ[0],rotZtoZ[1],rotZtoZ[2]]))\n # Find the nearest alignement\n local structAligned = structAlignedToZ\n if(structAligned.distance > structAlignedToX.distance):\n structAligned = structAlignedToX \n end\n if(structAligned.distance > structAlignedToY.distance):\n structAligned = structAlignedToY \n end\n # Move the robot to the aligned pose\n movel(pose_trans(get_actual_tcp_pose(), p[0,0,0.00001,0,0,0]), v = 0.1)\n movel(pose_trans(structAligned.referencePose, structAligned.pose ), v = 0.1)\nend\n# End of Align Z to Nearest Axis\n# Start of Center to Object\n###\n# Centers to an object by touching the externals of it. It works well for fixtured or heavy parts.\n# @param push_force number Force the robot uses to determine if a contact has been achieved\n# @param velocity_move number Velocity in freespace\n# @param velocity_search number First move is used then search\n# @param acc_move number Acceleration in freespace\n# @param max_radius_search number Maximum search radius\n# @param num_fingers number Number of fingers that the gripper has\n###\ndef ur_center_to_object(push_force = 10, velocity_move = 0.10, velocity_search = 0.01, acc_move = 0.2, max_radius_search = 0.05, num_fingers = 3):\n def compute_circle_center(p_list):\n # Compute the circle center by circular regression\n # Source: https://math.stackexchange.com/questions/2898295/how-to-quickly-fit-a-circle-by-given-random-arc-points\n local itr = 0\n local x = 0\n local y = 1\n \n local m1 = [[0,0,0],[0,0,0],[0,0,0]]\n local m2 = [[0,0],[0,0],[0,0]]\n local m3 = [[0],[0],[0]]\n \n while(itr < get_list_length(p_list)):\n local p = p_list[itr]\n \n if(p_list[itr] == p[0,0,0,0,0,0]):\n break\n end\n \n m1[0,0] = m1[0,0] + (p[x]*p[x])\n m1[0,1] = m1[0,1] + (p[x]*p[y])\n m1[0,2] = m1[0,2] + (p[x])\n \n m1[1,0] = m1[1,0] + (p[x]*p[y])\n m1[1,1] = m1[1,1] + (p[y]*p[y])\n m1[1,2] = m1[1,2] + (p[y])\n \n m1[2,0] = m1[2,0] + (p[x])\n m1[2,1] = m1[2,1] + (p[y])\n \n m2[0,0] = m2[0,0] + (pow(p[x], 3))\n m2[0,1] = m2[0,1] + (p[x] * pow(p[y], 2))\n \n m2[1,0] = m2[1,0] + (pow(p[y], 3))\n m2[1,1] = m2[1,1] + (pow(p[x], 2) * p[y])\n \n m2[2,0] = m2[2,0] + (pow(p[x], 2))\n m2[2,1] = m2[2,1] + (pow(p[y], 2))\n \n itr = itr +1\n end\n \n if(itr < 2):\n return p[0,0,0,0,0,0]\n elif(itr > get_list_length(p_list)):\n return p[0,0,0,0,0,0]\n end\n \n m1[0,0] = 2 * m1[0,0]\n m1[0,1] = 2 * m1[0,1]\n m1[1,0] = 2 * m1[1,0]\n m1[1,1] = 2 * m1[1,1]\n m1[2,0] = 2 * m1[2,0]\n m1[2,1] = 2 * m1[2,1]\n m1[2,2] = itr\n m3[0,0] = m2[0,0] + m2[0,1]\n m3[1,0] = m2[1,0] + m2[1,1]\n m3[2,0] = m2[2,0] + m2[2,1]\n \n local center = inv(m1) * m3\n \n return p[center[0,0], center[1,0],0,0,0,0]\n end\n \n def sanity_checked_move(p_org, p_new, max_diff, acc, vel):\n if (pose_dist(p_org, p_new) > max_diff):\n movel(p_org, a = acc, v = vel)\n popup(\"New pose is too far away from original. Returning to original\", title = \"Failed\", warning = False, error = True, blocking = True)\n else:\n movel(p_new, a = acc, v = vel)\n end\n end\n # Start by zeroing the FT sensor\n sleep(0.25)\n zero_ftsensor()\n local p_start = get_actual_tcp_pose()\n local p0 = p[0,0,0,0,0,0]\n local DIR_X = [1, 0, 0]\n if (num_fingers == 2):\n local dir_list = [DIR_X, -DIR_X, DIR_X, -DIR_X]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0]\n elif (num_fingers == 3):\n local DIR_P1 = DIR_X\n local DIR_P2 = [-1 / 2, sqrt(3.0) / 2.0, 0]\n local DIR_P3 = [-1 / 2, -sqrt(3.0) / 2.0, 0]\n local dir_list = [DIR_P1, DIR_P2, DIR_P3, DIR_P1, DIR_P2, DIR_P3]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0, p0, p0]\n else:\n popup(\"Number of fingers not supported\")\n halt\n end\n # Loop through directions\n local it = 0\n local dir_list_size = size(dir_list)\n local dir_list_length = dir_list_size[0]\n while(it < dir_list_length):\n # Move to starting position if more than 3 positions is stored then calculate a new starting position\n if(it < 3):\n movel(pose_trans(p_start, start_offset[it]), a = acc_move, v = velocity_move)\n else:\n local p_start_temp = pose_trans(pose_trans(p_start, compute_circle_center(p_list)), start_offset[it])\n local p_start_w_offset = pose_trans(p_start, start_offset[it])\n sanity_checked_move(p_start_w_offset, p_start_temp, max_radius_search, acc_move, velocity_move)\n end\n local p_start_temp = get_actual_tcp_pose()\n # Move into contact and store contact point\n sleep(0.1)\n local contact_point = ur_move_until_force(distance = max_radius_search, direction = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]], velocity = velocity_search, acceleration = acc_move, stop_force = push_force)\n \n local dir = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]]\n dir = normalize(dir) * 0.05\n contact_point = pose_trans(contact_point, p[dir[0], dir[1], dir[2], 0, 0, 0])\n p_list[it] = pose_trans(pose_inv(p_start), contact_point)\n # Move out of contact\n movel(p_start_temp, a = acc_move, v = velocity_move)\n it = it + 1\n end\n # Find circle center based on n stored points\n local center_offset_xy = compute_circle_center(p_list)\n local p_center = pose_trans(p_start, center_offset_xy)\n \n # Move the robot to the center if it can\n sanity_checked_move(p_start, p_center, max_radius_search, acc_move, velocity_move)\nend\n# End of Center to Object\n# Start of Move Into Contact\n###\n# Moves the robot into contact in the TCP direction set\n# @param force number Force that determines when a contact has been achieved\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_search number velocity_search\n# @param retract number Retract distance after a contact has been found\n# @param move_tcp_dir array TCP direction (3D vector)\n# @param zero_ft_on_start bool Determines if the force-torque sensor should be zeroed on start\n###\ndef ur_move_into_contact(force = 10, velocity = 0.05, acceleration = 0.1, max_distance = 0.25, retract = 0.0, move_tcp_dir = [0, 0, 1], zero_ft_on_start = True):\n # Zero the force torque sensor\n if (zero_ft_on_start):\n sleep(0.25)\n zero_ftsensor()\n end\n # Move the robot\n ur_move_until_force(max_distance, move_tcp_dir, velocity, acceleration, force)\n # If a retract distance is set, move the robot back to that position\n if (retract != 0):\n # Compute position offset from TCP direction and retract distance\n local position = normalize(move_tcp_dir) * retract\n movel(pose_trans(get_actual_tcp_pose(), p[position[0], position[1], position[2], 0, 0, 0]))\n end\nend\n# End of Move Into Contact\n# Start of Retract\n###\n# Retract in the TCP direction set\n# @param distance number Retraction distance\n# @param direction array TCP direction to move in (3D vector)\n# @param acceleration number Acceleration used by the robot\n# @param velocity number Velocity used by the robot\n###\ndef ur_retract(distance = -0.1, direction = [0, 0, 1], acceleration = 0.4, velocity = 0.1):\n local movement = normalize(direction) * distance\n movel(pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]), a = acceleration, v = velocity)\nend\n# End of Retract","nodeIDList":[]}},"program":{"id":"5","programContent":{"guid":"563d78e2-b8b7-818d-6886-d3d169c13afb","contributedNode":{"type":"ur-program","version":"0.0.3","allowsChildren":true,"lockChildren":true,"parameters":{"name":"Default program","symbolHistory":{"variables":[{"name":"base","valueType":"frame","reference":{"id":"4aa883ca-3eba-49ec-bbcc-ee1ee65176ed","_IDENTIFIER":"VariableReference"}},{"name":"tcp","valueType":"frame","reference":{"id":"551721e6-e865-fe61-5a00-85c8f3fcb7ce","_IDENTIFIER":"VariableReference"}},{"name":"world","valueType":"frame","reference":{"id":"15d05e2b-ad37-603f-cfbd-3b344fdee9d4","_IDENTIFIER":"VariableReference"}},{"name":"flange","valueType":"frame","reference":{"id":"af9509cc-f84c-71b2-3578-10a8172db29b","_IDENTIFIER":"VariableReference"}},{"name":"grid","valueType":"grid","reference":{"id":"17606307-89d9-eb68-6cfb-918c0ddbed4c","_IDENTIFIER":"VariableReference"}},{"name":"grid_iterator","valueType":"waypoint","reference":{"id":"74c6d3f7-b0ad-09c5-4bf6-2acc5848f9d5","_IDENTIFIER":"VariableReference"}},{"name":"Joint_fast","valueType":"profile","reference":{"id":"9cadc383-da1d-d089-1320-9760cfb9fe0e","_IDENTIFIER":"VariableReference"}},{"name":"Joint_slow","valueType":"profile","reference":{"id":"9f7cd9c1-6b40-8ecf-cc23-dd5e94791815","_IDENTIFIER":"VariableReference"}},{"name":"Linear_fast","valueType":"profile","reference":{"id":"bbc53484-5136-9422-baed-131c092effdc","_IDENTIFIER":"VariableReference"}},{"name":"Linear_slow","valueType":"profile","reference":{"id":"a4ce2084-6f47-6b6b-1e85-cb174d0397b3","_IDENTIFIER":"VariableReference"}},{"name":"Process","valueType":"profile","reference":{"id":"9e716d40-7af5-7806-2a39-f1d98dce92dd","_IDENTIFIER":"VariableReference"}},{"name":"Home","valueType":"waypoint","reference":{"id":"038b8cab-70ae-6958-23c8-174fa0b397d1","_IDENTIFIER":"VariableReference"}}],"functions":[],"modules":[{"name":"application","reference":{"id":"application-module-id","_IDENTIFIER":"ModuleReference"}}]}}},"children":[{"children":[],"contributedNode":{"type":"ur-modules","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"e98f5594-3223-dccd-3a54-e4a0ebb448ae","parentId":"563d78e2-b8b7-818d-6886-d3d169c13afb"},{"children":[],"contributedNode":{"type":"ur-functions","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"f486e2c5-15bd-418e-bbce-fdb9b1bf0b36","parentId":"563d78e2-b8b7-818d-6886-d3d169c13afb"},{"children":[],"contributedNode":{"type":"ur-before-start","version":"0.0.1","allowsChildren":true},"guid":"f37a4466-36c4-fecd-db39-70c813c3e4cf","parentId":"563d78e2-b8b7-818d-6886-d3d169c13afb"},{"children":[],"contributedNode":{"type":"ur-configuration","version":"0.0.1","allowsChildren":true,"parameters":{}},"guid":"afe54a28-c4e9-e549-5537-623168b12932","parentId":"563d78e2-b8b7-818d-6886-d3d169c13afb"},{"children":[],"contributedNode":{"type":"ur-status","version":"0.0.1","allowsChildren":true,"parameters":{}},"guid":"9560e234-e99c-194a-61ab-6d9086f9cc3b","parentId":"563d78e2-b8b7-818d-6886-d3d169c13afb"},{"children":[{"children":[],"contributedNode":{"type":"ur-wait","version":"0.0.4","parameters":{"type":"time","time":{"entity":{"value":23,"unit":"s"},"selectedType":"VALUE","value":"23"}}},"guid":"fb05e537-a239-564a-9f4a-8241657bb343","parentId":"6a3b8974-6b6b-a47d-0e37-f636c0bec306","programLabel":[{"type":"secondary","value":"23.00 s"}]}],"contributedNode":{"type":"ur-code","version":"0.0.1","allowsChildren":true,"lockChildren":false,"parameters":{"loopForever":false}},"guid":"6a3b8974-6b6b-a47d-0e37-f636c0bec306","parentId":"563d78e2-b8b7-818d-6886-d3d169c13afb"}]},"programInformation":{"name":"test upload","description":"","createdDate":1786960428687,"lastSavedDate":1786960433126,"lastModifiedDate":1786960455874,"programState":"DRAFT","functionsBlockShown":false},"urscript":{"script":"$ 1 \"ur-program\"\n$ 2 \"ur-modules\"\n$ 3 \"ur-functions\"\n$ 4 \"ur-before-start\"\n$ 5 \"ur-code\"\n$ 6 \"ur-wait\"\nsleep(23)","nodeIDList":["00000000-0000-0000-0000-000000000000","563d78e2-b8b7-818d-6886-d3d169c13afb","e98f5594-3223-dccd-3a54-e4a0ebb448ae","f486e2c5-15bd-418e-bbce-fdb9b1bf0b36","f37a4466-36c4-fecd-db39-70c813c3e4cf","6a3b8974-6b6b-a47d-0e37-f636c0bec306","fb05e537-a239-564a-9f4a-8241657bb343"]}}} \ No newline at end of file diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 30191573b..a249e0061 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -96,7 +96,8 @@ class DashboardClientTestX : public ::testing::Test std::unique_ptr primary_client_; std::shared_ptr polyscope_version_; bool skip_remote_control_tests = true; - int error_code_exists = 400; + static constexpr int ERROR_CODE_EXISTS = 400; + static constexpr int ERROR_CODE_CONFLICT = 409; }; TEST_F(DashboardClientTestX, connect) @@ -117,6 +118,10 @@ TEST_F(DashboardClientTestX, get_loaded_program) } else { + if (!skip_remote_control_tests) + { + dashboard_client_->commandLoadProgram("Default program"); + } auto response = dashboard_client_->commandGetLoadedProgram(); ASSERT_TRUE(response.ok); ASSERT_EQ(std::get(response.data["program_name"]), "Default program"); @@ -384,7 +389,9 @@ TEST_F(DashboardClientTestX, upload_program_from_file) if (!response.ok) { URCL_LOG_INFO("status code: %d", std::get(response.data["status_code"])); - ASSERT_EQ(std::get(response.data["status_code"]), error_code_exists); + bool is_exists_error = std::get(response.data["status_code"]) == ERROR_CODE_EXISTS; + bool is_conflict_error = std::get(response.data["status_code"]) == ERROR_CODE_CONFLICT; + ASSERT_TRUE(is_exists_error || is_conflict_error); } response = dashboard_client_->commandUploadProgram("non_existent_file.urpx"); @@ -405,7 +412,9 @@ TEST_F(DashboardClientTestX, upload_and_update_program_from_file) auto response = dashboard_client_->commandUploadProgram("resources/update_prog.urpx"); if (!response.ok) { - ASSERT_EQ(std::get(response.data["status_code"]), error_code_exists); + bool is_exists_error = std::get(response.data["status_code"]) == ERROR_CODE_EXISTS; + bool is_conflict_error = std::get(response.data["status_code"]) == ERROR_CODE_CONFLICT; + ASSERT_TRUE(is_exists_error || is_conflict_error); } response = dashboard_client_->commandUpdateProgram("resources/update_prog.urpx"); @@ -539,7 +548,7 @@ TEST_F(DashboardClientTestX, open_and_close_popups) { auto response = dashboard_client_->commandClosePopup(); ASSERT_FALSE(response.ok); - ASSERT_TRUE(response.message.find("Failed to close system dialog") != response.message.npos); + ASSERT_TRUE(response.message.find("\"closed\":false") != response.message.npos); response = dashboard_client_->commandPopup("Test popup", "Test"); ASSERT_TRUE(response.ok); ASSERT_TRUE(response.message.find("Popup opened successfully") != response.message.npos); @@ -547,7 +556,7 @@ TEST_F(DashboardClientTestX, open_and_close_popups) ASSERT_TRUE(response.ok); response = dashboard_client_->commandCloseSafetyPopup(); ASSERT_FALSE(response.ok); - ASSERT_TRUE(response.message.find("Failed to close safety popup") != response.message.npos); + ASSERT_TRUE(response.message.find("\"closed\":false") != response.message.npos); } } } From 5ecd62b62701cfabcd0b4ffedd93aedeef013cbf Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 17 Aug 2026 14:46:19 +0200 Subject: [PATCH 10/46] Update docstring --- include/ur_client_library/ur/dashboard_client_implementation.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/ur_client_library/ur/dashboard_client_implementation.h b/include/ur_client_library/ur/dashboard_client_implementation.h index 39ebc76ac..0b48c3553 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation.h +++ b/include/ur_client_library/ur/dashboard_client_implementation.h @@ -470,7 +470,8 @@ class DashboardClientImpl * * \param report_type The report type to set for the flight report * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with software version + * lower than 10.14.0 */ virtual DashboardResponse commandGenerateFlightReport(const std::string& report_type) = 0; From 60c4e795e39fc848a8b9a9a80a7177489af72fd4 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 17 Aug 2026 14:48:02 +0200 Subject: [PATCH 11/46] Remove maybe_unused from add_to_log argument --- src/ur/dashboard_client_implementation_x.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 25c5c49a2..6055b025a 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -382,7 +382,7 @@ DashboardResponse DashboardClientImplX::commandPopup(const std::string& popup_te return post("/popup/v1", R"({"title": "TITLE )" + title + R"(", "message": ")" + popup_text + R"("})"); } -DashboardResponse DashboardClientImplX::commandAddToLog([[maybe_unused]] const std::string& log_text) +DashboardResponse DashboardClientImplX::commandAddToLog(const std::string& log_text) { assertHasCommand("add_to_log"); const std::string endpoint = g_command_list["add_to_log"].endpoint; From 2972cd87de41d4b86a09b2e5f41e5e2388a64e86 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 17 Aug 2026 15:25:16 +0200 Subject: [PATCH 12/46] Implement support file endpoints --- .../ur/dashboard_client_implementation_x.h | 3 +- src/ur/dashboard_client_implementation_x.cpp | 66 +++++++++++++++++-- tests/test_dashboard_client_x.cpp | 47 +++++++++++++ 3 files changed, 109 insertions(+), 7 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client_implementation_x.h b/include/ur_client_library/ur/dashboard_client_implementation_x.h index 09d4d2bdc..4df6e6b98 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_x.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_x.h @@ -133,7 +133,8 @@ class DashboardClientImplX : public DashboardClientImpl DashboardResponse commandClearOperationalMode() override; DashboardResponse commandClosePopup() override; DashboardResponse commandCloseSafetyPopup() override; - DashboardResponse commandGenerateFlightReport(const std::string& report_type) override; + DashboardResponse commandGenerateFlightReport(const std::string& report_type = "") override; + DashboardResponse commandDownloadSupportFiles(const std::string& save_path); DashboardResponse commandGenerateSupportFile(const std::string& dir_path) override; DashboardResponse commandGetLoadedProgram() override; DashboardResponse commandGetOperationalMode() override; diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 6055b025a..644d89fec 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -84,7 +84,7 @@ std::unordered_map DashboardClientImplX::g_command { "/system/v1/log", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, { "generate_flight_report", { "/supportfiles/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, - { "download_flight_reports", + { "download_support_files", { "/supportfiles/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, { "set_operational_mode", { "/operational-mode/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, @@ -517,13 +517,67 @@ DashboardResponse DashboardClientImplX::commandGetUserRole() DashboardResponse DashboardClientImplX::commandGenerateFlightReport([[maybe_unused]] const std::string& report_type) { assertHasCommand("generate_flight_report"); + + if (!report_type.empty()) + { + URCL_LOG_WARN("The report_type parameter is not used in the PolyScope X Robot API. Ignoring it."); + } + auto timeout = std::chrono::seconds(30); // Default timeout for generating flight report + + timeval configured_tv = getConfiguredReceiveTimeout(); + // Preserve sub-second precision: duration_cast truncates fractional values, + // so go via microseconds (the smallest unit timeval can represent) and split. + const auto pwron_us = std::chrono::duration_cast(timeout); + timeval flightreport_tv; + flightreport_tv.tv_sec = static_cast(pwron_us.count() / 1'000'000); + flightreport_tv.tv_usec = static_cast(pwron_us.count() % 1'000'000); + setReceiveTimeout(flightreport_tv); const std::string endpoint = g_command_list["generate_flight_report"].endpoint; - auto response = post(endpoint, "", "application/json"); - auto json = json::parse(response.message); - std::cout << json << std::endl; - // std::cout << response << std::endl; + + DashboardResponse response; + try + { + response = post(endpoint, "", "application/json"); + } + catch (...) + { + setReceiveTimeout(configured_tv); + throw; + } + + setReceiveTimeout(configured_tv); + return response; +} + +DashboardResponse DashboardClientImplX::commandDownloadSupportFiles([[maybe_unused]] const std::string& save_path) +{ + assertHasCommand("download_support_files"); + const std::string endpoint = g_command_list["download_support_files"].endpoint; + + auto response = get(endpoint, false); // The json response is pretty long. Don't print it. + // + + std::cout << "Saving support files to: " << save_path << std::endl; + if (response.ok) + { + std::ofstream save_file(save_path, std::ios_base::out); + if (!save_file.is_open()) + { + DashboardResponse error_response; + error_response.ok = false; + error_response.message = "Failed to open file for saving: " + save_path; + URCL_LOG_ERROR(error_response.message.c_str()); + return error_response; + } + save_file << response.message; + + response.message = "Downloaded support files to " + save_path; + } + else + { + URCL_LOG_ERROR("Failed to download program. Response message: %s", response.message.c_str()); + } return response; - // throw NotImplementedException("commandGenerateFlightReport is not implemented for DashboardClientImplX."); } DashboardResponse DashboardClientImplX::commandGenerateSupportFile([[maybe_unused]] const std::string& dir_path) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index a249e0061..476a78a21 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -661,6 +661,53 @@ TEST_F(DashboardClientTestX, add_to_log) } } +TEST_F(DashboardClientTestX, generate_flight_report) +{ + if (skip_remote_control_tests) + { + GTEST_SKIP_("Skipping test that would require remote control to be enabled on robot"); + } + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandGenerateFlightReport(), NotImplementedException); + } + else + { + if (skip_remote_control_tests) + { + auto response = dashboard_client_->commandGenerateFlightReport(); + ASSERT_FALSE(response.ok); + EXPECT_TRUE(response.message.find("Forbidden") != response.message.npos); + } + else + { + auto response = dashboard_client_->commandGenerateFlightReport(); + ASSERT_TRUE(response.ok); + EXPECT_TRUE(response.message.find("Flight report generated") != response.message.npos); + } + } +} + +TEST_F(DashboardClientTestX, download_support_files) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + { + ASSERT_THROW(dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip"), NotImplementedException); + } + else + { + auto response = dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip"); + ASSERT_TRUE(response.ok); + // Check that the file was created and has some content + std::ifstream file("/tmp/support_files.zip", std::ios::binary | std::ios::ate); + ASSERT_TRUE(file.is_open()); + std::streamsize size = file.tellg(); + ASSERT_GT(size, 0); + } +} + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); From 675b382e8421d906deebb5ed5cd11eec97f79dd5 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 17 Aug 2026 17:25:32 +0200 Subject: [PATCH 13/46] Finalized Support file download * Updated documentation * Brought it up to the Dashboard Client API * Implemented NotImplemented errors on G5 --- doc/architecture/dashboard_client.rst | 7 ++++++ .../ur_client_library/ur/dashboard_client.h | 22 +++++++++++++++++++ .../ur/dashboard_client_implementation.h | 9 ++++++++ .../ur/dashboard_client_implementation_g5.h | 1 + .../ur/dashboard_client_implementation_x.h | 2 +- src/ur/dashboard_client.cpp | 10 +++++++++ src/ur/dashboard_client_implementation_g5.cpp | 5 +++++ tests/test_dashboard_client.cpp | 9 ++++++++ tests/test_dashboard_client_g5.cpp | 1 + 9 files changed, 65 insertions(+), 1 deletion(-) diff --git a/doc/architecture/dashboard_client.rst b/doc/architecture/dashboard_client.rst index 81d68cd06..1e10e1806 100644 --- a/doc/architecture/dashboard_client.rst +++ b/doc/architecture/dashboard_client.rst @@ -80,3 +80,10 @@ Internally, the dashboard client makes calls against a RESTful (**Re**\ presenta .. code-block:: json {"state":"PAUSED","message":"Program state changed: PAUSED","details":"Pause successful"} + +The following commands are only available for PolyScope X robots and will throw a +``NotImplementedException`` when called on G5 (CB3 / PolyScope 5) robots: + +- ``commandDownloadSupportFiles(save_path)``: Downloads support files from the robot as a zip + archive and saves them to the given path on the local machine. Available from PolyScope X + 10.14.0 onward. diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 0d89b3a46..3e4265691 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -709,6 +709,28 @@ class DashboardClient */ DashboardResponse commandGenerateSupportFileWithResponse(const std::string& dir_path); + /*! + * \brief Download support files from the robot as a zip archive + * + * \note Only available for PolyScope X robots. + * + * \param save_path Filepath where the downloaded support file archive should be saved on the machine where the + * dashboard client is running. + * + * \return True on success + */ + bool commandDownloadSupportFiles(const std::string& save_path); + + /*! + * \brief Download support files from the robot as a zip archive + * + * \note Only available for PolyScope X robots. + * + * \param save_path Filepath where the downloaded support file archive should be saved on the machine where the + * dashboard client is running. + */ + DashboardResponse commandDownloadSupportFilesWithResponse(const std::string& save_path); + /*! * \brief Flush the polyscope log to the log_history.txt file * diff --git a/include/ur_client_library/ur/dashboard_client_implementation.h b/include/ur_client_library/ur/dashboard_client_implementation.h index 0b48c3553..fa0e0a117 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation.h +++ b/include/ur_client_library/ur/dashboard_client_implementation.h @@ -485,6 +485,15 @@ class DashboardClientImpl */ virtual DashboardResponse commandGenerateSupportFile(const std::string& dir_path) = 0; + /*! + * \brief Download support files from the robot as a zip archive + * + * \param save_path Filepath where the downloaded support file archive should be saved on the user's computer + * + * \throws an NotImplementedException when called on G5 robots + */ + virtual DashboardResponse commandDownloadSupportFiles(const std::string& save_path) = 0; + /*! * \brief Flush the polyscope log to the log_history.txt file * diff --git a/include/ur_client_library/ur/dashboard_client_implementation_g5.h b/include/ur_client_library/ur/dashboard_client_implementation_g5.h index fdf0ac897..f40742d6f 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_g5.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_g5.h @@ -133,6 +133,7 @@ class DashboardClientImplG5 : public DashboardClientImpl, comm::TCPSocket DashboardResponse commandCloseSafetyPopup() override; DashboardResponse commandGenerateFlightReport(const std::string& report_type) override; DashboardResponse commandGenerateSupportFile(const std::string& dir_path) override; + DashboardResponse commandDownloadSupportFiles(const std::string& save_path) override; DashboardResponse commandGetLoadedProgram() override; DashboardResponse commandGetOperationalMode() override; DashboardResponse commandGetRobotModel() override; diff --git a/include/ur_client_library/ur/dashboard_client_implementation_x.h b/include/ur_client_library/ur/dashboard_client_implementation_x.h index 4df6e6b98..5b35b27c8 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_x.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_x.h @@ -134,7 +134,7 @@ class DashboardClientImplX : public DashboardClientImpl DashboardResponse commandClosePopup() override; DashboardResponse commandCloseSafetyPopup() override; DashboardResponse commandGenerateFlightReport(const std::string& report_type = "") override; - DashboardResponse commandDownloadSupportFiles(const std::string& save_path); + DashboardResponse commandDownloadSupportFiles(const std::string& save_path) override; DashboardResponse commandGenerateSupportFile(const std::string& dir_path) override; DashboardResponse commandGetLoadedProgram() override; DashboardResponse commandGetOperationalMode() override; diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index 612e05fb5..44fc7a34c 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -538,6 +538,16 @@ DashboardResponse DashboardClient::commandGenerateSupportFileWithResponse(const return impl_->commandGenerateSupportFile(dir_path); } +bool DashboardClient::commandDownloadSupportFiles(const std::string& save_path) +{ + return commandDownloadSupportFilesWithResponse(save_path).ok; +} + +DashboardResponse DashboardClient::commandDownloadSupportFilesWithResponse(const std::string& save_path) +{ + return impl_->commandDownloadSupportFiles(save_path); +} + bool DashboardClient::commandSaveLog() { return commandSaveLogWithResponse().ok; diff --git a/src/ur/dashboard_client_implementation_g5.cpp b/src/ur/dashboard_client_implementation_g5.cpp index fd8f86728..efce9d109 100644 --- a/src/ur/dashboard_client_implementation_g5.cpp +++ b/src/ur/dashboard_client_implementation_g5.cpp @@ -1171,6 +1171,11 @@ DashboardResponse DashboardClientImplG5::commandGenerateSupportFile(const std::s return response; } +DashboardResponse DashboardClientImplG5::commandDownloadSupportFiles([[maybe_unused]] const std::string& save_path) +{ + throw NotImplementedException("commandDownloadSupportFiles is not implemented for DashboardClientImplG5."); +} + DashboardResponse DashboardClientImplG5::commandSaveLog() { DashboardResponse response; diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp index ee7c6f70d..9b8608224 100644 --- a/tests/test_dashboard_client.cpp +++ b/tests/test_dashboard_client.cpp @@ -65,6 +65,7 @@ class MockDashboardClientImpl : public DashboardClientImplG5 MOCK_METHOD(DashboardResponse, commandCloseSafetyPopup, (), (override)); MOCK_METHOD(DashboardResponse, commandGenerateFlightReport, (const std::string&), (override)); MOCK_METHOD(DashboardResponse, commandGenerateSupportFile, (const std::string&), (override)); + MOCK_METHOD(DashboardResponse, commandDownloadSupportFiles, (const std::string&), (override)); MOCK_METHOD(DashboardResponse, commandGetLoadedProgram, (), (override)); MOCK_METHOD(DashboardResponse, commandGetOperationalMode, (), (override)); MOCK_METHOD(DashboardResponse, commandGetRobotModel, (), (override)); @@ -243,6 +244,14 @@ TEST_F(DashboardClientTest, flight_report_and_support_file) EXPECT_TRUE(dashboard_client_->commandGenerateSupportFile(".")); } +TEST_F(DashboardClientTest, download_support_files) +{ + EXPECT_TRUE(dashboard_client_->connect()); + const auto impl = dashboard_client_->getImplPtr(); + EXPECT_CALL(*impl, commandDownloadSupportFiles("/tmp/support.zip")).WillOnce(testing::Return(SUCCESS_RESPONSE)); + EXPECT_TRUE(dashboard_client_->commandDownloadSupportFiles("/tmp/support.zip")); +} + TEST_F(DashboardClientTest, version_specific_calls) { // Since we mock everything, we can call all version-specific calls in this test. diff --git a/tests/test_dashboard_client_g5.cpp b/tests/test_dashboard_client_g5.cpp index 8b546c657..bc58a25ed 100644 --- a/tests/test_dashboard_client_g5.cpp +++ b/tests/test_dashboard_client_g5.cpp @@ -656,6 +656,7 @@ TEST_F(DashboardClientTestG5, all_x_only_commands_throw) EXPECT_THROW(dashboard_client_->commandUpdateProgram(""), NotImplementedException); EXPECT_THROW(dashboard_client_->commandDownloadProgram("", ""), NotImplementedException); EXPECT_THROW(dashboard_client_->commandResume(), NotImplementedException); + EXPECT_THROW(dashboard_client_->commandDownloadSupportFiles(""), NotImplementedException); } int main(int argc, char* argv[]) From efca9b229fb6f96204c4bebed5f7be26bc6056bf Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 18 Aug 2026 10:46:10 +0200 Subject: [PATCH 14/46] Add flight reports to version compatibility table --- doc/polyscope_compatibility.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index 5f4f5cbbf..c76c2a70c 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -76,6 +76,9 @@ table below or checkout the latest tag before the breaking changes were introduc * - 10.14.0 - Logging - Add entry to system log + * - 10.14.0 + - FLight reports + - Generating and downloading flight reports - Using external control on |polyscope| X requires another URCapX for making external control work. This is currently in the process of being created. From c57205d322ef83c77152a9c3b8e36e5ea7b27e8a Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Thu, 20 Aug 2026 15:57:04 +0200 Subject: [PATCH 15/46] Add 10.14 preview image to CI --- .github/workflows/ci.yml | 5 +++++ tests/test_dashboard_client_x.cpp | 22 ++++++++++------------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4ee3bcda..fabaf9eeb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,6 +78,11 @@ jobs: PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e' POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' + - ROBOT_MODEL: 'ur7e' + URSIM_VERSION: '10.14.0-0.10.703-preview-1' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e' + POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' + CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' steps: - uses: actions/checkout@v7 diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index d4b23a55f..72e54deb9 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -676,10 +676,6 @@ TEST_F(DashboardClientTestX, add_to_log) TEST_F(DashboardClientTestX, generate_flight_report) { - if (skip_remote_control_tests) - { - GTEST_SKIP_("Skipping test that would require remote control to be enabled on robot"); - } ASSERT_TRUE(dashboard_client_->connect()); if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) { @@ -696,8 +692,9 @@ TEST_F(DashboardClientTestX, generate_flight_report) else { auto response = dashboard_client_->commandGenerateFlightReport(); - ASSERT_TRUE(response.ok); - EXPECT_TRUE(response.message.find("Flight report generated") != response.message.npos); + // On URSim this can't be used + // ASSERT_TRUE(response.ok); + // EXPECT_TRUE(response.message.find("Flight report generated") != response.message.npos); } } } @@ -712,12 +709,13 @@ TEST_F(DashboardClientTestX, download_support_files) else { auto response = dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip"); - ASSERT_TRUE(response.ok); - // Check that the file was created and has some content - std::ifstream file("/tmp/support_files.zip", std::ios::binary | std::ios::ate); - ASSERT_TRUE(file.is_open()); - std::streamsize size = file.tellg(); - ASSERT_GT(size, 0); + // On URSim this can't be used + // ASSERT_TRUE(response.ok); + //// Check that the file was created and has some content + // std::ifstream file("/tmp/support_files.zip", std::ios::binary | std::ios::ate); + // ASSERT_TRUE(file.is_open()); + // std::streamsize size = file.tellg(); + // ASSERT_GT(size, 0); } } From f4b52687c5baf23fb13aa96a35ea4a65cfa46b4a Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 09:01:47 +0200 Subject: [PATCH 16/46] Use RobotAPI version from preview image --- src/ur/dashboard_client_implementation_x.cpp | 26 +++++++++++--------- tests/test_dashboard_client_x.cpp | 16 ++++++------ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 644d89fec..41d819427 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -63,33 +63,35 @@ std::unordered_map DashboardClientImplX::g_command { "get_operational_mode", { "/system/v1/operationalmode", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") } }, - { "popup", { "/popup/v1", VersionInformation::fromString("3.3.3"), VersionInformation::fromString("10.14.0") } }, + { "popup", { "/popup/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "close_popup", - { "/popup/v1", VersionInformation::fromString("3.3.3"), VersionInformation::fromString("10.14.0") } }, + { "/popup/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "close_safety_popup", - { "/popup/v1/safety", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/popup/v1/safety", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "is_in_remote_control", { "/system/v1/controlmode", VersionInformation::fromString("3.1.4"), VersionInformation::fromString("10.12.0") }, }, { "PolyscopeVersion", - { "/versions/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/versions/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "get_robot_model", - { "/system/v1/information", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/system/v1/information", VersionInformation::fromString("5.0.107"), + VersionInformation::fromString("10.14.0") } }, { "get_serial_number", - { "/system/v1/information", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/system/v1/information", VersionInformation::fromString("5.0.107"), + VersionInformation::fromString("10.14.0") } }, { "shutdown", - { "/system/v1/shutdown", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/system/v1/shutdown", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "add_to_log", - { "/system/v1/log", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/system/v1/log", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "generate_flight_report", - { "/supportfiles/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/supportfiles/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "download_support_files", - { "/supportfiles/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/supportfiles/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "set_operational_mode", - { "/operational-mode/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } }, + { "/operational-mode/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, { "clear_operational_mode", - { "/operational-mode/v1", VersionInformation::fromString("4.2.0"), VersionInformation::fromString("10.14.0") } } + { "/operational-mode/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } } }; DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 72e54deb9..eb95e8eca 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -532,7 +532,7 @@ TEST_F(DashboardClientTestX, microsecond_receive_timeout_makes_connect_fail) TEST_F(DashboardClientTestX, open_and_close_popups) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandPopup(""), NotImplementedException); ASSERT_THROW(dashboard_client_->commandClosePopup(), NotImplementedException); @@ -577,7 +577,7 @@ TEST_F(DashboardClientTestX, open_and_close_popups) TEST_F(DashboardClientTestX, get_polyscope_version) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandPolyscopeVersion(), NotImplementedException); } @@ -593,7 +593,7 @@ TEST_F(DashboardClientTestX, get_polyscope_version) TEST_F(DashboardClientTestX, get_robot_model) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandGetRobotModel(), NotImplementedException); } @@ -614,7 +614,7 @@ TEST_F(DashboardClientTestX, get_robot_model) TEST_F(DashboardClientTestX, get_serial_number) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandGetSerialNumber(), NotImplementedException); } @@ -630,7 +630,7 @@ TEST_F(DashboardClientTestX, get_serial_number) TEST_F(DashboardClientTestX, shutdown_robot) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandShutdown(), NotImplementedException); } @@ -653,7 +653,7 @@ TEST_F(DashboardClientTestX, shutdown_robot) TEST_F(DashboardClientTestX, add_to_log) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandAddToLog(""), NotImplementedException); } @@ -677,7 +677,7 @@ TEST_F(DashboardClientTestX, add_to_log) TEST_F(DashboardClientTestX, generate_flight_report) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandGenerateFlightReport(), NotImplementedException); } @@ -702,7 +702,7 @@ TEST_F(DashboardClientTestX, generate_flight_report) TEST_F(DashboardClientTestX, download_support_files) { ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("4.2.0")) + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { ASSERT_THROW(dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip"), NotImplementedException); } From 42bab3431cf08821fc3d89d49a563516d578d848 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 10:36:46 +0200 Subject: [PATCH 17/46] Changes from PR review --- doc/polyscope_compatibility.rst | 2 +- .../ur/dashboard_client_implementation.h | 7 +++--- .../ur/dashboard_client_implementation_g5.h | 2 +- .../ur/dashboard_client_implementation_x.h | 6 ++++- src/ur/dashboard_client_implementation_g5.cpp | 2 +- src/ur/dashboard_client_implementation_x.cpp | 22 ++++++++++++++----- tests/test_dashboard_client_x.cpp | 3 +++ 7 files changed, 30 insertions(+), 14 deletions(-) diff --git a/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index c76c2a70c..b32c6309c 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -77,7 +77,7 @@ table below or checkout the latest tag before the breaking changes were introduc - Logging - Add entry to system log * - 10.14.0 - - FLight reports + - Flight reports - Generating and downloading flight reports - Using external control on |polyscope| X requires another URCapX for making external control diff --git a/include/ur_client_library/ur/dashboard_client_implementation.h b/include/ur_client_library/ur/dashboard_client_implementation.h index fa0e0a117..8126857d4 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation.h +++ b/include/ur_client_library/ur/dashboard_client_implementation.h @@ -61,8 +61,7 @@ struct ProgramInformation inline std::ostream& operator<<(std::ostream& os, const ProgramInformation& pi) { - os << "Program Information: { " - << "\nCreated Date: " << pi.createdDate << "\nDescription: " << pi.description + os << "Program Information: { " << "\nCreated Date: " << pi.createdDate << "\nDescription: " << pi.description << "\nLast Modified Date: " << pi.lastModifiedDate << "\nLast Saved Date: " << pi.lastSavedDate << "\nName: " << pi.name << "\nProgram State: " << pi.programState << "\n} \n"; return os; @@ -321,7 +320,7 @@ class DashboardClientImpl * * \param popup_text The text to be shown in the popup * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with software version lower than 10.14.0 */ virtual DashboardResponse commandPopup(const std::string& popup_text, const std::string& popup_title = "") = 0; @@ -554,7 +553,7 @@ class DashboardClientImpl } protected: - virtual void assertHasCommand(const std::string& command) const = 0; + virtual void assertHasCommand(const std::string& command) = 0; VersionInformation polyscope_version_; std::string host_; diff --git a/include/ur_client_library/ur/dashboard_client_implementation_g5.h b/include/ur_client_library/ur/dashboard_client_implementation_g5.h index f40742d6f..b84a992b6 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_g5.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_g5.h @@ -176,7 +176,7 @@ class DashboardClientImplG5 : public DashboardClientImpl, comm::TCPSocket protected: virtual VersionInformation queryPolyScopeVersion(); - void assertHasCommand(const std::string& command) const override; + void assertHasCommand(const std::string& command) override; static std::string replacePayload(const std::string& command, const std::string& payload); std::string retryCommandString(const std::string& requestCommand, const std::string& requestExpectedResponse, const std::string& waitRequest, const std::string& waitExpectedResponse, diff --git a/include/ur_client_library/ur/dashboard_client_implementation_x.h b/include/ur_client_library/ur/dashboard_client_implementation_x.h index 5b35b27c8..29882b873 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_x.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_x.h @@ -28,6 +28,9 @@ #pragma once +#include +#include + #include #include "ur_client_library/ur/version_information.h" @@ -191,7 +194,7 @@ class DashboardClientImplX : public DashboardClientImpl DashboardResponse del(const std::string& endpoint, const bool debug = true); virtual VersionInformation queryPolyScopeVersion(); - void assertHasCommand(const std::string& command) const override; + void assertHasCommand(const std::string& command) override; const std::string base_url_ = "/universal-robots/robot-api"; @@ -202,6 +205,7 @@ class DashboardClientImplX : public DashboardClientImpl timeval send_timeout_ = { 10, 0 }; static std::unordered_map g_command_list; + std::atomic is_connected_{ false }; }; } // namespace urcl diff --git a/src/ur/dashboard_client_implementation_g5.cpp b/src/ur/dashboard_client_implementation_g5.cpp index efce9d109..2c2150d32 100644 --- a/src/ur/dashboard_client_implementation_g5.cpp +++ b/src/ur/dashboard_client_implementation_g5.cpp @@ -278,7 +278,7 @@ VersionInformation DashboardClientImplG5::queryPolyScopeVersion() return VersionInformation::fromString(version_string); } -void DashboardClientImplG5::assertHasCommand(const std::string& command) const +void DashboardClientImplG5::assertHasCommand(const std::string& command) { if (polyscope_version_ == VersionInformation::fromString("0.0.0")) { diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 41d819427..7dfa4fec1 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -151,6 +151,7 @@ bool DashboardClientImplX::connect([[maybe_unused]] const size_t max_num_tries, { if (res->status != 200) { + is_connected_ = false; URCL_LOG_ERROR("Received non-200 response code when connecting to Robot API: %d", res->status); return false; } @@ -161,15 +162,17 @@ bool DashboardClientImplX::connect([[maybe_unused]] const size_t max_num_tries, { robot_api_version_ = VersionInformation::fromString(json_data["info"]["version"]); URCL_LOG_DEBUG("Connected to Robot API version: %s", robot_api_version_.toString().c_str()); + is_connected_ = true; return true; } } + is_connected_ = false; return false; } void DashboardClientImplX::disconnect() { - // Nothing to do here, since the Robot API doesn't keep any active connections. + is_connected_ = false; return; } @@ -190,8 +193,16 @@ VersionInformation DashboardClientImplX::queryPolyScopeVersion() return VersionInformation::fromString(version_string); } -void DashboardClientImplX::assertHasCommand(const std::string& command) const +void DashboardClientImplX::assertHasCommand(const std::string& command) { + if (is_connected_ == false) + { + // connect will query the robot API version and set robot_api_version_ if successful. + if (!connect()) + { + throw UrException("Failed to connect to the robot API. Cannot assert command availability."); + } + } if (robot_api_version_ < g_command_list.at(command).robotAPIVersion) { std::stringstream ss; @@ -381,7 +392,8 @@ DashboardResponse DashboardClientImplX::commandIsInRemoteControl() DashboardResponse DashboardClientImplX::commandPopup(const std::string& popup_text, const std::string& title) { assertHasCommand("popup"); - return post("/popup/v1", R"({"title": "TITLE )" + title + R"(", "message": ")" + popup_text + R"("})"); + nlohmann::json payload = { { "title", title }, { "message", popup_text } }; + return post("/popup/v1", payload.dump()); } DashboardResponse DashboardClientImplX::commandAddToLog(const std::string& log_text) @@ -389,7 +401,6 @@ DashboardResponse DashboardClientImplX::commandAddToLog(const std::string& log_t assertHasCommand("add_to_log"); const std::string endpoint = g_command_list["add_to_log"].endpoint; const std::string message = R"({"message": ")" + log_text + R"("})"; - std::cout << message << std::endl; return post(endpoint, message); } @@ -557,7 +568,6 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles([[maybe_unus const std::string endpoint = g_command_list["download_support_files"].endpoint; auto response = get(endpoint, false); // The json response is pretty long. Don't print it. - // std::cout << "Saving support files to: " << save_path << std::endl; if (response.ok) @@ -568,7 +578,7 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles([[maybe_unus DashboardResponse error_response; error_response.ok = false; error_response.message = "Failed to open file for saving: " + save_path; - URCL_LOG_ERROR(error_response.message.c_str()); + URCL_LOG_ERROR("%", error_response.message.c_str()); return error_response; } save_file << response.message; diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index eb95e8eca..471898ee1 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -629,6 +629,9 @@ TEST_F(DashboardClientTestX, get_serial_number) TEST_F(DashboardClientTestX, shutdown_robot) { + // On URSim this will not really shutdown the docker container, so we can test it in CI. When + // tests are run on a real robot, this test should be skipped, as it will shutdown the robot and + // the test will fail. ASSERT_TRUE(dashboard_client_->connect()); if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) { From 96b9eb471cd756989934a21811710aa286ec0c64 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 10:44:32 +0200 Subject: [PATCH 18/46] Use httplib's response handler for downloading support files --- src/ur/dashboard_client_implementation_x.cpp | 74 ++++++++++++++++---- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 7dfa4fec1..55ca76d6c 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -562,32 +562,76 @@ DashboardResponse DashboardClientImplX::commandGenerateFlightReport([[maybe_unus return response; } -DashboardResponse DashboardClientImplX::commandDownloadSupportFiles([[maybe_unused]] const std::string& save_path) +DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::string& save_path) { assertHasCommand("download_support_files"); const std::string endpoint = g_command_list["download_support_files"].endpoint; - auto response = get(endpoint, false); // The json response is pretty long. Don't print it. + DashboardResponse response; - std::cout << "Saving support files to: " << save_path << std::endl; - if (response.ok) + std::ofstream save_file(save_path, std::ios::out | std::ios::binary); + if (!save_file.is_open()) { - std::ofstream save_file(save_path, std::ios_base::out); - if (!save_file.is_open()) - { - DashboardResponse error_response; - error_response.ok = false; - error_response.message = "Failed to open file for saving: " + save_path; - URCL_LOG_ERROR("%", error_response.message.c_str()); - return error_response; - } - save_file << response.message; + response.ok = false; + response.message = "Failed to open file for saving: " + save_path; + URCL_LOG_ERROR("%s", response.message.c_str()); + return response; + } + bool http_ok = true; + bool write_error = false; + std::string error_body; + + // Since support files can be rather large, but not time-critical, we stream the response body to a file instead of + // loading it all into memory. + auto res = cli_->Get( + base_url_ + endpoint, + [&](const httplib::Response& r) -> bool { + response.data["status_code"] = r.status; + http_ok = (r.status >= 200 && r.status < 300); + return true; // always receive body: success body goes to file, error body to error_body + }, + [&](const char* data, size_t data_length) -> bool { + if (!http_ok) + { + error_body.append(data, data_length); + return true; + } + save_file.write(data, static_cast(data_length)); + if (!save_file) + { + write_error = true; + return false; + } + return true; + }); + + if (!res) + { + response.ok = false; + response.message = "HTTP request failed: " + httplib::to_string(res.error()); + URCL_LOG_ERROR("%s", response.message.c_str()); + return response; + } + + if (write_error) + { + response.ok = false; + response.message = "Write error while streaming support files to: " + save_path; + URCL_LOG_ERROR("%s", response.message.c_str()); + return response; + } + + if (http_ok) + { + response.ok = true; response.message = "Downloaded support files to " + save_path; } else { - URCL_LOG_ERROR("Failed to download program. Response message: %s", response.message.c_str()); + response.ok = false; + response.message = error_body; + URCL_LOG_ERROR("Failed to download support files. Response message: %s", response.message.c_str()); } return response; } From d1e339ad27708ea387b44e2957343861c2a1bda6 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 10:47:01 +0200 Subject: [PATCH 19/46] Add workaround to handle ur7e and ur12e in tests correctly --- tests/test_dashboard_client_x.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 471898ee1..5cc808225 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -606,8 +606,20 @@ TEST_F(DashboardClientTestX, get_robot_model) waitFor([this]() { return primary_client_->getRobotType() != urcl::RobotType::UNDEFINED; }, std::chrono::milliseconds(1000)); - const std::string true_robot = robotTypeString(primary_client_->getRobotType()); - EXPECT_EQ(model_string, true_robot); + const std::string primary_client_version = robotTypeString(primary_client_->getRobotType()); + std::string expected_model_string = primary_client_version; + + // On the primary interface UR7 and UR12 show as UR5 and UR10, so we need to adjust the + // expected value accordingly. + if (model_string == "UR7") + { + expected_model_string = "UR5"; + } + else if (model_string == "UR12") + { + expected_model_string = "UR10"; + } + EXPECT_EQ(model_string, primary_client_version); } } From 45ba45c9a08e69b4d957c2f1f2f53f712ab71867 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 11:33:08 +0200 Subject: [PATCH 20/46] Fix expected value --- tests/test_dashboard_client_x.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 5cc808225..b990e427b 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -619,7 +619,7 @@ TEST_F(DashboardClientTestX, get_robot_model) { expected_model_string = "UR10"; } - EXPECT_EQ(model_string, primary_client_version); + EXPECT_EQ(model_string, expected_model_string); } } From e7d6659239f69c199f689bada257a8710182f597 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 12:51:47 +0200 Subject: [PATCH 21/46] Download to tempfile to avoid overwriting when no data was fetched --- src/ur/dashboard_client_implementation_x.cpp | 26 ++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 55ca76d6c..7d402d536 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -25,9 +25,10 @@ // POSSIBILITY OF SUCH DAMAGE. #include -#include +#include #include #include +#include #include "ur_client_library/ur/version_information.h" #include @@ -569,7 +570,9 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s DashboardResponse response; - std::ofstream save_file(save_path, std::ios::out | std::ios::binary); + std::string temp_save_path = save_path + ".tmp"; + + std::ofstream save_file(temp_save_path, std::ios::out | std::ios::binary); if (!save_file.is_open()) { response.ok = false; @@ -606,11 +609,14 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s return true; }); + save_file.close(); + if (!res) { response.ok = false; response.message = "HTTP request failed: " + httplib::to_string(res.error()); URCL_LOG_ERROR("%s", response.message.c_str()); + std::filesystem::remove(temp_save_path); return response; } @@ -619,11 +625,25 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s response.ok = false; response.message = "Write error while streaming support files to: " + save_path; URCL_LOG_ERROR("%s", response.message.c_str()); + std::filesystem::remove(temp_save_path); return response; } if (http_ok) { + // std::filesystem::rename replaces an existing destination atomically on POSIX and + // uses MoveFileExW(MOVEFILE_REPLACE_EXISTING) on Windows, so repeat downloads to + // the same path work correctly on both platforms. + std::error_code ec; + std::filesystem::rename(temp_save_path, save_path, ec); + if (ec) + { + std::filesystem::remove(temp_save_path); + response.ok = false; + response.message = "Failed to rename temporary file to final destination: " + ec.message(); + URCL_LOG_ERROR("%s", response.message.c_str()); + return response; + } response.ok = true; response.message = "Downloaded support files to " + save_path; } @@ -632,6 +652,8 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s response.ok = false; response.message = error_body; URCL_LOG_ERROR("Failed to download support files. Response message: %s", response.message.c_str()); + // delete temp file if it exists + std::remove(temp_save_path.c_str()); } return response; } From e3dc459c0d986baf0933f64b8629a5245e6f5e3f Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 13:04:02 +0200 Subject: [PATCH 22/46] Use json constructor instead of manually constructing json string --- src/ur/dashboard_client_implementation_x.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 7d402d536..654f78047 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -401,8 +401,8 @@ DashboardResponse DashboardClientImplX::commandAddToLog(const std::string& log_t { assertHasCommand("add_to_log"); const std::string endpoint = g_command_list["add_to_log"].endpoint; - const std::string message = R"({"message": ")" + log_text + R"("})"; - return post(endpoint, message); + nlohmann::json payload = { { "message", log_text } }; + return post(endpoint, payload.dump()); } DashboardResponse DashboardClientImplX::commandPolyscopeVersion() From c01b3cba923aee764a5800f8addd367739909c3f Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 13:22:42 +0200 Subject: [PATCH 23/46] Address minor issues --- .../ur_client_library/ur/dashboard_client.h | 4 ++-- .../ur/dashboard_client_implementation.h | 18 ++++++++++-------- .../ur/dashboard_client_implementation_g5.h | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 3e4265691..4d5a723dd 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -676,7 +676,7 @@ class DashboardClient * * \return True succeeded */ - bool commandGenerateFlightReport(const std::string& report_type); + bool commandGenerateFlightReport(const std::string& report_type = ""); /*! * \brief Send Generate flight report command @@ -685,7 +685,7 @@ class DashboardClient * * \param report_type The report type to set for the flight report */ - DashboardResponse commandGenerateFlightReportWithResponse(const std::string& report_type); + DashboardResponse commandGenerateFlightReportWithResponse(const std::string& report_type = ""); /*! * \brief Send Generate support file command diff --git a/include/ur_client_library/ur/dashboard_client_implementation.h b/include/ur_client_library/ur/dashboard_client_implementation.h index 8126857d4..7901ac94b 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation.h +++ b/include/ur_client_library/ur/dashboard_client_implementation.h @@ -249,14 +249,14 @@ class DashboardClientImpl /*! * \brief Send Close popup command * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with version lower than 10.14.0 */ virtual DashboardResponse commandClosePopup() = 0; /*! * \brief Send Close safety popup command * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with version lower than 10.14.0 */ virtual DashboardResponse commandCloseSafetyPopup() = 0; @@ -273,7 +273,7 @@ class DashboardClientImpl /*! * \brief Send Shutdown command * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with version lower than 10.14.0 */ virtual DashboardResponse commandShutdown() = 0; @@ -319,6 +319,8 @@ class DashboardClientImpl * \brief Send popup command * * \param popup_text The text to be shown in the popup + * \param popup_title The title of the popup. This is optional and only used on PolyScope X + * robots. * * \throws an NotImplementedException when called on PolyScope X robots with software version lower than 10.14.0 */ @@ -329,7 +331,7 @@ class DashboardClientImpl * * \param log_text The text to be sent to the log * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with software version lower than 10.14.0 */ virtual DashboardResponse commandAddToLog(const std::string& log_text) = 0; @@ -340,7 +342,7 @@ class DashboardClientImpl * * - 'polyscope_version': std::string * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with software version lower than 10.14.0 */ virtual DashboardResponse commandPolyscopeVersion() = 0; @@ -351,7 +353,7 @@ class DashboardClientImpl * * - 'robot_model': std::string * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with software version lower than 10.14.0 */ virtual DashboardResponse commandGetRobotModel() = 0; @@ -362,7 +364,7 @@ class DashboardClientImpl * * - 'serial_number': std::string * - * \throws an NotImplementedException when called on PolyScope X robots + * \throws an NotImplementedException when called on PolyScope X robots with software version lower than 10.14.0 */ virtual DashboardResponse commandGetSerialNumber() = 0; @@ -472,7 +474,7 @@ class DashboardClientImpl * \throws an NotImplementedException when called on PolyScope X robots with software version * lower than 10.14.0 */ - virtual DashboardResponse commandGenerateFlightReport(const std::string& report_type) = 0; + virtual DashboardResponse commandGenerateFlightReport(const std::string& report_type = "") = 0; /*! * \brief Send Generate support file command diff --git a/include/ur_client_library/ur/dashboard_client_implementation_g5.h b/include/ur_client_library/ur/dashboard_client_implementation_g5.h index b84a992b6..314f702b4 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_g5.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_g5.h @@ -131,7 +131,7 @@ class DashboardClientImplG5 : public DashboardClientImpl, comm::TCPSocket DashboardResponse commandClearOperationalMode() override; DashboardResponse commandClosePopup() override; DashboardResponse commandCloseSafetyPopup() override; - DashboardResponse commandGenerateFlightReport(const std::string& report_type) override; + DashboardResponse commandGenerateFlightReport(const std::string& report_type = "") override; DashboardResponse commandGenerateSupportFile(const std::string& dir_path) override; DashboardResponse commandDownloadSupportFiles(const std::string& save_path) override; DashboardResponse commandGetLoadedProgram() override; From 57e975b0c223916426d1e310c605671cf9b58df9 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 14:07:06 +0200 Subject: [PATCH 24/46] Add mock tests for support files Since the flight recorder service doesn't run on URSim, we mock the interaction by manually setting the expected OpenAPI responses. --- tests/CMakeLists.txt | 1 + tests/test_dashboard_client_x.cpp | 238 ++++++++++++++++++++++++++++++ 2 files changed, 239 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dd707df4d..40d802d92 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -58,6 +58,7 @@ if (INTEGRATION_TESTS) ) add_executable(dashboard_client_x_tests test_dashboard_client_x.cpp) + target_include_directories(dashboard_client_x_tests PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty) target_link_libraries(dashboard_client_x_tests PRIVATE ur_client_library::urcl GTest::gtest_main) gtest_add_tests(TARGET dashboard_client_x_tests WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index b990e427b..a9384b156 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -34,8 +34,10 @@ #include #include #include +#include #include #include +#include #ifndef _WIN32 # include # include @@ -734,6 +736,242 @@ TEST_F(DashboardClientTestX, download_support_files) } } +// --------------------------------------------------------------------------- +// Mock tests: httplib::Server replaces a real robot so the actual +// DashboardClientImplX code (endpoint routing, streaming download, +// response parsing, temp-file lifecycle) is exercised without hardware. +// Response bodies follow the OpenAPI spec at /universal-robots/robot-api/openapi.json. +// --------------------------------------------------------------------------- + +// Minimal OpenAPI response: version 5.0.107 satisfies the minimum required by +// both generate_flight_report and download_support_files. +static constexpr const char* MOCK_OPENAPI_RESPONSE = R"({"info":{"version":"5.0.107"}})"; +// POST /supportfiles/v1 → Generate Flight Report +// GET /supportfiles/v1/ → Download Support Files (zip) +static constexpr const char* MOCK_SUPPORTFILES_ENDPOINT = "/universal-robots/robot-api/supportfiles/v1"; +static constexpr const char* MOCK_OPENAPI_ENDPOINT = "/universal-robots/robot-api/openapi.json"; + +class DashboardClientImplXMockTest : public ::testing::Test +{ +protected: + void SetUp() override + { + server_.Get(MOCK_OPENAPI_ENDPOINT, [](const httplib::Request&, httplib::Response& res) { + res.set_content(MOCK_OPENAPI_RESPONSE, "application/json"); + }); + + port_ = server_.bind_to_any_port("127.0.0.1"); + server_thread_ = std::thread([this]() { server_.listen_after_bind(); }); + + while (!server_.is_running()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + impl_ = std::make_unique("127.0.0.1:" + std::to_string(port_)); + impl_->connect(); + } + + void TearDown() override + { + server_.stop(); + if (server_thread_.joinable()) + { + server_thread_.join(); + } + } + + httplib::Server server_; + int port_ = 0; + std::thread server_thread_; + std::unique_ptr impl_; +}; + +// --- commandGenerateFlightReport --- +// Spec: POST /supportfiles/v1 +// 200 → GenerateFlightReportResponse {"message": string|null, "details": string|null} +// 408 → APIError {"message": string, "details": string} +// 429 → APIError +// 507 → APIError + +TEST_F(DashboardClientImplXMockTest, generate_flight_report_success) +{ + // 200 OK with GenerateFlightReportResponse body + const std::string body = R"({"message":"Flight report generated successfully.","details":null})"; + server_.Post(MOCK_SUPPORTFILES_ENDPOINT, + [&body](const httplib::Request&, httplib::Response& res) { res.set_content(body, "application/json"); }); + + auto response = impl_->commandGenerateFlightReport(""); + EXPECT_TRUE(response.ok); + EXPECT_EQ(response.message, body); +} + +TEST_F(DashboardClientImplXMockTest, generate_flight_report_timeout) +{ + // 408 Request Timeout — flight recorder took too long + const std::string body = + R"({"message":"Flight recorder service took longer time to generate the report than expected.","details":""})"; + server_.Post(MOCK_SUPPORTFILES_ENDPOINT, [&body](const httplib::Request&, httplib::Response& res) { + res.status = 408; + res.set_content(body, "application/json"); + }); + + auto response = impl_->commandGenerateFlightReport(""); + EXPECT_FALSE(response.ok); + EXPECT_EQ(response.message, body); +} + +TEST_F(DashboardClientImplXMockTest, generate_flight_report_insufficient_storage) +{ + // 507 Insufficient Storage + const std::string body = R"({"message":"Insufficient storage to generate flight report.","details":""})"; + server_.Post(MOCK_SUPPORTFILES_ENDPOINT, [&body](const httplib::Request&, httplib::Response& res) { + res.status = 507; + res.set_content(body, "application/json"); + }); + + auto response = impl_->commandGenerateFlightReport(""); + EXPECT_FALSE(response.ok); + EXPECT_EQ(response.message, body); +} + +TEST_F(DashboardClientImplXMockTest, generate_flight_report_report_type_ignored) +{ + // PolyScope X ignores the report_type argument (logs a warning). A non-empty + // value must not prevent a successful call. + const std::string body = R"({"message":"Flight report generated successfully.","details":null})"; + server_.Post(MOCK_SUPPORTFILES_ENDPOINT, + [&body](const httplib::Request&, httplib::Response& res) { res.set_content(body, "application/json"); }); + + auto response = impl_->commandGenerateFlightReport("blackbox"); + EXPECT_TRUE(response.ok); +} + +// --- commandDownloadSupportFiles --- +// Spec: GET /supportfiles/v1 +// 200 → application/zip binary +// 204 → No Content (no flight reports available yet) +// 404 → APIError {"message": string, "details": string} +// 500 → APIError + +TEST_F(DashboardClientImplXMockTest, download_support_files_success) +{ + // 200 OK with zip binary body + const std::string fake_zip(R"(PK)" + "\x03\x04" + "fake_zip_data", + 18); + server_.Get(MOCK_SUPPORTFILES_ENDPOINT, [&fake_zip](const httplib::Request&, httplib::Response& res) { + res.set_content(fake_zip, "application/zip"); + }); + + const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support.zip"; + std::filesystem::remove(out); + + auto response = impl_->commandDownloadSupportFiles(out.string()); + EXPECT_TRUE(response.ok); + + std::ifstream file(out, std::ios::binary); + ASSERT_TRUE(file.is_open()); + const std::string written((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + EXPECT_EQ(written, fake_zip); + + std::filesystem::remove(out); +} + +TEST_F(DashboardClientImplXMockTest, download_support_files_no_content) +{ + // 204 No Content — no flight reports are available yet. + // The implementation treats any 2xx as success, so ok=true and an empty file is created. + server_.Get(MOCK_SUPPORTFILES_ENDPOINT, [](const httplib::Request&, httplib::Response& res) { res.status = 204; }); + + const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support_empty.zip"; + std::filesystem::remove(out); + + auto response = impl_->commandDownloadSupportFiles(out.string()); + EXPECT_TRUE(response.ok); + EXPECT_TRUE(std::filesystem::exists(out)); + EXPECT_EQ(std::filesystem::file_size(out), 0u); + + std::filesystem::remove(out); +} + +TEST_F(DashboardClientImplXMockTest, download_support_files_overwrites_existing) +{ + // A second download to the same destination path must overwrite the previous file. + std::string server_content = "version1_data"; + server_.Get(MOCK_SUPPORTFILES_ENDPOINT, [&server_content](const httplib::Request&, httplib::Response& res) { + res.set_content(server_content, "application/zip"); + }); + + const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support_overwrite.zip"; + std::filesystem::remove(out); + + EXPECT_TRUE(impl_->commandDownloadSupportFiles(out.string()).ok); + + server_content = "version2_longer_data"; + EXPECT_TRUE(impl_->commandDownloadSupportFiles(out.string()).ok); + + std::ifstream file(out, std::ios::binary); + ASSERT_TRUE(file.is_open()); + const std::string result((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + EXPECT_EQ(result, server_content); + + std::filesystem::remove(out); +} + +TEST_F(DashboardClientImplXMockTest, download_support_files_not_found) +{ + // 404 Not Found — flight recorder service not found + const std::string body = R"({"message":"Flight recorder service not found.","details":""})"; + server_.Get(MOCK_SUPPORTFILES_ENDPOINT, [&body](const httplib::Request&, httplib::Response& res) { + res.status = 404; + res.set_content(body, "application/json"); + }); + + const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support_notfound.zip"; + std::filesystem::remove(out); + std::filesystem::remove(std::filesystem::path(out.string() + ".tmp")); + + auto response = impl_->commandDownloadSupportFiles(out.string()); + + EXPECT_FALSE(response.ok); + EXPECT_EQ(response.message, body); + EXPECT_FALSE(std::filesystem::exists(out)); + EXPECT_FALSE(std::filesystem::exists(std::filesystem::path(out.string() + ".tmp"))); +} + +TEST_F(DashboardClientImplXMockTest, download_support_files_server_error) +{ + // 500 Internal Server Error + const std::string body = R"({"message":"Internal server error.","details":""})"; + server_.Get(MOCK_SUPPORTFILES_ENDPOINT, [&body](const httplib::Request&, httplib::Response& res) { + res.status = 500; + res.set_content(body, "application/json"); + }); + + const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support_err.zip"; + std::filesystem::remove(out); + std::filesystem::remove(std::filesystem::path(out.string() + ".tmp")); + + auto response = impl_->commandDownloadSupportFiles(out.string()); + + EXPECT_FALSE(response.ok); + EXPECT_EQ(response.message, body); + EXPECT_FALSE(std::filesystem::exists(out)) << "Final file must not be created on server error"; + EXPECT_FALSE(std::filesystem::exists(std::filesystem::path(out.string() + ".tmp"))) << "Temp file must be cleaned up " + "on server error"; +} + +TEST_F(DashboardClientImplXMockTest, download_support_files_invalid_path) +{ + // A non-writable path must produce an error before any HTTP request is sent. + auto response = impl_->commandDownloadSupportFiles("/nonexistent_directory/support.zip"); + + EXPECT_FALSE(response.ok); + EXPECT_NE(response.message.find("Failed to open file"), std::string::npos); +} + class PolyScopeScreenshotListener : public ::testing::EmptyTestEventListener { public: From d9b04f398f7b6325593b851530cbff3375cb8427 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 14:22:00 +0200 Subject: [PATCH 25/46] Add default implementation for commandDownloadSupportFiles --- .../ur_client_library/ur/dashboard_client_implementation.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/include/ur_client_library/ur/dashboard_client_implementation.h b/include/ur_client_library/ur/dashboard_client_implementation.h index 7901ac94b..2f01267da 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation.h +++ b/include/ur_client_library/ur/dashboard_client_implementation.h @@ -493,7 +493,10 @@ class DashboardClientImpl * * \throws an NotImplementedException when called on G5 robots */ - virtual DashboardResponse commandDownloadSupportFiles(const std::string& save_path) = 0; + virtual DashboardResponse commandDownloadSupportFiles([[maybe_unused]] const std::string& save_path) + { + throw NotImplementedException("commandDownloadSupportFiles is not implemented for this dashboard client."); + } /*! * \brief Flush the polyscope log to the log_history.txt file From 43bf03c9bd4d4415c54cdbcd4c20667a2f68bb55 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 14:22:28 +0200 Subject: [PATCH 26/46] Use a new file for downloading the temp supportfiles --- src/ur/dashboard_client_implementation_x.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 654f78047..65cdfbcf4 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -570,7 +570,7 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s DashboardResponse response; - std::string temp_save_path = save_path + ".tmp"; + std::string temp_save_path = (std::filesystem::path(save_path).parent_path() / "support_file_download.tmp").string(); std::ofstream save_file(temp_save_path, std::ios::out | std::ios::binary); if (!save_file.is_open()) From af5b0a15b1512bc7b1161548ea36601c8252d21d Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 14:22:36 +0200 Subject: [PATCH 27/46] Remove unused response --- tests/test_dashboard_client_x.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index a9384b156..17138848a 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -708,7 +708,7 @@ TEST_F(DashboardClientTestX, generate_flight_report) } else { - auto response = dashboard_client_->commandGenerateFlightReport(); + dashboard_client_->commandGenerateFlightReport(); // On URSim this can't be used // ASSERT_TRUE(response.ok); // EXPECT_TRUE(response.message.find("Flight report generated") != response.message.npos); From 059c1139b158f03924a999a444f41713821b8ddd Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 15:20:05 +0200 Subject: [PATCH 28/46] Add robot_api test to ignore list for maybe_uninitialized warnings --- tests/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 40d802d92..313489a16 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -60,6 +60,7 @@ if (INTEGRATION_TESTS) add_executable(dashboard_client_x_tests test_dashboard_client_x.cpp) target_include_directories(dashboard_client_x_tests PRIVATE ${CMAKE_SOURCE_DIR}/3rdparty) target_link_libraries(dashboard_client_x_tests PRIVATE ur_client_library::urcl GTest::gtest_main) + target_compile_options(dashboard_client_x_tests PRIVATE -Wno-uninitialized) gtest_add_tests(TARGET dashboard_client_x_tests WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} EXTRA_ARGS ${INTEGRATION_TESTS_ROBOT_IP_ARG} From cc09f47067f1ac8d6e1544515ad7d47392670636 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 15:21:11 +0200 Subject: [PATCH 29/46] Harden against local-privilege symlink attacks --- src/ur/dashboard_client_implementation_x.cpp | 88 ++++++++++++++++---- tests/test_dashboard_client_x.cpp | 12 +-- 2 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 65cdfbcf4..27aee90ee 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -30,6 +30,14 @@ #include #include +#ifdef _WIN32 +# include +# include +# include +#else +# include +#endif + #include "ur_client_library/ur/version_information.h" #include #include @@ -570,23 +578,34 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s DashboardResponse response; - std::string temp_save_path = (std::filesystem::path(save_path).parent_path() / "support_file_download.tmp").string(); - - std::ofstream save_file(temp_save_path, std::ios::out | std::ios::binary); - if (!save_file.is_open()) + // Place the temp file in the same directory as the destination so that the + // later std::filesystem::rename stays on the same filesystem. + std::filesystem::path dest_dir = std::filesystem::path(save_path).parent_path(); + if (dest_dir.empty()) { - response.ok = false; - response.message = "Failed to open file for saving: " + save_path; - URCL_LOG_ERROR("%s", response.message.c_str()); - return response; + dest_dir = "."; } bool http_ok = true; bool write_error = false; std::string error_body; - // Since support files can be rather large, but not time-critical, we stream the response body to a file instead of - // loading it all into memory. +#ifndef _WIN32 + // mkstemp atomically creates the temp file with O_CREAT|O_EXCL and a random + // suffix, so a symlink pre-placed at the path is rejected rather than followed. + // This prevents local-privilege symlink attacks in shared directories such as /tmp. + std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".XXXXXX")).string(); + int tmp_fd = mkstemp(temp_save_path.data()); + if (tmp_fd < 0) + { + response.ok = false; + response.message = "Failed to create temporary file for saving: " + save_path; + URCL_LOG_ERROR("%s", response.message.c_str()); + return response; + } + + // Since support files can be rather large, but not time-critical, we stream the response body + // to a file instead of loading it all into memory. auto res = cli_->Get( base_url_ + endpoint, [&](const httplib::Response& r) -> bool { @@ -600,8 +619,8 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s error_body.append(data, data_length); return true; } - save_file.write(data, static_cast(data_length)); - if (!save_file) + ssize_t n = write(tmp_fd, data, data_length); + if (n != static_cast(data_length)) { write_error = true; return false; @@ -609,7 +628,47 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s return true; }); - save_file.close(); + close(tmp_fd); + +#else // _WIN32 + + // _open with _O_EXCL rejects a pre-existing path (including reparse points / + // junctions) instead of truncating through it, mitigating symlink-style attacks. + std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".tmp")).string(); + int tmp_fd = _open(temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _S_IREAD | _S_IWRITE); + if (tmp_fd < 0) + { + response.ok = false; + response.message = "Failed to create temporary file for saving: " + save_path; + URCL_LOG_ERROR("%s", response.message.c_str()); + return response; + } + + auto res = cli_->Get( + base_url_ + endpoint, + [&](const httplib::Response& r) -> bool { + response.data["status_code"] = r.status; + http_ok = (r.status >= 200 && r.status < 300); + return true; + }, + [&](const char* data, size_t data_length) -> bool { + if (!http_ok) + { + error_body.append(data, data_length); + return true; + } + int n = _write(tmp_fd, data, static_cast(data_length)); + if (n != static_cast(data_length)) + { + write_error = true; + return false; + } + return true; + }); + + _close(tmp_fd); + +#endif // _WIN32 if (!res) { @@ -652,8 +711,7 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s response.ok = false; response.message = error_body; URCL_LOG_ERROR("Failed to download support files. Response message: %s", response.message.c_str()); - // delete temp file if it exists - std::remove(temp_save_path.c_str()); + std::filesystem::remove(temp_save_path); } return response; } diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 17138848a..ce3dfc7a4 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -931,14 +931,12 @@ TEST_F(DashboardClientImplXMockTest, download_support_files_not_found) const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support_notfound.zip"; std::filesystem::remove(out); - std::filesystem::remove(std::filesystem::path(out.string() + ".tmp")); auto response = impl_->commandDownloadSupportFiles(out.string()); EXPECT_FALSE(response.ok); EXPECT_EQ(response.message, body); - EXPECT_FALSE(std::filesystem::exists(out)); - EXPECT_FALSE(std::filesystem::exists(std::filesystem::path(out.string() + ".tmp"))); + EXPECT_FALSE(std::filesystem::exists(out)) << "Final file must not be created on server error"; } TEST_F(DashboardClientImplXMockTest, download_support_files_server_error) @@ -952,24 +950,22 @@ TEST_F(DashboardClientImplXMockTest, download_support_files_server_error) const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support_err.zip"; std::filesystem::remove(out); - std::filesystem::remove(std::filesystem::path(out.string() + ".tmp")); auto response = impl_->commandDownloadSupportFiles(out.string()); EXPECT_FALSE(response.ok); EXPECT_EQ(response.message, body); EXPECT_FALSE(std::filesystem::exists(out)) << "Final file must not be created on server error"; - EXPECT_FALSE(std::filesystem::exists(std::filesystem::path(out.string() + ".tmp"))) << "Temp file must be cleaned up " - "on server error"; } TEST_F(DashboardClientImplXMockTest, download_support_files_invalid_path) { - // A non-writable path must produce an error before any HTTP request is sent. + // A non-writable path must produce an error before any HTTP request is sent (mkstemp + // fails because the parent directory does not exist). auto response = impl_->commandDownloadSupportFiles("/nonexistent_directory/support.zip"); EXPECT_FALSE(response.ok); - EXPECT_NE(response.message.find("Failed to open file"), std::string::npos); + EXPECT_NE(response.message.find("Failed to create temporary file"), std::string::npos); } class PolyScopeScreenshotListener : public ::testing::EmptyTestEventListener From d5ac556be99a9381c15fe2b9a095dc55bc0c9c49 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 16:15:24 +0200 Subject: [PATCH 30/46] Fix comparison --- tests/test_dashboard_client_x.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index ce3dfc7a4..37a6e2a5d 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -603,25 +603,24 @@ TEST_F(DashboardClientTestX, get_robot_model) { auto response = dashboard_client_->commandGetRobotModel(); ASSERT_TRUE(response.ok); - const std::string model_string = std::get(response.data["robot_model"]); + std::string model_string = std::get(response.data["robot_model"]); waitFor([this]() { return primary_client_->getRobotType() != urcl::RobotType::UNDEFINED; }, std::chrono::milliseconds(1000)); const std::string primary_client_version = robotTypeString(primary_client_->getRobotType()); - std::string expected_model_string = primary_client_version; // On the primary interface UR7 and UR12 show as UR5 and UR10, so we need to adjust the // expected value accordingly. if (model_string == "UR7") { - expected_model_string = "UR5"; + model_string = "UR5"; } else if (model_string == "UR12") { - expected_model_string = "UR10"; + model_string = "UR10"; } - EXPECT_EQ(model_string, expected_model_string); + EXPECT_EQ(model_string, primary_client_version); } } From 103bf56d1436bf2820ce2e428407f20ea54ad4b7 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 16:26:34 +0200 Subject: [PATCH 31/46] Use sopen_s instead of open on windows --- src/ur/dashboard_client_implementation_x.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 27aee90ee..9ba0ee7ec 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -632,10 +632,8 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s #else // _WIN32 - // _open with _O_EXCL rejects a pre-existing path (including reparse points / - // junctions) instead of truncating through it, mitigating symlink-style attacks. std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".tmp")).string(); - int tmp_fd = _open(temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _S_IREAD | _S_IWRITE); + int tmp_fd = _sopen_s(temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _S_IREAD | _S_IWRITE); if (tmp_fd < 0) { response.ok = false; From 0cc88d26d47831106f5a318a9c28bbc744ba1800 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 16:27:00 +0200 Subject: [PATCH 32/46] Check for write errors first as this will not get reached otherwise --- src/ur/dashboard_client_implementation_x.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 9ba0ee7ec..95fda37bd 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -668,19 +668,19 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s #endif // _WIN32 - if (!res) + if (write_error) { response.ok = false; - response.message = "HTTP request failed: " + httplib::to_string(res.error()); + response.message = "Write error while streaming support files to: " + save_path; URCL_LOG_ERROR("%s", response.message.c_str()); std::filesystem::remove(temp_save_path); return response; } - if (write_error) + if (!res) { response.ok = false; - response.message = "Write error while streaming support files to: " + save_path; + response.message = "HTTP request failed: " + httplib::to_string(res.error()); URCL_LOG_ERROR("%s", response.message.c_str()); std::filesystem::remove(temp_save_path); return response; From fde78fd7031438f69507d7b180642451f64aea86 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 16:27:06 +0200 Subject: [PATCH 33/46] Fix minor typo --- tests/test_dashboard_client_x.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 37a6e2a5d..2f96d6e9e 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -636,7 +636,7 @@ TEST_F(DashboardClientTestX, get_serial_number) auto response = dashboard_client_->commandGetSerialNumber(); ASSERT_TRUE(response.ok); const std::string serial_number = std::get(response.data["serial_number"]); - EXPECT_FALSE(serial_number.empty()); // Dont know what to check for here otherwise + EXPECT_FALSE(serial_number.empty()); // Don't know what to check for here otherwise } } From 0b22f1669d4810fd6ed8d7114021e5b7458f554b Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 17:13:04 +0200 Subject: [PATCH 34/46] Fix using sopen_s --- src/ur/dashboard_client_implementation_x.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 95fda37bd..d5fef3d6f 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -33,6 +33,7 @@ #ifdef _WIN32 # include # include +# include # include #else # include @@ -633,7 +634,9 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s #else // _WIN32 std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".tmp")).string(); - int tmp_fd = _sopen_s(temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _S_IREAD | _S_IWRITE); + int tmp_fd = -1; + _sopen_s(&tmp_fd, temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _SH_DENYRW, + _S_IREAD | _S_IWRITE); if (tmp_fd < 0) { response.ok = false; From e17ed5bdb95af54b57cba1961c5e1fc8513a0296 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 17:23:29 +0200 Subject: [PATCH 35/46] Minor changes --- src/ur/dashboard_client_implementation_x.cpp | 2 +- tests/test_dashboard_client_x.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index d5fef3d6f..82749ecbd 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -560,7 +560,7 @@ DashboardResponse DashboardClientImplX::commandGenerateFlightReport([[maybe_unus DashboardResponse response; try { - response = post(endpoint, "", "application/json"); + response = post(endpoint, ""); } catch (...) { diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 2f96d6e9e..e6e0ca012 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -724,7 +724,7 @@ TEST_F(DashboardClientTestX, download_support_files) } else { - auto response = dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip"); + dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip"); // On URSim this can't be used // ASSERT_TRUE(response.ok); //// Check that the file was created and has some content From 99788477e0c7866f0b8407477cc7fc83da363c89 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 17:47:09 +0200 Subject: [PATCH 36/46] Simplified windows path --- src/ur/dashboard_client_implementation_x.cpp | 80 ++++++++------------ 1 file changed, 31 insertions(+), 49 deletions(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 82749ecbd..e12e7dd6e 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -35,6 +35,20 @@ # include # include # include + +// _mktemp_s only generates a unique name but does not open the file, so this +// wrapper adds the exclusive open to match the POSIX mkstemp contract. +static int mkstemp(char* templ) +{ + if (_mktemp_s(templ, std::strlen(templ) + 1) != 0) + { + return -1; + } + int fd = -1; + _sopen_s(&fd, templ, _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _SH_DENYRW, _S_IREAD | _S_IWRITE); + return fd; +} +# define close _close #else # include #endif @@ -591,52 +605,13 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s bool write_error = false; std::string error_body; -#ifndef _WIN32 - // mkstemp atomically creates the temp file with O_CREAT|O_EXCL and a random - // suffix, so a symlink pre-placed at the path is rejected rather than followed. - // This prevents local-privilege symlink attacks in shared directories such as /tmp. + // mkstemp atomically creates the temp file with O_CREAT|O_EXCL and a random suffix so + // that a symlink pre-placed at the path is rejected rather than followed, preventing + // local-privilege symlink attacks in shared directories such as /tmp. + // On Windows the mkstemp wrapper above calls _mktemp_s + _sopen_s with _O_EXCL. std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".XXXXXX")).string(); int tmp_fd = mkstemp(temp_save_path.data()); - if (tmp_fd < 0) - { - response.ok = false; - response.message = "Failed to create temporary file for saving: " + save_path; - URCL_LOG_ERROR("%s", response.message.c_str()); - return response; - } - - // Since support files can be rather large, but not time-critical, we stream the response body - // to a file instead of loading it all into memory. - auto res = cli_->Get( - base_url_ + endpoint, - [&](const httplib::Response& r) -> bool { - response.data["status_code"] = r.status; - http_ok = (r.status >= 200 && r.status < 300); - return true; // always receive body: success body goes to file, error body to error_body - }, - [&](const char* data, size_t data_length) -> bool { - if (!http_ok) - { - error_body.append(data, data_length); - return true; - } - ssize_t n = write(tmp_fd, data, data_length); - if (n != static_cast(data_length)) - { - write_error = true; - return false; - } - return true; - }); - close(tmp_fd); - -#else // _WIN32 - - std::string temp_save_path = (dest_dir / (std::filesystem::path(save_path).filename().string() + ".tmp")).string(); - int tmp_fd = -1; - _sopen_s(&tmp_fd, temp_save_path.c_str(), _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _SH_DENYRW, - _S_IREAD | _S_IWRITE); if (tmp_fd < 0) { response.ok = false; @@ -645,12 +620,22 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s return response; } + auto write_chunk = [&tmp_fd](const char* data, size_t len) -> bool { +#ifndef _WIN32 + return write(tmp_fd, data, len) == static_cast(len); +#else + return _write(tmp_fd, data, static_cast(len)) == static_cast(len); +#endif + }; + + // Since support files can be rather large, we stream the response body to a file + // instead of loading it all into memory. auto res = cli_->Get( base_url_ + endpoint, [&](const httplib::Response& r) -> bool { response.data["status_code"] = r.status; http_ok = (r.status >= 200 && r.status < 300); - return true; + return true; // always receive body: success body → file, error body → error_body }, [&](const char* data, size_t data_length) -> bool { if (!http_ok) @@ -658,8 +643,7 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s error_body.append(data, data_length); return true; } - int n = _write(tmp_fd, data, static_cast(data_length)); - if (n != static_cast(data_length)) + if (!write_chunk(data, data_length)) { write_error = true; return false; @@ -667,9 +651,7 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s return true; }); - _close(tmp_fd); - -#endif // _WIN32 + close(tmp_fd); if (write_error) { From 9411d89cba45322ddc11aff1435380c01ca37b2d Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 17:56:43 +0200 Subject: [PATCH 37/46] Fix close --- src/ur/dashboard_client_implementation_x.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index e12e7dd6e..641501354 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -48,7 +48,6 @@ static int mkstemp(char* templ) _sopen_s(&fd, templ, _O_WRONLY | _O_CREAT | _O_EXCL | _O_BINARY, _SH_DENYRW, _S_IREAD | _S_IWRITE); return fd; } -# define close _close #else # include #endif @@ -651,7 +650,11 @@ DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::s return true; }); +#ifndef _WIN32 close(tmp_fd); +#else + _close(tmp_fd); +#endif if (write_error) { From e426a6438aaaa4846a4a92207654d10afc4e79b0 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Fri, 21 Aug 2026 21:28:51 +0200 Subject: [PATCH 38/46] Replace shutdown test with mock test --- tests/test_dashboard_client_x.cpp | 62 ++++++++++++++++++------------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index e6e0ca012..56539eeab 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -640,32 +640,6 @@ TEST_F(DashboardClientTestX, get_serial_number) } } -TEST_F(DashboardClientTestX, shutdown_robot) -{ - // On URSim this will not really shutdown the docker container, so we can test it in CI. When - // tests are run on a real robot, this test should be skipped, as it will shutdown the robot and - // the test will fail. - ASSERT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) - { - ASSERT_THROW(dashboard_client_->commandShutdown(), NotImplementedException); - } - else - { - if (skip_remote_control_tests) - { - auto response = dashboard_client_->commandShutdown(); - ASSERT_FALSE(response.ok); - EXPECT_TRUE(response.message.find("Forbidden") != response.message.npos); - } - else - { - auto response = dashboard_client_->commandShutdown(); - ASSERT_TRUE(response.ok); - } - } -} - TEST_F(DashboardClientTestX, add_to_log) { ASSERT_TRUE(dashboard_client_->connect()); @@ -786,6 +760,42 @@ class DashboardClientImplXMockTest : public ::testing::Test std::unique_ptr impl_; }; +static constexpr const char* MOCK_SHUTDOWN_ENDPOINT = "/universal-robots/robot-api/system/v1/shutdown"; + +// --- commandShutdown --- +// Spec: PUT /system/v1/shutdown +// 202 → APIResponse {"message": string|null} +// 403 → APIError {"message": string, "details": string} (not in remote-control mode) + +TEST_F(DashboardClientImplXMockTest, shutdown_success) +{ + // 202 Accepted + const std::string body = R"({"message":null})"; + server_.Put(MOCK_SHUTDOWN_ENDPOINT, [&body](const httplib::Request&, httplib::Response& res) { + res.status = 202; + res.set_content(body, "application/json"); + }); + + auto response = impl_->commandShutdown(); + EXPECT_TRUE(response.ok); + EXPECT_EQ(response.message, body); +} + +TEST_F(DashboardClientImplXMockTest, shutdown_forbidden) +{ + // 403 Forbidden — robot is not in remote-control mode + const std::string body = + R"({"message":"Forbidden","details":"Not authorized to perform this operation under the current robot control mode."})"; + server_.Put(MOCK_SHUTDOWN_ENDPOINT, [&body](const httplib::Request&, httplib::Response& res) { + res.status = 403; + res.set_content(body, "application/json"); + }); + + auto response = impl_->commandShutdown(); + EXPECT_FALSE(response.ok); + EXPECT_EQ(response.message, body); +} + // --- commandGenerateFlightReport --- // Spec: POST /supportfiles/v1 // 200 → GenerateFlightReportResponse {"message": string|null, "details": string|null} From 4bd983ad0a6f3a3b44dfeb9e5cb0fbaa96f5bee4 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 31 Aug 2026 08:29:42 +0200 Subject: [PATCH 39/46] Add specific programs for 10.14.0 --- .../polyscopex/10.14.0/ur7e/.allow-empty | 0 .../polyscopex/10.14.0/ur7e/.id-counters.json | 4 + .../.xodus-to-filesystem-migration-complete | 4 + .../10.14.0/ur7e/0/0.program.final.json | 1 + .../10.14.0/ur7e/0/0.program.final.script | 7 + .../polyscopex/10.14.0/ur7e/0/0.program.json | 1 + .../10.14.0/ur7e/0/1.program.final.json | 1 + .../10.14.0/ur7e/0/1.program.final.script | 7 + .../polyscopex/10.14.0/ur7e/0/1.program.json | 1 + .../10.14.0/ur7e/0/application-info.json | 1 + .../10.14.0/ur7e/0/application.final.json | 1 + .../10.14.0/ur7e/0/application.final.script | 533 ++++++++++++++++++ .../polyscopex/10.14.0/ur7e/00000000000.xd | Bin 0 -> 1675 bytes .../polyscopex/10.14.0/ur7e/blobs/version | Bin 0 -> 4 bytes 14 files changed, 561 insertions(+) create mode 100755 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.allow-empty create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.id-counters.json create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.xodus-to-filesystem-migration-complete create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.json create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.script create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.json create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.json create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.script create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.json create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application-info.json create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.json create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.script create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/00000000000.xd create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/blobs/version diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.allow-empty b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.allow-empty new file mode 100755 index 000000000..e69de29bb diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.id-counters.json b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.id-counters.json new file mode 100644 index 000000000..92a220510 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.id-counters.json @@ -0,0 +1,4 @@ +{ + "application": 1, + "program": 2 +} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.xodus-to-filesystem-migration-complete b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.xodus-to-filesystem-migration-complete new file mode 100644 index 000000000..11a4c6143 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/.xodus-to-filesystem-migration-complete @@ -0,0 +1,4 @@ +Application migrated: 0 (errors: 0) +Program migrated: 0 (errors: 0) +Script migrated: 0 (errors: 0) +Timestamp: 2026-08-31 06:18:43.439 diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.json b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.json new file mode 100644 index 000000000..6dc7e7fd3 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.json @@ -0,0 +1 @@ +{"guid":"1a411aa9-30c2-cad2-1623-1fa52f5f07d5","contributedNode":{"type":"ur-program","version":"0.0.3","allowsChildren":true,"lockChildren":true,"parameters":{"name":"Default program","symbolHistory":{"variables":[{"name":"base","valueType":"frame","reference":{"id":"94a4ec42-bb27-2cef-f135-3952d2e5fea0","_IDENTIFIER":"VariableReference"}},{"name":"tcp","valueType":"frame","reference":{"id":"7c380fec-6bff-c2fa-b150-10adb61aca87","_IDENTIFIER":"VariableReference"}},{"name":"world","valueType":"frame","reference":{"id":"be32a21f-0c64-76eb-644d-048fe77e2a0b","_IDENTIFIER":"VariableReference"}},{"name":"flange","valueType":"frame","reference":{"id":"3f8646ae-7d82-8b2e-f4bf-61fb35bc0db7","_IDENTIFIER":"VariableReference"}},{"name":"grid","valueType":"grid","reference":{"id":"d11c1d03-1d1e-7f1b-626c-f70b67d468d4","_IDENTIFIER":"VariableReference"}},{"name":"grid_iterator","valueType":"waypoint","reference":{"id":"1f913fef-57dc-4647-bb87-3841c607365a","_IDENTIFIER":"VariableReference"}},{"name":"Joint_fast","valueType":"profile","reference":{"id":"dac49340-32cf-bf98-84ed-38e360d25471","_IDENTIFIER":"VariableReference"}},{"name":"Joint_slow","valueType":"profile","reference":{"id":"590c162c-242a-d9d2-2d99-c8c2c6091e8b","_IDENTIFIER":"VariableReference"}},{"name":"Linear_fast","valueType":"profile","reference":{"id":"50313b22-7c8b-1257-4c87-c631cd8fa25b","_IDENTIFIER":"VariableReference"}},{"name":"Linear_slow","valueType":"profile","reference":{"id":"22c5de53-d059-ea19-1262-4f5bd71883c3","_IDENTIFIER":"VariableReference"}},{"name":"Process","valueType":"profile","reference":{"id":"b35e1b76-b793-91a0-ec59-b8c496e6c29a","_IDENTIFIER":"VariableReference"}},{"name":"Home","valueType":"waypoint","reference":{"id":"33541d24-a117-1fee-79df-31a7a97b448f","_IDENTIFIER":"VariableReference"}}],"functions":[],"modules":[{"name":"application","reference":{"id":"application-module-id","_IDENTIFIER":"ModuleReference"}}]}}},"children":[{"children":[],"contributedNode":{"type":"ur-modules","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"516a9644-4cc4-ec94-107e-2f3bca4d7c4a","parentId":"1a411aa9-30c2-cad2-1623-1fa52f5f07d5"},{"children":[],"contributedNode":{"type":"ur-functions","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"e81c37b1-786d-393d-5d18-c627e9920246","parentId":"1a411aa9-30c2-cad2-1623-1fa52f5f07d5"},{"children":[],"contributedNode":{"type":"ur-before-start","version":"0.0.1","allowsChildren":true},"guid":"3d4ebeec-9f0e-8886-a283-67016b9d36e7","parentId":"1a411aa9-30c2-cad2-1623-1fa52f5f07d5"},{"children":[],"contributedNode":{"type":"ur-configuration","version":"0.0.1","allowsChildren":true,"parameters":{}},"guid":"da83ca2f-4482-afc0-83f9-49b912105de3","parentId":"1a411aa9-30c2-cad2-1623-1fa52f5f07d5"},{"children":[],"contributedNode":{"type":"ur-status","version":"0.0.1","allowsChildren":true,"parameters":{}},"guid":"8e8c1dd9-b998-91f4-e47c-2cb196d4da69","parentId":"1a411aa9-30c2-cad2-1623-1fa52f5f07d5"},{"children":[{"children":[],"contributedNode":{"type":"ur-wait","version":"0.0.4","parameters":{"type":"time","time":{"entity":{"value":1,"unit":"s"},"selectedType":"VALUE","value":1}}},"guid":"accb3f5b-a671-3461-6f1d-66a7301faa6c","parentId":"7bd095f9-dcdd-7965-0b15-ae323c5af3d2","programLabel":[{"type":"secondary","value":"1 s"}]}],"contributedNode":{"type":"ur-code","version":"0.0.1","allowsChildren":true,"lockChildren":false,"parameters":{"loopForever":false}},"guid":"7bd095f9-dcdd-7965-0b15-ae323c5af3d2","parentId":"1a411aa9-30c2-cad2-1623-1fa52f5f07d5"}],"_scriptMetadata":{"nodeIDList":["00000000-0000-0000-0000-000000000000","1a411aa9-30c2-cad2-1623-1fa52f5f07d5","516a9644-4cc4-ec94-107e-2f3bca4d7c4a","e81c37b1-786d-393d-5d18-c627e9920246","3d4ebeec-9f0e-8886-a283-67016b9d36e7","7bd095f9-dcdd-7965-0b15-ae323c5af3d2","accb3f5b-a671-3461-6f1d-66a7301faa6c"]}} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.script b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.script new file mode 100644 index 000000000..56de36e9f --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.final.script @@ -0,0 +1,7 @@ +$ 1 "ur-program" +$ 2 "ur-modules" +$ 3 "ur-functions" +$ 4 "ur-before-start" +$ 5 "ur-code" +$ 6 "ur-wait" +sleep(1) \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.json b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.json new file mode 100644 index 000000000..c56d0a28d --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/0.program.json @@ -0,0 +1 @@ +{"dateSaved":1788157482508,"functionsBlockShown":false,"dateCreated":1788157390710,"name":"Default program","description":"","dateModified":null,"id":"0"} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.json b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.json new file mode 100644 index 000000000..5b9571a35 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.json @@ -0,0 +1 @@ +{"guid":"12718803-37f5-9af9-cee2-5542b23a1e5b","contributedNode":{"type":"ur-program","version":"0.0.3","allowsChildren":true,"lockChildren":true,"parameters":{"name":"wait_program","symbolHistory":{"variables":[{"name":"base","valueType":"frame","reference":{"id":"94a4ec42-bb27-2cef-f135-3952d2e5fea0","_IDENTIFIER":"VariableReference"}},{"name":"tcp","valueType":"frame","reference":{"id":"7c380fec-6bff-c2fa-b150-10adb61aca87","_IDENTIFIER":"VariableReference"}},{"name":"world","valueType":"frame","reference":{"id":"be32a21f-0c64-76eb-644d-048fe77e2a0b","_IDENTIFIER":"VariableReference"}},{"name":"flange","valueType":"frame","reference":{"id":"3f8646ae-7d82-8b2e-f4bf-61fb35bc0db7","_IDENTIFIER":"VariableReference"}},{"name":"grid","valueType":"grid","reference":{"id":"d11c1d03-1d1e-7f1b-626c-f70b67d468d4","_IDENTIFIER":"VariableReference"}},{"name":"grid_iterator","valueType":"waypoint","reference":{"id":"1f913fef-57dc-4647-bb87-3841c607365a","_IDENTIFIER":"VariableReference"}},{"name":"Joint_fast","valueType":"profile","reference":{"id":"dac49340-32cf-bf98-84ed-38e360d25471","_IDENTIFIER":"VariableReference"}},{"name":"Joint_slow","valueType":"profile","reference":{"id":"590c162c-242a-d9d2-2d99-c8c2c6091e8b","_IDENTIFIER":"VariableReference"}},{"name":"Linear_fast","valueType":"profile","reference":{"id":"50313b22-7c8b-1257-4c87-c631cd8fa25b","_IDENTIFIER":"VariableReference"}},{"name":"Linear_slow","valueType":"profile","reference":{"id":"22c5de53-d059-ea19-1262-4f5bd71883c3","_IDENTIFIER":"VariableReference"}},{"name":"Process","valueType":"profile","reference":{"id":"b35e1b76-b793-91a0-ec59-b8c496e6c29a","_IDENTIFIER":"VariableReference"}},{"name":"Home","valueType":"waypoint","reference":{"id":"33541d24-a117-1fee-79df-31a7a97b448f","_IDENTIFIER":"VariableReference"}}],"functions":[],"modules":[{"name":"application","reference":{"id":"application-module-id","_IDENTIFIER":"ModuleReference"}}]}}},"children":[{"children":[],"contributedNode":{"type":"ur-modules","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"8ae1f782-d3c5-1628-e729-e52a7ea3ddf1","parentId":"12718803-37f5-9af9-cee2-5542b23a1e5b"},{"children":[],"contributedNode":{"type":"ur-functions","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"92113496-b1aa-9f0c-6269-ca23fa3874c0","parentId":"12718803-37f5-9af9-cee2-5542b23a1e5b"},{"children":[],"contributedNode":{"type":"ur-before-start","version":"0.0.1","allowsChildren":true},"guid":"df426f41-3ae3-688d-a617-4fda515d7bb5","parentId":"12718803-37f5-9af9-cee2-5542b23a1e5b"},{"children":[],"contributedNode":{"type":"ur-configuration","version":"0.0.1","allowsChildren":true,"parameters":{}},"guid":"b040f75b-2d9b-e904-0f26-3d4a359463cf","parentId":"12718803-37f5-9af9-cee2-5542b23a1e5b"},{"children":[],"contributedNode":{"type":"ur-status","version":"0.0.1","allowsChildren":true,"parameters":{}},"guid":"fad150ad-6f48-fd9f-2117-514d224f4e0a","parentId":"12718803-37f5-9af9-cee2-5542b23a1e5b"},{"children":[{"children":[],"contributedNode":{"type":"ur-wait","version":"0.0.4","parameters":{"type":"time","time":{"entity":{"value":10,"unit":"s"},"selectedType":"VALUE","value":10}}},"guid":"e60c67d3-9233-3f59-8e63-e79b268f20a5","parentId":"4b03d6b3-bcdf-0827-d050-729a53a212ef","programLabel":[{"type":"secondary","value":"10 s"}]}],"contributedNode":{"type":"ur-code","version":"0.0.1","allowsChildren":true,"lockChildren":false,"parameters":{"loopForever":false}},"guid":"4b03d6b3-bcdf-0827-d050-729a53a212ef","parentId":"12718803-37f5-9af9-cee2-5542b23a1e5b"}],"_scriptMetadata":{"nodeIDList":["00000000-0000-0000-0000-000000000000","12718803-37f5-9af9-cee2-5542b23a1e5b","8ae1f782-d3c5-1628-e729-e52a7ea3ddf1","92113496-b1aa-9f0c-6269-ca23fa3874c0","df426f41-3ae3-688d-a617-4fda515d7bb5","4b03d6b3-bcdf-0827-d050-729a53a212ef","e60c67d3-9233-3f59-8e63-e79b268f20a5"]}} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.script b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.script new file mode 100644 index 000000000..0fd2eb740 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.final.script @@ -0,0 +1,7 @@ +$ 1 "ur-program" +$ 2 "ur-modules" +$ 3 "ur-functions" +$ 4 "ur-before-start" +$ 5 "ur-code" +$ 6 "ur-wait" +sleep(10) \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.json b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.json new file mode 100644 index 000000000..2d79370ee --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/1.program.json @@ -0,0 +1 @@ +{"dateSaved":1788157505384,"functionsBlockShown":false,"dateCreated":1788157493588,"name":"wait_program","description":"","dateModified":null,"id":"1"} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application-info.json b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application-info.json new file mode 100644 index 000000000..3c7d2ae2a --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application-info.json @@ -0,0 +1 @@ +{"dateCreated":1788157390692,"robotType":"UR7","name":"Default application","dateModified":1788157508085,"id":"0"} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.json b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.json new file mode 100644 index 000000000..a2f4f3799 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.json @@ -0,0 +1 @@ +{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{}},"activeOperatorScreen":"ur-operator-screen-default","smartSkills":[{"name":"Align to Plane","enabled":true,"type":"ur-align-to-plane","parameters":{"radius":0.05,"push_force":20,"n_plane_points":3,"max_distance":0.25,"velocity_slow":0.001,"velocity_search":0.035,"velocity_move":0.1,"acceleration":0.1}},{"name":"Align Z to Nearest Axis","enabled":true,"type":"ur-align-z-to-nearest-axis"},{"name":"Center","enabled":true,"type":"ur-center","parameters":{"push_force":10,"velocity_move":0.05,"acc_move":0.2,"max_radius_search":0.05,"num_fingers":3}},{"name":"Freedrive","enabled":true,"type":"ur-freedrive","version":"1.0.0","recordingFrequency":50,"recordingSignals":["timestamp","target_q","actual_TCP_pose","tcp_offset"]},{"name":"Move into Contact","enabled":true,"type":"ur-move-into-contact","version":"1.0.0","parameters":{"force":10,"velocity":0.05,"acceleration":0.2,"max_distance":0.25,"retract":0}},{"name":"Retract","enabled":true,"type":"ur-retract","version":"1.0.0","parameters":{"distance":0.1,"acceleration":0.4,"velocity":0.1}},{"name":"Put into Box","enabled":false,"type":"ur-put-in-box","version":"1.0.0"},{"name":"Custom","enabled":false,"type":"ur-custom-smart-skill","parameters":{"includePreamble":true,"includeModules":false},"version":"1.0.0"},{"name":"Home","enabled":true,"type":"ur-position","version":"1.1.2","parameters":{"actualWaypoint":{"frame":"base","pose":{"position":[-1.8246917738038495E-9,-0.2329000001676105,1.0793999999522315],"orientation":[3.987257497300885E-9,2.2214414675120993,-2.221441467056474]},"qNear":{"base":0,"shoulder":-1.5707963249999999,"elbow":0,"wrist1":-1.5707963249999999,"wrist2":0,"wrist3":0}},"variable":{"name":"Home","reference":false,"type":"$$Variable","valueType":"waypoint","id":"33541d24-a117-1fee-79df-31a7a97b448f","_IDENTIFIER":"VariableDeclaration"}}}],"safety":{"settings":{"io":{"automaticModeSafeguardResetInput":{"name":"automaticModeSafeguardResetInput","valueA":255,"valueB":255},"automaticModeSafeguardStopInput":{"name":"automaticModeSafeguardStopInput","valueA":255,"valueB":255},"emergencyStopInput":{"name":"emergencyStopInput","valueA":255,"valueB":255},"notReducedModeOutput":{"name":"notReducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"operationalModeInput":{"name":"operationalModeInput","valueA":255,"valueB":255},"reducedModeInput":{"name":"reducedModeInput","valueA":255,"valueB":255},"reducedModeOutput":{"name":"reducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotMovingOutput":{"name":"robotMovingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotNotStoppingOutput":{"name":"robotNotStoppingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeHomeOutput":{"name":"safeHomeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeguardResetInput":{"name":"safeguardResetInput","valueA":0,"valueB":1},"systemEmergencyStoppedOutput":{"name":"systemEmergencyStoppedOutput","ossdEnabled":false,"valueA":255,"valueB":255},"threePositionSwitchInput":{"name":"threePositionSwitchInput","valueA":255,"valueB":255},"freedriveEnabledInput":{"name":"freedriveEnabledInput","valueA":255,"valueB":255},"threePositionEnablingStopOutput":{"name":"threePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255},"notThreePositionEnablingStopOutput":{"name":"notThreePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255}},"major":5,"minor":14,"normalJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"normalJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"normalRobotLimits":{"elbowForce":150,"elbowSpeed":1.5,"momentum":25,"power":300,"stoppingDistance":0.5,"stoppingTime":0.4,"toolForce":150,"toolSpeed":1.5},"reducedJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"reducedJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"reducedRobotLimits":{"elbowForce":120,"elbowSpeed":0.75,"momentum":10,"power":200,"stoppingDistance":0.3,"stoppingTime":0.3,"toolForce":120,"toolSpeed":0.75},"safetyHardware":{"injectionMoldingMachineInterface":"NONE","teachPendant":"NORMAL"},"safetyPlanes":{"planes":[{"id":"c772fc01-fefd-bdbc-70ec-f8758525931b","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"733d6229-a355-9fe1-3087-144f5c9328e2","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"14439acf-8f24-7242-eff4-04e29c0aac0b","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"ac7afda2-3fe1-4505-8cab-54385c105899","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"2a95c4f3-3fe1-6184-8af8-31ee9710bec9","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"f3e35b4e-7a6e-1ed1-a8dc-c7c11c971b97","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"ccde1830-a4e6-d97f-b455-a96632afdf87","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"f0592203-5bc7-a5b9-677b-c3be1aaf48cb","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"}],"ioSafetyPlanes":[{"id":"90a9e3bf-f743-8994-c293-e8d91b460589","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"41c51a56-d034-4e1e-77f2-18bb70207f5f","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"7c4bfa2b-9293-bb0e-2d3e-f3b6f18c5593","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"909b3440-2c32-9229-6cfb-c2b92b1e56e7","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"0ebc5b04-97c5-e41c-a2a2-a24d9da2702e","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"01a34592-2096-7645-f842-30b9367f33bc","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"10a6ad31-4832-4d9c-ec4f-1148795b2a40","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"164da68e-d890-078f-7546-68ed40918168","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"}]},"safetySafeHome":{"base":-1,"elbow":-1,"shoulder":-1,"wrist1":-1,"wrist2":-1,"wrist3":-1,"enabled":false},"safetyAPIParameters":{"numberOfClients":0,"clients":[]},"safetyFieldbusses":{"enablePROFIsafe":false,"sourceAddressPROFIsafe":0,"destAddressPROFIsafe":0,"modeControlPROFIsafe":false},"threePosition":{"allowManualHighSpeed":true,"useTeachPendantAs3PE":false},"toolDirection":{"limitDeviation":6.2831855,"limitDirection":{"x":0,"y":0,"z":1},"limitRestriction":"DISABLED","tcp":{"id":"toolFlangeTcpId","name":"Tool_flange"},"toolPan":0,"toolTilt":0,"limitTilt":0,"limitRotation":0},"toolPositions":{"toolPositions":[{"name":"Tool Flange","center":{"x":0,"y":0,"z":0},"radius":0,"definition":2},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0}]},"normalWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"},"reducedWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"},"safetyPayloadLimits":{"minimumPayload":0,"maximumPayload":7.5,"payloadCoGX":0,"payloadCoGY":0,"payloadCoGZ":0,"payloadCoGRadius":1.732}},"crc":"2850523693","confirmed":true},"operatorScreens":[{"type":"ur-operator-screen-default","version":"0.0.2","parameters":{"status":[],"configuration":[]}}],"_scriptMetadata":{"nodeIDList":[]},"applicationContributions":{"universal-robots-external-control-external-control-application":{"type":"universal-robots-external-control-external-control-application","version":"1.0.0","port":50002,"robotIP":"192.168.56.1"},"ur-mounting":{"type":"ur-mounting","version":"0.0.1","mounting":{"baseAngle":{"value":0,"unit":"deg"},"tiltAngle":{"value":0,"unit":"deg"}}},"ur-frames":{"type":"ur-frames","version":"0.0.7","framesList":[{"name":"base","nameVariable":{"name":"base","reference":false,"type":"$$Variable","valueType":"frame","id":"94a4ec42-bb27-2cef-f135-3952d2e5fea0","_IDENTIFIER":"VariableDeclaration"},"parent":"world","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"tcp","nameVariable":{"name":"tcp","reference":false,"type":"$$Variable","valueType":"frame","id":"7c380fec-6bff-c2fa-b150-10adb61aca87","_IDENTIFIER":"VariableDeclaration"},"parent":"flange","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"world","nameVariable":{"name":"world","reference":false,"type":"$$Variable","valueType":"frame","id":"be32a21f-0c64-76eb-644d-048fe77e2a0b","_IDENTIFIER":"VariableDeclaration"},"pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"flange","nameVariable":{"name":"flange","reference":false,"type":"$$Variable","valueType":"frame","id":"3f8646ae-7d82-8b2e-f4bf-61fb35bc0db7","_IDENTIFIER":"VariableDeclaration"},"parent":"base","pose":{"position":[0,0,0],"orientation":[0,0,0]}}]},"ur-grid-pattern":{"type":"ur-grid-pattern","version":"0.0.3","grids":[{"grid":{"name":"grid","reference":false,"type":"$$Variable","valueType":"grid","id":"d11c1d03-1d1e-7f1b-626c-f70b67d468d4","_IDENTIFIER":"VariableDeclaration"},"waypoint":{"name":"grid_iterator","reference":false,"type":"$$Variable","valueType":"waypoint","id":"1f913fef-57dc-4647-bb87-3841c607365a","_IDENTIFIER":"VariableDeclaration"},"corners":[null,null,null,null],"numRows":4,"numColumns":5}]},"ur-end-effector":{"type":"ur-end-effector","version":"0.0.2","endEffectors":[{"id":"c2484934-082b-c8dd-9124-07516cfe0f15","name":"Robot","payload":{"weight":{"value":0,"unit":"kg"}},"cog":{"cx":{"value":0,"unit":"m"},"cy":{"value":0,"unit":"m"},"cz":{"value":0,"unit":"m"}},"inertia":{"Ixx":{"value":0,"unit":"kg*m^2"},"Iyy":{"value":0,"unit":"kg*m^2"},"Izz":{"value":0,"unit":"kg*m^2"},"Ixy":{"value":0,"unit":"kg*m^2"},"Ixz":{"value":0,"unit":"kg*m^2"},"Iyz":{"value":0,"unit":"kg*m^2"}},"useCustomInertia":false,"tcps":[{"id":"487a6572-d9cc-da14-6ac7-17a953631179","name":"Tool_flange","x":{"value":0,"unit":"m"},"y":{"value":0,"unit":"m"},"z":{"value":0,"unit":"m"},"rx":{"value":0,"unit":"rad"},"ry":{"value":0,"unit":"rad"},"rz":{"value":0,"unit":"rad"}}]}],"defaultTcp":{"endEffectorId":"c2484934-082b-c8dd-9124-07516cfe0f15","tcpId":"487a6572-d9cc-da14-6ac7-17a953631179"}},"ur-motion-profiles":{"type":"ur-motion-profiles","version":"0.0.1","moveProfiles":{"joint":[{"isDefault":false,"profile":{"name":"Joint_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"dac49340-32cf-bf98-84ed-38e360d25471","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Joint_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"590c162c-242a-d9d2-2d99-c8c2c6091e8b","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"linear":[{"isDefault":false,"profile":{"name":"Linear_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"50313b22-7c8b-1257-4c87-c631cd8fa25b","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Linear_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"22c5de53-d059-ea19-1262-4f5bd71883c3","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"process":[{"isDefault":true,"profile":{"name":"Process","reference":false,"type":"$$Variable","valueType":"profile","id":"b35e1b76-b793-91a0-ec59-b8c496e6c29a","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"Classic","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2}}}]}},"ur-smart-skills":{"type":"ur-smart-skills","version":"0.0.3","preamble":"# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper"},"ur-application-variables":{"type":"ur-application-variables","version":"0.0.1","variables":{}}},"sidebarItems":[{"type":"ur-global-variables","version":"1.0.0","disabled":{"master":false,"automaticMode":false,"remoteMode":true}},{"type":"ur-log-messages-sidebar","version":"0.0.1","disabled":{"master":false,"automaticMode":false,"remoteMode":true}}],"logicPrograms":{"4dc3d69c-db5e-9ca0-50d6-6523941b4f63":{"id":"4dc3d69c-db5e-9ca0-50d6-6523941b4f63","programContent":{"children":[{"children":[],"contributedNode":{"type":"ur-modules","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"f187ff29-cb39-5fa3-2bdf-074143d00428","parentId":"8dd0aa0a-f7e9-4c04-5cc8-c07e4a249fca"},{"children":[],"contributedNode":{"type":"ur-functions","version":"0.0.1","allowsChildren":true,"lockChildren":false},"guid":"7ca629da-e511-e20f-7ddc-5c27b51493fe","parentId":"8dd0aa0a-f7e9-4c04-5cc8-c07e4a249fca"},{"children":[],"contributedNode":{"type":"ur-before-start","version":"0.0.1","allowsChildren":true},"guid":"57baeb50-25cb-c725-f449-db91d437ae86","parentId":"8dd0aa0a-f7e9-4c04-5cc8-c07e4a249fca"},{"children":[],"contributedNode":{"type":"ur-logic-program","version":"0.0.1","allowsChildren":true,"parameters":{"logicProgram":{"name":"Logic_Program","reference":false,"type":"$$LogicProgram"}}},"guid":"d5bef401-821a-ddeb-84f0-4a195376a3b4","parentId":"8dd0aa0a-f7e9-4c04-5cc8-c07e4a249fca"}],"contributedNode":{"type":"ur-logic-programs","version":"0.0.1","allowsChildren":true,"lockChildren":true,"parameters":{"name":""}},"guid":"8dd0aa0a-f7e9-4c04-5cc8-c07e4a249fca"},"programInformation":{"name":"Logic_Program","description":"","programState":"FINAL","functionsBlockShown":false,"createdDate":0,"lastSavedDate":0,"lastModifiedDate":0},"urscript":{"script":"","nodeIDList":[]}}},"sourcesNodes":{"robot":{"groupId":"robot","version":"1.0.0","sources":[{"sourceID":"ur-wired-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"},{"signalID":"AO 0","direction":"OUT","valueType":"FLOAT"},{"signalID":"AO 1","direction":"OUT","valueType":"FLOAT"}],"webSocketURL":"/sources/wired-io"},{"sourceID":"ur-tool-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"}],"webSocketURL":"/sources/tool-io"}],"isDynamic":false},"ur-modbus":{"groupId":"ur-modbus","isDynamic":true,"version":"1.0.0","sources":[]},"ur-robot-io":{"type":"ur-robot-io","groupId":"ur-robot-io","isDynamic":false,"version":"1.0.3","sources":[{"sourceID":"ur-robot-wired-io","name":"Wired I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 0","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 1","valueType":"FLOAT"}]},{"sourceID":"ur-robot-tool-io","name":"Tool I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"}]}],"parameters":{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{},"toolOutput":{"dualPinPower":false,"voltage":{"value":0,"unit":"V"},"powerOutput":{"DO 0":1,"DO 1":1}},"smartPanel":{}},"controlBoxLayout":"cb5","migrateSourceConfigDone":true}}}} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.script b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.script new file mode 100644 index 000000000..09dde4df6 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/0/application.final.script @@ -0,0 +1,533 @@ +set_safety_mode_transition_hardness(1) +reset_world_model() +set_input_actions_to_default() +set_analog_outputdomain(0,0) +set_analog_outputdomain(1,0) +set_standard_analog_input_domain(0,0) +set_standard_analog_input_domain(1,0) +set_tool_output_mode(0) +set_tool_voltage(0) +set_tool_digital_output_mode(0,1) +set_tool_digital_output_mode(1,1) +set_tool_analog_input_domain(0,0) +set_tool_analog_input_domain(1,0) +set_gravity([0, 0, 9.82]) +local existingBaseParent = get_frame_parent("base") +local basePose = get_pose("base", existingBaseParent) +basePose[3] = 0 +basePose[4] = 0 +basePose[5] = 0 +move_frame("base", basePose, existingBaseParent) +global base = "base" +global tcp = "tcp" +global world = "world" +global flange = "flange" +set_target_payload(0, [0, 0, 0], [0, 0, 0, 0, 0, 0]) +set_tcp(p[0, 0, 0, 0, 0, 0], "Tool_flange") +# Start of Forces +### +# Transforms the force and torque values along the axes of the given pose +# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored +# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively +### +def get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]): + # we are only interested in the rotation of pose, set translations to zero + local target_pose = pose + target_pose[0] = 0 + target_pose[1] = 0 + target_pose[2] = 0 + # the conversion needs to happen as poses, so we need to convert back and forth a bit + local force = get_tcp_force() + local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0] + local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0] + local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose) + local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose) + return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]] +end +### +# See documentation for @link:get_tcp_wrench_in_frame() +# @return forces and torques measured in TCP frame +### +def get_tcp_wrench(): + return get_tcp_wrench_in_frame(get_target_tcp_pose()) +end +### +# Projects the measured TCP force along the axis given +# @param axis array 3D vector +### +def project_tcp_force(axis): + local wrench = get_tcp_wrench() + local force = [wrench[0], wrench[1], wrench[2]] + return dot(force, axis) +end +# End of Forces +# Start of Math +# Definitions of constants +global PI = acos(-1) +### +# Calculates the cross product between to 3D vectors +# @param v1 array 3D vector +# @param v2 array 3D vector +### +def cross(v1, v2): + if length(v1) != length(v2): + popup(str_cat("For computing the cross product, the two vectors must have the same length. Provided lengths: ", [length(v1), length(v2)]), error=True, blocking=True) + return -1 + end + if length(v1) != 3: + popup(str_cat("For computing the cross product, the two vectors must have length 3. Provided lengths: ", [length(v1), length(v2)]), error=True, blocking=True) + return -1 + end + local cross = [0.0, 0.0, 0.0] + local i = 0 + while i < 3: + local j = (i + 1) % 3 # The next index in a cyclic order + local k = (i + 2) % 3 # The next next index in a cyclic order + cross[i] = v1[j] * v2[k] - v1[k] * v2[j] + i = i + 1 + end + return cross +end +### +# Calculates the dot product between to n-dimensional vectors +# @param v1 array nD vector +# @param v2 array nD vector +### +def dot(v1, v2): + if length(v1) != length(v2): + popup(str_cat("For computing the dot product, the two vectors must have the same length. Provided lengths: ", [length(v1), length(v2)]), error=True, blocking=True) + return -1 + end + local result = 0 + local i = 0 + while i < length(v1): + result = result + (v1[i] * v2[i]) + i = i + 1 + end + return result +end +### +# Return the larger number of a and b +# @param a number a +# @param b number b +### +def max(a, b): + if a > b: + return a + end + return b +end +### +# Find the maximum value in a list. The list must be of non-zero length and contain numbers +# @param list array list +### +def list_max(list): + local length = get_list_length(list) + if length == 0: + popup("Getting the maximum of an empty list is impossible in list_max().", error = True, blocking = True) + halt + end + local i = 0 + local max = list[0] + while i < length: + if list[i] > max: + max = list[i] + end + i = i + 1 + sync_at_multiple(i, 30) + end + return max +end +def sync_at_multiple(i, n): + local tmp = i / n + if tmp == floor(tmp): + sync() + end +end +# End of Math +# Start of Move Helper +ur_move_until_force_distance = 0.1 +ur_move_until_force_direction = [0, 0, 1] +ur_move_until_force_velocity = 0.1 +ur_move_until_force_acceleration = 0.2 +def ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius): + local current_pose = get_target_tcp_pose() + local movement = normalize(direction) * distance + local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0]) + movel(target_pose, a = 0.2, v = velocity, r = blend_radius) +end +thread ur_move_until_force_thread(): + ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0) + popup("No contact detected.", title = "No Contact", warning = False, error = True, blocking = False) + halt +end +### +# Moves the robot in the TCP direction specified until a contact point is reached *or* +# the robot reaches the maximum distance allowed specified by the distance parameter. +# @param distance number The maximum distance the robot is allowed to travel in the direction specified +# @param direction array 3D vector determining the move direction of the TCP +# @param velocity number Velocity of the robot +# @param acceleration number Acceleration of the robot +# @param stop_force number Maximum search radius +### +def ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20): + ur_move_until_force_distance = distance + ur_move_until_force_direction = direction + ur_move_until_force_velocity = velocity + ur_move_until_force_acceleration = acceleration + + thrd = run ur_move_until_force_thread() + while - project_tcp_force(direction) < stop_force: + sync() + end + kill thrd + local actual_pose = get_actual_tcp_pose() + stopl(1.0) + return actual_pose +end +def ur_get_joint_speeds_before_offset(previous_q, time): + local current_q = get_joint_positions() + local delta_q = current_q - previous_q + return delta_q / time +end +def ur_path_move(end_q, v, rampdown=False): + # Calculate distance to target + local start_q = get_joint_positions() + local delta_q = end_q - start_q + local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])] + # Calculate time to move based on desired velocity + local t = list_max(positive_delta_q) / v + servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500) + if(rampdown): + while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001): + t = max(t, 0.001) + start_q = get_joint_positions() + servoj(end_q , 0, 0, t) + end + end +end +# End of Move Helper +# Waypoint variable for Home smart skill +global Home = struct(p=p[-1.8246917738038495e-9, -0.2329000001676105, 1.0793999999522315, 3.987257497300885e-9, 2.2214414675120993, -2.221441467056474], frame="base", q=[0, -1.5707963249999999, 0, -1.5707963249999999, 0, 0]) +# Start of Align to Plane +### +# Align to plane will touch up a plane by moving the robot into contact with the table or part in several locations to determine its orientation. Afterwards the robot will orient its tool to the plane. +# @param radius number Radius [m] of the circle within the plane will be touched up +# @param push_force number How hard to robot pushed against the plane +# @param n_plane_points number Number of points that the robot uses to compute the plane +# @param max_distance number Maximum distance that the robot searches +# @param velocity_slow number Velocity when pressing downwards +# @param velocity_search number Velocity used when approaching the touch up point +# @param velocity_move number Velocity used in freespace +# @param acceleration number Acceleration of the robot +# @param direction array 3D vector determining the direction of the TCP for touching up the plane +### +def ur_align_to_plane(radius = 0.05, push_force = 20, n_plane_points = 3, max_distance = 0.25, velocity_slow = 0.001, velocity_search = 0.035, velocity_move = 0.10, acceleration = 0.1, direction = [0, 0, 1]): + local angle = 2 * PI / n_plane_points + local start_pos = get_target_tcp_pose() + local retract_distance = -0.015 + ur_move_tcp_direction(retract_distance, direction, velocity_move, acceleration, 0) + sleep(0.25) + zero_ftsensor() + local cnt = 0 + local t_base_target = get_target_tcp_pose() + local mean_point = [0.0, 0.0, 0.0] + local A = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]] + local b = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] + while cnt < n_plane_points: + local new_pos = pose_trans(t_base_target, p[cos(angle * cnt) * radius, sin(angle * cnt) * radius, 0.0, 0.0, 0.0, 0.0]) + local blend_radius = norm(point_dist(get_actual_tcp_pose(), new_pos))/5 + movel(new_pos, a = acceleration, v = velocity_move, r = blend_radius) + ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_search, acceleration, push_force) + local movement = normalize(direction * -1) * 0.0005 + local target_pose = pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]) + movel(target_pose) + sleep(0.2) + ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_slow, acceleration, push_force) + sleep(0.2) + while (not is_steady()): + sync() + end + local poked_point = get_target_tcp_pose() + poked_point = pose_trans(inv(t_base_target), poked_point) + A[cnt, 0] = poked_point[0] + A[cnt, 1] = poked_point[1] + A[cnt, 2] = 1.0 + b[cnt] = poked_point[2] + mean_point = mean_point + [poked_point[0], poked_point[1], poked_point[2]] + movel(new_pos, a = 0.2, v = velocity_move, r = blend_radius) + cnt = cnt + 1 + end + mean_point = mean_point / n_plane_points + cnt = 0 + while cnt < n_plane_points: + local cntj = 0 + while cntj < 2: + A[cnt, cntj] = A[cnt, cntj] - mean_point[cntj] + cntj = cntj + 1 + end + b[cnt] = b[cnt] - mean_point[2] + cnt = cnt + 1 + end + local x1 = inv(transpose(A) * A) * transpose(A) * b + local x = normalize([x1[0], x1[1], -1]) + local d = dot(mean_point, x) + local dval = dot(direction, x) + if dval < 0: + x = -x + dval = -dval + end + local eaa = [0.0, 0.0, 0.0] + local EPSILON = 1e-10 + if norm(dval - 1) < EPSILON: + # if the projection is close to 1 then the angle between the vectors are almost 0 and we cannot + # reliably determine the perpendicular axis. + # A good approximation is therefore just to set the EAA equal to 0. + eaa = [0.0, 0.0, 0.0] + else: + local axis = cross(direction, x) + local eaa = normalize(axis) * acos(dval) + end + local t_base_target_aligned = pose_trans(t_base_target, p[0, 0, 0, eaa[0], eaa[1], eaa[2]]) + movel(t_base_target_aligned, a = 0.2, v = velocity_move) +end +# End of Align to Plane +# Start of Align Z to Nearest Axis +### +# Aligns the TCP Z axis to the nearest axis of the given frame +# @param frame_id string frame_id to lookup frame +### +def ur_align_z_to_nearest_axis(frame_id = "world"): + ### + # Given a reference frame as input this function returns a struct with the nearest + # pose which aligns the z-axis of the robot TCP with the z-axis of the given reference frame. + # The pose is in the reference of the given frame. + # @param frame bool frame + # @returns struct pose, distance, referencePose + ### + def get_aligned_z_pose(frame): + local actualPose = get_actual_tcp_pose() + local actualPoseInFrame = pose_trans(pose_inv(frame), actualPose) + # Create rotation vector and convert that to RPY representation + local actualRotInFrame = [actualPoseInFrame[3], actualPoseInFrame[4], actualPoseInFrame[5]] + local actRPY = rotvec2rpy(actualRotInFrame) + # Set RX and RY to 0 and convert back to rotation vector + local alignedRot = rpy2rotvec([0, 0, actRPY[2]]) + local alignedRotFlipped = rpy2rotvec([PI, 0, actRPY[2]]) + local zUpPose = actualPoseInFrame + zUpPose[3] = alignedRot[0] + zUpPose[4] = alignedRot[1] + zUpPose[5] = alignedRot[2] + zUpStruct = struct(pose = zUpPose, distance=pose_dist(actualPoseInFrame, zUpPose), referencePose=frame) + local zDownPose = actualPoseInFrame + zDownPose[3] = alignedRotFlipped[0] + zDownPose[4] = alignedRotFlipped[1] + zDownPose[5] = alignedRotFlipped[2] + local zDownStruct = struct(pose = zDownPose, distance=pose_dist(actualPoseInFrame, zDownPose), referencePose=frame) + # Return the solution which is closer to the current robot pose + if (zDownStruct.distance > zUpStruct.distance): + return zUpStruct + else: + return zDownStruct + end + end + local frame = get_pose(frame_id) + # Rotate the given frame so that Z can be align to X-Y-Z respectively + local rotZtoX = rpy2rotvec([0,0.5*PI,0]) + local rotZtoY = rpy2rotvec([0.5*PI,0,0]) + local rotZtoZ = rpy2rotvec([0,0,0]) + # Get aligned poses for each of the rotated frames + local structAlignedToX = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoX[0],rotZtoX[1],rotZtoX[2]])) + structAlignedToY = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoY[0],rotZtoY[1],rotZtoY[2]])) + structAlignedToZ = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoZ[0],rotZtoZ[1],rotZtoZ[2]])) + # Find the nearest alignement + local structAligned = structAlignedToZ + if(structAligned.distance > structAlignedToX.distance): + structAligned = structAlignedToX + end + if(structAligned.distance > structAlignedToY.distance): + structAligned = structAlignedToY + end + # Move the robot to the aligned pose + movel(pose_trans(get_actual_tcp_pose(), p[0,0,0.00001,0,0,0]), v = 0.1) + movel(pose_trans(structAligned.referencePose, structAligned.pose ), v = 0.1) +end +# End of Align Z to Nearest Axis +# Start of Center to Object +### +# Centers to an object by touching the externals of it. It works well for fixtured or heavy parts. +# @param push_force number Force the robot uses to determine if a contact has been achieved +# @param velocity_move number Velocity in freespace +# @param velocity_search number First move is used then search +# @param acc_move number Acceleration in freespace +# @param max_radius_search number Maximum search radius +# @param num_fingers number Number of fingers that the gripper has +### +def ur_center_to_object(push_force = 10, velocity_move = 0.10, velocity_search = 0.01, acc_move = 0.2, max_radius_search = 0.05, num_fingers = 3): + def compute_circle_center(p_list): + # Compute the circle center by circular regression + # Source: https://math.stackexchange.com/questions/2898295/how-to-quickly-fit-a-circle-by-given-random-arc-points + local itr = 0 + local x = 0 + local y = 1 + + local m1 = [[0,0,0],[0,0,0],[0,0,0]] + local m2 = [[0,0],[0,0],[0,0]] + local m3 = [[0],[0],[0]] + + while(itr < get_list_length(p_list)): + local p = p_list[itr] + + if(p_list[itr] == p[0,0,0,0,0,0]): + break + end + + m1[0,0] = m1[0,0] + (p[x]*p[x]) + m1[0,1] = m1[0,1] + (p[x]*p[y]) + m1[0,2] = m1[0,2] + (p[x]) + + m1[1,0] = m1[1,0] + (p[x]*p[y]) + m1[1,1] = m1[1,1] + (p[y]*p[y]) + m1[1,2] = m1[1,2] + (p[y]) + + m1[2,0] = m1[2,0] + (p[x]) + m1[2,1] = m1[2,1] + (p[y]) + + m2[0,0] = m2[0,0] + (pow(p[x], 3)) + m2[0,1] = m2[0,1] + (p[x] * pow(p[y], 2)) + + m2[1,0] = m2[1,0] + (pow(p[y], 3)) + m2[1,1] = m2[1,1] + (pow(p[x], 2) * p[y]) + + m2[2,0] = m2[2,0] + (pow(p[x], 2)) + m2[2,1] = m2[2,1] + (pow(p[y], 2)) + + itr = itr +1 + end + + if(itr < 2): + return p[0,0,0,0,0,0] + elif(itr > get_list_length(p_list)): + return p[0,0,0,0,0,0] + end + + m1[0,0] = 2 * m1[0,0] + m1[0,1] = 2 * m1[0,1] + m1[1,0] = 2 * m1[1,0] + m1[1,1] = 2 * m1[1,1] + m1[2,0] = 2 * m1[2,0] + m1[2,1] = 2 * m1[2,1] + m1[2,2] = itr + m3[0,0] = m2[0,0] + m2[0,1] + m3[1,0] = m2[1,0] + m2[1,1] + m3[2,0] = m2[2,0] + m2[2,1] + + local center = inv(m1) * m3 + + return p[center[0,0], center[1,0],0,0,0,0] + end + + def sanity_checked_move(p_org, p_new, max_diff, acc, vel): + if (pose_dist(p_org, p_new) > max_diff): + movel(p_org, a = acc, v = vel) + popup("New pose is too far away from original. Returning to original", title = "Failed", warning = False, error = True, blocking = True) + else: + movel(p_new, a = acc, v = vel) + end + end + # Start by zeroing the FT sensor + sleep(0.25) + zero_ftsensor() + local p_start = get_actual_tcp_pose() + local p0 = p[0,0,0,0,0,0] + local DIR_X = [1, 0, 0] + if (num_fingers == 2): + local dir_list = [DIR_X, -DIR_X, DIR_X, -DIR_X] + local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]] + local p_list = [p0, p0, p0, p0] + elif (num_fingers == 3): + local DIR_P1 = DIR_X + local DIR_P2 = [-1 / 2, sqrt(3.0) / 2.0, 0] + local DIR_P3 = [-1 / 2, -sqrt(3.0) / 2.0, 0] + local dir_list = [DIR_P1, DIR_P2, DIR_P3, DIR_P1, DIR_P2, DIR_P3] + local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]] + local p_list = [p0, p0, p0, p0, p0, p0] + else: + popup("Number of fingers not supported") + halt + end + # Loop through directions + local it = 0 + local dir_list_size = size(dir_list) + local dir_list_length = dir_list_size[0] + while(it < dir_list_length): + # Move to starting position if more than 3 positions is stored then calculate a new starting position + if(it < 3): + movel(pose_trans(p_start, start_offset[it]), a = acc_move, v = velocity_move) + else: + local p_start_temp = pose_trans(pose_trans(p_start, compute_circle_center(p_list)), start_offset[it]) + local p_start_w_offset = pose_trans(p_start, start_offset[it]) + sanity_checked_move(p_start_w_offset, p_start_temp, max_radius_search, acc_move, velocity_move) + end + local p_start_temp = get_actual_tcp_pose() + # Move into contact and store contact point + sleep(0.1) + local contact_point = ur_move_until_force(distance = max_radius_search, direction = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]], velocity = velocity_search, acceleration = acc_move, stop_force = push_force) + + local dir = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]] + dir = normalize(dir) * 0.05 + contact_point = pose_trans(contact_point, p[dir[0], dir[1], dir[2], 0, 0, 0]) + p_list[it] = pose_trans(pose_inv(p_start), contact_point) + # Move out of contact + movel(p_start_temp, a = acc_move, v = velocity_move) + it = it + 1 + end + # Find circle center based on n stored points + local center_offset_xy = compute_circle_center(p_list) + local p_center = pose_trans(p_start, center_offset_xy) + + # Move the robot to the center if it can + sanity_checked_move(p_start, p_center, max_radius_search, acc_move, velocity_move) +end +# End of Center to Object +# Start of Move Into Contact +### +# Moves the robot into contact in the TCP direction set +# @param force number Force that determines when a contact has been achieved +# @param velocity number Velocity of the robot +# @param acceleration number Acceleration of the robot +# @param max_distance number Maximum distance that the robot searches +# @param velocity_search number velocity_search +# @param retract number Retract distance after a contact has been found +# @param move_tcp_dir array TCP direction (3D vector) +# @param zero_ft_on_start bool Determines if the force-torque sensor should be zeroed on start +### +def ur_move_into_contact(force = 10, velocity = 0.05, acceleration = 0.1, max_distance = 0.25, retract = 0.0, move_tcp_dir = [0, 0, 1], zero_ft_on_start = True): + # Zero the force torque sensor + if (zero_ft_on_start): + sleep(0.25) + zero_ftsensor() + end + # Move the robot + ur_move_until_force(max_distance, move_tcp_dir, velocity, acceleration, force) + # If a retract distance is set, move the robot back to that position + if (retract != 0): + # Compute position offset from TCP direction and retract distance + local position = normalize(move_tcp_dir) * retract + movel(pose_trans(get_actual_tcp_pose(), p[position[0], position[1], position[2], 0, 0, 0])) + end +end +# End of Move Into Contact +# Start of Retract +### +# Retract in the TCP direction set +# @param distance number Retraction distance +# @param direction array TCP direction to move in (3D vector) +# @param acceleration number Acceleration used by the robot +# @param velocity number Velocity used by the robot +### +def ur_retract(distance = -0.1, direction = [0, 0, 1], acceleration = 0.4, velocity = 0.1): + local movement = normalize(direction) * distance + movel(pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]), a = acceleration, v = velocity) +end +# End of Retract \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/00000000000.xd b/tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e/00000000000.xd new file mode 100644 index 0000000000000000000000000000000000000000..4d24bf3718785986ee3756492772a71be75cfdd8 GIT binary patch literal 1675 zcmb7^T~E_c7{|}L4N)-h19(B=e7lf%f$@eIL|BZO1i=_@H0jt8Dy6M$M_l4XPuHGy z?Exj?ONhpxqCwsRi5kCwFQ34BKZnnOZq8*Lx{H6h?RlQx^YY(pVq1h*jzumL+lJ@) zGO=t29&DEj<>GPixl};r17gjG5XImPC1oNouE{yBsV7|13tZb?aXs*sYx^?SgX>%m zZFBAT$hGqeB|DoM)(y!pv0@G?reu~zO;yL;XW%6%Ce{^E?l!P#O3JvwKSam^*KL+GY(QCul8u%9Bai8L8d}DX;Zm}+*gQj) zl*w9UpOUrZ{o?BCRw&tAZ62@bDtrN5j};A5or(~G6;SeN=RZpQ0ZQEqy>8gPGP!{FT2)c=GqXN2%G&GGKARB!~zY<3h z{fWDYg~Sh`UFZ=eh~<_XmZOxtd_Gi^W$;Epf@)nlz9Wut#}rTrirdU3wjhAX9MU=J z#)E{Pv7o%m&UutAzUT)t87I4xdY-0Z1AG&~JlheHMU z04E-t(#+DZE=`GgDF;@%F6L!yoI2elYMLzZVPVutneNh)a6uUNg4croHJg$i%MZYEy4wOTa8zENLkcJ`f#cv>SQ2@ z?<9RAwv$@{JIlc13w-}E#)HGpd9#2_KNGf-VIA~**aG@37CI8L<1>^ky{?YWeP8z{ a+Hm*B{-X%q*7+B{hsERDTHK$p% Date: Mon, 31 Aug 2026 09:11:05 +0200 Subject: [PATCH 40/46] Use 10.14 program folder on 10.14 run --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fabaf9eeb..4a4da2d53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,7 +80,7 @@ jobs: CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' - ROBOT_MODEL: 'ur7e' URSIM_VERSION: '10.14.0-0.10.703-preview-1' - PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e' POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' From 6f6a143ca8d2b26d7a4ea472db8e6df853d90d4b Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 31 Aug 2026 09:21:31 +0200 Subject: [PATCH 41/46] Use released version of 10.14.0 in tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a4da2d53..501f5f1a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' - ROBOT_MODEL: 'ur7e' - URSIM_VERSION: '10.14.0-0.10.703-preview-1' + URSIM_VERSION: '10.14.0' PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e' POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' From 82b1722d0408a723358c209220064c70fbf15dd3 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 31 Aug 2026 10:08:39 +0200 Subject: [PATCH 42/46] Add clang-format exclude rule for ursim data --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7977dfad0..c2c1773c9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: rev: 'v19.1.5' hooks: - id: clang-format - exclude: ^3rdparty/ + exclude: ^3rdparty/|tests/resources/dockerursim - repo: https://github.com/DavidAnson/markdownlint-cli2 rev: v0.13.0 From f7cb33179ee5a5cec5d1f02f00c7e6974b84f693 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 31 Aug 2026 10:25:53 +0200 Subject: [PATCH 43/46] Citadel folder per ursim version --- .github/workflows/ci.yml | 4 ++-- .../citadelDB/{ => 10.13.0}/000001.sst | Bin .../citadelDB/{ => 10.13.0}/000001.vlog | Bin .../citadelDB/{ => 10.13.0}/DISCARD | Bin .../citadelDB/{ => 10.13.0}/KEYREGISTRY | 0 .../citadelDB/{ => 10.13.0}/MANIFEST | Bin .../dockerursim/citadelDB/10.14.0/000001.sst | Bin 0 -> 496 bytes .../dockerursim/citadelDB/10.14.0/000001.vlog | Bin 0 -> 20 bytes .../dockerursim/citadelDB/10.14.0/DISCARD | Bin 0 -> 1048576 bytes .../dockerursim/citadelDB/10.14.0/KEYREGISTRY | 1 + .../dockerursim/citadelDB/10.14.0/MANIFEST | Bin 0 -> 30 bytes 11 files changed, 3 insertions(+), 2 deletions(-) rename tests/resources/dockerursim/citadelDB/{ => 10.13.0}/000001.sst (100%) rename tests/resources/dockerursim/citadelDB/{ => 10.13.0}/000001.vlog (100%) rename tests/resources/dockerursim/citadelDB/{ => 10.13.0}/DISCARD (100%) rename tests/resources/dockerursim/citadelDB/{ => 10.13.0}/KEYREGISTRY (100%) rename tests/resources/dockerursim/citadelDB/{ => 10.13.0}/MANIFEST (100%) create mode 100644 tests/resources/dockerursim/citadelDB/10.14.0/000001.sst create mode 100644 tests/resources/dockerursim/citadelDB/10.14.0/000001.vlog create mode 100644 tests/resources/dockerursim/citadelDB/10.14.0/DISCARD create mode 100644 tests/resources/dockerursim/citadelDB/10.14.0/KEYREGISTRY create mode 100644 tests/resources/dockerursim/citadelDB/10.14.0/MANIFEST diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 501f5f1a7..3d816cef8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,12 +77,12 @@ jobs: URSIM_VERSION: '10.13.0' PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e' POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' - CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' + CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB/10.13.0' - ROBOT_MODEL: 'ur7e' URSIM_VERSION: '10.14.0' PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.14.0/ur7e' POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' - CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' + CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB/10.14.0' steps: - uses: actions/checkout@v7 diff --git a/tests/resources/dockerursim/citadelDB/000001.sst b/tests/resources/dockerursim/citadelDB/10.13.0/000001.sst similarity index 100% rename from tests/resources/dockerursim/citadelDB/000001.sst rename to tests/resources/dockerursim/citadelDB/10.13.0/000001.sst diff --git a/tests/resources/dockerursim/citadelDB/000001.vlog b/tests/resources/dockerursim/citadelDB/10.13.0/000001.vlog similarity index 100% rename from tests/resources/dockerursim/citadelDB/000001.vlog rename to tests/resources/dockerursim/citadelDB/10.13.0/000001.vlog diff --git a/tests/resources/dockerursim/citadelDB/DISCARD b/tests/resources/dockerursim/citadelDB/10.13.0/DISCARD similarity index 100% rename from tests/resources/dockerursim/citadelDB/DISCARD rename to tests/resources/dockerursim/citadelDB/10.13.0/DISCARD diff --git a/tests/resources/dockerursim/citadelDB/KEYREGISTRY b/tests/resources/dockerursim/citadelDB/10.13.0/KEYREGISTRY similarity index 100% rename from tests/resources/dockerursim/citadelDB/KEYREGISTRY rename to tests/resources/dockerursim/citadelDB/10.13.0/KEYREGISTRY diff --git a/tests/resources/dockerursim/citadelDB/MANIFEST b/tests/resources/dockerursim/citadelDB/10.13.0/MANIFEST similarity index 100% rename from tests/resources/dockerursim/citadelDB/MANIFEST rename to tests/resources/dockerursim/citadelDB/10.13.0/MANIFEST diff --git a/tests/resources/dockerursim/citadelDB/10.14.0/000001.sst b/tests/resources/dockerursim/citadelDB/10.14.0/000001.sst new file mode 100644 index 0000000000000000000000000000000000000000..e214d32dad1e295c40b3147d03c5bc3b66d9d8b1 GIT binary patch literal 496 zcmZ9J&1(}u7{;H~-O>+RRw-%LrqB!lTS!aWlk_CDh=}+H*p_8XW}Ag%C(KM5Xvv{B zPkIpq#s8w{q4eOre?mdfi>IFa>gW2T_2dh`d0ysuKVasS{}h-&k{Z>O&1PKFS(0w% zLtg#z0k9*|%qf$|nzCYfM<~~LxU%34hiTUCIB)2FTN_7bZKGEey5m*~DE63U(s^RK zDiUqF($u}t$336`qgvXjIqq;qH@&TyJkNh{jWZqfqKwi{=80_DL;&ZJSS`Y68`pJ9 zrde#AbShATd43jERtvDThHA-pOBtK$EYE|=`UU$JfxED_5zCHisLZ7`=^|16L#r*V z?P^muv%tv`kI7)Ryo`A6j-O_WU7SX5aXYv-zX$w^_8v_EzoV%mU>m@DazMV2n;uXm zPlzS&$S1-W2|s-Mz8^3fC|+FvAu5KpI(Qi#e|&SuT^RTa64!m7 literal 0 HcmV?d00001 diff --git a/tests/resources/dockerursim/citadelDB/10.14.0/000001.vlog b/tests/resources/dockerursim/citadelDB/10.14.0/000001.vlog new file mode 100644 index 0000000000000000000000000000000000000000..f5276abbf5c3f864c59c9cae14710ec70c4e2275 GIT binary patch literal 20 XcmZQzfPl)RHVtjJ`z|MLNJ{|#AYugb literal 0 HcmV?d00001 diff --git a/tests/resources/dockerursim/citadelDB/10.14.0/DISCARD b/tests/resources/dockerursim/citadelDB/10.14.0/DISCARD new file mode 100644 index 0000000000000000000000000000000000000000..9e0f96a2a253b173cb45b41868209a5d043e1437 GIT binary patch literal 1048576 zcmeIuF#!Mo0K%a4Pi+Wah(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ T0|pEjFkrxd0RsjM82APT0Pp|- literal 0 HcmV?d00001 diff --git a/tests/resources/dockerursim/citadelDB/10.14.0/KEYREGISTRY b/tests/resources/dockerursim/citadelDB/10.14.0/KEYREGISTRY new file mode 100644 index 000000000..fdc7bb182 --- /dev/null +++ b/tests/resources/dockerursim/citadelDB/10.14.0/KEYREGISTRY @@ -0,0 +1 @@ +0— vdoÆ7tTѰ:5W(Hello Badger \ No newline at end of file diff --git a/tests/resources/dockerursim/citadelDB/10.14.0/MANIFEST b/tests/resources/dockerursim/citadelDB/10.14.0/MANIFEST new file mode 100644 index 0000000000000000000000000000000000000000..4683694ac4fc3c96686aa8ef69a60762d45e6c47 GIT binary patch literal 30 dcmZ=tNiSkxVBi2^7+`yQRO%lW3kRbCBLGA<1fT!_ literal 0 HcmV?d00001 From ca2a05892154b2a0fbd94d57835ad7522f0261cb Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 31 Aug 2026 15:26:41 +0200 Subject: [PATCH 44/46] Add programs for 10.12.0 --- .github/workflows/ci.yml | 2 +- .../polyscopex/10.12.0/ur5e/00000000000.xd | Bin 0 -> 57646 bytes .../polyscopex/10.12.0/ur5e/blobs/7.blob | 1 + .../polyscopex/10.12.0/ur5e/blobs/8.blob | 1 + .../polyscopex/10.12.0/ur5e/blobs/version | Bin 0 -> 4 bytes 5 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/00000000000.xd create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/7.blob create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/8.blob create mode 100644 tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/version diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d816cef8..8051c4b8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex' - ROBOT_MODEL: 'ur5e' URSIM_VERSION: '10.12.0' - PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e' - ROBOT_MODEL: 'ur7e' URSIM_VERSION: '10.13.0' PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e' diff --git a/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/00000000000.xd b/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/00000000000.xd new file mode 100644 index 0000000000000000000000000000000000000000..783e9030c4dc57ccd778cf3c99ed664696cc9bea GIT binary patch literal 57646 zcmeHw3wRvWm1cLfEXnwR0b{IyY`J9ahjo^~5at?7r2 zc|~24T7I-7%UG5K!A54Y8#_QqhU{d($%bs+Rnvy)9G*$^hl%znuvBy-Na z)!o&4*xf<`oN0fb`(D-S-a7Z*b5H&E{O8(9yDgb)>u5{fm9%ZhA8Svx*&UFvJMkxl zKVA590Dlg`&#^ga4LEFV;8wKBcoI*`Kf0Mxifmn{#C+k z(TSjKJNe1u&oG`lJS#n`JsUh*JUcyxXOE}Vv)A*m$M*cK=cnE|-aEZ}ypMao$y72y zW_Pl!bALyAbt;v9a&T>;sR{aq5r(JLL`_T8vBwym{qS%*-I=r*20B?sa^}X)@e5-9 zVcdeW&a~K-N)LbZPUuXr_tvzy%WI9AG3Yl=VO@3y#rf_Yx48AG^x^}l z^z+YVUZozIctvw-e1AhU+^k0TuZFI)N!6On*bO(18uW`L`Yzzx=Wby#Imp z;zJ|LZvYw4cOJL=?#@Hm^2?|MZasK`8k`$f0viveuRokhzwoJy1XhM0SZnT5l`a|Q_WZFIB)?~+_G?#`x{y55ioQ?+# z-`CXxMNS)F{@QogRcPq&1q%JUaSPpWIK8AhmHybN%8otg7!@TEV>=cgiinfYF6-QHDT}cnQ z9BxZ`;qp{hcam{kZg-KZpR4$6#TN#;JE2}lPpW6YhN>i5p&uKK?pmZk=pL`ldF&ok zV--KC_)~9hs@>!DCX?CBP%m_3Z=bjFJxt{l$Bw|{-v|GXCfibvb=V%pV?TzkQhDpZ zu>;8thTNO#?|*EJ;yiXgx%8gY@eU}|V;?7f?nTwXNIjVN>Pb@8NRnv}kfhw-i{~mD zNHS|XNoH3Z!Go9ALego^nZwcaiXcs|lxcd^a+=Ql5KXUcq3OIwAU$~4kK5POoR z?Xe5n8E{dp+SJq-HXiW!+K!EH3uHPfJd|nc9=~L?8L(tu8&q#R3&*+0Rr1k`Uz{;n zd~IN;jb{#FPXf~dMihB_kp6h)ko!tkMWAUu{oL3>&AVf-+LSO+Y@Em21~y}HH}P=O zaXb0_Gv8w>J1f(bk5xWiIb8Yij0I7CcjYH5|7+#vD!){DuJU`8|GV-hmA|a~7gTXt&Vi>F zsin$n8|Nj|QZuS&te)}kj5Dc@-flGG>Yy!lMb%~)`yFK9ubuI$RA+C}n|IRgB6m_5)(kH8;z%$sZDsIe5f|y2s5}yoTy6Piio2d&-h+9`92h z{41D!ei>ezD_j7YjQ=z_BU&hrn}&iEfU?2SMF7# zVPc;8mu}16r>QX$>w`mm6RoI)kB4JmW|&YFoxB8*K~Pm?Sj(zOuOwYcKd7Y$A{0~)IuLWq?(!D4kwGgv_rfE?21C$=1agi zG`a&o3v-gdLH}0_g*7!vW+jjStMIbU8HQ#WL45+nH-@1%tI$tJ)MKBzzcm8r0vsM) zmrzib48aPq8mAg82t{OrnkBITRo6v1C|iml7Fn0Ov9LxbxdMLVT z=z=V;hRPeP!O05C3jvN*Kvp0d#WJ~KdnK($3=~2!^{_&^0g8x~v>=QLQ-{u}2cTMp zXez-FG!b8{JXVKWOf_1pKtLskLQoE}A}>g+5)6ROR80e6(C_-TB z-wN6!o*?S4phh7hAZM()UPlon&I|}JViZMZc_pZ_5)Ud$G89ghEHH5dG-Tf504i!U z?Nh^}yIYV}BKHm%b3Ps>N|+>ITodfTF|)yx_Ad-%kW;U5Rd=)rG z_C3p1Z(f1j0u67tCLs8Uesfye(!kKf=2ZjGb43Znqge1G2ZFpLhBPoZaq4o3M^Xh% zRzfh@;XJ1XLtqn`@BmAKVZdpoE^uV%(oc}ps%AD3t?hiB#7yY02B6q^b*kgI8mRq_ zM|NC9z3TX_Q+OxO|EwXpbwpQzFP}*3N>Br!uKi1?_RS53LSMF6^}7QbN{@zH$(LUy z=U-gI*KBuvz-{+$Z&_RupvRnkR?nMv!d_T(qiwHGI!OyZJfiAB*XTpUgS9slcz+0;jVAr-uR+aDk0j&`<#5zZwBqGN^&aeu^C2KoHLD1DKR~1~Hj-9ZAX?NK$dx z5FV_&i6qlkLV}p)TAE&QA5E`}()5}~X*&M~SZ9Uns_JR_o^>?6b|X!%`!G$buYZwT zx1f%u3-6|BO(Uci#z1+%K%X2RT%^A zsu3`wNtGlnq)f9@h-GGQ#hPwJ76+5l;Nv+1O1iGLyU4{^D6!_n)L^F#5AoQ8xIBU| zUd_+4K;Gcc;r1bl-%YFeLu$A^2}={hIB*wTNe1piU%;gNhL{BR+W8` z{Jm&Cq{TI%2u(O_C(Dvw@FbZHr zFO9nl=Pe2j=e)Fi{a5;LqVRLgs-E_a_L^6!N%G_AgY5vDdi-&(zqzN|PI}1CU^h8< z*y~T>vXvK?tq^Y>wQS|}+B_^<;W`{HTlt^xf2OC`PA17uA1qmUV9Cn=UB-WIjKlxL z`2P`>wK{NF%l|s#|6jP21%R>r7^1wDx7Jql9K&bf&(N{7-44rIecsw?rglDE+8R|Q zuicNg*7B8PaSIkG?BnEFduun6KD6yinD`zd$*ehF#=mCYMv}`PgamrjRkOcB(z$+` zUcHK@^X~mhrjyhfwfEyl&+jC)(c1mBZFHp9cGdRQ4%D8ketwEN$$P&#eEHXl>I%H8 zD=ZG-N<_`7-XrOQ8JM0)+U`FAwI4b{mmC*&F^eDQ?K{xv@gkbvPO}8YAL{F|0mg^p zecr{-F^iw>If^ZYKSM{m?GAi6ocT7h_#4oVpm*WV-~jfn#Xt8h{z(cTe{UA@$Mz(U zzc&NjHL5ba(|IA+QF$2j~!X33qPR62)zOIDXH5mBi; zx%?z_u^=)3%r$q^U7;`;i1gKJ=dFlG-DagDn5>#>n%dF`k0aPu>fniiVQ zzxEsCeAQN(R@eQ4{Jr2#nl996TC@Kb8OTU%A0ILj4~a*`$1{+TcuG7YewyNn;)~)J z#czoJV^VO%d;j0?ov$MV>m?8@g0neTMcOkOL6HtJQU@R?sSJW5^&AGE)Pq0XuHl18 zPe%tMotB;hnAWx#3AjFp$VPujg>&ueBwcj->k|=z^i}EG`9hGsKMCM!ICH9w zQEmhzIfY2FA~Q;L>alIl0Q)S9^sRYjl-IAzOKEw=rI)%rJ%m$3BoWn3H~}sm=vv!^ z%js*>R>;Fy6*wW0UG))stL#BSNQzA0K+Z5s--nS7ONdU;LU66SFBvMc11mYD57t{GH^u@Mdyz@ z$sbt7F`o#O&ejP&&}=rNyTDl(X`vH+MoT2V!88)OY2bUVOT@>2wMZ;xtY|@RX7H_Y z{0A=Zx=5HHjyP%)K6--h2;{{~<=*-eCiC^85iJs56WI&g+ev@y+DIImv~`l7jolsn z7$>v7_{`!)2A3NbJdVo!z;A9|;qJ<nGF)NSoCM!p(_h|5B+=5Jc3K&XK%Z{7ZqUAqE6dk>%_a8_d0trUbU-@<;+% z@K7Qtn?8`uJu%#ZhekY6stX953{Y$Ko-rQC1`x|_3O9q3d`8*9tC~$3xQAb|)sO9t zB$^Bq*rnP4`=a2A#b4s}4_xB)7cSNM!w7cirH!qi%w0-Js1t&`s01XKld=QFbOsRk(k+%J**#fvaak^`igaG(x#Buw5daa5ZD z_^UOt567~=!KE?BT&Rcfp%VHShc{#nZ$igeP^O^t;*m&`yAb?4R|eG0vAFWorb|qh zs_H1|Zk!CqnCsrI$ul!rF4Yc1>R-~`?~w5|LNUFQ%G*(p7t-E=5!#!p3+1VA9?m=J zJ20O51}l1``i4Vp^^K*t)HfCaVxpkNT>w_>16O>UtHUjOfCd2u^O{JL0iPOcRPd>4 zfiH3}o*_aIQX$r^TZ_zyxCy5kOdus74;zkdShH+3VNwvJqydPt1UvD!IyvN{g(C=$ zKuDhKv1~2y1YyoWEvV_wg1T{3F=lf!E&i2J5!pNpw`?A!P|TZo2t>A|Weuwu+>R&y zmPE5=M%P)(AtX=>POu;>J(J%?NC%|WE5l}!p(SE5`eO9)4I9?2T!n(n0v!}QT4n%I zJQj1EhNl9Y6d&^hc%UI(6^}-mTz6y~u=2hFv6+ZR!5XzCp#p<^*KTK0ralCTn7PT- zHp^l{!-@<=gLZ=YaFjMOK8k#~)SG+5lnDi2Spiw#W&F;fr`#7mKx6|j7`)4aU}=C% z$wh#50j-{N9Ow)6t2QpXb2Y)Zu+KH9z?XOW-==UA^cD{8BWIj1ZDKQwIFRGOZ-Qsk ztjrLxb!Z~`T{5CF_|T|ogn|Err5Pp!e<9!TU;?=q?QR5aBF}WtSmw(Zq`P|6npK+`SFFv5n0CY5rJEvAi1{<>LX-{#$a0rXQAtpg z9@U`+k^sbOQQ&#h3l!vwL^nd*3o}DdjfD+}RS;bTxDGTM9c!ZrJ^-XPQeD-E1Enz@ zRUw)ds;S1^pu9lQ#BShk+}?sg#o~=5XaKpe>4s4&a<~HZ7&@?5-WqyASx;d9BgX)( z2G8WsmxqoWZU?}-&%5qrX5HCT|6_flpl`Pi)a9u+!T~=s# zUxUzaYr~4ET2|;uv`hWG@5PtYy~M6UXEuNAWbeBo7@;x3|M%;cPRr zez)VCjH?&KIhk1xRET3Xlp76 zk7f9pHC2pTp-P&?Y6b@ccngTRQa}#LYCwXZ797m^F0WWlHUdFilvvr4@D3g#euOk2 zTpK~)@QXsgvN#y^MY&;TX?eIfQXqkK45}jN8poADhaeNO6qMOuz~ETq`LiMfgkga* z%>#o|&=g&n`qz4k9?K>t@FA7wfLRPwZ&gz;W`totXcK6$U@)MCq0~(AS!Gahk7KZrXId~o2 zgxD4^gapYP5pZB?8tHAN`Kkfl(n0}|m2u=7QV=4E2%5%1*ou$^TEhT3^hDZl$0g%y z3pLFttq~D7z|zQRq5@lcEOs+UFppbId}s&-4}54gR1pW7BPU>@BEB<2CmaiAOFOgW z(bUPKN5Bwvhn@m!yoW?li^p=6$&JW7Y38)Uc(Ugn*M`FM_B}0zn+`;iF1uJ@o)VV zv-RgN@gK}g{9AvAJ~m9(@L`A>nIb+m_g)EJHegxadkvB2{PhAANL3k>iV|M5Bsd{~ z+{@l8Z7hU_x@1s${2ZLx!P=-<=a+HU2S|Bt9#qR z5mxuMK-TKs7DB5VdtO`wxpXFdbDm83IXE=jswttw2 zrW%Sv(MXu0Xg=g*=5Cj>qS?M@d-TGh+5X`6_9+$3OeHxSUz+Sa(tQYw_-r&mF#7F2 z?~Vn;=s%buCxDg+#y%1b(b(UifU)0u6qu#RLj=!2V&w1G3`Ty2d|4LyJ49}AhUJa}Ff2zLdIGHX5r>|mRy%mf?f6XDj#JZi^x98$*_{xmU_({!30j8} zhrvM(zB6{b0xA`K5j#P9Rx9X!*a_-#%USA9*9k+N40O19Cqvq1PTf-4NLQ?IGFNVQ zGS}SiWajU6GF8iNp%+)Lb21C=b21BCoJ`FFPG-^UTMKIJo%41s+<8N$v3K%2m+o9n z6~@lhJ2&s#xpUXf)=5wp`)^GStG6X7A9>p>=+PM#GI)_8#rPnh7^8z&Z!c{o>KN8| zGdPBgICLj<2!qS|yp5}vMhV^YI^m0bdwuUQ+9b!ECTVPfVS&8iPWU3Pyax%xaN%D3 z42K~y<2Eu>X3eAN>_s%ad^4svbTlV3H|Aun_TElU&6{z%OTLZ$qX$#tlZ_v1e3r_$ z@iUE|Z~SWGccx6fTW?PeFI$!zIMSUw=)~b<7!1t`f8(fO=*RQGQ3$R9!-a9;axzIm z*Y9F7BLpb?aISYKKZV%~*!%arSa#p1r`>l3BXzVh8>$9QbU}*OV_Xg}C!WAi9FU$R z{C`_pn|*NAt!U?jAiu5HS?Uz`~^;?>P{z9-QZ*vY;iIRH78TE$H^>u zU|B&ep?cKm>Xn(6P_I=Nt1Rv5s-)ho-leWr@12C6uHTs){!o3A1gtJYDn1Djje>^w z&z%hI;f{EFX_|@ttW`1E6&UKdo!MSLa4aibi0Ir7-4{bSLrS}DVmSz}Gdb%cG@V^h zPyW7q5vJI6>zvG7$;n)Ohm)CiSG`MMTEpnBt8LNl({@vVX|39R&DIW2nZV|*NDe=| z66#!J?A;1#W8|hz{rYTNZzHN&aZhkrb17v0HnatnP#s%t1&G0r3GF7gCd2n_uh{VWExWh!V;+dsa^*n=28fetVISDAq| zIMmZIoE=aV#*EaH%TC%%dDf*>c-NBGOHhy#5wO5dwxSTO5)_mPjO_Y0Ektbf6ok;Ka!_L-9%)F2u;;2_;wncm6(}<7xu}E$jNQrxWR-(Ry8)dIN}R|B zVEKjDf~FKKS_H*}Jr@E{3eO4f+ByVshFG|)z(YlX5Yjq;!SBUTv7xoG_z8tQx2%v9 zG%e6+h7NFe5Q0iW^hTXEd5*VqPU1pbvAuz~R~8%Pn-e8yeNBY$>+oZQ_z-J|5Q>tA zMI=LmAf;RY*H?<^6to^i2Kk1#w}vJLAgZ<`aV(6)0IU<5CJ6|yYY<#pDmoX)J)hWK~h;G0Gku&yrF4YVNteo?jI8idoIH2hyd%X5ab&+CqxJS(Z|4 z_b4{(IYfOI1>Mw0^lDZM;BE~ZR0fu2l@OQ-JOqg@NDdcTgvrh*F2W!@bh5*qJL}Db z&Bx%%HrmDI9gbnkk8+eLG=yzI?|hTLz|YD8SL_R16ED!BD#$D)%WlXw-%w>N?mgik zJWe`z0zhph_J{J=Z&Rn7*tGx-F!m`7ff=8TP!6*NKoCgF=Y5c*6g5tNt>nU;; zUlYHFB4_c&iD75)-R|(1UDe^_sq*{bt&4Ce;+)l{?Y@LSSDWnDK@$@so=SRFLPHJ~g~ybbl%FlS};MJbHDG401_x z#7g|+cZehA5Qqe1KwOFe2Eenh#7{=XP>G*>$%V!!>24Cx;8~_imXOx zdtgBU984dj>FoCbDi42OZeWTIrVaCLl9_8bnXBhl(IfNTTV-d~71Ey?Yh5Azx9OMD zU#AYH>F=h0nEuD~FV$uTDwqGzi+>MvQr==q4R=fTlO`+Yq}z$G;` z?CW{FtfvEfyopQdhP3Tk#i)PRvXcch2y4@p#VQ(bjg6oJ8{CTl@F9Se@}S1aCa0*d zsOxeYlNDYUHB*4)bp>34zyk-oVHAbcLy8Vte9Jx&@>91vrt zzHKI(oh(VPy>9@VxFl5yz^>syYE=}GXTdeXROOHed-jfCCr?GwOd31cQZz+2VIyZj zPy#F`3o0;P4d?Qhm15C8~bKf%V(U?uKVqhSoXOhTy?N=+`XsmF3!fjW#$ zO-3Jyp1;Wj`0qPGrl?PRL1ucp^j&w3xtEV$XpEL`nm zYV`MIEt~%4@hzMFME|4xT^Y-!|49E(|0(J*(f^757y3Wf{}uJ#N%ELj_kR17%-YCJ z_D#dxSNpnp@{EAv=BNq(c*=J?2@`%-X2L&y7$$teO2!{wCj9^Lj~{=QhW|hQY3hp7 zd=nK-)iqEgig?j&?1Ut8{42+u9RM;CIsSu5l8DOMr|!NPY`t;F$CqD0$iH1PkxvyP1o3}k9v4}2cXYtqNAfD*tS_SIsF_HfY2%w2wSRw4u69e4%l zRry{u)cU1;WZ=gGzd-A^|Hu?dBzCiXYCAu^M9L|ZYVcrAB5u0&E7gRzIc@N4`QT?z zKt85CBcQ==<$r&GfCgV90;+`qQ9#QvMFDMgGII?+E1<#O4F21N1vJDA&6sil%?;S6 z9uZ&=fyL`^x87t|(8xa-?iL!<$IYQLqej!v_k2U&hS7AuHJXNANj-VnN!yoWbaC%m zMpwyvb{Jg+&U8glYf9$x7>urx`8*z}GM!*JvVuq^lub56BP-PZA)o z9pFsBEd!1OM7mO}KrjSfm2hJ*^o-)@T&AR{RtO@a1$3Tgc}szuV9KmyDuEEZy=Ev# z-7Ka8Mbfz>S(TBD$?|~bus{}q0Dr(r0pyK0^^m{^1EFGTPz;@m2VKI40IkzZQGlp$ zs>Vu^2^9$iHBp8=KvWK!t{4GKn9jvnK^1xghB?%+3kHGCWf~UHr~slf0=xno)*|aN zAv%|77z(EeK%;`{0Mk$dFhf>V*5X1CKUL=;mfpnbQ7oNHutEwC1UpfM4#6wXK$eWH z6i_Wr6g5c`i|rZ{qjLeNj^_~$wnP9=1rwSWcS63`7<&VXr9kTlui6f+=8#p;XV>0FYi^17;UtO#sZRWBj@vt|KV!=p(sULt+jt5IX4G63mGC^eq3{WM75g%c48#kt2c1C$y z=v;+O#(!CKu7ckACVzq7lm#wv6u5{`pgC2LO-kro7`lZ*l|%o36j*us?|i4f0$^na z0V_{`Crj)C*k<))VQ$exC zGjq;VA+~t@2WR|~!p3_4Ci~Rfd-7os>9(<8kuxu+o~+NqJERic0jo|B^^51BBs@VpGuklJAaC z;8J8UmhY?sq z&03r8HA*m}FSzcSy{QCxG@(;LVW>+9^eBNIre(t5#66~9A1c^N)n5WVMhc_^dbqkr z3G^7zJzSs&3xQA*O(qNDXe8{jhMd`u9c_<*3E_&ySP%g4$h#8s;3gczrl6Pp%R@cR z{FI;`j&~LS9xtBF0v<2EoC7?h+p>Vii$9=%N5f_cZ&cSM9Gqi~lkuC03E> zUi{rlUW#+PblIff99QkNPd&U3{9}p?L0mb~Z|0@bsnqdf`SF5d>RyO9Txq9p(+GFG z;g?>g5r>Z_E62LS?jkpg2rqng0dZ2PqydKCT={>-LSxRZBY}o}xk%oFLwyHMJKo*k z>~;3x%Cm8b*yO_0ln@(#39%_~V=YkdPE4}`vDqnxok|Yg(S%7bh1}PvYtnvu9K1`2 z%_!&b5@K@!e$B;zDTn8TAVJ=4f4ELacDOAeHYGQe3wY|3+*C41Ny$wmA31_xvL!c_ zJS?r`rb02rlA8+Xj7dx&i zKi7mohubme@Y7BtVn7jm_R|=N_}n0&?rZ}r1gJaZtg>?Tj;xIyQ> zeC}J+3Fh4Q&z+~>(7B(V`^`k*p>u!83rozsr~T>oT-Q$acFI66>)YMX%QwG4`9Wvb zjp7G=^Iv@5d?TM9RQb){(;c63=u2tGr?(k>DKM0{3%DpKlzEYRn!iPVnF0fyPw`7k zHu_T9@u{@qQ)$PirTzl`9iK*Yk4f>CDe#jai@3atu$Kut%M|p^e@Qz&eQVy?K?1l8op8d${OJDz zlc*yCjU4>+0H%WiF- z2r}~O+*hk9GV<#6uii|NkyqtcmrW8fa`&z6PX}*nA2-;JvrG4@KgooadFk|6p=DmX zrsB0ZG_*`bCbZ0JH;xJ|^V(8^Pd)kpkP?KJSpWs2*skK7%-q{=%L?hWHLq>P&@$u0 zN3ZR8&7z@YrdUXk+uEPrddK)ea{P$SPrsm$D$d_pd0wOeiYlp)&aWLQr1OnFM@Y3$ qAc?<&DGF(`lbLJWkrmSU==ldPETr@H`NMCEkmfFHfBMlo+y5P_vBjDI literal 0 HcmV?d00001 diff --git a/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/7.blob b/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/7.blob new file mode 100644 index 000000000..038ec4144 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/7.blob @@ -0,0 +1 @@ +{"jsonContent":{"applicationContributions":{"universal-robots-external-control-external-control-application":{"type":"universal-robots-external-control-external-control-application","version":"1.0.0","port":50002,"robotIP":"192.168.56.1"},"ur-mounting":{"type":"ur-mounting","version":"0.0.1","mounting":{"baseAngle":{"value":0,"unit":"deg"},"tiltAngle":{"value":0,"unit":"deg"}}},"ur-frames":{"type":"ur-frames","version":"0.0.7","framesList":[{"name":"base","nameVariable":{"name":"base","reference":false,"type":"$$Variable","valueType":"frame","id":"d9f00c7f-8b75-8d68-0214-06051f1d8736","_IDENTIFIER":"VariableDeclaration"},"parent":"world","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"tcp","nameVariable":{"name":"tcp","reference":false,"type":"$$Variable","valueType":"frame","id":"6ca71c28-a663-bd2e-a74c-0b729f247436","_IDENTIFIER":"VariableDeclaration"},"parent":"flange","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"world","nameVariable":{"name":"world","reference":false,"type":"$$Variable","valueType":"frame","id":"e6baf704-2c11-1f94-23e8-6e927d87bd93","_IDENTIFIER":"VariableDeclaration"},"pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"flange","nameVariable":{"name":"flange","reference":false,"type":"$$Variable","valueType":"frame","id":"68a861d6-1cfd-b16f-b354-d41ec7314273","_IDENTIFIER":"VariableDeclaration"},"parent":"base","pose":{"position":[0,0,0],"orientation":[0,0,0]}}]},"ur-grid-pattern":{"type":"ur-grid-pattern","version":"0.0.3","grids":[{"grid":{"name":"grid","reference":false,"type":"$$Variable","valueType":"grid","id":"1c461799-be53-5fab-66ed-b74b58335a0e","_IDENTIFIER":"VariableDeclaration"},"waypoint":{"name":"grid_iterator","reference":false,"type":"$$Variable","valueType":"waypoint","id":"0f4a6ca6-8fc4-0645-eedf-14b08d21979a","_IDENTIFIER":"VariableDeclaration"},"corners":[null,null,null,null],"numRows":4,"numColumns":5}]},"ur-end-effector":{"type":"ur-end-effector","version":"0.0.2","endEffectors":[{"id":"dab06a35-b16d-af95-83fe-5859d39a238e","name":"Robot","payload":{"weight":{"value":0,"unit":"kg"}},"cog":{"cx":{"value":0,"unit":"m"},"cy":{"value":0,"unit":"m"},"cz":{"value":0,"unit":"m"}},"inertia":{"Ixx":{"value":0,"unit":"kg*m^2"},"Iyy":{"value":0,"unit":"kg*m^2"},"Izz":{"value":0,"unit":"kg*m^2"},"Ixy":{"value":0,"unit":"kg*m^2"},"Ixz":{"value":0,"unit":"kg*m^2"},"Iyz":{"value":0,"unit":"kg*m^2"}},"useCustomInertia":false,"tcps":[{"id":"d8373c0b-897b-d3df-81e7-17b4a127e865","name":"Tool_flange","x":{"value":0,"unit":"m"},"y":{"value":0,"unit":"m"},"z":{"value":0,"unit":"m"},"rx":{"value":0,"unit":"rad"},"ry":{"value":0,"unit":"rad"},"rz":{"value":0,"unit":"rad"}}]}],"defaultTcp":{"endEffectorId":"dab06a35-b16d-af95-83fe-5859d39a238e","tcpId":"d8373c0b-897b-d3df-81e7-17b4a127e865"}},"ur-motion-profiles":{"type":"ur-motion-profiles","version":"0.0.1","moveProfiles":{"joint":[{"isDefault":false,"profile":{"name":"Joint_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"edd90b36-2a6c-49b0-b8aa-f07ebac18fda","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Joint_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"23f7919b-5a54-1953-f853-52af055b6b53","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"linear":[{"isDefault":false,"profile":{"name":"Linear_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"e9811b6f-f5c2-3abf-2764-0cf99c0379d0","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Linear_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"ca02498b-b2f4-4ee1-0d29-36b8f09e2866","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"process":[{"isDefault":true,"profile":{"name":"Process","reference":false,"type":"$$Variable","valueType":"profile","id":"65a1ca90-550a-bfbb-613e-ec1920320ecb","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"Classic","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2}}}]}},"ur-smart-skills":{"type":"ur-smart-skills","version":"0.0.3","preamble":"# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper"},"ur-application-variables":{"type":"ur-application-variables","version":"0.0.1","variables":{}}},"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{}},"sourcesNodes":{"robot":{"groupId":"robot","version":"1.0.0.","sources":[{"sourceID":"ur-wired-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"},{"signalID":"AO 0","direction":"OUT","valueType":"FLOAT"},{"signalID":"AO 1","direction":"OUT","valueType":"FLOAT"}],"webSocketURL":"/sources/wired-io"},{"sourceID":"ur-tool-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"}],"webSocketURL":"/sources/tool-io"}],"isDynamic":false},"ur-modbus":{"groupId":"ur-modbus","isDynamic":true,"version":"1.0.0","sources":[]},"ur-robot-io":{"type":"ur-robot-io","groupId":"ur-robot-io","isDynamic":false,"version":"1.0.2","sources":[{"sourceID":"ur-robot-wired-io","name":"Wired I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 0","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 1","valueType":"FLOAT"}]},{"sourceID":"ur-robot-tool-io","name":"Tool I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"}]}],"parameters":{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{},"toolOutput":{"dualPinPower":false,"voltage":{"value":0,"unit":"V"},"powerOutput":{"DO 0":1,"DO 1":1}}},"migrateSourceConfigDone":true}}},"safety":{"settings":{"io":{"automaticModeSafeguardResetInput":{"name":"automaticModeSafeguardResetInput","valueA":255,"valueB":255},"automaticModeSafeguardStopInput":{"name":"automaticModeSafeguardStopInput","valueA":255,"valueB":255},"emergencyStopInput":{"name":"emergencyStopInput","valueA":255,"valueB":255},"notReducedModeOutput":{"name":"notReducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"operationalModeInput":{"name":"operationalModeInput","valueA":255,"valueB":255},"reducedModeInput":{"name":"reducedModeInput","valueA":255,"valueB":255},"reducedModeOutput":{"name":"reducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotMovingOutput":{"name":"robotMovingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotNotStoppingOutput":{"name":"robotNotStoppingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeHomeOutput":{"name":"safeHomeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeguardResetInput":{"name":"safeguardResetInput","valueA":0,"valueB":1},"systemEmergencyStoppedOutput":{"name":"systemEmergencyStoppedOutput","ossdEnabled":false,"valueA":255,"valueB":255},"threePositionSwitchInput":{"name":"threePositionSwitchInput","valueA":255,"valueB":255},"freedriveEnabledInput":{"name":"freedriveEnabledInput","valueA":255,"valueB":255},"threePositionEnablingStopOutput":{"name":"threePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255},"notThreePositionEnablingStopOutput":{"name":"notThreePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255}},"major":5,"minor":13,"normalJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"normalJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"normalRobotLimits":{"elbowForce":150,"elbowSpeed":1.5,"momentum":25,"power":300,"stoppingDistance":0.5,"stoppingTime":0.4,"toolForce":150,"toolSpeed":1.5},"reducedJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"reducedJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"reducedRobotLimits":{"elbowForce":120,"elbowSpeed":0.75,"momentum":10,"power":200,"stoppingDistance":0.3,"stoppingTime":0.3,"toolForce":120,"toolSpeed":0.75},"safetyHardware":{"injectionMoldingMachineInterface":"NONE","teachPendant":"NORMAL"},"safetyPlanes":{"planes":[{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"}],"ioSafetyPlanes":[{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"}]},"safetySafeHome":{"base":-1,"elbow":-1,"shoulder":-1,"wrist1":-1,"wrist2":-1,"wrist3":-1,"enabled":false},"safetyAPIParameters":{"numberOfClients":0,"clients":[]},"safetyFieldbusses":{"enablePROFIsafe":false,"sourceAddressPROFIsafe":0,"destAddressPROFIsafe":0,"modeControlPROFIsafe":false},"threePosition":{"allowManualHighSpeed":true,"useTeachPendantAs3PE":false},"toolDirection":{"limitDeviation":6.2831855,"limitDirection":{"x":0,"y":0,"z":1},"limitRestriction":"DISABLED","toolPan":0,"toolTilt":0},"toolPositions":{"toolPositions":[{"name":"Tool Flange","center":{"x":0,"y":0,"z":0},"radius":0,"definition":2},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0}]},"normalWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"},"reducedWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"}},"crc":"633835311"},"operatorScreens":[{"type":"ur-operator-screen-default","version":"0.0.2","parameters":{"status":[],"configuration":[]}}],"sidebarItems":[{"type":"ur-global-variables","version":"1.0.0","disabled":{"master":false,"automaticMode":false,"remoteMode":true}},{"type":"ur-log-messages-sidebar","version":"0.0.1","disabled":{"master":true,"automaticMode":true,"remoteMode":true}}],"smartSkills":[{"name":"Align to Plane","enabled":true,"type":"ur-align-to-plane","parameters":{"radius":0.05,"push_force":20,"n_plane_points":3,"max_distance":0.25,"velocity_slow":0.001,"velocity_search":0.035,"velocity_move":0.1,"acceleration":0.1}},{"name":"Align Z to Nearest Axis","enabled":true,"type":"ur-align-z-to-nearest-axis"},{"name":"Center","enabled":true,"type":"ur-center","parameters":{"push_force":10,"velocity_move":0.05,"acc_move":0.2,"max_radius_search":0.05,"num_fingers":3}},{"name":"Freedrive","enabled":true,"type":"ur-freedrive","version":"1.0.0","recordingFrequency":50,"recordingSignals":["timestamp","target_q","actual_TCP_pose","tcp_offset"]},{"name":"Move into Contact","enabled":true,"type":"ur-move-into-contact","parameters":{"force":10,"velocity":0.05,"acceleration":0.2,"max_distance":0.25,"retract":0}},{"name":"Retract","enabled":true,"type":"ur-retract","parameters":{"distance":-0.1,"acceleration":0.4,"velocity":0.1}},{"name":"Put into Box","enabled":false,"type":"ur-put-in-box","version":"1.0.0"},{"name":"Custom","enabled":false,"type":"ur-custom-smart-skill","parameters":{"includePreamble":true,"includeModules":false},"version":"1.0.0"},{"name":"Home","enabled":true,"type":"ur-position","version":"1.1.2","parameters":{"actualWaypoint":{"frame":"base","pose":{"position":[-1.8246917738038495E-9,-0.2329000001676105,1.0793999999522315],"orientation":[3.987257497300885E-9,2.2214414675120993,-2.221441467056474]},"qNear":{"base":0,"shoulder":-1.5707963249999999,"elbow":0,"wrist1":-1.5707963249999999,"wrist2":0,"wrist3":0}},"variable":{"name":"Home","reference":false,"type":"$$Variable","valueType":"waypoint","id":"13559fa6-4a01-5019-ca23-e7e7432dcfd2","_IDENTIFIER":"VariableDeclaration"}}}]},"script":{"script":"set_safety_mode_transition_hardness(1)\nreset_world_model()\nset_input_actions_to_default()\nset_analog_outputdomain(0,0)\nset_analog_outputdomain(1,0)\nset_standard_analog_input_domain(0,0)\nset_standard_analog_input_domain(1,0)\nset_tool_output_mode(0)\nset_tool_voltage(0)\nset_tool_digital_output_mode(0,1)\nset_tool_digital_output_mode(1,1)\nset_tool_analog_input_domain(0,0)\nset_tool_analog_input_domain(1,0)\nset_gravity([0, 0, 9.82])\nlocal existingBaseParent = get_frame_parent(\"base\")\nlocal basePose = get_pose(\"base\", existingBaseParent)\nbasePose[3] = 0\nbasePose[4] = 0\nbasePose[5] = 0\nmove_frame(\"base\", basePose, existingBaseParent)\nglobal base = \"base\"\nglobal tcp = \"tcp\"\nglobal world = \"world\"\nglobal flange = \"flange\"\nset_target_payload(0, [0, 0, 0], [0, 0, 0, 0, 0, 0])\nset_tcp(p[0, 0, 0, 0, 0, 0], \"Tool_flange\")\n# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper\n# Waypoint variable for Home smart skill\nglobal Home = struct(p=p[-1.8246917738038495e-9, -0.2329000001676105, 1.0793999999522315, 3.987257497300885e-9, 2.2214414675120993, -2.221441467056474], frame=\"base\", q=[0, -1.5707963249999999, 0, -1.5707963249999999, 0, 0])\n# Start of Align to Plane\n###\n# Align to plane will touch up a plane by moving the robot into contact with the table or part in several locations to determine its orientation. Afterwards the robot will orient its tool to the plane.\n# @param radius number Radius [m] of the circle within the plane will be touched up\n# @param push_force number How hard to robot pushed against the plane\n# @param n_plane_points number Number of points that the robot uses to compute the plane\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_slow number Velocity when pressing downwards\n# @param velocity_search number Velocity used when approaching the touch up point\n# @param velocity_move number Velocity used in freespace\n# @param acceleration number Acceleration of the robot\n# @param direction array 3D vector determining the direction of the TCP for touching up the plane\n###\ndef ur_align_to_plane(radius = 0.05, push_force = 20, n_plane_points = 3, max_distance = 0.25, velocity_slow = 0.001, velocity_search = 0.035, velocity_move = 0.10, acceleration = 0.1, direction = [0, 0, 1]):\n local angle = 2 * PI / n_plane_points\n local start_pos = get_target_tcp_pose()\n local retract_distance = -0.015\n ur_move_tcp_direction(retract_distance, direction, velocity_move, acceleration, 0)\n sleep(0.25)\n zero_ftsensor()\n local cnt = 0\n local t_base_target = get_target_tcp_pose()\n local mean_point = [0.0, 0.0, 0.0]\n local A = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]\n local b = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\n while cnt < n_plane_points:\n local new_pos = pose_trans(t_base_target, p[cos(angle * cnt) * radius, sin(angle * cnt) * radius, 0.0, 0.0, 0.0, 0.0])\n local blend_radius = norm(point_dist(get_actual_tcp_pose(), new_pos))/5\n movel(new_pos, a = acceleration, v = velocity_move, r = blend_radius)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_search, acceleration, push_force)\n local movement = normalize(direction * -1) * 0.0005\n local target_pose = pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose)\n sleep(0.2)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_slow, acceleration, push_force)\n sleep(0.2)\n while (not is_steady()):\n sync()\n end\n local poked_point = get_target_tcp_pose()\n poked_point = pose_trans(inv(t_base_target), poked_point)\n A[cnt, 0] = poked_point[0]\n A[cnt, 1] = poked_point[1]\n A[cnt, 2] = 1.0\n b[cnt] = poked_point[2]\n mean_point = mean_point + [poked_point[0], poked_point[1], poked_point[2]]\n movel(new_pos, a = 0.2, v = velocity_move, r = blend_radius)\n cnt = cnt + 1\n end\n mean_point = mean_point / n_plane_points\n cnt = 0\n while cnt < n_plane_points:\n local cntj = 0\n while cntj < 2:\n A[cnt, cntj] = A[cnt, cntj] - mean_point[cntj]\n cntj = cntj + 1\n end\n b[cnt] = b[cnt] - mean_point[2]\n cnt = cnt + 1\n end\n local x1 = inv(transpose(A) * A) * transpose(A) * b\n local x = normalize([x1[0], x1[1], -1])\n local d = dot(mean_point, x)\n local dval = dot(direction, x)\n if dval < 0:\n x = -x\n dval = -dval\n end\n local eaa = [0.0, 0.0, 0.0]\n local EPSILON = 1e-10\n if norm(dval - 1) < EPSILON:\n # if the projection is close to 1 then the angle between the vectors are almost 0 and we cannot\n # reliably determine the perpendicular axis.\n # A good approximation is therefore just to set the EAA equal to 0.\n eaa = [0.0, 0.0, 0.0]\n else:\n local axis = cross(direction, x)\n local eaa = normalize(axis) * acos(dval)\n end\n local t_base_target_aligned = pose_trans(t_base_target, p[0, 0, 0, eaa[0], eaa[1], eaa[2]])\n movel(t_base_target_aligned, a = 0.2, v = velocity_move)\nend\n# End of Align to Plane\n# Start of Align Z to Nearest Axis\n###\n# Aligns the TCP Z axis to the nearest axis of the given frame\n# @param frame_id string frame_id to lookup frame\n###\ndef ur_align_z_to_nearest_axis(frame_id = \"world\"):\n ###\n # Given a reference frame as input this function returns a struct with the nearest\n # pose which aligns the z-axis of the robot TCP with the z-axis of the given reference frame.\n # The pose is in the reference of the given frame.\n # @param frame bool frame\n # @returns struct pose, distance, referencePose\n ###\n def get_aligned_z_pose(frame):\n local actualPose = get_actual_tcp_pose()\n local actualPoseInFrame = pose_trans(pose_inv(frame), actualPose)\n # Create rotation vector and convert that to RPY representation\n local actualRotInFrame = [actualPoseInFrame[3], actualPoseInFrame[4], actualPoseInFrame[5]]\n local actRPY = rotvec2rpy(actualRotInFrame)\n # Set RX and RY to 0 and convert back to rotation vector\n local alignedRot = rpy2rotvec([0, 0, actRPY[2]])\n local alignedRotFlipped = rpy2rotvec([PI, 0, actRPY[2]])\n local zUpPose = actualPoseInFrame\n zUpPose[3] = alignedRot[0]\n zUpPose[4] = alignedRot[1]\n zUpPose[5] = alignedRot[2]\n zUpStruct = struct(pose = zUpPose, distance=pose_dist(actualPoseInFrame, zUpPose), referencePose=frame)\n local zDownPose = actualPoseInFrame\n zDownPose[3] = alignedRotFlipped[0]\n zDownPose[4] = alignedRotFlipped[1]\n zDownPose[5] = alignedRotFlipped[2]\n local zDownStruct = struct(pose = zDownPose, distance=pose_dist(actualPoseInFrame, zDownPose), referencePose=frame)\n # Return the solution which is closer to the current robot pose\n if (zDownStruct.distance > zUpStruct.distance):\n return zUpStruct\n else:\n return zDownStruct\n end\n end\n local frame = get_pose(frame_id)\n # Rotate the given frame so that Z can be align to X-Y-Z respectively \n local rotZtoX = rpy2rotvec([0,0.5*PI,0])\n local rotZtoY = rpy2rotvec([0.5*PI,0,0])\n local rotZtoZ = rpy2rotvec([0,0,0])\n # Get aligned poses for each of the rotated frames\n local structAlignedToX = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoX[0],rotZtoX[1],rotZtoX[2]]))\n structAlignedToY = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoY[0],rotZtoY[1],rotZtoY[2]]))\n structAlignedToZ = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoZ[0],rotZtoZ[1],rotZtoZ[2]]))\n # Find the nearest alignement\n local structAligned = structAlignedToZ\n if(structAligned.distance > structAlignedToX.distance):\n structAligned = structAlignedToX \n end\n if(structAligned.distance > structAlignedToY.distance):\n structAligned = structAlignedToY \n end\n # Move the robot to the aligned pose\n movel(pose_trans(get_actual_tcp_pose(), p[0,0,0.00001,0,0,0]), v = 0.1)\n movel(pose_trans(structAligned.referencePose, structAligned.pose ), v = 0.1)\nend\n# End of Align Z to Nearest Axis\n# Start of Center to Object\n###\n# Centers to an object by touching the externals of it. It works well for fixtured or heavy parts.\n# @param push_force number Force the robot uses to determine if a contact has been achieved\n# @param velocity_move number Velocity in freespace\n# @param velocity_search number First move is used then search\n# @param acc_move number Acceleration in freespace\n# @param max_radius_search number Maximum search radius\n# @param num_fingers number Number of fingers that the gripper has\n###\ndef ur_center_to_object(push_force = 10, velocity_move = 0.10, velocity_search = 0.01, acc_move = 0.2, max_radius_search = 0.05, num_fingers = 3):\n def compute_circle_center(p_list):\n # Compute the circle center by circular regression\n # Source: https://math.stackexchange.com/questions/2898295/how-to-quickly-fit-a-circle-by-given-random-arc-points\n local itr = 0\n local x = 0\n local y = 1\n \n local m1 = [[0,0,0],[0,0,0],[0,0,0]]\n local m2 = [[0,0],[0,0],[0,0]]\n local m3 = [[0],[0],[0]]\n \n while(itr < get_list_length(p_list)):\n local p = p_list[itr]\n \n if(p_list[itr] == p[0,0,0,0,0,0]):\n break\n end\n \n m1[0,0] = m1[0,0] + (p[x]*p[x])\n m1[0,1] = m1[0,1] + (p[x]*p[y])\n m1[0,2] = m1[0,2] + (p[x])\n \n m1[1,0] = m1[1,0] + (p[x]*p[y])\n m1[1,1] = m1[1,1] + (p[y]*p[y])\n m1[1,2] = m1[1,2] + (p[y])\n \n m1[2,0] = m1[2,0] + (p[x])\n m1[2,1] = m1[2,1] + (p[y])\n \n m2[0,0] = m2[0,0] + (pow(p[x], 3))\n m2[0,1] = m2[0,1] + (p[x] * pow(p[y], 2))\n \n m2[1,0] = m2[1,0] + (pow(p[y], 3))\n m2[1,1] = m2[1,1] + (pow(p[x], 2) * p[y])\n \n m2[2,0] = m2[2,0] + (pow(p[x], 2))\n m2[2,1] = m2[2,1] + (pow(p[y], 2))\n \n itr = itr +1\n end\n \n if(itr < 2):\n return p[0,0,0,0,0,0]\n elif(itr > get_list_length(p_list)):\n return p[0,0,0,0,0,0]\n end\n \n m1[0,0] = 2 * m1[0,0]\n m1[0,1] = 2 * m1[0,1]\n m1[1,0] = 2 * m1[1,0]\n m1[1,1] = 2 * m1[1,1]\n m1[2,0] = 2 * m1[2,0]\n m1[2,1] = 2 * m1[2,1]\n m1[2,2] = itr\n m3[0,0] = m2[0,0] + m2[0,1]\n m3[1,0] = m2[1,0] + m2[1,1]\n m3[2,0] = m2[2,0] + m2[2,1]\n \n local center = inv(m1) * m3\n \n return p[center[0,0], center[1,0],0,0,0,0]\n end\n \n def sanity_checked_move(p_org, p_new, max_diff, acc, vel):\n if (pose_dist(p_org, p_new) > max_diff):\n movel(p_org, a = acc, v = vel)\n popup(\"New pose is too far away from original. Returning to original\", title = \"Failed\", warning = False, error = True, blocking = True)\n else:\n movel(p_new, a = acc, v = vel)\n end\n end\n # Start by zeroing the FT sensor\n sleep(0.25)\n zero_ftsensor()\n local p_start = get_actual_tcp_pose()\n local p0 = p[0,0,0,0,0,0]\n local DIR_X = [1, 0, 0]\n if (num_fingers == 2):\n local dir_list = [DIR_X, -DIR_X, DIR_X, -DIR_X]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0]\n elif (num_fingers == 3):\n local DIR_P1 = DIR_X\n local DIR_P2 = [-1 / 2, sqrt(3.0) / 2.0, 0]\n local DIR_P3 = [-1 / 2, -sqrt(3.0) / 2.0, 0]\n local dir_list = [DIR_P1, DIR_P2, DIR_P3, DIR_P1, DIR_P2, DIR_P3]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0, p0, p0]\n else:\n popup(\"Number of fingers not supported\")\n halt\n end\n # Loop through directions\n local it = 0\n local dir_list_size = size(dir_list)\n local dir_list_length = dir_list_size[0]\n while(it < dir_list_length):\n # Move to starting position if more than 3 positions is stored then calculate a new starting position\n if(it < 3):\n movel(pose_trans(p_start, start_offset[it]), a = acc_move, v = velocity_move)\n else:\n local p_start_temp = pose_trans(pose_trans(p_start, compute_circle_center(p_list)), start_offset[it])\n local p_start_w_offset = pose_trans(p_start, start_offset[it])\n sanity_checked_move(p_start_w_offset, p_start_temp, max_radius_search, acc_move, velocity_move)\n end\n local p_start_temp = get_actual_tcp_pose()\n # Move into contact and store contact point\n sleep(0.1)\n local contact_point = ur_move_until_force(distance = max_radius_search, direction = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]], velocity = velocity_search, acceleration = acc_move, stop_force = push_force)\n \n local dir = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]]\n dir = normalize(dir) * 0.05\n contact_point = pose_trans(contact_point, p[dir[0], dir[1], dir[2], 0, 0, 0])\n p_list[it] = pose_trans(pose_inv(p_start), contact_point)\n # Move out of contact\n movel(p_start_temp, a = acc_move, v = velocity_move)\n it = it + 1\n end\n # Find circle center based on n stored points\n local center_offset_xy = compute_circle_center(p_list)\n local p_center = pose_trans(p_start, center_offset_xy)\n \n # Move the robot to the center if it can\n sanity_checked_move(p_start, p_center, max_radius_search, acc_move, velocity_move)\nend\n# End of Center to Object\n# Start of Move Into Contact\n###\n# Moves the robot into contact in the TCP direction set\n# @param force number Force that determines when a contact has been achieved\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_search number velocity_search\n# @param retract number Retract distance after a contact has been found\n# @param move_tcp_dir array TCP direction (3D vector)\n# @param zero_ft_on_start bool Determines if the force-torque sensor should be zeroed on start\n###\ndef ur_move_into_contact(force = 10, velocity = 0.05, acceleration = 0.1, max_distance = 0.25, retract = 0.0, move_tcp_dir = [0, 0, 1], zero_ft_on_start = True):\n # Zero the force torque sensor\n if (zero_ft_on_start):\n sleep(0.25)\n zero_ftsensor()\n end\n # Move the robot\n ur_move_until_force(max_distance, move_tcp_dir, velocity, acceleration, force)\n # If a retract distance is set, move the robot back to that position\n if (retract != 0):\n # Compute position offset from TCP direction and retract distance\n local position = normalize(move_tcp_dir) * retract\n movel(pose_trans(get_actual_tcp_pose(), p[position[0], position[1], position[2], 0, 0, 0]))\n end\nend\n# End of Move Into Contact\n# Start of Retract\n###\n# Retract in the TCP direction set\n# @param distance number Retraction distance\n# @param direction array TCP direction to move in (3D vector)\n# @param acceleration number Acceleration used by the robot\n# @param velocity number Velocity used by the robot\n###\ndef ur_retract(distance = -0.1, direction = [0, 0, 1], acceleration = 0.4, velocity = 0.1):\n local movement = normalize(direction) * distance\n movel(pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]), a = acceleration, v = velocity)\nend\n# End of Retract","nodeIDList":[]}} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/8.blob b/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/8.blob new file mode 100644 index 000000000..3cbd1d641 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/8.blob @@ -0,0 +1 @@ +{"jsonContent":{"applicationContributions":{"universal-robots-external-control-external-control-application":{"type":"universal-robots-external-control-external-control-application","version":"1.0.0","port":50002,"robotIP":"192.168.56.1"},"ur-mounting":{"type":"ur-mounting","version":"0.0.1","mounting":{"baseAngle":{"value":0,"unit":"deg"},"tiltAngle":{"value":0,"unit":"deg"}}},"ur-frames":{"type":"ur-frames","version":"0.0.7","framesList":[{"name":"base","nameVariable":{"name":"base","reference":false,"type":"$$Variable","valueType":"frame","id":"45a3ea74-40fd-968b-7168-5aa0bb450afb","_IDENTIFIER":"VariableDeclaration"},"parent":"world","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"tcp","nameVariable":{"name":"tcp","reference":false,"type":"$$Variable","valueType":"frame","id":"fe707afe-4948-157e-7667-539ca280fd52","_IDENTIFIER":"VariableDeclaration"},"parent":"flange","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"world","nameVariable":{"name":"world","reference":false,"type":"$$Variable","valueType":"frame","id":"d48fc758-d1cb-9746-384b-54a7cfdeade9","_IDENTIFIER":"VariableDeclaration"},"pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"flange","nameVariable":{"name":"flange","reference":false,"type":"$$Variable","valueType":"frame","id":"5973813c-28a0-3a2c-4605-29d81b4e6452","_IDENTIFIER":"VariableDeclaration"},"parent":"base","pose":{"position":[0,0,0],"orientation":[0,0,0]}}]},"ur-grid-pattern":{"type":"ur-grid-pattern","version":"0.0.3","grids":[{"grid":{"name":"grid","reference":false,"type":"$$Variable","valueType":"grid","id":"3281b103-38c0-7b9b-31a0-41312ee1e575","_IDENTIFIER":"VariableDeclaration"},"waypoint":{"name":"grid_iterator","reference":false,"type":"$$Variable","valueType":"waypoint","id":"ff764efc-6dc2-b4f5-e98c-e101fc060702","_IDENTIFIER":"VariableDeclaration"},"corners":[null,null,null,null],"numRows":4,"numColumns":5}]},"ur-end-effector":{"type":"ur-end-effector","version":"0.0.2","endEffectors":[{"id":"4c24420d-ca88-1b37-fe73-f3a3342e36c8","name":"Robot","payload":{"weight":{"value":0,"unit":"kg"}},"cog":{"cx":{"value":0,"unit":"m"},"cy":{"value":0,"unit":"m"},"cz":{"value":0,"unit":"m"}},"inertia":{"Ixx":{"value":0,"unit":"kg*m^2"},"Iyy":{"value":0,"unit":"kg*m^2"},"Izz":{"value":0,"unit":"kg*m^2"},"Ixy":{"value":0,"unit":"kg*m^2"},"Ixz":{"value":0,"unit":"kg*m^2"},"Iyz":{"value":0,"unit":"kg*m^2"}},"useCustomInertia":false,"tcps":[{"id":"227bd394-4a66-d9b6-6844-32ffbb6c67ab","name":"Tool_flange","x":{"value":0,"unit":"m"},"y":{"value":0,"unit":"m"},"z":{"value":0,"unit":"m"},"rx":{"value":0,"unit":"rad"},"ry":{"value":0,"unit":"rad"},"rz":{"value":0,"unit":"rad"}}]}],"defaultTcp":{"endEffectorId":"4c24420d-ca88-1b37-fe73-f3a3342e36c8","tcpId":"227bd394-4a66-d9b6-6844-32ffbb6c67ab"}},"ur-motion-profiles":{"type":"ur-motion-profiles","version":"0.0.1","moveProfiles":{"joint":[{"isDefault":false,"profile":{"name":"Joint_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"056a7cb5-b0b5-d717-d500-14cedb165027","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Joint_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"293db526-2660-afb2-f7ee-09c5cb2dd64c","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"linear":[{"isDefault":false,"profile":{"name":"Linear_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"562882d9-1a44-debe-3e6a-d28f3c1dbbb1","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Linear_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"5fa73a6b-233f-7e56-5c16-4c912d883544","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"process":[{"isDefault":true,"profile":{"name":"Process","reference":false,"type":"$$Variable","valueType":"profile","id":"06b0b8ee-fb2c-03cf-61ce-30015720ff97","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"Classic","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2}}}]}},"ur-smart-skills":{"type":"ur-smart-skills","version":"0.0.3","preamble":"# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper"},"ur-application-variables":{"type":"ur-application-variables","version":"0.0.1","variables":{}}},"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{}},"sourcesNodes":{"robot":{"groupId":"robot","version":"1.0.0.","sources":[{"sourceID":"ur-wired-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"},{"signalID":"AO 0","direction":"OUT","valueType":"FLOAT"},{"signalID":"AO 1","direction":"OUT","valueType":"FLOAT"}],"webSocketURL":"/sources/wired-io"},{"sourceID":"ur-tool-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"}],"webSocketURL":"/sources/tool-io"}],"isDynamic":false},"ur-modbus":{"groupId":"ur-modbus","isDynamic":true,"version":"1.0.0","sources":[]},"ur-robot-io":{"type":"ur-robot-io","groupId":"ur-robot-io","isDynamic":false,"version":"1.0.2","sources":[{"sourceID":"ur-robot-wired-io","name":"Wired I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 0","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 1","valueType":"FLOAT"}]},{"sourceID":"ur-robot-tool-io","name":"Tool I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"}]}],"parameters":{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{},"toolOutput":{"dualPinPower":false,"voltage":{"value":0,"unit":"V"},"powerOutput":{"DO 0":1,"DO 1":1}}},"migrateSourceConfigDone":true}}},"safety":{"settings":{"io":{"automaticModeSafeguardResetInput":{"name":"automaticModeSafeguardResetInput","valueA":255,"valueB":255},"automaticModeSafeguardStopInput":{"name":"automaticModeSafeguardStopInput","valueA":255,"valueB":255},"emergencyStopInput":{"name":"emergencyStopInput","valueA":255,"valueB":255},"notReducedModeOutput":{"name":"notReducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"operationalModeInput":{"name":"operationalModeInput","valueA":255,"valueB":255},"reducedModeInput":{"name":"reducedModeInput","valueA":255,"valueB":255},"reducedModeOutput":{"name":"reducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotMovingOutput":{"name":"robotMovingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotNotStoppingOutput":{"name":"robotNotStoppingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeHomeOutput":{"name":"safeHomeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeguardResetInput":{"name":"safeguardResetInput","valueA":0,"valueB":1},"systemEmergencyStoppedOutput":{"name":"systemEmergencyStoppedOutput","ossdEnabled":false,"valueA":255,"valueB":255},"threePositionSwitchInput":{"name":"threePositionSwitchInput","valueA":255,"valueB":255},"freedriveEnabledInput":{"name":"freedriveEnabledInput","valueA":255,"valueB":255},"threePositionEnablingStopOutput":{"name":"threePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255},"notThreePositionEnablingStopOutput":{"name":"notThreePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255}},"major":5,"minor":13,"normalJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"normalJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"normalRobotLimits":{"elbowForce":150,"elbowSpeed":1.5,"momentum":25,"power":300,"stoppingDistance":0.5,"stoppingTime":0.4,"toolForce":150,"toolSpeed":1.5},"reducedJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"reducedJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"reducedRobotLimits":{"elbowForce":120,"elbowSpeed":0.75,"momentum":10,"power":200,"stoppingDistance":0.3,"stoppingTime":0.3,"toolForce":120,"toolSpeed":0.75},"safetyHardware":{"injectionMoldingMachineInterface":"NONE","teachPendant":"NORMAL"},"safetyPlanes":{"planes":[{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"}],"ioSafetyPlanes":[{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"}]},"safetySafeHome":{"base":-1,"elbow":-1,"shoulder":-1,"wrist1":-1,"wrist2":-1,"wrist3":-1,"enabled":false},"safetyAPIParameters":{"numberOfClients":0,"clients":[]},"safetyFieldbusses":{"enablePROFIsafe":false,"sourceAddressPROFIsafe":0,"destAddressPROFIsafe":0,"modeControlPROFIsafe":false},"threePosition":{"allowManualHighSpeed":true,"useTeachPendantAs3PE":false},"toolDirection":{"limitDeviation":6.2831855,"limitDirection":{"x":0,"y":0,"z":1},"limitRestriction":"DISABLED","toolPan":0,"toolTilt":0},"toolPositions":{"toolPositions":[{"name":"Tool Flange","center":{"x":0,"y":0,"z":0},"radius":0,"definition":2},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0}]},"normalWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"},"reducedWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"}},"crc":"633835311"},"operatorScreens":[{"type":"ur-operator-screen-default","version":"0.0.2","parameters":{"status":[],"configuration":[]}}],"sidebarItems":[{"type":"ur-global-variables","version":"1.0.0","disabled":{"master":false,"automaticMode":false,"remoteMode":true}},{"type":"ur-log-messages-sidebar","version":"0.0.1","disabled":{"master":true,"automaticMode":true,"remoteMode":true}}],"smartSkills":[{"name":"Align to Plane","enabled":true,"type":"ur-align-to-plane","parameters":{"radius":0.05,"push_force":20,"n_plane_points":3,"max_distance":0.25,"velocity_slow":0.001,"velocity_search":0.035,"velocity_move":0.1,"acceleration":0.1}},{"name":"Align Z to Nearest Axis","enabled":true,"type":"ur-align-z-to-nearest-axis"},{"name":"Center","enabled":true,"type":"ur-center","parameters":{"push_force":10,"velocity_move":0.05,"acc_move":0.2,"max_radius_search":0.05,"num_fingers":3}},{"name":"Freedrive","enabled":true,"type":"ur-freedrive","version":"1.0.0","recordingFrequency":50,"recordingSignals":["timestamp","target_q","actual_TCP_pose","tcp_offset"]},{"name":"Move into Contact","enabled":true,"type":"ur-move-into-contact","parameters":{"force":10,"velocity":0.05,"acceleration":0.2,"max_distance":0.25,"retract":0}},{"name":"Retract","enabled":true,"type":"ur-retract","parameters":{"distance":-0.1,"acceleration":0.4,"velocity":0.1}},{"name":"Put into Box","enabled":false,"type":"ur-put-in-box","version":"1.0.0"},{"name":"Custom","enabled":false,"type":"ur-custom-smart-skill","parameters":{"includePreamble":true,"includeModules":false},"version":"1.0.0"},{"name":"Home","enabled":true,"type":"ur-position","version":"1.1.2","parameters":{"actualWaypoint":{"frame":"base","pose":{"position":[-1.8246917738038495E-9,-0.2329000001676105,1.0793999999522315],"orientation":[3.987257497300885E-9,2.2214414675120993,-2.221441467056474]},"qNear":{"base":0,"shoulder":-1.5707963249999999,"elbow":0,"wrist1":-1.5707963249999999,"wrist2":0,"wrist3":0}},"variable":{"name":"Home","reference":false,"type":"$$Variable","valueType":"waypoint","id":"6f453ceb-fdb9-b23f-c02c-e1b9750b1446","_IDENTIFIER":"VariableDeclaration"}}}]},"script":{"script":"set_safety_mode_transition_hardness(1)\nreset_world_model()\nset_input_actions_to_default()\nset_analog_outputdomain(0,0)\nset_analog_outputdomain(1,0)\nset_standard_analog_input_domain(0,0)\nset_standard_analog_input_domain(1,0)\nset_tool_output_mode(0)\nset_tool_voltage(0)\nset_tool_digital_output_mode(0,1)\nset_tool_digital_output_mode(1,1)\nset_tool_analog_input_domain(0,0)\nset_tool_analog_input_domain(1,0)\nset_gravity([0, 0, 9.82])\nlocal existingBaseParent = get_frame_parent(\"base\")\nlocal basePose = get_pose(\"base\", existingBaseParent)\nbasePose[3] = 0\nbasePose[4] = 0\nbasePose[5] = 0\nmove_frame(\"base\", basePose, existingBaseParent)\nglobal base = \"base\"\nglobal tcp = \"tcp\"\nglobal world = \"world\"\nglobal flange = \"flange\"\nset_target_payload(0, [0, 0, 0], [0, 0, 0, 0, 0, 0])\nset_tcp(p[0, 0, 0, 0, 0, 0], \"Tool_flange\")\n# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper\n# Waypoint variable for Home smart skill\nglobal Home = struct(p=p[-1.8246917738038495e-9, -0.2329000001676105, 1.0793999999522315, 3.987257497300885e-9, 2.2214414675120993, -2.221441467056474], frame=\"base\", q=[0, -1.5707963249999999, 0, -1.5707963249999999, 0, 0])\n# Start of Align to Plane\n###\n# Align to plane will touch up a plane by moving the robot into contact with the table or part in several locations to determine its orientation. Afterwards the robot will orient its tool to the plane.\n# @param radius number Radius [m] of the circle within the plane will be touched up\n# @param push_force number How hard to robot pushed against the plane\n# @param n_plane_points number Number of points that the robot uses to compute the plane\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_slow number Velocity when pressing downwards\n# @param velocity_search number Velocity used when approaching the touch up point\n# @param velocity_move number Velocity used in freespace\n# @param acceleration number Acceleration of the robot\n# @param direction array 3D vector determining the direction of the TCP for touching up the plane\n###\ndef ur_align_to_plane(radius = 0.05, push_force = 20, n_plane_points = 3, max_distance = 0.25, velocity_slow = 0.001, velocity_search = 0.035, velocity_move = 0.10, acceleration = 0.1, direction = [0, 0, 1]):\n local angle = 2 * PI / n_plane_points\n local start_pos = get_target_tcp_pose()\n local retract_distance = -0.015\n ur_move_tcp_direction(retract_distance, direction, velocity_move, acceleration, 0)\n sleep(0.25)\n zero_ftsensor()\n local cnt = 0\n local t_base_target = get_target_tcp_pose()\n local mean_point = [0.0, 0.0, 0.0]\n local A = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]\n local b = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\n while cnt < n_plane_points:\n local new_pos = pose_trans(t_base_target, p[cos(angle * cnt) * radius, sin(angle * cnt) * radius, 0.0, 0.0, 0.0, 0.0])\n local blend_radius = norm(point_dist(get_actual_tcp_pose(), new_pos))/5\n movel(new_pos, a = acceleration, v = velocity_move, r = blend_radius)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_search, acceleration, push_force)\n local movement = normalize(direction * -1) * 0.0005\n local target_pose = pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose)\n sleep(0.2)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_slow, acceleration, push_force)\n sleep(0.2)\n while (not is_steady()):\n sync()\n end\n local poked_point = get_target_tcp_pose()\n poked_point = pose_trans(inv(t_base_target), poked_point)\n A[cnt, 0] = poked_point[0]\n A[cnt, 1] = poked_point[1]\n A[cnt, 2] = 1.0\n b[cnt] = poked_point[2]\n mean_point = mean_point + [poked_point[0], poked_point[1], poked_point[2]]\n movel(new_pos, a = 0.2, v = velocity_move, r = blend_radius)\n cnt = cnt + 1\n end\n mean_point = mean_point / n_plane_points\n cnt = 0\n while cnt < n_plane_points:\n local cntj = 0\n while cntj < 2:\n A[cnt, cntj] = A[cnt, cntj] - mean_point[cntj]\n cntj = cntj + 1\n end\n b[cnt] = b[cnt] - mean_point[2]\n cnt = cnt + 1\n end\n local x1 = inv(transpose(A) * A) * transpose(A) * b\n local x = normalize([x1[0], x1[1], -1])\n local d = dot(mean_point, x)\n local dval = dot(direction, x)\n if dval < 0:\n x = -x\n dval = -dval\n end\n local eaa = [0.0, 0.0, 0.0]\n local EPSILON = 1e-10\n if norm(dval - 1) < EPSILON:\n # if the projection is close to 1 then the angle between the vectors are almost 0 and we cannot\n # reliably determine the perpendicular axis.\n # A good approximation is therefore just to set the EAA equal to 0.\n eaa = [0.0, 0.0, 0.0]\n else:\n local axis = cross(direction, x)\n local eaa = normalize(axis) * acos(dval)\n end\n local t_base_target_aligned = pose_trans(t_base_target, p[0, 0, 0, eaa[0], eaa[1], eaa[2]])\n movel(t_base_target_aligned, a = 0.2, v = velocity_move)\nend\n# End of Align to Plane\n# Start of Align Z to Nearest Axis\n###\n# Aligns the TCP Z axis to the nearest axis of the given frame\n# @param frame_id string frame_id to lookup frame\n###\ndef ur_align_z_to_nearest_axis(frame_id = \"world\"):\n ###\n # Given a reference frame as input this function returns a struct with the nearest\n # pose which aligns the z-axis of the robot TCP with the z-axis of the given reference frame.\n # The pose is in the reference of the given frame.\n # @param frame bool frame\n # @returns struct pose, distance, referencePose\n ###\n def get_aligned_z_pose(frame):\n local actualPose = get_actual_tcp_pose()\n local actualPoseInFrame = pose_trans(pose_inv(frame), actualPose)\n # Create rotation vector and convert that to RPY representation\n local actualRotInFrame = [actualPoseInFrame[3], actualPoseInFrame[4], actualPoseInFrame[5]]\n local actRPY = rotvec2rpy(actualRotInFrame)\n # Set RX and RY to 0 and convert back to rotation vector\n local alignedRot = rpy2rotvec([0, 0, actRPY[2]])\n local alignedRotFlipped = rpy2rotvec([PI, 0, actRPY[2]])\n local zUpPose = actualPoseInFrame\n zUpPose[3] = alignedRot[0]\n zUpPose[4] = alignedRot[1]\n zUpPose[5] = alignedRot[2]\n zUpStruct = struct(pose = zUpPose, distance=pose_dist(actualPoseInFrame, zUpPose), referencePose=frame)\n local zDownPose = actualPoseInFrame\n zDownPose[3] = alignedRotFlipped[0]\n zDownPose[4] = alignedRotFlipped[1]\n zDownPose[5] = alignedRotFlipped[2]\n local zDownStruct = struct(pose = zDownPose, distance=pose_dist(actualPoseInFrame, zDownPose), referencePose=frame)\n # Return the solution which is closer to the current robot pose\n if (zDownStruct.distance > zUpStruct.distance):\n return zUpStruct\n else:\n return zDownStruct\n end\n end\n local frame = get_pose(frame_id)\n # Rotate the given frame so that Z can be align to X-Y-Z respectively \n local rotZtoX = rpy2rotvec([0,0.5*PI,0])\n local rotZtoY = rpy2rotvec([0.5*PI,0,0])\n local rotZtoZ = rpy2rotvec([0,0,0])\n # Get aligned poses for each of the rotated frames\n local structAlignedToX = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoX[0],rotZtoX[1],rotZtoX[2]]))\n structAlignedToY = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoY[0],rotZtoY[1],rotZtoY[2]]))\n structAlignedToZ = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoZ[0],rotZtoZ[1],rotZtoZ[2]]))\n # Find the nearest alignement\n local structAligned = structAlignedToZ\n if(structAligned.distance > structAlignedToX.distance):\n structAligned = structAlignedToX \n end\n if(structAligned.distance > structAlignedToY.distance):\n structAligned = structAlignedToY \n end\n # Move the robot to the aligned pose\n movel(pose_trans(get_actual_tcp_pose(), p[0,0,0.00001,0,0,0]), v = 0.1)\n movel(pose_trans(structAligned.referencePose, structAligned.pose ), v = 0.1)\nend\n# End of Align Z to Nearest Axis\n# Start of Center to Object\n###\n# Centers to an object by touching the externals of it. It works well for fixtured or heavy parts.\n# @param push_force number Force the robot uses to determine if a contact has been achieved\n# @param velocity_move number Velocity in freespace\n# @param velocity_search number First move is used then search\n# @param acc_move number Acceleration in freespace\n# @param max_radius_search number Maximum search radius\n# @param num_fingers number Number of fingers that the gripper has\n###\ndef ur_center_to_object(push_force = 10, velocity_move = 0.10, velocity_search = 0.01, acc_move = 0.2, max_radius_search = 0.05, num_fingers = 3):\n def compute_circle_center(p_list):\n # Compute the circle center by circular regression\n # Source: https://math.stackexchange.com/questions/2898295/how-to-quickly-fit-a-circle-by-given-random-arc-points\n local itr = 0\n local x = 0\n local y = 1\n \n local m1 = [[0,0,0],[0,0,0],[0,0,0]]\n local m2 = [[0,0],[0,0],[0,0]]\n local m3 = [[0],[0],[0]]\n \n while(itr < get_list_length(p_list)):\n local p = p_list[itr]\n \n if(p_list[itr] == p[0,0,0,0,0,0]):\n break\n end\n \n m1[0,0] = m1[0,0] + (p[x]*p[x])\n m1[0,1] = m1[0,1] + (p[x]*p[y])\n m1[0,2] = m1[0,2] + (p[x])\n \n m1[1,0] = m1[1,0] + (p[x]*p[y])\n m1[1,1] = m1[1,1] + (p[y]*p[y])\n m1[1,2] = m1[1,2] + (p[y])\n \n m1[2,0] = m1[2,0] + (p[x])\n m1[2,1] = m1[2,1] + (p[y])\n \n m2[0,0] = m2[0,0] + (pow(p[x], 3))\n m2[0,1] = m2[0,1] + (p[x] * pow(p[y], 2))\n \n m2[1,0] = m2[1,0] + (pow(p[y], 3))\n m2[1,1] = m2[1,1] + (pow(p[x], 2) * p[y])\n \n m2[2,0] = m2[2,0] + (pow(p[x], 2))\n m2[2,1] = m2[2,1] + (pow(p[y], 2))\n \n itr = itr +1\n end\n \n if(itr < 2):\n return p[0,0,0,0,0,0]\n elif(itr > get_list_length(p_list)):\n return p[0,0,0,0,0,0]\n end\n \n m1[0,0] = 2 * m1[0,0]\n m1[0,1] = 2 * m1[0,1]\n m1[1,0] = 2 * m1[1,0]\n m1[1,1] = 2 * m1[1,1]\n m1[2,0] = 2 * m1[2,0]\n m1[2,1] = 2 * m1[2,1]\n m1[2,2] = itr\n m3[0,0] = m2[0,0] + m2[0,1]\n m3[1,0] = m2[1,0] + m2[1,1]\n m3[2,0] = m2[2,0] + m2[2,1]\n \n local center = inv(m1) * m3\n \n return p[center[0,0], center[1,0],0,0,0,0]\n end\n \n def sanity_checked_move(p_org, p_new, max_diff, acc, vel):\n if (pose_dist(p_org, p_new) > max_diff):\n movel(p_org, a = acc, v = vel)\n popup(\"New pose is too far away from original. Returning to original\", title = \"Failed\", warning = False, error = True, blocking = True)\n else:\n movel(p_new, a = acc, v = vel)\n end\n end\n # Start by zeroing the FT sensor\n sleep(0.25)\n zero_ftsensor()\n local p_start = get_actual_tcp_pose()\n local p0 = p[0,0,0,0,0,0]\n local DIR_X = [1, 0, 0]\n if (num_fingers == 2):\n local dir_list = [DIR_X, -DIR_X, DIR_X, -DIR_X]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0]\n elif (num_fingers == 3):\n local DIR_P1 = DIR_X\n local DIR_P2 = [-1 / 2, sqrt(3.0) / 2.0, 0]\n local DIR_P3 = [-1 / 2, -sqrt(3.0) / 2.0, 0]\n local dir_list = [DIR_P1, DIR_P2, DIR_P3, DIR_P1, DIR_P2, DIR_P3]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0, p0, p0]\n else:\n popup(\"Number of fingers not supported\")\n halt\n end\n # Loop through directions\n local it = 0\n local dir_list_size = size(dir_list)\n local dir_list_length = dir_list_size[0]\n while(it < dir_list_length):\n # Move to starting position if more than 3 positions is stored then calculate a new starting position\n if(it < 3):\n movel(pose_trans(p_start, start_offset[it]), a = acc_move, v = velocity_move)\n else:\n local p_start_temp = pose_trans(pose_trans(p_start, compute_circle_center(p_list)), start_offset[it])\n local p_start_w_offset = pose_trans(p_start, start_offset[it])\n sanity_checked_move(p_start_w_offset, p_start_temp, max_radius_search, acc_move, velocity_move)\n end\n local p_start_temp = get_actual_tcp_pose()\n # Move into contact and store contact point\n sleep(0.1)\n local contact_point = ur_move_until_force(distance = max_radius_search, direction = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]], velocity = velocity_search, acceleration = acc_move, stop_force = push_force)\n \n local dir = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]]\n dir = normalize(dir) * 0.05\n contact_point = pose_trans(contact_point, p[dir[0], dir[1], dir[2], 0, 0, 0])\n p_list[it] = pose_trans(pose_inv(p_start), contact_point)\n # Move out of contact\n movel(p_start_temp, a = acc_move, v = velocity_move)\n it = it + 1\n end\n # Find circle center based on n stored points\n local center_offset_xy = compute_circle_center(p_list)\n local p_center = pose_trans(p_start, center_offset_xy)\n \n # Move the robot to the center if it can\n sanity_checked_move(p_start, p_center, max_radius_search, acc_move, velocity_move)\nend\n# End of Center to Object\n# Start of Move Into Contact\n###\n# Moves the robot into contact in the TCP direction set\n# @param force number Force that determines when a contact has been achieved\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_search number velocity_search\n# @param retract number Retract distance after a contact has been found\n# @param move_tcp_dir array TCP direction (3D vector)\n# @param zero_ft_on_start bool Determines if the force-torque sensor should be zeroed on start\n###\ndef ur_move_into_contact(force = 10, velocity = 0.05, acceleration = 0.1, max_distance = 0.25, retract = 0.0, move_tcp_dir = [0, 0, 1], zero_ft_on_start = True):\n # Zero the force torque sensor\n if (zero_ft_on_start):\n sleep(0.25)\n zero_ftsensor()\n end\n # Move the robot\n ur_move_until_force(max_distance, move_tcp_dir, velocity, acceleration, force)\n # If a retract distance is set, move the robot back to that position\n if (retract != 0):\n # Compute position offset from TCP direction and retract distance\n local position = normalize(move_tcp_dir) * retract\n movel(pose_trans(get_actual_tcp_pose(), p[position[0], position[1], position[2], 0, 0, 0]))\n end\nend\n# End of Move Into Contact\n# Start of Retract\n###\n# Retract in the TCP direction set\n# @param distance number Retraction distance\n# @param direction array TCP direction to move in (3D vector)\n# @param acceleration number Acceleration used by the robot\n# @param velocity number Velocity used by the robot\n###\ndef ur_retract(distance = -0.1, direction = [0, 0, 1], acceleration = 0.4, velocity = 0.1):\n local movement = normalize(direction) * distance\n movel(pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]), a = acceleration, v = velocity)\nend\n# End of Retract","nodeIDList":[]}} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/version b/tests/resources/dockerursim/programs/polyscopex/10.12.0/ur5e/blobs/version new file mode 100644 index 0000000000000000000000000000000000000000..720d64f4baafc33efdf971f02084aca5f25b34a5 GIT binary patch literal 4 LcmZQzU|<9Q00jU7 literal 0 HcmV?d00001 From eb66a0486d761255ea5502477b99f2fae4aade22 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 31 Aug 2026 16:31:22 +0200 Subject: [PATCH 45/46] Delete unused queryPolyScopeVersion function --- .../ur/dashboard_client_implementation_x.h | 1 - src/ur/dashboard_client_implementation_x.cpp | 7 ------- 2 files changed, 8 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client_implementation_x.h b/include/ur_client_library/ur/dashboard_client_implementation_x.h index 29882b873..2eb52180b 100644 --- a/include/ur_client_library/ur/dashboard_client_implementation_x.h +++ b/include/ur_client_library/ur/dashboard_client_implementation_x.h @@ -193,7 +193,6 @@ class DashboardClientImplX : public DashboardClientImpl DashboardResponse del(const std::string& endpoint, const bool debug = true); - virtual VersionInformation queryPolyScopeVersion(); void assertHasCommand(const std::string& command) override; const std::string base_url_ = "/universal-robots/robot-api"; diff --git a/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index 641501354..b8aa79fbc 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -209,13 +209,6 @@ timeval DashboardClientImplX::getConfiguredSendTimeout() const return send_timeout_; } -VersionInformation DashboardClientImplX::queryPolyScopeVersion() -{ - DashboardResponse response = commandPolyscopeVersion(); - std::string version_string = std::get(response.data["polyscope_version"]); - return VersionInformation::fromString(version_string); -} - void DashboardClientImplX::assertHasCommand(const std::string& command) { if (is_connected_ == false) From 5b29ea84a0199aed5459378583e1c53fd8d7853e Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 31 Aug 2026 16:31:33 +0200 Subject: [PATCH 46/46] Add more tests --- tests/test_dashboard_client_x.cpp | 174 ++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 56539eeab..d74501c5b 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -724,6 +724,17 @@ static constexpr const char* MOCK_OPENAPI_RESPONSE = R"({"info":{"version":"5.0. static constexpr const char* MOCK_SUPPORTFILES_ENDPOINT = "/universal-robots/robot-api/supportfiles/v1"; static constexpr const char* MOCK_OPENAPI_ENDPOINT = "/universal-robots/robot-api/openapi.json"; +// Thin subclass used only in tests to expose the protected is_connected_ field. +class TestableDashboardClientMock : public DashboardClientImplX +{ +public: + using DashboardClientImplX::DashboardClientImplX; + bool isConnected() const + { + return is_connected_; + } +}; + class DashboardClientImplXMockTest : public ::testing::Test { protected: @@ -796,6 +807,18 @@ TEST_F(DashboardClientImplXMockTest, shutdown_forbidden) EXPECT_EQ(response.message, body); } +TEST_F(DashboardClientImplXMockTest, shutdown_endpoint_not_found) +{ + // The server is reachable but no handler is registered for the shutdown + // endpoint, so httplib returns 404 by default. The client must treat this as + // a failed (non-ok) response without throwing an exception. + // Note: no server_.Put(MOCK_SHUTDOWN_ENDPOINT, ...) registration here. + + auto response = impl_->commandShutdown(); + EXPECT_FALSE(response.ok); + EXPECT_EQ(std::get(response.data.at("status_code")), 404); +} + // --- commandGenerateFlightReport --- // Spec: POST /supportfiles/v1 // 200 → GenerateFlightReportResponse {"message": string|null, "details": string|null} @@ -977,6 +1000,157 @@ TEST_F(DashboardClientImplXMockTest, download_support_files_invalid_path) EXPECT_NE(response.message.find("Failed to create temporary file"), std::string::npos); } +TEST_F(DashboardClientImplXMockTest, download_support_files_connection_error) +{ + // The server is stopped after connect() so the streaming GET has no server to talk to. + // The httplib::Result is falsy (error != Success) and the client must surface this as a + // non-ok response containing "HTTP request failed". The final file must not be created. + const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_support_conn_err.zip"; + std::filesystem::remove(out); + + server_.stop(); + if (server_thread_.joinable()) + { + server_thread_.join(); + } + + auto response = impl_->commandDownloadSupportFiles(out.string()); + + EXPECT_FALSE(response.ok); + EXPECT_NE(response.message.find("HTTP request failed"), std::string::npos); + EXPECT_FALSE(std::filesystem::exists(out)) << "Final file must not be created on connection error"; +} + +TEST_F(DashboardClientImplXMockTest, download_support_files_rename_failure) +{ + // When the destination path already exists as a directory, std::filesystem::rename + // fails (EISDIR on POSIX). The implementation must return a non-ok response + // containing "Failed to rename" and must clean up the temporary file. + const std::string fake_zip = "fake_zip_data"; + server_.Get(MOCK_SUPPORTFILES_ENDPOINT, [&fake_zip](const httplib::Request&, httplib::Response& res) { + res.set_content(fake_zip, "application/zip"); + }); + + // Create a directory at the save_path so that rename() cannot replace it with a file. + const std::filesystem::path out = std::filesystem::temp_directory_path() / "urcl_test_rename_fail_dir"; + std::filesystem::remove_all(out); + std::filesystem::create_directory(out); + + auto response = impl_->commandDownloadSupportFiles(out.string()); + + EXPECT_FALSE(response.ok); + EXPECT_NE(response.message.find("Failed to rename"), std::string::npos); + + // The directory itself must be untouched; no temp file should be left behind. + EXPECT_TRUE(std::filesystem::is_directory(out)); + EXPECT_TRUE(std::filesystem::is_empty(out)) << "Temp file must be cleaned up after rename failure"; + + std::filesystem::remove_all(out); +} + +// --------------------------------------------------------------------------- +// Connection-state tests +// +// These tests use TestableDashboardClientMock so that is_connected_ can be +// observed directly. Unlike DashboardClientImplXMockTest, SetUp() does NOT +// call connect() — each test controls the connection lifecycle explicitly. +// --------------------------------------------------------------------------- + +class DashboardClientImplXConnectionStateTest : public ::testing::Test +{ +protected: + void SetUp() override + { + port_ = server_.bind_to_any_port("127.0.0.1"); + server_thread_ = std::thread([this]() { server_.listen_after_bind(); }); + while (!server_.is_running()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + impl_ = std::make_unique("127.0.0.1:" + std::to_string(port_)); + } + + void TearDown() override + { + server_.stop(); + if (server_thread_.joinable()) + { + server_thread_.join(); + } + } + + void registerOpenApiEndpoint() + { + server_.Get(MOCK_OPENAPI_ENDPOINT, [](const httplib::Request&, httplib::Response& res) { + res.set_content(MOCK_OPENAPI_RESPONSE, "application/json"); + }); + } + + httplib::Server server_; + int port_ = 0; + std::thread server_thread_; + std::unique_ptr impl_; +}; + +TEST_F(DashboardClientImplXConnectionStateTest, initially_not_connected) +{ + // A freshly constructed client must report disconnected before connect() is called. + EXPECT_FALSE(impl_->isConnected()); +} + +TEST_F(DashboardClientImplXConnectionStateTest, connect_success_sets_connected) +{ + registerOpenApiEndpoint(); + EXPECT_TRUE(impl_->connect()); + EXPECT_TRUE(impl_->isConnected()); +} + +TEST_F(DashboardClientImplXConnectionStateTest, connect_failure_leaves_not_connected) +{ + // The server is reachable but openapi.json returns 404, so connect() must fail + // and leave the client in the disconnected state. + server_.Get(MOCK_OPENAPI_ENDPOINT, [](const httplib::Request&, httplib::Response& res) { res.status = 404; }); + + EXPECT_FALSE(impl_->connect()); + EXPECT_FALSE(impl_->isConnected()); +} + +TEST_F(DashboardClientImplXConnectionStateTest, assert_has_command_reconnects_when_not_connected) +{ + // When assertHasCommand() is called while disconnected it must trigger connect(). + // commandShutdown() calls assertHasCommand("shutdown") internally, so a successful + // commandShutdown() proves that reconnection happened. + registerOpenApiEndpoint(); + server_.Put(MOCK_SHUTDOWN_ENDPOINT, [](const httplib::Request&, httplib::Response& res) { + res.status = 202; + res.set_content(R"({"message":null})", "application/json"); + }); + + ASSERT_FALSE(impl_->isConnected()); + impl_->commandShutdown(); + EXPECT_TRUE(impl_->isConnected()); +} + +TEST_F(DashboardClientImplXConnectionStateTest, assert_has_command_throws_when_reconnect_fails) +{ + // assertHasCommand() is called while disconnected and the reconnect attempt fails + // (openapi.json returns 404). A UrException must be thrown. + server_.Get(MOCK_OPENAPI_ENDPOINT, [](const httplib::Request&, httplib::Response& res) { res.status = 404; }); + + ASSERT_FALSE(impl_->isConnected()); + EXPECT_THROW(impl_->commandShutdown(), UrException); +} + +TEST_F(DashboardClientImplXConnectionStateTest, disconnect_clears_connected) +{ + registerOpenApiEndpoint(); + ASSERT_TRUE(impl_->connect()); + ASSERT_TRUE(impl_->isConnected()); + + impl_->disconnect(); + EXPECT_FALSE(impl_->isConnected()); +} + class PolyScopeScreenshotListener : public ::testing::EmptyTestEventListener { public: