Delen via


Ontwikkelaarshandleiding voor IoT Plug en Play-apparaten

Met IoT Plug en Play kunt u IoT-apparaten bouwen die hun mogelijkheden adverteren naar Azure IoT-toepassingen. Voor IoT Plug en Play-apparaten is geen handmatige configuratie vereist wanneer een klant deze verbindt met IoT Plug en Play-toepassingen zoals IoT Central.

U kunt een IoT-apparaat rechtstreeks implementeren met behulp van modules of met behulp van IoT Edge-modules.

In deze handleiding worden de basisstappen beschreven die nodig zijn voor het maken van een apparaat, module of IoT Edge-module die de IoT Plug en Play-conventies volgt.

Als u een IoT Plug and Play-apparaat, module of IoT Edge-module wilt bouwen, voert u de volgende stappen uit:

  1. Zorg ervoor dat uw apparaat het MQTT- of MQTT-protocol gebruikt via het WebSockets-protocol om verbinding te maken met Azure IoT Hub.
  2. Maak een DTDL-model (Digital Twins Definition Language) om uw apparaat te beschrijven. Zie Onderdelen in IoT Plug en Play-modellen begrijpen voor meer informatie.
  3. Werk uw apparaat of module bij om het model-id aan te kondigen als onderdeel van de apparaatverbinding.
  4. Telemetrie, eigenschappen en opdrachten implementeren die voldoen aan de IoT Plug en Play-conventies

Zodra de implementatie van uw apparaat of module gereed is, gebruikt u Azure IoT Explorer om te controleren of het apparaat de IoT Plug en Play-conventies volgt.

Voorbeeldcode

Je kunt de voorbeeldcode voor veel van de IoT Plug and Play-constructies die in dit artikel worden beschreven vinden in de GitHub-opslagplaats voor Azure IoT C SDK's en bibliotheken.

Aankondiging van model-id

Als u de model-id wilt aankondigen, moet het apparaat deze opnemen in de verbindingsgegevens:

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

Hint

Gebruik voor modules en IoT Edge IoTHubModuleClient_LL in plaats van IoTHubDeviceClient_LL.

Hint

Dit is de enige keer dat een apparaat model-id kan instellen, het kan niet worden bijgewerkt nadat het apparaat verbinding heeft gemaakt.

DPS-belading

Apparaten die gebruikmaken van Device Provisioning Service (DPS) kunnen modelId gebruiken tijdens het inrichtingsproces door gebruik te maken van het volgende JSON-payload:

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

Onderdelen gebruiken

Zoals beschreven in Onderdelen begrijpen in IoT Plug en Play-modellen, moet u beslissen of u onderdelen wilt gebruiken om uw apparaten te beschrijven. Wanneer u onderdelen gebruikt, moeten apparaten de regels volgen die in de volgende secties worden beschreven:

Telemetrie

Voor een standaardonderdeel is geen speciale eigenschap vereist die is toegevoegd aan het telemetriebericht.

Wanneer u geneste onderdelen gebruikt, moeten apparaten een berichteigenschap instellen met de naam van het onderdeel:

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

Alleen-lezen eigenschappen

Voor het rapporteren van een eigenschap van het standaardonderdeel is geen speciale constructie vereist:

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

De device twin wordt bijgewerkt met de volgende gerapporteerde eigenschappen:

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

Wanneer u geneste onderdelen gebruikt, maakt u eigenschappen binnen de onderdeelnaam en voegt u een markering toe:

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

De apparaat-twin wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Schrijfbare eigenschappen

Deze eigenschappen kunnen worden ingesteld door het apparaat of worden bijgewerkt door de back-endtoepassing. Als de back-endtoepassing een eigenschap bijwerkt, ontvangt de client een melding als callback in de DeviceClient of ModuleClient. Om de IoT Plug en Play-conventies te volgen, moet het apparaat de service informeren dat de eigenschap succesvol is ontvangen.

Als het eigenschapstype is Object, moet de service een volledig object naar het apparaat verzenden, zelfs als het alleen een subset van de velden van het object bijwerkt. De bevestiging die het apparaat verzendt, kan ook een volledig object zijn.

