排定及廣播作業 (Java)
使用 Azure IoT 中樞排程和追蹤會更新數百萬部裝置的作業。 使用作業以:
- 更新所需屬性
- 更新標籤
- 叫用直接方法
作業會包裝上述其中一個動作,然後針對一組裝置追蹤執行進度。 裝置對應項查詢會定義對其執行作業的一組裝置。 例如,後端應用程式可以使用作業,以在 10,000 部裝置上叫用重新開機裝置的直接方法。 您以裝置對應項查詢指定一組裝置,並排程在未來的時間執行作業。 作業會在每部裝置接收和執行重新開機直接方法時追蹤進度。
若要一一了解這些功能,請參閱:
裝置對應項和屬性:開始使用裝置對應項和了解並使用 IoT 中樞中的裝置對應項屬性
直接方法:IoT 中樞開發人員指南 - 直接方法 (部分機器翻譯)
注意
本文中所述的功能僅適用於 IoT 中樞的標準層。 如需基本和標準/免費 IoT 中樞層的詳細資訊,請參閱選擇適合您解決方案的 IoT 中樞層 (部分機器翻譯)。
本文說明如何建立兩個 Java 應用程式:
裝置應用程式 (simulated-device) 會實作稱為 lockDoor 的直接方法,其可由後端應用程式進行呼叫。
後端應用程式 (schedule-jobs) 會建立兩個作業。 一個作業會呼叫 lockDoor 直接方法,而另一個作業會將所需的屬性更新傳送至多個裝置。
注意
如需可用來建置裝置和後端應用程式的 SDK 工具詳細資訊,請參閱 Azure IoT SDK。
必要條件
Azure 訂用帳戶中的 IoT 中樞。 如果您還沒有中樞,可遵循建立 IoT 中樞中的步驟。
在 IoT 中樞內註冊的裝置。 如果 IoT 中樞中沒有裝置,請遵循註冊裝置中的步驟。
Java SE 開發套件 8。 請務必選取 [長期支援] 下的 [Java 8],以取得 JDK 8 的下載。
請確定您的防火牆已開啟連接埠 8883。 本文中的裝置範例會使用 MQTT 通訊協定,其會透過連接埠 8883 進行通訊。 某些公司和教育網路環境可能會封鎖此連接埠。 如需此問題的詳細資訊和解決方法,請參閱連線至 IoT 中樞 (MQTT)。
注意
為了簡單起見,本文章不會實作重試原則。 在生產環境程式碼中,您應該如暫時性錯誤處理一文中所建議,實作重試原則 (例如指數型輪詢)。
取得 IoT 中樞連接字串
在本文中,您會建立後端服務,其會排程在裝置上叫用直接方法的作業、排程更新裝置對應項的作業,以及監視每項作業的進度。 若要執行這些作業,則服務需要有登錄讀取和登錄寫入權限。 根據預設,每個 IoT 中樞都是使用授與這些權限且名為 registryReadWrite 的共用存取原則所建立。
若要取得 registryReadWrite 原則的 IoT 中樞連接字串,請遵循下列步驟:
在 Azure 入口網站中,選取 [資源群組]。 選取中樞所在的資源群組,然後從資源清單選取中樞。
在中樞的左側窗格中,選取 [共用存取原則]。
從原則清單中,選取 registryReadWrite 原則。
複製 [主要連接字串] 並儲存該值。
如需 IoT 中樞共用存取原則和權限的詳細資訊,請參閱存取控制及權限。
重要
本文包含使用共用存取簽章連線至服務的步驟。 此驗證方法方便進行測試和評估,但使用 Microsoft Entra ID 或受控識別向服務進行驗證是更安全的方法。 若要深入了解,請參閱安全性最佳做法 > 雲端安全性。
建立服務應用程式
在本節中,您將建立 Java 主控台應用程式,使用作業以:
在多個裝置上呼叫 lockDoor 直接方法。
將所需屬性傳送至多個裝置。
建立應用程式:
在開發電腦上建立稱為 iot-java-schedule-jobs 的空資料夾。
在 iot-java-schedule-jobs 資料夾的命令提示字元中,使用下列命令建立稱為 schedule-jobs 的 Maven 專案。 注意,這是一個單一且非常長的命令:
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=schedule-jobs -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
在命令提示字元中,巡覽至 schedule-jobs 資料夾。
使用文字編輯器,開啟 schedule-jobs 資料夾中的 pom.xml 檔案,並將下列相依性新增至 [相依性] 節點。 這個相依性可讓您在應用程式中使用 iot-service-client 套件與 IoT 中樞通訊:
<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。
將下列 [建置] 節點新增至 [相依性] 節點之後。 此設定會指示 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>
儲存並關閉 pom.xml 檔案。
使用文字編輯器,開啟 schedule-jobs\src\main\java\com\mycompany\app\App.java 檔案。
在此檔案中新增下列 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;
將下列類別層級變數新增到 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;
將下列方法新增至 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; } }
若要排程作業以呼叫 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; } };
若要監視作業,將下列方法新增至 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; } }
若要查詢您執行之作業的詳細資料,請新增下列方法:
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)); } }
更新 Main 方法簽章,以包含下列
throws
子句:public static void main( String[] args ) throws Exception
若要循序執行及監視兩個作業,請將 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");
儲存並關閉 schedule-jobs\src\main\java\com\mycompany\app\App.java 檔案
建置 schedule-jobs 應用程式,並更正所有錯誤。 在命令提示字元中,巡覽至 schedule-jobs 資料夾,並執行下列命令:
mvn clean package -DskipTests
建立裝置應用程式
在本節中,您會建立 Java 主控台應用程式來處理從 IoT 中樞傳送的所需屬性,以及實作直接方法呼叫。
重要
本文包含使用共用存取簽章 (也稱為對稱金鑰驗證) 連線裝置的步驟。 此驗證方法方便進行測試和評估,但使用 X.509 憑證來驗證裝置是更安全的方法。 若要深入了解,請參閱安全性最佳做法>連線安全性。
在 iot-java-schedule-jobs 資料夾的命令提示字元中,使用下列命令建立稱為 simulated-device 的 Maven 專案。 注意,這是一個單一且非常長的命令:
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=simulated-device -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
在命令提示字元中,巡覽至 simulated-device 資料夾。
使用文字編輯器,開啟 simulated-device 資料夾中的 pom.xml 檔案,並將下列相依性新增至 [相依性] 節點。 這個相依性可讓您在應用程式中使用 iot-device-client 套件與 IoT 中樞通訊:
<dependency> <groupId>com.microsoft.azure.sdk.iot</groupId> <artifactId>iot-device-client</artifactId> <version>1.17.5</version> </dependency>
注意
您可以使用 Maven 搜尋來檢查最新版的 iot-device-client。
將下列相依性新增至 [相依性] 節點。 此相依性會設定 Apache SLF4J 記錄外觀的 NOP,以供裝置用戶端 SDK 用來實作記錄。 這是選擇性設定,但如果予以省略,則可能會在執行應用程式時於主控台中看到一則警告。 如需裝置用戶端 SDK 中記錄的詳細資訊,請參閱<適用於 Java 的 Azure IoT 裝置 SDK 範例>讀我檔案中的記錄 (英文)。
<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-nop</artifactId> <version>1.7.28</version> </dependency>
將下列 [建置] 節點新增至 [相依性] 節點之後。 此設定會指示 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>
儲存並關閉 pom.xml 檔案。
使用文字編輯器,開啟 simulated-device\src\main\java\com\mycompany\app\App.java 檔案。
在此檔案中新增下列 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;
將下列類別層級變數新增到 App 類別中。 以您在 IoT 中樞內註冊裝置時看到的裝置連接字串,取代
{yourdeviceconnectionstring}
: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 變數。
若要將裝置對應項通知列印至主控台,請在 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()); } }
若要將直接方法通知列印至主控台,請在 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()); } }
若要處理來自 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; } }
更新 Main 方法簽章,以包含下列
throws
子句:public static void main( String[] args ) throws IOException, URISyntaxException
將 main 方法中的程式碼取代為下列程式碼,以便:
- 建立與 IoT 中樞通訊的裝置用戶端。
- 建立裝置物件以儲存裝置對應項屬性。
// 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); } };
若要啟動裝置用戶端服務,請將下列程式碼新增至 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..."); }
若要在關閉之前等候使用者按下 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();
儲存並關閉 simulated-device\src\main\java\com\mycompany\app\App.java 檔案。
建置 simulated-device 應用程式,並更正所有錯誤。 在命令提示字元中,巡覽至 simulated-device 資料夾,並執行下列命令:
mvn clean package -DskipTests
執行應用程式
您現在已經準備好執行主控台應用程式。
在 simulated-device 資料夾的命令提示字元中,執行下列命令以啟動裝置應用程式來接聽所需屬性變更和直接方法呼叫:
mvn exec:java -Dexec.mainClass="com.mycompany.app.App"
在
schedule-jobs
資料夾的命令提示字元中執行下列命令,執行 schedule-jobs 服務應用程式以執行兩個作業。 第一個作業會設定所需屬性值,第二個作業會呼叫直接方法:mvn exec:java -Dexec.mainClass="com.mycompany.app.App"
裝置應用程式會處理所需屬性變更和直接方法呼叫:
下一步
在本文中,您已排程工作來執行直接方法,並更新裝置對應項的屬性。
若要繼續探索 IoT 中樞和裝置管理模式,請更新使用 Raspberry Pi 3 B+ 參考映像的 Azure IoT 中樞裝置更新教學課程中的映像。