docs(phase4b): Modbus driver-type canonical + Galaxy nesting + FOCAS auto-scale (managed-backend caveat)
v2-ci / build (push) Failing after 37s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

This commit is contained in:
Joseph Doherty
2026-06-16 19:58:11 -04:00
parent 6855be288f
commit 08a6551360
4 changed files with 17 additions and 7 deletions
+5 -3
View File
@@ -164,9 +164,11 @@ before publishing, so a CNC that reports millimetres × 1000 is corrected by set
] ]
``` ```
Auto-fetching the decimal-place count via `cnc_getfigure` is deferred (wire-gated). Until The driver now attempts to auto-scale each axis position using the CNC's `cnc_getfigure` figure when available; `PositionDecimalPlaces` is the **per-axis fallback** used only when the CNC reports no figure for that axis. **Caveat:** the managed `WireFocasClient` (FOCAS/2 Ethernet wire protocol) does not issue `cnc_getfigure` today, so on real hardware all axes still fall back to the manual `PositionDecimalPlaces` config field. Live auto-fetch is a follow-up requiring a `cnc_getfigure` command in `FocasWireClient`. Negative values from `PositionDecimalPlaces` are clamped to `0`.
that lands, the config field is the authoritative source — consult the MTB / machine
parameter sheets for the correct value. Negative values are clamped to `0`. **Follow-ups (known gaps, non-blocking):**
- No upper-bound clamp on the figure magnitude returned by the CNC — a misbehaving figure could overflow `Math.Pow`; a max-figure guard should be added.
- The negative-figure branch (falls back to manual `PositionDecimalPlaces`) is handled but not explicitly unit-tested.
**Still user-authored**: `PARAM:6711`, `MACRO:500`, `R100` etc. — specific **Still user-authored**: `PARAM:6711`, `MACRO:500`, `R100` etc. — specific
numbers whose meaning is MTB-specific. Those go under the device folder numbers whose meaning is MTB-specific. Those go under the device folder
+4
View File
@@ -100,6 +100,10 @@ The session-less alarm feed (`GatewayGalaxyAlarmFeed`) and alarm acknowledger (`
- **Parity rig + dev-rig walkthrough**: see [docs/v2/Galaxy.ParityRig.md](../v2/Galaxy.ParityRig.md). The rig stands up a real `mxaccessgw` against a live Galaxy and exercises the full read / write / subscribe / rediscover path. - **Parity rig + dev-rig walkthrough**: see [docs/v2/Galaxy.ParityRig.md](../v2/Galaxy.ParityRig.md). The rig stands up a real `mxaccessgw` against a live Galaxy and exercises the full read / write / subscribe / rediscover path.
- **Performance + soak**: see [docs/v2/Galaxy.Performance.md](../v2/Galaxy.Performance.md). - **Performance + soak**: see [docs/v2/Galaxy.Performance.md](../v2/Galaxy.Performance.md).
## Browse Tree Shape
`GalaxyDiscoverer` builds the OPC UA address space as a **true nested tree** by `parent_gobject_id`: each gobject folder is parented under its Galaxy parent folder rather than being emitted flat under the driver root. Parentage falls back gracefully — a gobject with `parent_gobject_id` of 0, a parent not present in the returned set, a self-referencing parent, or a cycle attaches directly to the driver root. This matches the Galaxy `ArchestrA` object hierarchy as seen in the IDE.
## Operational Notes ## Operational Notes
- **MXAccess `ClientName` collisions**: two OtOpcUa instances sharing a `ClientName` cause the older Wonderware session to lose subscription state. Redundancy pairs (decision #149) enforce uniqueness via install scripts. - **MXAccess `ClientName` collisions**: two OtOpcUa instances sharing a `ClientName` cause the older Wonderware session to lose subscription state. Redundancy pairs (decision #149) enforce uniqueness via install scripts.
+4
View File
@@ -111,6 +111,10 @@ is reproduced in [docs/v2/driver-specs.md §2](../v2/driver-specs.md).
- **Integration tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/` run against the Docker Modbus simulator fixture. See [Modbus-Test-Fixture.md](Modbus-Test-Fixture.md) for the coverage map and the `MODBUS_SIM_ENDPOINT` wiring. - **Integration tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.IntegrationTests/` run against the Docker Modbus simulator fixture. See [Modbus-Test-Fixture.md](Modbus-Test-Fixture.md) for the coverage map and the `MODBUS_SIM_ENDPOINT` wiring.
- **Manual client** — [Driver.Modbus.Cli.md](../Driver.Modbus.Cli.md). - **Manual client** — [Driver.Modbus.Cli.md](../Driver.Modbus.Cli.md).
## Driver-Type String
The canonical stored/dispatched `DriverType` value is **`"Modbus"`** — this is what the runtime factory key, docker-dev seed, AdminUI editor-map, tag-config validator, and probe all use. The AdminUI displays the friendly label **"Modbus TCP"** and the new-driver route slug is `modbustcp`, but neither the slug nor the label is stored in the database. Any pre-existing `DriverInstance` rows that carry the legacy value `"ModbusTcp"` must be updated to `"Modbus"` to receive typed-editor dispatch and factory instantiation.
## Operational Notes ## Operational Notes
- **Wrong-endian readings are silently plausible.** A byte-order misconfiguration produces a wrong number, not a Bad quality code — surface byte-order mismatches as data-validation alerts, not status codes (see [docs/v2/driver-specs.md §2](../v2/driver-specs.md)). - **Wrong-endian readings are silently plausible.** A byte-order misconfiguration produces a wrong number, not a Bad quality code — surface byte-order mismatches as data-validation alerts, not status codes (see [docs/v2/driver-specs.md §2](../v2/driver-specs.md)).
+4 -4
View File
@@ -241,7 +241,7 @@ CREATE TABLE dbo.DriverInstance (
ClusterId nvarchar(64) NOT NULL FOREIGN KEY REFERENCES dbo.ServerCluster(ClusterId), ClusterId nvarchar(64) NOT NULL FOREIGN KEY REFERENCES dbo.ServerCluster(ClusterId),
NamespaceId nvarchar(64) NOT NULL FOREIGN KEY REFERENCES dbo.Namespace(NamespaceId), NamespaceId nvarchar(64) NOT NULL FOREIGN KEY REFERENCES dbo.Namespace(NamespaceId),
Name nvarchar(128) NOT NULL, Name nvarchar(128) NOT NULL,
DriverType nvarchar(32) NOT NULL, -- Galaxy | ModbusTcp | AbCip | AbLegacy | S7 | TwinCat | Focas | OpcUaClient DriverType nvarchar(32) NOT NULL, -- Galaxy | Modbus | AbCip | AbLegacy | S7 | TwinCat | Focas | OpcUaClient
Enabled bit NOT NULL DEFAULT 1, Enabled bit NOT NULL DEFAULT 1,
DriverConfig nvarchar(max) NOT NULL CHECK (ISJSON(DriverConfig) = 1) DriverConfig nvarchar(max) NOT NULL CHECK (ISJSON(DriverConfig) = 1)
); );
@@ -259,7 +259,7 @@ CREATE UNIQUE INDEX UX_DriverInstance_Generation_LogicalId
| `DriverType` | Allowed `Namespace.Kind` | | `DriverType` | Allowed `Namespace.Kind` |
|--------------|--------------------------| |--------------|--------------------------|
| Galaxy | SystemPlatform | | Galaxy | SystemPlatform |
| ModbusTcp / AbCip / AbLegacy / S7 / TwinCat / Focas | Equipment | | Modbus / AbCip / AbLegacy / S7 / TwinCat / Focas | Equipment |
| OpcUaClient | Equipment OR SystemPlatform (per-instance config decides) | | OpcUaClient | Equipment OR SystemPlatform (per-instance config decides) |
**Same-cluster invariant** (revised after adversarial review 2026-04-17 finding #1): the `Namespace` referenced by `DriverInstance.NamespaceId` MUST belong to the same `ClusterId`. This is a cross-cluster trust boundary — without enforcement, a draft for cluster A could bind to a namespace owned by cluster B, leaking that cluster's URI into A's endpoint and breaking tenant isolation. Three layers of enforcement: **Same-cluster invariant** (revised after adversarial review 2026-04-17 finding #1): the `Namespace` referenced by `DriverInstance.NamespaceId` MUST belong to the same `ClusterId`. This is a cross-cluster trust boundary — without enforcement, a draft for cluster A could bind to a namespace owned by cluster B, leaking that cluster's URI into A's endpoint and breaking tenant isolation. Three layers of enforcement:
@@ -801,7 +801,7 @@ Examples of the per-driver shapes — full specs in `driver-specs.md`:
"Historian": { "Enabled": false } "Historian": { "Enabled": false }
} }
// DeviceConfig for DriverType=ModbusTcp // DeviceConfig for DriverType=Modbus
{ {
"Host": "10.0.3.42", "Host": "10.0.3.42",
"Port": 502, "Port": 502,
@@ -810,7 +810,7 @@ Examples of the per-driver shapes — full specs in `driver-specs.md`:
"AddressFormat": "Standard" // or "DL205" "AddressFormat": "Standard" // or "DL205"
} }
// TagConfig for DriverType=ModbusTcp // TagConfig for DriverType=Modbus
{ {
"RegisterType": "HoldingRegister", "RegisterType": "HoldingRegister",
"Address": 100, "Address": 100,