教程:使用 Azure 资源管理器模板配置 IoT 中心消息路由

消息路由能够将遥测数据从 IoT 设备发送到内置的与事件中心兼容的终结点或自定义终结点,例如,Blob 存储、服务总线队列、服务总线主题和事件中心。 若要配置自定义消息路由,请创建路由查询来自定义与特定条件匹配的路由。 设置完成后,引入的数据将通过 IoT 中心自动路由到终结点。 如果某个消息不匹配定义的任何路由查询,它将路由到默认终结点。

本教程包括 2 个部分,介绍如何通过 IoT 中心设置和使用这些自定义路由查询。 将消息从 IoT 设备路由到多个终结点中的一个,包括 Blob 存储和服务总线队列。 路由到服务总线队列的消息将由逻辑应用拾取,并通过电子邮件发送。 未定义自定义消息路由的消息将发送到默认终结点,然后由 Azure 流分析拾取,可在 Power BI 中直观查看。

若要完成本教程的第 1 和第 2 部分,请执行以下任务:

第 I 部分:创建资源并设置消息路由

  • 创建资源 - IoT 中心、存储帐户、服务总线队列和模拟设备。 使用 Azure 门户、Azure 资源管理器模板、Azure CLI 或 Azure PowerShell 即可完成此操作。
  • 在 IoT 中心为存储帐户和服务总线队列配置终结点和消息路由。

第 II 部分:将消息发送到中心并查看路由的结果

  • 创建一个逻辑应用,该应用将在消息添加到服务总线队列时触发,并发送电子邮件。
  • 下载并运行应用,该应用模拟 IoT 设备将消息发送到中心,以获得不同的路由选择。
  • 为发送至默认终结点的数据创建 Power BI 可视化。
  • 查看结果...
  • ...在服务总线队列和电子邮件中。
  • ...在存储帐户中。
  • ...在 Power BI 可视化中。

先决条件

  • 对于本教程的第 1 部分:

    • 必须拥有 Azure 订阅。 如果没有 Azure 订阅,请在开始前创建一个试用订阅
  • 对于本教程的第 2 部分:

    • 必须事先完成本教程的第 1 部分,并保留一些可用的资源。
    • 安装 Visual Studio
    • 有权访问用于分析默认终结点的流分析的 Power BI 帐户。 (免费试用 Power BI。)
    • 提供一个用于发送通知电子邮件的工作或学校帐户。
    • 确保已在防火墙中打开端口 8883。 本教程中的示例使用 MQTT 协议,该协议通过端口 8883 进行通信。 在某些公司和教育网络环境中,此端口可能被阻止。 有关解决此问题的更多信息和方法,请参阅连接到 IoT 中心(MQTT)

创建基础资源

在配置消息路由之前,需创建 IoT 中心、存储帐户和服务总线队列。 可以参阅适用于本教程第 1 部分的四篇文章中的一篇来创建这些资源:Azure门户、Azure 资源管理器模板、Azure CLI 或 Azure PowerShell。

为所有资源使用相同的资源组和位置。 在本教程结束后,可以通过删除资源组一次性删除所有资源。

下面是将在以下部分中执行的步骤摘要:

  1. 创建资源组

  2. 在 S1 层级中创建 IoT 中心。 将使用者组添加到 IoT 中心。 检索数据时,Azure 流分析使用使用者组。

    备注

    必须使用付费层中的 IoT 中心来完成本教程。 免费层只允许设置一个终结点,但本教程需要多个终结点。

  3. 使用 Standard_LRS 副本创建标准 V1 存储帐户。

  4. 创建服务总线命名空间和队列。

  5. 为发送消息到中心的模拟设备创建设备标识。 保存测试阶段的密钥。 (如果创建资源管理器模板,则会在部署模板后执行此操作。)

消息路由

根据模拟设备附加到消息的属性将消息路由到不同资源。 未自定义路由的消息将发送到默认终结点(消息/事件)。 在下一教程中,我们会将消息发送到 IoT 中心,看其如何路由到不同的目标。

结果
级别=“storage” 写入到 Azure 存储。
级别=“critical” 写入服务总线队列。 逻辑应用从队列检索消息并使用 Office 365 通过电子邮件发送该消息。
default 使用 Power BI 显示此数据。

第一步是设置终结点,以便将数据路由到其中。 第二步是设置使用该终结点的消息路由。 设置路由后,即可在门户中查看终结点和消息路由。

下载模板和参数文件

