Skip to content

Commit 966a5e4

Browse files
authored
Windows time accuracy (#936)
* fixes #732: add support for VN8900 xlGetChannelTime function * improve time stamp accuracy of the CAN messages for Kvaser and Vector buses on the Windows platform. (see #934) On Windows the value returned by time.time is refreshed at best every 0.5ms, and on some PCs the default refresh rate is every 10ms. The idea is to get the value of time.perf_counter at the moment when time.time is refreshed and use this as a reference point. * check the time.time resolution before using the new time correlation function
1 parent 7ddea9e commit 966a5e4

File tree

3 files changed

+64
-8
lines changed

3 files changed

+64
-8
lines changed

can/interfaces/kvaser/canlib.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
from can import CanError, BusABC
1515
from can import Message
16+
from can.util import time_perfcounter_correlation
1617
from . import constants as canstat
1718
from . import structures
1819

@@ -492,11 +493,20 @@ def __init__(self, channel, can_filters=None, **kwargs):
492493

493494
timer = ctypes.c_uint(0)
494495
try:
495-
kvReadTimer(self._read_handle, ctypes.byref(timer))
496+
if time.get_clock_info("time").resolution > 1e-5:
497+
ts, perfcounter = time_perfcounter_correlation()
498+
kvReadTimer(self._read_handle, ctypes.byref(timer))
499+
current_perfcounter = time.perf_counter()
500+
now = ts + (current_perfcounter - perfcounter)
501+
self._timestamp_offset = now - (timer.value * TIMESTAMP_FACTOR)
502+
else:
503+
kvReadTimer(self._read_handle, ctypes.byref(timer))
504+
self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR)
505+
496506
except Exception as exc:
497507
# timer is usually close to 0
498508
log.info(str(exc))
499-
self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR)
509+
self._timestamp_offset = time.time() - (timer.value * TIMESTAMP_FACTOR)
500510

501511
self._is_filtered = False
502512
super().__init__(channel=channel, can_filters=can_filters, **kwargs)

can/interfaces/vector/canlib.py

+22-6
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@
3232
# Import Modules
3333
# ==============
3434
from can import BusABC, Message
35-
from can.util import len2dlc, dlc2len, deprecated_args_alias
35+
from can.util import (
36+
len2dlc,
37+
dlc2len,
38+
deprecated_args_alias,
39+
time_perfcounter_correlation,
40+
)
3641
from .exceptions import VectorError
3742

3843
# Define Module Logger
@@ -295,11 +300,22 @@ def __init__(
295300
# Calculate time offset for absolute timestamps
296301
offset = xlclass.XLuint64()
297302
try:
298-
try:
299-
xldriver.xlGetSyncTime(self.port_handle, offset)
300-
except VectorError:
301-
xldriver.xlGetChannelTime(self.port_handle, self.mask, offset)
302-
self._time_offset = time.time() - offset.value * 1e-9
303+
if time.get_clock_info("time").resolution > 1e-5:
304+
ts, perfcounter = time_perfcounter_correlation()
305+
try:
306+
xldriver.xlGetSyncTime(self.port_handle, offset)
307+
except VectorError:
308+
xldriver.xlGetChannelTime(self.port_handle, self.mask, offset)
309+
current_perfcounter = time.perf_counter()
310+
now = ts + (current_perfcounter - perfcounter)
311+
self._time_offset = now - offset.value * 1e-9
312+
else:
313+
try:
314+
xldriver.xlGetSyncTime(self.port_handle, offset)
315+
except VectorError:
316+
xldriver.xlGetChannelTime(self.port_handle, self.mask, offset)
317+
self._time_offset = time.time() - offset.value * 1e-9
318+
303319
except VectorError:
304320
self._time_offset = 0.0
305321

can/util.py

+30
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import functools
55
import warnings
66
from typing import Dict, Optional, Union
7+
from time import time, perf_counter, get_clock_info
78

89
from can import typechecking
910

@@ -326,6 +327,35 @@ def rename_kwargs(func_name, kwargs, aliases):
326327
warnings.warn("{} is deprecated".format(alias), DeprecationWarning)
327328

328329

330+
def time_perfcounter_correlation():
331+
"""Get the `perf_counter` value nearest to when time.time() is updated
332+
333+
Computed if the default timer used by `time.time` on this platform has a resolution
334+
higher than 10μs, otherwise the current time and perf_counter is directly returned.
335+
This was chosen as typical timer resolution on Linux/macOS is ~1μs, and the Windows
336+
platform can vary from ~500μs to 10ms.
337+
338+
Note this value is based on when `time.time()` is observed to update from Python,
339+
it is not directly returned by the operating system.
340+
341+
:returns:
342+
(t, performance_counter) time.time value and time.perf_counter value when the time.time
343+
is updated
344+
345+
"""
346+
347+
# use this if the resolution is higher than 10us
348+
if get_clock_info("time").resolution > 1e-5:
349+
t0 = time()
350+
while True:
351+
t1, performance_counter = time(), perf_counter()
352+
if t1 != t0:
353+
break
354+
else:
355+
return time(), perf_counter()
356+
return t1, performance_counter
357+
358+
329359
if __name__ == "__main__":
330360
print("Searching for configuration named:")
331361
print("\n".join(CONFIG_FILES))

0 commit comments

Comments
 (0)