计划和广播作业 (Java)

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

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

作业包装上述一项操作,并跟踪一组设备中的执行情况。 设备孪生查询定义作业执行的一组设备。 例如,后端应用可以使用作业在 10,000 台设备上调用直接方法来重启设备。 使用设备孪生查询指定设备集,并将作业计划为在以后运行。 每个设备接收和执行 reboot 直接方法时,该作业会跟踪进度。

若要详细了解其中的每项功能,请参阅:

Note

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

本教程演示如何:

  • 创建设备应用,用于实现名为 lockDoor 的直接方法。 该设备应用还从后端应用接收所需的属性更改。
  • 创建一个后端应用,该应用创建一个作业以在多台设备上调用 lockDoor 直接方法。 另一个作业将所需的属性更新发送到多台设备。

本教程结束时,将有一个 java 控制台设备应用,以及一个 java 控制台后端应用:

simulated-device:连接到 IoT 中心、实现 lockDoor 直接方法,并处理所需的属性更改。

schedule-jobs:使用作业来调用 lockDoor 直接方法,并在多个设备上更新设备孪生的必需属性。

Note

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

先决条件

要完成本教程,需要:

创建 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 中心开发人员指南”中的访问控制

    共享访问策略

创建设备标识

在本部分中,将使用 Azure 门户在 IoT 中心的标识注册表中创建设备标识。 设备无法连接到 IoT 中心,除非它在标识注册表中具有条目。 有关详细信息,请参阅 IoT 中心开发人员指南的“标识注册表”部分。 使用门户中的“IoT 设备”面板为设备生成唯一设备 ID 和密钥,以用于在 IoT 中心中标识它本身。 设备 ID 区分大小写。

  1. 登录到 Azure 门户

  2. 选择“所有资源”,并查找 IoT 中心资源。

  3. 打开 IoT 中心资源后,单击“IoT 设备”工具,并单击顶部的“添加”。

    在门户中创建设备标识

  4. 提供新设备的名称(例如 myDeviceId),然后单击“保存”。 此操作会为 IoT 中心创建新设备标识。

    Important

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

    添加新设备

  5. 在设备列表中,单击新创建的设备,复制“连接字符串---主键”供以后使用。

    设备连接字符串

Note

IoT 中心标识注册表仅存储用于实现 IoT 中心安全访问的设备标识。 它存储设备 ID 和密钥作为安全凭据,以及启用/禁用标志让你禁用对单个设备的访问。 如果应用程序需要存储其他特定于设备的元数据,则应使用特定于应用程序的存储。 有关详细信息,请参阅 IoT 中心开发人员指南

还可使用适用于 Azure CLI 2.0 的 IoT 扩展工具向 IoT 中心添加设备。

创建服务应用

本部分中将创建一个使用作业进行如下操作的 Java 控制台应用:

  • 在多台设备上调用 lockDoor 直接方法。
  • 向多台设备发送必需属性。

