diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 138d3c727..b70a4b73b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,24 +3,29 @@ on: [push, pull_request] jobs: build: + timeout-minutes: 30 runs-on: ubuntu-latest strategy: matrix: env: - - DOCKER_RUN_OPTS: --network static_test_net + - DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' CTEST_OUTPUT_ON_FAILURE: 1 - URSIM_VERSION: 3.14.1.1031110 - - DOCKER_RUN_OPTS: --network static_test_net + ROBOT_MODEL: 'ur5' + URSIM_VERSION: '3.12.1' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/cb3' + - DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' CTEST_OUTPUT_ON_FAILURE: 1 - URSIM_VERSION: 5.8.0.10253 + ROBOT_MODEL: 'ur5e' + URSIM_VERSION: '5.5.1' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series' steps: - uses: actions/checkout@v1 - name: start ursim run: | - tests/resources/dockerursim/build_and_run_docker_ursim.sh $URSIM_VERSION + scripts/start_ursim.sh -m $ROBOT_MODEL -v $URSIM_VERSION -p $PROGRAM_FOLDER -d env: ${{matrix.env}} - name: install gtest run: sudo apt-get install -y libgtest-dev @@ -36,6 +41,8 @@ jobs: run: cmake --build build --config Debug - name: test run: cd build && ctest --output-on-failure + - name: run examples + run: run-parts -v --exit-on-error -a "192.168.56.101" -a "1" ./build/examples - name: install gcovr run: sudo apt-get install -y gcovr - name: gcovr @@ -59,11 +66,11 @@ jobs: --exclude-dir=CMakeModules \ --exclude=tcp_socket.cpp \ --exclude-dir=debian \ - --exclude=real_time.md + --exclude=real_time.md \ + --exclude=start_ursim.sh rosdoc_lite_check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: ./.github/actions/rosdoc_lite_action - diff --git a/.github/workflows/industrial-ci.yml b/.github/workflows/industrial-ci.yml index 98d83ada6..7c0105b1f 100644 --- a/.github/workflows/industrial-ci.yml +++ b/.github/workflows/industrial-ci.yml @@ -23,55 +23,67 @@ jobs: ROS_REPO: main IMMEDIATE_TEST_OUTPUT: true DOWNSTREAM_WORKSPACE: "github:UniversalRobots/Universal_Robots_ROS_Driver#master https://raw.githubusercontent.com/UniversalRobots/Universal_Robots_ROS_Driver/master/.melodic.rosinstall" - DOCKER_RUN_OPTS: --network static_test_net + DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' - URSIM_VERSION: 5.8.0.10253 + URSIM_VERSION: '5.5.1' + ROBOT_MODEL: 'ur5e' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series' - ROS_DISTRO: noetic ROS_REPO: main IMMEDIATE_TEST_OUTPUT: true DOWNSTREAM_WORKSPACE: "github:UniversalRobots/Universal_Robots_ROS_Driver#master https://raw.githubusercontent.com/UniversalRobots/Universal_Robots_ROS_Driver/master/.noetic.rosinstall" BUILDER: catkin_tools - DOCKER_RUN_OPTS: --network static_test_net + DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' - URSIM_VERSION: 5.8.0.10253 + URSIM_VERSION: '5.5.1' + ROBOT_MODEL: 'ur5e' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series' - ROS_DISTRO: foxy ROS_REPO: main IMMEDIATE_TEST_OUTPUT: true DOWNSTREAM_WORKSPACE: "github:UniversalRobots/Universal_Robots_ROS2_Driver#foxy" - DOCKER_RUN_OPTS: --network static_test_net + DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' - URSIM_VERSION: 5.8.0.10253 + URSIM_VERSION: '5.5.1' + ROBOT_MODEL: 'ur5e' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series' NOT_TEST_DOWNSTREAM: true - ROS_DISTRO: galactic ROS_REPO: main IMMEDIATE_TEST_OUTPUT: true DOWNSTREAM_WORKSPACE: "github:UniversalRobots/Universal_Robots_ROS2_Driver#galactic" - DOCKER_RUN_OPTS: --network static_test_net + DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' - URSIM_VERSION: 5.8.0.10253 + URSIM_VERSION: '5.5.1' + ROBOT_MODEL: 'ur5e' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series' NOT_TEST_DOWNSTREAM: true - ROS_DISTRO: humble ROS_REPO: main IMMEDIATE_TEST_OUTPUT: true DOWNSTREAM_WORKSPACE: "github:UniversalRobots/Universal_Robots_ROS2_Driver#main https://raw.githubusercontent.com/UniversalRobots/Universal_Robots_ROS2_Driver/main/Universal_Robots_ROS2_Driver-not-released.humble.repos" - DOCKER_RUN_OPTS: --network static_test_net + DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' - URSIM_VERSION: 5.8.0.10253 + URSIM_VERSION: '5.5.1' + ROBOT_MODEL: 'ur5e' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series' NOT_TEST_DOWNSTREAM: true - ROS_DISTRO: rolling ROS_REPO: main IMMEDIATE_TEST_OUTPUT: true DOWNSTREAM_WORKSPACE: "github:UniversalRobots/Universal_Robots_ROS2_Driver#main https://raw.githubusercontent.com/UniversalRobots/Universal_Robots_ROS2_Driver/main/Universal_Robots_ROS2_Driver-not-released.rolling.repos" - DOCKER_RUN_OPTS: --network static_test_net + DOCKER_RUN_OPTS: --network ursim_net BEFORE_INIT: 'apt-get update -qq && apt-get install -y iproute2 iputils-ping && ip addr && ping -c5 192.168.56.101' - URSIM_VERSION: 5.8.0.10253 + URSIM_VERSION: '5.5.1' + ROBOT_MODEL: 'ur5e' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series' NOT_TEST_DOWNSTREAM: true steps: - uses: actions/checkout@v1 - name: start ursim run: | - tests/resources/dockerursim/build_and_run_docker_ursim.sh $URSIM_VERSION + scripts/start_ursim.sh -m $ROBOT_MODEL -v $URSIM_VERSION -p $PROGRAM_FOLDER -d env: ${{matrix.env}} - uses: 'ros-industrial/industrial_ci@master' env: ${{matrix.env}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 756e9e197..6ebf32645 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ add_library(urcl SHARED src/ur/calibration_checker.cpp src/ur/dashboard_client.cpp src/ur/tool_communication.cpp + src/ur/version_information.cpp src/rtde/rtde_writer.cpp src/default_log_handler.cpp src/log.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5daa20d12..3028c4868 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,11 +1,11 @@ cmake_minimum_required(VERSION 3.0.2) project(ur_driver_examples) -#find_package(ur_client_library REQUIRED) +# find_package(ur_client_library REQUIRED) -## -## Check C++11 support / enable global pedantic and Wall -## +# # +# # Check C++11 support / enable global pedantic and Wall +# # include(DefineCXX17CompilerFlag) DEFINE_CXX_17_COMPILER_FLAG(CXX17_FLAG) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic") @@ -20,7 +20,17 @@ add_executable(primary_pipeline_example target_compile_options(primary_pipeline_example PUBLIC ${CXX17_FLAG}) target_link_libraries(primary_pipeline_example ur_client_library::urcl) +add_executable(primary_pipeline_calibration_example + primary_pipeline_calibration.cpp) +target_compile_options(primary_pipeline_calibration_example PUBLIC ${CXX17_FLAG}) +target_link_libraries(primary_pipeline_calibration_example ur_client_library::urcl) + add_executable(rtde_client_example rtde_client.cpp) target_compile_options(rtde_client_example PUBLIC ${CXX17_FLAG}) target_link_libraries(rtde_client_example ur_client_library::urcl) + +add_executable(dashboard_example + dashboard_example.cpp) +target_compile_options(dashboard_example PUBLIC ${CXX17_FLAG}) +target_link_libraries(dashboard_example ur_client_library::urcl) diff --git a/examples/dashboard_example.cpp b/examples/dashboard_example.cpp new file mode 100644 index 000000000..cc01f5e63 --- /dev/null +++ b/examples/dashboard_example.cpp @@ -0,0 +1,124 @@ +// this is for emacs file handling -*- mode: c++; indent-tabs-mode: nil -*- + +// -- BEGIN LICENSE BLOCK ---------------------------------------------- +// Copyright 2022 Universal Robots A/S +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// -- END LICENSE BLOCK ------------------------------------------------ + +#include +#include + +#include +#include +#include + +using namespace urcl; + +// In a real-world example it would be better to get those values from command line parameters / a +// better configuration system such as Boost.Program_options +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; + +// We need a callback function to register. See UrDriver's parameters for details. + +int main(int argc, char* argv[]) +{ + urcl::setLogLevel(urcl::LogLevel::DEBUG); + + // Parse the ip arguments if given + std::string robot_ip = DEFAULT_ROBOT_IP; + if (argc > 1) + { + robot_ip = std::string(argv[1]); + } + + // Making the robot ready for the program by: + // Connect the the robot Dashboard + std::unique_ptr my_dashboard; + my_dashboard.reset(new DashboardClient(robot_ip)); + if (!my_dashboard->connect()) + { + URCL_LOG_ERROR("Could not connect to dashboard"); + return 1; + } + + if (!my_dashboard->commandPowerOff()) + { + URCL_LOG_ERROR("Could not send power off"); + return 1; + } + + my_dashboard->commandCloseSafetyPopup(); + + // Power it on + if (!my_dashboard->commandPowerOn()) + { + URCL_LOG_ERROR("Could not send Power on command"); + return 1; + } + + // Release the brakes + if (!my_dashboard->commandBrakeRelease()) + { + URCL_LOG_ERROR("Could not send BrakeRelease command"); + return 1; + } + + // Load existing program + const std::string program_file_name_to_be_loaded("wait_program.urp"); + if (!my_dashboard->commandLoadProgram(program_file_name_to_be_loaded)) + { + URCL_LOG_ERROR("Could not load %s program", program_file_name_to_be_loaded.c_str()); + return 1; + } + + // Play loaded program + if (!my_dashboard->commandPlay()) + { + URCL_LOG_ERROR("Could not play program"); + return 1; + } + + // Pause running program + if (!my_dashboard->commandPause()) + { + URCL_LOG_ERROR("Could not pause program"); + return 1; + } + + // Play loaded program + if (!my_dashboard->commandPlay()) + { + URCL_LOG_ERROR("Could not play program"); + return 1; + } + + // Stop program + if (!my_dashboard->commandStop()) + { + URCL_LOG_ERROR("Could not stop program"); + return 1; + } + + // Power it off + if (!my_dashboard->commandPowerOff()) + { + URCL_LOG_ERROR("Could not send Power off command"); + return 1; + } + + // Now the robot is ready to receive a program + + return 0; +} diff --git a/examples/full_driver.cpp b/examples/full_driver.cpp index 45213d4c2..3a2135abd 100644 --- a/examples/full_driver.cpp +++ b/examples/full_driver.cpp @@ -28,6 +28,7 @@ */ //---------------------------------------------------------------------- +#include #include #include @@ -38,13 +39,14 @@ using namespace urcl; // In a real-world example it would be better to get those values from command line parameters / a // better configuration system such as Boost.Program_options -const std::string ROBOT_IP = "192.168.56.101"; +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; const std::string SCRIPT_FILE = "resources/external_control.urscript"; const std::string OUTPUT_RECIPE = "examples/resources/rtde_output_recipe.txt"; const std::string INPUT_RECIPE = "examples/resources/rtde_input_recipe.txt"; const std::string CALIBRATION_CHECKSUM = "calib_12788084448423163542"; std::unique_ptr g_my_driver; +std::unique_ptr g_my_dashboard; vector6d_t g_joint_positions; // We need a callback function to register. See UrDriver's parameters for details. @@ -56,15 +58,71 @@ void handleRobotProgramState(bool program_running) int main(int argc, char* argv[]) { + urcl::setLogLevel(urcl::LogLevel::INFO); + + // Parse the ip arguments if given + std::string robot_ip = DEFAULT_ROBOT_IP; + if (argc > 1) + { + robot_ip = std::string(argv[1]); + } + + // Making the robot ready for the program by: + // Connect the the robot Dashboard + g_my_dashboard.reset(new DashboardClient(robot_ip)); + if (!g_my_dashboard->connect()) + { + URCL_LOG_ERROR("Could not connect to dashboard"); + return 1; + } + + // Stop program, if there is one running + if (!g_my_dashboard->commandStop()) + { + URCL_LOG_ERROR("Could not send stop program command"); + return 1; + } + + // Power it off + if (!g_my_dashboard->commandPowerOff()) + { + URCL_LOG_ERROR("Could not send Power off command"); + return 1; + } + + // Power it on + if (!g_my_dashboard->commandPowerOn()) + { + URCL_LOG_ERROR("Could not send Power on command"); + return 1; + } + + // Release the brakes + if (!g_my_dashboard->commandBrakeRelease()) + { + URCL_LOG_ERROR("Could not send BrakeRelease command"); + return 1; + } + + // Now the robot is ready to receive a program + std::unique_ptr tool_comm_setup; - g_my_driver.reset(new UrDriver(ROBOT_IP, SCRIPT_FILE, OUTPUT_RECIPE, INPUT_RECIPE, &handleRobotProgramState, false, + const bool HEADLESS = true; + g_my_driver.reset(new UrDriver(robot_ip, SCRIPT_FILE, OUTPUT_RECIPE, INPUT_RECIPE, &handleRobotProgramState, HEADLESS, std::move(tool_comm_setup), CALIBRATION_CHECKSUM)); + // Once RTDE communication is started, we have to make sure to read from the interface buffer, as // otherwise we will get pipeline overflows. Therefor, do this directly before starting your main // loop. + g_my_driver->startRTDECommunication(); + double increment = 0.01; - while (true) + + bool passed_slow_part = false; + bool passed_fast_part = false; + URCL_LOG_INFO("Start moving the robot"); + while (!(passed_slow_part && passed_fast_part)) { // Read latest RTDE package. This will block for a hard-coded timeout (see UrDriver), so the // robot will effectively be in charge of setting the frequency of this loop. @@ -84,20 +142,28 @@ int main(int argc, char* argv[]) // Simple motion command of last joint if (g_joint_positions[5] > 3) { + passed_fast_part = increment > 0.01 || passed_fast_part; increment = -3; // this large jump will activate speed scaling } else if (g_joint_positions[5] < -3) { + passed_slow_part = increment < 0.01 || passed_slow_part; increment = 0.02; } g_joint_positions[5] += increment; - g_my_driver->writeJointCommand(g_joint_positions, comm::ControlMode::MODE_SERVOJ); - std::cout << data_pkg->toString() << std::endl; + bool ret = g_my_driver->writeJointCommand(g_joint_positions, comm::ControlMode::MODE_SERVOJ); + if (!ret) + { + URCL_LOG_ERROR("Could not send joint command. Is the robot in remote control?"); + return 1; + } + URCL_LOG_DEBUG("data_pkg:\n%s", data_pkg->toString()); } else { - std::cout << "Could not get fresh data package from robot" << std::endl; + URCL_LOG_WARN("Could not get fresh data package from robot"); } } + URCL_LOG_INFO("Movement done"); return 0; } diff --git a/examples/primary_pipeline.cpp b/examples/primary_pipeline.cpp index 14c246217..e4390d8b5 100644 --- a/examples/primary_pipeline.cpp +++ b/examples/primary_pipeline.cpp @@ -34,12 +34,29 @@ using namespace urcl; // In a real-world example it would be better to get those values from command line parameters / a better configuration // system such as Boost.Program_options -const std::string ROBOT_IP = "192.168.56.101"; +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; int main(int argc, char* argv[]) { + // Set the loglevel to info get print out the DH parameters + urcl::setLogLevel(urcl::LogLevel::INFO); + + // Parse the ip arguments if given + std::string robot_ip = DEFAULT_ROBOT_IP; + if (argc > 1) + { + robot_ip = std::string(argv[1]); + } + + // Parse how many seconds to run + int second_to_run = -1; + if (argc > 2) + { + second_to_run = std::stoi(argv[2]); + } + // First of all, we need a stream that connects to the robot - comm::URStream primary_stream(ROBOT_IP, urcl::primary_interface::UR_PRIMARY_PORT); + comm::URStream primary_stream(robot_ip, urcl::primary_interface::UR_PRIMARY_PORT); // This will parse the primary packages primary_interface::PrimaryParser parser; @@ -62,9 +79,9 @@ int main(int argc, char* argv[]) // Package contents will be printed while not being interrupted // Note: Packages for which the parsing isn't implemented, will only get their raw bytes printed. - while (true) + do { - std::this_thread::sleep_for(std::chrono::seconds(1)); - } + std::this_thread::sleep_for(std::chrono::seconds(second_to_run)); + } while (second_to_run < 0); return 0; } diff --git a/examples/primary_pipeline_calibration.cpp b/examples/primary_pipeline_calibration.cpp new file mode 100644 index 000000000..76a3094d7 --- /dev/null +++ b/examples/primary_pipeline_calibration.cpp @@ -0,0 +1,117 @@ +// this is for emacs file handling -*- mode: c++; indent-tabs-mode: nil -*- + +// -- BEGIN LICENSE BLOCK ---------------------------------------------- +// Copyright 2022 Universal Robots A/S +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// -- END LICENSE BLOCK ------------------------------------------------ + +#include +#include +#include +#include + +using namespace urcl; + +class CalibrationConsumer : public urcl::comm::IConsumer +{ +public: + CalibrationConsumer() : calibrated_(0), have_received_data(false) + { + } + virtual ~CalibrationConsumer() = default; + + virtual bool consume(std::shared_ptr product) + { + auto kin_info = std::dynamic_pointer_cast(product); + if (kin_info != nullptr) + { + URCL_LOG_INFO("%s", product->toString().c_str()); + calibrated_ = kin_info->calibration_status_; + have_received_data = true; + } + return true; + } + + bool isCalibrated() const + { + const uint32_t LINEARIZED = 2; + return calibrated_ == LINEARIZED; + } + + bool calibrationStatusReceived() + { + return have_received_data; + } + +private: + uint32_t calibrated_; + bool have_received_data; +}; + +// In a real-world example it would be better to get those values from command line parameters / a better configuration +// system such as Boost.Program_options +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; + +int main(int argc, char* argv[]) +{ + // Set the loglevel to info get print out the DH parameters + urcl::setLogLevel(urcl::LogLevel::INFO); + + // Parse the ip arguments if given + std::string robot_ip = DEFAULT_ROBOT_IP; + if (argc > 1) + { + robot_ip = std::string(argv[1]); + } + + // First of all, we need a stream that connects to the robot + comm::URStream primary_stream(robot_ip, urcl::primary_interface::UR_PRIMARY_PORT); + + // This will parse the primary packages + primary_interface::PrimaryParser parser; + + // The producer needs both, the stream and the parser to fully work + comm::URProducer prod(primary_stream, parser); + prod.setupProducer(); + + // The calibration consumer will print the package contents to the shell + CalibrationConsumer calib_consumer; + + // The notifer will be called at some points during connection setup / loss. This isn't fully + // implemented atm. + comm::INotifier notifier; + + // Now that we have all components, we can create and start the pipeline to run it all. + comm::Pipeline calib_pipeline(prod, &calib_consumer, "Pipeline", notifier); + calib_pipeline.run(); + + // Package contents will be printed while not being interrupted + // Note: Packages for which the parsing isn't implemented, will only get their raw bytes printed. + while (!calib_consumer.calibrationStatusReceived()) + { + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + if (calib_consumer.isCalibrated()) + { + printf("The robot on IP: %s is calibrated\n", robot_ip.c_str()); + } + else + { + printf("The robot controller on IP: %s do not have a valid calibration\n", robot_ip.c_str()); + printf("Remeber to turn on the robot to get calibration stored on the robot!\n"); + } + + return 0; +} diff --git a/examples/rtde_client.cpp b/examples/rtde_client.cpp index ca503a429..387f045e0 100644 --- a/examples/rtde_client.cpp +++ b/examples/rtde_client.cpp @@ -29,21 +29,36 @@ #include #include +#include using namespace urcl; // In a real-world example it would be better to get those values from command line parameters / a better configuration // system such as Boost.Program_options -const std::string ROBOT_IP = "192.168.56.101"; +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; const std::string OUTPUT_RECIPE = "examples/resources/rtde_output_recipe.txt"; const std::string INPUT_RECIPE = "examples/resources/rtde_input_recipe.txt"; const std::chrono::milliseconds READ_TIMEOUT{ 100 }; int main(int argc, char* argv[]) { + // Parse the ip arguments if given + std::string robot_ip = DEFAULT_ROBOT_IP; + if (argc > 1) + { + robot_ip = std::string(argv[1]); + } + + // Parse how may seconds to run + int second_to_run = -1; + if (argc > 2) + { + second_to_run = std::stoi(argv[2]); + } + // TODO: Write good docstring for notifier comm::INotifier notifier; - rtde_interface::RTDEClient my_client(ROBOT_IP, notifier, OUTPUT_RECIPE, INPUT_RECIPE); + rtde_interface::RTDEClient my_client(robot_ip, notifier, OUTPUT_RECIPE, INPUT_RECIPE); my_client.init(); // We will use the speed_slider_fraction as an example how to write to RTDE @@ -54,7 +69,9 @@ int main(int argc, char* argv[]) // otherwise we will get pipeline overflows. Therefor, do this directly before starting your main // loop. my_client.start(); - while (true) + + unsigned long startTime = clock(); + while (second_to_run < 0 || ((clock() - startTime) / CLOCKS_PER_SEC) < static_cast(second_to_run)) { // Read latest RTDE package. This will block for READ_TIMEOUT, so the // robot will effectively be in charge of setting the frequency of this loop unless RTDE @@ -69,6 +86,7 @@ int main(int argc, char* argv[]) else { std::cout << "Could not get fresh data package from robot" << std::endl; + return 1; } if (!my_client.getWriter().sendSpeedSlider(speed_slider_fraction)) @@ -78,6 +96,7 @@ int main(int argc, char* argv[]) std::cout << "\033[1;31mSending RTDE data failed." << "\033[0m\n" << std::endl; + return 1; } // Change the speed slider so that it will move between 0 and 1 all the time. This is for @@ -96,5 +115,8 @@ int main(int argc, char* argv[]) speed_slider_fraction += speed_slider_increment; } + // Resetting the speedslider back to 100% + my_client.getWriter().sendSpeedSlider(1); + return 0; } diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index bc778e2f1..327533e0d 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -29,6 +29,7 @@ #define UR_ROBOT_DRIVER_DASHBOARD_CLIENT_DASHBOARD_CLIENT_H_INCLUDED #include +#include namespace urcl { @@ -57,7 +58,7 @@ class DashboardClient : public comm::TCPSocket const int DASHBOARD_SERVER_PORT = 29999; /*! - * \brief Opens a connection to the dasboard server on the host as specified in the constructor. + * \brief Opens a connection to the dashboard server on the host as specified in the constructor. * * \returns True on successful connection, false otherwise. */ @@ -71,13 +72,356 @@ class DashboardClient : public comm::TCPSocket /*! * \brief Sends a command through the socket and waits for an answer. * - * \param command Command that will be sent to the server. It is important, that the - * command sent is finished with a '\n' (newline) so it will be processed by the server. + * \param command Command that will be sent to the server. + * + * \throws UrException if no response was read from the dashboard server * * \returns Answer as received by the server cut off any trailing newlines. */ std::string sendAndReceive(const std::string& command); + /*! + * \brief Sends command and compare it with the expected answer + * + * \param command Command that will be sent to the server. + * \param expected Expected response + * + * \return True if the reply to the command is as expected + */ + bool sendRequest(const std::string& command, const std::string& expected); + + /*! + * \brief Sends command and compare it with the expected answer + * + * \param command Command that will be sent to the server. + * \param expected Expected response + * + * \throws UrException if the received answer does not match the expected one. + * + * \return Answer string as received by the server + */ + std::string sendRequestString(const std::string& command, const std::string& expected); + + /*! + * \brief brief Sends a command and wait until it returns the expected answer + * + * \param command Command that will be sent to the server + * \param expected Expected replay + * \param timeout Timeout to wait before the command is considered failed. + * + * \return True if the reply was as expected within the timeout time + */ + bool waitForReply(const std::string& command, const std::string& expected, + std::chrono::duration timeout = std::chrono::seconds(30)); + + /*! + * \brief Keep Sending the requesting Command and wait until it returns the expected answer. + * + * \param requestCommand Request command that will be sent to the server + * \param requestExpectedResponse The expected reply to the request + * \param waitRequest The status request + * \param waitExpectedResponse The expected reply on the status + * \param timeout Timeout before the command is ultimately considered failed + * \param retry_period Retries will be done with this period + * + * \return True when both the requested command was receive with the expected reply as well as the resulting status + * also is as expected within the timeout time + */ + bool retryCommand(const std::string& requestCommand, const std::string& requestExpectedResponse, + const std::string& waitRequest, const std::string& waitExpectedResponse, + const std::chrono::duration timeout, + const std::chrono::duration retry_period = std::chrono::seconds(1)); + + /*! + * \brief Send Power off command + * + * \return True succeeded + */ + bool commandPowerOff(); + + /*! + * \brief Send Power on command + * + * \param timeout Timeout in seconds - The robot might take some time to boot before this call can + * be made successfully. + * + * \return True succeeded + */ + bool commandPowerOn(const std::chrono::duration timeout = std::chrono::seconds(300)); + + /*! + * \brief Send Brake release command + * + * \return True succeeded + */ + bool commandBrakeRelease(); + + /*! + * \brief Send Load program command + * + * \param program_file_name The urp program file name with the urp extension + * + * \return True succeeded + */ + bool commandLoadProgram(const std::string& program_file_name); + + /*! + * \brief Send Load installation command + * + * \param installation_file_name The installation file name with the installation extension + * + * \return True succeeded + */ + bool commandLoadInstallation(const std::string& installation_file_name); + + /*! + * \brief Send Play program command + * + * \return True succeeded + */ + bool commandPlay(); + + /*! + * \brief Send Pause program command + * + * \return True succeeded + */ + bool commandPause(); + + /*! + * \brief Send Stop program command + * + * \return True succeeded + */ + bool commandStop(); + + /*! + * \brief Send Close popup command + * + * \return True succeeded + */ + bool commandClosePopup(); + + /*! + * \brief Send Close safety popup command + * + * \return True succeeded + */ + bool commandCloseSafetyPopup(); + + /*! + * \brief Send Restart Safety command + * + * \return True succeeded + */ + bool commandRestartSafety(); + + /*! + * \brief Send Unlock Protective stop popup command + * + * \return True succeeded + */ + bool commandUnlockProtectiveStop(); + + /*! + * \brief Send Shutdown command + * + * \return True succeeded + */ + bool commandShutdown(); + + /*! + * \brief Send Quit command + * + * \return True succeeded + */ + bool commandQuit(); + + /*! + * \brief Send Running command + * + * \return True succeeded + */ + bool commandRunning(); + + /*! + * \brief Send "Is program saved" request command + * + * \return True if the program is saved correctly + */ + bool commandIsProgramSaved(); + + /*! + * \brief Send "Is in remote control" query command + * + * \throws an UrException when called on CB3 robots + * + * \return True if the robot is currently in remote control + */ + bool commandIsInRemoteControl(); + + /*! + * \brief Send popup command + * + * \param popup_text The text to be shown in the popup + * + * \return True succeeded + */ + bool commandPopup(const std::string& popup_text); + + /*! + * \brief Send text to log + * + * \param log_text The text to be sent to the log + * + * \return True succeeded + */ + bool commandAddToLog(const std::string& log_text); + + /*! + * \brief Get Polyscope version + * + * \param polyscope_version The string for the polyscope version number returned + * + * \return True succeeded + */ + bool commandPolyscopeVersion(std::string& polyscope_version); + + /*! + * \brief Get Robot model + * + * \param robot_model The string for the robot model returned + * + * \return True succeeded + */ + bool commandGetRobotModel(std::string& robot_model); + + /*! + * \brief Get Serial number + * + * \param serial_number The serial number of the robot returned + * + * \return True succeeded + */ + bool commandGetSerialNumber(std::string& serial_number); + + /*! + * \brief Get Robot mode + * + * \param robot_mode The mode of the robot returned + * + * \return True succeeded + */ + bool commandRobotMode(std::string& robot_mode); + + /*! + * \brief Get Loaded Program + * + * \param loaded_program The path to the loaded program + * + * \return True succeeded + */ + bool commandGetLoadedProgram(std::string& loaded_program); + + /*! + * \brief Get Safety mode + * + * \param safety_mode The safety mode of the robot returned + * + * \return True succeeded + */ + bool commandSafetyMode(std::string& safety_mode); + + /*! + * \brief Get Safety status + * + * \param safety_status The safety status of the robot returned + * + * \return True succeeded + */ + bool commandSafetyStatus(std::string& safety_status); + + /*! + * \brief Get Program state + * + * \param program_state The program state of the robot returned + * + * \return True succeeded + */ + bool commandProgramState(std::string& program_state); + + /*! + * \brief Get Operational mode + * + * \param operational_mode The operational mode of the robot returned (Only available for e-series) + * + * \throws an UrException when called on CB3 robots + * + * \return True succeeded + */ + bool commandGetOperationalMode(std::string& operational_mode); + + /*! + * \brief Send Set operational mode command (Only available for e-series) + * + * \param operational_mode The operational mode to set on the robot + * + * \throws an UrException when called on CB3 robots + * + * \return True succeeded + */ + bool commandSetOperationalMode(const std::string& operational_mode); + + /*! + * \brief Send Clear operational mode command + * + * \throws an UrException when called on CB3 robots + * + * \return True succeeded + */ + bool commandClearOperationalMode(); + + /*! + * \brief Send Set user role command (Only available for CB3) + * + * \param user_role The user role to set on the robot + * + * \throws an UrException when called on e-series robots + * + * \return True succeeded + */ + bool commandSetUserRole(const std::string& user_role); + + /*! + * \brief Send Get user role command (Only available for CB3) + * + * \param user_role The user role on the robot + * + * \throws an UrException when called on e-series robots + * + * \return True succeeded + */ + bool commandGetUserRole(std::string& user_role); + + /*! + * \brief Send Generate flight report command + * + * \param report_type The report type to set for the flight report + * + * \return True succeeded + */ + bool commandGenerateFlightReport(const std::string& report_type); + + /*! + * \brief Send Generate support file command + * + * \param dir_path The path to the directory of an already existing directory location inside the programs directory, + * where the support file is saved + * + * \return True succeeded + */ + bool commandGenerateSupportFile(const std::string& dir_path); + protected: virtual bool open(int socket_fd, struct sockaddr* address, size_t address_len) { @@ -85,14 +429,22 @@ class DashboardClient : public comm::TCPSocket } private: + /*! + * \brief Makes sure that the dashboard_server's version is above the required version + * + * \param e_series_min_ver SW version for e-Series + * \param cb3_min_ver SW version for cb3 + * \param required_call The dashboard call that should be checked + * + * \throws UrException if the robot's version isn't large enough + */ + void assertVersion(const std::string& e_series_min_ver, const std::string& cb3_min_ver, + const std::string& required_call); bool send(const std::string& text); std::string read(); + void rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r "); - void rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") - { - str.erase(str.find_last_not_of(chars) + 1); - } - + VersionInformation polyscope_version_; std::string host_; int port_; std::mutex write_mutex_; diff --git a/include/ur_client_library/ur/version_information.h b/include/ur_client_library/ur/version_information.h index 4746b26bf..55dc636dc 100644 --- a/include/ur_client_library/ur/version_information.h +++ b/include/ur_client_library/ur/version_information.h @@ -29,6 +29,9 @@ #ifndef UR_CLIENT_LIBRARY_UR_VERSION_INFORMATION_H_INCLUDED #define UR_CLIENT_LIBRARY_UR_VERSION_INFORMATION_H_INCLUDED +#include +#include + #include namespace urcl @@ -36,15 +39,29 @@ namespace urcl /*! * \brief Struct containing a robot's version information */ -struct VersionInformation +class VersionInformation { - VersionInformation() - { - major = 0; - minor = 0; - bugfix = 0; - build = 0; - } +public: + VersionInformation(); + ~VersionInformation() = default; + + /*! + * \brief Parses a version string into a VersionInformation object + * + * \param str Version string such as "5.12.0.1101319" + * + * \returns A parsed VersionInformation object + */ + static VersionInformation fromString(const std::string& str); + + bool isESeries() const; + + friend bool operator==(const VersionInformation& v1, const VersionInformation& v2); + friend bool operator!=(const VersionInformation& v1, const VersionInformation& v2); + friend bool operator<(const VersionInformation& v1, const VersionInformation& v2); + friend bool operator<=(const VersionInformation& v1, const VersionInformation& v2); + friend bool operator>(const VersionInformation& v1, const VersionInformation& v2); + friend bool operator>=(const VersionInformation& v1, const VersionInformation& v2); friend std::ostream& operator<<(std::ostream& os, const VersionInformation& version_info) { @@ -56,6 +73,8 @@ struct VersionInformation uint32_t bugfix; ///< Bugfix version number uint32_t build; ///< Build number }; + +std::vector splitString(std::string input, const std::string& delimiter = "."); } // namespace urcl #endif // ifndef UR_CLIENT_LIBRARY_UR_VERSION_INFORMATION_H_INCLUDED diff --git a/scripts/start_ursim.sh b/scripts/start_ursim.sh new file mode 100755 index 000000000..31e9c5af8 --- /dev/null +++ b/scripts/start_ursim.sh @@ -0,0 +1,174 @@ +#!/bin/bash + +# copyright 2022 Universal Robots A/S +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of the {copyright_holder} nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +PERSISTENT_BASE="${HOME}/.ursim" + +help() +{ + # Display Help + echo "Starts URSim inside a docker container" + echo + echo "Syntax: `basename "$0"` [-m|s|h]" + echo "options:" + echo " -m Robot model. One of [ur3, ur3e, ur5, ur5e, ur10, ur10e, ur16e]. Defaults to ur5e." + echo " -v URSim version that should be used. + See https://hub.docker.com/r/universalrobots/ursim_e-series/tags + for available versions. Defaults to 'latest'" + echo " -p Location from which programs are read / to which programs are written. + If not specified, will fallback to ${PERSISTENT_BASE}/${ROBOT_SERIES}/programs" + echo " -u Location from which URCaps are read / to which URCaps are written. + If not specified, will fallback to ${PERSISTENT_BASE}/${ROBOT_SERIES}/urcaps" + echo " -d Detached mode - start in backgound" + echo " -h Print this Help." + echo +} + +ROBOT_MODEL=UR5 +ROBOT_SERIES=e-series +URSIM_VERSION=latest +URCAP_STORAGE="${PERSISTENT_BASE}/${ROBOT_SERIES}/urcaps" +PROGRAM_STORAGE="${PERSISTENT_BASE}/${ROBOT_SERIES}/programs" +DETACHED=false + + +validate_model() +{ + case $ROBOT_MODEL in + ur3|ur5|ur10) + ROBOT_MODEL=${ROBOT_MODEL^^} + ROBOT_SERIES=cb3 + ;; + ur3e|ur5e|ur10e|ur16e) + ROBOT_MODEL=${ROBOT_MODEL^^} + ROBOT_MODEL=$(echo ${ROBOT_MODEL:0:$((${#ROBOT_MODEL}-1))}) + ROBOT_SERIES=e-series + ;; + *) + echo "Not a valid robot model: $ROBOT_MODEL" + exit + ;; + esac +} + +verlte() +{ + [ "$1" = $(printf "$1\n$2" | sort -V | head -n1) ] +} + +validate_ursim_version() +{ + [ $URSIM_VERSION == "latest" ] && return 0 + local MIN_CB3="3.12.1" + local MIN_E_SERIES="5.5.1" + + case $ROBOT_SERIES in + cb3) + verlte "4.0.0" $URSIM_VERSION && echo "$URSIM_VERSION is no valid CB3 version!" && exit + verlte $MIN_CB3 $URSIM_VERSION && return 0 + ;; + e-series) + verlte $MIN_E_SERIES $URSIM_VERSION && return 0 + ;; + esac + + echo "Illegal version given. Version must be greater or equal to $MIN_CB3 / $MIN_E_SERIES. Given version: $URSIM_VERSION." + exit +} + + +while getopts ":hm:v:p:u:d" option; do + case $option in + h) # display Help + help + exit;; + m) # robot model + ROBOT_MODEL=${OPTARG} + validate_model + ;; + v) # ursim_version + URSIM_VERSION=${OPTARG} + validate_ursim_version + ;; + p) # program_folder + PROGRAM_STORAGE=${OPTARG} + ;; + u) # urcaps_folder + URCAP_STORAGE=${OPTARG} + ;; + d) # detached mode + DETACHED=true + ;; + \?) # invalid option + echo "Error: Invalid option" + help + exit;; + esac +done + +# Create local storage for programs and URCaps +mkdir -p "${URCAP_STORAGE}" +mkdir -p "${PROGRAM_STORAGE}" +URCAP_STORAGE=$(realpath "$URCAP_STORAGE") +PROGRAM_STORAGE=$(realpath "$PROGRAM_STORAGE") + + +# Check whether network already exists +docker network inspect ursim_net > /dev/null +if [ $? -eq 0 ]; then + echo "ursim_net already exists" +else + echo "Creating ursim_net" + docker network create --subnet=192.168.56.0/24 ursim_net +fi + +# run docker contain +docker run --rm -d --net ursim_net --ip 192.168.56.101\ + -v "${URCAP_STORAGE}":/urcaps \ + -v "${PROGRAM_STORAGE}":/ursim/programs \ + -e ROBOT_MODEL="${ROBOT_MODEL}" \ + -p 30001-30004:30001-30004 \ + -p 29999:29999 \ + --name ursim \ + universalrobots/ursim_${ROBOT_SERIES}:$URSIM_VERSION || exit + +trap "echo killing; docker container kill ursim; exit" SIGINT SIGTERM + +echo "Docker URSim is running" +printf "\nTo access Polyscope, open the following URL in a web browser.\n\thttp://192.168.56.101:6080/vnc.html\n\n" + +if [ "$DETACHED" = false ]; then +echo "To exit, press CTRL+C" + while : + do + sleep 1 + done +else + echo "To kill it, please execute 'docker stop ursim'" +fi diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index d60627e7d..4e804a168 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -26,17 +26,27 @@ */ //---------------------------------------------------------------------- +#include #include +#include +#include #include #include #include +using namespace std::chrono_literals; + namespace urcl { DashboardClient::DashboardClient(const std::string& host) : host_(host), port_(DASHBOARD_SERVER_PORT) { } +void DashboardClient::rtrim(std::string& str, const std::string& chars) +{ + str.erase(str.find_last_not_of(chars) + 1); +} + bool DashboardClient::connect() { if (getState() == comm::SocketState::Connected) @@ -56,6 +66,9 @@ bool DashboardClient::connect() tv.tv_usec = 0; TCPSocket::setReceiveTimeout(tv); + std::string pv; + commandPolyscopeVersion(pv); + return ret_val; } @@ -98,9 +111,14 @@ std::string DashboardClient::read() std::string DashboardClient::sendAndReceive(const std::string& text) { + std::string command = text; + if (text.back() != '\n') + { + command = text + "\n"; + } std::string response = "ERROR"; std::lock_guard lock(write_mutex_); - if (send(text)) + if (send(command)) { response = read(); } @@ -113,4 +131,352 @@ std::string DashboardClient::sendAndReceive(const std::string& text) return response; } +bool DashboardClient::sendRequest(const std::string& command, const std::string& expected) +{ + URCL_LOG_DEBUG("Send Request: %s", command.c_str()); + std::string response = sendAndReceive(command); + bool ret = std::regex_match(response, std::regex(expected)); + if (!ret) + { + throw UrException("Expected: " + expected + ", but received: " + response); + } + return ret; +} + +std::string DashboardClient::sendRequestString(const std::string& command, const std::string& expected) +{ + URCL_LOG_DEBUG("Send Request: %s", command.c_str()); + std::string response = sendAndReceive(command); + bool ret = std::regex_match(response, std::regex(expected)); + if (!ret) + { + throw UrException("Expected: " + expected + ", but received: " + response); + } + return response; +} + +bool DashboardClient::waitForReply(const std::string& command, const std::string& expected, + const std::chrono::duration timeout) +{ + const std::chrono::duration wait_period = 100ms; + + std::chrono::duration time_done(0); + std::string response; + + while (time_done < timeout) + { + // Send the request + response = sendAndReceive(command); + + // Check if the response was as expected + if (std::regex_match(response, std::regex(expected))) + { + return true; + } + + // wait 100ms before trying again + std::this_thread::sleep_for(wait_period); + time_done += wait_period; + } + + URCL_LOG_WARN("Did not got the expected \"%s\" response within the timeout. Last response was: \"%s\"", + expected.c_str(), response.c_str()); // Is a warning here so retryCommand does not throw when retrying + return false; +} + +bool DashboardClient::retryCommand(const std::string& requestCommand, const std::string& requestExpectedResponse, + const std::string& waitRequest, const std::string& waitExpectedResponse, + const std::chrono::duration timeout, + const std::chrono::duration retry_period) +{ + std::chrono::duration time_done(0); + do + { + sendRequest(requestCommand, requestExpectedResponse); + time_done += retry_period; + + if (waitForReply(waitRequest, waitExpectedResponse, retry_period)) + { + return true; + } + } while (time_done < timeout); + return false; +} + +bool DashboardClient::commandPowerOff() +{ + assertVersion("5.0.0", "3.0", "power off"); + return sendRequest("power off", "Powering off") && waitForReply("robotmode", "Robotmode: POWER_OFF"); +} + +bool DashboardClient::commandPowerOn(const std::chrono::duration timeout) +{ + assertVersion("5.0.0", "3.0", "power on"); + return retryCommand("power on", "Powering on", "robotmode", "Robotmode: IDLE", timeout); +} + +bool DashboardClient::commandBrakeRelease() +{ + assertVersion("5.0.0", "3.0", "brake release"); + return sendRequest("brake release", "Brake releasing") && waitForReply("robotmode", "Robotmode: RUNNING"); +} + +bool DashboardClient::commandLoadProgram(const std::string& program_file_name) +{ + assertVersion("5.0.0", "1.4", "load "); + return sendRequest("load " + program_file_name + "", "(?:Loading program: ).*(?:" + program_file_name + ").*") && + waitForReply("programState", "STOPPED " + program_file_name); +} + +bool DashboardClient::commandLoadInstallation(const std::string& installation_file_name) +{ + assertVersion("5.0.0", "3.2", "load installation"); + return sendRequest("load installation " + installation_file_name, + "(?:Loading installation: ).*(?:" + installation_file_name + ").*"); +} + +bool DashboardClient::commandPlay() +{ + assertVersion("5.0.0", "1.4", "play"); + return sendRequest("play", "Starting program") && waitForReply("programState", "(?:PLAYING ).*"); +} + +bool DashboardClient::commandPause() +{ + assertVersion("5.0.0", "1.4", "pause"); + return sendRequest("pause", "Pausing program") && waitForReply("programState", "(?:PAUSED ).*"); +} + +bool DashboardClient::commandStop() +{ + assertVersion("5.0.0", "1.4", "stop"); + return sendRequest("stop", "Stopped") && waitForReply("programState", "(?:STOPPED ).*"); +} + +bool DashboardClient::commandClosePopup() +{ + assertVersion("5.0.0", "1.6", "close popup"); + return sendRequest("close popup", "closing popup"); +} + +bool DashboardClient::commandCloseSafetyPopup() +{ + assertVersion("5.0.0", "3.1", "close safety popup"); + return sendRequest("close safety popup", "closing safety popup"); +} + +bool DashboardClient::commandRestartSafety() +{ + assertVersion("5.1.0", "3.7", "restart safety"); + return sendRequest("restart safety", "Restarting safety") && waitForReply("robotmode", "Robotmode: POWER_OFF"); +} + +bool DashboardClient::commandUnlockProtectiveStop() +{ + assertVersion("5.0.0", "3.1", "unlock protective stop"); + return sendRequest("unlock protective stop", "Protective stop releasing"); +} + +bool DashboardClient::commandShutdown() +{ + assertVersion("5.0.0", "1.4", "shutdown"); + return sendRequest("shutdown", "Shutting down"); +} + +bool DashboardClient::commandQuit() +{ + assertVersion("5.0.0", "1.4", "quit"); + return sendRequest("quit", "Disconnected"); +} + +bool DashboardClient::commandRunning() +{ + assertVersion("5.0.0", "1.6", "running"); + return sendRequest("running", "Program running: true"); +} + +bool DashboardClient::commandIsProgramSaved() +{ + assertVersion("5.0.0", "1.8", "isProgramSaved"); + return sendRequest("isProgramSaved", "(?:true ).*"); +} + +bool DashboardClient::commandIsInRemoteControl() +{ + assertVersion("5.6.0", "-", "is in remote control"); + std::string response = sendAndReceive("is in remote control"); + bool ret = std::regex_match(response, std::regex("true")); + return ret; +} + +bool DashboardClient::commandPopup(const std::string& popup_text) +{ + assertVersion("5.0.0", "1.6", "popup"); + return sendRequest("popup " + popup_text, "showing popup"); +} + +bool DashboardClient::commandAddToLog(const std::string& log_text) +{ + assertVersion("5.0.0", "1.8", "addToLog"); + return sendRequest("addToLog " + log_text, "Added log message"); +} + +bool DashboardClient::commandPolyscopeVersion(std::string& polyscope_version) +{ + std::string expected = "(?:URSoftware ).*"; + polyscope_version = sendRequestString("PolyscopeVersion", expected); + std::string version_string = polyscope_version.substr(polyscope_version.find(" ") + 1, + polyscope_version.find(" (") - polyscope_version.find(" ") - 1); + polyscope_version_ = VersionInformation::fromString(version_string); + return std::regex_match(polyscope_version, std::regex(expected)); +} + +bool DashboardClient::commandGetRobotModel(std::string& robot_model) +{ + assertVersion("5.6.0", "3.12", "get robot model"); + std::string expected = "(?:UR).*"; + robot_model = sendRequestString("get robot model", expected); + return std::regex_match(robot_model, std::regex(expected)); +} + +bool DashboardClient::commandGetSerialNumber(std::string& serial_number) +{ + assertVersion("5.6.0", "3.12", "get serial number"); + std::string expected = "(?:20).*"; + serial_number = sendRequestString("get serial number", expected); + return std::regex_match(serial_number, std::regex(expected)); +} + +bool DashboardClient::commandRobotMode(std::string& robot_mode) +{ + assertVersion("5.0.0", "1.6", "robotmode"); + std::string expected = "(?:Robotmode: ).*"; + robot_mode = sendRequestString("robotmode", expected); + return std::regex_match(robot_mode, std::regex(expected)); +} + +bool DashboardClient::commandGetLoadedProgram(std::string& loaded_program) +{ + assertVersion("5.0.0", "1.6", "get loaded program"); + std::string expected = "(?:Loaded program: ).*"; + loaded_program = sendRequestString("get loaded program", expected); + return std::regex_match(loaded_program, std::regex(expected)); +} + +bool DashboardClient::commandSafetyMode(std::string& safety_mode) +{ + assertVersion("5.0.0", "3.0", "safetymode"); + std::string expected = "(?:Safetymode: ).*"; + safety_mode = sendRequestString("safetymode", expected); + return std::regex_match(safety_mode, std::regex(expected)); +} + +bool DashboardClient::commandSafetyStatus(std::string& safety_status) +{ + assertVersion("5.4.0", "3.11", "safetystatus"); + std::string expected = "(?:Safetystatus: ).*"; + safety_status = sendRequestString("safetystatus", expected); + return std::regex_match(safety_status, std::regex(expected)); +} + +bool DashboardClient::commandProgramState(std::string& program_state) +{ + assertVersion("5.0.0", "1.8", "programState"); + std::string expected = "(?:).*"; + program_state = sendRequestString("programState", expected); + return !std::regex_match(program_state, std::regex("(?:could not understand).*")); +} + +bool DashboardClient::commandGetOperationalMode(std::string& operational_mode) +{ + assertVersion("5.6.0", "-", "get operational mode"); + std::string expected = "(?:).*"; + operational_mode = sendRequestString("get operational mode", expected); + return !std::regex_match(operational_mode, std::regex("(?:could not understand).*")); +} + +bool DashboardClient::commandSetOperationalMode(const std::string& operational_mode) +{ + assertVersion("5.0.0", "-", "set operational mode"); + return sendRequest("set operational mode " + operational_mode, + "(?:Operational mode ).*(?:" + operational_mode + ").*"); +} + +bool DashboardClient::commandClearOperationalMode() +{ + assertVersion("5.0.0", "-", "clear operational mode"); + return sendRequest("clear operational mode", "(?:No longer controlling the operational mode. ).*"); +} + +bool DashboardClient::commandSetUserRole(const std::string& user_role) +{ + assertVersion("-", "1.8", "setUserRole"); + return sendRequest("setUserRole " + user_role, "(?:Setting user role: ).*"); +} + +bool DashboardClient::commandGetUserRole(std::string& user_role) +{ + assertVersion("-", "1.8", "getUserRole"); + std::string expected = "(?:).*"; + user_role = sendRequestString("getUserRole", expected); + return !std::regex_match(user_role, std::regex("(?:could not understand).*")); +} + +bool DashboardClient::commandGenerateFlightReport(const std::string& report_type) +{ + assertVersion("5.8.0", "3.13", "generate flight report"); + timeval tv; + tv.tv_sec = 180; + tv.tv_usec = 0; + TCPSocket::setReceiveTimeout(tv); // Set timeout to 3 minutes as this command can take a long time to complete + bool ret = sendRequest("generate flight report " + report_type, "(?:Flight Report generated with id:).*"); + tv.tv_sec = 1; // Reset timeout to standard timeout + TCPSocket::setReceiveTimeout(tv); + return ret; +} + +bool DashboardClient::commandGenerateSupportFile(const std::string& dir_path) +{ + assertVersion("5.8.0", "3.13", "generate support file"); + timeval tv; + tv.tv_sec = 600; + tv.tv_usec = 0; + TCPSocket::setReceiveTimeout(tv); // Set timeout to 10 minutes as this command can take a long time to complete + bool ret = sendRequest("generate support file " + dir_path, "(?:Completed successfully:).*"); + tv.tv_sec = 1; // Reset timeout to standard timeout + TCPSocket::setReceiveTimeout(tv); + return ret; +} + +void DashboardClient::assertVersion(const std::string& e_series_min_ver, const std::string& cb3_min_ver, + const std::string& required_call) +{ + if (!polyscope_version_.isESeries() && cb3_min_ver == "-") + { + std::stringstream ss; + ss << "The dasboard call '" << required_call + << "' is only available on e-series robots, but you seem to be running version " << polyscope_version_; + throw UrException(ss.str()); + } + + if (polyscope_version_.isESeries() && e_series_min_ver == "-") + { + std::stringstream ss; + ss << "The dasboard call '" << required_call + << "' is only available on pre-e-series robots (5.x.y), but you seem to be running version " + << polyscope_version_; + throw UrException(ss.str()); + } + + auto ref = polyscope_version_.isESeries() ? VersionInformation::fromString(e_series_min_ver) : + VersionInformation::fromString(cb3_min_ver); + if (ref > polyscope_version_) + { + std::stringstream ss; + ss << "Polyscope version " << polyscope_version_ << " isn't recent enough to use dashboard call '" << required_call + << "'"; + throw UrException(ss.str()); + } +} + } // namespace urcl diff --git a/src/ur/version_information.cpp b/src/ur/version_information.cpp new file mode 100644 index 000000000..1bc5cf456 --- /dev/null +++ b/src/ur/version_information.cpp @@ -0,0 +1,147 @@ +// this is for emacs file handling -*- mode: c++; indent-tabs-mode: nil -*- + +// -- BEGIN LICENSE BLOCK ---------------------------------------------- +// Copyright 2022 FZI Forschungszentrum Informatik +// Created on behalf of Universal Robots A/S +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// -- END LICENSE BLOCK ------------------------------------------------ + +//---------------------------------------------------------------------- +/*!\file + * + * \author "Felix Exner" + * \date 2022-11-22 + * + */ +//---------------------------------------------------------------------- + +#include +#include + +namespace urcl +{ +std::vector splitString(std::string input, const std::string& delimiter) +{ + std::vector result; + size_t pos = 0; + std::string substring; + while ((pos = input.find(delimiter)) != std::string::npos) + { + substring = input.substr(0, pos); + result.push_back(substring); + input.erase(0, pos + delimiter.length()); + } + result.push_back(input); + return result; +} + +VersionInformation::VersionInformation() +{ + // Since 'major' and 'minor' are keywords in we don't use the initializer list and + // specify this->major explicitly. + this->major = 0; + this->minor = 0; + this->bugfix = 0; + this->build = 0; +} + +VersionInformation VersionInformation::fromString(const std::string& str) +{ + auto components = splitString(str); + VersionInformation info; + if (components.size() >= 2) + { + info.major = std::stoi(components[0]); + info.minor = std::stoi(components[1]); + if (components.size() >= 3) + { + info.bugfix = std::stoi(components[2]); + if (components.size() == 4) + { + info.build = std::stoi(components[3]); + } + else if (components.size() > 4) + { + throw UrException("Given string '" + str + "' does not conform a version string format."); + } + } + } + else + { + throw UrException("Given string '" + str + "' does not conform a version string format."); + } + + return info; +} + +bool VersionInformation::isESeries() const +{ + return this->major >= 5; +} + +bool operator==(const VersionInformation& v1, const VersionInformation& v2) +{ + return v1.major == v2.major && v1.minor == v2.minor && v1.bugfix == v2.bugfix && v1.build == v2.build; +} + +bool operator!=(const VersionInformation& v1, const VersionInformation& v2) +{ + return !(v1 == v2); +} + +bool operator<(const VersionInformation& v1, const VersionInformation& v2) +{ + if (v1.major <= v2.major) + { + if (v1.major < v2.major) + { + return true; + } + if (v1.minor <= v2.minor) + { + if (v1.minor < v2.minor) + { + return true; + } + if (v1.bugfix <= v2.bugfix) + { + if (v1.bugfix < v2.bugfix) + { + return true; + } + } + if (v1.build < v2.build) + { + return true; + } + } + } + return false; +} + +bool operator<=(const VersionInformation& v1, const VersionInformation& v2) +{ + return v1 < v2 || v1 == v2; +} + +bool operator>(const VersionInformation& v1, const VersionInformation& v2) +{ + return !(v1 <= v2); +} + +bool operator>=(const VersionInformation& v1, const VersionInformation& v2) +{ + return !(v1 < v2); +} +} // namespace urcl diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed87d4882..ea8bb0119 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -37,10 +37,18 @@ if (INTEGRATION_TESTS) gtest_add_tests(TARGET rtde_tests WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) + + add_executable(dashboard_client_tests test_dashboard_client.cpp) + target_compile_options(dashboard_client_tests PRIVATE ${CXX17_FLAG}) + target_include_directories(dashboard_client_tests PRIVATE ${GTEST_INCLUDE_DIRS}) + target_link_libraries(dashboard_client_tests PRIVATE ur_client_library::urcl ${GTEST_LIBRARIES}) + gtest_add_tests(TARGET dashboard_client_tests + ) else() message(STATUS "Skipping integration tests.") endif() + add_executable(primary_parser_tests test_primary_parser.cpp) target_compile_options(primary_parser_tests PRIVATE ${CXX17_FLAG}) target_include_directories(primary_parser_tests PRIVATE ${GTEST_INCLUDE_DIRS}) @@ -139,3 +147,9 @@ target_link_libraries(rtde_writer_tests PRIVATE ur_client_library::urcl ${GTEST_ gtest_add_tests(TARGET rtde_writer_tests ) +add_executable(version_information_tests test_version_information.cpp) +target_compile_options(version_information_tests PRIVATE ${CXX17_FLAG}) +target_include_directories(version_information_tests PRIVATE ${GTEST_INCLUDE_DIRS}) +target_link_libraries(version_information_tests PRIVATE ur_client_library::urcl ${GTEST_LIBRARIES}) +gtest_add_tests(TARGET version_information_tests +) diff --git a/tests/resources/dockerursim/Dockerfile b/tests/resources/dockerursim/Dockerfile deleted file mode 100644 index 253b60d34..000000000 --- a/tests/resources/dockerursim/Dockerfile +++ /dev/null @@ -1,128 +0,0 @@ -# MIT License -# -# Original from https://github.com/ahobsonsayers/DockURSim -# Copyright (c) 2019 Arran Hobson Sayers -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -FROM lsiobase/guacgui:latest - -# Set Version Information -ARG VERSION="5.8.0.10253" -LABEL build_version="URSim Version: ${VERSION}" -ENV APPNAME="URSim" - -# Set Timezone -ARG TZ="Europe/London" -ENV TZ ${TZ} - -# Setup Environment -ENV DEBIAN_FRONTEND noninteractive - -# Set Home Directory -ENV HOME /ursim - -# Set robot model - Can be UR3, UR5 or UR10 -ENV ROBOT_MODEL UR5 - -RUN \ - echo "**** Installing Dependencies ****" && \ - apt-get update && \ - apt-get install -qy --no-install-recommends \ - openjdk-8-jre psmisc && \ - # Change java alternatives so we use openjdk8 (required by URSim) not openjdk11 that comes with guacgui - update-alternatives --install /usr/bin/java java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java 10000 - -# Setup JAVA_HOME -ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64 - -RUN \ - echo "**** Downloading URSim ****" && \ - # Make sure we are in the root - cd / - # Download URSim Linux tar.gz -RUN \ - if [ "$VERSION" = "5.8.0.10253" ]; then \ - curl https://s3-eu-west-1.amazonaws.com/ur-support-site/69987/URSim_Linux-5.8.0.10253.tar.gz -o URSim-Linux.tar.gz; \ - elif [ "$VERSION" = "5.9.1.1031110" ]; then \ - curl https://s3-eu-west-1.amazonaws.com/ur-support-site/77055/URSim_Linux-5.9.1.1031110.tar.gz -o URSim-Linux.tar.gz; \ - elif [ "$VERSION" = "3.14.1.1031110" ]; then \ - curl https://s3-eu-west-1.amazonaws.com/ur-support-site/77041/URSim_Linux-3.14.1.1031110.tar.gz -o URSim-Linux.tar.gz; \ - else \ - echo "Unknown version passed to Dockerfile. Exiting" &&\ - exit 1; \ - fi -RUN \ - # Extract tarball - tar xvzf URSim-Linux.tar.gz && \ - #Remove the tarball - rm URSim-Linux.tar.gz && \ - # Rename the URSim folder to jus ursim - mv /ursim* /ursim - -RUN \ - echo "**** Installing URSim ****" && \ - # cd to ursim folder - cd /ursim && \ - # Make URControl and all sh files executable - chmod +x ./*.sh ./URControl && \ - # - # Stop install of unnecessary packages and install required ones quietly - sed -i 's|apt-get -y install|apt-get -qy install --no-install-recommends|g' ./install.sh && \ - # Skip xterm command. We dont have a desktop - sed -i 's|tty -s|(exit 0)|g' install.sh && \ - # Skip Check of Java Version as we have the correct installed and the command will fail - sed -i 's|needToInstallJava$|(exit 0)|g' install.sh && \ - # Skip install of desktop shortcuts - we dont have a desktop - sed -i '/for TYPE in UR3 UR5 UR10/,$ d' ./install.sh && \ - # Remove commands that are not relevant on docker as we are root user - sed -i 's|pkexec ||g' ./install.sh && \ - sed -i 's|sudo ||g' ./install.sh && \ - #sed -i 's|sudo ||g' ./ursim-certificate-check.sh && \ - # - # Install URSim - ./install.sh && \ - # - echo "Installed URSim" - -RUN \ - echo "**** Clean Up ****" && \ - rm -rf \ - /tmp/* \ - /var/lib/apt/lists/* \ - /var/tmp/* - -# Copy ursim run service script -COPY ursim /etc/services.d/ursim -COPY safety.conf.UR5 /ursim/.urcontrol/ -# Expose ports -# Guacamole web browser viewer -EXPOSE 8080 -# VNC viewer -EXPOSE 3389 -# Modbus Port -EXPOSE 502 -# Interface Ports -EXPOSE 29999 -EXPOSE 30001-30004 - -# Mount Volumes -VOLUME /ursim - -ENTRYPOINT ["/init"] diff --git a/tests/resources/dockerursim/build_and_run_docker_ursim.sh b/tests/resources/dockerursim/build_and_run_docker_ursim.sh deleted file mode 100755 index 070227a92..000000000 --- a/tests/resources/dockerursim/build_and_run_docker_ursim.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -URSIM_VERSION="${1:-5.8.0.10253}" - -docker network create --subnet=192.168.56.0/24 static_test_net - -docker build ${DIR} -t mydockerursim --build-arg VERSION="${URSIM_VERSION}" -docker volume create dockerursim -docker run --name="mydockerursim" -d \ - -e ROBOT_MODEL=UR5 \ - --net static_test_net \ - --ip 192.168.56.101 \ - -p 8080:8080 \ - -p 29999:29999 \ - -p 30001-30004:30001-30004 \ - -v "${DIR}/.vol":/ursim/programs \ - -v dockursim:/ursim \ - --privileged \ - --cpus=1 \ - mydockerursim diff --git a/tests/resources/dockerursim/programs/cb3/default.installation b/tests/resources/dockerursim/programs/cb3/default.installation new file mode 100644 index 000000000..6cce84c73 Binary files /dev/null and b/tests/resources/dockerursim/programs/cb3/default.installation differ diff --git a/tests/resources/dockerursim/programs/cb3/default.variables b/tests/resources/dockerursim/programs/cb3/default.variables new file mode 100644 index 000000000..b546614dd --- /dev/null +++ b/tests/resources/dockerursim/programs/cb3/default.variables @@ -0,0 +1,2 @@ +# +#Thu Oct 20 15:39:24 CEST 2022 diff --git a/tests/resources/dockerursim/programs/cb3/programs.UR5 b/tests/resources/dockerursim/programs/cb3/programs.UR5 new file mode 120000 index 000000000..ebd12373d --- /dev/null +++ b/tests/resources/dockerursim/programs/cb3/programs.UR5 @@ -0,0 +1 @@ +/ursim/programs.UR5 \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/cb3/wait_program.urp b/tests/resources/dockerursim/programs/cb3/wait_program.urp new file mode 100644 index 000000000..8f842454b Binary files /dev/null and b/tests/resources/dockerursim/programs/cb3/wait_program.urp differ diff --git a/tests/resources/dockerursim/.vol/default.installation b/tests/resources/dockerursim/programs/e-series/default.installation similarity index 100% rename from tests/resources/dockerursim/.vol/default.installation rename to tests/resources/dockerursim/programs/e-series/default.installation diff --git a/tests/resources/dockerursim/.vol/default.variables b/tests/resources/dockerursim/programs/e-series/default.variables similarity index 100% rename from tests/resources/dockerursim/.vol/default.variables rename to tests/resources/dockerursim/programs/e-series/default.variables diff --git a/tests/resources/dockerursim/.vol/programs.UR5 b/tests/resources/dockerursim/programs/e-series/programs.UR5 similarity index 100% rename from tests/resources/dockerursim/.vol/programs.UR5 rename to tests/resources/dockerursim/programs/e-series/programs.UR5 diff --git a/tests/resources/dockerursim/programs/e-series/wait_program.urp b/tests/resources/dockerursim/programs/e-series/wait_program.urp new file mode 100644 index 000000000..df9b12951 Binary files /dev/null and b/tests/resources/dockerursim/programs/e-series/wait_program.urp differ diff --git a/tests/resources/dockerursim/safety.conf.UR5 b/tests/resources/dockerursim/safety.conf.UR5 deleted file mode 100644 index 908b382f4..000000000 --- a/tests/resources/dockerursim/safety.conf.UR5 +++ /dev/null @@ -1,101 +0,0 @@ -# Beware: This file is auto-generated from PolyScope. -# NOTE: The SafetyParameters section is protected by a CRC checksum, please use the supplied tool - -## SafetyParameters ## -[NormalModeSafetyLimits] -maxTcpSpeed = 1.5 -maxForce = 150.0 -maxElbowSpeed = 1.5 -maxElbowForce = 150.0 -maxStoppingDistance = 0.5 -maxStoppingTime = 0.4 -maxPower = 300.0 -maxMomentum = 25.0 -maxJointSpeed = [3.3415926, 3.3415926, 3.3415926, 3.3415926, 3.3415926, 3.3415926] -minJointPosition = [6.2308254, 6.2308254, 6.2308254, 6.2308254, 6.2308254, 6.2308254] -maxJointPosition = [0.05235988, 0.05235988, 0.05235988, 0.05235988, 0.05235988, 0.05235988] -minJointRevolutions = [-2, -2, -2, -2, -2, -2] -maxJointRevolutions = [1, 1, 1, 1, 1, 1] -plane0 = [0.0, 0.0, 0.0, 0.0, 0] -plane1 = [0.0, 0.0, 0.0, 0.0, 0] -plane2 = [0.0, 0.0, 0.0, 0.0, 0] -plane3 = [0.0, 0.0, 0.0, 0.0, 0] -plane4 = [0.0, 0.0, 0.0, 0.0, 0] -plane5 = [0.0, 0.0, 0.0, 0.0, 0] -plane6 = [0.0, 0.0, 0.0, 0.0, 0] -plane7 = [0.0, 0.0, 0.0, 0.0, 0] -tcpOrientationVector = [0.0, 0.0, 1.0] -maximumTcpOrientationDeviation = 6.2831855 - -[ReducedModeSafetyLimits] -maxTcpSpeed = 0.75 -maxForce = 120.0 -maxElbowSpeed = 0.75 -maxElbowForce = 120.0 -maxStoppingDistance = 0.3 -maxStoppingTime = 0.3 -maxPower = 200.0 -maxMomentum = 10.0 -maxJointSpeed = [3.3415926, 3.3415926, 3.3415926, 3.3415926, 3.3415926, 3.3415926] -minJointPosition = [6.2308254, 6.2308254, 6.2308254, 6.2308254, 6.2308254, 6.2308254] -maxJointPosition = [0.05235988, 0.05235988, 0.05235988, 0.05235988, 0.05235988, 0.05235988] -minJointRevolutions = [-2, -2, -2, -2, -2, -2] -maxJointRevolutions = [1, 1, 1, 1, 1, 1] -plane0 = [0.0, 0.0, 0.0, 0.0, 0] -plane1 = [0.0, 0.0, 0.0, 0.0, 0] -plane2 = [0.0, 0.0, 0.0, 0.0, 0] -plane3 = [0.0, 0.0, 0.0, 0.0, 0] -plane4 = [0.0, 0.0, 0.0, 0.0, 0] -plane5 = [0.0, 0.0, 0.0, 0.0, 0] -plane6 = [0.0, 0.0, 0.0, 0.0, 0] -plane7 = [0.0, 0.0, 0.0, 0.0, 0] -tcpOrientationVector = [0.0, 0.0, 1.0] -maximumTcpOrientationDeviation = 6.2831855 - -[MiscConfiguration] -teach_pendant = 1 -euromap67 = 0 - -[SafetyIOConfiguration] -emergencyStopInputA = 255 -emergencyStopInputB = 255 -reducedModeInputA = 255 -reducedModeInputB = 255 -safeguardStopResetInputA = 0 -safeguardStopResetInputB = 1 -threePositionEnablingInputA = 255 -threePositionEnablingInputB = 255 -operationalModeInputA = 255 -operationalModeInputB = 255 -systemEmergencyStopOutputA = 255 -systemEmergencyStopOutputB = 255 -robotMovingOutputA = 255 -robotMovingOutputB = 255 -robotNotStoppingOutputA = 255 -robotNotStoppingOutputB = 255 -reducedModeOutputA = 255 -reducedModeOutputB = 255 -notReducedModeOutputA = 255 -notReducedModeOutputB = 255 - -[ReducedModeTriggerPlanes] -plane0 = [0.0, 0.0, 0.0, 0.0, 0] -plane1 = [0.0, 0.0, 0.0, 0.0, 0] -plane2 = [0.0, 0.0, 0.0, 0.0, 0] -plane3 = [0.0, 0.0, 0.0, 0.0, 0] -plane4 = [0.0, 0.0, 0.0, 0.0, 0] -plane5 = [0.0, 0.0, 0.0, 0.0, 0] -plane6 = [0.0, 0.0, 0.0, 0.0, 0] -plane7 = [0.0, 0.0, 0.0, 0.0, 0] - -[WorkpieceConfiguration] -toolSpheres = [[0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0]] -toolDirectionInclination = 0.0 -toolDirectionAzimuth = 0.0 - - -## SafetyParameters ## -[Checksum] -safetyParameters = 3478627865 -majorVersion = 5 -minorVersion = 0 diff --git a/tests/resources/dockerursim/ursim/run b/tests/resources/dockerursim/ursim/run deleted file mode 100644 index 4021c48fc..000000000 --- a/tests/resources/dockerursim/ursim/run +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/execlineb -P - -s6-envdir -fn -- /var/run/s6/container_environment -importas -i ROBOT_MODEL ROBOT_MODEL - -# Redirect stderr to stdout. -fdmove -c 2 1 - -# Wait until openbox is running -if { s6-svwait -t 10000 -U /var/run/s6/services/openbox/ } - -# Set env -s6-env DISPLAY=:1 - -# Execute URSim -/ursim/start-ursim.sh ${ROBOT_MODEL} \ No newline at end of file diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp new file mode 100644 index 000000000..acce96fb7 --- /dev/null +++ b/tests/test_dashboard_client.cpp @@ -0,0 +1,173 @@ +// -- BEGIN LICENSE BLOCK ---------------------------------------------- +// Copyright 2022 Universal Robots A/S +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of the {copyright_holder} nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// -- END LICENSE BLOCK ------------------------------------------------ + +#include +#include +#include +#include +#include "ur_client_library/ur/version_information.h" +#define private public +#include + +using namespace urcl; + +std::string ROBOT_IP = "192.168.56.101"; + +class DashboardClientTest : public ::testing::Test +{ +protected: + void SetUp() + { + dashboard_client_.reset(new DashboardClient(ROBOT_IP)); + } + + void TearDown() + { + dashboard_client_.reset(); + } + + std::unique_ptr dashboard_client_; +}; + +TEST_F(DashboardClientTest, connect) +{ + EXPECT_TRUE(dashboard_client_->connect()); + dashboard_client_->commandCloseSafetyPopup(); +} + +TEST_F(DashboardClientTest, run_program) +{ + EXPECT_TRUE(dashboard_client_->connect()); + EXPECT_TRUE(dashboard_client_->commandLoadProgram("wait_program.urp")); + EXPECT_TRUE(dashboard_client_->commandPowerOff()); + dashboard_client_->commandClosePopup(); // Necessary for CB3 test + EXPECT_TRUE(dashboard_client_->commandPowerOn()); + EXPECT_TRUE(dashboard_client_->commandBrakeRelease()); + EXPECT_TRUE(dashboard_client_->commandPlay()); + EXPECT_TRUE(dashboard_client_->commandPause()); + EXPECT_TRUE(dashboard_client_->commandPlay()); + EXPECT_TRUE(dashboard_client_->commandStop()); + EXPECT_TRUE(dashboard_client_->commandPowerOff()); + dashboard_client_->commandClosePopup(); // Necessary for CB3 test +} + +TEST_F(DashboardClientTest, load_installation) +{ + EXPECT_TRUE(dashboard_client_->connect()); + EXPECT_TRUE(dashboard_client_->commandLoadInstallation("default.installation")); +} + +TEST_F(DashboardClientTest, not_connected) +{ + EXPECT_THROW(dashboard_client_->commandPowerOff(), UrException); +} + +TEST_F(DashboardClientTest, popup) +{ + EXPECT_TRUE(dashboard_client_->connect()); + EXPECT_TRUE(dashboard_client_->commandPopup("Test Popup")); + std::this_thread::sleep_for(std::chrono::milliseconds(500)); // Give time for popup to pop up + EXPECT_TRUE(dashboard_client_->commandClosePopup()); +} + +TEST_F(DashboardClientTest, log_and_getters) +{ + std::string msg; + EXPECT_TRUE(dashboard_client_->connect()); + EXPECT_TRUE(dashboard_client_->commandAddToLog("Testing Log:")); + EXPECT_TRUE(dashboard_client_->commandPolyscopeVersion(msg)); + EXPECT_TRUE(dashboard_client_->commandAddToLog("Polyscope Version: " + msg)); + EXPECT_TRUE(dashboard_client_->commandRobotMode(msg)); + EXPECT_TRUE(dashboard_client_->commandAddToLog("Robot mode: " + msg)); + EXPECT_TRUE(dashboard_client_->commandGetLoadedProgram(msg)); + EXPECT_TRUE(dashboard_client_->commandAddToLog("Loaded program: " + msg)); + EXPECT_TRUE(dashboard_client_->commandProgramState(msg)); + EXPECT_TRUE(dashboard_client_->commandAddToLog("Program state: " + msg)); +} + +TEST_F(DashboardClientTest, flight_report_and_support_file) +{ + EXPECT_TRUE(dashboard_client_->connect()); + bool correct_polyscope_version = true; + try + { + dashboard_client_->assertVersion("5.6.0", "3.13", "test_function"); + } + catch (const UrException& e) + { + correct_polyscope_version = false; + } + + if (correct_polyscope_version) + { + EXPECT_TRUE(dashboard_client_->commandGenerateFlightReport("")); + EXPECT_TRUE(dashboard_client_->commandGenerateSupportFile(".")); + } + else + { + EXPECT_THROW(dashboard_client_->commandGenerateFlightReport(""), UrException); + EXPECT_THROW(dashboard_client_->commandGenerateSupportFile("."), UrException); + } +} + +// Only runs this test if robot is e-series +TEST_F(DashboardClientTest, e_series_version) +{ + std::string msg; + EXPECT_TRUE(dashboard_client_->connect()); + if (!dashboard_client_->polyscope_version_.isESeries()) + GTEST_SKIP(); + dashboard_client_->polyscope_version_ = VersionInformation::fromString("5.0.0"); + EXPECT_THROW(dashboard_client_->commandSafetyStatus(msg), UrException); + dashboard_client_->polyscope_version_ = VersionInformation::fromString("5.5.0"); + EXPECT_TRUE(dashboard_client_->commandSafetyStatus(msg)); + EXPECT_THROW(dashboard_client_->commandSetUserRole("none"), UrException); +} + +// Only runs this test if robot is CB3 +TEST_F(DashboardClientTest, cb3_version) +{ + std::string msg; + EXPECT_TRUE(dashboard_client_->connect()); + if (dashboard_client_->polyscope_version_.isESeries()) + GTEST_SKIP(); + dashboard_client_->polyscope_version_ = VersionInformation::fromString("1.6.0"); + EXPECT_THROW(dashboard_client_->commandIsProgramSaved(), UrException); + dashboard_client_->polyscope_version_ = VersionInformation::fromString("1.8.0"); + EXPECT_TRUE(dashboard_client_->commandIsProgramSaved()); + EXPECT_THROW(dashboard_client_->commandIsInRemoteControl(), UrException); +} + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} diff --git a/tests/test_version_information.cpp b/tests/test_version_information.cpp new file mode 100644 index 000000000..165ee4135 --- /dev/null +++ b/tests/test_version_information.cpp @@ -0,0 +1,90 @@ +// this is for emacs file handling -*- mode: c++; indent-tabs-mode: nil -*- + +// -- BEGIN LICENSE BLOCK ---------------------------------------------- +// Copyright 2022 FZI Forschungszentrum Informatik +// Created on behalf of Universal Robots A/S +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// -- END LICENSE BLOCK ------------------------------------------------ + +//---------------------------------------------------------------------- +/*!\file + * + * \author Felix Exner mauch@fzi.de + * \date 2022-11-22 + * + */ +//---------------------------------------------------------------------- + +#include + +#include +#include + +using namespace urcl; + +TEST(version_information, test_split) +{ + const std::string version_string1 = "5.12.0.1101319"; + std::vector expected = { "5", "12", "0", "1101319" }; + + EXPECT_EQ(expected, splitString(version_string1)); +} + +TEST(version_information, string_parsing) +{ + const std::string version_string_full = "5.12.1.1234"; + const std::string version_3 = "5.12.1"; + const std::string version_2 = "5.12"; + auto expected = VersionInformation(); + expected.major = 5; + expected.minor = 12; + expected.bugfix = 1; + expected.build = 1234; + + EXPECT_EQ(expected, VersionInformation::fromString(version_string_full)); + expected.build = 0; + EXPECT_EQ(expected, VersionInformation::fromString(version_3)); + expected.bugfix = 0; + EXPECT_EQ(expected, VersionInformation::fromString(version_2)); + + const std::string illegal_string("asdy"); + EXPECT_THROW(VersionInformation::fromString(illegal_string), UrException); + const std::string illegal_string_2("1"); + EXPECT_THROW(VersionInformation::fromString(illegal_string_2), UrException); +} + +TEST(version_information, test_relations) +{ + auto v1 = VersionInformation::fromString("5.5.0.1101319"); + auto v2 = VersionInformation::fromString("5.5.0.1101318"); + auto v3 = VersionInformation::fromString("5.5.1"); + auto v4 = VersionInformation::fromString("3.12.0.1234"); + + EXPECT_EQ(v1, v1); + EXPECT_LT(v2, v1); + EXPECT_LE(v2, v1); + EXPECT_LE(v1, v1); + EXPECT_GT(v1, v2); + EXPECT_GE(v1, v1); + EXPECT_LT(v1, v3); + EXPECT_LT(v4, v1); + EXPECT_TRUE(v1 != v2); +} + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +}