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
This commit is contained in:
@@ -0,0 +1,408 @@
|
||||
# MTConnect (Agent-first) Driver — Research & Design
|
||||
|
||||
**Status:** Research / design proposal. No code written yet.
|
||||
**Author:** research pass, 2026-07-15.
|
||||
**Scope:** A new standard **Equipment-kind driver** (`DriverType = "MTConnect"`) exposing an
|
||||
MTConnect Agent's data under the OtOpcUa unified address space — the same shape as
|
||||
Modbus / S7 / AbCip / TwinCAT / FOCAS / OpcUaClient.
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR
|
||||
|
||||
- **Browseable: YES.** MTConnect `/probe` returns a fully self-describing device model
|
||||
(Device → Components → DataItems) — a natural `IDriverBrowser` tree, closest in spirit to the
|
||||
OpcUaClient browser.
|
||||
- **Recommended library: TrakHound `MTConnect.NET` (client packages), MIT-licensed.** It is
|
||||
mature (v6.9.0.2, Oct 2025, 500k+ NuGet downloads), targets **netstandard2.0** (so it loads on
|
||||
.NET 10) plus net6/7/8/9, and covers MTConnect versions up to 2.5. Hand-rolling is a viable
|
||||
fallback but not recommended for v1.
|
||||
- **Read-only v1** (Discover + Read + Subscribe). No Write. MTConnect *Interfaces* (request/response
|
||||
write-back) exist but are rare, optional, and out of scope for v1.
|
||||
- **Top risks:** (1) MTConnect data is *loosely typed* — DataItems are strings with `type`/`units`
|
||||
metadata, so the OPC UA data-type mapping is a heuristic, not a guarantee; (2) `CONDITION`
|
||||
category has no clean scalar OPC UA analog and needs a modelling decision (fold to string vs.
|
||||
native Part 9 alarm).
|
||||
|
||||
---
|
||||
|
||||
## 1. Protocol summary + .NET library options
|
||||
|
||||
### 1.1 The two MTConnect layers
|
||||
|
||||
MTConnect ([standard docs](https://docs.mtconnect.org/), [SysML model v2.5](https://model.mtconnect.org/Version2.5/Fundamentals/))
|
||||
has two wire layers:
|
||||
|
||||
| Layer | Transport | Device model? | Role |
|
||||
|---|---|---|---|
|
||||
| **Agent** (primary) | HTTP REST, XML (or JSON) | **Yes** — self-describing | The queryable server. What apps talk to. |
|
||||
| **Adapter / SHDR** | Raw pipe-delimited TCP (default `:7878`) | **No** | Feeds an Agent from a machine; no model, no discovery. |
|
||||
|
||||
We target the **Agent** as the primary source. SHDR is a possible phase-2 ingest mode (§7) but
|
||||
loses auto-discovery.
|
||||
|
||||
### 1.2 Agent REST verbs
|
||||
|
||||
The Agent exposes three request types (MTConnect Standard Part 1, §5.4 / §8):
|
||||
|
||||
- **`/probe`** (optionally `/{device}/probe`) → an **`MTConnectDevices`** response document: the
|
||||
Device Information Model — every `Device`, its nested `Components`, and each `DataItem`
|
||||
definition (`id`, `type`, `category`, `subType`, `units`, `nativeUnits`, `name`).
|
||||
*This is the discovery + browse surface.*
|
||||
- **`/current`** (optionally `?at=<sequence>`) → an **`MTConnectStreams`** response document: a
|
||||
snapshot of the latest `Observation` for every DataItem at the moment of the request. *This is the
|
||||
Read surface.*
|
||||
- **`/sample?from=<seq>&count=<n>&interval=<ms>`** → an **`MTConnectStreams`** document containing a
|
||||
*time-ordered set* of observations since sequence `from`. *This is the Subscribe surface.*
|
||||
|
||||
**Streaming / long-poll.** When `interval` is supplied, the Agent holds the HTTP connection open and
|
||||
pushes successive `MTConnectStreams` chunks as a **`multipart/x-mixed-replace`** boundary stream —
|
||||
each chunk is one batch of new observations. Each response `Header` carries `nextSequence`; a poller
|
||||
issues the next `/sample` with `from=nextSequence` to get a contiguous, gap-free stream. The Agent's
|
||||
ring buffer has a finite `bufferSize`; if a consumer falls behind past the buffer, it must re-`/current`
|
||||
to re-baseline. (Sources:
|
||||
[MTConnect.NET README](https://github.com/TrakHound/MTConnect.NET/blob/master/README.md),
|
||||
[cppagent README](https://github.com/mtconnect/cppagent/blob/master/README.md),
|
||||
[Part 1 Overview](https://docs.mtconnect.org/MTC_Part_1_Overview+V1.2.pdf).)
|
||||
|
||||
### 1.3 The information model (probe)
|
||||
|
||||
`Device → Component(s) → DataItem(s)`. A `DataItem` has three **categories** (from the
|
||||
[SysML model](https://model.mtconnect.org/Version2.5/Fundamentals/)):
|
||||
|
||||
- **SAMPLE** — continuous numeric measurement over time. Types e.g. `Position`, `Temperature`,
|
||||
`SpindleSpeed`, `PathFeedrate`, `Load`, `Acceleration`. Carries `units`/`nativeUnits`.
|
||||
- **EVENT** — discrete state / value change. Types e.g. `Availability` (AVAILABLE/UNAVAILABLE),
|
||||
`Execution` (READY/ACTIVE/INTERRUPTED/STOPPED), `ControllerMode` (AUTOMATIC/MANUAL/...),
|
||||
`Program`, `EmergencyStop`, `Block`. Values are typically controlled vocabularies (enums) or free
|
||||
strings/ints.
|
||||
- **CONDITION** — health/alarm state. An observation is reported as one of `Normal` / `Warning` /
|
||||
`Fault` / `Unavailable`, with optional `nativeCode`, `nativeSeverity`, `qualifier`, and message
|
||||
text. Subtypes: `Actuator`, `Communications`, `System`, `Temperature`, `LogicProgram`, etc.
|
||||
|
||||
An observation in an `MTConnectStreams` document references its definition by **`dataItemId`** (and
|
||||
carries a `sequence`, `timestamp`, and value). This `dataItemId` is the stable per-tag address (§3).
|
||||
|
||||
### 1.4 .NET library options
|
||||
|
||||
**Recommended: TrakHound `MTConnect.NET` client packages (MIT).**
|
||||
|
||||
| Fact | Value | Source |
|
||||
|---|---|---|
|
||||
| Latest version | 6.9.0.2 (published 2025-10-16) | [NuGet MTConnect.NET-HTTP](https://www.nuget.org/packages/MTConnect.NET-HTTP/) |
|
||||
| License | **MIT** (per latest NuGet package metadata) | [NuGet MTConnect.NET-HTTP](https://www.nuget.org/packages/MTConnect.NET-HTTP/) |
|
||||
| TFMs | netstandard2.0, net6.0–net9.0, net48 (**runs on .NET 10 via netstandard2.0**) | [README](https://github.com/TrakHound/MTConnect.NET/blob/master/README.md) |
|
||||
| MTConnect versions | up to 2.5; auto-strips data not valid for requested version | [README](https://github.com/TrakHound/MTConnect.NET/blob/master/README.md) |
|
||||
| Maturity | 500k+ NuGet downloads; actively maintained | [README](https://github.com/TrakHound/MTConnect.NET/blob/master/README.md) |
|
||||
|
||||
Packages we would consume (client side only — **not** the Agent/Adapter host packages):
|
||||
|
||||
- **`MTConnect.NET-Common`** — model types (`IDevice`, `IComponent`, `IDataItem`, `IObservation`),
|
||||
the `IMTConnectClient` / `IMTConnectEntityClient` interfaces.
|
||||
- **`MTConnect.NET-HTTP`** — `MTConnectHttpClient`: probe/current/sample with polling **and**
|
||||
streaming, gzip compression, XML **and** JSON. Exposes events for received documents
|
||||
(`OnProbeReceived`, `OnCurrentReceived`, `OnSampleReceived`, plus a per-observation callback) —
|
||||
a clean fit for pushing into `ISubscribable.OnDataChange`.
|
||||
- (`MTConnect.NET-XML` / `-JSON` are pulled transitively for serialization.)
|
||||
|
||||
> ⚠️ **License due-diligence caveat:** older release notes and the repo copyright header have shown
|
||||
> mixed "MIT" / "Apache-2.0" / "© TrakHound, All Rights Reserved" strings across versions. The
|
||||
> *current* NuGet package metadata for 6.9.0.2 declares **MIT**. Before taking the dependency,
|
||||
> pin a specific version and confirm that exact version's embedded `LICENSE` / package license
|
||||
> expression (a legal-review checkbox, not a blocker).
|
||||
|
||||
**Fallback: hand-roll a minimal XML client.** The Agent protocol is just three HTTP GETs returning
|
||||
XML. A hand-rolled client (`HttpClient` + `System.Xml.Linq` for probe, a `multipart/x-mixed-replace`
|
||||
boundary reader for sample) is ~300–500 LoC and removes a third-party dependency + its transitive
|
||||
`System.Text.Json` version constraints. **Recommendation: use the library for v1** (it handles version
|
||||
negotiation, the multipart framing, buffer-overflow re-baselining, and JSON/XML dual-format — all
|
||||
fiddly to reimplement correctly), and keep hand-roll as a contingency if the license review fails.
|
||||
|
||||
---
|
||||
|
||||
## 2. Capability mapping to the Equipment-kind seams
|
||||
|
||||
The driver implements the composable capability interfaces in
|
||||
`src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/`. Mapping:
|
||||
|
||||
| OtOpcUa seam | MTConnect mechanism | Notes |
|
||||
|---|---|---|
|
||||
| `IDriver` (required) | Construct `MTConnectHttpClient` from config, hold `/probe` model | `InitializeAsync` does one probe; `GetHealth` reflects last successful poll + Agent `instanceId` |
|
||||
| `ITagDiscovery.DiscoverAsync` | Walk the `/probe` DeviceModel, stream `Device→Component→DataItem` into `IAddressSpaceBuilder` | `RediscoverPolicy = Once` (probe is synchronous & complete). Redeploy on Agent `instanceId` change (see `IRediscoverable`, §below) |
|
||||
| `IReadable.ReadAsync(fullRefs)` | `/current` snapshot, index observations by `dataItemId`, return one `DataValueSnapshot` per requested ref | Reads are idempotent (matches `IReadable` contract). Per-ref miss → `Bad`-coded snapshot, not a throw |
|
||||
| `ISubscribable.SubscribeAsync` | Start the `MTConnectHttpClient` **sample stream** (`from`/`interval`); fan each received observation to `OnDataChange` keyed by `dataItemId` | One shared stream per driver instance (not per tag) — the Agent streams the whole device; filter to subscribed refs. Fire initial values from `/current` on subscribe (OPC UA convention) |
|
||||
| `IWritable` | **NOT implemented (v1)** | See verdict below |
|
||||
| `IRediscoverable.OnRediscoveryNeeded` | Raise when the Agent's `Header/@instanceId` or `@assetBufferSize` changes (Agent restarted / model changed) → Host rebuilds address space | Mirrors Galaxy's `DeployWatcher` pattern |
|
||||
| `IHostConnectivityProbe` | Cheap periodic `/probe` HEAD or `/current` to flip Running↔Stopped | Mirrors `ModbusProbeOptions` |
|
||||
| `IAlarmSource` | *(optional, phase 1.5)* map `CONDITION` Fault/Warning → Part 9 native alarm | See §2.3 |
|
||||
|
||||
### 2.1 Write verdict: **read-only for v1**
|
||||
|
||||
Justification:
|
||||
1. The mainstream MTConnect surface (`/probe`, `/current`, `/sample`) is **strictly read-only** by
|
||||
design — MTConnect was conceived as a read-only telemetry standard.
|
||||
2. Write-back exists only via **MTConnect *Interfaces*** (a request/response handshake modelled as
|
||||
its own DataItem categories — `Request`/`Response` events) which is **optional, rarely deployed**,
|
||||
and semantically a state-machine handshake, not a simple "set value." Modelling it as OPC UA
|
||||
`IWritable` would be misleading.
|
||||
3. The Equipment-kind seams are composable — a driver implements only what its backend supports
|
||||
(`IDriver` doc-comment). Omitting `IWritable` is idiomatic (Galaxy's write path is even
|
||||
fire-and-forget; several drivers are read-mostly). Nodes materialize without the
|
||||
`AccessLevels.CurrentWrite` bit.
|
||||
|
||||
Revisit Interfaces write-back in a later phase only if a concrete deployment needs it.
|
||||
|
||||
### 2.2 Data-type mapping (MTConnect → `DriverDataType` → OPC UA)
|
||||
|
||||
MTConnect DataItems are **weakly typed on the wire** (values arrive as strings). The mapping is
|
||||
inferred from `category` + `type` + `units`, and stored per-tag in the TagConfig (§3) so a wrong
|
||||
inference can be corrected by the author without a code change.
|
||||
|
||||
| MTConnect | Inferred `DriverDataType` | Rationale |
|
||||
|---|---|---|
|
||||
| `SAMPLE` (numeric, has `units`) | `Float64` | Continuous analog; `Double` is the safe superset |
|
||||
| `SAMPLE` w/ `representation="TIME_SERIES"` | `Float64` array (ValueRank=1) | Time-series sample bursts |
|
||||
| `EVENT` controlled-vocab (Execution, ControllerMode, Availability, …) | `String` | Enum-like; keep the vocab string. (Optional: OPC UA enum modelling later) |
|
||||
| `EVENT` numeric (e.g. `PartCount`, `Line`) | `Int64` | Integer counters |
|
||||
| `EVENT` free text (`Program`, `Block`, `Message`) | `String` | |
|
||||
| `CONDITION` | `String` (v1: the state word `Normal`/`Warning`/`Fault`/`Unavailable`) **or** native alarm (§2.3) | No clean scalar analog |
|
||||
| `timestamp` on every observation | → OPC UA `SourceTimestamp` | Not a separate node |
|
||||
|
||||
`DataValueSnapshot.StatusCode` mapping: an observation value of `UNAVAILABLE` (MTConnect's explicit
|
||||
"no data" sentinel) → OPC UA `Bad`/`Uncertain` quality rather than a literal string, so downstream
|
||||
sees proper quality. Empty CONDITION or missing dataItem → `Bad`.
|
||||
|
||||
### 2.3 CONDITION modelling (decision point)
|
||||
|
||||
`CONDITION` is the awkward one. Two options:
|
||||
- **v1 simple:** expose each CONDITION DataItem as a `String` variable node whose value is the current
|
||||
condition state (`Fault`/`Warning`/`Normal`/`Unavailable`), optionally suffixed with `nativeCode`.
|
||||
Zero alarm plumbing.
|
||||
- **v1.5 native:** treat a Fault/Warning CONDITION as an OtOpcUa **native alarm** by emitting a
|
||||
TagConfig `alarm` object and implementing `IAlarmSource` — matching the Galaxy/Phase-B native-alarm
|
||||
pattern (route on the authored dotted reference, per the alarms memory). Higher value, more work.
|
||||
|
||||
Recommend **v1 simple, v1.5 native** as a fast-follow.
|
||||
|
||||
---
|
||||
|
||||
## 3. TagConfig JSON shape
|
||||
|
||||
Consistent with the Equipment-kind model: **connection-level settings live in the driver config**
|
||||
(agent base URL, auth, poll interval); **per-tag addressing lives in `TagConfig`**, and the platform
|
||||
`FullName` binds the tag to a driver-side reference. For MTConnect the natural stable reference is the
|
||||
**`dataItemId`** (globally unique within an Agent), so `FullName = "<dataItemId>"` (or, for
|
||||
readability, the driver can also accept a `device/component/dataItemName` path and resolve it to the id
|
||||
at discovery time).
|
||||
|
||||
### 3.1 Driver config (per driver instance) — `MTConnectDriverOptions`
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"AgentUri": "https://demo.mtconnect.org", // Agent base URL (http or https)
|
||||
"DeviceName": "", // optional: scope to one device (else all devices)
|
||||
"PreferJson": false, // request application/json instead of XML if Agent supports it
|
||||
"SampleIntervalMs": 1000, // long-poll interval passed to /sample
|
||||
"SampleCount": 500, // max observations per sample chunk
|
||||
"HeartbeatMs": 10000, // Agent keep-alive heartbeat
|
||||
"RequestTimeoutMs": 30000,
|
||||
"AllowUntrustedTls": false, // dev/on-prem self-signed agents
|
||||
"Probe": { "Enabled": true, "IntervalMs": 5000 } // IHostConnectivityProbe
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Per-tag `TagConfig` (authored on the /uns Tags tab)
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"FullName": "avail", // <-- MTConnect dataItemId; the driver-side reference
|
||||
"mtDevice": "OKUMA.Lathe", // optional human context (device name from probe)
|
||||
"mtComponent": "Controller", // optional
|
||||
"mtCategory": "EVENT", // SAMPLE | EVENT | CONDITION (from probe; drives typing)
|
||||
"mtType": "AVAILABILITY", // MTConnect DataItem type
|
||||
"mtSubType": null,
|
||||
"dataType": "String", // resolved DriverDataType (overridable by author)
|
||||
"units": null, // e.g. "CELSIUS" for a SAMPLE
|
||||
"writable": false // always false for MTConnect v1
|
||||
}
|
||||
```
|
||||
|
||||
A picked SAMPLE example:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"FullName": "Xact",
|
||||
"mtDevice": "OKUMA.Lathe",
|
||||
"mtComponent": "Linear[X]",
|
||||
"mtCategory": "SAMPLE",
|
||||
"mtType": "POSITION",
|
||||
"mtSubType": "ACTUAL",
|
||||
"dataType": "Float64",
|
||||
"units": "MILLIMETER",
|
||||
"writable": false
|
||||
}
|
||||
```
|
||||
|
||||
The runtime driver resolves reads/subscriptions by `FullName` (= `dataItemId`) against the observation
|
||||
index it maintains from `/current` and `/sample` — exactly the "bind by `TagConfig.FullName`" pattern
|
||||
the other drivers use (`EquipmentTagRefResolver<TDef>`). Only `FullName` is load-bearing at runtime;
|
||||
the `mt*`/`units` fields are author-facing metadata + type inference inputs preserved across edits.
|
||||
|
||||
---
|
||||
|
||||
## 4. Browseability verdict + browse design
|
||||
|
||||
### 4.1 Verdict: **YES — fully browseable.**
|
||||
|
||||
`/probe` returns the complete `MTConnectDevices` model in one call — a static, self-describing tree.
|
||||
This is the *cleanest possible* browse source (better than Galaxy, which needs per-node expansion;
|
||||
comparable to a single-shot OPC UA browse). It maps directly onto `IDriverBrowser`/`IBrowseSession`.
|
||||
|
||||
### 4.2 New project: `ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Browser`
|
||||
|
||||
Mirror `OpcUaClient.Browser` layout exactly:
|
||||
|
||||
```
|
||||
src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Browser/
|
||||
MTConnectDriverBrowser.cs // IDriverBrowser: DriverType="MTConnect"; OpenAsync(configJson) -> session
|
||||
MTConnectBrowseSession.cs // IBrowseSession over the fetched probe model
|
||||
ZB.MOM.WW.OtOpcUa.Driver.MTConnect.Browser.csproj
|
||||
```
|
||||
|
||||
Register in AdminUI DI alongside the others
|
||||
(`src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/EndpointRouteBuilderExtensions.cs`, ~line 49-50):
|
||||
|
||||
```csharp
|
||||
services.AddSingleton<IDriverBrowser, MTConnectDriverBrowser>();
|
||||
```
|
||||
|
||||
### 4.3 Session behaviour (map to `IBrowseSession`)
|
||||
|
||||
Unlike OPC UA (live one-level-per-call), MTConnect probe is fetched **once** in
|
||||
`OpenAsync` and cached in the session; expansion is served from the in-memory tree (no further network
|
||||
calls). This is simpler and faster than the OpcUaClient browser.
|
||||
|
||||
| Method | Returns |
|
||||
|---|---|
|
||||
| `OpenAsync(configJson)` | Deserialize `MTConnectDriverOptions`, `GET {AgentUri}/probe`, hold the parsed `IDevice[]` model in the session |
|
||||
| `RootAsync()` | One `BrowseNode(Kind=Folder)` per **Device** (or, if `DeviceName` is scoped, the top-level **Components** of that device) |
|
||||
| `ExpandAsync(nodeId)` | Children of a Device/Component: nested **Components** → `Folder` nodes; **DataItems** → `Leaf` nodes. `NodeId` encodes the path (e.g. `dev:OKUMA.Lathe`, `comp:<componentId>`, `di:<dataItemId>`) |
|
||||
| `AttributesAsync(nodeId)` | For a DataItem leaf, one-or-few `AttributeInfo` rows describing it (`Name=dataItemName`, `DriverDataType=<inferred>`, `IsArray=<TIME_SERIES?>`, `SecurityClass`, `IsAlarm = (category==CONDITION)`). For Device/Component folders, empty. This drives the picker's side-panel and lets a CONDITION pre-fill a native-alarm TagConfig (like Galaxy alarm attrs) |
|
||||
|
||||
- `BrowseNode.NodeId` for a DataItem leaf is `di:<dataItemId>` → the picker commits it, and the
|
||||
TagModal maps it to `TagConfig.FullName = <dataItemId>` plus the `mt*` metadata read from the cached
|
||||
probe model.
|
||||
- `BrowseNodeKind.Leaf` = DataItem (terminal, commit-on-select); `Folder` = Device/Component.
|
||||
- `HasChildrenHint = true` for Devices/Components that have child components or DataItems.
|
||||
|
||||
### 4.4 Picked-DataItem → TagConfig
|
||||
|
||||
When the picker commits a `di:<dataItemId>` leaf, the AdminUI builds the TagConfig in §3.2 from the
|
||||
cached probe DataItem: `FullName=id`, `mtCategory/mtType/mtSubType/units` copied verbatim, and
|
||||
`dataType` set from the §2.2 inference table (author can override in the typed editor).
|
||||
|
||||
---
|
||||
|
||||
## 5. Typed tag-editor (AdminUI)
|
||||
|
||||
To avoid the raw-JSON fallback, add a driver-typed editor (mirrors the driver-typed tag-editor pattern):
|
||||
|
||||
- `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/MTConnectTagConfigModel.cs`
|
||||
(pure `FromJson`/`ToJson`/`Validate`, preserves unknown keys) — copy the Modbus template.
|
||||
- `.../Components/Shared/Uns/TagEditors/MTConnectTagConfigEditor.razor` — a thin shell:
|
||||
fields for `FullName` (dataItemId), read-only `mtCategory/mtType`, and a `dataType` override dropdown.
|
||||
- Register `["MTConnect"] = typeof(...MTConnectTagConfigEditor)` in
|
||||
`Uns/TagEditors/TagConfigEditorMap.cs`, and add validation in `TagConfigValidator`.
|
||||
|
||||
Because most tags are authored via the **browse picker** (which fills the model), the editor is mostly
|
||||
a confirm/override surface.
|
||||
|
||||
---
|
||||
|
||||
## 6. Test-fixture strategy
|
||||
|
||||
Ranked by value:
|
||||
|
||||
1. **Public demo Agents (fastest smoke path).**
|
||||
- `https://demo.mtconnect.org/` — the MTConnect Institute reference demo (probe/current/sample).
|
||||
- `http://mtconnect.mazakcorp.com/` — Mazak demo agents.
|
||||
- NIST **smstestbed** publishes real agent configs + `Devices.xml`
|
||||
([usnistgov/smstestbed](https://github.com/usnistgov/smstestbed/blob/master/mtconnect/agent/Devices.xml)).
|
||||
These give a live, real-shape model for browse + read + streaming verification with zero infra.
|
||||
(Caveat: public agents are internet-dependent and not reproducible in CI.)
|
||||
|
||||
2. **Dockerized reference C++ Agent (reproducible integration fixture).**
|
||||
The MTConnect reference Agent ([mtconnect/cppagent](https://github.com/mtconnect/cppagent)) has an
|
||||
official Docker image. Add a fixture under `tests/.../Docker/docker-compose.yml` with the
|
||||
`project: lmxopcua` label (per the repo's Docker workflow), seeded with a canned `Devices.xml` and
|
||||
an SHDR simulator or the built-in agent adapter, exposed on the shared docker host `10.100.0.35`.
|
||||
This is the integration-test analog of the Modbus/S7 sims.
|
||||
|
||||
3. **Canned probe/current/sample XML fixtures (unit tests).**
|
||||
Capture one `MTConnectDevices` (probe) and a couple of `MTConnectStreams` (current + sample) XML
|
||||
documents from a demo agent into `tests/.../MTConnect.Tests/Fixtures/`. Drive the parser,
|
||||
type-inference, browse-tree, and observation-indexing logic with **no network** — the bulk of unit
|
||||
coverage. This is where the tricky bits (type inference, `nextSequence` paging, `UNAVAILABLE`→Bad,
|
||||
CONDITION handling, multipart chunk framing) get pinned.
|
||||
|
||||
Recommended: **#3 for unit CI + #2 for the env-gated integration suite + #1 for manual live smoke.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Effort / risk / phasing
|
||||
|
||||
### Phase 1 — Agent read + subscribe + browse (the MVP)
|
||||
- Projects: `Driver.MTConnect` (+ `.Contracts` for `MTConnectDriverOptions`/DTOs),
|
||||
`Driver.MTConnect.Browser`, `MTConnect.Tests`.
|
||||
- Implement `IDriver` + `ITagDiscovery` (probe→builder) + `IReadable` (current) +
|
||||
`ISubscribable` (sample stream) + `IHostConnectivityProbe` + `IRediscoverable` (instanceId change).
|
||||
- `IDriverProbe` for the AdminUI Test-Connect button (probe reachability).
|
||||
- `IDriverBrowser`/`IBrowseSession` from cached probe model; register in AdminUI DI.
|
||||
- Typed tag editor + `TagConfigEditorMap`/`TagConfigValidator` entries.
|
||||
- Register the factory in the Host bootstrapper (mirror `ModbusDriverFactoryExtensions.Register`).
|
||||
- CONDITION as `String` (simple).
|
||||
- **Effort estimate:** ~1–1.5 weeks with the TrakHound library (it removes the protocol grind).
|
||||
~2.5–3 weeks hand-rolled.
|
||||
|
||||
### Phase 1.5 (fast-follow)
|
||||
- CONDITION → native OPC UA Part 9 alarms via `IAlarmSource` (Galaxy native-alarm pattern).
|
||||
- `TIME_SERIES` SAMPLE arrays; EVENT controlled-vocab → OPC UA enumerations.
|
||||
|
||||
### Phase 2 — SHDR adapter ingest (second source mode)
|
||||
- Add an optional `SourceMode: "Agent" | "Shdr"` to the driver config. In SHDR mode the driver opens
|
||||
the raw pipe-delimited TCP socket (default `:7878`) and parses `<timestamp>|<key>|<value>` lines.
|
||||
- **Loses auto-discovery**: SHDR has no device model, so `ITagDiscovery` cannot self-populate — tags
|
||||
must be **manually authored** (like Modbus), and the browser is unavailable in SHDR mode. Type
|
||||
inference falls back to author-supplied `dataType`.
|
||||
- Value: connect directly to a machine adapter with no Agent deployed. Niche; do only on demand.
|
||||
|
||||
### Risk register
|
||||
| Risk | Sev | Mitigation |
|
||||
|---|---|---|
|
||||
| Weak wire typing → wrong OPC UA type inference | Med | Store resolved `dataType` in TagConfig; author-overridable; unit-test the inference table against real probe XML |
|
||||
| CONDITION has no scalar analog | Med | v1 = String state; v1.5 = native alarm |
|
||||
| TrakHound license string inconsistent across versions | Low-Med | Pin a version; confirm its embedded MIT license in legal review before merge |
|
||||
| Public demo agents unreliable for CI | Low | Docker cppagent fixture + canned XML for CI; demo agents only for manual smoke |
|
||||
| Agent ring-buffer overflow if consumer stalls | Low | Library re-baselines via `/current` on sequence gap; verify + test the fall-behind path |
|
||||
| `netstandard2.0`-only lib on .NET 10 | Low | netstandard2.0 loads on net10.0; validate no trim/AOT issues at build |
|
||||
|
||||
---
|
||||
|
||||
## 8. Source references
|
||||
|
||||
- MTConnect Standard Part 1 (Overview & Protocol): https://docs.mtconnect.org/MTC_Part_1_Overview+V1.2.pdf
|
||||
- MTConnect SysML Model v2.5 (Fundamentals — Device/Component/DataItem, categories): https://model.mtconnect.org/Version2.5/Fundamentals/
|
||||
- TrakHound MTConnect.NET (README, client API, TFMs, versions): https://github.com/TrakHound/MTConnect.NET/blob/master/README.md
|
||||
- NuGet MTConnect.NET-HTTP (license=MIT, v6.9.0.2 2025-10-16, TFMs, deps): https://www.nuget.org/packages/MTConnect.NET-HTTP/
|
||||
- MTConnect reference C++ Agent (Docker fixture): https://github.com/mtconnect/cppagent/blob/master/README.md
|
||||
- NIST smstestbed agent config + Devices.xml (real fixtures): https://github.com/usnistgov/smstestbed/blob/master/mtconnect/agent/Devices.xml
|
||||
- Public demo agent: https://demo.mtconnect.org/ · Mazak demo agents: http://mtconnect.mazakcorp.com/
|
||||
|
||||
### OtOpcUa code seams referenced
|
||||
- Capability interfaces: `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/` (`IDriver`, `ITagDiscovery`, `IReadable`, `ISubscribable`, `IWritable`, `IRediscoverable`, `IHostConnectivityProbe`, `IDriverFactory`, `DriverDataType`).
|
||||
- Browse seam: `src/Core/ZB.MOM.WW.OtOpcUa.Commons/Browsing/` (`IDriverBrowser`, `IBrowseSession`, `BrowseNode`, `AttributeInfo`).
|
||||
- Browser template: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Browser/`.
|
||||
- Driver template: `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus/` (+ `.Contracts`), factory `ModbusDriverFactoryExtensions`.
|
||||
- AdminUI browser DI registration: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/EndpointRouteBuilderExtensions.cs:49-50`.
|
||||
- Typed tag editors: `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/TagEditors/TagConfigEditorMap.cs` + `TagConfigValidator.cs`.
|
||||
- Browse design doc: `docs/plans/2026-05-28-driver-browsers-design.md`.
|
||||
Reference in New Issue
Block a user