merge: Task 6 — CIP path encoding + Logix type codes
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Wire;
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip;
|
||||
|
||||
public static class CipPath
|
||||
{
|
||||
/// <summary>Route path from "port,link[,port,link…]". Links that are not a byte value are IP
|
||||
/// addresses, encoded as extended link-address port segments.</summary>
|
||||
public static byte[] EncodeRoute(string cipPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(cipPath)) return [];
|
||||
var w = new CipWriter();
|
||||
var parts = cipPath.Split(',', StringSplitOptions.TrimEntries);
|
||||
if (parts.Length % 2 != 0) throw new ArgumentException($"CIP path '{cipPath}' must be port,link pairs.", nameof(cipPath));
|
||||
for (var i = 0; i < parts.Length; i += 2)
|
||||
{
|
||||
var port = int.Parse(parts[i], CultureInfo.InvariantCulture);
|
||||
var link = parts[i + 1];
|
||||
var isByte = byte.TryParse(link, NumberStyles.None, CultureInfo.InvariantCulture, out var linkByte);
|
||||
if (port < 15)
|
||||
{
|
||||
if (isByte) w.U8((byte)port).U8(linkByte);
|
||||
else { var a = Encoding.ASCII.GetBytes(link); w.U8((byte)(0x10 | port)).U8((byte)a.Length).Bytes(a).PadToEven(); }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isByte) w.U8(0x0F).U16((ushort)port).U8(linkByte);
|
||||
else { var a = Encoding.ASCII.GetBytes(link); w.U8(0x1F).U8((byte)a.Length).U16((ushort)port).Bytes(a).PadToEven(); }
|
||||
}
|
||||
}
|
||||
return w.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Splits a trailing purely numeric member ("Tag.5") into the tag name and bit index.</summary>
|
||||
public static (string Name, int? Bit) SplitBitSelector(string tagName)
|
||||
{
|
||||
var dot = tagName.LastIndexOf('.');
|
||||
if (dot > 0 && dot < tagName.Length - 1 && tagName[(dot + 1)..].All(char.IsAsciiDigit))
|
||||
return (tagName[..dot], int.Parse(tagName[(dot + 1)..], CultureInfo.InvariantCulture));
|
||||
return (tagName, null);
|
||||
}
|
||||
|
||||
public static byte[] EncodeSymbolic(string tagName)
|
||||
{
|
||||
if (SplitBitSelector(tagName).Bit is not null)
|
||||
throw new ArgumentException($"'{tagName}' ends in a bit selector; strip it with SplitBitSelector first.", nameof(tagName));
|
||||
var w = new CipWriter();
|
||||
foreach (var part in SplitMembers(tagName))
|
||||
{
|
||||
var bracket = part.IndexOf('[');
|
||||
var name = bracket < 0 ? part : part[..bracket];
|
||||
var a = Encoding.ASCII.GetBytes(name);
|
||||
w.U8(0x91).U8((byte)a.Length).Bytes(a).PadToEven();
|
||||
if (bracket >= 0)
|
||||
{
|
||||
foreach (var idxText in part[(bracket + 1)..part.IndexOf(']')].Split(',', StringSplitOptions.TrimEntries))
|
||||
{
|
||||
var idx = uint.Parse(idxText, CultureInfo.InvariantCulture);
|
||||
if (idx < 0x100) w.U8(0x28).U8((byte)idx);
|
||||
else if (idx < 0x10000) w.U8(0x29).U8(0).U16((ushort)idx);
|
||||
else w.U8(0x2A).U8(0).U32(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
return w.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Split on '.' outside brackets, keeping "Program:X" whole.</summary>
|
||||
private static IEnumerable<string> SplitMembers(string name)
|
||||
{
|
||||
var depth = 0; var start = 0;
|
||||
for (var i = 0; i < name.Length; i++)
|
||||
{
|
||||
if (name[i] == '[') depth++;
|
||||
else if (name[i] == ']') depth--;
|
||||
else if (name[i] == '.' && depth == 0) { yield return name[start..i]; start = i + 1; }
|
||||
}
|
||||
yield return name[start..];
|
||||
}
|
||||
|
||||
public static byte[] EncodeClassInstance(ushort cls, uint instance)
|
||||
{
|
||||
var w = new CipWriter();
|
||||
if (cls < 0x100) w.U8(0x20).U8((byte)cls); else w.U8(0x21).U8(0).U16(cls);
|
||||
if (instance < 0x100) w.U8(0x24).U8((byte)instance);
|
||||
else if (instance < 0x10000) w.U8(0x25).U8(0).U16((ushort)instance);
|
||||
else w.U8(0x26).U8(0).U32(instance);
|
||||
return w.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Prefixes the path with its size in 16-bit words.</summary>
|
||||
public static byte[] WithPathSize(byte[] segments)
|
||||
{
|
||||
var w = new CipWriter();
|
||||
w.U8((byte)(segments.Length / 2)).Bytes(segments);
|
||||
return w.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>Message Router (class 2, instance 1).</summary>
|
||||
public static readonly byte[] MessageRouter = [0x20, 0x02, 0x24, 0x01];
|
||||
|
||||
/// <summary>Connection Manager (class 6, instance 1).</summary>
|
||||
public static readonly byte[] ConnectionManager = [0x20, 0x06, 0x24, 0x01];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Logix;
|
||||
|
||||
public static class LogixTypeCode
|
||||
{
|
||||
public const ushort Bool = 0xC1, SInt = 0xC2, Int = 0xC3, DInt = 0xC4, LInt = 0xC5, USInt = 0xC6,
|
||||
UInt = 0xC7, UDInt = 0xC8, ULInt = 0xC9, Real = 0xCA, LReal = 0xCB, BoolArray = 0xD3, Struct = 0x02A0;
|
||||
|
||||
/// <summary>Byte width of an atomic type, or -1 when the code is not atomic (e.g. <see cref="Struct"/>).</summary>
|
||||
public static int ByteWidth(ushort code) => code switch
|
||||
{
|
||||
Bool or SInt or USInt => 1,
|
||||
Int or UInt => 2,
|
||||
DInt or UDInt or Real or BoolArray => 4,
|
||||
LInt or ULInt or LReal => 8,
|
||||
_ => -1,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user