5
5
:meth:`can.BusABC.send_periodic`.
6
6
"""
7
7
8
+ import abc
9
+ import logging
10
+ import sys
11
+ import threading
12
+ import time
8
13
from typing import Optional , Sequence , Tuple , Union , Callable , TYPE_CHECKING
9
14
15
+ from typing_extensions import Final
16
+
10
17
from can import typechecking
18
+ from can .message import Message
11
19
12
20
if TYPE_CHECKING :
13
21
from can .bus import BusABC
14
22
15
- from can .message import Message
16
-
17
- import abc
18
- import logging
19
- import threading
20
- import time
21
23
22
24
# try to import win32event for event-based cyclic send task (needs the pywin32 package)
25
+ USE_WINDOWS_EVENTS = False
23
26
try :
24
27
import win32event
25
28
26
- HAS_EVENTS = True
29
+ # Python 3.11 provides a more precise sleep implementation on Windows, so this is not necessary.
30
+ # Put version check here, so mypy does not complain about `win32event` not being defined.
31
+ if sys .version_info < (3 , 11 ):
32
+ USE_WINDOWS_EVENTS = True
27
33
except ImportError :
28
- HAS_EVENTS = False
34
+ pass
29
35
30
36
log = logging .getLogger ("can.bcm" )
31
37
38
+ NANOSECONDS_IN_SECOND : Final [int ] = 1_000_000_000
39
+ NANOSECONDS_IN_MILLISECOND : Final [int ] = 1_000_000
40
+
32
41
33
42
class CyclicTask (abc .ABC ):
34
43
"""
@@ -64,6 +73,7 @@ def __init__(
64
73
# Take the Arbitration ID of the first element
65
74
self .arbitration_id = messages [0 ].arbitration_id
66
75
self .period = period
76
+ self .period_ns = int (round (period * 1e9 ))
67
77
self .messages = messages
68
78
69
79
@staticmethod
@@ -246,7 +256,7 @@ def __init__(
246
256
)
247
257
self .on_error = on_error
248
258
249
- if HAS_EVENTS :
259
+ if USE_WINDOWS_EVENTS :
250
260
self .period_ms = int (round (period * 1000 , 0 ))
251
261
try :
252
262
self .event = win32event .CreateWaitableTimerEx (
@@ -261,7 +271,7 @@ def __init__(
261
271
self .start ()
262
272
263
273
def stop (self ) -> None :
264
- if HAS_EVENTS :
274
+ if USE_WINDOWS_EVENTS :
265
275
win32event .CancelWaitableTimer (self .event .handle )
266
276
self .stopped = True
267
277
@@ -272,7 +282,7 @@ def start(self) -> None:
272
282
self .thread = threading .Thread (target = self ._run , name = name )
273
283
self .thread .daemon = True
274
284
275
- if HAS_EVENTS :
285
+ if USE_WINDOWS_EVENTS :
276
286
win32event .SetWaitableTimer (
277
287
self .event .handle , 0 , self .period_ms , None , None , False
278
288
)
@@ -281,10 +291,11 @@ def start(self) -> None:
281
291
282
292
def _run (self ) -> None :
283
293
msg_index = 0
294
+ msg_due_time_ns = time .perf_counter_ns ()
295
+
284
296
while not self .stopped :
285
297
# Prevent calling bus.send from multiple threads
286
298
with self .send_lock :
287
- started = time .perf_counter ()
288
299
try :
289
300
self .bus .send (self .messages [msg_index ])
290
301
except Exception as exc : # pylint: disable=broad-except
@@ -294,13 +305,19 @@ def _run(self) -> None:
294
305
break
295
306
else :
296
307
break
308
+ msg_due_time_ns += self .period_ns
297
309
if self .end_time is not None and time .perf_counter () >= self .end_time :
298
310
break
299
311
msg_index = (msg_index + 1 ) % len (self .messages )
300
312
301
- if HAS_EVENTS :
302
- win32event .WaitForSingleObject (self .event .handle , self .period_ms )
303
- else :
304
- # Compensate for the time it takes to send the message
305
- delay = self .period - (time .perf_counter () - started )
306
- time .sleep (max (0.0 , delay ))
313
+ # Compensate for the time it takes to send the message
314
+ delay_ns = msg_due_time_ns - time .perf_counter_ns ()
315
+
316
+ if delay_ns > 0 :
317
+ if USE_WINDOWS_EVENTS :
318
+ win32event .WaitForSingleObject (
319
+ self .event .handle ,
320
+ int (round (delay_ns / NANOSECONDS_IN_MILLISECOND )),
321
+ )
322
+ else :
323
+ time .sleep (delay_ns / NANOSECONDS_IN_SECOND )
0 commit comments