使用所需属性配置设备 (Node)

简介

IoT 中心设备孪生入门介绍了如何使用标记来设置设备元数据, 并说明了如何使用报告的属性通过设备应用接收设备条件,然后使用类似 SQL 的语言查询此信息。

本教程介绍如何使用设备孪生的所需属性和报告的属性来远程配置设备应用。 可以使用设备孪生中报告的属性和所需属性通过多个步骤来配置设备应用程序,并可在所有设备中查看此操作的状态。 有关在配置设备时所需角色的详细信息,请参阅使用 IoT 中心进行设备管理概述

Note

本文中所述的功能仅可在 IoT 中心的标准层中使用。 有关基本和标准 IoT 中心层的详细信息,请参阅如何选择合适的 IoT 中心层

简而言之,使用设备孪生时,解决方案后端可以为托管的设备指定所需配置,而不需要发送特定的命令。 设备负责设置更新其配置状态的最佳方式(对于特定设备条件影响即时执行特定命令功能的 IoT 方案而言十分重要),同时持续报告更新过程的当前状态和潜在的错误条件。 此模式可用来管理大量设备,因为它让解决方案后端能够跨所有设备完全掌握配置流程状态。

Tip

在以更具交互性的方式控制设备的方案中(例如,通过用户控制的应用打开风扇),请考虑使用直接方法

在本教程中,解决方案后端更改目标设备的遥测配置,这样一来,设备应用就可以应用配置更新。 例如,某项配置更新可能要求软件模块重启,这在本教程中以简单的延迟进行模拟。

解决方案后端采用以下方式将配置存储在设备孪生的所需属性中:

    {
        ...
        "properties": {
            ...
            "desired": {
                "telemetryConfig": {
                    "configId": "{id of the configuration}",
                    "sendFrequency": "{config}"
                }
            }
            ...
        }
        ...
    }

由于配置可能是复杂对象,因此会为它们分配唯一 ID(哈希值或 GUID)。

设备应用以镜像报告属性中的所需属性 telemetryConfig 的形式报告其当前配置:

    {
        "properties": {
            ...
            "reported": {
                "telemetryConfig": {
                    "configId": "{id of the current configuration}",
                    "sendFrequency": "{current configuration}",
                    "status": "Success",
                }
            }
            ...
        }
    }

请注意,报告的 telemetryConfig 具有 status 这个其他属性,该属性用于报告配置更新过程的状态。

收到新的所需配置时,设备应用通过更改以下状态报告挂起的配置:

    {
        "properties": {
            ...
            "reported": {
                "telemetryConfig": {
                    "configId": "{id of the current configuration}",
                    "sendFrequency": "{current configuration}",
                    "status": "Pending",
                    "pendingConfig": {
                        "configId": "{id of the pending configuration}",
                        "sendFrequency": "{pending configuration}"
                    }
                }
            }
            ...
        }
    }

然后,在稍后的某个时间,设备应用会通过更新属性报告此操作是成功还是失败。 解决方案后端可以跨所有设备随时查询配置流程的状态。

本教程演示如何:

  • 创建一个模拟设备应用,用于接收来自解决方案后端的配置更新,以及将多个更新作为配置更新过程的报告属性进行报告。
  • 创建一个后端应用,用于更新设备的所需配置,并查询配置更新流程。

在本教程结束时,将拥有两个 Node.js 控制台应用:

  • SimulateDeviceConfiguration.js,一个模拟设备应用,它等待所需配置更新并报告模拟配置更新过程的状态。
  • SetDesiredConfigurationAndQuery.js(Node.js 后端应用),用于在设备上设置所需配置并查询配置更新过程。

Note

Azure IoT SDK 文章介绍了可用于构建设备和后端应用的 Azure IoT SDK。

若要完成本教程,需要满足以下条件:

  • Node.js 版本 4.0.x 或更高版本。

  • 有效的 Azure 帐户。 如果没有帐户,可以创建一个试用帐户,只需几分钟即可完成。

如果已按照设备孪生入门教程执行了操作,则已经有一个 IoT 中心和一个名为 myDeviceId 的设备标识;可以跳到创建模拟设备应用部分。

创建 IoT 中心

