using MQTTnet; using MQTTnet.Client; using MQTTnet.Formatter; using MQTTnet.Protocol; namespace Evergreen.Health.Application.Health.Integrations.Ib43; public interface IIb43MqttTransport : IDisposable { bool IsConnected { get; } event Func Message; event Action OversizedMessage; Task ConnectAndSubscribeAsync(Ib43Options options, CancellationToken cancellationToken); } public interface IIb43MqttTransportFactory { IIb43MqttTransport Create(); } public sealed class Ib43MqttTransportFactory : IIb43MqttTransportFactory { public IIb43MqttTransport Create() => new Ib43MqttTransport(); } public sealed class Ib43MqttTransport : IIb43MqttTransport { private readonly IMqttClient _client = new MqttFactory().CreateMqttClient(); public bool IsConnected => _client.IsConnected; public event Func Message; public event Action OversizedMessage; public Ib43MqttTransport() { _client.ApplicationMessageReceivedAsync += async e => { var m = e.ApplicationMessage; // 在解码前限制长度;不要将大包全部复制进诊断缓存。 if (m.PayloadSegment.Count > Ib43StateStore.MaxPayloadBytes) OversizedMessage?.Invoke(m.Topic); else if (Message != null) await Message.Invoke(m.Topic, m.ConvertPayloadToString(), m.Retain); }; } public async Task ConnectAndSubscribeAsync(Ib43Options options, CancellationToken cancellationToken) { var builder = new MqttClientOptionsBuilder().WithTcpServer(options.Host, options.Port) .WithClientId(options.ClientId).WithCredentials(options.Username, options.Password) .WithProtocolVersion(MqttProtocolVersion.V311).WithCleanSession() .WithKeepAlivePeriod(TimeSpan.FromSeconds(30)).WithTimeout(TimeSpan.FromSeconds(10)); if (options.UseTls) builder.WithTlsOptions(t => t.UseTls()); await _client.ConnectAsync(builder.Build(), cancellationToken); var subscription = new MqttClientSubscribeOptionsBuilder(); foreach (var device in options.Devices) subscription.WithTopicFilter(f => f.WithTopic(device.PostTopic) .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)); var result = await _client.SubscribeAsync(subscription.Build(), cancellationToken); if (result.Items.Count != options.Devices.Count || result.Items.Any(i => (int)i.ResultCode >= 128)) throw new InvalidOperationException("IB43 subscription rejected"); } public void Dispose() => _client.Dispose(); }