Een beschrijfbare eigenschap rapporteren

Wanneer een apparaat een beschrijfbare eigenschap rapporteert, moet het de ack waarden bevatten die in de conventies zijn gedefinieerd.

Een beschrijfbare eigenschap rapporteren vanuit het standaardonderdeel:

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

De device twin wordt bijgewerkt met de volgende geregistreerde eigenschap:

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

Als u een beschrijfbare eigenschap van een genest onderdeel wilt rapporteren, moet de dubbel een markering bevatten:

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

De apparaatdubbel wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Abonneren op gewenste vastgoedupdates

Services kunnen de gewenste eigenschappen bijwerken die een melding activeren op de verbonden apparaten. Deze melding bevat de bijgewerkte gewenste eigenschappen, inclusief het versienummer waarmee de update wordt geïdentificeerd. Apparaten moeten dit versienummer bevatten in het ack bericht dat naar de service wordt verzonden.

Een standaardcomponent ziet de enige eigenschap en creëert de gerapporteerde ack met de ontvangen versie.

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

De apparaatdubbel voor een genest onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Een geneste component ontvangt de gewenste eigenschappen die zijn verpakt met de naam van het onderdeel en moet de ack gerapporteerde eigenschap rapporteren:

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);
}

De device twin voor een genest onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Opdrachten

Een standaardonderdeel ontvangt de opdrachtnaam precies zoals die door de service werd aangeroepen.

Een geneste component ontvangt de opdrachtnaam, voorafgegaan door de naam van de component en het *-scheidingsteken.

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

Nettoladingen aanvragen en antwoorden

Opdrachten gebruiken typen om hun nettoladingen voor aanvragen en antwoorden te definiëren. Een apparaat moet de binnenkomende invoerparameter deserialiseren en het antwoord serialiseren.

In het volgende voorbeeld wordt uitgelegd hoe u een opdracht implementeert met complexe typen die zijn gedefinieerd in de payloads.

{
  "@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"
        }
      ]
    }
  }
}

De volgende codefragmenten laten zien hoe een apparaat deze opdrachtdefinitie implementeert, inclusief de typen die worden gebruikt om serialisatie en deserialisatie in te schakelen:

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;
}

Hint

De namen van verzoeken en antwoorden zijn niet aanwezig in de geserialiseerde payloads die over de lijn worden verzonden.

SDK's

De codefragmenten in dit artikel zijn gebaseerd op voorbeelden die gebruikmaken van de Azure IoT Middleware-invoegtoepassing voor Eclipse ThreadX. De invoegtoepassing is een bindingslaag tussen de Eclipse ThreadX en de Azure SDK voor Embedded C.

De codefragmenten in dit artikel zijn gebaseerd op de volgende voorbeelden:

Aankondiging van model-id

Als u de model-id wilt aankondigen, moet het apparaat deze opnemen in de verbindingsgegevens:

#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);

Hint

Dit is de enige keer dat een apparaat model-id kan instellen, het kan niet worden bijgewerkt nadat het apparaat verbinding heeft gemaakt.

DPS-nettolading

Apparaten die de Device Provisioning Service (DPS) gebruiken, kunnen het modelId gebruiken tijdens het inrichtingsproces door de volgende JSON-payload te gebruiken:

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

In het voorbeeld wordt de volgende code gebruikt om deze payload te verzenden.

#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);

Onderdelen gebruiken

Zoals beschreven in Onderdelen begrijpen in IoT Plug en Play-modellen, moet u beslissen of u onderdelen wilt gebruiken om uw apparaten te beschrijven. Wanneer u onderdelen gebruikt, moeten apparaten de regels volgen die in de volgende secties worden beschreven. Om het werken met de IoT Plug en Play-conventies voor onderdelen te vereenvoudigen, gebruiken de voorbeelden de helperfuncties in nx_azure_iot_hub_client.h.

Telemetrie

Voor een standaardonderdeel is geen speciale eigenschap vereist die is toegevoegd aan het telemetriebericht.

