using System.Text.Json; using Microsoft.Extensions.Logging; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Core.Hosting; namespace ZB.MOM.WW.OtOpcUa.Driver.Mqtt; /// /// Registers the MQTT / Sparkplug B driver with the . The /// Host's DriverFactoryBootstrap calls once at startup; the /// driver-instance bootstrapper then materialises DriverInstance rows of type /// into live instances. /// /// /// /// Direct deserialization, no intermediate DTO. The DriverConfig blob binds /// straight onto — the same shape /// , MqttDriverBrowser and /// already bind, and the shape the options record /// was designed for (every knob carries its own default, and rawTags is a plain /// list needing no translation). Mirrors /// OpcUaClientDriverFactoryExtensions. ModbusDriverFactoryExtensions' separate /// DTO exists to service a legacy nullable-everything blob plus string→enum parsing this /// driver does with a converter instead; copying it here would add a fourth parse /// shape for one config, which is precisely the divergence /// exists to prevent. /// /// /// Connection-free. parses and constructs only — the /// constructor touches no network, and the registry contract /// forbids the factory from calling InitializeAsync itself (the driver host owns the /// retry semantics). /// /// public static class MqttDriverFactoryExtensions { /// /// Driver type name — matches DriverInstance.DriverType values. Sourced from /// so the registration key can never drift from the /// constant the dispatch maps, the probe and the browser reference. /// public const string DriverTypeName = DriverTypeNames.Mqtt; /// /// Register the MQTT factory with the driver registry. The optional /// is captured at registration time and used to construct an /// per driver instance — without it the driver runs with no /// logger (standalone/test callers stay unchanged). /// /// The driver factory registry to register with. /// Optional logger factory used to create per-instance loggers. /// /// Gitea #511: the Primary data-plane verdict the Sparkplug primary-host claim is gated on, /// captured at registration time like . Null falls back to /// (always claims) — correct for a standalone /// host or a test, and the reason DriverFactoryBootstrap resolves it with /// GetRequiredService rather than letting a redundant deployment reach that fallback. /// public static void Register( DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null, IDriverPrimaryRoleSource? primaryRoleSource = null) { ArgumentNullException.ThrowIfNull(registry); registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory, primaryRoleSource)); } /// Public for the Server-side bootstrapper + test consumers. /// Stable logical id of the driver instance. /// The DriverConfig JSON blob for the instance. /// Optional logger factory for the per-instance logger. /// /// Gitea #511: the Primary data-plane verdict gating the Sparkplug primary-host claim; null /// falls back to . /// /// A configured, not-yet-connected . /// /// or is blank. /// /// The config blob deserialised to null. /// The config blob is not valid JSON for the options shape. public static MqttDriver CreateInstance( string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory = null, IDriverPrimaryRoleSource? primaryRoleSource = null) { ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId); ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson); // MqttJson.Options — the one shared instance (see its remarks). A local copy here is the // repo's documented systemic enum bug in the making. var options = JsonSerializer.Deserialize(driverConfigJson, MqttJson.Options) ?? throw new InvalidOperationException( $"MQTT driver config for '{driverInstanceId}' deserialised to null"); return new MqttDriver( options, driverInstanceId, loggerFactory?.CreateLogger(), primaryRoleSource); } }