132 lines
5.3 KiB
C#
132 lines
5.3 KiB
C#
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using ScadaLink.Commons.Types.DataConnections;
|
|
|
|
namespace ScadaLink.Commons.Serialization;
|
|
|
|
/// <summary>
|
|
/// Serializes <see cref="OpcUaEndpointConfig"/> to/from the typed nested JSON
|
|
/// shape stored in <c>DataConnection.PrimaryConfiguration</c> / <c>BackupConfiguration</c>.
|
|
/// On read, falls back to the legacy flat string-dict shape for pre-refactor rows
|
|
/// and returns IsLegacy=true so the form can prompt the user to re-save.
|
|
/// </summary>
|
|
public static class OpcUaEndpointConfigSerializer
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOpts = new()
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
|
WriteIndented = false,
|
|
Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) }
|
|
};
|
|
|
|
public static string Serialize(OpcUaEndpointConfig config)
|
|
=> JsonSerializer.Serialize(config, JsonOpts);
|
|
|
|
public static (OpcUaEndpointConfig Config, bool IsLegacy) Deserialize(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
return (new OpcUaEndpointConfig(), false);
|
|
|
|
try
|
|
{
|
|
using var doc = JsonDocument.Parse(json);
|
|
if (doc.RootElement.ValueKind == JsonValueKind.Object
|
|
&& doc.RootElement.TryGetProperty("endpointUrl", out _))
|
|
{
|
|
var typed = JsonSerializer.Deserialize<OpcUaEndpointConfig>(json, JsonOpts);
|
|
if (typed != null)
|
|
return (typed, false);
|
|
}
|
|
}
|
|
catch (JsonException) { /* fall through to legacy */ }
|
|
|
|
try
|
|
{
|
|
return (LoadLegacy(json!), IsLegacy: true);
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
return (new OpcUaEndpointConfig(), IsLegacy: true);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flattens the typed config to the IDictionary<string,string> shape that
|
|
/// IDataConnection.ConnectAsync expects. Keys match the historical convention
|
|
/// used by OpcUaDataConnection so the adapter can keep that interface.
|
|
/// </summary>
|
|
public static IDictionary<string, string> ToFlatDict(OpcUaEndpointConfig config)
|
|
{
|
|
var dict = new Dictionary<string, string>
|
|
{
|
|
["endpoint"] = config.EndpointUrl,
|
|
["SecurityMode"] = config.SecurityMode.ToString(),
|
|
["AutoAcceptUntrustedCerts"] = config.AutoAcceptUntrustedCerts.ToString(),
|
|
["SessionTimeoutMs"] = config.SessionTimeoutMs.ToString(),
|
|
["OperationTimeoutMs"] = config.OperationTimeoutMs.ToString(),
|
|
["PublishingIntervalMs"] = config.PublishingIntervalMs.ToString(),
|
|
["SamplingIntervalMs"] = config.SamplingIntervalMs.ToString(),
|
|
["QueueSize"] = config.QueueSize.ToString(),
|
|
["KeepAliveCount"] = config.KeepAliveCount.ToString(),
|
|
["LifetimeCount"] = config.LifetimeCount.ToString(),
|
|
["MaxNotificationsPerPublish"] = config.MaxNotificationsPerPublish.ToString(),
|
|
};
|
|
if (config.Heartbeat is { } hb)
|
|
{
|
|
dict["HeartbeatTagPath"] = hb.TagPath;
|
|
dict["HeartbeatMaxSilence"] = hb.MaxSilenceSeconds.ToString();
|
|
}
|
|
return dict;
|
|
}
|
|
|
|
public static OpcUaEndpointConfig FromFlatDict(IDictionary<string, string> dict)
|
|
{
|
|
var c = new OpcUaEndpointConfig();
|
|
|
|
if (dict.TryGetValue("endpoint", out var ep) && !string.IsNullOrWhiteSpace(ep))
|
|
c.EndpointUrl = ep;
|
|
else if (dict.TryGetValue("EndpointUrl", out var ep2) && !string.IsNullOrWhiteSpace(ep2))
|
|
c.EndpointUrl = ep2;
|
|
|
|
if (dict.TryGetValue("SecurityMode", out var smStr)
|
|
&& Enum.TryParse<OpcUaSecurityMode>(smStr, ignoreCase: true, out var sm))
|
|
c.SecurityMode = sm;
|
|
|
|
if (dict.TryGetValue("AutoAcceptUntrustedCerts", out var aacStr)
|
|
&& bool.TryParse(aacStr, out var aac))
|
|
c.AutoAcceptUntrustedCerts = aac;
|
|
|
|
TryAssignInt(dict, "SessionTimeoutMs", v => c.SessionTimeoutMs = v);
|
|
TryAssignInt(dict, "OperationTimeoutMs", v => c.OperationTimeoutMs = v);
|
|
TryAssignInt(dict, "PublishingIntervalMs", v => c.PublishingIntervalMs = v);
|
|
TryAssignInt(dict, "SamplingIntervalMs", v => c.SamplingIntervalMs = v);
|
|
TryAssignInt(dict, "QueueSize", v => c.QueueSize = v);
|
|
TryAssignInt(dict, "KeepAliveCount", v => c.KeepAliveCount = v);
|
|
TryAssignInt(dict, "LifetimeCount", v => c.LifetimeCount = v);
|
|
TryAssignInt(dict, "MaxNotificationsPerPublish", v => c.MaxNotificationsPerPublish = v);
|
|
|
|
if (dict.TryGetValue("HeartbeatTagPath", out var hbPath)
|
|
&& !string.IsNullOrWhiteSpace(hbPath))
|
|
{
|
|
var hb = new OpcUaHeartbeatConfig { TagPath = hbPath };
|
|
TryAssignInt(dict, "HeartbeatMaxSilence", v => hb.MaxSilenceSeconds = v);
|
|
c.Heartbeat = hb;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
private static OpcUaEndpointConfig LoadLegacy(string json)
|
|
{
|
|
var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json)
|
|
?? new Dictionary<string, string>();
|
|
return FromFlatDict(dict);
|
|
}
|
|
|
|
private static void TryAssignInt(IDictionary<string, string> dict, string key, Action<int> assign)
|
|
{
|
|
if (dict.TryGetValue(key, out var s) && int.TryParse(s, out var v))
|
|
assign(v);
|
|
}
|
|
}
|