Wanneer u geneste onderdelen gebruikt, moeten apparaten een berichteigenschap instellen met de naam van het onderdeel. In het volgende codefragment component_name_ptr is dit de naam van een onderdeel, zoals thermostat1. De helperfunctie nx_azure_iot_pnp_helper_telemetry_message_create die is gedefinieerd in nx_azure_iot_pnp_helpers.h voegt de berichteigenschap toe met de naam van het onderdeel:

#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);
}

Alleen-lezen eigenschappen

Voor het rapporteren van een eigenschap van het standaardonderdeel is geen speciale constructie vereist:

#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))))
{
    // ...
}

De apparaat-tweeling wordt bijgewerkt met het volgende gerapporteerde attribuut:

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

Wanneer u geneste componenten gebruikt, moeten eigenschappen binnen de naam van het component worden gedefinieerd en een markering bevatten. In het volgende codefragment component_name_ptr is dit de naam van een onderdeel, zoals thermostat1. De helperfunctie nx_azure_iot_pnp_helper_build_reported_property die is gedefinieerd in nx_azure_iot_pnp_helpers.h , maakt de gerapporteerde eigenschap in de juiste indeling:

#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))))
    {
        // ...
    }

    // ...
}

De apparaatdubbel wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Schrijfbare eigenschappen

Deze eigenschappen kunnen worden ingesteld door het apparaat of worden bijgewerkt door de back-endtoepassing. Als u de IoT Plug en Play-conventies wilt volgen, moet het apparaat de service informeren dat de eigenschap succesvol is ontvangen.

Een beschrijfbare eigenschap rapporteren

Wanneer een apparaat een beschrijfbare eigenschap rapporteert, moet het de ack waarden bevatten die in de conventies zijn gedefinieerd.

Een beschrijfbare eigenschap rapporteren vanuit het standaardonderdeel:

#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
    // ...
}

De apparaat-tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Als u een beschrijfbare eigenschap van een geneste component wilt rapporteren, moet de tweeling een marker bevatten en moeten de eigenschappen worden aangemaakt binnen de componentnaam. In het volgende codefragment component_name_ptr is dit de naam van een onderdeel, zoals thermostat1. De helperfunctie nx_azure_iot_pnp_helper_build_reported_property_with_status die is gedefinieerd in nx_azure_iot_pnp_helpers.h maakt de payload van de gerapporteerde eigenschap:

#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
    {
        // ...
    }

    // ...
}

De digitale tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Abonneer je op gewenste woningupdates

Services kunnen de gewenste eigenschappen bijwerken die een melding activeren op de verbonden apparaten. Deze melding bevat de bijgewerkte gewenste eigenschappen en het versienummer waarmee de update wordt geïdentificeerd. Apparaten moeten dit versienummer bevatten in het ack bericht dat naar de service wordt verzonden.

Een standaardonderdeel ziet de individuele eigenschap en maakt het gerapporteerde ack met de ontvangen versie.

#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);

    // ...
}

Een geneste component ontvangt de gewenste eigenschappen verpakt met de naam van de component en creëert de gerapporteerde ack met de ontvangen versie.

#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);

    // ...
}

De device twin voor een genest onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Opdrachten

Een standaardonderdeel ontvangt de opdrachtnaam terwijl het is aangeroepen door de service.

Een geneste component ontvangt de opdrachtnaam met als voorvoegsel de naam van de component en het * scheidingsteken. In het volgende codefragment parseert de helperfunctie nx_azure_iot_pnp_helper_command_name_parse die is gedefinieerd in nx_azure_iot_pnp_helpers.h de onderdeelnaam en opdrachtnaam van het bericht dat het apparaat van de service ontvangt:

#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;
        }

        // ...
    }
}

Nettoladingen aanvragen en antwoorden

Opdrachten gebruiken typen om hun nettoladingen voor aanvragen en antwoorden te definiëren. Een apparaat moet de binnenkomende invoerparameter deserialiseren en het antwoord serialiseren.

In het volgende voorbeeld wordt getoond hoe je een commando implementeert met complexe typen die zijn gedefinieerd in de payloads.

