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:
Joseph Doherty
2026-05-29 16:49:25 -04:00
parent 0d30b7dec0
commit c7411700dc
6 changed files with 295 additions and 1 deletions
@@ -246,6 +246,61 @@ public sealed class RealMxGatewayClient : IMxGatewayClient
}
}
/// <inheritdoc />
public async Task RunAlarmStreamAsync(
string? alarmFilterPrefix, Action<Commons.Types.Alarms.NativeAlarmTransition> onTransition,
CancellationToken ct = default)
{
var reconnectDelay = TimeSpan.FromSeconds(5);
while (!ct.IsCancellationRequested)
{
try
{
var request = new StreamAlarmsRequest
{
ClientCorrelationId = Guid.NewGuid().ToString("N"),
AlarmFilterPrefix = alarmFilterPrefix ?? string.Empty,
};
await foreach (var message in _client!.StreamAlarmsAsync(request, ct)
.WithCancellation(ct).ConfigureAwait(false))
{
if (ct.IsCancellationRequested) break;
switch (message.PayloadCase)
{
case AlarmFeedMessage.PayloadOneofCase.ActiveAlarm:
onTransition(MxGatewayAlarmMapper.MapSnapshot(message.ActiveAlarm));
break;
case AlarmFeedMessage.PayloadOneofCase.Transition:
onTransition(MxGatewayAlarmMapper.MapTransition(message.Transition));
break;
case AlarmFeedMessage.PayloadOneofCase.SnapshotComplete:
onTransition(MxGatewayAlarmMapper.SnapshotComplete());
break;
}
}
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
return; // clean shutdown
}
catch (Exception ex)
{
_logger.LogWarning(ex,
"MxGateway alarm stream faulted; reopening in {DelaySeconds}s", reconnectDelay.TotalSeconds);
}
try
{
await Task.Delay(reconnectDelay, ct).ConfigureAwait(false);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
return;
}
}
}
/// <inheritdoc />
public async ValueTask DisposeAsync()
{