• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hardbyte / python-can / 16362801995

18 Jul 2025 05:17AM UTC coverage: 70.862% (+0.1%) from 70.763%
16362801995

Pull #1920

github

web-flow
Merge f9e8a3c29 into 958fc64ed
Pull Request #1920: add FD support to slcan according to CANable 2.0 impementation

6 of 45 new or added lines in 1 file covered. (13.33%)

838 existing lines in 35 files now uncovered.

7770 of 10965 relevant lines covered (70.86%)

13.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.96
/can/interfaces/socketcan/socketcan.py
1
"""
21✔
2
The main module of the socketcan interface containing most user-facing classes and methods
3
along some internal methods.
4

5
At the end of the file the usage of the internal methods is shown.
6
"""
7

8
import ctypes
21✔
9
import ctypes.util
21✔
10
import errno
21✔
11
import logging
21✔
12
import select
21✔
13
import socket
21✔
14
import struct
21✔
15
import threading
21✔
16
import time
21✔
17
import warnings
21✔
18
from collections.abc import Sequence
21✔
19
from typing import Callable, Optional, Union
21✔
20

21
import can
21✔
22
from can import BusABC, CanProtocol, Message
21✔
23
from can.broadcastmanager import (
21✔
24
    LimitedDurationCyclicSendTaskABC,
25
    ModifiableCyclicTaskABC,
26
    RestartableCyclicTaskABC,
27
)
28
from can.interfaces.socketcan import constants
21✔
29
from can.interfaces.socketcan.utils import find_available_interfaces, pack_filters
21✔
30
from can.typechecking import CanFilters
21✔
31

32
log = logging.getLogger(__name__)
21✔
33
log_tx = log.getChild("tx")
21✔
34
log_rx = log.getChild("rx")
21✔
35

36
try:
21✔
37
    from socket import CMSG_SPACE
21✔
38

39
    CMSG_SPACE_available = True
14✔
40
except ImportError:
7✔
41
    CMSG_SPACE_available = False
7✔
42
    log.error("socket.CMSG_SPACE not available on this platform")
7✔
43

44

45
# Constants needed for precise handling of timestamps
46
RECEIVED_TIMESTAMP_STRUCT = struct.Struct("@ll")
21✔
47
RECEIVED_ANCILLARY_BUFFER_SIZE = (
21✔
48
    CMSG_SPACE(RECEIVED_TIMESTAMP_STRUCT.size) if CMSG_SPACE_available else 0
49
)
50

51

52
# Setup BCM struct
53
def bcm_header_factory(
21✔
54
    fields: list[tuple[str, Union[type[ctypes.c_uint32], type[ctypes.c_long]]]],
55
    alignment: int = 8,
56
):
57
    curr_stride = 0
21✔
58
    results: list[
21✔
59
        tuple[
60
            str, Union[type[ctypes.c_uint8], type[ctypes.c_uint32], type[ctypes.c_long]]
61
        ]
62
    ] = []
63
    pad_index = 0
21✔
64
    for field in fields:
21✔
65
        field_alignment = ctypes.alignment(field[1])
21✔
66
        field_size = ctypes.sizeof(field[1])
21✔
67

68
        # If the current stride index isn't a multiple of the alignment
69
        # requirements of this field, then we must add padding bytes until we
70
        # are aligned
71
        while curr_stride % field_alignment != 0:
21✔
72
            results.append((f"pad_{pad_index}", ctypes.c_uint8))
21✔
73
            pad_index += 1
21✔
74
            curr_stride += 1
21✔
75

76
        # Now can it fit?
77
        # Example: If this is 8 bytes and the type requires 4 bytes alignment
78
        # then we can only fit when we're starting at 0. Otherwise, we will
79
        # split across 2 strides.
80
        #
81
        # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
82
        results.append(field)
21✔
83
        curr_stride += field_size
21✔
84

85
    # Add trailing padding to align to a multiple of the largest scalar member
86
    # in the structure
87
    while curr_stride % alignment != 0:
21✔
88
        results.append((f"pad_{pad_index}", ctypes.c_uint8))
21✔
89
        pad_index += 1
21✔
90
        curr_stride += 1
21✔
91

92
    return type("BcmMsgHead", (ctypes.Structure,), {"_fields_": results})
21✔
93

94

95
# The fields definition is taken from the C struct definitions in
96
# <linux/can/bcm.h>
97
#
98
#     struct bcm_timeval {
99
#             long tv_sec;
100
#             long tv_usec;
101
#     };
102
#
103
#     /**
104
#      * struct bcm_msg_head - head of messages to/from the broadcast manager
105
#      * @opcode:    opcode, see enum below.
106
#      * @flags:     special flags, see below.
107
#      * @count:     number of frames to send before changing interval.
108
#      * @ival1:     interval for the first @count frames.
109
#      * @ival2:     interval for the following frames.
110
#      * @can_id:    CAN ID of frames to be sent or received.
111
#      * @nframes:   number of frames appended to the message head.
112
#      * @frames:    array of CAN frames.
113
#      */
114
#     struct bcm_msg_head {
115
#             __u32 opcode;
116
#             __u32 flags;
117
#             __u32 count;
118
#             struct bcm_timeval ival1, ival2;
119
#             canid_t can_id;
120
#             __u32 nframes;
121
#             struct can_frame frames[0];
122
#     };
123
BcmMsgHead = bcm_header_factory(
21✔
124
    fields=[
125
        ("opcode", ctypes.c_uint32),
126
        ("flags", ctypes.c_uint32),
127
        ("count", ctypes.c_uint32),
128
        ("ival1_tv_sec", ctypes.c_long),
129
        ("ival1_tv_usec", ctypes.c_long),
130
        ("ival2_tv_sec", ctypes.c_long),
131
        ("ival2_tv_usec", ctypes.c_long),
132
        ("can_id", ctypes.c_uint32),
133
        ("nframes", ctypes.c_uint32),
134
    ]
135
)
136

