Files
lmxopcua/docs/Configuration.md

20 KiB

Configuration

Overview

The service loads configuration from appsettings.json at startup using the Microsoft.Extensions.Configuration stack. AppConfiguration is the root holder class that aggregates typed sections: OpcUa, MxAccess, GalaxyRepository, Dashboard, Historian, Authentication, and Security. Each section binds to a dedicated POCO class with sensible defaults, so the service runs with zero configuration on a standard deployment.

Config Binding Pattern

The production constructor in OpcUaService builds the configuration pipeline and binds each JSON section to its typed class:

var configuration = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json", optional: false)
    .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", optional: true)
    .AddEnvironmentVariables()
    .Build();

_config = new AppConfiguration();
configuration.GetSection("OpcUa").Bind(_config.OpcUa);
configuration.GetSection("MxAccess").Bind(_config.MxAccess);
configuration.GetSection("GalaxyRepository").Bind(_config.GalaxyRepository);
configuration.GetSection("Dashboard").Bind(_config.Dashboard);
configuration.GetSection("Historian").Bind(_config.Historian);
configuration.GetSection("Authentication").Bind(_config.Authentication);
configuration.GetSection("Security").Bind(_config.Security);

This pattern uses IConfiguration.GetSection().Bind() rather than IOptions<T> because the service targets .NET Framework 4.8, where the full dependency injection container is not used.

Environment-Specific Overrides

The configuration pipeline supports three layers of override, applied in order:

  1. appsettings.json -- base configuration (required)
  2. appsettings.{DOTNET_ENVIRONMENT}.json -- environment-specific overlay (optional)
  3. Environment variables -- highest priority, useful for deployment automation

Set the DOTNET_ENVIRONMENT variable to load a named overlay file. For example, setting DOTNET_ENVIRONMENT=Staging loads appsettings.Staging.json if it exists.

Environment variables follow the standard Section__Property naming convention. For example, OpcUa__Port=5840 overrides the OPC UA port.

Configuration Sections

OpcUa

Controls the OPC UA server endpoint and session limits. Defined in OpcUaConfiguration.

Property Type Default Description
BindAddress string "0.0.0.0" IP address or hostname the server binds to. Use 0.0.0.0 for all interfaces, localhost for local-only, or a specific IP
Port int 4840 TCP port the OPC UA server listens on
EndpointPath string "/LmxOpcUa" Path appended to the host URI
ServerName string "LmxOpcUa" Server name presented to OPC UA clients
GalaxyName string "ZB" Galaxy name used as the OPC UA namespace
MaxSessions int 100 Maximum simultaneous OPC UA sessions
SessionTimeoutMinutes int 30 Idle session timeout in minutes
AlarmTrackingEnabled bool false Enables AlarmConditionState nodes for alarm attributes
AlarmFilter.ObjectFilters List<string> [] Wildcard template-name patterns (with *) that scope alarm tracking to matching objects and their descendants. Empty list disables filtering. See Alarm Tracking
ApplicationUri string? null Explicit application URI for this server instance. Required when redundancy is enabled. Defaults to urn:{GalaxyName}:LmxOpcUa when null

MxAccess

Controls the MXAccess runtime connection used for live tag reads and writes. Defined in MxAccessConfiguration.

Property Type Default Description
ClientName string "LmxOpcUa" Client name registered with MXAccess
NodeName string? null Optional Galaxy node name to target
GalaxyName string? null Optional Galaxy name for MXAccess reference resolution
ReadTimeoutSeconds int 5 Maximum wait for a live tag read
WriteTimeoutSeconds int 5 Maximum wait for a write acknowledgment
MaxConcurrentOperations int 10 Cap on concurrent MXAccess operations
MonitorIntervalSeconds int 5 Connectivity monitor probe interval
AutoReconnect bool true Automatically re-establish dropped MXAccess sessions
ProbeTag string? null Optional tag used to verify the runtime returns fresh data
ProbeStaleThresholdSeconds int 60 Seconds a probe value may remain unchanged before the connection is considered stale
RuntimeStatusProbesEnabled bool true Advises <Host>.ScanState on every deployed $WinPlatform and $AppEngine to track per-host runtime state. Drives the Galaxy Runtime dashboard panel, HealthCheck Rule 2e, and the Read-path short-circuit that invalidates OPC UA variable quality when a host is Stopped. Set false to return to legacy behavior where host state is invisible and the bridge serves whatever quality MxAccess reports for individual tags. See MXAccess Bridge
RuntimeStatusUnknownTimeoutSeconds int 15 Maximum seconds to wait for the initial probe callback before marking a host as Stopped. Only applies to the Unknown → Stopped transition; Running hosts never time out because ScanState is delivered on-change only. A value below 5s triggers a validator warning

GalaxyRepository

Controls the Galaxy repository database connection used to build the OPC UA address space. Defined in GalaxyRepositoryConfiguration.

Property Type Default Description
ConnectionString string "Server=localhost;Database=ZB;Integrated Security=true;" SQL Server connection string for the Galaxy database
ChangeDetectionIntervalSeconds int 30 How often the service polls for Galaxy deploy changes
CommandTimeoutSeconds int 30 SQL command timeout for repository queries
ExtendedAttributes bool false Load extended Galaxy attribute metadata into the OPC UA model

Dashboard

Controls the embedded HTTP status dashboard. Defined in DashboardConfiguration.

Property Type Default Description
Enabled bool true Whether the status dashboard is hosted
Port int 8081 HTTP port for the dashboard endpoint
RefreshIntervalSeconds int 10 HTML auto-refresh interval in seconds

Historian

Controls the Wonderware Historian SDK connection for OPC UA historical data access. Defined in HistorianConfiguration.

Property Type Default Description
Enabled bool false Enables OPC UA historical data access
ServerName string "localhost" Single Historian server hostname used when ServerNames is empty. Preserved for backward compatibility with pre-cluster deployments
ServerNames List<string> [] Ordered list of Historian cluster nodes. When non-empty, supersedes ServerName and enables read-only cluster failover. See Historical Data Access
FailureCooldownSeconds int 60 How long a failed cluster node is skipped before being re-tried. Zero disables the cooldown
IntegratedSecurity bool true Use Windows authentication
UserName string? null Username when IntegratedSecurity is false
Password string? null Password when IntegratedSecurity is false
Port int 32568 Historian TCP port
CommandTimeoutSeconds int 30 SDK packet timeout in seconds
MaxValuesPerRead int 10000 Maximum values returned per HistoryRead request

Authentication

Controls user authentication and write authorization for the OPC UA server. Defined in AuthenticationConfiguration.

Property Type Default Description
AllowAnonymous bool true Accepts anonymous client connections when true
AnonymousCanWrite bool true Permits anonymous users to write when true

LDAP Authentication

When Ldap.Enabled is true, credentials are validated against the configured LDAP server and group membership determines OPC UA permissions.

Property Type Default Description
Ldap.Enabled bool false Enables LDAP authentication
Ldap.Host string localhost LDAP server hostname
Ldap.Port int 3893 LDAP server port
Ldap.BaseDN string dc=lmxopcua,dc=local Base DN for LDAP operations
Ldap.BindDnTemplate string cn={username},dc=lmxopcua,dc=local Bind DN template ({username} is replaced)
Ldap.ServiceAccountDn string "" Service account DN for group lookups
Ldap.ServiceAccountPassword string "" Service account password
Ldap.TimeoutSeconds int 5 Connection timeout
Ldap.ReadOnlyGroup string ReadOnly LDAP group granting read-only access
Ldap.WriteOperateGroup string WriteOperate LDAP group granting write access for FreeAccess/Operate attributes
Ldap.WriteTuneGroup string WriteTune LDAP group granting write access for Tune attributes
Ldap.WriteConfigureGroup string WriteConfigure LDAP group granting write access for Configure attributes
Ldap.AlarmAckGroup string AlarmAck LDAP group granting alarm acknowledgment

Permission Model

When LDAP is enabled, LDAP group membership is mapped to OPC UA session role NodeIds during authentication. All authenticated LDAP users can browse and read nodes regardless of group membership. Groups grant additional permissions:

LDAP Group Permission
ReadOnly No additional permissions (read-only access)
WriteOperate Write FreeAccess and Operate attributes
WriteTune Write Tune attributes
WriteConfigure Write Configure attributes
AlarmAck Acknowledge alarms

Users can belong to multiple groups. The admin user in the default GLAuth configuration belongs to all three groups.

Write access depends on both the user's role and the Galaxy attribute's security classification. See the Effective Permission Matrix in the Security Guide for the full breakdown.

Example configuration:

