教程:使用 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 存储。

编码格式只能在配置 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,请将模板和参数文件上传到 Azure Cloud Shell,然后执行某个脚本来部署模板。 打开 Azure Cloud Shell 并登录。 本示例使用 PowerShell。

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

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

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

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

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

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

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

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

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

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

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

下面是 PowerShell 脚本。 请复制此 PowerShell 脚本并将其粘贴到 Cloud Shell 窗口中,然后按 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

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

创建模拟设备

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

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

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

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

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

    “添加设备”屏幕

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

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

在门户中查看消息路由

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

IoT 中心属性平面

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

已设置的路由

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

为路由设置的终结点

后续步骤

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