You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
13 KiB
C#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Diagnostics;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace IoTSharp.MqttSdk
{
public class MqttClientHost : BackgroundService
{
private readonly MQTTClient client;
public MqttClientHost(MQTTClient client)
{
this.client = client;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
do
{
await Task.Run(async () =>
{
if (!client.IsConnected)
{
await client.ConnectAsync();
}
});
await Task.Delay(TimeSpan.FromSeconds(10));
} while (!stoppingToken.IsCancellationRequested);
}
}
public class MQTTClient
{
public MQTTClient(IServiceScopeFactory scopeFactor, IOptions<MqttSettings> options, ILogger<MQTTClient> logger)
{
_settings = options.Value;
BrokerUri = new Uri(_settings.MqttBroker);
DeviceId = _settings.DeviceId;
OnReceiveAttributes += MQTTClient_OnReceiveAttributes;
OnExcRpc += Client_OnExcRpc;
OnConnected += MQTTClient_OnConnected;
_logger = logger;
}
private void MQTTClient_OnReceiveAttributes(object? sender, AttributeResponse e)
{
switch (e.KeyName)
{
case "rootuser":
break;
default:
break;
}
}
private void MQTTClient_OnConnected(object? sender, MqttClientConnectedEventArgs e)
{
}
public async Task DisconnectAsync()
{
await Client.DisconnectAsync();
Client.Dispose();
}
private void Client_OnExcRpc(object? sender, RpcRequest e)
{
}
public string DeviceId { get; set; } = string.Empty;
private readonly MqttSettings _settings;
private readonly ILogger<MQTTClient> _logger;
public Uri BrokerUri { get; set; }
public bool IsConnected => (Client?.IsConnected).GetValueOrDefault();
private IMqttClient Client { get; set; }
public event EventHandler<RpcRequest> OnExcRpc;
public event EventHandler<AttributeResponse> OnReceiveAttributes;
public event EventHandler<MqttClientConnectedEventArgs> OnConnected;
public async Task<bool> ConnectAsync(Uri uri)
{
BrokerUri = uri;
return await ConnectAsync();
}
public async Task<bool> ConnectAsync()
{
var uri = BrokerUri;
bool initok = false;
string username = "";
string password = "";
if (!string.IsNullOrEmpty(uri.UserInfo))
{
var uinfo = uri.UserInfo.Split(':');
if (uinfo.Length > 0) username = uinfo[0];
if (uinfo.Length > 1) password = uinfo[1];
}
try
{
if (Client != null)
{
Disconnect();
}
var factory = new MqttFactory();
Client = factory.CreateMqttClient();
var clientOptions = new MqttClientOptionsBuilder()
.WithClientId(uri.PathAndQuery)
.WithTcpServer(uri.Host, uri.Port)
.WithCredentials(username, password)
.Build();
Client.ApplicationMessageReceivedAsync += Client_ApplicationMessageReceived;
Client.ConnectedAsync+= Client_ConnectedAsync;
Client.DisconnectedAsync += async (MqttClientDisconnectedEventArgs e) =>
{
try
{
await Client.ConnectAsync(clientOptions);
}
catch (Exception exception)
{
_logger.LogError(exception, "CONNECTING FAILED");
}
};
try
{
var result = await Client.ConnectAsync(clientOptions);
initok = result.ResultCode == MqttClientConnectResultCode.Success;
}
catch (Exception exception)
{
_logger.LogError(exception, "CONNECTING FAILED");
}
_logger.LogInformation("WAITING FOR APPLICATION MESSAGES");
}
catch (Exception exception)
{
_logger.LogError(exception, "CONNECTING FAILED");
}
return initok;
}
public void Disconnect()
{
try
{
Client.DisconnectAsync();
}
catch
{
}
try
{
Client.Dispose();
}
catch
{
}
}
private async Task Client_ConnectedAsync(MqttClientConnectedEventArgs e)
{
await Client.SubscribeAsync($"devices/{DeviceId}/rpc/request/+/+");
await Client.SubscribeAsync($"devices/{DeviceId}/attributes/update/", MqttQualityOfServiceLevel.ExactlyOnce);
_logger.LogInformation($"CONNECTED WITH SERVER ");
OnConnected?.Invoke(this, e);
}
/// <summary>
/// 网关的字设备订阅名称, 不订阅 ID因为 网关可能没法知道自己子设备的ID但一定知道自己的ID因此 连接时订阅自己的消息, 后期订阅子设备的消息
/// </summary>
/// <param name="name">子设备名称</param>
public async Task SubscribeDeviceAsync(string name)
{
///devices/SafetyBeltBox_863488055345618/rpc/request/clasp/6c86c79cbb2f486cb3ae5c491acb828d
///devices/SafetyBeltBox_863488055345618/rpc/request/+/+
var res1 = await Client.SubscribeAsync($"devices/{name}/rpc/request/+/+", MqttQualityOfServiceLevel.ExactlyOnce);
_logger.LogInformation($"订阅{name} {res1.ReasonString}");
var resu2 = await Client.SubscribeAsync($"devices/{name}/attributes/update/", MqttQualityOfServiceLevel.ExactlyOnce);
_logger.LogInformation($"订阅{name} {res1.ReasonString}");
}
private Task Client_ApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs e)
{
_logger.LogInformation($"ApplicationMessageReceived Topic {e.ApplicationMessage.Topic} QualityOfServiceLevel:{e.ApplicationMessage.QualityOfServiceLevel} Retain:{e.ApplicationMessage.Retain} ");
try
{
if (e.ApplicationMessage.Topic.StartsWith($"devices/") && e.ApplicationMessage.Topic.Contains("/response/"))
{
ReceiveAttributes(e);
}
else if (e.ApplicationMessage.Topic.StartsWith($"devices/") && e.ApplicationMessage.Topic.Contains("/rpc/request/"))
{
var tps = e.ApplicationMessage.Topic.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var rpcmethodname = tps[4];
var rpcdevicename = tps[1];
var rpcrequestid = tps[5];
_logger.LogInformation($"rpcmethodname={rpcmethodname} ");
_logger.LogInformation($"rpcdevicename={rpcdevicename } ");
_logger.LogInformation($"rpcrequestid={rpcrequestid} ");
if (!string.IsNullOrEmpty(rpcmethodname) && !string.IsNullOrEmpty(rpcdevicename) && !string.IsNullOrEmpty(rpcrequestid))
{
OnExcRpc?.Invoke(Client, new RpcRequest()
{
Method = rpcmethodname,
DeviceId = rpcdevicename,
RequestId = rpcrequestid,
Params = e.ApplicationMessage.ConvertPayloadToString()
});
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"ClientId:{e.ClientId} Topic:{e.ApplicationMessage.Topic},Payload:{e.ApplicationMessage.ConvertPayloadToString()}");
}
return Task.CompletedTask;
}
private void ReceiveAttributes(MqttApplicationMessageReceivedEventArgs e)
{
var tps = e.ApplicationMessage.Topic.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
var rpcmethodname = tps[2];
var rpcdevicename = tps[1];
var rpcrequestid = tps[4];
_logger.LogInformation($"rpcmethodname={rpcmethodname} ");
_logger.LogInformation($"rpcdevicename={rpcdevicename } ");
_logger.LogInformation($"rpcrequestid={rpcrequestid} ");
if (!string.IsNullOrEmpty(rpcmethodname) && !string.IsNullOrEmpty(rpcdevicename) && !string.IsNullOrEmpty(rpcrequestid))
{
if (e.ApplicationMessage.Topic.Contains("/attributes/"))
{
OnReceiveAttributes?.Invoke(Client, new AttributeResponse()
{
KeyName = rpcmethodname,
DeviceName = rpcdevicename,
Id = rpcrequestid,
Data = e.ApplicationMessage.ConvertPayloadToString()
});
}
}
}
public Task UploadAttributeAsync(object obj) => UploadAttributeAsync("me", obj);
public Task UploadAttributeAsync(string _devicename, object obj)
{
return Client.PublishAsync($"devices/{_devicename}/attributes", Newtonsoft.Json.JsonConvert.SerializeObject(obj));
}
public Task UploadAttributeAsync(string _devicename, Newtonsoft.Json.Linq.JObject jo)
{
return Client.PublishAsync($"devices/{_devicename}/attributes", jo.ToString());
}
public Task UploadTelemetryDataAsync(object obj) => UploadTelemetryDataAsync("me", obj);
public Task UploadTelemetryDataAsync(string _devicename, object obj)
{
return Client.PublishAsync($"devices/{_devicename}/telemetry", Newtonsoft.Json.JsonConvert.SerializeObject(obj));
}
public Task UploadDeviceStatusAsync(string _devicename, bool online)
{
return Client.PublishAsync($"devices/{_devicename}/status/{(online ? "online" : "offline")}", Newtonsoft.Json.JsonConvert.SerializeObject(new { online }));
}
public Task ResponseExecommand(RpcResponse rpcResult)
{
///IoTSharp/Clients/RpcClient.cs#L65 var responseTopic = $"/devices/{deviceid}/rpc/response/{methodName}/{rpcid}";
string topic = $"devices/{rpcResult.DeviceId}/rpc/response/{rpcResult.Method.ToString()}/{rpcResult.ResponseId}";
return Client.PublishAsync( topic, Newtonsoft.Json.JsonConvert.SerializeObject(rpcResult.Data) , MqttQualityOfServiceLevel.ExactlyOnce );
}
public Task RequestExecommand(RpcRequest rpcRequest)
{
///IoTSharp/Clients/RpcClient.cs#L65 var responseTopic = $"/devices/{deviceid}/rpc/response/{methodName}/{rpcid}";
string topic = $"devices/{rpcRequest.DeviceId}/rpc/request/{rpcRequest.Method.ToString()}/{rpcRequest.RequestId}";
return Client.PublishAsync(topic, rpcRequest.Params.ToString(), MqttQualityOfServiceLevel.ExactlyOnce);
}
public Task RequestAttributes(params string[] args) => RequestAttributes("me", false, args);
public Task RequestAttributes(string _device, params string[] args) => RequestAttributes(_device, false, args);
public Task RequestAttributes(bool anySide = true, params string[] args) => RequestAttributes("me", true, args);
public Task RequestAttributes(string _device, bool anySide, params string[] args)
{
string id = Guid.NewGuid().ToString();
string topic = $"devices/{_device}/attributes/request/{id}";
Dictionary<string, string> keys = new Dictionary<string, string>();
keys.Add(anySide ? "anySide" : "server", string.Join(",", args));
Client.SubscribeAsync($"devices/{_device}/attributes/response/{id}", MqttQualityOfServiceLevel.ExactlyOnce);
return Client.PublishAsync(topic, Newtonsoft.Json.JsonConvert.SerializeObject(keys), MqttQualityOfServiceLevel.ExactlyOnce);
}
}
}