Compare commits
1 Commits
phase-3-pr
...
phase-3-pr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9892a0253d |
@@ -0,0 +1,71 @@
|
|||||||
|
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 },
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@
|
|||||||
[1280, 1282],
|
[1280, 1282],
|
||||||
[1343, 1343],
|
[1343, 1343],
|
||||||
[1407, 1407],
|
[1407, 1407],
|
||||||
|
[1, 1],
|
||||||
[128, 128],
|
[128, 128],
|
||||||
[192, 192],
|
[192, 192],
|
||||||
[250, 250],
|
[250, 250],
|
||||||
@@ -88,6 +89,9 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"bits": [
|
"bits": [
|
||||||
|
{"_quirk": "X-input bank marker cell. X0 -> DI 0 conflicts with uint16 V0 at cell 0, so this marker covers X20 octal (= decimal 16 = DI 16 = cell 1 bit 0). X20=ON, X23 octal (DI 19 = cell 1 bit 3)=ON -> cell 1 value = 0b00001001 = 9.",
|
||||||
|
"addr": 1, "value": 9},
|
||||||
|
|
||||||
{"_quirk": "Y-output bank marker cell. pymodbus's simulator maps Modbus FC01/02/05 bit-addresses to cell index = bit_addr / 16; so Modbus coil 2048 lives at cell 128 bit 0. Y0=ON (bit 0), Y1=OFF (bit 1), Y2=ON (bit 2) -> value=0b00000101=5 proves DL260 mapping Y0 -> coil 2048.",
|
{"_quirk": "Y-output bank marker cell. pymodbus's simulator maps Modbus FC01/02/05 bit-addresses to cell index = bit_addr / 16; so Modbus coil 2048 lives at cell 128 bit 0. Y0=ON (bit 0), Y1=OFF (bit 1), Y2=ON (bit 2) -> value=0b00000101=5 proves DL260 mapping Y0 -> coil 2048.",
|
||||||
"addr": 128, "value": 5},
|
"addr": 128, "value": 5},
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user