14
14
import socket
15
15
import struct
16
16
import time
17
+ import threading
17
18
import errno
18
19
19
20
log = logging .getLogger (__name__ )
@@ -162,7 +163,7 @@ def build_can_frame(msg: Message) -> bytes:
162
163
__u8 data[CANFD_MAX_DLEN] __attribute__((aligned(8)));
163
164
};
164
165
"""
165
- can_id = _add_flags_to_can_id (msg )
166
+ can_id = _compose_arbitration_id (msg )
166
167
flags = 0
167
168
if msg .bitrate_switch :
168
169
flags |= CANFD_BRS
@@ -286,7 +287,7 @@ def send_bcm(bcm_socket: socket.socket, data: bytes) -> int:
286
287
raise e
287
288
288
289
289
- def _add_flags_to_can_id (message : Message ) -> int :
290
+ def _compose_arbitration_id (message : Message ) -> int :
290
291
can_id = message .arbitration_id
291
292
if message .is_extended_id :
292
293
log .debug ("sending an extended id type message" )
@@ -297,7 +298,6 @@ def _add_flags_to_can_id(message: Message) -> int:
297
298
if message .is_error_frame :
298
299
log .debug ("sending error frame" )
299
300
can_id |= CAN_ERR_FLAG
300
-
301
301
return can_id
302
302
303
303
@@ -310,18 +310,22 @@ class CyclicSendTask(
310
310
- setting of a task duration
311
311
- modifying the data
312
312
- stopping then subsequent restarting of the task
313
-
314
313
"""
315
314
316
315
def __init__ (
317
316
self ,
318
317
bcm_socket : socket .socket ,
318
+ task_id : int ,
319
319
messages : Union [Sequence [Message ], Message ],
320
320
period : float ,
321
321
duration : Optional [float ] = None ,
322
322
):
323
- """
323
+ """Construct and :meth:`~start` a task.
324
+
324
325
:param bcm_socket: An open BCM socket on the desired CAN channel.
326
+ :param task_id:
327
+ The identifier used to uniquely reference particular cyclic send task
328
+ within Linux BCM.
325
329
:param messages:
326
330
The messages to be sent periodically.
327
331
:param period:
@@ -336,12 +340,12 @@ def __init__(
336
340
super ().__init__ (messages , period , duration )
337
341
338
342
self .bcm_socket = bcm_socket
343
+ self .task_id = task_id
339
344
self ._tx_setup (self .messages )
340
345
341
346
def _tx_setup (self , messages : Sequence [Message ]) -> None :
342
347
# Create a low level packed frame to pass to the kernel
343
348
body = bytearray ()
344
- self .can_id_with_flags = _add_flags_to_can_id (messages [0 ])
345
349
self .flags = CAN_FD_FRAME if messages [0 ].is_fd else 0
346
350
347
351
if self .duration :
@@ -353,9 +357,19 @@ def _tx_setup(self, messages: Sequence[Message]) -> None:
353
357
ival1 = 0.0
354
358
ival2 = self .period
355
359
356
- # First do a TX_READ before creating a new task, and check if we get
357
- # EINVAL. If so, then we are referring to a CAN message with the same
358
- # ID
360
+ self ._check_bcm_task ()
361
+
362
+ header = build_bcm_transmit_header (
363
+ self .task_id , count , ival1 , ival2 , self .flags , nframes = len (messages )
364
+ )
365
+ for message in messages :
366
+ body += build_can_frame (message )
367
+ log .debug ("Sending BCM command" )
368
+ send_bcm (self .bcm_socket , header + body )
369
+
370
+ def _check_bcm_task (self ):
371
+ # Do a TX_READ on a task ID, and check if we get EINVAL. If so,
372
+ # then we are referring to a CAN message with the existing ID
359
373
check_header = build_bcm_header (
360
374
opcode = CAN_BCM_TX_READ ,
361
375
flags = 0 ,
@@ -364,7 +378,7 @@ def _tx_setup(self, messages: Sequence[Message]) -> None:
364
378
ival1_usec = 0 ,
365
379
ival2_seconds = 0 ,
366
380
ival2_usec = 0 ,
367
- can_id = self .can_id_with_flags ,
381
+ can_id = self .task_id ,
368
382
nframes = 0 ,
369
383
)
370
384
try :
@@ -374,45 +388,33 @@ def _tx_setup(self, messages: Sequence[Message]) -> None:
374
388
raise e
375
389
else :
376
390
raise ValueError (
377
- "A periodic Task for Arbitration ID {} has already been created " .format (
378
- messages [ 0 ]. arbitration_id
391
+ "A periodic task for Task ID {} is already in progress by SocketCAN Linux layer " .format (
392
+ self . task_id
379
393
)
380
394
)
381
395
382
- header = build_bcm_transmit_header (
383
- self .can_id_with_flags ,
384
- count ,
385
- ival1 ,
386
- ival2 ,
387
- self .flags ,
388
- nframes = len (messages ),
389
- )
390
- for message in messages :
391
- body += build_can_frame (message )
392
- log .debug ("Sending BCM command" )
393
- send_bcm (self .bcm_socket , header + body )
394
-
395
396
def stop (self ) -> None :
396
- """Send a TX_DELETE message to cancel this task .
397
+ """Stop a task by sending TX_DELETE message to Linux kernel .
397
398
398
399
This will delete the entry for the transmission of the CAN-message
399
- with the specified can_id CAN identifier. The message length for the command
400
- TX_DELETE is {[bcm_msg_head]} (only the header).
400
+ with the specified :attr:`~task_id` identifier. The message length
401
+ for the command TX_DELETE is {[bcm_msg_head]} (only the header).
401
402
"""
402
403
log .debug ("Stopping periodic task" )
403
404
404
- stopframe = build_bcm_tx_delete_header (self .can_id_with_flags , self .flags )
405
+ stopframe = build_bcm_tx_delete_header (self .task_id , self .flags )
405
406
send_bcm (self .bcm_socket , stopframe )
406
407
407
408
def modify_data (self , messages : Union [Sequence [Message ], Message ]) -> None :
408
- """Update the contents of the periodically sent messages.
409
+ """Update the contents of the periodically sent CAN messages by
410
+ sending TX_SETUP message to Linux kernel.
409
411
410
- Note: The messages must all have the same
411
- :attr:`~can.Message.arbitration_id` like the first message.
412
-
413
- Note: The number of new cyclic messages to be sent must be equal to the
412
+ The number of new cyclic messages to be sent must be equal to the
414
413
original number of messages originally specified for this task.
415
414
415
+ .. note:: The messages must all have the same
416
+ :attr:`~can.Message.arbitration_id` like the first message.
417
+
416
418
:param messages:
417
419
The messages with the new :attr:`can.Message.data`.
418
420
"""
@@ -423,14 +425,22 @@ def modify_data(self, messages: Union[Sequence[Message], Message]) -> None:
423
425
424
426
body = bytearray ()
425
427
header = build_bcm_update_header (
426
- can_id = self .can_id_with_flags , msg_flags = self .flags , nframes = len (messages )
428
+ can_id = self .task_id , msg_flags = self .flags , nframes = len (messages )
427
429
)
428
430
for message in messages :
429
431
body += build_can_frame (message )
430
432
log .debug ("Sending BCM command" )
431
433
send_bcm (self .bcm_socket , header + body )
432
434
433
435
def start (self ) -> None :
436
+ """Start a periodic task by sending TX_SETUP message to Linux kernel.
437
+
438
+ It verifies presence of the particular BCM task through sending TX_READ
439
+ message to Linux kernel prior to scheduling.
440
+
441
+ :raises ValueError:
442
+ If the task referenced by :attr:`~task_id` is already running.
443
+ """
434
444
self ._tx_setup (self .messages )
435
445
436
446
@@ -443,16 +453,17 @@ class MultiRateCyclicSendTask(CyclicSendTask):
443
453
def __init__ (
444
454
self ,
445
455
channel : socket .socket ,
456
+ task_id : int ,
446
457
messages : Sequence [Message ],
447
458
count : int ,
448
459
initial_period : float ,
449
460
subsequent_period : float ,
450
461
):
451
- super ().__init__ (channel , messages , subsequent_period )
462
+ super ().__init__ (channel , task_id , messages , subsequent_period )
452
463
453
464
# Create a low level packed frame to pass to the kernel
454
465
header = build_bcm_transmit_header (
455
- self .can_id_with_flags ,
466
+ self .task_id ,
456
467
count ,
457
468
initial_period ,
458
469
subsequent_period ,
@@ -571,8 +582,10 @@ def capture_message(
571
582
572
583
573
584
class SocketcanBus (BusABC ):
574
- """
575
- Implements :meth:`can.BusABC._detect_available_configs`.
585
+ """ A SocketCAN interface to CAN.
586
+
587
+ It implements :meth:`can.BusABC._detect_available_configs` to search for
588
+ available interfaces.
576
589
"""
577
590
578
591
def __init__ (
@@ -601,6 +614,8 @@ def __init__(
601
614
self .channel_info = "socketcan channel '%s'" % channel
602
615
self ._bcm_sockets : Dict [str , socket .socket ] = {}
603
616
self ._is_filtered = False
617
+ self ._task_id = 0
618
+ self ._task_id_guard = threading .Lock ()
604
619
605
620
# set the receive_own_messages parameter
606
621
try :
@@ -712,18 +727,26 @@ def _send_periodic_internal(
712
727
) -> CyclicSendTask :
713
728
"""Start sending messages at a given period on this bus.
714
729
715
- The kernel's Broadcast Manager SocketCAN API will be used.
730
+ The Linux kernel's Broadcast Manager SocketCAN API is used to schedule
731
+ periodic sending of CAN messages. The wrapping 32-bit counter (see
732
+ :meth:`~_get_next_task_id()`) designated to distinguish different
733
+ :class:`CyclicSendTask` within BCM provides flexibility to schedule
734
+ CAN messages sending with the same CAN ID, but different CAN data.
716
735
717
736
:param messages:
718
- The messages to be sent periodically
737
+ The message(s) to be sent periodically.
719
738
:param period:
720
739
The rate in seconds at which to send the messages.
721
740
:param duration:
722
741
Approximate duration in seconds to continue sending messages. If
723
742
no duration is provided, the task will continue indefinitely.
724
743
744
+ :raises ValueError:
745
+ If task identifier passed to :class:`CyclicSendTask` can't be used
746
+ to schedule new task in Linux BCM.
747
+
725
748
:return:
726
- A started task instance. This can be used to modify the data,
749
+ A :class:`CyclicSendTask` task instance. This can be used to modify the data,
727
750
pause/resume the transmission and to stop the transmission.
728
751
729
752
.. note::
@@ -732,18 +755,20 @@ def _send_periodic_internal(
732
755
be exactly the same as the duration specified by the user. In
733
756
general the message will be sent at the given rate until at
734
757
least *duration* seconds.
735
-
736
758
"""
737
759
msgs = LimitedDurationCyclicSendTaskABC ._check_and_convert_messages (msgs )
738
760
739
761
msgs_channel = str (msgs [0 ].channel ) if msgs [0 ].channel else None
740
762
bcm_socket = self ._get_bcm_socket (msgs_channel or self .channel )
741
- # TODO: The SocketCAN BCM interface treats all cyclic tasks sharing an
742
- # Arbitration ID as the same Cyclic group. We should probably warn the
743
- # user instead of overwriting the old group?
744
- task = CyclicSendTask (bcm_socket , msgs , period , duration )
763
+ task_id = self ._get_next_task_id ()
764
+ task = CyclicSendTask (bcm_socket , task_id , msgs , period , duration )
745
765
return task
746
766
767
+ def _get_next_task_id (self ) -> int :
768
+ with self ._task_id_guard :
769
+ self ._task_id = (self ._task_id + 1 ) % (2 ** 32 - 1 )
770
+ return self ._task_id
771
+
747
772
def _get_bcm_socket (self , channel : str ) -> socket .socket :
748
773
if channel not in self ._bcm_sockets :
749
774
self ._bcm_sockets [channel ] = create_bcm_socket (self .channel )
0 commit comments