未验证 提交 41e22025 编写于 作者: K Krzysztof Wicher 提交者: GitHub

Basic SocketCan implementation (#438)

* Basic SocketCan implementation

* Make usage cleaner

* make CanId.Raw internal

* Make CanFlags internal, address remaining feedback

* Apply feedback
上级 cd24ae0a
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
namespace Iot.Device.SocketCan
{
[Flags]
internal enum CanFlags : uint
{
ExtendedFrameFormat = 0x80000000,
RemoteTransmissionRequest = 0x40000000,
Error = 0x20000000,
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
namespace Iot.Device.SocketCan
{
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct CanFrame
{
public const int MaxLength = 8;
// RawId (can_id) includes EFF, RTR and ERR flags
public CanId Id;
// data length code (can_dlc)
// see: ISO 11898-1 Chapter 8.4.2.4
public byte Length;
private byte _pad;
private byte _res0;
private byte _res1;
public fixed byte Data[MaxLength];
public bool IsValid
{
get
{
return Length <= MaxLength && Id.IsValid;
}
}
}
}
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
namespace Iot.Device.SocketCan
{
public struct CanId
{
// Raw (can_id) includes EFF, RTR and ERR flags
internal uint Raw { get; set; }
public uint Value => ExtendedFrameFormat ? Extended : Standard;
public uint Standard
{
get
{
if (ExtendedFrameFormat)
throw new InvalidOperationException($"{nameof(Standard)} can be obtained only when {nameof(ExtendedFrameFormat)} is not set.");
return Raw & Interop.CAN_SFF_MASK;
}
set
{
if ((value & ~Interop.CAN_SFF_MASK) != 0)
throw new InvalidOperationException($"{nameof(value)} must be 11 bit identifier.");
ExtendedFrameFormat = false;
// note: we clear all bits, not just SFF
Raw &= ~Interop.CAN_EFF_MASK;
Raw |= value;
}
}
public uint Extended
{
get
{
if (!ExtendedFrameFormat)
throw new InvalidOperationException($"{nameof(Extended)} can be obtained only when {nameof(ExtendedFrameFormat)} is set.");
return Raw & Interop.CAN_EFF_MASK;
}
set
{
if ((value & ~Interop.CAN_EFF_MASK) != 0)
throw new InvalidOperationException($"{nameof(value)} must be 29 bit identifier.");
ExtendedFrameFormat = true;
Raw &= ~Interop.CAN_EFF_MASK;
Raw |= value;
}
}
public bool Error
{
get => ((CanFlags)Raw).HasFlag(CanFlags.Error);
set => SetCanFlag(CanFlags.Error, value);
}
public bool ExtendedFrameFormat
{
get => ((CanFlags)Raw).HasFlag(CanFlags.ExtendedFrameFormat);
set => SetCanFlag(CanFlags.ExtendedFrameFormat, value);
}
public bool RemoteTransmissionRequest
{
get => ((CanFlags)Raw).HasFlag(CanFlags.RemoteTransmissionRequest);
set => SetCanFlag(CanFlags.RemoteTransmissionRequest, value);
}
public bool IsValid
{
get
{
uint idMask = ExtendedFrameFormat ? Interop.CAN_EFF_MASK : Interop.CAN_SFF_MASK;
return !Error && (Raw & idMask) == (Raw & Interop.CAN_EFF_MASK);
}
}
private void SetCanFlag(CanFlags flag, bool value)
{
if (value)
{
Raw |= (uint)flag;
}
else
{
Raw &= ~(uint)flag;
}
}
}
}
\ No newline at end of file
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
namespace Iot.Device.SocketCan
{
public class CanRaw : IDisposable
{
private SafeCanRawSocketHandle _handle;
public CanRaw(string networkInterface = "can0")
{
_handle = new SafeCanRawSocketHandle(networkInterface);
}
public void WriteFrame(ReadOnlySpan<byte> data, CanId id)
{
if (!id.IsValid)
throw new ArgumentException(nameof(id), "Id is not valid. Ensure Error flag is not set and that id is in the valid range (11-bit for standard frame and 29-bit for extended frame).");
if (data.Length > CanFrame.MaxLength)
throw new ArgumentException(nameof(data), $"Data length cannot exceed {CanFrame.MaxLength} bytes.");
CanFrame frame = new CanFrame();
frame.Id = id;
frame.Length = (byte)data.Length;
Debug.Assert(frame.IsValid);
unsafe
{
Span<byte> frameData = new Span<byte>(frame.Data, data.Length);
data.CopyTo(frameData);
}
ReadOnlySpan<CanFrame> frameSpan = MemoryMarshal.CreateReadOnlySpan(ref frame, 1);
ReadOnlySpan<byte> buff = MemoryMarshal.AsBytes(frameSpan);
Interop.Write(_handle, buff);
}
public bool TryReadFrame(Span<byte> buffer, out int frameLength, out CanId id)
{
if (buffer.Length < CanFrame.MaxLength)
{
throw new ArgumentException($"Buffer length must be at minimum {CanFrame.MaxLength} bytes", nameof(buffer));
}
CanFrame frame = new CanFrame();
Span<CanFrame> frameSpan = MemoryMarshal.CreateSpan(ref frame, 1);
Span<byte> buff = MemoryMarshal.AsBytes(frameSpan);
while (buff.Length > 0)
{
int read = Interop.Read(_handle, buff);
buff = buff.Slice(read);
}
id = frame.Id;
frameLength = frame.Length;
if (!frame.IsValid)
{
// invalid frame
// we will leave id filled in case it is useful for anyone
frameLength = 0;
return false;
}
// This is guaranteed by minimum buffer length and the fact that frame is valid
Debug.Assert(frame.Length <= buffer.Length);
unsafe
{
// We should not use input buffer directly for reading:
// - we do not know how many bytes will be read up front without reading length first
// - we should not write anything more than pointed by frameLength
// - we still need to read the remaining bytes to read the full frame
// Considering there are at most 8 bytes to read it is cheaper
// to copy rather than doing multiple syscalls.
Span<byte> frameData = new Span<byte>(frame.Data, frame.Length);
frameData.CopyTo(buffer);
}
return true;
}
public void Filter(CanId id)
{
if (!id.IsValid)
{
throw new ArgumentException($"{nameof(id)} must be a valid CanId");
}
Span<Interop.CanFilter> filters = stackalloc Interop.CanFilter[1];
filters[0].can_id = id.Raw;
filters[0].can_mask = id.Value | (uint)CanFlags.ExtendedFrameFormat | (uint)CanFlags.RemoteTransmissionRequest;
Interop.SetCanRawSocketOption<Interop.CanFilter>(_handle, Interop.CanSocketOption.CAN_RAW_FILTER, filters);
}
private static bool IsEff(uint address)
{
// has explicit flag or address does not fit in SFF addressing mode
return (address & (uint)CanFlags.ExtendedFrameFormat) != 0
|| (address & Interop.CAN_EFF_MASK) != (address & Interop.CAN_SFF_MASK);
}
public void Dispose()
{
_handle.Dispose();
}
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
namespace Iot.Device.SocketCan
{
internal class Interop
{
private const int PF_CAN = 29;
// SFF = Standard Frame Format - 11 bit
public const uint CAN_SFF_MASK = 0x000007FF;
// EFF = Extended Frame Format - 29 bit
public const uint CAN_EFF_MASK = 0x1FFFFFFF;
public const uint CAN_ERR_MASK = 0x1FFFFFFF;
public const int SOL_CAN_BASE = 100;
public const int SOL_CAN_RAW = SOL_CAN_BASE + (int)CanProtocol.CAN_RAW;
[DllImport("libc", EntryPoint = "socket", CallingConvention = CallingConvention.Cdecl)]
private static extern int CreateNativeSocket(int domain, int type, CanProtocol protocol);
[DllImport("libc", EntryPoint = "ioctl", CallingConvention = CallingConvention.Cdecl)]
private static extern int Ioctl3(int fd, uint request, ref ifreq ifr);
[DllImport("libc", EntryPoint = "bind", CallingConvention = CallingConvention.Cdecl)]
private static extern int BindSocket(int fd, ref CanSocketAddress addr, uint addrlen);
[DllImport("libc", EntryPoint = "close", CallingConvention = CallingConvention.Cdecl)]
private static extern int CloseSocket(int fd);
[DllImport("libc", EntryPoint = "write", CallingConvention = CallingConvention.Cdecl)]
private static unsafe extern int SocketWrite(int fd, byte* buffer, int size);
[DllImport("libc", EntryPoint = "read", CallingConvention = CallingConvention.Cdecl)]
private static unsafe extern int SocketRead(int fd, byte* buffer, int size);
[DllImport("libc", EntryPoint = "setsockopt", CallingConvention = CallingConvention.Cdecl)]
private static unsafe extern int SetSocketOpt(int fd, int level, int optName, byte* optVal, int optlen);
public static unsafe void Write(SafeHandle handle, ReadOnlySpan<byte> buffer)
{
fixed (byte* b = buffer)
{
int totalBytesWritten = 0;
while (totalBytesWritten < buffer.Length)
{
int bytesWritten = Interop.SocketWrite((int)handle.DangerousGetHandle(), b, buffer.Length);
if (bytesWritten < 0)
{
throw new IOException("`write` operation failed");
}
totalBytesWritten += bytesWritten;
}
}
}
public static unsafe int Read(SafeHandle handle, Span<byte> buffer)
{
fixed (byte* b = buffer)
{
int bytesRead = Interop.SocketRead((int)handle.DangerousGetHandle(), b, buffer.Length);
if (bytesRead < 0)
{
throw new IOException("`read` operation failed");
}
return bytesRead;
}
}
public static void CloseSocket(IntPtr fd)
{
CloseSocket((int)fd);
}
public static IntPtr CreateCanRawSocket(string networkInterface)
{
const int SOCK_RAW = 3;
int socket = CreateNativeSocket(PF_CAN, SOCK_RAW, CanProtocol.CAN_RAW);
if (socket == -1)
throw new IOException("CAN socket could not be created");
BindToInterface(socket, networkInterface);
return new IntPtr(socket);
}
public static bool SetCanRawSocketOption<T>(SafeHandle handle, CanSocketOption optName, ReadOnlySpan<T> data)
where T : struct
{
return SetSocketOption(handle, SOL_CAN_RAW, optName, data);
}
private static unsafe bool SetSocketOption<T>(SafeHandle handle, int level, CanSocketOption optName, ReadOnlySpan<T> data)
where T : struct
{
int fd = (int)handle.DangerousGetHandle();
ReadOnlySpan<byte> buf = MemoryMarshal.AsBytes(data);
fixed (byte* pinned = buf)
{
return SetSocketOpt(fd, level, (int)optName, pinned, buf.Length) == 0;
}
}
private static unsafe void BindToInterface(int fd, string interfaceName)
{
int idx = GetInterfaceIndex(fd, interfaceName);
CanSocketAddress addr = new CanSocketAddress();
addr.can_family = PF_CAN;
addr.can_ifindex = idx;
if (-1 == BindSocket(fd, ref addr, (uint)Marshal.SizeOf<CanSocketAddress>()))
{
throw new IOException($"Cannot bind to socket to `{interfaceName}`");
}
}
private static unsafe int GetInterfaceIndex(int fd, string name)
{
const uint SIOCGIFINDEX = 0x8933;
const int MaxLen = ifreq.IFNAMSIZ - 1;
if (name.Length >= MaxLen)
{
throw new ArgumentException($"`{name}` exceeds maximum allowed length of {MaxLen} size", nameof(name));
}
ifreq ifr = new ifreq();
fixed (char* inIntefaceName = name)
{
int written = Encoding.ASCII.GetBytes(inIntefaceName, name.Length, ifr.ifr_name, MaxLen);
ifr.ifr_name[written] = 0;
}
int ret = Ioctl3(fd, SIOCGIFINDEX, ref ifr);
if (ret == -1)
{
throw new IOException($"Could not get interface index for `{name}`");
}
return ifr.ifr_ifindex;
}
internal unsafe struct ifreq
{
internal const int IFNAMSIZ = 16;
public fixed byte ifr_name[IFNAMSIZ];
public int ifr_ifindex;
private fixed byte _padding[IFNAMSIZ - sizeof(int)];
}
internal struct CanSocketAddress
{
public short can_family;
public int can_ifindex;
public uint rx_id;
public uint tx_id;
}
internal struct CanFilter
{
public uint can_id;
public uint can_mask;
}
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct CanFdFrame
{
private const int CANFD_MAX_DLEN = 64;
// can_id includes EFF, RTR and ERR flags
public uint can_id;
public byte len;
private CanFdFlags flags;
private byte _res0;
private byte _res1;
public fixed byte data[CANFD_MAX_DLEN];
}
internal enum CanProtocol : int
{
CAN_RAW = 1,
// Broadcast Manager
CAN_BCM = 2,
// VAG Transport Protocol v1.6
CAN_TP16 = 3,
// VAG Transport Protocol v2.0
CAN_TP20 = 4,
// Bosch MCNet
CAN_MCNET = 5,
// ISO 15765-2 Transport Protocol
CAN_ISOTP = 6,
CAN_NPROTO = 7,
}
internal enum CanSocketOption : int
{
// set 0 .. n can_filter(s)
CAN_RAW_FILTER = 1,
// set filter for error frames
CAN_RAW_ERR_FILTER,
// local loopback (default:on)
CAN_RAW_LOOPBACK,
// receive my own msgs (default:off)
CAN_RAW_RECV_OWN_MSGS,
// allow CAN FD frames (default:off)
CAN_RAW_FD_FRAMES,
// all filters must match to trigger
CAN_RAW_JOIN_FILTERS,
}
[Flags]
internal enum CanFdFlags : byte
{
CANFD_BRS = 0x01,
CANFD_ESI = 0x02,
}
}
}
# SocketCan
Controller Area Network Protocol Family bindings (SocketCAN).
[samples/README.md](See sample for usage.)
## Setup for Raspberry PI and MCP2515
- Connect SPI device to regular SPI pins (SI/MOSI - `BCM 10`; SO/MISO - `BCM 9`; CLK/SCK - `BCM 11`; CS - `CE0`)
- Interrupt pin should be connected to any GPIO pin i.e. `BCM 25` (note: interrupt pin can be adjusted below)
- Add following in `/boot/config.txt`
```
dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=25
dtoverlay=spi-bcm2835-overlay
```
For test run `ifconfig -a` and check if `can0` (or similar) device is on the list.
Now we need to set network bitrate and "start" the network.
Other popular bit rates: 10000, 20000, 50000, 100000, 125000, 250000, 500000, 800000, 1000000
```sh
sudo ip link set can0 up type can bitrate 125000
sudo ifconfig can0 up
```
## Diagnosing the network (tested on Raspberry Pi)
These steps are not required but might be useful for diagnosing potential issues.
- Install can-utils package (i.e. `sudo apt-get install can-utils`)
```sh
sudo apt-get -y install can-utils
```
- On first device listen to CAN frames (can be also sent on the same device but ensure seperate terminal)
```sh
candump can0
```
- On second device send a packet
```sh
cansend can0 01a#11223344AABBCCDD
```
- On the first device you should see the packet being send by the second device
## References
- https://www.kernel.org/doc/Documentation/networking/can.txt
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
namespace Iot.Device.SocketCan
{
internal class SafeCanRawSocketHandle : SafeHandle
{
public SafeCanRawSocketHandle(string networkInterface) : base(Interop.CreateCanRawSocket(networkInterface), true)
{
}
public override bool IsInvalid => (int)handle == -1;
protected override bool ReleaseHandle()
{
Interop.CloseSocket(handle);
return true;
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<!--Disabling default items so samples source won't get build by the main library-->
<EnableDefaultItems>false</EnableDefaultItems>
<LangVersion>Latest</LangVersion>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Include="*.cs" />
<None Include="README.md" />
<PackageReference Include="System.Device.Gpio" Version="$(SystemDeviceGpioPackageVersion)" />
</ItemGroup>
</Project>
# SocketCan
APIs are under `Iot.Device.SocketCan` namespace.
## Reading a frame
```csharp
using (CanRaw can = new CanRaw())
{
byte[] buffer = new byte[8];
// to scope to specific id
// can.Filter(id);
while (true)
{
if (can.TryReadFrame(buffer, out int frameLength, out CanId id))
{
Span<byte> data = new Span<byte>(buffer, 0, frameLength);
string type = id.ExtendedFrameFormat ? "EFF" : "SFF";
string dataAsHex = string.Join("", data.ToArray().Select((x) => x.ToString("X2")));
Console.WriteLine($"Id: 0x{id.Value:X2} [{type}]: {dataAsHex}");
}
else
{
Console.WriteLine($"Invalid frame received!");
}
}
}
```
## Writing a frame
```csharp
CanId id = new CanId()
{
Standard = 0x1A // arbitrary id
};
using (CanRaw can = new CanRaw())
{
byte[][] buffers = new byte[][]
{
new byte[8] { 1, 2, 3, 40, 50, 60, 70, 80 },
new byte[7] { 1, 2, 3, 40, 50, 60, 70 },
new byte[0] { },
new byte[1] { 254 },
};
while (true)
{
foreach (byte[] buffer in buffers)
{
can.WriteFrame(buffer, id);
string dataAsHex = string.Join("", buffer.Select((x) => x.ToString("X2")));
Console.WriteLine($"Sending: {dataAsHex}");
Thread.Sleep(1000);
}
}
}
```
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
namespace Iot.Device.SocketCan.Samples
{
class Program
{
static readonly CanId Id = new CanId()
{
Standard = 0x1A // arbitrary id
};
static Dictionary<string, Action> s_samples = new Dictionary<string, Action>
{
{ "send", SendExample },
{ "receive", ReceiveAllExample },
{ "receive-on-specific-address", ReceiveOnAddressExample },
};
static void Main(string[] args)
{
if (args.Length == 0 || !s_samples.ContainsKey(args[0]))
{
Console.WriteLine("Usage: SocketCan.Sample <sample-name>");
Console.WriteLine("Available samples:");
foreach (var kv in s_samples)
{
Console.WriteLine($"- {kv.Key}");
}
return;
}
s_samples[args[0]]();
}
private static void SendExample()
{
Console.WriteLine($"Sending to id = 0x{Id.Value:X2}");
using (CanRaw can = new CanRaw())
{
byte[][] buffers = new byte[][]
{
new byte[8] { 1, 2, 3, 40, 50, 60, 70, 80 },
new byte[7] { 1, 2, 3, 40, 50, 60, 70 },
new byte[0] { },
new byte[1] { 254 },
};
if (!Id.IsValid)
{
// This is more form of the self-test rather than actual part of the sample
throw new Exception("Id is invalid");
}
while (true)
{
foreach (byte[] buffer in buffers)
{
can.WriteFrame(buffer, Id);
string dataAsHex = string.Join("", buffer.Select((x) => x.ToString("X2")));
Console.WriteLine($"Sending: {dataAsHex}");
Thread.Sleep(1000);
}
}
}
}
private static void ReceiveAllExample()
{
Console.WriteLine("Listening for any id");
using (CanRaw can = new CanRaw())
{
byte[] buffer = new byte[8];
while (true)
{
if (can.TryReadFrame(buffer, out int frameLength, out CanId id))
{
Span<byte> data = new Span<byte>(buffer, 0, frameLength);
string type = id.ExtendedFrameFormat ? "EFF" : "SFF";
string dataAsHex = string.Join("", data.ToArray().Select((x) => x.ToString("X2")));
Console.WriteLine($"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("", data.ToArray().Select((x) => x.ToString("X2")));
Console.WriteLine($"Id: 0x{id.Value:X2} [{type}]: {dataAsHex}");
}
}
}
}
private static void ReceiveOnAddressExample()
{
Console.WriteLine($"Listening for id = 0x{Id.Value:X2}");
using (CanRaw can = new CanRaw())
{
byte[] buffer = new byte[8];
can.Filter(Id);
while (true)
{
if (can.TryReadFrame(buffer, out int frameLength, out CanId id))
{
Span<byte> data = new Span<byte>(buffer, 0, frameLength);
string type = id.ExtendedFrameFormat ? "EFF" : "SFF";
string dataAsHex = string.Join("", data.ToArray().Select((x) => x.ToString("X2")));
Console.WriteLine($"Id: 0x{id.Value:X2} [{type}]: {dataAsHex}");
}
else
{
Console.WriteLine($"Invalid frame received!");
}
}
}
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../SocketCan.csproj" />
</ItemGroup>
</Project>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册