计划和广播作业 (Node.js)

使用 Azure IoT 中心来计划和跟踪可更新数百万台设备的作业。 使用作业可以:

  • 更新所需属性
  • 更新标记
  • 调用直接方法

从概念上讲,作业包装这些操作之一,并跟踪针对一组设备执行(由设备孪生查询定义)的进度。 例如,后端应用可使用作业重启 10,000 台设备(由设备孪生查询指定并计划在将来执行)。 该应用程序随后可以在其中每个设备接收和执行重新启动方法时跟踪进度。

可在以下文章中了解有关所有这些功能的详细信息:

注意

本文所述的功能只能用于 IoT 中心的标准层。 有关 IoT 中心基本层和标准/免费层的详细信息,请参阅选择适合你的解决方案的 IoT 中心层

本文介绍如何创建两个 Node.js 应用:

  • 一个 Node.js 模拟设备应用 simDevice.js,它实现了一个称为 lockDoor 的直接方法,可供后端应用调用。

  • 一个 Node.js 控制台应用 scheduleJobService.js,它创建了两个作业。 一个作业调用 lockDoor 直接方法,另一个作业将所需的属性更新发送到多个设备。

注意

有关可用于生成设备和后端应用的 SDK 工具的详细信息,请参阅 Azure IoT SDK

先决条件

  • Azure 订阅中的 IoT 中心。 如果还没有中心,则可以按照创建 IoT 中心中的步骤进行操作。

  • 在 IoT 中心注册的设备。 如果 IoT 中心没有设备,请按照注册设备中的步骤操作。

  • Node.js 版本 10.0.x 或更高版本。 准备开发环境介绍了如何在 Windows 或 Linux 上安装本文所用的 Node.js。

  • 确保已在防火墙中打开端口 8883。 本文中的设备示例使用 MQTT 协议,该协议通过端口 8883 进行通信。 在某些公司和教育网络环境中,此端口可能被阻止。 有关解决此问题的更多信息和方法,请参阅连接到 IoT 中心(MQTT)

创建模拟设备应用程序

本部分将创建一个 Node.js 控制台应用,用于响应通过云调用的方法,这会触发模拟 lockDoor 方法。

重要

本文包括使用共享访问签名(也称为对称密钥身份验证)连接设备的步骤。 此身份验证方法便于测试和评估,但使用 X.509 证书对设备进行身份验证是一种更安全的方法。

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

    npm init
    
  2. simDevice 文件夹的命令提示符处,运行下述命令以安装 azure-iot-device 设备 SDK 包和 azure-iot-device-mqtt 包:

    npm install azure-iot-device azure-iot-device-mqtt --save
    
  3. simDevice.js 文件夹中,利用文本编辑器创建新的 simDevice 文件。

  4. simDevice.js 文件的开头添加以下“require”语句:

    'use strict';
    
    var Client = require('azure-iot-device').Client;
    var Protocol = require('azure-iot-device-mqtt').Mqtt;
    
  5. 添加 connectionString 变量,并使用它创建一个客户端实例。 将 {yourDeviceConnectionString} 占位符值替换为之前复制的设备连接字符串。

    var connectionString = '{yourDeviceConnectionString}';
    var client = Client.fromConnectionString(connectionString, Protocol);
    
  6. 添加以下函数以处理 lockDoor 方法。

    var onLockDoor = function(request, response) {
    
        // Respond the cloud app for the direct method
        response.send(200, function(err) {
            if (err) {
                console.error('An error occurred when sending a method response:\n' + err.toString());
            } else {
                console.log('Response to method \'' + request.methodName + '\' sent successfully.');
            }
        });
    
        console.log('Locking Door!');
    };
    
  7. 添加以下代码以注册 lockDoor 方法的处理程序。

    client.open(function(err) {
         if (err) {
             console.error('Could not connect to IotHub client.');
         }  else {
             console.log('Client connected to IoT Hub. Register handler for lockDoor direct method.');
             client.onDeviceMethod('lockDoor', onLockDoor);
         }
    });
    
  8. 保存并关闭 simDevice.js 文件。

注意

为简单起见,本文不实现重试策略。 在生产代码中,应该按文章 Transient Fault Handling(暂时性故障处理)中所述实施重试策略(例如指数性的回退)。

获取 IoT 中心连接字符串

在本文中,你将创建一项后端服务,该服务计划用于在设备上调用直接方法的作业及计划用于更新设备孪生的作业,并监视每个作业的进度。 若要执行这些操作,服务需要“注册表读取”和“注册表写入”权限。 默认情况下,每个 IoT 中心都使用名为 registryReadWrite的共享访问策略创建,该策略会授予这些权限。

