diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205XInputTests.cs b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205XInputTests.cs
new file mode 100644
index 0000000..5073113
--- /dev/null
+++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/DL205/DL205XInputTests.cs
@@ -0,0 +1,71 @@
+using Shouldly;
+using Xunit;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests.DL205;
+
+///
+/// Verifies the DL260 X-input discrete-input mapping against the dl205.json
+/// 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.
+///
+///
+/// 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.
+///
+[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 tags)
+ => new()
+ {
+ Host = sim.Host,
+ Port = sim.Port,
+ UnitId = 1,
+ Timeout = TimeSpan.FromSeconds(2),
+ Tags = tags,
+ Probe = new ModbusProbeOptions { Enabled = false },
+ };
+}
diff --git a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/dl205.json b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/dl205.json
index 0f01e8c..f39abd2 100644
--- a/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/dl205.json
+++ b/tests/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/Pymodbus/dl205.json
@@ -36,6 +36,7 @@
[1280, 1282],
[1343, 1343],
[1407, 1407],
+ [1, 1],
[128, 128],
[192, 192],
[250, 250],
@@ -88,6 +89,9 @@
],
"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.",
"addr": 128, "value": 5},