fix(dcl): apply native-alarm conditionFilter (client-side gate + OPC UA WhereClause) (#8)
conditionFilter was plumbed end-to-end but applied nowhere — a filtered source silently mirrored all conditions. Define the filter as a comma-separated, case-insensitive list of condition type names (blank = all); enforce it authoritatively client-side in DataConnectionActor routing (uniform across OPC UA + MxGateway) and, for OPC UA, additionally build a server-side EventFilter WhereClause as a bandwidth optimization.
This commit is contained in:
@@ -99,8 +99,14 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
|
||||
// routed to subscribers (NativeAlarmActors) by source-object reference.
|
||||
/// <summary>sourceReference → set of subscriber actor refs (NativeAlarmActors), for routing + ref-count.</summary>
|
||||
private readonly Dictionary<string, HashSet<IActorRef>> _alarmSourceSubscribers = new();
|
||||
/// <summary>sourceReference → optional condition filter (first subscriber wins).</summary>
|
||||
/// <summary>sourceReference → raw condition filter string passed to the adapter (first subscriber wins).</summary>
|
||||
private readonly Dictionary<string, string?> _alarmSourceFilter = new();
|
||||
/// <summary>
|
||||
/// sourceReference → parsed condition-type predicate (M2.4 / #8). The authoritative
|
||||
/// client-side gate in <see cref="HandleAlarmTransitionReceived"/>; applies uniformly
|
||||
/// across OPC UA and the gateway-wide MxGateway feed.
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, AlarmConditionFilter> _alarmSourceFilterPredicate = new();
|
||||
/// <summary>sourceReference → adapter alarm subscription id.</summary>
|
||||
private readonly Dictionary<string, string> _alarmSubscriptionIds = new();
|
||||
/// <summary>sourceReferences whose adapter SubscribeAlarmsAsync is currently in flight.</summary>
|
||||
@@ -1480,6 +1486,9 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
|
||||
}
|
||||
subs.Add(subscriber);
|
||||
_alarmSourceFilter[request.SourceReference] = request.ConditionFilter;
|
||||
// Parse the type-name filter once; this is the authoritative client-side
|
||||
// gate consulted on every routed transition (M2.4 / #8).
|
||||
_alarmSourceFilterPredicate[request.SourceReference] = AlarmConditionFilter.Parse(request.ConditionFilter);
|
||||
|
||||
// If the adapter feed for this source is already (being) established, the
|
||||
// existing subscription serves the new subscriber too.
|
||||
@@ -1546,6 +1555,14 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
|
||||
if (!match)
|
||||
continue;
|
||||
|
||||
// M2.4 (#8): authoritative client-side condition-type gate. Applied
|
||||
// per matched source because two sources may share a prefix yet carry
|
||||
// different filters. Empty filter = allow all (historical behaviour);
|
||||
// framing sentinels (SnapshotComplete) are never dropped.
|
||||
if (_alarmSourceFilterPredicate.TryGetValue(sourceRef, out var predicate) &&
|
||||
!predicate.IsAllowed(transition))
|
||||
continue;
|
||||
|
||||
foreach (var sub in subs)
|
||||
{
|
||||
if (notified.Add(sub))
|
||||
@@ -1566,6 +1583,7 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
|
||||
// No subscribers remain for this source — tear down the adapter feed.
|
||||
_alarmSourceSubscribers.Remove(request.SourceReference);
|
||||
_alarmSourceFilter.Remove(request.SourceReference);
|
||||
_alarmSourceFilterPredicate.Remove(request.SourceReference);
|
||||
if (_alarmSubscriptionIds.Remove(request.SourceReference, out var subId) &&
|
||||
_adapter is IAlarmSubscribableConnection alarmable)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user