FOCAS: browse-committed tags are unreadable (BadNodeIdUnknown) — device-host prefix is written into TagConfig.Address #540

Closed
opened 2026-08-05 15:27:22 -04:00 by dohertj2 · 2 comments
Owner

Summary

Authoring FOCAS tags through /raw → device → Browse device… produces tags the driver can never read. Every committed tag reads 0x80340000 BadNodeIdUnknown forever. The failure is silent end to end: the AdminUI accepts it, the deploy gate seals green, and the driver reports Healthy with 0 errors while ignoring the tags entirely.

Net effect: the fixed-tree surface (Identity / Axes / Spindle / Program / OperationMode / Timers) cannot be published through any v3 authoring path. v2 auto-injected it; #507 removed that injection as superseded, and the browse-commit route that was supposed to replace it does not work.

Found while cutting wonder-app-vd03 over to v3 (4e461951) against a real Makino Pro 5 / Fanuc 30i at 10.201.31.5:8193.

Reproduce

  1. Author a FOCAS driver + device in /raw (device endpoint = a real CNC, series Thirty_i).
  2. Device ⋯ → Browse device… → Connect & browse → toggle Create matching tag-groups on.
  3. Expand FOCAS → <device> and tick every leaf under Identity, Spindle, Program, OperationMode, Timers.
  4. Commit, then Deploy current configuration.

Deployment seals. The tree materializes over OPC UA. Then every new node fails:

ns=2;s=focas-z-34184/z-34184/parts-count                                → Good   (MACRO:3901, hand-authored)
ns=2;s=focas-z-34184/z-34184/parts-required                             → Good   (MACRO:3902, hand-authored)
ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Identity/SeriesNumber        → 0x80340000
ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Identity/CncType             → 0x80340000
ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/OperationMode/Mode           → 0x80340000
ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Program/Name                 → 0x80340000
ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Timers/PowerOnSeconds        → 0x80340000
ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Spindle/S1/Load              → 0x80340000

0 of 20 browse-committed tags read. The two hand-authored MACRO: tags on the same device read Good, which isolates it to the address, not the connection.

Root cause

The browse FullName carries the device host, and it is written into the tag address unmodified:

  • src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs — FixedTreeReference(deviceHost, path) => $"{deviceHost}/{path}", used by EmitIdentityVariable / EmitAxisVariable / the generic emitter (~lines 628, 649, 684). So a leaf's FullName is literally 10.201.31.5:8193/Identity/SeriesNumber.
  • src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawBrowseCommitMapper.cs:248 — Address = address where address is that FullName. No prefix stripping, no translation.
  • src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasAddress.cs — TryParse accepts only PARAM:<n>[/bit], MACRO:<n>, or PMC letter + digits [.bit]. A string beginning 1 fails at char.IsLetter(src[0]) and returns null.
  • FocasDriver.InitializeAsync then logs "FOCAS tag has invalid Address …; skipping" and the tag never enters the poll table, so every read returns BadNodeIdUnknown.

Confirmed by opening a committed tag in the AdminUI — its Address field reads 10.201.31.5:8193/Identity/SeriesNumber verbatim.

Why nothing catches it

Three gates that look like they'd catch this, don't:

  1. FocasTagConfigModel.Validate() only checks the address is non-empty. Authoring accepts it.
  2. The deploy gate passes. Deployment 225e145e reached Sealed / In sync with 20 dead tags in it. A sealed deployment is not evidence the tags work.
  3. Driver health stays green. focas-z-34184 shows Healthy, errors/5 min = 0, Last error —, with a live-advancing Last read — while silently ignoring 20 of its 22 tags. Unparseable addresses are skipped at init rather than counted as errors, so per-tag authoring mistakes are invisible in driver health.

(3) is arguably the more dangerous half: an operator has no signal anywhere that their tags aren't live.

Second defect, same area

The manual FOCAS picker is broken the same way:

  • src/Server/…/Pickers/FocasAddressBuilder.cs:14 — Build(group, id) => $"{group}:{id}"
  • src/Server/…/Pickers/FOCASAddressPickerBody.razor:82 — defaults _group = "axis", so the picker's initial value is axis:5

