140 lines
6.9 KiB
C#
140 lines
6.9 KiB
C#
using System;
|
|
using System.Security.Principal;
|
|
using System.Threading;
|
|
using Serilog;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Galaxy;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Historian;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.MxAccess;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Ipc;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Sta;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host;
|
|
|
|
/// <summary>
|
|
/// Entry point for the <c>OtOpcUaGalaxyHost</c> Windows service / console host. Reads the
|
|
/// pipe name, allowed-SID, and shared secret from environment (passed by the supervisor at
|
|
/// spawn time per <c>driver-stability.md</c>).
|
|
/// </summary>
|
|
public static class Program
|
|
{
|
|
public static int Main(string[] args)
|
|
{
|
|
Log.Logger = new LoggerConfiguration()
|
|
.MinimumLevel.Information()
|
|
.WriteTo.File(
|
|
@"%ProgramData%\OtOpcUa\galaxy-host-.log".Replace("%ProgramData%", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)),
|
|
rollingInterval: RollingInterval.Day)
|
|
.CreateLogger();
|
|
|
|
try
|
|
{
|
|
var pipeName = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_PIPE") ?? "OtOpcUaGalaxy";
|
|
var allowedSidValue = Environment.GetEnvironmentVariable("OTOPCUA_ALLOWED_SID")
|
|
?? throw new InvalidOperationException("OTOPCUA_ALLOWED_SID not set — supervisor must pass the server principal SID");
|
|
var sharedSecret = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_SECRET")
|
|
?? throw new InvalidOperationException("OTOPCUA_GALAXY_SECRET not set — supervisor must pass the per-process secret at spawn time");
|
|
|
|
var allowedSid = new SecurityIdentifier(allowedSidValue);
|
|
|
|
using var server = new PipeServer(pipeName, allowedSid, sharedSecret, Log.Logger);
|
|
using var cts = new CancellationTokenSource();
|
|
Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };
|
|
|
|
Log.Information("OtOpcUaGalaxyHost starting — pipe={Pipe} allowedSid={Sid}", pipeName, allowedSidValue);
|
|
|
|
// Backend selection — env var picks the implementation:
|
|
// OTOPCUA_GALAXY_BACKEND=stub → StubGalaxyBackend (no Galaxy required)
|
|
// OTOPCUA_GALAXY_BACKEND=db → DbBackedGalaxyBackend (Discover only, against ZB)
|
|
// OTOPCUA_GALAXY_BACKEND=mxaccess → MxAccessGalaxyBackend (real COM + ZB; default)
|
|
var backendKind = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_BACKEND")?.ToLowerInvariant() ?? "mxaccess";
|
|
var zbConn = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_ZB_CONN")
|
|
?? "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;";
|
|
var clientName = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_CLIENT_NAME") ?? "OtOpcUa-Galaxy.Host";
|
|
|
|
IGalaxyBackend backend;
|
|
StaPump? pump = null;
|
|
MxAccessClient? mx = null;
|
|
switch (backendKind)
|
|
{
|
|
case "stub":
|
|
backend = new StubGalaxyBackend();
|
|
break;
|
|
case "db":
|
|
backend = new DbBackedGalaxyBackend(new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = zbConn }));
|
|
break;
|
|
default: // mxaccess
|
|
pump = new StaPump("Galaxy.Sta");
|
|
pump.WaitForStartedAsync().GetAwaiter().GetResult();
|
|
mx = new MxAccessClient(pump, new MxProxyAdapter(), clientName);
|
|
var historian = BuildHistorianIfEnabled();
|
|
backend = new MxAccessGalaxyBackend(
|
|
new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = zbConn }),
|
|
mx,
|
|
historian);
|
|
break;
|
|
}
|
|
|
|
Log.Information("OtOpcUaGalaxyHost backend={Backend}", backendKind);
|
|
var handler = new GalaxyFrameHandler(backend, Log.Logger);
|
|
try { server.RunAsync(handler, cts.Token).GetAwaiter().GetResult(); }
|
|
finally
|
|
{
|
|
(backend as IDisposable)?.Dispose();
|
|
mx?.Dispose();
|
|
pump?.Dispose();
|
|
}
|
|
|
|
Log.Information("OtOpcUaGalaxyHost stopped cleanly");
|
|
return 0;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Fatal(ex, "OtOpcUaGalaxyHost fatal");
|
|
return 2;
|
|
}
|
|
finally { Log.CloseAndFlush(); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds a <see cref="HistorianDataSource"/> from the OTOPCUA_HISTORIAN_* environment
|
|
/// variables the supervisor passes at spawn time. Returns null when the historian is
|
|
/// disabled (default) so <c>MxAccessGalaxyBackend.HistoryReadAsync</c> returns a clear
|
|
/// "not configured" error instead of attempting an SDK connection to localhost.
|
|
/// </summary>
|
|
private static IHistorianDataSource? BuildHistorianIfEnabled()
|
|
{
|
|
var enabled = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_ENABLED");
|
|
if (!string.Equals(enabled, "true", StringComparison.OrdinalIgnoreCase) && enabled != "1")
|
|
return null;
|
|
|
|
var cfg = new HistorianConfiguration
|
|
{
|
|
Enabled = true,
|
|
ServerName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_SERVER") ?? "localhost",
|
|
Port = TryParseInt("OTOPCUA_HISTORIAN_PORT", 32568),
|
|
IntegratedSecurity = !string.Equals(Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_INTEGRATED"), "false", StringComparison.OrdinalIgnoreCase),
|
|
UserName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_USER"),
|
|
Password = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_PASS"),
|
|
CommandTimeoutSeconds = TryParseInt("OTOPCUA_HISTORIAN_TIMEOUT_SEC", 30),
|
|
MaxValuesPerRead = TryParseInt("OTOPCUA_HISTORIAN_MAX_VALUES", 10000),
|
|
FailureCooldownSeconds = TryParseInt("OTOPCUA_HISTORIAN_COOLDOWN_SEC", 60),
|
|
};
|
|
|
|
var servers = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_SERVERS");
|
|
if (!string.IsNullOrWhiteSpace(servers))
|
|
cfg.ServerNames = new System.Collections.Generic.List<string>(
|
|
servers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
|
|
|
|
Log.Information("Historian enabled — {NodeCount} configured node(s), port={Port}",
|
|
cfg.ServerNames.Count > 0 ? cfg.ServerNames.Count : 1, cfg.Port);
|
|
return new HistorianDataSource(cfg);
|
|
}
|
|
|
|
private static int TryParseInt(string envName, int defaultValue)
|
|
{
|
|
var raw = Environment.GetEnvironmentVariable(envName);
|
|
return int.TryParse(raw, out var parsed) ? parsed : defaultValue;
|
|
}
|
|
}
|