在本教程的第二部分,我们将下载并运行一个 Visual Studio 应用程序,以将消息发送到 IoT 中心。 该下载内容的某个文件夹中包含 Azure 资源管理器模板和参数文件,以及 Azure CLI 和 PowerShell 脚本。

现在请继续下载 Azure IoT C# 示例。 解压缩 master.zip 文件。 资源管理器模板和参数文件位于 /iot-hub/Tutorials/Routing/SimulatedDevice/resources/ 中,其文件名分别为 template_iothub.jsontemplate_iothub_parameters.json

创建资源

我们将使用 Azure 资源管理器 (RM) 模板来创建所有资源。 每次可以运行几行 Azure CLI 和 PowerShell 脚本。 RM 模板是通过一个步骤部署的。 本文通过单独的部分来介绍每种方法。 然后,介绍如何部署模板,以及创建虚拟设备用于测试。 部署模板后,可以在门户中查看消息路由配置。

有几个资源名称必须全局唯一,例如 IoT 中心名称和存储帐户名称。 为了简化资源的命名,将在设置的这些资源名称后面追加一个基于当前日期/时间随机生成的字母数字值。

查看模板时,将会看到为这些资源设置的变量的位置。这些变量采用传入的参数,并将 randomValue 连接到该参数。

以下部分将解释所用的参数。

参数

其中的大部分参数都具有默认值。 以 _in 结尾的参数将与 randomValue 相连接,使参数名称全局唯一。

randomValue:此值是基于部署模板时的日期/时间生成的。 此字段不包含在参数文件中,因为它是在模板本身中生成的。

subscriptionId:此字段用于标识要在其中部署模板的订阅。 此字段不包含在参数文件中,因为它是由系统设置的。

IoTHubName_in:此字段是基本 IoT 中心名称,与 randomValue 相连接,使名称全局唯一。

location:此字段是要部署到的 Azure 区域,例如“chinaeast”。

consumer_group:此字段是为通过路由终结点传入的消息设置的使用者组。 它用于筛选 Azure 流分析中的结果。 例如,如果我们想要获取整个流中的所有内容,或者数据通过设置为 Contoso 的 consumer_group 传入,则我们可将 Azure 流分析流(和 Power BI 报表)设置为仅显示这些条目。 本教程的第 2 部分将使用此字段。

sku_name:此字段是 IoT 中心的规模。 此值必须为 S1 或更高;免费层不适用于本教程,因为它不允许多个终结点。

sku_units:此字段类似于 sku_name,表示可以使用的 IoT 中心单位数。

d2c_partitions:此字段表示用于事件流的分区数。

storageAccountName_in:此字段是要创建的存储帐户的名称。 消息将路由到存储帐户中的容器。 此字段与 randomValue 相连接,使名称全局唯一。

storageContainerName:此字段是路由到存储帐户的消息的存储容器。

storage_endpoint:此字段是消息路由使用的存储帐户终结点的名称。

service_bus_namespace_in:此字段是要创建的服务总线命名空间的名称。 此值与 randomValue 相连接,使名称全局唯一。

service_bus_queue_in:此字段是用于路由消息的服务总线队列的名称。 此值与 randomValue 相连接,使名称全局唯一。

AuthRules_sb_queue:此字段是服务总线队列的授权规则,用于检索队列的连接字符串。

变量

这些值将在模板中使用,往往派生自参数。

queueAuthorizationRuleResourceId:此字段是服务总线队列的授权规则的 ResourceId。 而 ResourceId 用于检索队列的连接字符串。

iotHubName:此字段是连接 randomValue 后的 IoT 中心名称。

storageAccountName:此字段是连接 randomValue 后的存储帐户名称。

service_bus_namespace:此字段是连接 randomValue 后的命名空间。

service_bus_queue:此字段是连接 randomValue 后的服务总线队列名称。

sbVersion:要使用的服务总线 API 版本。 在本例中为“2017-04-01”。

资源:存储帐户和容器

创建的第一个资源是存储帐户,以及消息要路由到的容器。 容器是存储帐户下的资源。 容器具有存储帐户的 dependsOn 子句,因此,必须先创建存储帐户,再创建容器。

下面是此节的外观:

{
    "type": "Microsoft.Storage/storageAccounts",
    "name": "[variables('storageAccountName')]",
    "apiVersion": "2018-07-01",
    "location": "[parameters('location')]",
    "sku": {
        "name": "Standard_LRS",
        "tier": "Standard"
    },
    "kind": "Storage",
    "properties": {},
    "resources": [
        {
        "type": "blobServices/containers",
        "apiVersion": "2018-07-01",
        "name": "[concat('default/', parameters('storageContainerName'))]",
        "properties": {
            "publicAccess": "None"
            } ,
        "dependsOn": [
            "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]"
            ]
        }
    ]
}

