디바이스를 원격 모니터링 솔루션 가속기에 연결(Windows)

이 자습서에서는 원격 모니터링 솔루션 가속기에 다음과 같은 원격 분석을 보내는 냉각기 디바이스를 구현합니다.

  • 온도
  • 압력
  • 습도

간단히 하기 위해 코드는 냉각기에 대한 샘플 원격 분석 값을 생성합니다. 실제 센서를 디바이스에 연결하고 실제 원격 분석을 보내 샘플을 확장할 수 있습니다.

샘플 디바이스는 또한:

  • 솔루션에 메타데이터를 보내 해당 기능을 설명합니다.
  • 솔루션의 디바이스 페이지에서 트리거된 작업에 응답합니다.
  • 솔루션의 디바이스 페이지에서 보내는 구성 변경 내용에 응답합니다.

이 자습서를 완료하려면 활성 Azure 계정이 필요합니다. 계정이 없는 경우 몇 분 만에 평가판 계정을 만들 수 있습니다. 자세한 내용은 Azure 평가판을 참조하세요.

시작하기 전에

디바이스에 대한 코드를 작성하기 전에 원격 모니터링 솔루션 가속기를 배포하고 이 솔루션에 새 실제 디바이스를 추가합니다.

원격 모니터링 솔루션 가속기 배포

이 자습서에서 만드는 냉각기 디바이스는 원격 모니터링 솔루션 가속기의 인스턴스에 데이터를 전송합니다. Azure 계정에서 원격 모니터링 솔루션 가속기를 미리 프로비전하지 않은 경우 원격 모니터링 솔루션 가속기 배포를 참조하세요.

원격 모니터링 솔루션의 배포 프로세스가 완료되면 시작을 클릭하여 브라우저에서 솔루션 대시보드를 엽니다.

솔루션 대시보드

디바이스를 원격 모니터링 솔루션에 추가

참고

솔루션에 디바이스가 이미 추가되어 있으면 이 단계를 건너뜁니다. 그러나 다음 단계에서는 디바이스 연결 문자열이 필요합니다. Azure Portal에서 검색하거나 az iot CLI 도구를 사용하여 디바이스 연결 문자열을 검색할 수 있습니다.

솔루션 가속기에 연결하는 디바이스는 유효한 자격 증명을 사용하여 IoT Hub에 자신을 식별할 수 있어야 합니다. 솔루션에 디바이스를 추가할 때 자격 증명이 포함된 디바이스 연결 문자열을 저장할 기회가 있습니다. 이 자습서의 뒷부분에서는 클라이언트 애플리케이션에 디바이스 연결 문자열을 포함시킵니다.

원격 모니터링 솔루션에 디바이스를 추가하려면 솔루션의 Device Explorer 페이지에서 다음 단계를 완료합니다.

  1. + 새 디바이스를 선택한 다음, 디바이스 유형으로 실제를 선택합니다.

    실제 디바이스 추가

  2. 디바이스 ID로 물리적 냉각기를 입력합니다. 대칭 키자동 생성 키 옵션을 선택합니다.

    디바이스 옵션 선택

  3. 적용을 선택합니다. 그런 다음, 디바이스 ID, 기본 키, 연결 문자열 기본 키 값을 메모합니다.

    자격 증명 검색

이제 실제 디바이스를 원격 모니터링 솔루션 가속기에 추가하고 해당 디바이스 연결 문자열을 적어 두었습니다. 다음 섹션에서는 디바이스 연결 문자열을 사용하여 솔루션에 연결하는 클라이언트 애플리케이션을 구현합니다.

클라이언트 애플리케이션은 기본 제공 냉각기 디바이스 모델을 구현합니다. 솔루션 가속기 디바이스 모델은 디바이스에 대해 다음을 지정합니다.

  • 디바이스가 솔루션에 보고하는 속성 예를 들어, 냉각기 디바이스는 해당 펌웨어 및 위치에 대한 정보를 보고합니다.
  • 디바이스가 솔루션에 보내는 원격 분석의 유형 예를 들어, 냉각기 디바이스는 온도, 습도 및 압력 값을 보냅니다.
  • 디바이스에서 실행되도록 솔루션에서 예약할 수 있는 메서드 예를 들어, 냉각기 디바이스는 Reboot, FirmwareUpdate, EmergencyValveReleaseIncreasePressure 메서드를 구현해야 합니다.

이 자습서는 원격 모니터링 솔루션 가속기에 실제 디바이스를 연결하는 방법을 보여 줍니다.

제한된 디바이스에서 실행되는 대부분의 임베디드 애플리케이션과 마찬가지로, 디바이스 애플리케이션의 클라이언트 코드는 C로 작성됩니다. 이 자습서에서는 Windows를 실행하는 머신에서 디바이스 클라이언트 애플리케이션을 빌드합니다.

디바이스를 시뮬레이션하려면 새 시뮬레이션된 디바이스 만들기 및 테스트를 참조하세요.

필수 구성 요소

이 방법 가이드의 단계를 완료하려면 Windows 개발 환경 설정의 단계에 따라 필요한 개발 도구 및 라이브러리를 Windows 머신에 추가합니다.

코드 보기

이 가이드에서 사용되는 샘플 코드는 Azure IoT C SDK GitHub 리포지토리에서 제공됩니다.

소스 코드 다운로드 및 프로젝트 준비

프로젝트를 준비하려면 GitHub에서 Azure IoT C SDK 리포지토리를 복제합니다.

샘플은 samples/solutions/remote_monitoring_client 폴더에 있습니다.

samples/solutions/remote_monitoring_client 폴더의 remote_monitoring.c 파일을 텍스트 편집기에서 엽니다.

코드 연습

이 섹션에서는 샘플 코드의 몇 가지 주요 부분 및 원격 모니터링 솔루션 가속기와의 관계에 대해 설명합니다.

다음 코드 조각은 디바이스의 기능을 설명하는 보고된 속성이 정의된 방식을 보여 줍니다. 이러한 속성은 다음과 같습니다.

  • 솔루션 가속기가 디바이스를 맵에 추가할 수 있도록 하는 디바이스의 위치입니다.
  • 현재 펌웨어 버전입니다.
  • 디바이스에서 지원하는 메서드 목록입니다.
  • 디바이스에서 보낸 원격 분석 메시지의 스키마입니다.
typedef struct MESSAGESCHEMA_TAG
{
    char* name;
    char* format;
    char* fields;
} MessageSchema;

typedef struct TELEMETRYSCHEMA_TAG
{
    MessageSchema messageSchema;
} TelemetrySchema;

typedef struct TELEMETRYPROPERTIES_TAG
{
    TelemetrySchema temperatureSchema;
    TelemetrySchema humiditySchema;
    TelemetrySchema pressureSchema;
} TelemetryProperties;

typedef struct CHILLER_TAG
{
    // Reported properties
    char* protocol;
    char* supportedMethods;
    char* type;
    char* firmware;
    FIRMWARE_UPDATE_STATUS firmwareUpdateStatus;
    char* location;
    double latitude;
    double longitude;
    TelemetryProperties telemetry;

    // Manage firmware update process
    char* new_firmware_version;
    char* new_firmware_URI;
} Chiller;

샘플에는 Parson 라이브러리를 사용하여 이 데이터 구조를 직렬화하는 serializeToJson 함수가 포함되어 있습니다.

샘플에는 클라이언트가 솔루션 가속기와 상호 작용할 때 정보를 콘솔에 출력하는 몇 가지 콜백 함수가 포함되어 있습니다.

  • connection_status_callback
  • send_confirm_callback
  • reported_state_callback
  • device_method_callback

다음 코드 조각은 device_method_callback 함수를 보여 줍니다. 이 함수는 솔루션 가속기에서 메서드 호출이 수신될 때 수행할 작업을 결정합니다. 함수는 userContextCallback 매개 변수에서 Chiller 데이터 구조에 대한 참조를 받습니다. userContextCallback의 값은 main 함수에서 콜백 함수를 구성할 때 설정됩니다.

