ジョブのスケジュールとブロードキャスト (Java)

Azure IoT Hub を使用して、数百万のデバイスを更新するジョブのスケジュールと追跡を行います。 ジョブを使用して、次の操作を行います。

  • 必要なプロパティを更新する
  • タグを更新する
  • ダイレクト メソッドを呼び出す

ジョブはこれらのアクションの 1 つをラップし、デバイスのセットに対して実行状況を追跡します。 ジョブが実行されるデバイスのセットはデバイス ツイン クエリで定義されます。 たとえば、バックエンド アプリは、ジョブを使用して 10,000 台のデバイスでダイレクト メソッドを呼び出し、デバイスを再起動できます。 デバイス ツイン クエリでデバイスのセットを指定し、ジョブが将来実行されるようにスケジュールを設定します。 各デバイスが reboot ダイレクト メソッドを受信し、実行するたびに、ジョブは進行状況を追跡します。

これらの各機能の詳細については、次の記事をご覧ください。

Note

この記事で説明されている機能は、Standard レベルの IoT Hub でのみ使用できます。 Basic および Standard または Free レベルの IoT Hub の詳細については、「ソリューションに適した IoT Hub のレベルを選択する」を参照してください。

この記事では、2 つの Java アプリを作成する方法について説明します。

  • バックエンド アプリから呼び出すことができる lockDoor というダイレクト メソッドを実装するデバイス アプリ simulated-device

  • 2 つのジョブを作成するバックエンド アプリ schedule-jobs。 1 つのジョブが lockDoor ダイレクト メソッドを呼び出し、別のジョブが必要なプロパティの更新を複数のデバイスに送信します。

注意

デバイスとバックエンド アプリの両方の構築に使用できる SDK ツールに関する詳細については、「Azure IoT Hub SDK」を参照してください。

前提条件

  • IoT Hub。 CLI または Azure portal を使って作成します。

  • 登録済みのデバイス。 Azure portal に登録してください。

  • Java SE Development Kit 8。 JDK 8 のダウンロードを利用するには、「長期サポート」の「Java 8」を選択します。

  • Maven 3

  • ポート 8883 がファイアウォールで開放されていることを確認してください。 この記事のデバイス サンプルでは、ポート 8883 を介して通信する MQTT プロトコルを使用しています。 このポートは、企業や教育用のネットワーク環境によってはブロックされている場合があります。 この問題の詳細と対処方法については、「IoT Hub への接続 (MQTT)」を参照してください。

注意

わかりやすくするために、この記事では再試行ポリシーは実装しません。 運用環境のコードでは、「一時的な障害の処理」の記事で推奨されているように、再試行ポリシー (指数関数的バックオフなど) を実装することをお勧めします。

IoT ハブ接続文字列を取得する

この記事では、デバイス上で直接メソッドを呼び出すようにジョブをスケジュールし、デバイス ツインを更新するようにジョブをスケジュールし、各ジョブの進行状況を監視するバックエンド サービスを作成します。 これらの操作を実行するには、サービスにレジストリ読み取りおよびレジストリ書き込みのアクセス許可が必要です。 既定では、すべての IoT ハブは、これらのアクセス許可を付与する registryReadWrite という名前の共有アクセス ポリシーがある状態で作成されます。

registryReadWrite ポリシーの IoT Hub 接続文字列を取得するには、次の手順を実行します。

  1. Azure portal で、 [リソース グループ] を選択します。 ハブが配置されているリソース グループを選択し、リソースの一覧からハブを選択します。

  2. ハブの左側のウィンドウで、 [共有アクセス ポリシー] を選択します。

  3. ポリシーの一覧から、 [registryReadWrite] ポリシーを選択します。

  4. [プライマリ接続文字列] をコピーし、値を保存します。

    接続文字列を取得する方法を示すスクリーン キャプチャ

IoT Hub の共有アクセス ポリシーとアクセス許可の詳細については、「アクセス制御とアクセス許可」を参照してください。

デバイス アプリを作成する

このセクションでは、ジョブを使用する Java コンソール アプリを作成して、次の操作を行います。

  • 複数のデバイスで lockDoor ダイレクト メソッドを呼び出す。

  • 必要なプロパティを複数のデバイスに送信する。

