Add ServerCapabilities/OperationLimits node, enable diagnostics, add OnModifyMonitoredItemsComplete override for DA compliance. Wire shelving, enable/disable, confirm, and addcomment handlers on alarm conditions with LocalTime/Quality event fields for Part 9 compliance. Add Aes128/Aes256 security profiles, X.509 certificate authentication, and AUDIT-prefixed auth logging. Fix flaky probe monitor test. Update docs for all changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
13 KiB
Service Update Summary
Updated service instance: C:\publish\lmxopcua\instance1
Update time: 2026-03-25 12:54-12:55 America/New_York
Backup created before deploy: C:\publish\lmxopcua\backups\20260325-125444
Configuration preserved:
C:\publish\lmxopcua\instance1\appsettings.jsonwas not overwritten.
Deployed binary:
C:\publish\lmxopcua\instance1\ZB.MOM.WW.LmxOpcUa.Host.exe- Last write time:
2026-03-25 12:53:58 - Size:
143360
Windows service:
- Name:
LmxOpcUa - Display name:
LMX OPC UA Server - Account:
LocalSystem - Status after update:
Running - Process ID after restart:
29236
Restart evidence:
- Service log file:
C:\publish\lmxopcua\instance1\logs\lmxopcua-20260325_004.log - Last startup line:
2026-03-25 12:55:08.619 -04:00 [INF] The LmxOpcUa service was started.
CLI Verification
Endpoint from deployed config:
opc.tcp://localhost:4840/LmxOpcUa
CLI used:
C:\Users\dohertj2\Desktop\lmxopcua\tools\opcuacli-dotnet\bin\Debug\net10.0\opcuacli-dotnet.exe
Commands run:
opcuacli-dotnet.exe connect -u opc.tcp://localhost:4840/LmxOpcUa
opcuacli-dotnet.exe read -u opc.tcp://localhost:4840/LmxOpcUa -n 'ns=1;s=MESReceiver_001.MoveInPartNumbers'
opcuacli-dotnet.exe read -u opc.tcp://localhost:4840/LmxOpcUa -n 'ns=1;s=MESReceiver_001.MoveInPartNumbers[]'
Observed results:
connect: succeeded, server reported asLmxOpcUa.read ns=1;s=MESReceiver_001.MoveInPartNumbers: succeeded with good status0x00000000.read ns=1;s=MESReceiver_001.MoveInPartNumbers[]: failed withBadNodeIdUnknown(0x80340000).
Instance 2 (Redundant Secondary)
Deployed: 2026-03-28
Deployment path: C:\publish\lmxopcua\instance2
Configuration:
OpcUa.Port:4841OpcUa.ServerName:LmxOpcUa2OpcUa.ApplicationUri:urn:localhost:LmxOpcUa:instance2Dashboard.Port:8082MxAccess.ClientName:LmxOpcUa2Redundancy.Enabled:trueRedundancy.Mode:WarmRedundancy.Role:SecondaryRedundancy.ServerUris:["urn:localhost:LmxOpcUa:instance1", "urn:localhost:LmxOpcUa:instance2"]
Windows service:
- Name:
LmxOpcUa2 - Display name:
LMX OPC UA Server (Instance 2) - Account:
LocalSystem - Endpoint:
opc.tcp://localhost:4841/LmxOpcUa
Instance 1 redundancy update (same date):
OpcUa.ApplicationUri:urn:localhost:LmxOpcUa:instance1Redundancy.Enabled:trueRedundancy.Mode:WarmRedundancy.Role:PrimaryRedundancy.ServerUris:["urn:localhost:LmxOpcUa:instance1", "urn:localhost:LmxOpcUa:instance2"]
CLI verification:
opcuacli-dotnet.exe redundancy -u opc.tcp://localhost:4840/LmxOpcUa
→ Redundancy Mode: Warm, Service Level: 200, Application URI: urn:localhost:LmxOpcUa:instance1
opcuacli-dotnet.exe redundancy -u opc.tcp://localhost:4841/LmxOpcUa
→ Redundancy Mode: Warm, Service Level: 150, Application URI: urn:localhost:LmxOpcUa:instance2
Both instances report the same ServerUriArray and expose the same Galaxy namespace (urn:ZB:LmxOpcUa).
LDAP Authentication Update
Updated: 2026-03-28
Both instances updated to use LDAP authentication via GLAuth.
Configuration changes (both instances):
Authentication.AllowAnonymous:true(anonymous can browse/read)Authentication.AnonymousCanWrite:false(anonymous writes blocked)Authentication.Ldap.Enabled:trueAuthentication.Ldap.Host:localhostAuthentication.Ldap.Port:3893Authentication.Ldap.BaseDN:dc=lmxopcua,dc=local
LDAP server: GLAuth v2.4.0 at C:\publish\glauth\ (Windows service: GLAuth)
Permission verification (instance1, port 4840):
anonymous read → allowed
anonymous write → denied (BadUserAccessDenied)
readonly read → allowed
readonly write → denied (BadUserAccessDenied)
readwrite write → allowed
admin write → allowed
alarmack write → denied (BadUserAccessDenied)
bad password → denied (connection rejected)
Alarm Notifier Chain Update
Updated: 2026-03-28
Both instances updated with alarm event propagation up the notifier chain.
Code changes:
- Alarm events now walk up the parent chain (
ReportEventUpNotifierChain), reporting to every ancestor node EventNotifier = SubscribeToEventsis set on all ancestors of alarm-containing nodes (EnableEventNotifierUpChain)- Removed separate
Server.ReportEventcall (no longer needed — the walk reaches the root)
No configuration changes required — alarm tracking was already enabled (AlarmTrackingEnabled: true).
Verification (instance1, port 4840):
alarms --node TestArea --refresh:
TestMachine_001.TestAlarm001 → visible (Severity=500, Retain=True)
TestMachine_001.TestAlarm002 → visible (Severity=500, Retain=True)
TestMachine_001.TestAlarm003 → visible (Severity=500, Retain=True)
TestMachine_002.TestAlarm001 → visible (Severity=500, Retain=True)
TestMachine_002.TestAlarm003 → visible (Severity=500, Retain=True)
alarms --node DEV --refresh:
Same 5 alarms visible at DEV (grandparent) level
Auth Consolidation Update
Updated: 2026-03-28
Both instances updated to consolidate LDAP roles into OPC UA session roles (RoleBasedIdentity.GrantedRoleIds).
Code changes:
- LDAP groups now map to custom OPC UA role NodeIds in
urn:zbmom:lmxopcua:rolesnamespace - Roles stored on session identity via
GrantedRoleIds— no username-to-role side cache - Permission checks use
GrantedRoleIds.Contains()instead of username extraction AnonymousCanWritebehavior is consistent regardless of LDAP state- Galaxy namespace moved from
ns=2tons=3(roles namespace isns=2)
No configuration changes required.
Verification (instance1, port 4840):
anonymous read → allowed
anonymous write → denied (BadUserAccessDenied, AnonymousCanWrite=false)
readonly write → denied (BadUserAccessDenied)
readwrite write → allowed
admin write → allowed
alarmack write → denied (BadUserAccessDenied)
bad password → rejected (connection failed)
Granular Write Roles Update
Updated: 2026-03-28
Both instances updated with granular write roles replacing the single ReadWrite role.
Code changes:
ReadWriterole replaced byWriteOperate,WriteTune,WriteConfigure- Write permission checks now consider the Galaxy security classification of the target attribute
SecurityClassificationstored inTagMetadatafor per-node lookup at write time
GLAuth changes:
- New groups:
WriteOperate(5502),WriteTune(5504),WriteConfigure(5505) - New users:
writeop,writetune,writeconfig adminuser added to all groups (5502, 5503, 5504, 5505)
Config changes (both instances):
Authentication.Ldap.ReadWriteGroupreplaced byWriteOperateGroup,WriteTuneGroup,WriteConfigureGroup
Verification (instance1, port 4840, Operate-classified attributes):
anonymous read → allowed
anonymous write → denied (AnonymousCanWrite=false)
readonly write → denied (no write role)
writeop write → allowed (WriteOperate matches Operate classification)
writetune write → denied (WriteTune doesn't match Operate)
writeconfig write → denied (WriteConfigure doesn't match Operate)
admin write → allowed (has all write roles)
Historian SDK Migration
Updated: 2026-04-06
Both instances updated to use the Wonderware Historian SDK (aahClientManaged.dll) instead of direct SQL queries for historical data access.
Code changes:
HistorianDataSourcerewritten fromSqlConnection/SqlDataReadertoArchestrA.HistorianAccessSDK- Persistent connection with lazy connect and auto-reconnect on failure
HistorianConfiguration.ConnectionStringreplaced withServerName,IntegratedSecurity,UserName,Password,PortHistorianDataSourcenow implementsIDisposable, disposed on service shutdownConfigurationValidatorvalidates Historian SDK settings at startup
SDK DLLs deployed to both instances:
aahClientManaged.dll(primary SDK, v2.0.0.0)aahClient.dll,aahClientCommon.dll(dependencies)Historian.CBE.dll,Historian.DPAPI.dll,ArchestrA.CloudHistorian.Contract.dll
Configuration changes (both instances):
Historian.ConnectionStringremovedHistorian.ServerName:"localhost"Historian.IntegratedSecurity:trueHistorian.Port:32568Historian.Enabled:true(unchanged)
Verification (instance1 startup log):
Historian.Enabled=true, ServerName=localhost, IntegratedSecurity=true, Port=32568
Historian.CommandTimeoutSeconds=30, MaxValuesPerRead=10000
=== Configuration Valid ===
LmxOpcUa service started successfully
HistoryServerCapabilities and Continuation Points
Updated: 2026-04-06
Both instances updated with OPC UA Part 11 spec compliance improvements.
Code changes:
HistoryServerCapabilitiesnode populated underServerCapabilitieswith all boolean capability propertiesAggregateFunctionsfolder populated with references to 7 supported aggregate functionsHistoryContinuationPointManageradded — stores remaining data when results exceedNumValuesPerNodeHistoryReadRawModifiedandHistoryReadProcessednow returnContinuationPointinHistoryReadResultfor partial reads- Follow-up requests with
ContinuationPointresume from stored state; invalid/expired points returnBadContinuationPointInvalid
No configuration changes required.
Verification (instance1 startup log):
HistoryServerCapabilities configured with 7 aggregate functions
LmxOpcUa service started successfully
Remaining Historian Gaps Fix
Updated: 2026-04-06
Both instances updated with remaining OPC UA Part 11 spec compliance fixes.
Code changes:
- Gap 4:
HistoryReadRawModifiedreturnsBadHistoryOperationUnsupportedwhenIsReadModified=true - Gap 5:
HistoryReadAtTimeoverride added withReadAtTimeAsyncusing SDKHistorianRetrievalMode.Interpolated - Gap 8:
HistoricalDataConfigurationStatechild nodes added to historized variables (Stepped=false,Definition="Wonderware Historian") - Gap 10:
ReturnBoundsparameter handled — boundaryDataValueentries withBadBoundNotFoundinserted at StartTime/EndTime - Gap 11:
StandardDeviationaggregate added to client enum, mapper, CLI (aliases:stddev/stdev), and UI dropdown
No configuration changes required.
Historical Event Access
Updated: 2026-04-06
Both instances updated with OPC UA historical event access (Gap 7).
Code changes:
HistorianDataSource.ReadEventsAsyncqueries Historian event store via separateHistorianConnectionType.EventconnectionLmxNodeManager.HistoryReadEventsoverride mapsHistorianEventrecords to OPC UAHistoryEventFieldListentriesAccessHistoryEventsCapabilityset totruewhenAlarmTrackingEnabledis true- Event fields: EventId, EventType, SourceNode, SourceName, Time, ReceiveTime, Message, Severity
No configuration changes required. All historian gaps (1-11) are now resolved.
Data Access Gaps Fix
Updated: 2026-04-06
Both instances updated with OPC UA DA spec compliance fixes.
Code changes:
ConfigureServerCapabilities()populatesServerCapabilitiesnode:ServerProfileArray,LocaleIdArray,MinSupportedSampleRate, continuation point limits, array/string limits, and 12OperationLimitsvaluesServer_ServerDiagnostics_EnabledFlagset totrue— SDK auto-tracks session/subscription countsOnModifyMonitoredItemsCompleteoverride logs monitored item modifications
No configuration changes required. All DA gaps (1-8) resolved.
Alarms & Conditions Gaps Fix
Updated: 2026-04-06
Both instances updated with OPC UA Part 9 alarm spec compliance fixes.
Code changes:
- Wired
OnConfirm,OnAddComment,OnEnableDisable,OnShelve,OnTimedUnshelvehandlers on eachAlarmConditionState - Shelving:
SetShelvingState()managesTimedShelve,OneShotShelve,Unshelvestate machine ReportAlarmEventnow populatesLocalTime(timezone offset + DST) andQualityevent fields- Flaky
Monitor_ProbeDataChange_PreventsStaleReconnecttest fixed (increased stale threshold from 2s to 5s)
No configuration changes required. All A&C gaps (1-10) resolved.
Security Gaps Fix
Updated: 2026-04-06
Both instances updated with OPC UA Part 2/4/7 security spec compliance fixes.
Code changes:
SecurityProfileResolver: Added 4 modern AES profiles (Aes128_Sha256_RsaOaep-Sign/SignAndEncrypt,Aes256_Sha256_RsaPss-Sign/SignAndEncrypt)OnImpersonateUser: AddedX509IdentityTokenhandling with CN extraction and role assignmentBuildUserTokenPolicies: AdvertisesUserTokenType.Certificatewhen non-None security profiles are configuredOnCertificateValidation: Enhanced logging with certificate thumbprint, subject, and expiry- Authentication audit logging:
AUDIT:prefixed log entries for success/failure with session ID and roles
No configuration changes required. All security gaps (1-10) resolved.
Notes
The service deployment and restart succeeded. The live CLI checks confirm the endpoint is reachable and that the array node identifier has changed to the bracketless form. The array value on the live service still prints as blank even though the status is good, so if this environment should have populated MoveInPartNumbers, the runtime data path still needs follow-up investigation.