×

重访The Things Industries (TTI) V3 Azure IoT连接器

消耗积分:0 | 格式:zip | 大小:0.00 MB | 2023-06-14

旧念

分享资料个

描述

早在 2020 年 11 月,我就发布了我的 The Things Network V2 Azure IoT Hubs & IoT Central Gateway。该项目是关于构建物联网 (TTN)HTTP 应用程序集成,它启用Azure IoT 中心Azure IoT CentralAzure IoT 中心设备供应服务 (DPS)供应支持的连接。

该项目使用了“符合流行语”的Microsoft Azure服务选择,但它不支持云到设备 (C2D) 消息,存在消息排序问题,并且部署和设置复杂。还有其他问题,但不值得在这里重新讨论

然后在 2021 年 3 月,我再次尝试使用我的 The Things Industries(TTI) V3 Azure IoT 连接器,它是一个TTI(消息队列遥测传输)MQTT 集成,也使用了 TTI应用程序终端设备 API

此版本使用MQTTNet (这是一个很棒的库)和在应用程序启动时连接到Azure IoT HubAzure IoT Central的 TTI 设备。即使我使用多个线程并对 Application 和 EndDevice 请求进行分页,这个过程也很慢

该应用程序更容易调试,因为我可以在桌面上运行它,并且更容易配置,因为我将配置转移回appsettings.json文件(我可能会重新考虑放弃Azure Key Vault支持的决定)。

此版本具有基本的Azure 数字孪生定义语言 (DTDL)支持,因此可以在Azure IoT Central中“自动”预配设备

我还添加了对Azure IoT HubsAzure IoT Central的C2D 支持,并基于下行链路消息有效负载确认标志跟踪消息传递我发现TTI 交付进度更新的顺序可能有问题

在生产环境中使用基于MQTT的集成后,我发现它过于“有状态”并且无法从意外事件中很好地恢复。还有其他问题,但不值得在这里重新讨论

然后在 2021 年 10 月,我决定我的“学习之旅”还没有结束,我将构建另一个TTI 连接器,该连接器将 Azure 存储队列用于 C2D 和 D2C 消息

