e2e.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2024 The urg_stamped Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <iostream>
18 #include <map>
19 #include <memory>
20 #include <sstream>
21 #include <string>
22 #include <thread>
23 #include <vector>
24 
25 #include <boost/asio/ip/tcp.hpp>
26 
27 #include <ros/ros.h>
28 #include <sensor_msgs/LaserScan.h>
29 #include <urg_stamped/Status.h>
30 
31 #include <ros/network.h>
32 #include <ros/xmlrpc_manager.h>
33 #include <xmlrpcpp/XmlRpc.h>
34 
35 #include <urg_sim/urg_sim.h>
36 #include <e2e_utils.h>
37 
38 #include <gtest/gtest.h>
39 
40 namespace
41 {
42 
43 bool shutdownUrgStamped()
44 {
45  return shutdownNode("urg_stamped");
46 }
47 
48 } // namespace
49 
50 class E2E : public ::testing::Test
51 {
52 public:
53  E2E()
54  : nh_("")
55  , pnh_("~")
56  , sim_killed_(false)
57  , cnt_(0)
58  {
59  }
60 
61  void TearDown()
62  {
63  if (sim_)
64  {
65  sim_->kill();
66  th_sim_.join();
67  }
68  }
69 
70 protected:
75 
77  std::thread th_sim_;
79  urg_stamped::Status::ConstPtr status_msg_;
80 
81  int cnt_;
82 
83  std::vector<sensor_msgs::LaserScan::ConstPtr> scans_;
84  std::vector<urg_sim::RawScanData::Ptr> raw_scans_;
85  std::map<int, ros::Time> true_stamps_;
86 
87  void cbScan(const sensor_msgs::LaserScan::ConstPtr& msg)
88  {
89  scans_.push_back(msg);
90  }
91 
92  void cbStatus(const urg_stamped::Status ::ConstPtr& msg)
93  {
94  status_msg_ = msg;
95  }
96 
98  {
99  data->ranges[0] = cnt_;
100  raw_scans_.push_back(data);
101  true_stamps_[cnt_] = ros::Time::fromBoost(data->full_time);
102  cnt_++;
103  }
104 
105  void waitScans(const size_t num, const ros::Duration& timeout)
106  {
107  const ros::Time deadline = ros::Time::now() + timeout;
108  ros::Rate wait(10);
109  scans_.clear();
110  while (scans_.size() <= num)
111  {
112  wait.sleep();
113  ros::spinOnce();
114  ASSERT_TRUE(ros::ok());
115  ASSERT_LT(ros::Time::now(), deadline) << "Timeout: received " << scans_.size() << "/" << num;
116  }
117  }
118 
120  {
122  boost::asio::ip::tcp::endpoint(
123  boost::asio::ip::tcp::v4(),
124  0),
125  params,
126  std::bind(&E2E::cbRawScanData, this, std::placeholders::_1));
127  th_sim_ = std::thread(std::bind(&urg_sim::URGSimulator::spin, sim_));
128  ros::Duration(0.1).sleep(); // Wait boot
129  }
130 
132  {
133  nh_.setParam("/urg_stamped/ip_address", "127.0.0.1");
134  nh_.setParam("/urg_stamped/ip_port", sim_->getLocalEndpoint().port());
135 
136  // Shutdown urg_stamped to initialize internal state and reload parameters
137  if (!shutdownUrgStamped())
138  {
139  ros::Duration(1).sleep(); // Retry
140  ASSERT_TRUE(shutdownUrgStamped());
141  }
142  ros::Duration(1).sleep(); // Wait node respawn
143 
144  sub_scan_ = nh_.subscribe("scan", 100, &E2E::cbScan, this);
145  sub_status_ = nh_.subscribe("/urg_stamped/status", 10, &E2E::cbStatus, this);
146  }
147 };
148 
149 class E2EWithParam : public E2E,
150  public ::testing::WithParamInterface<urg_sim::URGSimulator::Params>
151 {
152 };
153 
154 std::vector<urg_sim::URGSimulator::Params> params(
155  {
156  {
158  .boot_duration = 0.01,
159  .comm_delay_base = 0.0002,
160  .comm_delay_sigma = 0.0004,
161  .scan_interval = 0.02505,
162  .clock_rate = 1.0,
163  .hex_ii_timestamp = false,
164  .angle_resolution = 1440,
165  .angle_min = 0,
166  .angle_max = 1080,
167  .angle_front = 540,
168  }, // NOLINT(whitespace/braces)
169  {
171  .boot_duration = 0.01,
172  .comm_delay_base = 0.0002,
173  .comm_delay_sigma = 0.0004,
174  .scan_interval = 0.02505,
175  .clock_rate = 1.001,
176  .hex_ii_timestamp = false,
177  .angle_resolution = 1440,
178  .angle_min = 0,
179  .angle_max = 1080,
180  .angle_front = 540,
181  }, // NOLINT(whitespace/braces)
182  {
184  .boot_duration = 0.01,
185  .comm_delay_base = 0.0002,
186  .comm_delay_sigma = 0.0004,
187  .scan_interval = 0.02505,
188  .clock_rate = 0.999,
189  .hex_ii_timestamp = false,
190  .angle_resolution = 1440,
191  .angle_min = 0,
192  .angle_max = 1080,
193  .angle_front = 540,
194  }, // NOLINT(whitespace/braces)
195  {
197  .boot_duration = 0.01,
198  .comm_delay_base = 0.0005,
199  .comm_delay_sigma = 0.0005,
200  .scan_interval = 0.02505,
201  .clock_rate = 1.0,
202  .hex_ii_timestamp = true,
203  .angle_resolution = 1440,
204  .angle_min = 0,
205  .angle_max = 1080,
206  .angle_front = 540,
207  }, // NOLINT(whitespace/braces)
208  {
210  .boot_duration = 0.01,
211  .comm_delay_base = 0.0005,
212  .comm_delay_sigma = 0.0005,
213  .scan_interval = 0.02505,
214  .clock_rate = 1.001,
215  .hex_ii_timestamp = true,
216  .angle_resolution = 1440,
217  .angle_min = 0,
218  .angle_max = 1080,
219  .angle_front = 540,
220  }, // NOLINT(whitespace/braces)
221  {
223  .boot_duration = 0.01,
224  .comm_delay_base = 0.0005,
225  .comm_delay_sigma = 0.0005,
226  .scan_interval = 0.02505,
227  .clock_rate = 0.999,
228  .hex_ii_timestamp = true,
229  .angle_resolution = 1440,
230  .angle_min = 0,
231  .angle_max = 1080,
232  .angle_front = 540,
233  }, // NOLINT(whitespace/braces)
234  {
236  .boot_duration = 0.01,
237  .comm_delay_base = 0.0002,
238  .comm_delay_sigma = 0.0002,
239  .scan_interval = 0.02505,
240  .clock_rate = 1.0,
241  .hex_ii_timestamp = true,
242  .angle_resolution = 1440,
243  .angle_min = 0,
244  .angle_max = 1080,
245  .angle_front = 540,
246  }, // NOLINT(whitespace/braces)
247  {
249  .boot_duration = 0.01,
250  .comm_delay_base = 0.0002,
251  .comm_delay_sigma = 0.0002,
252  .scan_interval = 0.02505,
253  .clock_rate = 1.001,
254  .hex_ii_timestamp = true,
255  .angle_resolution = 1440,
256  .angle_min = 0,
257  .angle_max = 1080,
258  .angle_front = 540,
259  }, // NOLINT(whitespace/braces)
260  {
262  .boot_duration = 0.01,
263  .comm_delay_base = 0.0002,
264  .comm_delay_sigma = 0.0002,
265  .scan_interval = 0.02505,
266  .clock_rate = 0.999,
267  .hex_ii_timestamp = true,
268  .angle_resolution = 1440,
269  .angle_min = 0,
270  .angle_max = 1080,
271  .angle_front = 540,
272  }, // NOLINT(whitespace/braces)
273  }); // NOLINT(whitespace/braces)
274 
276  Simple, E2EWithParam,
277  ::testing::ValuesIn(params));
278 
280 {
281  const auto param = GetParam();
282  ASSERT_NO_FATAL_FAILURE(startSimulator(param));
283 
284  // Make time sync happens frequently
285  pnh_.setParam("/urg_stamped/clock_estim_interval", 2.5);
286  pnh_.setParam("/urg_stamped/error_limit", 4);
287  pnh_.setParam("/urg_stamped/debug", true);
288  pnh_.setParam("/urg_stamped/uust2_stamp_offset", -0.0002);
289  ASSERT_NO_FATAL_FAILURE(startUrgStamped());
290 
291  std::shared_ptr<std::stringstream> serr;
292  bool ok = false;
293  int err_rms;
294  for (int retry = 0; retry < 2; retry++)
295  {
296  SCOPED_TRACE("try " + std::to_string(retry));
297 
298  serr.reset(new std::stringstream());
299  ASSERT_NO_FATAL_FAILURE(waitScans(300, ros::Duration(15)));
300 
301  err_rms = 0;
302  int num_fail = 0;
303  for (size_t i = 250; i < 300; ++i)
304  {
305  const int index = std::lround(scans_[i]->ranges[0] * 1000);
306  ASSERT_NE(true_stamps_.find(index), true_stamps_.end()) << "Can not find corresponding ground truth timestamp";
307  const double err = (true_stamps_[index] - scans_[i]->header.stamp).toSec();
308  if (std::abs(err) > 0.001)
309  {
310  num_fail++;
311  *serr << std::setprecision(6) << std::fixed
312  << "err " << err << " "
313  << std::setprecision(9)
314  << "scan " << i << " gain " << status_msg_->sensor_clock_gain
315  << " stamp " << scans_[i]->header.stamp << std::endl;
316  }
317  err_rms += err * err;
318  }
319  if (num_fail == 0)
320  {
321  ok = true;
322  break;
323  }
324  std::cerr << "Test attempt " << retry << " failed: " << serr->str() << std::endl;
325  }
326  EXPECT_TRUE(ok) << serr->str();
327  err_rms = std::sqrt(err_rms / 50);
328  EXPECT_LT(err_rms, 0.0002);
329 
330  ASSERT_TRUE(static_cast<bool>(status_msg_));
331  ASSERT_EQ(status_msg_->header.frame_id, std::string("laser"));
332  ASSERT_NEAR(status_msg_->sensor_clock_gain, param.clock_rate, 2.5e-4);
333  ASSERT_GE(status_msg_->communication_delay.toSec(), param.comm_delay_base);
334  ASSERT_LT(status_msg_->communication_delay.toSec(), param.comm_delay_base + param.comm_delay_sigma * 3);
335  ASSERT_NEAR(status_msg_->scan_interval.toSec(), param.scan_interval, 5e-5);
336 }
337 
338 TEST_F(E2E, RebootOnError)
339 {
341  {
343  .boot_duration = 0.01,
344  .comm_delay_base = 0.00025,
345  .comm_delay_sigma = 0.00005,
346  .scan_interval = 0.025,
347  .clock_rate = 1.0,
348  .hex_ii_timestamp = false,
349  .angle_resolution = 1440,
350  .angle_min = 0,
351  .angle_max = 1080,
352  .angle_front = 540,
353  };
354 
355  ASSERT_NO_FATAL_FAILURE(startSimulator(params));
357 
358  pnh_.setParam("/urg_stamped/error_limit", 0);
359  ASSERT_NO_FATAL_FAILURE(startUrgStamped());
360 
361  ros::Duration(2).sleep();
362  ASSERT_GE(sim_->getBootCnt(), 2);
363 }
364 
365 int main(int argc, char** argv)
366 {
367  testing::InitGoogleTest(&argc, argv);
368  ros::init(argc, argv, "urg_stamped_e2e");
369  ros::NodeHandle nh; // workaround to keep the test node during the process life time
370 
371  return RUN_ALL_TESTS();
372 }
373 
ros::NodeHandle::setParam
void setParam(const std::string &key, bool b) const
urg_sim::URGSimulator::Params
Definition: urg_sim.h:68
E2E::pnh_
ros::NodeHandle pnh_
Definition: e2e.cpp:72
E2E::startSimulator
void startSimulator(const urg_sim::URGSimulator::Params &params)
Definition: e2e.cpp:119
urg_sim::URGSimulator
Definition: urg_sim.h:59
ros::init
ROSCPP_DECL void init(const M_string &remappings, const std::string &name, uint32_t options=0)
urg_sim::URGSimulator::kill
void kill()
Definition: urg_sim.cpp:825
E2E
Definition: e2e.cpp:50
ros.h
E2E::cbScan
void cbScan(const sensor_msgs::LaserScan::ConstPtr &msg)
Definition: e2e.cpp:87
INSTANTIATE_TEST_CASE_P
INSTANTIATE_TEST_CASE_P(Simple, E2EWithParam, ::testing::ValuesIn(params))
TEST_P
TEST_P(E2EWithParam, Simple)
Definition: e2e.cpp:279
main
int main(int argc, char **argv)
Definition: e2e.cpp:365
ros::spinOnce
ROSCPP_DECL void spinOnce()
E2E::sim_
urg_sim::URGSimulator * sim_
Definition: e2e.cpp:76
network.h
urg_sim::URGSimulator::Model::UTM
@ UTM
E2E::status_msg_
urg_stamped::Status::ConstPtr status_msg_
Definition: e2e.cpp:79
urg_sim::URGSimulator::Model::UST_UUST2
@ UST_UUST2
urg_sim::URGSimulator::Model::UST_UUST1
@ UST_UUST1
urg_sim::URGSimulator::SensorState::ERROR_DETECTED
@ ERROR_DETECTED
urg_sim::RawScanData::Ptr
std::shared_ptr< RawScanData > Ptr
Definition: urg_sim.h:47
E2E::startUrgStamped
void startUrgStamped()
Definition: e2e.cpp:131
ros::ok
ROSCPP_DECL bool ok()
E2E::sub_scan_
ros::Subscriber sub_scan_
Definition: e2e.cpp:73
e2e_utils.h
xmlrpc_manager.h
E2E::raw_scans_
std::vector< urg_sim::RawScanData::Ptr > raw_scans_
Definition: e2e.cpp:84
params
std::vector< urg_sim::URGSimulator::Params > params({ { .model=urg_sim::URGSimulator::Model::UTM,.boot_duration=0.01,.comm_delay_base=0.0002,.comm_delay_sigma=0.0004,.scan_interval=0.02505,.clock_rate=1.0,.hex_ii_timestamp=false,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UTM,.boot_duration=0.01,.comm_delay_base=0.0002,.comm_delay_sigma=0.0004,.scan_interval=0.02505,.clock_rate=1.001,.hex_ii_timestamp=false,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UTM,.boot_duration=0.01,.comm_delay_base=0.0002,.comm_delay_sigma=0.0004,.scan_interval=0.02505,.clock_rate=0.999,.hex_ii_timestamp=false,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UST_UUST1,.boot_duration=0.01,.comm_delay_base=0.0005,.comm_delay_sigma=0.0005,.scan_interval=0.02505,.clock_rate=1.0,.hex_ii_timestamp=true,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UST_UUST1,.boot_duration=0.01,.comm_delay_base=0.0005,.comm_delay_sigma=0.0005,.scan_interval=0.02505,.clock_rate=1.001,.hex_ii_timestamp=true,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UST_UUST1,.boot_duration=0.01,.comm_delay_base=0.0005,.comm_delay_sigma=0.0005,.scan_interval=0.02505,.clock_rate=0.999,.hex_ii_timestamp=true,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UST_UUST2,.boot_duration=0.01,.comm_delay_base=0.0002,.comm_delay_sigma=0.0002,.scan_interval=0.02505,.clock_rate=1.0,.hex_ii_timestamp=true,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UST_UUST2,.boot_duration=0.01,.comm_delay_base=0.0002,.comm_delay_sigma=0.0002,.scan_interval=0.02505,.clock_rate=1.001,.hex_ii_timestamp=true,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, { .model=urg_sim::URGSimulator::Model::UST_UUST2,.boot_duration=0.01,.comm_delay_base=0.0002,.comm_delay_sigma=0.0002,.scan_interval=0.02505,.clock_rate=0.999,.hex_ii_timestamp=true,.angle_resolution=1440,.angle_min=0,.angle_max=1080,.angle_front=540, }, })
XmlRpc.h
ros::NodeHandle::subscribe
Subscriber subscribe(const std::string &topic, uint32_t queue_size, const boost::function< void(C)> &callback, const VoidConstPtr &tracked_object=VoidConstPtr(), const TransportHints &transport_hints=TransportHints())
urg_sim::URGSimulator::getLocalEndpoint
boost::asio::ip::tcp::endpoint getLocalEndpoint() const
Definition: urg_sim.h:144
ros::Rate::sleep
bool sleep()
E2E::th_sim_
std::thread th_sim_
Definition: e2e.cpp:77
urg_sim::URGSimulator::spin
void spin()
Definition: urg_sim.cpp:779
E2E::E2E
E2E()
Definition: e2e.cpp:53
E2E::true_stamps_
std::map< int, ros::Time > true_stamps_
Definition: e2e.cpp:85
E2EWithParam
Definition: e2e.cpp:149
ros::Time
E2E::TearDown
void TearDown()
Definition: e2e.cpp:61
E2E::sim_killed_
bool sim_killed_
Definition: e2e.cpp:78
E2E::cbRawScanData
void cbRawScanData(const urg_sim::RawScanData::Ptr data)
Definition: e2e.cpp:97
shutdownNode
bool shutdownNode(const std::string &name)
Definition: e2e_utils.h:30
urg_sim.h
E2E::cbStatus
void cbStatus(const urg_stamped::Status ::ConstPtr &msg)
Definition: e2e.cpp:92
E2E::sub_status_
ros::Subscriber sub_status_
Definition: e2e.cpp:74
param
T param(const std::string &param_name, const T &default_val)
ros::Rate
ros::Duration::sleep
bool sleep() const
E2E::nh_
ros::NodeHandle nh_
Definition: e2e.cpp:71
E2E::cnt_
int cnt_
Definition: e2e.cpp:81
E2E::waitScans
void waitScans(const size_t num, const ros::Duration &timeout)
Definition: e2e.cpp:105
ros::Duration
TEST_F
TEST_F(E2E, RebootOnError)
Definition: e2e.cpp:338
E2E::scans_
std::vector< sensor_msgs::LaserScan::ConstPtr > scans_
Definition: e2e.cpp:83
ros::NodeHandle
ros::Subscriber
ros::Time::fromBoost
static Time fromBoost(const boost::posix_time::ptime &t)
ros::Time::now
static Time now()


urg_stamped
Author(s): Atsushi Watanabe
autogenerated on Wed Dec 18 2024 03:10:57