Files
wwtools/mbproxy/README.md
Joseph Doherty e719dd51c1 mbproxy: replace status page with a live SignalR web dashboard
The single auto-refreshing zero-JS status page gave operators a 25-column
wall and no way to drill into one connection. This adds a Bootstrap fleet
dashboard (filterable/sortable KPI table) and a per-PLC detail page with a
real-time debug view of raw PLC-side BCD vs. decoded client-side values,
streamed live over a SignalR feed. The debug view is fed by an on-demand
per-tag value capture, armed only while a detail page is open. All assets
(Bootstrap, SignalR client, fonts) are embedded so the UI works unchanged
on firewalled networks; GET /status.json is untouched for scrapers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 10:41:02 -04:00

8.2 KiB
Raw Permalink Blame History

mbproxy

A .NET 10 background service — a Windows Service or a Linux systemd unit — 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. The proxy also offers an opt-in per-tag response cache (default OFF) for FC03/FC04 reads with bounded operator-configured staleness — see docs/Architecture/ResponseCache.md before enabling it.

32-bit BCD wire format is "two base-10000 digits in CDAB", not standard CDAB binary Int32. A 32-bit BCD tag at address A decodes as decimal = high * 10_000 + low where low is the register at A and high is the register at A+1. Each word independently must be 09999. Standard Modbus clients (NModbus, FluentModbus, Wonderware DAServer) that interpret CDAB as straight binary Int32 will silently corrupt any value > 9999 on writes and read garbage on reads. Configure your client to send/receive each register as a separate base-10000 BCD digit pair, not as a single binary Int32. Full details in docs/Features/BcdRewriting.md.

Hard constraints / prerequisites

  • Windows (10 / Server 2019+) or Linux (any systemd distro), 64-bit. Ships as a Windows Service (Application Event Log integration) or a systemd unit (syslog integration); builds single-file for win-x64 and linux-x64. macOS is not a deployment target — it runs only as a foreground console process.
  • 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 / root rights to install the service (install.ps1 requires elevation; install.sh requires root).
  • 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 (unit + simulator-backed E2E tests)
install/              Install/uninstall + publish scripts (PowerShell + shell), systemd unit, config templates
docs/                 Architecture, features, operations, reference, and testing docs

Resource index

Task Go to
Architecture entry point — listener topology, request flow, per-PLC isolation docs/Architecture/Overview.md
DL205/DL260 Modbus quirks (BCD, CDAB, octal V-memory, FC limits) docs/Reference/dl205.md
pymodbus simulator profile (register seeds for E2E tests) tests/sim/dl205.json
Agent-oriented coding guide (architecture bullets, device quirks, phase context) CLAUDE.md

Detailed documentation

The docs/ tree is organized by topic. Start with Architecture/Overview.md for the end-to-end picture; jump to the focused pages below when you need depth on one area.

Architecture

Features

Operations

Reference

Testing

  • Testing/Simulator.md — pymodbus DL205 fixture, skip policy, and the load-bearing pymodbus 3.13 framer quirk.

Build and run

Build (Debug, multi-file — fast for iteration):

dotnet build Mbproxy.slnx -c Debug

Publish (Release, single-file):

.\install\publish.ps1 -Clean                 # win-x64 (default)
.\install\publish.ps1 -Rid linux-x64 -Clean  # cross-publish for linux-x64

On a Linux build host, use the shell counterpart:

./install/publish.sh --clean                  # linux-x64 (default)

Each run produces both flavours under publish-out\:

Flavour Path (win-x64) Size Target prerequisite
Self-contained publish-out\self-contained\Mbproxy.exe ~100 MB None — bundles .NET 10 + ASP.NET Core runtime
Framework-dependent publish-out\framework-dependent\Mbproxy.exe ~1.6 MB .NET 10 + ASP.NET Core preinstalled

On linux-x64 the binary is Mbproxy (no extension) and ships the Linux config template. Pass -OutputDir/-o to publish elsewhere; omit -Clean/--clean to skip the wipe. The scripts wrap dotnet publish src/Mbproxy/Mbproxy.csproj -c Release -r <rid> [-p:SelfContained=false] — run that directly if you only need one flavour.

Run tests:

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):

cd src/Mbproxy
dotnet run --configuration Debug

Edit src/Mbproxy/appsettings.json to configure PLCs before running. The admin dashboard will be at http://localhost:8080/ by default — a live SignalR-backed fleet view; click any PLC row for its per-connection detail page and real-time BCD debug view.

Install

The install/ directory holds the publish, install, and uninstall scripts for both platforms.

Windows — elevated PowerShell:

.\install\publish.ps1 -Clean
.\install\install.ps1 -PublishOutput .\publish-out\self-contained -Start
# Config is placed at %ProgramData%\mbproxy\appsettings.json — edit it, then:
#   Restart-Service mbproxy
Invoke-WebRequest http://localhost:8080/ -UseBasicParsing

Linux — root / sudo on a systemd host:

./install/publish.sh --clean
sudo ./install/install.sh --publish-dir ./publish-out/self-contained
# Config is placed at /etc/mbproxy/appsettings.json — edit it, then:
#   sudo systemctl restart mbproxy
curl http://localhost:8080/

uninstall.ps1 / uninstall.sh reverse the install; both archive log files rather than deleting them. The systemd unit runs mbproxy as Type=exec under a dedicated mbproxy service account.

Maintenance

Documentation doctrine for this repo: ../DOCS-GUIDE.md.

  • This README routes to deep docs — it does not duplicate them.
  • Design decisions and rationale live in the docs/ tree (Architecture, Features, Operations, Reference, Testing).
  • When the service's public surface or task→tool mapping changes, update this README and the root ../CLAUDE.md index row.