137

138
# struct module defines a binary packing format:
139
# https://docs.python.org/3/library/struct.html#struct-format-strings
140
# The 32bit can id is directly followed by the 8bit data link count
141
# The data field is aligned on an 8 byte boundary, hence we add padding
142
# which aligns the data field to an 8 byte boundary.
143
CAN_FRAME_HEADER_STRUCT = struct.Struct("=IBB1xB")
21✔
144

145

146
def build_can_frame(msg: Message) -> bytes:
21✔
147
    """CAN frame packing/unpacking (see 'struct can_frame' in <linux/can.h>)
148
    /**
149
    * struct can_frame - Classical CAN frame structure (aka CAN 2.0B)
150
    * @can_id:   CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
151
    * @len:      CAN frame payload length in byte (0 .. 8)
152
    * @can_dlc:  deprecated name for CAN frame payload length in byte (0 .. 8)
153
    * @__pad:    padding
154
    * @__res0:   reserved / padding
155
    * @len8_dlc: optional DLC value (9 .. 15) at 8 byte payload length
156
    *            len8_dlc contains values from 9 .. 15 when the payload length is
157
    *            8 bytes but the DLC value (see ISO 11898-1) is greater then 8.
158
    *            CAN_CTRLMODE_CC_LEN8_DLC flag has to be enabled in CAN driver.
159
    * @data:     CAN frame payload (up to 8 byte)
160
    */
161
    struct can_frame {
162
        canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
163
        union {
164
            /* CAN frame payload length in byte (0 .. CAN_MAX_DLEN)
165
            * was previously named can_dlc so we need to carry that
166
            * name for legacy support
167
            */
168
            __u8 len;
169
            __u8 can_dlc; /* deprecated */
170
        } __attribute__((packed)); /* disable padding added in some ABIs */
171
        __u8 __pad; /* padding */
172
        __u8 __res0; /* reserved / padding */
173
        __u8 len8_dlc; /* optional DLC for 8 byte payload length (9 .. 15) */
174
        __u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));
175
    };
176

177
    /*
178
    * defined bits for canfd_frame.flags
179
    *
180
    * The use of struct canfd_frame implies the FD Frame (FDF) bit to
181
    * be set in the CAN frame bitstream on the wire. The FDF bit switch turns
182
    * the CAN controllers bitstream processor into the CAN FD mode which creates
183
    * two new options within the CAN FD frame specification:
184
    *
185
    * Bit Rate Switch - to indicate a second bitrate is/was used for the payload
186
    * Error State Indicator - represents the error state of the transmitting node
187
    *
188
    * As the CANFD_ESI bit is internally generated by the transmitting CAN
189
    * controller only the CANFD_BRS bit is relevant for real CAN controllers when
190
    * building a CAN FD frame for transmission. Setting the CANFD_ESI bit can make
191
    * sense for virtual CAN interfaces to test applications with echoed frames.
192
    *
193
    * The struct can_frame and struct canfd_frame intentionally share the same
194
    * layout to be able to write CAN frame content into a CAN FD frame structure.
195
    * When this is done the former differentiation via CAN_MTU / CANFD_MTU gets
196
    * lost. CANFD_FDF allows programmers to mark CAN FD frames in the case of
197
    * using struct canfd_frame for mixed CAN / CAN FD content (dual use).
198
    * Since the introduction of CAN XL the CANFD_FDF flag is set in all CAN FD
199
    * frame structures provided by the CAN subsystem of the Linux kernel.
200
    */
201
    #define CANFD_BRS 0x01 /* bit rate switch (second bitrate for payload data) */
202
    #define CANFD_ESI 0x02 /* error state indicator of the transmitting node */
203
    #define CANFD_FDF 0x04 /* mark CAN FD for dual use of struct canfd_frame */
204

205
    /**
206
    * struct canfd_frame - CAN flexible data rate frame structure
207
    * @can_id: CAN ID of the frame and CAN_*_FLAG flags, see canid_t definition
208
    * @len:    frame payload length in byte (0 .. CANFD_MAX_DLEN)
209
    * @flags:  additional flags for CAN FD
210
    * @__res0: reserved / padding
211
    * @__res1: reserved / padding
212
    * @data:   CAN FD frame payload (up to CANFD_MAX_DLEN byte)
213
    */
214
    struct canfd_frame {
215
        canid_t can_id;  /* 32 bit CAN_ID + EFF/RTR/ERR flags */
216
        __u8    len;     /* frame payload length in byte */
217
        __u8    flags;   /* additional flags for CAN FD */
218
        __u8    __res0;  /* reserved / padding */
219
        __u8    __res1;  /* reserved / padding */
220
        __u8    data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
221
    };
222
    """
223
    can_id = _compose_arbitration_id(msg)
5✔
224

225
    flags = 0
5✔
226

