a107a1a8b9
Register the MTConnect factory + probe with the Host, add the DriverTypeNames.MTConnect constant, and keep DriverTypeNamesGuardTests green. - DriverTypeNames: new MTConnect const + appended to All. - DriverFactoryBootstrap: MTConnectDriverFactoryExtensions.Register in Register(...) at the default Tier A (managed HttpClient/XML, in-process; Tier C is the only tier arming process-recycle, wrong here), and TryAddEnumerable(IDriverProbe -> MTConnectDriverProbe) in AddOtOpcUaDriverProbes so the probe reaches admin-only nodes (the admin-pinned Test-Connect singleton) without double-registering on a fused admin,driver node. - Host.csproj: ProjectReference to Driver.MTConnect (required to compile the Register call). - Core.Abstractions.Tests.csproj: ProjectReference to Driver.MTConnect so the guard's bin scan discovers the factory. This and the constant MUST land together -- verified RED (2/4 facts) with the constant alone. - DriverProbeRegistrationTests: MTConnect added to AdminUiDriverTypeKeys; its idempotency fact asserts distinct-probe-count and goes RED without this (and RED if TryAddEnumerable is downgraded to AddSingleton). Reconciles all four "MTConnect" literals onto the one constant: the factory's DriverTypeName, MTConnectDriver.DriverType, and MTConnectDriverProbe.DriverType now all alias DriverTypeNames.MTConnect (no circular reference -- Core.Abstractions is a leaf both driver projects already reference). This is the repo's TwinCat/Focas anti-drift convention.
117 lines
7.2 KiB
C#
117 lines
7.2 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.MTConnect;
|
|
|
|
/// <summary>
|
|
/// Static factory registration helper for <see cref="MTConnectDriver"/>. The Host registers it
|
|
/// once at startup; the bootstrapper then materialises MTConnect <c>DriverInstance</c> rows from
|
|
/// the deployed configuration into live driver instances. Mirrors
|
|
/// <c>ModbusDriverFactoryExtensions</c> / <c>FocasDriverFactoryExtensions</c>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// <b>There is exactly one parser for the config document.</b> This factory does not carry a
|
|
/// config DTO of its own — it delegates to <see cref="MTConnectDriver.ParseOptions"/>, which
|
|
/// the driver itself must own because the runtime delivers a config <i>change</i> to a live
|
|
/// instance (<c>DriverInstanceActor</c> assigns its driver once and calls
|
|
/// <c>ReinitializeAsync(newJson)</c> thereafter). A second DTO here would drift from that
|
|
/// one silently, and the drift would first show up at deploy time as an operator's edit
|
|
/// being applied differently by the two paths — or not at all.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>Construction opens nothing.</b> The Wave-0 universal discovery browser builds a
|
|
/// throwaway instance through this factory purely to read
|
|
/// <c>ITagDiscovery.SupportsOnlineDiscovery</c>, and never initializes it — so a factory
|
|
/// that dialled the Agent (or that pre-built an <see cref="IMTConnectAgentClient"/>) would
|
|
/// open a connection per AdminUI browse render against a possibly-unreachable Agent. The
|
|
/// agent-client factory is therefore left null: <see cref="MTConnectDriver"/> builds the
|
|
/// production client inside <c>InitializeAsync</c>.
|
|
/// </para>
|
|
/// <para>
|
|
/// <b>The instance is returned as its concrete type.</b> The runtime resolves every optional
|
|
/// capability (<see cref="IReadable"/>, <see cref="ISubscribable"/>,
|
|
/// <see cref="ITagDiscovery"/>, <see cref="IHostConnectivityProbe"/>,
|
|
/// <see cref="IRediscoverable"/>) by pattern-matching the object
|
|
/// <see cref="DriverFactoryRegistry"/> hands back, so nothing here may narrow it — a
|
|
/// narrowed return is how a capability ends up implemented but never dispatched. There is
|
|
/// deliberately no <c>IWritable</c>: the MTConnect Agent surface is read-only.
|
|
/// </para>
|
|
/// </remarks>
|
|
public static class MTConnectDriverFactoryExtensions
|
|
{
|
|
/// <summary>
|
|
/// The <c>DriverInstance.DriverType</c> value this factory answers to.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <b>Aliased to <see cref="DriverTypeNames.MTConnect"/>, not a literal.</b> The registry key,
|
|
/// the instance's self-reported <see cref="MTConnectDriver.DriverType"/>, the probe's
|
|
/// <c>DriverType</c>, and the constant every dispatch map keys off are therefore the same
|
|
/// symbol — the repo-wide fix for the historical <c>TwinCat</c>/<c>Focas</c> drift, where a
|
|
/// driver's own spelling silently diverged from the constant and dispatch maps missed it.
|
|
/// Retained as a named const (rather than deleted in favour of the constant) so the existing
|
|
/// factory callers and the sibling <c>*DriverFactoryExtensions.DriverTypeName</c> convention
|
|
/// keep working; it is now an alias with no independent value.
|
|
/// </remarks>
|
|
public const string DriverTypeName = DriverTypeNames.MTConnect;
|
|
|
|
/// <summary>
|
|
/// Register the MTConnect factory with the driver registry. The optional
|
|
/// <paramref name="loggerFactory"/> is captured at registration time and used to construct
|
|
/// an <see cref="ILogger{TCategoryName}"/> per driver instance — without it the driver runs
|
|
/// with the null logger (tests and standalone callers stay unchanged).
|
|
/// </summary>
|
|
/// <param name="registry">The driver factory registry to register with.</param>
|
|
/// <param name="loggerFactory">Optional logger factory for creating loggers per driver instance.</param>
|
|
public static void Register(DriverFactoryRegistry registry, ILoggerFactory? loggerFactory = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(registry);
|
|
registry.Register(DriverTypeName, (id, json) => CreateInstance(id, json, loggerFactory));
|
|
}
|
|
|
|
/// <summary>Public for the Server-side bootstrapper + test consumers.</summary>
|
|
/// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
|
|
/// <param name="driverConfigJson">The driver's <c>DriverConfig</c> JSON.</param>
|
|
/// <returns>The constructed <see cref="MTConnectDriver"/> instance.</returns>
|
|
public static MTConnectDriver CreateInstance(string driverInstanceId, string driverConfigJson)
|
|
=> CreateInstance(driverInstanceId, driverConfigJson, loggerFactory: null);
|
|
|
|
/// <summary>Logger-aware overload — used by <see cref="Register"/>'s closure when wired through DI.</summary>
|
|
/// <param name="driverInstanceId">The unique identifier for the driver instance.</param>
|
|
/// <param name="driverConfigJson">The driver's <c>DriverConfig</c> JSON.</param>
|
|
/// <param name="loggerFactory">Optional logger factory for creating loggers per driver instance.</param>
|
|
/// <returns>The constructed <see cref="MTConnectDriver"/> instance.</returns>
|
|
/// <exception cref="ArgumentException">
|
|
/// <paramref name="driverInstanceId"/> or <paramref name="driverConfigJson"/> is null or blank.
|
|
/// </exception>
|
|
/// <exception cref="InvalidOperationException">
|
|
/// The config document is unparseable, deserialises to null, omits the required
|
|
/// <c>agentUri</c>, or carries an unauthorable enum/tag value. Throwing is correct at this
|
|
/// seam: a deployment carrying such a row must fail rather than register a driver pointed at
|
|
/// nothing, and the discovery browser's <c>CanBrowse</c> catches factory exceptions so a
|
|
/// half-authored config merely disables the address picker.
|
|
/// </exception>
|
|
public static MTConnectDriver CreateInstance(
|
|
string driverInstanceId, string driverConfigJson, ILoggerFactory? loggerFactory)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(driverInstanceId);
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(driverConfigJson);
|
|
|
|
// The single config authority — see the class remarks for why this is not a second DTO.
|
|
// Note the asymmetry with the driver's own lifecycle path: an empty ("{}" / "[]") document
|
|
// there means "no config supplied, keep the constructor options", but this factory HAS no
|
|
// constructor options to keep, so an empty document is simply a config with no agentUri and
|
|
// must fault.
|
|
var options = MTConnectDriver.ParseOptions(driverInstanceId, driverConfigJson);
|
|
|
|
// Named arguments deliberately: the ctor's trailing parameters are all optional, so a
|
|
// future insertion could silently re-bind a positional argument to the wrong one.
|
|
return new MTConnectDriver(
|
|
options,
|
|
driverInstanceId,
|
|
agentClientFactory: null,
|
|
logger: loggerFactory?.CreateLogger<MTConnectDriver>());
|
|
}
|
|
}
|