From 8069495256bf514618f6b95cd5e7f19040055566 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Chalet?= Date: Fri, 12 Jun 2026 02:56:27 +0200 Subject: [PATCH] Remove events for which no state is reported without an explicit request --- .../OpenNettyMqttHostedService.cs | 160 ----------- src/OpenNetty.Mqtt/OpenNettyMqttWorker.cs | 168 ++++++++++- src/OpenNetty/OpenNettyCoordinator.cs | 267 ------------------ src/OpenNetty/OpenNettyEvents.cs | 106 ------- 4 files changed, 154 insertions(+), 547 deletions(-) diff --git a/src/OpenNetty.Mqtt/OpenNettyMqttHostedService.cs b/src/OpenNetty.Mqtt/OpenNettyMqttHostedService.cs index 3da66c1..5623d6a 100644 --- a/src/OpenNetty.Mqtt/OpenNettyMqttHostedService.cs +++ b/src/OpenNetty.Mqtt/OpenNettyMqttHostedService.cs @@ -174,26 +174,6 @@ await _events.DimmingScenarioReported .Retry() .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.FirmwareVersionReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.FirmwareVersion, builder => - { - builder.WithPayload(arguments.Version.ToString()); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - - await _events.HardwareVersionReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.HardwareVersion, builder => - { - builder.WithPayload(arguments.Version.ToString()); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.IncomingMessageReported .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.IncomingMessage, builder => @@ -225,16 +205,6 @@ await _events.IncomingMessageReported .Retry() .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.MacAddressReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.MacAddress, builder => - { - builder.WithPayload(arguments.Address.ToString()); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.OnOffScenarioReported .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.Scenario, builder => @@ -516,106 +486,6 @@ await _events.ShutterStateReported .Retry() .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.SmartMeterIndexesReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Where(static arguments => arguments.Indexes.BaseIndex is not null) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterBaseIndex, builder => - { - builder.WithPayload(arguments.Indexes.BaseIndex!.BaseIndex.ToString(CultureInfo.InvariantCulture)); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - - await _events.SmartMeterIndexesReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Where(static arguments => arguments.Indexes.BlueIndex is not null) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterBlueIndex, builder => - { - var node = new JsonObject - { - ["base_index"] = arguments.Indexes.BlueIndex!.BaseIndex, - ["off_peak_index"] = arguments.Indexes.BlueIndex!.OffPeakIndex - }; - - builder.WithContentType(MediaTypeNames.Application.Json); - builder.WithPayload(node.ToJsonString()); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - - await _events.SmartMeterIndexesReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Where(static arguments => arguments.Indexes.PeakOffPeakIndex is not null) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterPeakOffPeakIndex, builder => - { - var node = new JsonObject - { - ["base_index"] = arguments.Indexes.PeakOffPeakIndex!.BaseIndex, - ["off_peak_index"] = arguments.Indexes.PeakOffPeakIndex!.OffPeakIndex - }; - - builder.WithContentType(MediaTypeNames.Application.Json); - builder.WithPayload(node.ToJsonString()); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - - await _events.SmartMeterIndexesReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Where(static arguments => arguments.Indexes.RedIndex is not null) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterRedIndex, builder => - { - var node = new JsonObject - { - ["base_index"] = arguments.Indexes.RedIndex!.BaseIndex, - ["off_peak_index"] = arguments.Indexes.RedIndex!.OffPeakIndex - }; - - builder.WithContentType(MediaTypeNames.Application.Json); - builder.WithPayload(node.ToJsonString()); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - - await _events.SmartMeterIndexesReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Where(static arguments => arguments.Indexes.WhiteIndex is not null) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterWhiteIndex, builder => - { - var node = new JsonObject - { - ["base_index"] = arguments.Indexes.WhiteIndex!.BaseIndex, - ["off_peak_index"] = arguments.Indexes.WhiteIndex!.OffPeakIndex - }; - - builder.WithContentType(MediaTypeNames.Application.Json); - builder.WithPayload(node.ToJsonString()); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - - await _events.SmartMeterIndexesReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterSubscriptionType, builder => - { - builder.WithPayload(arguments.Indexes.SubscriptionType switch - { - OpenNettyModels.TemperatureControl.SmartMeterSubscriptionType.Base => "base", - OpenNettyModels.TemperatureControl.SmartMeterSubscriptionType.PeakOffPeak => "peak/off_peak", - OpenNettyModels.TemperatureControl.SmartMeterSubscriptionType.Tempo => "tempo", - - _ => throw new InvalidDataException(SR.GetResourceString(SR.ID0068)) - }); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.SmartMeterPowerCutModeReported .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.SmartMeterPowerCutMode, builder => @@ -706,16 +576,6 @@ await _events.ToggleScenarioReported .Retry() .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.UptimeReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.StartupDate, builder => - { - builder.WithPayload((TimeProvider.System.GetUtcNow() - arguments.Duration).ToString("o", CultureInfo.InvariantCulture)); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.WaterHeaterSetpointModeReported .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.WaterHeaterSetpointMode, builder => @@ -790,26 +650,6 @@ await _events.ZigbeeBindingEventReported .Retry() .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.ZigbeeChannelReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.ZigbeeChannel, builder => - { - builder.WithPayload(arguments.Channel.ToString(CultureInfo.InvariantCulture)); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - - await _events.ZigbeeDevicesCountReported - .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) - .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.ZigbeeDevicesCount, builder => - { - builder.WithPayload(arguments.Count.ToString(CultureInfo.InvariantCulture)); - builder.WithRetainFlag(); - })) - .Retry() - .SubscribeAsync(static arguments => ValueTask.CompletedTask), - await _events.ZigbeeNetworkEventReported .Where(static arguments => !string.IsNullOrEmpty(arguments.Endpoint.Name)) .Do(arguments => ReportAsync(arguments.Endpoint, OpenNettyMqttAttributes.ZigbeeNetwork, builder => diff --git a/src/OpenNetty.Mqtt/OpenNettyMqttWorker.cs b/src/OpenNetty.Mqtt/OpenNettyMqttWorker.cs index 3b109ea..a142f15 100644 --- a/src/OpenNetty.Mqtt/OpenNettyMqttWorker.cs +++ b/src/OpenNetty.Mqtt/OpenNettyMqttWorker.cs @@ -197,13 +197,11 @@ async ValueTask ExecuteAsync(MqttApplicationMessage message, OpenNettyEndpoint e switch (message.ConvertPayloadToString()?.ToLowerInvariant()) { case "off": - await client.EnqueueAsync(new MqttApplicationMessageBuilder() - .WithPayloadFormatIndicator(MqttPayloadFormatIndicator.CharacterData) - .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce) - .WithTopic(message.Topic[..^4]) - .WithPayload("OFF") - .WithRetainFlag() - .Build()); + await ReportAsync(endpoint, OpenNettyMqttAttributes.BatteryAlert, builder => + { + builder.WithPayload("OFF"); + builder.WithRetainFlag(); + }); break; } break; @@ -211,6 +209,7 @@ await client.EnqueueAsync(new MqttApplicationMessageBuilder() case OpenNettyMqttAttributes.Brightness when operation is OpenNettyMqttOperation.Get: { + // Note: the response to this request is monitored by the hosted service and doesn't need to be reported here. _ = await _controller.EnumerateBrightnessAsync(endpoint, cancellationToken).ToListAsync(cancellationToken); break; } @@ -228,19 +227,34 @@ await client.EnqueueAsync(new MqttApplicationMessageBuilder() case OpenNettyMqttAttributes.FirmwareVersion when operation is OpenNettyMqttOperation.Get: { - _ = await _controller.GetFirmwareVersionAsync(endpoint, cancellationToken); + var version = await _controller.GetFirmwareVersionAsync(endpoint, cancellationToken); + await ReportAsync(endpoint, OpenNettyMqttAttributes.FirmwareVersion, builder => + { + builder.WithPayload(version.ToString()); + builder.WithRetainFlag(); + }); break; } case OpenNettyMqttAttributes.HardwareVersion when operation is OpenNettyMqttOperation.Get: { - _ = await _controller.GetHardwareVersionAsync(endpoint, cancellationToken); + var version = await _controller.GetHardwareVersionAsync(endpoint, cancellationToken); + await ReportAsync(endpoint, OpenNettyMqttAttributes.HardwareVersion, builder => + { + builder.WithPayload(version.ToString()); + builder.WithRetainFlag(); + }); break; } case OpenNettyMqttAttributes.MacAddress when operation is OpenNettyMqttOperation.Get: { - _ = await _controller.GetMacAddressAsync(endpoint, cancellationToken); + var address = await _controller.GetMacAddressAsync(endpoint, cancellationToken); + await ReportAsync(endpoint, OpenNettyMqttAttributes.MacAddress, builder => + { + builder.WithPayload(address.ToString()); + builder.WithRetainFlag(); + }); break; } @@ -263,6 +277,7 @@ await client.EnqueueAsync(new MqttApplicationMessageBuilder() case OpenNettyMqttAttributes.PilotWireSetpointMode when operation is OpenNettyMqttOperation.Get: case OpenNettyMqttAttributes.PilotWireShutdownMode when operation is OpenNettyMqttOperation.Get: { + // Note: the response to this request is monitored by the hosted service and doesn't need to be reported here. _ = await _controller.GetPilotWireConfigurationAsync(endpoint, cancellationToken); break; } @@ -538,6 +553,7 @@ await _controller.DispatchOnOffScenarioAsync(endpoint, case OpenNettyMqttAttributes.ShutterPosition when operation is OpenNettyMqttOperation.Get: { + // Note: the response to this request is monitored by the hosted service and doesn't need to be reported here. _ = await _controller.EnumerateShutterPositionsAsync(endpoint, cancellationToken).ToListAsync(cancellationToken); break; } @@ -555,6 +571,7 @@ await _controller.DispatchOnOffScenarioAsync(endpoint, case OpenNettyMqttAttributes.ShutterState when operation is OpenNettyMqttOperation.Get: { + // Note: the response to this request is monitored by the hosted service and doesn't need to be reported here. _ = await _controller.EnumerateShutterStatesAsync(endpoint, cancellationToken).ToListAsync(cancellationToken); break; } @@ -585,25 +602,117 @@ await _controller.DispatchOnOffScenarioAsync(endpoint, case OpenNettyMqttAttributes.SmartMeterSubscriptionType when operation is OpenNettyMqttOperation.Get: case OpenNettyMqttAttributes.SmartMeterWhiteIndex when operation is OpenNettyMqttOperation.Get: { - _ = await _controller.GetSmartMeterIndexesAsync(endpoint, cancellationToken); + var indexes = await _controller.GetSmartMeterIndexesAsync(endpoint, cancellationToken); + if (indexes.BaseIndex is not null) + { + await ReportAsync(endpoint, OpenNettyMqttAttributes.SmartMeterBaseIndex, builder => + { + builder.WithPayload(indexes.BaseIndex.BaseIndex.ToString(CultureInfo.InvariantCulture)); + builder.WithRetainFlag(); + }); + } + + if (indexes.BlueIndex is not null) + { + await ReportAsync(endpoint, OpenNettyMqttAttributes.SmartMeterBlueIndex, builder => + { + var node = new JsonObject + { + ["base_index"] = indexes.BlueIndex.BaseIndex, + ["off_peak_index"] = indexes.BlueIndex.OffPeakIndex + }; + + builder.WithContentType(MediaTypeNames.Application.Json); + builder.WithPayload(node.ToJsonString()); + builder.WithRetainFlag(); + }); + } + + if (indexes.PeakOffPeakIndex is not null) + { + await ReportAsync(endpoint, OpenNettyMqttAttributes.SmartMeterPeakOffPeakIndex, builder => + { + var node = new JsonObject + { + ["base_index"] = indexes.PeakOffPeakIndex.BaseIndex, + ["off_peak_index"] = indexes.PeakOffPeakIndex.OffPeakIndex + }; + + builder.WithContentType(MediaTypeNames.Application.Json); + builder.WithPayload(node.ToJsonString()); + builder.WithRetainFlag(); + }); + } + + if (indexes.RedIndex is not null) + { + await ReportAsync(endpoint, OpenNettyMqttAttributes.SmartMeterRedIndex, builder => + { + var node = new JsonObject + { + ["base_index"] = indexes.RedIndex.BaseIndex, + ["off_peak_index"] = indexes.RedIndex.OffPeakIndex + }; + + builder.WithContentType(MediaTypeNames.Application.Json); + builder.WithPayload(node.ToJsonString()); + builder.WithRetainFlag(); + }); + } + + if (indexes.WhiteIndex is not null) + { + await ReportAsync(endpoint, OpenNettyMqttAttributes.SmartMeterWhiteIndex, builder => + { + var node = new JsonObject + { + ["base_index"] = indexes.WhiteIndex.BaseIndex, + ["off_peak_index"] = indexes.WhiteIndex.OffPeakIndex + }; + + builder.WithContentType(MediaTypeNames.Application.Json); + builder.WithPayload(node.ToJsonString()); + builder.WithRetainFlag(); + }); + } + + await ReportAsync(endpoint, OpenNettyMqttAttributes.SmartMeterSubscriptionType, builder => + { + builder.WithPayload(indexes.SubscriptionType switch + { + OpenNettyModels.TemperatureControl.SmartMeterSubscriptionType.Base => "base", + OpenNettyModels.TemperatureControl.SmartMeterSubscriptionType.PeakOffPeak => "peak/off_peak", + OpenNettyModels.TemperatureControl.SmartMeterSubscriptionType.Tempo => "tempo", + + _ => throw new InvalidDataException(SR.GetResourceString(SR.ID0068)) + }); + builder.WithRetainFlag(); + }); break; } case OpenNettyMqttAttributes.SmartMeterPowerCutMode or OpenNettyMqttAttributes.SmartMeterRateType when operation is OpenNettyMqttOperation.Get: { + // Note: the response to this request is monitored by the hosted service and doesn't need to be reported here. _ = await _controller.GetSmartMeterInformationAsync(endpoint, cancellationToken); break; } case OpenNettyMqttAttributes.StartupDate when operation is OpenNettyMqttOperation.Get: { - _ = await _controller.GetUptimeAsync(endpoint, cancellationToken); + var duration = await _controller.GetUptimeAsync(endpoint, cancellationToken); + await ReportAsync(endpoint, OpenNettyMqttAttributes.StartupDate, builder => + { + builder.WithPayload((TimeProvider.System.GetUtcNow() - duration).ToString("o", CultureInfo.InvariantCulture)); + builder.WithRetainFlag(); + }); break; } case OpenNettyMqttAttributes.SwitchState when operation is OpenNettyMqttOperation.Get: { + // Note: the response to this request is monitored by the hosted service and doesn't need to be reported here. _ = await _controller.EnumerateSwitchStatesAsync(endpoint, cancellationToken).ToListAsync(cancellationToken); break; } @@ -651,6 +760,7 @@ await _controller.SetWaterHeaterSetpointModeAsync(endpoint, case OpenNettyMqttAttributes.WaterHeaterState when operation is OpenNettyMqttOperation.Get: { + // Note: the response to this request is monitored by the hosted service and doesn't need to be reported here. _ = await _controller.GetWaterHeaterStateAsync(endpoint, cancellationToken); break; } @@ -672,13 +782,23 @@ await _controller.SetWaterHeaterSetpointModeAsync(endpoint, case OpenNettyMqttAttributes.ZigbeeChannel when operation is OpenNettyMqttOperation.Get: { - _ = await _controller.GetZigbeeChannelAsync(endpoint, cancellationToken); + var channel = await _controller.GetZigbeeChannelAsync(endpoint, cancellationToken); + await ReportAsync(endpoint, OpenNettyMqttAttributes.ZigbeeChannel, builder => + { + builder.WithPayload(channel.ToString(CultureInfo.InvariantCulture)); + builder.WithRetainFlag(); + }); break; } case OpenNettyMqttAttributes.ZigbeeDevicesCount when operation is OpenNettyMqttOperation.Get: { - _ = await _controller.CountZigbeeDevicesAsync(endpoint, cancellationToken); + var count = await _controller.CountZigbeeDevicesAsync(endpoint, cancellationToken); + await ReportAsync(endpoint, OpenNettyMqttAttributes.ZigbeeDevicesCount, builder => + { + builder.WithPayload(count.ToString(CultureInfo.InvariantCulture)); + builder.WithRetainFlag(); + }); break; } @@ -737,6 +857,26 @@ await _controller.SetWaterHeaterSetpointModeAsync(endpoint, return null; } } + + async ValueTask ReportAsync(OpenNettyEndpoint endpoint, string attribute, Action configuration) + { + var builder = new MqttApplicationMessageBuilder() + .WithPayloadFormatIndicator(MqttPayloadFormatIndicator.CharacterData) + .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.ExactlyOnce) + .WithTopic(GetMessageTopic(endpoint, attribute)); + + configuration(builder); + + await client.EnqueueAsync(builder.Build()); + } + + string GetMessageTopic(OpenNettyEndpoint endpoint, string attribute) => new StringBuilder() + .Append(_options.CurrentValue.RootTopic) + .Append('/') + .Append(endpoint.GetStringSetting(OpenNettySettings.MqttTopic) ?? endpoint.Name.ToLowerInvariant()) + .Append('/') + .Append(attribute) + .ToString(); } } diff --git a/src/OpenNetty/OpenNettyCoordinator.cs b/src/OpenNetty/OpenNettyCoordinator.cs index 15eacb8..7c450b2 100644 --- a/src/OpenNetty/OpenNettyCoordinator.cs +++ b/src/OpenNetty/OpenNettyCoordinator.cs @@ -644,88 +644,6 @@ await Parallel.ForEachAsync(endpoints, async (endpoint, cancellationToken) => break; } - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo, - Type : OpenNettyMessageType.DimensionRead, - Address : not null, - Dimension: OpenNettyDimension dimension, - Values : [{ Length: > 0 }, { Length: > 0 }, ..] values }) - when dimension == OpenNettyDimensions.TemperatureControl.SmartMeterIndexes: - { - var endpoints = _manager.FindEndpointsByAddressAsync(notification.Gateway, message.Address.Value); - - await Parallel.ForEachAsync(endpoints, async (endpoint, cancellationToken) => - { - if (endpoint.HasCapability(OpenNettyCapabilities.SmartMeterIndexes)) - { - var indexes = OpenNettyModels.TemperatureControl.SmartMeterIndexes.CreateFromDimensionValues([.. values]); - - // Note: Nitoo smart meter devices are affected by an index overflow issue: to work around this limitation, - // dedicated "index offset" settings can be used to amend the indexes returned by each smart meter device. - await _events.PublishAsync(new SmartMeterIndexesReportedEventArgs(endpoint, indexes with - { - BaseIndex = indexes.BaseIndex switch - { - OpenNettyModels.TemperatureControl.SmartMeterIndex index => new() - { - BaseIndex = ComputeIndex(index.BaseIndex, endpoint, OpenNettySettings.SmartMeterBaseIndexOffset), - OffPeakIndex = default - }, - - _ => null - }, - BlueIndex = indexes.BlueIndex switch - { - OpenNettyModels.TemperatureControl.SmartMeterIndex index => new() - { - BaseIndex = ComputeIndex(index.BaseIndex, endpoint, OpenNettySettings.SmartMeterBlueIndexOffsetBase), - OffPeakIndex = ComputeIndex(index.OffPeakIndex, endpoint, OpenNettySettings.SmartMeterBlueIndexOffsetOffPeak) - }, - - _ => null - }, - PeakOffPeakIndex = indexes.PeakOffPeakIndex switch - { - OpenNettyModels.TemperatureControl.SmartMeterIndex index => new() - { - BaseIndex = ComputeIndex(index.BaseIndex, endpoint, OpenNettySettings.SmartMeterPeakOffPeakIndexOffsetBase), - OffPeakIndex = ComputeIndex(index.OffPeakIndex, endpoint, OpenNettySettings.SmartMeterPeakOffPeakIndexOffsetOffPeak) - }, - - _ => null - }, - RedIndex = indexes.RedIndex switch - { - OpenNettyModels.TemperatureControl.SmartMeterIndex index => new() - { - BaseIndex = ComputeIndex(index.BaseIndex, endpoint, OpenNettySettings.SmartMeterRedIndexOffsetBase), - OffPeakIndex = ComputeIndex(index.OffPeakIndex, endpoint, OpenNettySettings.SmartMeterRedIndexOffsetOffPeak) - }, - - _ => null - }, - WhiteIndex = indexes.WhiteIndex switch - { - OpenNettyModels.TemperatureControl.SmartMeterIndex index => new() - { - BaseIndex = ComputeIndex(index.BaseIndex, endpoint, OpenNettySettings.SmartMeterWhiteIndexOffsetBase), - OffPeakIndex = ComputeIndex(index.OffPeakIndex, endpoint, OpenNettySettings.SmartMeterWhiteIndexOffsetOffPeak) - }, - - _ => null - } - }), cancellationToken); - } - - static ulong ComputeIndex(ulong index, OpenNettyEndpoint endpoint, OpenNettySetting setting) => index switch - { - ulong value when endpoint.GetIntegerSetting(setting) is long offset => (ulong) (((long) value) + offset), - ulong value => value - }; - }); - break; - } - case (OpenNettyNotifications.MessageReceived, OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo, Type : OpenNettyMessageType.DimensionRead, @@ -1245,170 +1163,6 @@ await Parallel.ForEachAsync(endpoints, async (endpoint, cancellationToken) => break; } - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo or OpenNettyProtocol.Scs or OpenNettyProtocol.Zigbee, - Type : OpenNettyMessageType.DimensionRead, - Dimension: OpenNettyDimension dimension, - Values : [{ Length: > 0 }, { Length: > 0 }, { Length: > 0 }] values }) - when dimension == OpenNettyDimensions.Management.FirmwareVersion: - { - // Note: firmware version DIMENSION READ messages may be sent by the gateway itself or by a remote device. - if (message.Address is not null) - { - var endpoints = _manager.FindEndpointsByAddressAsync(notification.Gateway, message.Address.Value) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.FirmwareVersion)); - - await Parallel.ForEachAsync(endpoints, ReportFirmwareVersionAsync); - } - - else - { - var endpoints = _manager.EnumerateEndpointsAsync() - .Where(endpoint => endpoint.Device == notification.Gateway.Device && endpoint.Unit is null) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.FirmwareVersion)); - - await Parallel.ForEachAsync(endpoints, ReportFirmwareVersionAsync); - } - - async ValueTask ReportFirmwareVersionAsync(OpenNettyEndpoint endpoint, CancellationToken cancellationToken) => - await _events.PublishAsync(new FirmwareVersionReportedEventArgs(endpoint, new Version( - major: int.Parse(values[0], CultureInfo.InvariantCulture), - minor: int.Parse(values[1], CultureInfo.InvariantCulture), - build: int.Parse(values[2], CultureInfo.InvariantCulture))), cancellationToken); - break; - } - - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo or OpenNettyProtocol.Scs or OpenNettyProtocol.Zigbee, - Type : OpenNettyMessageType.DimensionRead, - Dimension: OpenNettyDimension dimension, - Values : [{ Length: > 0 }, { Length: > 0 }, { Length: > 0 }] values }) - when dimension == OpenNettyDimensions.Management.HardwareVersion: - { - // Note: hardware version DIMENSION READ messages may be sent by the gateway itself or by a remote device. - if (message.Address is not null) - { - var endpoints = _manager.FindEndpointsByAddressAsync(notification.Gateway, message.Address.Value) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.HardwareVersion)); - - await Parallel.ForEachAsync(endpoints, ReportHardwareVersionAsync); - } - - else - { - var endpoints = _manager.EnumerateEndpointsAsync() - .Where(endpoint => endpoint.Device == notification.Gateway.Device && endpoint.Unit is null) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.HardwareVersion)); - - await Parallel.ForEachAsync(endpoints, ReportHardwareVersionAsync); - } - - async ValueTask ReportHardwareVersionAsync(OpenNettyEndpoint endpoint, CancellationToken cancellationToken) => - await _events.PublishAsync(new HardwareVersionReportedEventArgs(endpoint, new Version( - major: int.Parse(values[0], CultureInfo.InvariantCulture), - minor: int.Parse(values[1], CultureInfo.InvariantCulture), - build: int.Parse(values[2], CultureInfo.InvariantCulture))), cancellationToken); - break; - } - - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo or OpenNettyProtocol.Scs or OpenNettyProtocol.Zigbee, - Type : OpenNettyMessageType.DimensionRead, - Dimension: OpenNettyDimension dimension, - Values : { Length: >= 6 } values }) - when dimension == OpenNettyDimensions.Management.MacAddress: - { - // Note: MAC address DIMENSION READ messages may be sent by the gateway itself or by a remote device. - if (message.Address is not null) - { - var endpoints = _manager.FindEndpointsByAddressAsync(notification.Gateway, message.Address.Value) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.MacAddress)); - - await Parallel.ForEachAsync(endpoints, ReportMacAddressAsync); - } - - else - { - var endpoints = _manager.EnumerateEndpointsAsync() - .Where(endpoint => endpoint.Device == notification.Gateway.Device && endpoint.Unit is null) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.MacAddress)); - - await Parallel.ForEachAsync(endpoints, ReportMacAddressAsync); - } - - async ValueTask ReportMacAddressAsync(OpenNettyEndpoint endpoint, CancellationToken cancellationToken) - => await _events.PublishAsync(new MacAddressReportedEventArgs(endpoint, string.Join(":", - values.Select(static value => uint.Parse(value, CultureInfo.InvariantCulture) - .ToString("X2", CultureInfo.InvariantCulture)))), cancellationToken); - break; - } - - // Note: uptime DIMENSION READ messages never include an address, as they are sent by the gateway itself. - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo or OpenNettyProtocol.Scs or OpenNettyProtocol.Zigbee, - Type : OpenNettyMessageType.DimensionRead, - Address : null, - Dimension: OpenNettyDimension dimension, - Values : [{ Length: > 0 }, { Length: > 0 }, { Length: > 0 }, { Length: > 0 }] values }) - when dimension == OpenNettyDimensions.Management.Uptime: - { - var endpoints = _manager.EnumerateEndpointsAsync() - .Where(endpoint => endpoint.Device == notification.Gateway.Device && endpoint.Unit is null) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.Uptime)); - - await Parallel.ForEachAsync(endpoints, async (endpoint, cancellationToken) => - { - await _events.PublishAsync(new UptimeReportedEventArgs(endpoint, new TimeSpan( - days : int.Parse(values[0], CultureInfo.InvariantCulture), - hours : int.Parse(values[1], CultureInfo.InvariantCulture), - minutes: int.Parse(values[2], CultureInfo.InvariantCulture), - seconds: int.Parse(values[3], CultureInfo.InvariantCulture))), cancellationToken); - }); - break; - } - - // Note: Zigbee channel DIMENSION READ messages never include an address, as they are sent by the gateway itself. - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Zigbee, - Type : OpenNettyMessageType.DimensionRead, - Address : null, - Dimension: OpenNettyDimension dimension, - Values : [{ Length: > 0 } value] }) - when dimension == OpenNettyDimensions.Management.ZigbeeChannel: - { - var endpoints = _manager.EnumerateEndpointsAsync() - .Where(endpoint => endpoint.Device == notification.Gateway.Device && endpoint.Unit is null) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.ZigbeeNetworkManagement)); - - await Parallel.ForEachAsync(endpoints, async (endpoint, cancellationToken) => - { - await _events.PublishAsync(new ZigbeeChannelReportedEventArgs(endpoint, - byte.Parse(value, CultureInfo.InvariantCulture)), cancellationToken); - }); - break; - } - - // Note: Zigbee devices count DIMENSION READ messages never include an address, as they are sent by the gateway itself. - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Zigbee, - Type : OpenNettyMessageType.DimensionRead, - Address : null, - Dimension: OpenNettyDimension dimension, - Values : [{ Length: > 0 } value] }) - when dimension == OpenNettyDimensions.Management.NumberOfProducts: - { - var endpoints = _manager.EnumerateEndpointsAsync() - .Where(endpoint => endpoint.Device == notification.Gateway.Device && endpoint.Unit is null) - .Where(static endpoint => endpoint.HasCapability(OpenNettyCapabilities.ZigbeeNetworkManagement)); - - await Parallel.ForEachAsync(endpoints, async (endpoint, cancellationToken) => - { - await _events.PublishAsync(new ZigbeeDevicesCountReportedEventArgs(endpoint, - byte.Parse(value, CultureInfo.InvariantCulture)), cancellationToken); - }); - break; - } - case (OpenNettyNotifications.MessageReceived or OpenNettyNotifications.MessageSent, OpenNettyMessage { Protocol: OpenNettyProtocol.Zigbee, Type : OpenNettyMessageType.BusCommand, @@ -1462,27 +1216,6 @@ await _events.PublishAsync(new ZigbeeBindingEventReportedEventArgs(endpoint, break; } - case (OpenNettyNotifications.MessageReceived, - OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo, - Type : OpenNettyMessageType.DimensionRead, - Address : not null, - Dimension: OpenNettyDimension dimension, - Values : [{ Length: > 0 }, { Length: > 0 }, { Length: > 0 }, { Length: > 0 }] values }) - when dimension == OpenNettyDimensions.Diagnostics.DeviceDescription: - { - var endpoints = _manager.FindEndpointsByAddressAsync(notification.Gateway, message.Address.Value); - - await Parallel.ForEachAsync(endpoints, async (endpoint, cancellationToken) => - { - var description = OpenNettyModels.Diagnostics.DeviceDescription.CreateFromDeviceDescription([.. values]); - - await Task.WhenAll( - _events.PublishAsync(new DeviceDescriptionReportedEventArgs(endpoint, description), cancellationToken).AsTask(), - _events.PublishAsync(new FirmwareVersionReportedEventArgs(endpoint, description.Version), cancellationToken).AsTask()); - }); - break; - } - case (OpenNettyNotifications.MessageReceived or OpenNettyNotifications.MessageSent, OpenNettyMessage { Protocol : OpenNettyProtocol.Nitoo, Type : OpenNettyMessageType.DimensionSet, diff --git a/src/OpenNetty/OpenNettyEvents.cs b/src/OpenNetty/OpenNettyEvents.cs index 43aa3d3..0d824e8 100644 --- a/src/OpenNetty/OpenNettyEvents.cs +++ b/src/OpenNetty/OpenNettyEvents.cs @@ -106,42 +106,18 @@ public IAsyncObservable BrightnessReported public IAsyncObservable DeviceCommunicationReported => _observable.OfType(); - /// - /// Gets an event triggered when a device description is reported. - /// - public IAsyncObservable DeviceDescriptionReported - => _observable.OfType(); - /// /// Gets an event triggered when a dimming scenario is reported. /// public IAsyncObservable DimmingScenarioReported => _observable.OfType(); - /// - /// Gets an event triggered when a firmware version is reported. - /// - public IAsyncObservable FirmwareVersionReported - => _observable.OfType(); - - /// - /// Gets an event triggered when a hardware version is reported. - /// - public IAsyncObservable HardwareVersionReported - => _observable.OfType(); - /// /// Gets an event triggered when an incoming message is reported. /// public IAsyncObservable IncomingMessageReported => _observable.OfType(); - /// - /// Gets an event triggered when a MAC address is reported. - /// - public IAsyncObservable MacAddressReported - => _observable.OfType(); - /// /// Gets an event triggered when an ON/OFF scenario is reported. /// @@ -202,12 +178,6 @@ public IAsyncObservable ShutterPositionReporte public IAsyncObservable ShutterStateReported => _observable.OfType(); - /// - /// Gets an event triggered when smart meter indexes are reported. - /// - public IAsyncObservable SmartMeterIndexesReported - => _observable.OfType(); - /// /// Gets an event triggered when a smart meter power cut mode is reported. /// @@ -244,12 +214,6 @@ public IAsyncObservable TimedScenarioReported public IAsyncObservable ToggleScenarioReported => _observable.OfType(); - /// - /// Gets an event triggered when an uptime duration is reported. - /// - public IAsyncObservable UptimeReported - => _observable.OfType(); - /// /// Gets an event triggered when a water heater setpoint mode is reported. /// @@ -268,24 +232,12 @@ public IAsyncObservable WaterHeaterStateRepor public IAsyncObservable WirelessBurglarAlarmStateReported => _observable.OfType(); - /// - /// Gets an event triggered when a Zigbee channel is reported. - /// - public IAsyncObservable ZigbeeChannelReported - => _observable.OfType(); - /// /// Gets an event triggered when a Zigbee binding event is reported. /// public IAsyncObservable ZigbeeBindingEventReported => _observable.OfType(); - /// - /// Gets an event triggered when the number of Zigbee devices in the database is reported. - /// - public IAsyncObservable ZigbeeDevicesCountReported - => _observable.OfType(); - /// /// Gets an event triggered when a Zigbee network event is reported. /// @@ -367,14 +319,6 @@ public sealed record class BrightnessReportedEventArgs(OpenNettyEndpoint Endpoin public sealed record class DeviceCommunicationReportedEventArgs(OpenNettyEndpoint Endpoint, OpenNettyMessage Message) : EventArgs(Endpoint); - /// - /// Represents event arguments used when a device description is reported. - /// - /// The endpoint. - /// The device description. - public sealed record class DeviceDescriptionReportedEventArgs(OpenNettyEndpoint Endpoint, - OpenNettyModels.Diagnostics.DeviceDescription Description) : EventArgs(Endpoint); - /// /// Represents event arguments used when a dimming scenario is reported. /// @@ -382,20 +326,6 @@ public sealed record class DeviceDescriptionReportedEventArgs(OpenNettyEndpoint /// The dimming step (positive or negative). public sealed record class DimmingScenarioReportedEventArgs(OpenNettyEndpoint Endpoint, short Step) : EventArgs(Endpoint); - /// - /// Represents event arguments used when a firmware version is reported. - /// - /// The endpoint. - /// The firmware version. - public sealed record class FirmwareVersionReportedEventArgs(OpenNettyEndpoint Endpoint, Version Version) : EventArgs(Endpoint); - - /// - /// Represents event arguments used when a hardware version is reported. - /// - /// The endpoint. - /// The hardware version. - public sealed record class HardwareVersionReportedEventArgs(OpenNettyEndpoint Endpoint, Version Version) : EventArgs(Endpoint); - /// /// Represents event arguments used when an incoming message is reported. /// @@ -405,13 +335,6 @@ public sealed record class HardwareVersionReportedEventArgs(OpenNettyEndpoint En public sealed record class IncomingMessageReportedEventArgs(OpenNettyEndpoint Endpoint, OpenNettyMessage Message, OpenNettySession Session) : EventArgs(Endpoint); - /// - /// Represents event arguments used when a MAC address is reported. - /// - /// The endpoint. - /// The MAC address. - public sealed record class MacAddressReportedEventArgs(OpenNettyEndpoint Endpoint, string Address) : EventArgs(Endpoint); - /// /// Represents event arguments used when an ON/OFF scenario is reported. /// @@ -496,14 +419,6 @@ public sealed record class ShutterPositionReportedEventArgs(OpenNettyEndpoint En public sealed record class ShutterStateReportedEventArgs(OpenNettyEndpoint Endpoint, OpenNettyModels.Automation.ShutterState State) : EventArgs(Endpoint); - /// - /// Represents event arguments used when smart meter indexes are reported. - /// - /// The endpoint. - /// The indexes. - public sealed record class SmartMeterIndexesReportedEventArgs(OpenNettyEndpoint Endpoint, - OpenNettyModels.TemperatureControl.SmartMeterIndexes Indexes) : EventArgs(Endpoint); - /// /// Represents event arguments used when a smart meter power cut mode is reported. /// @@ -548,13 +463,6 @@ public sealed record class TimedScenarioReportedEventArgs(OpenNettyEndpoint Endp /// The endpoint. public sealed record class ToggleScenarioReportedEventArgs(OpenNettyEndpoint Endpoint) : EventArgs(Endpoint); - /// - /// Represents event arguments used when an uptime duration is reported. - /// - /// The endpoint. - /// The uptime duration. - public sealed record class UptimeReportedEventArgs(OpenNettyEndpoint Endpoint, TimeSpan Duration) : EventArgs(Endpoint); - /// /// Represents event arguments used when a water heater setpoint mode is reported. /// @@ -579,13 +487,6 @@ public sealed record class WaterHeaterStateReportedEventArgs(OpenNettyEndpoint E public sealed record class WirelessBurglarAlarmStateReportedEventArgs(OpenNettyEndpoint Endpoint, OpenNettyModels.Alarm.WirelessBurglarAlarmState State) : EventArgs(Endpoint); - /// - /// Represents event arguments used when a Zigbee channel is reported. - /// - /// The endpoint. - /// The channel. - public sealed record class ZigbeeChannelReportedEventArgs(OpenNettyEndpoint Endpoint, byte Channel) : EventArgs(Endpoint); - /// /// Represents event arguments used when a Zigbee binding event is reported. /// @@ -594,13 +495,6 @@ public sealed record class ZigbeeChannelReportedEventArgs(OpenNettyEndpoint Endp public sealed record class ZigbeeBindingEventReportedEventArgs(OpenNettyEndpoint Endpoint, OpenNettyModels.ScenariosPlus.ZigbeeBindingEventType Type) : EventArgs(Endpoint); - /// - /// Represents event arguments used when the number of Zigbee devices in the database is reported. - /// - /// The endpoint. - /// The number of Zigbee devices in the database. - public sealed record class ZigbeeDevicesCountReportedEventArgs(OpenNettyEndpoint Endpoint, byte Count) : EventArgs(Endpoint); - /// /// Represents event arguments used when a Zigbee network event is reported. ///