axis:5 is not a form FocasAddress.TryParse accepts either. So only hand-typed PARAM: / MACRO: / PMC addresses currently work at all — both UI-assisted authoring paths emit strings the runtime rejects.

Suggested direction

This is a design call, not a one-liner. Roughly:

  • Strip {deviceHost}/ at commit — necessary but not sufficient: Identity/SeriesNumber is a logical fixed-tree path, not a PMC/PARAM/MACRO address, so the parser must also learn a fourth address form (e.g. FIXED:<path>) that resolves against FixedTreeCache rather than a FOCAS area read. Same treatment for the picker's axis: / spindle: groups.
  • Or re-materialize the fixed tree through a discovery/injection path instead of authored tags, accepting that it re-opens what #507 closed.

Whichever way it goes, two guardrails are worth adding independently:

  • Make FocasTagConfigModel.Validate() actually run FocasAddress.TryParse so a bad address is rejected at authoring time and at the deploy gate, instead of at driver init where nothing surfaces it.
  • Surface skipped-at-init tags somewhere an operator sees them — a count on /hosts, or a degraded driver state — so "Healthy, 0 errors" cannot coexist with "ignoring most of my tags".

Note for anyone reproducing

On a real CNC the browse may report duplicate sibling names — this Makino returns two axis folders both called AA, which would collide as RawPath segments. Axes was excluded from this test for that reason, so the 20 tags above are the non-axis folders only. Worth deciding how commit should disambiguate duplicates before the axis path is fixed.

