diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverFactoryExtensions.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverFactoryExtensions.cs
new file mode 100644
index 00000000..6943927f
--- /dev/null
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverFactoryExtensions.cs
@@ -0,0 +1,56 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Extensions.Logging;
+using ZB.MOM.WW.OtOpcUa.Core.Hosting;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
+
+///
+/// Registers the OPC UA Client driver with the . The driver's
+/// DriverConfig JSON deserialises directly into
+/// (the same shape reads), so no separate DTO is needed.
+/// Mirrors ModbusDriverFactoryExtensions / GalaxyDriverFactoryExtensions.
+///
+public static class OpcUaClientDriverFactoryExtensions
+{
+ /// Driver type name — matches DriverInstance.DriverType values.
+ public const string DriverTypeName = "OpcUaClient";
+
+ // Match OpcUaClientDriverProbe exactly so factory + probe parse a config identically.
+ // The JsonStringEnumConverter lets enum-valued knobs (SecurityMode / SecurityPolicy /
+ // AuthType / TargetNamespaceKind) be authored as their string names — the natural form
+ // for human-edited + AdminUI-emitted DriverConfig JSON.
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ PropertyNameCaseInsensitive = true,
+ UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
+ Converters = { new JsonStringEnumConverter() },
+ };
+
+ /// Register the OpcUaClient factory with the driver registry.
+ /// The driver factory registry to register with.
+ /// Optional logger factory used to create per-instance loggers.
+ public static void Register(DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null)
+ {
+ ArgumentNullException.ThrowIfNull(registry);
+ registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory));
+ }
+
+ /// Public for the Server-side bootstrapper + test consumers.
+ /// The unique identifier for the driver instance.
+ /// The JSON configuration string for the driver.
+ /// Optional logger factory for the per-instance logger.
+ /// A configured .
+ public static OpcUaClientDriver CreateInstance(
+ string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory = null)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
+ ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);
+
+ var options = JsonSerializer.Deserialize(driverConfigJson, JsonOptions)
+ ?? throw new InvalidOperationException(
+ $"OpcUaClient driver config for '{driverInstanceId}' deserialised to null");
+
+ return new OpcUaClientDriver(options, driverInstanceId, loggerFactory?.CreateLogger());
+ }
+}
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverProbe.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverProbe.cs
index c5f82b2f..5f6fba17 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverProbe.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriverProbe.cs
@@ -17,10 +17,14 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient;
///
public sealed class OpcUaClientDriverProbe : IDriverProbe
{
+ // Kept identical to OpcUaClientDriverFactoryExtensions.JsonOptions so the probe and the
+ // factory parse a given DriverConfig the same way. The JsonStringEnumConverter lets
+ // enum-valued knobs be authored as their string names.
private static readonly JsonSerializerOptions _opts = new()
{
PropertyNameCaseInsensitive = true,
UnmappedMemberHandling = JsonUnmappedMemberHandling.Skip,
+ Converters = { new JsonStringEnumConverter() },
};
///
diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.csproj b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.csproj
index d41c0dce..34c57c0d 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.csproj
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Drivers/DriverFactoryBootstrap.cs b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Drivers/DriverFactoryBootstrap.cs
index 26ef2284..e61aa142 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.Host/Drivers/DriverFactoryBootstrap.cs
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.Host/Drivers/DriverFactoryBootstrap.cs
@@ -102,6 +102,7 @@ public static class DriverFactoryBootstrap
Driver.FOCAS.FocasDriverFactoryExtensions.Register(registry);
Driver.Galaxy.GalaxyDriverFactoryExtensions.Register(registry, loggerFactory);
Driver.Modbus.ModbusDriverFactoryExtensions.Register(registry, loggerFactory);
+ Driver.OpcUaClient.OpcUaClientDriverFactoryExtensions.Register(registry, loggerFactory);
Driver.S7.S7DriverFactoryExtensions.Register(registry);
Driver.TwinCAT.TwinCATDriverFactoryExtensions.Register(registry);
}
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientDriverFactoryTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientDriverFactoryTests.cs
new file mode 100644
index 00000000..27a4df15
--- /dev/null
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/OpcUaClientDriverFactoryTests.cs
@@ -0,0 +1,36 @@
+using Shouldly;
+using Microsoft.Extensions.Logging.Abstractions;
+using Xunit;
+
+namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests;
+
+///
+/// Tests for — the factory that lets the
+/// Server-side DriverFactoryBootstrap materialise a real
+/// from a DriverInstance row instead of falling back to a stub.
+///
+public class OpcUaClientDriverFactoryTests
+{
+ private const string SampleConfig =
+ """{"EndpointUrl":"opc.tcp://host:4840","SecurityMode":"None","AutoAcceptCertificates":true}""";
+
+ /// Verifies the factory builds a driver carrying the right type + instance identity.
+ [Fact]
+ public void CreateInstance_builds_an_OpcUaClient_driver_with_the_right_identity()
+ {
+ var driver = OpcUaClientDriverFactoryExtensions.CreateInstance("drv-1", SampleConfig, NullLoggerFactory.Instance);
+ driver.DriverType.ShouldBe("OpcUaClient");
+ driver.DriverInstanceId.ShouldBe("drv-1");
+ }
+
+ /// Verifies the public driver-type-name constant matches the driver's DriverType.
+ [Fact]
+ public void DriverTypeName_is_OpcUaClient()
+ => OpcUaClientDriverFactoryExtensions.DriverTypeName.ShouldBe("OpcUaClient");
+
+ /// Verifies a JSON literal that deserialises to null is rejected with a clear error.
+ [Fact]
+ public void CreateInstance_throws_on_null_json_deserialisation()
+ => Should.Throw(
+ () => OpcUaClientDriverFactoryExtensions.CreateInstance("drv-1", "null", NullLoggerFactory.Instance));
+}