@@ -1038,6 +1038,9 @@ public async Task<IList<MonitoredItem>> CreateItemsAsync(CancellationToken ct =
10381038 m_changeMask |= SubscriptionChangeMask . ItemsCreated ;
10391039 ChangesCompleted ( ) ;
10401040
1041+ // Restore triggering relationships after items are created
1042+ await RestoreTriggeringAsync ( ct ) . ConfigureAwait ( false ) ;
1043+
10411044 // return the list of items affected by the change.
10421045 return itemsToCreate ;
10431046 }
@@ -1137,6 +1140,82 @@ public async Task<IList<MonitoredItem>> DeleteItemsAsync(
11371140 return itemsToDelete ;
11381141 }
11391142
1143+ /// <summary>
1144+ /// Restores triggering relationships for monitored items that were
1145+ /// configured with triggers before reconnection.
1146+ /// </summary>
1147+ private async Task RestoreTriggeringAsync ( CancellationToken ct = default )
1148+ {
1149+ VerifySessionAndSubscriptionState ( true ) ;
1150+
1151+ // Build triggering groups outside of lock to avoid await in lock
1152+ Dictionary < uint , List < uint > > triggeringGroups ;
1153+ lock ( m_cache )
1154+ {
1155+ // Group monitored items by their triggering item
1156+ triggeringGroups = new Dictionary < uint , List < uint > > ( ) ;
1157+ foreach ( MonitoredItem item in m_monitoredItems . Values )
1158+ {
1159+ if ( item . TriggeredItems != null && item . TriggeredItems . Count > 0 )
1160+ {
1161+ // This item triggers other items
1162+ var triggeredServerIds = new List < uint > ( ) ;
1163+ foreach ( uint triggeredClientHandle in item . TriggeredItems )
1164+ {
1165+ // Find the monitored item by client handle
1166+ if ( m_monitoredItems . TryGetValue ( triggeredClientHandle , out MonitoredItem ? triggeredItem ) &&
1167+ triggeredItem . Status . Created )
1168+ {
1169+ triggeredServerIds . Add ( triggeredItem . Status . Id ) ;
1170+ }
1171+ }
1172+
1173+ if ( triggeredServerIds . Count > 0 )
1174+ {
1175+ if ( ! triggeringGroups . TryGetValue ( item . Status . Id , out List < uint > ? list ) )
1176+ {
1177+ list = [ ] ;
1178+ triggeringGroups [ item . Status . Id ] = list ;
1179+ }
1180+ list . AddRange ( triggeredServerIds ) ;
1181+ }
1182+ }
1183+ }
1184+ }
1185+
1186+ // Call SetTriggering for each triggering item
1187+ foreach ( var kvp in triggeringGroups )
1188+ {
1189+ uint triggeringItemId = kvp . Key ;
1190+ var linksToAdd = new UInt32Collection ( kvp . Value ) ;
1191+
1192+ try
1193+ {
1194+ await Session . SetTriggeringAsync (
1195+ null ,
1196+ Id ,
1197+ triggeringItemId ,
1198+ linksToAdd ,
1199+ null ,
1200+ ct ) . ConfigureAwait ( false ) ;
1201+
1202+ m_logger . LogInformation (
1203+ "Restored {Count} triggering links for MonitoredItem {TriggeringItemId} in Subscription {SubscriptionId}" ,
1204+ linksToAdd . Count ,
1205+ triggeringItemId ,
1206+ Id ) ;
1207+ }
1208+ catch ( Exception ex )
1209+ {
1210+ m_logger . LogError (
1211+ ex ,
1212+ "Failed to restore triggering links for MonitoredItem {TriggeringItemId} in Subscription {SubscriptionId}" ,
1213+ triggeringItemId ,
1214+ Id ) ;
1215+ }
1216+ }
1217+ }
1218+
11401219 /// <summary>
11411220 /// Set monitoring mode of items.
11421221 /// </summary>
@@ -1198,6 +1277,125 @@ public async Task<IList<MonitoredItem>> DeleteItemsAsync(
11981277 return errors ;
11991278 }
12001279
1280+ /// <summary>
1281+ /// Sets the triggering relationships for a monitored item in this subscription
1282+ /// and tracks them for automatic restoration after reconnection.
1283+ /// </summary>
1284+ /// <param name="triggeringItem">The monitored item that will trigger other items.</param>
1285+ /// <param name="linksToAdd">Monitored items to be reported when the triggering item changes.</param>
1286+ /// <param name="linksToRemove">Monitored items to stop reporting when the triggering item changes.</param>
1287+ /// <param name="ct">Cancellation token.</param>
1288+ /// <returns>The response from the server.</returns>
1289+ /// <exception cref="ArgumentNullException">Thrown when triggeringItem is null.</exception>
1290+ /// <exception cref="ServiceResultException">Thrown when the operation fails.</exception>
1291+ public async Task < SetTriggeringResponse > SetTriggeringAsync (
1292+ MonitoredItem triggeringItem ,
1293+ IList < MonitoredItem > ? linksToAdd ,
1294+ IList < MonitoredItem > ? linksToRemove ,
1295+ CancellationToken ct = default )
1296+ {
1297+ if ( triggeringItem == null )
1298+ {
1299+ throw new ArgumentNullException ( nameof ( triggeringItem ) ) ;
1300+ }
1301+
1302+ using Activity ? activity = m_telemetry . StartActivity ( ) ;
1303+ VerifySessionAndSubscriptionState ( true ) ;
1304+
1305+ if ( ! triggeringItem . Status . Created )
1306+ {
1307+ throw new ServiceResultException (
1308+ StatusCodes . BadInvalidState ,
1309+ "Triggering item has not been created on the server." ) ;
1310+ }
1311+
1312+ // Convert monitored items to server IDs
1313+ var serverIdsToAdd = new UInt32Collection ( ) ;
1314+ var clientHandlesToAdd = new UInt32Collection ( ) ;
1315+ if ( linksToAdd != null )
1316+ {
1317+ foreach ( MonitoredItem item in linksToAdd )
1318+ {
1319+ if ( ! item . Status . Created )
1320+ {
1321+ throw new ServiceResultException (
1322+ StatusCodes . BadInvalidState ,
1323+ $ "Monitored item '{ item . DisplayName } ' has not been created on the server.") ;
1324+ }
1325+ serverIdsToAdd . Add ( item . Status . Id ) ;
1326+ clientHandlesToAdd . Add ( item . ClientHandle ) ;
1327+ }
1328+ }
1329+
1330+ var serverIdsToRemove = new UInt32Collection ( ) ;
1331+ var clientHandlesToRemove = new UInt32Collection ( ) ;
1332+ if ( linksToRemove != null )
1333+ {
1334+ foreach ( MonitoredItem item in linksToRemove )
1335+ {
1336+ if ( ! item . Status . Created )
1337+ {
1338+ throw new ServiceResultException (
1339+ StatusCodes . BadInvalidState ,
1340+ $ "Monitored item '{ item . DisplayName } ' has not been created on the server.") ;
1341+ }
1342+ serverIdsToRemove . Add ( item . Status . Id ) ;
1343+ clientHandlesToRemove . Add ( item . ClientHandle ) ;
1344+ }
1345+ }
1346+
1347+ // Call the Session SetTriggering method
1348+ SetTriggeringResponse response = await Session . SetTriggeringAsync (
1349+ null ,
1350+ Id ,
1351+ triggeringItem . Status . Id ,
1352+ serverIdsToAdd ,
1353+ serverIdsToRemove ,
1354+ ct ) . ConfigureAwait ( false ) ;
1355+
1356+ // Update the triggering relationships for automatic restoration
1357+ lock ( m_cache )
1358+ {
1359+ // Initialize the triggered items collection if needed
1360+ triggeringItem . TriggeredItems ??= new UInt32Collection ( ) ;
1361+
1362+ // Add new links
1363+ if ( clientHandlesToAdd . Count > 0 )
1364+ {
1365+ foreach ( uint clientHandle in clientHandlesToAdd )
1366+ {
1367+ if ( ! triggeringItem . TriggeredItems . Contains ( clientHandle ) )
1368+ {
1369+ triggeringItem . TriggeredItems . Add ( clientHandle ) ;
1370+ }
1371+
1372+ // Update the triggered item to remember its triggering item
1373+ if ( m_monitoredItems . TryGetValue ( clientHandle , out MonitoredItem ? triggeredItem ) )
1374+ {
1375+ triggeredItem . TriggeringItemId = triggeringItem . Status . Id ;
1376+ }
1377+ }
1378+ }
1379+
1380+ // Remove links
1381+ if ( clientHandlesToRemove . Count > 0 )
1382+ {
1383+ foreach ( uint clientHandle in clientHandlesToRemove )
1384+ {
1385+ triggeringItem . TriggeredItems . Remove ( clientHandle ) ;
1386+
1387+ // Clear the triggering item reference
1388+ if ( m_monitoredItems . TryGetValue ( clientHandle , out MonitoredItem ? triggeredItem ) )
1389+ {
1390+ triggeredItem . TriggeringItemId = 0 ;
1391+ }
1392+ }
1393+ }
1394+ }
1395+
1396+ return response ;
1397+ }
1398+
12011399 /// <summary>
12021400 /// Tells the server to refresh all conditions being monitored by the subscription.
12031401 /// </summary>
@@ -1350,6 +1548,9 @@ await session.RemoveSubscriptionsAsync(subscriptionsToRemove, ct)
13501548 m_changeMask |= SubscriptionChangeMask . Transferred ;
13511549 ChangesCompleted ( ) ;
13521550
1551+ // Restore triggering relationships after subscription transfer
1552+ await RestoreTriggeringAsync ( ct ) . ConfigureAwait ( false ) ;
1553+
13531554 StartKeepAliveTimer ( ) ;
13541555
13551556 TraceState ( "TRANSFERRED ASYNC" ) ;
0 commit comments