static int device_method_callback(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* response_size, void* userContextCallback)
{
    Chiller *chiller = (Chiller *)userContextCallback;

    int result;

    (void)printf("Direct method name:    %s\r\n", method_name);

    (void)printf("Direct method payload: %.*s\r\n", (int)size, (const char*)payload);

    if (strcmp("Reboot", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Rebooting\" }")
    }
    else if (strcmp("EmergencyValveRelease", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Releasing emergency valve\" }")
    }
    else if (strcmp("IncreasePressure", method_name) == 0)
    {
        MESSAGERESPONSE(201, "{ \"Response\": \"Increasing pressure\" }")
    }
    else if (strcmp("FirmwareUpdate", method_name) == 0)
    {
        if (chiller->firmwareUpdateStatus != IDLE)
        {
            (void)printf("Attempt to invoke firmware update out of order\r\n");
            MESSAGERESPONSE(400, "{ \"Response\": \"Attempting to initiate a firmware update out of order\" }")
        }
        else
        {
            getFirmwareUpdateValues(chiller, payload);

            if (chiller->new_firmware_version != NULL && chiller->new_firmware_URI != NULL)
            {
                // Create a thread for the long-running firmware update process.
                THREAD_HANDLE thread_apply;
                THREADAPI_RESULT t_result = ThreadAPI_Create(&thread_apply, do_firmware_update, chiller);
                if (t_result == THREADAPI_OK)
                {
                    (void)printf("Starting firmware update thread\r\n");
                    MESSAGERESPONSE(201, "{ \"Response\": \"Starting firmware update thread\" }")
                }
                else
                {
                    (void)printf("Failed to start firmware update thread\r\n");
                    MESSAGERESPONSE(500, "{ \"Response\": \"Failed to start firmware update thread\" }")
                }
            }
            else
            {
                (void)printf("Invalid method payload\r\n");
                MESSAGERESPONSE(400, "{ \"Response\": \"Invalid payload\" }")
            }
        }
    }
    else
    {
        // All other entries are ignored.
        (void)printf("Method not recognized\r\n");
        MESSAGERESPONSE(400, "{ \"Response\": \"Method not recognized\" }")
    }

    return result;
}

솔루션 가속기가 펌웨어 업데이트 메서드를 호출할 때 샘플은 JSON 페이로드를 역직렬화하고 백그라운드 스레드를 시작하여 업데이트 프로세스를 완료합니다. 다음 코드 조각은 스레드에서 실행되는 do_firmware_update를 보여 줍니다.

/*
 This is a thread allocated to process a long-running device method call.
 It uses device twin reported properties to communicate status values
 to the Remote Monitoring solution accelerator.
*/
static int do_firmware_update(void *param)
{
    Chiller *chiller = (Chiller *)param;
    printf("Running simulated firmware update: URI: %s, Version: %s\r\n", chiller->new_firmware_URI, chiller->new_firmware_version);

    printf("Simulating download phase...\r\n");
    chiller->firmwareUpdateStatus = DOWNLOADING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating apply phase...\r\n");
    chiller->firmwareUpdateStatus = APPLYING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    printf("Simulating reboot phase...\r\n");
    chiller->firmwareUpdateStatus = REBOOTING;
    sendChillerReportedProperties(chiller);

    ThreadAPI_Sleep(5000);

    size_t size = strlen(chiller->new_firmware_version) + 1;
    (void)memcpy(chiller->firmware, chiller->new_firmware_version, size);

    chiller->firmwareUpdateStatus = IDLE;
    sendChillerReportedProperties(chiller);

    return 0;
}

다음 코드 조각은 클라이언트가 솔루션 가속기에 원격 분석 메시지를 보내는 방법을 보여 줍니다. 메시지 속성에는 솔루션 가속기가 대시보드에 원격 분석을 표시하는 데 도움이 되는 메시지 스키마가 포함됩니다.

static void send_message(IOTHUB_DEVICE_CLIENT_HANDLE handle, char* message, char* schema)
{
    IOTHUB_MESSAGE_HANDLE message_handle = IoTHubMessage_CreateFromString(message);
    if (message_handle != NULL)
    {
        // Set system properties
        (void)IoTHubMessage_SetMessageId(message_handle, "MSG_ID");
        (void)IoTHubMessage_SetCorrelationId(message_handle, "CORE_ID");
        (void)IoTHubMessage_SetContentTypeSystemProperty(message_handle, "application%2fjson");
        (void)IoTHubMessage_SetContentEncodingSystemProperty(message_handle, "utf-8");

        // Set application properties
        MAP_HANDLE propMap = IoTHubMessage_Properties(message_handle);
        (void)Map_AddOrUpdate(propMap, "$$MessageSchema", schema);
        (void)Map_AddOrUpdate(propMap, "$$ContentType", "JSON");

        time_t now = time(0);
        struct tm* timeinfo;
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4996) /* Suppress warning about possible unsafe function in Visual Studio */
#endif
        timeinfo = gmtime(&now);
#ifdef _MSC_VER
#pragma warning(pop)
#endif
        char timebuff[50];
        strftime(timebuff, 50, "%Y-%m-%dT%H:%M:%SZ", timeinfo);
        (void)Map_AddOrUpdate(propMap, "$$CreationTimeUtc", timebuff);

        IoTHubDeviceClient_SendEventAsync(handle, message_handle, send_confirm_callback, NULL);

        IoTHubMessage_Destroy(message_handle);
    }
}