{
  "@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"
        }
      ]
    }
  }
}

De volgende codefragmenten laten zien hoe een apparaat deze opdrachtdefinitie implementeert, inclusief de typen die worden gebruikt om serialisatie en deserialisatie in te schakelen:

#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);
}

Hint

De verzoek- en responstnamen zijn niet aanwezig in de geserialiseerde gegevens die via de verbinding worden verzonden.

Voorbeeldcode

U vindt de voorbeeldcode voor veel van de IoT Plug en Play-constructies die in dit artikel worden beschreven in de Microsoft Azure IoT SDK voor .NET GitHub-opslagplaats.

Aankondiging van model-id

Als u de model-id wilt aankondigen, moet het apparaat deze opnemen in de verbindingsgegevens:

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

De nieuwe ClientOptions overbelasting is beschikbaar in alle DeviceClient methoden die worden gebruikt om een verbinding te initialiseren.

Hint

Gebruik voor modules en IoT Edge ModuleClient in plaats van DeviceClient.

Hint

Dit is de enige keer dat een apparaat model-id kan instellen, het kan niet worden bijgewerkt nadat het apparaat verbinding heeft gemaakt.

DPS-nettolading

Apparaten die de Device Provisioning Service (DPS) gebruiken, kunnen de modelId opnemen die moet worden gebruikt tijdens het inrichtingsproces met behulp van de volgende JSON-belasting:

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

Onderdelen gebruiken

Zoals beschreven in Onderdelen begrijpen in IoT Plug en Play-modellen, moet u beslissen of u onderdelen wilt gebruiken om uw apparaten te beschrijven. Wanneer u onderdelen gebruikt, moeten apparaten de regels volgen die in de volgende secties worden beschreven.

Telemetrie

Voor een standaardonderdeel is geen speciale eigenschap vereist die is toegevoegd aan het telemetriebericht.

Wanneer u geneste onderdelen gebruikt, moeten apparaten een berichteigenschap instellen met de naam van het onderdeel:

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);
}

Alleen-lezen eigenschappen

Voor het rapporteren van een eigenschap van het standaardonderdeel is geen speciale constructie vereist:

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

De tweeling van het apparaat wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Wanneer u geneste onderdelen gebruikt, maakt u eigenschappen binnen de onderdeelnaam en voegt u een markering toe:

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

De apparaat-tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Schrijfbare eigenschappen

Deze eigenschappen kunnen worden ingesteld door het apparaat of worden bijgewerkt door de back-endtoepassing. Als de back-endtoepassing een eigenschap bijwerkt, ontvangt de client een melding als een callback in de DeviceClient of ModuleClient. Om de IoT Plug-and-Play-conventies te volgen, moet het apparaat de service informeren dat de eigenschap met succes is ontvangen.

Als het eigenschapstype is Object, moet de service een volledig object naar het apparaat verzenden, zelfs als het alleen een subset van de velden van het object bijwerkt. De bevestiging die het apparaat verzendt, moet ook een volledig object zijn.

Een beschrijfbare eigenschap rapporteren

Wanneer een apparaat een beschrijfbare eigenschap rapporteert, moet het de ack waarden bevatten die in de conventies zijn gedefinieerd.

Een beschrijfbare eigenschap rapporteren vanuit het standaardonderdeel:

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

De apparaat-tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Als u een beschrijfbare eigenschap van een genest onderdeel wilt rapporteren, moet de dubbel een markering bevatten:

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

De apparaat-tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Abonneer je op updates over gewenste vastgoed

Services kunnen de gewenste eigenschappen bijwerken die een melding activeren op de verbonden apparaten. Deze melding bevat de bijgewerkte gewenste eigenschappen, inclusief het versienummer waarmee de update wordt geïdentificeerd. Apparaten moeten dit versienummer bevatten in het ack bericht dat naar de service wordt verzonden.

Een standaardonderdeel herkent de enkele eigenschap en maakt de gerapporteerde ack met de ontvangen versie.

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

