Panduan pengembang perangkat IoT Plug and Play

IoT Plug and Play memungkinkan Anda membangun perangkat IoT yang mengiklankan kemampuannya ke aplikasi Azure IoT. Perangkat IoT Plug and Play tidak memerlukan konfigurasi manual saat pelanggan menghubungkannya ke aplikasi yang mendukung IoT Plug and Play seperti IoT Central.

Anda dapat menerapkan perangkat IoT secara langsung dengan menggunakan modul, atau dengan menggunakan modul IoT Edge.

Panduan ini menjelaskan langkah-langkah dasar yang diperlukan untuk membuat perangkat, modul, atau modul IoT Edge yang mengikuti konvensi IoT Plug and Play.

Untuk membuat perangkat IoT Plug and Play, modul, atau modul IoT Edge, ikuti langkah-langkah berikut ini:

  1. Pastikan perangkat Anda menggunakan protokol MQTT atau MQTT melalui WebSockets untuk menyambungkan ke Azure IoT Hub.
  2. Buat model Digital Twins Definition Language (DTDL) untuk menjelaskan perangkat Anda. Untuk mempelajari lebih lanjut, lihat Memahami komponen di model IoT Plug and Play.
  3. Perbarui perangkat atau modul Anda untuk mengumumkan model-id sebagai bagian dari sambungan perangkat.
  4. Menerapkan telemetri, properti, dan perintah yang mengikuti konvensi IoT Plug and Play

Setelah implementasi perangkat atau modul Anda siap, gunakan penjelajah Azure IoT untuk memvalidasi bahwa perangkat mengikuti konvensi IoT Plug and Play.

Pengumuman ID Model

Untuk mengumumkan ID model, perangkat harus menyertakannya dalam informasi sambungan:

static const char g_ThermostatModelId[] = "dtmi:com:example:Thermostat;1";
IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceHandle = NULL;
deviceHandle = CreateDeviceClientLLHandle();
iothubResult = IoTHubDeviceClient_LL_SetOption(
    deviceHandle, OPTION_MODEL_ID, g_ThermostatModelId);

Tip

Untuk modul dan IoT Edge, gunakan IoTHubModuleClient_LL sebagai ganti IoTHubDeviceClient_LL.

Tip

Ini adalah satu-satunya saat perangkat dapat mengatur ID model, karena tidak dapat diperbarui jika perangkat telah tersambung.

Payload DPS

Perangkat yang menggunakan Device Provisioning Service (DPS) dapat menyertakan yang modelId akan digunakan selama proses provisi dengan menggunakan payload JSON berikut:

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

Menggunakan komponen

Seperti yang dijelaskan dalam Memahami komponen dalam model IoT Plug and Play, Anda harus memutuskan apakah Anda ingin menggunakan komponen untuk menjelaskan perangkat Anda. Saat Anda menggunakan komponen, perangkat harus mengikuti aturan yang dijelaskan di bagian berikut:

telemetri

Komponen default tidak memerlukan properti khusus yang ditambahkan ke pesan telemetri.

Saat Anda menggunakan komponen berlapis, perangkat harus mengatur properti pesan dengan nama komponen:

void PnP_ThermostatComponent_SendTelemetry(
    PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle,
    IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClientLL)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    IOTHUB_MESSAGE_HANDLE messageHandle = NULL;
    IOTHUB_CLIENT_RESULT iothubResult;

    char temperatureStringBuffer[32];

    if (snprintf(
        temperatureStringBuffer,
        sizeof(temperatureStringBuffer),
        g_temperatureTelemetryBodyFormat,
        pnpThermostatComponent->currentTemperature) < 0)
    {
        LogError("snprintf of current temperature telemetry failed");
    }
    else if ((messageHandle = PnP_CreateTelemetryMessageHandle(
        pnpThermostatComponent->componentName, temperatureStringBuffer)) == NULL)
    {
        LogError("Unable to create telemetry message");
    }
    else if ((iothubResult = IoTHubDeviceClient_LL_SendEventAsync(
        deviceClientLL, messageHandle, NULL, NULL)) != IOTHUB_CLIENT_OK)
    {
        LogError("Unable to send telemetry message, error=%d", iothubResult);
    }

    IoTHubMessage_Destroy(messageHandle);
}

// ...

PnP_ThermostatComponent_SendTelemetry(g_thermostatHandle1, deviceClient);

Properti baca-saja

Melaporkan properti dari komponen default tidak memerlukan konstruksi khusus:

static const char g_maxTemperatureSinceRebootFormat[] = "{\"maxTempSinceLastReboot\":%.2f}";

char maxTemperatureSinceRebootProperty[256];

snprintf(
    maxTemperatureSinceRebootProperty,
    sizeof(maxTemperatureSinceRebootProperty),
    g_maxTemperatureSinceRebootFormat,
    38.7);

IOTHUB_CLIENT_RESULT iothubClientResult = IoTHubDeviceClient_LL_SendReportedState(
    deviceClientLL,
    (const unsigned char*)maxTemperatureSinceRebootProperty,
    strlen(maxTemperatureSinceRebootProperty), NULL, NULL));

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "maxTempSinceLastReboot" : 38.7
  }
}

Saat Anda menggunakan komponen berlapis, buat properti dalam nama komponen dan sertakan penanda:

STRING_HANDLE PnP_CreateReportedProperty(
    const char* componentName,
    const char* propertyName,
    const char* propertyValue
)
{
    STRING_HANDLE jsonToSend;

    if (componentName == NULL) 
    {
        jsonToSend = STRING_construct_sprintf(
            "{\"%s\":%s}",
            propertyName, propertyValue);
    }
    else 
    {
       jsonToSend = STRING_construct_sprintf(
            "{\"""%s\":{\"__t\":\"c\",\"%s\":%s}}",
            componentName, propertyName, propertyValue);
    }

    if (jsonToSend == NULL)
    {
        LogError("Unable to allocate JSON buffer");
    }

    return jsonToSend;
}

void PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(
    PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle,
    IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClientLL)
{
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent =
        (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
    char maximumTemperatureAsString[32];
    IOTHUB_CLIENT_RESULT iothubClientResult;
    STRING_HANDLE jsonToSend = NULL;

    if (snprintf(maximumTemperatureAsString, sizeof(maximumTemperatureAsString),
        "%.2f", pnpThermostatComponent->maxTemperature) < 0)
    {
        LogError("Unable to create max temp since last reboot string for reporting result");
    }
    else if ((jsonToSend = PnP_CreateReportedProperty(
                pnpThermostatComponent->componentName,
                g_maxTempSinceLastRebootPropertyName,
                maximumTemperatureAsString)) == NULL)
    {
        LogError("Unable to build max temp since last reboot property");
    }
    else
    {
        const char* jsonToSendStr = STRING_c_str(jsonToSend);
        size_t jsonToSendStrLen = strlen(jsonToSendStr);

        if ((iothubClientResult = IoTHubDeviceClient_LL_SendReportedState(
                deviceClientLL,
                (const unsigned char*)jsonToSendStr,
                jsonToSendStrLen, NULL, NULL)) != IOTHUB_CLIENT_OK)
        {
            LogError("Unable to send reported state, error=%d", iothubClientResult);
        }
        else
        {
            LogInfo("Sending maximumTemperatureSinceLastReboot property to IoTHub for component=%s",
                pnpThermostatComponent->componentName);
        }
    }

    STRING_delete(jsonToSend);
}

// ...

PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(g_thermostatHandle1, deviceClient);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1" : {  
      "__t" : "c",  
      "maxTemperature" : 38.7
     }
  }
}

Properti bisa-tulis

Properti ini dapat diatur oleh perangkat atau diperbarui oleh aplikasi back-end. Jika aplikasi back-end memperbarui properti, klien menerima pemberitahuan sebagai panggilan balik di DeviceClient atau ModuleClient. Untuk mengikuti konvensi IoT Plug and Play, perangkat harus memberi tahu layanan bahwa properti telah berhasil diterima.

Jika jenis properti adalah Object, layanan harus mengirimkan objek lengkap ke perangkat meskipun hanya memperbarui subset bidang objek tersebut. Pengakuan yang dikirim perangkat juga bisa merupakan objek yang lengkap.

Melaporkan properti bisa-tulis

Saat perangkat melaporkan properti bisa-tulis, perangkat tersebut harus menyertakan nilai ack yang ditentukan dalam konvensi.

Untuk melaporkan properti bisa-tulis dari komponen default:

IOTHUB_CLIENT_RESULT iothubClientResult;
char targetTemperatureResponseProperty[256];

snprintf(
    targetTemperatureResponseProperty,
    sizeof(targetTemperatureResponseProperty),
    "{\"targetTemperature\":{\"value\":%.2f,\"ac\":%d,\"av\":%d,\"ad\":\"%s\"}}",
    23.2, 200, 3, "Successfully updated target temperature");

iothubClientResult = IoTHubDeviceClient_LL_SendReportedState(
    deviceClientLL,
    (const unsigned char*)targetTemperatureResponseProperty,
    strlen(targetTemperatureResponseProperty), NULL, NULL);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "Successfully updated target temperature"
      }
  }
}

Untuk melaporkan properti bisa-tulis dari komponen berlapis, kembarannya harus menyertakan penanda:

STRING_HANDLE PnP_CreateReportedPropertyWithStatus(const char* componentName,
    const char* propertyName, const char* propertyValue,
    int result, const char* description, int ackVersion
)
{
    STRING_HANDLE jsonToSend;

    if (componentName == NULL) 
    {
        jsonToSend = STRING_construct_sprintf(
            "{\"%s\":{\"value\":%s,\"ac\":%d,\"ad\":\"%s\",\"av\":%d}}",
            propertyName, propertyValue,
            result, description, ackVersion);
    }
    else
    {
       jsonToSend = STRING_construct_sprintf(
            "{\"""%s\":{\"__t\":\"c\",\"%s\":{\"value\":%s,\"ac\":%d,\"ad\":\"%s\",\"av\":%d}}}",
            componentName, propertyName, propertyValue,
            result, description, ackVersion);
    }

    if (jsonToSend == NULL)
    {
        LogError("Unable to allocate JSON buffer");
    }

    return jsonToSend;
}

// ...

char targetTemperatureAsString[32];
IOTHUB_CLIENT_RESULT iothubClientResult;
STRING_HANDLE jsonToSend = NULL;

snprintf(targetTemperatureAsString,
    sizeof(targetTemperatureAsString),
    "%.2f",
    23.2);
jsonToSend = PnP_CreateReportedPropertyWithStatus(
    "thermostat1",
    "targetTemperature",
    targetTemperatureAsString,
    200,
    "complete",
    3);

