다음을 통해 공유


기존 디바이스를 IoT 플러그 앤 플레이 디바이스로 변환하는 방법

이 문서에서는 기존 디바이스를 IoT 플러그 앤 플레이 디바이스로 변환하기 위해 수행해야 하는 단계를 간략하게 설명합니다. 모든 IoT 플러그 앤 플레이 디바이스에 필요한 모델을 만드는 방법에 대해 설명하고 디바이스가 IoT 플러그 앤 플레이 디바이스로 작동할 수 있도록 하는 데 필요한 코드를 설명합니다.

코드 샘플의 경우 이 문서는 MQTT 라이브러리를 사용하여 IoT hub에 연결하는 C 코드를 보여 줍니다. 이 문서에서 설명하는 변경 내용을 다른 언어 및 SDK를 사용하여 구현된 디바이스에 적용할 수 있습니다.

기존 디바이스를 IoT 플러그 앤 플레이 디바이스로 변환하려면 다음을 수행합니다.

  1. 디바이스 코드를 검토하여 구현하는 원격 분석, 속성 및 명령을 이해합니다.
  2. 디바이스에서 구현하는 원격 분석, 속성 및 명령을 설명하는 모델을 만듭니다.
  3. 서비스에 연결할 때 모델 ID를 알리도록 디바이스 코드를 수정합니다.

디바이스 코드 검토

디바이스에 대한 모델을 만들기 전에 디바이스의 기존 기능을 이해해야 합니다.

  • 디바이스가 정기적으로 보내는 원격 분석
  • 디바이스가 서비스와 동기화하는 읽기 전용 및 쓰기 가능한 속성
  • 디바이스가 응답하는 서비스에서 호출되는 명령

예를 들어 다양한 디바이스 기능을 구현하는 다음 디바이스 코드 조각을 검토합니다.

다음 코드 조각에서는 온도 원격 분석을 보내는 디바이스를 보여 줍니다.

#define TOPIC "devices/" DEVICEID "/messages/events/"

// ...

void Thermostat_SendCurrentTemperature()
{
  char msg[] = "{\"temperature\":25.6}";
  int msgId = rand();
  int rc = mosquitto_publish(mosq, &msgId, TOPIC, sizeof(msg) - 1, msg, 1, true);
  if (rc != MOSQ_ERR_SUCCESS)
  {
    printf("Error: %s\r\n", mosquitto_strerror(rc));
  }
}

원격 분석 필드의 이름은 temperature이고 해당 형식은 float 또는 double입니다. 이 원격 분석 형식에 대한 모델 정의는 다음 JSON과 같습니다. 자세히 알아보려면 아래의 모델 디자인을 참조하세요.

{
  "@type": [
    "Telemetry"
  ],
  "name": "temperature",
  "displayName": "Temperature",
  "description": "Temperature in degrees Celsius.",
  "schema": "double"
}

다음 코드 조각은 속성 값을 보고하는 디바이스를 보여 줍니다.

#define DEVICETWIN_MESSAGE_PATCH "$iothub/twin/PATCH/properties/reported/?$rid=patch_temp"

static void SendMaxTemperatureSinceReboot()
{
  char msg[] = "{\"maxTempSinceLastReboot\": 42.500}";
  int msgId = rand();
  int rc = mosquitto_publish(mosq, &msgId, DEVICETWIN_MESSAGE_PATCH, sizeof(msg) - 1, msg, 1, true);
  if (rc != MOSQ_ERR_SUCCESS)
  {
    printf("Error: %s\r\n", mosquitto_strerror(rc));
  }
}

속성의 이름은 maxTempSinceLastReboot이고 해당 형식은 float 또는 double입니다. 이 속성은 디바이스에서 보고되며, 디바이스는 서비스에서 이 값에 대한 업데이트를 받지 못합니다. 이 속성에 대한 모델 정의는 다음 JSON처럼 보입니다. 자세히 알아보려면 아래의 모델 디자인을 참조하세요.

{
  "@type": [
    "Property"
  ],
  "name": "maxTempSinceLastReboot",
  "schema": "double",
  "displayName": "Max temperature since last reboot.",
  "description": "Returns the max temperature since last device reboot."
}

