timestamp_estimator.py
Go to the documentation of this file.
1 #
2 # Copyright (C) 2014-2016 UAVCAN Development Team <uavcan.org>
3 #
4 # This software is distributed under the terms of the MIT License.
5 #
6 # Author: Pavel Kirienko <pavel.kirienko@zubax.com>
7 # Ben Dyer <ben_dyer@mac.com>
8 #
9 
10 from __future__ import division, absolute_import, print_function, unicode_literals
11 import decimal
12 
13 
15  """
16  This class contains logic that recovers absolute value of a remote clock observable via small overflowing
17  integer samples.
18  For example, consider a remote system that reports timestamps as a 16-bit integer number of milliseconds that
19  overflows every 60 seconds (this method is used in SLCAN for example). This class can recover the time difference
20  in the remote clock domain between two arbitrary timestamps, even if the timestamp variable overflowed more than
21  once between these events.
22  """
23 
24  def __init__(self, source_clock_overflow_period=None):
25  """
26  Args:
27  source_clock_overflow_period: Overflow period of the remote clock, in seconds.
28  If not provided, the remote clock is considered to never
29  overflow (i.e. absolute).
30  """
32  decimal.Decimal(source_clock_overflow_period) if source_clock_overflow_period else None
33 
34  if self.source_clock_overflow_period is not None and self.source_clock_overflow_period <= 0:
35  raise ValueError('source_clock_overflow_period must be positive or None')
36 
37  # Internal states
38  self._resolved_time = None
39  self._prev_source_sample = None
40  self._prev_target_sample = None
41 
42  def reset(self):
43  """
44  Resets the internal logic; resolved time will start over.
45  """
46  self._resolved_time = None
47  self._prev_source_sample = None
48  self._prev_target_sample = None
49 
50  def update(self, source_clock_sample, target_clock_sample):
51  """
52  Args:
53  source_clock_sample: Sample of the source clock, in seconds
54  target_clock_sample: Sample of the target clock, in seconds
55 
56  Returns: Resolved absolute source clock value
57  """
58  if self._resolved_time is None or self.source_clock_overflow_period is None:
59  self._resolved_time = decimal.Decimal(source_clock_sample)
60  self._prev_source_sample = source_clock_sample
61  self._prev_target_sample = target_clock_sample
62  else:
63  # Time between updates in the target clock domain
64  tgt_delta = target_clock_sample - self._prev_target_sample
65  self._prev_target_sample = target_clock_sample
66  assert tgt_delta >= 0
67 
68  # Time between updates in the source clock domain
69  src_delta = source_clock_sample - self._prev_source_sample
70  self._prev_source_sample = source_clock_sample
71 
72  # Using the target clock we can resolve the integer ambiguity (number of overflows)
73  full_cycles = int(round((tgt_delta - src_delta) / float(self.source_clock_overflow_period), 0))
74 
75  # Updating the source clock now; in two steps, in order to avoid error accumulation in floats
76  self._resolved_time += decimal.Decimal(full_cycles * self.source_clock_overflow_period)
77  self._resolved_time += decimal.Decimal(src_delta)
78 
79  return self._resolved_time
80 
81 
83  """
84  Based on "A Passive Solution to the Sensor Synchronization Problem" [Edwin Olson 2010]
85  https://april.eecs.umich.edu/pdfs/olson2010.pdf
86  """
87 
88  DEFAULT_MAX_DRIFT_PPM = 200
89  DEFAULT_MAX_PHASE_ERROR_TO_RESYNC = 1.
90 
91  def __init__(self,
92  max_rate_error=None,
93  source_clock_overflow_period=None,
94  fixed_delay=None,
95  max_phase_error_to_resync=None):
96  """
97  Args:
98  max_rate_error: The max drift parameter must be not lower than maximum relative clock
99  drift in PPM. If the max relative drift is guaranteed to be lower,
100  reducing this value will improve estimation. The default covers vast
101  majority of low-cost (and up) crystal oscillators.
102  source_clock_overflow_period: How often the source clocks wraps over, in seconds.
103  For example, for SLCAN this value is 60 seconds.
104  If not provided, the source clock is considered to never wrap over.
105  fixed_delay: This value will be unconditionally added to the delay estimations.
106  Represented in seconds. Default is zero.
107  For USB-interfaced sources it should be safe to use as much as 100 usec.
108  max_phase_error_to_resync: When this value is exceeded, the estimator will start over.
109  Defaults to a large value.
110  """
111  self.max_rate_error = float(max_rate_error or (self.DEFAULT_MAX_DRIFT_PPM / 1e6))
112  self.fixed_delay = fixed_delay or 0
113  self.max_phase_error_to_resync = max_phase_error_to_resync or self.DEFAULT_MAX_PHASE_ERROR_TO_RESYNC
114 
115  if self.max_rate_error < 0:
116  raise ValueError('max_rate_error must be non-negative')
117 
118  if self.fixed_delay < 0:
119  raise ValueError('fixed_delay must be non-negative')
120 
122  raise ValueError('max_phase_error_to_resync must be positive')
123 
124  # This is used to recover absolute source time
125  self._source_time_resolver = SourceTimeResolver(source_clock_overflow_period=source_clock_overflow_period)
126 
127  # Refer to the paper for explanations
128  self._p = None
129  self._q = None
130 
131  # Statistics
132  self._estimated_delay = 0.0
133  self._resync_count = 0
134 
135  def update(self, source_clock_sample, target_clock_sample):
136  """
137  Args:
138  source_clock_sample: E.g. value received from the source system, in seconds
139  target_clock_sample: E.g. target time sampled when the data arrived to the local system, in seconds
140  Returns: Event timestamp converted to the target time domain.
141  """
142  pi = float(self._source_time_resolver.update(source_clock_sample, target_clock_sample))
143  qi = target_clock_sample
144 
145  # Initialization
146  if self._p is None:
147  self._p = pi
148  self._q = qi
149 
150  # Sync error - refer to the reference implementation of the algorithm
151  self._estimated_delay = abs((pi - self._p) - (qi - self._q))
152 
153  # Resynchronization (discarding known state)
155  self._source_time_resolver.reset()
156  self._resync_count += 1
157  self._p = pi = float(self._source_time_resolver.update(source_clock_sample, target_clock_sample))
158  self._q = qi
159 
160  # Offset options
161  assert pi >= self._p
162  offset = self._p - self._q - self.max_rate_error * (pi - self._p) - self.fixed_delay
163  new_offset = pi - qi - self.fixed_delay
164 
165  # Updating p/q if the new offset is lower by magnitude
166  if new_offset >= offset:
167  offset = new_offset
168  self._p = pi
169  self._q = qi
170 
171  ti = pi - offset
172 
173  return ti
174 
175  @property
176  def estimated_delay(self):
177  """Estimated delay, updated in the last call to update()"""
178  return self._estimated_delay
179 
180  @property
181  def resync_count(self):
182  return self._resync_count
183 
184 
185 if __name__ == '__main__':
186  # noinspection PyPackageRequirements
187  import matplotlib.pyplot as plt
188  # noinspection PyPackageRequirements
189  import numpy
190  import time
191 
192  if 1:
193  estimator = TimestampEstimator()
194  print(estimator.update(.0, 1000.0))
195  print(estimator.update(.1, 1000.1))
196  print(estimator.update(.2, 1000.1)) # Repeat
197  print(estimator.update(.3, 1000.1)) # Repeat
198  print(estimator.update(.4, 1000.2))
199  print(estimator.update(.5, 1000.3))
200 
201  if 1:
202  # Conversion from Real to Monotonic
203  estimator = TimestampEstimator(max_rate_error=1e-5,
204  fixed_delay=1e-6,
205  max_phase_error_to_resync=1e-2)
206  print('Initial mono to real:', time.time() - time.monotonic())
207  while True:
208  mono = time.monotonic()
209  real = time.time()
210  est_real = estimator.update(mono, real)
211  mono_to_real_offset = est_real - mono
212  print(mono_to_real_offset)
213  time.sleep(1)
214 
215  max_rate_error = None
216  source_clock_range = 10
217  delay_min = 0.0001
218  delay_max = 0.02
219  num_samples = 200
220 
221  x = range(num_samples)
222  delays = numpy.random.uniform(delay_min, delay_max, size=num_samples)
223 
224  estimator = TimestampEstimator(max_rate_error=max_rate_error, fixed_delay=delay_min,
225  source_clock_overflow_period=source_clock_range)
226 
227  source_clocks = []
228  estimated_times = []
229  offset_errors = []
230  estimated_delays = []
231  for i, delay in enumerate(delays):
232  source_clock = i
233  source_clocks.append(source_clock)
234  target_clock = i + delay
235 
236  estimated_time = estimator.update(source_clock % source_clock_range, target_clock)
237  estimated_times.append(estimated_time)
238  offset_errors.append(estimated_time - source_clock)
239  estimated_delays.append(estimator.estimated_delay)
240 
241  fig = plt.figure()
242 
243  ax1 = fig.add_subplot(211)
244  ax1.plot(x, numpy.array(delays) * 1e3)
245  ax1.plot(x, numpy.array(offset_errors) * 1e3)
246 
247  ax2 = fig.add_subplot(212)
248  ax2.plot(x, (numpy.array(estimated_times) - numpy.array(source_clocks)) * 1e3)
249 
250  plt.show()
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.DEFAULT_MAX_DRIFT_PPM
int DEFAULT_MAX_DRIFT_PPM
Definition: timestamp_estimator.py:88
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver.source_clock_overflow_period
source_clock_overflow_period
Definition: timestamp_estimator.py:31
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.DEFAULT_MAX_PHASE_ERROR_TO_RESYNC
int DEFAULT_MAX_PHASE_ERROR_TO_RESYNC
Definition: timestamp_estimator.py:89
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.max_phase_error_to_resync
max_phase_error_to_resync
Definition: timestamp_estimator.py:109
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.__init__
def __init__(self, max_rate_error=None, source_clock_overflow_period=None, fixed_delay=None, max_phase_error_to_resync=None)
Definition: timestamp_estimator.py:91
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver._resolved_time
_resolved_time
Definition: timestamp_estimator.py:38
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.resync_count
def resync_count(self)
Definition: timestamp_estimator.py:181
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator._resync_count
_resync_count
Definition: timestamp_estimator.py:129
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator._q
_q
Definition: timestamp_estimator.py:125
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver.update
def update(self, source_clock_sample, target_clock_sample)
Definition: timestamp_estimator.py:50
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver._prev_target_sample
_prev_target_sample
Definition: timestamp_estimator.py:40
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator._estimated_delay
_estimated_delay
Definition: timestamp_estimator.py:128
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.estimated_delay
def estimated_delay(self)
Definition: timestamp_estimator.py:176
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver.reset
def reset(self)
Definition: timestamp_estimator.py:42
int
int
Definition: libstubs.cpp:120
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver.__init__
def __init__(self, source_clock_overflow_period=None)
Definition: timestamp_estimator.py:24
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator._source_time_resolver
_source_time_resolver
Definition: timestamp_estimator.py:121
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator
Definition: timestamp_estimator.py:82
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.update
def update(self, source_clock_sample, target_clock_sample)
Definition: timestamp_estimator.py:135
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.max_rate_error
max_rate_error
Definition: timestamp_estimator.py:107
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver
Definition: timestamp_estimator.py:14
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator.fixed_delay
fixed_delay
Definition: timestamp_estimator.py:108
pyuavcan_v0.driver.timestamp_estimator.SourceTimeResolver._prev_source_sample
_prev_source_sample
Definition: timestamp_estimator.py:39
pyuavcan_v0.driver.timestamp_estimator.TimestampEstimator._p
_p
Definition: timestamp_estimator.py:124


uavcan_communicator
Author(s):
autogenerated on Fri Dec 13 2024 03:10:03