using System.Text.Json; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Hosting; namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT; /// /// Static factory registration helper for . Server's Program.cs /// calls once at startup; the bootstrapper materialises TwinCAT /// DriverInstance rows from the central config DB into live driver instances. Mirrors /// S7DriverFactoryExtensions / AbCipDriverFactoryExtensions. /// public static class TwinCATDriverFactoryExtensions { public const string DriverTypeName = "TwinCAT"; /// Registers the TwinCAT driver factory with the provided registry. /// The driver factory registry to register with. public static void Register(DriverFactoryRegistry registry) { ArgumentNullException.ThrowIfNull(registry); registry.Register(DriverTypeName, CreateInstance); } /// Creates a TwinCAT driver instance from the provided configuration. /// The driver instance identifier. /// The driver configuration as JSON. /// The constructed instance. internal static TwinCATDriver CreateInstance(string driverInstanceId, string driverConfigJson) { ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId); var options = ParseOptions(driverConfigJson, driverInstanceId); return new TwinCATDriver(options, driverInstanceId); } /// /// Parse a TwinCAT driver-config JSON document into a . /// Shared by (constructor-time) and /// / /// so a config generation pushed via Reinitialize is actually applied. /// /// The JSON configuration string. /// The driver instance identifier. /// The parsed . internal static TwinCATDriverOptions ParseOptions(string driverConfigJson, string driverInstanceId) { ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson); var dto = JsonSerializer.Deserialize(driverConfigJson, JsonOptions) ?? throw new InvalidOperationException( $"TwinCAT driver config for '{driverInstanceId}' deserialised to null"); return new TwinCATDriverOptions { Devices = dto.Devices is { Count: > 0 } ? [.. dto.Devices.Select(d => new TwinCATDeviceOptions( HostAddress: d.HostAddress ?? throw new InvalidOperationException( $"TwinCAT config for '{driverInstanceId}' has a device missing HostAddress"), DeviceName: d.DeviceName))] : [], // v3: the deploy artifact (Wave C) populates rawTags with the cluster's authored raw TwinCAT // tags (RawPath + TagConfig blob + WriteIdempotent + owning DeviceName). The driver maps each // through TwinCATTagDefinitionFactory at Initialize; the legacy pre-declared DTO tag path // (tags[] → BuildTag) is retired. RawTags = dto.RawTags is { Count: > 0 } ? [.. dto.RawTags] : [], Probe = new TwinCATProbeOptions { Enabled = dto.Probe?.Enabled ?? true, Interval = TimeSpan.FromMilliseconds(dto.Probe?.IntervalMs ?? 5_000), Timeout = TimeSpan.FromMilliseconds(dto.Probe?.TimeoutMs ?? 2_000), }, Timeout = TimeSpan.FromMilliseconds(dto.TimeoutMs ?? 2_000), UseNativeNotifications = dto.UseNativeNotifications ?? true, EnableControllerBrowse = dto.EnableControllerBrowse ?? false, NotificationMaxDelayMs = dto.NotificationMaxDelayMs ?? 0, }; } /// /// Test-visible wrapper around for the regression suite — /// keeps the public driver surface unchanged while letting tests assert that JSON /// fields like NotificationMaxDelayMs and Structure-tag rejection are /// honored end-to-end. /// /// The JSON configuration string. /// The driver instance identifier. /// The parsed . public static TwinCATDriverOptions ParseOptionsForTests(string driverConfigJson, string driverInstanceId) => ParseOptions(driverConfigJson, driverInstanceId); private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true, ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; internal sealed class TwinCATDriverConfigDto { /// Gets or sets the timeout in milliseconds. public int? TimeoutMs { get; init; } /// Gets or sets a value indicating whether to use native notifications. public bool? UseNativeNotifications { get; init; } /// Gets or sets a value indicating whether to enable controller browsing. public bool? EnableControllerBrowse { get; init; } /// Gets or sets the maximum notification delay in milliseconds. public int? NotificationMaxDelayMs { get; init; } /// Gets or sets the list of configured devices. public List? Devices { get; init; } /// Gets or sets the authored raw tags (RawPath + TagConfig blob + WriteIdempotent + /// owning DeviceName) the deploy artifact delivers. The driver maps each through the /// tag-definition factory at Initialize; the legacy pre-declared tags[] path is retired. public List? RawTags { get; init; } /// Gets or sets the probe configuration. public TwinCATProbeDto? Probe { get; init; } } internal sealed class TwinCATDeviceDto { /// Gets or sets the host address. public string? HostAddress { get; init; } /// Gets or sets the device name. public string? DeviceName { get; init; } } internal sealed class TwinCATProbeDto { /// Gets or sets a value indicating whether probing is enabled. public bool? Enabled { get; init; } /// Gets or sets the probe interval in milliseconds. public int? IntervalMs { get; init; } /// Gets or sets the probe timeout in milliseconds. public int? TimeoutMs { get; init; } } }