227
    # The socketcan code identify the received FD frame by the packet length.
228
    # So, padding to the data length is performed according to the message type (Classic / FD)
229
    if msg.is_fd:
5✔
230
        flags |= constants.CANFD_FDF
5✔
231
        max_len = constants.CANFD_MAX_DLEN
5✔
232
    else:
233
        max_len = constants.CAN_MAX_DLEN
5✔
234

235
    if msg.bitrate_switch:
5✔
236
        flags |= constants.CANFD_BRS
5✔
237
    if msg.error_state_indicator:
5✔
UNCOV
238
        flags |= constants.CANFD_ESI
×
239

240
    data = bytes(msg.data).ljust(max_len, b"\x00")
5✔
241

242
    if msg.is_remote_frame:
5✔
243
        data_len = msg.dlc
5✔
244
    else:
245
        data_len = min(i for i in can.util.CAN_FD_DLC if i >= len(msg.data))
5✔
246
    header = CAN_FRAME_HEADER_STRUCT.pack(can_id, data_len, flags, msg.dlc)
5✔
247
    return header + data
5✔
248

249

250
def build_bcm_header(
21✔
251
    opcode: int,
252
    flags: int,
253
    count: int,
254
    ival1_seconds: int,
255
    ival1_usec: int,
256
    ival2_seconds: int,
257
    ival2_usec: int,
258
    can_id: int,
259
    nframes: int,
260
) -> bytes:
261
    result = BcmMsgHead(
21✔
262
        opcode=opcode,
263
        flags=flags,
264
        count=count,
265
        ival1_tv_sec=ival1_seconds,
266
        ival1_tv_usec=ival1_usec,
267
        ival2_tv_sec=ival2_seconds,
268
        ival2_tv_usec=ival2_usec,
269
        can_id=can_id,
270
        nframes=nframes,
271
    )
272
    return ctypes.string_at(ctypes.addressof(result), ctypes.sizeof(result))
21✔
273

274

275
def build_bcm_tx_delete_header(can_id: int, flags: int) -> bytes:
21✔
276
    opcode = constants.CAN_BCM_TX_DELETE
21✔
277
    return build_bcm_header(opcode, flags, 0, 0, 0, 0, 0, can_id, 1)
21✔
278

279

280
def build_bcm_transmit_header(
21✔
281
    can_id: int,
282
    count: int,
283
    initial_period: float,
284
    subsequent_period: float,
285
    msg_flags: int,
286
    nframes: int = 1,
287
) -> bytes:
288
    opcode = constants.CAN_BCM_TX_SETUP
21✔
289

290
    flags = msg_flags | constants.SETTIMER | constants.STARTTIMER
21✔
291

292
    if initial_period > 0:
21✔
293
        # Note `TX_COUNTEVT` creates the message TX_EXPIRED when count expires
294
        flags |= constants.TX_COUNTEVT
21✔
295

296
    def split_time(value: float) -> tuple[int, int]:
21✔
297
        """Given seconds as a float, return whole seconds and microseconds"""
298
        seconds = int(value)
21✔
299
        microseconds = int(1e6 * (value - seconds))
21✔
300
        return seconds, microseconds
21✔
301

302
    ival1_seconds, ival1_usec = split_time(initial_period)
21✔
303
    ival2_seconds, ival2_usec = split_time(subsequent_period)
21✔
304

305
    return build_bcm_header(
21✔
306
        opcode,
307
        flags,
308
        count,
309
        ival1_seconds,
310
        ival1_usec,
311
        ival2_seconds,
312
        ival2_usec,
313
        can_id,
314
        nframes,
315
    )
316

317

318
def build_bcm_update_header(can_id: int, msg_flags: int, nframes: int = 1) -> bytes:
21✔
319
    return build_bcm_header(
21✔
320
        constants.CAN_BCM_TX_SETUP, msg_flags, 0, 0, 0, 0, 0, can_id, nframes
321
    )
322

323

324
def is_frame_fd(frame: bytes):
21✔
325
    # According to the SocketCAN implementation the frame length
326
    # should indicate if the message is FD or not (not the flag value)
327
    return len(frame) == constants.CANFD_MTU
5✔
328

329

330
def dissect_can_frame(frame: bytes) -> tuple[int, int, int, bytes]:
21✔
331
    can_id, data_len, flags, len8_dlc = CAN_FRAME_HEADER_STRUCT.unpack_from(frame)
5✔
332

333
    if data_len not in can.util.CAN_FD_DLC:
5✔
UNCOV
334
        data_len = min(i for i in can.util.CAN_FD_DLC if i >= data_len)
×
335

336
    can_dlc = data_len
5✔
337

338
    if not is_frame_fd(frame):
5✔
339
        # Flags not valid in non-FD frames
340
        flags = 0
5✔
341

342
        if (
5✔
343
            data_len == constants.CAN_MAX_DLEN
344
            and constants.CAN_MAX_DLEN < len8_dlc <= constants.CAN_MAX_RAW_DLC
345
        ):
UNCOV
346
            can_dlc = len8_dlc
×
347

348
    return can_id, can_dlc, flags, frame[8 : 8 + data_len]
5✔
349

350

351
def create_bcm_socket(channel: str) -> socket.socket:
21✔
352
    """create a broadcast manager socket and connect to the given interface"""
353
    s = socket.socket(constants.PF_CAN, socket.SOCK_DGRAM, constants.CAN_BCM)
