다음을 통해 공유


자습서: 클라이언트 애플리케이션을 만들어 Azure IoT Central 애플리케이션에 연결

Important

이 문서에서는 공유 액세스 서명(대칭 키 인증이라고도 함)을 사용하여 디바이스를 연결하는 단계를 설명합니다. 이 인증 방법은 테스트와 평가에 편리하지만, X.509 인증서를 사용하여 디바이스를 인증하는 것이 더 안전한 방식입니다. 자세한 내용은 보안 모범 사례 > 연결 보안을 참조하세요.

이 자습서에서는 클라이언트 애플리케이션을 Azure IoT Central 애플리케이션에 연결하는 방법을 보여 줍니다. 이 애플리케이션은 온도 컨트롤러 디바이스의 동작을 시뮬레이션합니다. 애플리케이션이 IoT Central에 연결되면 온도 컨트롤러 디바이스 모델의 모델 ID를 보냅니다. IoT Central은 모델 ID를 사용하여 디바이스 모델을 검색하고 디바이스 템플릿을 만듭니다. 운영자가 디바이스와 상호 작용할 수 있도록 디바이스 템플릿에 보기를 추가합니다.

이 자습서에서는 다음을 하는 방법을 알아볼 수 있습니다.

  • 디바이스 코드를 만들고 실행하며, IoT Central 애플리케이션에 연결되는지 확인합니다.
  • 디바이스에서 보낸 시뮬레이션된 원격 분석 데이터 보기
  • 디바이스 템플릿에 사용자 지정 보기를 추가합니다.
  • 디바이스 템플릿을 게시합니다.
  • 보기를 사용하여 디바이스 속성을 관리합니다.
  • 명령을 호출하여 디바이스를 제어합니다.

코드 찾아보기

필수 조건

이 자습서를 완료하려면 다음이 필요합니다.

Linux 또는 Windows에서 이 자습서를 실행할 수 있습니다. 이 자습서의 셸 명령은 '/' 경로 구분 기호에 대한 Linux 규칙을 따릅니다. Windows에서 계속 실행 중인 경우 '\'에 대해 이러한 구분 기호를 교환해야 합니다.

필수 구성 요소는 운영 체제에 따라 다릅니다.

Linux

이 자습서에서는 Ubuntu Linux를 사용하고 있다고 가정합니다. 이 자습서의 단계는 Ubuntu 18.04를 사용하여 테스트했습니다.

Linux에서 이 자습서를 완료하려면 로컬 Linux 환경에 다음 소프트웨어를 설치해야 합니다.

apt-get 명령을 사용하여 GCC, Git, cmake 및 필요한 모든 종속성을 설치합니다.

sudo apt-get update
sudo apt-get install -y git cmake build-essential curl libcurl4-openssl-dev libssl-dev uuid-dev

cmake 버전이 2.8.12 이상이고 GCC 버전이 4.4.7 이상인지 확인합니다.

cmake --version
gcc --version

Windows

Windows에서 이 자습서를 완료하려면 로컬 Windows 환경에 다음 소프트웨어를 설치합니다.

코드 다운로드

이 자습서에서는 Azure IoT Hub Device C SDK를 복제하고 빌드하는 데 사용할 수 있는 개발 환경을 준비합니다.

선택한 디렉터리에서 명령 프롬프트를 엽니다. 다음 명령을 실행하여 Azure IoT C SDK 및 라이브러리 GitHub 리포지토리를 이 위치에 복제합니다.

git clone https://github.com/Azure/azure-iot-sdk-c.git
cd azure-iot-sdk-c
git submodule update --init

이 작업을 완료하는 데 몇 분 정도가 걸립니다.

코드 검토

이전에 다운로드한 C용 Microsoft Azure IoT SDK 복사본의 텍스트 편집기에서 azure-iot-sdk-c/iothub_client/samples/pnp/pnp_temperature_controller/pnp_temperature_controller.cazure-iot-sdk-c/iothub_client/samples/pnp/pnp_temperature_controller/pnp_thermostat_component.c 파일을 엽니다.

이 샘플은 다중 구성 요소 온도 컨트롤러 디지털 트윈 정의 언어 모델을 구현합니다.

샘플을 실행하여 IoT Central에 연결하면 DPS(Device Provisioning Service)를 사용하여 디바이스를 등록하고 연결 문자열을 생성합니다. 이 샘플은 명령줄 환경에서 필요한 DPS 연결 정보를 검색합니다.

pnp_temperature_controller.c에서 main 함수는 먼저 다음에 대한 CreateDeviceClientAndAllocateComponents를 호출합니다.

  • dtmi:com:example:Thermostat;1 모델 ID를 설정합니다. IoT Central은 모델 ID를 사용하여 이 디바이스에 대한 디바이스 템플릿을 식별하거나 생성합니다. 자세히 알아보려면 디바이스 템플릿에 디바이스 할당을 참조하세요.
  • DPS를 사용하여 디바이스를 프로비저닝하고 등록합니다.
  • 디바이스 클라이언트 핸들을 만들고 IoT Central 애플리케이션에 연결합니다.
  • 온도 컨트롤러 구성 요소에서 명령에 대한 처리기를 만듭니다.
  • 온도 컨트롤러 구성 요소에서 속성 업데이트에 대한 처리기를 만듭니다.
  • 두 개의 자동 온도 조절기 구성 요소를 만듭니다.

다음 main 함수:

  • 모든 구성 요소에 대한 몇 가지 초기 속성 값을 보고합니다.
  • 루프를 시작하여 모든 구성 요소에서 원격 분석을 보냅니다.

그러면 main 함수는 스레드를 시작하여 주기적으로 원격 분석을 보냅니다.

int main(void)
{
    IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient = NULL;

    g_pnpDeviceConfiguration.modelId = g_temperatureControllerModelId;
    g_pnpDeviceConfiguration.enableTracing = g_hubClientTraceEnabled;

    // First determine the IoT Hub / credentials / device to use.
    if (GetConnectionSettingsFromEnvironment(&g_pnpDeviceConfiguration) == false)
    {
        LogError("Cannot read required environment variable(s)");
    }
    // Creates the thermostat subcomponents defined by this model.  Since everything
    // is simulated, this setup stage just creates simulated objects in memory.
    else if (AllocateThermostatComponents() == false)
    {
        LogError("Failure allocating thermostat components");
    }
    // Create a handle to device client handle.  Note that this call may block
    // for extended periods of time when using DPS.
    else if ((deviceClient = CreateAndConfigureDeviceClientHandleForPnP()) == NULL)
    {
        LogError("Failure creating Iot Hub device client");
        PnP_ThermostatComponent_Destroy(g_thermostatHandle1);
        PnP_ThermostatComponent_Destroy(g_thermostatHandle2);
    }
    else
    {
        LogInfo("Successfully created device client.  Hit Control-C to exit program\n");

        int numberOfIterations = 0;

        // During startup, send what DTDLv2 calls "read-only properties" to indicate initial device state.
        PnP_TempControlComponent_ReportSerialNumber_Property(deviceClient);
        PnP_DeviceInfoComponent_Report_All_Properties(g_deviceInfoComponentName, deviceClient);
        PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(g_thermostatHandle1, deviceClient);
        PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(g_thermostatHandle2, deviceClient);

        while (true)
        {
            // Wake up periodically to poll.  Even if we do not plan on sending telemetry, we still need to poll periodically in order to process
            // incoming requests from the server and to do connection keep alives.
            if ((numberOfIterations % g_sendTelemetryPollInterval) == 0)
            {
                PnP_TempControlComponent_SendWorkingSet(deviceClient);
                PnP_ThermostatComponent_SendCurrentTemperature(g_thermostatHandle1, deviceClient);
                PnP_ThermostatComponent_SendCurrentTemperature(g_thermostatHandle2, deviceClient);
            }

            IoTHubDeviceClient_LL_DoWork(deviceClient);
            ThreadAPI_Sleep(g_sleepBetweenPollsMs);
            numberOfIterations++;
        }

        // The remainder of the code is used for cleaning up our allocated resources. It won't be executed in this 
        // sample (because the loop above is infinite and is only broken out of by Control-C of the program), but 
        // it is included for reference.

        // Free the memory allocated to track simulated thermostat.
        PnP_ThermostatComponent_Destroy(g_thermostatHandle1);
        PnP_ThermostatComponent_Destroy(g_thermostatHandle2);

        // Clean up the IoT Hub SDK handle.
        IoTHubDeviceClient_LL_Destroy(deviceClient);
        // Free all IoT Hub subsystem.
        IoTHub_Deinit();
    }

    return 0;
}

pnp_thermostat_component.c에서 PnP_ThermostatComponent_SendCurrentTemperature 함수는 디바이스가 구성 요소에서 IoT Central로 온도 원격 분석을 보내는 방법을 보여 줍니다.

void PnP_ThermostatComponent_SendCurrentTemperature(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    IOTHUB_MESSAGE_HANDLE messageHandle = NULL;
    IOTHUB_MESSAGE_RESULT messageResult;
    IOTHUB_CLIENT_RESULT iothubClientResult;

    char temperatureStringBuffer[CURRENT_TEMPERATURE_BUFFER_SIZE];

    // Create the telemetry message body to send.
    if (snprintf(temperatureStringBuffer, sizeof(temperatureStringBuffer), g_temperatureTelemetryBodyFormat, pnpThermostatComponent->currentTemperature) < 0)
    {
        LogError("snprintf of current temperature telemetry failed");
    }
    // Create the message handle and specify its metadata.
    else if ((messageHandle = IoTHubMessage_CreateFromString(temperatureStringBuffer)) == NULL)
    {
        LogError("IoTHubMessage_PnP_CreateFromString failed");
    }
    else if ((messageResult = IoTHubMessage_SetContentTypeSystemProperty(messageHandle, g_jsonContentType)) != IOTHUB_MESSAGE_OK)
    {
        LogError("IoTHubMessage_SetContentTypeSystemProperty failed, error=%d", messageResult);
    }
    else if ((messageResult = IoTHubMessage_SetContentEncodingSystemProperty(messageHandle, g_utf8EncodingType)) != IOTHUB_MESSAGE_OK)
    {
        LogError("IoTHubMessage_SetContentEncodingSystemProperty failed, error=%d", messageResult);
    }
    else if ((messageResult = IoTHubMessage_SetComponentName(messageHandle, pnpThermostatComponent->componentName)) != IOTHUB_MESSAGE_OK)
    {
        LogError("IoTHubMessage_SetContentEncodingSystemProperty failed, error=%d", messageResult);
    }
    // Send the telemetry message.
    else if ((iothubClientResult = IoTHubDeviceClient_LL_SendTelemetryAsync(deviceClient, messageHandle, NULL, NULL)) != IOTHUB_CLIENT_OK)
    {
        LogError("Unable to send telemetry message, error=%d", iothubClientResult);
    }

    IoTHubMessage_Destroy(messageHandle);
}

pnp_thermostat_component.c에서 PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property 함수는 구성 요소에서 IoT Central로 maxTempSinceLastReboot 속성 업데이트를 보냅니다.

void PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    char maximumTemperatureAsString[MAX_TEMPERATURE_SINCE_REBOOT_BUFFER_SIZE];
    IOTHUB_CLIENT_RESULT iothubClientResult;

    if (snprintf(maximumTemperatureAsString, sizeof(maximumTemperatureAsString), g_maxTempSinceLastRebootPropertyFormat, pnpThermostatComponent->maxTemperature) < 0)
    {
        LogError("Unable to create max temp since last reboot string for reporting result");
    }
    else
    {
        IOTHUB_CLIENT_PROPERTY_REPORTED maxTempProperty;
        maxTempProperty.structVersion = IOTHUB_CLIENT_PROPERTY_REPORTED_STRUCT_VERSION_1;
        maxTempProperty.name = g_maxTempSinceLastRebootPropertyName;
        maxTempProperty.value =  maximumTemperatureAsString;

        unsigned char* propertySerialized = NULL;
        size_t propertySerializedLength;

        // The first step of reporting properties is to serialize IOTHUB_CLIENT_PROPERTY_WRITABLE_RESPONSE into JSON for sending.
        if ((iothubClientResult = IoTHubClient_Properties_Serializer_CreateReported(&maxTempProperty, 1, pnpThermostatComponent->componentName, &propertySerialized, &propertySerializedLength)) != IOTHUB_CLIENT_OK)
        {
            LogError("Unable to serialize reported state, error=%d", iothubClientResult);
        }
        // The output of IoTHubClient_Properties_Serializer_CreateReported is sent to IoTHubDeviceClient_LL_SendPropertiesAsync to perform network I/O.
        else if ((iothubClientResult = IoTHubDeviceClient_LL_SendPropertiesAsync(deviceClient, propertySerialized, propertySerializedLength,  NULL, NULL)) != IOTHUB_CLIENT_OK)
        {
            LogError("Unable to send reported state, error=%d", iothubClientResult);
        }
        else
        {
            LogInfo("Sending %s property to IoTHub for component %s", g_maxTempSinceLastRebootPropertyName, pnpThermostatComponent->componentName);
        }
        IoTHubClient_Properties_Serializer_Destroy(propertySerialized);
    }
}

pnp_thermostat_component.c에서 PnP_ThermostatComponent_ProcessPropertyUpdate 함수는 IoT Central에서 쓰기 가능한 속성 업데이트를 처리합니다.