const char* jsonToSendStr = STRING_c_str(jsonToSend);
size_t jsonToSendStrLen = strlen(jsonToSendStr);

iothubClientResult = IoTHubDeviceClient_LL_SendReportedState(
    deviceClientLL,
    (const unsigned char*)jsonToSendStr,
    jsonToSendStrLen, NULL, NULL);

STRING_delete(jsonToSend);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1": {
      "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Berlangganan pembaruan properti yang diinginkan

Layanan dapat memperbarui properti yang diinginkan yang memicu pemberitahuan pada perangkat yang tersambung. Pemberitahuan ini mencakup properti yang diinginkan yang diperbarui, termasuk nomor versi yang mengidentifikasi pembaruan. Perangkat harus menyertakan nomor versi ini dalam pesan yang ack dikirim kembali ke layanan.

Komponen default melihat satu properti dan membuat ack yang dilaporkan dengan versi yang diterima:

static void Thermostat_DeviceTwinCallback(
    DEVICE_TWIN_UPDATE_STATE updateState,
    const unsigned char* payload,
    size_t size,
    void* userContextCallback)
{
    // The device handle associated with this request is passed as the context,
    // since we will need to send reported events back.
    IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClientLL =
        (IOTHUB_DEVICE_CLIENT_LL_HANDLE)userContextCallback;

    char* jsonStr = NULL;
    JSON_Value* rootValue = NULL;
    JSON_Object* desiredObject;
    JSON_Value* versionValue = NULL;
    JSON_Value* targetTemperatureValue = NULL;

    jsonStr = CopyTwinPayloadToString(payload, size));
    rootValue = json_parse_string(jsonStr));
    desiredObject = GetDesiredJson(updateState, rootValue));
    targetTemperatureValue = json_object_get_value(desiredObject, "targetTemperature"));
    versionValue = json_object_get_value(desiredObject, "$version"));
    json_value_get_type(versionValue);
    json_value_get_type(targetTemperatureValue);

    double targetTemperature = json_value_get_number(targetTemperatureValue);
    int version = (int)json_value_get_number(versionValue);

    // ...

    // The device needs to let the service know that it has received the targetTemperature desired property.
    SendTargetTemperatureReport(deviceClientLL, targetTemperature, 200, version, "Successfully updated target temperature");

    json_value_free(rootValue);
    free(jsonStr);
}

// ...

IOTHUB_CLIENT_RESULT iothubResult;
iothubResult = IoTHubDeviceClient_LL_SetDeviceTwinCallback(
    deviceHandle, Thermostat_DeviceTwinCallback, (void*)deviceHandle))

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "targetTemperature": 23.2,
    "$version" : 3
  },
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "Successfully updated target temperature"
      }
  }
}

Komponen berlapis menerima properti yang diinginkan yang dibungkus dengan nama komponen, dan harus melaporkan kembali ack properti yang dilaporkan:

bool PnP_ProcessTwinData(
    DEVICE_TWIN_UPDATE_STATE updateState,
    const unsigned char* payload,
    size_t size, const char** componentsInModel,
    size_t numComponentsInModel,
    PnP_PropertyCallbackFunction pnpPropertyCallback,
    void* userContextCallback)
{
    char* jsonStr = NULL;
    JSON_Value* rootValue = NULL;
    JSON_Object* desiredObject;
    bool result;

    jsonStr = PnP_CopyPayloadToString(payload, size));
    rootValue = json_parse_string(jsonStr));
    desiredObject = GetDesiredJson(updateState, rootValue));
    
    result = VisitDesiredObject(
        desiredObject, componentsInModel,
        numComponentsInModel, pnpPropertyCallback,
        userContextCallback);


    json_value_free(rootValue);
    free(jsonStr);

    return result;
}

// ...
static const char g_thermostatComponent1Name[] = "thermostat1";
static const size_t g_thermostatComponent1Size = sizeof(g_thermostatComponent1Name) - 1;
static const char g_thermostatComponent2Name[] = "thermostat2";

static const char* g_modeledComponents[] = {g_thermostatComponent1Name, g_thermostatComponent2Name};
static const size_t g_numModeledComponents = sizeof(g_modeledComponents) / sizeof(g_modeledComponents[0]);

static void PnP_TempControlComponent_DeviceTwinCallback(
    DEVICE_TWIN_UPDATE_STATE updateState,
    const unsigned char* payload,
    size_t size,
    void* userContextCallback
)
{
    PnP_ProcessTwinData(
        updateState, payload,
        size, g_modeledComponents,
        g_numModeledComponents,
        PnP_TempControlComponent_ApplicationPropertyCallback,
        userContextCallback);
}

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "thermostat1" : {
        "__t" : "c",
        "targetTemperature": 23.2,
    }
    "$version" : 3
  },
  "reported": {
    "thermostat1" : {
        "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Perintah

Komponen default menerima nama perintah saat dipanggil oleh layanan.

Komponen berlapis menerima nama perintah yang diawali dengan nama komponen dan pemisah *.

void PnP_ParseCommandName(
    const char* deviceMethodName,
    unsigned const char** componentName,
    size_t* componentNameSize,
    const char** pnpCommandName
)
{
    const char* separator;

    if ((separator = strchr(deviceMethodName, "*")) != NULL)
    {
        *componentName = (unsigned const char*)deviceMethodName;
        *componentNameSize = separator - deviceMethodName;
        *pnpCommandName = separator + 1;
    }
    else
    {
        *componentName = NULL;
        *componentNameSize = 0;
        *pnpCommandName = deviceMethodName;
    }
}

static int PnP_TempControlComponent_DeviceMethodCallback(
    const char* methodName,
    const unsigned char* payload,
    size_t size,
    unsigned char** response,
    size_t* responseSize,
    void* userContextCallback)
{
    (void)userContextCallback;

    char* jsonStr = NULL;
    JSON_Value* rootValue = NULL;
    int result;
    unsigned const char *componentName;
    size_t componentNameSize;
    const char *pnpCommandName;

    *response = NULL;
    *responseSize = 0;

    // Parse the methodName into its componentName and CommandName.
    PnP_ParseCommandName(methodName, &componentName, &componentNameSize, &pnpCommandName);

    // Parse the JSON of the payload request.
    jsonStr = PnP_CopyPayloadToString(payload, size));
    rootValue = json_parse_string(jsonStr));
    if (componentName != NULL)
    {
        if (strncmp((const char*)componentName, g_thermostatComponent1Name, g_thermostatComponent1Size) == 0)
        {
            result = PnP_ThermostatComponent_ProcessCommand(g_thermostatHandle1, pnpCommandName, rootValue, response, responseSize);
        }
        else if (strncmp((const char*)componentName, g_thermostatComponent2Name, g_thermostatComponent2Size) == 0)
        {
            result = PnP_ThermostatComponent_ProcessCommand(g_thermostatHandle2, pnpCommandName, rootValue, response, responseSize);
        }
        else
        {
            LogError("PnP component=%.*s is not supported by TemperatureController", (int)componentNameSize, componentName);
            result = PNP_STATUS_NOT_FOUND;
        }
    }
    else
    {
        LogInfo("Received PnP command for TemperatureController component, command=%s", pnpCommandName);
        if (strcmp(pnpCommandName, g_rebootCommand) == 0)
        {
            result = PnP_TempControlComponent_InvokeRebootCommand(rootValue);
        }
        else
        {
            LogError("PnP command=s%s is not supported by TemperatureController", pnpCommandName);
            result = PNP_STATUS_NOT_FOUND;
        }
    }

    if (*response == NULL)
    {
        SetEmptyCommandResponse(response, responseSize, &result);
    }

    json_value_free(rootValue);
    free(jsonStr);

    return result;
}

// ...

PNP_DEVICE_CONFIGURATION g_pnpDeviceConfiguration;
g_pnpDeviceConfiguration.deviceMethodCallback = PnP_TempControlComponent_DeviceMethodCallback;
deviceClient = PnP_CreateDeviceClientLLHandle(&g_pnpDeviceConfiguration);

Payload permintaan dan respons

Perintah menggunakan jenis untuk menentukan payload permintaan dan responsnya. Perangkat harus mendeserialisasi parameter input yang masuk dan menserialisasi respon.

Contoh berikut menunjukkan cara mengimplementasikan perintah dengan jenis kompleks yang ditentukan dalam payload:

{
  "@type": "Command",
  "name": "getMaxMinReport",
  "displayName": "Get Max-Min report.",
  "description": "This command returns the max, min and average temperature from the specified time to the current time.",
  "request": {
    "name": "since",
    "displayName": "Since",
    "description": "Period to return the max-min report.",
    "schema": "dateTime"
  },
  "response": {
    "name" : "tempReport",
    "displayName": "Temperature Report",
    "schema": {
      "@type": "Object",
      "fields": [
        {
          "name": "maxTemp",
          "displayName": "Max temperature",
          "schema": "double"
        },
        {
          "name": "minTemp",
          "displayName": "Min temperature",
          "schema": "double"
        },
        {
          "name" : "avgTemp",
          "displayName": "Average Temperature",
          "schema": "double"
        },
        {
          "name" : "startTime",
          "displayName": "Start Time",
          "schema": "dateTime"
        },
        {
          "name" : "endTime",
          "displayName": "End Time",
          "schema": "dateTime"
        }
      ]
    }
  }
}

Cuplikan kode berikut menunjukkan bagaimana perangkat mengimplementasikan definisi perintah ini, termasuk jenis yang digunakan untuk mengaktifkan serialisasi dan deserialisasi:

static const char g_maxMinCommandResponseFormat[] = "{\"maxTemp\":%.2f,\"minTemp\":%.2f,\"avgTemp\":%.2f,\"startTime\":\"%s\",\"endTime\":\"%s\"}";

// ...

static bool BuildMaxMinCommandResponse(
    PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent,
    unsigned char** response,
    size_t* responseSize)
{
    int responseBuilderSize = 0;
    unsigned char* responseBuilder = NULL;
    bool result;
    char currentTime[TIME_BUFFER_SIZE];

    BuildUtcTimeFromCurrentTime(currentTime, sizeof(currentTime));
    responseBuilderSize = snprintf(NULL, 0, g_maxMinCommandResponseFormat,
        pnpThermostatComponent->maxTemperature,
        pnpThermostatComponent->minTemperature,
        pnpThermostatComponent->allTemperatures /
        pnpThermostatComponent->numTemperatureUpdates,
        g_programStartTime, currentTime));

    responseBuilder = calloc(1, responseBuilderSize + 1));

    responseBuilderSize = snprintf(
        (char*)responseBuilder, responseBuilderSize + 1, g_maxMinCommandResponseFormat,
        pnpThermostatComponent->maxTemperature,
        pnpThermostatComponent->minTemperature,
        pnpThermostatComponent->allTemperatures / pnpThermostatComponent->numTemperatureUpdates,
        g_programStartTime,
        currentTime));

    *response = responseBuilder;
    *responseSize = (size_t)responseBuilderSize;

    return true;
}