## Summary Authoring FOCAS tags through **`/raw` → device → Browse device…** produces tags the driver can never read. Every committed tag reads `0x80340000 BadNodeIdUnknown` forever. The failure is silent end to end: the AdminUI accepts it, the deploy gate seals green, and the driver reports **Healthy with 0 errors** while ignoring the tags entirely. Net effect: **the fixed-tree surface (`Identity` / `Axes` / `Spindle` / `Program` / `OperationMode` / `Timers`) cannot be published through any v3 authoring path.** v2 auto-injected it; #507 removed that injection as superseded, and the browse-commit route that was supposed to replace it does not work. Found while cutting `wonder-app-vd03` over to v3 (`4e461951`) against a real Makino Pro 5 / Fanuc 30i at `10.201.31.5:8193`. ## Reproduce 1. Author a FOCAS driver + device in `/raw` (device endpoint = a real CNC, series `Thirty_i`). 2. Device `⋯` → **Browse device…** → **Connect & browse** → toggle **Create matching tag-groups** on. 3. Expand `FOCAS` → `<device>` and tick every leaf under `Identity`, `Spindle`, `Program`, `OperationMode`, `Timers`. 4. Commit, then **Deploy current configuration**. Deployment seals. The tree materializes over OPC UA. Then every new node fails: ``` ns=2;s=focas-z-34184/z-34184/parts-count → Good (MACRO:3901, hand-authored) ns=2;s=focas-z-34184/z-34184/parts-required → Good (MACRO:3902, hand-authored) ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Identity/SeriesNumber → 0x80340000 ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Identity/CncType → 0x80340000 ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/OperationMode/Mode → 0x80340000 ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Program/Name → 0x80340000 ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Timers/PowerOnSeconds → 0x80340000 ns=2;s=focas-z-34184/z-34184/FOCAS/z-34184/Spindle/S1/Load → 0x80340000 ``` 0 of 20 browse-committed tags read. The two hand-authored `MACRO:` tags on the *same device* read Good, which isolates it to the address, not the connection. ## Root cause The browse `FullName` carries the device host, and it is written into the tag address unmodified: - `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs` — `FixedTreeReference(deviceHost, path) => $"{deviceHost}/{path}"`, used by `EmitIdentityVariable` / `EmitAxisVariable` / the generic emitter (~lines 628, 649, 684). So a leaf's `FullName` is literally `10.201.31.5:8193/Identity/SeriesNumber`. - `src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Uns/RawBrowseCommitMapper.cs:248` — `Address = address` where `address` is that `FullName`. No prefix stripping, no translation. - `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasAddress.cs` — `TryParse` accepts **only** `PARAM:<n>[/bit]`, `MACRO:<n>`, or PMC `letter + digits [.bit]`. A string beginning `1` fails at `char.IsLetter(src[0])` and returns `null`. - `FocasDriver.InitializeAsync` then logs *"FOCAS tag has invalid Address …; skipping"* and the tag never enters the poll table, so every read returns `BadNodeIdUnknown`. Confirmed by opening a committed tag in the AdminUI — its `Address` field reads `10.201.31.5:8193/Identity/SeriesNumber` verbatim. ## Why nothing catches it Three gates that look like they'd catch this, don't: 1. **`FocasTagConfigModel.Validate()` only checks the address is non-empty.** Authoring accepts it. 2. **The deploy gate passes.** Deployment `225e145e` reached `Sealed` / `In sync` with 20 dead tags in it. A sealed deployment is not evidence the tags work. 3. **Driver health stays green.** `focas-z-34184` shows **Healthy, errors/5 min = 0**, `Last error —`, with a live-advancing `Last read` — while silently ignoring 20 of its 22 tags. Unparseable addresses are skipped at *init* rather than counted as errors, so per-tag authoring mistakes are invisible in driver health. (3) is arguably the more dangerous half: an operator has no signal anywhere that their tags aren't live. ## Second defect, same area The **manual** FOCAS picker is broken the same way: - `src/Server/…/Pickers/FocasAddressBuilder.cs:14` — `Build(group, id) => $"{group}:{id}"` - `src/Server/…/Pickers/FOCASAddressPickerBody.razor:82` — defaults `_group = "axis"`, so the picker's initial value is `axis:5` `axis:5` is not a form `FocasAddress.TryParse` accepts either. So **only hand-typed `PARAM:` / `MACRO:` / PMC addresses currently work at all** — both UI-assisted authoring paths emit strings the runtime rejects. ## Suggested direction This is a design call, not a one-liner. Roughly: - **Strip `{deviceHost}/` at commit** — necessary but not sufficient: `Identity/SeriesNumber` is a *logical fixed-tree path*, not a PMC/PARAM/MACRO address, so the parser must also learn a fourth address form (e.g. `FIXED:<path>`) that resolves against `FixedTreeCache` rather than a FOCAS area read. Same treatment for the picker's `axis:` / `spindle:` groups. - **Or** re-materialize the fixed tree through a discovery/injection path instead of authored tags, accepting that it re-opens what #507 closed. Whichever way it goes, two guardrails are worth adding independently: - Make `FocasTagConfigModel.Validate()` actually run `FocasAddress.TryParse` so a bad address is rejected at authoring time and at the deploy gate, instead of at driver init where nothing surfaces it. - Surface skipped-at-init tags somewhere an operator sees them — a count on `/hosts`, or a degraded driver state — so "Healthy, 0 errors" cannot coexist with "ignoring most of my tags". ## Note for anyone reproducing On a real CNC the browse may report **duplicate sibling names** — this Makino returns two axis folders both called `AA`, which would collide as RawPath segments. `Axes` was excluded from this test for that reason, so the 20 tags above are the non-axis folders only. Worth deciding how commit should disambiguate duplicates before the axis path is fixed.
Author
Owner

Fixed on fix/gitea-540-focas-fixed-tree-addressing @ b61fe388. Both defects, plus the first of the two suggested guardrails. The second guardrail is fleet-wide and is now #541.

What was done

Taking the first of the two suggested directions. Re-materializing the fixed tree through discovery/injection would re-open what #507 closed, and it fights v3's model, where raw tags are authored explicitly and the served tree changes only on a config redeploy.

Within that direction the issue suggested stripping {deviceHost}/ at commit. It was fixed one step earlier instead — at what the driver emits — because a captured leaf's id IS its DriverAttributeInfo.FullName, and that one string feeds both the /raw browse-commit mapper and the manual picker's tree-select. Fixing the emitter repairs both paths at once, RawBrowseCommitMapper needs no FOCAS special-case, and there is exactly one canonical address form rather than one emitted and a different one stored.

The address now carries no device at all. A tag's device already comes from its raw-tag Device row, so a host in the address was a second, conflicting routing channel — which is the actual root cause, not just the parse failure.