void PnP_ThermostatComponent_ProcessPropertyUpdate(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient, const char* propertyName, const char* propertyValue, int version)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;

    if (strcmp(propertyName, g_targetTemperaturePropertyName) != 0)
    {
        LogError("Property %s was requested to be changed but is not part of the thermostat interface definition", propertyName);
    }
    else
    {
        char* next;
        double targetTemperature = strtod(propertyValue, &next);
        if ((propertyValue == next) || (targetTemperature == HUGE_VAL) || (targetTemperature == (-1*HUGE_VAL)))
        {
            LogError("Property %s is not a valid number", propertyValue);
            SendTargetTemperatureResponse(pnpThermostatComponent, deviceClient, propertyValue, PNP_STATUS_BAD_FORMAT, version, g_temperaturePropertyResponseDescriptionNotInt);
        }
        else
        {
            LogInfo("Received targetTemperature %f for component %s", targetTemperature, pnpThermostatComponent->componentName);
            
            bool maxTempUpdated = false;
            UpdateTemperatureAndStatistics(pnpThermostatComponent, targetTemperature, &maxTempUpdated);

            // The device needs to let the service know that it has received the targetTemperature desired property.
            SendTargetTemperatureResponse(pnpThermostatComponent, deviceClient, propertyValue, PNP_STATUS_SUCCESS, version, NULL);
            
            if (maxTempUpdated)
            {
                // If the maximum temperature has been updated, we also report this as a property.
                PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(pnpThermostatComponent, deviceClient);
            }
        }
    }
}

pnp_thermostat_component.c에서 PnP_ThermostatComponent_ProcessCommand 함수는 IoT Central에서 호출된 명령을 처리합니다.

void PnP_ThermostatComponent_ProcessCommand(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, const char *pnpCommandName, JSON_Value* commandJsonValue, IOTHUB_CLIENT_COMMAND_RESPONSE* commandResponse)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    const char* sinceStr;

    if (strcmp(pnpCommandName, g_getMaxMinReportCommandName) != 0)
    {
        LogError("Command %s is not supported on thermostat component", pnpCommandName);
        commandResponse->statusCode = PNP_STATUS_NOT_FOUND;
    }
    // See caveats section in ../readme.md; we don't actually respect this sinceStr to keep the sample simple,
    // but want to demonstrate how to parse out in any case.
    else if ((sinceStr = json_value_get_string(commandJsonValue)) == NULL)
    {
        LogError("Cannot retrieve JSON string for command");
        commandResponse->statusCode = PNP_STATUS_BAD_FORMAT;
    }
    else if (BuildMaxMinCommandResponse(pnpThermostatComponent, commandResponse) == false)
    {
        LogError("Unable to build response for component %s", pnpThermostatComponent->componentName);
        commandResponse->statusCode = PNP_STATUS_INTERNAL_ERROR;
    }
    else
    {
        LogInfo("Returning success from command request for component %s", pnpThermostatComponent->componentName);
        commandResponse->statusCode = PNP_STATUS_SUCCESS;
    }
}

코드 빌드

디바이스 SDK를 사용하여 포함된 샘플 코드를 빌드합니다.

  1. 디바이스 SDK의 루트 폴더에 cmake 하위 디렉터리를 만들고 해당 폴더로 이동합니다.

    cd azure-iot-sdk-c
    mkdir cmake
    cd cmake
    
  2. 다음 명령을 실행하여 SDK 및 샘플을 빌드합니다.

    cmake -Duse_prov_client=ON -Dhsm_type_symm_key=ON -Drun_e2e_tests=OFF ..
    cmake --build .
    

연결 정보 가져오기

이 자습서의 뒷부분에서 샘플 디바이스 애플리케이션을 실행하는 경우 다음과 같은 구성 값이 필요합니다.

  • ID 범위: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹으로 이동합니다. ID 범위 값을 기록해 둡니다.
  • 그룹 기본 키: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹 > SAS-IoT-Devices로 이동합니다. 공유 액세스 서명 기본 키 값을 기록해 둡니다.

Azure Cloud Shell을 사용하여 검색한 그룹 기본 키에서 디바이스 키를 생성합니다.

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

생성된 디바이스 키를 기록해 두고 이 자습서의 뒷부분에서 사용합니다.

참고 항목

이 샘플을 실행하기 위해 IoT Central 애플리케이션에서 디바이스를 미리 등록할 필요가 없습니다. 이 샘플에서는 IoT Central 기능을 사용하여 처음으로 연결할 때 디바이스를 자동으로 등록합니다.

코드 실행

샘플 애플리케이션을 실행하려면 명령줄 환경을 열고 azure-iot-sdk-c\cmake 폴더로 이동합니다.

환경 변수를 설정하여 샘플을 구성합니다. 다음 코드 조각에서는 Windows 명령 프롬프트에서 환경 변수를 설정하는 방법을 보여줍니다. bash 셸을 사용하는 경우 set 명령을 export 명령으로 바꿉니다.

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

샘플을 실행하려면:

# Bash
cd iothub_client/samples/pnp/pnp_temperature_controller/
./pnp_temperature_controller
REM Windows
cd iothub_client\samples\pnp\pnp_temperature_controller\Debug
.\pnp_temperature_controller.exe

다음 출력은 IoT Central에 등록하고 연결하는 디바이스를 보여줍니다. 샘플은 원격 분석 보내기를 시작합니다.

Info: Initiating DPS client to retrieve IoT Hub connection information
-> 09:43:27 CONNECT | VER: 4 | KEEPALIVE: 0 | FLAGS: 194 | USERNAME: 0ne0026656D/registrations/sample-device-01/api-version=2019-03-31&ClientVersion=1.6.0 | PWD: XXXX | CLEAN: 1
<- 09:43:28 CONNACK | SESSION_PRESENT: false | RETURN_CODE: 0x0
-> 09:43:29 SUBSCRIBE | PACKET_ID: 1 | TOPIC_NAME: $dps/registrations/res/# | QOS: 1
<- 09:43:30 SUBACK | PACKET_ID: 1 | RETURN_CODE: 1
-> 09:43:30 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/PUT/iotdps-register/?$rid=1 | PAYLOAD_LEN: 102
<- 09:43:31 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/202/?$rid=1&retry-after=3 | PACKET_ID: 2 | PAYLOAD_LEN: 94
-> 09:43:31 PUBACK | PACKET_ID: 2
-> 09:43:33 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/GET/iotdps-get-operationstatus/?$rid=2&operationId=4.2f792ade0a5c3e68.baf0e879-d88a-4153-afef-71aff51fd847 | PAYLOAD_LEN: 102
<- 09:43:34 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/202/?$rid=2&retry-after=3 | PACKET_ID: 2 | PAYLOAD_LEN: 173
-> 09:43:34 PUBACK | PACKET_ID: 2
-> 09:43:36 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/GET/iotdps-get-operationstatus/?$rid=3&operationId=4.2f792ade0a5c3e68.baf0e879-d88a-4153-afef-71aff51fd847 | PAYLOAD_LEN: 102
<- 09:43:37 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/200/?$rid=3 | PACKET_ID: 2 | PAYLOAD_LEN: 478
-> 09:43:37 PUBACK | PACKET_ID: 2
Info: Provisioning callback indicates success.  iothubUri=iotc-60a....azure-devices.net, deviceId=sample-device-01
-> 09:43:37 DISCONNECT
Info: DPS successfully registered.  Continuing on to creation of IoTHub device client handle.
Info: Successfully created device client.  Hit Control-C to exit program

Info: Sending serialNumber property to IoTHub
Info: Sending device information property to IoTHub.  propertyName=swVersion, propertyValue="1.0.0.0"
Info: Sending device information property to IoTHub.  propertyName=manufacturer, propertyValue="Sample-Manufacturer"
Info: Sending device information property to IoTHub.  propertyName=model, propertyValue="sample-Model-123"
Info: Sending device information property to IoTHub.  propertyName=osName, propertyValue="sample-OperatingSystem-name"
Info: Sending device information property to IoTHub.  propertyName=processorArchitecture, propertyValue="Contoso-Arch-64bit"
Info: Sending device information property to IoTHub.  propertyName=processorManufacturer, propertyValue="Processor Manufacturer(TM)"
Info: Sending device information property to IoTHub.  propertyName=totalStorage, propertyValue=10000
Info: Sending device information property to IoTHub.  propertyName=totalMemory, propertyValue=200
Info: Sending maximumTemperatureSinceLastReboot property to IoTHub for component=thermostat1
Info: Sending maximumTemperatureSinceLastReboot property to IoTHub for component=thermostat2
-> 09:43:44 CONNECT | VER: 4 | KEEPALIVE: 240 | FLAGS: 192 | USERNAME: iotc-60a576a2-eec7-48e2-9306-9e7089a79995.azure-devices.net/sample-device-01/?api-version=2020-09-30&DeviceClientType=iothubclient%2f1.6.0%20(native%3b%20Linux%3b%20x86_64)&model-id=dtmi%3acom%3aexample%3aTemperatureController%3b1 | PWD: XXXX | CLEAN: 0
<- 09:43:44 CONNACK | SESSION_PRESENT: false | RETURN_CODE: 0x0
-> 09:43:44 SUBSCRIBE | PACKET_ID: 2 | TOPIC_NAME: $iothub/twin/res/# | QOS: 0 | TOPIC_NAME: $iothub/methods/POST/# | QOS: 0
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/ | PACKET_ID: 3 | PAYLOAD_LEN: 19
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/%24.sub=thermostat1 | PACKET_ID: 4 | PAYLOAD_LEN: 21
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/%24.sub=thermostat2 | PACKET_ID: 5 | PAYLOAD_LEN: 21

Azure IoT Central 애플리케이션의 운영자는 다음을 수행할 수 있습니다.

  • 개요 페이지에서 두 자동 온도 조절기 구성 요소에서 보낸 원격 분석을 봅니다.

    디바이스 개요 페이지를 보여 주는 스크린샷.

  • 정보 페이지에서 디바이스 속성을 봅니다. 이 페이지에서는 디바이스 정보 구성 요소 및 두 개의 자동 온도 조절기 구성 요소에 있는 속성을 보여 줍니다.

    디바이스 속성 보기를 보여 주는 스크린샷.

디바이스 템플릿 사용자 지정

솔루션 개발자는 온도 조절 디바이스가 연결되면 IoT Central이 자동으로 생성한 디바이스 템플릿을 사용자 지정할 수 있습니다.

디바이스와 연결된 고객 이름을 저장하는 클라우드 속성을 추가하려면 다음을 수행합니다.

  1. IoT Central 애플리케이션에서 디바이스 템플릿 페이지의 온도 조절 디바이스 템플릿으로 이동합니다.

  2. 온도 컨트롤러 모델에서 +기능 추가를 선택합니다.

  3. 표시 이름으로 고객 이름을 입력하고, 클라우드 속성기능 유형으로 선택하고, 항목을 확장하고, 문자열스키마로 선택합니다. 그런 다음 저장을 선택합니다.

Get Max-Min report 명령이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. getMaxMinReport(thermostat1)의 경우 Max-Min 보고서 가져오기thermostat1 상태 보고서로 바꿉니다.

  3. getMaxMinReport(thermostat2)의 경우 Max-Min 보고서 가져오기thermostat2 상태 보고서로 바꿉니다.

  4. 저장을 선택합니다.

대상 온도 쓰기 가능 속성이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. targetTemperature(thermostat1)의 경우 대상 온도대상 온도(1)로 바꿉니다.

  3. targetTemperature(thermostat2)의 경우 대상 온도대상 온도(2)로 바꿉니다.

  4. 저장을 선택합니다.

온도 조절 모델의 자동 온도 조절기 구성 요소에는 대상 온도 쓰기 가능 속성이 포함되고, 디바이스 템플릿에는 고객 이름 클라우드 속성이 포함됩니다. 운영자가 다음 속성을 편집하는 데 사용할 수 있는 보기를 만듭니다.

  1. 보기, 디바이스 및 클라우드 데이터를 편집하는 중 타일을 차례로 선택합니다.

  2. 양식 이름으로 속성을 입력합니다.

  3. 대상 온도(1), 대상 온도(2)고객 이름 속성을 선택합니다. 그런 다음, 섹션 추가를 선택합니다.

  4. 변경 내용을 저장합니다.

속성 값을 업데이트하기 위한 보기를 보여 주는 스크린샷

디바이스 템플릿 게시

운영자가 만든 사용자 지정을 보고 사용하려면 먼저 디바이스 템플릿을 게시해야 합니다.

자동 온도 조절 디바이스 템플릿에서 게시를 선택합니다. 이 디바이스 템플릿을 애플리케이션에 게시 패널에서 게시를 선택합니다.

이제 운영자는 속성 보기를 사용하여 속성 값을 업데이트하고 디바이스 명령 페이지에서 Get thermostat1 status reportGet thermostat2 status report라는 명령을 호출할 수 있습니다.

  • 속성 페이지에서 쓰기 가능한 속성 값을 업데이트합니다.

    디바이스 속성 업데이트를 보여 주는 스크린샷

  • 명령 페이지에서 명령을 호출합니다. 상태 보고서 명령을 실행하는 경우 다음 이후 매개 변수를 실행하기 전에 이 매개 변수의 날짜와 시간을 선택합니다.

    명령 호출을 보여 주는 스크린샷

    명령 응답을 보여 주는 스크린샷.