De apparaat-twin voor een genest onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Een geneste component ontvangt de gewenste eigenschappen die zijn verpakt met de naam van het onderdeel en moet de ack gerapporteerde eigenschap rapporteren:

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

De apparaatdubbel voor een genest onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Opdrachten

Een standaard component ontvangt de opdrachtnaam zoals het door de service is aangeroepen.

Een genest onderdeel ontvangt de opdrachtnaam, voorafgegaan door de componentnaam en het * scheidingsteken.

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

Nettoladingen aanvragen en antwoorden

Opdrachten gebruiken typen om hun gegevensstructuren voor aanvragen en reacties te definiëren. Een apparaat moet de invoerparameter deserialiseren en het antwoord serialiseren.

In het volgende voorbeeld ziet u hoe u een opdracht implementeert met complexe typen die zijn gedefinieerd in de payloads.

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

De volgende codefragmenten laten zien hoe een apparaat deze opdrachtdefinitie implementeert, inclusief de typen die worden gebruikt om serialisatie en deserialisatie in te schakelen:

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

Hint

De namen van aanvragen en antwoorden zijn niet aanwezig in de geserialiseerde payloads die via het netwerk worden verzonden.

Voorbeeldcode

U vindt de voorbeeldcode voor veel van de IoT Plug en Play-constructies die in dit artikel worden beschreven in de Microsoft Azure IoT SDK's voor Java GitHub-opslagplaats.

Aankondiging van model-id

Als u de model-id wilt aankondigen, moet het apparaat deze opnemen in de verbindingsgegevens:

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

De ClientOptions overbelasting is beschikbaar in alle DeviceClient methoden die worden gebruikt om een verbinding te initialiseren.

Hint

Gebruik voor modules en IoT Edge ModuleClient in plaats van DeviceClient.

Hint

Dit is de enige keer dat een apparaat model-id kan instellen, het kan niet worden bijgewerkt nadat het apparaat verbinding heeft gemaakt.

DPS-nettolading

Apparaten die de Device Provisioning Service (DPS) gebruiken, kunnen modelId opnemen dat moet worden gebruikt tijdens het inrichtingsproces met behulp van de volgende JSON-payload.

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

Onderdelen gebruiken

Zoals beschreven in Onderdelen begrijpen in IoT Plug en Play-modellen, moet u beslissen of u onderdelen wilt gebruiken om uw apparaten te beschrijven. Wanneer u onderdelen gebruikt, moeten apparaten de regels volgen die in de volgende secties worden beschreven.

Telemetrie

Voor een standaardonderdeel is geen speciale eigenschap vereist die is toegevoegd aan het telemetriebericht.

Wanneer u geneste onderdelen gebruikt, moet het apparaat een berichteigenschap instellen met de naam van het onderdeel:

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);
}

Alleen-lezen eigenschappen

Voor het rapporteren van een eigenschap van het standaardonderdeel is geen speciale constructie vereist:

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

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

De apparaatdubbel wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Wanneer u geneste onderdelen gebruikt, maakt u eigenschappen binnen de onderdeelnaam en voegt u een markering toe:

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

De digitale tweeling van het apparaat wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Schrijfbare eigenschappen

Deze eigenschappen kunnen worden ingesteld door het apparaat of worden bijgewerkt door de back-endtoepassing. Als de back-endtoepassing een eigenschap bijwerkt, ontvangt de client een melding als een callback in de DeviceClient of ModuleClient. Als u de IoT Plug en Play-conventies wilt volgen, moet het apparaat de service informeren dat de eigenschap succesvol is ontvangen.

Als het eigenschapstype is Object, moet de service een volledig object naar het apparaat verzenden, zelfs als het alleen een subset van de velden van het object bijwerkt. De bevestiging die het apparaat verzendt, moet ook een volledig object zijn.

Een beschrijfbare eigenschap rapporteren

Wanneer een apparaat een beschrijfbare eigenschap rapporteert, moet het de ack waarden bevatten die in de conventies zijn gedefinieerd.

Een beschrijfbare eigenschap rapporteren vanuit het standaardonderdeel:

@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));

