From f7a3ef4fff3fbd8c6bc2bad4dfa23e547525524d Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Sat, 28 Sep 2019 14:22:09 +0200 Subject: [PATCH 01/12] Add auto-modifying cyclic tasks --- can/broadcastmanager.py | 39 +++++++++++++++++++-- can/bus.py | 12 +++++-- examples/cyclic_checksum.py | 69 +++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 examples/cyclic_checksum.py diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 52f0550f2..abf8e19f6 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -5,7 +5,7 @@ :meth:`can.BusABC.send_periodic`. """ -from typing import Optional, Sequence, Tuple, Union, TYPE_CHECKING +from typing import Callable, Optional, Sequence, Tuple, Union, TYPE_CHECKING from can import typechecking @@ -129,6 +129,37 @@ def start(self): class ModifiableCyclicTaskABC(CyclicSendTaskABC): """Adds support for modifying a periodic message""" + def __init__( + self, + messages: Union[Sequence[Message], Message], + period: float, + duration: Optional[float], + modifier_callback: Optional[Callable[[Tuple[Message, ...]], + Tuple[Message, ...]]], + ): + """ + :param messages: + The messages to be sent periodically. + :param period: The rate in seconds at which to send the messages. + :param modifier_callback: + Callback function which takes takes a can.Message as input and + returns a modified can.Message. + """ + super().__init__(messages, period, duration) + + if modifier_callback is not None: + self.modifier_callback = self._check_modifier_callback(modifier_callback) + + def _check_modifier_callback(self, modifier_callback): + modified_msgs = modifier_callback(self.messages) + + if modified_msgs[0].arbitration_id != self.arbitration_id: + raise ValueError( + "The modifier callback function must not modify the " + "messages' arbitration ID." + ) + + return modifier_callback def _check_modified_messages(self, messages: Tuple[Message, ...]): """Helper function to perform error checking when modifying the data in @@ -207,8 +238,10 @@ def __init__( messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + modifier_callback: Optional[Callable[[Tuple[Message, ...]], + Tuple[Message, ...]]] = None, ): - super().__init__(messages, period, duration) + super().__init__(messages, period, duration, modifier_callback) self.bus = bus self.send_lock = lock self.stopped = True @@ -247,6 +280,8 @@ def _run(self): with self.send_lock: started = time.perf_counter() try: + if self.modifier_callback is not None: + self.messages = self.modifier_callback(self.messages) self.bus.send(self.messages[msg_index]) except Exception as exc: log.exception(exc) diff --git a/can/bus.py b/can/bus.py index fb6410cf5..5ca5302f8 100644 --- a/can/bus.py +++ b/can/bus.py @@ -2,7 +2,7 @@ Contains the ABC bus implementation and its documentation. """ -from typing import Iterator, List, Optional, Sequence, Tuple, Union +from typing import Callable, Iterator, List, Optional, Sequence, Tuple, Union import can.typechecking @@ -176,6 +176,8 @@ def send_periodic( period: float, duration: Optional[float] = None, store_task: bool = True, + modifier_callback: Optional[Callable[[Tuple[Message, ...]], + Tuple[Message, ...]]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -222,7 +224,8 @@ def send_periodic( raise ValueError("Must be either a list, tuple, or a Message") if not msgs: raise ValueError("Must be at least a list or tuple of length 1") - task = self._send_periodic_internal(msgs, period, duration) + task = self._send_periodic_internal(msgs, period, duration, + modifier_callback) # we wrap the task's stop method to also remove it from the Bus's list of tasks original_stop_method = task.stop @@ -246,6 +249,8 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, + modifier_callback: Optional[Callable[[Tuple[Message, ...]], + Tuple[Message, ...]]] = None ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. @@ -269,7 +274,8 @@ def _send_periodic_internal( threading.Lock() ) # pylint: disable=attribute-defined-outside-init task = ThreadBasedCyclicSendTask( - self, self._lock_send_periodic, msgs, period, duration + self, self._lock_send_periodic, msgs, period, duration, + modifier_callback ) return task diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py new file mode 100644 index 000000000..7ba07a7fd --- /dev/null +++ b/examples/cyclic_checksum.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +""" +This example demonstrates how to send a periodic message containing +an automatically updating counter and checksum. + +Expects a virtual interface: + + python3 -m examples.cyclic_checksum +""" + +import logging +import time + +import can + +logging.basicConfig(level=logging.INFO) + + +def cyclic_checksum_send(bus): + """ + Sends periodic messages every 1 s with no explicit timeout. + The message's counter and checksum is updated before each send. + Sleeps for 10 seconds then stops the task. + """ + message = can.Message(arbitration_id=0xdeadbeef, + data=[0, 1, 2, 3, 4, 5, 6, 0]) + print("Starting to send an auto-updating message every 1 s for 10 s") + task = bus.send_periodic(message, 1, modifier_callback=update_message) + assert isinstance(task, can.CyclicSendTaskABC) + time.sleep(10) + task.stop() + print("stopped cyclic send") + + +def update_message(messages): + for m in messages: + counter = increment_counter(m) + checksum = compute_xbr_checksum(m, counter) + m.data[7] = (checksum << 4) + counter + + return messages + + +def increment_counter(message): + counter = message.data[7] & 0x0F + counter += 1 + counter %= 16 + + return counter + + +def compute_xbr_checksum(message, counter): + """ + Computes an XBR checksum as per SAE J1939 SPN 3188. + """ + checksum = sum(message.data[:7]) + checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder='big')) + checksum += counter & 0x0F + xbr_checksum = ((checksum >> 4) + checksum) & 0x0F + + return xbr_checksum + + +if __name__ == "__main__": + with can.Bus( # type: ignore + 'test', interface='virtual' + ) as BUS: + cyclic_checksum_send(BUS) From e9990dc27c23aa8bc3b1806e30b45bf9a742cf52 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Sat, 28 Sep 2019 16:48:47 +0200 Subject: [PATCH 02/12] Make sure self.modifier_callback exists --- can/broadcastmanager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index abf8e19f6..574d28d0e 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -149,6 +149,8 @@ def __init__( if modifier_callback is not None: self.modifier_callback = self._check_modifier_callback(modifier_callback) + else: + self.modifier_callback = None def _check_modifier_callback(self, modifier_callback): modified_msgs = modifier_callback(self.messages) From 6cdb43e47ada5a36373a3267581ed976879cb2e3 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Sat, 28 Sep 2019 17:59:42 +0200 Subject: [PATCH 03/12] Don't break socketcan Added modifier_callback arguments where necessary, and changed MRO of sockercan's CyclicSendTask to match that of the fallback. --- can/interfaces/socketcan/socketcan.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index de304e218..ac4c37bf0 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -289,7 +289,7 @@ def _add_flags_to_can_id(message): class CyclicSendTask( - LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC + ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC ): """ A SocketCAN cyclic send task supports: @@ -300,7 +300,8 @@ class CyclicSendTask( """ - def __init__(self, bcm_socket, messages, period, duration=None): + def __init__(self, bcm_socket, messages, period, duration=None, + modifier_callback=None): """ :param bcm_socket: An open BCM socket on the desired CAN channel. :param Union[Sequence[can.Message], can.Message] messages: @@ -314,7 +315,7 @@ def __init__(self, bcm_socket, messages, period, duration=None): # - self.messages # - self.period # - self.duration - super().__init__(messages, period, duration) + super().__init__(messages, period, duration, modifier_callback) self.bcm_socket = bcm_socket self._tx_setup(self.messages) @@ -665,7 +666,8 @@ def _send_once(self, data, channel=None): raise can.CanError("Failed to transmit: %s" % exc) return sent - def _send_periodic_internal(self, msgs, period, duration=None): + def _send_periodic_internal(self, msgs, period, duration=None, + modifier_callback=None): """Start sending messages at a given period on this bus. The kernel's Broadcast Manager SocketCAN API will be used. From 56021d75c9838bfc572500131ed707c9a9dd400c Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Sat, 28 Sep 2019 18:49:13 +0200 Subject: [PATCH 04/12] Remove __init__ from ModifiableCyclicTaskABC This makes several changes to socketcan in previous commits unnecessary. These changes are also removed. --- can/broadcastmanager.py | 42 +++++++-------------------- can/interfaces/socketcan/socketcan.py | 7 ++--- 2 files changed, 14 insertions(+), 35 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index 574d28d0e..d66ce81fb 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -129,39 +129,18 @@ def start(self): class ModifiableCyclicTaskABC(CyclicSendTaskABC): """Adds support for modifying a periodic message""" - def __init__( - self, - messages: Union[Sequence[Message], Message], - period: float, - duration: Optional[float], - modifier_callback: Optional[Callable[[Tuple[Message, ...]], - Tuple[Message, ...]]], - ): - """ - :param messages: - The messages to be sent periodically. - :param period: The rate in seconds at which to send the messages. - :param modifier_callback: - Callback function which takes takes a can.Message as input and - returns a modified can.Message. - """ - super().__init__(messages, period, duration) - - if modifier_callback is not None: - self.modifier_callback = self._check_modifier_callback(modifier_callback) - else: - self.modifier_callback = None def _check_modifier_callback(self, modifier_callback): - modified_msgs = modifier_callback(self.messages) + if modifier_callback is not None: + modified_msgs = modifier_callback(self.messages) - if modified_msgs[0].arbitration_id != self.arbitration_id: - raise ValueError( - "The modifier callback function must not modify the " - "messages' arbitration ID." - ) + if modified_msgs[0].arbitration_id != self.arbitration_id: + raise ValueError( + "The modifier callback function must not modify the " + "messages' arbitration ID." + ) - return modifier_callback + self.modifier_callback = modifier_callback def _check_modified_messages(self, messages: Tuple[Message, ...]): """Helper function to perform error checking when modifying the data in @@ -241,14 +220,15 @@ def __init__( period: float, duration: Optional[float] = None, modifier_callback: Optional[Callable[[Tuple[Message, ...]], - Tuple[Message, ...]]] = None, + Tuple[Message, ...] = None, ): - super().__init__(messages, period, duration, modifier_callback) + super().__init__(messages, period, duration) self.bus = bus self.send_lock = lock self.stopped = True self.thread = None self.end_time = time.perf_counter() + duration if duration else None + self._check_modifier_callback(modifier_callback) if HAS_EVENTS: self.period_ms: int = int(round(period * 1000, 0)) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index ac4c37bf0..f3065fbc8 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -289,7 +289,7 @@ def _add_flags_to_can_id(message): class CyclicSendTask( - ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC + LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC ): """ A SocketCAN cyclic send task supports: @@ -300,8 +300,7 @@ class CyclicSendTask( """ - def __init__(self, bcm_socket, messages, period, duration=None, - modifier_callback=None): + def __init__(self, bcm_socket, messages, period, duration=None): """ :param bcm_socket: An open BCM socket on the desired CAN channel. :param Union[Sequence[can.Message], can.Message] messages: @@ -315,7 +314,7 @@ def __init__(self, bcm_socket, messages, period, duration=None, # - self.messages # - self.period # - self.duration - super().__init__(messages, period, duration, modifier_callback) + super().__init__(messages, period, duration) self.bcm_socket = bcm_socket self._tx_setup(self.messages) From c3784ebb1da74eb590a126f7e2bff1ccbbb46c58 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Sat, 28 Sep 2019 19:00:44 +0200 Subject: [PATCH 05/12] Forgot some brackets... --- can/broadcastmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index d66ce81fb..eb01e5d58 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -220,7 +220,7 @@ def __init__( period: float, duration: Optional[float] = None, modifier_callback: Optional[Callable[[Tuple[Message, ...]], - Tuple[Message, ...] = None, + Tuple[Message, ...]]] = None, ): super().__init__(messages, period, duration) self.bus = bus From 7d08dc914668d10319e0d182fdad89fb84d644a2 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Sat, 28 Sep 2019 19:13:11 +0200 Subject: [PATCH 06/12] Reformatting by black --- can/broadcastmanager.py | 9 +++++---- can/bus.py | 16 ++++++++-------- can/interfaces/socketcan/socketcan.py | 5 +++-- examples/cyclic_checksum.py | 7 +++---- 4 files changed, 19 insertions(+), 18 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index eb01e5d58..b11b7a331 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -136,8 +136,8 @@ def _check_modifier_callback(self, modifier_callback): if modified_msgs[0].arbitration_id != self.arbitration_id: raise ValueError( - "The modifier callback function must not modify the " - "messages' arbitration ID." + "The modifier callback function must not modify the " + "messages' arbitration ID." ) self.modifier_callback = modifier_callback @@ -219,8 +219,9 @@ def __init__( messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - modifier_callback: Optional[Callable[[Tuple[Message, ...]], - Tuple[Message, ...]]] = None, + modifier_callback: Optional[ + Callable[[Tuple[Message, ...]], Tuple[Message, ...]] + ] = None, ): super().__init__(messages, period, duration) self.bus = bus diff --git a/can/bus.py b/can/bus.py index 5ca5302f8..283f1a312 100644 --- a/can/bus.py +++ b/can/bus.py @@ -176,8 +176,9 @@ def send_periodic( period: float, duration: Optional[float] = None, store_task: bool = True, - modifier_callback: Optional[Callable[[Tuple[Message, ...]], - Tuple[Message, ...]]] = None, + modifier_callback: Optional[ + Callable[[Tuple[Message, ...]], Tuple[Message, ...]] + ] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -224,8 +225,7 @@ def send_periodic( raise ValueError("Must be either a list, tuple, or a Message") if not msgs: raise ValueError("Must be at least a list or tuple of length 1") - task = self._send_periodic_internal(msgs, period, duration, - modifier_callback) + task = self._send_periodic_internal(msgs, period, duration, modifier_callback) # we wrap the task's stop method to also remove it from the Bus's list of tasks original_stop_method = task.stop @@ -249,8 +249,9 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - modifier_callback: Optional[Callable[[Tuple[Message, ...]], - Tuple[Message, ...]]] = None + modifier_callback: Optional[ + Callable[[Tuple[Message, ...]], Tuple[Message, ...]] + ] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. @@ -274,8 +275,7 @@ def _send_periodic_internal( threading.Lock() ) # pylint: disable=attribute-defined-outside-init task = ThreadBasedCyclicSendTask( - self, self._lock_send_periodic, msgs, period, duration, - modifier_callback + self, self._lock_send_periodic, msgs, period, duration, modifier_callback ) return task diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index f3065fbc8..3b0ee5581 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -665,8 +665,9 @@ def _send_once(self, data, channel=None): raise can.CanError("Failed to transmit: %s" % exc) return sent - def _send_periodic_internal(self, msgs, period, duration=None, - modifier_callback=None): + def _send_periodic_internal( + self, msgs, period, duration=None, modifier_callback=None + ): """Start sending messages at a given period on this bus. The kernel's Broadcast Manager SocketCAN API will be used. diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py index 7ba07a7fd..fc20734b6 100644 --- a/examples/cyclic_checksum.py +++ b/examples/cyclic_checksum.py @@ -23,8 +23,7 @@ def cyclic_checksum_send(bus): The message's counter and checksum is updated before each send. Sleeps for 10 seconds then stops the task. """ - message = can.Message(arbitration_id=0xdeadbeef, - data=[0, 1, 2, 3, 4, 5, 6, 0]) + message = can.Message(arbitration_id=0xDEADBEEF, data=[0, 1, 2, 3, 4, 5, 6, 0]) print("Starting to send an auto-updating message every 1 s for 10 s") task = bus.send_periodic(message, 1, modifier_callback=update_message) assert isinstance(task, can.CyclicSendTaskABC) @@ -55,7 +54,7 @@ def compute_xbr_checksum(message, counter): Computes an XBR checksum as per SAE J1939 SPN 3188. """ checksum = sum(message.data[:7]) - checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder='big')) + checksum += sum(message.arbitration_id.to_bytes(length=4, byteorder="big")) checksum += counter & 0x0F xbr_checksum = ((checksum >> 4) + checksum) & 0x0F @@ -64,6 +63,6 @@ def compute_xbr_checksum(message, counter): if __name__ == "__main__": with can.Bus( # type: ignore - 'test', interface='virtual' + "test", interface="virtual" ) as BUS: cyclic_checksum_send(BUS) From 1ea949dc7612833d91b457ace2c3e13dfb495a05 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Mon, 30 Sep 2019 21:25:56 +0200 Subject: [PATCH 07/12] modifier_callback should change one message per send --- can/broadcastmanager.py | 11 +++++------ can/bus.py | 7 ++++--- examples/cyclic_checksum.py | 11 +++++------ 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index b11b7a331..03f6e6e6e 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -132,9 +132,9 @@ class ModifiableCyclicTaskABC(CyclicSendTaskABC): def _check_modifier_callback(self, modifier_callback): if modifier_callback is not None: - modified_msgs = modifier_callback(self.messages) + modified_msg = modifier_callback(self.messages[0]) - if modified_msgs[0].arbitration_id != self.arbitration_id: + if modified_msg.arbitration_id != self.arbitration_id: raise ValueError( "The modifier callback function must not modify the " "messages' arbitration ID." @@ -219,9 +219,7 @@ def __init__( messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - modifier_callback: Optional[ - Callable[[Tuple[Message, ...]], Tuple[Message, ...]] - ] = None, + modifier_callback: Optional[Callable[[Message], Message]] = None, ): super().__init__(messages, period, duration) self.bus = bus @@ -264,7 +262,8 @@ def _run(self): started = time.perf_counter() try: if self.modifier_callback is not None: - self.messages = self.modifier_callback(self.messages) + modified_msg = self.modifier_callback(self.messages[msg_index]) + self.messages[msg_index].data = modified_msg.data self.bus.send(self.messages[msg_index]) except Exception as exc: log.exception(exc) diff --git a/can/bus.py b/can/bus.py index 283f1a312..1a9065ff5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -176,9 +176,7 @@ def send_periodic( period: float, duration: Optional[float] = None, store_task: bool = True, - modifier_callback: Optional[ - Callable[[Tuple[Message, ...]], Tuple[Message, ...]] - ] = None, + modifier_callback: Optional[Callable[[Message], Message]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -200,6 +198,9 @@ def send_periodic( :param store_task: If True (the default) the task will be attached to this Bus instance. Disable to instead manage tasks manually. + :param modifier_callback: + Function which should be used to modify each message's data before + sending. Should take a Message as input and return the same. :return: A started task instance. Note the task can be stopped (and depending on the backend modified) by calling the :meth:`stop` method. diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py index fc20734b6..623e559b7 100644 --- a/examples/cyclic_checksum.py +++ b/examples/cyclic_checksum.py @@ -32,13 +32,12 @@ def cyclic_checksum_send(bus): print("stopped cyclic send") -def update_message(messages): - for m in messages: - counter = increment_counter(m) - checksum = compute_xbr_checksum(m, counter) - m.data[7] = (checksum << 4) + counter +def update_message(message): + counter = increment_counter(message) + checksum = compute_xbr_checksum(message, counter) + message.data[7] = (checksum << 4) + counter - return messages + return message def increment_counter(message): From 433b91f6b510ae15b2542f8b05926138e582c244 Mon Sep 17 00:00:00 2001 From: Alexander Bessman Date: Mon, 30 Sep 2019 21:51:58 +0200 Subject: [PATCH 08/12] Forgot to change type hint --- can/bus.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/can/bus.py b/can/bus.py index 1a9065ff5..81103cdc5 100644 --- a/can/bus.py +++ b/can/bus.py @@ -250,9 +250,7 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - modifier_callback: Optional[ - Callable[[Tuple[Message, ...]], Tuple[Message, ...]] - ] = None, + modifier_callback: Optional[Callable[[Message], Message]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. From 64e25bab78a67023ca80a1219177bc1ca4488f7c Mon Sep 17 00:00:00 2001 From: zariiii9003 Date: Sun, 8 Jan 2023 14:10:45 +0100 Subject: [PATCH 09/12] fix CI --- can/broadcastmanager.py | 15 +++++++++++---- can/bus.py | 7 ++++++- doc/conf.py | 1 + examples/cyclic_checksum.py | 6 ++---- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index f5be16669..fa2ccccd6 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -5,7 +5,7 @@ :meth:`can.BusABC.send_periodic`. """ -from typing import Callable, Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING +from typing import Optional, Sequence, Tuple, Union, Callable, TYPE_CHECKING from can import typechecking @@ -135,9 +135,16 @@ def start(self) -> None: class ModifiableCyclicTaskABC(CyclicSendTaskABC): - """Adds support for modifying a periodic message""" + def __init__( + self, messages: Union[Sequence[Message], Message], period: float + ) -> None: + """Adds support for modifying a periodic message""" + super().__init__(messages, period) + self.modifier_callback: Optional[Callable[[Message], Message]] = None - def _check_modifier_callback(self, modifier_callback): + def _check_modifier_callback( + self, modifier_callback: Optional[Callable[[Message], Message]] + ) -> None: if modifier_callback is not None: modified_msg = modifier_callback(self.messages[0]) @@ -220,7 +227,7 @@ def __init__( class ThreadBasedCyclicSendTask( - ModifiableCyclicTaskABC, LimitedDurationCyclicSendTaskABC, RestartableCyclicTaskABC + LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC ): """Fallback cyclic send task using daemon thread.""" diff --git a/can/bus.py b/can/bus.py index 9f3e358b2..28d2201b4 100644 --- a/can/bus.py +++ b/can/bus.py @@ -294,7 +294,12 @@ def _send_periodic_internal( threading.Lock() ) task = ThreadBasedCyclicSendTask( - self, self._lock_send_periodic, msgs, period, duration, modifier_callback + bus=self, + lock=self._lock_send_periodic, + messages=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, ) return task diff --git a/doc/conf.py b/doc/conf.py index de08fc300..f8bf418ee 100755 --- a/doc/conf.py +++ b/doc/conf.py @@ -120,6 +120,7 @@ # disable specific warnings nitpick_ignore = [ # Ignore warnings for type aliases. Remove once Sphinx supports PEP613 + ("py:class", "BusConfig"), ("py:class", "can.typechecking.BusConfig"), ("py:class", "can.typechecking.CanFilter"), ("py:class", "can.typechecking.CanFilterExtended"), diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py index 623e559b7..9e4fd049a 100644 --- a/examples/cyclic_checksum.py +++ b/examples/cyclic_checksum.py @@ -61,7 +61,5 @@ def compute_xbr_checksum(message, counter): if __name__ == "__main__": - with can.Bus( # type: ignore - "test", interface="virtual" - ) as BUS: - cyclic_checksum_send(BUS) + with can.Bus("test", interface="virtual") as _bus: + cyclic_checksum_send(_bus) From 164c2822dea977813906381cdbf9cd3d66a172ba Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 7 Apr 2023 22:22:13 +0200 Subject: [PATCH 10/12] use mutating callback, adapt SocketcanBus and ixxat, add test --- can/broadcastmanager.py | 52 ++++++++--------------- can/bus.py | 9 ++-- can/interfaces/ixxat/canlib.py | 15 +++++-- can/interfaces/ixxat/canlib_vcinpl.py | 46 +++++++++++++++----- can/interfaces/ixxat/canlib_vcinpl2.py | 49 +++++++++++++++------ can/interfaces/socketcan/socketcan.py | 35 ++++++++++----- examples/cyclic_checksum.py | 23 +++++----- test/simplecyclic_test.py | 59 ++++++++++++++++++++++---- 8 files changed, 192 insertions(+), 96 deletions(-) diff --git a/can/broadcastmanager.py b/can/broadcastmanager.py index f9116ffb2..47691d4d0 100644 --- a/can/broadcastmanager.py +++ b/can/broadcastmanager.py @@ -53,7 +53,7 @@ def stop(self) -> None: """ -class CyclicSendTaskABC(CyclicTask): +class CyclicSendTaskABC(CyclicTask, abc.ABC): """ Message send task with defined period """ @@ -114,7 +114,7 @@ def _check_and_convert_messages( return messages -class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC): +class LimitedDurationCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC): def __init__( self, messages: Union[Sequence[Message], Message], @@ -136,7 +136,7 @@ def __init__( self.duration = duration -class RestartableCyclicTaskABC(CyclicSendTaskABC): +class RestartableCyclicTaskABC(CyclicSendTaskABC, abc.ABC): """Adds support for restarting a stopped cyclic task""" @abc.abstractmethod @@ -144,28 +144,7 @@ def start(self) -> None: """Restart a stopped periodic task.""" -class ModifiableCyclicTaskABC(CyclicSendTaskABC): - def __init__( - self, messages: Union[Sequence[Message], Message], period: float - ) -> None: - """Adds support for modifying a periodic message""" - super().__init__(messages, period) - self.modifier_callback: Optional[Callable[[Message], Message]] = None - - def _check_modifier_callback( - self, modifier_callback: Optional[Callable[[Message], Message]] - ) -> None: - if modifier_callback is not None: - modified_msg = modifier_callback(self.messages[0]) - - if modified_msg.arbitration_id != self.arbitration_id: - raise ValueError( - "The modifier callback function must not modify the " - "messages' arbitration ID." - ) - - self.modifier_callback = modifier_callback - +class ModifiableCyclicTaskABC(CyclicSendTaskABC, abc.ABC): def _check_modified_messages(self, messages: Tuple[Message, ...]) -> None: """Helper function to perform error checking when modifying the data in the cyclic task. @@ -209,7 +188,7 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: self.messages = messages -class MultiRateCyclicSendTaskABC(CyclicSendTaskABC): +class MultiRateCyclicSendTaskABC(CyclicSendTaskABC, abc.ABC): """A Cyclic send task that supports switches send frequency after a set time.""" def __init__( @@ -249,7 +228,7 @@ def __init__( period: float, duration: Optional[float] = None, on_error: Optional[Callable[[Exception], bool]] = None, - modifier_callback: Optional[Callable[[Message], Message]] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, ) -> None: """Transmits `messages` with a `period` seconds for `duration` seconds on a `bus`. @@ -275,7 +254,7 @@ def __init__( time.perf_counter() + duration if duration else None ) self.on_error = on_error - self._check_modifier_callback(modifier_callback) + self.modifier_callback = modifier_callback if USE_WINDOWS_EVENTS: self.period_ms = int(round(period * 1000, 0)) @@ -319,16 +298,21 @@ def _run(self) -> None: with self.send_lock: try: if self.modifier_callback is not None: - modified_msg = self.modifier_callback(self.messages[msg_index]) - self.messages[msg_index].data = modified_msg.data + self.modifier_callback(self.messages[msg_index]) self.bus.send(self.messages[msg_index]) except Exception as exc: # pylint: disable=broad-except log.exception(exc) - if self.on_error: - if not self.on_error(exc): - break - else: + + # stop if `on_error` callback was not given + if self.on_error is None: + self.stop() + raise exc + + # stop if `on_error` returns False + if not self.on_error(exc): + self.stop() break + msg_due_time_ns += self.period_ns if self.end_time is not None and time.perf_counter() >= self.end_time: break diff --git a/can/bus.py b/can/bus.py index 17efc4c92..1890d0033 100644 --- a/can/bus.py +++ b/can/bus.py @@ -186,7 +186,7 @@ def send_periodic( period: float, duration: Optional[float] = None, store_task: bool = True, - modifier_callback: Optional[Callable[[Message], Message]] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. @@ -210,7 +210,8 @@ def send_periodic( Disable to instead manage tasks manually. :param modifier_callback: Function which should be used to modify each message's data before - sending. Should take a Message as input and return the same. + sending. The callback modifies the ``data`` :class:`bytearray` of the + message and returns ``None``. :return: A started task instance. Note the task can be stopped (and depending on the backend modified) by calling the task's @@ -225,7 +226,7 @@ def send_periodic( .. note:: - For extremely long running Bus instances with many short lived + For extremely long-running Bus instances with many short-lived tasks the default api with ``store_task==True`` may not be appropriate as the stopped tasks are still taking up memory as they are associated with the Bus instance. @@ -269,7 +270,7 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - modifier_callback: Optional[Callable[[Message], Message]] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, ) -> can.broadcastmanager.CyclicSendTaskABC: """Default implementation of periodic message sending using threading. diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 3db719f96..13813d3e2 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,5 +1,6 @@ -from typing import Optional +from typing import Callable, Optional, Sequence, Union +import can import can.interfaces.ixxat.canlib_vcinpl as vcinpl import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 from can import BusABC, Message @@ -142,8 +143,16 @@ def _recv_internal(self, timeout): def send(self, msg: Message, timeout: Optional[float] = None) -> None: return self.bus.send(msg, timeout) - def _send_periodic_internal(self, msgs, period, duration=None): - return self.bus._send_periodic_internal(msgs, period, duration) + def _send_periodic_internal( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> can.broadcastmanager.CyclicSendTaskABC: + return self.bus._send_periodic_internal( + msgs, period, duration, modifier_callback + ) def shutdown(self) -> None: super().shutdown() diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 550484f3e..1135854a1 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -13,8 +13,10 @@ import functools import logging import sys -from typing import Callable, Optional, Tuple +import warnings +from typing import Callable, Optional, Sequence, Tuple, Union +import can from can import BusABC, Message from can.broadcastmanager import ( LimitedDurationCyclicSendTaskABC, @@ -784,17 +786,39 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: # Want to log outgoing messages? # log.log(self.RECV_LOGGING_LEVEL, "Sent: %s", message) - def _send_periodic_internal(self, msgs, period, duration=None): + def _send_periodic_internal( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> can.broadcastmanager.CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) - caps = structures.CANCAPABILITIES() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask( - self._scheduler, msgs, period, duration, self._scheduler_resolution + if modifier_callback is None: + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen( + self._device_handle, self.channel, self._scheduler + ) + caps = structures.CANCAPABILITIES() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = caps.dwClockFreq / caps.dwCmsDivisor + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask( + self._scheduler, msgs, period, duration, self._scheduler_resolution + ) + + # fallback to thread based cyclic task + warnings.warn( + f"{self.__class__.__name__} falls back to a thread-based cyclic task, " + "when the `modifier_callback` argument is given." + ) + return BusABC._send_periodic_internal( + self, + msgs=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, ) def shutdown(self): diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 3e7f2ff91..4e2bf645a 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -13,7 +13,8 @@ import functools import logging import sys -from typing import Callable, Optional, Tuple +import warnings +from typing import Callable, Optional, Sequence, Tuple, Union import can.util from can import BusABC, Message @@ -931,19 +932,41 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None: else: _canlib.canChannelPostMessage(self._channel_handle, message) - def _send_periodic_internal(self, msgs, period, duration=None): + def _send_periodic_internal( + self, + msgs: Union[Sequence[Message], Message], + period: float, + duration: Optional[float] = None, + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> can.broadcastmanager.CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" - if self._scheduler is None: - self._scheduler = HANDLE() - _canlib.canSchedulerOpen(self._device_handle, self.channel, self._scheduler) - caps = structures.CANCAPABILITIES2() - _canlib.canSchedulerGetCaps(self._scheduler, caps) - self._scheduler_resolution = ( - caps.dwCmsClkFreq / caps.dwCmsDivisor - ) # TODO: confirm - _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) - return CyclicSendTask( - self._scheduler, msgs, period, duration, self._scheduler_resolution + if modifier_callback is None: + if self._scheduler is None: + self._scheduler = HANDLE() + _canlib.canSchedulerOpen( + self._device_handle, self.channel, self._scheduler + ) + caps = structures.CANCAPABILITIES2() + _canlib.canSchedulerGetCaps(self._scheduler, caps) + self._scheduler_resolution = ( + caps.dwCmsClkFreq / caps.dwCmsDivisor + ) # TODO: confirm + _canlib.canSchedulerActivate(self._scheduler, constants.TRUE) + return CyclicSendTask( + self._scheduler, msgs, period, duration, self._scheduler_resolution + ) + + # fallback to thread based cyclic task + warnings.warn( + f"{self.__class__.__name__} falls back to a thread-based cyclic task, " + "when the `modifier_callback` argument is given." + ) + return BusABC._send_periodic_internal( + self, + msgs=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, ) def shutdown(self): diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 3ee1bbd1c..39d041c08 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -14,6 +14,7 @@ import struct import threading import time +import warnings from typing import Callable, Dict, List, Optional, Sequence, Tuple, Type, Union log = logging.getLogger(__name__) @@ -801,8 +802,8 @@ def _send_periodic_internal( msgs: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, - modifier_callback: Optional[Callable[[Message], Message]] = None, - ) -> CyclicSendTask: + modifier_callback: Optional[Callable[[Message], None]] = None, + ) -> can.broadcastmanager.CyclicSendTaskABC: """Start sending messages at a given period on this bus. The Linux kernel's Broadcast Manager SocketCAN API is used to schedule @@ -834,15 +835,29 @@ def _send_periodic_internal( general the message will be sent at the given rate until at least *duration* seconds. """ - msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages( # pylint: disable=protected-access - msgs - ) + if modifier_callback is None: + msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages( # pylint: disable=protected-access + msgs + ) + + msgs_channel = str(msgs[0].channel) if msgs[0].channel else None + bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) + task_id = self._get_next_task_id() + task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) + return task - msgs_channel = str(msgs[0].channel) if msgs[0].channel else None - bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) - task_id = self._get_next_task_id() - task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) - return task + # fallback to thread based cyclic task + warnings.warn( + f"{self.__class__.__name__} falls back to a thread-based cyclic task, " + "when the `modifier_callback` argument is given." + ) + return BusABC._send_periodic_internal( + self, + msgs=msgs, + period=period, + duration=duration, + modifier_callback=modifier_callback, + ) def _get_next_task_id(self) -> int: with self._task_id_guard: diff --git a/examples/cyclic_checksum.py b/examples/cyclic_checksum.py index 9e4fd049a..3ab6c78ac 100644 --- a/examples/cyclic_checksum.py +++ b/examples/cyclic_checksum.py @@ -17,30 +17,27 @@ logging.basicConfig(level=logging.INFO) -def cyclic_checksum_send(bus): +def cyclic_checksum_send(bus: can.BusABC) -> None: """ Sends periodic messages every 1 s with no explicit timeout. The message's counter and checksum is updated before each send. Sleeps for 10 seconds then stops the task. """ - message = can.Message(arbitration_id=0xDEADBEEF, data=[0, 1, 2, 3, 4, 5, 6, 0]) - print("Starting to send an auto-updating message every 1 s for 10 s") - task = bus.send_periodic(message, 1, modifier_callback=update_message) - assert isinstance(task, can.CyclicSendTaskABC) - time.sleep(10) + message = can.Message(arbitration_id=0x78, data=[0, 1, 2, 3, 4, 5, 6, 0]) + print("Starting to send an auto-updating message every 100ms for 3 s") + task = bus.send_periodic(msgs=message, period=0.1, modifier_callback=update_message) + time.sleep(3) task.stop() print("stopped cyclic send") -def update_message(message): +def update_message(message: can.Message) -> None: counter = increment_counter(message) checksum = compute_xbr_checksum(message, counter) message.data[7] = (checksum << 4) + counter - return message - -def increment_counter(message): +def increment_counter(message: can.Message) -> int: counter = message.data[7] & 0x0F counter += 1 counter %= 16 @@ -48,7 +45,7 @@ def increment_counter(message): return counter -def compute_xbr_checksum(message, counter): +def compute_xbr_checksum(message: can.Message, counter: int) -> int: """ Computes an XBR checksum as per SAE J1939 SPN 3188. """ @@ -61,5 +58,7 @@ def compute_xbr_checksum(message, counter): if __name__ == "__main__": - with can.Bus("test", interface="virtual") as _bus: + with can.Bus(channel=0, interface="virtual", receive_own_messages=True) as _bus: + notifier = can.Notifier(bus=_bus, listeners=[print]) cyclic_checksum_send(_bus) + notifier.stop() diff --git a/test/simplecyclic_test.py b/test/simplecyclic_test.py index 9e01be457..650a1fddf 100644 --- a/test/simplecyclic_test.py +++ b/test/simplecyclic_test.py @@ -5,8 +5,10 @@ """ import gc +import time import unittest from time import sleep +from typing import List from unittest.mock import MagicMock import can @@ -160,34 +162,73 @@ def test_thread_based_cyclic_send_task(self): # good case, bus is up on_error_mock = MagicMock(return_value=False) task = can.broadcastmanager.ThreadBasedCyclicSendTask( - bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + bus=bus, + lock=bus._lock_send_periodic, + messages=msg, + period=0.1, + duration=3, + on_error=on_error_mock, ) - task.start() sleep(1) on_error_mock.assert_not_called() task.stop() bus.shutdown() - # bus has been shutted down + # bus has been shut down on_error_mock = MagicMock(return_value=False) task = can.broadcastmanager.ThreadBasedCyclicSendTask( - bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + bus=bus, + lock=bus._lock_send_periodic, + messages=msg, + period=0.1, + duration=3, + on_error=on_error_mock, ) - task.start() sleep(1) - self.assertEqual(on_error_mock.call_count, 1) + self.assertEqual(1, on_error_mock.call_count) task.stop() - # bus is still shutted down, but on_error returns True + # bus is still shut down, but on_error returns True on_error_mock = MagicMock(return_value=True) task = can.broadcastmanager.ThreadBasedCyclicSendTask( - bus, bus._lock_send_periodic, msg, 0.1, 3, on_error_mock + bus=bus, + lock=bus._lock_send_periodic, + messages=msg, + period=0.1, + duration=3, + on_error=on_error_mock, ) - task.start() sleep(1) self.assertTrue(on_error_mock.call_count > 1) task.stop() + def test_modifier_callback(self) -> None: + msg_list: List[can.Message] = [] + + def increment_first_byte(msg: can.Message) -> None: + msg.data[0] += 1 + + original_msg = can.Message( + is_extended_id=False, arbitration_id=0x123, data=[0] * 8 + ) + + with can.ThreadSafeBus(interface="virtual", receive_own_messages=True) as bus: + notifier = can.Notifier(bus=bus, listeners=[msg_list.append]) + task = bus.send_periodic( + msgs=original_msg, period=0.001, modifier_callback=increment_first_byte + ) + time.sleep(0.2) + task.stop() + notifier.stop() + + self.assertEqual(b"\x01\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[0].data)) + self.assertEqual(b"\x02\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[1].data)) + self.assertEqual(b"\x03\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[2].data)) + self.assertEqual(b"\x04\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[3].data)) + self.assertEqual(b"\x05\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[4].data)) + self.assertEqual(b"\x06\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[5].data)) + self.assertEqual(b"\x07\x00\x00\x00\x00\x00\x00\x00", bytes(msg_list[6].data)) + if __name__ == "__main__": unittest.main() From 0b02d1addff14df1559f3d0d062b7307a7d5a9df Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Fri, 7 Apr 2023 23:15:22 +0200 Subject: [PATCH 11/12] improve docstring --- can/bus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/can/bus.py b/can/bus.py index 1890d0033..d1c00f87a 100644 --- a/can/bus.py +++ b/can/bus.py @@ -210,7 +210,7 @@ def send_periodic( Disable to instead manage tasks manually. :param modifier_callback: Function which should be used to modify each message's data before - sending. The callback modifies the ``data`` :class:`bytearray` of the + sending. The callback modifies the :attr:`~can.Message.data` of the message and returns ``None``. :return: A started task instance. Note the task can be stopped (and depending on From 93ef6981be1d5b5db261f98596b3925cc69da241 Mon Sep 17 00:00:00 2001 From: zariiii9003 <52598363+zariiii9003@users.noreply.github.com> Date: Mon, 15 May 2023 17:20:25 +0200 Subject: [PATCH 12/12] fix ixxat imports --- can/interfaces/ixxat/canlib.py | 11 +++++++---- can/interfaces/ixxat/canlib_vcinpl.py | 12 +++++++----- can/interfaces/ixxat/canlib_vcinpl2.py | 9 ++++++--- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/can/interfaces/ixxat/canlib.py b/can/interfaces/ixxat/canlib.py index 9e6880a17..d47fc2d6a 100644 --- a/can/interfaces/ixxat/canlib.py +++ b/can/interfaces/ixxat/canlib.py @@ -1,10 +1,13 @@ from typing import Callable, Optional, Sequence, Union -import can import can.interfaces.ixxat.canlib_vcinpl as vcinpl import can.interfaces.ixxat.canlib_vcinpl2 as vcinpl2 -from can import BusABC, Message -from can.bus import BusState +from can import ( + BusABC, + BusState, + CyclicSendTaskABC, + Message, +) class IXXATBus(BusABC): @@ -152,7 +155,7 @@ def _send_periodic_internal( period: float, duration: Optional[float] = None, modifier_callback: Optional[Callable[[Message], None]] = None, - ) -> can.broadcastmanager.CyclicSendTaskABC: + ) -> CyclicSendTaskABC: return self.bus._send_periodic_internal( msgs, period, duration, modifier_callback ) diff --git a/can/interfaces/ixxat/canlib_vcinpl.py b/can/interfaces/ixxat/canlib_vcinpl.py index 7106d9ef8..cbf2fb61c 100644 --- a/can/interfaces/ixxat/canlib_vcinpl.py +++ b/can/interfaces/ixxat/canlib_vcinpl.py @@ -16,13 +16,15 @@ import warnings from typing import Callable, Optional, Sequence, Tuple, Union -import can -from can import BusABC, CanProtocol, Message -from can.broadcastmanager import ( +from can import ( + BusABC, + BusState, + CanProtocol, + CyclicSendTaskABC, LimitedDurationCyclicSendTaskABC, + Message, RestartableCyclicTaskABC, ) -from can.bus import BusState from can.ctypesutil import HANDLE, PHANDLE, CLibrary from can.ctypesutil import HRESULT as ctypes_HRESULT from can.exceptions import CanInitializationError, CanInterfaceNotImplementedError @@ -793,7 +795,7 @@ def _send_periodic_internal( period: float, duration: Optional[float] = None, modifier_callback: Optional[Callable[[Message], None]] = None, - ) -> can.broadcastmanager.CyclicSendTaskABC: + ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" if modifier_callback is None: if self._scheduler is None: diff --git a/can/interfaces/ixxat/canlib_vcinpl2.py b/can/interfaces/ixxat/canlib_vcinpl2.py index 87c076e21..b796be744 100644 --- a/can/interfaces/ixxat/canlib_vcinpl2.py +++ b/can/interfaces/ixxat/canlib_vcinpl2.py @@ -16,9 +16,12 @@ import warnings from typing import Callable, Optional, Sequence, Tuple, Union -from can import BusABC, CanProtocol, Message -from can.broadcastmanager import ( +from can import ( + BusABC, + CanProtocol, + CyclicSendTaskABC, LimitedDurationCyclicSendTaskABC, + Message, RestartableCyclicTaskABC, ) from can.ctypesutil import HANDLE, PHANDLE, CLibrary @@ -938,7 +941,7 @@ def _send_periodic_internal( period: float, duration: Optional[float] = None, modifier_callback: Optional[Callable[[Message], None]] = None, - ) -> can.broadcastmanager.CyclicSendTaskABC: + ) -> CyclicSendTaskABC: """Send a message using built-in cyclic transmit list functionality.""" if modifier_callback is None: if self._scheduler is None: