Phase 7 follow-up #246 — Phase7Composer + Program.cs wire-in
Activates the Phase 7 engines in production. Loads Script + VirtualTag + ScriptedAlarm rows from the bootstrapped generation, wires the engines through the Phase7EngineComposer kernel (#243), starts the DriverSubscriptionBridge feed (#244), and late-binds the resulting IReadable sources to OpcUaApplicationHost before OPC UA server start. ## Phase7Composer (Server.Phase7) Singleton orchestrator. PrepareAsync loads the three Phase 7 row sets in one DB scope, builds CachedTagUpstreamSource, calls Phase7EngineComposer.Compose, constructs DriverSubscriptionBridge with one DriverFeed per registered ISubscribable driver (path-to-fullRef map built from EquipmentNamespaceContent via MapPathsToFullRefs), starts the bridge. DisposeAsync tears down in the right order: bridge first (no more events fired into the cache), then engines (cascades + timers stop), then any disposable sink. MapPathsToFullRefs: deterministic path convention is /{areaName}/{lineName}/{equipmentName}/{tagName} matching exactly what EquipmentNodeWalker emits into the OPC UA browse tree, so script literals against the operator-visible UNS tree work without translation. Tags missing EquipmentId or pointing at unknown Equipment are skipped silently (Galaxy SystemPlatform-style tags + dangling references handled). ## OpcUaApplicationHost.SetPhase7Sources New late-bind setter. Throws InvalidOperationException if called after StartAsync because OtOpcUaServer + DriverNodeManagers capture the field values at construction; mutation post-start would silently fail. ## OpcUaServerService After bootstrap loads the current generation, calls phase7Composer.PrepareAsync + applicationHost.SetPhase7Sources before applicationHost.StartAsync. StopAsync disposes Phase7Composer first so the bridge stops feeding the cache before the OPC UA server tears down its node managers (avoids in-flight cascades surfacing as noisy shutdown warnings). ## Program.cs Registers IAlarmHistorianSink as NullAlarmHistorianSink.Instance (task #247 swaps in the real Galaxy.Host-writer-backed SqliteStoreAndForwardSink), Serilog root logger, and Phase7Composer singleton. ## Tests — 5 new Phase7ComposerMappingTests = 34 Phase 7 tests total Maps tag → walker UNS path, skips null EquipmentId, skips unknown Equipment reference, multiple tags under same equipment map distinctly, empty content yields empty map. Pure functions; no DI/DB needed. The real PrepareAsync DB query path can't be exercised without SQL Server in the test environment — it's exercised by the live E2E smoke (task #240) which unblocks once #247 lands. ## Phase 7 production wiring chain status - ✅ #243 composition kernel - ✅ #245 scripted-alarm IReadable adapter - ✅ #244 driver bridge - ✅ #246 this — Program.cs wire-in - 🟡 #247 — Galaxy.Host SqliteStoreAndForwardSink writer adapter (replaces NullSink) - 🟡 #240 — live E2E smoke (unblocks once #247 lands)
This commit is contained in:
@@ -34,9 +34,11 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
// Phase 7 Stream G follow-up (task #239). When composed with the VirtualTagEngine +
|
||||
// ScriptedAlarmEngine sources these route node reads to the engines instead of the
|
||||
// driver. Null = Phase 7 engines not enabled for this deployment (identical to pre-
|
||||
// Phase-7 behaviour).
|
||||
private readonly ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? _virtualReadable;
|
||||
private readonly ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? _scriptedAlarmReadable;
|
||||
// Phase-7 behaviour). Late-bindable via SetPhase7Sources because the engines need
|
||||
// the bootstrapped generation id before they can compose, which is only known after
|
||||
// the host has been DI-constructed (task #246).
|
||||
private ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? _virtualReadable;
|
||||
private ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? _scriptedAlarmReadable;
|
||||
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly ILogger<OpcUaApplicationHost> _logger;
|
||||
@@ -75,6 +77,24 @@ public sealed class OpcUaApplicationHost : IAsyncDisposable
|
||||
|
||||
public OtOpcUaServer? Server => _server;
|
||||
|
||||
/// <summary>
|
||||
/// Late-bind the Phase 7 engine-backed <c>IReadable</c> sources. Must be
|
||||
/// called BEFORE <see cref="StartAsync"/> — once the OPC UA server starts, the
|
||||
/// <see cref="OtOpcUaServer"/> ctor captures the field values + per-node
|
||||
/// <see cref="DriverNodeManager"/>s are constructed. Calling this after start has
|
||||
/// no effect on already-materialized node managers.
|
||||
/// </summary>
|
||||
public void SetPhase7Sources(
|
||||
ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? virtualReadable,
|
||||
ZB.MOM.WW.OtOpcUa.Core.Abstractions.IReadable? scriptedAlarmReadable)
|
||||
{
|
||||
if (_server is not null)
|
||||
throw new InvalidOperationException(
|
||||
"Phase 7 sources must be set before OpcUaApplicationHost.StartAsync; the OtOpcUaServer + DriverNodeManagers have already captured the previous values.");
|
||||
_virtualReadable = virtualReadable;
|
||||
_scriptedAlarmReadable = scriptedAlarmReadable;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the <see cref="ApplicationConfiguration"/>, validates/creates the application
|
||||
/// certificate, constructs + starts the <see cref="OtOpcUaServer"/>, then drives
|
||||
|
||||
Reference in New Issue
Block a user