De apparaat-tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Als u een beschrijfbare eigenschap van een geneste component wilt rapporteren, moet de tweeling een markering bevatten.

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

De apparaat-twin wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Inschrijven voor de gewenste updates over eigendommen

Services kunnen de gewenste eigenschappen bijwerken die een melding activeren op de verbonden apparaten. Deze melding bevat de bijgewerkte gewenste eigenschappen, inclusief het versienummer waarmee de update wordt geïdentificeerd. Apparaten moeten dit versienummer bevatten in het ack bericht dat naar de service wordt verzonden.

Een standaardcomponent detecteert de enkelvoudige eigenschap en genereert het gerapporteerde ack met de ontvangen versie.

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

De device twin voor een geneste component toont de gewenste en gerapporteerde secties als volgt:

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

Een geneste component ontvangt de gewenste eigenschappen die zijn verpakt met de naam van het onderdeel en moet de ack gerapporteerde eigenschap rapporteren:

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

De apparaatdubbel voor een genest onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Opdrachten

Een standaardonderdeel ontvangt de opdrachtnaam zoals die door de service is aangeroepen.

Een geneste component ontvangt de opdrachtnaam, voorafgegaan door de naam van de component en het * scheidingsteken.

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);
    }
  }
}

Nettoladingen aanvragen en antwoorden

Opdrachten gebruiken typen om hun nettoladingen voor aanvragen en antwoorden te definiëren. Een apparaat moet de binnenkomende invoerparameter deserialiseren en de respons serialiseren.

In het volgende voorbeeld ziet u hoe u een commando implementeert met complexe typen die zijn gedefinieerd in de payloads:

{
  "@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"
        }
      ]
    }
  }
}

De volgende codefragmenten laten zien hoe een apparaat deze opdrachtdefinitie implementeert, inclusief de typen die worden gebruikt om serialisatie en deserialisatie in te schakelen:

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);
    }
}

Hint

De namen van verzoeken en reacties zijn niet aanwezig in de geserialiseerde datapakketten die via de verbinding worden verzonden.

Voorbeeldcode

U vindt de voorbeeldcode voor veel van de IoT Plug en Play-constructies die in dit artikel worden beschreven in de Microsoft Azure IoT SDK's voor Node.js GitHub-opslagplaats.

Aankondiging van model-id

Als u de model-id wilt aankondigen, moet het apparaat deze opnemen in de verbindingsgegevens:

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

Hint

Gebruik voor modules en IoT Edge ModuleClient in plaats van Client.

Hint

Dit is de enige keer dat een apparaat model-id kan instellen, het kan niet worden bijgewerkt nadat het apparaat verbinding heeft gemaakt.

DPS-nettolading

Apparaten die de Device Provisioning Service (DPS) gebruiken, kunnen de modelId opnemen die tijdens het voorzieningsproces moet worden gebruikt met behulp van de volgende JSON-payload.

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

Onderdelen gebruiken

Zoals beschreven in Onderdelen begrijpen in IoT Plug en Play-modellen, moet u beslissen of u onderdelen wilt gebruiken om uw apparaten te beschrijven. Wanneer u onderdelen gebruikt, moeten apparaten de regels volgen die in de volgende secties worden beschreven.

Telemetrie

Voor een standaardonderdeel is geen speciale eigenschap vereist die is toegevoegd aan het telemetriebericht.

Wanneer u geneste onderdelen gebruikt, moeten apparaten een berichteigenschap instellen met de naam van het onderdeel:

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);
}

Alleen-lezen eigenschappen

Voor het rapporteren van een eigenschap van het standaardonderdeel is geen speciale constructie vereist:

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;
});

De apparaat-tweeling is bijgewerkt met de volgende gerapporteerde eigenschap.

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

Wanneer u gebruikmaakt van geneste onderdelen, moeten eigenschappen worden gekoppeld aan de naam van het onderdeel en voorzien zijn van een markering.

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;
});

De apparaatdubbel wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Schrijfbare eigenschappen