若要获取 registryReadWrite策略的 IoT 中心连接字符串,请执行以下步骤:

  1. Azure 门户中,选择“资源组”。 选择中心所在的资源组,然后从资源列表中选择中心。

  2. 在中心的左侧窗格上,选择“共享访问策略”。

  3. 在策略列表中,选择“registryReadWrite”策略。

  4. 复制“主连接字符串”并保存该值。

    显示如何检索连接字符串的屏幕截图

有关 IoT 中心共享访问策略和权限的详细信息,请参阅访问控制和权限

重要

本文介绍使用共享访问签名连接到服务的步骤。 虽然可使用此身份验证方法进行测试和评估,但使用 Microsoft Entra ID 或托管标识对设备进行身份验证是一种更安全的方法。

安排作业,用于调用直接方法和更新设备孪生的属性

在此部分中,会创建一个 Node.js 控制台应用,它使用直接方法对设备启动远程 lockDoor 并更新设备孪生的属性。

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

    npm init
    
  2. 在 scheduleJobService 文件夹的命令提示符处,运行以下命令安装 azure-iothub 设备 SDK 包和 azure-iot-device-mqtt 包:

    npm install azure-iothub uuid --save
    
  3. scheduleJobService 文件夹中,利用文本编辑器创建新的 scheduleJobService.js 文件。

  4. scheduleJobService.js 文件的开头添加以下“require”语句:

    'use strict';
    
    var uuid = require('uuid');
    var JobClient = require('azure-iothub').JobClient;
    
  5. 添加以下变量声明。 将 {iothubconnectionstring} 占位符值替换为在获取 IoT 中心连接字符串中复制的值。 如果你注册的设备不是 myDeviceId,请确保在查询条件中对其进行更改。

    var connectionString = '{iothubconnectionstring}';
    var queryCondition = "deviceId IN ['myDeviceId']";
    var startTime = new Date();
    var maxExecutionTimeInSeconds =  300;
    var jobClient = JobClient.fromConnectionString(connectionString);
    
  6. 添加以下用于监视作业执行的函数:

    function monitorJob (jobId, callback) {
        var jobMonitorInterval = setInterval(function() {
            jobClient.getJob(jobId, function(err, result) {
            if (err) {
                console.error('Could not get job status: ' + err.message);
            } else {
                console.log('Job: ' + jobId + ' - status: ' + result.status);
                if (result.status === 'completed' || result.status === 'failed' || result.status === 'cancelled') {
                clearInterval(jobMonitorInterval);
                callback(null, result);
                }
            }
            });
        }, 5000);
    }
    
  7. 添加以下代码以安排调用设备方法的作业:

    var methodParams = {
        methodName: 'lockDoor',
        payload: null,
        responseTimeoutInSeconds: 15 // Timeout after 15 seconds if device is unable to process method
    };
    
    var methodJobId = uuid.v4();
    console.log('scheduling Device Method job with id: ' + methodJobId);
    jobClient.scheduleDeviceMethod(methodJobId,
                                queryCondition,
                                methodParams,
                                startTime,
                                maxExecutionTimeInSeconds,
                                function(err) {
        if (err) {
            console.error('Could not schedule device method job: ' + err.message);
        } else {
            monitorJob(methodJobId, function(err, result) {
                if (err) {
                    console.error('Could not monitor device method job: ' + err.message);
                } else {
                    console.log(JSON.stringify(result, null, 2));
                }
            });
        }
    });
    
  8. 添加以下代码以安排更新设备孪生的作业:

    var twinPatch = {
       etag: '*',
       properties: {
           desired: {
               building: '43',
               floor: 3
           }
       }
    };
    
    var twinJobId = uuid.v4();
    
    console.log('scheduling Twin Update job with id: ' + twinJobId);
    jobClient.scheduleTwinUpdate(twinJobId,
                                queryCondition,
                                twinPatch,
                                startTime,
                                maxExecutionTimeInSeconds,
                                function(err) {
        if (err) {
            console.error('Could not schedule twin update job: ' + err.message);
        } else {
            monitorJob(twinJobId, function(err, result) {
                if (err) {
                    console.error('Could not monitor twin update job: ' + err.message);
                } else {
                    console.log(JSON.stringify(result, null, 2));
                }
            });
        }
    });
    
  9. 保存并关闭 scheduleJobService.js 文件。

运行应用程序

现在,已准备就绪,可以运行应用程序了。

  1. 在 simDevice 文件夹的命令提示符处,运行以下命令以开始侦听重启直接方法。

    node simDevice.js
    
  2. scheduleJobService 文件夹的命令提示符处运行以下命令,以便触发作业进行锁门和孪生项的更新

    node scheduleJobService.js
    
  3. 可以在控制台中看到设备对直接方法的响应和作业状态。

    下面显示了设备对直接方法的响应:

    模拟设备应用输出

    下面显示了直接方法和设备孪生更新的服务计划作业,以及运行至完成的作业:

    运行模拟设备应用

后续步骤

在本文中,你安排了作业以运行直接方法并更新设备孪生的属性。