Tip

Nama permintaan dan respons tidak ada dalam payload yang diserialisasi yang dikirimkan melalui kabel.

SDK

Cuplikan kode dalam artikel ini didasarkan pada sampel yang menggunakan addon Middleware Azure IoT untuk Eclipse ThreadX. Addon adalah lapisan pengikatan antara Eclipse ThreadX dan Azure SDK untuk Embedded C.

Cuplikan kode dalam artikel ini didasarkan pada contoh berikut:

Pengumuman ID Model

Untuk mengumumkan ID model, perangkat harus menyertakannya dalam informasi sambungan:

#include "nx_azure_iot_hub_client.h"

// ...

#define SAMPLE_PNP_MODEL_ID "dtmi:com:example:Thermostat;1"

// ...

status = nx_azure_iot_hub_client_model_id_set(iothub_client_ptr, (UCHAR *)SAMPLE_PNP_MODEL_ID, sizeof(SAMPLE_PNP_MODEL_ID) - 1);

Tip

Ini adalah satu-satunya saat perangkat dapat mengatur ID model, karena tidak dapat diperbarui jika perangkat telah tersambung.

Payload DPS

Perangkat yang menggunakan Device Provisioning Service (DPS) dapat menyertakan yang modelId akan digunakan selama proses provisi dengan menggunakan payload JSON berikut:

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

Sampel menggunakan kode berikut untuk mengirim payload ini:

#include "nx_azure_iot_provisioning_client.h"

// ...

#define SAMPLE_PNP_MODEL_ID "dtmi:com:example:Thermostat;1"
#define SAMPLE_PNP_DPS_PAYLOAD "{\"modelId\":\"" SAMPLE_PNP_MODEL_ID "\"}"

// ...

status = nx_azure_iot_provisioning_client_registration_payload_set(prov_client_ptr, (UCHAR *)SAMPLE_PNP_DPS_PAYLOAD, sizeof(SAMPLE_PNP_DPS_PAYLOAD) - 1);

Menggunakan komponen

Seperti yang dijelaskan dalam Memahami komponen dalam model IoT Plug and Play, Anda harus memutuskan apakah Anda ingin menggunakan komponen untuk menjelaskan perangkat Anda. Saat Anda menggunakan komponen, perangkat harus mengikuti aturan yang dijelaskan di bagian berikut. Untuk menyederhanakan bekerja dengan konvensi IoT Plug and Play untuk komponen, sampel menggunakan fungsi pembantu dalam nx_azure_iot_hub_client.h.

telemetri

Komponen default tidak memerlukan properti khusus yang ditambahkan ke pesan telemetri.

Saat Anda menggunakan komponen berlapis, perangkat harus mengatur properti pesan dengan nama komponen. Dalam cuplikan berikut, component_name_ptr adalah nama komponen seperti thermostat1. Fungsi pembantu nx_azure_iot_pnp_helper_telemetry_message_create yang didefinisikan dalam nx_azure_iot_pnp_helpers.h menambahkan properti pesan dengan nama komponen:

#include "nx_azure_iot_pnp_helpers.h"

// ...

static const CHAR telemetry_name[] = "temperature";

// ...

UINT sample_pnp_thermostat_telemetry_send(SAMPLE_PNP_THERMOSTAT_COMPONENT *handle, NX_AZURE_IOT_HUB_CLIENT *iothub_client_ptr)
{
UINT status;
NX_PACKET *packet_ptr;
NX_AZURE_IOT_JSON_WRITER json_writer;
UINT buffer_length;

    // ...

    /* Create a telemetry message packet. */
    if ((status = nx_azure_iot_pnp_helper_telemetry_message_create(iothub_client_ptr, handle -> component_name_ptr,
        handle -> component_name_length,
        &packet_ptr, NX_WAIT_FOREVER)))
    {
        // ...
    }

    // ...

    if ((status = nx_azure_iot_hub_client_telemetry_send(iothub_client_ptr, packet_ptr,
        (UCHAR *)scratch_buffer, buffer_length, NX_WAIT_FOREVER)))
    {
        // ...
    }

    // ...

    return(status);
}

Properti baca-saja

Melaporkan properti dari komponen default tidak memerlukan konstruksi khusus:

#include "nx_azure_iot_hub_client.h"
#include "nx_azure_iot_json_writer.h"

// ...

static const CHAR reported_max_temp_since_last_reboot[] = "maxTempSinceLastReboot";

// ...

static UINT sample_build_reported_property(NX_AZURE_IOT_JSON_WRITER *json_builder_ptr, double temp)
{
UINT ret;

    if (nx_azure_iot_json_writer_append_begin_object(json_builder_ptr) ||
        nx_azure_iot_json_writer_append_property_with_double_value(json_builder_ptr,
            (UCHAR *)reported_max_temp_since_last_reboot,
            sizeof(reported_max_temp_since_last_reboot) - 1,
            temp, DOUBLE_DECIMAL_PLACE_DIGITS) ||
        nx_azure_iot_json_writer_append_end_object(json_builder_ptr))
    {
        ret = 1;
        printf("Failed to build reported property\r\n");
    }
    else
    {
        ret = 0;
    }

    return(ret);
}

// ...

if ((status = sample_build_reported_property(&json_builder, device_max_temp)))
{
    // ...
}

reported_properties_length = nx_azure_iot_json_writer_get_bytes_used(&json_builder);
if ((status = nx_azure_iot_hub_client_device_twin_reported_properties_send(&(context -> iothub_client),
    scratch_buffer,
    reported_properties_length,
    &request_id, &response_status,
    &reported_property_version,
    (5 * NX_IP_PERIODIC_RATE))))
{
    // ...
}

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "maxTempSinceLastReboot" : 38.7
  }
}

Saat Anda menggunakan komponen berlapis, properti harus dibuat dalam nama komponen dan menyertakan penanda. Dalam cuplikan berikut, component_name_ptr adalah nama komponen seperti thermostat1. Fungsi pembantu nx_azure_iot_pnp_helper_build_reported_property yang didefinisikan dalam nx_azure_iot_pnp_helpers.h menciptakan properti yang dilaporkan dalam format yang benar:

#include "nx_azure_iot_pnp_helpers.h"

// ...

static const CHAR reported_max_temp_since_last_reboot[] = "maxTempSinceLastReboot";

UINT sample_pnp_thermostat_report_max_temp_since_last_reboot_property(SAMPLE_PNP_THERMOSTAT_COMPONENT *handle, NX_AZURE_IOT_HUB_CLIENT *iothub_client_ptr)
{
UINT reported_properties_length;
UINT status;
UINT response_status;
UINT request_id;
NX_AZURE_IOT_JSON_WRITER json_builder;
ULONG reported_property_version;

    // ...

    if ((status = nx_azure_iot_pnp_helper_build_reported_property(handle -> component_name_ptr,
        handle -> component_name_length,
        append_max_temp, (VOID *)handle,
        &json_builder)))
    {
        // ...
    }

    reported_properties_length = nx_azure_iot_json_writer_get_bytes_used(&json_builder);
    if ((status = nx_azure_iot_hub_client_device_twin_reported_properties_send(iothub_client_ptr,
        scratch_buffer,
        reported_properties_length,
        &request_id, &response_status,
        &reported_property_version,
        (5 * NX_IP_PERIODIC_RATE))))
    {
        // ...
    }

    // ...
}

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
    "reported": {
        "thermostat1" : {  
            "__t" : "c",  
            "maxTemperature" : 38.7
        } 
    }
}

Properti bisa-tulis

Properti ini dapat diatur oleh perangkat atau diperbarui oleh aplikasi back-end. Untuk mengikuti konvensi IoT Plug and Play, perangkat harus memberi tahu layanan bahwa properti telah berhasil diterima.

Melaporkan properti bisa-tulis

Saat perangkat melaporkan properti bisa-tulis, perangkat tersebut harus menyertakan nilai ack yang ditentukan dalam konvensi.

Untuk melaporkan properti bisa-tulis dari komponen default:

#include "nx_azure_iot_hub_client.h"
#include "nx_azure_iot_json_writer.h"

// ...

static const CHAR reported_temp_property_name[] = "targetTemperature";
static const CHAR reported_value_property_name[] = "value";
static const CHAR reported_status_property_name[] = "ac";
static const CHAR reported_version_property_name[] = "av";
static const CHAR reported_description_property_name[] = "ad";

// ...

static VOID sample_send_target_temperature_report(SAMPLE_CONTEXT *context, double current_device_temp_value,
    UINT status, UINT version, UCHAR *description_ptr,
    UINT description_len)
{
NX_AZURE_IOT_JSON_WRITER json_builder;
UINT bytes_copied;
UINT response_status;
UINT request_id;
ULONG reported_property_version;

    // ...

    if (nx_azure_iot_json_writer_append_begin_object(&json_builder) ||
        nx_azure_iot_json_writer_append_property_name(&json_builder,
            (UCHAR *)reported_temp_property_name,
            sizeof(reported_temp_property_name) - 1) ||
        nx_azure_iot_json_writer_append_begin_object(&json_builder) ||
        nx_azure_iot_json_writer_append_property_with_double_value(&json_builder,
            (UCHAR *)reported_value_property_name,
            sizeof(reported_value_property_name) - 1,
            current_device_temp_value, DOUBLE_DECIMAL_PLACE_DIGITS) ||
        nx_azure_iot_json_writer_append_property_with_int32_value(&json_builder,
            (UCHAR *)reported_status_property_name,
            sizeof(reported_status_property_name) - 1,
            (int32_t)status) ||
        nx_azure_iot_json_writer_append_property_with_int32_value(&json_builder,
            (UCHAR *)reported_version_property_name,
            sizeof(reported_version_property_name) - 1,
            (int32_t)version) ||
        nx_azure_iot_json_writer_append_property_with_string_value(&json_builder,
            (UCHAR *)reported_description_property_name,
            sizeof(reported_description_property_name) - 1,
            description_ptr, description_len) ||
        nx_azure_iot_json_writer_append_end_object(&json_builder) ||
        nx_azure_iot_json_writer_append_end_object(&json_builder))
    {
        // ...
    }
    else
    // ...
}

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "success"
      }
  }
}