다음 코드 조각은 서비스의 메시지에 응답하는 디바이스를 보여 줍니다.

void message_callback(struct mosquitto* mosq, void* obj, const struct mosquitto_message* message)
{
  printf("Message received: %s payload: %s \r\n", message->topic, (char*)message->payload);
  
  if (strncmp(message->topic, "$iothub/methods/POST/getMaxMinReport/?$rid=1",37) == 0)
  {
    char* pch;
    char* context;
    int msgId = 0;
    pch = strtok_s((char*)message->topic, "=",&context);
    while (pch != NULL)
    {
      pch = strtok_s(NULL, "=", &context);
      if (pch != NULL) {
        char * pEnd;
        msgId = strtol(pch,&pEnd,16 );
      }
    }
    char topic[64];
    sprintf_s(topic, "$iothub/methods/res/200/?$rid=%d", msgId);
    char msg[] = "{\"maxTemp\":83.51,\"minTemp\":77.68}";
    int rc = mosquitto_publish(mosq, &msgId, topic, sizeof(msg) - 1, msg, 1, true);
    if (rc != MOSQ_ERR_SUCCESS)
    {
      printf("Error: %s\r\n", mosquitto_strerror(rc));
    }
    delete pch;
  }

  if (strncmp(message->topic, "$iothub/twin/PATCH/properties/desired/?$version=1", 38) == 0)
  {
    char* pch;
    char* context;
    int version = 0; 
    pch = strtok_s((char*)message->topic, "=", &context);
    while (pch != NULL)
    {
      pch = strtok_s(NULL, "=", &context);
      if (pch != NULL) {
        char* pEnd;
        version = strtol(pch, &pEnd, 10);
      }
    }
    // To do: Parse payload and extract target value
    char msg[128];
    int value = 46;
    sprintf_s(msg, "{\"targetTemperature\":{\"value\":%d,\"ac\":200,\"av\":%d,\"ad\":\"success\"}}", value, version);
    int rc = mosquitto_publish(mosq, &version, DEVICETWIN_MESSAGE_PATCH, strlen(msg), msg, 1, true);
    if (rc != MOSQ_ERR_SUCCESS)
    {
      printf("Error: %s\r\n", mosquitto_strerror(rc));
    }
    delete pch;
  }
}

$iothub/methods/POST/getMaxMinReport/ 토픽은 서비스에서 getMaxMinReport라는 명령에 대한 요청을 받으며, 이 요청에는 명령 매개 변수를 사용하는 페이로드가 포함될 수 있습니다. 디바이스는 maxTempminTemp 값을 포함하는 페이로드를 사용하여 응답을 보냅니다.

