feat(dcl): MxGateway StreamAlarms adapter (snapshot + live transitions, reconnecting)
Adds IAlarmSubscribableConnection to MxGatewayDataConnection (shared session-less feed, ref-counted), IMxGatewayClient.RunAlarmStreamAsync over the package StreamAlarmsAsync with internal reconnect, and MxGatewayAlarmMapper (AlarmFeedMessage/OnAlarmTransitionEvent -> NativeAlarmTransition). Behavior verified against a live gateway in Task 28; mapper unit-tested.
This commit is contained in:
@@ -20,7 +20,7 @@ namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters;
|
||||
/// <see cref="Disconnected"/>, the actor disposes this adapter, creates a fresh one,
|
||||
/// reconnects and re-subscribes all tags.
|
||||
/// </summary>
|
||||
public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection, IAlarmSubscribableConnection
|
||||
{
|
||||
private readonly IMxGatewayClientFactory _clientFactory;
|
||||
private readonly ILogger<MxGatewayDataConnection> _logger;
|
||||
@@ -28,6 +28,15 @@ public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
private ConnectionHealth _status = ConnectionHealth.Disconnected;
|
||||
private CancellationTokenSource? _eventLoopCts;
|
||||
|
||||
// Native alarm feed: the gateway StreamAlarms RPC is session-less and
|
||||
// gateway-wide, so one shared feed serves the whole connection. The
|
||||
// DataConnectionActor routes transitions to instances by source reference,
|
||||
// so a single shared callback (the first registered) suffices; subscriptions
|
||||
// are ref-counted so the feed stops when the last one is removed.
|
||||
private CancellationTokenSource? _alarmCts;
|
||||
private int _alarmSubCount;
|
||||
private readonly object _alarmLock = new();
|
||||
|
||||
// subscriptionId → (tagPath, callback) so the event loop can route updates by tag,
|
||||
// plus tagPath → subscriptionId for reverse lookup. Concurrent because the event
|
||||
// loop reads from a background thread while Subscribe/Unsubscribe mutate.
|
||||
@@ -112,6 +121,13 @@ public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
public async Task DisconnectAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_eventLoopCts?.Cancel();
|
||||
lock (_alarmLock)
|
||||
{
|
||||
_alarmCts?.Cancel();
|
||||
_alarmCts?.Dispose();
|
||||
_alarmCts = null;
|
||||
_alarmSubCount = 0;
|
||||
}
|
||||
if (_client is not null)
|
||||
await _client.DisconnectAsync(cancellationToken);
|
||||
_status = ConnectionHealth.Disconnected;
|
||||
@@ -134,6 +150,43 @@ public class MxGatewayDataConnection : IDataConnection, IBrowsableDataConnection
|
||||
await _client!.UnsubscribeAsync(subscriptionId, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<string> SubscribeAlarmsAsync(
|
||||
string sourceReference, string? conditionFilter,
|
||||
AlarmTransitionCallback callback, CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (_alarmLock)
|
||||
{
|
||||
_alarmSubCount++;
|
||||
if (_alarmCts == null)
|
||||
{
|
||||
_alarmCts = new CancellationTokenSource();
|
||||
var token = _alarmCts.Token;
|
||||
var client = _client!;
|
||||
// Gateway-wide feed (null prefix); the actor filters per source reference.
|
||||
_ = Task.Run(() => client.RunAlarmStreamAsync(null, t => callback(t), token), token);
|
||||
}
|
||||
}
|
||||
return Task.FromResult(Guid.NewGuid().ToString());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeAlarmsAsync(string subscriptionId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
lock (_alarmLock)
|
||||
{
|
||||
if (_alarmSubCount > 0)
|
||||
_alarmSubCount--;
|
||||
if (_alarmSubCount == 0)
|
||||
{
|
||||
_alarmCts?.Cancel();
|
||||
_alarmCts?.Dispose();
|
||||
_alarmCts = null;
|
||||
}
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ReadResult> ReadAsync(string tagPath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user