다음과 같이 디바이스가 명령 및 속성 업데이트에 응답하는 방식을 확인할 수 있습니다.

<- 09:49:03 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/methods/POST/thermostat1*getMaxMinReport/?$rid=1 | PAYLOAD_LEN: 26
Info: Received PnP command for component=thermostat1, command=getMaxMinReport
Info: Returning success from command request for component=thermostat1
-> 09:49:03 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/methods/res/200/?$rid=1 | PAYLOAD_LEN: 117

...

<- 09:50:04 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/twin/PATCH/properties/desired/?$version=2 | PAYLOAD_LEN: 63
Info: Received targetTemperature=67.000000 for component=thermostat2
Info: Sending acknowledgement of property to IoTHub for component=thermostat2

코드 찾아보기

필수 조건

이 문서의 단계를 완료하려면 다음 리소스가 필요합니다.

코드 검토

이전에 다운로드한 C#용 Microsoft Azure IoT SDK 리포지토리의 복사본에서 Visual Studio의 azure-iot-sdk-csharp-main\azureiot.sln 솔루션 파일을 엽니다. 솔루션 탐색기에서 PnpDeviceSamples > TemperatureController 폴더를 확장하고 Program.csTemperatureControllerSample.cs 파일을 열어 이 샘플에 대한 코드를 확인합니다.

이 샘플은 다중 구성 요소 온도 컨트롤러 디지털 트윈 정의 언어 모델을 구현합니다.

샘플을 실행하여 IoT Central에 연결하면 DPS(Device Provisioning Service)를 사용하여 디바이스를 등록하고 연결 문자열을 생성합니다. 이 샘플은 환경에서 필요한 DPS 연결 정보를 검색합니다.

Program.cs에서 Main 메서드는 SetupDeviceClientAsync를 호출하여 다음을 수행합니다.

  • DPS를 사용하여 디바이스를 프로비저닝할 때 모델 ID dtmi:com:example:TemperatureController;2을 사용합니다. IoT Central은 모델 ID를 사용하여 이 디바이스에 대한 디바이스 템플릿을 식별하거나 생성합니다. 자세히 알아보려면 디바이스 템플릿에 디바이스 할당을 참조하세요.
  • IoT Central에 연결할 DeviceClient 인스턴스를 만듭니다.
private static async Task<DeviceClient> SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken)
{
  DeviceClient deviceClient;
  switch (parameters.DeviceSecurityType.ToLowerInvariant())
  {
    case "dps":
      DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken);
      var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey);
      deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod);
      break;

    case "connectionstring":
      // ...

    default:
      // ...
  }
  return deviceClient;
}

그런 다음, 기본 메서드는 TemperatureControllerSample 인스턴스를 만들고 PerformOperationsAsync 메서드를 호출하여 IoT Central과의 상호 작용을 처리합니다.

TemperatureControllerSample.cs에서 PerformOperationsAsync 메서드는 다음을 수행합니다.

  • 기본 구성 요소에서 reboot 명령에 대한 처리기를 설정합니다.
  • 두 자동 온도 조절기 구성 요소에서 getMaxMinReport 명령에 대한 처리기를 설정합니다.
  • 두 자동 온도 조절기 구성 요소에서 대상 온도 속성 업데이트를 수신하도록 처리기를 설정합니다.
  • 초기 디바이스 정보 속성 업데이트를 보냅니다.
  • 두 자동 온도 조절기 구성 요소에서 온도 원격 분석을 주기적으로 보냅니다.
  • 기본 구성 요소에서 작업 세트 원격 분석을 주기적으로 보냅니다.
  • 마지막 재부팅 이후 두 자동 온도 조절기 구성 요소에서 새로운 최대 온도에 도달할 때마다 최대 온도를 보냅니다.
public async Task PerformOperationsAsync(CancellationToken cancellationToken)
{
  await _deviceClient.SetMethodHandlerAsync("reboot", HandleRebootCommandAsync, _deviceClient, cancellationToken);

  // For a component-level command, the command name is in the format "<component-name>*<command-name>".
  await _deviceClient.SetMethodHandlerAsync("thermostat1*getMaxMinReport", HandleMaxMinReportCommand, Thermostat1, cancellationToken);
  await _deviceClient.SetMethodHandlerAsync("thermostat2*getMaxMinReport", HandleMaxMinReportCommand, Thermostat2, cancellationToken);

  await _deviceClient.SetDesiredPropertyUpdateCallbackAsync(SetDesiredPropertyUpdateCallback, null, cancellationToken);
  _desiredPropertyUpdateCallbacks.Add(Thermostat1, TargetTemperatureUpdateCallbackAsync);
  _desiredPropertyUpdateCallbacks.Add(Thermostat2, TargetTemperatureUpdateCallbackAsync);

  await UpdateDeviceInformationAsync(cancellationToken);
  await SendDeviceSerialNumberAsync(cancellationToken);

  bool temperatureReset = true;
  _maxTemp[Thermostat1] = 0d;
  _maxTemp[Thermostat2] = 0d;

  while (!cancellationToken.IsCancellationRequested)
  {
    if (temperatureReset)
    {
      // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component.
      _temperature[Thermostat1] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1);
      _temperature[Thermostat2] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1);
    }

    await SendTemperatureAsync(Thermostat1, cancellationToken);
    await SendTemperatureAsync(Thermostat2, cancellationToken);
    await SendDeviceMemoryAsync(cancellationToken);

    temperatureReset = _temperature[Thermostat1] == 0 && _temperature[Thermostat2] == 0;
    await Task.Delay(5 * 1000);
  }
}

SendTemperatureAsync 메서드는 디바이스가 구성 요소에서 IoT Central로 온도 원격 분석을 보내는 방법을 보여 줍니다. SendTemperatureTelemetryAsync 메서드는 PnpConvention 클래스를 사용하여 메시지를 빌드합니다.

private async Task SendTemperatureAsync(string componentName, CancellationToken cancellationToken)
{
  await SendTemperatureTelemetryAsync(componentName, cancellationToken);

  double maxTemp = _temperatureReadingsDateTimeOffset[componentName].Values.Max<double>();
  if (maxTemp > _maxTemp[componentName])
  {
    _maxTemp[componentName] = maxTemp;
    await UpdateMaxTemperatureSinceLastRebootAsync(componentName, cancellationToken);
  }
}

private async Task SendTemperatureTelemetryAsync(string componentName, CancellationToken cancellationToken)
{
  const string telemetryName = "temperature";
  double currentTemperature = _temperature[componentName];
  using Message msg = PnpConvention.CreateMessage(telemetryName, currentTemperature, componentName);

  await _deviceClient.SendEventAsync(msg, cancellationToken);

  if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName))
  {
    _temperatureReadingsDateTimeOffset[componentName].TryAdd(DateTimeOffset.UtcNow, currentTemperature);
  }
  else
  {
    _temperatureReadingsDateTimeOffset.TryAdd(
      componentName,
      new Dictionary<DateTimeOffset, double>
      {
        { DateTimeOffset.UtcNow, currentTemperature },
      });
  }
}

UpdateMaxTemperatureSinceLastRebootAsync 메서드는 IoT Central에 maxTempSinceLastReboot 속성 업데이트를 보냅니다. 이 메서드는 PnpConvention 클래스를 사용하여 패치를 만듭니다.

private async Task UpdateMaxTemperatureSinceLastRebootAsync(string componentName, CancellationToken cancellationToken)
{
  const string propertyName = "maxTempSinceLastReboot";
  double maxTemp = _maxTemp[componentName];
  TwinCollection reportedProperties = PnpConvention.CreateComponentPropertyPatch(componentName, propertyName, maxTemp);

  await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties, cancellationToken);
}

TargetTemperatureUpdateCallbackAsync 메서드는 IoT Central에서 쓰기 가능 대상 온도 속성 업데이트를 처리합니다. 이 메서드는 PnpConvention 클래스를 사용하여 속성 업데이트 메시지를 읽고 응답을 생성합니다.

private async Task TargetTemperatureUpdateCallbackAsync(TwinCollection desiredProperties, object userContext)
{
  const string propertyName = "targetTemperature";
  string componentName = (string)userContext;

  bool targetTempUpdateReceived = PnpConvention.TryGetPropertyFromTwin(
    desiredProperties,
    propertyName,
    out double targetTemperature,
    componentName);
  if (!targetTempUpdateReceived)
  {
      return;
  }

  TwinCollection pendingReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse(
      componentName,
      propertyName,
      targetTemperature,
      (int)StatusCode.InProgress,
      desiredProperties.Version);

  await _deviceClient.UpdateReportedPropertiesAsync(pendingReportedProperty);

  // Update Temperature in 2 steps
  double step = (targetTemperature - _temperature[componentName]) / 2d;
  for (int i = 1; i <= 2; i++)
  {
      _temperature[componentName] = Math.Round(_temperature[componentName] + step, 1);
      await Task.Delay(6 * 1000);
  }

  TwinCollection completedReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse(
      componentName,
      propertyName,
      _temperature[componentName],
      (int)StatusCode.Completed,
      desiredProperties.Version,
      "Successfully updated target temperature");

  await _deviceClient.UpdateReportedPropertiesAsync(completedReportedProperty);
}

HandleMaxMinReportCommand 메서드는 IoT Central에서 호출되는 구성 요소에 대한 명령을 처리합니다.

private Task<MethodResponse> HandleMaxMinReportCommand(MethodRequest request, object userContext)
{
    try
    {
        string componentName = (string)userContext;
        DateTime sinceInUtc = JsonConvert.DeserializeObject<DateTime>(request.DataAsJson);
        var sinceInDateTimeOffset = new DateTimeOffset(sinceInUtc);

        if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName))
        {

            Dictionary<DateTimeOffset, double> allReadings = _temperatureReadingsDateTimeOffset[componentName];
            Dictionary<DateTimeOffset, double> filteredReadings = allReadings.Where(i => i.Key > sinceInDateTimeOffset)
                .ToDictionary(i => i.Key, i => i.Value);

            if (filteredReadings != null && filteredReadings.Any())
            {
                var report = new
                {
                    maxTemp = filteredReadings.Values.Max<double>(),
                    minTemp = filteredReadings.Values.Min<double>(),
                    avgTemp = filteredReadings.Values.Average(),
                    startTime = filteredReadings.Keys.Min(),
                    endTime = filteredReadings.Keys.Max(),
                };

                byte[] responsePayload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report));
                return Task.FromResult(new MethodResponse(responsePayload, (int)StatusCode.Completed));
            }

            return Task.FromResult(new MethodResponse((int)StatusCode.NotFound));
        }

        return Task.FromResult(new MethodResponse((int)StatusCode.NotFound));
    }
    catch (JsonReaderException ex)
    {
        // ...
    }
}

연결 정보 가져오기

이 자습서의 뒷부분에서 샘플 디바이스 애플리케이션을 실행하는 경우 다음과 같은 구성 값이 필요합니다.

  • ID 범위: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹으로 이동합니다. ID 범위 값을 기록해 둡니다.
  • 그룹 기본 키: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹 > SAS-IoT-Devices로 이동합니다. 공유 액세스 서명 기본 키 값을 기록해 둡니다.

Azure Cloud Shell을 사용하여 검색한 그룹 기본 키에서 디바이스 키를 생성합니다.

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

생성된 디바이스 키를 기록해 두고 이 자습서의 뒷부분에서 사용합니다.

참고 항목

이 샘플을 실행하기 위해 IoT Central 애플리케이션에서 디바이스를 미리 등록할 필요가 없습니다. 이 샘플에서는 IoT Central 기능을 사용하여 처음으로 연결할 때 디바이스를 자동으로 등록합니다.

코드 실행

참고 항목

코드를 실행하기 전에 TemperatureController를 시작 프로젝트로 설정합니다.

Visual Studio에서 샘플 애플리케이션을 실행하려면 다음을 수행합니다.

  1. 솔루션 탐색기에서 PnpDeviceSamples > TemperatureController 프로젝트 파일을 선택합니다.

  2. 프로젝트 > TemperatureController 속성 > 디버그로 이동합니다. 그런 다음, 프로젝트에 다음 환경 변수를 추가합니다.

    속성
    IOTHUB_DEVICE_SECURITY_TYPE DPS
    IOTHUB_DEVICE_DPS_ENDPOINT global.azure-devices-provisioning.net
    IOTHUB_DEVICE_DPS_ID_SCOPE 이전에 기록해 둔 ID 범위 값입니다.
    IOTHUB_DEVICE_DPS_DEVICE_ID sample-device-01
    IOTHUB_DEVICE_DPS_DEVICE_KEY 이전에 기록해 둔 생성된 디바이스 키 값입니다.

이제 Visual Studio에서 샘플을 실행하고 디버그할 수 있습니다.

다음 출력은 IoT Central에 등록하고 연결하는 디바이스를 보여줍니다. 샘플은 원격 분석 보내기를 시작합니다.

