feat(ab-transport): project skeleton with AbStatus, CIP reply parsing and the ICipChannel seam
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport;
|
||||
|
||||
/// <summary>Transport-neutral status shared by the libplctag and native AB transports.
|
||||
/// Zero is success; every error is negative so existing <c>status != 0</c> checks stay correct.</summary>
|
||||
public enum AbStatus
|
||||
{
|
||||
Ok = 0,
|
||||
Pending = 1,
|
||||
Timeout = -1,
|
||||
NotFound = -2,
|
||||
NotAllowed = -3,
|
||||
OutOfBounds = -4,
|
||||
TooLarge = -5,
|
||||
TooSmall = -6,
|
||||
Unsupported = -7,
|
||||
BadConnection = -8,
|
||||
BadReply = -9,
|
||||
Partial = -10,
|
||||
Abort = -11,
|
||||
TypeMismatch = -12,
|
||||
DeviceStateConflict = -13,
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport;
|
||||
|
||||
public static class AbStatusCodes
|
||||
{
|
||||
public static AbStatus FromCip(byte general, ReadOnlySpan<ushort> extended) => general switch
|
||||
{
|
||||
0x00 or 0x0B => AbStatus.Ok,
|
||||
0x04 or 0x05 or 0x16 => AbStatus.NotFound,
|
||||
0x06 => AbStatus.Partial,
|
||||
0x08 => AbStatus.Unsupported,
|
||||
0x0A => AbStatus.OutOfBounds,
|
||||
0x0C or 0x10 => AbStatus.DeviceStateConflict,
|
||||
0x0E or 0x0F => AbStatus.NotAllowed,
|
||||
0x13 => AbStatus.TooSmall,
|
||||
0x15 => AbStatus.TooLarge,
|
||||
0xFF when extended.Length > 0 && extended[0] == 0x2105 => AbStatus.OutOfBounds,
|
||||
0xFF when extended.Length > 0 && extended[0] == 0x2107 => AbStatus.TypeMismatch,
|
||||
_ => AbStatus.BadReply,
|
||||
};
|
||||
|
||||
public static AbStatus FromPccc(byte sts, byte? extSts) => sts switch
|
||||
{
|
||||
0x00 => AbStatus.Ok,
|
||||
0x10 => AbStatus.Unsupported,
|
||||
0x20 => AbStatus.NotFound,
|
||||
0x30 => AbStatus.NotAllowed,
|
||||
0x40 or 0x50 => AbStatus.DeviceStateConflict,
|
||||
0xF0 when extSts is 0x06 or 0x07 => AbStatus.NotFound,
|
||||
_ => AbStatus.BadReply,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport;
|
||||
|
||||
/// <summary>Thrown by both AB transports. <see cref="Status"/> drives OPC UA status mapping and the
|
||||
/// driver's "controller answered, don't throttle the device" classification (05/STAB-8).</summary>
|
||||
public sealed class AbTransportException(AbStatus status, string message, Exception? inner = null)
|
||||
: Exception(message, inner)
|
||||
{
|
||||
public AbStatus Status { get; } = status;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Wire;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip;
|
||||
|
||||
/// <summary>A parsed CIP message-router reply: service (request code, 0x80 bit stripped), general
|
||||
/// status, extended status words, and the reply data.</summary>
|
||||
public sealed record CipReply(byte Service, byte GeneralStatus, ushort[] ExtendedStatus, ReadOnlyMemory<byte> Data)
|
||||
{
|
||||
public AbStatus Status => AbStatusCodes.FromCip(GeneralStatus, ExtendedStatus);
|
||||
|
||||
public static CipReply Parse(ReadOnlyMemory<byte> raw)
|
||||
{
|
||||
var r = new CipReader(raw.Span);
|
||||
var service = (byte)(r.U8() & 0x7F);
|
||||
r.U8(); // reserved
|
||||
var general = r.U8();
|
||||
var extWords = r.U8();
|
||||
var ext = new ushort[extWords];
|
||||
for (var i = 0; i < extWords; i++) ext[i] = r.U16();
|
||||
var data = raw[r.Position..];
|
||||
return new CipReply(service, general, ext, data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip;
|
||||
|
||||
/// <summary>A path to a CIP message router: connected (Forward Open) or unconnected (UCMM). Task 9
|
||||
/// implements it; Task 10's scheduler and the native adapters consume it.</summary>
|
||||
public interface ICipChannel
|
||||
{
|
||||
/// <summary>Largest CIP request (service byte through end of data) one call may carry.</summary>
|
||||
int MaxRequestBytes { get; }
|
||||
|
||||
/// <summary>Send one CIP request and return the raw CIP reply bytes (reply-service byte first).
|
||||
/// Throws <see cref="AbTransportException"/> on transport failure or timeout.</summary>
|
||||
Task<ReadOnlyMemory<byte>> SendAsync(ReadOnlyMemory<byte> cipRequest, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Buffers.Binary;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Wire;
|
||||
|
||||
/// <summary>Bounds-checked little-endian reader. Any overrun throws AbTransportException(BadReply).</summary>
|
||||
public ref struct CipReader(ReadOnlySpan<byte> data)
|
||||
{
|
||||
private readonly ReadOnlySpan<byte> _data = data;
|
||||
public int Position { get; private set; }
|
||||
public readonly int Remaining => _data.Length - Position;
|
||||
|
||||
private ReadOnlySpan<byte> Take(int n)
|
||||
{
|
||||
if (n < 0 || Position + n > _data.Length)
|
||||
throw new AbTransportException(AbStatus.BadReply, $"Reply truncated: need {n} bytes at {Position}, have {Remaining}.");
|
||||
var s = _data.Slice(Position, n);
|
||||
Position += n;
|
||||
return s;
|
||||
}
|
||||
|
||||
public byte U8() => Take(1)[0];
|
||||
public ushort U16() => BinaryPrimitives.ReadUInt16LittleEndian(Take(2));
|
||||
public uint U32() => BinaryPrimitives.ReadUInt32LittleEndian(Take(4));
|
||||
public ulong U64() => BinaryPrimitives.ReadUInt64LittleEndian(Take(8));
|
||||
public ReadOnlySpan<byte> Bytes(int n) => Take(n);
|
||||
public ReadOnlySpan<byte> Rest() => Take(Remaining);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Buffers.Binary;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Wire;
|
||||
|
||||
/// <summary>Growable little-endian writer for CIP / EIP frames.</summary>
|
||||
public sealed class CipWriter
|
||||
{
|
||||
private byte[] _buf = new byte[256];
|
||||
public int Length { get; private set; }
|
||||
|
||||
private Span<byte> Take(int n)
|
||||
{
|
||||
if (Length + n > _buf.Length) Array.Resize(ref _buf, Math.Max(_buf.Length * 2, Length + n));
|
||||
var s = _buf.AsSpan(Length, n);
|
||||
Length += n;
|
||||
return s;
|
||||
}
|
||||
|
||||
public CipWriter U8(byte v) { Take(1)[0] = v; return this; }
|
||||
public CipWriter U16(ushort v) { BinaryPrimitives.WriteUInt16LittleEndian(Take(2), v); return this; }
|
||||
public CipWriter U32(uint v) { BinaryPrimitives.WriteUInt32LittleEndian(Take(4), v); return this; }
|
||||
public CipWriter U64(ulong v) { BinaryPrimitives.WriteUInt64LittleEndian(Take(8), v); return this; }
|
||||
public CipWriter Bytes(ReadOnlySpan<byte> v) { v.CopyTo(Take(v.Length)); return this; }
|
||||
public CipWriter PadToEven() { if ((Length & 1) == 1) U8(0); return this; }
|
||||
|
||||
/// <summary>Overwrite a u16 already written at <paramref name="offset"/> (length back-patching).</summary>
|
||||
public void PatchU16(int offset, ushort v) => BinaryPrimitives.WriteUInt16LittleEndian(_buf.AsSpan(offset, 2), v);
|
||||
|
||||
public byte[] ToArray() => _buf.AsSpan(0, Length).ToArray();
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||
<RootNamespace>ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport</RootNamespace>
|
||||
<AssemblyName>ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests"/>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ZB.MOM.WW.OtOpcUa.Driver.AbCip.Contracts\ZB.MOM.WW.OtOpcUa.Driver.AbCip.Contracts.csproj"/>
|
||||
<ProjectReference Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.csproj"/>
|
||||
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core.Abstractions\ZB.MOM.WW.OtOpcUa.Core.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core\ZB.MOM.WW.OtOpcUa.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Contracts\ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Contracts.csproj"/>
|
||||
<ProjectReference Include="..\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport\ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.csproj"/>
|
||||
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core.Abstractions\ZB.MOM.WW.OtOpcUa.Core.Abstractions.csproj"/>
|
||||
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core\ZB.MOM.WW.OtOpcUa.Core.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
Reference in New Issue
Block a user