创建应用:

  1. 在开发计算机上,创建名为 iot-java-schedule-jobs 的空文件夹。

  2. iot-java-schedule-jobs 文件夹中,通过命令提示符使用以下命令创建名为 schedule-jobs 的 Maven 项目。 请注意,这是一条很长的命令:

    mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=schedule-jobs -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

  3. 在命令提示符下,导航到 schedule-jobs 文件夹。

  4. 使用文本编辑器打开 schedule-jobs 文件夹中的 pom.xml 文件,在 dependencies 节点中添加以下依赖项。 通过此依赖项可以使用应用中的 iot-service-client 包来与 IoT 中心通信:

    <dependency>
      <groupId>com.microsoft.azure.sdk.iot</groupId>
      <artifactId>iot-service-client</artifactId>
      <version>1.7.23</version>
      <type>jar</type>
    </dependency>
    

    Note

    可以使用 Maven 搜索检查是否有最新版本的 iot-service-client

  5. dependencies 节点后添加以下 build 节点。 此配置指示 Maven 使用 Java 1.8 来生成应用:

    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
            <source>1.8</source>
            <target>1.8</target>
          </configuration>
        </plugin>
      </plugins>
    </build>
    
  6. 保存并关闭 pom.xml 文件。

  7. 使用文本编辑器打开 schedule-jobs\src\main\java\com\mycompany\app\App.java 文件。

  8. 在该文件中添加以下 import 语句:

    import com.microsoft.azure.sdk.iot.service.devicetwin.DeviceTwinDevice;
    import com.microsoft.azure.sdk.iot.service.devicetwin.Pair;
    import com.microsoft.azure.sdk.iot.service.devicetwin.Query;
    import com.microsoft.azure.sdk.iot.service.devicetwin.SqlQuery;
    import com.microsoft.azure.sdk.iot.service.jobs.JobClient;
    import com.microsoft.azure.sdk.iot.service.jobs.JobResult;
    import com.microsoft.azure.sdk.iot.service.jobs.JobStatus;
    
    import java.util.Date;
    import java.time.Instant;
    import java.util.HashSet;
    import java.util.Set;
    import java.util.UUID;
    
  9. 将以下类级变量添加到 App 类。 将 {youriothubconnectionstring} 替换为在“创建 IoT 中心”部分记下的 IoT 中心连接字符串:

    public static final String iotHubConnectionString = "{youriothubconnectionstring}";
    public static final String deviceId = "myDeviceId";
    
    // How long the job is permitted to run without
    // completing its work on the set of devices
    private static final long maxExecutionTimeInSeconds = 30;
    
  10. 向 App 类添加以下方法,以安排作业更新设备孪生中的 Building 和 Floor 必需属性:

    private static JobResult scheduleJobSetDesiredProperties(JobClient jobClient, String jobId) {
      DeviceTwinDevice twin = new DeviceTwinDevice(deviceId);
      Set<Pair> desiredProperties = new HashSet<Pair>();
      desiredProperties.add(new Pair("Building", 43));
      desiredProperties.add(new Pair("Floor", 3));
      twin.setDesiredProperties(desiredProperties);
      // Optimistic concurrency control
      twin.setETag("*");
    
      // Schedule the update twin job to run now
      // against a single device
      System.out.println("Schedule job " + jobId + " for device " + deviceId);
      try {
        JobResult jobResult = jobClient.scheduleUpdateTwin(jobId, 
          "deviceId='" + deviceId + "'",
          twin,
          new Date(),
          maxExecutionTimeInSeconds);
        return jobResult;
      } catch (Exception e) {
        System.out.println("Exception scheduling desired properties job: " + jobId);
        System.out.println(e.getMessage());
        return null;
      }
    }
    
  11. 若要安排作业调用 lockDoor 方法,请向 App 类添加以下方法:

    private static JobResult scheduleJobCallDirectMethod(JobClient jobClient, String jobId) {
      // Schedule a job now to call the lockDoor direct method
      // against a single device. Response and connection
      // timeouts are set to 5 seconds.
      System.out.println("Schedule job " + jobId + " for device " + deviceId);
      try {
        JobResult jobResult = jobClient.scheduleDeviceMethod(jobId,
          "deviceId='" + deviceId + "'",
          "lockDoor",
          5L, 5L, null,
          new Date(),
          maxExecutionTimeInSeconds);
        return jobResult;
      } catch (Exception e) {
        System.out.println("Exception scheduling direct method job: " + jobId);
        System.out.println(e.getMessage());
        return null;
      }
    };
    
  12. 若要监视作业,请向 App 类添加以下方法:

    private static void monitorJob(JobClient jobClient, String jobId) {
      try {
        JobResult jobResult = jobClient.getJob(jobId);
        if(jobResult == null)
        {
          System.out.println("No JobResult for: " + jobId);
          return;
        }
        // Check the job result until it's completed
        while(jobResult.getJobStatus() != JobStatus.completed)
        {
          Thread.sleep(100);
          jobResult = jobClient.getJob(jobId);
          System.out.println("Status " + jobResult.getJobStatus() + " for job " + jobId);
        }
        System.out.println("Final status " + jobResult.getJobStatus() + " for job " + jobId);
      } catch (Exception e) {
        System.out.println("Exception monitoring job: " + jobId);
        System.out.println(e.getMessage());
        return;
      }
    }
    
  13. 若要查询已运行作业的详细信息,请添加以下方法:

    private static void queryDeviceJobs(JobClient jobClient, String start) throws Exception {
      System.out.println("\nQuery device jobs since " + start);
    
      // Create a jobs query using the time the jobs started
      Query deviceJobQuery = jobClient
          .queryDeviceJob(SqlQuery.createSqlQuery("*", SqlQuery.FromType.JOBS, "devices.jobs.startTimeUtc > '" + start + "'", null).getQuery());
    
      // Iterate over the list of jobs and print the details
      while (jobClient.hasNextJob(deviceJobQuery)) {
        System.out.println(jobClient.getNextJob(deviceJobQuery));
      }
    }
    
  14. 更新 main 方法签名,以包含以下 throws 子句:

    public static void main( String[] args ) throws Exception
    
  15. 若要依次运行和监视两个作业,请向 main 方法添加以下代码:

    // Record the start time
    String start = Instant.now().toString();
    
    // Create JobClient
    JobClient jobClient = JobClient.createFromConnectionString(iotHubConnectionString);
    System.out.println("JobClient created with success");
    
    // Schedule twin job desired properties
    // Maximum concurrent jobs is 1 for Free and S1 tiers
    String desiredPropertiesJobId = "DPCMD" + UUID.randomUUID();
    scheduleJobSetDesiredProperties(jobClient, desiredPropertiesJobId);
    monitorJob(jobClient, desiredPropertiesJobId);
    
    // Schedule twin job direct method
    String directMethodJobId = "DMCMD" + UUID.randomUUID();
    scheduleJobCallDirectMethod(jobClient, directMethodJobId);
    monitorJob(jobClient, directMethodJobId);
    
    // Run a query to show the job detail
    queryDeviceJobs(jobClient, start);
    
    System.out.println("Shutting down schedule-jobs app");
    
  16. 保存并关闭 schedule-jobs\src\main\java\com\mycompany\app\App.java 文件

  17. 生成 schedule-jobs 应用并更正任何错误。 在命令提示符下,导航到 schedule-jobs 文件夹并运行以下命令:

    mvn clean package -DskipTests

创建设备应用

本部分中将创建一个 Java 控制台应用,处理从 IoT 中心发送的必需属性并实现直接方法调用。

  1. 在命令提示符下使用以下命令,在 iot-java-schedule-jobs 文件夹中创建名为 simulated-device 的 Maven 项目。 请注意,这是一条很长的命令:

    mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=simulated-device -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

  2. 在命令提示符下,导航到 simulated-device 文件夹。

  3. 使用文本编辑器打开 simulated-device 文件夹中的 pom.xml 文件,在 dependencies 节点中添加以下依赖项。 通过此依赖项可以使用应用中的 iot-device-client 包来与 IoT 中心进行通信:

    <dependency>
      <groupId>com.microsoft.azure.sdk.iot</groupId>
      <artifactId>iot-device-client</artifactId>
      <version>1.3.32</version>
    </dependency>
    

    Note

    可以使用 Maven 搜索检查是否有最新版本的 iot-device-client

  4. dependencies 节点后添加以下 build 节点。 此配置指示 Maven 使用 Java 1.8 来生成应用:

    <build>
      <plugins>
        <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.3</version>
          <configuration>
            <source>1.8</source>
            <target>1.8</target>
          </configuration>
        </plugin>
      </plugins>
    </build>
    
  5. 保存并关闭 pom.xml 文件。

  6. 使用文本编辑器打开 simulated-device\src\main\java\com\mycompany\app\App.java 文件。

  7. 在该文件中添加以下 import 语句:

    import com.microsoft.azure.sdk.iot.device.*;
    import com.microsoft.azure.sdk.iot.device.DeviceTwin.*;
    
    import java.io.IOException;
    import java.net.URISyntaxException;
    import java.util.Scanner;
    
  8. 将以下类级变量添加到 App 类。 将 {youriothubname} 替换为 IoT 中心名称,将 {yourdevicekey} 替换为在“创建设备标识”部分中生成的设备密钥值:

    private static String connString = "HostName={youriothubname}.azure-devices.cn;DeviceId=myDeviceID;SharedAccessKey={yourdevicekey}";
    private static IotHubClientProtocol protocol = IotHubClientProtocol.MQTT;
    private static final int METHOD_SUCCESS = 200;
    private static final int METHOD_NOT_DEFINED = 404;
    

    本示例应用在实例化 DeviceClient 对象时使用 protocol 变量。

  9. 若要在控制台中列显设备孪生通知,请向 App 类添加以下嵌套类:

    // Handler for device twin operation notifications from IoT Hub
    protected static class DeviceTwinStatusCallBack implements IotHubEventCallback {
      public void execute(IotHubStatusCode status, Object context) {
        System.out.println("IoT Hub responded to device twin operation with status " + status.name());
      }
    }
    
  10. 若要在控制台中列显直接方法通知,请向 App 类添加以下嵌套类:

    // Handler for direct method notifications from IoT Hub
    protected static class DirectMethodStatusCallback implements IotHubEventCallback {
      public void execute(IotHubStatusCode status, Object context) {
        System.out.println("IoT Hub responded to direct method operation with status " + status.name());
      }
    }
    
  11. 若要处理 IoT 中心的直接方法调用,请向 App 类添加以下嵌套类:

    // Handler for direct method calls from IoT Hub
    protected static class DirectMethodCallback
        implements DeviceMethodCallback {
      @Override
      public DeviceMethodData call(String methodName, Object methodData, Object context) {
        DeviceMethodData deviceMethodData;
        switch (methodName) {
          case "lockDoor": {
            System.out.println("Executing direct method: " + methodName);
            deviceMethodData = new DeviceMethodData(METHOD_SUCCESS, "Executed direct method " + methodName);
            break;
          }
          default: {
            deviceMethodData = new DeviceMethodData(METHOD_NOT_DEFINED, "Not defined direct method " + methodName);
          }
        }
        // Notify IoT Hub of result
        return deviceMethodData;
      }
    }
    
  12. 更新 main 方法签名,以包含以下 throws 子句:

    public static void main( String[] args ) throws IOException, URISyntaxException
    
  13. 将以下代码添加到 main 方法,以便:

    • 创建用来与 IoT 中心通信的设备客户端。
    • 创建一个 Device 对象用于存储设备孪生属性。

      // Create a device client
      DeviceClient client = new DeviceClient(connString, protocol);
      
      // An object to manage device twin desired and reported properties
      Device dataCollector = new Device() {
      @Override
      public void PropertyCall(String propertyKey, Object propertyValue, Object context)
      {
        System.out.println("Received desired property change: " + propertyKey + " " + propertyValue);
      }
      };
      
  14. 若要启动设备客户端服务,请向 main 方法添加以下代码:

    try {
      // Open the DeviceClient
      // Start the device twin services
      // Subscribe to direct method calls
      client.open();
      client.startDeviceTwin(new DeviceTwinStatusCallBack(), null, dataCollector, null);
      client.subscribeToDeviceMethod(new DirectMethodCallback(), null, new DirectMethodStatusCallback(), null);
    } catch (Exception e) {
      System.out.println("Exception, shutting down \n" + " Cause: " + e.getCause() + " \n" + e.getMessage());
      dataCollector.clean();
      client.closeNow();
      System.out.println("Shutting down...");
    }
    
  15. 若要在关闭前等待用户按 Enter 键,请向 main 方法末尾添加以下代码:

    // Close the app
    System.out.println("Press any key to exit...");
    Scanner scanner = new Scanner(System.in);
    scanner.nextLine();
    dataCollector.clean();
    client.closeNow();
    scanner.close();
    
  16. 保存并关闭 simulated-device\src\main\java\com\mycompany\app\App.java 文件。

  17. 生成 simulated-device 应用并更正任何错误。 在命令提示符下,导航到 simulated-device 文件夹并运行以下命令:

    mvn clean package -DskipTests

运行应用

现在可以运行控制台应用了。

  1. simulated-device 文件夹中的命令提示符处,运行以下命令启动设备应用用于侦听所需属性更改和直接方法调用:

    mvn exec:java -Dexec.mainClass="com.mycompany.app.App"

    设备客户端启动

  2. schedule-jobs 文件夹中的命令提示符处,运行以下命令以运行 schedule-jobs 服务应用,从而运行两个作业。 第一个作业设置所需的属性值,第二个作业调用直接方法:

    mvn exec:java -Dexec.mainClass="com.mycompany.app.App"

    Java IoT 中心服务应用创建两个作业

  3. 设备应用处理所需的属性更改和直接方法调用:

    设备客户端对更改作出响应

后续步骤

本教程中,在 Azure 门户中配置了新的 IoT 中心,并在 IoT 中心的标识注册表中创建了设备标识。 创建了运行两个作业的后端应用。 第一个作业设置了所需的属性值,第二个作业调用了直接方法。

充分利用以下资源:

  • 通过 IoT 中心入门教程学习如何从设备发送遥测数据。
  • 通过使用直接方法教程学习如何以交互方式控制设备(例如从用户控制的应用打开风扇)。