# mbproxy A .NET 10 Windows Service that sits inline as a Modbus TCP proxy in front of a fleet of AutomationDirect DirectLOGIC DL205/DL260 controllers, rewriting BCD-encoded registers bidirectionally so upstream clients can read and write them as plain integers. Since Phase 11, the proxy also offers an opt-in per-tag response cache (default OFF) for FC03/FC04 reads with bounded operator-configured staleness — see [`docs/design.md`](docs/design.md) → "Response cache (Phase 11)" before enabling it. ## Hard constraints / prerequisites - **Windows 10 / Server 2019 or later, 64-bit.** No Linux or Docker support — the service uses `Microsoft.Extensions.Hosting.WindowsServices` and the Windows Event Log. - **Modbus TCP backends reachable** from the proxy host on port 502 (or the port configured per PLC). The H2-ECOM100 module caps simultaneous connections at **4 per PLC** — a fifth upstream client will fail to connect. - **Admin rights** to install the service (`install.ps1` requires elevation). - **No COM dependency** — this is a pure .NET 10 socket-level proxy (unlike the `.NET Framework 4.8 / x86` siblings in this repo). - **Python 3.10+** on the test machine to run the pymodbus-backed E2E simulator (not needed to run the service in production). ## Layout ``` src/Mbproxy/ Main C# project (net10.0, Microsoft.NET.Sdk.Worker) tests/Mbproxy.Tests/ xUnit v3 test project (314 unit + 48 E2E tests) install/ PowerShell install/uninstall scripts and config template docs/ Design document, phase plans, and operations runbook DL260/ DL205/DL260 reference material and pymodbus simulator profile ``` ## Resource index | Task | Go to | |---|---| | Full architecture, schema, log events, status counters, test strategy | [`docs/design.md`](docs/design.md) | | Phase-by-phase implementation plan | [`docs/plan/README.md`](docs/plan/README.md) | | Install, upgrade, config, logs, troubleshooting | [`docs/operations.md`](docs/operations.md) | | DL205/DL260 Modbus quirks (BCD, CDAB, octal V-memory, FC limits) | [`DL260/dl205.md`](DL260/dl205.md) | | pymodbus simulator profile (register seeds for E2E tests) | [`DL260/dl205.json`](DL260/dl205.json) | | Agent-oriented coding guide (architecture bullets, device quirks, phase context) | [`CLAUDE.md`](CLAUDE.md) | ## Build and run **Build (Debug, multi-file — fast for iteration):** ```powershell dotnet build Mbproxy.slnx -c Debug ``` **Publish (Release, single-file self-contained, win-x64):** ```powershell dotnet publish src/Mbproxy/Mbproxy.csproj -c Release -r win-x64 --self-contained true -o C:\build\mbproxy-publish ``` The published output is a single `Mbproxy.exe` (~100 MB). The self-contained publish bundles the full .NET 10 + ASP.NET Core runtime. No .NET installation is required on the target machine. **Run tests:** ```powershell dotnet test Mbproxy.slnx -c Debug # all tests dotnet test Mbproxy.slnx -c Debug --filter Category=Unit # unit tests only (no Python required) dotnet test Mbproxy.slnx -c Debug --filter Category=E2E # E2E tests (require Python + pymodbus) ``` **Run interactively (without installing as a service):** ```powershell cd src/Mbproxy dotnet run --configuration Debug ``` Edit `src/Mbproxy/appsettings.json` to configure PLCs before running. The admin status page will be at `http://localhost:8080/` by default. ## Install Full detail is in [`docs/operations.md`](docs/operations.md). Quick path: ```powershell # 1. Publish dotnet publish src/Mbproxy/Mbproxy.csproj -c Release -r win-x64 --self-contained true -o C:\build\mbproxy-publish # 2. Install (elevated PowerShell) .\install\install.ps1 -PublishOutput C:\build\mbproxy-publish -Start # 3. Edit the config that was placed at %ProgramData%\mbproxy\appsettings.json # 4. Verify Invoke-WebRequest http://localhost:8080/ -UseBasicParsing ``` ## Maintenance Documentation doctrine for this repo: [`../DOCS-GUIDE.md`](../DOCS-GUIDE.md). - This README routes to deep docs — it does not duplicate them. - Design decisions: [`docs/design.md`](docs/design.md) is the source of truth. - When the service's public surface or task→tool mapping changes, update this README and the root [`../CLAUDE.md`](../CLAUDE.md) index row.