"Authentication": {
  "AllowAnonymous": true,
  "AnonymousCanWrite": false,
  "Ldap": {
    "Enabled": true,
    "Host": "localhost",
    "Port": 3893,
    "BaseDN": "dc=lmxopcua,dc=local",
    "BindDnTemplate": "cn={username},dc=lmxopcua,dc=local",
    "ServiceAccountDn": "cn=serviceaccount,dc=lmxopcua,dc=local",
    "ServiceAccountPassword": "serviceaccount123",
    "TimeoutSeconds": 5,
    "ReadOnlyGroup": "ReadOnly",
    "WriteOperateGroup": "WriteOperate",
    "WriteTuneGroup": "WriteTune",
    "WriteConfigureGroup": "WriteConfigure",
    "AlarmAckGroup": "AlarmAck"
  }
}

Security

Controls OPC UA transport security profiles and certificate handling. Defined in SecurityProfileConfiguration. See Security Guide for detailed usage.

Property Type Default Description
Profiles List<string> ["None"] Security profiles to expose. Valid: None, Basic256Sha256-Sign, Basic256Sha256-SignAndEncrypt, Aes128_Sha256_RsaOaep-Sign, Aes128_Sha256_RsaOaep-SignAndEncrypt, Aes256_Sha256_RsaPss-Sign, Aes256_Sha256_RsaPss-SignAndEncrypt
AutoAcceptClientCertificates bool true Auto-accept untrusted client certificates. Set to false in production
RejectSHA1Certificates bool true Reject client certificates signed with SHA-1
MinimumCertificateKeySize int 2048 Minimum RSA key size for client certificates
PkiRootPath string? null Override for PKI root directory. Defaults to %LOCALAPPDATA%\OPC Foundation\pki
CertificateSubject string? null Override for server certificate subject. Defaults to CN={ServerName}, O=ZB MOM, DC=localhost

Example — production deployment with encrypted transport:

"Security": {
  "Profiles": ["Basic256Sha256-SignAndEncrypt"],
  "AutoAcceptClientCertificates": false,
  "RejectSHA1Certificates": true,
  "MinimumCertificateKeySize": 2048
}

Redundancy

Controls non-transparent OPC UA redundancy. Defined in RedundancyConfiguration. See Redundancy Guide for detailed usage.

Property Type Default Description
Enabled bool false Enables redundancy mode and ServiceLevel computation
Mode string "Warm" Redundancy mode: Warm or Hot
Role string "Primary" Instance role: Primary (higher ServiceLevel) or Secondary
ServerUris List<string> [] ApplicationUri values for all servers in the redundant set
ServiceLevelBase int 200 Base ServiceLevel when healthy (1-255). Secondary receives base - 50

Example — two-instance redundant pair (Primary):

"Redundancy": {
  "Enabled": true,
  "Mode": "Warm",
  "Role": "Primary",
  "ServerUris": ["urn:localhost:LmxOpcUa:instance1", "urn:localhost:LmxOpcUa:instance2"],
  "ServiceLevelBase": 200
}

Feature Flags

Three boolean properties act as feature flags that control optional subsystems:

  • OpcUa.AlarmTrackingEnabled -- When true, the node manager creates AlarmConditionState nodes for alarm attributes and monitors InAlarm transitions. Disabled by default because alarm tracking adds per-attribute overhead.
  • OpcUa.AlarmFilter.ObjectFilters -- List of wildcard template-name patterns that scope alarm tracking to matching objects and their descendants. An empty list preserves the current unfiltered behavior; a non-empty list includes an object only when any name in its template derivation chain matches any pattern, then propagates the inclusion to every descendant in the containment hierarchy. * is the only wildcard, matching is case-insensitive, and the Galaxy $ prefix on template names is normalized so operators can write TestMachine* instead of $TestMachine*. Each list entry may itself contain comma-separated patterns ("TestMachine*, Pump_*") for convenience. When the list is non-empty but AlarmTrackingEnabled is false, the validator emits a warning because the filter has no effect. See Alarm Tracking for the full matching algorithm and telemetry.
  • Historian.Enabled -- When true, the service calls HistorianPluginLoader.TryLoad(config) to load the ZB.MOM.WW.LmxOpcUa.Historian.Aveva plugin from the Historian/ subfolder next to the host exe and registers the resulting IHistorianDataSource with the OPC UA server host. Disabled by default because not all deployments have a Historian instance -- when disabled the plugin is not probed and the Wonderware SDK DLLs are not required on the host. If the flag is true but the plugin or its SDK dependencies cannot be loaded, the server still starts and every history read returns BadHistoryOperationUnsupported with a warning in the log.
  • GalaxyRepository.ExtendedAttributes -- When true, the repository loads additional Galaxy attribute metadata beyond the core set needed for the address space. Disabled by default to minimize startup query time.

Configuration Validation

