10 from __future__
import division, absolute_import, print_function, unicode_literals
16 This class contains logic that recovers absolute value of a remote clock observable via small overflowing
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.
24 def __init__(self, source_clock_overflow_period=None):
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).
32 decimal.Decimal(source_clock_overflow_period)
if source_clock_overflow_period
else None
35 raise ValueError(
'source_clock_overflow_period must be positive or None')
44 Resets the internal logic; resolved time will start over.
50 def update(self, source_clock_sample, target_clock_sample):
53 source_clock_sample: Sample of the source clock, in seconds
54 target_clock_sample: Sample of the target clock, in seconds
56 Returns: Resolved absolute source clock value
84 Based on "A Passive Solution to the Sensor Synchronization Problem" [Edwin Olson 2010]
85 https://april.eecs.umich.edu/pdfs/olson2010.pdf
88 DEFAULT_MAX_DRIFT_PPM = 200
89 DEFAULT_MAX_PHASE_ERROR_TO_RESYNC = 1.
93 source_clock_overflow_period=None,
95 max_phase_error_to_resync=None):
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.
116 raise ValueError(
'max_rate_error must be non-negative')
119 raise ValueError(
'fixed_delay must be non-negative')
122 raise ValueError(
'max_phase_error_to_resync must be positive')
135 def update(self, source_clock_sample, target_clock_sample):
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.
143 qi = target_clock_sample
166 if new_offset >= offset:
177 """Estimated delay, updated in the last call to update()"""
185 if __name__ ==
'__main__':
187 import matplotlib.pyplot
as plt
194 print(estimator.update(.0, 1000.0))
195 print(estimator.update(.1, 1000.1))
196 print(estimator.update(.2, 1000.1))
197 print(estimator.update(.3, 1000.1))
198 print(estimator.update(.4, 1000.2))
199 print(estimator.update(.5, 1000.3))
205 max_phase_error_to_resync=1e-2)
206 print(
'Initial mono to real:', time.time() - time.monotonic())
208 mono = time.monotonic()
210 est_real = estimator.update(mono, real)
211 mono_to_real_offset = est_real - mono
212 print(mono_to_real_offset)
215 max_rate_error =
None
216 source_clock_range = 10
221 x = range(num_samples)
222 delays = numpy.random.uniform(delay_min, delay_max, size=num_samples)
225 source_clock_overflow_period=source_clock_range)
230 estimated_delays = []
231 for i, delay
in enumerate(delays):
233 source_clocks.append(source_clock)
234 target_clock = i + delay
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)
243 ax1 = fig.add_subplot(211)
244 ax1.plot(x, numpy.array(delays) * 1e3)
245 ax1.plot(x, numpy.array(offset_errors) * 1e3)
247 ax2 = fig.add_subplot(212)
248 ax2.plot(x, (numpy.array(estimated_times) - numpy.array(source_clocks)) * 1e3)