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():" 
270+       << "  textmsg(\" Command through primary interface complete \" )" 
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