72 lines
3.3 KiB
C#
72 lines
3.3 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.DL205;
|
|
|
|
/// <summary>
|
|
/// Verifies the DL260 X-input discrete-input mapping against the <c>dl205.json</c>
|
|
/// pymodbus profile. X-inputs are FC02 discrete-input-only (Modbus doesn't allow writes
|
|
/// to discrete inputs), and the DirectLOGIC convention is X0 → DI 0 with octal offsets
|
|
/// for subsequent addresses. The sim seeds X20 octal (= DI 16) = ON so the test can
|
|
/// prove the helper routes through to the right cell.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// X0 / X1 / …X17 octal all share cell 0 (DI 0-15 → cell 0 bits 0-15) which conflicts
|
|
/// with the V0 uint16 marker; we can't seed both types at cell 0 under shared-blocks
|
|
/// semantics. So the test uses X20 octal (first address beyond the cell-0 boundary) which
|
|
/// lands cleanly at cell 1 bit 0 and leaves the V0 register-zero quirk intact.
|
|
/// </remarks>
|
|
[Collection(ModbusSimulatorCollection.Name)]
|
|
[Trait("Category", "Integration")]
|
|
[Trait("Device", "DL205")]
|
|
public sealed class DL205XInputTests(ModbusSimulatorFixture sim)
|
|
{
|
|
[Fact]
|
|
public async Task DL260_X20_octal_maps_to_DiscreteInput_16_and_reads_ON()
|
|
{
|
|
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.");
|
|
}
|
|
|
|
// X20 octal = decimal 16 = DI 16 per the DL260 convention (X-inputs start at DI 0).
|
|
var di = DirectLogicAddress.XInputToDiscrete("X20");
|
|
di.ShouldBe((ushort)16);
|
|
|
|
var options = BuildOptions(sim, [
|
|
new ModbusTagDefinition("DL260_X20",
|
|
ModbusRegion.DiscreteInputs, Address: di,
|
|
DataType: ModbusDataType.Bool, Writable: false),
|
|
// Unpopulated-X control: pymodbus returns 0 (not exception) for any bit in the
|
|
// configured DI range that wasn't explicitly seeded — per docs/v2/dl205.md
|
|
// "Reading a non-populated X input ... returns zero, not an exception".
|
|
new ModbusTagDefinition("DL260_X21_off",
|
|
ModbusRegion.DiscreteInputs, Address: DirectLogicAddress.XInputToDiscrete("X21"),
|
|
DataType: ModbusDataType.Bool, Writable: false),
|
|
]);
|
|
await using var driver = new ModbusDriver(options, driverInstanceId: "dl205-xinput");
|
|
await driver.InitializeAsync("{}", TestContext.Current.CancellationToken);
|
|
|
|
var results = await driver.ReadAsync(["DL260_X20", "DL260_X21_off"], TestContext.Current.CancellationToken);
|
|
|
|
results[0].StatusCode.ShouldBe(0u);
|
|
results[0].Value.ShouldBe(true, "dl205.json seeds cell 1 bit 0 (X20 octal = DI 16) = ON");
|
|
|
|
results[1].StatusCode.ShouldBe(0u, "unpopulated X inputs must read cleanly — DL260 does NOT raise an exception");
|
|
results[1].Value.ShouldBe(false);
|
|
}
|
|
|
|
private static ModbusDriverOptions BuildOptions(ModbusSimulatorFixture sim, IReadOnlyList<ModbusTagDefinition> tags)
|
|
=> new()
|
|
{
|
|
Host = sim.Host,
|
|
Port = sim.Port,
|
|
UnitId = 1,
|
|
Timeout = TimeSpan.FromSeconds(2),
|
|
Tags = tags,
|
|
Probe = new ModbusProbeOptions { Enabled = false },
|
|
};
|
|
}
|