5✔
354
    s.connect((channel,))
5✔
355
    return s
5✔
356

357

358
def send_bcm(bcm_socket: socket.socket, data: bytes) -> int:
21✔
359
    """
360
    Send raw frame to a BCM socket and handle errors.
361
    """
362
    try:
5✔
363
        return bcm_socket.send(data)
5✔
364
    except OSError as error:
×
UNCOV
365
        base = f"Couldn't send CAN BCM frame due to OS Error: {error.strerror}"
×
366

367
        if error.errno == errno.EINVAL:
×
368
            specific_message = " You are probably referring to a non-existing frame."
×
369
        elif error.errno == errno.ENETDOWN:
×
370
            specific_message = " The CAN interface appears to be down."
×
371
        elif error.errno == errno.EBADF:
×
UNCOV
372
            specific_message = " The CAN socket appears to be closed."
×
373
        else:
UNCOV
374
            specific_message = ""
×
375

UNCOV
376
        raise can.CanOperationError(base + specific_message, error.errno) from error
×
377

378

379
def _compose_arbitration_id(message: Message) -> int:
21✔
380
    can_id = message.arbitration_id
5✔
381
    if message.is_extended_id:
5✔
382
        log.debug("sending an extended id type message")
5✔
383
        can_id |= constants.CAN_EFF_FLAG
5✔
384
    if message.is_remote_frame:
5✔
385
        log.debug("requesting a remote frame")
5✔
386
        can_id |= constants.CAN_RTR_FLAG
5✔
387
    if message.is_error_frame:
5✔
388
        log.debug("sending error frame")
×
UNCOV
389
        can_id |= constants.CAN_ERR_FLAG
×
390
    return can_id
5✔
391

392

393
class CyclicSendTask(
21✔
394
    LimitedDurationCyclicSendTaskABC, ModifiableCyclicTaskABC, RestartableCyclicTaskABC
395
):
396
    """
21✔
397
    A SocketCAN cyclic send task supports:
398

399
        - setting of a task duration
400
        - modifying the data
401
        - stopping then subsequent restarting of the task
402
    """
403

404
    def __init__(
21✔
405
        self,
406
        bcm_socket: socket.socket,
407
        task_id: int,
408
        messages: Union[Sequence[Message], Message],
409
        period: float,
410
        duration: Optional[float] = None,
411
        autostart: bool = True,
412
    ) -> None:
413
        """Construct and :meth:`~start` a task.
414

415
        :param bcm_socket: An open BCM socket on the desired CAN channel.
416
        :param task_id:
417
            The identifier used to uniquely reference particular cyclic send task
418
            within Linux BCM.
419
        :param messages:
420
            The messages to be sent periodically.
421
        :param period:
422
            The rate in seconds at which to send the messages.
423
        :param duration:
424
            Approximate duration in seconds to send the messages for.
425
        """
426
        # The following are assigned by LimitedDurationCyclicSendTaskABC:
427
        #   - self.messages
428
        #   - self.period
429
        #   - self.duration
430
        super().__init__(messages, period, duration)
5✔
431

432
        self.bcm_socket = bcm_socket
5✔
433
        self.task_id = task_id
5✔
434
        if autostart:
5✔
435
            self._tx_setup(self.messages)
5✔
436

437
    def _tx_setup(
21✔
438
        self,
439
        messages: Sequence[Message],
440
        raise_if_task_exists: bool = True,
441
    ) -> None:
442
        # Create a low level packed frame to pass to the kernel
443
        body = bytearray()
5✔
444
        self.flags = constants.CAN_FD_FRAME if messages[0].is_fd else 0
5✔
445

446
        if self.duration:
5✔
447
            count = int(self.duration / self.period)
×
448
            ival1 = self.period
×
UNCOV
449
            ival2 = 0.0
×
450
        else:
451
            count = 0
5✔
452
            ival1 = 0.0
5✔
453
            ival2 = self.period
5✔
454

455
        if raise_if_task_exists:
5✔
456
            self._check_bcm_task()
5✔
457

458
        header = build_bcm_transmit_header(
5✔
459
            self.task_id, count, ival1, ival2, self.flags, nframes=len(messages)
460
        )
461
        for message in messages:
5✔
462
            body += build_can_frame(message)
5✔
463
        log.debug("Sending BCM command")
5✔
464
        send_bcm(self.bcm_socket, header + body)
5✔
465

466
    def _check_bcm_task(self) -> None:
21✔
467
        # Do a TX_READ on a task ID, and check if we get EINVAL. If so,
468
        # then we are referring to a CAN message with an existing ID
469
        check_header = build_bcm_header(
5✔
470
            opcode=constants.CAN_BCM_TX_READ,
471
            flags=0,
472
            count=0,
473
            ival1_seconds=0,
474
            ival1_usec=0,
475
            ival2_seconds=0,
476
            ival2_usec=0,
477
            can_id=self.task_id,
478
            nframes=0,
479
        )
480
        log.debug(
5✔
481
            "Reading properties of (cyclic) transmission task id=%d", self.task_id
482
        )
483
        try:
5✔
484
            self.bcm_socket.send(check_header)
5✔
485
        except OSError as error:
5✔
486
            if error.errno != errno.EINVAL:
5✔
UNCOV
487
                raise can.CanOperationError("failed to check", error.errno) from error
×
488
            else:
