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;
}
}