21 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:
appsettings.json-- base configuration (required)appsettings.{DOTNET_ENVIRONMENT}.json-- environment-specific overlay (optional)- 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 |
RequestTimeoutSeconds |
int |
30 |
Outer safety timeout applied to sync-over-async MxAccess operations invoked from the OPC UA stack thread (Read, Write, address-space rebuild probe sync). Backstop for the inner ReadTimeoutSeconds / WriteTimeoutSeconds. A timed-out operation returns BadTimeout. Validator rejects values < 1 and warns if set below the inner Read/Write timeouts. See MXAccess Bridge. Stability review 2026-04-13 Finding 3 |
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 (inner async bound) |
RequestTimeoutSeconds |
int |
60 |
Outer safety timeout applied to sync-over-async Historian operations invoked from the OPC UA stack thread (HistoryReadRaw, HistoryReadProcessed, HistoryReadAtTime, HistoryReadEvents). Backstop for CommandTimeoutSeconds; a timed-out read returns BadTimeout. Validator rejects values < 1 and warns if set below CommandTimeoutSeconds. Stability review 2026-04-13 Finding 3 |
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-- Whentrue, the node manager createsAlarmConditionStatenodes for alarm attributes and monitorsInAlarmtransitions. 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 writeTestMachine*instead of$TestMachine*. Each list entry may itself contain comma-separated patterns ("TestMachine*, Pump_*") for convenience. When the list is non-empty butAlarmTrackingEnabledisfalse, the validator emits a warning because the filter has no effect. See Alarm Tracking for the full matching algorithm and telemetry.Historian.Enabled-- Whentrue, the service callsHistorianPluginLoader.TryLoad(config)to load theZB.MOM.WW.LmxOpcUa.Historian.Avevaplugin from theHistorian/subfolder next to the host exe and registers the resultingIHistorianDataSourcewith 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 istruebut the plugin or its SDK dependencies cannot be loaded, the server still starts and every history read returnsBadHistoryOperationUnsupportedwith a warning in the log.GalaxyRepository.ExtendedAttributes-- Whentrue, 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.Portmust be between 1 and 65535OpcUa.GalaxyNamemust not be emptyMxAccess.ClientNamemust not be emptyGalaxyRepository.ConnectionStringmust not be emptySecurity.MinimumCertificateKeySizemust be at least 2048- Unknown security profile names are logged as warnings
AutoAcceptClientCertificates = trueemits a warning- Only-
Noneprofile configuration emits a warning OpcUa.AlarmFilter.ObjectFiltersis non-empty whileOpcUa.AlarmTrackingEnabled = falseemits a warning (filter has no effect)Historian.ServerName(orHistorian.ServerNames) must not be empty whenHistorian.Enabled = trueHistorian.FailureCooldownSecondsmust be zero or positiveHistorian.ServerNameis set alongside a non-emptyHistorian.ServerNamesemits a warning (single ServerName is ignored)MxAccess.RuntimeStatusUnknownTimeoutSecondsbelow 5s emits a warning (below the reasonable floor for MxAccess initial-resolution latency)OpcUa.ApplicationUrimust be set whenRedundancy.Enabled = trueRedundancy.ServiceLevelBasemust be between 1 and 255Redundancy.ServerUrisshould contain at least 2 entries when enabled- Local
ApplicationUrishould appear inRedundancy.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,
"RequestTimeoutSeconds": 30
},
"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,
"RequestTimeoutSeconds": 60,
"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
}
}