早在 2020 年 11 月,我就发布了我的 The Things Network V2 Azure IoT Hubs & IoT Central Gateway。该项目是关于构建物联网 (TTN)HTTP 应用程序集成,它启用Azure IoT 中心和Azure IoT Central与Azure IoT 中心设备供应服务 (DPS)供应支持的连接。
该项目使用了“符合流行语”的Microsoft Azure服务选择,但它不支持云到设备 (C2D) 消息,存在消息排序问题,并且部署和设置复杂。(还有其他问题,但不值得在这里重新讨论)
然后在 2021 年 3 月,我再次尝试使用我的 The Things Industries(TTI) V3 Azure IoT 连接器,它是一个TTI(消息队列遥测传输)MQTT 集成,也使用了 TTI应用程序和终端设备 API 。
此版本使用MQTTNet (这是一个很棒的库)和在应用程序启动时连接到Azure IoT Hub或Azure IoT Central的 TTI 设备。即使我使用多个线程并对 Application 和 EndDevice 请求进行分页,这个过程也很慢。
该应用程序更容易调试,因为我可以在桌面上运行它,并且更容易配置,因为我将配置转移回appsettings.json文件(我可能会重新考虑放弃Azure Key Vault支持的决定)。
此版本具有基本的Azure 数字孪生定义语言 (DTDL)支持,因此可以在Azure IoT Central中“自动”预配设备。
我还添加了对Azure IoT Hubs和Azure 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) 。
在我所有的集成中,TTI 一直是设备配置的单一事实来源 (SSOT) ,因为LoRaWAN配置设置的数量和复杂性会使从其他应用程序管理它成为一个难题。(我还考虑过使用TTSEndDevice 模板来创建我可能会回来的设备)
当前版本的一个限制是,EndDevice 将连接到Azure IoT Hub (提供应用程序配置连接字符串或Azure IoT Hub DPS ),并且只有在收到 TTI 上行链路或Azure IoT Hub D2C 消息后才会处理 C2D 消息。一体化。
这可能是一个问题(尤其是在重新启动集成后)或配置了新设备。我考虑过添加几个Azure HTTP 触发器函数,应用程序调用这些函数可以检查设备的连接状态并可选择启动连接。(短期内从 TTI EndDevice 用户界面或 API 模拟上行链路应该可以工作)
我从D2C 消息传递开始,然后添加了C2D 消息传递,然后添加了支持DTDLV2 的 Device Provisioning(DPS) ,然后扩展了C2D 消息传递,最后实现了Azure IoT Central D2C和C2D (使用少参数、单值和JavaScript 对象表示法(JSON )有效载荷命令)
该应用程序的核心是五个Azure HTTP 触发函数(已发送函数当前未使用)和一个为 C2D 调用的方法(与SetReceiveMessageHandlerAsync方法连接)消息。
Azure IoT 中心可以调用方法(同步)或将消息(异步)发送到设备进行处理。Azure IoT 中心 DeviceClient有两个方法SetMethodDefaultHandlerAsync和SetReceiveMessageHandlerAsync ,它们可以处理直接方法和消息。
在对以前的 TTI 连接器进行了一些实验之后,我发现DirectMethods的同步特性不适用于LoRAWAN通常“不规则”的上行链路,因此目前不支持它们。
该集成广泛使用了Microsoft.Extensions.Logging功能和Azure Application Insights ,因此调试、监控和故障查找更省时。
我已将有用的“元数据”添加到各个日志项中,因此更容易跟踪为处理事件而执行的所有步骤,例如 ReceiveMessageCallback 、AbandonAsync 、CompleteAsync和RejectAsync 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 功能配置刀片
要发送下行链路和接收上行链路消息,必须配置 TTI 应用程序和TTI 连接器并配置 API 密钥。
注意 – TTN URL 和 Azure IoT 中心设备标识符区分大小写
TTI 连接器需要webhookbaseURL ,然后是每个 TTI 应用程序和一个API 密钥,以及 WebhookId
当调用 Azure Functions 时,Azure Function Host Key会在名为“x-functions-key”的HTTP 标头中传递
当调用TTI webhook 下行链路端点时, TTI 应用程序密钥在标准HTTP 授权标头中传递。
TTI 连接器需要共享访问签名 (SAS) 设备策略连接字符串才能连接到Azure IoT 中心。
Azure IoT Hub设备必须手动或通过Azure IoT Hub REST API进行预配。我已经试用了一个Azure 逻辑应用程序,它管理设备配置并且可以在操作失败时稳健地处理所需的补偿事务。
如果同时配置了Azure IoT 中心/Azure IoT 中心设备预配 (DPS) 支持,则 TTI 连接器应用程序将不会启动。
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 连接器应用程序将不会启动。
Azure IoT 中心设备预配服务 (DPS)具有确定设备分配方式的服务级别设置。有四种支持的分配策略:
在我的测试环境中,我使用均匀加权分布,当我预置 1000 台设备时,它们分布在我的五个Azure IoT 中心。
TTI 连接器支持Azure IoT Central应用程序所需的Azure IoT 中心设备预配服务 (DPS) (有一种预配单个设备的方法) 。TTI 连接器实现还支持用于“自动”设备预配的Azure IoT Central 数字孪生定义语言( DTDL V2 )。
如果同时配置了Azure IoT 中心/Azure IoT 中心设备预配 (DPS) 支持,则 TTI 连接器应用程序将不会启动。
第一步是配置Azure IoT Central 注册组(确保“自动连接该组中的设备”为“零接触”配置)并将IDScope和组注册密钥复制到 TTI 连接器配置
然后,我为我的RAK3172 分线板基于 .Net Core 供电的测试设备创建了一个 Azure IoT Central 模板。
还可以使用在 TTI 连接器配置中指定的可选 dtdlmodelid 为 TTI 应用程序设置设备模板 @Id。
LoRaWAN设备使用共享访问签名 (SAS) 设备策略连接字符串连接到Azure IoT 中心。我正在使用Device Twin Explorer显示遥测数据并向我的传感器节点发送消息。
如果有效负载已被有效负载格式化程序解码,则将对其进行后处理,然后包含在消息有效负载中。
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 中心 C2D 消息传递仅需要端口号、TTI 确认、队列和优先级(如果未提供)使用默认值。
这些选项在消息属性中指定。为了测试此功能,我使用了Azure Device Explorer Twin应用程序,该应用程序还显示消息传递进度。
如果负载无效,则假定JSON是Base64编码的(需要额外验证)并复制到下行链路消息的 payload_raw 字段中。
如果有效载荷是有效的JSON ,它被“嫁接”(想不出更好的词)到TTI 下行链路消息 decoded_payload 字段中。
连接器“转换”了The Things Industries(TTI) MyDevices Cayenne 低功耗协议 (LPP) 有效负载格式化程序的输出(它还支持自定义编码器/解码器,但尚未经过广泛测试),以便它可以被Azure IoT Central摄取.
用于处理TTI 上行链路消息的Azure 函数首先反序列化JSON负载,丢弃任何LoRaWAN 控制消息和具有空负载的消息。
为了测试更复杂的场景,我创建了一个Azure IoT Central 设备模板,该模板具有“功能类型”的位置。
如果消息已由有效负载格式化程序成功解码,则 PayloadDecoded 内容将被“嫁接”到Azure IoT Central 遥测消息中。
Azure IoT Central 位置遥测消息的格式与 TTI Cayenne LPP Payload格式化程序的输出格式略有不同,因此必须对有效负载进行“后处理”(使用新的Azure IoT Central 地图遥测入口功能,这可能不是必需的) .
我可能必须扩展后处理以支持其他Cayenne LPP 或第三方有效负载格式化程序。
要发送下行链路消息,TTI 需要一个无法通过 Azure IoT Central 命令设置提供的LoRaWAN 端口号(加上可选队列、确认和优先级值),因此这些值在集成配置中进行配置。
我的集成仅使用离线排队命令,因为消息通常不会立即传递到传感器节点,特别是如果传感器节点仅每半小时/小时/天发送一条消息。
每个 TTI 应用程序都有零个或多个Azure IoT Central 命令配置,这些配置指定 LoRaWAN 端口号,以及可选的有效负载、已确认的 TTI 下行链路消息、优先级和队列设置。
即使该命令没有参数,也必须配置下行链路消息负载(当前只有JSON编码的负载,考虑到原始Base64负载支持)
此示例说明如何使用内置的Cayenne LPP 有效负载格式化程序配置打开和关闭灯的命令。
此示例显示如何通过从选项列表中选择所需状态来配置打开和关闭风扇的命令。
此示例显示如何配置用于设置警报的最低温度的命令。
此示例说明如何配置命令以设置警报的最低和最高温度。
为了处理消息传递确认,将包含消息LockToken 的相关标识符添加到下行链路有效负载中的相关 ID 。
唯一需要的消息属性是 LoRaWAN 端口号,确认、队列、优先级和有效负载字段是可选的。
如果端口号属性或任何其他属性不正确,则调用DeviceClient.RejectAsync ,这会从设备队列中删除消息并向服务器指示无法处理该消息。
使用存储在 TTI CorrelationID 中的 Azure 令牌跟踪消息传递确认过程。
未确认的消息
TTI 连接器调用CompleteAsync方法(使用 TTI CorrelationIDs 列表中的 LockToken),该方法在调用“排队”Azure 函数时从Azure IoT 中心设备队列中删除消息。
确认消息
如果消息传递成功(调用 Ack 函数),则会调用CompleteAsync方法(使用 TTI CorrelationIDs 列表中的 LockToken)从 Azure IoT 中心设备队列中删除消息。
如果消息传递失败(调用失败的函数),则调用AbandonAsync方法(使用 TTI CorrelationIDs 列表中的 LockToken)将下行链路消息放回 Azure IoT 中心设备队列。
如果消息传递不成功(调用 Nack 函数),则会调用RejectAsync方法(使用 CorrelationIDs 列表中的 LockToken),该方法从设备队列中删除消息并向服务器指示无法处理该消息。
消息 Failed( AbandonAsync )、Ack( CompleteAsync ) 和 Nack( RejectAsync ) 的处理方式需要进行更多测试,以确认我对 TTI 确认消息传递顺序的理解。
谨防
当Azure IoT 中心下行链路消息超时并重新发送时,将确认消息与不定期发送上行链路消息的设备一起使用可能会导致奇怪的问题。
这个项目已经付出了一年多的努力。我学到了很多关于LoRaWAN以及The Things Industries如何运作的知识。
有时是一些愚蠢的事情,比如拖慢进度的错字
在我确信它已准备好投入生产之前,我对该软件进行了一个月的浸泡测试,但有几次我达到了我的Azure 支出限制,这禁用了我的所有服务,因此我不得不重新运行浸泡测试。
如果您有任何问题或反馈给我留言,我在Twitter上,我的博客上有更多关于我的“学习之旅”的详细信息。
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
全部0条评论
快来发表一下你的评论吧 !