资源:服务总线命名空间和队列

创建的第二个资源是服务总线命名空间,以及消息要路由到的服务总线队列。 SKU 设置为“标准”。 从变量中检索 API 版本。 此资源设置为在部署此节时激活服务总线命名空间 (status:Active)。

{
    "type": "Microsoft.ServiceBus/namespaces",
    "comments": "The Sku should be 'Standard' for this tutorial.",
    "sku": {
        "name": "Standard",
        "tier": "Standard"
    },
    "name": "[variables('service_bus_namespace')]",
    "apiVersion": "[variables('sbVersion')]",
    "location": "[parameters('location')]",
    "properties": {
        "provisioningState": "Succeeded",
        "metricId": "[concat('a4295411-5eff-4f81-b77e-276ab1ccda12:', variables('service_bus_namespace'))]",
        "serviceBusEndpoint": "[concat('https://', variables('service_bus_namespace'),'.servicebus.windows.net:443/')]",
        "status": "Active"
    },
    "dependsOn": []
}

此节创建服务总线队列。 此脚本部分包含 dependsOn 子句,用于确保先创建命名空间,再创建队列。

{
    "type": "Microsoft.ServiceBus/namespaces/queues",
    "name": "[concat(variables('service_bus_namespace'), '/', variables('service_bus_queue'))]",
    "apiVersion": "[variables('sbVersion')]",
    "location": "[parameters('location')]",
    "scale": null,
    "properties": {},
    "dependsOn": [
        "[resourceId('Microsoft.ServiceBus/namespaces', variables('service_bus_namespace'))]"
    ]
}

资源:IoT 中心和消息路由

创建存储帐户和服务总线队列后,可以创建要将消息路由到的 IoT 中心。 RM 模板使用 dependsOn 子句,因此,它不会在创建服务总线资源和存储帐户之前尝试创建中心。

下面是 IoT 中心节的第一个部分。 此模板部分设置依赖项,以属性开头。

