docs(xmldoc): fill missing XML docs + strip tracking-ID comments across src
v2-ci / build (push) Failing after 41s
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
v2-ci / build (push) Failing after 41s
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
Adds <summary>/<param>/<returns>/<inheritdoc> where missing and removes project bookkeeping IDs (task/tracking refs) from shipped code comments, so the docs read cleanly and CommentChecker is quiet except for known false positives (PLC/protocol terms, event/IEqualityComparer inheritdoc). Doc/comment-only; no logic changed; solution builds clean.
This commit is contained in:
@@ -58,7 +58,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// Keyed by NodeId → the actual <see cref="FolderState"/> so <see cref="RebuildAddressSpace"/> can
|
||||
/// pass the folder to <c>RemoveRootNotifier</c> on teardown.</summary>
|
||||
private readonly Dictionary<NodeId, FolderState> _notifierFolders = new();
|
||||
/// <summary>Phase C (Task 4): event-notifier folder NodeId-identifier → the event-history source
|
||||
/// <summary>Phase C: event-notifier folder NodeId-identifier → the event-history source
|
||||
/// name passed to <see cref="IHistorianDataSource.ReadEventsAsync"/>. The equipment-folder NodeId
|
||||
/// identifier IS the equipment id, which IS the sourceName, so key and value are the same string;
|
||||
/// the map's presence (not its value) is what makes a folder an event-history source. Populated by
|
||||
@@ -168,7 +168,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// this source — so a single registered historian (e.g. Wonderware) serves many drivers' nodes,
|
||||
/// independent of any driver's lifecycle.
|
||||
/// <para>
|
||||
/// Set by the Host at <c>StartAsync</c> (Task 5). The <see cref="NullHistorianDataSource"/>
|
||||
/// Set by the Host at <c>StartAsync</c>. The <see cref="NullHistorianDataSource"/>
|
||||
/// default (assigning <c>null</c> restores it) means "no historian wired" → every read
|
||||
/// returns empty, so a historized node's HistoryRead surfaces <c>GoodNoData</c> rather than
|
||||
/// faulting. Backed by a <c>volatile</c> field (auto-properties can't be volatile) to make
|
||||
@@ -261,7 +261,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(nodeId);
|
||||
EnsureAddressSpaceCreated(); // OpcUaServer-004: fail legibly if called before the server started.
|
||||
EnsureAddressSpaceCreated();
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
@@ -297,7 +297,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(alarmNodeId);
|
||||
ArgumentNullException.ThrowIfNull(state);
|
||||
EnsureAddressSpaceCreated(); // OpcUaServer-004: fail legibly if called before the server started.
|
||||
EnsureAddressSpaceCreated();
|
||||
|
||||
// Look up + project under a SINGLE Lock so a concurrent RebuildAddressSpace can't clear
|
||||
// _alarmConditions / detach the condition node between the lookup and the Set* calls.
|
||||
@@ -573,6 +573,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// <param name="displayName">Human-readable condition name (BrowseName / DisplayName / Message / ConditionName).</param>
|
||||
/// <param name="alarmType">Domain alarm type — maps to the SDK condition subtype (see remarks).</param>
|
||||
/// <param name="severity">Domain severity (treated as an OPC UA 1..1000 severity); mapped to <see cref="EventSeverity"/>.</param>
|
||||
/// <param name="isNative">True when the condition is a NATIVE (driver-fed, e.g. Galaxy) alarm rather than a scripted one.</param>
|
||||
/// <remarks>
|
||||
/// <para><b>AlarmType → SDK subtype mapping.</b> Script-driven alarms have no OPC limit /
|
||||
/// setpoint values, so any limit-style subtype would have unset limit children. We therefore
|
||||
@@ -586,7 +587,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(alarmNodeId);
|
||||
ArgumentException.ThrowIfNullOrEmpty(displayName);
|
||||
EnsureAddressSpaceCreated(); // OpcUaServer-004: fail legibly if called before the server started.
|
||||
EnsureAddressSpaceCreated();
|
||||
|
||||
lock (Lock)
|
||||
{
|
||||
@@ -693,7 +694,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
// (ScriptedAlarmEngine.EnableAsync/DisableAsync, dispatched by ScriptedAlarmHostActor on the
|
||||
// "Enable"/"Disable" AlarmCommand operations), so a scripted condition routes through the same
|
||||
// AlarmAck-gated HandleAlarmCommand as the other handlers. NATIVE (driver-fed) conditions have no
|
||||
// engine enable/disable surface (Phase 3 decision #2) — they short-circuit to BadNotSupported.
|
||||
// engine enable/disable surface — they short-circuit to BadNotSupported.
|
||||
alarm.OnEnableDisable = (context, condition, enabling) =>
|
||||
{
|
||||
if (IsNativeAlarmNode(alarmNodeId))
|
||||
@@ -720,6 +721,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// (driver-fed) alarm rather than a scripted one. A later task uses this to route a native condition's
|
||||
/// inbound Acknowledge to the driver instead of the scripted engine.</summary>
|
||||
/// <param name="alarmNodeId">The alarm condition node id.</param>
|
||||
/// <returns>True when the condition is native (driver-fed); false when it is scripted or not found.</returns>
|
||||
internal bool IsNativeAlarmNode(string alarmNodeId)
|
||||
{
|
||||
// _nativeAlarmNodeIds is a plain HashSet mutated only under Lock (in MaterialiseAlarmCondition /
|
||||
@@ -811,7 +813,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="NodeValueEventHandler"/> attached to a writable equipment-tag variable by
|
||||
/// <see cref="EnsureVariable"/> (Task 11). The OPC UA SDK invokes it when a client writes the
|
||||
/// <see cref="EnsureVariable"/>. The OPC UA SDK invokes it when a client writes the
|
||||
/// node's Value. It resolves the calling principal off the SDK <paramref name="context"/> the
|
||||
/// SAME way <see cref="HandleAlarmCommand"/> does, gates on the
|
||||
/// <see cref="OpcUaDataPlaneRoles.WriteOperate"/> role + the gateway being wired
|
||||
@@ -1230,7 +1232,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// register it as a root notifier (idempotent — guarded by <see cref="_notifierFolders"/>) so the
|
||||
/// alarm condition has a notifier path to the Server object for T16's event propagation.</summary>
|
||||
/// <remarks>
|
||||
/// Phase C (Task 4): when a real historian is wired at promotion time (the source is NOT the
|
||||
/// Phase C: when a real historian is wired at promotion time (the source is NOT the
|
||||
/// <see cref="NullHistorianDataSource"/>), the folder ALSO gets the
|
||||
/// <see cref="EventNotifiers.HistoryRead"/> bit OR-ed in (keeping SubscribeToEvents) and registers
|
||||
/// its NodeId identifier as an event-history source so the <see cref="HistoryReadEvents"/> override
|
||||
@@ -1272,7 +1274,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// <summary>
|
||||
/// Ensure a folder node exists at <paramref name="folderNodeId"/> with the given display
|
||||
/// name, parented under <paramref name="parentNodeId"/> (or the namespace root when null).
|
||||
/// #85 — used by <see cref="AddressSpaceApplier"/> to materialise the UNS Area/Line/Equipment
|
||||
/// Used by <see cref="AddressSpaceApplier"/> to materialise the UNS Area/Line/Equipment
|
||||
/// folder hierarchy. Idempotent: the second call with the same id returns the cached
|
||||
/// folder so adding child variables under it still works.
|
||||
/// </summary>
|
||||
@@ -1283,7 +1285,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(folderNodeId);
|
||||
ArgumentException.ThrowIfNullOrEmpty(displayName);
|
||||
EnsureAddressSpaceCreated(); // OpcUaServer-004: fail legibly if called before the server started.
|
||||
EnsureAddressSpaceCreated();
|
||||
|
||||
if (_folders.ContainsKey(folderNodeId)) return;
|
||||
|
||||
@@ -1310,7 +1312,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// <summary>
|
||||
/// Update an EXISTING folder node's <see cref="FolderState.DisplayName"/> in place and notify
|
||||
/// subscribers, WITHOUT a rebuild — the in-place counterpart of <see cref="EnsureFolder"/> for a
|
||||
/// UNS Area / Line rename (OpcUaServer-001). <see cref="EnsureFolder"/> early-returns for an
|
||||
/// UNS Area / Line rename. <see cref="EnsureFolder"/> early-returns for an
|
||||
/// already-present folder id and never touches an existing folder's display name, so a
|
||||
/// rename-only deploy would otherwise be invisible until a full <see cref="RebuildAddressSpace"/>
|
||||
/// cleared <c>_folders</c>. The NodeId + BrowseName are unchanged (a rename only touches the
|
||||
@@ -1351,7 +1353,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// <param name="dataType">The OPC UA data type name (e.g., "Boolean", "Int32", "String").</param>
|
||||
/// <param name="writable">When true the node is created <c>CurrentReadWrite</c> (an authored
|
||||
/// ReadWrite equipment tag) and the inbound-write handler <see cref="OnEquipmentTagWrite"/> is attached
|
||||
/// to its <c>OnWriteValue</c> (Task 11) so a client write gates on the <c>WriteOperate</c> role + routes
|
||||
/// to its <c>OnWriteValue</c> so a client write gates on the <c>WriteOperate</c> role + routes
|
||||
/// to the backing driver; when false it stays <c>CurrentRead</c> (read-only) with no write handler.</param>
|
||||
/// <param name="historianTagname">Phase C: null ⇒ the node is NOT historized (Historizing=false, no
|
||||
/// HistoryRead bit, not registered). Non-null ⇒ the node is created <c>Historizing</c> with the
|
||||
@@ -1368,7 +1370,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrEmpty(variableNodeId);
|
||||
ArgumentException.ThrowIfNullOrEmpty(displayName);
|
||||
EnsureAddressSpaceCreated(); // OpcUaServer-004: fail legibly if called before the server started.
|
||||
EnsureAddressSpaceCreated();
|
||||
|
||||
// If already present, leave it alone (idempotent re-applies).
|
||||
if (_variables.ContainsKey(variableNodeId)) return;
|
||||
@@ -1401,7 +1403,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
StatusCode = StatusCodes.BadWaitingForInitialData,
|
||||
Timestamp = DateTime.MinValue,
|
||||
};
|
||||
// Task 11: a writable equipment tag owns an inbound-write handler. The SDK invokes
|
||||
// A writable equipment tag owns an inbound-write handler. The SDK invokes
|
||||
// OnWriteValue on a client write; it gates on the WriteOperate role and routes to the backing
|
||||
// driver via NodeWriteGateway. Read-only nodes leave the handler unattached so a write is
|
||||
// rejected by the SDK's own AccessLevel check before it ever reaches a handler.
|
||||
@@ -1437,7 +1439,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// in place and notify subscribers, WITHOUT a rebuild — so client MonitoredItems on the node survive.
|
||||
/// Returns false when the node id is unknown (caller falls back to a full rebuild).
|
||||
/// <para>
|
||||
/// FB-7: when the shape (DataType / ValueRank / ArrayDimensions) ACTUALLY changes, two extra things
|
||||
/// When the shape (DataType / ValueRank / ArrayDimensions) ACTUALLY changes, two extra things
|
||||
/// happen. (1) The node's value is reset to <c>BadWaitingForInitialData</c> (Value=null) so no value
|
||||
/// still typed to the OLD DataType is exposed between this update and the driver's next publish — the
|
||||
/// same fresh-node state <see cref="EnsureVariable"/> creates, which closes the "brief value-type
|
||||
@@ -1476,7 +1478,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
if (historized) _historizedTagnames[variableNodeId] = historianTagname!;
|
||||
else _historizedTagnames.TryRemove(variableNodeId, out _);
|
||||
|
||||
// FB-7: swap DataType + ValueRank + ArrayDimensions in place, but only when they actually differ
|
||||
// Swap DataType + ValueRank + ArrayDimensions in place, but only when they actually differ
|
||||
// from the live node — a Writable/Historizing-only change leaves the shape untouched (no value
|
||||
// reset, no model-change event), preserving the original surgical behaviour byte-for-byte.
|
||||
var newDataType = ResolveBuiltInDataType(dataType);
|
||||
@@ -1727,7 +1729,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
// Drop the notifier-folder guard so re-materialised alarms re-promote their (rebuilt)
|
||||
// equipment folders to event notifiers.
|
||||
_notifierFolders.Clear();
|
||||
// Phase C (Task 4): drop the event-history source registrations alongside the notifier folders
|
||||
// Phase C: drop the event-history source registrations alongside the notifier folders
|
||||
// they map; re-materialised alarms re-register them (with the HistoryRead bit) on re-promotion.
|
||||
_eventNotifierSources.Clear();
|
||||
}
|
||||
@@ -1740,7 +1742,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
return _folders.TryGetValue(parentNodeId, out var existing) ? existing : _root!;
|
||||
}
|
||||
|
||||
/// <summary>OpcUaServer-004: guard the address-space mutators against a too-early call. <c>_root</c>
|
||||
/// <summary>Guard the address-space mutators against a too-early call. <c>_root</c>
|
||||
/// is only assigned in <see cref="CreateAddressSpace"/>, which the SDK invokes during
|
||||
/// <c>StandardServer</c> start; every public mutator (<see cref="WriteValue"/>,
|
||||
/// <see cref="WriteAlarmCondition"/>, <see cref="EnsureFolder"/>, <see cref="EnsureVariable"/>,
|
||||
@@ -1766,7 +1768,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
// that carry the HistoryRead access bit), validates the timestamp args, handles
|
||||
// `releaseContinuationPoints`, and dispatches by `details` runtime type to the per-details
|
||||
// protected virtuals below. We override all four arms: the three variable-history virtuals
|
||||
// (Raw/Processed/AtTime) and the event-history arm (HistoryReadEvents, Task 4). Each override
|
||||
// (Raw/Processed/AtTime) and the event-history arm (HistoryReadEvents). Each override
|
||||
// receives the pre-filtered handles and fills results[handle.Index] / errors[handle.Index] —
|
||||
// handle.Index is the original index into the service-level results/errors lists, seeded by the
|
||||
// base. The base pre-seeds every handle's error to BadHistoryOperationUnsupported, so a handle
|
||||
@@ -1778,18 +1780,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
// backend throw) never throws out of the batch.
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Serve a HistoryRead-Raw request over the pre-filtered historized variable handles, dispatching
|
||||
/// each to <see cref="IHistorianDataSource.ReadRawAsync"/>. Modified-history reads
|
||||
/// (<c>IsReadModified</c>) are unsupported — we don't serve a modified-value history surface.
|
||||
/// <para>
|
||||
/// Raw is the only arm that pages server-side: <c>ReadRawModifiedDetails</c> carries a client
|
||||
/// count cap (<c>NumValuesPerNode</c>), so a page that returns exactly that many samples MAY
|
||||
/// have more behind it ⇒ a time-based continuation point is emitted (see
|
||||
/// <see cref="ServeRawPaged"/>). An inbound continuation point on a node resumes its stored
|
||||
/// read. <c>NumValuesPerNode == 0</c> ("all values") never pages.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
protected override void HistoryReadRawModified(
|
||||
ServerSystemContext context,
|
||||
ReadRawModifiedDetails details,
|
||||
@@ -1816,15 +1807,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serve a HistoryRead-Processed request, mapping each node's per-node aggregate NodeId (from the
|
||||
/// parallel <c>AggregateType</c> collection — the base guarantees it is the same length as
|
||||
/// <c>nodesToRead</c>) to a <see cref="HistoryAggregateType"/> and dispatching to
|
||||
/// <see cref="IHistorianDataSource.ReadProcessedAsync"/>. An unknown aggregate yields
|
||||
/// <c>BadAggregateNotSupported</c> for that node. Single-shot (no continuation point):
|
||||
/// <c>ReadProcessedDetails</c> carries no client count cap — the bucket count is deterministic
|
||||
/// (window / interval) — so there is no "full page" signal to page on.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
protected override void HistoryReadProcessed(
|
||||
ServerSystemContext context,
|
||||
ReadProcessedDetails details,
|
||||
@@ -1864,12 +1847,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serve a HistoryRead-AtTime request, dispatching the requested timestamps to
|
||||
/// <see cref="IHistorianDataSource.ReadAtTimeAsync"/>. Single-shot (no continuation point):
|
||||
/// AtTime carries no client count cap — the request IS the timestamp list and the result is
|
||||
/// exactly one sample per requested timestamp — so there is no "full page" signal to page on.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
protected override void HistoryReadAtTime(
|
||||
ServerSystemContext context,
|
||||
ReadAtTimeDetails details,
|
||||
@@ -1891,18 +1869,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serve a HistoryRead-Events request over the equipment-folder event-notifier nodes (the folders
|
||||
/// that own alarm conditions). Each handle's NodeId identifier is resolved against
|
||||
/// <see cref="_eventNotifierSources"/>: a miss ⇒ <c>BadHistoryOperationUnsupported</c> (a node we own
|
||||
/// that isn't a registered event-history source — e.g. a plain folder, or one promoted while no
|
||||
/// historian was wired); a hit block-bridges to <see cref="IHistorianDataSource.ReadEventsAsync"/>
|
||||
/// for the folder's source name and projects each <see cref="HistoricalEvent"/> into a
|
||||
/// <see cref="HistoryEventFieldList"/> per the request's event filter. Like the Raw/Processed/AtTime
|
||||
/// arms this is NOT invoked under the node-manager <c>Lock</c>, so the block-bridge is safe; each
|
||||
/// handle is served under try/catch so a backend throw becomes a Bad status for THAT node only and
|
||||
/// never throws out of the batch.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
protected override void HistoryReadEvents(
|
||||
ServerSystemContext context,
|
||||
ReadEventDetails details,
|
||||
@@ -1936,7 +1903,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
sourceName,
|
||||
details.StartTime,
|
||||
details.EndTime,
|
||||
// OpcUaServer-002: NumValuesPerNode==0 means "no limit — return ALL values" per OPC UA
|
||||
// NumValuesPerNode==0 means "no limit — return ALL values" per OPC UA
|
||||
// Part 4/11, so translate it to UNBOUNDED (int.MaxValue) here. Passing the int<=0
|
||||
// backend-default-cap sentinel instead would silently truncate a "give me everything"
|
||||
// events read at the backend default. A positive cap passes through (clamped).
|
||||
@@ -1974,7 +1941,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
/// <returns>The clamped non-negative int.</returns>
|
||||
private static int ClampToInt(uint value) => value > int.MaxValue ? int.MaxValue : (int)value;
|
||||
|
||||
/// <summary>OpcUaServer-002: map a HistoryRead-Events <c>NumValuesPerNode</c> request cap onto the
|
||||
/// <summary>Map a HistoryRead-Events <c>NumValuesPerNode</c> request cap onto the
|
||||
/// <see cref="IHistorianDataSource.ReadEventsAsync"/> <c>maxEvents</c> argument, honouring the OPC UA
|
||||
/// Part 4/11 semantics that <c>NumValuesPerNode == 0</c> means "no limit — return ALL values".
|
||||
/// We translate 0 to UNBOUNDED (<see cref="int.MaxValue"/>) — a very large positive cap — rather than
|
||||
@@ -2315,14 +2282,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop the resume state for any continuation points the client asked to release
|
||||
/// (<c>releaseContinuationPoints == true</c>) and return WITHOUT reading data, per OPC UA Part 4.
|
||||
/// The base dispatcher routes a release-only HistoryRead here (it never reaches the per-details
|
||||
/// arms), so this is the single place that must free Raw's stored cursors. Each handle's released
|
||||
/// point is <c>nodesToRead[handle.Index].ContinuationPoint</c>; releasing an unknown / null point
|
||||
/// is a harmless no-op. Errors are left Good (the base pre-seeds them) — a release does not fail.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
protected override void HistoryReleaseContinuationPoints(
|
||||
ServerSystemContext context,
|
||||
IList<HistoryReadValueId> nodesToRead,
|
||||
|
||||
Reference in New Issue
Block a user