创建模拟设备应用要连接到的 IoT 中心。 以下步骤说明如何使用 Azure 门户来完成此任务。

  1. 登录到 Azure 门户

  2. 选择“创建资源” > “物联网” > “IoT 中心”。

    Azure 门户跳转栏

  3. 在“IoT 中心”窗格中,输入 IoT 中心的以下信息:

    • 订阅:选择需要将其用于创建此 IoT 中心的订阅。

    • 资源组:创建用于托管 IoT 中心的资源组,或使用现有的资源组。 有关详细信息,请参阅使用资源组管理 Azure 资源

    • 区域:选择最近的位置。

    • 名称:创建 IoT 中心的名称。 如果输入的名称可用,会显示一个绿色复选标记。

    Important

    IoT 中心将公开为 DNS 终结点,因此,命名时请务必避免包含任何敏感信息。

    IoT 中心基本信息窗口

  4. 选择“下一步: 大小和规模”,以便继续创建 IoT 中心。

  5. 选择“定价和缩放层”。 就本文来说,请选择“F1 - 免费”层(前提是此层在订阅上仍然可用)。 有关详细信息,请参阅定价和缩放层

    IoT 中心大小和规模窗口

  6. 选择“查看 + 创建”。

  7. 查看 IoT 中心信息,然后单击“创建”。 创建 IoT 中心可能需要数分钟的时间。 可在“通知”窗格中监视进度。

  8. 新的 IoT 中心就绪以后,请在 Azure 门户中单击其磁贴,打开其属性窗口。 创建 IoT 中心以后,即可找到将设备和应用程序连接到 IoT 中心时需要使用的重要信息。 单击“共享访问策略”。

  9. 在“共享访问策略”中,选择 iothubowner 策略。 复制 IoT 中心连接字符串 ---主密钥供以后使用。 有关详细信息,请参阅“IoT 中心开发人员指南”中的访问控制

    共享访问策略

创建设备标识

本部分使用名为 iothub-explorer 的 Node.js 工具为本教程创建设备标识。 设备 ID 区分大小写。

  1. 在命令行环境中运行以下命令:

    npm install -g iothub-explorer@latest

  2. 然后,运行以下命令登录到中心。 将 {iot hub connection string} 替换为前面复制的 IoT 中心连接字符串:

    iothub-explorer login "{iot hub connection string}"

  3. 最后,以下使用命令创建名为 myDeviceId 的新设备标识:

    iothub-explorer create myDeviceId --connection-string

    Important

    收集的日志中可能会显示设备 ID 用于客户支持和故障排除,因此,在为日志命名时,请务必避免包含任何敏感信息。

记下结果中的设备连接字符串。 设备应用使用此设备连接字符串以设备身份连接到 IoT 中心。

若要以编程方式创建设备标识,请参阅 IoT 中心入门

创建模拟设备应用

