using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Protocol; using ZB.MOM.WW.ScadaBridge.Commons.Types.Alarms; namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters; /// /// Configuration options for OPC UA connections, parsed from connection details JSON. /// All values have defaults matching the OPC Foundation SDK's typical settings. /// public record OpcUaConnectionOptions( int SessionTimeoutMs = 60000, int OperationTimeoutMs = 15000, int PublishingIntervalMs = 1000, int KeepAliveCount = 10, int LifetimeCount = 30, int MaxNotificationsPerPublish = 100, int SamplingIntervalMs = 1000, int QueueSize = 10, string SecurityMode = "None", // DataConnectionLayer-012: secure-by-default — untrusted server certificates are // rejected unless an operator explicitly opts in per connection. Accepting any // certificate defeats the Sign / SignAndEncrypt modes against a man-in-the-middle. bool AutoAcceptUntrustedCerts = false, bool DiscardOldest = true, byte SubscriptionPriority = 0, string SubscriptionDisplayName = "ScadaBridge", string TimestampsToReturn = "Source", OpcUaDeadbandOptions? Deadband = null, OpcUaUserIdentityOptions? UserIdentity = null); public record OpcUaDeadbandOptions(string Type, double Value); public record OpcUaUserIdentityOptions( string TokenType, string Username, string Password, string CertificatePath, string CertificatePassword); /// /// WP-7: Abstraction over OPC UA client library for testability. /// The real implementation would wrap an OPC UA SDK (e.g., OPC Foundation .NET Standard Library). /// public interface IOpcUaClient : IAsyncDisposable { /// /// Connects to an OPC UA server at the specified endpoint URL. /// /// The OPC UA server endpoint URL. /// Connection options; if null, defaults are used. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task ConnectAsync(string endpointUrl, OpcUaConnectionOptions? options = null, CancellationToken cancellationToken = default); /// /// Disconnects from the OPC UA server. /// /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task DisconnectAsync(CancellationToken cancellationToken = default); /// /// Gets a value indicating whether the client is currently connected to the server. /// bool IsConnected { get; } /// /// Creates a monitored item subscription for a node. Returns a subscription handle. /// /// The OPC UA node ID to monitor. /// Callback invoked when the monitored value changes, receiving nodeId, value, sourceTimestamp, and statusCode. /// A cancellation token that can be used to cancel the operation. /// A task that completes with a subscription handle string. Task CreateSubscriptionAsync( string nodeId, Action onValueChanged, CancellationToken cancellationToken = default); /// /// Removes a monitored item subscription by handle. /// /// The subscription handle returned by CreateSubscriptionAsync. /// A cancellation token that can be used to cancel the operation. /// A task representing the asynchronous operation. Task RemoveSubscriptionAsync(string subscriptionHandle, CancellationToken cancellationToken = default); /// /// Subscribes to OPC UA Alarms & Conditions events under /// (or the Server object when null). On /// (re)subscribe the adapter issues a ConditionRefresh and replays the /// active conditions as Snapshot…SnapshotComplete transitions. Returns a /// handle for . /// Task CreateAlarmSubscriptionAsync( string? sourceNodeId, string? conditionFilter, Action onTransition, CancellationToken cancellationToken = default); /// Removes an alarm-event subscription by handle. Task RemoveAlarmSubscriptionAsync(string subscriptionHandle, CancellationToken cancellationToken = default); /// /// Reads the current value of a node. /// /// The OPC UA node ID to read. /// A cancellation token that can be used to cancel the operation. /// A task that completes with a tuple of (value, sourceTimestamp, statusCode). Task<(object? Value, DateTime SourceTimestamp, uint StatusCode)> ReadValueAsync( string nodeId, CancellationToken cancellationToken = default); /// /// Writes a value to a node. /// /// The OPC UA node ID to write to. /// The value to write. /// A cancellation token that can be used to cancel the operation. /// A task that completes with the OPC UA status code of the write operation. Task WriteValueAsync(string nodeId, object? value, CancellationToken cancellationToken = default); /// /// Raised when the OPC UA session detects a keep-alive failure or the server /// becomes unreachable. The adapter layer uses this to trigger reconnection. /// event Action? ConnectionLost; /// /// Enumerates the immediate children of /// (or the server's ObjectsFolder when null). Throws /// when the session is not /// currently up. /// /// Node id whose children to browse, or null for the server root. /// A cancellation token that can be used to cancel the operation. /// A task that completes with the immediate children of the requested node. Task BrowseChildrenAsync( string? parentNodeId, CancellationToken cancellationToken = default); } /// /// Factory for creating IOpcUaClient instances. /// public interface IOpcUaClientFactory { /// /// Creates a new IOpcUaClient instance. /// /// A new IOpcUaClient instance. IOpcUaClient Create(); } /// /// Default factory that creates stub OPC UA clients. /// In production, this would create real OPC UA SDK client instances. /// public class DefaultOpcUaClientFactory : IOpcUaClientFactory { /// public IOpcUaClient Create() => new StubOpcUaClient(); } /// /// Stub OPC UA client for development/testing. A real implementation would /// wrap the OPC Foundation .NET Standard Library. /// internal class StubOpcUaClient : IOpcUaClient { /// public bool IsConnected { get; private set; } #pragma warning disable CS0067 /// public event Action? ConnectionLost; #pragma warning restore CS0067 /// public Task ConnectAsync(string endpointUrl, OpcUaConnectionOptions? options = null, CancellationToken cancellationToken = default) { IsConnected = true; return Task.CompletedTask; } /// public Task DisconnectAsync(CancellationToken cancellationToken = default) { IsConnected = false; return Task.CompletedTask; } /// public Task CreateSubscriptionAsync( string nodeId, Action onValueChanged, CancellationToken cancellationToken = default) { return Task.FromResult(Guid.NewGuid().ToString()); } /// public Task RemoveSubscriptionAsync(string subscriptionHandle, CancellationToken cancellationToken = default) { return Task.CompletedTask; } /// public Task CreateAlarmSubscriptionAsync( string? sourceNodeId, string? conditionFilter, Action onTransition, CancellationToken cancellationToken = default) { // Stub: no events. Real A&C subscription lives in RealOpcUaClient. return Task.FromResult(Guid.NewGuid().ToString()); } /// public Task RemoveAlarmSubscriptionAsync(string subscriptionHandle, CancellationToken cancellationToken = default) => Task.CompletedTask; /// public Task<(object? Value, DateTime SourceTimestamp, uint StatusCode)> ReadValueAsync( string nodeId, CancellationToken cancellationToken = default) { return Task.FromResult<(object?, DateTime, uint)>((null, DateTime.UtcNow, 0)); } /// public Task WriteValueAsync(string nodeId, object? value, CancellationToken cancellationToken = default) { return Task.FromResult(0); // Good status } /// public Task BrowseChildrenAsync( string? parentNodeId, CancellationToken cancellationToken = default) => throw new NotImplementedException(); /// public ValueTask DisposeAsync() { IsConnected = false; return ValueTask.CompletedTask; } }