feat(dcl): Layer C runtime wires new OPC UA settings through to OPC SDK
OpcUaConnectionOptions record gains DiscardOldest, SubscriptionPriority, SubscriptionDisplayName, TimestampsToReturn, plus OpcUaDeadbandOptions and OpcUaUserIdentityOptions nullable sub-records. OpcUaDataConnection.ConnectAsync copies all new fields from the typed OpcUaEndpointConfig (including the Deadband and UserIdentity sub-objects) into the OpcUaConnectionOptions record. RealOpcUaClient: - BuildUserIdentity translates TokenType into Opc.Ua.UserIdentity: Anonymous → null, UsernamePassword → new UserIdentity(name, utf8(pass)), X509Certificate → new UserIdentity(X509CertificateLoader.LoadPkcs12FromFile(...)). - Subscription uses opts.SubscriptionDisplayName and opts.SubscriptionPriority. - MonitoredItem.DiscardOldest is opts.DiscardOldest (was hardcoded true). - BuildDataChangeFilter materializes a DataChangeFilter when Deadband is set. - ReadAsync uses MapTimestampsToReturn for opts.TimestampsToReturn (was hardcoded Source). X509CertificateLoader replaces obsolete X509Certificate2(string,string) ctor (SYSLIB0057 on .NET 10). UserIdentity(string,byte[]) ctor used because the (string,string) overload was removed in OPC Foundation 1.5.378.106.
This commit is contained in:
@@ -14,7 +14,22 @@ public record OpcUaConnectionOptions(
|
|||||||
int SamplingIntervalMs = 1000,
|
int SamplingIntervalMs = 1000,
|
||||||
int QueueSize = 10,
|
int QueueSize = 10,
|
||||||
string SecurityMode = "None",
|
string SecurityMode = "None",
|
||||||
bool AutoAcceptUntrustedCerts = true);
|
bool AutoAcceptUntrustedCerts = true,
|
||||||
|
bool DiscardOldest = true,
|
||||||
|
byte SubscriptionPriority = 0,
|
||||||
|
string SubscriptionDisplayName = "ScadaLink",
|
||||||
|
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);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WP-7: Abstraction over OPC UA client library for testability.
|
/// WP-7: Abstraction over OPC UA client library for testability.
|
||||||
|
|||||||
@@ -61,7 +61,19 @@ public class OpcUaDataConnection : IDataConnection
|
|||||||
SamplingIntervalMs: config.SamplingIntervalMs,
|
SamplingIntervalMs: config.SamplingIntervalMs,
|
||||||
QueueSize: config.QueueSize,
|
QueueSize: config.QueueSize,
|
||||||
SecurityMode: config.SecurityMode.ToString(),
|
SecurityMode: config.SecurityMode.ToString(),
|
||||||
AutoAcceptUntrustedCerts: config.AutoAcceptUntrustedCerts);
|
AutoAcceptUntrustedCerts: config.AutoAcceptUntrustedCerts,
|
||||||
|
DiscardOldest: config.DiscardOldest,
|
||||||
|
SubscriptionPriority: config.SubscriptionPriority,
|
||||||
|
SubscriptionDisplayName: config.SubscriptionDisplayName,
|
||||||
|
TimestampsToReturn: config.TimestampsToReturn.ToString(),
|
||||||
|
Deadband: config.Deadband is { } db
|
||||||
|
? new OpcUaDeadbandOptions(db.Type.ToString(), db.Value)
|
||||||
|
: null,
|
||||||
|
UserIdentity: config.UserIdentity is { } ui
|
||||||
|
? new OpcUaUserIdentityOptions(
|
||||||
|
ui.TokenType.ToString(), ui.Username, ui.Password,
|
||||||
|
ui.CertificatePath, ui.CertificatePassword)
|
||||||
|
: null);
|
||||||
|
|
||||||
_status = ConnectionHealth.Connecting;
|
_status = ConnectionHealth.Connecting;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Security.Cryptography.X509Certificates;
|
||||||
using Opc.Ua;
|
using Opc.Ua;
|
||||||
using Opc.Ua.Client;
|
using Opc.Ua.Client;
|
||||||
using Opc.Ua.Configuration;
|
using Opc.Ua.Configuration;
|
||||||
@@ -77,9 +78,10 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
#pragma warning disable CS0618 // Allow obsolete DefaultSessionFactory constructor for compatibility
|
#pragma warning disable CS0618 // Allow obsolete DefaultSessionFactory constructor for compatibility
|
||||||
var sessionFactory = new DefaultSessionFactory();
|
var sessionFactory = new DefaultSessionFactory();
|
||||||
#pragma warning restore CS0618
|
#pragma warning restore CS0618
|
||||||
|
var userIdentity = BuildUserIdentity(opts.UserIdentity);
|
||||||
_session = await sessionFactory.CreateAsync(
|
_session = await sessionFactory.CreateAsync(
|
||||||
appConfig, configuredEndpoint, false,
|
appConfig, configuredEndpoint, false,
|
||||||
"ScadaLink-DCL-Session", (uint)opts.SessionTimeoutMs, null, null, cancellationToken);
|
"ScadaLink-DCL-Session", (uint)opts.SessionTimeoutMs, userIdentity, null, cancellationToken);
|
||||||
|
|
||||||
// Detect server going offline via keep-alive failures
|
// Detect server going offline via keep-alive failures
|
||||||
_connectionLostFired = false;
|
_connectionLostFired = false;
|
||||||
@@ -91,7 +93,8 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
// Create a default subscription for all monitored items
|
// Create a default subscription for all monitored items
|
||||||
_subscription = new Subscription(_session.DefaultSubscription)
|
_subscription = new Subscription(_session.DefaultSubscription)
|
||||||
{
|
{
|
||||||
DisplayName = "ScadaLink",
|
DisplayName = opts.SubscriptionDisplayName,
|
||||||
|
Priority = opts.SubscriptionPriority,
|
||||||
PublishingEnabled = true,
|
PublishingEnabled = true,
|
||||||
PublishingInterval = opts.PublishingIntervalMs,
|
PublishingInterval = opts.PublishingIntervalMs,
|
||||||
KeepAliveCount = (uint)opts.KeepAliveCount,
|
KeepAliveCount = (uint)opts.KeepAliveCount,
|
||||||
@@ -135,7 +138,8 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
AttributeId = Attributes.Value,
|
AttributeId = Attributes.Value,
|
||||||
SamplingInterval = _options.SamplingIntervalMs,
|
SamplingInterval = _options.SamplingIntervalMs,
|
||||||
QueueSize = (uint)_options.QueueSize,
|
QueueSize = (uint)_options.QueueSize,
|
||||||
DiscardOldest = true
|
DiscardOldest = _options.DiscardOldest,
|
||||||
|
Filter = BuildDataChangeFilter(_options.Deadband)
|
||||||
};
|
};
|
||||||
|
|
||||||
_callbacks[handle] = onValueChanged;
|
_callbacks[handle] = onValueChanged;
|
||||||
@@ -185,7 +189,7 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
};
|
};
|
||||||
|
|
||||||
var response = await _session.ReadAsync(
|
var response = await _session.ReadAsync(
|
||||||
null, 0, TimestampsToReturn.Source,
|
null, 0, MapTimestampsToReturn(_options.TimestampsToReturn),
|
||||||
new ReadValueIdCollection { readValue }, cancellationToken);
|
new ReadValueIdCollection { readValue }, cancellationToken);
|
||||||
|
|
||||||
var result = response.Results[0];
|
var result = response.Results[0];
|
||||||
@@ -227,6 +231,45 @@ public class RealOpcUaClient : IOpcUaClient
|
|||||||
{
|
{
|
||||||
await DisconnectAsync();
|
await DisconnectAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UserIdentity? BuildUserIdentity(OpcUaUserIdentityOptions? options)
|
||||||
|
{
|
||||||
|
if (options is null) return null;
|
||||||
|
return options.TokenType.ToUpperInvariant() switch
|
||||||
|
{
|
||||||
|
"USERNAMEPASSWORD" => new UserIdentity(
|
||||||
|
options.Username,
|
||||||
|
System.Text.Encoding.UTF8.GetBytes(options.Password ?? "")),
|
||||||
|
"X509CERTIFICATE" => new UserIdentity(
|
||||||
|
X509CertificateLoader.LoadPkcs12FromFile(
|
||||||
|
options.CertificatePath, options.CertificatePassword)),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MonitoringFilter? BuildDataChangeFilter(OpcUaDeadbandOptions? deadband)
|
||||||
|
{
|
||||||
|
if (deadband is null) return null;
|
||||||
|
var deadbandType = deadband.Type.ToUpperInvariant() switch
|
||||||
|
{
|
||||||
|
"PERCENT" => DeadbandType.Percent,
|
||||||
|
_ => DeadbandType.Absolute
|
||||||
|
};
|
||||||
|
return new DataChangeFilter
|
||||||
|
{
|
||||||
|
Trigger = DataChangeTrigger.StatusValue,
|
||||||
|
DeadbandType = (uint)deadbandType,
|
||||||
|
DeadbandValue = deadband.Value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TimestampsToReturn MapTimestampsToReturn(string mode) =>
|
||||||
|
mode.ToUpperInvariant() switch
|
||||||
|
{
|
||||||
|
"SERVER" => TimestampsToReturn.Server,
|
||||||
|
"BOTH" => TimestampsToReturn.Both,
|
||||||
|
_ => TimestampsToReturn.Source
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user