Untuk melaporkan properti yang dapat ditulis dari komponen bertingkat, perangkat kembar harus menyertakan penanda dan properti harus dibuat dalam nama komponen. Dalam cuplikan berikut, component_name_ptr adalah nama komponen seperti thermostat1. Fungsi pembantu nx_azure_iot_pnp_helper_build_reported_property_with_status yang ditentukan dalam nx_azure_iot_pnp_helpers.h membuat payload properti yang dilaporkan:

#include "nx_azure_iot_pnp_helpers.h"

// ...

static VOID sample_send_target_temperature_report(SAMPLE_PNP_THERMOSTAT_COMPONENT *handle,
    NX_AZURE_IOT_HUB_CLIENT *iothub_client_ptr, double temp,
    INT status_code, UINT version, const CHAR *description)
{
UINT bytes_copied;
UINT response_status;
UINT request_id;
NX_AZURE_IOT_JSON_WRITER json_writer;
ULONG reported_property_version;

    // ...

    if (nx_azure_iot_pnp_helper_build_reported_property_with_status(handle -> component_name_ptr, handle -> component_name_length,
        (UCHAR *)target_temp_property_name,
        sizeof(target_temp_property_name) - 1,
        append_temp, (VOID *)&temp, status_code,
        (UCHAR *)description,
        strlen(description), version, &json_writer))
    {
        // ...
    }
    else
    {
        // ...
    }

    // ...
}

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1": {
      "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "success"
      }
    }
  }
}

Berlangganan pembaruan properti yang diinginkan

Layanan dapat memperbarui properti yang diinginkan yang memicu pemberitahuan pada perangkat yang tersambung. Pemberitahuan ini mencakup properti yang diinginkan yang diperbarui, termasuk nomor versi yang mengidentifikasi pembaruan. Perangkat harus menyertakan nomor versi ini dalam pesan yang ack dikirim kembali ke layanan.

Komponen default melihat satu properti dan membuat ack yang dilaporkan dengan versi yang diterima:

#include "nx_azure_iot_hub_client.h"
#include "nx_azure_iot_json_writer.h"

// ...

static const CHAR temp_response_description[] = "success";

// ...

static UINT sample_parse_desired_temp_property(SAMPLE_CONTEXT *context,
    NX_AZURE_IOT_JSON_READER *json_reader_ptr,
    UINT is_partial)
{
double parsed_value;
UINT version;
NX_AZURE_IOT_JSON_READER copy_json_reader;
UINT status;

    // ...

    copy_json_reader = *json_reader_ptr;
    if (sample_json_child_token_move(&copy_json_reader,
            (UCHAR *)desired_version_property_name,
            sizeof(desired_version_property_name) - 1) ||
        nx_azure_iot_json_reader_token_int32_get(&copy_json_reader, (int32_t *)&version))
    {
        // ...
    }

    // ...

    sample_send_target_temperature_report(context, current_device_temp, 200,
        (UINT)version, (UCHAR *)temp_response_description,
        sizeof(temp_response_description) - 1);

    // ...
}

Komponen bertingkat menerima properti yang diinginkan dibungkus dengan nama komponen dan membuat laporan ack dengan versi yang diterima:

#include "nx_azure_iot_pnp_helpers.h"

// ...

static const CHAR target_temp_property_name[] = "targetTemperature";
static const CHAR temp_response_description_success[] = "success";
static const CHAR temp_response_description_failed[] = "failed";

// ...

UINT sample_pnp_thermostat_process_property_update(SAMPLE_PNP_THERMOSTAT_COMPONENT *handle,
    NX_AZURE_IOT_HUB_CLIENT *iothub_client_ptr,
    UCHAR *component_name_ptr, UINT component_name_length,
    UCHAR *property_name_ptr, UINT property_name_length,
    NX_AZURE_IOT_JSON_READER *property_value_reader_ptr, UINT version)
{
double parsed_value = 0;
INT status_code;
const CHAR *description;

    // ...

    if (property_name_length != (sizeof(target_temp_property_name) - 1) ||
        strncmp((CHAR *)property_name_ptr, (CHAR *)target_temp_property_name, property_name_length) != 0)
    {
        // ...
    }
    else if (nx_azure_iot_json_reader_token_double_get(property_value_reader_ptr, &parsed_value))
    {
        status_code = 401;
        description = temp_response_description_failed;
    }
    else
    {
        status_code = 200;
        description = temp_response_description_success;

        // ...
    }

    sample_send_target_temperature_report(handle, iothub_client_ptr, parsed_value,
                                          status_code, version, description);

    // ...
}

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "thermostat1" : {
        "__t" : "c",
        "targetTemperature": 23.2,
    }
    "$version" : 3
  },
  "reported": {
    "thermostat1" : {
        "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "success"
      }
    }
  }
}

Perintah

Komponen default menerima nama perintah saat dipanggil oleh layanan.

Komponen berlapis menerima nama perintah yang diawali dengan nama komponen dan pemisah *. Dalam cuplikan berikut, fungsi pembantu nx_azure_iot_pnp_helper_command_name_parse yang didefinisikan dalam nx_azure_iot_pnp_helpers.h mengurai nama komponen dan nama perintah dari pesan yang diterima perangkat dari layanan:

#include "nx_azure_iot_hub_client.h"
#include "nx_azure_iot_pnp_helpers.h"

// ...

static VOID sample_direct_method_action(SAMPLE_CONTEXT *sample_context_ptr)
{
NX_PACKET *packet_ptr;
UINT status;
USHORT method_name_length;
const UCHAR *method_name_ptr;
USHORT context_length;
VOID *context_ptr;
UINT component_name_length;
const UCHAR *component_name_ptr;
UINT pnp_command_name_length;
const UCHAR *pnp_command_name_ptr;
NX_AZURE_IOT_JSON_WRITER json_writer;
NX_AZURE_IOT_JSON_READER json_reader;
NX_AZURE_IOT_JSON_READER *json_reader_ptr;
UINT status_code;
UINT response_length;

    // ...

    if ((status = nx_azure_iot_hub_client_direct_method_message_receive(&(sample_context_ptr -> iothub_client),
        &method_name_ptr, &method_name_length,
        &context_ptr, &context_length,
        &packet_ptr, NX_WAIT_FOREVER)))
    {
        // ...
    }

    // ...

    if ((status = nx_azure_iot_pnp_helper_command_name_parse(method_name_ptr, method_name_length,
        &component_name_ptr, &component_name_length,
        &pnp_command_name_ptr,
        &pnp_command_name_length)) != NX_AZURE_IOT_SUCCESS)
    {
        // ...
    }
    
    // ...

    else
    {
        // ...

        if ((status = sample_pnp_thermostat_process_command(&sample_thermostat_1, component_name_ptr,
            component_name_length, pnp_command_name_ptr,
            pnp_command_name_length, json_reader_ptr,
            &json_writer, &status_code)) == NX_AZURE_IOT_SUCCESS)
        {
            // ...
        }
        else if ((status = sample_pnp_thermostat_process_command(&sample_thermostat_2, component_name_ptr,
            component_name_length, pnp_command_name_ptr,
            pnp_command_name_length, json_reader_ptr,
            &json_writer, &status_code)) == NX_AZURE_IOT_SUCCESS)
        {
            // ...
        }
        else if((status = sample_pnp_temp_controller_process_command(component_name_ptr, component_name_length,
            pnp_command_name_ptr, pnp_command_name_length,
            json_reader_ptr, &json_writer,
            &status_code)) == NX_AZURE_IOT_SUCCESS)
        {
            // ...
        }
        else
        {
            printf("Failed to find any handler for method %.*s\r\n", method_name_length, method_name_ptr);
            status_code = SAMPLE_COMMAND_NOT_FOUND_STATUS;
            response_length = 0;
        }

        // ...
    }
}

Payload permintaan dan respons

Perintah menggunakan jenis untuk menentukan payload permintaan dan responsnya. Perangkat harus mendeserialisasi parameter input yang masuk dan menserialisasi respon.

Contoh berikut menunjukkan cara mengimplementasikan perintah dengan jenis kompleks yang ditentukan dalam payload:

{
  "@type": "Command",
  "name": "getMaxMinReport",
  "displayName": "Get Max-Min report.",
  "description": "This command returns the max, min and average temperature from the specified time to the current time.",
  "request": {
    "name": "since",
    "displayName": "Since",
    "description": "Period to return the max-min report.",
    "schema": "dateTime"
  },
  "response": {
    "name" : "tempReport",
    "displayName": "Temperature Report",
    "schema": {
      "@type": "Object",
      "fields": [
        {
          "name": "maxTemp",
          "displayName": "Max temperature",
          "schema": "double"
        },
        {
          "name": "minTemp",
          "displayName": "Min temperature",
          "schema": "double"
        },
        {
          "name" : "avgTemp",
          "displayName": "Average Temperature",
          "schema": "double"
        },
        {
          "name" : "startTime",
          "displayName": "Start Time",
          "schema": "dateTime"
        },
        {
          "name" : "endTime",
          "displayName": "End Time",
          "schema": "dateTime"
        }
      ]
    }
  }
}

Cuplikan kode berikut menunjukkan bagaimana perangkat mengimplementasikan definisi perintah ini, termasuk jenis yang digunakan untuk mengaktifkan serialisasi dan deserialisasi:

#include "nx_azure_iot_pnp_helpers.h"

// ...

static const CHAR report_max_temp_name[] = "maxTemp";
static const CHAR report_min_temp_name[] = "minTemp";
static const CHAR report_avg_temp_name[] = "avgTemp";
static const CHAR report_start_time_name[] = "startTime";
static const CHAR report_end_time_name[] = "endTime";
static const CHAR fake_start_report_time[] = "2020-01-10T10:00:00Z";
static const CHAR fake_end_report_time[] = "2023-01-10T10:00:00Z";

// ...