Changes

  • FocasAddress gains FIXED:<path> and moves to Driver.FOCAS.Contracts, so the AdminUI and ControlPlane can parse an address without dragging the runtime driver into their build graphs (#526). Pure file move, same namespace.
  • The fixed tree emits FIXED:Identity/SeriesNumber etc. as its leaf reference. FixedTreeReference survives as the internal per-device cache key, now documented as such.
  • A FIXED: read is served from the poll-loop snapshot — no area read, no connection forced. A path the machine does not expose is a genuine BadNodeIdUnknown; a device that has not answered yet reports a comms fault instead, because the address may be fine. Writes refuse with BadNotWritable before opening a connection: nothing in that tree is a writable location on the CNC.
  • FocasCapabilityMatrix handles the new kind explicitly (no numeric range applies).

Two things the issue did not mention, found while fixing it

Both would have made the fix look complete and still not work in the field:

  1. FixedTree.Enabled defaults to false, and the universal browser force-enables it only for the duration of a browse. So the corrected flow — browse, commit, deploy — lands on a driver whose fixed-tree loop never starts: every tag Bad, with a perfectly correct address. Authoring any FIXED: tag now arms the loop by itself. The config flag remains the way to publish the tree without authoring anything.

  2. A dead CNC would have served its last fixed-tree values as Good forever. The poll loop swallows a failed tick by design (LogDebug + retry), and unlike an area read a fixed-tree read issues no traffic that could fail — so nothing marked the caches stale. That was harmless while nothing could bind to the tree. It is not harmless now. Values are dated from the last answered poll and go BadCommunicationError once it is older than 8 poll cadences (floored at 30 s).

Second defect — the manual picker

FocasAddressBuilder composed {group}:{id} over axis/spindle/program/status, none of which is a FOCAS addressing space. It now composes PMC / PARAM: / MACRO: through FocasAddress, so its output is parseable by construction and cannot drift from the parser. The picker UI follows: area-letter dropdown for PMC, optional bit with the right range per space, and an explicit "not a valid address" state instead of rendering an unusable string as if it were fine. FIXED: is deliberately absent from the manual composer — fixed-tree paths are discovered from the machine, not composed from parts.

Guardrail 1 (suggested, done)

  • FocasTagConfigModel.Validate() runs the real parser instead of a non-empty check, with a message naming the four legal forms and calling out the "pasted a browse id" case by name.
  • FocasTagDefinitionFactory.Inspect() — already wired into the deploy gate via EquipmentTagConfigInspector — warns on an unparseable address, which covers CSV import and the API, i.e. the paths that never touch the modal.

Guardrail 2 (suggested) → #541

Not done here, on purpose. Eight drivers (FOCAS, Modbus, S7, AbCip, AbLegacy, TwinCAT, Sql, Mqtt) skip-and-log unmappable tags identically, so a FOCAS-only counter would be the wrong shape. It also cannot simply be Degraded: that would interact badly with #527's comms-loss conjunction and reconnect-loop a healthy device whose tags were all skipped. Filed as #541 with a DriverHealthChanged + /hosts direction modelled on #521.

Tests

Written to assert addresses round-trip through the parser, not to match an expected string. The old FocasAddressBuilderTests pinned "axis:5" exactly — which is how a fully green suite coexisted with a picker whose default value nothing could read. The regression checks now sweep the whole emitted tree and the whole offered PMC surface rather than sampling.

Verified red-first: restoring the host-prefixed emitter fails 2 of the new tests.

FOCAS 308 · AdminUI 1095 · ControlPlane 169 · Core.Abstractions 265 · Commons 316 — all pass, 0 warnings under TreatWarningsAsErrors.

Still open, recorded rather than fixed

Duplicate sibling names. Your note stands: the Makino reports two axis folders both called AA. Commit-time collision detection already refuses to author both (same RawPath), so nothing silently binds wrong — but the runtime cache is keyed by the same display name too, so the second axis would overwrite the first. The Axes/ subtree is therefore not trustworthy on such a machine. Documented in docs/drivers/FOCAS.md as a caveat; disambiguation is a separate decision (suffix by index? by the CNC's axis number?) and did not belong in this fix.

Not yet on the box

wonder-app-vd03 still carries the 20 dead tags. They now serve as the live reproduction. Redeploying with this build should turn them Good without re-authoring — the tags' RawPaths do not change, only their addresses, so the browse-commit needs re-running to pick up the FIXED: form.

Fixed on `fix/gitea-540-focas-fixed-tree-addressing` @ `b61fe388`. Both defects, plus the first of the two suggested guardrails. The second guardrail is fleet-wide and is now #541. ## What was done Taking the **first** of the two suggested directions. Re-materializing the fixed tree through discovery/injection would re-open what #507 closed, and it fights v3's model, where raw tags are authored explicitly and the served tree changes only on a config redeploy. Within that direction the issue suggested stripping `{deviceHost}/` **at commit**. It was fixed one step earlier instead — at what the driver *emits* — because a captured leaf's id IS its `DriverAttributeInfo.FullName`, and that one string feeds both the `/raw` browse-commit mapper and the manual picker's tree-select. Fixing the emitter repairs both paths at once, `RawBrowseCommitMapper` needs no FOCAS special-case, and there is exactly one canonical address form rather than one emitted and a different one stored. **The address now carries no device at all.** A tag's device already comes from its raw-tag Device row, so a host in the address was a second, conflicting routing channel — which is the actual root cause, not just the parse failure. ### Changes - **`FocasAddress` gains `FIXED:<path>`** and moves to `Driver.FOCAS.Contracts`, so the AdminUI and ControlPlane can parse an address without dragging the runtime driver into their build graphs (#526). Pure file move, same namespace. - **The fixed tree emits `FIXED:Identity/SeriesNumber`** etc. as its leaf reference. `FixedTreeReference` survives as the internal per-device cache key, now documented as such. - **A `FIXED:` read is served from the poll-loop snapshot** — no area read, no connection forced. A path the machine does not expose is a genuine `BadNodeIdUnknown`; a device that has not answered yet reports a comms fault instead, because the address may be fine. Writes refuse with `BadNotWritable` before opening a connection: nothing in that tree is a writable location on the CNC. - **`FocasCapabilityMatrix`** handles the new kind explicitly (no numeric range applies). ### Two things the issue did not mention, found while fixing it Both would have made the fix look complete and still not work in the field: 1. **`FixedTree.Enabled` defaults to `false`,** and the universal browser force-enables it only for the duration of a browse. So the corrected flow — browse, commit, deploy — lands on a driver whose fixed-tree loop never starts: every tag Bad, with a perfectly correct address. **Authoring any `FIXED:` tag now arms the loop by itself.** The config flag remains the way to publish the tree without authoring anything. 2. **A dead CNC would have served its last fixed-tree values as `Good` forever.** The poll loop swallows a failed tick by design (`LogDebug` + retry), and unlike an area read a fixed-tree read issues no traffic that could fail — so nothing marked the caches stale. That was harmless while nothing could bind to the tree. It is not harmless now. Values are dated from the last answered poll and go `BadCommunicationError` once it is older than 8 poll cadences (floored at 30 s). ### Second defect — the manual picker `FocasAddressBuilder` composed `{group}:{id}` over `axis/spindle/program/status`, none of which is a FOCAS addressing space. It now composes PMC / `PARAM:` / `MACRO:` **through `FocasAddress`**, so its output is parseable by construction and cannot drift from the parser. The picker UI follows: area-letter dropdown for PMC, optional bit with the right range per space, and an explicit "not a valid address" state instead of rendering an unusable string as if it were fine. `FIXED:` is deliberately absent from the manual composer — fixed-tree paths are discovered from the machine, not composed from parts. ### Guardrail 1 (suggested, done) - `FocasTagConfigModel.Validate()` runs the real parser instead of a non-empty check, with a message naming the four legal forms and calling out the "pasted a browse id" case by name. - `FocasTagDefinitionFactory.Inspect()` — already wired into the deploy gate via `EquipmentTagConfigInspector` — warns on an unparseable address, which covers CSV import and the API, i.e. the paths that never touch the modal. ### Guardrail 2 (suggested) → #541 Not done here, on purpose. Eight drivers (FOCAS, Modbus, S7, AbCip, AbLegacy, TwinCAT, Sql, Mqtt) skip-and-log unmappable tags identically, so a FOCAS-only counter would be the wrong shape. It also cannot simply be `Degraded`: that would interact badly with #527's comms-loss conjunction and reconnect-loop a healthy device whose tags were all skipped. Filed as **#541** with a `DriverHealthChanged` + `/hosts` direction modelled on #521. ## Tests Written to assert addresses **round-trip through the parser**, not to match an expected string. The old `FocasAddressBuilderTests` pinned `"axis:5"` exactly — which is how a fully green suite coexisted with a picker whose default value nothing could read. The regression checks now sweep the whole emitted tree and the whole offered PMC surface rather than sampling. Verified red-first: restoring the host-prefixed emitter fails 2 of the new tests. `FOCAS 308` · `AdminUI 1095` · `ControlPlane 169` · `Core.Abstractions 265` · `Commons 316` — all pass, 0 warnings under `TreatWarningsAsErrors`. ## Still open, recorded rather than fixed **Duplicate sibling names.** Your note stands: the Makino reports two axis folders both called `AA`. Commit-time collision detection already refuses to author both (same RawPath), so nothing silently binds wrong — but the runtime cache is keyed by the same display name too, so the second axis would overwrite the first. The `Axes/` subtree is therefore not trustworthy on such a machine. Documented in `docs/drivers/FOCAS.md` as a caveat; disambiguation is a separate decision (suffix by index? by the CNC's axis number?) and did not belong in this fix. ## Not yet on the box `wonder-app-vd03` still carries the 20 dead tags. They now serve as the live reproduction. Redeploying with this build should turn them Good without re-authoring — the tags' RawPaths do not change, only their addresses, so the browse-commit needs re-running to pick up the `FIXED:` form.
Author
Owner

Closing — fixed, merged, and now proven on the box.

Merged to master 2026-08-05, merge 4e22f024 (branch fix/gitea-540-focas-fixed-tree-addressing, fix commit b61fe388 — see the comment above for the full record).

Deployed to wonder-app-vd03 the same day (box 4e461951 → 4e22f024; no EF migration in the delta, plain binary swap). The 20 dead tags had their addresses rewritten in place to the FIXED: form rather than being re-browsed — an exact 1:1 transform preserving every RawPath / Name / DataType / TagGroup / UNS reference, so nothing downstream rebound — and they now read 20/20 Good over OPC UA with real machine data (Series G431, 7 axes, mode AUTO, program //CNC_MEM/USER/LIBRARY/O1234), where they had been 0/20 since the v3 cutover. The "Not yet on the box" note above is superseded.

Both split-outs are resolved:

  • Guardrail 2 → #541, since FIXED and merged (355e59aa, 2026-08-06): every driver now reports its skipped tags on DriverHealthChanged, surfaced as the Tags column on /hosts — "Healthy, 0 errors" can no longer coexist silently with "ignoring most of my tags".
  • The duplicate-sibling-axis residual ("Still open, recorded rather than fixed") → #548, so it survives this close. Axes/ remains deliberately unauthored on the wonder Makino until that lands.
Closing — fixed, merged, and now proven on the box. **Merged to `master`** 2026-08-05, merge `4e22f024` (branch `fix/gitea-540-focas-fixed-tree-addressing`, fix commit `b61fe388` — see the comment above for the full record). **Deployed to `wonder-app-vd03` the same day** (box `4e461951` → `4e22f024`; no EF migration in the delta, plain binary swap). The 20 dead tags had their addresses rewritten in place to the `FIXED:` form rather than being re-browsed — an exact 1:1 transform preserving every RawPath / Name / DataType / TagGroup / UNS reference, so nothing downstream rebound — and they now read **20/20 Good** over OPC UA with real machine data (Series `G431`, 7 axes, mode `AUTO`, program `//CNC_MEM/USER/LIBRARY/O1234`), where they had been 0/20 since the v3 cutover. The "Not yet on the box" note above is superseded. **Both split-outs are resolved:** - Guardrail 2 → **#541**, since FIXED and merged (`355e59aa`, 2026-08-06): every driver now reports its skipped tags on `DriverHealthChanged`, surfaced as the Tags column on `/hosts` — "Healthy, 0 errors" can no longer coexist silently with "ignoring most of my tags". - The duplicate-sibling-axis residual ("Still open, recorded rather than fixed") → **#548**, so it survives this close. `Axes/` remains deliberately unauthored on the wonder Makino until that lands.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#540