试用该应用程序后,我意识到消息排序和部署复杂性可能是一个问题(我忘记了我的 TTN V2 网关学习),所以我暂停了项目(虽然我确实认为这个项目可能对一些集成项目有用

此时,我回顾了从多个 TTI 集成项目中学到的知识,并决定再次尝试使用The Things Stack(TTS) 网络挂钩集成

我的“The Things Industries(TTI) V3 connector revisited”项目是一个身份转换云网关,它将LoRaWAN EndDevices映射Azure IoT Hub Devices

连接器为每个LoRaWAN设备创建一个DeviceClient ,并且可以使用Azure 设备连接字符串或Azure 设备预配服务 (DPS)

poYBAGNYzOGAcTfrAAECnxtH-Cg828.png
The Things Industries Azure IoT 集成架构
 

在我所有的集成中,TTI 一直是设备配置的单一事实来源 (SSOT) ,因为LoRaWAN配置设置的数量和复杂性会使从其他应用程序管理它成为一个难题。(我还考虑过使用TTSEndDevice 模板来创建我可能会回来的设备)

当前版本的一个限制是,EndDevice 将连接到Azure IoT Hub (提供应用程序配置连接字符串或Azure IoT Hub DPS ),并且只有在收到 TTI 上行链路或Azure IoT Hub D2C 消息后才会处理 C2D 消息。一体化。

poYBAGNYzOOAE3kXAACiA5yQL2s896.png
Azure IoT Hub 设备在第一个 TTI 上行链路上连接
 

这可能是一个问题(尤其是在重新启动集成后)或配置了新设备。我考虑过添加几个Azure HTTP 触发器函数,应用程序调用这些函数可以检查设备的连接状态并可选择启动连接。(短期内从 TTI EndDevice 用户界面或 API 模拟上行链路应该可以工作)

我从D2C 消息传递开始,然后添加了C2D 消息传递,然后添加了支持DTDLV2 的 Device Provisioning(DPS) ,然后扩展了C2D 消息传递,最后实现了Azure IoT Central D2CC2D (使用少参数、单值和JavaScript 对象表示法(JSON )有效载荷命令

poYBAGNYzOeAGhl3AAFd9pLQHAk861.jpg
Azure IoT Central 仪表板,用于我的测试环境中的 Seeeduino LoRaWAN 传感器
 

该应用程序的核心是五个Azure HTTP 触发函数(已发送函数当前未使用)和一个为 C2D 调用的方法(与SetReceiveMessageHandlerAsync方法连接)消息。

pYYBAGNYzOqAFz3XAAEZr8FBBVM795.png
Azure 门户功能列表
 

Azure IoT 中心可以调用方法(同步)消息(异步)发送到设备进行处理。Azure IoT 中心 DeviceClient两个方法SetMethodDefaultHandlerAsyncSetReceiveMessageHandlerAsync ,它们可以处理直接方法和消息。

在对以前的 TTI 连接器进行了一些实验之后,我发现DirectMethods的同步特性不适用于LoRAWAN通常“不规则”的上行链路,因此目前不支持它们。

该集成广泛使用了Microsoft.Extensions.Logging功能和Azure Application Insights ,因此调试、监控和故障查找更省时。

poYBAGNYzOyAauTpAAH12E5TmNo741.png
Azure Application Insights(说明搜索功能)
 

我已将有用的“元数据”添加到各个日志项中,因此更容易跟踪为处理事件而执行的所有步骤,例如 ReceiveMessageCallback AbandonAsync CompleteAsyncRejectAsync C2D 消息处理中使用的 LockToken。

应用程序配置概述

可以使用appsettings.json文件配置应用程序(对桌面开发和调试很有用)

{
   "Values": {
      "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=...",
      "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
      "APPINSIGHTS_INSTRUMENTATIONKEY": "..."
      },
      "TheThingsIndustries": {
      "WebhookBaseURL": "https://....eu1.cloud.thethings.industries/api/v3/as/applicat      ions",
      "Applications": {
         "seeeduinolorawan": {
         "webhookId": "azure-iot-hub-connector",
         "APIKey": "..."
         },
         "Wisnode Devices": {
         "webhookId": "azure-iot-hub-connector",
         "APIKey": "..."
         },
         "dragino-lht65": {
         "webhookId": "azure-iot-hub-connector",
         "APIKey": "..."
         },
         "SeeeduinoLoRaWAN100": {
         "webhookId": "azure-iot-hub-connector",
         "APIKey": "..."
         },
         "rak3172": {
            "webhookId": "azure-iot-hub-connector",
            "APIKey": "..."
         },
       "application1": {
          "webhookId": "azure-iot-hub-connector",
          "APIKey": "..."
        }
      }
   },
   "AzureIoT": {
      "DeviceClientCacheSlidingExpiration": "P2H30M",
      "IoTHub": {
         "IoTHubConnectionString": "HostName=...",
         "Applications": {
            "SeeeduinoLoRaWAN": {
            "DtdlModelId": "dtmi:ttnv3connectorclient:SeeeduinoLoRaWAN4cz;1"
            },
            "Wisnode Devices": {
            },
            "Dragino LHT65": {
            }
         }
      },
      "DeviceProvisioningService": {
         "IdScope": "0ne..",
         "Applications": {
         "seeeduinolorawan": {
            "DtdlModelId": "dtmi:ttnv3connectorclient:SeeeduinoLoRaWAN4cz;1",
            "GroupEnrollmentKey": "...",
         },
         "Wisnode Devices": {
            "GroupEnrollmentKey": "..."
         },
         "dragino-lht65": {
            "GroupEnrollmentKey": "..."
         },
         "rak3172": {
            "GroupEnrollmentKey": "..."
         },
      "application1": {
         "DtdlModelId": "dtmi:ttnv3connectorclient:FezduinoWisnodeV14x8;4",
         "GroupEnrollmentKey": "..."
      }
   }
},
"IoTCentral": {
   "methods": {
      "LightsGoOn": {
         "Port": 10,
         "Payload": "{\"value_1\": 1}"
      },
      "LightsGoOff": {
         "Port": 10,
         "Payload": "{\"value_1\": 0}"
      },
      "value_0": {
         "Port": 20
      },
      "value_1": {
         "Port": 21
      },
      "value_2": {
         "Port": 22
      },
      "TemperatureOOBAlertMinimumAndMaximum": {
         "Port": 23
      },
   }
}

暂存和生产部署的首选方法)是使用Azure 门户Azure 功能配置刀片

pYYBAGNYzO-Aa-SEAAKdZ3Fh_tg207.png
Azure 门户 Azure Function 配置
 

Things Industries Webhook 集成配置

要发送下行链路和接收上行链路消息,必须配置 TTI 应用程序和TTI 连接器并配置 API 密钥。

注意 – TTN URL 和 Azure IoT 中心设备标识符区分大小写

poYBAGNYzPWAREuHAALngodwyP0595.png
Azure Function 配置 TTI webhook 配置设置
 

TTI 连接器需要webhookbaseURL ,然后是每个 TTI 应用程序和一个API 密钥,以及 WebhookId

pYYBAGNYzPiAZ8lbAAEjSkcSAOg368.png
Azure 功能配置应用程序密钥
 

调用 Azure Functions 时,Azure Function Host Key会在名为“x-functions-key”的HTTP 标头中传递

poYBAGNYzPqAH0iUAADHLPcEWyc812.png
Azure Function API Key TTI Webhook 消息处理进度配置
 

使用TTI App Key保护下行链路消息处理

poYBAGNYzP2ADiANAACBjeNeImQ098.png
显示所需权限的 TTI 应用程序密钥创建
 

调用TTI webhook 下行链路端点时, TTI 应用程序密钥在标准HTTP 授权标头中传递

poYBAGNYzQOAIj9rAACIlpf8IHU855.png
使用 Azure Function 端点作为基本 URL 创建 TTI Webhook 集成
 

Azure IoT 中心连接配置

TTI 连接器需要共享访问签名 (SAS) 设备策略连接字符串才能连接到Azure IoT 中心。

pYYBAGNYzQaAbmJZAAJFJikKiLk343.png
突出显示 Azure IoT 中心配置的 Azure 函数配置
 
poYBAGNYzQmAHUyeAAGInG4Qun4095.png
Azure IoT 中心设备策略连接字符串配置刀片
 

Azure IoT Hub设备必须手动或通过Azure IoT Hub REST API进行预配我已经试用了一个Azure 逻辑应用程序,它管理设备配置并且可以在操作失败时稳健地处理所需的补偿事务。

poYBAGNYzQuAcK8UAAEth8zEceU341.png
Azure IoT 中心设备
 

如果同时配置了Azure IoT 中心/Azure IoT 中心设备预配 (DPS) 支持,则 TTI 连接器应用程序将不会启动。

请注意:Azure IoT 中心设备 ID 是一个区分大小写的字符串(最长 128 个字符),由 ASCII 7 位字母数字字符加上某些特殊字符组成:-。+ % _ # * ? ( ), : = @$ '

Azure IoT 中心设备预配服务 (DPS) 配置

TTI 连接器支持用于独立Azure IoT 中心应用程序的Azure IoT 中心设备预配服务 (DPS) 。TTI 连接器实现还支持用于设备配置的Azure IoT Central 数字孪生定义语言( DTDL V2 )。

Azure IoT 中心设备预配服务支持使用X.509证书、可信平台模块 (TPM)使用共享访问签名(SAS) 安全令牌的对称密钥进行设备证明

Things Industries(TTI) V3 Azure IoT 连接器仅支持对称密钥设备证明。

如果Azure IoT 中心/ Azure IoT 中心设备预配 (DPS)支持两者/两者均未配置,则 TTI 连接器应用程序将不会启动。

pYYBAGNYzQ-AZBoNAAIeq-DXUuA965.png
Azure 功能配置与 Azure IoT 中心设备预配服务配置
 

Azure IoT 中心设备预配服务 (DPS)具有确定设备分配方式的服务级别设置有四种支持的分配策略:

  • 均匀加权分布:链接的物联网中心同样可能有设备配置给它们。默认设置。如果您仅将设备预配到一个 IoT 中心,则可以保留此设置。
  • 最低延迟:将设备预配到设备延迟最低的 IoT 中心。如果多个链接的 IoT 集线器将提供相同的最低延迟,则供应服务会在这些集线器之间散列设备
  • 通过注册列表进行静态配置:注册列表中所需 IoT 中心的规范优先于服务级别分配策略。
  • 自定义(使用 Azure 函数)自定义分配策略使您可以更好地控制如何将设备分配给 IoT 中心。这是通过使用 Azure 函数中的自定义代码将设备分配给 IoT 中心来实现的。设备预配服务调用您的 Azure 函数代码,提供有关设备和代码注册的所有相关信息。您的函数代码将执行并返回用于预配设备的 IoT 中心信息。

在我的测试环境中,我使用均匀加权分布,当我预置 1000 台设备时,它们分布在我的五个Azure IoT 中心

pYYBAGNYzRKAX1gwAAEhJo3m_5c814.png
Azure IoT 中心设备预配服务 Azure IoT 中心配置
 
pYYBAGNYzRWAHe27AAGk_px_ISU214.png
Azure IoT Hub DPS 设备分发
 

Azure IoT Central DPS 配置

TTI 连接器支持Azure IoT Central应用程序所需的Azure IoT 中心设备预配服务 (DPS) (有一种预配单个设备的方法) 。TTI 连接器实现还支持用于“自动”设备预配的Azure IoT Central 数字孪生定义语言( DTDL V2 )。

如果同时配置了Azure IoT 中心/Azure IoT 中心设备预配 (DPS) 支持,则 TTI 连接器应用程序将不会启动。

pYYBAGNYzQ-AZBoNAAIeq-DXUuA965.png
Azure 功能配置与 Azure IoT 中心设备预配服务配置
 

第一步是配置Azure IoT Central 注册组(确保“自动连接该组中的设备”为“零接触”配置)并将IDScope组注册密钥复制到 TTI 连接器配置

poYBAGNYzR6AGi-eAADsoRNAUkE603.png
RAK3172 设备连接配置
 

然后,我为我的RAK3172 分线板基于 .Net Core 供电的测试设备创建了一个 Azure IoT Central 模板

pYYBAGNYzSCAE2kLAAD3d8M7ht8562.png
RAK3172 设备模板
 
poYBAGNYzSOAFwe9AAEJdHLmI6g896.png
RAK3172 设备注册组
 
pYYBAGNYzSWAdJ9TAADgw9ydDE4677.png
将设备组与组注册配置关联
 
poYBAGNYzSmAcFdyAAD2g049uFo336.png
复制 RAK3172 DTDL V2 模板 ID
 

还可以使用在 TTI 连接器配置中指定的可选 dtdlmodelid 为 TTI 应用程序设置设备模板 @Id。

Azure IoT Hub 设备到云 (D2C)

LoRaWAN设备使用共享访问签名 (SAS) 设备策略连接字符串连接到Azure IoT 中心我正在使用Device Twin Explorer显示遥测数据并向我的传感器节点发送消息。

poYBAGNYzSyAXyr1AAEXMpbrU2w884.jpg
显示来自我的 Seeeduinolorawan2 设备的遥测数据的设备资源管理器
 

如果有效负载已被有效负载格式化程序解码,则将对其进行后处理,然后包含在消息有效负载中。

try
{
	JObject telemetryEvent = new JObject
	{
		{ "ApplicationID", applicationId },
		{ "DeviceEUI" , payload.EndDeviceIds.DeviceEui},
		{ "DeviceID", deviceId },
		{ "Port", port },
		{ "Simulated", payload.Simulated },
		{ "ReceivedAtUtc", payload.UplinkMessage.ReceivedAtUtc.ToString("s", CultureInfo.InvariantCulture) },
		{ "PayloadRaw", payload.UplinkMessage.PayloadRaw }
	};

	// If the payload has been decoded by payload formatter, put it in the message body.
	if (payload.UplinkMessage.PayloadDecoded != null)
	{
		EnumerateChildren(telemetryEvent, payload.UplinkMessage.PayloadDecoded);
	}

	// Send the message to Azure IoT Hub
	using (Message ioTHubmessage = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(telemetryEvent))))
	{
		// Ensure the displayed time is the acquired time rather than the uploaded time. 
		ioTHubmessage.Properties.Add("iothub-creation-time-utc", payload.UplinkMessage.ReceivedAtUtc.ToString("s", CultureInfo.InvariantCulture));
		ioTHubmessage.Properties.Add("ApplicationId", applicationId);
		ioTHubmessage.Properties.Add("DeviceEUI", payload.EndDeviceIds.DeviceEui);
		ioTHubmessage.Properties.Add("DeviceId", deviceId);
		ioTHubmessage.Properties.Add("port", port.ToString());
		ioTHubmessage.Properties.Add("Simulated", payload.Simulated.ToString());

		await deviceClient.SendEventAsync(ioTHubmessage);

		logger.LogInformation("Uplink-DeviceID:{deviceId} SendEventAsync success", deviceId);
	}
}
catch( Exception ex)
{
	logger.LogError(ex, "Uplink-DeviceID:{deviceId} SendEventAsync failure", deviceId);

	// If retries etc fail remove from the cache and it will get tried again on the next message
	_DeviceClients.Remove(deviceId);
}

Azure IoT Hub 云到设备 (C2D)

基本 Azure IoT 中心 C2D 消息传递仅需要端口号、TTI 确认、队列和优先级(如果未提供)使用默认值。

  • 确认 - 真/
  • 队列 - 推送/替换
  • 优先级 - 最低/低/低于正常/正常/高于正常/高/最高

这些选项在消息属性中指定为了测试此功能,我使用了Azure Device Explorer Twin应用程序,该应用程序还显示消息传递进度。

如果负载无效,则假定JSONBase64编码的(需要额外验证)并复制到下行链路消息的 payload_raw 字段中。

pYYBAGNYzS6AL6-tAADFC8wNbCA676.jpg
Azure 设备资源管理器 RAW 有效负载
 
poYBAGNYzTGASEbNAADbST3YReU601.jpg
Arduino 串行监视器 RAW Payload
 

如果有效载荷是有效的JSON ,它被“嫁接”(想不出更好的词)到TTI 下行链路消息 decoded_pa​​yload 字段中

poYBAGNYzTSALNYjAADqGCT16N8236.jpg
Arduino 串行监视器 JSON 有效负载
 

Azure IoT Central 设备到云 (D2C)

连接器“转换”了The Things Industries(TTI) MyDevices Cayenne 低功耗协议 (LPP) 有效负载格式化程序的输出(它还支持自定义编码器/解码器,但尚未经过广泛测试),以便它可以被Azure IoT Central摄取.

用于处理TTI 上行链路消息Azure 函数首先反序列化JSON负载,丢弃任何LoRaWAN 控制消息和具有空负载的消息。

poYBAGNYzTeAG07PAAFpehk3pkk470.png
Azure IoT Central Device Raw Data 选项卡,其中包含成功解码的 GPS 位置有效负载
 
pYYBAGNYzTqAV4vWAADDpUwi9wM730.png
使用基本数据类型的简单 Seeeduino LoRaWAN 模板
 
poYBAGNYzT2AAeBPAAE_z9uFLiI949.png
Dragino LHT65 模板使用一系列基本数据类型
 

为了测试更复杂的场景,我创建了一个Azure IoT Central 设备模板,该模板具有“功能类型”的位置。

poYBAGNYzT-AVS2BAADgvu0et3I503.png
带有温度、湿度和位置数据类型的 Seeeduino LoRaWAN/GPS 模板
 

如果消息已由有效负载格式化程序成功解码,则 PayloadDecoded 内容将被“嫁接”到Azure IoT Central 遥测消息中。

pYYBAGNYzUKATIRkAADNm0nMt3I672.png
 

Azure IoT Central 位置遥测消息的格式与 TTI Cayenne LPP Payload格式化程序的输出格式略有不同,因此必须对有效负载进行“后处理”(使用新的Azure IoT Central 地图遥测入口功能,这可能不是必需的) .

poYBAGNYzUaAKkppAADj0rmQKAg249.png
适用于 Azure IoT Central 的“后处理”TTI JSON GPS 位置数据
 
poYBAGNYzUmAH61MAAH7C6JZOSk094.png
突出显示设备位置的 Azure IoT Central 地图
 

我可能必须扩展后处理以支持其他Cayenne LPP 或第三方有效负载格式化程序

Azure IoT Central 云到设备 (C2D)

要发送下行链路消息,TTI 需要一个无法通过 Azure IoT Central 命令设置提供的LoRaWAN 端口号(加上可选队列、确认和优先级值),因此这些值在集成配置中进行配置。

我的集成仅使用离线排队命令,因为消息通常不会立即传递到传感器节点,特别是如果传感器节点仅每半小时/小时/天发送一条消息。

pYYBAGNYzUyAOm77AAJJGbGzZ10765.png
Azure IoT Function 连接器配置显示命令设置选项
 

每个 TTI 应用程序都有零个或多个Azure IoT Central 命令配置,这些配置指定 LoRaWAN 端口号,以及可选的有效负载、已确认的 TTI 下行链路消息、优先级和队列设置。

poYBAGNYzU6AQE2lAAEkhVC1Sf8619.png
Azure IoT Central 设备模板,包含支持的命令类型示例
 

无参数命令

即使该命令没有参数,也必须配置下行链路消息负载(当前只有JSON编码的负载,考虑到原始Base64负载支持)

此示例说明如何使用内置的Cayenne LPP 有效负载格式化程序配置打开和关闭灯的命令

poYBAGNYzVGAca-6AAHf6HmAFOw123.png
Azure IoT Central 命令 LoRaWAN 端口分配
 
poYBAGNYzVaAOqnIAAHrCZQngcQ596.png
Azure IoT Central 无参数命令 JSON 有效负载配置
 
pYYBAGNYzViAA4W-AAEgUvdADkU891.png
Azure IoT Central 无参数命令模板配置
 
pYYBAGNYzVuAeG_rAADfSCq_FFg765.png
Azure IoT Central 无参数命令启动
 

枚举参数

此示例显示如何通过从选项列表中选择所需状态来配置打开和关闭风扇的命令。

poYBAGNYzV6AfV97AAFVeOpPRCg292.png
Azure IoT Central 枚举命令模板配置
 
poYBAGNYzWaATaciAABGdXU4gms769.png
Azure IoT Central Enumeration 命令枚举值配置
 
pYYBAGNYzWiAbr60AAD_WtAy6zc232.png
Azure IoT Central 枚举命令启动
 
pYYBAGNYzWyAJsZ4AAGoU_bimTE677.png
Azure IoT Central 枚举命令交付
 

单值参数

此示例显示如何配置用于设置警报的最低温度的命令。

poYBAGNYzXGAWtMqAAE1EvoSrfY040.png
Azure IoT Central 单参数命令模板配置
 
pYYBAGNYzXSAK48IAADmwlSMS-I789.png
Azure IoT Central 单参数命令启动
 
poYBAGNYzXeAHMF5AAHyekH5NNk048.png
Azure IoT Central 单参数命令传输
 
poYBAGNYzXmAAAqFAADKRJ4uDbE545.png
Azure IoT Central 单参数命令交付
 

JSON 参数

此示例说明如何配置命令以设置警报的最低和最高温度。

poYBAGNYzX6ASWkVAAErBj8j3K4234.png
Azure IoT Central JSON 参数命令模板配置
 
pYYBAGNYzYGAU9d2AABo8tF3JyI211.png
Azure IoT Central JSON 参数命令有效负载格式配置
 
poYBAGNYzYSAAF1pAAD3g5sIhtI854.png
Azure IoT Central JSON 参数命令启动
 

交货确认

为了处理消息传递确认,将包含消息LockToken 的相关标识符添加到下行链路有效负载中的相关 ID

唯一需要的消息属性是 LoRaWAN 端口号,确认、队列、优先级和有效负载字段是可选的。

如果端口号属性或任何其他属性不正确,则调用DeviceClient.RejectAsync ,这会从设备队列中删除消息并向服务器指示无法处理该消息。

使用存储在 TTI CorrelationID 中的 Azure 令牌跟踪消息传递确认过程。

poYBAGNYzYqAB7aoAAGKv_1lXT4884.jpg
下行链路消息中的 Azure CorrelationID
 

未确认的消息

TTI 连接器调用CompleteAsync方法(使用 TTI CorrelationIDs 列表中的 LockToken),该方法在调用“排队”Azure 函数时从Azure IoT 中心设备队列中删除消息。

pYYBAGNYzY2AaoBaAAEdwWzyzks123.png
Azure IoT Explorer 云到设备消息,未确认传递
 
pYYBAGNYzZCADiOJAAIJADSaxSg885.png
Azure 应用程序洞察未确认的下行链路消息事件
 
poYBAGNYzZSAfEl9AAJw7Sbdq8g862.png
Azure 应用洞察未确认的下行消息事件序列
 
pYYBAGNYzZqAHrpRAAGZVxPMYzc267.png
未确认的下行消息传递
 

确认消息

pYYBAGNYzZyAOVy0AADsk8NCb8o393.png
Azure IoT Explorer Cloud 到设备的消息并确认交付
 
pYYBAGNYzZ-AE2YEAAGZR6h0tRg845.png
未确认的下行消息传递
 
poYBAGNYzaOAOOdkAAGstxc21LY531.png
Azure 应用程序洞察未确认的下行链路消息事件
 

如果消息传递成功(调用 Ack 函数),则会调用CompleteAsync方法(使用 TTI CorrelationIDs 列表中的 LockToken)从 Azure IoT 中心设备队列中删除消息。

poYBAGNYzaeAfnW2AAEVcxoSN3o859.png
已确认的下行链路消息传递
 
pYYBAGNYzaqAB3A3AAHqlE3PxbA750.png
Azure 应用洞察未确认的下行消息 Ack 事件
 

如果消息传递失败(调用失败的函数),则调用AbandonAsync方法(使用 TTI CorrelationIDs 列表中的 LockToken)将下行链路消息放回 Azure IoT 中心设备队列。

pYYBAGNYza2AJgZgAAH7SNS55Jo876.png
Azure 应用洞察确认下行消息事件序列 Nack 成功
 

如果消息传递不成功(调用 Nack 函数),则会调用RejectAsync方法(使用 CorrelationIDs 列表中的 LockToken),该方法从设备队列中删除消息并向服务器指示无法处理该消息。

poYBAGNYzbCADQ5nAAH9Rh0ucGA513.png
Azure 应用洞察确认下行消息事件序列 Nack 超时
 

消息 Failed( AbandonAsync )、Ack( CompleteAsync ) 和 Nack( RejectAsync ) 的处理方式需要进行更多测试,以确认我对 TTI 确认消息传递顺序的理解。

谨防

当Azure IoT 中心下行链路消息超时并重新发送时,将确认消息与不定期发送上行链路消息的设备一起使用可能会导致奇怪的问题。

执行摘要

这个项目已经付出了一年多的努力。我学到了很多关于LoRaWAN以及The Things Industries如何运作的知识

有时是一些愚蠢的事情,比如拖慢进度的错字

pYYBAGNYzbOAQzYoAAA1Z5TFv_s523.png
我生命中失去了 1 小时 DevEUI Typo
 

在我确信它已准备好投入生产之前,我对该软件进行了一个月的浸泡测试,但有几次我达到了我的Azure 支出限制,这禁用了我的所有服务,因此我不得不重新运行浸泡测试。

如果您有任何问题或反馈给我留言,我在Twitter上,我的博客上有更多关于我的“学习之旅”的详细信息


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !

'+ '

'+ '

'+ ''+ '
'+ ''+ ''+ '
'+ ''+ '' ); $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code ==5){ $(pop_this).attr('href',"/login/index.html"); return false } if(data.code == 2){ //跳转到VIP升级页面 window.location.href="//m.obk20.com/vip/index?aid=" + webid return false } //是会员 if (data.code > 0) { $('body').append(htmlSetNormalDownload); var getWidth=$("#poplayer").width(); $("#poplayer").css("margin-left","-"+getWidth/2+"px"); $('#tips').html(data.msg) $('.download_confirm').click(function(){ $('#dialog').remove(); }) } else { var down_url = $('#vipdownload').attr('data-url'); isBindAnalysisForm(pop_this, down_url, 1) } }); }); //是否开通VIP $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code == 2 || data.code ==5){ //跳转到VIP升级页面 $('#vipdownload>span').text("开通VIP 免费下载") return false }else{ // 待续费 if(data.code == 3) { vipExpiredInfo.ifVipExpired = true vipExpiredInfo.vipExpiredDate = data.data.endoftime } $('#vipdownload .icon-vip-tips').remove() $('#vipdownload>span').text("VIP免积分下载") } }); }).on("click",".download_cancel",function(){ $('#dialog').remove(); }) var setWeixinShare={};//定义默认的微信分享信息,页面如果要自定义分享,直接更改此变量即可 if(window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){ var d={ title:'重访The Things Industries (TTI) V3 Azure IoT连接器',//标题 desc:$('[name=description]').attr("content"), //描述 imgUrl:'https://'+location.host+'/static/images/ele-logo.png',// 分享图标,默认是logo link:'',//链接 type:'',// 分享类型,music、video或link,不填默认为link dataUrl:'',//如果type是music或video,则要提供数据链接,默认为空 success:'', // 用户确认分享后执行的回调函数 cancel:''// 用户取消分享后执行的回调函数 } setWeixinShare=$.extend(d,setWeixinShare); $.ajax({ url:"//www.obk20.com/app/wechat/index.php?s=Home/ShareConfig/index", data:"share_url="+encodeURIComponent(location.href)+"&format=jsonp&domain=m", type:'get', dataType:'jsonp', success:function(res){ if(res.status!="successed"){ return false; } $.getScript('https://res.wx.qq.com/open/js/jweixin-1.0.0.js',function(result,status){ if(status!="success"){ return false; } var getWxCfg=res.data; wx.config({ //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId:getWxCfg.appId, // 必填,公众号的唯一标识 timestamp:getWxCfg.timestamp, // 必填,生成签名的时间戳 nonceStr:getWxCfg.nonceStr, // 必填,生成签名的随机串 signature:getWxCfg.signature,// 必填,签名,见附录1 jsApiList:['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: setWeixinShare.title, // 分享标题 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 type: setWeixinShare.type, // 分享类型,music、video或link,不填默认为link dataUrl: setWeixinShare.dataUrl, // 如果type是music或video,则要提供数据链接,默认为空 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ”按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ空间”按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); }); }); } }); } function openX_ad(posterid, htmlid, width, height) { if ($(htmlid).length > 0) { var randomnumber = Math.random(); var now_url = encodeURIComponent(window.location.href); var ga = document.createElement('iframe'); ga.src = 'https://www1.elecfans.com/www/delivery/myafr.php?target=_blank&cb=' + randomnumber + '&zoneid=' + posterid+'&prefer='+now_url; ga.width = width; ga.height = height; ga.frameBorder = 0; ga.scrolling = 'no'; var s = $(htmlid).append(ga); } } openX_ad(828, '#berry-300', 300, 250);