489
                log.debug("Invalid argument - transmission task not known to kernel")
5✔
490
        else:
491
            # No exception raised - transmission task with this ID exists in kernel.
492
            # Existence of an existing transmission task might not be a problem!
UNCOV
493
            raise can.CanOperationError(
×
494
                f"A periodic task for task ID {self.task_id} is already in progress "
495
                "by the SocketCAN Linux layer"
496
            )
497

498
    def stop(self) -> None:
21✔
499
        """Stop a task by sending TX_DELETE message to Linux kernel.
500

501
        This will delete the entry for the transmission of the CAN-message
502
        with the specified ``task_id`` identifier. The message length
503
        for the command TX_DELETE is {[bcm_msg_head]} (only the header).
504
        """
505
        log.debug("Stopping periodic task")
5✔
506

507
        stopframe = build_bcm_tx_delete_header(self.task_id, self.flags)
5✔
508
        send_bcm(self.bcm_socket, stopframe)
5✔
509

510
    def modify_data(self, messages: Union[Sequence[Message], Message]) -> None:
21✔
511
        """Update the contents of the periodically sent CAN messages by
512
        sending TX_SETUP message to Linux kernel.
513

514
        The number of new cyclic messages to be sent must be equal to the
515
        original number of messages originally specified for this task.
516

517
        .. note:: The messages must all have the same
518
                  :attr:`~can.Message.arbitration_id` like the first message.
519

520
        :param messages:
521
            The messages with the new :attr:`can.Message.data`.
522
        """
523
        messages = self._check_and_convert_messages(messages)
5✔
524
        self._check_modified_messages(messages)
5✔
525

526
        self.messages = messages
5✔
527

528
        body = bytearray()
5✔
529
        header = build_bcm_update_header(
5✔
530
            can_id=self.task_id, msg_flags=self.flags, nframes=len(messages)
531
        )
532
        for message in messages:
5✔
533
            body += build_can_frame(message)
5✔
534
        log.debug("Sending BCM command")
5✔
535
        send_bcm(self.bcm_socket, header + body)
5✔
536

537
    def start(self) -> None:
21✔
538
        """Restart a periodic task by sending TX_SETUP message to Linux kernel.
539

540
        It verifies presence of the particular BCM task through sending TX_READ
541
        message to Linux kernel prior to scheduling.
542

543
        :raises ValueError:
544
            If the task referenced by ``task_id`` is already running.
545
        """
546
        self._tx_setup(self.messages, raise_if_task_exists=False)
5✔
547

548

549
class MultiRateCyclicSendTask(CyclicSendTask):
21✔
550
    """Exposes more of the full power of the TX_SETUP opcode."""
21✔
551

552
    def __init__(
21✔
553
        self,
554
        channel: socket.socket,
555
        task_id: int,
556
        messages: Sequence[Message],
557
        count: int,
558
        initial_period: float,
559
        subsequent_period: float,
560
    ):
UNCOV
561
        super().__init__(channel, task_id, messages, subsequent_period)
×
562

563
        # Create a low level packed frame to pass to the kernel
UNCOV
564
        header = build_bcm_transmit_header(
×
565
            self.task_id,
566
            count,
567
            initial_period,
568
            subsequent_period,
569
            self.flags,
570
            nframes=len(messages),
571
        )
572

573
        body = bytearray()
×
574
        for message in messages:
×
UNCOV
575
            body += build_can_frame(message)
×
576

577
        log.info("Sending BCM TX_SETUP command")
×
UNCOV
578
        send_bcm(self.bcm_socket, header + body)
×
579

580

581
def create_socket() -> socket.socket:
21✔
582
    """Creates a raw CAN socket. The socket will
583
    be returned unbound to any interface.
584
    """
585
    sock = socket.socket(constants.PF_CAN, socket.SOCK_RAW, constants.CAN_RAW)
7✔
586

587
    log.info("Created a socket")
7✔
588

589
    return sock
7✔
590

591

592
def bind_socket(sock: socket.socket, channel: str = "can0") -> None:
21✔
593
    """
594
    Binds the given socket to the given interface.
595

596
    :param sock:
597
        The socket to be bound
598
    :param channel:
599
        The channel / interface to bind to
600
    :raises OSError:
601
        If the specified interface isn't found.
602
    """
603
    log.debug("Binding socket to channel=%s", channel)
7✔
604
    sock.bind((channel,))
7✔
605
    log.debug("Bound socket.")
5✔
606

607

608
def capture_message(
21✔
609
    sock: socket.socket, get_channel: bool = False
610
) -> Optional[Message]:
611
    """
612
    Captures a message from given socket.
613

614
    :param sock:
615
        The socket to read a message from.
616
    :param get_channel:
617
        Find out which channel the message comes from.
618

619
    :return: The received message, or None on failure.
620
    """
621
    # Fetching the Arb ID, DLC and Data
622
    try:
5✔
623
        cf, ancillary_data, msg_flags, addr = sock.recvmsg(
5✔
624
            constants.CANFD_MTU, RECEIVED_ANCILLARY_BUFFER_SIZE
625
        )
626
        if get_channel:
5✔
627
            channel = addr[0] if isinstance(addr, tuple) else addr
5✔
628
        else:
629
            channel = None
5✔
630
    except OSError as error:
×
UNCOV
631
        raise can.CanOperationError(
×
632
            f"Error receiving: {error.strerror}", error.errno
633
        ) from error
