Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

102 linhas
3.3 KiB

  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Threading.Tasks;
  4. using MQTTnet.Client;
  5. using MQTTnet.Internal;
  6. using MQTTnet.Protocol;
  7. namespace MQTTnet.Extensions.Rpc
  8. {
  9. public sealed class MqttRpcClient : IDisposable
  10. {
  11. private const string ResponseTopic = "$MQTTnet.RPC/+/+/response";
  12. private readonly ConcurrentDictionary<string, TaskCompletionSource<byte[]>> _waitingCalls = new ConcurrentDictionary<string, TaskCompletionSource<byte[]>>();
  13. private readonly IMqttClient _mqttClient;
  14. private bool _isEnabled;
  15. public MqttRpcClient(IMqttClient mqttClient)
  16. {
  17. _mqttClient = mqttClient ?? throw new ArgumentNullException(nameof(mqttClient));
  18. _mqttClient.ApplicationMessageReceived += OnApplicationMessageReceived;
  19. }
  20. public async Task EnableAsync()
  21. {
  22. await _mqttClient.SubscribeAsync(new TopicFilterBuilder().WithTopic(ResponseTopic).WithAtLeastOnceQoS().Build());
  23. _isEnabled = true;
  24. }
  25. public async Task DisableAsync()
  26. {
  27. await _mqttClient.UnsubscribeAsync(ResponseTopic);
  28. _isEnabled = false;
  29. }
  30. public async Task<byte[]> ExecuteAsync(TimeSpan timeout, string methodName, byte[] payload, MqttQualityOfServiceLevel qualityOfServiceLevel)
  31. {
  32. if (methodName == null) throw new ArgumentNullException(nameof(methodName));
  33. if (methodName.Contains("/") || methodName.Contains("+") || methodName.Contains("#"))
  34. {
  35. throw new ArgumentException("The method name cannot contain /, + or #.");
  36. }
  37. if (!_isEnabled)
  38. {
  39. throw new InvalidOperationException("The RPC client is not enabled.");
  40. }
  41. var requestTopic = $"$MQTTnet.RPC/{Guid.NewGuid():N}/{methodName}";
  42. var responseTopic = requestTopic + "/response";
  43. var requestMessage = new MqttApplicationMessageBuilder()
  44. .WithTopic(requestTopic)
  45. .WithPayload(payload)
  46. .WithQualityOfServiceLevel(qualityOfServiceLevel)
  47. .Build();
  48. try
  49. {
  50. var tcs = new TaskCompletionSource<byte[]>();
  51. if (!_waitingCalls.TryAdd(responseTopic, tcs))
  52. {
  53. throw new InvalidOperationException();
  54. }
  55. await _mqttClient.PublishAsync(requestMessage);
  56. return await tcs.Task.TimeoutAfter(timeout);
  57. }
  58. finally
  59. {
  60. _waitingCalls.TryRemove(responseTopic, out _);
  61. }
  62. }
  63. private void OnApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs eventArgs)
  64. {
  65. if (!_waitingCalls.TryRemove(eventArgs.ApplicationMessage.Topic, out TaskCompletionSource<byte[]> tcs))
  66. {
  67. return;
  68. }
  69. if (tcs.Task.IsCompleted || tcs.Task.IsCanceled)
  70. {
  71. return;
  72. }
  73. tcs.TrySetResult(eventArgs.ApplicationMessage.Payload);
  74. }
  75. public void Dispose()
  76. {
  77. foreach (var tcs in _waitingCalls)
  78. {
  79. tcs.Value.SetCanceled();
  80. }
  81. _waitingCalls.Clear();
  82. }
  83. }
  84. }