[03/31/2021 14:43:17]info: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Press Control+C to quit the sample.
[03/31/2021 14:43:17]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set up the device client.
[03/31/2021 14:43:18]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Initializing via DPS
[03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler for 'reboot' command.
[03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Connection status change registered - status=Connected, reason=Connection_Ok.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler for "getMaxMinReport" command.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler to receive 'targetTemperature' updates.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component = 'deviceInformation', properties update is complete.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - { "serialNumber": "SR-123456" } is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat1", { "temperature": 34.2 } in °C.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", { "maxTempSinceLastReboot": 34.2 } in °C is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat2", { "temperature": 25.1 } in °C.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat2", { "maxTempSinceLastReboot": 25.1 } in °C is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - {"workingSet":31412} in KB.

Azure IoT Central 애플리케이션의 운영자는 다음을 수행할 수 있습니다.

  • 개요 페이지에서 두 자동 온도 조절기 구성 요소에서 보낸 원격 분석을 봅니다.

    디바이스 개요 페이지를 보여 주는 스크린샷.

  • 정보 페이지에서 디바이스 속성을 봅니다. 이 페이지에서는 디바이스 정보 구성 요소 및 두 개의 자동 온도 조절기 구성 요소에 있는 속성을 보여 줍니다.

    디바이스 속성 보기를 보여 주는 스크린샷.

디바이스 템플릿 사용자 지정

솔루션 개발자는 온도 조절 디바이스가 연결되면 IoT Central이 자동으로 생성한 디바이스 템플릿을 사용자 지정할 수 있습니다.

디바이스와 연결된 고객 이름을 저장하는 클라우드 속성을 추가하려면 다음을 수행합니다.

  1. IoT Central 애플리케이션에서 디바이스 템플릿 페이지의 온도 조절 디바이스 템플릿으로 이동합니다.

  2. 온도 컨트롤러 모델에서 +기능 추가를 선택합니다.

  3. 표시 이름으로 고객 이름을 입력하고, 클라우드 속성기능 유형으로 선택하고, 항목을 확장하고, 문자열스키마로 선택합니다. 그런 다음 저장을 선택합니다.

Get Max-Min report 명령이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. getMaxMinReport(thermostat1)의 경우 Max-Min 보고서 가져오기thermostat1 상태 보고서로 바꿉니다.

  3. getMaxMinReport(thermostat2)의 경우 Max-Min 보고서 가져오기thermostat2 상태 보고서로 바꿉니다.

  4. 저장을 선택합니다.

대상 온도 쓰기 가능 속성이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. targetTemperature(thermostat1)의 경우 대상 온도대상 온도(1)로 바꿉니다.

  3. targetTemperature(thermostat2)의 경우 대상 온도대상 온도(2)로 바꿉니다.

  4. 저장을 선택합니다.

온도 조절 모델의 자동 온도 조절기 구성 요소에는 대상 온도 쓰기 가능 속성이 포함되고, 디바이스 템플릿에는 고객 이름 클라우드 속성이 포함됩니다. 운영자가 다음 속성을 편집하는 데 사용할 수 있는 보기를 만듭니다.

  1. 보기, 디바이스 및 클라우드 데이터를 편집하는 중 타일을 차례로 선택합니다.

  2. 양식 이름으로 속성을 입력합니다.

  3. 대상 온도(1), 대상 온도(2)고객 이름 속성을 선택합니다. 그런 다음, 섹션 추가를 선택합니다.

  4. 변경 내용을 저장합니다.

속성 값을 업데이트하기 위한 보기를 보여 주는 스크린샷

디바이스 템플릿 게시

운영자가 만든 사용자 지정을 보고 사용하려면 먼저 디바이스 템플릿을 게시해야 합니다.

자동 온도 조절 디바이스 템플릿에서 게시를 선택합니다. 이 디바이스 템플릿을 애플리케이션에 게시 패널에서 게시를 선택합니다.

이제 운영자는 속성 보기를 사용하여 속성 값을 업데이트하고 디바이스 명령 페이지에서 Get thermostat1 status reportGet thermostat2 status report라는 명령을 호출할 수 있습니다.

  • 속성 페이지에서 쓰기 가능한 속성 값을 업데이트합니다.

    디바이스 속성 업데이트를 보여 주는 스크린샷

  • 명령 페이지에서 명령을 호출합니다. 상태 보고서 명령을 실행하는 경우 다음 이후 매개 변수를 실행하기 전에 이 매개 변수의 날짜와 시간을 선택합니다.

    명령 호출을 보여 주는 스크린샷

    명령 응답을 보여 주는 스크린샷.

다음과 같이 디바이스가 명령 및 속성 업데이트에 응답하는 방식을 확인할 수 있습니다.

[03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Command: Received - component="thermostat2", generating max, min and avg temperature report since 31/03/2021 06:00:00.
[03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Command: component="thermostat2", MaxMinReport since 31/03/2021 06:00:00: maxTemp=36.4, minTemp=36.4, avgTemp=36.4, startTime=31/03/2021 14:46:33, endTime=31/03/2021 14:46:55

...

[03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Received - component="thermostat1", { "targetTemperature": 67°C }.
[03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is InProgress.
[03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is Completed
[03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat1", { "temperature": 67 } in °C.

코드 찾아보기

필수 조건

이 문서의 단계를 완료하려면 다음 리소스가 필요합니다.

  • Java SE Development Kit 8 이상이 포함된 개발 머신. 자세한 내용은 JDK 설치를 참조하세요.

  • Apache Maven 3.

  • 샘플 코드가 포함된 Java용 Microsoft Azure IoT SDK GitHub 리포지토리의 로컬 복사본입니다. 다음 링크를 사용하여 리포지토리의 복사본을 다운로드합니다. ZIP 다운로드 그런 다음, 로컬 머신의 적절한 위치에 파일의 압축을 풉니다.

코드 검토

이전에 다운로드한 Java용 Microsoft Azure IoT SDK 복사본의 텍스트 편집기에서 azure-iot-sdk-java/iothub/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/device/TemperatureController.java 파일을 엽니다.

이 샘플은 다중 구성 요소 온도 컨트롤러 디지털 트윈 정의 언어 모델을 구현합니다.

샘플을 실행하여 IoT Central에 연결하면 DPS(Device Provisioning Service)를 사용하여 디바이스를 등록하고 연결 문자열을 생성합니다. 이 샘플은 명령줄 환경에서 필요한 DPS 연결 정보를 검색합니다.

main 메서드는 다음 작업을 수행합니다.

  • initializeAndProvisionDevice를 호출하여 dtmi:com:example:TemperatureController;2 모델 ID를 설정하고, DPS를 사용하여 디바이스를 프로비저닝 및 등록하고, DeviceClient 인스턴스를 만들고, IoT Central 애플리케이션에 연결합니다. IoT Central은 모델 ID를 사용하여 이 디바이스에 대한 디바이스 템플릿을 식별하거나 생성합니다. 자세히 알아보려면 디바이스 템플릿에 디바이스 할당을 참조하세요.
  • getMaxMinReportreboot 명령에 대한 명령 처리기를 만듭니다.
  • 쓰기 가능한 targetTemperature 속성에 대한 속성 업데이트 처리기를 만듭니다.
  • 디바이스 정보 인터페이스 및 디바이스 메모리일련 번호 속성의 속성에 대한 초기 값을 보냅니다.
  • 스레드를 시작하여 두 자동 온도 조절기에서 온도 원격 분석을 보내고 5초마다 maxTempSinceLastReboot 속성을 업데이트합니다.
public static void main(String[] args) throws Exception {

  // ...
  
  switch (deviceSecurityType.toLowerCase())
  {
    case "dps":
    {
      if (validateArgsForDpsFlow())
      {
        initializeAndProvisionDevice();
        break;
      }
      throw new IllegalArgumentException("Required environment variables are not set for DPS flow, please recheck your environment.");
    }
    case "connectionstring":
    {
      // ...
    }
    default:
    {
      // ...
    }
  }
  
  deviceClient.subscribeToMethods(new MethodCallback(), null);
  
  deviceClient.subscribeToDesiredPropertiesAsync(
  {
  (twin, context) ->
      TwinCollection desiredProperties = twin.getDesiredProperties();
      for (String desiredPropertyKey : desiredProperties.keySet())
      {
          TargetTemperatureUpdateCallback.onPropertyChanged(new Property(desiredPropertyKey, desiredProperties.get(desiredPropertyKey)), null);
      }
  },
  null,
  (exception, context) ->
  {
      if (exception == null)
      {
          log.info("Successfully subscribed to desired properties. Getting initial state");
          deviceClient.getTwinAsync(
              (twin, getTwinException, getTwinContext) ->
              {
                  log.info("Initial twin state received");
                  log.info(twin.toString());
              },
              null);
      }
      else
      {
          log.info("Failed to subscribe to desired properties. Error code {}", exception.getStatusCode());
          System.exit(-1);
      }
  },
  null);

  updateDeviceInformation();
  sendDeviceMemory();
  sendDeviceSerialNumber();
  
  final AtomicBoolean temperatureReset = new AtomicBoolean(true);
  maxTemperature.put(THERMOSTAT_1, 0.0d);
  maxTemperature.put(THERMOSTAT_2, 0.0d);
  
  new Thread(new Runnable() {
    @SneakyThrows({InterruptedException.class, IOException.class})
    @Override
    public void run() {
      while (true) {
        if (temperatureReset.get()) {
          // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component.
          temperature.put(THERMOSTAT_1, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue());
          temperature.put(THERMOSTAT_2, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue());
        }

        sendTemperatureReading(THERMOSTAT_1);
        sendTemperatureReading(THERMOSTAT_2);

        temperatureReset.set(temperature.get(THERMOSTAT_1) == 0 && temperature.get(THERMOSTAT_2) == 0);
        Thread.sleep(5 * 1000);
      }
    }
  }).start();
}

initializeAndProvisionDevice 메서드는 디바이스에서 DPS를 사용하여 IoT Central에 등록하고 연결하는 방법을 보여줍니다. 페이로드에는 IoT Central이 디바이스 템플릿에 디바이스를 할당하는 데 사용하는 모델 ID가 포함됩니다.

private static void initializeAndProvisionDevice() throws Exception {
  SecurityProviderSymmetricKey securityClientSymmetricKey = new SecurityProviderSymmetricKey(deviceSymmetricKey.getBytes(), registrationId);
  ProvisioningDeviceClient provisioningDeviceClient;
  ProvisioningStatus provisioningStatus = new ProvisioningStatus();

  provisioningDeviceClient = ProvisioningDeviceClient.create(globalEndpoint, scopeId, provisioningProtocol, securityClientSymmetricKey);

  AdditionalData additionalData = new AdditionalData();
  additionalData.setProvisioningPayload(com.microsoft.azure.sdk.iot.provisioning.device.plugandplay.PnpHelper.createDpsPayload(MODEL_ID));

  ProvisioningDeviceClientRegistrationResult registrationResult = provisioningDeviceClient.registerDeviceSync(additionalData);

    ClientOptions options = ClientOptions.builder().modelId(MODEL_ID).build();
    if (registrationResult.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) {
        System.out.println("IotHUb Uri : " + registrationResult.getIothubUri());
        System.out.println("Device ID : " + registrationResult.getDeviceId());
        String iotHubUri = registrationResult.getIothubUri();
        String deviceId = registrationResult.getDeviceId();
        log.debug("Opening the device client.");
        deviceClient = new DeviceClient(iotHubUri, deviceId, securityClientSymmetricKey, IotHubClientProtocol.MQTT, options);
        deviceClient.open(true);
    }
}

sendTemperatureTelemetry 메서드는 디바이스가 구성 요소에서 IoT Central로 온도 원격 분석을 보내는 방법을 보여 줍니다. 이 메서드는 PnpConvention 클래스를 사용하여 메시지를 만듭니다.

  private static void sendTemperatureTelemetry(String componentName) {
    String telemetryName = "temperature";
    double currentTemperature = temperature.get(componentName);

    Message message = PnpConvention.createIotHubMessageUtf8(telemetryName, currentTemperature, componentName);
    deviceClient.sendEventAsync(message, new MessageIotHubEventCallback(), message);

    // Add the current temperature entry to the list of temperature readings.
    Map<Date, Double> currentReadings;
    if (temperatureReadings.containsKey(componentName)) {
      currentReadings = temperatureReadings.get(componentName);
    } else {
      currentReadings = new HashMap<>();
    }
    currentReadings.put(new Date(), currentTemperature);
    temperatureReadings.put(componentName, currentReadings);
  }

updateMaxTemperatureSinceLastReboot 메서드는 구성 요소에서 IoT Central로 maxTempSinceLastReboot 속성 업데이트를 전송합니다. 이 메서드는 PnpConvention 클래스를 사용하여 패치를 만듭니다.

private static void updateMaxTemperatureSinceLastReboot(String componentName) throws IOException {
  String propertyName = "maxTempSinceLastReboot";
  double maxTemp = maxTemperature.get(componentName);

  TwinCollection reportedProperty = PnpConvention.createComponentPropertyPatch(propertyName, maxTemp, componentName);
  deviceClient.updateReportedPropertiesAsync(reportedProperty, sendReportedPropertiesResponseCallback, null);
  log.debug("Property: Update - {\"{}\": {}°C} is {}.", propertyName, maxTemp, StatusCode.COMPLETED);
}

TargetTemperatureUpdateCallback 클래스에는 IoT Central에서 구성 요소에 쓰기 가능한 속성 업데이트를 처리하는 onPropertyChanged 메서드가 포함되어 있습니다. 이 메서드는 PnpConvention 클래스를 사용하여 다음과 같이 응답을 만듭니다.

private static class TargetTemperatureUpdateCallback
{
    final static String propertyName = "targetTemperature";
    @SneakyThrows(InterruptedException.class)
    public static void onPropertyChanged(Property property, Object context) {
        String componentName = (String) context;
        if (property.getKey().equalsIgnoreCase(componentName)) {
            double targetTemperature = (double) ((TwinCollection) property.getValue()).get(propertyName);
            log.debug("Property: Received - component=\"{}\", {\"{}\": {}°C}.", componentName, propertyName, targetTemperature);
            TwinCollection pendingPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
                    propertyName,
                    targetTemperature,
                    componentName,
                    StatusCode.IN_PROGRESS.value,
                    property.getVersion().longValue(),
                    null);
            deviceClient.updateReportedPropertiesAsync(pendingPropertyPatch, sendReportedPropertiesResponseCallback, null);
            log.debug("Property: Update - component=\"{}\", {\"{}\": {}°C} is {}", componentName, propertyName, targetTemperature, StatusCode.IN_PROGRESS);
            // Update temperature in 2 steps
            double step = (targetTemperature - temperature.get(componentName)) / 2;
            for (int i = 1; i <=2; i++) {
                temperature.put(componentName, BigDecimal.valueOf(temperature.get(componentName) + step).setScale(1, RoundingMode.HALF_UP).doubleValue());
                Thread.sleep(5 * 1000);
            }
            TwinCollection completedPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
                    propertyName,
                    temperature.get(componentName),
                    componentName,
                    StatusCode.COMPLETED.value,
                    property.getVersion().longValue(),
                    "Successfully updated target temperature.");
            deviceClient.updateReportedPropertiesAsync(completedPropertyPatch, sendReportedPropertiesResponseCallback, null);
            log.debug("Property: Update - {\"{}\": {}°C} is {}", propertyName, temperature.get(componentName), StatusCode.COMPLETED);
        } else {
            log.debug("Property: Received an unrecognized property update from service.");
        }
    }
}

MethodCallback 클래스에는 IoT Central에서 호출된 구성 요소 명령을 처리하는 onMethodInvoked 메서드가 포함되어 있습니다.

private static class MethodCallback implements com.microsoft.azure.sdk.iot.device.twin.MethodCallback
{
    final String reboot = "reboot";
    final String getMaxMinReport1 = "thermostat1*getMaxMinReport";
    final String getMaxMinReport2 = "thermostat2*getMaxMinReport";
    @SneakyThrows(InterruptedException.class)
    @Override
    public DirectMethodResponse onMethodInvoked(String methodName, DirectMethodPayload methodData, Object context) {
        String jsonRequest = methodData.getPayload(String.class);
        switch (methodName) {
            case reboot:
                int delay = getCommandRequestValue(jsonRequest, Integer.class);
                log.debug("Command: Received - Rebooting thermostat (resetting temperature reading to 0°C after {} seconds).", delay);
                Thread.sleep(delay * 1000L);
                temperature.put(THERMOSTAT_1, 0.0d);
                temperature.put(THERMOSTAT_2, 0.0d);
                maxTemperature.put(THERMOSTAT_1, 0.0d);
                maxTemperature.put(THERMOSTAT_2, 0.0d);
                temperatureReadings.clear();
                return new DirectMethodResponse(StatusCode.COMPLETED.value, null);
            case getMaxMinReport1:
            case getMaxMinReport2:
                String[] words = methodName.split("\\*");
                String componentName = words[0];
                if (temperatureReadings.containsKey(componentName)) {
                    Date since = getCommandRequestValue(jsonRequest, Date.class);
                    log.debug("Command: Received - component=\"{}\", generating min, max, avg temperature report since {}", componentName, since);
                    Map<Date, Double> allReadings = temperatureReadings.get(componentName);
                    Map<Date, Double> filteredReadings = allReadings.entrySet().stream()
                            .filter(map -> map.getKey().after(since))
                            .collect(Collectors.toMap(Entry::getKey, Entry::getValue));
                    if (!filteredReadings.isEmpty()) {
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
                        double maxTemp = Collections.max(filteredReadings.values());
                        double minTemp = Collections.min(filteredReadings.values());
                        double avgTemp = filteredReadings.values().stream().mapToDouble(Double::doubleValue).average().orElse(Double.NaN);
                        String startTime =  sdf.format(Collections.min(filteredReadings.keySet()));
                        String endTime =  sdf.format(Collections.max(filteredReadings.keySet()));
                        String responsePayload = String.format(
                                "{\"maxTemp\": %.1f, \"minTemp\": %.1f, \"avgTemp\": %.1f, \"startTime\": \"%s\", \"endTime\": \"%s\"}",
                                maxTemp,
                                minTemp,
                                avgTemp,
                                startTime,
                                endTime);
                        log.debug("Command: MaxMinReport since {}: \"maxTemp\": {}°C, \"minTemp\": {}°C, \"avgTemp\": {}°C, \"startTime\": {}, \"endTime\": {}",
                                since,
                                maxTemp,
                                minTemp,
                                avgTemp,
                                startTime,
                                endTime);
                        return new DirectMethodResponse(StatusCode.COMPLETED.value, responsePayload);
                    }
                    log.debug("Command: component=\"{}\", no relevant readings found since {}, cannot generate any report.", componentName, since);
                    return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
                }
                log.debug("Command: component=\"{}\", no temperature readings sent yet, cannot generate any report.", componentName);
                return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
            default:
                log.debug("Command: command=\"{}\" is not implemented, no action taken.", methodName);
                return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
        }
    }
}

연결 정보 가져오기

이 자습서의 뒷부분에서 샘플 디바이스 애플리케이션을 실행하는 경우 다음과 같은 구성 값이 필요합니다.

  • ID 범위: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹으로 이동합니다. ID 범위 값을 기록해 둡니다.
  • 그룹 기본 키: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹 > SAS-IoT-Devices로 이동합니다. 공유 액세스 서명 기본 키 값을 기록해 둡니다.

Azure Cloud Shell을 사용하여 검색한 그룹 기본 키에서 디바이스 키를 생성합니다.

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

생성된 디바이스 키를 기록해 두고 이 자습서의 뒷부분에서 사용합니다.

참고 항목

이 샘플을 실행하기 위해 IoT Central 애플리케이션에서 디바이스를 미리 등록할 필요가 없습니다. 이 샘플에서는 IoT Central 기능을 사용하여 처음으로 연결할 때 디바이스를 자동으로 등록합니다.

Windows에서 다운로드한 Java용 Azure IoT SDK 리포지토리의 루트 폴더로 이동합니다.

다음 명령을 실행하여 샘플 애플리케이션을 빌드합니다.

mvn install -T 2C -DskipTests

코드 실행

샘플 애플리케이션을 실행하려면 명령줄 환경을 열고 TemperatureController.java 샘플 파일이 포함된 src 폴더를 포함하는 azure-iot-sdk-java/iothub/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample 폴더로 이동합니다.

환경 변수를 설정하여 샘플을 구성합니다. 다음 코드 조각에서는 Windows 명령 프롬프트에서 환경 변수를 설정하는 방법을 보여줍니다. bash 셸을 사용하는 경우 set 명령을 export 명령으로 바꿉니다.

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

샘플을 실행합니다.

mvn exec:java -Dexec.mainClass="samples.com.microsoft.azure.sdk.iot.device.TemperatureController"

다음 출력은 IoT Central에 등록하고 연결하는 디바이스를 보여줍니다. 샘플은 원격 분석 보내기를 시작합니다.

2021-03-30 15:33:25.138 DEBUG TemperatureController:123 - Initialize the device client.
Waiting for Provisioning Service to register
Waiting for Provisioning Service to register
IotHUb Uri : iotc-60a.....azure-devices.net
Device ID : sample-device-01
2021-03-30 15:33:38.294 DEBUG TemperatureController:247 - Opening the device client.
2021-03-30 15:33:38.307 INFO  ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true
2021-03-30 15:33:38.321 INFO  ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true
2021-03-30 15:33:38.427 DEBUG MqttIotHubConnection:274 - Opening MQTT connection...
2021-03-30 15:33:38.427 DEBUG Mqtt:123 - Sending MQTT CONNECT packet...
2021-03-30 15:33:44.628 DEBUG Mqtt:126 - Sent MQTT CONNECT packet was acknowledged
2021-03-30 15:33:44.630 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/#
2021-03-30 15:33:44.731 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/# was acknowledged
2021-03-30 15:33:44.733 DEBUG MqttIotHubConnection:279 - MQTT connection opened successfully
2021-03-30 15:33:44.733 DEBUG IotHubTransport:302 - The connection to the IoT Hub has been established
2021-03-30 15:33:44.734 INFO  IotHubTransport:1429 - Updating transport status to new status CONNECTED with reason CONNECTION_OK
2021-03-30 15:33:44.735 DEBUG IotHubTransport:1439 - Invoking connection status callbacks with new status details
2021-03-30 15:33:44.739 DEBUG IotHubTransport:394 - Client connection opened successfully
2021-03-30 15:33:44.740 INFO  DeviceClient:438 - Device client opened successfully
2021-03-30 15:33:44.740 DEBUG TemperatureController:152 - Set handler for "reboot" command.
2021-03-30 15:33:44.742 DEBUG TemperatureController:153 - Set handler for "getMaxMinReport" command.
2021-03-30 15:33:44.774 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] )
2021-03-30 15:33:44.774 DEBUG TemperatureController:156 - Set handler to receive "targetTemperature" updates.
2021-03-30 15:33:44.775 INFO  IotHubTransport:1344 - Sending message ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] )
2021-03-30 15:33:44.779 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/methods/POST/#
2021-03-30 15:33:44.793 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.794 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [eeee4444-ff55-6666-77aa-888888bbbbbb] Message Id [ffff5555-aa66-7777-88bb-999999cccccc] Request Id [0] Device Operation Type [DEVICE_OPERATION_TWIN_GET_REQUEST] )
2021-03-30 15:33:44.819 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa6666-bb77-8888-99cc-000000dddddd] Message Id [aaaa0000-bb11-2222-33cc-444444dddddd] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.881 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic $iothub/methods/POST/# was acknowledged
2021-03-30 15:33:44.882 INFO  IotHubTransport:1344 - Sending message ( Message details: Correlation Id [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.882 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/twin/res/#
2021-03-30 15:33:44.893 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [bbbb1111-cc22-3333-44dd-555555eeeeee] Message Id [cccc2222-dd33-4444-55ee-666666ffffff] Request Id [1] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.904 DEBUG TemperatureController:423 - Property: Update - component = "deviceInformation" is COMPLETED.
2021-03-30 15:33:44.915 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [dddd3333-ee44-5555-66ff-777777aaaaaa] Message Id [eeee4444-ff55-6666-77aa-888888bbbbbb] )
2021-03-30 15:33:44.915 DEBUG TemperatureController:434 - Telemetry: Sent - {"workingSet": 1024.0KiB }
2021-03-30 15:33:44.915 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [ffff5555-aa66-7777-88bb-999999cccccc] Message Id [aaaa6666-bb77-8888-99cc-000000dddddd] Request Id [2] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.916 DEBUG TemperatureController:442 - Property: Update - {"serialNumber": SR-123456} is COMPLETED
2021-03-30 15:33:44.927 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] )
2021-03-30 15:33:44.927 DEBUG TemperatureController:461 - Telemetry: Sent - {"temperature": 5.8°C} with message Id bbbb1111-cc22-3333-44dd-555555eeeeee.