static UINT sample_get_maxmin_report(SAMPLE_PNP_THERMOSTAT_COMPONENT *handle,
    NX_AZURE_IOT_JSON_READER *json_reader_ptr,
    NX_AZURE_IOT_JSON_WRITER *out_json_builder_ptr)
{
UINT status;
UCHAR *start_time = (UCHAR *)fake_start_report_time;
UINT start_time_len = sizeof(fake_start_report_time) - 1;
UCHAR time_buf[32];

    // ...

    /* Build the method response payload */
    if (nx_azure_iot_json_writer_append_begin_object(out_json_builder_ptr) ||
        nx_azure_iot_json_writer_append_property_with_double_value(out_json_builder_ptr,
            (UCHAR *)report_max_temp_name,
            sizeof(report_max_temp_name) - 1,
            handle -> maxTemperature,
            DOUBLE_DECIMAL_PLACE_DIGITS) ||
        nx_azure_iot_json_writer_append_property_with_double_value(out_json_builder_ptr,
            (UCHAR *)report_min_temp_name,
            sizeof(report_min_temp_name) - 1,
            handle -> minTemperature,
            DOUBLE_DECIMAL_PLACE_DIGITS) ||
        nx_azure_iot_json_writer_append_property_with_double_value(out_json_builder_ptr,
            (UCHAR *)report_avg_temp_name,
            sizeof(report_avg_temp_name) - 1,
            handle -> avgTemperature,
            DOUBLE_DECIMAL_PLACE_DIGITS) ||
        nx_azure_iot_json_writer_append_property_with_string_value(out_json_builder_ptr,
            (UCHAR *)report_start_time_name,
            sizeof(report_start_time_name) - 1,
            (UCHAR *)start_time, start_time_len) ||
        nx_azure_iot_json_writer_append_property_with_string_value(out_json_builder_ptr,
            (UCHAR *)report_end_time_name,
            sizeof(report_end_time_name) - 1,
            (UCHAR *)fake_end_report_time,
            sizeof(fake_end_report_time) - 1) ||
        nx_azure_iot_json_writer_append_end_object(out_json_builder_ptr))
    {
        status = NX_NOT_SUCCESSFUL;
    }
    else
    {
        status = NX_AZURE_IOT_SUCCESS;
    }

    return(status);
}

Tip

Nama permintaan dan respons tidak ada dalam payload yang diserialisasi yang dikirimkan melalui kabel.

Pengumuman ID Model

Untuk mengumumkan ID model, perangkat harus menyertakannya dalam informasi sambungan:

DeviceClient.CreateFromConnectionString(
  connectionString,
  TransportType.Mqtt,
  new ClientOptions() { ModelId = modelId })

Kelebihan beban ClientOptions baru tersedia di semua metode DeviceClient yang digunakan untuk menginisialisasi koneksi.

Tip

Untuk modul dan IoT Edge, gunakan ModuleClient sebagai ganti DeviceClient.

Tip

Ini adalah satu-satunya saat perangkat dapat mengatur ID model, karena tidak dapat diperbarui jika perangkat telah tersambung.

Payload DPS

Perangkat yang menggunakan Device Provisioning Service (DPS) dapat menyertakan yang modelId akan digunakan selama proses provisi dengan menggunakan payload JSON berikut:

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

Menggunakan komponen

Seperti yang dijelaskan dalam Memahami komponen dalam model IoT Plug and Play, Anda harus memutuskan apakah Anda ingin menggunakan komponen untuk menjelaskan perangkat Anda. Saat Anda menggunakan komponen, perangkat harus mengikuti aturan yang dijelaskan di bagian berikut.

telemetri

Komponen default tidak memerlukan properti khusus yang ditambahkan ke pesan telemetri.

Saat Anda menggunakan komponen berlapis, perangkat harus mengatur properti pesan dengan nama komponen:

public async Task SendComponentTelemetryValueAsync(string componentName, string serializedTelemetry)
{
  var message = new Message(Encoding.UTF8.GetBytes(serializedTelemetry));
  message.ComponentName = componentName;
  message.ContentType = "application/json";
  message.ContentEncoding = "utf-8";
  await client.SendEventAsync(message);
}

Properti baca-saja

Melaporkan properti dari komponen default tidak memerlukan konstruksi khusus:

TwinCollection reportedProperties = new TwinCollection();
reportedProperties["maxTemperature"] = 38.7;
await client.UpdateReportedPropertiesAsync(reportedProperties);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "maxTemperature" : 38.7
  }
}

Saat Anda menggunakan komponen berlapis, buat properti dalam nama komponen dan sertakan penanda:

TwinCollection reportedProperties = new TwinCollection();
TwinCollection component = new TwinCollection();
component["maxTemperature"] = 38.7;
component["__t"] = "c"; // marker to identify a component
reportedProperties["thermostat1"] = component;
await client.UpdateReportedPropertiesAsync(reportedProperties);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1" : {  
      "__t" : "c",  
      "maxTemperature" : 38.7
     } 
  }
}

Properti bisa-tulis

Properti ini dapat diatur oleh perangkat atau diperbarui oleh aplikasi back-end. Jika aplikasi back-end memperbarui properti, klien menerima pemberitahuan sebagai panggilan balik di DeviceClient atau ModuleClient. Untuk mengikuti konvensi IoT Plug and Play, perangkat harus memberi tahu layanan bahwa properti telah berhasil diterima.

Jika jenis properti adalah Object, layanan harus mengirimkan objek lengkap ke perangkat meskipun hanya memperbarui subset bidang objek tersebut. Pengakuan yang dikirim perangkat juga harus merupakan objek yang lengkap.

Melaporkan properti bisa-tulis

Saat perangkat melaporkan properti bisa-tulis, perangkat tersebut harus menyertakan nilai ack yang ditentukan dalam konvensi.

Untuk melaporkan properti bisa-tulis dari komponen default:

TwinCollection reportedProperties = new TwinCollection();
TwinCollection ackProps = new TwinCollection();
ackProps["value"] = 23.2;
ackProps["ac"] = 200; // using HTTP status codes
ackProps["av"] = 0; // not readed from a desired property
ackProps["ad"] = "reported default value";
reportedProperties["targetTemperature"] = ackProps;
await client.UpdateReportedPropertiesAsync(reportedProperties);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
  }
}

Untuk melaporkan properti bisa-tulis dari komponen berlapis, kembarannya harus menyertakan penanda:

TwinCollection reportedProperties = new TwinCollection();
TwinCollection component = new TwinCollection();
TwinCollection ackProps = new TwinCollection();
component["__t"] = "c"; // marker to identify a component
ackProps["value"] = 23.2;
ackProps["ac"] = 200; // using HTTP status codes
ackProps["av"] = 0; // not read from a desired property
ackProps["ad"] = "reported default value";
component["targetTemperature"] = ackProps;
reportedProperties["thermostat1"] = component;
await client.UpdateReportedPropertiesAsync(reportedProperties);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1": {
      "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Berlangganan pembaruan properti yang diinginkan

Layanan dapat memperbarui properti yang diinginkan yang memicu pemberitahuan pada perangkat yang tersambung. Pemberitahuan ini mencakup properti yang diinginkan yang diperbarui, termasuk nomor versi yang mengidentifikasi pembaruan. Perangkat harus menyertakan nomor versi ini dalam pesan yang ack dikirim kembali ke layanan.

Komponen default melihat satu properti dan membuat ack yang dilaporkan dengan versi yang diterima:

await client.SetDesiredPropertyUpdateCallbackAsync(async (desired, ctx) => 
{
  JValue targetTempJson = desired["targetTemperature"];
  double targetTemperature = targetTempJson.Value<double>();

  TwinCollection reportedProperties = new TwinCollection();
  TwinCollection ackProps = new TwinCollection();
  ackProps["value"] = targetTemperature;
  ackProps["ac"] = 200;
  ackProps["av"] = desired.Version; 
  ackProps["ad"] = "desired property received";
  reportedProperties["targetTemperature"] = ackProps;

  await client.UpdateReportedPropertiesAsync(reportedProperties);
}, null);

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "targetTemperature": 23.2,
    "$version" : 3
  },
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
  }
}

Komponen berlapis menerima properti yang diinginkan yang dibungkus dengan nama komponen, dan harus melaporkan kembali ack properti yang dilaporkan:

await client.SetDesiredPropertyUpdateCallbackAsync(async (desired, ctx) =>
{
  JObject thermostatComponent = desired["thermostat1"];
  JToken targetTempProp = thermostatComponent["targetTemperature"];
  double targetTemperature = targetTempProp.Value<double>();

  TwinCollection reportedProperties = new TwinCollection();
  TwinCollection component = new TwinCollection();
  TwinCollection ackProps = new TwinCollection();
  component["__t"] = "c"; // marker to identify a component
  ackProps["value"] = targetTemperature;
  ackProps["ac"] = 200; // using HTTP status codes
  ackProps["av"] = desired.Version; // not readed from a desired property
  ackProps["ad"] = "desired property received";
  component["targetTemperature"] = ackProps;
  reportedProperties["thermostat1"] = component;

  await client.UpdateReportedPropertiesAsync(reportedProperties);
}, null);

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "thermostat1" : {
        "__t" : "c",
        "targetTemperature": 23.2,
    }
    "$version" : 3
  },
  "reported": {
    "thermostat1" : {
        "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Perintah

Komponen default menerima nama perintah saat dipanggil oleh layanan.

Komponen berlapis menerima nama perintah yang diawali dengan nama komponen dan pemisah *.

await client.SetMethodHandlerAsync("themostat*reboot", (MethodRequest req, object ctx) =>
{
  Console.WriteLine("REBOOT");
  return Task.FromResult(new MethodResponse(200));
},
null);

Payload permintaan dan respons

Perintah menggunakan jenis untuk menentukan payload permintaan dan responsnya. Perangkat harus mendeserialisasi parameter input yang masuk dan menserialisasi respon.

Contoh berikut menunjukkan cara mengimplementasikan perintah dengan jenis kompleks yang ditentukan dalam payload:

{
  "@type": "Command",
  "name": "start",
  "request": {
    "name": "startRequest",
    "schema": {
      "@type": "Object",
      "fields": [
        {
          "name": "startPriority",
          "schema": "integer"
        },
        {
          "name": "startMessage",
          "schema" : "string"
        }
      ]
    }
  },
  "response": {
    "name": "startReponse",
    "schema": {
      "@type": "Object",
      "fields": [
        {
            "name": "startupTime",
            "schema": "integer" 
        },
        {
          "name": "startupMessage",
          "schema": "string"
        }
      ]
    }
  }
}

Cuplikan kode berikut menunjukkan bagaimana perangkat mengimplementasikan definisi perintah ini, termasuk jenis yang digunakan untuk mengaktifkan serialisasi dan deserialisasi:

class startRequest
{
  public int startPriority { get; set; }
  public string startMessage { get; set; }
}

class startResponse
{
  public int startupTime { get; set; }
  public string startupMessage { get; set; }
}

// ... 

await client.SetMethodHandlerAsync("start", (MethodRequest req, object ctx) =>
{
  var startRequest = JsonConvert.DeserializeObject<startRequest>(req.DataAsJson);
  Console.WriteLine($"Received start command with priority ${startRequest.startPriority} and ${startRequest.startMessage}");

  var startResponse = new startResponse
  {
    startupTime = 123,
    startupMessage = "device started with message " + startRequest.startMessage
  };

  string responsePayload = JsonConvert.SerializeObject(startResponse);
  MethodResponse response = new MethodResponse(Encoding.UTF8.GetBytes(responsePayload), 200);
  return Task.FromResult(response);
},null);

Tip

Nama permintaan dan respons tidak ada dalam payload yang diserialisasi yang dikirimkan melalui kabel.

Pengumuman ID Model

Untuk mengumumkan ID model, perangkat harus menyertakannya dalam informasi sambungan:

ClientOptions options = new ClientOptions();
options.setModelId(MODEL_ID);
deviceClient = new DeviceClient(deviceConnectionString, protocol, options);

Kelebihan ClientOptions tersedia di semua metode DeviceClient yang digunakan untuk menginisialisasi sambungan.

Tip

Untuk modul dan IoT Edge, gunakan ModuleClient sebagai ganti DeviceClient.

Tip

Ini adalah satu-satunya saat perangkat dapat mengatur ID model, karena tidak dapat diperbarui jika perangkat telah tersambung.

Payload DPS

Perangkat yang menggunakan Device Provisioning Service (DPS) dapat menyertakan yang modelId akan digunakan selama proses provisi dengan menggunakan payload JSON berikut.

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

Menggunakan komponen

Seperti yang dijelaskan dalam Memahami komponen dalam model IoT Plug and Play, Anda harus memutuskan apakah Anda ingin menggunakan komponen untuk menjelaskan perangkat Anda. Saat Anda menggunakan komponen, perangkat harus mengikuti aturan yang dijelaskan di bagian berikut.

telemetri

Komponen default tidak memerlukan properti khusus yang ditambahkan ke pesan telemetri.

Saat Anda menggunakan komponen berlapis, perangkat harus mengatur properti pesan dengan nama komponen:

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

  Map<String, Object> payload = singletonMap("temperature", currentTemperature);

  Message message = new Message(gson.toJson(payload));
  message.setContentEncoding("utf-8");
  message.setContentTypeFinal("application/json");

  if (componentName != null) {
      message.setProperty("$.sub", componentName);
  }
  deviceClient.sendEventAsync(message, new MessageIotHubEventCallback(), message);
}

Properti baca-saja

Melaporkan properti dari komponen default tidak memerlukan konstruksi khusus:

Property reportedProperty = new Property("maxTempSinceLastReboot", 38.7);

deviceClient.sendReportedProperties(Collections.singleton(reportedProperty));

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "maxTempSinceLastReboot" : 38.7
  }
}

