diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 7df59da..fbdb690 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.19.9 +current_version = 0.20.0 commit = True tag = False diff --git a/pyxcp/__init__.py b/pyxcp/__init__.py index ba8b859..d5181b3 100644 --- a/pyxcp/__init__.py +++ b/pyxcp/__init__.py @@ -1,6 +1,22 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """Universal Calibration Protocol for Python""" +import sys + +if sys.platform == 'win32' and sys.version_info[:2] < (3, 11): + # patch the time module with the high resolution alternatives + try: + from win_precise_time import sleep as wpsleep + from win_precise_time import time as wptime + + import time + time.sleep = wpsleep + time.time = wptime + + except ImportError: + pass + + from .master import Master from .transport import Can from .transport import Eth @@ -8,4 +24,4 @@ from .transport import Usb # if you update this manually, do not forget to update .bumpversion.cfg -__version__ = "0.19.9" +__version__ = "0.20.0" diff --git a/pyxcp/master/master.py b/pyxcp/master/master.py index 1ce36a2..9a333b3 100644 --- a/pyxcp/master/master.py +++ b/pyxcp/master/master.py @@ -35,7 +35,7 @@ from pyxcp.master.errorhandler import disable_error_handling from pyxcp.master.errorhandler import wrapped from pyxcp.transport.base import createTransport -from pyxcp.utils import delay +from pyxcp.utils import delay, SHORT_SLEEP def broadcasted(func: Callable): @@ -452,7 +452,7 @@ def upload(self, length: int): response += data[1 : rem + 1] rem = length - len(response) else: - sleep(0.001) + sleep(SHORT_SLEEP) return response diff --git a/pyxcp/transport/base.py b/pyxcp/transport/base.py index 0602507..114b147 100644 --- a/pyxcp/transport/base.py +++ b/pyxcp/transport/base.py @@ -4,7 +4,6 @@ import threading from collections import deque from datetime import datetime -from time import get_clock_info from time import sleep from time import time @@ -13,7 +12,7 @@ from ..timing import Timing from ..utils import flatten from ..utils import hexDump -from ..utils import time_perfcounter_correlation +from ..utils import SHORT_SLEEP from pyxcp.config import Configuration @@ -30,7 +29,7 @@ def get(q, timeout, restart_event): restart_event.clear() if time() - start > timeout: raise Empty - sleep(0.001) + sleep(SHORT_SLEEP) item = q.popleft() return item @@ -86,18 +85,9 @@ def __init__(self, config=None): self.cro_callback = None self.first_daq_timestamp = None - if get_clock_info("time").resolution > 1e-5: - ts, pc = time_perfcounter_correlation() - self.timestamp_origin = ts - self.datetime_origin = datetime.fromtimestamp(ts) - self.perf_counter_origin = pc - else: - self.timestamp_origin = time() - self.datetime_origin = datetime.fromtimestamp(self.timestamp_origin) - # we will later use this to know if the current platform has a high - # resolution time.time implementation - self.perf_counter_origin = -1 + self.timestamp_origin = time() + self.datetime_origin = datetime.fromtimestamp(self.timestamp_origin) self.pre_send_timestamp = time() self.post_send_timestamp = time() @@ -233,7 +223,7 @@ def block_receive(self, length_required: int) -> bytes: else: if time() - start > self.timeout: raise types.XcpTimeoutError("Response timed out [block_receive].") from None - sleep(0.001) + sleep(SHORT_SLEEP) return block_response @abc.abstractmethod diff --git a/pyxcp/transport/can.py b/pyxcp/transport/can.py index eb05eb9..524d765 100644 --- a/pyxcp/transport/can.py +++ b/pyxcp/transport/can.py @@ -343,16 +343,9 @@ def send(self, frame): if self.max_dlc_required: frame = padFrame(frame, self.padding_value, self.padding_len) # send the request - if self.perf_counter_origin < 0: - self.pre_send_timestamp = time() - self.canInterface.transmit(payload=frame) - self.post_send_timestamp = time() - else: - pre_send_timestamp = perf_counter() - self.canInterface.transmit(payload=frame) - post_send_timestamp = perf_counter() - self.pre_send_timestamp = self.timestamp_origin + pre_send_timestamp - self.perf_counter_origin - self.post_send_timestamp = self.timestamp_origin + post_send_timestamp - self.perf_counter_origin + self.pre_send_timestamp = time() + self.canInterface.transmit(payload=frame) + self.post_send_timestamp = time() def closeConnection(self): if hasattr(self, "canInterface"): diff --git a/pyxcp/transport/eth.py b/pyxcp/transport/eth.py index 25a2166..9337a9d 100644 --- a/pyxcp/transport/eth.py +++ b/pyxcp/transport/eth.py @@ -6,6 +6,7 @@ import threading from collections import deque from pyxcp.transport.base import BaseTransport +from pyxcp.utils import SHORT_SLEEP from time import perf_counter from time import sleep from time import time @@ -100,10 +101,6 @@ def _packet_listen(self): socket_fileno = self.sock.fileno select = self.selector.select - high_resolution_time = self.perf_counter_origin < 0 - timestamp_origin = self.timestamp_origin - perf_counter_origin = self.perf_counter_origin - _packets = self._packets if use_tcp: @@ -118,10 +115,7 @@ def _packet_listen(self): sel = select(0.02) for _, events in sel: if events & EVENT_READ: - if high_resolution_time: - recv_timestamp = time() - else: - recv_timestamp = timestamp_origin + perf_counter() - perf_counter_origin + recv_timestamp = time() if use_tcp: response = sock_recv(RECV_SIZE) @@ -164,7 +158,7 @@ def listen(self): count = len(_packets) if not count: - sleep(0.002) + sleep(SHORT_SLEEP) continue for _ in range(count): @@ -199,16 +193,9 @@ def listen(self): break def send(self, frame): - if self.perf_counter_origin < 0: - self.pre_send_timestamp = time() - self.sock.send(frame) - self.post_send_timestamp = time() - else: - pre_send_timestamp = perf_counter() - self.sock.send(frame) - post_send_timestamp = perf_counter() - self.pre_send_timestamp = self.timestamp_origin + pre_send_timestamp - self.perf_counter_origin - self.post_send_timestamp = self.timestamp_origin + post_send_timestamp - self.perf_counter_origin + self.pre_send_timestamp = time() + self.sock.send(frame) + self.post_send_timestamp = time() def closeConnection(self): if not self.invalidSocket: diff --git a/pyxcp/transport/sxi.py b/pyxcp/transport/sxi.py index 0e9de8f..7dc43c1 100644 --- a/pyxcp/transport/sxi.py +++ b/pyxcp/transport/sxi.py @@ -58,19 +58,14 @@ def flush(self): self.commPort.flush() def listen(self): - high_resolution_time = self.perf_counter_origin < 0 - timestamp_origin = self.timestamp_origin - perf_counter_origin = self.perf_counter_origin while True: if self.closeEvent.isSet(): return if not self.commPort.inWaiting(): continue - if high_resolution_time: - recv_timestamp = time() - else: - recv_timestamp = timestamp_origin + perf_counter() - perf_counter_origin + + recv_timestamp = time() length, counter = self.HEADER.unpack(self.commPort.read(self.HEADER_SIZE)) response = self.commPort.read(length) @@ -82,16 +77,9 @@ def listen(self): self.processResponse(response, length, counter, recv_timestamp) def send(self, frame): - if self.perf_counter_origin < 0: - self.pre_send_timestamp = time() - self.commPort.write(frame) - self.post_send_timestamp = time() - else: - pre_send_timestamp = perf_counter() - self.commPort.write(frame) - post_send_timestamp = perf_counter() - self.pre_send_timestamp = self.timestamp_origin + pre_send_timestamp - self.perf_counter_origin - self.post_send_timestamp = self.timestamp_origin + post_send_timestamp - self.perf_counter_origin + self.pre_send_timestamp = time() + self.commPort.write(frame) + self.post_send_timestamp = time() def closeConnection(self): if hasattr(self, "commPort") and self.commPort.isOpen(): diff --git a/pyxcp/transport/usb_transport.py b/pyxcp/transport/usb_transport.py index 8fffb3a..a50e8b1 100644 --- a/pyxcp/transport/usb_transport.py +++ b/pyxcp/transport/usb_transport.py @@ -5,6 +5,7 @@ from array import array from collections import deque from pyxcp.transport.base import BaseTransport +from pyxcp.utils import SHORT_SLEEP from time import perf_counter from time import sleep from time import time @@ -94,10 +95,6 @@ def _packet_listen(self): close_event_set = self.closeEvent.isSet - high_resolution_time = self.perf_counter_origin < 0 - timestamp_origin = self.timestamp_origin - perf_counter_origin = self.perf_counter_origin - _packets = self._packets read = self.reply_endpoint.read @@ -110,10 +107,7 @@ def _packet_listen(self): return try: - if high_resolution_time: - recv_timestamp = time() - else: - recv_timestamp = timestamp_origin + perf_counter() - perf_counter_origin + recv_timestamp = time() read_count = read(buffer, 100) # 100ms timeout if read_count != RECV_SIZE: _packets.append((buffer_view[:read_count].tobytes(), recv_timestamp)) @@ -121,7 +115,7 @@ def _packet_listen(self): _packets.append((buffer.tobytes(), recv_timestamp)) except BaseException: # print(format_exc()) - sleep(0.001) + sleep(SHORT_SLEEP) continue except BaseException: @@ -151,7 +145,7 @@ def listen(self): count = len(_packets) if not count: - sleep(0.001) + sleep(SHORT_SLEEP) last_sleep = perf_counter() continue @@ -164,7 +158,7 @@ def listen(self): while True: if perf_counter() - last_sleep >= 0.005: - sleep(0.001) + sleep(SHORT_SLEEP) last_sleep = perf_counter() if length is None: @@ -191,30 +185,17 @@ def listen(self): break def send(self, frame): - if self.perf_counter_origin < 0: - self.pre_send_timestamp = time() - try: - self.command_endpoint.write(frame) - except BaseException: - # sometimes usb.core.USBError: [Errno 5] Input/Output Error is raised - # even though the command is send and a reply is received from the device. - # Ignore this here since a Timeout error will be raised anyway if - # the device does not respond - pass - self.post_send_timestamp = time() - else: - pre_send_timestamp = perf_counter() - try: - self.command_endpoint.write(frame) - except BaseException: - # sometimes usb.core.USBError: [Errno 5] Input/Output Error is raised - # even though the command is send and a reply is received from the device. - # Ignore this here since a Timeout error will be raised anyway if - # the device does not respond - pass - post_send_timestamp = perf_counter() - self.pre_send_timestamp = self.timestamp_origin + pre_send_timestamp - self.perf_counter_origin - self.post_send_timestamp = self.timestamp_origin + post_send_timestamp - self.perf_counter_origin + + self.pre_send_timestamp = time() + try: + self.command_endpoint.write(frame) + except BaseException: + # sometimes usb.core.USBError: [Errno 5] Input/Output Error is raised + # even though the command is send and a reply is received from the device. + # Ignore this here since a Timeout error will be raised anyway if + # the device does not respond + pass + self.post_send_timestamp = time() def closeConnection(self): if self.device is not None: diff --git a/pyxcp/utils.py b/pyxcp/utils.py index f967c25..f276f3f 100644 --- a/pyxcp/utils.py +++ b/pyxcp/utils.py @@ -49,35 +49,7 @@ def getPythonVersion(): PYTHON_VERSION = getPythonVersion() - - -def time_perfcounter_correlation(): - """Get the `perf_counter` value nearest to when time.time() is updated if the `time.time` on - this platform has a resolution higher than 10us. This is tipical for the Windows platform - were the beste resolution is ~500us. - - On non Windows platforms the current time and perf_counter is directly returned since the - resolution is tipical ~1us. - - Note this value is based on when `time.time()` is observed to update from Python, it is not - directly returned by the operating system. - - :return: - (t, performance_counter) time.time value and perf_counter value when the time.time - is updated - - """ - - # use this if the resolution is higher than 10us - if get_clock_info("time").resolution > 1e-5: - t0 = time() - while True: - t1, performance_counter = time(), perf_counter() - if t1 != t0: - break - else: - return time(), perf_counter() - return t1, performance_counter +SHORT_SLEEP = 0.0005 def delay(amount: float): @@ -86,3 +58,4 @@ def delay(amount: float): start = perf_counter() while perf_counter() < start + amount: pass + diff --git a/requirements.txt b/requirements.txt index d72344e..621f110 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ numpydoc sphinxcontrib-napoleon toml pyusb +win-precise-time; python_version >= "3.7" and sys_platform == "win32"