【摘要】 .Net平台通過原生MQTT接口,作為南向設備對接OceanConnect平台
因為種種歷史原因吧,目前華為平台上對.net的支持案例SDK確實比較少,當看到各種語言的SDK和Demo,唯獨缺.net平台的,廣大.net開發者也會懷疑.net是不是真的不適合IoT,甚至是互聯網。
我的分析是
1、微軟從.Net Framework開始所謂的跨平台,僅僅局限在Windows個版本的平台,對其他系統並不支持
2、微軟多年以來給人的感覺是閉源,和Linux是死對頭,在華為2019HC大會上和一位鯤鵬的工程師聊天也佐證了這一點,他們2019年了還依然對微軟是這種認識
一提到開源,一提到互聯網、分布式,.net就被輕視,這種態度很常見。
但是,我說的是但是,廣大的.neter為什么選擇.net平台的,為何mono這么多年都對.net跨平台不懈努力?為什么visual studio被稱為宇宙第一的IDE?
那就是生產力。
以前的種種都成為歷史了,自從.net core被提出,微軟已經是一個親Linux的代表了,跨平台、高性能已經是核心基礎 。且不提Windows IoT, .net core在Linux,Arm上已經非常成熟了,樹莓派們的小設備都能輕松玩轉。
作為平台也好,工具也好就是為了提高生產力的。再來看看.net對接華為IoT平台,我現在從之前羡慕C/C++,Java,PHP等,在華為的幫助文檔中都提供了南向設備的SDK,北向接口的SDK。我也一度懷疑華為為何不提供.net的SDK?沒有SDK的幫助,對接將是比其他語言更要復雜,困難要一點點的肯。我也嘗試在.net里用[DllImport]特性用c的sdk。
到現在,其實我可以負責任的告訴大家,.net根本不需要華為提供SDK,SDK本身對接口封裝后就有不少限制,.net就利用公開的原始接口開發,非常簡單和便捷,而且應用起來也靈活。這就是.net的強大生產力,而且.net framework(也可對接桌面程序)和.net core都可以實現。
1、北向接口對接
因為工作關系,對北向接口比較熟悉,華為提供的是restful的接口,和語言無關,實現起來輕車熟路。當時遇到一個困難就是用錯了證書密碼,現在回頭看幫助文檔中也說明了用哪個證書和密碼,我也專門寫過一個帖子來說明北向接口對接。
2、南向設備對接
南向設備比較復雜,對我來說確實是陌生的領域,小熊派這種MCU的板子恐怕只能有C來搞了,借助LiteOS+IoT Studio 也可以勝任。在我的計划中類似小熊派這樣的設備甚至是定制更輕巧的,是物聯網的常用終端類型。但還有些場景需要配合更豐富的應用,C恐怕做不到(起碼需要帶個界面啥的我做不到),或者說做到成本太高,在類似樹莓派的獨立設備,甚至是在Windows電腦上外接其他設備的場景,.net正是用武之地。
.Net core + Mqttnet 作為南向設備對接華為IoT平台
開發工具:Visual Studio 2019,.Net core 2.2和.net framework 4.7.2
Nuget包:MQTTnet 版本v3.0.8
核心內容三個事件
連接Mqtt服務器:
mqttClient.ConnectAsync(options);
訂閱服務器Topic
var result = await mqttClient.SubscribeAsync(new TopicFilter()
{
Topic = topicSubscribe,
QualityOfServiceLevel = MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce
});
服務器發送命令的響應
mqttClient.UseApplicationMessageReceivedHandler(async handle =>
{
var payload = Encoding.Default.GetString(handle.ApplicationMessage.Payload);
var data = JsonConvert.DeserializeObject<ReceivedMessage>(payload);
Console.WriteLine($"收到MSG:{payload}");
var result = await mqttClient.PublishAsync(topicPublish, $"{{\"msgType\":\"deviceRsp\",\"mid\":{data.Mid},\"errcode\":0,\"body\":{{\"response\":\"ok\"}}}}");
Console.WriteLine($"設備響應命令:{result.ReasonCode}");
});
上報設備多信息到服務器:
var msg = new SendMessage
{
Data = new List<Service>
{
new Service
{
ServiceData = new Dictionary<string, dynamic> { { "storage", st }, { "usedPercent", 10 } },
ServiceId = "Storage",
EventTime = DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ")
}
}
};
var payload = JsonConvert.SerializeObject(msg, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
var result = await mqttClient.PublishAsync(topicPublish, payload);
完整代碼如下:
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Options;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Linq;
using MQTTnet.Client.Receiving;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using MQTTnet.Client.Subscribing;
using MQTTnet.Extensions.ManagedClient;
using System.Security.Cryptography;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace CampusApp
{
class Program
{
private static IMqttClient mqttClient;
private static string topicPublish = "/huawei/v1/devices/00b1db5e-5331-4ade-8b62-6c670e6c8d98/data/json";
private static string topicSubscribe = "/huawei/v1/devices/00b1db5e-5331-4ade-8b62-6c670e6c8d98/command/json";
private static async Task ConnectMqttServerAsync()
{
if (mqttClient == null)
{
mqttClient = new MqttFactory().CreateMqttClient();
mqttClient.UseConnectedHandler(async handle =>
{
var result = await mqttClient.SubscribeAsync(new TopicFilter()
{
Topic = topicSubscribe,
QualityOfServiceLevel = MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce
});
Console.WriteLine("連接成功");
});
mqttClient.UseApplicationMessageReceivedHandler(async handle =>
{
var payload = Encoding.Default.GetString(handle.ApplicationMessage.Payload);
var data = JsonConvert.DeserializeObject<ReceivedMessage>(payload);
Console.WriteLine($"收到MSG:{payload}");
var result = await mqttClient.PublishAsync(topicPublish, $"{{\"msgType\":\"deviceRsp\",\"mid\":{data.Mid},\"errcode\":0,\"body\":{{\"response\":\"ok\"}}}}");
Console.WriteLine($"設備響應命令:{result.ReasonCode}");
});
mqttClient.UseDisconnectedHandler(handle =>
{
Console.WriteLine($"MQTT 斷開連接");
});
}
var utctime = DateTime.UtcNow.ToString("yyyyMMddhh");
var pwd = Encrypt("9e4fbd64f6be2337732a", utctime);
var options = new MqttClientOptionsBuilder()
.WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311)
.WithClientId($"00b1db5e-5331-4ade-8b62-6c670e6c8d98_0_0_{utctime}")
.WithTcpServer("49.4.93.24", 8883)
.WithCredentials("00b1db5e-5331-4ade-8b62-6c670e6c8d98", pwd)
//.WithKeepAlivePeriod(TimeSpan.FromSeconds(10))
.WithKeepAliveSendInterval(TimeSpan.FromSeconds(3))
.WithTls(new MqttClientOptionsBuilderTlsParameters()
{
AllowUntrustedCertificates = false,
UseTls = true,
Certificates = new List<byte[]> { new X509Certificate2("rootcert.pem").Export(X509ContentType.Cert) },
CertificateValidationCallback = delegate { return true; },
IgnoreCertificateChainErrors = false,
IgnoreCertificateRevocatireplaceStrings = false
})
.WithCleanSession()
.Build();
try
{
var result = await mqttClient.ConnectAsync(options);
Console.WriteLine(result.ResultCode);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
static string Encrypt(string message, string secret)
{
secret = secret ?? "";
//var encoding = new System.Text.ASCIIEncoding();
var encoding = new System.Text.UTF8Encoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
//return Convert.ToBase64String(hashmessage);
StringBuilder rst = new StringBuilder();
for (int i = 0; i < hashmessage.Length; i++)
{
rst.Append(hashmessage[i].ToString("x2"));
}
return rst.ToString();
}
}
static async Task Main(string[] args)
{
try
{
_ = Task.Run(async () =>
{
await ConnectMqttServerAsync();
});
while (mqttClient?.IsConnected != true)
{
await Task.Delay(1000);
}
while (true)
{
Console.WriteLine("輸入數字 storage:");
var input = Console.ReadLine();
if (!int.TryParse(input, out int st))
continue;
var msg = new SendMessage
{
Data = new List<Service>
{
new Service
{
ServiceData = new Dictionary<string, dynamic> { { "storage", st }, { "usedPercent", 10 } },
ServiceId = "Storage",
EventTime = DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ")
}
}
};
var payload = JsonConvert.SerializeObject(msg, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() });
var result = await mqttClient.PublishAsync(topicPublish, payload);
Console.WriteLine($"Publish Result: {result.ReasonCode}");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + ex.StackTrace);
}
}
}
}
看這段代碼多買的簡單,就搞定了整個過程。
我在開發中重新整理了一下,作為一個SDK提供給.net framework和.net core兩邊使用,封裝之后更加簡單。完成之后作為nuget包再分享。
作者:神龍居市