Azure IoT Central 애플리케이션의 운영자는 다음을 수행할 수 있습니다.

  • 개요 페이지에서 두 자동 온도 조절기 구성 요소에서 보낸 원격 분석을 봅니다.

    디바이스 개요 페이지를 보여 주는 스크린샷.

  • 정보 페이지에서 디바이스 속성을 봅니다. 이 페이지에서는 디바이스 정보 구성 요소 및 두 개의 자동 온도 조절기 구성 요소에 있는 속성을 보여 줍니다.

    디바이스 속성 보기를 보여 주는 스크린샷.

디바이스 템플릿 사용자 지정

솔루션 개발자는 온도 조절 디바이스가 연결되면 IoT Central이 자동으로 생성한 디바이스 템플릿을 사용자 지정할 수 있습니다.

디바이스와 연결된 고객 이름을 저장하는 클라우드 속성을 추가하려면 다음을 수행합니다.

  1. IoT Central 애플리케이션에서 디바이스 템플릿 페이지의 온도 조절 디바이스 템플릿으로 이동합니다.

  2. 온도 컨트롤러 모델에서 +기능 추가를 선택합니다.

  3. 표시 이름으로 고객 이름을 입력하고, 클라우드 속성기능 유형으로 선택하고, 항목을 확장하고, 문자열스키마로 선택합니다. 그런 다음 저장을 선택합니다.

Get Max-Min report 명령이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. getMaxMinReport(thermostat1)의 경우 Max-Min 보고서 가져오기thermostat1 상태 보고서로 바꿉니다.

  3. getMaxMinReport(thermostat2)의 경우 Max-Min 보고서 가져오기thermostat2 상태 보고서로 바꿉니다.

  4. 저장을 선택합니다.

대상 온도 쓰기 가능 속성이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. targetTemperature(thermostat1)의 경우 대상 온도대상 온도(1)로 바꿉니다.

  3. targetTemperature(thermostat2)의 경우 대상 온도대상 온도(2)로 바꿉니다.

  4. 저장을 선택합니다.

