Files
lmxopcua/docs/research/drivers/README.md
T
Joseph Doherty 8fc147d8d4
v2-ci / build (push) Successful in 3m14s
v2-ci / unit-tests (push) Failing after 9m13s
docs: driver-expansion program — 8 research reports + 7 design docs, parallel-reviewed
Adds the driver-expansion program design (umbrella: universal Discover-backed
browser + MTConnect, MQTT/Sparkplug B, BACnet/IP, SQL poll, Omron, Modbus RTU;
MELSEC deferred) plus the per-driver research reports.

All docs went through a 7-agent parallel review against the codebase before
this commit. Highlights fixed in review:

- universal browser: FOCAS FixedTree fills post-connect -> UntilStable settle
  + FixedTree.Enabled patch; MQTT reconciled to bespoke (was contradicting the
  program doc's SupportsOnlineDiscovery=false verdict)
- modbus-rtu: SerialPort.ReadTimeout doesn't bound async BaseStream reads ->
  linked-CTS per-op deadline (R2-01 class); BCL enum reuse would leak
  System.IO.Ports into Contracts
- bacnet: DiscoveryRediscoverPolicy enum name; UDP 47808 contention; live
  suite rewritten around unicast Who-Is + BBMD (broadcast doesn't cross VMs)
- sql-poll: real tier registration via DriverFactoryRegistry.Register;
  blackhole gate must not docker-pause the shared central SQL Server
- mqtt: Sparkplug v3.0 STATE topic form; first-in-repo proto codegen noted
- omron: host hardcodes isIdempotent:false today (retry seam unshipped);
  v1 scopes UDTs to dotted-leaf access
- mtconnect: SecurityClassification.ViewOnly; factory ParseEnum<T> pattern
- program doc: both valid enum-serialization patterns; IRediscoverable is
  change-signal-gated; RTU P2 adds System.IO.Ports; label is host-side
2026-07-15 16:40:36 -04:00

152 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Next-driver research — index & consolidated roadmap
> **Overall program design:** [`docs/plans/2026-07-15-driver-expansion-program-design.md`](../../plans/2026-07-15-driver-expansion-program-design.md)
> is the authoritative umbrella — shared architecture, cross-cutting rules, the browse-tier
> decision, the dependency graph, and the wave build order. Start there; this file is the
> research/design index it points back to.
Research sweep run 2026-07-15 (8 parallel agents). Each per-driver report covers
protocol + .NET library options (with license analysis), capability mapping to the
Equipment-kind driver seams, `TagConfig` JSON shape, a **browseability verdict +
`IDriverBrowser`/`IBrowseSession` design where browsable**, test-fixture strategy,
and effort/phasing. This index synthesizes them.
Scope decided with the user (2026-07-15): 7 new drivers + a browse-support audit of
the existing driver fleet. See `[[project_next_driver_roadmap]]` memory.
## Reports
| Report | Driver |
|---|---|
| [`mtconnect-agent.md`](mtconnect-agent.md) | MTConnect (Agent-first) |
| [`mqtt-sparkplug.md`](mqtt-sparkplug.md) | MQTT / Sparkplug B |
| [`melsec-slmp.md`](melsec-slmp.md) | Mitsubishi MELSEC SLMP / MC Protocol |
| [`omron.md`](omron.md) | Omron (CIP + FINS) |
| [`bacnet-ip.md`](bacnet-ip.md) | BACnet/IP |
| [`sql-poll.md`](sql-poll.md) | SQL poll |
| [`modbus-rtu.md`](modbus-rtu.md) | Modbus RTU (extend existing Modbus) |
| [`00-existing-driver-browse-audit.md`](00-existing-driver-browse-audit.md) | Browse audit of the 6 shipped browser-less drivers |
## Design docs (executable — built from the reports 2026-07-15)
Each non-deferred driver now has a build-ready design under `docs/plans/`:
| Driver | Design doc | Browse integration |
|---|---|---|
| Universal browser (Wave 0) | [`2026-07-15-universal-discovery-browser-design.md`](../../plans/2026-07-15-universal-discovery-browser-design.md) | — (the mechanism itself) |
| MTConnect Agent | [`2026-07-15-mtconnect-driver-design.md`](../../plans/2026-07-15-mtconnect-driver-design.md) | **Universal** (`SupportsOnlineDiscovery`) |
| BACnet/IP | [`2026-07-15-bacnet-ip-driver-design.md`](../../plans/2026-07-15-bacnet-ip-driver-design.md) | **Universal** (small sites); bespoke-lazy P-later |
| MQTT/Sparkplug B | [`2026-07-15-mqtt-sparkplug-driver-design.md`](../../plans/2026-07-15-mqtt-sparkplug-driver-design.md) | **Bespoke** observation browser |
| SQL poll | [`2026-07-15-sql-poll-driver-design.md`](../../plans/2026-07-15-sql-poll-driver-design.md) | **Bespoke** schema browser |
| Omron | [`2026-07-15-omron-driver-design.md`](../../plans/2026-07-15-omron-driver-design.md) | None — offline Sysmac tag-export import |
| Modbus RTU | [`2026-07-15-modbus-rtu-driver-design.md`](../../plans/2026-07-15-modbus-rtu-driver-design.md) | None (flat registers) — extends existing Modbus |
Browse split confirmed by the designs: **universal** covers MTConnect + BACnet (from `DiscoverAsync`);
**bespoke** required for MQTT (passive observation window) and SQL (schema walk ≠ discovery);
**Omron/Modbus-RTU** don't browse. All designs consume the universal browser's P1 addition of
`ITagDiscovery.SupportsOnlineDiscovery` — build the universal browser first.
## New drivers — at a glance
| Driver | Library (license) | Read/Write | Browsable | Write in v1 | Effort |
|---|---|---|---|---|---|
| **MTConnect Agent** | TrakHound MTConnect.NET (MIT) | Read-only | **Yes**`/probe` device model | No (telemetry std) | SM (~11.5 wk) |
| **MQTT / Sparkplug B** | MQTTnet v5 (MIT) + SparkplugNet (MIT, net10 risk) or hand-rolled Tahu | Read + subscribe | **Yes** — birth certs / topic tree (passive, time-bounded) | No | M |
| **MELSEC SLMP** _(deferred)_ | Hand-roll 3E-binary framer (HslCommunication is a commercial-license blocker) | Read/Write | No — flat device memory | Yes | M (addressing) |
| **Omron** | libplctag.NET (MPL-2.0) for CIP + hand-rolled FINS | Read/Write | CIP conditional (net-new) / FINS no | Yes | ML (no free sim) |
| **BACnet/IP** | System.IO.BACnet / ela-compil (MIT, net10 TFM) | Read + COV subscribe | **Yes** — Who-Is + object-list | No (write P3) | ML |
| **SQL poll** | Microsoft.Data.SqlClient (already in-repo) | Read-only | **Yes** — INFORMATION_SCHEMA | No (write later) | SM |
| **Modbus RTU** | Extend existing Modbus (new `IModbusTransport`) | Read/Write | No — flat registers | Yes | **S (lowest)** |
Notes:
- **All fit the existing all-.NET-10 Equipment-kind model** — no COM gateway.
- **MELSEC**: `HslCommunication` is a hard commercial-license blocker; the clean-license
alternatives (McpX/MIT, McProtocol/LGPL) are too immature → hand-roll, patterned on S7.
Repo already has `MelsecAddress`/`MelsecFamily` (from the Modbus-path work) to port.
- **Omron CIP** reuses ~7080% of AbCip's libplctag body, **but** libplctag's `@tags`
walker returns `ErrorUnsupported` on Omron NJ/NX — CIP browse is net-new Omron work
gated on the Sysmac "Network Publish" attribute, not free reuse.
- **MQTT/Sparkplug** & **BACnet** discovery is **passive/time-bounded** (listen/broadcast
over a window), unlike a synchronous OPC UA browse — the browse session handles this
with an observation window (+ Sparkplug rebirth NCMD to force near-synchronous enum).
- Every new driver needs a typed tag editor (`TagConfigEditorMap`+`TagConfigValidator`)
and must apply `JsonStringEnumConverter` on enum config fields (the known
`[[project_driver_enum_serialization_bug]]` trap).
## Existing-driver browse audit — verdicts
| Driver | Browsable? | Mechanism | Action |
|---|---|---|---|
| **AbCip** (ControlLogix EtherNet/IP) | **Yes** | CIP Symbol Object 0x6B + Template 0x6C — decode stack already ships | **Add browser** (~23 d) |
| **TwinCAT** (Beckhoff ADS) | **Yes** | ADS symbol upload (`SymbolLoaderFactory`) — enumerator already ships | **Add browser** (~34 d) |
| **FOCAS** (Fanuc CNC) | Borderline | Curated, not free-form (axes/spindles + macro/param/PMC ranges) | Curated browser, **deferred** (~35 d) |
| **AbLegacy** (PCCC) | No | Numeric addresses (`N7:0`), no symbolic catalog | Skip (documented) |
| **S7** (S7comm) | No | Symbols live in the TIA project, not the CPU | Skip (use OpcUaClient for optimized DBs) |
| **Modbus** (TCP/RTU) | No | Flat register space, no discovery | Skip (documented) |
**Key finding:** AbCip & TwinCAT already contain complete, unit-tested symbol-enumeration
logic buried in the runtime driver's `DiscoverAsync` (gated by `EnableControllerBrowse` /
Flat-mode) and simply not surfaced as the interactive `IDriverBrowser` the AdminUI picker
consumes. Their browsers are a **wrapping job over existing code**, not new protocol work —
the razor picker bodies already exist as static manual-entry stubs.
**Shareable code:** the CIP browse core (0x6B/0x6C decoders + system-tag filter + UDT
layout) is protocol-CIP, not Rockwell-specific — extract a shared `Cip.Core` lib **only
if/when** Omron CIP browse lands (don't pre-abstract). Both AbCip & TwinCAT already expose
their enumerator behind an interface = the ready-made reuse seam.
## Unified browse-support picture
**Browsers to build (net + existing), by protocol richness:**
- Synchronous, model-rich: **AbCip**, **TwinCAT**, **MTConnect** (`/probe`), **SQL** (schema), **BACnet** (object-list), **Omron-CIP**.
- Passive/time-bounded: **MQTT/Sparkplug** (observation window / rebirth).
- Curated/fixed surface: **FOCAS** (deferred).
**Not browsable — documented, decision recorded so it isn't revisited:**
MELSEC SLMP · Modbus TCP · Modbus RTU · S7 · AbLegacy · Omron-FINS (all flat address spaces / no on-wire symbol table).
## Recommended build order (value × effort)
**Wave 0 — universal Discover-backed browser (the foundational browse piece):**
1. **Universal `DiscoveryDriverBrowser`** — ONE generic `IDriverBrowser` that runs any
discovery-capable driver's `ITagDiscovery.DiscoverAsync` against a capturing
`IAddressSpaceBuilder` and serves the captured tree back through the picker. Lights up
AbCip, TwinCAT, FOCAS, MTConnect, BACnet (small sites), authored-MQTT, and every future
discovery driver's picker with **zero per-driver browser code**. Gated on a new
online-discovery capability flag so flat-address drivers stay manual-entry. Design:
[`docs/plans/2026-07-15-universal-discovery-browser-design.md`](../../plans/2026-07-15-universal-discovery-browser-design.md).
2. **AbCip / TwinCAT** — covered *immediately* by the universal browser (their enumeration
already lives in `DiscoverAsync`). Graduate each to a **bespoke lazy `IBrowseSession`**
only if a real controller's symbol set is too large for the universal browser's eager
one-shot discovery (UDT drill-down / per-click expand). Not up-front work anymore.
**Wave 1 — low-effort, high-leverage new drivers:**
3. **Modbus RTU** — lowest-effort new capability; extends the existing driver via one new `IModbusTransport` (lead with RTU-over-TCP for the containerized server).
4. **SQL poll** — zero new NuGet deps (SqlClient already in-repo), broad MES/ERP glue value, browsable.
**Wave 2 — strategic new drivers:**
5. **MTConnect Agent** — MIT library, clean browse, complements FOCAS with vendor-neutral machine-tool telemetry.
6. **MQTT / Sparkplug B** — UNS-strategic; plain-MQTT first to stand up the skeleton + validate MQTTnet/net10, then Sparkplug birth-driven discovery.
**Wave 3 — heavier / test-gated:**
7. **BACnet/IP** — opens facilities/HVAC/energy; COV + discovery + BBMD are the meaty parts (live-gate the broadcast reachability).
8. **Omron** — CIP-first (libplctag) then FINS; last of the active set because there's **no free NJ/NX CIP simulator**, so real-wire correctness is a live-hardware `LiveIntegration` gate.
**Deferred (research complete, not scheduled):**
- **MELSEC SLMP** — deferred (2026-07-15). The mature library is a commercial-license
blocker so it's a full hand-rolled 3E-binary framer with the hardest addressing model
on the roadmap (device-code × bit/word × hex/octal/decimal × word order); revisit when
a Mitsubishi-shop customer need makes it worth the build. Report stays valid as the
starting point.
FOCAS browser slots opportunistically whenever the CNC surface is being touched.
## Per-driver add pattern (reference)
`*.Contracts` project + driver impl (Connect/Discover/Read/Subscribe[/Write]) + factory +
`DriverType` string + typed tag editor (`TagConfigEditorMap`+`TagConfigValidator`) +
optional `*.Browser` project (register one line at
`EndpointRouteBuilderExtensions` AddAdminUI DI) + docker fixture (`project=lmxopcua`
label). Watch the enum-serialization trap and add a per-query/-op timeout deadline
(the R2-01 frozen-peer lesson) on every network call.