Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/devices/SocketCan/CanRaw.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ public bool TryReadFrame(Span<byte> data, out int frameLength, out CanId id)
return true;
}

/// <summary>
/// Reads the timestamp of the last read frame from the kernel
/// </summary>
/// <returns>unix time of the last read frame in [us]</returns>
public ulong GetLastTimeStamp()
{
return Interop.GetLastTimeStamp(_handle);
}

/// <summary>
/// Set filter on the bus to read only from specified recipient.
/// </summary>
Expand Down
24 changes: 24 additions & 0 deletions src/devices/SocketCan/Interop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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, ref TimeVal tv);

[DllImport("libc", EntryPoint = "bind", CallingConvention = CallingConvention.Cdecl)]
private static extern int BindSocket(int fd, ref CanSocketAddress addr, uint addrlen);

Expand Down Expand Up @@ -149,6 +152,27 @@ private static unsafe int GetInterfaceIndex(int fd, string name)
return ifr.ifr_ifindex;
}

public static unsafe ulong GetLastTimeStamp(SafeHandle handle)
{
// From Linux 6.12.33+deb13-amd64 x86_64
const uint SIOCGSTAMP = 0x8906;

TimeVal tv = new TimeVal();
int ret = Ioctl4((int)handle.DangerousGetHandle(), SIOCGSTAMP, ref tv);
if (ret == -1)
{
throw new IOException("Could not get socketcan timestamp");
}

return ((ulong)tv.tv_sec * 1000000 + (ulong)tv.tv_usec); // unix time in [us]
}

internal unsafe struct TimeVal
Copy link
Member

@krwq krwq Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there should be a comment that long is correct type only because we're using this in conjunction with latest kernel. Please double check this actually works with 32-bit OS-es (if not then please make sure to display correct error message)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note, you don't need to test it - just double check what type is defined in the latest kernel's .h file, if it's int64_t or equivalent we're good if it's platform specific like int then likely not

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I did some digging. In C, the ioctl call returns:

       struct timeval {
           time_t       tv_sec;   /* Seconds */
           suseconds_t  tv_usec;  /* Microseconds */
       };

time_t can be a signed integer of 32 bit or 64 bit, depending on the platform.

In C#, I map this to a long type which is always a 64 bit signed integer.

If I run this on a 64 bit system, everything fits bit by bit.

If I run this on a 32-bit system, the C# runtime will promote the 32-bit return value to long properly.

Please correct me if I'm wrong.

I will look into testing this on a 32 bit system.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is DateTime the correct for this timestamp?
When I see a DateTime, I expect this represent the "real" moment when the event occurred. I also expect to be able to compare this value with other DateTime instances coming from other sensors.

If instead this timestamp represents the time elapsed since the OS started, IMO the TimeSpan type would be more appropriate.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The kernel does indeed return the absolute value of the system clock when the CAN frame was received (microseconds since the 1.1.1970). So I think DateTime is a good type for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know, thanks

{
public long tv_sec; /* Seconds */
public long tv_usec; /* Microseconds */
}

internal unsafe struct InterfaceIndexQuery
{
internal const int IFNAMSIZ = 16;
Expand Down
6 changes: 4 additions & 2 deletions src/devices/SocketCan/samples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,17 @@ void ReceiveAllExample()
Span<byte> data = new Span<byte>(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}");
ulong ts = can.GetLastTimeStamp(); // Get the kernel timestamp of the frame
Console.WriteLine($"Ts: {ts} Id: 0x{id.Value:X2} [{type}]: {dataAsHex}");
}
else
{
Console.WriteLine($"Invalid frame received!");
Span<byte> data = new Span<byte>(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}");
ulong ts = can.GetLastTimeStamp();
Console.WriteLine($"Ts: {ts} Id: 0x{id.Value:X2} [{type}]: {dataAsHex}");
}
}
}
Expand Down