ConfigurationValidator.ValidateAndLog() runs at the start of OpcUaService.Start(). It logs every resolved configuration value at Information level and validates required constraints:

  • OpcUa.Port must be between 1 and 65535
  • OpcUa.GalaxyName must not be empty
  • MxAccess.ClientName must not be empty
  • GalaxyRepository.ConnectionString must not be empty
  • Security.MinimumCertificateKeySize must be at least 2048
  • Unknown security profile names are logged as warnings
  • AutoAcceptClientCertificates = true emits a warning
  • Only-None profile configuration emits a warning
  • OpcUa.AlarmFilter.ObjectFilters is non-empty while OpcUa.AlarmTrackingEnabled = false emits a warning (filter has no effect)
  • Historian.ServerName (or Historian.ServerNames) must not be empty when Historian.Enabled = true
  • Historian.FailureCooldownSeconds must be zero or positive
  • Historian.ServerName is set alongside a non-empty Historian.ServerNames emits a warning (single ServerName is ignored)
  • MxAccess.RuntimeStatusUnknownTimeoutSeconds below 5s emits a warning (below the reasonable floor for MxAccess initial-resolution latency)
  • OpcUa.ApplicationUri must be set when Redundancy.Enabled = true
  • Redundancy.ServiceLevelBase must be between 1 and 255
  • Redundancy.ServerUris should contain at least 2 entries when enabled
  • Local ApplicationUri should appear in Redundancy.ServerUris

If validation fails, the service throws InvalidOperationException and does not start.

Test Constructor Pattern

OpcUaService provides an internal constructor that accepts pre-built dependencies instead of loading appsettings.json:

internal OpcUaService(
    AppConfiguration config,
    IMxProxy? mxProxy,
    IGalaxyRepository? galaxyRepository,
    IMxAccessClient? mxAccessClientOverride = null,
    bool hasMxAccessClientOverride = false)

Integration tests use this constructor to inject substitute implementations of IMxProxy, IGalaxyRepository, and IMxAccessClient, bypassing the STA thread, COM interop, and SQL Server dependencies. The hasMxAccessClientOverride flag tells the service to use the injected IMxAccessClient directly instead of creating one from the IMxProxy on the STA thread.

Example appsettings.json

{
  "OpcUa": {
    "BindAddress": "0.0.0.0",
    "Port": 4840,
    "EndpointPath": "/LmxOpcUa",
    "ServerName": "LmxOpcUa",
    "GalaxyName": "ZB",
    "MaxSessions": 100,
    "SessionTimeoutMinutes": 30,
    "AlarmTrackingEnabled": false,
    "AlarmFilter": {
      "ObjectFilters": []
    },
    "ApplicationUri": null
  },
  "MxAccess": {
    "ClientName": "LmxOpcUa",
    "NodeName": null,
    "GalaxyName": null,
    "ReadTimeoutSeconds": 5,
    "WriteTimeoutSeconds": 5,
    "MaxConcurrentOperations": 10,
    "MonitorIntervalSeconds": 5,
    "AutoReconnect": true,
    "ProbeTag": null,
    "ProbeStaleThresholdSeconds": 60,
    "RuntimeStatusProbesEnabled": true,
    "RuntimeStatusUnknownTimeoutSeconds": 15
  },
  "GalaxyRepository": {
    "ConnectionString": "Server=localhost;Database=ZB;Integrated Security=true;",
    "ChangeDetectionIntervalSeconds": 30,
    "CommandTimeoutSeconds": 30,
    "ExtendedAttributes": false
  },
  "Dashboard": {
    "Enabled": true,
    "Port": 8081,
    "RefreshIntervalSeconds": 10
  },
  "Historian": {
    "Enabled": false,
    "ServerName": "localhost",
    "ServerNames": [],
    "FailureCooldownSeconds": 60,
    "IntegratedSecurity": true,
    "UserName": null,
    "Password": null,
    "Port": 32568,
    "CommandTimeoutSeconds": 30,
    "MaxValuesPerRead": 10000
  },
  "Authentication": {
    "AllowAnonymous": true,
    "AnonymousCanWrite": true,
    "Ldap": {
      "Enabled": false
    }
  },
  "Security": {
    "Profiles": ["None"],
    "AutoAcceptClientCertificates": true,
    "RejectSHA1Certificates": true,
    "MinimumCertificateKeySize": 2048,
    "PkiRootPath": null,
    "CertificateSubject": null
  },
  "Redundancy": {
    "Enabled": false,
    "Mode": "Warm",
    "Role": "Primary",
    "ServerUris": [],
    "ServiceLevelBase": 200
  }
}