Saat Anda menggunakan komponen berlapis, buat properti dalam nama komponen dan sertakan penanda:

Map<String, Object> componentProperty = new HashMap<String, Object>() {{
    put("__t", "c");
    put("maxTemperature", 38.7);
}};

Set<Property> reportedProperty = new Property("thermostat1", componentProperty)

deviceClient.sendReportedProperties(reportedProperty);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1" : {  
      "__t" : "c",  
      "maxTemperature" : 38.7
     }
  }
}

Properti bisa-tulis

Properti ini dapat diatur oleh perangkat atau diperbarui oleh aplikasi back-end. Jika aplikasi back-end memperbarui properti, klien menerima pemberitahuan sebagai panggilan balik di DeviceClient atau ModuleClient. Untuk mengikuti konvensi IoT Plug and Play, perangkat harus memberi tahu layanan bahwa properti telah berhasil diterima.

Jika jenis properti adalah Object, layanan harus mengirimkan objek lengkap ke perangkat meskipun hanya memperbarui subset bidang objek tersebut. Pengakuan yang dikirim perangkat juga harus merupakan objek yang lengkap.

Melaporkan properti bisa-tulis

Saat perangkat melaporkan properti bisa-tulis, perangkat tersebut harus menyertakan nilai ack yang ditentukan dalam konvensi.

Untuk melaporkan properti bisa-tulis dari komponen default:

@AllArgsConstructor
private static class EmbeddedPropertyUpdate {
  @NonNull
  @SerializedName("value")
  public Object value;
  @NonNull
  @SerializedName("ac")
  public Integer ackCode;
  @NonNull
  @SerializedName("av")
  public Integer ackVersion;
  @SerializedName("ad")
  public String ackDescription;
}

EmbeddedPropertyUpdate completedUpdate = new EmbeddedPropertyUpdate(23.2, 200, 3, "Successfully updated target temperature");
Property reportedPropertyCompleted = new Property("targetTemperature", completedUpdate);
deviceClient.sendReportedProperties(Collections.singleton(reportedPropertyCompleted));

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "Successfully updated target temperature"
      }
  }
}

Untuk melaporkan properti bisa-tulis dari komponen berlapis, kembarannya harus menyertakan penanda:

Map<String, Object> embeddedProperty = new HashMap<String, Object>() {{
    put("value", 23.2);
    put("ac", 200);
    put("av", 3);
    put("ad", "complete");
}};

Map<String, Object> componentProperty = new HashMap<String, Object>() {{
    put("__t", "c");
    put("targetTemperature", embeddedProperty);
}};

Set<Property> reportedProperty = new Property("thermostat1", componentProperty));

deviceClient.sendReportedProperties(reportedProperty);

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1": {
      "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Berlangganan pembaruan properti yang diinginkan

Layanan dapat memperbarui properti yang diinginkan yang memicu pemberitahuan pada perangkat yang tersambung. Pemberitahuan ini mencakup properti yang diinginkan yang diperbarui, termasuk nomor versi yang mengidentifikasi pembaruan. Perangkat harus menyertakan nomor versi ini dalam pesan yang ack dikirim kembali ke layanan.

Komponen default melihat satu properti dan membuat ack yang dilaporkan dengan versi yang diterima:

private static class TargetTemperatureUpdateCallback implements TwinPropertyCallBack {

    String propertyName = "targetTemperature";

    @Override
    public void TwinPropertyCallBack(Property property, Object context) {
        double targetTemperature = ((Number)property.getValue()).doubleValue();

        EmbeddedPropertyUpdate completedUpdate = new EmbeddedPropertyUpdate(temperature, 200, property.getVersion(), "Successfully updated target temperature");
        Property reportedPropertyCompleted = new Property(propertyName, completedUpdate);
        deviceClient.sendReportedProperties(Collections.singleton(reportedPropertyCompleted));
    }
}

// ...

deviceClient.startDeviceTwin(new TwinIotHubEventCallback(), null, new TargetTemperatureUpdateCallback(), null);
Map<Property, Pair<TwinPropertyCallBack, Object>> desiredPropertyUpdateCallback =
  Collections.singletonMap(
    new Property("targetTemperature", null),
    new Pair<>(new TargetTemperatureUpdateCallback(), null));
deviceClient.subscribeToTwinDesiredProperties(desiredPropertyUpdateCallback);

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "targetTemperature": 23.2,
    "$version" : 3
  },
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "Successfully updated target temperature"
      }
  }
}

Komponen berlapis menerima properti yang diinginkan yang dibungkus dengan nama komponen, dan harus melaporkan kembali ack properti yang dilaporkan:

private static final Map<String, Double> temperature = new HashMap<>();

private static class TargetTemperatureUpdateCallback implements TwinPropertyCallBack {

    String propertyName = "targetTemperature";

    @Override
    public void TwinPropertyCallBack(Property property, Object context) {
        String componentName = (String) context;

        if (property.getKey().equalsIgnoreCase(componentName)) {
            double targetTemperature = (double) ((TwinCollection) property.getValue()).get(propertyName);

            Map<String, Object> embeddedProperty = new HashMap<String, Object>() {{
                put("value", temperature.get(componentName));
                put("ac", 200);
                put("av", property.getVersion().longValue());
                put("ad", "Successfully updated target temperature.");
            }};

            Map<String, Object> componentProperty = new HashMap<String, Object>() {{
                put("__t", "c");
                put(propertyName, embeddedProperty);
            }};

            Set<Property> completedPropertyPatch = new Property(componentName, componentProperty));

            deviceClient.sendReportedProperties(completedPropertyPatch);
        } else {
            log.debug("Property: Received an unrecognized property update from service.");
        }
    }
}

// ...

deviceClient.startDeviceTwin(new TwinIotHubEventCallback(), null, new GenericPropertyUpdateCallback(), null);
Map<Property, Pair<TwinPropertyCallBack, Object>> desiredPropertyUpdateCallback = Stream.of(
  new AbstractMap.SimpleEntry<Property, Pair<TwinPropertyCallBack, Object>>(
    new Property("thermostat1", null),
    new Pair<>(new TargetTemperatureUpdateCallback(), "thermostat1")),
  new AbstractMap.SimpleEntry<Property, Pair<TwinPropertyCallBack, Object>>(
    new Property("thermostat2", null),
    new Pair<>(new TargetTemperatureUpdateCallback(), "thermostat2"))
).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));

