4242
4343
4444# Constants needed for precise handling of timestamps
45- RECEIVED_TIMESTAMP_STRUCT = struct .Struct ("@ll" )
45+ RECEIVED_TIMESPEC_STRUCT = struct .Struct ("@ll" )
4646RECEIVED_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
4848)
4949
5050
@@ -556,11 +556,24 @@ def capture_message(
556556 # Fetching the timestamp
557557 assert len (ancillary_data ) == 1 , "only requested a single extra field"
558558 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 ,
561562 ), "received control message type that was not requested"
562563 # 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+
564577 if nanoseconds >= 1e9 :
565578 raise can .CanOperationError (
566579 f"Timestamp nanoseconds field was out of range: { nanoseconds } not less than 1e9"
@@ -619,6 +632,7 @@ def __init__(
619632 self ,
620633 channel : str = "" ,
621634 receive_own_messages : bool = False ,
635+ can_hardware_timestamps : bool = False ,
622636 local_loopback : bool = True ,
623637 fd : bool = False ,
624638 can_filters : Optional [CanFilters ] = None ,
@@ -642,6 +656,17 @@ def __init__(
642656 channel using :attr:`can.Message.channel`.
643657 :param receive_own_messages:
644658 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
645670 :param local_loopback:
646671 If local loopback should be enabled on this bus.
647672 Please note that local loopback does not mean that messages sent
@@ -659,6 +684,7 @@ def __init__(
659684 self .socket = create_socket ()
660685 self .channel = channel
661686 self .channel_info = f"socketcan channel '{ channel } '"
687+ self ._can_hardware_timestamps = can_hardware_timestamps
662688 self ._bcm_sockets : Dict [str , socket .socket ] = {}
663689 self ._is_filtered = False
664690 self ._task_id = 0
@@ -703,12 +729,25 @@ def __init__(
703729 except OSError as error :
704730 log .error ("Could not enable error frames (%s)" , error )
705731
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+ )
712751
713752 try :
714753 bind_socket (self .socket , channel )
0 commit comments