Adds the mbproxy service end-to-end. Phases 00-08 implement the production-ready single-listener / 1:1-backend transparent Modbus TCP proxy with bidirectional BCD rewriting for the ~54-PLC DL205/DL260 fleet. Phase 9 replaces the connection layer with a single backend socket per PLC plus MBAP TxId rewriting, lifting the H2-ECOM100's 4-concurrent-client cap as an operational ceiling. Phase 9 additions of note: - PlcMultiplexer + UpstreamPipe + TxIdAllocator + CorrelationMap - InFlightRequest with IReadOnlyList<InterestedParty> (load-bearing for Phase 10 read coalescing — do not collapse to a single field) - Per-request watchdog: surfaces Modbus exception 0x0B to upstream on BackendRequestTimeoutMs, defending against lost responses, dead-PLC paths, and pymodbus 3.13.0's concurrent-multiplexed- request bug (its ServerRequestHandler.last_pdu state race) - Status DTO + HTML gain inFlight / maxInFlight / txIdWraps / disconnectCascades / queueDepth (Tier 1.6 in docs/kpi.md) Tests: 263 unit + 38 E2E. Multiplexer correctness under truly concurrent backend traffic is proved against a stub backend in PlcMultiplexerTests; MultiplexerE2ETests paces requests so pymodbus 3.13's single-PDU framer stays in known-good mode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
DL205 Modbus Simulator
Wraps the DL260/dl205.json pymodbus profile as a standalone launcher and as an xUnit managed lifecycle.
Manual launch
pwsh tests/sim/run-dl205-sim.ps1 -Port 5020
On first run the script creates a Python venv at tests/sim/.venv and installs:
pymodbus==3.13.0
aiohttp
(pymodbus 3.13.0 does not provide a [server] extra; the simulator is included in
the base package. aiohttp is required by the simulator's HTTP console.)
Re-runs detect the existing venv and skip provisioning (fast path, < 2 s to first packet).
Ctrl-C exits cleanly. The venv directory is gitignored.
Requirements
- Python 3.10+ on
PATH(tested with 3.13). The script also tries the Windowspylauncher. - Network access for first-run venv provisioning. Subsequent runs are fully offline.
Parameters
| Parameter | Default | Description |
|---|---|---|
-Profile |
../../DL260/dl205.json |
pymodbus JSON device profile |
-Port |
5020 |
TCP port the Modbus server binds |
xUnit integration
Test classes that need a live simulator declare:
[Collection(nameof(DL205SimulatorCollection))]
The DL205SimulatorFixture (in tests/Mbproxy.Tests/Sim/) spawns run-dl205-sim.ps1 via pwsh -NoProfile -File, polls for a TCP connection within 10 s, and exposes Host, Port, and LogTail. If Python is unavailable, SkipReason is populated and every test in the collection skips cleanly rather than failing.
Version pin
pymodbus[server]==3.13.0 — update this README and run-dl205-sim.ps1 together when re-pinning.