샘플의 main 함수는 다음을 수행합니다.

  • SDK 하위 시스템을 초기화하고 종료합니다.
  • Chiller 데이터 구조를 초기화합니다.
  • 보고된 속성을 솔루션 가속기에 보냅니다.
  • 디바이스 메서드 콜백 함수를 구성합니다.
  • 시뮬레이션된 원격 분석값을 솔루션 가속기에 보냅니다.
int main(void)
{
    srand((unsigned int)time(NULL));
    double minTemperature = 50.0;
    double minPressure = 55.0;
    double minHumidity = 30.0;
    double temperature = 0;
    double pressure = 0;
    double humidity = 0;

    (void)printf("This sample simulates a Chiller device connected to the Remote Monitoring solution accelerator\r\n\r\n");

    // Used to initialize sdk subsystem
    (void)IoTHub_Init();

    (void)printf("Creating IoTHub handle\r\n");
    // Create the iothub handle here
    device_handle = IoTHubDeviceClient_CreateFromConnectionString(connectionString, MQTT_Protocol);
    if (device_handle == NULL)
    {
        (void)printf("Failure creating IotHub device. Hint: Check your connection string.\r\n");
    }
    else
    {
        // Setting connection status callback to get indication of connection to iothub
        (void)IoTHubDeviceClient_SetConnectionStatusCallback(device_handle, connection_status_callback, NULL);

        Chiller chiller;
        memset(&chiller, 0, sizeof(Chiller));
        chiller.protocol = "MQTT";
        chiller.supportedMethods = "Reboot,FirmwareUpdate,EmergencyValveRelease,IncreasePressure";
        chiller.type = "Chiller";
        size_t size = strlen(initialFirmwareVersion) + 1;
        chiller.firmware = malloc(size);
        if (chiller.firmware == NULL)
        {
            (void)printf("Chiller Firmware failed to allocate memory.\r\n");
        }
        else
        {
            memcpy(chiller.firmware, initialFirmwareVersion, size);
            chiller.firmwareUpdateStatus = IDLE;
            chiller.location = "Building 44";
            chiller.latitude = 47.638928;
            chiller.longitude = -122.13476;
            chiller.telemetry.temperatureSchema.messageSchema.name = "chiller-temperature;v1";
            chiller.telemetry.temperatureSchema.messageSchema.format = "JSON";
            chiller.telemetry.temperatureSchema.messageSchema.fields = "{\"temperature\":\"Double\",\"temperature_unit\":\"Text\"}";
            chiller.telemetry.humiditySchema.messageSchema.name = "chiller-humidity;v1";
            chiller.telemetry.humiditySchema.messageSchema.format = "JSON";
            chiller.telemetry.humiditySchema.messageSchema.fields = "{\"humidity\":\"Double\",\"humidity_unit\":\"Text\"}";
            chiller.telemetry.pressureSchema.messageSchema.name = "chiller-pressure;v1";
            chiller.telemetry.pressureSchema.messageSchema.format = "JSON";
            chiller.telemetry.pressureSchema.messageSchema.fields = "{\"pressure\":\"Double\",\"pressure_unit\":\"Text\"}";

            sendChillerReportedProperties(&chiller);

            (void)IoTHubDeviceClient_SetDeviceMethodCallback(device_handle, device_method_callback, &chiller);

            while (1)
            {
                temperature = minTemperature + ((double)(rand() % 10) + 5);
                pressure = minPressure + ((double)(rand() % 10) + 5);
                humidity = minHumidity + ((double)(rand() % 20) + 5);

                if (chiller.firmwareUpdateStatus == IDLE)
                {
                    (void)printf("Sending sensor value Temperature = %f %s,\r\n", temperature, "F");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"temperature\":%.2f,\"temperature_unit\":\"F\"}", temperature);
                    send_message(device_handle, msgText, chiller.telemetry.temperatureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Pressure = %f %s,\r\n", pressure, "psig");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"pressure\":%.2f,\"pressure_unit\":\"psig\"}", pressure);
                    send_message(device_handle, msgText, chiller.telemetry.pressureSchema.messageSchema.name);


                    (void)printf("Sending sensor value Humidity = %f %s,\r\n", humidity, "%");
                    (void)sprintf_s(msgText, sizeof(msgText), "{\"humidity\":%.2f,\"humidity_unit\":\"%%\"}", humidity);
                    send_message(device_handle, msgText, chiller.telemetry.humiditySchema.messageSchema.name);
                }

                ThreadAPI_Sleep(5000);
            }

            (void)printf("\r\nShutting down\r\n");

            // Clean up the iothub sdk handle and free resources
            IoTHubDeviceClient_Destroy(device_handle);
            free(chiller.firmware);
            free(chiller.new_firmware_URI);
            free(chiller.new_firmware_version);
        }
    }
    // Shutdown the sdk subsystem
    IoTHub_Deinit();

    return 0;
}