Deze eigenschappen kunnen worden ingesteld door het apparaat of worden bijgewerkt door de back-endtoepassing. Als de back-endtoepassing een eigenschap bijwerkt, ontvangt de client een melding als de callback in de Client of ModuleClient. Als u de IoT Plug en Play-conventies wilt volgen, moet het apparaat de service informeren dat de eigenschap succesvol is ontvangen.

Als het eigenschapstype is Object, moet de service een volledig object naar het apparaat verzenden, zelfs als het alleen een subset van de velden van het object bijwerkt. De bevestiging die het apparaat verzendt, moet ook een volledig object zijn.

Een beschrijfbare eigenschap rapporteren

Wanneer een apparaat een beschrijfbare eigenschap rapporteert, moet het de ack waarden bevatten die in de conventies zijn gedefinieerd.

Een beschrijfbare eigenschap rapporteren vanuit het standaardonderdeel:

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;
});

De tweeling-entiteit van het apparaat wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Als u een schrijfbare eigenschap van een geneste component wilt rapporteren, moet de tweeling een markering bevatten.

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;
});

De apparaatdubbel wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Abonneer je op gewenste vastgoedupdates

Services kunnen de gewenste eigenschappen bijwerken die een melding activeren op de verbonden apparaten. Deze melding bevat de bijgewerkte gewenste eigenschappen, inclusief het versienummer waarmee de update wordt geïdentificeerd. Apparaten moeten dit versienummer bevatten in het ack bericht dat naar de service wordt verzonden.

Een standaardcomponent ziet de enkelvoudige eigenschap en creëert de gerapporteerde ack met de ontvangen versie.

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);
      }
    });
  });
};

De apparaatdubbel voor een genest onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Een geneste component ontvangt de gewenste eigenschappen die zijn verpakt met de naam van het onderdeel en moet de ack gerapporteerde eigenschap rapporteren:

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);
      }
    });
  });
};

De apparaatdubbel voor onderdelen toont de gewenste en gerapporteerde secties als volgt:

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

Opdrachten

Een standaardonderdeel ontvangt de opdrachtnaam zoals het is aangeroepen door de service.

Een genest onderdeel ontvangt de commando-naam met de naam van het onderdeel als voorvoegsel en het * scheidingsteken.

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

Nettoladingen aanvragen en antwoorden

Opdrachten gebruiken typen om hun nettoladingen voor aanvragen en antwoorden te definiëren. Een apparaat moet de binnenkomende invoerparameter deserialiseren en het antwoord serialiseren.

In het volgende voorbeeld laten we zien hoe je een commando implementeert met complexe typen die gedefinieerd zijn in de payloads:

{
  "@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"
        }
      ]
    }
  }
}

De volgende codefragmenten laten zien hoe een apparaat deze opdrachtdefinitie implementeert, inclusief de typen die worden gebruikt om serialisatie en deserialisatie in te schakelen:

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;
  }
};

Hint

De namen van aanvragen en antwoorden zijn niet aanwezig in de geserialiseerde gegevens die via de verbinding worden verstuurd.

Voorbeeldcode

U vindt de voorbeeldcode voor veel van de IoT Plug en Play-constructies die in dit artikel worden beschreven in de Opslagplaats microsoft Azure IoT SDK's voor Python GitHub.

Aankondiging van model-id

Als u de model-id wilt aankondigen, moet het apparaat deze opnemen in de verbindingsgegevens:

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

Hint

Gebruik voor modules en IoT Edge IoTHubModuleClient in plaats van IoTHubDeviceClient.

Hint

Dit is de enige keer dat een apparaat model-id kan instellen, het kan niet worden bijgewerkt nadat het apparaat verbinding heeft gemaakt.

DPS-nettolading

Apparaten die de Device Provisioning Service (DPS) gebruiken, kunnen modelId opnemen voor gebruik tijdens het provisioning-proces door gebruik te maken van de volgende JSON payload.

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

Onderdelen gebruiken

Zoals beschreven in Onderdelen begrijpen in IoT Plug en Play-modellen, moet u beslissen of u onderdelen wilt gebruiken om uw apparaten te beschrijven. Wanneer u onderdelen gebruikt, moeten apparaten de regels volgen die in de volgende secties worden beschreven.