634

635
    can_id, can_dlc, flags, data = dissect_can_frame(cf)
5✔
636

637
    # Fetching the timestamp
638
    assert len(ancillary_data) == 1, "only requested a single extra field"
5✔
639
    cmsg_level, cmsg_type, cmsg_data = ancillary_data[0]
5✔
640
    assert (
5✔
641
        cmsg_level == socket.SOL_SOCKET and cmsg_type == constants.SO_TIMESTAMPNS
642
    ), "received control message type that was not requested"
643
    # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
644
    seconds, nanoseconds = RECEIVED_TIMESTAMP_STRUCT.unpack_from(cmsg_data)
5✔
645
    if nanoseconds >= 1e9:
5✔
UNCOV
646
        raise can.CanOperationError(
×
647
            f"Timestamp nanoseconds field was out of range: {nanoseconds} not less than 1e9"
648
        )
649
    timestamp = seconds + nanoseconds * 1e-9
5✔
650

651
    # EXT, RTR, ERR flags -> boolean attributes
652
    #   /* special address description flags for the CAN_ID */
653
    #   #define CAN_EFF_FLAG 0x80000000U /* EFF/SFF is set in the MSB */
654
    #   #define CAN_RTR_FLAG 0x40000000U /* remote transmission request */
655
    #   #define CAN_ERR_FLAG 0x20000000U /* error frame */
656
    is_extended_frame_format = bool(can_id & constants.CAN_EFF_FLAG)
5✔
657
    is_remote_transmission_request = bool(can_id & constants.CAN_RTR_FLAG)
5✔
658
    is_error_frame = bool(can_id & constants.CAN_ERR_FLAG)
5✔
659
    is_fd = len(cf) == constants.CANFD_MTU
5✔
660
    bitrate_switch = bool(flags & constants.CANFD_BRS)
5✔
661
    error_state_indicator = bool(flags & constants.CANFD_ESI)
5✔
662

663
    # Section 4.7.1: MSG_DONTROUTE: set when the received frame was created on the local host.
664
    is_rx = not bool(msg_flags & socket.MSG_DONTROUTE)
5✔
665

666
    if is_extended_frame_format:
5✔
667
        # log.debug("CAN: Extended")
668
        # TODO does this depend on SFF or EFF?
669
        arbitration_id = can_id & 0x1FFFFFFF
5✔
670
    else:
671
        # log.debug("CAN: Standard")
672
        arbitration_id = can_id & 0x000007FF
5✔
673

674
    msg = Message(
5✔
675
        timestamp=timestamp,
676
        channel=channel,
677
        arbitration_id=arbitration_id,
678
        is_extended_id=is_extended_frame_format,
679
        is_remote_frame=is_remote_transmission_request,
680
        is_error_frame=is_error_frame,
681
        is_fd=is_fd,
682
        is_rx=is_rx,
683
        bitrate_switch=bitrate_switch,
684
        error_state_indicator=error_state_indicator,
685
        dlc=can_dlc,
686
        data=data,
687
    )
688

689
    return msg
5✔
690

691

692
class SocketcanBus(BusABC):  # pylint: disable=abstract-method
21✔
693
    """A SocketCAN interface to CAN.
21✔
694

695
    It implements :meth:`can.BusABC._detect_available_configs` to search for
696
    available interfaces.
697
    """
698

699
    def __init__(
21✔
700
        self,
701
        channel: str = "",
702
        receive_own_messages: bool = False,
703
        local_loopback: bool = True,
704
        fd: bool = False,
705
        can_filters: Optional[CanFilters] = None,
706
        ignore_rx_error_frames=False,
707
        **kwargs,
708
    ) -> None:
709
        """Creates a new socketcan bus.
710

711
        If setting some socket options fails, an error will be printed
712
        but no exception will be thrown. This includes enabling:
713

714
            - that own messages should be received,
715
            - CAN-FD frames and
716
            - error frames.
717

718
        :param channel:
719
            The can interface name with which to create this bus.
720
            An example channel would be 'vcan0' or 'can0'.
721
            An empty string '' will receive messages from all channels.
722
            In that case any sent messages must be explicitly addressed to a
723
            channel using :attr:`can.Message.channel`.
724
        :param receive_own_messages:
725
            If transmitted messages should also be received by this bus.
726
        :param local_loopback:
727
            If local loopback should be enabled on this bus.
728
            Please note that local loopback does not mean that messages sent
729
            on a socket will be readable on the same socket, they will only
730
            be readable on other open sockets on the same machine. More info
731
            can be read on the socketcan documentation:
732
            See https://www.kernel.org/doc/html/latest/networking/can.html#socketcan-local-loopback1
733
        :param fd:
734
            If CAN-FD frames should be supported.
735
        :param can_filters:
736
            See :meth:`can.BusABC.set_filters`.
737
        :param ignore_rx_error_frames:
738
            If incoming error frames should be discarded.
739
        """
740
        self.socket = create_socket()
7✔
741
        self.channel = channel
7✔
742
        self.channel_info = f"socketcan channel '{channel}'"
7✔
743
        self._bcm_sockets: dict[str, socket.socket] = {}
7✔
744
        self._is_filtered = False
7✔
745
        self._task_id = 0
7✔
746
        self._task_id_guard = threading.Lock()
7✔
747
        self._can_protocol = CanProtocol.CAN_FD if fd else CanProtocol.CAN_20
