using Shouldly; using Xunit; namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.S7; /// /// Verifies the Siemens S7 big-endian (ABCD) word-order default for Float32 and /// Int32 against the s7_1500.json pymodbus profile. S7's native CPU types are /// big-endian end-to-end, so MB_SERVER places the high word at the lower register /// address — opposite of DL260's CDAB. The driver's S7-family tag config must /// therefore default to ; selecting /// against an S7 would decode garbage. /// [Collection(ModbusSimulatorCollection.Name)] [Trait("Category", "Integration")] [Trait("Device", "S7")] public sealed class S7_ByteOrderTests(ModbusSimulatorFixture sim) { [Fact] public async Task S7_Float32_ABCD_decodes_1_5f_from_HR100() { if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason); if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "s7_1500", StringComparison.OrdinalIgnoreCase)) { Assert.Skip("MODBUS_SIM_PROFILE != s7_1500 — skipping (s7_1500 profile is the only one seeding HR[100..101] ABCD)."); } var options = new ModbusDriverOptions { Host = sim.Host, Port = sim.Port, UnitId = 1, Timeout = TimeSpan.FromSeconds(2), Tags = [ new ModbusTagDefinition("S7_Float_ABCD", ModbusRegion.HoldingRegisters, Address: 100, DataType: ModbusDataType.Float32, Writable: false, ByteOrder: ModbusByteOrder.BigEndian), // Control: same address with WordSwap should decode garbage — proves the // two code paths diverge on S7 wire bytes. new ModbusTagDefinition("S7_Float_CDAB_control", ModbusRegion.HoldingRegisters, Address: 100, DataType: ModbusDataType.Float32, Writable: false, ByteOrder: ModbusByteOrder.WordSwap), ], Probe = new ModbusProbeOptions { Enabled = false }, }; await using var driver = new ModbusDriver(options, driverInstanceId: "s7-float-abcd"); await driver.InitializeAsync("{}", TestContext.Current.CancellationToken); var results = await driver.ReadAsync( ["S7_Float_ABCD", "S7_Float_CDAB_control"], TestContext.Current.CancellationToken); results[0].StatusCode.ShouldBe(0u); results[0].Value.ShouldBe(1.5f, "S7 MB_SERVER stores Float32 in ABCD word order; BigEndian decode returns 1.5f"); results[1].StatusCode.ShouldBe(0u); results[1].Value.ShouldNotBe(1.5f, "applying CDAB swap to S7 ABCD bytes must produce a different value — confirms the flag is not a no-op and S7 profile default must be BigEndian"); } [Fact] public async Task S7_Int32_ABCD_decodes_0x12345678_from_HR300() { if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason); if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "s7_1500", StringComparison.OrdinalIgnoreCase)) { Assert.Skip("MODBUS_SIM_PROFILE != s7_1500 — skipping."); } var options = new ModbusDriverOptions { Host = sim.Host, Port = sim.Port, UnitId = 1, Timeout = TimeSpan.FromSeconds(2), Tags = [ new ModbusTagDefinition("S7_Int32_ABCD", ModbusRegion.HoldingRegisters, Address: 300, DataType: ModbusDataType.Int32, Writable: false, ByteOrder: ModbusByteOrder.BigEndian), ], Probe = new ModbusProbeOptions { Enabled = false }, }; await using var driver = new ModbusDriver(options, driverInstanceId: "s7-int-abcd"); await driver.InitializeAsync("{}", TestContext.Current.CancellationToken); var results = await driver.ReadAsync(["S7_Int32_ABCD"], TestContext.Current.CancellationToken); results[0].StatusCode.ShouldBe(0u); results[0].Value.ShouldBe(0x12345678, "S7 Int32 stored as HR[300]=0x1234, HR[301]=0x5678 with ABCD order decodes to 0x12345678 — DL260 would store the reverse order"); } [Fact] public async Task S7_DB1_fingerprint_marker_at_HR0_reads_0xABCD() { if (sim.SkipReason is not null) Assert.Skip(sim.SkipReason); if (!string.Equals(Environment.GetEnvironmentVariable("MODBUS_SIM_PROFILE"), "s7_1500", StringComparison.OrdinalIgnoreCase)) { Assert.Skip("MODBUS_SIM_PROFILE != s7_1500 — skipping."); } // Real-world MB_SERVER deployments typically reserve DB1.DBW0 as a fingerprint so // clients can verify they're pointing at the right DB (protects against typos in // the MB_SERVER.MB_HOLD_REG.DB_number parameter). 0xABCD is the convention. var options = new ModbusDriverOptions { Host = sim.Host, Port = sim.Port, UnitId = 1, Timeout = TimeSpan.FromSeconds(2), Tags = [ new ModbusTagDefinition("S7_Fingerprint", ModbusRegion.HoldingRegisters, Address: 0, DataType: ModbusDataType.UInt16, Writable: false), ], Probe = new ModbusProbeOptions { Enabled = false }, }; await using var driver = new ModbusDriver(options, driverInstanceId: "s7-fingerprint"); await driver.InitializeAsync("{}", TestContext.Current.CancellationToken); var results = await driver.ReadAsync(["S7_Fingerprint"], TestContext.Current.CancellationToken); results[0].StatusCode.ShouldBe(0u); results[0].Value.ShouldBe((ushort)0xABCD); } }