Telemetrie

Voor een standaardonderdeel is geen speciale eigenschap vereist die is toegevoegd aan het telemetriebericht.

Wanneer u geneste onderdelen gebruikt, moeten apparaten een berichteigenschap instellen met de naam van het onderdeel:

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)

Alleen-lezen eigenschappen

Voor het rapporteren van een eigenschap van het standaardonderdeel is geen speciale constructie vereist:

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

De device twin wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Wanneer u geneste onderdelen gebruikt, moeten eigenschappen worden gemaakt binnen de onderdeelnaam en moet u een markering opnemen:

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)

De apparaatdubbel wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Schrijfbare eigenschappen

Deze eigenschappen kunnen worden ingesteld door het apparaat of worden bijgewerkt door de back-endtoepassing. Als de back-endtoepassing een eigenschap bijwerkt, ontvangt de client een melding als een callback in de IoTHubDeviceClient of IoTHubModuleClient. Als u de IoT Plug en Play-conventies wilt volgen, moet het apparaat de service informeren dat de eigenschap succesvol is ontvangen.

Als het eigenschapstype is Object, moet de service een volledig object naar het apparaat verzenden, zelfs als het alleen een subset van de velden van het object bijwerkt. De bevestiging die het apparaat verzendt, moet ook een volledig object zijn.

Een beschrijfbare eigenschap rapporteren

Wanneer een apparaat een beschrijfbare eigenschap rapporteert, moet het de ack waarden bevatten die in de conventies zijn gedefinieerd.

Een beschrijfbare eigenschap rapporteren vanuit het standaardonderdeel:

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)

De apparaat-tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Als u een beschrijfbaar eigenschap van een genest onderdeel wilt rapporteren, moet de twin een markering bevatten:

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)

De apparaat-tweeling wordt bijgewerkt met de volgende gerapporteerde eigenschap:

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

Abonneer je op gewenste vastgoedupdates

Services kunnen de gewenste eigenschappen bijwerken die een melding activeren op de verbonden apparaten. Deze melding bevat de bijgewerkte gewenste eigenschappen, inclusief het versienummer waarmee de update wordt geïdentificeerd. Apparaten moeten dit versienummer bevatten in het ack bericht dat naar de service wordt verzonden.

Een standaardonderdeel ziet de enkele eigenschap en maakt de gerapporteerde ack met de ontvangen versie:

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)

De apparaat-tweeling voor een ingebouwd onderdeel toont de gewenste en gerapporteerde secties als volgt:

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

Een geneste component ontvangt de gewenste eigenschappen die zijn verpakt met de naam van het onderdeel en moet de ack gerapporteerde eigenschap rapporteren:

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)

De apparaat-tweeling voor onderdelen laat de gewenste en gerapporteerde secties zien als volgt:

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

Opdrachten

Een standaardonderdeel ontvangt de opdrachtnaam zoals deze door de service is aangeroepen.

Een geneste component ontvangt de opdrachtnaam voorafgegaan door de naam van de component en het * scheidingsteken.

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

Verzoek en antwoord payloads

Opdrachten gebruiken typen om hun gegevenspakketten voor aanvragen en antwoorden te definiëren. Een apparaat moet de binnenkomende invoerparameter deserialiseren en het antwoord serialiseren.

In het volgende voorbeeld ziet u hoe u een opdracht implementeert met complexe typen die zijn gedefinieerd in de payloads:

{
  "@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"
        }
      ]
    }
  }
}

De volgende codefragmenten laten zien hoe een apparaat deze opdrachtdefinitie implementeert, inclusief de typen die worden gebruikt om serialisatie en deserialisatie in te schakelen:

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

Hint

De namen van aanvragen en antwoorden zijn niet aanwezig in de geserialiseerde datapakketten die over het netwerk worden verzonden.

Volgende stappen

Nu u meer hebt geleerd over de ontwikkeling van IoT Plug en Play-apparaten, vindt u hier enkele andere bronnen: