urg_stamped.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2018-2021 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 <ros/console.h>
18 #include <ros/ros.h>
19 #include <sensor_msgs/LaserScan.h>
20 
21 #include <boost/bind/bind.hpp>
22 #include <boost/format.hpp>
23 #include <boost/thread.hpp>
24 
25 #include <algorithm>
26 #include <list>
27 #include <map>
28 #include <random>
29 #include <string>
30 #include <vector>
31 
32 #include <scip2/scip2.h>
33 #include <scip2/walltime.h>
34 
39 #include <urg_stamped/ros_logger.h>
40 
42 
43 namespace urg_stamped
44 {
46  const boost::posix_time::ptime& time_read,
47  const std::string& echo_back,
48  const std::string& status,
49  const scip2::ScanData& scan,
50  const bool has_intensity)
51 {
52  if (status != "99")
53  {
54  if (status != "00")
55  {
56  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
57  errorCountIncrement(status);
58  }
59  return;
60  }
61 
62  const uint64_t walltime_device = walltime_.update(scan.timestamp_);
63  if (detectDeviceTimeJump(time_read, walltime_device))
64  {
65  errorCountIncrement(status);
66  return;
67  }
69 
70  const auto estimated_timestamp_lf =
72  ros::Duration().fromNSec(walltime_device * 1e6 * device_time_origin_.gain_) +
73  ros::Duration(msg_base_.time_increment * step_min_);
74 
75  if (t0_ == ros::Time(0))
76  t0_ = estimated_timestamp_lf;
77 
78  const auto receive_time =
80  ros::Time::fromBoost(time_read) -
82  ros::Duration(msg_base_.scan_time));
83 
84  sensor_msgs::LaserScan msg(msg_base_);
85  msg.header.stamp =
87  t0_ +
89  timestamp_lpf_.update((estimated_timestamp_lf - t0_).toSec()) +
90  timestamp_hpf_.update((receive_time - t0_).toSec())));
91 
92  if (scan.ranges_.size() != step_max_ - step_min_ + 1)
93  {
94  ROS_DEBUG("Size of the received scan data is wrong (expected: %d, received: %lu); refreshing",
95  step_max_ - step_min_ + 1, scan.ranges_.size());
96  scip_->sendCommand(
97  (has_intensity ? "ME" : "MD") +
98  (boost::format("%04d%04d") % step_min_ % step_max_).str() +
99  "00000");
100  return;
101  }
102 
103  msg.ranges.reserve(scan.ranges_.size());
104  for (auto& r : scan.ranges_)
105  msg.ranges.push_back(r * 1e-3);
106  if (has_intensity)
107  {
108  msg.intensities.reserve(scan.intensities_.size());
109  for (auto& r : scan.intensities_)
110  msg.intensities.push_back(r);
111  }
112 
113  pub_scan_.publish(msg);
114 }
115 
116 void UrgStampedNode::cbTMSend(const boost::posix_time::ptime& time_send)
117 {
118  time_tm_request = time_send;
119 }
120 
122  const boost::posix_time::ptime& time_read,
123  const std::string& echo_back,
124  const std::string& status,
125  const scip2::Timestamp& time_device)
126 {
127  if (status != "00")
128  {
129  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
130  errorCountIncrement(status);
131 
132  if (echo_back[2] == '0' && delay_estim_state_ == DelayEstimState::ESTIMATION_STARTING)
133  {
134  ROS_INFO(
135  "Failed to enter the time synchronization mode, "
136  "even after receiving successful QT command response. "
137  "QT command may be ignored by the sensor firmware");
139  }
140  return;
141  }
142 
144  switch (echo_back[2])
145  {
146  case '0':
147  {
148  ROS_DEBUG("Entered the time synchronization mode");
150  scip_->sendCommand(
151  "TM1",
152  boost::bind(&UrgStampedNode::cbTMSend, this, boost::arg<1>()));
153  break;
154  }
155  case '1':
156  {
157  const uint64_t walltime_device = walltime_.update(time_device.timestamp_);
158  if (detectDeviceTimeJump(time_read, walltime_device))
159  {
160  errorCountIncrement(status);
161  break;
162  }
163 
164  const auto delay =
165  ros::Time::fromBoost(time_read) -
167  communication_delays_.push_back(delay);
169  communication_delays_.pop_front();
170 
172  time_tm_request, time_read, walltime_device);
173  device_time_origins_.push_back(origin);
175  device_time_origins_.pop_front();
176 
177  if (communication_delays_.size() >= tm_iter_num_)
178  {
179  std::vector<ros::Duration> delays(communication_delays_.begin(), communication_delays_.end());
180  std::vector<ros::Time> origins(device_time_origins_.begin(), device_time_origins_.end());
181  sort(delays.begin(), delays.end());
182  sort(origins.begin(), origins.end());
183 
185  {
188  }
189  else
190  {
194  }
196  ROS_DEBUG("delay: %0.6f, device timestamp: %ld, device time origin: %0.6f",
198  walltime_device,
199  origins[tm_iter_num_ / 2].toSec());
200  scip_->sendCommand("TM2");
201  }
202  else
203  {
204  ros::Duration(0.005).sleep();
205  scip_->sendCommand(
206  "TM1",
207  boost::bind(&UrgStampedNode::cbTMSend, this, boost::arg<1>()));
208  }
209  break;
210  }
211  case '2':
212  {
214  scip_->sendCommand(
215  (publish_intensity_ ? "ME" : "MD") +
216  (boost::format("%04d%04d") % step_min_ % step_max_).str() +
217  "00000");
218  timeSync();
221  t0_ = ros::Time();
222  ROS_DEBUG("Leaving the time synchronization mode");
223  break;
224  }
225  }
226 }
227 
229  const boost::posix_time::ptime& time_read,
230  const std::string& echo_back,
231  const std::string& status,
232  const std::map<std::string, std::string>& params)
233 {
234  if (status != "00")
235  {
236  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
237  errorCountIncrement(status);
238  return;
239  }
240 
241  const auto amin = params.find("AMIN");
242  const auto amax = params.find("AMAX");
243  const auto dmin = params.find("DMIN");
244  const auto dmax = params.find("DMAX");
245  const auto ares = params.find("ARES");
246  const auto afrt = params.find("AFRT");
247  const auto scan = params.find("SCAN");
248  if (amin == params.end() || amax == params.end() ||
249  dmin == params.end() || dmax == params.end() ||
250  ares == params.end() || afrt == params.end() ||
251  scan == params.end())
252  {
253  ROS_ERROR("PP doesn't have required parameters");
254  return;
255  }
256  step_min_ = std::stoi(amin->second);
257  step_max_ = std::stoi(amax->second);
258  msg_base_.scan_time = 60.0 / std::stoi(scan->second);
259  msg_base_.angle_increment = 2.0 * M_PI / std::stoi(ares->second);
260  msg_base_.time_increment = msg_base_.scan_time / std::stoi(ares->second);
261  msg_base_.range_min = std::stoi(dmin->second) * 1e-3;
262  msg_base_.range_max = std::stoi(dmax->second) * 1e-3;
263  msg_base_.angle_min =
264  (std::stoi(amin->second) - std::stoi(afrt->second)) * msg_base_.angle_increment;
265  msg_base_.angle_max =
266  (std::stoi(amax->second) - std::stoi(afrt->second)) * msg_base_.angle_increment;
267 
270  delayEstimation();
271 }
272 
274  const boost::posix_time::ptime& time_read,
275  const std::string& echo_back,
276  const std::string& status,
277  const std::map<std::string, std::string>& params)
278 {
279  if (status != "00")
280  {
281  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
282  errorCountIncrement(status);
283  return;
284  }
285 }
286 
287 void UrgStampedNode::cbIISend(const boost::posix_time::ptime& time_send)
288 {
289  time_ii_request = time_send;
290 }
291 
293  const boost::posix_time::ptime& time_read,
294  const std::string& echo_back,
295  const std::string& status,
296  const std::map<std::string, std::string>& params)
297 {
298  if (status != "00")
299  {
300  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
301  errorCountIncrement(status);
302  return;
303  }
304 
305  const auto delay =
306  ros::Time::fromBoost(time_read) -
308 
309  if (delay.toSec() < 0.002)
310  {
311  const auto time = params.find("TIME");
312  if (time == params.end())
313  {
314  ROS_DEBUG("II doesn't have timestamp");
315  return;
316  }
317  if (time->second.size() != 6 && time->second.size() != 4)
318  {
319  ROS_DEBUG("Timestamp in II is ill-formatted (%s)", time->second.c_str());
320  return;
321  }
322  const uint32_t time_device =
323  time->second.size() == 6 ?
324  std::stoi(time->second, nullptr, 16) :
325  *(scip2::Decoder<4>(time->second).begin());
326 
327  const uint64_t walltime_device = walltime_.update(time_device);
328  if (detectDeviceTimeJump(time_read, walltime_device))
329  {
330  errorCountIncrement(status);
331  return;
332  }
333 
334  ros::Time time_at_device_timestamp;
336  time_read, walltime_device, estimated_communication_delay_, time_at_device_timestamp);
337 
338  const auto now = ros::Time::fromBoost(time_read);
339  if (last_sync_time_ == ros::Time(0))
340  last_sync_time_ = now;
341  const double dt = std::min((now - last_sync_time_).toSec(), 10.0);
342  last_sync_time_ = now;
343 
344  const double gain =
345  (time_at_device_timestamp - device_time_origin_.origin_).toSec() /
346  (time_at_device_timestamp - origin).toSec();
347  const double exp_lpf_alpha =
348  dt * (1.0 / 30.0); // 30 seconds exponential LPF
349  const double updated_gain =
350  (1.0 - exp_lpf_alpha) * device_time_origin_.gain_ + exp_lpf_alpha * gain;
351  device_time_origin_.gain_ = updated_gain;
353  ros::Duration(exp_lpf_alpha * (origin - device_time_origin_.origin_).toSec());
354 
355  ROS_DEBUG("on scan delay: %0.6f, device timestamp: %ld, device time origin: %0.6f, gain: %0.6f",
356  delay.toSec(),
357  walltime_device,
358  origin.toSec(),
359  updated_gain);
360  }
361  else
362  {
363  ROS_DEBUG("on scan delay (%0.6f) is larger than expected; skipping",
364  delay.toSec());
365  }
366 }
367 
369  const boost::posix_time::ptime& time_read,
370  const std::string& echo_back,
371  const std::string& status)
372 {
373  if (status != "00")
374  {
375  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
376  errorCountIncrement(status);
377  return;
378  }
379 
380  ROS_DEBUG("Scan data stopped");
381 
383  {
385  retryTM();
386  }
387 }
388 
390  const boost::posix_time::ptime& time_read,
391  const std::string& echo_back,
392  const std::string& status)
393 {
394  if (status != "00" && status != "01")
395  {
396  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
397  ROS_ERROR("Failed to reboot. Please power-off the sensor.");
398  }
399 }
400 
402  const boost::posix_time::ptime& time_read,
403  const std::string& echo_back,
404  const std::string& status)
405 {
406  if (status != "00")
407  {
408  ROS_ERROR("%s errored with %s", echo_back.c_str(), status.c_str());
409  ROS_ERROR("Failed to reset. Rebooting the sensor and exiting.");
410  hardReset();
411  return;
412  }
413  ROS_INFO("Sensor reset succeeded");
414  if (!device_initialized_)
415  {
416  device_initialized_ = true;
417  scip_->sendCommand("PP");
418  }
419 }
420 
422 {
423  scip_->sendCommand("RS");
424  device_->startWatchdog(boost::posix_time::seconds(1));
425 }
426 
428 {
429  scip_->sendCommand(
430  "II",
431  boost::bind(&UrgStampedNode::cbIISend, this, boost::arg<1>()));
434  &UrgStampedNode::timeSync, this, true);
435 }
436 
438 {
439  timer_sync_.stop(); // Stop timer for sync using II command.
440  ROS_DEBUG("Starting communication delay estimation");
444  ros::Duration(0.1),
445  &UrgStampedNode::retryTM, this);
446  retryTM();
447 }
448 
450 {
451  switch (delay_estim_state_)
452  {
454  ROS_DEBUG("Stopping scan");
455  scip_->sendCommand("QT");
456  break;
458  ROS_DEBUG("Entering the time synchronization mode");
459  // The sensor sometimes doesn't stop scan
460  // even if successful QT response was returned.
461  // Send the QT command again to avoid it.
462  scip_->sendCommand("QT");
463  scip_->sendCommand("TM0");
464  break;
466  ROS_WARN("Timeout occured during the time synchronization");
467  scip_->sendCommand("TM2");
468  break;
469  default:
470  break;
471  }
472 }
473 
474 void UrgStampedNode::errorCountIncrement(const std::string& status)
475 {
476  if (status == "00")
477  return;
478 
479  if (status == "0L")
480  {
483  {
484  ROS_ERROR("Error count exceeded limit, rebooting the sensor and exiting.");
485  hardReset();
486  }
487  }
488  else
489  {
492  {
493  ROS_ERROR("Error count exceeded limit, resetting the sensor and exiting.");
494  softReset();
495  }
496  }
497 }
498 
500 {
501  scip_->sendCommand("RS");
502  ros::Duration(0.05).sleep();
503  ros::shutdown();
504 }
505 
507 {
508  scip_->sendCommand("RB");
509  scip_->sendCommand("RB"); // Sending it 2 times in 1 sec. is needed
510  ros::Duration(0.05).sleep();
511  ros::shutdown();
512 }
513 
515  const boost::posix_time::ptime& time_response,
516  const uint64_t& device_timestamp)
517 {
518  ros::Time time_at_device_timestamp;
519  const ros::Time current_origin =
521  time_response, device_timestamp, estimated_communication_delay_, time_at_device_timestamp);
522 
525 
526  if (jumped)
527  {
528  ROS_ERROR(
529  "Device time origin jumped.\n"
530  "last origin: %0.3f, current origin: %0.3f, "
531  "allowed_device_time_origin_diff: %0.3f, device_timestamp: %ld",
532  device_time_origin_.origin_.toSec(), current_origin.toSec(),
533  allowed_device_time_origin_diff_, device_timestamp);
534  }
535  return jumped;
536 }
537 
539  : nh_("")
540  , pnh_("~")
541  , device_initialized_(false)
543  , tm_iter_num_(5)
544  , tm_median_window_(35)
547  , last_sync_time_(0)
548  , timestamp_lpf_(20)
549  , timestamp_hpf_(20)
550  , timestamp_outlier_removal_(ros::Duration(0.001), ros::Duration())
551  , timestamp_moving_average_(5, ros::Duration())
552 {
553  std::string ip;
554  int port;
555  double sync_interval_min;
556  double sync_interval_max;
557  double delay_estim_interval;
558 
559  pnh_.param("ip_address", ip, std::string("192.168.0.10"));
560  pnh_.param("ip_port", port, 10940);
561  pnh_.param("frame_id", msg_base_.header.frame_id, std::string("laser"));
562  pnh_.param("publish_intensity", publish_intensity_, true);
563  pnh_.param("sync_interval_min", sync_interval_min, 1.0);
564  pnh_.param("sync_interval_max", sync_interval_max, 1.5);
565  sync_interval_ = std::uniform_real_distribution<double>(sync_interval_min, sync_interval_max);
566  pnh_.param("delay_estim_interval", delay_estim_interval, 20.0);
567  pnh_.param("error_limit", error_count_max_, 4);
568  pnh_.param("allowed_device_time_origin_diff", allowed_device_time_origin_diff_, 1.0);
569 
570  bool debug;
571  pnh_.param("debug", debug, false);
572  if (debug)
573  {
574  // Enable debug level log at the beginning of the node to show initialization related logs.
576  {
578  }
579  }
580 
581  pub_scan_ = nh_.advertise<sensor_msgs::LaserScan>("scan", 10);
582 
583  device_.reset(new scip2::ConnectionTcp(ip, port));
584  device_->registerCloseCallback(ros::shutdown);
585  device_->registerConnectCallback(
586  boost::bind(&UrgStampedNode::cbConnect, this));
587 
588  scip_.reset(new scip2::Protocol(device_));
589  scip_->registerCallback<scip2::ResponsePP>(
590  boost::bind(&UrgStampedNode::cbPP, this,
591  boost::arg<1>(),
592  boost::arg<2>(),
593  boost::arg<3>(),
594  boost::arg<4>()));
595  scip_->registerCallback<scip2::ResponseVV>(
596  boost::bind(&UrgStampedNode::cbVV, this,
597  boost::arg<1>(),
598  boost::arg<2>(),
599  boost::arg<3>(),
600  boost::arg<4>()));
601  scip_->registerCallback<scip2::ResponseII>(
602  boost::bind(&UrgStampedNode::cbII, this,
603  boost::arg<1>(),
604  boost::arg<2>(),
605  boost::arg<3>(),
606  boost::arg<4>()));
607  scip_->registerCallback<scip2::ResponseMD>(
608  boost::bind(&UrgStampedNode::cbM, this,
609  boost::arg<1>(),
610  boost::arg<2>(),
611  boost::arg<3>(),
612  boost::arg<4>(),
613  false));
614  scip_->registerCallback<scip2::ResponseME>(
615  boost::bind(&UrgStampedNode::cbM, this,
616  boost::arg<1>(),
617  boost::arg<2>(),
618  boost::arg<3>(),
619  boost::arg<4>(),
620  true));
621  scip_->registerCallback<scip2::ResponseTM>(
622  boost::bind(&UrgStampedNode::cbTM, this,
623  boost::arg<1>(),
624  boost::arg<2>(),
625  boost::arg<3>(),
626  boost::arg<4>()));
627  scip_->registerCallback<scip2::ResponseQT>(
628  boost::bind(&UrgStampedNode::cbQT, this,
629  boost::arg<1>(),
630  boost::arg<2>(),
631  boost::arg<3>()));
632  scip_->registerCallback<scip2::ResponseRB>(
633  boost::bind(&UrgStampedNode::cbRB, this,
634  boost::arg<1>(),
635  boost::arg<2>(),
636  boost::arg<3>()));
637  scip_->registerCallback<scip2::ResponseRS>(
638  boost::bind(&UrgStampedNode::cbRS, this,
639  boost::arg<1>(),
640  boost::arg<2>(),
641  boost::arg<3>()));
642 
643  if (delay_estim_interval > 0.0)
644  {
646  ros::Duration(delay_estim_interval), &UrgStampedNode::delayEstimation, this);
647  }
648 }
649 
651 {
652  boost::thread thread(
653  boost::bind(&scip2::Connection::spin, device_.get()));
654  ros::spin();
655  timer_sync_.stop();
657  scip_->sendCommand("QT");
658  device_->stop();
659 }
660 } // namespace urg_stamped
ros::Time update(const ros::Time &stamp)
void cbTM(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status, const scip2::Timestamp &time_device)
scip2::Protocol::Ptr scip_
Definition: urg_stamped.h:60
scip2::Walltime< 24 > walltime_
Definition: urg_stamped.h:89
ROSCONSOLE_DECL void notifyLoggerLevelsChanged()
void publish(const boost::shared_ptr< M > &message) const
void cbM(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status, const scip2::ScanData &scan, const bool has_intensity)
Definition: urg_stamped.cpp:45
std::vector< int32_t > ranges_
Definition: stream.h:36
bool detectDeviceTimeJump(const boost::posix_time::ptime &time_response, const uint64_t &device_timestamp)
void cbQT(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status)
bool sleep() const
void stop()
sensor_msgs::LaserScan msg_base_
Definition: urg_stamped.h:55
void cbTMSend(const boost::posix_time::ptime &time_send)
void cbRS(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status)
void cbPP(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status, const std::map< std::string, std::string > &params)
DelayEstimState delay_estim_state_
Definition: urg_stamped.h:73
void cbRB(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status)
#define ROS_WARN(...)
void setInterval(const ros::Duration &interval)
uint32_t timestamp_
Definition: stream.h:35
void cbVV(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status, const std::map< std::string, std::string > &params)
std::uniform_real_distribution< double > sync_interval_
Definition: urg_stamped.h:92
ResponseErrorCount error_count_
Definition: urg_stamped.h:111
void retryTM(const ros::TimerEvent &event=ros::TimerEvent())
std::default_random_engine random_engine_
Definition: urg_stamped.h:91
std::list< ros::Duration > communication_delays_
Definition: urg_stamped.h:75
double communication_delay_filter_alpha_
Definition: urg_stamped.h:81
std::list< ros::Time > device_time_origins_
Definition: urg_stamped.h:76
uint64_t update(const uint32_t &time_device)
Definition: walltime.h:35
#define ROS_INFO(...)
boost::posix_time::ptime time_ii_request
Definition: urg_stamped.h:83
bool param(const std::string &param_name, T &param_val, const T &default_val) const
FirstOrderHPF< double > timestamp_hpf_
Definition: urg_stamped.h:97
boost::posix_time::ptime time_tm_request
Definition: urg_stamped.h:74
std::ostream & debug()
Definition: logger.cpp:93
ros::Duration estimated_communication_delay_
Definition: urg_stamped.h:77
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
ros::Time estimateOriginByAverage(const boost::posix_time::ptime &time_request, const boost::posix_time::ptime &time_response, const uint64_t &device_timestamp)
ros::Time update(const ros::Time &stamp)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
ROSCPP_DECL void spin()
bool detectTimeJump(const ros::Time &last_device_time_origin, const ros::Time &current_device_time_origin, const double allowed_device_time_origin_diff)
std::vector< int32_t > intensities_
Definition: stream.h:37
void timeSync(const ros::TimerEvent &event=ros::TimerEvent())
uint32_t timestamp_
Definition: time_sync.h:33
void delayEstimation(const ros::TimerEvent &event=ros::TimerEvent())
device_time_origin::DriftedTime device_time_origin_
Definition: urg_stamped.h:86
void cbII(const boost::posix_time::ptime &time_read, const std::string &echo_back, const std::string &status, const std::map< std::string, std::string > &params)
ros::Time estimateOrigin(const boost::posix_time::ptime &time_response, const uint64_t &device_timestamp, const ros::Duration &communication_delay, ros::Time &time_at_device_timestamp)
Duration & fromNSec(int64_t t)
FirstOrderLPF< double > timestamp_lpf_
Definition: urg_stamped.h:96
static Time fromBoost(const boost::posix_time::ptime &t)
ROSCPP_DECL void shutdown()
ROSCONSOLE_DECL bool set_logger_level(const std::string &name, levels::Level level)
void cbIISend(const boost::posix_time::ptime &time_send)
virtual void spin()=0
TimestampMovingAverage timestamp_moving_average_
Definition: urg_stamped.h:99
ros::Publisher pub_scan_
Definition: urg_stamped.h:50
#define ROS_ERROR(...)
void setInterval(const ros::Duration &interval)
TimestampOutlierRemover timestamp_outlier_removal_
Definition: urg_stamped.h:98
#define ROSCONSOLE_DEFAULT_NAME
void errorCountIncrement(const std::string &status)
scip2::Connection::Ptr device_
Definition: urg_stamped.h:59
#define ROS_DEBUG(...)


urg_stamped
Author(s): Atsushi Watanabe
autogenerated on Tue May 11 2021 02:14:05