From 1f080c0e2e5552dc60b1dcb76b972ebac45dea37 Mon Sep 17 00:00:00 2001 From: urrsk <41109954+urrsk@users.noreply.github.com> Date: Sat, 29 Oct 2022 09:37:01 +0200 Subject: [PATCH 01/18] Add example for receiving the calibration status Made a primary pipeline example for receiving the robot calibration based on the already existing primary pipeline example and the ROS calibration code --- examples/CMakeLists.txt | 13 ++- examples/primary_pipeline_calibration.cpp | 117 ++++++++++++++++++++++ 2 files changed, 126 insertions(+), 4 deletions(-) create mode 100644 examples/primary_pipeline_calibration.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 5daa20d12..0de5686e1 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,6 +20,11 @@ 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}) diff --git a/examples/primary_pipeline_calibration.cpp b/examples/primary_pipeline_calibration.cpp new file mode 100644 index 000000000..23347e40f --- /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 = "127.0.0.1"; + +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; +} From f43c61844b6e5dcb43d531f7c295496bdb169ff3 Mon Sep 17 00:00:00 2001 From: urrsk <41109954+urrsk@users.noreply.github.com> Date: Sat, 29 Oct 2022 09:57:38 +0200 Subject: [PATCH 02/18] Change to URSim docker image Instead of building one in the CI we now uses the image made by UR --- .github/workflows/ci.yml | 9 +++++--- .github/workflows/industrial-ci.yml | 20 ++++++++++++------ .../dockerursim/build_and_run_docker_ursim.sh | 14 ++++++------ .../programs/cb3/default.installation | Bin 0 -> 1902 bytes .../programs/cb3/default.variables | 2 ++ .../dockerursim/programs/cb3/programs.UR5 | 1 + .../e-series}/default.installation | Bin .../e-series}/default.variables | 0 .../{.vol => programs/e-series}/programs.UR5 | 0 9 files changed, 28 insertions(+), 18 deletions(-) create mode 100644 tests/resources/dockerursim/programs/cb3/default.installation create mode 100644 tests/resources/dockerursim/programs/cb3/default.variables create mode 120000 tests/resources/dockerursim/programs/cb3/programs.UR5 rename tests/resources/dockerursim/{.vol => programs/e-series}/default.installation (100%) rename tests/resources/dockerursim/{.vol => programs/e-series}/default.variables (100%) rename tests/resources/dockerursim/{.vol => programs/e-series}/programs.UR5 (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 138d3c727..e3fdf6ed7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ on: [push, pull_request] jobs: build: + timeout-minutes: 30 runs-on: ubuntu-latest strategy: matrix: @@ -10,17 +11,19 @@ jobs: - DOCKER_RUN_OPTS: --network static_test_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 + URSIM_DOCKER_IMAGE: 'universalrobots/ursim_cb3:3.12.1' + PROGRAM_FOLDER: 'programs/cb3' - DOCKER_RUN_OPTS: --network static_test_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 + URSIM_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' + PROGRAM_FOLDER: 'programs/e-series' steps: - uses: actions/checkout@v1 - name: start ursim run: | - tests/resources/dockerursim/build_and_run_docker_ursim.sh $URSIM_VERSION + tests/resources/dockerursim/build_and_run_docker_ursim.sh $URSIM_DOCKER_IMAGE $PROGRAM_FOLDER env: ${{matrix.env}} - name: install gtest run: sudo apt-get install -y libgtest-dev diff --git a/.github/workflows/industrial-ci.yml b/.github/workflows/industrial-ci.yml index 98d83ada6..a5ffaaf1f 100644 --- a/.github/workflows/industrial-ci.yml +++ b/.github/workflows/industrial-ci.yml @@ -25,7 +25,8 @@ jobs: 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 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' + PROGRAM_FOLDER: 'programs/e-series' - ROS_DISTRO: noetic ROS_REPO: main IMMEDIATE_TEST_OUTPUT: true @@ -33,14 +34,16 @@ jobs: BUILDER: catkin_tools DOCKER_RUN_OPTS: --network static_test_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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' + PROGRAM_FOLDER: '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 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' + PROGRAM_FOLDER: 'programs/e-series' NOT_TEST_DOWNSTREAM: true - ROS_DISTRO: galactic ROS_REPO: main @@ -48,7 +51,8 @@ jobs: DOWNSTREAM_WORKSPACE: "github:UniversalRobots/Universal_Robots_ROS2_Driver#galactic" DOCKER_RUN_OPTS: --network static_test_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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' + PROGRAM_FOLDER: 'programs/e-series' NOT_TEST_DOWNSTREAM: true - ROS_DISTRO: humble ROS_REPO: main @@ -56,7 +60,8 @@ jobs: 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 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' + PROGRAM_FOLDER: 'programs/e-series' NOT_TEST_DOWNSTREAM: true - ROS_DISTRO: rolling ROS_REPO: main @@ -64,14 +69,15 @@ jobs: 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 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' + PROGRAM_FOLDER: '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 + tests/resources/dockerursim/build_and_run_docker_ursim.sh $URSIM_DOCKER_IMAGE $PROGRAM_FOLDER env: ${{matrix.env}} - uses: 'ros-industrial/industrial_ci@master' env: ${{matrix.env}} diff --git a/tests/resources/dockerursim/build_and_run_docker_ursim.sh b/tests/resources/dockerursim/build_and_run_docker_ursim.sh index 070227a92..1ef6832ee 100755 --- a/tests/resources/dockerursim/build_and_run_docker_ursim.sh +++ b/tests/resources/dockerursim/build_and_run_docker_ursim.sh @@ -1,21 +1,19 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -URSIM_VERSION="${1:-5.8.0.10253}" +URSIM_DOCKER_IMAGE=${1} +PROGRAM_FOLDER=${2} 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 \ +docker run --rm -it -d \ + --name ursim \ --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 \ + -v "${DIR}/${PROGRAM_FOLDER}":/ursim/programs \ --privileged \ --cpus=1 \ - mydockerursim + ${URSIM_DOCKER_IMAGE} diff --git a/tests/resources/dockerursim/programs/cb3/default.installation b/tests/resources/dockerursim/programs/cb3/default.installation new file mode 100644 index 0000000000000000000000000000000000000000..6cce84c739f8a2f33ad947b05cc19172b2507017 GIT binary patch literal 1902 zcmV-!2a)(6iwFP!000000PR}qkK#BI|IS|#Fs zt&p=dC%(|N74_hW_%PI$up*Hsbxmy8>nsFV&OEvVJ`Sc)Fei(lz9OCg{g;}i4MGkA z_!ee#9T4wIP7;MP$wF;Qya@G49zk9E(O10{;}akgXb#0J_!>6bRW8+ZXEA78%Eu|GnsYm1Mjl-NfzhI zM=ucOWjv$R6O#o{^9=+ukaLESeo25Vo`+=78HZ{f_7iB-wLkgZYbOWyoRVN&F}o$! z#4T>oW;Dp-C`V?j^2#mXeHuUA)wTCtPloPEt*4{qe**-AVG)Th0hJ;n{R#na;!PqR zG6C0!6i6_IH5C&0xj_B9z4Uik?d(eRcYB>M+bEEia#Y+0`t=!o0+^ z{$UvSPC+~nA!lR@%TbWQ;IIM?fs2yOW-Fzp?7sr@?}5k&6T>w6F(r(`l!j@B#54%_ z6I(*ku96QN5GT(RUNaIBUm5q7b4~)WV%*nUj|ef;e=AV+13yZ{ZIV3_E;huAi)UBg zL1fi3jf!W&smgHnqy-Uh6|5D-XrfUh^X6->IsXn^iHgen=$Q z10&1K3JLQ%qRo(bJK?ernnDe#DLFuZBN5Ftj-q*Tq@{4m4>{HDqIkrcTeMq~vgT+! z1HPp$jQr3mN#lx<3Mot9F#mvDA|csppP47g8U=p8n~pQ~o5^8}xEF6>f?M@aN4g%1 zPRP!S$F1Tbt8Z)x^^{L2Ua_BL>4vd$IHuvars*_HX8{ezS-1`2_DI9(_m(|$U1D~+ zx)#K%S=t9rup@br?qCFJuLBcOdE?56FQ?Y`A%+wESz+qdny_Se%82n}K&XuUXY6qZiYvW=eAcC5bH?YDcKy7rfn z4(7MAI9zR3Pk+XAO68at07 z>=kP_8*smg$d6e>VobYf)m-kzOJlERwmTiiLWxHz+M zZfh}Xx<)6Hf7okYlmba`bDgs>r)wIdO5OrvR)aAw0>-KaV_gJ{T@A**2pFdtjB^n% z?P@UXi-74=gXvrZOt%_L_eX(I$7pQFPO^2%4Rg=W@vk|G+D)Y$*JbKQ{=C3_3tLKL zwld<5XgKY@(=m;H*X|s_&ybA9g{4x93(dHdCy`j@Q|CMay;C!jt2G z`iVn9qD;^D1tM5Sm{BQBm_&ixiy5aHujAEQZZQAJc9^>W&*^vqFZ6kuBkLWLsbtK= zJ3?I5mdU9k&BsPpwaoG;3o6dAo!3@0$a{G+y{G0kNz!nF#ZE}@@A8wG7E80)ImNJ_ zZnj|Zk!{hiMaiRvg2WS(N>^AdcGOzoTqUt`LnaY0IXxv*)wVa?pjFQ7(mbBiMRE#C zv&1jc8xkH;(>)8GU)U>V;3Lda)BHN(E(x113%0OhTyo-mL)A&0<0hrf57gYxy7C>* zsZ=$RGdj0;&QW!`F9B7DC0wAU%CsU&hqL~)eTTWK&5}r>eS_UlkL!NT^th#G#dEt* zx5{y=+TanlY2^zvIRkybXdpMh1CTUK%ypMOk0u{8R9??H6J>q0u#?sYu!fwiD9R;B oPc?l2^g-$z Date: Sat, 29 Oct 2022 10:05:06 +0200 Subject: [PATCH 03/18] Adding function to interact with the robot through the dashboard Adding short function to make it easier to: - Power off - Power on - Brake release - Load a program - Play - Pause - Stop - Close popup - Close safety popup - Restart safety - Unlock protective stop - Shutdown --- examples/dashboard_example.cpp | 127 +++++++++++++++++ .../ur_client_library/ur/dashboard_client.h | 131 +++++++++++++++++- src/ur/dashboard_client.cpp | 126 +++++++++++++++++ .../dockerursim/programs/cb3/wait_program.urp | Bin 0 -> 433 bytes .../programs/e-series/wait_program.urp | Bin 0 -> 422 bytes 5 files changed, 379 insertions(+), 5 deletions(-) create mode 100644 examples/dashboard_example.cpp create mode 100644 tests/resources/dockerursim/programs/cb3/wait_program.urp create mode 100644 tests/resources/dockerursim/programs/e-series/wait_program.urp diff --git a/examples/dashboard_example.cpp b/examples/dashboard_example.cpp new file mode 100644 index 000000000..d50a46b59 --- /dev/null +++ b/examples/dashboard_example.cpp @@ -0,0 +1,127 @@ +// 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; + +// 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 = "127.0.0.1"; + +std::unique_ptr my_dashboard; + +// We need a callback function to register. See UrDriver's parameters for details. +void handleRobotProgramState(bool program_running) +{ + // Print the text in green so we see it better + std::cout << "\033[1;32mProgram running: " << std::boolalpha << program_running << "\033[0m\n" << std::endl; +} + +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 + 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; + } + + // Power it on + if (!my_dashboard->commandPowerOn()) + { + URCL_LOG_ERROR("Could not send Power on command"); + return 1; + } + + // Release the brakes + if (!my_dashboard->commandBreakeRelease()) + { + URCL_LOG_ERROR("Could not send BreakeRelease 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/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index bc778e2f1..3e5f473d4 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -78,6 +78,131 @@ class DashboardClient : public comm::TCPSocket */ 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. It is important, that the command sent is finished with a + * '\n' (newline) so it will be processed by the server. + * \param expected Expected replay + * + * \return True if the reply to the command is as expected + */ + bool sendRequest(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 time in seconds + * + * \return True if the reply was as expected within the timeout time + */ + bool waitForReply(const std::string& command, const std::string& expected, double timeout = 30.0); + + /*! + * \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 time in seconds + * + * \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, unsigned int timeout); + + /*! + * \brief Send Power off command + * + * \return True succeeded + */ + bool commandPowerOff(); + + /*! + * \brief Send Power on command + * + * \param timeout Timeout in seconds + * + * \return True succeeded + */ + bool commandPowerOn(unsigned int timeout = 1200); + + /*! + * \brief Send Brake release command + * + * \return True succeeded + */ + bool commandBreakeRelease(); + + /*! + * \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 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(); + protected: virtual bool open(int socket_fd, struct sockaddr* address, size_t address_len) { @@ -87,11 +212,7 @@ class DashboardClient : public comm::TCPSocket private: bool send(const std::string& text); std::string read(); - - void rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") - { - str.erase(str.find_last_not_of(chars) + 1); - } + void rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r "); std::string host_; int port_; diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index d60627e7d..457cc3be8 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -27,6 +27,7 @@ //---------------------------------------------------------------------- #include +#include #include #include #include @@ -37,6 +38,11 @@ DashboardClient::DashboardClient(const std::string& host) : host_(host), port_(D { } +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) @@ -113,4 +119,124 @@ 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 + "\n"); + bool ret = std::regex_match(response, std::regex(expected)); + if (!ret) + { + URCL_LOG_WARN("Expected: \"%s\", but received: \"%s\"", expected.c_str(), response.c_str()); + } + return ret; +} + +bool DashboardClient::waitForReply(const std::string& command, const std::string& expected, double timeout) +{ + const unsigned int TIME_STEP_SIZE_US(100000); // 100ms + + double count = 0; + std::string response; + + while (count < timeout) + { + // Send the request + response = sendAndReceive(command + "\n"); + + // Check it the response was as expected + if (std::regex_match(response, std::regex(expected))) + { + return true; + } + + // wait 100ms before trying again + usleep(TIME_STEP_SIZE_US); + count = count + (0.000001 * TIME_STEP_SIZE_US); + } + + URCL_LOG_WARN("Did not got the expected \"%s\" respone within the timeout. Last respone was: \"%s\"", + expected.c_str(), response.c_str()); + return false; +} + +bool DashboardClient::retryCommand(const std::string& requestCommand, const std::string& requestExpectedResponse, + const std::string& waitRequest, const std::string& waitExpectedResponse, + unsigned int timeout) +{ + const double RETRY_EVERY_SECOND(1.0); + unsigned int count(0); + do + { + sendRequest(requestCommand, requestExpectedResponse); + count++; + + if (waitForReply(waitRequest, waitExpectedResponse, RETRY_EVERY_SECOND)) + { + return true; + } + } while (count < timeout); + return false; +} + +bool DashboardClient::commandPowerOff() +{ + return sendRequest("power off", "Powering off") && waitForReply("robotmode", "Robotmode: POWER_OFF"); +} + +bool DashboardClient::commandPowerOn(unsigned int timeout) +{ + return retryCommand("power on", "Powering on", "robotmode", "Robotmode: IDLE", timeout); +} + +bool DashboardClient::commandBreakeRelease() +{ + return sendRequest("brake release", "Brake releasing") && waitForReply("robotmode", "Robotmode: RUNNING"); +} + +bool DashboardClient::commandLoadProgram(const std::string& program_file_name) +{ + return sendRequest("load " + program_file_name + "", "(?:Loading program: ).*(?:" + program_file_name + ").*") && + waitForReply("programState", "STOPPED " + program_file_name); +} + +bool DashboardClient::commandPlay() +{ + return sendRequest("play", "Starting program") && waitForReply("programState", "(?:PLAYING ).*"); +} + +bool DashboardClient::commandPause() +{ + return sendRequest("pause", "Pausing program") && waitForReply("programState", "(?:PAUSED ).*"); +} + +bool DashboardClient::commandStop() +{ + return sendRequest("stop", "Stopped") && waitForReply("programState", "(?:STOPPED ).*"); +} + +bool DashboardClient::commandClosePopup() +{ + return sendRequest("close popup", "closing popup"); +} + +bool DashboardClient::commandCloseSafetyPopup() +{ + return sendRequest("close safety popup", "closing safety popup"); +} + +bool DashboardClient::commandRestartSafety() +{ + return sendRequest("restart safety", "Restarting safety") && waitForReply("robotmode", "Robotmode: POWER_OFF"); +} + +bool DashboardClient::commandUnlockProtectiveStop() +{ + return sendRequest("unlock protective stop", "Protective stop releasing"); +} + +bool DashboardClient::commandShutdown() +{ + return sendRequest("shutdown", "Shutting down"); +} + } // namespace urcl 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 0000000000000000000000000000000000000000..8f842454bb9ecb1d67701579fa86e1be35c98e53 GIT binary patch literal 433 zcmV;i0Z#rOiwFP!000000CiJQZ<{a>e&4UK{Jek-fnW(DRc#-TwxnvZPTEW5z_qwF zCbCVY{QDV_K*%;EuWLE_%|2!b$@bUB$TmSN^V7?GF-wN zZ>62OpP^e1@WgF(1u2Dw!Z!MtVZYTz)c#r2U{q+}7Rua7h8~T)#G@n*Lxz#$#%}mw zhy@P~>NfkPkfX`p92ehJd@Z3UKhur34f z$XE2A5BATrDSAe{c@Xt$$bxS7Z&rE5N*ImU6iA8l^%k4Qr%e)sV@|oRhP>2^7G%NV zFj!oROx&|u&>;2E2xTuPa*>?Tj&l2+IH^A#bZ-}xkfjE77W~eIx{R7`)mq8pS`~0P zY?LcuU%0#8odmOLN|;{5UxK~sr<+z}k9!B+NPs;aV=mgw@>bNabdAx}AIy>OcLGBK b|E6UBs^(LRBOvvMKWF&|GquS5MFRi;#%$J| literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..df9b12951a10319965623831f3bbc9429ca2f994 GIT binary patch literal 422 zcmV;X0a^YZiwFP!000000CiK}Z<{a>e&1hV`FQ~h0fYpRrmYW1T~ak^EA6Gaz;$>v zCbCVY{P(j-0!g|e!RgN5_uUU4`P1Wr>V7L|iG&sl`WLu)-5o=eaH$Ol0fu+7pf$ch zFU)oL5uM%R1DNIlQgels>C~a1ey=od{gbGsq*4e5>(WZb9t*v>$5_CUgi#_un>FnJ zf2g``XV$1Vh-KeyQ57@{SQf{bdz^0d45GJPM9Dmff;0#-y7)p!{>~+~w#-TsTe<0V zL6@t|Yq=~p<+uCt*WC|F_8@ruy}{~T_iaJnKxiD;mS3YVuxZeMgSiZW7knk3e5ik- zU884#HxJ{%Mp+nsMye+gOC0_Z#-b=1oGeREHks0iT{EU+UYrJLmPBECDRC8^-GWA` zKOL9mJdCsCj5*5fd*tN)G~>aq8ZK&u@+9~ZxIAx~>g7s`!%9|oo--*WH_u?Nb}LXX zyBe==;Dg{0da=t&%wcE2Yk|0<)0sVWtGwYYE^K2g_eXc)`%Yj?;NO(&U(|eTaRTK2 Q_~R`80d~$&Nkjtx0GY Date: Sat, 29 Oct 2022 10:09:44 +0200 Subject: [PATCH 04/18] Add CI step for running the examples The default behavior is to find a run all the examples For this it was needed to change the examples to not run forever --- .github/workflows/ci.yml | 3 +- examples/CMakeLists.txt | 5 +++ examples/full_driver.cpp | 78 ++++++++++++++++++++++++++++++++--- examples/primary_pipeline.cpp | 27 +++++++++--- examples/rtde_client.cpp | 28 +++++++++++-- 5 files changed, 126 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3fdf6ed7..23b700334 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,6 +39,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 @@ -69,4 +71,3 @@ jobs: steps: - uses: actions/checkout@v1 - uses: ./.github/actions/rosdoc_lite_action - diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 0de5686e1..3028c4868 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -29,3 +29,8 @@ 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/full_driver.cpp b/examples/full_driver.cpp index 45213d4c2..0f3175d1c 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 = "127.0.0.1"; 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 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 + my_dashboard.reset(new DashboardClient(robot_ip)); + if (!my_dashboard->connect()) + { + URCL_LOG_ERROR("Could not connect to dashboard"); + return 1; + } + + // Stop program, if there is one running + if (!my_dashboard->commandStop()) + { + URCL_LOG_ERROR("Could not send stop program command"); + return 1; + } + + // Power it on + if (!my_dashboard->commandPowerOff()) + { + URCL_LOG_ERROR("Could not send Power off command"); + return 1; + } + + // Power it on + if (!my_dashboard->commandPowerOn()) + { + URCL_LOG_ERROR("Could not send Power on command"); + return 1; + } + + // Release the brakes + if (!my_dashboard->commandBreakeRelease()) + { + URCL_LOG_ERROR("Could not send BreakeRelease 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..44942d965 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 = "127.0.0.1"; 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 may 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/rtde_client.cpp b/examples/rtde_client.cpp index ca503a429..254829d44 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 = "127.0.0.1"; 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; } From b7117ed79f2f9df522e0930425562aedc51b45c5 Mon Sep 17 00:00:00 2001 From: urrsk <41109954+urrsk@users.noreply.github.com> Date: Sat, 29 Oct 2022 10:42:56 +0200 Subject: [PATCH 05/18] Remove Dockerfile The dockerfile is no longer needed as the repo now uses the docker image --- .github/workflows/ci.yml | 19 +- .github/workflows/industrial-ci.yml | 44 +++-- scripts/start_ursim.sh | 172 ++++++++++++++++++ tests/resources/dockerursim/Dockerfile | 128 ------------- .../dockerursim/build_and_run_docker_ursim.sh | 19 -- tests/resources/dockerursim/safety.conf.UR5 | 101 ---------- tests/resources/dockerursim/ursim/run | 16 -- 7 files changed, 208 insertions(+), 291 deletions(-) create mode 100755 scripts/start_ursim.sh delete mode 100644 tests/resources/dockerursim/Dockerfile delete mode 100755 tests/resources/dockerursim/build_and_run_docker_ursim.sh delete mode 100644 tests/resources/dockerursim/safety.conf.UR5 delete mode 100644 tests/resources/dockerursim/ursim/run diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23b700334..b70a4b73b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,22 +8,24 @@ jobs: 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_DOCKER_IMAGE: 'universalrobots/ursim_cb3:3.12.1' - PROGRAM_FOLDER: 'programs/cb3' - - 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' - PROGRAM_FOLDER: 'programs/e-series' + 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_DOCKER_IMAGE $PROGRAM_FOLDER + 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 @@ -64,7 +66,8 @@ 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 diff --git a/.github/workflows/industrial-ci.yml b/.github/workflows/industrial-ci.yml index a5ffaaf1f..7c0105b1f 100644 --- a/.github/workflows/industrial-ci.yml +++ b/.github/workflows/industrial-ci.yml @@ -23,61 +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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' - PROGRAM_FOLDER: 'programs/e-series' + 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' - PROGRAM_FOLDER: 'programs/e-series' + 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' - PROGRAM_FOLDER: 'programs/e-series' + 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' - PROGRAM_FOLDER: 'programs/e-series' + 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' - PROGRAM_FOLDER: 'programs/e-series' + 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_DOCKER_IMAGE: 'universalrobots/ursim_e-series:5.5.1' - PROGRAM_FOLDER: 'programs/e-series' + 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_DOCKER_IMAGE $PROGRAM_FOLDER + 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/scripts/start_ursim.sh b/scripts/start_ursim.sh new file mode 100755 index 000000000..0fdfa372e --- /dev/null +++ b/scripts/start_ursim.sh @@ -0,0 +1,172 @@ +#!/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}" \ + --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/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 1ef6832ee..000000000 --- a/tests/resources/dockerursim/build_and_run_docker_ursim.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -URSIM_DOCKER_IMAGE=${1} -PROGRAM_FOLDER=${2} - -docker network create --subnet=192.168.56.0/24 static_test_net - -docker run --rm -it -d \ - --name ursim \ - --net static_test_net \ - --ip 192.168.56.101 \ - -p 8080:8080 \ - -p 29999:29999 \ - -p 30001-30004:30001-30004 \ - -v "${DIR}/${PROGRAM_FOLDER}":/ursim/programs \ - --privileged \ - --cpus=1 \ - ${URSIM_DOCKER_IMAGE} 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 From d2d083c27cf18fa28a17e5a4e74a3289eededb95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rune=20S=C3=B8e-Knudsen?= <41109954+urrsk@users.noreply.github.com> Date: Mon, 31 Oct 2022 13:59:11 +0100 Subject: [PATCH 06/18] Fix spell error in Brake Release dashboard command Co-authored-by: Mads Holm Peters <79145214+urmahp@users.noreply.github.com> --- examples/dashboard_example.cpp | 4 ++-- examples/full_driver.cpp | 4 ++-- include/ur_client_library/ur/dashboard_client.h | 2 +- src/ur/dashboard_client.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/dashboard_example.cpp b/examples/dashboard_example.cpp index d50a46b59..e63ecdf95 100644 --- a/examples/dashboard_example.cpp +++ b/examples/dashboard_example.cpp @@ -72,9 +72,9 @@ int main(int argc, char* argv[]) } // Release the brakes - if (!my_dashboard->commandBreakeRelease()) + if (!my_dashboard->commandBrakeRelease()) { - URCL_LOG_ERROR("Could not send BreakeRelease command"); + URCL_LOG_ERROR("Could not send BrakeRelease command"); return 1; } diff --git a/examples/full_driver.cpp b/examples/full_driver.cpp index 0f3175d1c..b499bee65 100644 --- a/examples/full_driver.cpp +++ b/examples/full_driver.cpp @@ -98,9 +98,9 @@ int main(int argc, char* argv[]) } // Release the brakes - if (!my_dashboard->commandBreakeRelease()) + if (!my_dashboard->commandBrakeRelease()) { - URCL_LOG_ERROR("Could not send BreakeRelease command"); + URCL_LOG_ERROR("Could not send BrakeRelease command"); return 1; } diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 3e5f473d4..bd4a6d491 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -136,7 +136,7 @@ class DashboardClient : public comm::TCPSocket * * \return True succeeded */ - bool commandBreakeRelease(); + bool commandBrakeRelease(); /*! * \brief Send Load program command diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index 457cc3be8..19b2e0356 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -188,7 +188,7 @@ bool DashboardClient::commandPowerOn(unsigned int timeout) return retryCommand("power on", "Powering on", "robotmode", "Robotmode: IDLE", timeout); } -bool DashboardClient::commandBreakeRelease() +bool DashboardClient::commandBrakeRelease() { return sendRequest("brake release", "Brake releasing") && waitForReply("robotmode", "Robotmode: RUNNING"); } From f146074d6858a0f4485b8c9252260fd2ab338d79 Mon Sep 17 00:00:00 2001 From: "Felix Exner (fexner)" Date: Tue, 22 Nov 2022 08:29:24 +0100 Subject: [PATCH 07/18] Minor code style fixes and cleanup --- examples/dashboard_example.cpp | 8 +------- examples/full_driver.cpp | 16 ++++++++-------- examples/primary_pipeline.cpp | 2 +- src/ur/dashboard_client.cpp | 2 +- 4 files changed, 11 insertions(+), 17 deletions(-) diff --git a/examples/dashboard_example.cpp b/examples/dashboard_example.cpp index e63ecdf95..666018bd3 100644 --- a/examples/dashboard_example.cpp +++ b/examples/dashboard_example.cpp @@ -29,14 +29,7 @@ using namespace urcl; // better configuration system such as Boost.Program_options const std::string DEFAULT_ROBOT_IP = "127.0.0.1"; -std::unique_ptr my_dashboard; - // We need a callback function to register. See UrDriver's parameters for details. -void handleRobotProgramState(bool program_running) -{ - // Print the text in green so we see it better - std::cout << "\033[1;32mProgram running: " << std::boolalpha << program_running << "\033[0m\n" << std::endl; -} int main(int argc, char* argv[]) { @@ -51,6 +44,7 @@ int main(int argc, char* argv[]) // 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()) { diff --git a/examples/full_driver.cpp b/examples/full_driver.cpp index b499bee65..762f7d32d 100644 --- a/examples/full_driver.cpp +++ b/examples/full_driver.cpp @@ -46,7 +46,7 @@ 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 my_dashboard; +std::unique_ptr g_my_dashboard; vector6d_t g_joint_positions; // We need a callback function to register. See UrDriver's parameters for details. @@ -69,36 +69,36 @@ int main(int argc, char* argv[]) // Making the robot ready for the program by: // Connect the the robot Dashboard - my_dashboard.reset(new DashboardClient(robot_ip)); - if (!my_dashboard->connect()) + 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 (!my_dashboard->commandStop()) + if (!g_my_dashboard->commandStop()) { URCL_LOG_ERROR("Could not send stop program command"); return 1; } - // Power it on - if (!my_dashboard->commandPowerOff()) + // Power it off + if (!g_my_dashboard->commandPowerOff()) { URCL_LOG_ERROR("Could not send Power off command"); return 1; } // Power it on - if (!my_dashboard->commandPowerOn()) + if (!g_my_dashboard->commandPowerOn()) { URCL_LOG_ERROR("Could not send Power on command"); return 1; } // Release the brakes - if (!my_dashboard->commandBrakeRelease()) + if (!g_my_dashboard->commandBrakeRelease()) { URCL_LOG_ERROR("Could not send BrakeRelease command"); return 1; diff --git a/examples/primary_pipeline.cpp b/examples/primary_pipeline.cpp index 44942d965..0da706e71 100644 --- a/examples/primary_pipeline.cpp +++ b/examples/primary_pipeline.cpp @@ -48,7 +48,7 @@ int main(int argc, char* argv[]) robot_ip = std::string(argv[1]); } - // Parse how may seconds to run + // Parse how many seconds to run int second_to_run = -1; if (argc > 2) { diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index 19b2e0356..9524cd341 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -143,7 +143,7 @@ bool DashboardClient::waitForReply(const std::string& command, const std::string // Send the request response = sendAndReceive(command + "\n"); - // Check it the response was as expected + // Check if the response was as expected if (std::regex_match(response, std::regex(expected))) { return true; From 607eeebf8ff44c4a1b17c5e7698e76b0e5bf486f Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 22 Nov 2022 08:55:01 +0100 Subject: [PATCH 08/18] Modernize timeout interface This should be more clear to API users what the timeouts actually mean and this should be more platform independent to use std::this_thread::sleep_for instead of usleep. --- .../ur_client_library/ur/dashboard_client.h | 19 +++++++----- src/ur/dashboard_client.cpp | 30 +++++++++++-------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index bd4a6d491..a025af059 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -94,26 +94,30 @@ class DashboardClient : public comm::TCPSocket * * \param command Command that will be sent to the server * \param expected Expected replay - * \param timeout Timeout time in seconds + * \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, double timeout = 30.0); + 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 + * \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 time in seconds + * \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, unsigned int timeout); + 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 @@ -125,11 +129,12 @@ class DashboardClient : public comm::TCPSocket /*! * \brief Send Power on command * - * \param timeout Timeout in seconds + * \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(unsigned int timeout = 1200); + bool commandPowerOn(const std::chrono::duration timeout = std::chrono::seconds(300)); /*! * \brief Send Brake release command diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index 9524cd341..65866c68b 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -27,11 +27,14 @@ //---------------------------------------------------------------------- #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) @@ -131,14 +134,15 @@ bool DashboardClient::sendRequest(const std::string& command, const std::string& return ret; } -bool DashboardClient::waitForReply(const std::string& command, const std::string& expected, double timeout) +bool DashboardClient::waitForReply(const std::string& command, const std::string& expected, + const std::chrono::duration timeout) { - const unsigned int TIME_STEP_SIZE_US(100000); // 100ms + const std::chrono::duration wait_period = 100ms; - double count = 0; + std::chrono::duration time_done(0); std::string response; - while (count < timeout) + while (time_done < timeout) { // Send the request response = sendAndReceive(command + "\n"); @@ -150,8 +154,8 @@ bool DashboardClient::waitForReply(const std::string& command, const std::string } // wait 100ms before trying again - usleep(TIME_STEP_SIZE_US); - count = count + (0.000001 * TIME_STEP_SIZE_US); + std::this_thread::sleep_for(wait_period); + time_done += wait_period; } URCL_LOG_WARN("Did not got the expected \"%s\" respone within the timeout. Last respone was: \"%s\"", @@ -161,20 +165,20 @@ bool DashboardClient::waitForReply(const std::string& command, const std::string bool DashboardClient::retryCommand(const std::string& requestCommand, const std::string& requestExpectedResponse, const std::string& waitRequest, const std::string& waitExpectedResponse, - unsigned int timeout) + const std::chrono::duration timeout, + const std::chrono::duration retry_period) { - const double RETRY_EVERY_SECOND(1.0); - unsigned int count(0); + std::chrono::duration time_done(0); do { sendRequest(requestCommand, requestExpectedResponse); - count++; + time_done += retry_period; - if (waitForReply(waitRequest, waitExpectedResponse, RETRY_EVERY_SECOND)) + if (waitForReply(waitRequest, waitExpectedResponse, retry_period)) { return true; } - } while (count < timeout); + } while (time_done < timeout); return false; } @@ -183,7 +187,7 @@ bool DashboardClient::commandPowerOff() return sendRequest("power off", "Powering off") && waitForReply("robotmode", "Robotmode: POWER_OFF"); } -bool DashboardClient::commandPowerOn(unsigned int timeout) +bool DashboardClient::commandPowerOn(const std::chrono::duration timeout) { return retryCommand("power on", "Powering on", "robotmode", "Robotmode: IDLE", timeout); } From 8cf6f5c82c667b2f636d9dc009f631ac61d742da Mon Sep 17 00:00:00 2001 From: urmarp Date: Mon, 7 Nov 2022 16:06:50 +0100 Subject: [PATCH 09/18] Added commands, version check and now throws exception Added tests for dashboard client --- examples/dashboard_example.cpp | 5 +- .../ur_client_library/ur/dashboard_client.h | 221 +++++++++++++- src/ur/dashboard_client.cpp | 270 +++++++++++++++++- tests/CMakeLists.txt | 8 + tests/test_dashboard_client.cpp | 174 +++++++++++ 5 files changed, 673 insertions(+), 5 deletions(-) create mode 100644 tests/test_dashboard_client.cpp diff --git a/examples/dashboard_example.cpp b/examples/dashboard_example.cpp index 666018bd3..77c874a41 100644 --- a/examples/dashboard_example.cpp +++ b/examples/dashboard_example.cpp @@ -22,12 +22,13 @@ #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 = "127.0.0.1"; +const std::string DEFAULT_ROBOT_IP = "10.53.253.22"; // We need a callback function to register. See UrDriver's parameters for details. @@ -58,6 +59,8 @@ int main(int argc, char* argv[]) return 1; } + my_dashboard->commandCloseSafetyPopup(); + // Power it on if (!my_dashboard->commandPowerOn()) { diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index a025af059..61e2f4ad5 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -57,7 +57,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. */ @@ -89,6 +89,17 @@ class DashboardClient : public comm::TCPSocket */ 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. It is important, that the command sent is finished with a + * '\n' (newline) so it will be processed by the server. + * \param expected Expected replay + * + * \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 * @@ -152,6 +163,15 @@ class DashboardClient : public comm::TCPSocket */ 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 * @@ -208,6 +228,186 @@ class DashboardClient : public comm::TCPSocket */ 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 command + * + * \return True succeeded + */ + bool commandIsProgramSaved(); + + /*! + * \brief Send Is in remote control command + * + * \return True succeeded + */ + 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) + * + * \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 + * + * \return True succeeded + */ + bool commandSetOperationalMode(const std::string& operational_mode); + + /*! + * \brief Send Clear operational mode command + * + * \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 + * + * \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 + * + * \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) { @@ -215,10 +415,29 @@ class DashboardClient : public comm::TCPSocket } private: + /*! + * \brief Parse input for comparison of sw versions + * + * \param result Parsed result + * \param input Input sw version string + */ + void parseSWVersion(int result[4], const std::string& input); + /*! + * \brief Compare two sw versions + * + * \param a Sw version string for e-series + * \param b Sw version string for cb3 + * \param c Sw version string read from robot + * + * \return If a/b is less than c return true else return false + */ + bool lessThanVersion(const std::string& a, const std::string& b, const std::string& c); bool send(const std::string& text); std::string read(); void rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r "); + bool e_series_; // Is the robot e-series or cb3 + std::string polyscope_version_; std::string host_; int port_; std::mutex write_mutex_; diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index 65866c68b..fb3ea0069 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -26,6 +26,7 @@ */ //---------------------------------------------------------------------- +#include #include #include #include @@ -65,6 +66,9 @@ bool DashboardClient::connect() tv.tv_usec = 0; TCPSocket::setReceiveTimeout(tv); + std::string pv; + commandPolyscopeVersion(pv); + return ret_val; } @@ -129,11 +133,23 @@ bool DashboardClient::sendRequest(const std::string& command, const std::string& bool ret = std::regex_match(response, std::regex(expected)); if (!ret) { - URCL_LOG_WARN("Expected: \"%s\", but received: \"%s\"", expected.c_str(), response.c_str()); + 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 + "\n"); + 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) { @@ -158,8 +174,8 @@ bool DashboardClient::waitForReply(const std::string& command, const std::string time_done += wait_period; } - URCL_LOG_WARN("Did not got the expected \"%s\" respone within the timeout. Last respone was: \"%s\"", - expected.c_str(), response.c_str()); + 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; } @@ -182,65 +198,313 @@ bool DashboardClient::retryCommand(const std::string& requestCommand, const std: return false; } +void DashboardClient::parseSWVersion(int result[4], const std::string& input) +{ + std::istringstream parser(input); + parser >> result[0]; + for (int idx = 1; idx < 4; idx++) + { + parser.get(); // Skip period + parser >> result[idx]; + } +} + +bool DashboardClient::lessThanVersion(const std::string& a, const std::string& b, const std::string& c) +{ + std::string ref = e_series_ == true ? a : b; + int parsedRef[4], parsedC[4]; + parseSWVersion(parsedRef, ref); + parseSWVersion(parsedC, c); + bool ret = std::lexicographical_compare(parsedRef, parsedRef + 4, parsedC, parsedC + 4); + if (!ret) + { + throw UrException("Polyscope software version required is: " + ref + ", but actual version is: " + c); + } + return ret; +} + bool DashboardClient::commandPowerOff() { + if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) + return false; return sendRequest("power off", "Powering off") && waitForReply("robotmode", "Robotmode: POWER_OFF"); } bool DashboardClient::commandPowerOn(const std::chrono::duration timeout) { + if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) + return false; return retryCommand("power on", "Powering on", "robotmode", "Robotmode: IDLE", timeout); } bool DashboardClient::commandBrakeRelease() { + if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) + return false; return sendRequest("brake release", "Brake releasing") && waitForReply("robotmode", "Robotmode: RUNNING"); } bool DashboardClient::commandLoadProgram(const std::string& program_file_name) { + if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) + return false; 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) +{ + if (!lessThanVersion("5.0.0", "3.2", polyscope_version_)) + return false; + return sendRequest("load installation " + installation_file_name, + "(?:Loading installation: ).*(?:" + installation_file_name + ").*"); +} + bool DashboardClient::commandPlay() { + if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) + return false; return sendRequest("play", "Starting program") && waitForReply("programState", "(?:PLAYING ).*"); } bool DashboardClient::commandPause() { + if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) + return false; return sendRequest("pause", "Pausing program") && waitForReply("programState", "(?:PAUSED ).*"); } bool DashboardClient::commandStop() { + if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) + return false; return sendRequest("stop", "Stopped") && waitForReply("programState", "(?:STOPPED ).*"); } bool DashboardClient::commandClosePopup() { + if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) + return false; return sendRequest("close popup", "closing popup"); } bool DashboardClient::commandCloseSafetyPopup() { + if (!lessThanVersion("5.0.0", "3.1", polyscope_version_)) + return false; return sendRequest("close safety popup", "closing safety popup"); } bool DashboardClient::commandRestartSafety() { + if (!lessThanVersion("5.1.0", "3.7", polyscope_version_)) + return false; return sendRequest("restart safety", "Restarting safety") && waitForReply("robotmode", "Robotmode: POWER_OFF"); } bool DashboardClient::commandUnlockProtectiveStop() { + if (!lessThanVersion("5.0.0", "3.1", polyscope_version_)) + return false; return sendRequest("unlock protective stop", "Protective stop releasing"); } bool DashboardClient::commandShutdown() { + if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) + return false; return sendRequest("shutdown", "Shutting down"); } +bool DashboardClient::commandQuit() +{ + if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) + return false; + return sendRequest("quit", "Disconnected"); +} + +bool DashboardClient::commandRunning() +{ + if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) + return false; + return sendRequest("running", "Program running: true"); +} + +bool DashboardClient::commandIsProgramSaved() +{ + if (!lessThanVersion("5.0.0", "1.8", polyscope_version_)) + return false; + return sendRequest("isProgramSaved", "(?:true ).*"); +} + +bool DashboardClient::commandIsInRemoteControl() +{ + if (!lessThanVersion("5.6.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on + // e-series + return false; + std::string response = sendAndReceive("is in remote control\n"); + bool ret = std::regex_match(response, std::regex("true")); + return ret; +} + +bool DashboardClient::commandPopup(const std::string& popup_text) +{ + if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) + return false; + return sendRequest("popup " + popup_text, "showing popup"); +} + +bool DashboardClient::commandAddToLog(const std::string& log_text) +{ + if (!lessThanVersion("5.0.0", "1.8", polyscope_version_)) + return false; + return sendRequest("addToLog " + log_text, "Added log message"); +} + +bool DashboardClient::commandPolyscopeVersion(std::string& polyscope_version) +{ + std::string expected = "(?:URSoftware ).*"; + polyscope_version = sendRequestString("PolyscopeVersion", expected); + polyscope_version_ = polyscope_version.substr(polyscope_version.find(" ") + 1, + polyscope_version.find(" (") - polyscope_version.find(" ") - 1); + e_series_ = stoi(polyscope_version_.substr(0, 1)) >= 5; + return std::regex_match(polyscope_version, std::regex(expected)); +} + +bool DashboardClient::commandGetRobotModel(std::string& robot_model) +{ + if (!lessThanVersion("5.6.0", "3.12", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.6.0", "3.12", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.4.0", "3.11", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.0.0", "1.8", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.6.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on + // e-series + return false; + 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) +{ + if (!lessThanVersion("5.0.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on + // e-series + return false; + return sendRequest("set operational mode " + operational_mode, + "(?:Operational mode ).*(?:" + operational_mode + ").*"); +} + +bool DashboardClient::commandClearOperationalMode() +{ + if (!lessThanVersion("5.0.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on + // e-series + return false; + return sendRequest("clear operational mode", "(?:No longer controlling the operational mode. ).*"); +} + +bool DashboardClient::commandSetUserRole(const std::string& user_role) +{ + if (!lessThanVersion("10. Only available on CB3 robot", "1.8", polyscope_version_)) // Only available on + // e-series + return false; + return sendRequest("setUserRole " + user_role, "(?:Setting user role: ).*"); +} + +bool DashboardClient::commandGetUserRole(std::string& user_role) +{ + if (!lessThanVersion("10. Only available on CB3 robot", "1.8", polyscope_version_)) // Only available on + // e-series + return false; + 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) +{ + if (!lessThanVersion("5.8.0", "3.13", polyscope_version_)) + return false; + 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) +{ + if (!lessThanVersion("5.8.0", "3.13", polyscope_version_)) + return false; + 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; +} + } // namespace urcl diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ed87d4882..2e3729e15 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}) diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp new file mode 100644 index 000000000..a20249133 --- /dev/null +++ b/tests/test_dashboard_client.cpp @@ -0,0 +1,174 @@ +// -- 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 +#define private public +#include + +using namespace urcl; + +// std::string ROBOT_IP = "192.168.56.101"; +std::string ROBOT_IP = "127.0.0.1"; + +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 + { + correct_polyscope_version = + dashboard_client_->lessThanVersion("5.6.0", "3.13", dashboard_client_->polyscope_version_); + } + catch (const std::exception& 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_->e_series_) + GTEST_SKIP(); + dashboard_client_->polyscope_version_ = "5.0.0"; + EXPECT_THROW(dashboard_client_->commandSafetyStatus(msg), UrException); + dashboard_client_->polyscope_version_ = "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_->e_series_) + GTEST_SKIP(); + dashboard_client_->polyscope_version_ = "1.6.0"; + EXPECT_THROW(dashboard_client_->commandIsProgramSaved(), UrException); + dashboard_client_->polyscope_version_ = "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(); +} \ No newline at end of file From 98ceaa0c90665713b8a0a2a2117f3a18538e84f7 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 22 Nov 2022 11:07:47 +0100 Subject: [PATCH 10/18] Added more capabilities to VersionInformation Allow parsing from a version string and added comparison operators. --- CMakeLists.txt | 1 + .../ur/version_information.h | 35 ++++- src/ur/version_information.cpp | 141 ++++++++++++++++++ tests/CMakeLists.txt | 6 + tests/test_version_information.cpp | 92 ++++++++++++ 5 files changed, 267 insertions(+), 8 deletions(-) create mode 100644 src/ur/version_information.cpp create mode 100644 tests/test_version_information.cpp 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/include/ur_client_library/ur/version_information.h b/include/ur_client_library/ur/version_information.h index 4746b26bf..2865ecbf0 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/src/ur/version_information.cpp b/src/ur/version_information.cpp new file mode 100644 index 000000000..db8408e88 --- /dev/null +++ b/src/ur/version_information.cpp @@ -0,0 +1,141 @@ +// 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() : major(0), minor(0), bugfix(0), 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 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 2e3729e15..ea8bb0119 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -147,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/test_version_information.cpp b/tests/test_version_information.cpp new file mode 100644 index 000000000..0c64c5119 --- /dev/null +++ b/tests/test_version_information.cpp @@ -0,0 +1,92 @@ +// 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(); +} From d230be36b263902b6743e2616c91e81125790c93 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 22 Nov 2022 12:11:01 +0100 Subject: [PATCH 11/18] Use VersionInformation for version checking inside dashboard calls --- .../ur_client_library/ur/dashboard_client.h | 23 +-- .../ur/version_information.h | 12 +- src/ur/dashboard_client.cpp | 169 +++++++----------- tests/test_version_information.cpp | 4 +- 4 files changed, 83 insertions(+), 125 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 61e2f4ad5..066502fb5 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 { @@ -416,28 +417,20 @@ class DashboardClient : public comm::TCPSocket private: /*! - * \brief Parse input for comparison of sw versions + * \brief Makes sure that the dashboard_server's version is above the required version * - * \param result Parsed result - * \param input Input sw version string - */ - void parseSWVersion(int result[4], const std::string& input); - /*! - * \brief Compare two sw versions - * - * \param a Sw version string for e-series - * \param b Sw version string for cb3 - * \param c Sw version string read from robot + * \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 * - * \return If a/b is less than c return true else return false + * \throws UrException if the robot's version isn't large enough */ - bool lessThanVersion(const std::string& a, const std::string& b, const std::string& c); + 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 "); - bool e_series_; // Is the robot e-series or cb3 - std::string polyscope_version_; + 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 2865ecbf0..55dc636dc 100644 --- a/include/ur_client_library/ur/version_information.h +++ b/include/ur_client_library/ur/version_information.h @@ -56,12 +56,12 @@ class VersionInformation 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 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) { diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index fb3ea0069..f48bd5f5e 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -198,166 +198,121 @@ bool DashboardClient::retryCommand(const std::string& requestCommand, const std: return false; } -void DashboardClient::parseSWVersion(int result[4], const std::string& input) -{ - std::istringstream parser(input); - parser >> result[0]; - for (int idx = 1; idx < 4; idx++) - { - parser.get(); // Skip period - parser >> result[idx]; - } -} - -bool DashboardClient::lessThanVersion(const std::string& a, const std::string& b, const std::string& c) -{ - std::string ref = e_series_ == true ? a : b; - int parsedRef[4], parsedC[4]; - parseSWVersion(parsedRef, ref); - parseSWVersion(parsedC, c); - bool ret = std::lexicographical_compare(parsedRef, parsedRef + 4, parsedC, parsedC + 4); - if (!ret) - { - throw UrException("Polyscope software version required is: " + ref + ", but actual version is: " + c); - } - return ret; -} - bool DashboardClient::commandPowerOff() { - if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) - return false; + 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) { - if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) - return false; + assertVersion("5.0.0", "3.0", "power on"); return retryCommand("power on", "Powering on", "robotmode", "Robotmode: IDLE", timeout); } bool DashboardClient::commandBrakeRelease() { - if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) - return false; + 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) { - if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) - return false; + 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) { - if (!lessThanVersion("5.0.0", "3.2", polyscope_version_)) - return false; + assertVersion("5.0.0", "3.2", "load installation"); return sendRequest("load installation " + installation_file_name, "(?:Loading installation: ).*(?:" + installation_file_name + ").*"); } bool DashboardClient::commandPlay() { - if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.4", "play"); return sendRequest("play", "Starting program") && waitForReply("programState", "(?:PLAYING ).*"); } bool DashboardClient::commandPause() { - if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.4", "pause"); return sendRequest("pause", "Pausing program") && waitForReply("programState", "(?:PAUSED ).*"); } bool DashboardClient::commandStop() { - if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.4", "stop"); return sendRequest("stop", "Stopped") && waitForReply("programState", "(?:STOPPED ).*"); } bool DashboardClient::commandClosePopup() { - if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.6", "close popup"); return sendRequest("close popup", "closing popup"); } bool DashboardClient::commandCloseSafetyPopup() { - if (!lessThanVersion("5.0.0", "3.1", polyscope_version_)) - return false; + assertVersion("5.0.0", "3.1", "close safety popup"); return sendRequest("close safety popup", "closing safety popup"); } bool DashboardClient::commandRestartSafety() { - if (!lessThanVersion("5.1.0", "3.7", polyscope_version_)) - return false; + assertVersion("5.1.0", "3.7", "restart safety"); return sendRequest("restart safety", "Restarting safety") && waitForReply("robotmode", "Robotmode: POWER_OFF"); } bool DashboardClient::commandUnlockProtectiveStop() { - if (!lessThanVersion("5.0.0", "3.1", polyscope_version_)) - return false; + assertVersion("5.0.0", "3.1", "unlock protective stop"); return sendRequest("unlock protective stop", "Protective stop releasing"); } bool DashboardClient::commandShutdown() { - if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.4", "shutdown"); return sendRequest("shutdown", "Shutting down"); } bool DashboardClient::commandQuit() { - if (!lessThanVersion("5.0.0", "1.4", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.4", "quit"); return sendRequest("quit", "Disconnected"); } bool DashboardClient::commandRunning() { - if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.6", "running"); return sendRequest("running", "Program running: true"); } bool DashboardClient::commandIsProgramSaved() { - if (!lessThanVersion("5.0.0", "1.8", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.8", "isProgramSaved"); return sendRequest("isProgramSaved", "(?:true ).*"); } bool DashboardClient::commandIsInRemoteControl() { - if (!lessThanVersion("5.6.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on - // e-series - return false; - std::string response = sendAndReceive("is in remote control\n"); + 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) { - if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.6", "popup"); return sendRequest("popup " + popup_text, "showing popup"); } bool DashboardClient::commandAddToLog(const std::string& log_text) { - if (!lessThanVersion("5.0.0", "1.8", polyscope_version_)) - return false; + assertVersion("5.0.0", "1.8", "addToLog"); return sendRequest("addToLog " + log_text, "Added log message"); } @@ -365,16 +320,15 @@ bool DashboardClient::commandPolyscopeVersion(std::string& polyscope_version) { std::string expected = "(?:URSoftware ).*"; polyscope_version = sendRequestString("PolyscopeVersion", expected); - polyscope_version_ = polyscope_version.substr(polyscope_version.find(" ") + 1, - polyscope_version.find(" (") - polyscope_version.find(" ") - 1); - e_series_ = stoi(polyscope_version_.substr(0, 1)) >= 5; + 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) { - if (!lessThanVersion("5.6.0", "3.12", polyscope_version_)) - return false; + 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)); @@ -382,8 +336,7 @@ bool DashboardClient::commandGetRobotModel(std::string& robot_model) bool DashboardClient::commandGetSerialNumber(std::string& serial_number) { - if (!lessThanVersion("5.6.0", "3.12", polyscope_version_)) - return false; + 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)); @@ -391,8 +344,7 @@ bool DashboardClient::commandGetSerialNumber(std::string& serial_number) bool DashboardClient::commandRobotMode(std::string& robot_mode) { - if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) - return false; + 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)); @@ -400,8 +352,7 @@ bool DashboardClient::commandRobotMode(std::string& robot_mode) bool DashboardClient::commandGetLoadedProgram(std::string& loaded_program) { - if (!lessThanVersion("5.0.0", "1.6", polyscope_version_)) - return false; + 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)); @@ -409,8 +360,7 @@ bool DashboardClient::commandGetLoadedProgram(std::string& loaded_program) bool DashboardClient::commandSafetyMode(std::string& safety_mode) { - if (!lessThanVersion("5.0.0", "3.0", polyscope_version_)) - return false; + 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)); @@ -418,8 +368,7 @@ bool DashboardClient::commandSafetyMode(std::string& safety_mode) bool DashboardClient::commandSafetyStatus(std::string& safety_status) { - if (!lessThanVersion("5.4.0", "3.11", polyscope_version_)) - return false; + 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)); @@ -427,8 +376,7 @@ bool DashboardClient::commandSafetyStatus(std::string& safety_status) bool DashboardClient::commandProgramState(std::string& program_state) { - if (!lessThanVersion("5.0.0", "1.8", polyscope_version_)) - return false; + 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).*")); @@ -436,9 +384,7 @@ bool DashboardClient::commandProgramState(std::string& program_state) bool DashboardClient::commandGetOperationalMode(std::string& operational_mode) { - if (!lessThanVersion("5.6.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on - // e-series - return false; + 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).*")); @@ -446,34 +392,26 @@ bool DashboardClient::commandGetOperationalMode(std::string& operational_mode) bool DashboardClient::commandSetOperationalMode(const std::string& operational_mode) { - if (!lessThanVersion("5.0.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on - // e-series - return false; + assertVersion("5.0.0", "-", "set operational mode"); return sendRequest("set operational mode " + operational_mode, "(?:Operational mode ).*(?:" + operational_mode + ").*"); } bool DashboardClient::commandClearOperationalMode() { - if (!lessThanVersion("5.0.0", "10. Only available on e-series robot", polyscope_version_)) // Only available on - // e-series - return false; + 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) { - if (!lessThanVersion("10. Only available on CB3 robot", "1.8", polyscope_version_)) // Only available on - // e-series - return false; + assertVersion("-", "1.8", "setUserRole"); return sendRequest("setUserRole " + user_role, "(?:Setting user role: ).*"); } bool DashboardClient::commandGetUserRole(std::string& user_role) { - if (!lessThanVersion("10. Only available on CB3 robot", "1.8", polyscope_version_)) // Only available on - // e-series - return false; + assertVersion("-", "1.8", "getUserRole"); std::string expected = "(?:).*"; user_role = sendRequestString("getUserRole", expected); return !std::regex_match(user_role, std::regex("(?:could not understand).*")); @@ -481,8 +419,7 @@ bool DashboardClient::commandGetUserRole(std::string& user_role) bool DashboardClient::commandGenerateFlightReport(const std::string& report_type) { - if (!lessThanVersion("5.8.0", "3.13", polyscope_version_)) - return false; + assertVersion("5.8.0", "3.13", "generate flight report"); timeval tv; tv.tv_sec = 180; tv.tv_usec = 0; @@ -495,8 +432,7 @@ bool DashboardClient::commandGenerateFlightReport(const std::string& report_type bool DashboardClient::commandGenerateSupportFile(const std::string& dir_path) { - if (!lessThanVersion("5.8.0", "3.13", polyscope_version_)) - return false; + assertVersion("5.8.0", "3.13", "generate support file"); timeval tv; tv.tv_sec = 600; tv.tv_usec = 0; @@ -507,4 +443,35 @@ bool DashboardClient::commandGenerateSupportFile(const std::string& dir_path) 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/tests/test_version_information.cpp b/tests/test_version_information.cpp index 0c64c5119..165ee4135 100644 --- a/tests/test_version_information.cpp +++ b/tests/test_version_information.cpp @@ -33,12 +33,10 @@ using namespace urcl; - TEST(version_information, test_split) { const std::string version_string1 = "5.12.0.1101319"; - std::vector expected = {"5", "12", "0", "1101319"}; - + std::vector expected = { "5", "12", "0", "1101319" }; EXPECT_EQ(expected, splitString(version_string1)); } From 89423b3e4c0569832820fb85aecc1273ed385db5 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Tue, 22 Nov 2022 12:15:50 +0100 Subject: [PATCH 12/18] Remove requirement to manually add line termination to the dashboard commands --- .../ur_client_library/ur/dashboard_client.h | 20 ++++++++++--------- src/ur/dashboard_client.cpp | 17 ++++++++++------ 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 066502fb5..95397b60f 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -72,8 +72,9 @@ 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. */ @@ -82,9 +83,8 @@ class DashboardClient : public comm::TCPSocket /*! * \brief Sends command and compare it with the expected 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 expected Expected replay + * \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 */ @@ -93,9 +93,10 @@ class DashboardClient : public comm::TCPSocket /*! * \brief Sends command and compare it with the expected 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 expected Expected replay + * \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 */ @@ -425,7 +426,8 @@ class DashboardClient : public comm::TCPSocket * * \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); + 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 "); diff --git a/src/ur/dashboard_client.cpp b/src/ur/dashboard_client.cpp index f48bd5f5e..4e804a168 100644 --- a/src/ur/dashboard_client.cpp +++ b/src/ur/dashboard_client.cpp @@ -111,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(); } @@ -129,7 +134,7 @@ std::string DashboardClient::sendAndReceive(const std::string& text) 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 + "\n"); + std::string response = sendAndReceive(command); bool ret = std::regex_match(response, std::regex(expected)); if (!ret) { @@ -141,7 +146,7 @@ bool DashboardClient::sendRequest(const std::string& command, const std::string& 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 + "\n"); + std::string response = sendAndReceive(command); bool ret = std::regex_match(response, std::regex(expected)); if (!ret) { @@ -161,7 +166,7 @@ bool DashboardClient::waitForReply(const std::string& command, const std::string while (time_done < timeout) { // Send the request - response = sendAndReceive(command + "\n"); + response = sendAndReceive(command); // Check if the response was as expected if (std::regex_match(response, std::regex(expected))) @@ -458,11 +463,11 @@ void DashboardClient::assertVersion(const std::string& e_series_min_ver, const s { 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_; + << "' 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_) From 56345db22198abe95443af8e99184b5d7383a101 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Thu, 24 Nov 2022 07:30:40 +0000 Subject: [PATCH 13/18] Explicitly use this->major to avoid name clashes from old c headers --- src/ur/version_information.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ur/version_information.cpp b/src/ur/version_information.cpp index db8408e88..1bc5cf456 100644 --- a/src/ur/version_information.cpp +++ b/src/ur/version_information.cpp @@ -46,8 +46,14 @@ std::vector splitString(std::string input, const std::string& delim return result; } -VersionInformation::VersionInformation() : major(0), minor(0), bugfix(0), build(0) +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) @@ -81,7 +87,7 @@ VersionInformation VersionInformation::fromString(const std::string& str) bool VersionInformation::isESeries() const { - return major >= 5; + return this->major >= 5; } bool operator==(const VersionInformation& v1, const VersionInformation& v2) From 92f535dd191497d086be3c609ffa11edb6281d72 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Thu, 24 Nov 2022 07:56:32 +0000 Subject: [PATCH 14/18] Adapt dashboard_client test to changed version API --- tests/test_dashboard_client.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp index a20249133..2b9913fe4 100644 --- a/tests/test_dashboard_client.cpp +++ b/tests/test_dashboard_client.cpp @@ -32,6 +32,7 @@ #include #include #include +#include "ur_client_library/ur/version_information.h" #define private public #include @@ -118,10 +119,9 @@ TEST_F(DashboardClientTest, flight_report_and_support_file) bool correct_polyscope_version = true; try { - correct_polyscope_version = - dashboard_client_->lessThanVersion("5.6.0", "3.13", dashboard_client_->polyscope_version_); + dashboard_client_->assertVersion("5.6.0", "3.13", "test_function"); } - catch (const std::exception& e) + catch (const UrException& e) { correct_polyscope_version = false; } @@ -143,11 +143,11 @@ TEST_F(DashboardClientTest, e_series_version) { std::string msg; EXPECT_TRUE(dashboard_client_->connect()); - if (!dashboard_client_->e_series_) + if (!dashboard_client_->polyscope_version_.isESeries()) GTEST_SKIP(); - dashboard_client_->polyscope_version_ = "5.0.0"; + dashboard_client_->polyscope_version_ = VersionInformation::fromString("5.0.0"); EXPECT_THROW(dashboard_client_->commandSafetyStatus(msg), UrException); - dashboard_client_->polyscope_version_ = "5.5.0"; + dashboard_client_->polyscope_version_ = VersionInformation::fromString("5.5.0"); EXPECT_TRUE(dashboard_client_->commandSafetyStatus(msg)); EXPECT_THROW(dashboard_client_->commandSetUserRole("none"), UrException); } @@ -157,11 +157,11 @@ TEST_F(DashboardClientTest, cb3_version) { std::string msg; EXPECT_TRUE(dashboard_client_->connect()); - if (dashboard_client_->e_series_) + if (dashboard_client_->polyscope_version_.isESeries()) GTEST_SKIP(); - dashboard_client_->polyscope_version_ = "1.6.0"; + dashboard_client_->polyscope_version_ = VersionInformation::fromString("1.6.0"); EXPECT_THROW(dashboard_client_->commandIsProgramSaved(), UrException); - dashboard_client_->polyscope_version_ = "1.8.0"; + dashboard_client_->polyscope_version_ = VersionInformation::fromString("1.8.0"); EXPECT_TRUE(dashboard_client_->commandIsProgramSaved()); EXPECT_THROW(dashboard_client_->commandIsInRemoteControl(), UrException); } @@ -171,4 +171,4 @@ int main(int argc, char* argv[]) ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} \ No newline at end of file +} From a85642346e48d8aabb0d259ddc3a693681d7bb6f Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Thu, 24 Nov 2022 08:09:15 +0000 Subject: [PATCH 15/18] Update comments --- .../ur_client_library/ur/dashboard_client.h | 20 +++++++++++++++---- tests/test_dashboard_client.cpp | 1 - 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/include/ur_client_library/ur/dashboard_client.h b/include/ur_client_library/ur/dashboard_client.h index 95397b60f..327533e0d 100644 --- a/include/ur_client_library/ur/dashboard_client.h +++ b/include/ur_client_library/ur/dashboard_client.h @@ -245,16 +245,18 @@ class DashboardClient : public comm::TCPSocket bool commandRunning(); /*! - * \brief Send Is program saved command + * \brief Send "Is program saved" request command * - * \return True succeeded + * \return True if the program is saved correctly */ bool commandIsProgramSaved(); /*! - * \brief Send Is in remote control command + * \brief Send "Is in remote control" query command * - * \return True succeeded + * \throws an UrException when called on CB3 robots + * + * \return True if the robot is currently in remote control */ bool commandIsInRemoteControl(); @@ -353,6 +355,8 @@ class DashboardClient : public comm::TCPSocket * * \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); @@ -362,6 +366,8 @@ class DashboardClient : public comm::TCPSocket * * \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); @@ -369,6 +375,8 @@ class DashboardClient : public comm::TCPSocket /*! * \brief Send Clear operational mode command * + * \throws an UrException when called on CB3 robots + * * \return True succeeded */ bool commandClearOperationalMode(); @@ -378,6 +386,8 @@ class DashboardClient : public comm::TCPSocket * * \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); @@ -387,6 +397,8 @@ class DashboardClient : public comm::TCPSocket * * \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); diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp index 2b9913fe4..feb44c9af 100644 --- a/tests/test_dashboard_client.cpp +++ b/tests/test_dashboard_client.cpp @@ -38,7 +38,6 @@ using namespace urcl; -// std::string ROBOT_IP = "192.168.56.101"; std::string ROBOT_IP = "127.0.0.1"; class DashboardClientTest : public ::testing::Test From 14ace412ba461bd2b61d5e34102af0570ce5c6a3 Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 28 Nov 2022 11:37:51 +0100 Subject: [PATCH 16/18] Use external IP address of test container --- tests/test_dashboard_client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_dashboard_client.cpp b/tests/test_dashboard_client.cpp index feb44c9af..acce96fb7 100644 --- a/tests/test_dashboard_client.cpp +++ b/tests/test_dashboard_client.cpp @@ -38,7 +38,7 @@ using namespace urcl; -std::string ROBOT_IP = "127.0.0.1"; +std::string ROBOT_IP = "192.168.56.101"; class DashboardClientTest : public ::testing::Test { From 4fa8eacbc84edc7b0d928c922b4a203eca06046a Mon Sep 17 00:00:00 2001 From: Felix Exner Date: Mon, 28 Nov 2022 11:48:11 +0100 Subject: [PATCH 17/18] Set default IP in examples to external IP address of container In my opinion this is clearer to users that they'll have to provide the robot's IP in that case. Also, when run with the start_ursim.sh script this will work right out of the box --- examples/dashboard_example.cpp | 2 +- examples/full_driver.cpp | 2 +- examples/primary_pipeline.cpp | 2 +- examples/primary_pipeline_calibration.cpp | 2 +- examples/rtde_client.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/dashboard_example.cpp b/examples/dashboard_example.cpp index 77c874a41..cc01f5e63 100644 --- a/examples/dashboard_example.cpp +++ b/examples/dashboard_example.cpp @@ -28,7 +28,7 @@ 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 = "10.53.253.22"; +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; // We need a callback function to register. See UrDriver's parameters for details. diff --git a/examples/full_driver.cpp b/examples/full_driver.cpp index 762f7d32d..3a2135abd 100644 --- a/examples/full_driver.cpp +++ b/examples/full_driver.cpp @@ -39,7 +39,7 @@ 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 = "127.0.0.1"; +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"; diff --git a/examples/primary_pipeline.cpp b/examples/primary_pipeline.cpp index 0da706e71..e4390d8b5 100644 --- a/examples/primary_pipeline.cpp +++ b/examples/primary_pipeline.cpp @@ -34,7 +34,7 @@ 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 = "127.0.0.1"; +const std::string DEFAULT_ROBOT_IP = "192.168.56.101"; int main(int argc, char* argv[]) { diff --git a/examples/primary_pipeline_calibration.cpp b/examples/primary_pipeline_calibration.cpp index 23347e40f..76a3094d7 100644 --- a/examples/primary_pipeline_calibration.cpp +++ b/examples/primary_pipeline_calibration.cpp @@ -61,7 +61,7 @@ class CalibrationConsumer : public urcl::comm::IConsumer Date: Mon, 28 Nov 2022 11:49:17 +0100 Subject: [PATCH 18/18] Added port forwarding to start_ursim.sh --- scripts/start_ursim.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/start_ursim.sh b/scripts/start_ursim.sh index 0fdfa372e..31e9c5af8 100755 --- a/scripts/start_ursim.sh +++ b/scripts/start_ursim.sh @@ -153,6 +153,8 @@ 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