Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests/Logix/LogixServicesTests.cs
T

119 lines
4.9 KiB
C#

using System.Text;
using System.Buffers.Binary;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Cip;
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Logix;
using ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests.Captures;
namespace ZB.MOM.WW.OtOpcUa.Driver.Ab.Transport.Tests.Logix;
// CAPTURE DECISION: libplctag sends Read Tag FRAGMENTED (0x52, offset 0) for plain reads, connected,
// with no MSP and no Unconnected Send wrapper. The brief's "ReadTag 0x4C" comparison is therefore made
// against ReadTagFragmented(path, 1, 0). Replies are service 0xD2 (0x52|0x80).
public sealed class LogixServicesTests
{
private static byte[] Int32Le(int v) { var a = new byte[4]; BinaryPrimitives.WriteInt32LittleEndian(a, v); return a; }
[Fact]
public void Error_read_reply_surfaces_status_from_capture() =>
Should.Throw<AbTransportException>(() => LogixServices.ParseReadReply(Rep("logix-read-unknown-tag.hex")))
.Status.ShouldBe(AbStatus.NotFound);
private static byte[] Req(string file, int i = 0) =>
CaptureFile.CipPayload(CaptureFile.Load(file).Where(f => f.FromClient).ElementAt(i));
private static CipReply Rep(string file, int i = 0) =>
CipReply.Parse(CaptureFile.CipPayload(CaptureFile.Load(file).Where(f => !f.FromClient).ElementAt(i)));
[Fact]
public void Read_request_matches_capture() =>
LogixServices.ReadTagFragmented(CipPath.EncodeSymbolic("TestDINT"), 1, 0).ShouldBe(Req("logix-read-dint.hex"));
[Fact]
public void Read_array_request_matches_capture() =>
LogixServices.ReadTagFragmented(CipPath.EncodeSymbolic("TestArray"), 16, 0).ShouldBe(Req("logix-read-dint-array16.hex"));
[Fact]
public void Read_dint_value()
{
var (type, handle, data) = LogixServices.ParseReadReply(Rep("logix-read-dint.hex"));
type.ShouldBe(LogixTypeCode.DInt);
handle.ShouldBeNull();
data.Length.ShouldBe(4);
BinaryPrimitives.ReadInt32LittleEndian(data.Span).ShouldBe(123456);
}
[Fact]
public void Read_array_values()
{
var (type, _, data) = LogixServices.ParseReadReply(Rep("logix-read-dint-array16.hex"));
type.ShouldBe(LogixTypeCode.DInt);
data.Length.ShouldBe(64);
for (var i = 0; i < 16; i++)
BinaryPrimitives.ReadUInt32LittleEndian(data.Span[(i * 4)..]).ShouldBe((uint)(((i + 1) << 24) | (i + 1)));
}
[Fact]
public void Read_bool_value()
{
var (type, _, data) = LogixServices.ParseReadReply(Rep("logix-read-bool.hex"));
type.ShouldBe(LogixTypeCode.Bool);
data.Length.ShouldBe(1);
data.Span[0].ShouldBe((byte)1);
}
// ab_server types STRING as atomic D0 00 (a real Logix STRING is A0 02 + handle): simulator behaviour.
[Fact]
public void Read_string_value_from_sim()
{
var (type, handle, data) = LogixServices.ParseReadReply(Rep("logix-read-string.hex"));
type.ShouldBe((ushort)0x00D0);
handle.ShouldBeNull();
data.Length.ShouldBe(88);
BinaryPrimitives.ReadInt32LittleEndian(data.Span).ShouldBe(7);
Encoding.ASCII.GetString(data.Span.Slice(4, 7)).ShouldBe("HelloAB");
}
[Fact]
public void Read_unknown_tag_reply_status()
{
var r = Rep("logix-read-unknown-tag.hex");
r.GeneralStatus.ShouldBe((byte)0x04);
r.Status.ShouldBe(AbStatus.NotFound);
}
[Fact]
public void Write_request_matches_capture() =>
LogixServices.WriteTag(CipPath.EncodeSymbolic("TestDINT"), LogixTypeCode.DInt, null, 1, Int32Le(123456))
.ShouldBe(Req("logix-write-dint.hex", 1));
[Fact]
public void Write_reply_is_ok() => Rep("logix-write-dint.hex", 1).GeneralStatus.ShouldBe((byte)0);
[Fact]
public void Read_tag_plain_layout() =>
Convert.ToHexString(LogixServices.ReadTag(CipPath.EncodeSymbolic("Abc"), 3)).ShouldBe("4C03" + "910341626300" + "0300");
[Fact]
public void Fragmented_write_layout() =>
Convert.ToHexString(LogixServices.WriteTagFragmented(CipPath.EncodeSymbolic("Abc"), LogixTypeCode.DInt, null, 2, 8, [1, 2, 3, 4]))
.ShouldBe("5303" + "910341626300" + "C400" + "0200" + "08000000" + "01020304");
[Fact]
public void Struct_write_includes_handle() =>
Convert.ToHexString(LogixServices.WriteTag(CipPath.EncodeSymbolic("Abc"), LogixTypeCode.Struct, 0x0FCE, 1, [9]))
.ShouldBe("4D03" + "910341626300" + "A002" + "CE0F" + "0100" + "09");
[Fact]
public void Struct_write_without_handle_throws() =>
Should.Throw<ArgumentException>(() => LogixServices.WriteTag(CipPath.EncodeSymbolic("Abc"), LogixTypeCode.Struct, null, 1, [9]));
[Fact]
public void Read_reply_with_struct_handle()
{
var reply = CipReply.Parse(Convert.FromHexString("CC000000" + "A002" + "CE0F" + "05000000" + "48656C6C6F"));
var (type, handle, data) = LogixServices.ParseReadReply(reply);
type.ShouldBe((ushort)0x02A0); handle.ShouldBe((ushort)0x0FCE); data.Length.ShouldBe(9);
}
}