Replace direct SQL queries against Historian Runtime database with the Wonderware Historian managed SDK (ArchestrA.HistorianAccess). Add HistoryServerCapabilities node, AggregateFunctions folder, continuation points, ReadAtTime interpolation, ReturnBounds, ReadModified rejection, HistoricalDataConfiguration per node, historical event access, and client-side StandardDeviation aggregate support. Remove screenshot tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
16 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 |
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 |
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" |
Historian server hostname |
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 |
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.Historian.Enabled-- Whentrue, the service creates aHistorianDataSourceconnected to the Wonderware Historian via the aahClientManaged SDK and registers it with the OPC UA server host. Disabled by default because not all deployments have a Historian instance.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.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,
"ApplicationUri": null
},
"MxAccess": {
"ClientName": "LmxOpcUa",
"NodeName": null,
"GalaxyName": null,
"ReadTimeoutSeconds": 5,
"WriteTimeoutSeconds": 5,
"MaxConcurrentOperations": 10,
"MonitorIntervalSeconds": 5,
"AutoReconnect": true,
"ProbeTag": null,
"ProbeStaleThresholdSeconds": 60
},
"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",
"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
}
}