{
    "apiVersion": "2018-04-01",
    "type": "Microsoft.Devices/IotHubs",
    "name": "[variables('IoTHubName')]",
    "location": "[parameters('location')]",
    "dependsOn": [
        "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName'))]",
        "[resourceId('Microsoft.ServiceBus/namespaces', variables('service_bus_namespace'))]",
        "[resourceId('Microsoft.ServiceBus/namespaces/queues', variables('service_bus_namespace'), variables('service_bus_queue'))]"
    ],
    "properties": {
        "eventHubEndpoints": {}
            "events": {
                "retentionTimeInDays": 1,
                "partitionCount": "[parameters('d2c_partitions')]"
                }
            },

下一节是 Iot 中心的消息路由配置节。 首先是终结点节。 此模板部分设置服务总线队列和存储帐户的路由终结点,包括连接字符串。

若要创建队列的连接字符串,需要使用内联检索的 queueAuthorizationRulesResourcedId。 若要创建存储帐户的连接字符串,需要检索主存储密钥,然后在连接字符串的格式中使用该密钥。

也需要在终结点配置中将 Blob 格式设置为 AVROJSON

备注

可以将数据以 Apache Avro 格式(默认)或 JSON 格式(预览版)写入 Blob 存储。

对 JSON 格式进行编码的功能在除“中国东部”和“中国北部”以外的可以使用 IoT 中心的所有区域均为预览版。 编码格式只能在配置 Blob 存储终结点时设置。 不能更改已设置的终结点的格式。 使用 JSON 编码时,必须在消息系统属性中将 contentType 设置为 JSON,将 contentEncoding 设置为 UTF-8。

若要更详细地了解如何使用 Blob 存储终结点,请参阅有关如何路由到存储的指南

"routing": {
   "endpoints": {
       "serviceBusQueues": [
       {
           "connectionString": "[Concat('Endpoint=sb://',variables('service_bus_namespace'),'.servicebus.windows.net/;SharedAccessKeyName=',parameters('AuthRules_sb_queue'),';SharedAccessKey=',listkeys(variables('queueAuthorizationRuleResourceId'),variables('sbVersion')).primaryKey,';EntityPath=',variables('service_bus_queue'))]",
           "name": "[parameters('service_bus_queue_endpoint')]",
           "subscriptionId": "[parameters('subscriptionId')]", 
           "resourceGroup": "[resourceGroup().Name]"
       }
       ],
       "serviceBusTopics": [],
       "eventHubs": [],
       "storageContainers": [
           {
               "connectionString": 
               "[Concat('DefaultEndpointsProtocol=https;AccountName=',variables('storageAccountName'),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountName')), providers('Microsoft.Storage', 'storageAccounts').apiVersions[0]).keys[0].value)]",
               "containerName": "[parameters('storageContainerName')]",
               "fileNameFormat": "{iothub}/{partition}/{YYYY}/{MM}/{DD}/{HH}/{mm}",
               "batchFrequencyInSeconds": 100,
               "maxChunkSizeInBytes": 104857600,
               "encoding": "avro",
               "name": "[parameters('storage_endpoint')]",
               "subscriptionId": "[parameters('subscriptionId')]",
               "resourceGroup": "[resourceGroup().Name]"
           }
       ]
   },

下一节是用于将消息路由到终结点。 每个终结点存在一项设置,因此,服务总线队列和存储帐户容器各有一项设置。

请记住,路由到存储的消息的查询条件是 level="storage",路由到服务总线队列的消息的查询条件是 level="critical"

"routes": [
    {
        "name": "contosoStorageRoute",
        "source": "DeviceMessages",
        "condition": "level=\"storage\"",
        "endpointNames": [
            "[parameters('storage_endpoint')]"
            ],
        "isEnabled": true
    },
    {
        "name": "contosoSBQueueRoute",
        "source": "DeviceMessages",
        "condition": "level=\"critical\"",
        "endpointNames": [
            "[parameters('service_bus_queue_endpoint')]"
            ],
        "isEnabled": true
    }
],

此 JSON 演示了 IoT 中心节的余下内容,其中包含默认信息和中心的 SKU。

            "fallbackRoute": {
                "name": "$fallback",
                "source": "DeviceMessages",
                "condition": "true",
                "endpointNames": [
                    "events"
                ],
                "isEnabled": true
            }
        },
        "storageEndpoints": {
            "$default": {
                "sasTtlAsIso8601": "PT1H",
                "connectionString": "",
                "containerName": ""
            }
        },
        "messagingEndpoints": {
            "fileNotifications": {
                "lockDurationAsIso8601": "PT1M",
                "ttlAsIso8601": "PT1H",
                "maxDeliveryCount": 10
            }
        },
        "enableFileUploadNotifications": false,
        "cloudToDevice": {
            "maxDeliveryCount": 10,
            "defaultTtlAsIso8601": "PT1H",
            "feedback": {
                "lockDurationAsIso8601": "PT1M",
                "ttlAsIso8601": "PT1H",
                "maxDeliveryCount": 10
            }
        }
    },
    "sku": {
        "name": "[parameters('sku_name')]",
        "capacity": "[parameters('sku_units')]"
    }
}

资源:服务总线队列授权规则

服务总线队列授权规则用于检索服务总线队列的连接字符串。 它使用 dependsOn 子句来确保先创建服务总线命名空间和服务总线队列,然后再创建授权规则。

{
    "type": "Microsoft.ServiceBus/namespaces/queues/authorizationRules",
    "name": "[concat(variables('service_bus_namespace'), '/', variables('service_bus_queue'), '/', parameters('AuthRules_sb_queue'))]",
    "apiVersion": "[variables('sbVersion')]",
    "location": "[parameters('location')]",
    "scale": null,
    "properties": {
        "rights": [
            "Send"
        ]
    },
    "dependsOn": [
        "[resourceId('Microsoft.ServiceBus/namespaces', variables('service_bus_namespace'))]",
        "[resourceId('Microsoft.ServiceBus/namespaces/queues', variables('service_bus_namespace'), variables('service_bus_queue'))]"
    ]
},

资源:使用者组

在此节中,创建 Azure 流分析在本教程第二部分使用的 IoT 中心数据的使用者组。

{
    "type": "Microsoft.Devices/IotHubs/eventHubEndpoints/ConsumerGroups",
    "name": "[concat(variables('iotHubName'), '/events/',parameters('consumer_group'))]",
    "apiVersion": "2018-04-01",
    "dependsOn": [
        "[concat('Microsoft.Devices/IotHubs/', variables('iotHubName'))]"
    ]
}

资源:Outputs

若要将值发回到部署脚本以供显示,请使用 output 节。 此模板部分返回服务总线队列的连接字符串。 不一定要返回值;包含值的目的是通过示例演示如何向调用方脚本返回结果。

"outputs": {
    "sbq_connectionString": {
      "type": "string",
      "value": "[Concat('Endpoint=sb://',variables('service_bus_namespace'),'.servicebus.windows.net/;SharedAccessKeyName=',parameters('AuthRules_sb_queue'),';SharedAccessKey=',listkeys(variables('queueAuthorizationRuleResourceId'),variables('sbVersion')).primaryKey,';EntityPath=',variables('service_bus_queue'))]"
    }
  }

部署 RM 模板

若要将模板部署到 Azure,请将模板和参数文件上传,然后执行某个脚本来部署模板。 本示例使用 PowerShell。

若要上传文件,请在菜单栏中选择“上传/下载文件”图标,然后选择“上传”。

Cloud Shell 菜单栏,其中已突出显示“上传/下载文件”

使用弹出的文件资源管理器找到本地磁盘上的文件并将其选中,然后选择“打开”。

上传文件后,结果对话框将显示下图所示的内容。

Cloud Shell 菜单栏,其中已突出显示“上传/下载文件”

文件将上传到 CLI 实例使用的共享。

运行脚本以执行部署。 此脚本的最后一行检索设置返回的变量 - 服务总线队列连接字符串。

该脚本设置并使用以下变量:

$RGName 是模板要部署到的资源组名称。 此字段是在部署模板之前创建的。

$location 是将要用于模板的 Azure 位置,例如“chinaeast”。

deploymentname 是分配给部署的名称,用于检索返回变量值。

下面是 PowerShell 脚本。 请复制此 PowerShell 脚本并将其粘贴,然后按 Enter 运行它。

$RGName="ContosoResources"
$location = "chinaeast"
$deploymentname="contoso-routing"

# Remove the resource group if it already exists. 
#Remove-AzResourceGroup -name $RGName 
# Create the resource group.
New-AzResourceGroup -name $RGName -Location $location 

# Set a path to the parameter file. 
$parameterFile = "$HOME/template_iothub_parameters.json"
$templateFile = "$HOME/template_iothub.json"

# Deploy the template.
New-AzResourceGroupDeployment `
    -Name $deploymentname `
    -ResourceGroupName $RGName `
    -TemplateParameterFile $parameterFile `
    -TemplateFile $templateFile `
    -verbose

# Get the returning value of the connection string.
(Get-AzResourceGroupDeployment -ResourceGroupName $RGName -Name $deploymentname).Outputs.sbq_connectionString.value

如果遇到脚本错误,可在本地编辑脚本,再次将其上传到 CLI,然后再次运行该脚本。 脚本成功完成运行后,请继续下一步。

创建模拟设备

接下来,创建设备标识并保存其密钥供之后使用。 此设备标识由模拟应用程序用来发送消息到 IoT 中心。 此功能在 PowerShell 中不可用,在使用 Azure 资源管理器模板时也不可用。 以下步骤介绍如何使用 Azure 门户创建模拟设备。

  1. 打开 Azure 门户并登录到 Azure 帐户。

  2. 选择“资源组”,然后选择你的资源组 。 本教程使用 ContosoResources 。

  3. 在资源列表中,选择 IoT 中心。 本教程使用 ContosoTestHub 。 从中心窗格选择“IoT 设备” 。

  4. 选择“+ 添加” 。 在添加设备窗格中,填写设备 ID。 本教程使用 Contoso-Test-Device 。 将密钥留空,勾选“自动生成密钥” 。 确保已启用“将设备连接到 IoT 中心” 。 选择“保存”。

    “添加设备”屏幕

  5. 创建设备后,即可选择它来查看生成的密钥。 选择主密钥上的“复制”图标,将其保存在某个位置(如记事本)供本教程的测试阶段使用。

    设备详细信息(包括密钥)

在门户中查看消息路由

设置终结点和消息路由以后,即可在门户中查看其配置。 登录 Azure 门户,转到“资源组”。 接下来选择你的资源组,然后选择中心(在本教程中,中心名称以 ContosoTestHub 开头)。 此时会看到“IoT 中心”窗格。

IoT 中心属性平面

在适用于 IoT 中心的选项中,选择“消息路由”。 此时会显示已成功设置的路由。

已设置的路由

在“消息路由”屏幕上 选择“自定义终结点” ,查看为路由定义的终结点。

为路由设置的终结点

后续步骤

设置所有资源并配置消息路由后,请继续学习下一篇教程,了解如何处理和显示有关路由的消息的信息。