작업 예약 및 브로드캐스트(Java)
Azure IoT Hub를 사용하여 수백만 대의 디바이스를 업데이트하는 작업을 예약하고 추적합니다. 작업을 사용하여 다음을 수행합니다.
- desired 속성 업데이트
- tags 업데이트
- 직접 메서드 호출
작업(job)은 이러한 작업(action) 중 하나를 래핑하고 디바이스 집합에 대한 실행을 추적합니다. 디바이스 쌍 쿼리에서는 작업 실행 대상인 디바이스 집합을 정의합니다. 예를 들어 백 엔드 앱은 작업(job)을 사용하여 10,000대 디바이스에 대해 디바이스를 재부팅하는 직접 메서드를 호출할 수 있습니다. 디바이스 쌍 쿼리로 디바이스 집합을 지정하고 향후 실행될 작업(job)을 예약합니다. 작업은 해당하는 각 디바이스에서 재부팅 직접 메서드를 수신 및 실행할 때 진행 상태를 추적합니다.
이러한 각 기능에 대한 자세한 내용은 다음을 참조하세요.
디바이스 쌍 및 속성: 디바이스 쌍 시작 및 IoT Hub의 디바이스 쌍 이해 및 사용
직접 메서드: IoT Hub 개발자 가이드 - 직접 메서드
참고 항목
이 문서에서 설명하는 기능은 IoT Hub의 표준 계층에서만 사용할 수 있습니다. 기본 및 표준/무료 IoT Hub 계층에 대한 자세한 내용은 솔루션에 적합한 IoT Hub 계층 선택을 참조하세요.
이 문서에서는 2개의 Java 앱을 만드는 방법을 보여 줍니다.
백 엔드 앱에 의해 호출될 수 있는 lockDoor라는 직접 메서드를 구현하는 simulated-device라는 디바이스 앱을 만듭니다.
2개의 작업을 만드는 schedule-jobs라는 백 엔드 앱을 만듭니다. 한 작업은 lockDoor 직접 메서드를 호출하고 다른 작업은 원하는 속성 업데이트를 여러 디바이스로 보냅니다.
참고 항목
디바이스 및 백 엔드 앱을 빌드하는 데 사용할 수 있는 SDK 도구에 대한 자세한 내용은 Azure IoT SDK를 참조하세요.
필수 조건
Azure 구독의 IoT Hub 아직 허브가 없는 경우 IoT Hub 만들기의 단계를 따를 수 있습니다.
IoT Hub에 등록된 디바이스. IoT 허브에 디바이스가 없으면 디바이스 등록의 단계를 따릅니다.
Java SE Development Kit 8. JDK 8용 다운로드를 가져오려면 장기 지원에서 Java 8을 선택해야 합니다.
방화벽에서 포트 8883이 열려 있는지 확인합니다. 이 문서의 디바이스 샘플은 포트 8883을 통해 통신하는 MQTT 프로토콜을 사용합니다. 이 포트는 일부 회사 및 교육용 네트워크 환경에서 차단될 수 있습니다. 이 문제를 해결하는 자세한 내용과 방법은 IoT Hub에 연결(MQTT)을 참조하세요.
참고 항목
간단히 하기 위해 이 문서에서는 다시 시도 정책을 구현하지 않습니다. 프로덕션 코드에서는 문서 일시적인 오류 처리에서 제시한 대로 다시 시도 정책(예: 지수 백오프)을 구현해야 합니다.
IoT Hub 연결 문자열 가져오기
이 문서에서는 디바이스에서 직접 메서드를 호출하는 작업을 예약하고, 디바이스 쌍을 업데이트하는 작업을 예약하고, 각 작업의 진행률을 모니터링하는 백 엔드 서비스를 만듭니다. 이러한 작업을 수행하려면 서비스에 레지스트리 읽기 및 레지스트리 쓰기 권한이 있어야 합니다. 기본적으로 모든 IoT Hub는 이 사용 권한을 부여하는 registryReadWrite라는 공유 액세스 정책을 사용하여 만듭니다.
registryReadWrite 정책에 대한 IoT Hub 연결 문자열을 가져오려면 다음 단계를 수행합니다.
Azure Portal에서 리소스 그룹을 선택합니다. 허브가 있는 리소스 그룹을 선택한 다음, 리소스 목록에서 허브를 선택합니다.
허브의 왼쪽 창에서 공유 액세스 정책을 선택합니다.
정책 목록에서 registryReadWrite 정책을 선택합니다.
기본 연결 문자열을 복사하고 값을 저장합니다.
IoT Hub 공유 액세스 정책 및 사용 권한에 대한 자세한 내용은 액세스 제어 및 권한을 참조하세요.
Important
이 문서에서는 공유 액세스 서명을 사용하여 서비스에 연결하는 단계를 설명합니다. 이 인증 방법은 테스트와 평가에 편리하지만, Microsoft Entra ID나 관리 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 Hub와 통신하도록 앱에서 iot-service-client 패키지를 사용할 수 있습니다.
<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 Hub 연결 문자열 가져오기에서 복사한 IoT Hub 연결 문자열로 바꿉니다.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)); } }
다음
throws
절을 포함하도록 main 메서드 서명을 업데이트합니다.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
디바이스 앱 만들기
이 섹션에서는 IoT Hub에서 전송한 원하는 속성을 처리하고 직접 메서드 호출을 구현하는 Java 콘솔 앱을 만듭니다.
Important
이 문서에서는 공유 액세스 서명(대칭 키 인증이라고도 함)을 사용하여 디바이스를 연결하는 단계를 설명합니다. 이 인증 방법은 테스트와 평가에 편리하지만, 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 허브와 통신하도록 앱에서 iot-device-client 패키지를 사용할 수 있습니다.
<dependency> <groupId>com.microsoft.azure.sdk.iot</groupId> <artifactId>iot-device-client</artifactId> <version>1.17.5</version> </dependency>
참고 항목
Maven 검색을 사용하여 iot-device-client의 최신 버전을 확인할 수 있습니다.
종속성 노드에 다음 종속성을 추가합니다. 이 종속성은 디바이스 클라이언트 SDK에서 로깅을 구현하는 데 사용하는 Apache SLF4J 로깅 외관에 맞게 NOP를 구성합니다. 이 구성은 선택 사항이지만, 건너뛰면 앱을 실행할 때 콘솔에 경고가 표시될 수 있습니다. 디바이스 클라이언트 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 클래스에 추가합니다.
{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 개체를 인스턴스화할 때 프로토콜 변수를 사용합니다.
콘솔에 디바이스 쌍 알림을 인쇄하려면 다음 중첩 클래스를 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 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; } }
다음
throws
절을 포함하도록 main 메서드 서명을 업데이트합니다.public static void main( String[] args ) throws IOException, URISyntaxException
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); } };
디바이스 클라이언트 서비스를 시작하려면 다음 코드를 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 Hub 및 디바이스 관리 패턴을 계속 살펴보려면 Raspberry Pi 3 B+ 참조 이미지를 사용하는 Azure IoT Hub용 디바이스 업데이트 자습서에서 이미지를 업데이트합니다.