Skip to content

Commit d17a43b

Browse files
committed
Use a separate class to read script code from a file
The class is used to read the script code from a file once so it can be passed to the script_sender.
1 parent 6825b64 commit d17a43b

File tree

9 files changed

+258
-13
lines changed

9 files changed

+258
-13
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_library(urcl
1818
src/comm/tcp_socket.cpp
1919
src/comm/tcp_server.cpp
2020
src/control/reverse_interface.cpp
21+
src/control/script_reader.cpp
2122
src/control/script_sender.cpp
2223
src/control/trajectory_point_interface.cpp
2324
src/control/script_command_interface.cpp
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// -- BEGIN LICENSE BLOCK ----------------------------------------------
2+
// Copyright 2025 Universal Robots A/S
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
//
10+
// * Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// * Neither the name of the {copyright_holder} nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
// POSSIBILITY OF SUCH DAMAGE.
29+
// -- END LICENSE BLOCK ------------------------------------------------
30+
31+
#pragma once
32+
#include <string>
33+
34+
#include <ur_client_library/ur/datatypes.h>
35+
#include <ur_client_library/ur/version_information.h>
36+
37+
namespace urcl
38+
{
39+
namespace control
40+
{
41+
class ScriptReader
42+
{
43+
public:
44+
struct RobotInfo
45+
{
46+
VersionInformation version_info;
47+
RobotType robot_type;
48+
};
49+
50+
ScriptReader() = delete;
51+
52+
/*!
53+
* \brief Creates a ScriptReader object with the robot info
54+
*/
55+
explicit ScriptReader(const RobotInfo& robot_info) : robot_info_(robot_info)
56+
{
57+
}
58+
59+
std::string readScriptFile(const std::string& filename);
60+
61+
private:
62+
RobotInfo robot_info_;
63+
};
64+
} // namespace control
65+
} // namespace urcl

include/ur_client_library/ur/datatypes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#include <ur_client_library/types.h>
3232
#include "ur_client_library/log.h"
33+
#include <sstream>
3334

3435
namespace urcl
3536
{

include/ur_client_library/ur/ur_driver.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "ur_client_library/control/reverse_interface.h"
3737
#include "ur_client_library/control/trajectory_point_interface.h"
3838
#include "ur_client_library/control/script_command_interface.h"
39+
#include "ur_client_library/control/script_reader.h"
3940
#include "ur_client_library/control/script_sender.h"
4041
#include "ur_client_library/ur/tool_communication.h"
4142
#include "ur_client_library/ur/version_information.h"
@@ -891,7 +892,7 @@ class UrDriver
891892
trajectory_interface_->registerDisconnectionCallback(fun);
892893
}
893894

894-
static std::string readScriptFile(const std::string& filename);
895+
std::string readScriptFile(const std::string& filename);
895896

896897
bool isReverseInterfaceConnected() const
897898
{
@@ -921,6 +922,7 @@ class UrDriver
921922
std::unique_ptr<control::TrajectoryPointInterface> trajectory_interface_;
922923
std::unique_ptr<control::ScriptCommandInterface> script_command_interface_;
923924
std::unique_ptr<control::ScriptSender> script_sender_;
925+
std::unique_ptr<control::ScriptReader> script_reader_;
924926

925927
size_t socket_connection_attempts_ = 0;
926928
std::chrono::milliseconds socket_reconnection_timeout_ = std::chrono::milliseconds(10000);

src/control/script_reader.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// -- BEGIN LICENSE BLOCK ----------------------------------------------
2+
// Copyright 2025 Universal Robots A/S
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
//
10+
// * Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// * Neither the name of the {copyright_holder} nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
// POSSIBILITY OF SUCH DAMAGE.
29+
// -- END LICENSE BLOCK ------------------------------------------------
30+
31+
#include <ur_client_library/exceptions.h>
32+
#include <ur_client_library/control/script_reader.h>
33+
34+
#include <fstream>
35+
36+
namespace urcl
37+
{
38+
namespace control
39+
{
40+
std::string ScriptReader::readScriptFile(const std::string& filename)
41+
{
42+
std::ifstream ifs;
43+
ifs.open(filename);
44+
std::string content;
45+
std::ifstream file(filename);
46+
if (ifs)
47+
{
48+
content = std::string((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
49+
ifs.close();
50+
}
51+
else
52+
{
53+
std::stringstream ss;
54+
ss << "Could not open script file '" << filename << "'. Please check if the file exists and is readable.";
55+
throw UrException(ss.str().c_str());
56+
}
57+
58+
return content;
59+
}
60+
} // namespace control
61+
} // namespace urcl

src/ur/ur_driver.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
#include "ur_client_library/ur/ur_driver.h"
3535
#include "ur_client_library/exceptions.h"
36+
#include "ur_client_library/helpers.h"
3637
#include "ur_client_library/primary/primary_parser.h"
3738
#include <memory>
3839
#include <sstream>
@@ -86,6 +87,12 @@ void UrDriver::init(const UrDriverConfiguration& config)
8687
// Figure out the ip automatically if the user didn't provide it
8788
std::string local_ip = config.reverse_ip.empty() ? rtde_client_->getIP() : config.reverse_ip;
8889

90+
startPrimaryClientCommunication();
91+
waitFor([this]() { return primary_client_->getConfigurationData() != nullptr; }, std::chrono::milliseconds(500));
92+
control::ScriptReader::RobotInfo robot_info;
93+
robot_info.robot_type = primary_client_->getRobotType();
94+
script_reader_.reset(new control::ScriptReader(robot_info));
95+
8996
std::string prog = readScriptFile(config.script_file);
9097
while (prog.find(JOINT_STATE_REPLACE) != std::string::npos)
9198
{
@@ -151,7 +158,6 @@ void UrDriver::init(const UrDriverConfiguration& config)
151158
trajectory_interface_.reset(new control::TrajectoryPointInterface(config.trajectory_port));
152159
script_command_interface_.reset(new control::ScriptCommandInterface(config.script_command_port));
153160

154-
startPrimaryClientCommunication();
155161
if (in_headless_mode_)
156162
{
157163
full_robot_program_ = "stop program\n";
@@ -552,17 +558,13 @@ bool UrDriver::stopControl()
552558

553559
std::string UrDriver::readScriptFile(const std::string& filename)
554560
{
555-
std::ifstream ifs;
556-
ifs.open(filename);
557-
if (!ifs)
561+
if (script_reader_ == nullptr)
558562
{
559-
std::stringstream ss;
560-
ss << "URScript file '" << filename << "' doesn't exists.";
561-
throw UrException(ss.str().c_str());
563+
throw std::runtime_error("Script reader is not initialized. Please initialize the UrDriver before using this "
564+
"function.");
562565
}
563-
std::string content((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
564566

565-
return content;
567+
return script_reader_->readScriptFile(filename);
566568
}
567569

568570
bool UrDriver::checkCalibration(const std::string& checksum)

tests/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ target_link_libraries(script_sender_tests PRIVATE ur_client_library::urcl GTest:
131131
gtest_add_tests(TARGET script_sender_tests
132132
)
133133

134+
add_executable(script_reader_tests test_script_reader.cpp)
135+
target_link_libraries(script_reader_tests PRIVATE ur_client_library::urcl GTest::gtest_main)
136+
gtest_add_tests(
137+
TARGET script_reader_tests
138+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
139+
)
140+
134141
add_executable(reverse_interface_tests test_reverse_interface.cpp)
135142
target_link_libraries(reverse_interface_tests PRIVATE ur_client_library::urcl GTest::gtest_main)
136143
gtest_add_tests(TARGET reverse_interface_tests
@@ -224,4 +231,4 @@ gtest_add_tests(TARGET robot_receive_timeout_tests
224231
add_executable(control_mode_tests test_control_mode.cpp)
225232
target_link_libraries(control_mode_tests PRIVATE ur_client_library::urcl GTest::gtest_main)
226233
gtest_add_tests(TARGET control_mode_tests
227-
)
234+
)

tests/test_script_reader.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// -- BEGIN LICENSE BLOCK ----------------------------------------------
2+
// Copyright 2025 Universal Robots A/S
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
//
10+
// * Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// * Neither the name of the {copyright_holder} nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
// POSSIBILITY OF SUCH DAMAGE.
29+
// -- END LICENSE BLOCK ------------------------------------------------
30+
31+
#include <gtest/gtest.h>
32+
#include "ur_client_library/control/script_reader.h"
33+
34+
#include <fstream>
35+
36+
using namespace urcl::control;
37+
38+
class ScriptReaderTest : public ::testing::Test
39+
{
40+
protected:
41+
std::string valid_script_path_;
42+
std::string invalid_script_path_;
43+
std::string empty_script_path_;
44+
45+
std::stringstream simple_script_;
46+
47+
ScriptReader::RobotInfo robot_info_;
48+
49+
void SetUp() override
50+
{
51+
invalid_script_path_ = "test_resources/non_existent_script.urscript";
52+
empty_script_path_ = "resources/empty.txt";
53+
char existing_script_file[] = "urscript.XXXXXX";
54+
#ifdef _WIN32
55+
# define mkstemp _mktemp_s
56+
#endif
57+
std::ignore = mkstemp(existing_script_file);
58+
std::ofstream ofs(existing_script_file);
59+
if (ofs.bad())
60+
{
61+
std::cout << "Failed to create temporary files" << std::endl;
62+
GTEST_FAIL();
63+
}
64+
ofs.close();
65+
66+
valid_script_path_ = existing_script_file;
67+
68+
simple_script_ << "movej([0,0,0,0,0,0])";
69+
70+
// Create test resources
71+
std::ofstream valid_script(valid_script_path_);
72+
valid_script << simple_script_.str();
73+
valid_script.close();
74+
75+
std::ofstream empty_script(empty_script_path_);
76+
empty_script.close();
77+
78+
robot_info_.robot_type = urcl::RobotType::UR3;
79+
}
80+
81+
void TearDown() override
82+
{
83+
std::remove(valid_script_path_.c_str());
84+
std::remove(empty_script_path_.c_str());
85+
}
86+
};
87+
88+
TEST_F(ScriptReaderTest, ReadValidScript)
89+
{
90+
ScriptReader reader(robot_info_);
91+
std::string content = reader.readScriptFile(valid_script_path_);
92+
EXPECT_EQ(content, "movej([0,0,0,0,0,0])");
93+
}
94+
95+
TEST_F(ScriptReaderTest, ReadEmptyScript)
96+
{
97+
ScriptReader reader(robot_info_);
98+
std::string content = reader.readScriptFile(empty_script_path_);
99+
EXPECT_EQ(content, "");
100+
}
101+
102+
TEST_F(ScriptReaderTest, ReadNonExistentScript)
103+
{
104+
ScriptReader reader(robot_info_);
105+
EXPECT_THROW(reader.readScriptFile(invalid_script_path_), std::runtime_error);
106+
}

tests/test_ur_driver.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class UrDriverTest : public ::testing::Test
8181
TEST_F(UrDriverTest, read_non_existing_script_file)
8282
{
8383
const std::string non_existing_script_file = "";
84-
EXPECT_THROW(UrDriver::readScriptFile(non_existing_script_file), UrException);
84+
EXPECT_THROW(g_my_robot->getUrDriver()->readScriptFile(non_existing_script_file), UrException);
8585
}
8686

8787
TEST_F(UrDriverTest, read_existing_script_file)
@@ -98,7 +98,7 @@ TEST_F(UrDriverTest, read_existing_script_file)
9898
std::cout << "Failed to create temporary files" << std::endl;
9999
GTEST_FAIL();
100100
}
101-
EXPECT_NO_THROW(UrDriver::readScriptFile(existing_script_file));
101+
EXPECT_NO_THROW(g_my_robot->getUrDriver()->readScriptFile(existing_script_file));
102102

103103
// clean up
104104
ofs.close();

0 commit comments

Comments
 (0)