在此部分,用户需创建一个 Node.js 控制台应用,该应用可作为 myDeviceId连接到中心并等待所需配置更新,并针对模拟配置更新过程报告更新。

  1. 新建名为 simulatedeviceconfiguration的空文件夹。 在命令提示符下的 simulatedeviceconfiguration 文件夹中,使用以下命令创建新的 package.json 文件。 接受所有默认值:

    npm init
    
  2. simulatedeviceconfiguration 文件夹的命令提示符下,运行以下命令安装 azure-iot-deviceazure-iot-device-mqtt 包:

    npm install azure-iot-device azure-iot-device-mqtt --save
    
  3. 使用文本编辑器,在 simulatedeviceconfiguration 文件夹中创建新的 SimulateDeviceConfiguration.js 文件。
  4. 将以下代码添加到 SimulateDeviceConfiguration.js 文件,并将 {device connection string} 占位符替换为创建 myDeviceId 设备标识时复制的设备连接字符串:

    'use strict';
    var Client = require('azure-iot-device').Client;
    var Protocol = require('azure-iot-device-mqtt').Mqtt;
    
    var connectionString = '{device connection string}';
    var client = Client.fromConnectionString(connectionString, Protocol);
    
    client.open(function(err) {
        if (err) {
            console.error('could not open IotHub client');
        } else {
            client.getTwin(function(err, twin) {
                if (err) {
                    console.error('could not get twin');
                } else {
                    console.log('retrieved device twin');
                    twin.properties.reported.telemetryConfig = {
                        configId: "0",
                        sendFrequency: "24h"
                    }
                    twin.on('properties.desired', function(desiredChange) {
                        console.log("received change: "+JSON.stringify(desiredChange));
                        var currentTelemetryConfig = twin.properties.reported.telemetryConfig;
                        if (desiredChange.telemetryConfig &&desiredChange.telemetryConfig.configId !== currentTelemetryConfig.configId) {
                            initConfigChange(twin);
                        }
                    });
                }
            });
        }
    });
    

    客户端对象公开从设备与设备孪生进行交互所需的所有方法。 上面的代码在初始化 Client 对象后会检索 myDeviceId 的设备孪生,并在所需属性上附加用于更新的处理程序。 该处理程序通过比较 configId 来验证是否存在实际配置更改请求,并调用启动配置更改的方法。

    请注意,为简单起见,上一代码对初始配置使用硬编码默认值。 实际的应用可能会从本地存储加载该配置。

    Important

    所需属性更改事件始终在设备连接时发出一次,请确保在执行任何操作之前检查所需属性中是否存在实际更改。

  5. client.open() 调用前添加以下方法:

    var initConfigChange = function(twin) {
        var currentTelemetryConfig = twin.properties.reported.telemetryConfig;
        currentTelemetryConfig.pendingConfig = twin.properties.desired.telemetryConfig;
        currentTelemetryConfig.status = "Pending";
    
        var patch = {
        telemetryConfig: currentTelemetryConfig
        };
        twin.properties.reported.update(patch, function(err) {
            if (err) {
                console.log('Could not report properties');
            } else {
                console.log('Reported pending config change: ' + JSON.stringify(patch));
                setTimeout(function() {completeConfigChange(twin);}, 60000);
            }
        });
    }
    
    var completeConfigChange =  function(twin) {
        var currentTelemetryConfig = twin.properties.reported.telemetryConfig;
        currentTelemetryConfig.configId = currentTelemetryConfig.pendingConfig.configId;
        currentTelemetryConfig.sendFrequency = currentTelemetryConfig.pendingConfig.sendFrequency;
        currentTelemetryConfig.status = "Success";
        delete currentTelemetryConfig.pendingConfig;
    
        var patch = {
            telemetryConfig: currentTelemetryConfig
        };
        patch.telemetryConfig.pendingConfig = null;
    
        twin.properties.reported.update(patch, function(err) {
            if (err) {
                console.error('Error reporting properties: ' + err);
            } else {
                console.log('Reported completed config change: ' + JSON.stringify(patch));
            }
        });
    };
    

    initConfigChange 方法使用配置更新请求更新本地设备孪生对象的报告属性,并将状态设置为“Pending”,然后更新服务的设备孪生。 成功更新设备孪生后,它会模拟在执行 completeConfigChange 期间终止的长时间运行的进程。 此方法会更新本地设备孪生的报告属性,将状态设置为 Success 并删除 pendingConfig 对象。 然后,它会更新服务的设备孪生。

    请注意,为了节省带宽,仅通过指定要修改的属性(在上述代码中名为 patch)而不是替换整个文档来更新报告属性。

    Note

    本教程不模拟并发配置更新的任何行为。 某些配置更新过程可能无法在更新运行期间适应目标配置的更改,另外一些过程可能必须对更改进行排队,还有一些过程可能会拒绝更改并显示错误条件。 请务必考虑特定配置过程的所需行为,并在启动配置更改之前添加相应的逻辑。

  6. 运行设备应用:

    node SimulateDeviceConfiguration.js
    

    此时会显示消息 retrieved device twin。 使应用保持运行状态。

创建服务应用

