using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.AbCip; namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests.Emulate; /// /// Golden-box-tier UDT read tests against Rockwell Studio 5000 Logix Emulate. /// Promotes the whole-UDT-read optimization (task #194) from unit-only coverage /// (golden Template Object byte buffers + ) /// to end-to-end wire-level coverage — Emulate's firmware speaks the same CIP /// Template Object responses real hardware does, so the member-offset math + the /// AbCipUdtReadPlanner grouping get validated against production semantics. /// /// /// Required Emulate project state (see LogixProject/README.md /// for the L5X export that seeds this; ship the project once Emulate is on the /// integration host): /// /// UDT Motor_UDT with members Speed : DINT, Torque : REAL, /// Status : DINT — the member set /// uses as its declared-layout golden reference. /// Controller-scope tag Motor1 : Motor_UDT with seed values /// Speed=1800, Torque=42.5f, Status=0x0001. /// /// Runs only when AB_SERVER_PROFILE=emulate. With ab_server /// (the default), skips cleanly — ab_server lacks UDT / Template Object emulation /// so this wire-level test couldn't pass against it regardless. /// [Collection("AbServerEmulate")] [Trait("Category", "Integration")] [Trait("Tier", "Emulate")] public sealed class AbCipEmulateUdtReadTests { [AbServerFact] public async Task WholeUdt_read_decodes_each_member_at_its_Template_Object_offset() { AbServerProfileGate.SkipUnless(AbServerProfileGate.Emulate); var endpoint = Environment.GetEnvironmentVariable("AB_SERVER_ENDPOINT") ?? throw new InvalidOperationException( "AB_SERVER_ENDPOINT must be set to the Logix Emulate instance " + "(e.g. '10.0.0.42:44818') when AB_SERVER_PROFILE=emulate."); var options = new AbCipDriverOptions { Devices = [new AbCipDeviceOptions($"ab://{endpoint}/1,0")], Tags = [ new AbCipTagDefinition( Name: "Motor1", DeviceHostAddress: $"ab://{endpoint}/1,0", TagPath: "Motor1", DataType: AbCipDataType.Structure, Members: [ new AbCipStructureMember("Speed", AbCipDataType.DInt), new AbCipStructureMember("Torque", AbCipDataType.Real), new AbCipStructureMember("Status", AbCipDataType.DInt), ]), ], Timeout = TimeSpan.FromSeconds(5), }; await using var drv = new AbCipDriver(options, driverInstanceId: "emulate-udt-smoke"); await drv.InitializeAsync("{}", TestContext.Current.CancellationToken); // Whole-UDT read optimization from task #194: one libplctag read on the // parent tag, three decodes from the buffer at member offsets. Asserts // Emulate's Template Object response matches what AbCipUdtMemberLayout // computes from the declared member set. var snapshots = await drv.ReadAsync( ["Motor1.Speed", "Motor1.Torque", "Motor1.Status"], TestContext.Current.CancellationToken); snapshots.Count.ShouldBe(3); foreach (var s in snapshots) s.StatusCode.ShouldBe(AbCipStatusMapper.Good); Convert.ToInt32(snapshots[0].Value).ShouldBe(1800); Convert.ToSingle(snapshots[1].Value).ShouldBe(42.5f, tolerance: 0.001f); Convert.ToInt32(snapshots[2].Value).ShouldBe(0x0001); } }