deviceClient.subscribeToTwinDesiredProperties(desiredPropertyUpdateCallback);

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "thermostat1" : {
        "__t" : "c",
        "targetTemperature": 23.2,
    }
    "$version" : 3
  },
  "reported": {
    "thermostat1" : {
        "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Perintah

Komponen default menerima nama perintah saat dipanggil oleh layanan.

Komponen berlapis menerima nama perintah yang diawali dengan nama komponen dan pemisah *.

deviceClient.subscribeToDeviceMethod(new MethodCallback(), null, new MethodIotHubEventCallback(), null);

// ...
private static final Map<String, Double> temperature = new HashMap<>();

private static class MethodCallback implements DeviceMethodCallback {
  final String reboot = "reboot";
  final String getMaxMinReport1 = "thermostat1*getMaxMinReport";
  final String getMaxMinReport2 = "thermostat2*getMaxMinReport";

  @Override
  public DeviceMethodData call(String methodName, Object methodData, Object context) {
    String jsonRequest = new String((byte[]) methodData, StandardCharsets.UTF_8);

    switch (methodName) {
      case reboot:
        int delay = gson.fromJson(jsonRequest, Integer.class);

        Thread.sleep(delay * 1000);

        temperature.put("thermostat1", 0.0d);
        temperature.put("thermostat2", 0.0d);

        return new DeviceMethodData(200, null);

      // ...

      default:
        log.debug("Command: command=\"{}\" is not implemented, no action taken.", methodName);
          return new DeviceMethodData(404, null);
    }
  }
}

Payload permintaan dan respons

Perintah menggunakan jenis untuk menentukan payload permintaan dan responsnya. Perangkat harus mendeserialisasi parameter input yang masuk dan menserialisasi respon.

Contoh berikut menunjukkan cara mengimplementasikan perintah dengan jenis kompleks yang ditentukan dalam payload:

{
  "@type": "Command",
  "name": "getMaxMinReport",
  "displayName": "Get Max-Min report.",
  "description": "This command returns the max, min and average temperature from the specified time to the current time.",
  "request": {
    "name": "since",
    "displayName": "Since",
    "description": "Period to return the max-min report.",
    "schema": "dateTime"
  },
  "response": {
    "name" : "tempReport",
    "displayName": "Temperature Report",
    "schema": {
      "@type": "Object",
      "fields": [
        {
          "name": "maxTemp",
          "displayName": "Max temperature",
          "schema": "double"
        },
        {
          "name": "minTemp",
          "displayName": "Min temperature",
          "schema": "double"
        },
        {
          "name" : "avgTemp",
          "displayName": "Average Temperature",
          "schema": "double"
        },
        {
          "name" : "startTime",
          "displayName": "Start Time",
          "schema": "dateTime"
        },
        {
          "name" : "endTime",
          "displayName": "End Time",
          "schema": "dateTime"
        }
      ]
    }
  }
}

Cuplikan kode berikut menunjukkan bagaimana perangkat mengimplementasikan definisi perintah ini, termasuk jenis yang digunakan untuk mengaktifkan serialisasi dan deserialisasi:

deviceClient.subscribeToDeviceMethod(new GetMaxMinReportMethodCallback(), "getMaxMinReport", new MethodIotHubEventCallback(), "getMaxMinReport");

// ...

private static class GetMaxMinReportMethodCallback implements DeviceMethodCallback {
    String commandName = "getMaxMinReport";

    @Override
    public DeviceMethodData call(String methodName, Object methodData, Object context) {

        String jsonRequest = new String((byte[]) methodData, StandardCharsets.UTF_8);
        Date since = gson.fromJson(jsonRequest, Date.class);

        String responsePayload = String.format(
                "{\"maxTemp\": %.1f, \"minTemp\": %.1f, \"avgTemp\": %.1f, \"startTime\": \"%s\", \"endTime\": \"%s\"}",
                maxTemp,
                minTemp,
                avgTemp,
                since,
                endTime);

        return new DeviceMethodData(StatusCode.COMPLETED.value, responsePayload);
    }
}

Tip

Nama permintaan dan respons tidak ada dalam payload yang diserialisasi yang dikirimkan melalui kabel.

Pengumuman ID Model

Untuk mengumumkan ID model, perangkat harus menyertakannya dalam informasi sambungan:

const modelIdObject = { modelId: 'dtmi:com:example:Thermostat;1' };
const client = Client.fromConnectionString(deviceConnectionString, Protocol);
await client.setOptions(modelIdObject);
await client.open();

Tip

Untuk modul dan IoT Edge, gunakan ModuleClient sebagai ganti Client.

Tip

Ini adalah satu-satunya saat perangkat dapat mengatur ID model, karena tidak dapat diperbarui jika perangkat telah tersambung.

Payload DPS

Perangkat yang menggunakan Device Provisioning Service (DPS) dapat menyertakan yang modelId akan digunakan selama proses provisi dengan menggunakan payload JSON berikut.

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

Menggunakan komponen

Seperti yang dijelaskan dalam Memahami komponen dalam model IoT Plug and Play, Anda harus memutuskan apakah Anda ingin menggunakan komponen untuk menjelaskan perangkat Anda. Saat Anda menggunakan komponen, perangkat harus mengikuti aturan yang dijelaskan di bagian berikut.

telemetri

Komponen default tidak memerlukan properti khusus yang ditambahkan ke pesan telemetri.

Saat Anda menggunakan komponen berlapis, perangkat harus mengatur properti pesan dengan nama komponen:

async function sendTelemetry(deviceClient, data, index, componentName) {
  const msg = new Message(data);
  if (!!(componentName)) {
    msg.properties.add(messageSubjectProperty, componentName);
  }
  msg.contentType = 'application/json';
  msg.contentEncoding = 'utf-8';
  await deviceClient.sendEvent(msg);
}

Properti baca-saja

Melaporkan properti dari komponen default tidak memerlukan konstruksi khusus:

const createReportPropPatch = (propertiesToReport) => {
  let patch;
  patch = { };
  patch = propertiesToReport;
  return patch;
};

deviceTwin = await client.getTwin();
patchThermostat = createReportPropPatch({
  maxTempSinceLastReboot: 38.7
});

deviceTwin.properties.reported.update(patchThermostat, function (err) {
  if (err) throw err;
});

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "maxTempSinceLastReboot" : 38.7
  }
}

Saat Anda menggunakan komponen berlapis, properti harus dibuat dalam nama komponen dan menyertakan penanda:

helperCreateReportedPropertiesPatch = (propertiesToReport, componentName) => {
  let patch;
  if (!!(componentName)) {
    patch = { };
    propertiesToReport.__t = 'c';
    patch[componentName] = propertiesToReport;
  } else {
    patch = { };
    patch = propertiesToReport;
  }
  return patch;
};

deviceTwin = await client.getTwin();
patchThermostat1Info = helperCreateReportedPropertiesPatch({
  maxTempSinceLastReboot: 38.7,
}, 'thermostat1');

deviceTwin.properties.reported.update(patchThermostat1Info, function (err) {
  if (err) throw err;
});

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1" : {  
      "__t" : "c",  
      "maxTempSinceLastReboot" : 38.7
     } 
  }
}

Properti bisa-tulis

Properti ini dapat diatur oleh perangkat atau diperbarui oleh aplikasi back-end. Jika aplikasi back-end memperbarui properti, klien menerima pemberitahuan sebagai panggilan balik di Client atau ModuleClient. Untuk mengikuti konvensi IoT Plug and Play, perangkat harus memberi tahu layanan bahwa properti telah berhasil diterima.

Jika jenis properti adalah Object, layanan harus mengirimkan objek lengkap ke perangkat meskipun hanya memperbarui subset bidang objek tersebut. Pengakuan yang dikirim perangkat juga harus merupakan objek yang lengkap.

Melaporkan properti bisa-tulis

Saat perangkat melaporkan properti bisa-tulis, perangkat tersebut harus menyertakan nilai ack yang ditentukan dalam konvensi.

Untuk melaporkan properti bisa-tulis dari komponen default:

patch = {
  targetTemperature:
    {
      'value': 23.2,
      'ac': 200,  // using HTTP status codes
      'ad': 'reported default value',
      'av': 0  // not read from a desired property
    }
};
deviceTwin.properties.reported.update(patch, function (err) {
  if (err) throw err;
});

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "targetTemperature": {
      "value": 23.2,
      "ac": 200,
      "av": 0,
      "ad": "reported default value"
    }
  }
}

Untuk melaporkan properti bisa-tulis dari komponen berlapis, kembarannya harus menyertakan penanda:

patch = {
  thermostat1: {
    '__t' : 'c',
    targetTemperature: {
      'value': 23.2,
      'ac': 200,  // using HTTP status codes
      'ad': 'reported default value',
      'av': 0  // not read from a desired property
    }
  }
};
deviceTwin.properties.reported.update(patch, function (err) {
  if (err) throw err;
});

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1": {
      "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 0,
          "ad": "complete"
      }
    }
  }
}

Berlangganan pembaruan properti yang diinginkan

Layanan dapat memperbarui properti yang diinginkan yang memicu pemberitahuan pada perangkat yang tersambung. Pemberitahuan ini mencakup properti yang diinginkan yang diperbarui, termasuk nomor versi yang mengidentifikasi pembaruan. Perangkat harus menyertakan nomor versi ini dalam pesan yang ack dikirim kembali ke layanan.

Komponen default melihat satu properti dan membuat ack yang dilaporkan dengan versi yang diterima:

const propertyUpdateHandler = (deviceTwin, propertyName, reportedValue, desiredValue, version) => {
  const patch = createReportPropPatch(
    { [propertyName]:
      {
        'value': desiredValue,
        'ac': 200,
        'ad': 'Successfully executed patch for ' + propertyName,
        'av': version
      }
    });
  updateComponentReportedProperties(deviceTwin, patch);
};

desiredPropertyPatchHandler = (deviceTwin) => {
  deviceTwin.on('properties.desired', (delta) => {
    const versionProperty = delta.$version;

    Object.entries(delta).forEach(([propertyName, propertyValue]) => {
      if (propertyName !== '$version') {
        propertyUpdateHandler(deviceTwin, propertyName, null, propertyValue, versionProperty);
      }
    });
  });
};

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "targetTemperature": 23.2,
    "$version" : 3
  },
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
  }
}

Komponen berlapis menerima properti yang diinginkan yang dibungkus dengan nama komponen, dan harus melaporkan kembali ack properti yang dilaporkan:

const desiredPropertyPatchListener = (deviceTwin, componentNames) => {
  deviceTwin.on('properties.desired', (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') {
            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 = { };
        const propertyContent = { value: values };
        propertyContent.ac = 200;
        propertyContent.ad = 'Successfully executed patch';
        propertyContent.av = version;
        patchForRoot[key] = propertyContent;
        updateComponentReportedProperties(deviceTwin, patchForRoot, null);
      }
    });
  });
};