アプリを作成するには:

  1. 開発用コンピューターで、iot-java-schedule-jobs という名前の空のフォルダーを作成します。

  2. コマンド プロンプトで次のコマンドを使用して、iot-java-schedule-jobs フォルダー内に schedule-jobs という名前の Maven プロジェクトを作成します。 これは、1 つの長いコマンドであることに注意してください。

    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 Hub と通信できるようになります。

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

    注意

    Maven 検索を使用して、iot-service-client の最新バージョンを確認できます。

  5. dependencies ノードの後に、次の build ノードを追加します。 この構成では、Java 1.8 を使用してアプリをビルドするように Maven に指示しています。

    <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. 2 つのジョブを順番に実行して監視するには、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
    

デバイス アプリの作成

このセクションでは、IoT Hub から送信された必要なプロパティを処理する Java コンソール アプリを作成し、ダイレクト メソッド呼び出しを実装します。

  1. コマンド プロンプトで次のコマンドを実行して、iot-java-schedule-jobs フォルダー内に simulated-device という名前の Maven プロジェクトを作成します。 これは、1 つの長いコマンドであることに注意してください。

    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 Hub と通信できるようになります。

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

    注意

    Maven 検索を使用して、iot-device-client の最新バージョンを確認できます。

  4. dependencies ノードに、次の依存関係を追加します。 この依存関係によって、Apache SLF4J ログ記録ファサード用の NOP が構成され、ログ記録を実装するためにデバイス クライアント SDK によって使用されます。 この構成は省略可能ですが、省略した場合、アプリの実行時にコンソールに警告が表示される可能性があります。 デバイス クライアント SDK でのログ記録の詳細については、Samples for the Azure IoT device SDK for Java readme ファイルに含まれているログ記録を参照してください。

    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-nop</artifactId>
      <version>1.7.28</version>
    </dependency>
    
  5. dependencies ノードの後に、次の build ノードを追加します。 この構成では、Java 1.8 を使用してアプリをビルドするように Maven に指示しています。

    <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. テキスト エディターを使用して、simulated-device\src\main\java\com\mycompany\app\App.java ファイルを開きます。

  8. ファイルに次の 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;
    
  9. 次のクラスレベル変数を App クラスに追加します。 {yourdeviceconnectionstring} を、IoT Hub にデバイスを登録したときに表示されたデバイス接続文字列に置き換えます。

    private static String connString = "{yourdeviceconnectionstring}";
    private static IotHubClientProtocol protocol = IotHubClientProtocol.MQTT;
    private static final int METHOD_SUCCESS = 200;
    private static final int METHOD_NOT_DEFINED = 404;
    

    このサンプル アプリでは、DeviceClient オブジェクトをインスタンス化するときに protocol 変数が使用されます。

  10. デバイス ツイン通知をコンソールに出力するには、次の入れ子になったクラスを 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());
      }
    }
    
  11. ダイレクト メソッド通知をコンソールに出力するには、次の入れ子になったクラスを 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());
      }
    }
    
  12. IoT Hub からのダイレクト メソッド呼び出しを処理するには、次の入れ子になったクラスを 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;
      }
    }
    
  13. main メソッドのシグネチャを、次の throws 句を含むように更新します。

    public static void main( String[] args ) throws IOException, URISyntaxException
    
  14. main メソッドのコードを次のコードに置き換えます。

    • IoT Hub と通信するデバイス クライアントを作成します。
    • デバイス ツインのプロパティを格納する 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);
      }
    };
    
  15. デバイス クライアント サービスを開始するには、次のコードを 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...");
    }
    
  16. ユーザーが 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();
    
  17. simulated-device\src\main\java\com\mycompany\app\App.java ファイルを保存して閉じます。

  18. 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 サービス アプリを実行し、2 つのジョブを実行します。 最初のジョブで必要なプロパティの値が設定され、2 つ目のジョブでダイレクト メソッドが呼び出されます。

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

    Java IoT Hub サービス アプリで 2 つのジョブが作成される

  3. デバイス アプリは必要なプロパティの変更とダイレクト メソッド呼び出しを処理します。

    デバイス クライアントは変更に応答する

次の手順

この記事では、ダイレクト メソッドを実行してデバイス ツインのプロパティを更新するようにジョブをスケジュールしました。

IoT Hub とデバイス管理パターンの調査を続けるには、「Raspberry Pi 3 B+ 参照イメージを使用した Device Update for Azure IoT Hub のチュートリアル」でイメージを更新してください。