# 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: ```csharp 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` 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 | ### 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 connection for OPC UA historical data access. Defined in `HistorianConfiguration`. | Property | Type | Default | Description | |----------|------|---------|-------------| | `Enabled` | `bool` | `false` | Enables OPC UA historical data access | | `ConnectionString` | `string` | `"Server=localhost;Database=Runtime;Integrated Security=true;"` | Connection string for the Historian Runtime database | | `CommandTimeoutSeconds` | `int` | `30` | SQL command timeout for historian queries | | `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` | | `Users` | `List` | `[]` | List of username/password credentials for `UserName` token authentication | Each entry in the `Users` list has two properties: `Username` (string) and `Password` (string). The defaults preserve the existing behavior: anonymous clients can connect, read, and write with no credentials required. To restrict writes to authenticated users, set `AnonymousCanWrite` to `false` and add entries to the `Users` list. Example configuration: ```json "Authentication": { "AllowAnonymous": true, "AnonymousCanWrite": false, "Users": [ { "Username": "operator", "Password": "op123" }, { "Username": "engineer", "Password": "eng456" } ] } ``` ### Security Controls OPC UA transport security profiles and certificate handling. Defined in `SecurityProfileConfiguration`. See [Security Guide](security.md) for detailed usage. | Property | Type | Default | Description | |----------|------|---------|-------------| | `Profiles` | `List` | `["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: ```json "Security": { "Profiles": ["Basic256Sha256-SignAndEncrypt"], "AutoAcceptClientCertificates": false, "RejectSHA1Certificates": true, "MinimumCertificateKeySize": 2048 } ``` ## 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. - **`Historian.Enabled`** -- When `true`, the service creates a `HistorianDataSource` connected to the Wonderware Historian Runtime database and registers it with the OPC UA server host. Disabled by default because not all deployments have a Historian instance. - **`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 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`: ```csharp 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 ```json { "OpcUa": { "BindAddress": "0.0.0.0", "Port": 4840, "EndpointPath": "/LmxOpcUa", "ServerName": "LmxOpcUa", "GalaxyName": "ZB", "MaxSessions": 100, "SessionTimeoutMinutes": 30, "AlarmTrackingEnabled": false }, "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, "ConnectionString": "Server=localhost;Database=Runtime;Integrated Security=true;", "CommandTimeoutSeconds": 30, "MaxValuesPerRead": 10000 }, "Authentication": { "AllowAnonymous": true, "AnonymousCanWrite": true, "Users": [] }, "Security": { "Profiles": ["None"], "AutoAcceptClientCertificates": true, "RejectSHA1Certificates": true, "MinimumCertificateKeySize": 2048, "PkiRootPath": null, "CertificateSubject": null } } ```