在此部分,会创建一个 Node.js 控制台应用,它会使用新的遥测配置对象更新与 myDeviceId 关联的设备孪生的 所需属性 。 该应用随后会查询存储在 IoT 中心的设备孪生,并显示设备的所需配置与报告配置之间的差异。

  1. 新建名为 setdesiredandqueryapp的空文件夹。 在命令提示符下,使用以下命令在 setdesiredandqueryapp 文件夹中创建新的 package.json 文件。 接受所有默认值:

    npm init
    
  2. setdesiredandqueryapp 文件夹的命令提示符下,运行以下命令以安装 azure-iothub 包:

    npm install azure-iothub node-uuid --save
    
  3. 使用文本编辑器,在 setdesiredandqueryapp 文件夹中新建 SetDesiredAndQuery.js 文件。
  4. 将以下代码添加到 SetDesiredAndQuery.js 文件,并将 {iot hub connection string} 占位符替换为创建中心时复制的 IoT 中心连接字符串:

    'use strict';
    var iothub = require('azure-iothub');
    var uuid = require('node-uuid');
    var connectionString = '{iot hub connection string}';
    var registry = iothub.Registry.fromConnectionString(connectionString);
    
    registry.getTwin('myDeviceId', function(err, twin){
        if (err) {
            console.error(err.constructor.name + ': ' + err.message);
        } else {
            var newConfigId = uuid.v4();
            var newFrequency = process.argv[2] || "5m";
            var patch = {
                properties: {
                    desired: {
                        telemetryConfig: {
                            configId: newConfigId,
                            sendFrequency: newFrequency
                        }
                    }
                }
            }
            twin.update(patch, function(err) {
                if (err) {
                    console.error('Could not update twin: ' + err.constructor.name + ': ' + err.message);
                } else {
                    console.log(twin.deviceId + ' twin updated successfully');
                }
            });
            setInterval(queryTwins, 10000);
        }
    });
    

    Registry 对象公开从服务与设备孪生进行交互所需的所有方法。 前面的代码在初始化 Registry 对象后检索 myDeviceId 的设备孪生,并使用新的遥测配置对象更新其所需属性。 在此之后,它调用 queryTwins 函数事件 10 秒。

    Important

    为进行说明,此应用程序每 10 秒查询 IoT 中心一次。 使用查询跨多个设备生成面向用户的报表,而不检测更改。 如果解决方案需要设备事件的实时通知,请使用孪生通知

  5. 恰好在 registry.getDeviceTwin() 调用前添加以下代码以实现 queryTwins 函数:

    var queryTwins = function() {
        var query = registry.createQuery("SELECT * FROM devices WHERE deviceId = 'myDeviceId'", 100);
        query.nextAsTwin(function(err, results) {
            if (err) {
                console.error('Failed to fetch the results: ' + err.message);
            } else {
                console.log();
                results.forEach(function(twin) {
                    var desiredConfig = twin.properties.desired.telemetryConfig;
                    var reportedConfig = twin.properties.reported.telemetryConfig;
                    console.log("Config report for: " + twin.deviceId);
                    console.log("Desired: ");
                    console.log(JSON.stringify(desiredConfig, null, 2));
                    console.log("Reported: ");
                    console.log(JSON.stringify(reportedConfig, null, 2));
                });
            }
        });
    };
    

    上述代码查询 IoT 中心内存储的设备孪生,并打印所需的遥测配置和报告遥测配置。 请参阅 IoT 中心查询语言 以了解如何跨所有设备生成丰富的报告。

  6. SimulateDeviceConfiguration.js 运行期间,使用以下内容运行应用程序:

    node SetDesiredAndQuery.js 5m
    

    应该看到报告的配置从 Success 更改为 Pending,再更改回 Success,此时新的活动发送频率已变为五分钟而不是 24 小时。

    Important

    设备报告操作与查询结果之间最多存在一分钟的延迟。 这是为了使查询基础结构可以采用非常大的规模来工作。 若要检索单个设备孪生的一致视图,请使用 Registry 类中的 getDeviceTwin 方法。

后续步骤

在本教程中,从后端应用将所需配置设置为所需属性,并编写一个模拟设备应用来检测该更改及模拟多步骤更新过程(将其状态作为报告属性报告给设备孪生)。

充分利用以下资源:

  • 通过 Get started with IoT Hub (IoT 中心入门)教程学习如何从设备发送遥测;
  • 关于对大型设备集进行计划或执行操作,请参阅 计划和广播作业 教程。
  • 通过使用直接方法教程学习如何以交互方式控制设备(例如从用户控制的应用打开风扇)。