feat(historian): AddServerHistorian DI + Host wiring of IHistorianDataSource
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian;
|
||||
|
||||
/// <summary>
|
||||
/// Binds the <c>ServerHistorian</c> configuration section that gates the server-side
|
||||
/// HistoryRead backend. When <see cref="Enabled"/> is <c>true</c>, <c>AddServerHistorian</c>
|
||||
/// registers a read-only <c>WonderwareHistorianClient</c> (supplied by the Host) as the
|
||||
/// <c>IHistorianDataSource</c> in place of the <c>NullHistorianDataSource</c> default;
|
||||
/// otherwise the Null default survives and HistoryRead returns <c>GoodNoData</c>-empty.
|
||||
/// <para>
|
||||
/// This is the READ path only — there are no DatabasePath / drain / capacity / retention
|
||||
/// knobs (those belong to the write-side <c>AlarmHistorian</c> store-and-forward sink). The
|
||||
/// client's own <c>CallTimeout</c> bounds each read; the node manager adds no extra timeout.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class ServerHistorianOptions
|
||||
{
|
||||
/// <summary>The configuration section name this options class binds.</summary>
|
||||
public const string SectionName = "ServerHistorian";
|
||||
|
||||
/// <summary>
|
||||
/// When <c>true</c>, the Wonderware read client is registered as the
|
||||
/// <c>IHistorianDataSource</c>; when <c>false</c> (the default) the no-op
|
||||
/// <c>NullHistorianDataSource</c> stays in place and HistoryRead returns empty.
|
||||
/// </summary>
|
||||
public bool Enabled { get; init; }
|
||||
|
||||
/// <summary>TCP hostname or IP address the Wonderware historian sidecar listens on.</summary>
|
||||
public string Host { get; init; } = "localhost";
|
||||
|
||||
/// <summary>TCP port the Wonderware historian sidecar listens on.</summary>
|
||||
public int Port { get; init; }
|
||||
|
||||
/// <summary>When <c>true</c>, the client connects over TLS.</summary>
|
||||
public bool UseTls { get; init; }
|
||||
|
||||
/// <summary>Expected TLS server certificate thumbprint (hex, no spaces). Null or empty disables pinning.</summary>
|
||||
public string? ServerCertThumbprint { get; init; }
|
||||
|
||||
/// <summary>Per-process shared secret the sidecar verifies in the Hello frame.</summary>
|
||||
public string SharedSecret { get; init; } = "";
|
||||
|
||||
/// <summary>Returns operator-facing misconfiguration warnings for an <c>Enabled</c> historian
|
||||
/// (empty when disabled or correctly configured). Pure — the registration logs each entry.</summary>
|
||||
/// <returns>Zero or more human-readable warning messages.</returns>
|
||||
public IEnumerable<string> Validate()
|
||||
{
|
||||
if (!Enabled) yield break;
|
||||
if (string.IsNullOrWhiteSpace(SharedSecret))
|
||||
yield return "ServerHistorian:SharedSecret is empty while the historian is enabled — the Wonderware sidecar Hello frame will carry an empty secret.";
|
||||
if (Port <= 0)
|
||||
yield return $"ServerHistorian:Port is {Port} — must be > 0; the read client cannot dial the sidecar.";
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@ public static class ServiceCollectionExtensions
|
||||
public static IServiceCollection AddOtOpcUaRuntime(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<IAlarmHistorianSink>(NullAlarmHistorianSink.Instance);
|
||||
services.TryAddSingleton<IHistorianDataSource>(NullHistorianDataSource.Instance);
|
||||
services.TryAddSingleton<IDriverFactory>(NullDriverFactory.Instance);
|
||||
services.TryAddSingleton<IOpcUaAddressSpaceSink>(NullOpcUaAddressSpaceSink.Instance);
|
||||
services.TryAddSingleton<IServiceLevelPublisher>(NullServiceLevelPublisher.Instance);
|
||||
@@ -93,6 +94,38 @@ public static class ServiceCollectionExtensions
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Config-gated server-side HistoryRead backend. When the <c>ServerHistorian</c> section has
|
||||
/// <c>Enabled=true</c>, registers the <paramref name="dataSourceFactory"/>-supplied
|
||||
/// <see cref="IHistorianDataSource"/> (the read-only Wonderware client) overriding the
|
||||
/// <see cref="NullHistorianDataSource"/> default from <see cref="AddOtOpcUaRuntime"/>. Otherwise
|
||||
/// a no-op (the Null default stays and the node manager's HistoryRead returns
|
||||
/// <c>GoodNoData</c>-empty). The data source is injected so the Wonderware client can be supplied
|
||||
/// by the Host, which is the only project that references it.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to register with.</param>
|
||||
/// <param name="configuration">The configuration carrying the <c>ServerHistorian</c> section.</param>
|
||||
/// <param name="dataSourceFactory">
|
||||
/// Factory the Host supplies to build the concrete read <see cref="IHistorianDataSource"/>
|
||||
/// (the Wonderware client) from the bound options + the resolving provider.
|
||||
/// </param>
|
||||
/// <returns>The same <paramref name="services"/> instance for chaining.</returns>
|
||||
public static IServiceCollection AddServerHistorian(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Func<ServerHistorianOptions, IServiceProvider, IHistorianDataSource> dataSourceFactory)
|
||||
{
|
||||
var opts = configuration.GetSection(ServerHistorianOptions.SectionName).Get<ServerHistorianOptions>();
|
||||
if (opts is not { Enabled: true }) return services; // leave the Null default from AddOtOpcUaRuntime
|
||||
|
||||
foreach (var warning in opts.Validate())
|
||||
Serilog.Log.Logger.ForContext<ServerHistorianOptions>().Warning("ServerHistorian config: {ServerHistorianConfigWarning}", warning);
|
||||
|
||||
// Last-registration-wins over the TryAddSingleton Null default seeded by AddOtOpcUaRuntime.
|
||||
services.AddSingleton<IHistorianDataSource>(sp => dataSourceFactory(opts, sp));
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns the per-node driver-role actors on the host's <see cref="ActorSystem"/>:
|
||||
/// <see cref="DriverHostActor"/> (one per node), <see cref="DbHealthProbeActor"/>
|
||||
|
||||
Reference in New Issue
Block a user