7✔
748

749
        # set the local_loopback parameter
750
        try:
7✔
751
            self.socket.setsockopt(
7✔
752
                constants.SOL_CAN_RAW,
753
                constants.CAN_RAW_LOOPBACK,
754
                1 if local_loopback else 0,
755
            )
756
        except OSError as error:
×
UNCOV
757
            log.error("Could not set local loopback flag(%s)", error)
×
758

759
        # set the receive_own_messages parameter
760
        try:
7✔
761
            self.socket.setsockopt(
7✔
762
                constants.SOL_CAN_RAW,
763
                constants.CAN_RAW_RECV_OWN_MSGS,
764
                1 if receive_own_messages else 0,
765
            )
766
        except OSError as error:
×
UNCOV
767
            log.error("Could not receive own messages (%s)", error)
×
768

769
        # enable CAN-FD frames if desired
770
        if fd:
7✔
771
            try:
5✔
772
                self.socket.setsockopt(
5✔
773
                    constants.SOL_CAN_RAW, constants.CAN_RAW_FD_FRAMES, 1
774
                )
775
            except OSError as error:
×
UNCOV
776
                log.error("Could not enable CAN-FD frames (%s)", error)
×
777

778
        if not ignore_rx_error_frames:
7✔
779
            # enable error frames
780
            try:
7✔
781
                self.socket.setsockopt(
7✔
782
                    constants.SOL_CAN_RAW, constants.CAN_RAW_ERR_FILTER, 0x1FFFFFFF
783
                )
784
            except OSError as error:
×
UNCOV
785
                log.error("Could not enable error frames (%s)", error)
×
786

787
        # enable nanosecond resolution timestamping
788
        # we can always do this since
789
        #  1) it is guaranteed to be at least as precise as without
790
        #  2) it is available since Linux 2.6.22, and CAN support was only added afterward
791
        #     so this is always supported by the kernel
792
        self.socket.setsockopt(socket.SOL_SOCKET, constants.SO_TIMESTAMPNS, 1)
7✔
793

794
        try:
7✔
795
            bind_socket(self.socket, channel)
7✔
796
            kwargs.update(
5✔
797
                {
798
                    "receive_own_messages": receive_own_messages,
799
                    "fd": fd,
800
                    "local_loopback": local_loopback,
801
                }
802
            )
803
        except OSError as error:
2✔
804
            log.error("Could not access SocketCAN device %s (%s)", channel, error)
2✔
805
            raise
2✔
806
        super().__init__(
5✔
807
            channel=channel,
808
            can_filters=can_filters,
809
            **kwargs,
810
        )
811

812
    def shutdown(self) -> None:
21✔
813
        """Stops all active periodic tasks and closes the socket."""
814
        super().shutdown()
21✔
815
        for channel, bcm_socket in self._bcm_sockets.items():
21✔
816
            log.debug("Closing bcm socket for channel %s", channel)
5✔
817
            bcm_socket.close()
5✔
818
        log.debug("Closing raw can socket")
5✔
819
        self.socket.close()
5✔
820

821
    def _recv_internal(
21✔
822
        self, timeout: Optional[float]
823
    ) -> tuple[Optional[Message], bool]:
824
        try:
5✔
825
            # get all sockets that are ready (can be a list with a single value
826
            # being self.socket or an empty list if self.socket is not ready)
827
            ready_receive_sockets, _, _ = select.select([self.socket], [], [], timeout)
5✔
UNCOV
828
        except OSError as error:
×
829
            # something bad happened (e.g. the interface went down)
UNCOV
830
            raise can.CanOperationError(
×
831
                f"Failed to receive: {error.strerror}", error.errno
832
            ) from error
833

834
        if ready_receive_sockets:  # not empty
5✔
835
            get_channel = self.channel == ""
5✔
836
            msg = capture_message(self.socket, get_channel)
5✔
837
            if msg and not msg.channel and self.channel:
5✔
838
                # Default to our own channel
839
                msg.channel = self.channel
5✔
840
            return msg, self._is_filtered
5✔
841

842
        # socket wasn't readable or timeout occurred
843
        return None, self._is_filtered
5✔
844

845
    def send(self, msg: Message, timeout: Optional[float] = None) -> None:
21✔
846
        """Transmit a message to the CAN bus.
847

848
        :param msg: A message object.
849
        :param timeout:
850
            Wait up to this many seconds for the transmit queue to be ready.
851
            If not given, the call may fail immediately.
852

853
        :raises ~can.exceptions.CanError:
854
            if the message could not be written.
855
        """
856
        log.debug("We've been asked to write a message to the bus")
5✔
857
        logger_tx = log.getChild("tx")
5✔
858
        logger_tx.debug("sending: %s", msg)
5✔
859

860
        started = time.time()
5✔
861
        # If no timeout is given, poll for availability
862
        if timeout is None:
5✔
863
            timeout = 0
5✔
864
        time_left = timeout
5✔
865
        data = build_can_frame(msg)
5✔
866

867
        while time_left >= 0:
5✔
868
            # Wait for write availability
869
            ready = select.select([], [self.socket], [], time_left)[1]
5✔
870
            if not ready:
5✔
871
                # Timeout
UNCOV
872
                break
×
873
            channel = str(msg.channel) if msg.channel else None
5✔
874
            sent = self._send_once(data, channel)
5✔
875
            if sent == len(data):