온도 조절 모델의 자동 온도 조절기 구성 요소에는 대상 온도 쓰기 가능 속성이 포함되고, 디바이스 템플릿에는 고객 이름 클라우드 속성이 포함됩니다. 운영자가 다음 속성을 편집하는 데 사용할 수 있는 보기를 만듭니다.

  1. 보기, 디바이스 및 클라우드 데이터를 편집하는 중 타일을 차례로 선택합니다.

  2. 양식 이름으로 속성을 입력합니다.

  3. 대상 온도(1), 대상 온도(2)고객 이름 속성을 선택합니다. 그런 다음, 섹션 추가를 선택합니다.

  4. 변경 내용을 저장합니다.

속성 값을 업데이트하기 위한 보기를 보여 주는 스크린샷

디바이스 템플릿 게시

운영자가 만든 사용자 지정을 보고 사용하려면 먼저 디바이스 템플릿을 게시해야 합니다.

자동 온도 조절 디바이스 템플릿에서 게시를 선택합니다. 이 디바이스 템플릿을 애플리케이션에 게시 패널에서 게시를 선택합니다.

이제 운영자는 속성 보기를 사용하여 속성 값을 업데이트하고 디바이스 명령 페이지에서 Get thermostat1 status reportGet thermostat2 status report라는 명령을 호출할 수 있습니다.

  • 속성 페이지에서 쓰기 가능한 속성 값을 업데이트합니다.

    디바이스 속성 업데이트를 보여 주는 스크린샷

  • 명령 페이지에서 명령을 호출합니다. 상태 보고서 명령을 실행하는 경우 다음 이후 매개 변수를 실행하기 전에 이 매개 변수의 날짜와 시간을 선택합니다.

    명령 호출을 보여 주는 스크린샷

    명령 응답을 보여 주는 스크린샷.

다음과 같이 디바이스가 명령 및 속성 업데이트에 응답하는 방식을 확인할 수 있습니다.

2021-03-30 15:43:57.133 DEBUG TemperatureController:309 - Command: Received - component="thermostat1", generating min, max, avg temperature report since Tue Mar 30 06:00:00 BST 2021
2021-03-30 15:43:57.153 DEBUG TemperatureController:332 - Command: MaxMinReport since Tue Mar 30 06:00:00 BST 2021: "maxTemp": 35.6°C, "minTemp": 35.6°C, "avgTemp": 35.6°C, "startTime": 2021-03-30T15:43:41Z, "endTime": 2021-03-30T15:43:56Z
2021-03-30 15:43:57.394 DEBUG TemperatureController:502 - Command - Response from IoT Hub: command name=null, status=OK_EMPTY


...

2021-03-30 15:48:47.808 DEBUG TemperatureController:372 - Property: Received - component="thermostat2", {"targetTemperature": 67.0°C}.
2021-03-30 15:48:47.837 DEBUG TemperatureController:382 - Property: Update - component="thermostat2", {"targetTemperature": 67.0°C} is IN_PROGRESS

코드 찾아보기

필수 조건

이 문서의 단계를 완료하려면 다음 리소스가 필요합니다.

  • Node.js 버전 6 이상이 설치된 개발 머신. 명령줄에서 node --version 명령을 실행하여 버전을 확인할 수 있습니다. 이 자습서의 지침에서는 Windows 명령 프롬프트에서 node 명령을 실행한다고 가정합니다. 하지만 여러 운영 체제에서 Node.js를 사용할 수 있습니다.

  • 샘플 코드가 포함된 Node.js용 Microsoft Azure IoT SDK GitHub 리포지토리의 로컬 복사본입니다. 다음 링크를 사용하여 리포지토리의 복사본을 다운로드합니다. ZIP 다운로드 그런 다음, 로컬 머신의 적절한 위치에 파일의 압축을 풉니다.

코드 검토

이전에 다운로드한 Node.js용 Microsoft Azure IoT SDK 복사본의 텍스트 편집기에서 azure-iot-sdk-node/device/samples/javascript/pnp_temperature_controller.js 파일을 엽니다.

이 샘플은 다중 구성 요소 온도 컨트롤러 디지털 트윈 정의 언어 모델을 구현합니다.

샘플을 실행하여 IoT Central에 연결하면 DPS(Device Provisioning Service)를 사용하여 디바이스를 등록하고 연결 문자열을 생성합니다. 이 샘플은 명령줄 환경에서 필요한 DPS 연결 정보를 검색합니다.

main 메서드는 다음 작업을 수행합니다.

  • 연결을 열기 전에 client 개체를 만들고 dtmi:com:example:TemperatureController;2 모델 ID를 설정합니다. IoT Central은 모델 ID를 사용하여 이 디바이스에 대한 디바이스 템플릿을 식별하거나 생성합니다. 자세히 알아보려면 디바이스 템플릿에 디바이스 할당을 참조하세요.
  • 세 가지 명령에 대한 명령 처리기를 만듭니다.
  • 각 자동 온도 조절기 구성 요소에 대해 5초마다 온도 원격 분석을 보내도록 루프를 시작합니다.
  • 기본 구성 요소에 대해 6초마다 작업 세트 크기 원격 분석을 보내도록 루프를 시작합니다.
  • 각 자동 온도 조절기 구성 요소에 대한 maxTempSinceLastReboot 속성을 보냅니다.
  • 디바이스 정보 속성을 보냅니다.
  • 세 가지 구성 요소에 대한 쓰기 가능한 속성 처리기를 만듭니다.
async function main() {
  // ...

  // fromConnectionString must specify a transport, coming from any transport package.
  const client = Client.fromConnectionString(deviceConnectionString, Protocol);
  console.log('Connecting using connection string: ' + deviceConnectionString);
  let resultTwin;

  try {
    // Add the modelId here
    await client.setOptions(modelIdObject);
    await client.open();
    console.log('Enabling the commands on the client');
    client.onDeviceMethod(commandNameGetMaxMinReport1, commandHandler);
    client.onDeviceMethod(commandNameGetMaxMinReport2, commandHandler);
    client.onDeviceMethod(commandNameReboot, commandHandler);

    // Send Telemetry after some interval
    let index1 = 0;
    let index2 = 0;
    let index3 = 0;
    intervalToken1 = setInterval(() => {
      const data = JSON.stringify(thermostat1.updateSensor().getCurrentTemperatureObject());
      sendTelemetry(client, data, index1, thermostat1ComponentName).catch((err) => console.log('error ', err.toString()));
      index1 += 1;
    }, 5000);

    intervalToken2 = setInterval(() => {
      const data = JSON.stringify(thermostat2.updateSensor().getCurrentTemperatureObject());
      sendTelemetry(client, data, index2, thermostat2ComponentName).catch((err) => console.log('error ', err.toString()));
      index2 += 1;
    }, 5500);


    intervalToken3 = setInterval(() => {
      const data = JSON.stringify({ workingset: 1 + (Math.random() * 90) });
      sendTelemetry(client, data, index3, null).catch((err) => console.log('error ', err.toString()));
      index3 += 1;
    }, 6000);

    // attach a standard input exit listener
    exitListener(client);

    try {
      resultTwin = await client.getTwin();
      // Only report readable properties
      const patchRoot = helperCreateReportedPropertiesPatch({ serialNumber: serialNumber }, null);
      const patchThermostat1Info = helperCreateReportedPropertiesPatch({
        maxTempSinceLastReboot: thermostat1.getMaxTemperatureValue(),
      }, thermostat1ComponentName);

      const patchThermostat2Info = helperCreateReportedPropertiesPatch({
        maxTempSinceLastReboot: thermostat2.getMaxTemperatureValue(),
      }, thermostat2ComponentName);

      const patchDeviceInfo = helperCreateReportedPropertiesPatch({
        manufacturer: 'Contoso Device Corporation',
        model: 'Contoso 47-turbo',
        swVersion: '10.89',
        osName: 'Contoso_OS',
        processorArchitecture: 'Contoso_x86',
        processorManufacturer: 'Contoso Industries',
        totalStorage: 65000,
        totalMemory: 640,
      }, deviceInfoComponentName);

      // the below things can only happen once the twin is there
      updateComponentReportedProperties(resultTwin, patchRoot, null);
      updateComponentReportedProperties(resultTwin, patchThermostat1Info, thermostat1ComponentName);
      updateComponentReportedProperties(resultTwin, patchThermostat2Info, thermostat2ComponentName);
      updateComponentReportedProperties(resultTwin, patchDeviceInfo, deviceInfoComponentName);
      desiredPropertyPatchListener(resultTwin, [thermostat1ComponentName, thermostat2ComponentName, deviceInfoComponentName]);
    } catch (err) {
      console.error('could not retrieve twin or report twin properties\n' + err.toString());
    }
  } catch (err) {
    console.error('could not connect Plug and Play client or could not attach interval function for telemetry\n' + err.toString());
  }
}

provisionDevice 함수는 디바이스에서 DPS를 사용하여 IoT Central에 등록하고 연결하는 방법을 보여줍니다. 페이로드에는 IoT Central이 디바이스 템플릿에 디바이스를 할당하는 데 사용하는 모델 ID가 포함됩니다.

async function provisionDevice(payload) {
  var provSecurityClient = new SymmetricKeySecurityClient(registrationId, symmetricKey);
  var provisioningClient = ProvisioningDeviceClient.create(provisioningHost, idScope, new ProvProtocol(), provSecurityClient);

  if (payload) {
    provisioningClient.setProvisioningPayload(payload);
  }

  try {
    let result = await provisioningClient.register();
    deviceConnectionString = 'HostName=' + result.assignedHub + ';DeviceId=' + result.deviceId + ';SharedAccessKey=' + symmetricKey;
    console.log('registration succeeded');
    console.log('assigned hub=' + result.assignedHub);
    console.log('deviceId=' + result.deviceId);
    console.log('payload=' + JSON.stringify(result.payload));
  } catch (err) {
    console.error("error registering device: " + err.toString());
  }
}

sendTelemetry 함수는 디바이스에서 IoT Central에 온도 원격 분석을 보내는 방법을 보여줍니다. 구성 요소의 원격 분석을 위해 구성 요소 이름을 사용하여 $.sub라는 속성을 추가합니다.

async function sendTelemetry(deviceClient, data, index, componentName) {
  if componentName) {
    console.log('Sending telemetry message %d from component: %s ', index, componentName);
  } else {
    console.log('Sending telemetry message %d from root interface', index);
  }
  const msg = new Message(data);
  if (componentName) {
    msg.properties.add(messageSubjectProperty, componentName);
  }
  msg.contentType = 'application/json';
  msg.contentEncoding = 'utf-8';
  await deviceClient.sendEvent(msg);
}

main 메서드는 helperCreateReportedPropertiesPatch라는 도우미 메서드를 사용하여 속성 업데이트 메시지를 만듭니다. 이 메서드는 선택적 매개 변수를 사용하여 속성을 보내는 구성 요소를 지정합니다.

const helperCreateReportedPropertiesPatch = (propertiesToReport, componentName) => {
  let patch;
  if (!!(componentName)) {
    patch = { };
    propertiesToReport.__t = 'c';
    patch[componentName] = propertiesToReport;
  } else {
    patch = { };
    patch = propertiesToReport;
  }
  if (!!(componentName)) {
    console.log('The following properties will be updated for component: ' + componentName);
  } else {
    console.log('The following properties will be updated for root interface.');
  }
  console.log(patch);
  return patch;
};

main 메서드는 다음 메서드를 사용하여 IoT Central의 쓰기 가능 속성에 대한 업데이트를 처리합니다. 메서드가 버전 및 상태 코드를 사용하여 응답을 빌드하는 방법을 확인합니다.

const desiredPropertyPatchListener = (deviceTwin, componentNames) => {
  deviceTwin.on('properties.desired', (delta) => {
    console.log('Received an update for device with value: ' + JSON.stringify(delta));
    Object.entries(delta).forEach(([key, values]) => {
      const version = delta.$version;
      if (!!(componentNames) && componentNames.includes(key)) { // then it is a component we are expecting
        const componentName = key;
        const patchForComponents = { [componentName]: {} };
        Object.entries(values).forEach(([propertyName, propertyValue]) => {
          if (propertyName !== '__t' && propertyName !== '$version') {
            console.log('Will update property: ' + propertyName + ' to value: ' + propertyValue + ' of component: ' + componentName);
            const propertyContent = { value: propertyValue };
            propertyContent.ac = 200;
            propertyContent.ad = 'Successfully executed patch';
            propertyContent.av = version;
            patchForComponents[componentName][propertyName] = propertyContent;
          }
        });
        updateComponentReportedProperties(deviceTwin, patchForComponents, componentName);
      }
      else if  (key !== '$version') { // individual property for root
        const patchForRoot = { };
        console.log('Will update property: ' + key + ' to value: ' + values + ' for root');
        const propertyContent = { value: values };
        propertyContent.ac = 200;
        propertyContent.ad = 'Successfully executed patch';
        propertyContent.av = version;
        patchForRoot[key] = propertyContent;
        updateComponentReportedProperties(deviceTwin, patchForRoot, null);
      }
    });
  });
};

main 메서드는 다음 메서드를 사용하여 IoT Central의 명령을 처리합니다.

const commandHandler = async (request, response) => {
  helperLogCommandRequest(request);
  switch (request.methodName) {
  case commandNameGetMaxMinReport1: {
    await sendCommandResponse(request, response, 200, thermostat1.getMaxMinReportObject());
    break;
  }
  case commandNameGetMaxMinReport2: {
    await sendCommandResponse(request, response, 200, thermostat2.getMaxMinReportObject());
    break;
  }
  case commandNameReboot: {
    await sendCommandResponse(request, response, 200, 'reboot response');
    break;
  }
  default:
    await sendCommandResponse(request, response, 404, 'unknown method');
    break;
  }
};

const sendCommandResponse = async (request, response, status, payload) => {
  try {
    await response.send(status, payload);
    console.log('Response to method: ' + request.methodName + ' sent successfully.' );
  } catch (err) {
    console.error('An error occurred when sending a method response:\n' + err.toString());
  }
};

연결 정보 가져오기

이 자습서의 뒷부분에서 샘플 디바이스 애플리케이션을 실행하는 경우 다음과 같은 구성 값이 필요합니다.

  • ID 범위: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹으로 이동합니다. ID 범위 값을 기록해 둡니다.
  • 그룹 기본 키: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹 > SAS-IoT-Devices로 이동합니다. 공유 액세스 서명 기본 키 값을 기록해 둡니다.

Azure Cloud Shell을 사용하여 검색한 그룹 기본 키에서 디바이스 키를 생성합니다.

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

생성된 디바이스 키를 기록해 두고 이 자습서의 뒷부분에서 사용합니다.

참고 항목

이 샘플을 실행하기 위해 IoT Central 애플리케이션에서 디바이스를 미리 등록할 필요가 없습니다. 이 샘플에서는 IoT Central 기능을 사용하여 처음으로 연결할 때 디바이스를 자동으로 등록합니다.

코드 실행

샘플 애플리케이션을 실행하려면 명령줄 환경을 열고 pnp_temperature_controller.js 샘플 파일이 포함된 azure-iot-sdk-node/device/samples/javascript 폴더로 이동합니다.

환경 변수를 설정하여 샘플을 구성합니다. 다음 코드 조각에서는 Windows 명령 프롬프트에서 환경 변수를 설정하는 방법을 보여줍니다. bash 셸을 사용하는 경우 set 명령을 export 명령으로 바꿉니다.

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

필요한 패키지를 설치합니다.

npm install

샘플을 실행합니다.

node pnp_temperature_controller.js

다음 출력은 IoT Central에 등록하고 연결하는 디바이스를 보여줍니다. 그런 다음, 샘플은 원격 분석 전송을 시작하기 전에 두 자동 온도 조절기 구성 요소의 maxTempSinceLastReboot 속성을 보냅니다.

registration succeeded
assigned hub=iotc-....azure-devices.net
deviceId=sample-device-01
payload=undefined
Connecting using connection string: HostName=iotc-....azure-devices.net;DeviceId=sample-device-01;SharedAccessKey=qdv...IpAo=
Enabling the commands on the client
Please enter q or Q to exit sample.
The following properties will be updated for root interface.
{ serialNumber: 'alwinexlepaho8329' }
The following properties will be updated for component: thermostat1
{ thermostat1: { maxTempSinceLastReboot: 1.5902294191855972, __t: 'c' } }
The following properties will be updated for component: thermostat2
{ thermostat2: { maxTempSinceLastReboot: 16.181771928614545, __t: 'c' } }
The following properties will be updated for component: deviceInformation
{ deviceInformation:
   { manufacturer: 'Contoso Device Corporation',
     model: 'Contoso 47-turbo',
     swVersion: '10.89',
     osName: 'Contoso_OS',
     processorArchitecture: 'Contoso_x86',
     processorManufacturer: 'Contoso Industries',
     totalStorage: 65000,
     totalMemory: 640,
     __t: 'c' } }
executed sample
Received an update for device with value: {"$version":1}
Properties have been reported for component: thermostat1
Properties have been reported for component: thermostat2
Properties have been reported for component: deviceInformation
Properties have been reported for root interface.
Sending telemetry message 0 from component: thermostat1 
Sending telemetry message 0 from component: thermostat2 
Sending telemetry message 0 from root interface

Azure IoT Central 애플리케이션의 운영자는 다음을 수행할 수 있습니다.

  • 개요 페이지에서 두 자동 온도 조절기 구성 요소에서 보낸 원격 분석을 봅니다.

    디바이스 개요 페이지를 보여 주는 스크린샷.

  • 정보 페이지에서 디바이스 속성을 봅니다. 이 페이지에서는 디바이스 정보 구성 요소 및 두 개의 자동 온도 조절기 구성 요소에 있는 속성을 보여 줍니다.

    디바이스 속성 보기를 보여 주는 스크린샷.

디바이스 템플릿 사용자 지정

솔루션 개발자는 온도 조절 디바이스가 연결되면 IoT Central이 자동으로 생성한 디바이스 템플릿을 사용자 지정할 수 있습니다.

디바이스와 연결된 고객 이름을 저장하는 클라우드 속성을 추가하려면 다음을 수행합니다.

  1. IoT Central 애플리케이션에서 디바이스 템플릿 페이지의 온도 조절 디바이스 템플릿으로 이동합니다.

  2. 온도 컨트롤러 모델에서 +기능 추가를 선택합니다.

  3. 표시 이름으로 고객 이름을 입력하고, 클라우드 속성기능 유형으로 선택하고, 항목을 확장하고, 문자열스키마로 선택합니다. 그런 다음 저장을 선택합니다.

Get Max-Min report 명령이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. getMaxMinReport(thermostat1)의 경우 Max-Min 보고서 가져오기thermostat1 상태 보고서로 바꿉니다.

  3. getMaxMinReport(thermostat2)의 경우 Max-Min 보고서 가져오기thermostat2 상태 보고서로 바꿉니다.

  4. 저장을 선택합니다.

대상 온도 쓰기 가능 속성이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. targetTemperature(thermostat1)의 경우 대상 온도대상 온도(1)로 바꿉니다.

  3. targetTemperature(thermostat2)의 경우 대상 온도대상 온도(2)로 바꿉니다.

  4. 저장을 선택합니다.

온도 조절 모델의 자동 온도 조절기 구성 요소에는 대상 온도 쓰기 가능 속성이 포함되고, 디바이스 템플릿에는 고객 이름 클라우드 속성이 포함됩니다. 운영자가 다음 속성을 편집하는 데 사용할 수 있는 보기를 만듭니다.

  1. 보기, 디바이스 및 클라우드 데이터를 편집하는 중 타일을 차례로 선택합니다.

  2. 양식 이름으로 속성을 입력합니다.

  3. 대상 온도(1), 대상 온도(2)고객 이름 속성을 선택합니다. 그런 다음, 섹션 추가를 선택합니다.

  4. 변경 내용을 저장합니다.

속성 값을 업데이트하기 위한 보기를 보여 주는 스크린샷

디바이스 템플릿 게시

운영자가 만든 사용자 지정을 보고 사용하려면 먼저 디바이스 템플릿을 게시해야 합니다.

자동 온도 조절 디바이스 템플릿에서 게시를 선택합니다. 이 디바이스 템플릿을 애플리케이션에 게시 패널에서 게시를 선택합니다.

이제 운영자는 속성 보기를 사용하여 속성 값을 업데이트하고 디바이스 명령 페이지에서 Get thermostat1 status reportGet thermostat2 status report라는 명령을 호출할 수 있습니다.

  • 속성 페이지에서 쓰기 가능한 속성 값을 업데이트합니다.

    디바이스 속성 업데이트를 보여 주는 스크린샷

  • 명령 페이지에서 명령을 호출합니다. 상태 보고서 명령을 실행하는 경우 다음 이후 매개 변수를 실행하기 전에 이 매개 변수의 날짜와 시간을 선택합니다.

    명령 호출을 보여 주는 스크린샷

    명령 응답을 보여 주는 스크린샷.

디바이스가 명령 및 속성 업데이트에 응답하는 방식을 확인할 수 있습니다. getMaxMinReport 명령은 thermostat2 구성 요소에 있고, reboot 명령은 기본 구성 요소에 있습니다. thermostat2 구성 요소에 대해 targetTemperature 쓰기 가능한 속성이 설정되었습니다.

Received command request for command name: thermostat2*getMaxMinReport
The command request payload is:
2021-03-26T06:00:00.000Z
Response to method: thermostat2*getMaxMinReport sent successfully.

...

Received command request for command name: reboot
The command request payload is:
10
Response to method: reboot sent successfully.

...

Received an update for device with value: {"thermostat2":{"targetTemperature":76,"__t":"c"},"$version":2}
Will update property: targetTemperature to value: 76 of component: thermostat2
Properties have been reported for component: thermostat2

코드 찾아보기

필수 조건

이 문서의 단계를 완료하려면 다음 리소스가 필요합니다.

  • Python이 설치된 개발 머신 현재 Python 버전 요구 사항에 대해서는 Azure IoT Python SDK를 확인합니다. 명령줄에서 python --version 명령을 실행하여 버전을 확인할 수 있습니다. Python은 다양한 운영 체제에 사용할 수 있습니다. 이 자습서의 지침에서는 Windows 명령 프롬프트에서 python 명령을 실행한다고 가정합니다.

  • 샘플 코드가 포함된 Python용 Microsoft Azure IoT SDK GitHub 리포지토리의 로컬 복사본입니다. 다음 링크를 사용하여 리포지토리의 복사본을 다운로드합니다. ZIP 다운로드 그런 다음, 로컬 머신의 적절한 위치에 파일의 압축을 풉니다.

코드 검토

이전에 다운로드한 Python용 Microsoft Azure IoT SDK 복사본의 텍스트 편집기에서 azure-iot-sdk-python/samples/pnp/temp_controller_with_thermostats.py 파일을 엽니다.

이 샘플은 다중 구성 요소 온도 컨트롤러 디지털 트윈 정의 언어 모델을 구현합니다.

샘플을 실행하여 IoT Central에 연결하면 DPS(Device Provisioning Service)를 사용하여 디바이스를 등록하고 연결 문자열을 생성합니다. 이 샘플은 명령줄 환경에서 필요한 DPS 연결 정보를 검색합니다.

main 함수:

  • DPS를 사용하여 디바이스를 프로비저닝합니다. 프로비저닝 정보에는 모델 ID가 포함됩니다. IoT Central은 모델 ID를 사용하여 이 디바이스에 대한 디바이스 템플릿을 식별하거나 생성합니다. 자세히 알아보려면 디바이스 템플릿에 디바이스 할당을 참조하세요.
  • 연결을 열기 전에 Device_client 개체를 만들고 dtmi:com:example:TemperatureController;2 모델 ID를 설정합니다.
  • IoT Central로 초기 속성 값을 보냅니다. pnp_helper를 사용하여 패치를 만듭니다.
  • getMaxMinReportreboot 명령에 대한 수신기를 만듭니다. 각 자동 온도 조절기 구성 요소에는 자체 getMaxMinReport 명령이 있습니다.
  • 쓰기 가능한 속성 업데이트를 수신할 속성 수신기를 만듭니다.
  • 2개의 자동 온도 조절기 구성 요소에서 온도 원격 분석을 보내고 8초마다 기본 구성 요소에서 작업 집합 원격 분석을 보내는 루프를 시작합니다.
async def main():
    switch = os.getenv("IOTHUB_DEVICE_SECURITY_TYPE")
    if switch == "DPS":
        provisioning_host = (
            os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            if os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            else "global.azure-devices-provisioning.net"
        )
        id_scope = os.getenv("IOTHUB_DEVICE_DPS_ID_SCOPE")
        registration_id = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_ID")
        symmetric_key = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_KEY")

        registration_result = await provision_device(
            provisioning_host, id_scope, registration_id, symmetric_key, model_id
        )

        if registration_result.status == "assigned":
            print("Device was assigned")
            print(registration_result.registration_state.assigned_hub)
            print(registration_result.registration_state.device_id)
            device_client = IoTHubDeviceClient.create_from_symmetric_key(
                symmetric_key=symmetric_key,
                hostname=registration_result.registration_state.assigned_hub,
                device_id=registration_result.registration_state.device_id,
                product_info=model_id,
            )
        else:
            raise RuntimeError(
                "Could not provision device. Aborting Plug and Play device connection."
            )

    elif switch == "connectionString":
        # ...

    # Connect the client.
    await device_client.connect()

    ################################################
    # Update readable properties from various components

    properties_root = pnp_helper.create_reported_properties(serialNumber=serial_number)
    properties_thermostat1 = pnp_helper.create_reported_properties(
        thermostat_1_component_name, maxTempSinceLastReboot=98.34
    )
    properties_thermostat2 = pnp_helper.create_reported_properties(
        thermostat_2_component_name, maxTempSinceLastReboot=48.92
    )
    properties_device_info = pnp_helper.create_reported_properties(
        device_information_component_name,
        swVersion="5.5",
        manufacturer="Contoso Device Corporation",
        model="Contoso 4762B-turbo",
        osName="Mac Os",
        processorArchitecture="x86-64",
        processorManufacturer="Intel",
        totalStorage=1024,
        totalMemory=32,
    )

    property_updates = asyncio.gather(
        device_client.patch_twin_reported_properties(properties_root),
        device_client.patch_twin_reported_properties(properties_thermostat1),
        device_client.patch_twin_reported_properties(properties_thermostat2),
        device_client.patch_twin_reported_properties(properties_device_info),
    )

    ################################################
    # Get all the listeners running
    print("Listening for command requests and property updates")

    global THERMOSTAT_1
    global THERMOSTAT_2
    THERMOSTAT_1 = Thermostat(thermostat_1_component_name, 10)
    THERMOSTAT_2 = Thermostat(thermostat_2_component_name, 10)

    listeners = asyncio.gather(
        execute_command_listener(
            device_client, method_name="reboot", user_command_handler=reboot_handler
        ),
        execute_command_listener(
            device_client,
            thermostat_1_component_name,
            method_name="getMaxMinReport",
            user_command_handler=max_min_handler,
            create_user_response_handler=create_max_min_report_response,
        ),
        execute_command_listener(
            device_client,
            thermostat_2_component_name,
            method_name="getMaxMinReport",
            user_command_handler=max_min_handler,
            create_user_response_handler=create_max_min_report_response,
        ),
        execute_property_listener(device_client),
    )

    ################################################
    # Function to send telemetry every 8 seconds

    async def send_telemetry():
        print("Sending telemetry from various components")

        while True:
            curr_temp_ext = random.randrange(10, 50)
            THERMOSTAT_1.record(curr_temp_ext)

            temperature_msg1 = {"temperature": curr_temp_ext}
            await send_telemetry_from_temp_controller(
                device_client, temperature_msg1, thermostat_1_component_name
            )

            curr_temp_int = random.randrange(10, 50)  # Current temperature in Celsius
            THERMOSTAT_2.record(curr_temp_int)

            temperature_msg2 = {"temperature": curr_temp_int}

            await send_telemetry_from_temp_controller(
                device_client, temperature_msg2, thermostat_2_component_name
            )

            workingset_msg3 = {"workingSet": random.randrange(1, 100)}
            await send_telemetry_from_temp_controller(device_client, workingset_msg3)

    send_telemetry_task = asyncio.ensure_future(send_telemetry())

    # ...

provision_device 함수는 DPS를 사용하여 디바이스를 프로비저닝하고 IoT Central에 등록합니다. 이 함수에는 IoT Central이 프로비저닝 페이로드에서 디바이스를 디바이스 템플릿에 할당하는 데 사용하는 디바이스 모델 ID가 포함됩니다.

async def provision_device(provisioning_host, id_scope, registration_id, symmetric_key, model_id):
    provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key(
        provisioning_host=provisioning_host,
        registration_id=registration_id,
        id_scope=id_scope,
        symmetric_key=symmetric_key,
    )

    provisioning_device_client.provisioning_payload = {"modelId": model_id}
    return await provisioning_device_client.register()

execute_command_listener 함수는 명령 요청을 처리하고, 디바이스에서 자동 온도 조절기 구성 요소에 대한 getMaxMinReport 명령을 수신할 때 max_min_handler 함수를 실행하고 디바이스에서 reboot 명령을 수신할 때 reboot_handler 함수를 실행합니다. pnp_helper 모듈을 사용하여 응답을 작성합니다.

async def execute_command_listener(
    device_client,
    component_name=None,
    method_name=None,
    user_command_handler=None,
    create_user_response_handler=None,
):
    while True:
        if component_name and method_name:
            command_name = component_name + "*" + method_name
        elif method_name:
            command_name = method_name
        else:
            command_name = None

        command_request = await device_client.receive_method_request(command_name)
        print("Command request received with payload")
        values = command_request.payload
        print(values)

        if user_command_handler:
            await user_command_handler(values)
        else:
            print("No handler provided to execute")

        (response_status, response_payload) = pnp_helper.create_response_payload_with_status(
            command_request, method_name, create_user_response=create_user_response_handler
        )

        command_response = MethodResponse.create_from_method_request(
            command_request, response_status, response_payload
        )

        try:
            await device_client.send_method_response(command_response)
        except Exception:
            print("responding to the {command} command failed".format(command=method_name))

async def execute_property_listener는 자동 온도 조절기 구성 요소에 대한 targetTemperature와 같은 쓰기 가능한 속성 업데이트를 처리하고 JSON 응답을 생성합니다. pnp_helper 모듈을 사용하여 응답을 작성합니다.

async def execute_property_listener(device_client):
    while True:
        patch = await device_client.receive_twin_desired_properties_patch()  # blocking call
        print(patch)
        properties_dict = pnp_helper.create_reported_properties_from_desired(patch)

        await device_client.patch_twin_reported_properties(properties_dict)

send_telemetry_from_temp_controller 함수는 원격 분석 메시지를 자동 온도 조절기 구성 요소에서 IoT Central로 보냅니다. pnp_helper 모듈을 사용하여 메시지를 작성합니다.

async def send_telemetry_from_temp_controller(device_client, telemetry_msg, component_name=None):
    msg = pnp_helper.create_telemetry(telemetry_msg, component_name)
    await device_client.send_message(msg)
    print("Sent message")
    print(msg)
    await asyncio.sleep(5)

연결 정보 가져오기

이 자습서의 뒷부분에서 샘플 디바이스 애플리케이션을 실행하는 경우 다음과 같은 구성 값이 필요합니다.

  • ID 범위: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹으로 이동합니다. ID 범위 값을 기록해 둡니다.
  • 그룹 기본 키: IoT Central 애플리케이션에서 권한 > 디바이스 연결 그룹 > SAS-IoT-Devices로 이동합니다. 공유 액세스 서명 기본 키 값을 기록해 둡니다.

Azure Cloud Shell을 사용하여 검색한 그룹 기본 키에서 디바이스 키를 생성합니다.

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

생성된 디바이스 키를 기록해 두고 이 자습서의 뒷부분에서 사용합니다.

참고 항목

이 샘플을 실행하기 위해 IoT Central 애플리케이션에서 디바이스를 미리 등록할 필요가 없습니다. 이 샘플에서는 IoT Central 기능을 사용하여 처음으로 연결할 때 디바이스를 자동으로 등록합니다.

코드 실행

샘플 애플리케이션을 실행하려면 명령줄 환경을 열고 temp_controller_with_thermostats.py 샘플 파일이 포함된 azure-iot-sdk-python-2/samples/pnp 폴더로 이동합니다.

환경 변수를 설정하여 샘플을 구성합니다. 다음 코드 조각에서는 Windows 명령 프롬프트에서 환경 변수를 설정하는 방법을 보여줍니다. bash 셸을 사용하는 경우 set 명령을 export 명령으로 바꿉니다.

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

필요한 패키지를 설치합니다.

pip install azure-iot-device

샘플을 실행합니다.

python temp_controller_with_thermostats.py

다음 출력은 IoT Central에 등록하고 연결하는 디바이스를 보여줍니다. 샘플은 원격 분석 전송을 시작하기 전에 두 자동 온도 조절기 구성 요소의 maxTempSinceLastReboot 속성을 보냅니다.

Device was assigned
iotc-60a.....azure-devices.net
sample-device-01
Updating pnp properties for root interface
{'serialNumber': 'alohomora'}
Updating pnp properties for thermostat1
{'thermostat1': {'maxTempSinceLastReboot': 98.34, '__t': 'c'}}
Updating pnp properties for thermostat2
{'thermostat2': {'maxTempSinceLastReboot': 48.92, '__t': 'c'}}
Updating pnp properties for deviceInformation
{'deviceInformation': {'swVersion': '5.5', 'manufacturer': 'Contoso Device Corporation', 'model': 'Contoso 4762B-turbo', 'osName': 'Mac Os', 'processorArchitecture': 'x86-64', 'processorManufacturer': 'Intel', 'totalStorage': 1024, 'totalMemory': 32, '__t': 'c'}}
Listening for command requests and property updates
Press Q to quit
Sending telemetry from various components
Sent message
{"temperature": 27}
Sent message
{"temperature": 17}
Sent message
{"workingSet": 13}

Azure IoT Central 애플리케이션의 운영자는 다음을 수행할 수 있습니다.

  • 개요 페이지에서 두 자동 온도 조절기 구성 요소에서 보낸 원격 분석을 봅니다.

    디바이스 개요 페이지를 보여 주는 스크린샷.

  • 정보 페이지에서 디바이스 속성을 봅니다. 이 페이지에서는 디바이스 정보 구성 요소 및 두 개의 자동 온도 조절기 구성 요소에 있는 속성을 보여 줍니다.

    디바이스 속성 보기를 보여 주는 스크린샷.

디바이스 템플릿 사용자 지정

솔루션 개발자는 온도 조절 디바이스가 연결되면 IoT Central이 자동으로 생성한 디바이스 템플릿을 사용자 지정할 수 있습니다.

디바이스와 연결된 고객 이름을 저장하는 클라우드 속성을 추가하려면 다음을 수행합니다.

  1. IoT Central 애플리케이션에서 디바이스 템플릿 페이지의 온도 조절 디바이스 템플릿으로 이동합니다.

  2. 온도 컨트롤러 모델에서 +기능 추가를 선택합니다.

  3. 표시 이름으로 고객 이름을 입력하고, 클라우드 속성기능 유형으로 선택하고, 항목을 확장하고, 문자열스키마로 선택합니다. 그런 다음 저장을 선택합니다.

Get Max-Min report 명령이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. getMaxMinReport(thermostat1)의 경우 Max-Min 보고서 가져오기thermostat1 상태 보고서로 바꿉니다.

  3. getMaxMinReport(thermostat2)의 경우 Max-Min 보고서 가져오기thermostat2 상태 보고서로 바꿉니다.

  4. 저장을 선택합니다.

대상 온도 쓰기 가능 속성이 IoT Central 애플리케이션에 표시되는 방식을 사용자 지정하려면 다음을 수행합니다.

  1. 디바이스 템플릿 페이지에서 온도 컨트롤러 디바이스 템플릿으로 이동합니다.

  2. targetTemperature(thermostat1)의 경우 대상 온도대상 온도(1)로 바꿉니다.

  3. targetTemperature(thermostat2)의 경우 대상 온도대상 온도(2)로 바꿉니다.

  4. 저장을 선택합니다.

온도 조절 모델의 자동 온도 조절기 구성 요소에는 대상 온도 쓰기 가능 속성이 포함되고, 디바이스 템플릿에는 고객 이름 클라우드 속성이 포함됩니다. 운영자가 다음 속성을 편집하는 데 사용할 수 있는 보기를 만듭니다.

  1. 보기, 디바이스 및 클라우드 데이터를 편집하는 중 타일을 차례로 선택합니다.

  2. 양식 이름으로 속성을 입력합니다.

  3. 대상 온도(1), 대상 온도(2)고객 이름 속성을 선택합니다. 그런 다음, 섹션 추가를 선택합니다.

  4. 변경 내용을 저장합니다.

속성 값을 업데이트하기 위한 보기를 보여 주는 스크린샷

디바이스 템플릿 게시

운영자가 만든 사용자 지정을 보고 사용하려면 먼저 디바이스 템플릿을 게시해야 합니다.

자동 온도 조절 디바이스 템플릿에서 게시를 선택합니다. 이 디바이스 템플릿을 애플리케이션에 게시 패널에서 게시를 선택합니다.

이제 운영자는 속성 보기를 사용하여 속성 값을 업데이트하고 디바이스 명령 페이지에서 Get thermostat1 status reportGet thermostat2 status report라는 명령을 호출할 수 있습니다.

  • 속성 페이지에서 쓰기 가능한 속성 값을 업데이트합니다.

    디바이스 속성 업데이트를 보여 주는 스크린샷

  • 명령 페이지에서 명령을 호출합니다. 상태 보고서 명령을 실행하는 경우 다음 이후 매개 변수를 실행하기 전에 이 매개 변수의 날짜와 시간을 선택합니다.

    명령 호출을 보여 주는 스크린샷

    명령 응답을 보여 주는 스크린샷.

다음과 같이 디바이스가 명령 및 속성 업데이트에 응답하는 방식을 확인할 수 있습니다.

{'thermostat1': {'targetTemperature': 67, '__t': 'c'}, '$version': 2}
the data in the desired properties patch was: {'thermostat1': {'targetTemperature': 67, '__t': 'c'}, '$version': 2}
Values received are :-
{'targetTemperature': 67, '__t': 'c'}
Sent message

...

Command request received with payload
2021-03-31T05:00:00.000Z
Will return the max, min and average temperature from the specified time 2021-03-31T05:00:00.000Z to the current time
Done generating
{"avgTemp": 4.0, "endTime": "2021-03-31T12:29:48.322427", "maxTemp": 18, "minTemp": null, "startTime": "2021-03-31T12:28:28.322381"}

원시 데이터 보기

원시 데이터 보기를 사용하여 디바이스에서 IoT Central로 전송하는 원시 데이터를 검사할 수 있습니다.

원시 데이터 보기를 보여 주는 스크린샷

이 보기에서 표시할 열을 선택하고 표시할 시간 범위를 설정할 수 있습니다. 모델링되지 않은 데이터 열에는 디바이스 템플릿의 속성 또는 원격 분석 정의와 일치하지 않는 디바이스 데이터가 표시됩니다.

리소스 정리

더 이상 IoT Central 빠른 시작 또는 자습서를 완료할 계획이 없는 경우 IoT Central 애플리케이션을 삭제할 수 있습니다.

  1. IoT Central 애플리케이션에서 애플리케이션 > 관리로 이동합니다.
  2. 삭제를 클릭한 다음, 작업을 확인합니다.

다음 단계

IoT Central 자습서 세트를 계속 진행하고 IoT Central 솔루션 빌드에 대해 자세히 알아보려면 다음을 참조하세요.