diff --git a/src/devices/SocketCan/CanRaw.cs b/src/devices/SocketCan/CanRaw.cs index 1f3c0118ea..01f342f3ec 100644 --- a/src/devices/SocketCan/CanRaw.cs +++ b/src/devices/SocketCan/CanRaw.cs @@ -117,6 +117,21 @@ public bool TryReadFrame(Span data, out int frameLength, out CanId id) return true; } + /// + /// Reads frame from the bus with the timestamp when it was received. + /// + /// Data where output data should be written to + /// Length of the data read + /// Recipient identifier + /// UTC time when this frame was received with microsecond resolution + /// + public bool TryReadFrame(Span data, out int frameLength, out CanId id, out DateTime timestamp) + { + bool ret = TryReadFrame(data, out frameLength, out id); + timestamp = Interop.GetLastTimeStamp(_handle); + return ret; + } + /// /// Set filter on the bus to read only from specified recipient. /// diff --git a/src/devices/SocketCan/Interop.cs b/src/devices/SocketCan/Interop.cs index 58d047b0a4..295279aff5 100644 --- a/src/devices/SocketCan/Interop.cs +++ b/src/devices/SocketCan/Interop.cs @@ -36,6 +36,9 @@ internal class Interop [DllImport("libc", EntryPoint = "ioctl", CallingConvention = CallingConvention.Cdecl)] private static extern int Ioctl3(int fd, uint request, ref InterfaceIndexQuery ifr); + [DllImport("libc", EntryPoint = "ioctl", CallingConvention = CallingConvention.Cdecl)] + private static extern int Ioctl4(int fd, uint request, out TimeVal tv); + [DllImport("libc", EntryPoint = "bind", CallingConvention = CallingConvention.Cdecl)] private static extern int BindSocket(int fd, ref CanSocketAddress addr, uint addrlen); @@ -149,6 +152,32 @@ private static unsafe int GetInterfaceIndex(int fd, string name) return ifr.ifr_ifindex; } + public static unsafe DateTime GetLastTimeStamp(SafeHandle handle) + { + // From Linux 6.12.33+deb13-amd64 x86_64 + const uint SIOCGSTAMP = 0x8906; + + TimeVal tv; + int ret = Ioctl4((int)handle.DangerousGetHandle(), SIOCGSTAMP, out tv); + if (ret == -1) + { + throw new IOException("Could not get socketcan timestamp"); + } + + DateTime dt_utc = DateTimeOffset.FromUnixTimeSeconds(tv.tv_sec) + .AddTicks(tv.tv_usec * 10) // 1 tick = 100 ns + .UtcDateTime; + + return dt_utc; // UtcDateTime + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct TimeVal + { + public long tv_sec; /* Seconds */ + public long tv_usec; /* Microseconds */ + } + internal unsafe struct InterfaceIndexQuery { internal const int IFNAMSIZ = 16; diff --git a/src/devices/SocketCan/samples/Program.cs b/src/devices/SocketCan/samples/Program.cs index c48c69788b..df90e747bb 100644 --- a/src/devices/SocketCan/samples/Program.cs +++ b/src/devices/SocketCan/samples/Program.cs @@ -19,9 +19,9 @@ { "receive-on-specific-address", ReceiveOnAddressExample }, }; -if (args.Length == 0 || !samples.ContainsKey(args[0])) +if (args.Length < 2 || !samples.ContainsKey(args[0])) { - Console.WriteLine("Usage: SocketCan.Sample "); + Console.WriteLine("Usage: SocketCan.Sample "); Console.WriteLine("Available samples:"); foreach (var kv in samples) @@ -38,7 +38,7 @@ void SendExample() { Console.WriteLine($"Sending to id = 0x{id.Value:X2}"); - using CanRaw can = new CanRaw(); + using CanRaw can = new CanRaw(args[1]); byte[][] buffers = new byte[][] { new byte[8] { 1, 2, 3, 40, 50, 60, 70, 80 }, @@ -69,17 +69,17 @@ void ReceiveAllExample() { Console.WriteLine("Listening for any id"); - using CanRaw can = new(); + using CanRaw can = new(args[1]); byte[] buffer = new byte[8]; while (true) { - if (can.TryReadFrame(buffer, out int frameLength, out CanId id)) + if (can.TryReadFrame(buffer, out int frameLength, out CanId id, out DateTime ts)) { Span data = new Span(buffer, 0, frameLength); string type = id.ExtendedFrameFormat ? "EFF" : "SFF"; string dataAsHex = string.Join(string.Empty, data.ToArray().Select((x) => x.ToString("X2"))); - Console.WriteLine($"Id: 0x{id.Value:X2} [{type}]: {dataAsHex}"); + Console.WriteLine($"Ts: {ts:o} Id: 0x{id.Value:X2} [{type}]: {dataAsHex}"); } else { @@ -87,7 +87,7 @@ void ReceiveAllExample() Span data = new Span(buffer, 0, frameLength); string type = id.ExtendedFrameFormat ? "EFF" : "SFF"; string dataAsHex = string.Join(string.Empty, data.ToArray().Select((x) => x.ToString("X2"))); - Console.WriteLine($"Id: 0x{id.Value:X2} [{type}]: {dataAsHex}"); + Console.WriteLine($"Ts: {ts:o} Id: 0x{id.Value:X2} [{type}]: {dataAsHex}"); } } } @@ -96,7 +96,7 @@ void ReceiveOnAddressExample() { Console.WriteLine($"Listening for id = 0x{id.Value:X2}"); - using CanRaw can = new(); + using CanRaw can = new(args[1]); byte[] buffer = new byte[8]; can.Filter(id);