3efcf8014b
Wave B of Batch 4 — the runtime binding seam for the dual namespace. Applier (both realms, explicit realm at every sink call site): - MaterialiseRawSubtree: Raw containers as folders + Raw tags as variables keyed by RawPath (native-alarm tag → single Part 9 condition at the RawPath), all in AddressSpaceRealm.Raw; historian tagname = override else RawPath. - MaterialiseUnsReferences: each UNS reference Variable under its equipment folder (Uns realm) + an Organizes UNS->Raw edge; inherits writable/array/ historian tagname from the backing raw tag (both NodeIds -> one tagname). - FeedHistorizedRefs / ProvisionHistorizedTags now source RAW tags (mux ref stays single, keyed by RawPath); ApplyPureRemove tears down raw tags + UNS refs in place (raw-container removal falls back to rebuild). DriverHostActor (dual-NodeId, single-source fan-out): - _nodeIdByDriverRef value gains a realm (NodeRealmRef); rebuilt from RawTags UNION UnsReferenceVariables so one (DriverInstanceId, RawPath) fans to the raw NodeId AND every referencing UNS NodeId with identical value/quality/timestamp. Write inverse map keyed by the bare id; the ns-qualified NodeId the write hook passes is normalised (BareNodeId) so a write to either NodeId resolves the same driver ref (-> RawPath write). - Native raw alarm condition routing is realm-tagged (Raw); AttributeValueUpdate + AlarmStateUpdate carry the realm through to the sink. Retire EquipmentNodeIds -> V3NodeIds.Uns (applier/VirtualTagHostActor) and RawPaths.Combine (DiscoveredNodeMapper, discovered nodes are Raw now). DeploymentArtifact.ParseComposition emits the Raw + UNS subtrees byte-parity with the composer (reconstruct entities -> AddressSpaceComposer.Compose). Sink surface: removed the transitional `= AddressSpaceRealm.Uns` defaults from IOpcUaAddressSpaceSink / ISurgicalAddressSpaceSink / SdkAddressSpaceSink / DeferredAddressSpaceSink / NullOpcUaAddressSpaceSink — every call site is now explicit (realm reordered before the trailing optionals on EnsureVariable + MaterialiseAlarmCondition). Node-manager convenience methods keep their defaults (they are not the interface impl; Sdk delegates explicitly). Tests: rewrote DriverHostActorLiveValueTests (fan-out drift, 1:N) + DriverHostActorWriteRoutingTests (dual-NodeId raw/uns write routing) to the v3 raw+uns model; new AddressSpaceApplierRawUnsTests; migrated the EquipmentTags provisioning/feed tests to RawTags; swept EquipmentNodeIds test callers. Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
74 lines
6.5 KiB
C#
74 lines
6.5 KiB
C#
namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|
|
|
/// <summary>Optional capability on an address-space sink: surgical in-place attribute updates on an
|
|
/// EXISTING variable node, used by AddressSpaceApplier to avoid a full RebuildAddressSpace for pure-property
|
|
/// tag changes (Writable / Historizing / DataType / array-shape). A sink that does not implement it ⇒ caller
|
|
/// falls back to a full rebuild (safe default).</summary>
|
|
public interface ISurgicalAddressSpaceSink
|
|
{
|
|
/// <summary>Update an existing variable node's surgically-updatable attributes IN PLACE, notifying
|
|
/// subscribers (ClearChangeMasks) without a rebuild — so client MonitoredItems on the node survive.
|
|
/// Covers Writable (AccessLevel + inbound-write handler), Historizing (+ historian-tagname binding),
|
|
/// and the node's presentation shape: <paramref name="dataType"/> (the OPC UA DataType) and
|
|
/// <paramref name="isArray"/>/<paramref name="arrayLength"/> (ValueRank + ArrayDimensions). When the
|
|
/// shape actually changes, the implementation resets the node's value to BadWaitingForInitialData
|
|
/// (no stale wrong-typed value is exposed until the driver republishes) and raises a Part 3
|
|
/// GeneralModelChangeEvent (verb=DataTypeChanged) so model-aware clients re-read the node definition.
|
|
/// Returns false if the node does not exist (caller should rebuild instead).</summary>
|
|
/// <param name="variableNodeId">The folder-scoped node id of the variable to update in place.</param>
|
|
/// <param name="writable">When true the node becomes read/write (with the inbound-write handler); otherwise read-only.</param>
|
|
/// <param name="historianTagname">null ⇒ not historized; non-null ⇒ Historizing with the HistoryRead bit and tagname binding.</param>
|
|
/// <param name="dataType">The OPC UA built-in data type name (e.g. "Boolean", "Int32", "Float").</param>
|
|
/// <param name="isArray">When true the node is a 1-D array (ValueRank=OneDimension); when false scalar.</param>
|
|
/// <param name="arrayLength">The declared length of the 1-D array when <paramref name="isArray"/> is true; ignored for scalars.</param>
|
|
/// <param name="realm">The namespace realm <paramref name="variableNodeId"/> lives in — the sink resolves the
|
|
/// namespace index from the realm rather than parsing it out of the id string.</param>
|
|
/// <returns>True when the in-place update was applied; false when the node is missing (caller rebuilds).</returns>
|
|
bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname, string dataType, bool isArray, uint? arrayLength, AddressSpaceRealm realm);
|
|
|
|
/// <summary>Update an existing folder node's <c>DisplayName</c> IN PLACE, notifying subscribers
|
|
/// (ClearChangeMasks) without a rebuild — used by AddressSpaceApplier to apply a UNS Area / Line
|
|
/// rename without tearing down + repopulating the whole address space (which would drop every
|
|
/// client's MonitoredItems). The folder's NodeId is unchanged (a rename only touches the friendly
|
|
/// browse/display name, not the logical id). Returns false if the folder does not exist (caller
|
|
/// should rebuild instead).</summary>
|
|
/// <param name="folderNodeId">The node id of the folder whose display name to update in place.</param>
|
|
/// <param name="displayName">The new display name to apply.</param>
|
|
/// <param name="realm">The namespace realm <paramref name="folderNodeId"/> lives in.</param>
|
|
/// <returns>True when the in-place update was applied; false when the folder is missing (caller rebuilds).</returns>
|
|
bool UpdateFolderDisplayName(string folderNodeId, string displayName, AddressSpaceRealm realm);
|
|
|
|
/// <summary>Remove ONE existing value-variable node IN PLACE (detach from its parent, drop it from the
|
|
/// predefined-node set, drop any historized-tagname registration), then raise a Part 3
|
|
/// GeneralModelChangeEvent (verb=NodeDeleted) outside the lock — so subscribers of OTHER nodes are
|
|
/// untouched and their MonitoredItems survive. The caller has already written a final Bad quality to the
|
|
/// node so in-flight MonitoredItems on the removed node observe the removal; after the node is gone,
|
|
/// re-subscription attempts get BadNodeIdUnknown from the SDK. Returns false when the node id is unknown
|
|
/// (the node-manager maps drifted from the plan — caller falls back to a full rebuild).</summary>
|
|
/// <param name="variableNodeId">The folder-scoped node id of the variable to remove in place.</param>
|
|
/// <param name="realm">The namespace realm <paramref name="variableNodeId"/> lives in.</param>
|
|
/// <returns>True when the node was removed; false when the id is unknown (caller rebuilds).</returns>
|
|
bool RemoveVariableNode(string variableNodeId, AddressSpaceRealm realm);
|
|
|
|
/// <summary>Remove ONE existing Part 9 alarm-condition node IN PLACE (detach, drop from the
|
|
/// predefined-node set, clear the native-alarm flag), then raise NodeDeleted outside the lock. The
|
|
/// caller has already written the terminal RemovedConditionState (Retain=false) so a removed condition
|
|
/// stops replaying on ConditionRefresh. Returns false when the condition id is unknown (caller
|
|
/// rebuilds).</summary>
|
|
/// <param name="alarmNodeId">The alarm condition node id (== ScriptedAlarmId, or a native alarm tag's folder-scoped id).</param>
|
|
/// <param name="realm">The namespace realm <paramref name="alarmNodeId"/> lives in.</param>
|
|
/// <returns>True when the condition was removed; false when the id is unknown (caller rebuilds).</returns>
|
|
bool RemoveAlarmConditionNode(string alarmNodeId, AddressSpaceRealm realm);
|
|
|
|
/// <summary>Remove an equipment folder AND its entire descendant subtree (sub-folders, value variables,
|
|
/// condition nodes) IN PLACE: per-descendant map/registration cleanup (variables, historized tagnames,
|
|
/// conditions, native flags), notifier demotion (sever the Server↔folder root-notifier ref +
|
|
/// notifier-folder + event-notifier-source registrations for the equipment id), then one NodeDeleted for
|
|
/// the equipment folder outside the lock. Returns false when the folder id is unknown (caller
|
|
/// rebuilds).</summary>
|
|
/// <param name="equipmentNodeId">The equipment folder node id whose entire subtree to remove.</param>
|
|
/// <param name="realm">The namespace realm <paramref name="equipmentNodeId"/> lives in.</param>
|
|
/// <returns>True when the subtree was removed; false when the id is unknown (caller rebuilds).</returns>
|
|
bool RemoveEquipmentSubtree(string equipmentNodeId, AddressSpaceRealm realm);
|
|
}
|