From e559e1a71871a45c1d3585ef06a29f0670ee05b8 Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Mon, 2 Mar 2020 19:34:42 +0300 Subject: [PATCH 1/9] relax restriction on unique arb_id for socketcan bcm --- can/interfaces/socketcan/socketcan.py | 61 +++++++++------------------ 1 file changed, 19 insertions(+), 42 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 307763d9f..03bbaf0d3 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -7,6 +7,7 @@ from typing import Dict, List, Optional, Sequence, Tuple, Type, Union +import threading import logging import ctypes import ctypes.util @@ -162,7 +163,7 @@ def build_can_frame(msg: Message) -> bytes: __u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8))); }; """ - can_id = _add_flags_to_can_id(msg) + can_id = _compose_arbitration_id(msg) flags = 0 if msg.bitrate_switch: flags |= CANFD_BRS @@ -286,7 +287,18 @@ def send_bcm(bcm_socket: socket.socket, data: bytes) -> int: raise e -def _add_flags_to_can_id(message: Message) -> int: +GLOBAL_TASK_ID = 0 +TASK_LOCK = threading.Lock() + + +def _compose_next_task_id() -> int: + with TASK_LOCK: + global GLOBAL_TASK_ID + GLOBAL_TASK_ID += 1 + return GLOBAL_TASK_ID + + +def _compose_arbitration_id(message: Message) -> int: can_id = message.arbitration_id if message.is_extended_id: log.debug("sending an extended id type message") @@ -297,7 +309,6 @@ def _add_flags_to_can_id(message: Message) -> int: if message.is_error_frame: log.debug("sending error frame") can_id |= CAN_ERR_FLAG - return can_id @@ -341,7 +352,7 @@ def __init__( def _tx_setup(self, messages: Sequence[Message]) -> None: # Create a low level packed frame to pass to the kernel body = bytearray() - self.can_id_with_flags = _add_flags_to_can_id(messages[0]) + self.task_id = _compose_next_task_id() self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 if self.duration: @@ -353,39 +364,8 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: ival1 = 0.0 ival2 = self.period - # First do a TX_READ before creating a new task, and check if we get - # EINVAL. If so, then we are referring to a CAN message with the same - # ID - check_header = build_bcm_header( - opcode=CAN_BCM_TX_READ, - flags=0, - count=0, - ival1_seconds=0, - ival1_usec=0, - ival2_seconds=0, - ival2_usec=0, - can_id=self.can_id_with_flags, - nframes=0, - ) - try: - self.bcm_socket.send(check_header) - except OSError as e: - if e.errno != errno.EINVAL: - raise e - else: - raise ValueError( - "A periodic Task for Arbitration ID {} has already been created".format( - messages[0].arbitration_id - ) - ) - header = build_bcm_transmit_header( - self.can_id_with_flags, - count, - ival1, - ival2, - self.flags, - nframes=len(messages), + self.task_id, count, ival1, ival2, self.flags, nframes=len(messages) ) for message in messages: body += build_can_frame(message) @@ -401,7 +381,7 @@ def stop(self) -> None: """ log.debug("Stopping periodic task") - stopframe = build_bcm_tx_delete_header(self.can_id_with_flags, self.flags) + stopframe = build_bcm_tx_delete_header(self.task_id, self.flags) send_bcm(self.bcm_socket, stopframe) def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: @@ -423,7 +403,7 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: body = bytearray() header = build_bcm_update_header( - can_id=self.can_id_with_flags, msg_flags=self.flags, nframes=len(messages) + can_id=self.task_id, msg_flags=self.flags, nframes=len(messages) ) for message in messages: body += build_can_frame(message) @@ -452,7 +432,7 @@ def __init__( # Create a low level packed frame to pass to the kernel header = build_bcm_transmit_header( - self.can_id_with_flags, + self.task_id, count, initial_period, subsequent_period, @@ -734,9 +714,6 @@ def _send_periodic_internal( msgs_channel = str(msgs[0].channel) if msgs[0].channel else None bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) - # TODO: The SocketCAN BCM interface treats all cyclic tasks sharing an - # Arbitration ID as the same Cyclic group. We should probably warn the - # user instead of overwriting the old group? task = CyclicSendTask(bcm_socket, msgs, period, duration) return task From 0a3672d88e66861e130517953f43419dae169104 Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Mon, 2 Mar 2020 19:49:59 +0300 Subject: [PATCH 2/9] remove test_create_same_id_raises_exception --- test/test_cyclic_socketcan.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index bb5411be9..569a1969e 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -244,27 +244,6 @@ def test_cyclic_initializer_different_arbitration_ids(self): with self.assertRaises(ValueError): task = self._send_bus.send_periodic(messages, self.PERIOD) - def test_create_same_id_raises_exception(self): - messages_a = can.Message( - arbitration_id=0x401, - data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], - is_extended_id=False, - ) - - messages_b = can.Message( - arbitration_id=0x401, - data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], - is_extended_id=False, - ) - - task_a = self._send_bus.send_periodic(messages_a, 1) - self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC) - - # The second one raises a ValueError when we attempt to create a new - # Task, since it has the same arbitration ID. - with self.assertRaises(ValueError): - task_b = self._send_bus.send_periodic(messages_b, 1) - def test_modify_data_list(self): messages_odd = [] messages_odd.append( From 9d7c59960b40012bf92fcdeba33aeeb717c84604 Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Thu, 5 Mar 2020 19:09:33 +0300 Subject: [PATCH 3/9] handle tasks id differently --- can/interfaces/socketcan/socketcan.py | 28 +++++++-------- test/test_cyclic_socketcan.py | 49 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 03bbaf0d3..1dc855093 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -7,7 +7,6 @@ from typing import Dict, List, Optional, Sequence, Tuple, Type, Union -import threading import logging import ctypes import ctypes.util @@ -287,17 +286,6 @@ def send_bcm(bcm_socket: socket.socket, data: bytes) -> int: raise e -GLOBAL_TASK_ID = 0 -TASK_LOCK = threading.Lock() - - -def _compose_next_task_id() -> int: - with TASK_LOCK: - global GLOBAL_TASK_ID - GLOBAL_TASK_ID += 1 - return GLOBAL_TASK_ID - - def _compose_arbitration_id(message: Message) -> int: can_id = message.arbitration_id if message.is_extended_id: @@ -327,6 +315,7 @@ class CyclicSendTask( def __init__( self, bcm_socket: socket.socket, + task_id: int, messages: Union[Sequence[Message], Message], period: float, duration: Optional[float] = None, @@ -347,12 +336,12 @@ def __init__( super().__init__(messages, period, duration) self.bcm_socket = bcm_socket + self.task_id = task_id self._tx_setup(self.messages) def _tx_setup(self, messages: Sequence[Message]) -> None: # Create a low level packed frame to pass to the kernel body = bytearray() - self.task_id = _compose_next_task_id() self.flags = CAN_FD_FRAME if messages[0].is_fd else 0 if self.duration: @@ -423,12 +412,13 @@ class MultiRateCyclicSendTask(CyclicSendTask): def __init__( self, channel: socket.socket, + task_id: int, messages: Sequence[Message], count: int, initial_period: float, subsequent_period: float, ): - super().__init__(channel, messages, subsequent_period) + super().__init__(channel, task_id, messages, subsequent_period) # Create a low level packed frame to pass to the kernel header = build_bcm_transmit_header( @@ -577,6 +567,7 @@ def __init__( self.channel_info = "socketcan channel '%s'" % channel self._bcm_sockets: Dict[str, socket.socket] = {} self._is_filtered = False + self._task_id = 0 # set the receive_own_messages parameter try: @@ -714,9 +705,16 @@ def _send_periodic_internal( msgs_channel = str(msgs[0].channel) if msgs[0].channel else None bcm_socket = self._get_bcm_socket(msgs_channel or self.channel) - task = CyclicSendTask(bcm_socket, msgs, period, duration) + task_id = self._get_next_task_id() + task = CyclicSendTask(bcm_socket, task_id, msgs, period, duration) return task + def _get_next_task_id(self) -> int: + if self._task_id >= 2 ** 11 - 1: + self._task_id = 0 + self._task_id += 1 + return self._task_id + def _get_bcm_socket(self, channel: str) -> socket.socket: if channel not in self._bcm_sockets: self._bcm_sockets[channel] = create_bcm_socket(self.channel) diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index 569a1969e..5bc2a913a 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -244,6 +244,55 @@ def test_cyclic_initializer_different_arbitration_ids(self): with self.assertRaises(ValueError): task = self._send_bus.send_periodic(messages, self.PERIOD) + def test_create_same_id(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + messages_b = can.Message( + arbitration_id=0x401, + data=[0x22, 0x22, 0x22, 0x22, 0x22, 0x22], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, self.PERIOD) + self.assertIsInstance(task_a, can.broadcastmanager.CyclicSendTaskABC) + task_b = self._send_bus.send_periodic(messages_b, self.PERIOD) + self.assertIsInstance(task_b, can.broadcastmanager.CyclicSendTaskABC) + + time.sleep(self.PERIOD * 4) + + task_a.stop() + task_b.stop() + + msgs = [] + for _ in range(4): + msg = self._recv_bus.recv(self.PERIOD * 2) + self.assertIsNotNone(msg) + + msgs.append(msg) + + self.assertTrue(len(msgs) >= 4) + + # Both messages should be recevied on the bus, + # even with the same arbitration id + msg_a_data_present = msg_b_data_present = False + for rx_message in msgs: + self.assertTrue( + rx_message.arbitration_id + == messages_a.arbitration_id + == messages_b.arbitration_id + ) + if rx_message.data == messages_a.data: + msg_a_data_present = True + if rx_message.data == messages_b.data: + msg_b_data_present = True + + self.assertTrue(msg_a_data_present) + self.assertTrue(msg_b_data_present) + def test_modify_data_list(self): messages_odd = [] messages_odd.append( From 1118e3b4317e1f0a6dace2dbfe57c0e481e283c1 Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Thu, 5 Mar 2020 19:25:58 +0300 Subject: [PATCH 4/9] wrap task id --- can/interfaces/socketcan/socketcan.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 1dc855093..9422bece9 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -710,9 +710,7 @@ def _send_periodic_internal( return task def _get_next_task_id(self) -> int: - if self._task_id >= 2 ** 11 - 1: - self._task_id = 0 - self._task_id += 1 + self._task_id = (self._task_id + 1) % (2 ** 11 - 1) return self._task_id def _get_bcm_socket(self, channel: str) -> socket.socket: From c724519030b095b1603318fe81a982c8e055c7fe Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Sat, 4 Apr 2020 16:28:06 +0300 Subject: [PATCH 5/9] Add a check that task id is not already present --- can/interfaces/socketcan/socketcan.py | 35 +++++++++++++++++++++++++-- test/test_cyclic_socketcan.py | 20 +++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 9422bece9..1c27a101b 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -14,6 +14,7 @@ import socket import struct import time +import threading import errno log = logging.getLogger(__name__) @@ -353,6 +354,8 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: ival1 = 0.0 ival2 = self.period + self._check_bcm_task() + header = build_bcm_transmit_header( self.task_id, count, ival1, ival2, self.flags, nframes=len(messages) ) @@ -361,6 +364,32 @@ def _tx_setup(self, messages: Sequence[Message]) -> None: log.debug("Sending BCM command") send_bcm(self.bcm_socket, header + body) + def _check_bcm_task(self): + # Do a TX_READ on a task ID, and check if we get EINVAL. If so, + # then we are referring to a CAN message with the existing ID + check_header = build_bcm_header( + opcode=CAN_BCM_TX_READ, + flags=0, + count=0, + ival1_seconds=0, + ival1_usec=0, + ival2_seconds=0, + ival2_usec=0, + can_id=self.task_id, + nframes=0, + ) + try: + self.bcm_socket.send(check_header) + except OSError as e: + if e.errno != errno.EINVAL: + raise e + else: + raise ValueError( + "A periodic task for Task ID {} is already in progress by SocketCAN Linux layer".format( + self.task_id + ) + ) + def stop(self) -> None: """Send a TX_DELETE message to cancel this task. @@ -568,6 +597,7 @@ def __init__( self._bcm_sockets: Dict[str, socket.socket] = {} self._is_filtered = False self._task_id = 0 + self._task_id_guard = threading.Lock() # set the receive_own_messages parameter try: @@ -710,8 +740,9 @@ def _send_periodic_internal( return task def _get_next_task_id(self) -> int: - self._task_id = (self._task_id + 1) % (2 ** 11 - 1) - return self._task_id + with self._task_id_guard: + self._task_id = (self._task_id + 1) % (2 ** 32 - 1) + return self._task_id def _get_bcm_socket(self, channel: str) -> socket.socket: if channel not in self._bcm_sockets: diff --git a/test/test_cyclic_socketcan.py b/test/test_cyclic_socketcan.py index 5bc2a913a..40c7af582 100644 --- a/test/test_cyclic_socketcan.py +++ b/test/test_cyclic_socketcan.py @@ -244,6 +244,26 @@ def test_cyclic_initializer_different_arbitration_ids(self): with self.assertRaises(ValueError): task = self._send_bus.send_periodic(messages, self.PERIOD) + def test_start_already_started_task(self): + messages_a = can.Message( + arbitration_id=0x401, + data=[0x11, 0x11, 0x11, 0x11, 0x11, 0x11], + is_extended_id=False, + ) + + task_a = self._send_bus.send_periodic(messages_a, self.PERIOD) + time.sleep(0.1) + + # Try to start it again, task_id is not incremented in this case + with self.assertRaises(ValueError) as ctx: + task_a.start() + self.assertEqual( + "A periodic task for Task ID 1 is already in progress by SocketCAN Linux layer", + str(ctx.exception), + ) + + task_a.stop() + def test_create_same_id(self): messages_a = can.Message( arbitration_id=0x401, From 3f3a9ba1772dd889a548224d3f3385b29f0ea65f Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Tue, 7 Apr 2020 18:56:25 +0300 Subject: [PATCH 6/9] Add task_id description --- can/interfaces/socketcan/socketcan.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index 1c27a101b..01884d3d8 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -323,6 +323,8 @@ def __init__( ): """ :param bcm_socket: An open BCM socket on the desired CAN channel. + :param task_id: + The identifier used to uniquely reference particular cyclic send task. :param messages: The messages to be sent periodically. :param period: From 53c25db4d568df1cbd4d9132dbc1d18f08f7d895 Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Thu, 9 Apr 2020 17:07:36 +0300 Subject: [PATCH 7/9] Update docs --- can/interfaces/socketcan/socketcan.py | 43 ++++++++----- doc/bus.rst | 2 +- doc/interfaces/socketcan.rst | 89 ++++++++++++++++++--------- 3 files changed, 87 insertions(+), 47 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index f9d921b48..cee22d14f 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -310,7 +310,6 @@ class CyclicSendTask( - setting of a task duration - modifying the data - stopping then subsequent restarting of the task - """ def __init__( @@ -321,10 +320,12 @@ def __init__( period: float, duration: Optional[float] = None, ): - """ + """Construct and :meth:`~start` a task. + :param bcm_socket: An open BCM socket on the desired CAN channel. :param task_id: - The identifier used to uniquely reference particular cyclic send task. + The identifier used to uniquely reference particular cyclic send task + within Linux BCM. :param messages: The messages to be sent periodically. :param period: @@ -393,11 +394,11 @@ def _check_bcm_task(self): ) def stop(self) -> None: - """Send a TX_DELETE message to cancel this task. + """Stop a task by sending TX_DELETE message to Linux kernel. This will delete the entry for the transmission of the CAN-message - with the specified can_id CAN identifier. The message length for the command - TX_DELETE is {[bcm_msg_head]} (only the header). + with the specified :attr:`~task_id` identifier. The message length + for the command TX_DELETE is {[bcm_msg_head]} (only the header). """ log.debug("Stopping periodic task") @@ -405,14 +406,15 @@ def stop(self) -> None: send_bcm(self.bcm_socket, stopframe) def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: - """Update the contents of the periodically sent messages. + """Update the contents of the periodically sent CAN messages by + sending TX_SETUP message to Linux kernel. - Note: The messages must all have the same - :attr:`~can.Message.arbitration_id` like the first message. - - Note: The number of new cyclic messages to be sent must be equal to the + The number of new cyclic messages to be sent must be equal to the original number of messages originally specified for this task. + .. note:: The messages must all have the same + :attr:`~can.Message.arbitration_id` like the first message. + :param messages: The messages with the new :attr:`can.Message.data`. """ @@ -431,6 +433,14 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None: send_bcm(self.bcm_socket, header + body) def start(self) -> None: + """Start a periodic task by sending TX_SETUP message to Linux kernel. + + It verifies presence of the particular BCM task through sending TX_READ + message to Linux kernel prior to scheduling. + + :raises ValueError: + If the task referenced by :attr:`~task_id` is already running. + """ self._tx_setup(self.messages) @@ -572,8 +582,10 @@ def capture_message( class SocketcanBus(BusABC): - """ - Implements :meth:`can.BusABC._detect_available_configs`. + """ A SocketCAN interface to CAN. + + It implements :meth:`can.BusABC._detect_available_configs` to search for + available interfaces. """ def __init__( @@ -715,7 +727,7 @@ def _send_periodic_internal( ) -> CyclicSendTask: """Start sending messages at a given period on this bus. - The kernel's Broadcast Manager SocketCAN API will be used. + The Linux kernel's Broadcast Manager SocketCAN API is used. :param messages: The messages to be sent periodically @@ -726,7 +738,7 @@ def _send_periodic_internal( no duration is provided, the task will continue indefinitely. :return: - A started task instance. This can be used to modify the data, + A :class:`CyclicSendTask` task instance. This can be used to modify the data, pause/resume the transmission and to stop the transmission. .. note:: @@ -735,7 +747,6 @@ def _send_periodic_internal( be exactly the same as the duration specified by the user. In general the message will be sent at the given rate until at least *duration* seconds. - """ msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(msgs) diff --git a/doc/bus.rst b/doc/bus.rst index 5c1e95606..b524d4868 100644 --- a/doc/bus.rst +++ b/doc/bus.rst @@ -67,7 +67,7 @@ This thread safe version of the :class:`~can.BusABC` class can be used by multip Sending and receiving is locked separately to avoid unnecessary delays. Conflicting calls are executed by blocking until the bus is accessible. -It can be used exactly like the normal :class:`~can.BusABC`: +It can be used exactly like the normal :class:`~can.BusABC`:: # 'socketcan' is only an example interface, it works with all the others too my_bus = can.ThreadSafeBus(interface='socketcan', channel='vcan0') diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index bdd934ca7..005b2fcc8 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -1,16 +1,26 @@ SocketCAN ========= -The full documentation for socketcan can be found in the kernel docs at -`networking/can.txt `_. - - -.. note:: - - Versions before 2.2 had two different implementations named +The `SocketCAN`_ documentation can be found in the Linux kernel docs at +``networking`` directory. Hereafter is quote from SocketCAN Linux document:: + +> The socketcan package is an implementation of CAN protocols +> (Controller Area Network) for Linux. CAN is a networking technology +> which has widespread use in automation, embedded devices, and +> automotive fields. While there have been other CAN implementations +> for Linux based on character devices, SocketCAN uses the Berkeley +> socket API, the Linux network stack and implements the CAN device +> drivers as network interfaces. The CAN socket API has been designed +> as similar as possible to the TCP/IP protocols to allow programmers, +> familiar with network programming, to easily learn how to use CAN +> sockets. + +.. important:: + + `python-can` versions before 2.2 had two different implementations named ``socketcan_ctypes`` and ``socketcan_native``. These are now deprecated and the aliases to ``socketcan`` will be removed in - version 4.0. 3.x releases raise a DeprecationWarning. + version 4.0. 3.x releases raise a DeprecationWarning. Socketcan Quickstart @@ -53,7 +63,8 @@ existing ``can0`` interface with a bitrate of 1MB: PCAN ~~~~ -Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketcan`, so there is no need to install any drivers. The CAN interface can be brought like so: +Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketcan`, +so there is no need to install any drivers. The CAN interface can be brought like so: :: @@ -61,12 +72,22 @@ Kernels >= 3.4 supports the PCAN adapters natively via :doc:`/interfaces/socketc sudo modprobe peak_pci sudo ip link set can0 up type can bitrate 500000 +Intrepid +~~~~~~~~ + +The Intrepid Control Systems, Inc provides several devices (e.g. ValueCAN) as well +as Linux module and user-space daemon to make it possible to use them via SocketCAN. + +Refer to below repositories for installation instructions: + +- `Intrepid kernel module`_ +- `Intrepid user-space daemon`_ + Send Test Message ^^^^^^^^^^^^^^^^^ -The `can-utils `_ library for linux -includes a script `cansend` which is useful to send known payloads. For -example to send a message on `vcan0`: +The `can-utils`_ library for Linux includes a `cansend` tool which is useful to +send known payloads. For example to send a message on `vcan0`: .. code-block:: bash @@ -138,7 +159,7 @@ To spam a bus: def producer(id): """:param id: Spam the bus with messages including the data id.""" - bus = can.interface.Bus(channel=channel, bustype=bustype) + bus = can.Bus(channel=channel, interface=bustype) for i in range(10): msg = can.Message(arbitration_id=0xc0ffee, data=[id, i, 0, 1, 3, 1, 4, 1], is_extended_id=False) bus.send(msg) @@ -170,8 +191,7 @@ function: import can - can_interface = 'vcan0' - bus = can.interface.Bus(can_interface, bustype='socketcan') + bus = can.Bus(channel='vcan0', interface='socketcan') message = bus.recv() By default, this performs a blocking read, which means ``bus.recv()`` won't @@ -204,30 +224,39 @@ socket api. This allows the cyclic transmission of CAN messages at given interva The overhead for periodic message sending is extremely low as all the heavy lifting occurs within the linux kernel. -send_periodic() -~~~~~~~~~~~~~~~ +The :class:`~can.BusABC` initialized for `socketcan` interface transparently handles +scheduling of CAN messages to Linux BCM via :meth:`~can.BusABC.send_periodic`: -An example that uses the send_periodic is included in ``python-can/examples/cyclic.py`` +.. code-block:: python -The object returned can be used to halt, alter or cancel the periodic message task. + with can.interface.Bus(interface="socketcan", channel="can0") as bus: + task = bus.send_periodic(...) -.. autoclass:: can.interfaces.socketcan.CyclicSendTask +More examples that uses :meth:`~can.BusABC.send_periodic` are included +in ``python-can/examples/cyclic.py``. + +The `task` object returned by :meth:`~can.BusABC.send_periodic` can be used to halt, +alter or cancel the periodic message task: +.. autoclass:: can.interfaces.socketcan.CyclicSendTask + :members: Bus --- -.. autoclass:: can.interfaces.socketcan.SocketcanBus +The :class:`~can.interfaces.socketcan.SocketcanBus` specializes :class:`~can.BusABC` +to ensure usage of SocketCAN Linux API. The most important differences are: - .. method:: recv(timeout=None) +- usage of SocketCAN BCM for periodic messages scheduling; +- filtering of CAN messages on Linux kernel level. - Block waiting for a message from the Bus. +.. autoclass:: can.interfaces.socketcan.SocketcanBus + :members: + :inherited-members: - :param float timeout: - seconds to wait for a message or None to wait indefinitely +.. External references - :rtype: can.Message or None - :return: - None on timeout or a :class:`can.Message` object. - :raises can.CanError: - if an error occurred while reading +.. _SocketCAN: https://www.kernel.org/doc/Documentation/networking/can.txt +.. _Intrepid kernel module: https://github.com/intrepidcs/intrepid-socketcan-kernel-module +.. _Intrepid user-space daemon: https://github.com/intrepidcs/icsscand +.. _can-utils: https://github.com/linux-can/can-utils From c2946e72159485642a456c99b3f406351f9c099b Mon Sep 17 00:00:00 2001 From: Mikhail Kulinich Date: Thu, 9 Apr 2020 20:59:57 +0300 Subject: [PATCH 8/9] Update docs --- can/interfaces/socketcan/socketcan.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/can/interfaces/socketcan/socketcan.py b/can/interfaces/socketcan/socketcan.py index cee22d14f..e16ad0c94 100644 --- a/can/interfaces/socketcan/socketcan.py +++ b/can/interfaces/socketcan/socketcan.py @@ -727,16 +727,24 @@ def _send_periodic_internal( ) -> CyclicSendTask: """Start sending messages at a given period on this bus. - The Linux kernel's Broadcast Manager SocketCAN API is used. + The Linux kernel's Broadcast Manager SocketCAN API is used to schedule + periodic sending of CAN messages. The wrapping 32-bit counter (see + :meth:`~_get_next_task_id()`) designated to distinguish different + :class:`CyclicSendTask` within BCM provides flexibility to schedule + CAN messages sending with the same CAN ID, but different CAN data. :param messages: - The messages to be sent periodically + The message(s) to be sent periodically. :param period: The rate in seconds at which to send the messages. :param duration: Approximate duration in seconds to continue sending messages. If no duration is provided, the task will continue indefinitely. + :raises ValueError: + If task identifier passed to :class:`CyclicSendTask` can't be used + to schedule new task in Linux BCM. + :return: A :class:`CyclicSendTask` task instance. This can be used to modify the data, pause/resume the transmission and to stop the transmission. From f67a780e001cc1cba29b1df6086a1837658ab735 Mon Sep 17 00:00:00 2001 From: Brian Thorne Date: Fri, 10 Apr 2020 09:29:21 +1200 Subject: [PATCH 9/9] minor rephrasing in doc/interfaces/socketcan.rst --- doc/interfaces/socketcan.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/interfaces/socketcan.rst b/doc/interfaces/socketcan.rst index 005b2fcc8..1783c388c 100644 --- a/doc/interfaces/socketcan.rst +++ b/doc/interfaces/socketcan.rst @@ -2,7 +2,7 @@ SocketCAN ========= The `SocketCAN`_ documentation can be found in the Linux kernel docs at -``networking`` directory. Hereafter is quote from SocketCAN Linux document:: +``networking`` directory. Quoting from the SocketCAN Linux documentation:: > The socketcan package is an implementation of CAN protocols > (Controller Area Network) for Linux. CAN is a networking technology