1+ // -- BEGIN LICENSE BLOCK ----------------------------------------------
2+ // Copyright 2022 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 < condition_variable>
33+ #include < chrono>
34+
35+ #include < ur_client_library/comm/tcp_server.h>
36+ #define private public
37+ #include < ur_client_library/primary/primary_client.h>
38+
39+ using namespace urcl ;
40+
41+ std::string ROBOT_IP = " 127.0.0.1" ;
42+ int PRIMARY_PORT = 30001 ;
43+ int DASHBOARD_PORT = 29999 ;
44+ int FAKE_PRIMARY_PORT = 60061 ;
45+ int FAKE_DASHBOARD_PORT = 60059 ;
46+
47+ class PrimaryClientTest : public ::testing::Test
48+ {
49+ protected:
50+ void SetUp ()
51+ {
52+ in_remote_control_ = true ;
53+ }
54+
55+ void TearDown ()
56+ {
57+ dashboard_server_.reset ();
58+ primary_server_.reset ();
59+ client_.reset ();
60+ }
61+
62+ void run ()
63+ {
64+ unsigned char message[] = { 0x00 , 0x00 , 0x00 , 0x50 , 0x19 , 0x00 , 0x00 , 0x00 ,
65+ 0x00 , 0x56 , 0x76 , 0xd3 , 0xa0 , 0x00 , 0x00 , 0x00 }; // Empty GlobalVariablesSetupMessage
66+ size_t len = sizeof (message);
67+ size_t written;
68+ while (running_)
69+ {
70+ primary_server_->write (client_fd_, message, len, written);
71+ std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
72+ }
73+ }
74+
75+ void stopThread ()
76+ {
77+ URCL_LOG_DEBUG (" Shutting down thread" );
78+ running_ = false ;
79+ if (server_thread_.joinable ())
80+ {
81+ server_thread_.join ();
82+ }
83+ }
84+
85+ void connectionCallback (const int filedescriptor)
86+ {
87+ std::lock_guard<std::mutex> lk (connect_mutex_);
88+ client_fd_ = filedescriptor;
89+ connect_cv_.notify_one ();
90+ connection_callback_ = true ;
91+ }
92+
93+ void connectionCallbackDB (const int filedescriptor)
94+ {
95+ std::lock_guard<std::mutex> lk (connect_mutex_db_);
96+ client_fd_db_ = filedescriptor;
97+
98+ unsigned char message[] = {
99+ 0x43 , 0x6f , 0x6e , 0x6e , 0x65 , 0x63 , 0x74 , 0x65 , 0x64 , 0x3a , 0x20 , 0x55 , 0x6e , 0x69 , 0x76 , 0x65 , 0x72 , 0x73 , 0x61 ,
100+ 0x6c , 0x20 , 0x52 , 0x6f , 0x62 , 0x6f , 0x74 , 0x73 , 0x20 , 0x46 , 0x61 , 0x6b , 0x65 , 0x20 , 0x54 , 0x65 , 0x73 , 0x74 , 0x20 ,
101+ 0x44 , 0x61 , 0x73 , 0x68 , 0x62 , 0x6f , 0x61 , 0x72 , 0x64 , 0x20 , 0x53 , 0x65 , 0x72 , 0x76 , 0x65 , 0x72 , 0x0a
102+ }; // "Connected: Universal Robots Fake Test Dashboard Server\n"
103+ size_t len = sizeof (message);
104+ size_t written;
105+ dashboard_server_->write (client_fd_db_, message, len, written);
106+ connect_cv_db_.notify_one ();
107+ connection_callback_db_ = true ;
108+ }
109+
110+ void messageCallback (const int filedescriptor, char * buffer)
111+ {
112+ std::lock_guard<std::mutex> lk (message_mutex_);
113+ message_ = std::string (buffer);
114+ message_cv_.notify_one ();
115+ message_callback_ = true ;
116+ }
117+
118+ void messageCallbackDB (const int filedescriptor, char * buffer)
119+ {
120+ std::lock_guard<std::mutex> lk (message_mutex_db_);
121+ message_db_ = std::string (buffer);
122+ if (message_db_ == " is in remote control\n " )
123+ {
124+ unsigned char message_true[] = { 0x74 , 0x72 , 0x75 , 0x65 , 0x0a }; // "true\n"
125+ unsigned char message_false[] = { 0x66 , 0x61 , 0x6c , 0x73 , 0x65 , 0x0a }; // "false\n"
126+
127+ size_t len = in_remote_control_ ? sizeof (message_true) : sizeof (message_false);
128+ size_t written;
129+ dashboard_server_->write (client_fd_db_, in_remote_control_ ? message_true : message_false, len, written);
130+ }
131+ if (message_db_ == " PolyscopeVersion\n " )
132+ {
133+ unsigned char message_pv[] = {
134+ 0x55 , 0x52 , 0x53 , 0x6f , 0x66 , 0x74 , 0x77 , 0x61 , 0x72 , 0x65 , 0x20 , 0x35 , 0x2e , 0x31 ,
135+ 0x32 , 0x2e , 0x32 , 0x2e , 0x31 , 0x31 , 0x30 , 0x31 , 0x35 , 0x33 , 0x34 , 0x20 , 0x28 , 0x4a ,
136+ 0x75 , 0x6c , 0x20 , 0x30 , 0x36 , 0x20 , 0x32 , 0x30 , 0x32 , 0x32 , 0x29 , 0x0a
137+ }; // URSoftware 5.12.2.1101534 (Jul 06 2022)
138+ size_t len = sizeof (message_pv);
139+ size_t written;
140+ dashboard_server_->write (client_fd_db_, message_pv, len, written);
141+ }
142+
143+ message_cv_db_.notify_one ();
144+ message_callback_db_ = true ;
145+ }
146+
147+ bool waitForConnectionCallback (int milliseconds = 100 )
148+ {
149+ std::unique_lock<std::mutex> lk (connect_mutex_);
150+ if (connect_cv_.wait_for (lk, std::chrono::milliseconds (milliseconds)) == std::cv_status::no_timeout ||
151+ connection_callback_ == true )
152+ {
153+ connection_callback_ = false ;
154+ return true ;
155+ }
156+ else
157+ {
158+ return false ;
159+ }
160+ }
161+
162+ bool waitForMessageCallback (int milliseconds = 100 )
163+ {
164+ std::unique_lock<std::mutex> lk (message_mutex_);
165+ if (message_cv_.wait_for (lk, std::chrono::milliseconds (milliseconds)) == std::cv_status::no_timeout ||
166+ message_callback_ == true )
167+ {
168+ message_callback_ = false ;
169+ return true ;
170+ }
171+ else
172+ {
173+ return false ;
174+ }
175+ }
176+
177+ bool waitForMessageCallbackDB (int milliseconds = 100 )
178+ {
179+ std::unique_lock<std::mutex> lk (message_mutex_db_);
180+ if (message_cv_db_.wait_for (lk, std::chrono::milliseconds (milliseconds)) == std::cv_status::no_timeout ||
181+ message_callback_db_ == true )
182+ {
183+ message_callback_db_ = false ;
184+ return true ;
185+ }
186+ else
187+ {
188+ return false ;
189+ }
190+ }
191+
192+ std::string message_ = " " ;
193+ std::string message_db_ = " " ;
194+ int client_fd_ = -1 ;
195+ int client_fd_db_ = -1 ;
196+
197+ std::atomic<bool > running_;
198+ std::thread server_thread_;
199+ std::unique_ptr<primary_interface::PrimaryClient> client_;
200+ std::unique_ptr<comm::TCPServer> primary_server_;
201+ std::unique_ptr<comm::TCPServer> dashboard_server_;
202+
203+ std::condition_variable connect_cv_, connect_cv_db_;
204+ std::mutex connect_mutex_, connect_mutex_db_;
205+
206+ std::condition_variable message_cv_, message_cv_db_;
207+ std::mutex message_mutex_, message_mutex_db_;
208+
209+ bool connection_callback_ = false ;
210+ bool connection_callback_db_ = false ;
211+ bool message_callback_ = false ;
212+ bool message_callback_db_ = false ;
213+
214+ bool in_remote_control_;
215+ };
216+
217+ TEST_F (PrimaryClientTest, check_remote_control)
218+ {
219+ dashboard_server_.reset (new comm::TCPServer (FAKE_DASHBOARD_PORT));
220+ primary_server_.reset (new comm::TCPServer (FAKE_PRIMARY_PORT));
221+ primary_server_->setMessageCallback (std::bind (&PrimaryClientTest_check_remote_control_Test::messageCallback, this ,
222+ std::placeholders::_1, std::placeholders::_2));
223+ dashboard_server_->setMessageCallback (std::bind (&PrimaryClientTest_check_remote_control_Test::messageCallbackDB, this ,
224+ std::placeholders::_1, std::placeholders::_2));
225+ primary_server_->setConnectCallback (
226+ std::bind (&PrimaryClientTest_check_remote_control_Test::connectionCallback, this , std::placeholders::_1));
227+ dashboard_server_->setConnectCallback (
228+ std::bind (&PrimaryClientTest_check_remote_control_Test::connectionCallbackDB, this , std::placeholders::_1));
229+ dashboard_server_->start ();
230+ primary_server_->start ();
231+ server_thread_ = std::thread (&PrimaryClientTest_check_remote_control_Test::run, this );
232+
233+ std::unique_ptr<primary_interface::PrimaryClient> temp_client;
234+ client_.reset (new primary_interface::PrimaryClient (ROBOT_IP, " " ));
235+ std::this_thread::sleep_for (std::chrono::milliseconds (1000 )); // Let connections set up
236+
237+ // Disconnect from URSim servers and connect to fake servers
238+ client_->pipeline_ ->stop ();
239+ client_->stream_ ->disconnect ();
240+ client_->dashboard_client_ ->disconnect ();
241+ client_->dashboard_client_ ->port_ = FAKE_DASHBOARD_PORT;
242+ client_->stream_ ->port_ = FAKE_PRIMARY_PORT;
243+ client_->stream_ ->connect ();
244+ client_->dashboard_client_ ->connect ();
245+ client_->pipeline_ ->run ();
246+ std::this_thread::sleep_for (std::chrono::milliseconds (1000 )); // let connections set up
247+
248+ // When in_remote_control_ is true the primary client should be able to send script
249+ in_remote_control_ = true ;
250+ client_->sendScript (" true\n " );
251+ EXPECT_TRUE (waitForMessageCallback (1000 ));
252+
253+ // Make sure thread sets in_remote_control_ to false and primary client has time to reconnect
254+ in_remote_control_ = false ;
255+ std::this_thread::sleep_for (std::chrono::milliseconds (1000 ));
256+
257+ // When in_remote_control_ is false the primary client NOT should be able to send script
258+ client_->sendScript (" false\n " );
259+ EXPECT_FALSE (waitForMessageCallback (1000 ));
260+
261+ stopThread ();
262+ }
263+
264+ TEST_F (PrimaryClientTest, send_script)
265+ {
266+ client_.reset (new primary_interface::PrimaryClient (ROBOT_IP, " " ));
267+ std::stringstream cmd;
268+ cmd.imbue (std::locale::classic ()); // Make sure, decimal divider is actually '.'
269+ cmd << " sec setup():" << std::endl
270+ << " textmsg(\" Command through primary interface complete \" )" << std::endl
271+ << " end" ;
272+
273+ std::string script_code = cmd.str ();
274+ auto program_with_newline = script_code + ' \n ' ;
275+ // Should always return false in pipeline as robot will never be in remote control
276+ EXPECT_FALSE (client_->sendScript (program_with_newline));
277+ }
278+
279+ TEST_F (PrimaryClientTest, get_data)
280+ {
281+ client_.reset (new primary_interface::PrimaryClient (ROBOT_IP, " " ));
282+ EXPECT_EQ (client_->getVersionMessage ()->build_number_ , 0 );
283+ vector6d_t zero_array = { 0 };
284+ EXPECT_EQ (client_->getCartesianInfo ()->tcp_offset_coordinates_ , zero_array);
285+ EXPECT_EQ (client_->getForceModeData ()->wrench_ , zero_array);
286+ // getGlobalVariablesSetupMessage() will throw an exception since a program on the robot has not been started while
287+ // this client has been connected
288+ EXPECT_THROW (client_->getGlobalVariablesSetupMessage ()->variable_names_ , UrException);
289+ }
290+
291+ int main (int argc, char * argv[])
292+ {
293+ ::testing::InitGoogleTest (&argc, argv);
294+
295+ return RUN_ALL_TESTS ();
296+ }
0 commit comments