2124f21ab6
v2-ci / build (pull_request) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped
HistorianGateway is now the sole historian backend (read + alarm SendEvent + continuous WriteLiveValues). Document the final state and retire the Wonderware sidecar from the docs/config/labels: - CLAUDE.md: rewrite the Historian section — ServerHistorian / ContinuousHistorization / AlarmHistorian config keys, the IHistorianProvisioning EnsureTags hook, the GatewayAlarmHistorianWriter SendEvent path + ReadEvents dependency on gateway RuntimeDb:EventReadsEnabled=true, gateway-side prerequisites (RuntimeDb flags + historian:read/write/tags:write scopes), migration note, and two KNOWN-LIMITATION callouts (live-validation gate + empty historized-ref-set recorder follow-on). - appsettings.json: fix the stale ServerHistorian block (Host/Port/SharedSecret/ ServerCertThumbprint -> Endpoint/ApiKey/UseTls/AllowUntrustedServerCertificate/ CaCertificatePath/CallTimeout, keep MaxTieClusterOverfetch); add a disabled ContinuousHistorization block; prune the orphaned Wonderware keys from AlarmHistorian (keep the SQLite knobs). ApiKey env-supplied via ServerHistorian__ApiKey (commented; valid strict JSON via _comment keys). - README.md + docs (Historian.md, AlarmHistorian.md, Configuration.md, ServiceHosting.md, DriverLifecycle.md, drivers/README.md, Uns.md, VirtualTags.md, AlarmTracking.md, Client.UI.md, README.md, TestConnectProbes.md): retire the Wonderware historian backend from current-backend descriptions; fix the stale ServerHistorian/AlarmHistorian config tables (now gateway shape); convert drivers/Historian.Wonderware.md to a retired stub pointing at the gateway. - Source/UI labels (descriptive text only, no behavior change): OtOpcUaServerHostedService.cs, HistoryPaging.cs, OtOpcUaSdkServer.cs, HistorianAdapterActor.cs, VirtualTagModal.razor, ScriptedAlarmModal.razor, AlarmsHistorian.razor now name the HistorianGateway backend. Build clean (0 errors); AdminUI.Tests green (514 passed). Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
114 lines
6.6 KiB
C#
114 lines
6.6 KiB
C#
using Opc.Ua;
|
|
using Opc.Ua.Server;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
|
|
|
/// <summary>
|
|
/// <see cref="StandardServer"/> subclass that wires in the v2 <see cref="OtOpcUaNodeManager"/>.
|
|
/// Exposes the live node manager after start so callers (<see cref="OpcUaApplicationHost"/>,
|
|
/// the fused Host's DI binding) can wrap it in a <see cref="SdkAddressSpaceSink"/> and hand
|
|
/// it to <c>OpcUaPublishActor</c>.
|
|
/// </summary>
|
|
public sealed class OtOpcUaSdkServer : StandardServer
|
|
{
|
|
private OtOpcUaNodeManager? _otOpcUaNodeManager;
|
|
|
|
/// <summary>The custom node manager once <c>StartAsync</c> has called
|
|
/// <see cref="CreateMasterNodeManager"/>. Null until the SDK has bootstrapped.</summary>
|
|
public OtOpcUaNodeManager? NodeManager => _otOpcUaNodeManager;
|
|
|
|
/// <summary>
|
|
/// Wire the reverse-path sink for inbound Part 9 alarm method calls onto the created
|
|
/// <see cref="OtOpcUaNodeManager"/>. The host calls this after start with a non-blocking
|
|
/// <c>mediator.Tell</c> that publishes each <see cref="AlarmCommand"/> onto the
|
|
/// <c>alarm-commands</c> DistributedPubSub topic. No-op (returns <c>false</c>) when the node
|
|
/// manager has not been created yet, so the caller can detect a too-early call.
|
|
/// </summary>
|
|
/// <param name="router">The router invoked by the condition handlers once the <c>AlarmAck</c>
|
|
/// gate passes; may be <c>null</c> to clear it.</param>
|
|
/// <returns><c>true</c> when the router was set on a live node manager; <c>false</c> when no node
|
|
/// manager exists yet.</returns>
|
|
public bool SetAlarmCommandRouter(Action<AlarmCommand>? router)
|
|
{
|
|
if (_otOpcUaNodeManager is null) return false;
|
|
_otOpcUaNodeManager.AlarmCommandRouter = router;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wire the reverse-path router for inbound Part 9 Acknowledge of a NATIVE (driver-fed, e.g. Galaxy)
|
|
/// condition onto the created <see cref="OtOpcUaNodeManager"/>. The host calls this after start with a
|
|
/// non-blocking router that Tells a <c>DriverHostActor.RouteNativeAlarmAck</c> into the local
|
|
/// <c>DriverHostActor</c>, which (Primary-gated) routes the acknowledge to the owning driver child.
|
|
/// Native conditions are not owned by the scripted-alarm engine, so the node manager branches their
|
|
/// inbound Acknowledge to this seam instead of the scripted <see cref="SetAlarmCommandRouter"/> path.
|
|
/// Passing <c>null</c> clears it. No-op (returns <c>false</c>) when the node manager has not been
|
|
/// created yet, so the caller can detect a too-early call (mirrors <see cref="SetAlarmCommandRouter"/>).
|
|
/// </summary>
|
|
/// <param name="router">The router invoked by the native condition's Acknowledge handler once the
|
|
/// <c>AlarmAck</c> gate passes; may be <c>null</c> to clear it.</param>
|
|
/// <returns><c>true</c> when the router was set on a live node manager; <c>false</c> when no node
|
|
/// manager exists yet.</returns>
|
|
public bool SetNativeAlarmAckRouter(Action<NativeAlarmAck>? router)
|
|
{
|
|
if (_otOpcUaNodeManager is null) return false;
|
|
_otOpcUaNodeManager.NativeAlarmAckRouter = router;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wire the reverse-path gateway for inbound operator writes to writable equipment-tag nodes onto the
|
|
/// created <see cref="OtOpcUaNodeManager"/>. The host calls this after start with an
|
|
/// <c>ActorNodeWriteGateway</c> whose <c>WriteAsync</c> kicks off a bounded <c>Ask</c> of the local
|
|
/// <c>DriverHostActor</c> (<c>RouteNodeWrite</c>); the node manager calls it fire-and-forget and uses
|
|
/// the resolved <c>NodeWriteOutcome</c> to self-correct the node on a failed device write (mirrors
|
|
/// <see cref="SetAlarmCommandRouter"/>). Passing <c>null</c> restores the
|
|
/// <c>NullOpcUaNodeWriteGateway</c> default (writes unavailable). No-op (returns <c>false</c>) when the
|
|
/// node manager has not been created yet, so the caller can detect a too-early call.
|
|
/// </summary>
|
|
/// <param name="gateway">The gateway invoked by the writable node's <c>OnWriteValue</c> handler once the
|
|
/// <c>WriteOperate</c> gate passes; may be <c>null</c> to restore the Null default.</param>
|
|
/// <returns><c>true</c> when the gateway was set on a live node manager; <c>false</c> when no node
|
|
/// manager exists yet.</returns>
|
|
public bool SetNodeWriteGateway(IOpcUaNodeWriteGateway? gateway)
|
|
{
|
|
if (_otOpcUaNodeManager is null) return false;
|
|
// The NodeWriteGateway setter null-coalesces to the Null default, so a null gateway is intentional
|
|
// (restores "writes unavailable"); forgive the nullable-in here.
|
|
_otOpcUaNodeManager.NodeWriteGateway = gateway!;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wire the server-side HistoryRead backend (the <see cref="IHistorianDataSource"/> the node
|
|
/// manager's HistoryRead overrides block-bridge to) onto the created
|
|
/// <see cref="OtOpcUaNodeManager"/>. The host calls this after start with the DI-resolved source —
|
|
/// the <c>NullHistorianDataSource</c> default (GoodNoData-empty reads) or the configured HistorianGateway
|
|
/// read client. Passing <c>null</c> restores the Null default (the property setter null-coalesces),
|
|
/// i.e. "no historian". No-op (returns <c>false</c>) when the node manager has not been created yet,
|
|
/// so the caller can detect a too-early call (mirrors <see cref="SetNodeWriteGateway"/>).
|
|
/// </summary>
|
|
/// <param name="source">The read backend invoked by the node manager's HistoryRead overrides; may be
|
|
/// <c>null</c> to restore the Null default (GoodNoData-empty reads).</param>
|
|
/// <returns><c>true</c> when the source was set on a live node manager; <c>false</c> when no node
|
|
/// manager exists yet.</returns>
|
|
public bool SetHistorianDataSource(IHistorianDataSource? source)
|
|
{
|
|
if (_otOpcUaNodeManager is null) return false;
|
|
// The HistorianDataSource setter null-coalesces to the Null default, so a null source is intentional
|
|
// (restores GoodNoData-empty reads); forgive the nullable-in here.
|
|
_otOpcUaNodeManager.HistorianDataSource = source!;
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected override MasterNodeManager CreateMasterNodeManager(
|
|
IServerInternal server, ApplicationConfiguration configuration)
|
|
{
|
|
_otOpcUaNodeManager = new OtOpcUaNodeManager(server, configuration);
|
|
return new MasterNodeManager(server, configuration, dynamicNamespaceUri: null, _otOpcUaNodeManager);
|
|
}
|
|
}
|