Kembaran perangkat untuk komponen menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "thermostat1" : {
        "__t" : "c",
        "targetTemperature": 23.2,
    }
    "$version" : 3
  },
  "reported": {
    "thermostat1" : {
        "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Perintah

Komponen default menerima nama perintah saat dipanggil oleh layanan.

Komponen berlapis menerima nama perintah yang diawali dengan nama komponen dan pemisah *.

const commandHandler = async (request, response) => {
  switch (request.methodName) {
  
  // ...

  case 'thermostat1*reboot': {
    await response.send(200, 'reboot response');
    break;
  }
  default:
    await response.send(404, 'unknown method');
    break;
  }
};

client.onDeviceMethod('thermostat1*reboot', commandHandler);

Payload permintaan dan respons

Perintah menggunakan jenis untuk menentukan payload permintaan dan responsnya. Perangkat harus mendeserialisasi parameter input yang masuk dan menserialisasi respon.

Contoh berikut menunjukkan cara mengimplementasikan perintah dengan jenis kompleks yang ditentukan dalam payload:

{
  "@type": "Command",
  "name": "getMaxMinReport",
  "displayName": "Get Max-Min report.",
  "description": "This command returns the max, min and average temperature from the specified time to the current time.",
  "request": {
    "name": "since",
    "displayName": "Since",
    "description": "Period to return the max-min report.",
    "schema": "dateTime"
  },
  "response": {
    "name" : "tempReport",
    "displayName": "Temperature Report",
    "schema": {
      "@type": "Object",
      "fields": [
        {
          "name": "maxTemp",
          "displayName": "Max temperature",
          "schema": "double"
        },
        {
          "name": "minTemp",
          "displayName": "Min temperature",
          "schema": "double"
        },
        {
          "name" : "avgTemp",
          "displayName": "Average Temperature",
          "schema": "double"
        },
        {
          "name" : "startTime",
          "displayName": "Start Time",
          "schema": "dateTime"
        },
        {
          "name" : "endTime",
          "displayName": "End Time",
          "schema": "dateTime"
        }
      ]
    }
  }
}

Cuplikan kode berikut menunjukkan bagaimana perangkat mengimplementasikan definisi perintah ini, termasuk jenis yang digunakan untuk mengaktifkan serialisasi dan deserialisasi:

class TemperatureSensor {

  // ...

  getMaxMinReportObject() {
    return {
      maxTemp: this.maxTemp,
      minTemp: this.minTemp,
      avgTemp: this.cumulativeTemperature / this.numberOfTemperatureReadings,
      endTime: (new Date(Date.now())).toISOString(),
      startTime: this.startTime
    };
  }
}

// ...

const deviceTemperatureSensor = new TemperatureSensor();

const commandHandler = async (request, response) => {
  switch (request.methodName) {
  case commandMaxMinReport: {
    console.log('MaxMinReport ' + request.payload);
    await response.send(200, deviceTemperatureSensor.getMaxMinReportObject());
    break;
  }
  default:
    await response.send(404, 'unknown method');
    break;
  }
};

Tip

Nama permintaan dan respons tidak ada dalam payload yang diserialisasi yang dikirimkan melalui kabel.

Pengumuman ID Model

Untuk mengumumkan ID model, perangkat harus menyertakannya dalam informasi sambungan:

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,
)

Tip

Untuk modul dan IoT Edge, gunakan IoTHubModuleClient sebagai ganti IoTHubDeviceClient.

Tip

Ini adalah satu-satunya saat perangkat dapat mengatur ID model, karena tidak dapat diperbarui jika perangkat telah tersambung.

Payload DPS

Perangkat yang menggunakan Device Provisioning Service (DPS) dapat menyertakan yang modelId akan digunakan selama proses provisi dengan menggunakan payload JSON berikut.

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

Menggunakan komponen

Seperti yang dijelaskan dalam Memahami komponen dalam model IoT Plug and Play, Anda harus memutuskan apakah Anda ingin menggunakan komponen untuk menjelaskan perangkat Anda. Saat Anda menggunakan komponen, perangkat harus mengikuti aturan yang dijelaskan di bagian berikut.

telemetri

Komponen default tidak memerlukan properti khusus yang ditambahkan ke pesan telemetri.

Saat Anda menggunakan komponen berlapis, perangkat harus mengatur properti pesan dengan nama komponen:

async def send_telemetry_from_temp_controller(device_client, telemetry_msg, component_name=None):
    msg = Message(json.dumps(telemetry_msg))
    msg.content_encoding = "utf-8"
    msg.content_type = "application/json"
    if component_name:
        msg.custom_properties["$.sub"] = component_name
    await device_client.send_message(msg)

Properti baca-saja

Melaporkan properti dari komponen default tidak memerlukan konstruksi khusus:

await device_client.patch_twin_reported_properties({"maxTempSinceLastReboot": 38.7})

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
      "maxTempSinceLastReboot" : 38.7
  }
}

Saat menggunakan komponen berlapis, properti harus dibuat di dalam nama komponen dan menyertakan penanda:

inner_dict = {}
inner_dict["targetTemperature"] = 38.7
inner_dict["__t"] = "c"
prop_dict = {}
prop_dict["thermostat1"] = inner_dict

await device_client.patch_twin_reported_properties(prop_dict)

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1" : {  
      "__t" : "c",  
      "maxTempSinceLastReboot" : 38.7
     }
  }
}

Properti bisa-tulis

Properti ini dapat diatur oleh perangkat atau diperbarui oleh aplikasi back-end. Jika aplikasi back-end memperbarui properti, klien menerima pemberitahuan sebagai panggilan balik di IoTHubDeviceClient atau IoTHubModuleClient. Untuk mengikuti konvensi IoT Plug and Play, perangkat harus memberi tahu layanan bahwa properti telah berhasil diterima.

Jika jenis properti adalah Object, layanan harus mengirimkan objek lengkap ke perangkat meskipun hanya memperbarui subset bidang objek tersebut. Pengakuan yang dikirim perangkat juga harus merupakan objek yang lengkap.

Melaporkan properti bisa-tulis

Saat perangkat melaporkan properti bisa-tulis, perangkat tersebut harus menyertakan nilai ack yang ditentukan dalam konvensi.

Untuk melaporkan properti bisa-tulis dari komponen default:

prop_dict = {}
prop_dict["targetTemperature"] = {
    "ac": 200,
    "ad": "reported default value",
    "av": 0,
    "value": 23.2
}

await device_client.patch_twin_reported_properties(prop_dict)

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "targetTemperature": {
      "value": 23.2,
      "ac": 200,
      "av": 0,
      "ad": "reported default value"
    }
  }
}

Untuk melaporkan properti bisa-tulis dari komponen berlapis, kembarannya harus menyertakan penanda:

inner_dict = {}
inner_dict["targetTemperature"] = {
    "ac": 200,
    "ad": "reported default value",
    "av": 0,
    "value": 23.2
}
inner_dict["__t"] = "c"
prop_dict = {}
prop_dict["thermostat1"] = inner_dict

await device_client.patch_twin_reported_properties(prop_dict)

Perangkat kembar diperbarui dengan properti yang dilaporkan berikut:

{
  "reported": {
    "thermostat1": {
      "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 0,
          "ad": "complete"
      }
    }
  }
}

Berlangganan pembaruan properti yang diinginkan

Layanan dapat memperbarui properti yang diinginkan yang memicu pemberitahuan pada perangkat yang tersambung. Pemberitahuan ini mencakup properti yang diinginkan yang diperbarui, termasuk nomor versi yang mengidentifikasi pembaruan. Perangkat harus menyertakan nomor versi ini dalam pesan yang ack dikirim kembali ke layanan.

Komponen default melihat satu properti dan membuat ack yang dilaporkan dengan versi yang diterima:

async def execute_property_listener(device_client):
    ignore_keys = ["__t", "$version"]
    while True:
        patch = await device_client.receive_twin_desired_properties_patch()  # blocking call

        version = patch["$version"]
        prop_dict = {}

        for prop_name, prop_value in patch.items():
            if prop_name in ignore_keys:
                continue
            else:
                prop_dict[prop_name] = {
                    "ac": 200,
                    "ad": "Successfully executed patch",
                    "av": version,
                    "value": prop_value,
                }

        await device_client.patch_twin_reported_properties(prop_dict)

Perangkat berlapis untuk komponen berlapis menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "targetTemperature": 23.2,
    "$version" : 3
  },
  "reported": {
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
  }
}

Komponen berlapis menerima properti yang diinginkan yang dibungkus dengan nama komponen, dan harus melaporkan kembali ack properti yang dilaporkan:

def create_reported_properties_from_desired(patch):
    ignore_keys = ["__t", "$version"]
    component_prefix = list(patch.keys())[0]
    values = patch[component_prefix]

    version = patch["$version"]
    inner_dict = {}

    for prop_name, prop_value in values.items():
        if prop_name in ignore_keys:
            continue
        else:
            inner_dict["ac"] = 200
            inner_dict["ad"] = "Successfully executed patch"
            inner_dict["av"] = version
            inner_dict["value"] = prop_value
            values[prop_name] = inner_dict

    properties_dict = dict()
    if component_prefix:
        properties_dict[component_prefix] = values
    else:
        properties_dict = values

    return properties_dict

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

        await device_client.patch_twin_reported_properties(properties_dict)

Kembaran perangkat untuk komponen menunjukkan bagian yang diinginkan dan dilaporkan sebagai berikut:

{
  "desired" : {
    "thermostat1" : {
        "__t" : "c",
        "targetTemperature": 23.2,
    }
    "$version" : 3
  },
  "reported": {
    "thermostat1" : {
        "__t" : "c",
      "targetTemperature": {
          "value": 23.2,
          "ac": 200,
          "av": 3,
          "ad": "complete"
      }
    }
  }
}

Perintah

Komponen default menerima nama perintah saat dipanggil oleh layanan.

Komponen berlapis menerima nama perintah yang diawali dengan nama komponen dan pemisah *.

command_request = await device_client.receive_method_request("thermostat1*reboot")

Payload permintaan dan respons

Perintah menggunakan jenis untuk menentukan payload permintaan dan responsnya. Perangkat harus mendeserialisasi parameter input yang masuk dan menserialisasi respon.

Contoh berikut menunjukkan cara mengimplementasikan perintah dengan jenis kompleks yang ditentukan dalam payload:

{
  "@type": "Command",
  "name": "getMaxMinReport",
  "displayName": "Get Max-Min report.",
  "description": "This command returns the max, min and average temperature from the specified time to the current time.",
  "request": {
    "name": "since",
    "displayName": "Since",
    "description": "Period to return the max-min report.",
    "schema": "dateTime"
  },
  "response": {
    "name" : "tempReport",
    "displayName": "Temperature Report",
    "schema": {
      "@type": "Object",
      "fields": [
        {
          "name": "maxTemp",
          "displayName": "Max temperature",
          "schema": "double"
        },
        {
          "name": "minTemp",
          "displayName": "Min temperature",
          "schema": "double"
        },
        {
          "name" : "avgTemp",
          "displayName": "Average Temperature",
          "schema": "double"
        },
        {
          "name" : "startTime",
          "displayName": "Start Time",
          "schema": "dateTime"
        },
        {
          "name" : "endTime",
          "displayName": "End Time",
          "schema": "dateTime"
        }
      ]
    }
  }
}

Cuplikan kode berikut menunjukkan bagaimana perangkat mengimplementasikan definisi perintah ini, termasuk jenis yang digunakan untuk mengaktifkan serialisasi dan deserialisasi:

def create_max_min_report_response(values):
    response_dict = {
        "maxTemp": max_temp,
        "minTemp": min_temp,
        "avgTemp": sum(avg_temp_list) / moving_window_size,
        "startTime": (datetime.now() - timedelta(0, moving_window_size * 8)).isoformat(),
        "endTime": datetime.now().isoformat(),
    }
    # serialize response dictionary into a JSON formatted str
    response_payload = json.dumps(response_dict, default=lambda o: o.__dict__, sort_keys=True)
    return response_payload

Tip

Nama permintaan dan respons tidak ada dalam payload yang diserialisasi yang dikirimkan melalui kabel.

Langkah berikutnya

Sekarang setelah Anda mempelajari tentang pengembangan perangkat IoT Plug and Play, berikut adalah beberapa sumber daya lainnya: