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
@@ -636,11 +636,24 @@ def capture_message(
636636 # Fetching the timestamp
637637 assert len (ancillary_data ) == 1 , "only requested a single extra field"
638638 cmsg_level , cmsg_type , cmsg_data = ancillary_data [0 ]
639- assert (
640- cmsg_level == socket .SOL_SOCKET and cmsg_type == constants .SO_TIMESTAMPNS
639+ assert cmsg_level == socket .SOL_SOCKET and cmsg_type in (
640+ constants .SO_TIMESTAMPNS ,
641+ constants .SO_TIMESTAMPING ,
641642 ), "received control message type that was not requested"
642643 # see https://man7.org/linux/man-pages/man3/timespec.3.html -> struct timespec for details
643- seconds , nanoseconds = RECEIVED_TIMESTAMP_STRUCT .unpack_from (cmsg_data )
644+ if cmsg_type == constants .SO_TIMESTAMPNS :
645+ seconds , nanoseconds = RECEIVED_TIMESPEC_STRUCT .unpack_from (cmsg_data )
646+ else :
647+ # cmsg_type == constants.SO_TIMESTAMPING
648+ #
649+ # stamp[0] is the software timestamp
650+ # stamp[1] is deprecated
651+ # stamp[2] is the raw hardware timestamp
652+ offset = struct .calcsize (RECEIVED_TIMESPEC_STRUCT .format ) * 2
653+ seconds , nanoseconds = RECEIVED_TIMESPEC_STRUCT .unpack_from (
654+ cmsg_data , offset = offset
655+ )
656+
644657 if nanoseconds >= 1e9 :
645658 raise can .CanOperationError (
646659 f"Timestamp nanoseconds field was out of range: { nanoseconds } not less than 1e9"
@@ -699,6 +712,7 @@ def __init__(
699712 self ,
700713 channel : str = "" ,
701714 receive_own_messages : bool = False ,
715+ can_hardware_timestamps : bool = False ,
702716 local_loopback : bool = True ,
703717 fd : bool = False ,
704718 can_filters : Optional [CanFilters ] = None ,
@@ -722,6 +736,17 @@ def __init__(
722736 channel using :attr:`can.Message.channel`.
723737 :param receive_own_messages:
724738 If transmitted messages should also be received by this bus.
739+ :param bool can_hardware_timestamps:
740+ Use raw hardware timestamp for can messages if available instead
741+ of the system timestamp. By default we use the SO_TIMESTAMPNS
742+ interface which provides ns resolution but low accuracy. If your
743+ can hardware supports it you can use this parameter to
744+ alternatively use the SO_TIMESTAMPING interface and request raw
745+ hardware timestamps. These are much higher precision but will
746+ almost certainly not be referenced to the time of day. There
747+ may be other pitfalls to such as loopback packets reporting with
748+ no timestamp at all.
749+ See https://www.kernel.org/doc/html/latest/networking/timestamping.html
725750 :param local_loopback:
726751 If local loopback should be enabled on this bus.
727752 Please note that local loopback does not mean that messages sent
@@ -739,6 +764,7 @@ def __init__(
739764 self .socket = create_socket ()
740765 self .channel = channel
741766 self .channel_info = f"socketcan channel '{ channel } '"
767+ self ._can_hardware_timestamps = can_hardware_timestamps
742768 self ._bcm_sockets : Dict [str , socket .socket ] = {}
743769 self ._is_filtered = False
744770 self ._task_id = 0
@@ -783,12 +809,25 @@ def __init__(
783809 except OSError as error :
784810 log .error ("Could not enable error frames (%s)" , error )
785811
786- # enable nanosecond resolution timestamping
787- # we can always do this since
788- # 1) it is guaranteed to be at least as precise as without
789- # 2) it is available since Linux 2.6.22, and CAN support was only added afterward
790- # so this is always supported by the kernel
791- self .socket .setsockopt (socket .SOL_SOCKET , constants .SO_TIMESTAMPNS , 1 )
812+ if not self ._can_hardware_timestamps :
813+ # Utilise SO_TIMESTAMPNS interface :
814+ # we can always do this since
815+ # 1) it is guaranteed to be at least as precise as without
816+ # 2) it is available since Linux 2.6.22, and CAN support was only added afterward
817+ # so this is always supported by the kernel
818+ self .socket .setsockopt (socket .SOL_SOCKET , constants .SO_TIMESTAMPNS , 1 )
819+ else :
820+ # Utilise SO_TIMESTAMPING interface :
821+ # Allows us to use raw hardware timestamps where available
822+ timestamping_flags = (
823+ constants .SOF_TIMESTAMPING_SOFTWARE
824+ | constants .SOF_TIMESTAMPING_RX_SOFTWARE
825+ | constants .SOF_TIMESTAMPING_RAW_HARDWARE
826+ )
827+
828+ self .socket .setsockopt (
829+ socket .SOL_SOCKET , constants .SO_TIMESTAMPING , timestamping_flags
830+ )
792831
793832 try :
794833 bind_socket (self .socket , channel )
0 commit comments