
# C# Demo使用说明
#### 概述
本文以C#语言为例，介绍通过MQTTS/MQTT协议接入平台，基于[平台接口](https://support.huaweicloud.com/api-iothub/iot_06_v5_3002.html)实现"属性上报"、"订阅接收命令"等功能。
![](https://support.huaweicloud.com/devg-iothub/public_sys-resources/note_3.0-zh-cn.png)
本文中使用的代码为样例代码，仅用于体验平台通信功能，如需进行商用，可以参考[资源获取](https://support.huaweicloud.com/devg-iothub/iot_02_1004.html)获取对应语言的IoT Device SDK进行集成。
#### 前提条件
- 已安装Microsoft Visual Studio，若未安装请参考[安装Microsoft Visual Studio]。
- 已在[管理控制台](https://console.huaweicloud.com/iotdm/#/dm-portal/home)获取设备接入地址。获取地址的操作步骤，请参考[平台对接信息](https://support.huaweicloud.com/devg-iothub/iot_02_1004.html#ZH-CN_TOPIC_0187644975__section436212919379)。
- 已在[管理控制台](https://console.huaweicloud.com/iotdm/#/dm-portal/home)创建产品和设备。创建产品和设备的具体操作细节，请参考[创建产品](https://support.huaweicloud.com/usermanual-iothub/iot_01_0054.html)、[注册单个设备](https://support.huaweicloud.com/usermanual-iothub/iot_01_0031.html)或[批量注册设备](https://support.huaweicloud.com/usermanual-iothub/iot_01_0032.html)。
 
#### 准备工作
- 访问[Microsoft官网](https://visualstudio.microsoft.com/zh-hans/)，选择合适系统的版本下载Microsoft Visual Studio。（本文以windows 64-bit系统，Microsoft Visual Studio 2017和.NET Framework 4.5.1为例）。
- 下载完成后，运行安装文件，根据界面提示安装。
 
#### 导入代码样例
1. 下载[quickStart(C#)](https://developer.obs.cn-north-4.myhuaweicloud.com:443/manage/south/quickStart/quickStart(cSharp).zip)样例。
2. 运行Microsoft Visual Studio 2017，单击"打开项目/解决方案"，选择步骤[1]中下载的样例。
   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0250411529.png "点击放大")
   
   
3. 完成代码导入。
   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0287689690.png "点击放大")
   **代码目录简述：**
   - **App.config：Server地址和设备信息配置文件**;
   
   - **C#** **：** **项目C#代码** **；**
     EncryptUtil.cs：设备密钥加密辅助类；
     FrmMqttDemo.cs：窗体界面；
     Program.cs：Demo程序启动入口。
     
   
   - **dll** ：**项目中使用到了第三方库**
     MQTTnet：v3.0.11，是一个基于 MQTT 通信的高性能 .NET 开源库，它同时支持 MQTT 服务器端和客户端，引用库文件包含MQTTnet.dll。
     MQTTnet.Extensions.ManagedClient：v3.0.11，这是一个扩展库，它使用MQTTnet为托管MQTT客户机提供附加功能。
     
   
   
   
   
4. Demo里的工程配置参数。 
   - App.config：需要配置服务器地址、设备ID和设备密钥，用于启动Demo程序的时候，程序将此信息自动写到Demo主界面。 \<add key="serverUri" value="serveruri"/\>
     \<add key="deviceId" value="deviceid"/\>
     \<add key="deviceSecret" value="secret"/\>
     \<add key="PortIsSsl" value="8883"/\>
     \<add key="PortNotSsl" value="1883"/\>
     
   
   
   
   
 
#### 界面展示
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0287698134.png "点击放大")
1. FrmMqttDemo主要提供了界面显示，默认启动后自动从App.config中获取Server地址、设备ID、设备密钥。请根据实际创建的设备信息填写。
   - Server地址：即域名，参考[平台对接信息](https://support.huaweicloud.com/devg-iothub/iot_02_1004.html#ZH-CN_TOPIC_0187644975__section436212919379)获取；
   
   - 设备ID和设备密钥：在物联网平台[注册设备](https://support.huaweicloud.com/usermanual-iothub/iot_01_0031.html)或调用[创建设备](https://support.huaweicloud.com/api-iothub/iot_06_v5_0046.html)接口后获取。
    
2. 示例中App.config默认写了设备侧接入的Server地址（SSL加密接入时该Server地址要与对应的[证书文件匹配使用](https://support.huaweicloud.com/devg-iothub/iot_02_1004.html#ZH-CN_TOPIC_0187644975__section197481637133318)）。
   ```
   <add key="serverUri" value="iot-mqtts.cn-north-4.myhuaweicloud.com"/>;
   ```
   
3. 用户可以选择设备侧建链时是否为SSL加密，选择Qos方式是0还是1，当前不支持Qos2，可参考[使用限制](https://support.huaweicloud.com/devg-iothub/iot_02_2200.html#ZH-CN_TOPIC_0246021145__section1565109112518)。
 
#### 新建连接
设备或网关在接入物联网平台时首先需要和平台建立连接，从而将设备或网关与平台进行关联。开发者通过传入设备信息，将设备或网关连接到物联网平台。
1. FrmMqttDemo类主要提供建立MQTT/MQTTS连接等方法，MQTT默认使用1883端口，MQTTS默认使用8883端口（需要加载设备校验平台身份的证书DigiCertGlobalRootCA.crt.pem，用于设备侧接入物联网平台登录鉴权使用，可以在资源获取中[下载证书文件](https://support.huaweicloud.com/devg-iothub/iot_02_1004.html#ZH-CN_TOPIC_0187644975__section197481637133318)），ManagedMqttClientOptionsBuilder中提供了设置初始化KeepAlivePeriod的属性。mqtt连接心跳时间的建议值是120秒，有[使用限制](https://support.huaweicloud.com/devg-iothub/iot_02_2200.html#ZH-CN_TOPIC_0246021145__section1565109112518)。
   ```
   int portIsSsl = int.Parse(ConfigurationManager.AppSettings["PortIsSsl"]);
   int portNotSsl = int.Parse(ConfigurationManager.AppSettings["PortNotSsl"]);
   if (client == null)
   {
   client = new MqttFactory().CreateManagedMqttClient();
   }
   string timestamp = DateTime.Now.ToString("yyyyMMddHH");
   string clientID = txtDeviceId.Text + "_0_0_" + timestamp;
   // 对密码进行HmacSHA256加密
   string secret = string.Empty;
   if (!string.IsNullOrEmpty(txtDeviceSecret.Text))
   {
   secret = EncryptUtil.HmacSHA256(txtDeviceSecret.Text, timestamp);
   }
   // 判断是否为安全连接
   if (!cbSSLConnect.Checked)
   {
   options = new ManagedMqttClientOptionsBuilder()
   .WithAutoReconnectDelay(TimeSpan.FromSeconds(RECONNECT_TIME))
   .WithClientOptions(new MqttClientOptionsBuilder()
   .WithTcpServer(txtServerUri.Text, portNotSsl)
   .WithCommunicationTimeout(TimeSpan.FromSeconds(DEFAULT_CONNECT_TIMEOUT))
   .WithCredentials(txtDeviceId.Text, secret)
   .WithClientId(clientID)
   .WithKeepAlivePeriod(TimeSpan.FromSeconds(DEFAULT_KEEPLIVE))
   .WithCleanSession(false)
   .WithProtocolVersion(MqttProtocolVersion.V311)
   .Build())
   .Build();
   }
   else
   {
   string caCertPath = Environment.CurrentDirectory + @"\certificate\rootcert.pem";
   X509Certificate2 crt = new X509Certificate2(caCertPath);
   options = new ManagedMqttClientOptionsBuilder()
   .WithAutoReconnectDelay(TimeSpan.FromSeconds(RECONNECT_TIME))
   .WithClientOptions(new MqttClientOptionsBuilder()
   .WithTcpServer(txtServerUri.Text, portIsSsl)
   .WithCommunicationTimeout(TimeSpan.FromSeconds(DEFAULT_CONNECT_TIMEOUT))
   .WithCredentials(txtDeviceId.Text, secret)
   .WithClientId(clientID)
   .WithKeepAlivePeriod(TimeSpan.FromSeconds(DEFAULT_KEEPLIVE))
   .WithCleanSession(false)
   .WithTls(new MqttClientOptionsBuilderTlsParameters()
   {
   AllowUntrustedCertificates = true,
   UseTls = true,
   Certificates = new List<X509Certificate> { crt },
   CertificateValidationHandler = delegate { return true; },
   IgnoreCertificateChainErrors = false,
   IgnoreCertificateRevocationErrors = false
   })
   .WithProtocolVersion(MqttProtocolVersion.V311)
   .Build())
   .Build();
   }
   ```
   
2. FrmMqttDemo类提供了Mqtt客户端建立连接的的方法StartAsync，连接成功后会通过回调函数OnMqttClientConnected打印连接成功日志。
   ```
   Invoke((new Action(() =>
   {
   ShowLogs($"{"try to connect to server " + txtServerUri.Text}{Environment.NewLine}");
   })));
   if (client.IsStarted)
   {
   await client.StopAsync();
   }
   // 注册事件
   client.ApplicationMessageProcessedHandler = new ApplicationMessageProcessedHandlerDelegate(new Action<ApplicationMessageProcessedEventArgs>(ApplicationMessageProcessedHandlerMethod)); // 消息发布回调
   client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(new Action<MqttApplicationMessageReceivedEventArgs>(MqttApplicationMessageReceived)); // 命令下发回调
   client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(new Action<MqttClientConnectedEventArgs>(OnMqttClientConnected)); // 连接成功回调
   client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(new Action<MqttClientDisconnectedEventArgs>(OnMqttClientDisconnected)); // 连接断开回调
   // 连接平台设备
   await client.StartAsync(options);
   ```
   **注：如果连接失败，在OnMqttClientDisconnected函数中已实现退避重连，代码样例如下：**
   ```
   private void OnMqttClientDisconnected(MqttClientDisconnectedEventArgs e)
   {
   try {
   Invoke((new Action(() =>
   {
   ShowLogs("mqtt server is disconnected" + Environment.NewLine);
   txtSubTopic.Enabled = true;
   btnConnect.Enabled = true;
   btnDisconnect.Enabled = false;
   btnPublish.Enabled = false;
   btnSubscribe.Enabled = false;
   })));
   
   if (cbReconnect.Checked)
   {
   Invoke((new Action(() =>
   {
   ShowLogs("reconnect is starting" + Environment.NewLine);
   })));
   //退避重连
   int lowBound = (int)(defaultBackoff * 0.8);
   int highBound = (int)(defaultBackoff * 1.2);
   long randomBackOff = random.Next(highBound - lowBound);
   long backOffWithJitter = (int)(Math.Pow(2.0, retryTimes)) * (randomBackOff + lowBound);
   long waitTImeUtilNextRetry = (int)(minBackoff + backOffWithJitter) > maxBackoff ? maxBackoff : (minBackoff + backOffWithJitter);
   Invoke((new Action(() =>
   {
   ShowLogs("next retry time: " + waitTImeUtilNextRetry + Environment.NewLine);
   })));
   Thread.Sleep((int)waitTImeUtilNextRetry);
   retryTimes++;
   Task.Run(async () => { await ConnectMqttServerAsync(); });
   }
   }
   catch (Exception ex)
   {
   Invoke((new Action(() =>
   {
   ShowLogs("mqtt demo error: " + ex.Message + Environment.NewLine);
   })));
   }
   }
   ```
   
 
#### 订阅Topic
订阅某Topic的设备才能接收broker发布的关于该Topic的消息，关于平台预置Topic可参考[Topic定义](https://support.huaweicloud.com/api-iothub/iot_06_v5_3004.html)。
在FrmMqttDemo类中提供了订阅命令下发Topic的功能：
```
List<MqttTopicFilter> listTopic = new List<MqttTopicFilter>();
var topicFilterBulderPreTopic = new MqttTopicFilterBuilder().WithTopic(topic).Build();
listTopic.Add(topicFilterBulderPreTopic);
// 订阅Topic
client.SubscribeAsync(listTopic.ToArray()).Wait();
```
建链后，如果成功订阅Topic，主界面日志栏显示如下信息：
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0287702523.png "点击放大")
#### 接收下发命令
在FrmMqttDemo类中提供了接收平台下发命令的功能，在MQTT建链完成并成功订阅Topic后，可以在[管理控制台](https://console.huaweicloud.com/iotdm/#/dm-portal/home)设备详情中命令下发或[使用应用侧Demo](https://support.huaweicloud.com/devg-iothub/iot_02_9994.html)对该设备ID进行命令下发。下发成功后，在MQTT的回调函数中接收到平台下发给设备的命令。
```
private void MqttApplicationMessageReceived(MqttApplicationMessageReceivedEventArgs e)
{
Invoke((new Action(() =>
{
ShowLogs($"received message is {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}{Environment.NewLine}");
string msg = "{\"result_code\": 0,\"response_name\": \"COMMAND_RESPONSE\",\"paras\": {\"result\": \"success\"}}";
string topic = "$oc/devices/" + txtDeviceId.Text + "/sys/commands/response/request_id=" + e.ApplicationMessage.Topic.Split('=')[1];
ShowLogs($"{"response message msg = " + msg}{Environment.NewLine}");

var appMsg = new MqttApplicationMessage();
appMsg.Payload = Encoding.UTF8.GetBytes(msg);
appMsg.Topic = topic;
appMsg.QualityOfServiceLevel = int.Parse(cbOosSelect.SelectedValue.ToString()) == 0 ? MqttQualityOfServiceLevel.AtMostOnce : MqttQualityOfServiceLevel.AtLeastOnce;
appMsg.Retain = false;
// 上行响应
client.PublishAsync(appMsg).Wait();
})));
}
```
例如下发参数名为SmokeDetectorControl: SILENCE，参数值为50的命令。
图1命令下发-同步命令下发   
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001981553705.png "点击放大")
命令下发成功后，Demo界面显示如下：
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0287706109.png "点击放大")
#### 发布Topic
发布Topic是指设备主动向平台上报自己的属性或消息，详细见[设备属性上报接口](https://support.huaweicloud.com/api-iothub/iot_06_v5_3010.html)文档。
在FrmMqttDemo中实现了上报Topic、属性上报功能。
```
var appMsg = new MqttApplicationMessage();
appMsg.Payload = Encoding.UTF8.GetBytes(inputString);
appMsg.Topic = topic;
appMsg.QualityOfServiceLevel = int.Parse(cbOosSelect.SelectedValue.ToString()) == 0 ? MqttQualityOfServiceLevel.AtMostOnce : MqttQualityOfServiceLevel.AtLeastOnce;
appMsg.Retain = false;
// 上行响应
client.PublishAsync(appMsg).Wait();
```
发布Topic后，Demo界面显示如下：
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0287707078.png "点击放大")
设备上报属性成功后可在[设备详情](https://console.huaweicloud.com/iotdm/#/dm-portal/device/all-device)页面查看到上报的属性：
图2查看上报数据-Demo_smokeDetector   
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001981636489.png "点击放大")
![](https://support.huaweicloud.com/devg-iothub/public_sys-resources/note_3.0-zh-cn.png)
如果在"设备详情"页面没有最新上报数据，请确认设备上报的服务/属性和产品模型中的服务/属性一致。
![](https://support.huaweicloud.com/devg-iothub/public_sys-resources/note_3.0-zh-cn.png)
由于是同步命令需要端侧回复响应可[参考接口](https://support.huaweicloud.com/api-iothub/iot_06_v5_3014.html#section4)。
