sys_time.cpp
Go to the documentation of this file.
1 
9 /*
10  * Copyright 2014,2015,2016,2017 Vladimir Ermakov, M.H.Kabir.
11  *
12  * This file is part of the mavros package and subject to the license terms
13  * in the top-level LICENSE file of the mavros repository.
14  * https://github.com/mavlink/mavros/tree/master/LICENSE.md
15  */
16 
17 #include <mavros/mavros_plugin.h>
18 
19 #include <sensor_msgs/TimeReference.h>
20 #include <std_msgs/Duration.h>
21 #include <mavros_msgs/TimesyncStatus.h>
22 #include <rosgraph_msgs/Clock.h>
23 
24 namespace mavros {
25 namespace std_plugins {
32 {
33 public:
34  TimeSyncStatus(const std::string &name, size_t win_size) :
36  times_(win_size),
37  seq_nums_(win_size),
38  window_size_(win_size),
39  min_freq_(0.01),
40  max_freq_(10),
41  tolerance_(0.1),
42  last_rtt(0),
43  rtt_sum(0),
44  last_remote_ts(0),
45  offset(0)
46  {
47  clear();
48  }
49 
50  void clear()
51  {
52  std::lock_guard<std::mutex> lock(mutex);
53 
54  ros::Time curtime = ros::Time::now();
55  count_ = 0;
56  rtt_sum = 0;
57 
58  for (size_t i = 0; i < window_size_; i++)
59  {
60  times_[i] = curtime;
61  seq_nums_[i] = count_;
62  }
63 
64  hist_indx_ = 0;
65  }
66 
67  void tick(int64_t rtt_ns, uint64_t remote_timestamp_ns, int64_t time_offset_ns)
68  {
69  std::lock_guard<std::mutex> lock(mutex);
70 
71  count_++;
72  last_rtt = rtt_ns;
73  rtt_sum += rtt_ns;
74  last_remote_ts = remote_timestamp_ns;
75  offset = time_offset_ns;
76  }
77 
78  void set_timestamp(uint64_t remote_timestamp_ns)
79  {
80  std::lock_guard<std::mutex> lock(mutex);
81  last_remote_ts = remote_timestamp_ns;
82  }
83 
85  {
86  std::lock_guard<std::mutex> lock(mutex);
87 
88  ros::Time curtime = ros::Time::now();
89  int curseq = count_;
90  int events = curseq - seq_nums_[hist_indx_];
91  double window = (curtime - times_[hist_indx_]).toSec();
92  double freq = events / window;
93  seq_nums_[hist_indx_] = curseq;
94  times_[hist_indx_] = curtime;
96 
97  if (events == 0) {
98  stat.summary(2, "No events recorded.");
99  }
100  else if (freq < min_freq_ * (1 - tolerance_)) {
101  stat.summary(1, "Frequency too low.");
102  }
103  else if (freq > max_freq_ * (1 + tolerance_)) {
104  stat.summary(1, "Frequency too high.");
105  }
106  else {
107  stat.summary(0, "Normal");
108  }
109 
110  stat.addf("Timesyncs since startup", "%d", count_);
111  stat.addf("Frequency (Hz)", "%f", freq);
112  stat.addf("Last RTT (ms)", "%0.6f", last_rtt / 1e6);
113  stat.addf("Mean RTT (ms)", "%0.6f", (count_) ? rtt_sum / count_ / 1e6 : 0.0);
114  stat.addf("Last remote time (s)", "%0.9f", last_remote_ts / 1e9);
115  stat.addf("Estimated time offset (s)", "%0.9f", offset / 1e9);
116  }
117 
118 private:
119  int count_;
120  std::vector<ros::Time> times_;
121  std::vector<int> seq_nums_;
124  const size_t window_size_;
125  const double min_freq_;
126  const double max_freq_;
127  const double tolerance_;
128  int64_t last_rtt;
129  int64_t rtt_sum;
130  uint64_t last_remote_ts;
131  int64_t offset;
132 };
133 
134 
139 public:
140  SystemTimePlugin() : PluginBase(),
141  nh("~"),
142  dt_diag("Time Sync", 10),
143  time_offset(0.0),
144  time_skew(0.0),
145  sequence(0),
146  filter_alpha(0),
147  filter_beta(0),
148  high_rtt_count(0),
149  high_deviation_count(0)
150  { }
151 
153 
154  void initialize(UAS &uas_) override
155  {
156  PluginBase::initialize(uas_);
157 
158  double conn_system_time_d;
159  double conn_timesync_d;
160  std::string ts_mode_str;
161 
162  ros::WallDuration conn_system_time;
163  ros::WallDuration conn_timesync;
164 
165  if (nh.getParam("conn/system_time_rate", conn_system_time_d) && conn_system_time_d != 0.0) {
166  conn_system_time = ros::WallDuration(ros::Rate(conn_system_time_d));
167  }
168 
169  if (nh.getParam("conn/timesync_rate", conn_timesync_d) && conn_timesync_d != 0.0) {
170  conn_timesync = ros::WallDuration(ros::Rate(conn_timesync_d));
171  }
172 
173  nh.param<std::string>("time/time_ref_source", time_ref_source, "fcu");
174  nh.param<std::string>("time/timesync_mode", ts_mode_str, "MAVLINK");
175 
176  // Filter gains
177  //
178  // Alpha : Used to smooth the overall clock offset estimate. Smaller values will lead
179  // to a smoother estimate, but track time drift more slowly, introducing a bias
180  // in the estimate. Larger values will cause low-amplitude oscillations.
181  //
182  // Beta : Used to smooth the clock skew estimate. Smaller values will lead to a
183  // tighter estimation of the skew (derivative), but will negatively affect how fast the
184  // filter reacts to clock skewing (e.g cause by temperature changes to the oscillator).
185  // Larger values will cause large-amplitude oscillations.
186  nh.param("time/timesync_alpha_initial", filter_alpha_initial, 0.05f);
187  nh.param("time/timesync_beta_initial", filter_beta_initial, 0.05f);
188  nh.param("time/timesync_alpha_final", filter_alpha_final, 0.003f);
189  nh.param("time/timesync_beta_final", filter_beta_final, 0.003f);
190  filter_alpha = filter_alpha_initial;
191  filter_beta = filter_beta_initial;
192 
193  // Filter gain scheduling
194  //
195  // The filter interpolates between the initial and final gains while the number of
196  // exhanged timesync packets is less than convergence_window. A lower value will
197  // allow the timesync to converge faster, but with potentially less accurate initial
198  // offset and skew estimates.
199  nh.param("time/convergence_window", convergence_window, 500);
200 
201  // Outlier rejection and filter reset
202  //
203  // Samples with round-trip time higher than max_rtt_sample are not used to update the filter.
204  // More than max_consecutive_high_rtt number of such events in a row will throw a warning
205  // but not reset the filter.
206  // Samples whose calculated clock offset is more than max_deviation_sample off from the current
207  // estimate are not used to update the filter. More than max_consecutive_high_deviation number
208  // of such events in a row will reset the filter. This usually happens only due to a time jump
209  // on the remote system.
210  nh.param("time/max_rtt_sample", max_rtt_sample, 10); // in ms
211  nh.param("time/max_deviation_sample", max_deviation_sample, 100); // in ms
212  nh.param("time/max_consecutive_high_rtt", max_cons_high_rtt, 5);
213  nh.param("time/max_consecutive_high_deviation", max_cons_high_deviation, 5);
214 
215  // Set timesync mode
216  auto ts_mode = utils::timesync_mode_from_str(ts_mode_str);
217  m_uas->set_timesync_mode(ts_mode);
218  ROS_INFO_STREAM_NAMED("time", "TM: Timesync mode: " << utils::to_string(ts_mode));
219 
220  nh.param("time/publish_sim_time", publish_sim_time, false);
221  if (publish_sim_time) {
222  sim_time_pub = nh.advertise<rosgraph_msgs::Clock>("/clock", 10);
223  ROS_INFO_STREAM_NAMED("time", "TM: Publishing sim time");
224  } else {
225  ROS_INFO_STREAM_NAMED("time", "TM: Not publishing sim time");
226  }
227  time_ref_pub = nh.advertise<sensor_msgs::TimeReference>("time_reference", 10);
228 
229  timesync_status_pub = nh.advertise<mavros_msgs::TimesyncStatus>("timesync_status", 10);
230 
231  // timer for sending system time messages
232  if (!conn_system_time.isZero()) {
233  sys_time_timer = nh.createWallTimer(conn_system_time,
235  sys_time_timer.start();
236  }
237 
238  // timer for sending timesync messages
239  if (!conn_timesync.isZero() && !(ts_mode == TSM::NONE || ts_mode == TSM::PASSTHROUGH)) {
240  // enable timesync diag only if that feature enabled
241  UAS_DIAG(m_uas).add(dt_diag);
242 
243  timesync_timer = nh.createWallTimer(conn_timesync,
245  timesync_timer.start();
246  }
247  }
248 
250  {
251  return {
253  make_handler(&SystemTimePlugin::handle_timesync),
254  };
255  }
256 
257 private:
262 
265 
267 
268  std::string time_ref_source;
269 
270  // Estimated statistics
271  double time_offset;
272  double time_skew;
273 
274  // Filter parameters
275  uint32_t sequence;
276  double filter_alpha;
277  double filter_beta;
278 
279  // Filter settings
285 
286  // Outlier rejection
293 
295 
296  void handle_system_time(const mavlink::mavlink_message_t *msg, mavlink::common::msg::SYSTEM_TIME &mtime)
297  {
298  // date -d @1234567890: Sat Feb 14 02:31:30 MSK 2009
299  const bool fcu_time_valid = mtime.time_unix_usec > 1234567890ULL * 1000000;
300 
301  if (fcu_time_valid) {
302  // continious publish for ntpd
303  auto time_unix = boost::make_shared<sensor_msgs::TimeReference>();
304  ros::Time time_ref(
305  mtime.time_unix_usec / 1000000, // t_sec
306  (mtime.time_unix_usec % 1000000) * 1000); // t_nsec
307 
308  time_unix->header.stamp = ros::Time::now();
309  time_unix->time_ref = time_ref;
310  time_unix->source = time_ref_source;
311 
312  time_ref_pub.publish(time_unix);
313  if(publish_sim_time) {
314  auto clock = boost::make_shared<rosgraph_msgs::Clock>();
315  clock->clock = time_ref;
316  sim_time_pub.publish(clock);
317  }
318  }
319  else {
320  ROS_WARN_THROTTLE_NAMED(60, "time", "TM: Wrong FCU time.");
321  }
322  }
323 
324  void handle_timesync(const mavlink::mavlink_message_t *msg, mavlink::common::msg::TIMESYNC &tsync)
325  {
326  uint64_t now_ns = ros::Time::now().toNSec();
327 
328  if (tsync.tc1 == 0) {
329  send_timesync_msg(now_ns, tsync.ts1);
330  return;
331  }
332  else if (tsync.tc1 > 0) {
333  // Time offset between this system and the remote system is calculated assuming RTT for
334  // the timesync packet is roughly equal both ways.
335  add_timesync_observation((tsync.ts1 + now_ns - tsync.tc1 * 2) / 2, tsync.ts1, tsync.tc1);
336  }
337  }
338 
339  void sys_time_cb(const ros::WallTimerEvent &event)
340  {
341  // For filesystem only
342  uint64_t time_unix_usec = ros::Time::now().toNSec() / 1000; // nano -> micro
343 
344  mavlink::common::msg::SYSTEM_TIME mtime {};
345  mtime.time_unix_usec = time_unix_usec;
346 
347  UAS_FCU(m_uas)->send_message_ignore_drop(mtime);
348  }
349 
350  void timesync_cb(const ros::WallTimerEvent &event)
351  {
352  auto ts_mode = m_uas->get_timesync_mode();
353  if (ts_mode == TSM::MAVLINK) {
354  send_timesync_msg(0, ros::Time::now().toNSec());
355  } else if (ts_mode == TSM::ONBOARD) {
356  // Calculate offset between CLOCK_REALTIME (ros::WallTime) and CLOCK_MONOTONIC
357  uint64_t realtime_now_ns = ros::Time::now().toNSec();
358  uint64_t monotonic_now_ns = get_monotonic_now();
359 
360  add_timesync_observation(realtime_now_ns - monotonic_now_ns, realtime_now_ns, monotonic_now_ns);
361  }
362  }
363 
364  void send_timesync_msg(uint64_t tc1, uint64_t ts1)
365  {
366  mavlink::common::msg::TIMESYNC tsync {};
367  tsync.tc1 = tc1;
368  tsync.ts1 = ts1;
369 
370  UAS_FCU(m_uas)->send_message_ignore_drop(tsync);
371  }
372 
373  void add_timesync_observation(int64_t offset_ns, uint64_t local_time_ns, uint64_t remote_time_ns)
374  {
375  uint64_t now_ns = ros::Time::now().toNSec();
376 
377  // Calculate the round trip time (RTT) it took the timesync packet to bounce back to us from remote system
378  uint64_t rtt_ns = now_ns - local_time_ns;
379 
380  // Calculate the difference of this sample from the current estimate
381  uint64_t deviation = llabs(int64_t(time_offset) - offset_ns);
382 
383  if (rtt_ns < max_rtt_sample * 1000000ULL) { // Only use samples with low RTT
384  if (sync_converged() && (deviation > max_deviation_sample * 1000000ULL)) {
385  // Increment the counter if we have a good estimate and are getting samples far from the estimate
386  high_deviation_count++;
387 
388  // We reset the filter if we received consecutive samples which violate our present estimate.
389  // This is most likely due to a time jump on the offboard system.
390  if (high_deviation_count > max_cons_high_deviation) {
391  ROS_ERROR_NAMED("time", "TM : Time jump detected. Resetting time synchroniser.");
392 
393  // Reset the filter
394  reset_filter();
395 
396  // Reset diagnostics
397  dt_diag.clear();
398  dt_diag.set_timestamp(remote_time_ns);
399  }
400  } else {
401  // Filter gain scheduling
402  if (!sync_converged()) {
403  // Interpolate with a sigmoid function
404  float progress = float(sequence) / convergence_window;
405  float p = 1.0f - expf(0.5f * (1.0f - 1.0f / (1.0f - progress)));
406  filter_alpha = p * filter_alpha_final + (1.0f - p) * filter_alpha_initial;
407  filter_beta = p * filter_beta_final + (1.0f - p) * filter_beta_initial;
408  } else {
409  filter_alpha = filter_alpha_final;
410  filter_beta = filter_beta_final;
411  }
412 
413  // Perform filter update
414  add_sample(offset_ns);
415 
416  // Save time offset for other components to use
417  m_uas->set_time_offset(sync_converged() ? time_offset : 0);
418 
419  // Increment sequence counter after filter update
420  sequence++;
421 
422  // Reset high deviation count after filter update
423  high_deviation_count = 0;
424 
425  // Reset high RTT count after filter update
426  high_rtt_count = 0;
427  }
428  } else {
429  // Increment counter if round trip time is too high for accurate timesync
430  high_rtt_count++;
431 
432  if (high_rtt_count > max_cons_high_rtt) {
433  // Issue a warning to the user if the RTT is constantly high
434  ROS_WARN_NAMED("time", "TM : RTT too high for timesync: %0.2f ms.", rtt_ns / 1000000.0);
435 
436  // Reset counter
437  high_rtt_count = 0;
438  }
439  }
440 
441  // Publish timesync status
442  auto timesync_status = boost::make_shared<mavros_msgs::TimesyncStatus>();
443 
444  timesync_status->header.stamp = ros::Time::now();
445  timesync_status->remote_timestamp_ns = remote_time_ns;
446  timesync_status->observed_offset_ns = offset_ns;
447  timesync_status->estimated_offset_ns = time_offset;
448  timesync_status->round_trip_time_ms = float(rtt_ns / 1000000.0);
449 
450  timesync_status_pub.publish(timesync_status);
451 
452  // Update diagnostics
453  dt_diag.tick(rtt_ns, remote_time_ns, time_offset);
454  }
455 
456  void add_sample(int64_t offset_ns)
457  {
458  /* Online exponential smoothing filter. The derivative of the estimate is also
459  * estimated in order to produce an estimate without steady state lag:
460  * https://en.wikipedia.org/wiki/Exponential_smoothing#Double_exponential_smoothing
461  */
462 
463  double time_offset_prev = time_offset;
464 
465  if (sequence == 0) { // First offset sample
466  time_offset = offset_ns;
467  } else {
468  // Update the clock offset estimate
469  time_offset = filter_alpha * offset_ns + (1.0 - filter_alpha) * (time_offset + time_skew);
470 
471  // Update the clock skew estimate
472  time_skew = filter_beta * (time_offset - time_offset_prev) + (1.0 - filter_beta) * time_skew;
473  }
474  }
475 
477  {
478  // Do a full reset of all statistics and parameters
479  sequence = 0;
480  time_offset = 0.0;
481  time_skew = 0.0;
482  filter_alpha = filter_alpha_initial;
483  filter_beta = filter_beta_initial;
484  high_deviation_count = 0;
485  high_rtt_count = 0;
486  }
487 
488  inline bool sync_converged()
489  {
490  return sequence >= convergence_window;
491  }
492 
493  uint64_t get_monotonic_now(void)
494  {
495  struct timespec spec;
496  clock_gettime(CLOCK_MONOTONIC, &spec);
497 
498  return spec.tv_sec * 1000000000ULL + spec.tv_nsec;
499  }
500 };
501 } // namespace std_plugins
502 } // namespace mavros
503 
MAVROS Plugin base class.
Definition: mavros_plugin.h:36
#define ROS_WARN_THROTTLE_NAMED(period, name,...)
#define ROS_WARN_NAMED(name,...)
utils::timesync_mode timesync_mode
Definition: mavros_uas.h:75
void summary(unsigned char lvl, const std::string s)
MAVROS Plugin base.
std::vector< ros::Time > times_
Definition: sys_time.cpp:120
uint64_t toNSec() const
void add_sample(int64_t offset_ns)
Definition: sys_time.cpp:456
#define UAS_DIAG(uasobjptr)
helper accessor to diagnostic updater
Definition: mavros_uas.h:48
#define ROS_INFO_STREAM_NAMED(name, args)
void set_timestamp(uint64_t remote_timestamp_ns)
Definition: sys_time.cpp:78
void addf(const std::string &key, const char *format,...)
void send_timesync_msg(uint64_t tc1, uint64_t ts1)
Definition: sys_time.cpp:364
void publish(const boost::shared_ptr< M > &message) const
UAS for plugins.
Definition: mavros_uas.h:67
Subscriptions get_subscriptions() override
Return vector of MAVLink message subscriptions (handlers)
Definition: sys_time.cpp:249
#define UAS_FCU(uasobjptr)
helper accessor to FCU link interface
Definition: mavros_uas.h:42
void initialize(UAS &uas_) override
Plugin initializer.
Definition: sys_time.cpp:154
std::string to_string(timesync_mode e)
void run(diagnostic_updater::DiagnosticStatusWrapper &stat)
Definition: sys_time.cpp:84
DiagnosticTask(const std::string name)
timesync_mode timesync_mode_from_str(const std::string &mode)
Retrieve timesync mode from name.
std::vector< HandlerInfo > Subscriptions
Subscriptions vector.
Definition: mavros_plugin.h:47
void sys_time_cb(const ros::WallTimerEvent &event)
Definition: sys_time.cpp:339
void add_timesync_observation(int64_t offset_ns, uint64_t local_time_ns, uint64_t remote_time_ns)
Definition: sys_time.cpp:373
void tick(int64_t rtt_ns, uint64_t remote_timestamp_ns, int64_t time_offset_ns)
Definition: sys_time.cpp:67
void timesync_cb(const ros::WallTimerEvent &event)
Definition: sys_time.cpp:350
#define ROS_ERROR_NAMED(name,...)
static Time now()
void handle_timesync(const mavlink::mavlink_message_t *msg, mavlink::common::msg::TIMESYNC &tsync)
Definition: sys_time.cpp:324
void handle_system_time(const mavlink::mavlink_message_t *msg, mavlink::common::msg::SYSTEM_TIME &mtime)
Definition: sys_time.cpp:296
TimeSyncStatus(const std::string &name, size_t win_size)
Definition: sys_time.cpp:34
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
std::recursive_mutex mutex


mavros
Author(s): Vladimir Ermakov
autogenerated on Tue Jun 13 2023 02:17:50