5✔
876
                return
5✔
877
            # Not all data were sent, try again with remaining data
878
            data = data[sent:]
×
UNCOV
879
            time_left = timeout - (time.time() - started)
×
880

UNCOV
881
        raise can.CanOperationError("Transmit buffer full")
×
882

883
    def _send_once(self, data: bytes, channel: Optional[str] = None) -> int:
21✔
884
        try:
5✔
885
            if self.channel == "" and channel:
5✔
886
                # Message must be addressed to a specific channel
887
                sent = self.socket.sendto(data, (channel,))
5✔
888
            else:
889
                sent = self.socket.send(data)
5✔
890
        except OSError as error:
×
UNCOV
891
            raise can.CanOperationError(
×
892
                f"Failed to transmit: {error.strerror}", error.errno
893
            ) from error
894
        return sent
5✔
895

896
    def _send_periodic_internal(
21✔
897
        self,
898
        msgs: Union[Sequence[Message], Message],
899
        period: float,
900
        duration: Optional[float] = None,
901
        autostart: bool = True,
902
        modifier_callback: Optional[Callable[[Message], None]] = None,
903
    ) -> can.broadcastmanager.CyclicSendTaskABC:
904
        """Start sending messages at a given period on this bus.
905

906
        The Linux kernel's Broadcast Manager SocketCAN API is used to schedule
907
        periodic sending of CAN messages. The wrapping 32-bit counter (see
908
        :meth:`~_get_next_task_id()`) designated to distinguish different
909
        :class:`CyclicSendTask` within BCM provides flexibility to schedule
910
        CAN messages sending with the same CAN ID, but different CAN data.
911

912
        :param msgs:
913
            The message(s) to be sent periodically.
914
        :param period:
915
            The rate in seconds at which to send the messages.
916
        :param duration:
917
            Approximate duration in seconds to continue sending messages. If
918
            no duration is provided, the task will continue indefinitely.
919
        :param autostart:
920
            If True (the default) the sending task will immediately start after creation.
921
            Otherwise, the task has to be started by calling the
922
            tasks :meth:`~can.RestartableCyclicTaskABC.start` method on it.
923

924
        :raises ValueError:
925
            If task identifier passed to :class:`CyclicSendTask` can't be used
926
            to schedule new task in Linux BCM.
927

928
        :return:
929
            A :class:`CyclicSendTask` task instance. This can be used to modify the data,
930
            pause/resume the transmission and to stop the transmission.
931

932
        .. note::
933

934
            Note the duration before the messages stop being sent may not
935
            be exactly the same as the duration specified by the user. In
936
            general the message will be sent at the given rate until at
937
            least *duration* seconds.
938
        """
939
        if modifier_callback is None:
5✔
940
            msgs = LimitedDurationCyclicSendTaskABC._check_and_convert_messages(  # pylint: disable=protected-access
5✔
941
                msgs
942
            )
943

944
            msgs_channel = str(msgs[0].channel) if msgs[0].channel else None
5✔
945
            bcm_socket = self._get_bcm_socket(msgs_channel or self.channel)
5✔
946
            task_id = self._get_next_task_id()
5✔
947
            task = CyclicSendTask(
5✔
948
                bcm_socket, task_id, msgs, period, duration, autostart=autostart
949
            )
950
            return task
5✔
951

952
        # fallback to thread based cyclic task
UNCOV
953
        warnings.warn(
×
954
            f"{self.__class__.__name__} falls back to a thread-based cyclic task, "
955
            "when the `modifier_callback` argument is given.",
956
            stacklevel=3,
957
        )
UNCOV
958
        return BusABC._send_periodic_internal(
×
959
            self,
960
            msgs=msgs,
961
            period=period,
962
            duration=duration,
963
            autostart=autostart,
964
            modifier_callback=modifier_callback,
965
        )
966

967
    def _get_next_task_id(self) -> int:
21✔
968
        with self._task_id_guard:
5✔
969
            self._task_id = (self._task_id + 1) % (2**32 - 1)
5✔
970
            return self._task_id
5✔
971

972
    def _get_bcm_socket(self, channel: str) -> socket.socket:
21✔
973
        if channel not in self._bcm_sockets:
5✔
974
            self._bcm_sockets[channel] = create_bcm_socket(self.channel)
5✔
975
        return self._bcm_sockets[channel]
5✔
976

977
    def _apply_filters(self, filters: Optional[can.typechecking.CanFilters]) -> None:
21✔
978
        try:
5✔
979
            self.socket.setsockopt(
5✔
980
                constants.SOL_CAN_RAW, constants.CAN_RAW_FILTER, pack_filters(filters)
981
            )
UNCOV
982
        except OSError as error:
×
983
            # fall back to "software filtering" (= not in kernel)
984
            self._is_filtered = False
×
UNCOV
985
            log.error(
×
986
                "Setting filters failed; falling back to software filtering (not in kernel): %s",
987
                error,
988
            )
989
        else:
990
            self._is_filtered = True
5✔
991

992
    def fileno(self) -> int:
21✔
993
        return self.socket.fileno()
5✔
994

995
    @staticmethod
21✔
996
    def _detect_available_configs() -> list[can.typechecking.AutoDetectedConfig]:
21✔
997
        return [
21✔
998
            {"interface": "socketcan", "channel": channel}
999
            for channel in find_available_interfaces()
1000
        ]
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc