diff --git a/CLAUDE.md b/CLAUDE.md index 2d0701b4..3244f490 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1105,6 +1105,31 @@ host logger factory (wired in `DriverFactoryBootstrap`; the FOCAS and TwinCAT fa primary-host claim backs off 2 s → 30 s; writes encode the birth's own datatype (unsupported birth types `BadNotSupported`). - **FOCAS / AbLegacy:** background loops are joined (≤ 5 s) on shutdown. +- **Re-Initialize without teardown (#630):** FOCAS, AbLegacy, AbCip, TwinCAT, S7 and OpcUaClient `InitializeAsync` + first release the prior device/session state (cancel, ≤ 5 s join, dispose — incl. the AbCip/FOCAS alarm + projections) — the device-state half of `ShutdownAsync` only; the poll engine is kept and the actor re-subscribes + after every successful init. AbCip publishes fresh tag tables by reference swap (a second init used to throw + "tag name collision" on `AdoptConfigDuringInit`); S7 swaps `Plc` under `_gate`. +- **S7 (#632/#638):** STRING/WSTRING/CHAR/WCHAR and DTL/DATE/TOD/TIME/S5TIME codecs (`S7ValueCodecs`; TOD/TIME/S5TIME + surface as Int32 ms); an undecodable buffer or unrepresentable write is `BadOutOfRange` with no health impact. + Optional `localTsap`/`remoteTsap`/`connectionType` override the slot TSAP (`remoteTsap` ⊕ `connectionType`). Test + Connect reads 2 bytes of `MB0` so a PUT/GET-disabled CPU fails the probe. The negotiated PDU size is logged and on + `S7Driver.GetDiagnostics()` only — there is no driver-diagnostics surface on `/hosts`. +- **TwinCAT (#637):** TIME/TOD publish Float64 ms, DATE/DT a UTC DateTime; a tag authored `UDInt` keeps the raw counter + (and keeps historizing — DateTime is not historized). +- **AbLegacy (#636):** `AbLegacyAddress` (in `AbLegacy.Contracts`) is family-aware: PLC-5 `I:`/`O:` indices are OCTAL + and are emitted to libplctag as decimal (libplctag's `pccc.c` parses decimal only). MicroLogix function files and + indirect `N7:[N7:0]` addresses parse but read `BadNotSupported`. One check (`AbLegacyAddress.Validate`) backs the + editor (family-independent) and the deploy gate (family from the tag's `DeviceConfig.PlcFamily`); an unparseable + address reads `BadNodeIdUnknown` for that tag only. +- **OpcUaClient (#635):** OperationLimits are honoured by the SDK session (`SessionClientBatched`) — do not re-add + driver-side chunking. Subscription tuning keys are range-checked at `ParseOptions`. `discoveryUrl` endpoints go + AFTER configured ones; more than one discovered server without `discoveryServerUri` fails the connect. Session + events are the OTel counter `otopcua.driver.opcuaclient.events`. `HistoryAggregateType` carries the full Part 13 + set; an aggregate the gateway can't serve is `BadAggregateNotSupported` per node. +- **MTConnect (#627/#628):** TIME_SERIES reads as a variable-length `double[]` (Float64 only); a count disagreeing + with `sampleCount` is `BadDecodingError`. One MTConnect driver = one agent client = at most one `/raw` Device + (`MTConnectMultipleDevices` deploy error; `RawTreeService.CreateDeviceAsync` refuses a second). - **Driver removal (#609):** removing a driver releases its capability-invoker pipelines and resilience tracker, *then* evicts its telemetry snapshots. ⚠️ Both are **tombstoned**, not just cleared: the removed child stops asynchronously and its in-flight subscribe/connect still completes through the invoker (and @@ -1193,6 +1218,15 @@ ack is refused `BadInvalidState` off-Primary** via `NativeAlarmAckPrimaryGate` ( changes, so the boot window before the first redundancy snapshot is closed — the same fix covers the MQTT Sparkplug primary-host claim). A native re-ack is still re-sent to the driver (the retry path, by design). +**Condition children are instance-scoped (#629).** Every child of a materialised condition carries +`{ConditionId}/{browse path}` in the condition's namespace (e.g. `…/temp_hi/ShelvingState/UnshelveTime`), assigned by +the `OtOpcUaNodeManager.New` override — the SDK default kept each child on the TYPE's ns=0 id, so a direct Read hit +the type (or `BadNodeIdUnknown`), translate failed, and children sat outside the ACL (the slash join now puts them +under their condition's scope). ⚠️ The shelve methods resolve by the `AlarmConditionType_ShelvingState_*` ids only +because `New` adopts those (a fixed set) as their `MethodDeclarationId`; calls by the `ShelvedStateMachineType_*` id +are no longer supported (pinned by a test). Condition teardown must forget the whole `PredefinedNodes` subtree +(`ForgetPredefinedSubtree`) or a removed condition's children keep answering. + ## Historian / HistoryRead **Backend: HistorianGateway (sole historian backend).** As of the gateway-integration cutover, the