using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.DL205; /// /// Verifies DL205/DL260 CDAB word ordering for 32-bit floats against the /// dl205.json pymodbus profile. DirectLOGIC stores IEEE-754 singles with the low /// word at the lower register address (CDAB) rather than the high word (ABCD). Reading /// HR[1056..1057] with produces a tiny /// denormal (~5.74e-41) instead of the intended 1.5f — a silent "value is 0" bug in the /// field unless the caller opts into . /// [Collection(ModbusSimulatorCollection.Name)] [Trait("Category", "Integration")] [Trait("Device", "DL205")] public sealed class DL205FloatCdabQuirkTests(ModbusSimulatorFixture sim) { [Fact] public async Task DL205_Float32_CDAB_decodes_1_5f_from_HR1056() { if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason); if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "dl205", StringComparison.OrdinalIgnoreCase)) { Assert.Skip("MODBUS_SIM_PROFILE != dl205 — skipping (standard profile does not seed HR[1056..1057])."); } var options = new ModbusDriverOptions { Host = sim.Host, Port = sim.Port, UnitId = 1, Timeout = TimeSpan.FromSeconds(2), Tags = [ new ModbusTagDefinition("DL205_Float_CDAB", ModbusRegion.HoldingRegisters, Address: 1056, DataType: ModbusDataType.Float32, Writable: false, ByteOrder: ModbusByteOrder.WordSwap), // Control: same address, BigEndian — proves the default decode produces garbage. new ModbusTagDefinition("DL205_Float_ABCD", ModbusRegion.HoldingRegisters, Address: 1056, DataType: ModbusDataType.Float32, Writable: false, ByteOrder: ModbusByteOrder.BigEndian), ], Probe = new ModbusProbeOptions { Enabled = false }, }; await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-cdab"); await driver.InitializeAsync("{}", TestContext.Current.CancellationToken); var results = await driver.ReadAsync(["DL205_Float_CDAB", "DL205_Float_ABCD"], TestContext.Current.CancellationToken); results[0].StatusCode.ShouldBe(0u); results[0].Value.ShouldBe(1.5f, "DL205 Float32 with WordSwap (CDAB) must decode HR[1056..1057] as 1.5f"); // The BigEndian read of the same wire bytes should differ — not asserting the exact // denormal value (that couples the test to IEEE-754 bit math) but the two decodes MUST // disagree, otherwise the word-order flag is a no-op. results[1].StatusCode.ShouldBe(0u); results[1].Value.ShouldNotBe(1.5f); } }