42
42
43
43
44
44
# Constants needed for precise handling of timestamps
45
- RECEIVED_TIMESTAMP_STRUCT = struct .Struct ("@ll" )
45
+ RECEIVED_TIMESPEC_STRUCT = struct .Struct ("@ll" )
46
46
RECEIVED_ANCILLARY_BUFFER_SIZE = (
47
- CMSG_SPACE (RECEIVED_TIMESTAMP_STRUCT .size ) if CMSG_SPACE_available else 0
47
+ CMSG_SPACE (RECEIVED_TIMESPEC_STRUCT .size * 3 ) if CMSG_SPACE_available else 0
48
48
)
49
49
50
50
@@ -556,11 +556,24 @@ def capture_message(
556
556
# Fetching the timestamp
557
557
assert len (ancillary_data ) == 1 , "only requested a single extra field"
558
558
cmsg_level , cmsg_type , cmsg_data = ancillary_data [0 ]
559
- assert (
560
- cmsg_level == socket .SOL_SOCKET and cmsg_type == constants .SO_TIMESTAMPNS
559
+ assert cmsg_level == socket .SOL_SOCKET and cmsg_type in (
560
+ constants .SO_TIMESTAMPNS ,
561
+ constants .SO_TIMESTAMPING ,
561
562
), "received control message type that was not requested"
562
563
# see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
563
- seconds , nanoseconds = RECEIVED_TIMESTAMP_STRUCT .unpack_from (cmsg_data )
564
+ if cmsg_type == constants .SO_TIMESTAMPNS :
565
+ seconds , nanoseconds = RECEIVED_TIMESPEC_STRUCT .unpack_from (cmsg_data )
566
+ else :
567
+ # cmsg_type == constants.SO_TIMESTAMPING
568
+ #
569
+ # stamp[0] is the software timestamp
570
+ # stamp[1] is deprecated
571
+ # stamp[2] is the raw hardware timestamp
572
+ offset = struct .calcsize (RECEIVED_TIMESPEC_STRUCT .format ) * 2
573
+ seconds , nanoseconds = RECEIVED_TIMESPEC_STRUCT .unpack_from (
574
+ cmsg_data , offset = offset
575
+ )
576
+
564
577
if nanoseconds >= 1e9 :
565
578
raise can .CanOperationError (
566
579
f"Timestamp nanoseconds field was out of range: { nanoseconds } not less than 1e9"
@@ -619,6 +632,7 @@ def __init__(
619
632
self ,
620
633
channel : str = "" ,
621
634
receive_own_messages : bool = False ,
635
+ can_hardware_timestamps : bool = False ,
622
636
local_loopback : bool = True ,
623
637
fd : bool = False ,
624
638
can_filters : Optional [CanFilters ] = None ,
@@ -642,6 +656,17 @@ def __init__(
642
656
channel using :attr:`can.Message.channel`.
643
657
:param receive_own_messages:
644
658
If transmitted messages should also be received by this bus.
659
+ :param bool can_hardware_timestamps:
660
+ Use raw hardware timestamp for can messages if available instead
661
+ of the system timestamp. By default we use the SO_TIMESTAMPNS
662
+ interface which provides ns resolution but low accuracy. If your
663
+ can hardware supports it you can use this parameter to
664
+ alternatively use the SO_TIMESTAMPING interface and request raw
665
+ hardware timestamps. These are much higher precision but will
666
+ almost certainly not be referenced to the time of day. There
667
+ may be other pitfalls to such as loopback packets reporting with
668
+ no timestamp at all.
669
+ See https://www.kernel.org/doc/html/latest/networking/timestamping.html
645
670
:param local_loopback:
646
671
If local loopback should be enabled on this bus.
647
672
Please note that local loopback does not mean that messages sent
@@ -659,6 +684,7 @@ def __init__(
659
684
self .socket = create_socket ()
660
685
self .channel = channel
661
686
self .channel_info = f"socketcan channel '{ channel } '"
687
+ self ._can_hardware_timestamps = can_hardware_timestamps
662
688
self ._bcm_sockets : Dict [str , socket .socket ] = {}
663
689
self ._is_filtered = False
664
690
self ._task_id = 0
@@ -703,12 +729,25 @@ def __init__(
703
729
except OSError as error :
704
730
log .error ("Could not enable error frames (%s)" , error )
705
731
706
- # enable nanosecond resolution timestamping
707
- # we can always do this since
708
- # 1) it is guaranteed to be at least as precise as without
709
- # 2) it is available since Linux 2.6.22, and CAN support was only added afterward
710
- # so this is always supported by the kernel
711
- self .socket .setsockopt (socket .SOL_SOCKET , constants .SO_TIMESTAMPNS , 1 )
732
+ if not self ._can_hardware_timestamps :
733
+ # Utilise SO_TIMESTAMPNS interface :
734
+ # we can always do this since
735
+ # 1) it is guaranteed to be at least as precise as without
736
+ # 2) it is available since Linux 2.6.22, and CAN support was only added afterward
737
+ # so this is always supported by the kernel
738
+ self .socket .setsockopt (socket .SOL_SOCKET , constants .SO_TIMESTAMPNS , 1 )
739
+ else :
740
+ # Utilise SO_TIMESTAMPING interface :
741
+ # Allows us to use raw hardware timestamps where available
742
+ timestamping_flags = (
743
+ constants .SOF_TIMESTAMPING_SOFTWARE
744
+ | constants .SOF_TIMESTAMPING_RX_SOFTWARE
745
+ | constants .SOF_TIMESTAMPING_RAW_HARDWARE
746
+ )
747
+
748
+ self .socket .setsockopt (
749
+ socket .SOL_SOCKET , constants .SO_TIMESTAMPING , timestamping_flags
750
+ )
712
751
713
752
try :
714
753
bind_socket (self .socket , channel )
0 commit comments