mbproxy/docs: retire superseded design/plan docs and dissolve DL260/

The standalone design.md, kpi.md, operations.md, and the docs/plan/
phase tree were point-in-time planning artefacts now superseded by the
topic-organized docs/ tree (Architecture/, Features/, Operations/,
Reference/, Testing/). The DL260/ folder mixed a device-reference doc, a
test fixture, a sample test, and a screenshot; its contents now live in
their natural homes (dl205.md + mbtcp_settings.JPG under docs/Reference/,
dl205.json next to its launcher in tests/sim/, sample test dropped).

All cross-references in the surviving docs, README, CLAUDE.md, the config
template, and source comments are repointed to the new locations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-15 07:37:18 -04:00
parent 0a603f94d0
commit 7466a46aa7
48 changed files with 67 additions and 3518 deletions
@@ -148,7 +148,7 @@ public sealed class BcdTagMapBuilderTests
/// <summary>
/// Same-address entries appearing in BOTH Global AND Add are the documented
/// width-override pattern (design.md "Hybrid tag resolution"). They must NOT
/// width-override pattern (docs/Features/BcdRewriting.md "Hybrid tag resolution"). They must NOT
/// be flagged as duplicates; Add wins.
/// </summary>
[Fact]
@@ -404,7 +404,7 @@ public sealed class BcdPduPipelineTests
/// <summary>
/// DL205/DL260 caps FC03/FC04 reads at qty=128 (above Modbus spec's 125; documented
/// in DL260/dl205.md). The proxy must NOT truncate the qty field — a request with
/// in docs/Reference/dl205.md). The proxy must NOT truncate the qty field — a request with
/// qty &gt; 128 at non-BCD addresses must pass through unchanged so the PLC's own
/// validator returns exception 03 to the client. This is the transparent-pass-through
/// contract for FCs and addresses the rewriter doesn't own.
@@ -172,7 +172,7 @@ public sealed class MultiplexerE2ETests
int proxyPort = PickFreePort();
// Configure three BCD addresses each width 16 for FC06 writes. The sim profile's
// writable HR range is [200..209] (see DL260/dl205.json's "write" list); reads
// writable HR range is [200..209] (see tests/sim/dl205.json's "write" list); reads
// outside that range succeed but writes return exception 02. We use 200/202/204.
var config = new Dictionary<string, string?>
{
@@ -207,7 +207,7 @@ public sealed class ReadCoalescingE2ETests
await Task.Delay(300, TestContext.Current.CancellationToken);
// Five different seeded addresses, sequential reads — none can coalesce.
// Selected from DL260/dl205.json's seeded ranges (200..209, 1024, 1040..1042).
// Selected from tests/sim/dl205.json's seeded ranges (200..209, 1024, 1040..1042).
ushort[] addrs = [200, 201, 202, 203, 204];
using (var client = new TcpClient())
{
@@ -6,7 +6,7 @@ namespace Mbproxy.Tests.Sim;
/// <summary>
/// End-to-end smoke tests that verify the pymodbus DL205 simulator is reachable and
/// serves the expected seeded register values from <c>DL260/dl205.json</c>.
/// serves the expected seeded register values from <c>tests/sim/dl205.json</c>.
/// </summary>
/// <remarks>
/// All three tests call <see cref="Assert.Skip"/> when
+2 -2
View File
@@ -1,6 +1,6 @@
# DL205 Modbus Simulator
Wraps the `DL260/dl205.json` pymodbus profile as a standalone launcher and as an xUnit managed lifecycle.
Wraps the `dl205.json` pymodbus profile as a standalone launcher and as an xUnit managed lifecycle.
## Manual launch
@@ -31,7 +31,7 @@ Ctrl-C exits cleanly. The venv directory is gitignored.
| Parameter | Default | Description |
|------------|--------------------------------|------------------------------------|
| `-Profile` | `../../DL260/dl205.json` | pymodbus JSON device profile |
| `-Profile` | `dl205.json` | pymodbus JSON device profile |
| `-Port` | `5020` | TCP port the Modbus server binds |
## xUnit integration
+113
View File
@@ -0,0 +1,113 @@
{
"_comment": "DL205.json — DirectLOGIC DL205/DL260 quirk simulator. Models docs/v2/dl205.md as concrete register values. NOTE: pymodbus rejects unknown keys at device-list / setup level; explanatory comments live at top-level _comment + in README + git. Inline _quirk keys WITHIN individual register entries are accepted by pymodbus 3.13.0 (it only validates addr / value / action / parameters per entry). Each quirky uint16 is a pre-computed raw 16-bit value; pymodbus serves it verbatim. shared blocks=true matches DL series memory model. write list mirrors each seeded block — pymodbus rejects sweeping write ranges that include undefined cells.",
"server_list": {
"srv": {
"comm": "tcp",
"host": "0.0.0.0",
"port": 5020,
"framer": "socket",
"device_id": 1
}
},
"device_list": {
"dev": {
"setup": {
"co size": 16384,
"di size": 8192,
"hr size": 16384,
"ir size": 1024,
"shared blocks": true,
"type exception": false,
"defaults": {
"value": {"bits": 0, "uint16": 0, "uint32": 0, "float32": 0.0, "string": " "},
"action": {"bits": null, "uint16": null, "uint32": null, "float32": null, "string": null}
}
},
"invalid": [],
"write": [
[0, 0],
[200, 209],
[1024, 1024],
[1040, 1042],
[1056, 1057],
[1072, 1073],
[1280, 1282],
[1343, 1343],
[1407, 1407],
[1, 1],
[128, 128],
[192, 192],
[250, 250],
[8448, 8448]
],
"uint16": [
{"_quirk": "V0 marker. HR[0]=0xCAFE proves register 0 is valid on DL205/DL260 (rejects-register-0 was a DL05/DL06 relative-mode artefact). 0xCAFE = 51966.",
"addr": 0, "value": 51966},
{"_quirk": "Scratch HR range 200..209 — mirrors the standard.json scratch range so the smoke test (DL205Profile.SmokeHoldingRegister=200) round-trips identically against either profile.",
"addr": 200, "value": 0},
{"addr": 201, "value": 0},
{"addr": 202, "value": 0},
{"addr": 203, "value": 0},
{"addr": 204, "value": 0},
{"addr": 205, "value": 0},
{"addr": 206, "value": 0},
{"addr": 207, "value": 0},
{"addr": 208, "value": 0},
{"addr": 209, "value": 0},
{"_quirk": "V2000 marker. V2000 octal = decimal 1024 = PDU 0x0400. Marker 0x2000 = 8192.",
"addr": 1024, "value": 8192},
{"_quirk": "V40400 marker. V40400 octal = decimal 8448 = PDU 0x2100 (NOT register 0). Marker 0x4040 = 16448.",
"addr": 8448, "value": 16448},
{"_quirk": "String 'Hello' first char in LOW byte. HR[0x410] = 'H'(0x48) lo + 'e'(0x65) hi = 0x6548 = 25928.",
"addr": 1040, "value": 25928},
{"_quirk": "String 'Hello' second char-pair: 'l'(0x6C) lo + 'l'(0x6C) hi = 0x6C6C = 27756.",
"addr": 1041, "value": 27756},
{"_quirk": "String 'Hello' third char-pair: 'o'(0x6F) lo + null(0x00) hi = 0x006F = 111.",
"addr": 1042, "value": 111},
{"_quirk": "Float32 1.5f in CDAB word order. IEEE 754 1.5 = 0x3FC00000. CDAB = low word first: HR[0x420]=0x0000, HR[0x421]=0x3FC0=16320.",
"addr": 1056, "value": 0},
{"_quirk": "Float32 1.5f CDAB high word.",
"addr": 1057, "value": 16320},
{"_quirk": "BCD register. Decimal 1234 stored as BCD nibbles 0x1234 = 4660. NOT binary 1234 (= 0x04D2).",
"addr": 1072, "value": 4660},
{"_quirk": "High word of a 32-bit BCD pair at 1072/1073 (CDAB order: 1072=low, 1073=high). Seeded 0 = high BCD digits 0000, making the 32-bit value 0000_1234 = decimal 1234. Also present in write[] so proxy write tests can round-trip the 32-bit BCD pair.",
"addr": 1073, "value": 0},
{"_quirk": "FC03 cap test marker — first cell of a 128-register span the FC03 cap test reads. Other cells in the span aren't seeded explicitly, so reads of HR[1283..1342] / 1344..1406 return the default 0; the seeded markers at 1280, 1281, 1282, 1343, 1407 prove the span boundaries.",
"addr": 1280, "value": 0},
{"addr": 1281, "value": 1},
{"addr": 1282, "value": 2},
{"addr": 1343, "value": 63},
{"addr": 1407, "value": 127}
],
"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},
{"_quirk": "C-relay bank marker cell. Modbus coil 3072 -> cell 192 bit 0. C0=ON (bit 0), C1=OFF (bit 1), C2=ON (bit 2) -> value=5 proves DL260 mapping C0 -> coil 3072.",
"addr": 192, "value": 5},
{"_quirk": "Scratch cell for coil 4000..4015 write round-trip tests. Cell 250 holds Modbus coils 4000-4015; all bits start at 0 and tests set specific bits via FC05.",
"addr": 250, "value": 0}
],
"uint32": [],
"float32": [],
"string": [],
"repeat": []
}
}
}
+4 -4
View File
@@ -9,7 +9,7 @@
port; the server process stays attached so Ctrl-C (or parent exit) kills it cleanly.
pymodbus version pin: 3.13.0
(Matches the profile comment in DL260/dl205.json. Record the version here AND in
(Matches the profile comment in dl205.json. Record the version here AND in
tests/sim/README.md so it is never lost across re-provisioning.)
API note: pymodbus 3.13.0 uses 'pymodbus.simulator' (not the legacy 'pymodbus.server
@@ -19,8 +19,8 @@
pymodbus.
.PARAMETER Profile
Path to the pymodbus JSON profile. Defaults to ../../DL260/dl205.json relative to
this script's directory (i.e. the checked-in DL205 quirk profile).
Path to the pymodbus JSON profile. Defaults to dl205.json in this script's
directory (i.e. the checked-in DL205 quirk profile).
.PARAMETER Port
TCP port for the Modbus server to listen on. Defaults to 5020.
@@ -33,7 +33,7 @@
#>
[CmdletBinding()]
param(
[string]$Profile = (Join-Path $PSScriptRoot '..\..\DL260\dl205.json'),
[string]$Profile = (Join-Path $PSScriptRoot 'dl205.json'),
[int]$Port = 5020
)