샘플 빌드 및 실행

  1. remote_monitoring.c 파일을 편집하여 <connectionstring>을 이 방법 가이드 시작 부분에서 솔루션 가속기에 디바이스를 추가할 때 적어 둔 디바이스 연결 문자열로 바꿉니다.

  2. Windows에서 C SDK 빌드의 단계에 따라 SDK 및 원격 모니터링 클라이언트 애플리케이션을 빌드합니다.

  3. 솔루션을 빌드하는 데 사용한 명령 프롬프트에서 다음을 실행합니다.

    samples\solutions\remote_monitoring_client\Release\remote_monitoring_client.exe
    

    다음과 같은 경우 콘솔에 메시지가 표시됩니다.

    • 애플리케이션이 샘플 원격 분석 데이터를 솔루션 가속기에 보낼 때.
    • 솔루션 대시보드에서 호출된 메서드에 응답할 때.

디바이스 원격 분석 보기

솔루션의 Device Explorer 페이지에서는 디바이스에서 보낸 원격 분석을 볼 수 있습니다.

  1. Device Explorer 페이지의 디바이스 목록에서 프로비저닝한 디바이스를 선택합니다. 패널은 디바이스 원격 분석의 그림을 포함한 디바이스에 대한 정보를 표시합니다.

    디바이스 세부 정보 보기

  2. 압력을 선택하여 원격 분석 표시를 변경합니다.

    압력 원격 분석 보기

  3. 디바이스에 대한 진단 정보를 보려면 진단 아래로 스크롤합니다.

    디바이스 진단 보기

디바이스에서 작동

디바이스에서 메서드를 호출하려면 원격 모니터링 솔루션의 Device Explorer 페이지를 사용합니다. 예를 들어, 원격 모니터링 솔루션에서 냉각기 디바이스는 Reboot 메서드를 구현합니다.

  1. 디바이스를 선택하여 솔루션의 Device Explorer 페이지로 이동합니다.

  2. Device Explorer 페이지의 디바이스 목록에서 프로비저닝한 디바이스를 선택합니다.

    실제 디바이스 선택

  3. 디바이스에서 호출할 수 있는 메서드의 목록을 표시하려면 작업을 선택한 다음, 메서드를 선택합니다. 여러 디바이스에서 작업이 실행되도록 예약하려면 목록에서 여러 디바이스를 선택하면 됩니다. 작업 패널에 선택한 모든 디바이스에 공통된 메서드 형식이 표시됩니다.

  4. Reboot를 선택하고, 작업 이름을 RebootPhysicalChiller로 설정하고, 적용을 선택합니다.

    펌웨어 업데이트 예약

  5. 시뮬레이션된 디바이스가 메서드를 처리하는 동안 디바이스 코드를 실행하는 콘솔에 메시지 시퀀스가 표시됩니다.

참고

솔루션에서 작업의 상태를 추적하려면 작업 상태 보기를 선택합니다.

다음 단계

원격 모니터링 솔루션 가속기 사용자 지정 문서에서는 솔루션 가속기를 사용자 지정하는 몇 가지 방법에 대해 설명합니다.