From 463c5a43201889546aac7debae177e087d8b47c1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 18 Apr 2026 21:51:15 -0400 Subject: [PATCH] Phase 3 PR 48 -- DL205 CDAB word order for Float32 end-to-end test. The driver has supported ModbusByteOrder.WordSwap (CDAB) since PR 24 for all multi-register types -- the underlying word-swap code path was already there. PR 48 closes the loop with an integration test that validates it end-to-end against the dl205 pymodbus profile: HR[1056..1057] stores IEEE-754 1.5f with the low word at the lower address (0x0000 at HR[1056], 0x3FC0 at HR[1057]). Reading with WordSwap returns 1.5f; reading with BigEndian returns a tiny denormal (~5.74e-41) -- a silent "value is 0" bug that typically surfaces in the field only when an operator notices a setpoint readout stuck at 0 while the PLC display shows the real value. Test asserts both: WordSwap==1.5f AND BigEndian!=1.5f, proving the flag is not a no-op. No driver code changes -- the word-swap normalization at NormalizeWordOrder() has handled Float32/Int32/UInt32 correctly since PR 24 and the unit test suite already covers it (Int32_WordSwap_decodes_CDAB_layout + Float32 equivalent). This PR exists primarily to lock in the integration-level validation so future refactors of the codec don't silently break DL205/DL260 floats. 6/6 DL205 integration tests pass with MODBUS_SIM_PROFILE=dl205. --- .../DL205/DL205FloatCdabQuirkTests.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205FloatCdabQuirkTests.cs diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205FloatCdabQuirkTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205FloatCdabQuirkTests.cs new file mode 100644 index 0000000..0d04148 --- /dev/null +++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205FloatCdabQuirkTests.cs @@ -0,0 +1,64 @@ +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); + } +} -- 2.49.1