$iothub/twin/PATCH/properties/desired/ 토픽은 서비스에서 속성 업데이트를 수신합니다. 이 예제에서는 속성 업데이트가 targetTemperature 속성에 대한 것이라고 가정합니다. {\"targetTemperature\":{\"value\":46,\"ac\":200,\"av\":12,\"ad\":\"success\"}}와 같은 승인으로 응답합니다.

요약하자면 이 샘플은 다음과 같은 기능을 구현합니다.

이름 기능 유형 세부 정보
온도 원격 데이터 형식이 double이라고 가정합니다.
maxTempSinceLastReboot 속성 데이터 형식이 double이라고 가정합니다.
targetTemperature 쓰기 가능 속성 데이터 형식이 Integer입니다.
getMaxMinReport 명령 double 형식의 maxTempminTemp 필드가 있는 JSON을 반환합니다.

모델 디자인

모든 IoT 플러그 앤 플레이 디바이스에는 디바이스의 특징과 기능을 설명하는 모델이 있습니다. 이 모델은 DTDL(디지털 쌍 정의 언어)을 사용하여 디바이스 기능을 설명합니다.

디바이스의 기존 기능을 매핑하는 단순 모델의 경우 원격 분석, 속성명령 DTDL 요소를 사용합니다.

이전 섹션에 설명된 샘플의 DTDL 모델은 다음 예제와 같습니다.

{
  "@context": "dtmi:dtdl:context;2",
  "@id": "dtmi:com:example:ConvertSample;1",
  "@type": "Interface",
  "displayName": "Simple device",
  "description": "Example that shows model for simple device converted to act as an IoT Plug and Play device.",
  "contents": [
    {
      "@type": [
        "Telemetry",
        "Temperature"
      ],
      "name": "temperature",
      "displayName": "Temperature",
      "description": "Temperature in degrees Celsius.",
      "schema": "double",
      "unit": "degreeCelsius"
    },
    {
      "@type": [
        "Property",
        "Temperature"
      ],
      "name": "targetTemperature",
      "schema": "double",
      "displayName": "Target Temperature",
      "description": "Allows to remotely specify the desired target temperature.",
      "unit": "degreeCelsius",
      "writable": true
    },
    {
      "@type": [
        "Property",
        "Temperature"
      ],
      "name": "maxTempSinceLastReboot",
      "schema": "double",
      "unit": "degreeCelsius",
      "displayName": "Max temperature since last reboot.",
      "description": "Returns the max temperature since last device reboot."
    },
    {
      "@type": "Command",
      "name": "getMaxMinReport",
      "displayName": "Get Max-Min report.",
      "description": "This command returns the max and min temperature.",
      "request": {
      },
      "response": {
        "name": "tempReport",
        "displayName": "Temperature Report",
        "schema": {
          "@type": "Object",
          "fields": [
            {
              "name": "maxTemp",
              "displayName": "Max temperature",
              "schema": "double"
            },
            {
              "name": "minTemp",
              "displayName": "Min temperature",
              "schema": "double"
            }
          ]
        }
      }
    }
  ]
}

이 모델에서는 다음이 적용됩니다.

  • nameschema 값은 디바이스에서 보내고 받는 데이터에 매핑됩니다.
  • 모든 기능은 단일 인터페이스로 그룹화됩니다.
  • @type 필드는 속성명령과 같은 DTDL 유형을 식별합니다.
  • unit, displayNamedescription과 같은 필드는 사용할 서비스에 대한 추가 정보를 제공합니다. 예를 들어, IoT Central은 디바이스 대시보드에 데이터를 표시할 때 이러한 값을 사용합니다.

자세히 알아보려면 플러그 앤 플레이 규칙 플러그 앤 플레이 모델링 가이드를 참조하세요.

코드 업데이트

디바이스가 이미 IoT Hub 또는 IoT Central을 사용하고 있는 경우 원격 분석, 속성 및 명령 기능의 구현을 변경할 필요가 없습니다. 디바이스가 IoT 플러그 앤 플레이 규칙을 따르도록 하려면 생성된 모델의 ID를 알리도록 디바이스가 서비스에 연결하는 방식을 수정합니다. 그러면 서비스는 이 모델을 사용하여 디바이스 기능을 이해할 수 있습니다. 예를 들어, IoT Central은 모델 ID를 사용하여 리포지토리에서 모델을 자동으로 검색하고 디바이스에 대한 디바이스 템플릿을 생성할 수 있습니다.

IoT 디바이스는 DPS(Device Provisioning Service)를 통해 또는 연결 문자열을 사용하여 직접 IoT 서비스에 연결합니다.

디바이스에서 DPS를 사용하여 연결하는 경우 디바이스를 등록할 때 보내는 페이로드에 모델 ID를 포함합니다. 이전에 표시된 예제 모델의 경우 페이로드는 다음과 같습니다.

{
  "modelId" : "dtmi:com:example:ConvertSample;1"
}

자세히 알아보려면 런타임 등록 - 디바이스 등록을 참조하세요.

디바이스에서 DPS를 사용하여 연결 문자열에 직접 연결하거나 코드에서 IoT Hub에 연결할 때 모델 ID를 포함합니다. 예시:

#define USERNAME IOTHUBNAME ".azure-devices.net/" DEVICEID "/?api-version=2020-09-30&model-id=dtmi:com:example:ConvertSample;1"

// ...

mosquitto_username_pw_set(mosq, USERNAME, PWD);

// ...

rc = mosquitto_connect(mosq, HOST, PORT, 10);

다음 단계

지금까지 기존 디바이스를 IoT 플러그 앤 플레이 디바이스로 변환하는 방법을 배웠으므로 이제 권장되는 다음 단계는 IoT 플러그 앤 플레이 모델링 가이드를 참조하세요.