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/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/doc/polyscope_compatibility.rst b/doc/polyscope_compatibility.rst index 23366f1ca..b32c6309c 100644 --- a/doc/polyscope_compatibility.rst +++ b/doc/polyscope_compatibility.rst @@ -42,10 +42,47 @@ 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 + * - 10.14.0 + - Get robot information + - Polyscope version, robot model, serial number + * - 10.14.0 + - System control + - Shut down robot + * - 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. + See `Universal Robots External Control URCapX `_ .. |polyscope| replace:: PolyScope diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 7613d70d6..4d5a723dd 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 @@ -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 @@ -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 6fda79375..2f01267da 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; @@ -250,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; @@ -274,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; @@ -320,17 +319,19 @@ 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 + * \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) = 0; + virtual DashboardResponse commandPopup(const std::string& popup_text, const std::string& popup_title = "") = 0; /*! * \brief Send text to log * * \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; @@ -341,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; @@ -352,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; @@ -363,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; @@ -470,9 +471,10 @@ 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; + virtual DashboardResponse commandGenerateFlightReport(const std::string& report_type = "") = 0; /*! * \brief Send Generate support file command @@ -484,6 +486,18 @@ 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([[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 * @@ -544,7 +558,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 fa66589da..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,8 +131,9 @@ 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; DashboardResponse commandGetOperationalMode() override; DashboardResponse commandGetRobotModel() override; @@ -146,7 +147,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; @@ -175,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 b257e1726..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" @@ -42,6 +45,13 @@ class Result; namespace urcl { +struct RobotAPICommand +{ + std::string endpoint; + VersionInformation robotAPIVersion; + VersionInformation marketingVersion; +}; + class DashboardClientImplX : public DashboardClientImpl { public: @@ -126,7 +136,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) override; DashboardResponse commandGenerateSupportFile(const std::string& dir_path) override; DashboardResponse commandGetLoadedProgram() override; DashboardResponse commandGetOperationalMode() override; @@ -141,7 +152,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; @@ -179,15 +190,22 @@ 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; + void assertHasCommand(const std::string& command) override; const std::string base_url_ = "/universal-robots/robot-api"; 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; + std::atomic is_connected_{ false }; }; } // namespace urcl diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index 080936390..44fc7a34c 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) @@ -537,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 c923d396c..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")) { @@ -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 @@ -1170,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/src/ur/dashboard_client_implementation_x.cpp b/src/ur/dashboard_client_implementation_x.cpp index d728d91d5..641501354 100644 --- a/src/ur/dashboard_client_implementation_x.cpp +++ b/src/ur/dashboard_client_implementation_x.cpp @@ -25,9 +25,32 @@ // POSSIBILITY OF SUCH DAMAGE. #include -#include +#include #include #include +#include + +#ifdef _WIN32 +# include +# 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; +} +#else +# include +#endif #include "ur_client_library/ur/version_information.h" #include @@ -43,6 +66,57 @@ 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("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "close_popup", + { "/popup/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "close_safety_popup", + { "/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("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "get_robot_model", + { "/system/v1/information", VersionInformation::fromString("5.0.107"), + VersionInformation::fromString("10.14.0") } }, + { "get_serial_number", + { "/system/v1/information", VersionInformation::fromString("5.0.107"), + VersionInformation::fromString("10.14.0") } }, + { "shutdown", + { "/system/v1/shutdown", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "add_to_log", + { "/system/v1/log", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "generate_flight_report", + { "/supportfiles/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "download_support_files", + { "/supportfiles/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "set_operational_mode", + { "/operational-mode/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } }, + { "clear_operational_mode", + { "/operational-mode/v1", VersionInformation::fromString("5.0.107"), VersionInformation::fromString("10.14.0") } } +}; + DashboardClientImplX::DashboardClientImplX(const std::string& host) : DashboardClientImpl(host) { cli_ = std::make_unique("http://" + host); @@ -100,6 +174,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; } @@ -110,15 +185,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; } @@ -134,14 +211,30 @@ 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([[maybe_unused]] const std::string& command) const +void DashboardClientImplX::assertHasCommand(const std::string& command) { - // 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 (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; + 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, @@ -256,12 +349,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() @@ -276,7 +371,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() @@ -297,11 +394,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) @@ -319,38 +412,63 @@ 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"); + nlohmann::json payload = { { "title", title }, { "message", popup_text } }; + return post("/popup/v1", payload.dump()); } -DashboardResponse DashboardClientImplX::commandAddToLog([[maybe_unused]] const std::string& log_text) +DashboardResponse DashboardClientImplX::commandAddToLog(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; + nlohmann::json payload = { { "message", log_text } }; + return post(endpoint, payload.dump()); } 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() { - 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() { - 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 +480,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 +492,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 +520,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) @@ -446,7 +552,154 @@ 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"); + + 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; + + DashboardResponse response; + try + { + response = post(endpoint, ""); + } + catch (...) + { + setReceiveTimeout(configured_tv); + throw; + } + + setReceiveTimeout(configured_tv); + return response; +} + +DashboardResponse DashboardClientImplX::commandDownloadSupportFiles(const std::string& save_path) +{ + assertHasCommand("download_support_files"); + const std::string endpoint = g_command_list["download_support_files"].endpoint; + + DashboardResponse response; + + // 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()) + { + dest_dir = "."; + } + + bool http_ok = true; + bool write_error = false; + std::string error_body; + + // 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; + } + + 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; // always receive body: success body → file, error body → error_body + }, + [&](const char* data, size_t data_length) -> bool { + if (!http_ok) + { + error_body.append(data, data_length); + return true; + } + if (!write_chunk(data, data_length)) + { + write_error = true; + return false; + } + return true; + }); + +#ifndef _WIN32 + close(tmp_fd); +#else + _close(tmp_fd); +#endif + + 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()); + std::filesystem::remove(temp_save_path); + return response; + } + + 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; + } + + 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; + } + else + { + response.ok = false; + response.message = error_body; + URCL_LOG_ERROR("Failed to download support files. Response message: %s", response.message.c_str()); + std::filesystem::remove(temp_save_path); + } + return response; } DashboardResponse DashboardClientImplX::commandGenerateSupportFile([[maybe_unused]] const std::string& dir_path) @@ -461,11 +714,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 +775,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 +783,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 +791,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."; @@ -598,7 +835,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; } @@ -695,6 +940,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/CMakeLists.txt b/tests/CMakeLists.txt index dd707df4d..313489a16 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -58,7 +58,9 @@ 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} 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.cpp b/tests/test_dashboard_client.cpp index c449c140c..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)); @@ -77,7 +78,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 +205,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()); } @@ -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[]) diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index d8a8788a2..56539eeab 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 @@ -107,7 +109,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) @@ -128,6 +131,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"); @@ -397,7 +404,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"); @@ -418,7 +427,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"); @@ -520,6 +531,452 @@ 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("5.0.107")) + { + 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("\"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); + response = dashboard_client_->commandClosePopup(); + ASSERT_TRUE(response.ok); + response = dashboard_client_->commandCloseSafetyPopup(); + ASSERT_FALSE(response.ok); + ASSERT_TRUE(response.message.find("\"closed\":false") != response.message.npos); + } + } +} + +TEST_F(DashboardClientTestX, get_polyscope_version) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) + { + 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)); + } +} + +TEST_F(DashboardClientTestX, get_robot_model) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) + { + ASSERT_THROW(dashboard_client_->commandGetRobotModel(), NotImplementedException); + } + else + { + auto response = dashboard_client_->commandGetRobotModel(); + ASSERT_TRUE(response.ok); + 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()); + + // 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") + { + model_string = "UR5"; + } + else if (model_string == "UR12") + { + model_string = "UR10"; + } + EXPECT_EQ(model_string, primary_client_version); + } +} + +TEST_F(DashboardClientTestX, get_serial_number) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) + { + 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()); // Don't know what to check for here otherwise + } +} + +TEST_F(DashboardClientTestX, add_to_log) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) + { + 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); + } + } +} + +TEST_F(DashboardClientTestX, generate_flight_report) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) + { + 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 + { + 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); + } + } +} + +TEST_F(DashboardClientTestX, download_support_files) +{ + ASSERT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->getRobotApiVersion() < VersionInformation::fromString("5.0.107")) + { + ASSERT_THROW(dashboard_client_->commandDownloadSupportFiles("/tmp/support_files.zip"), NotImplementedException); + } + else + { + 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 + // 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); + } +} + +// --------------------------------------------------------------------------- +// 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_; +}; + +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} +// 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); + + 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"; +} + +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); + + 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"; +} + +TEST_F(DashboardClientImplXMockTest, download_support_files_invalid_path) +{ + // 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 create temporary file"), std::string::npos); +} + class PolyScopeScreenshotListener : public ::testing::EmptyTestEventListener { public: