Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Mit IoT Plug and Play können Sie IoT-Geräte erstellen, die ihre Funktionen für Azure IoT-Anwendungen bewerben. IoT Plug and Play-Geräte erfordern keine manuelle Konfiguration, wenn ein Kunde sie mit IoT Plug- und Play-fähigen Anwendungen wie IoT Central verbindet.
Sie können ein IoT-Gerät direkt mithilfe von Modulen oder mithilfe von IoT Edge-Modulen implementieren.
In diesem Handbuch werden die grundlegenden Schritte beschrieben, die zum Erstellen eines Geräts, Moduls oder IoT Edge-Moduls erforderlich sind, das den IoT Plug and Play-Konventionen folgt.
Führen Sie die folgenden Schritte aus, um ein IoT Plug and Play-Gerät, ein Modul oder ein IoT Edge-Modul zu erstellen:
- Stellen Sie sicher, dass Ihr Gerät entweder das MQTT- oder MQTT-Protokoll über WebSockets verwendet, um eine Verbindung mit Azure IoT Hub herzustellen.
- Erstellen Sie ein DTDL-Modell (Digital Twins Definition Language), um Ihr Gerät zu beschreiben. Weitere Informationen finden Sie unter "Grundlegendes zu Komponenten in IoT Plug and Play"-Modellen.
- Aktualisieren Sie Ihr Gerät oder Modul, damit es
model-idals Teil der Geräteverbindung ankündigt. - Telemetrie, Eigenschaften und Befehle implementieren, die den IoT-Plug-and-Play-Konventionen entsprechen
Nachdem Ihre Geräte- oder Modulimplementierung fertig ist, verwenden Sie den Azure IoT-Explorer , um zu überprüfen, ob das Gerät den IoT-Plug- und Play-Konventionen folgt.
Beispielcode
Sie finden den Beispielcode für viele der in diesem Artikel beschriebenen IoT Plug and Play-Konstrukte im GitHub-Repository von Azure IoT C SDKs und Bibliotheken .
Modell-ID-Ankündigung
Um die Modell-ID ankündigen zu können, muss das Gerät sie in die Verbindungsinformationen aufnehmen.
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);
Tipp
Verwenden Sie IoTHubModuleClient_LL für Module und IoT Edge anstelle von IoTHubDeviceClient_LL.
Tipp
Dies ist das einzige Mal, wenn ein Gerät die Modell-ID festlegen kann, es kann nicht aktualisiert werden, nachdem das Gerät eine Verbindung hergestellt hat.
DPS-Nutzlast
Geräte, die den Device Provisioning Service (DPS) verwenden, können während des Bereitstellungsprozesses die modelId mithilfe der folgenden JSON-Nutzlast verwenden:
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
Verwenden von Komponenten
Wie unter "Grundlegendes zu Komponenten in IoT Plug and Play"-Modellen beschrieben, sollten Sie entscheiden, ob Sie Komponenten verwenden möchten, um Ihre Geräte zu beschreiben. Wenn Sie Komponenten verwenden, müssen Geräte die in den folgenden Abschnitten beschriebenen Regeln befolgen:
Telemetrie
Eine Standardkomponente erfordert keine spezielle Eigenschaft, die der Telemetrienachricht hinzugefügt wird.
Wenn Sie geschachtelte Komponenten verwenden, müssen Geräte eine Nachrichteneigenschaft mit dem Komponentennamen festlegen:
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);
Schreibgeschützte Eigenschaften
Für das Melden einer Eigenschaft aus der Standardkomponente ist kein spezielles Konstrukt erforderlich:
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));
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
Wenn Sie geschachtelte Komponenten verwenden, erstellen Sie Eigenschaften innerhalb des Komponentennamens, und fügen Sie eine Markierung ein:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
Schreibbare Eigenschaften
Diese Eigenschaften können vom Gerät festgelegt oder von der Back-End-Anwendung aktualisiert werden. Wenn die Back-End-Anwendung eine Eigenschaft aktualisiert, erhält der Client eine Benachrichtigung als Callback im DeviceClient oder ModuleClient. Um den IoT Plug and Play-Konventionen zu folgen, muss das Gerät den Service darüber informieren, dass die Eigenschaften erfolgreich empfangen wurden.
Wenn der Eigenschaftentyp lautet Object, muss der Dienst ein vollständiges Objekt an das Gerät senden, auch wenn nur eine Teilmenge der Felder des Objekts aktualisiert wird. Die Bestätigung, die das Gerät sendet, kann auch ein vollständiges Objekt sein.
Melden einer beschreibbaren Eigenschaft
Wenn ein Gerät eine schreibbare Eigenschaft meldet, muss es die ack in den Konventionen definierten Werte enthalten.
So melden Sie eine beschreibbare Eigenschaft aus der Standardkomponente:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
Um eine beschreibbare Eigenschaft aus einer geschachtelten Komponente zu melden, muss der Zwilling eine Markierung enthalten:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Abonnieren Sie die gewünschten Aktualisierungen zu Immobilien.
Dienste können die gewünschten Eigenschaften aktualisieren, die eine Benachrichtigung auf den verbundenen Geräten auslösen. Diese Benachrichtigung enthält die aktualisierten gewünschten Eigenschaften, einschließlich der Versionsnummer, die das Update identifiziert. Geräte müssen diese Versionsnummer in die ack an den Dienst zurückgesendete Nachricht enthalten.
Eine Standardkomponente erkennt die einzelne Eigenschaft und erstellt die angegebene ack mit der empfangenen Version:
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))
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
Eine geschachtelte Komponente empfängt die gewünschten Eigenschaften, die mit dem Komponentennamen umschlossen sind, und sollte die ack gemeldete Eigenschaft zurückmelden:
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);
}
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Befehle
Eine Standardkomponente empfängt den Befehlsnamen, wie sie vom Dienst aufgerufen wurde.
Eine geschachtelte Komponente empfängt den Befehlsnamen, der dem Komponentennamen und dem * Trennzeichen vorangestellt ist.
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);
Anforderungs- und Antwortnutzlasten
Befehle verwenden Typen, um ihre Anforderungs- und Antwortnutzlasten zu definieren. Ein Gerät muss den eingehenden Eingabeparameter deserialisieren und die Antwort serialisieren.
Das folgende Beispiel zeigt, wie Sie einen Befehl mit komplexen Typen implementieren, die in den Nutzlasten definiert sind:
{
"@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"
}
]
}
}
}
Die folgenden Codeausschnitte zeigen, wie ein Gerät diese Befehlsdefinition implementiert, einschließlich der Typen, die zum Aktivieren der Serialisierung und Deserialisierung verwendet werden:
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;
}
Tipp
Die Anforderungs- und Antwortnamen sind in den serialisierten Nutzlasten, die über die Leitung übertragen werden, nicht vorhanden.
SDKs
Die Codeausschnitte in diesem Artikel basieren auf Beispielen, die das Azure IoT Middleware-Addon für Eclipse ThreadX verwenden. Das Addon ist eine Bindungsebene zwischen Eclipse ThreadX und dem Azure SDK für Embedded C.
Die Codeausschnitte in diesem Artikel basieren auf den folgenden Beispielen:
Modell-ID-Ankündigung
Um die Modell-ID ankündigen zu können, muss das Gerät sie in die Verbindungsinformationen aufnehmen.
#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);
Tipp
Dies ist das einzige Mal, wenn ein Gerät die Modell-ID festlegen kann, es kann nicht aktualisiert werden, nachdem das Gerät eine Verbindung hergestellt hat.
DPS-Nutzlast
Geräte, die den Device Provisioning Service (DPS) verwenden, können während des Bereitstellungsprozesses mithilfe der folgenden JSON-Nutzlast das modelId einbinden.
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
Das folgende Beispiel verwendet diesen Code, um die Nutzlast zu senden.
#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);
Verwenden von Komponenten
Wie unter "Grundlegendes zu Komponenten in IoT Plug and Play"-Modellen beschrieben, müssen Sie entscheiden, ob Sie Komponenten verwenden möchten, um Ihre Geräte zu beschreiben. Wenn Sie Komponenten verwenden, müssen Geräte den in den folgenden Abschnitten beschriebenen Regeln entsprechen. Um die Arbeit mit den IoT Plug and Play-Konventionen für Komponenten zu vereinfachen, verwenden die Beispiele die Hilfsfunktionen in nx_azure_iot_hub_client.h.
Telemetrie
Eine Standardkomponente erfordert keine spezielle Eigenschaft, die der Telemetrienachricht hinzugefügt wird.
Wenn Sie geschachtelte Komponenten verwenden, müssen Geräte eine Nachrichteneigenschaft mit dem Komponentennamen festlegen. Im folgenden Codeausschnitt ist component_name_ptr der Name einer Komponente, wie thermostat1 zum Beispiel. Die in nx_azure_iot_pnp_helper_telemetry_message_create definierte Hilfsfunktion fügt die Nachrichteneigenschaft mit dem Komponentennamen hinzu:
#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);
}
Schreibgeschützte Eigenschaften
Für das Melden einer Eigenschaft aus der Standardkomponente ist kein spezielles Konstrukt erforderlich:
#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))))
{
// ...
}
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
Wenn Sie geschachtelte Komponenten verwenden, müssen innerhalb der Komponentendefinition Eigenschaften erstellt und mit einer Markierung versehen werden. Im folgenden Codeausschnitt ist component_name_ptr der Name einer Komponente, wie thermostat1 zum Beispiel. Die in nx_azure_iot_pnp_helper_build_reported_property definierte Hilfsfunktion erstellt die gemeldete Eigenschaft im richtigen Format:
#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))))
{
// ...
}
// ...
}
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
Schreibbare Eigenschaften
Diese Eigenschaften können vom Gerät festgelegt oder von der Back-End-Anwendung aktualisiert werden. Um den IoT Plug and Play-Konventionen zu folgen, muss das Gerät den Service darüber informieren, dass die Eigenschaften erfolgreich empfangen wurden.
Melden einer beschreibbaren Eigenschaft
Wenn ein Gerät eine schreibbare Eigenschaft meldet, muss es die ack in den Konventionen definierten Werte enthalten.
So melden Sie eine beschreibbare Eigenschaft aus der Standardkomponente:
#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
// ...
}
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "success"
}
}
}
Um eine beschreibbare Eigenschaft aus einer geschachtelten Komponente zu melden, muss der Zwilling eine Markierung enthalten, und die Eigenschaften müssen innerhalb des Komponentennamens erstellt werden. Im folgenden Codeausschnitt ist component_name_ptr der Name einer Komponente, wie thermostat1 zum Beispiel. Die in nx_azure_iot_pnp_helper_build_reported_property_with_status definierte Hilfsfunktion erstellt die gemeldete Eigenschaftsnutzlast:
#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
{
// ...
}
// ...
}
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "success"
}
}
}
}
Abonnieren Sie die gewünschten Aktualisierungen zu Immobilien.
Dienste können die gewünschten Eigenschaften aktualisieren, die eine Benachrichtigung auf den verbundenen Geräten auslösen. Diese Benachrichtigung enthält die aktualisierten gewünschten Eigenschaften und die Versionsnummer, die das Update identifiziert. Geräte müssen diese Versionsnummer in die ack an den Dienst zurückgesendete Nachricht enthalten.
Eine Standardkomponente erkennt die einzelne Eigenschaft und erstellt die angegebene ack mit der empfangenen Version:
#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(©_json_reader,
(UCHAR *)desired_version_property_name,
sizeof(desired_version_property_name) - 1) ||
nx_azure_iot_json_reader_token_int32_get(©_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);
// ...
}
Eine geschachtelte Komponente empfängt die gewünschten Eigenschaften, die mit dem Komponentennamen umschlossen sind, und erstellt die gemeldete ack mit der empfangenen Version:
#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);
// ...
}
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "success"
}
}
}
}
Befehle
Eine Standardkomponente empfängt den Befehlsnamen, wie sie vom Dienst aufgerufen wurde.
Eine geschachtelte Komponente empfängt den Befehlsnamen, der dem Komponentennamen und dem * Trennzeichen vorangestellt ist. Im folgenden Codeausschnitt analysiert die in nx_azure_iot_pnp_helper_command_name_parse definierte Hilfsfunktion den Komponentennamen und den Befehlsnamen aus der Nachricht, die das Gerät vom Dienst erhält:
#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;
}
// ...
}
}
Anforderungs- und Antwortnutzlasten
Befehle verwenden Typen, um ihre Anforderungs- und Antwortnutzlasten zu definieren. Ein Gerät muss den eingehenden Eingabeparameter deserialisieren und die Antwort serialisieren.
Das folgende Beispiel zeigt, wie Sie einen Befehl mit komplexen Typen implementieren, die in den Nutzlasten definiert sind:
{
"@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"
}
]
}
}
}
Die folgenden Codeausschnitte zeigen, wie ein Gerät diese Befehlsdefinition implementiert, einschließlich der Typen, die zum Aktivieren der Serialisierung und Deserialisierung verwendet werden:
#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);
}
Tipp
Die Anforderungs- und Antwortnamen sind in den serialisierten Nutzlasten, die über die Leitung übertragen werden, nicht vorhanden.
Beispielcode
Sie finden den Beispielcode für viele der in diesem Artikel beschriebenen IoT Plug and Play-Konstrukte im Microsoft Azure IoT SDK für .NET GitHub-Repository.
Modell-ID-Ankündigung
Um die Modell-ID ankündigen zu können, muss das Gerät sie in die Verbindungsinformationen aufnehmen.
DeviceClient.CreateFromConnectionString(
connectionString,
TransportType.Mqtt,
new ClientOptions() { ModelId = modelId })
Die neue ClientOptions Überladung ist in allen DeviceClient Methoden verfügbar, die zum Initialisieren einer Verbindung verwendet werden.
Tipp
Verwenden Sie ModuleClient für Module und IoT Edge anstelle von DeviceClient.
Tipp
Dies ist das einzige Mal, wenn ein Gerät die Modell-ID festlegen kann, es kann nicht aktualisiert werden, nachdem das Gerät eine Verbindung hergestellt hat.
DPS-Nutzlast
Geräte, die den Device Provisioning Service (DPS) verwenden, können während des Bereitstellungsprozesses mithilfe der folgenden JSON-Nutzlast das modelId einbinden.
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
Verwenden von Komponenten
Wie unter "Grundlegendes zu Komponenten in IoT Plug and Play"-Modellen beschrieben, müssen Sie entscheiden, ob Sie Komponenten verwenden möchten, um Ihre Geräte zu beschreiben. Wenn Sie Komponenten verwenden, müssen Geräte den in den folgenden Abschnitten beschriebenen Regeln entsprechen.
Telemetrie
Eine Standardkomponente erfordert keine spezielle Eigenschaft, die der Telemetrienachricht hinzugefügt wird.
Wenn Sie geschachtelte Komponenten verwenden, müssen Geräte eine Nachrichteneigenschaft mit dem Komponentennamen festlegen:
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);
}
Schreibgeschützte Eigenschaften
Für das Melden einer Eigenschaft aus der Standardkomponente ist kein spezielles Konstrukt erforderlich:
TwinCollection reportedProperties = new TwinCollection();
reportedProperties["maxTemperature"] = 38.7;
await client.UpdateReportedPropertiesAsync(reportedProperties);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"maxTemperature" : 38.7
}
}
Wenn Sie geschachtelte Komponenten verwenden, erstellen Sie Eigenschaften innerhalb des Komponentennamens, und fügen Sie eine Markierung ein:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
Schreibbare Eigenschaften
Diese Eigenschaften können vom Gerät festgelegt oder von der Back-End-Anwendung aktualisiert werden. Wenn die Back-End-Anwendung eine Eigenschaft aktualisiert, erhält der Client eine Benachrichtigung als Callback im DeviceClient oder ModuleClient. Um den IoT Plug and Play-Konventionen zu folgen, muss das Gerät den Service darüber informieren, dass die Eigenschaften erfolgreich empfangen wurden.
Wenn der Eigenschaftentyp lautet Object, muss der Dienst ein vollständiges Objekt an das Gerät senden, auch wenn nur eine Teilmenge der Felder des Objekts aktualisiert wird. Die Bestätigung, die das Gerät sendet, muss ebenfalls ein vollständiges Objekt sein.
Melden einer beschreibbaren Eigenschaft
Wenn ein Gerät eine schreibbare Eigenschaft meldet, muss es die ack in den Konventionen definierten Werte enthalten.
So melden Sie eine beschreibbare Eigenschaft aus der Standardkomponente:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
Um eine beschreibbare Eigenschaft aus einer geschachtelten Komponente zu melden, muss der Zwilling eine Markierung enthalten:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Abonnieren Sie die gewünschten Aktualisierungen zu Immobilien.
Dienste können die gewünschten Eigenschaften aktualisieren, die eine Benachrichtigung auf den verbundenen Geräten auslösen. Diese Benachrichtigung enthält die aktualisierten gewünschten Eigenschaften, einschließlich der Versionsnummer, die das Update identifiziert. Geräte müssen diese Versionsnummer in die ack an den Dienst zurückgesendete Nachricht enthalten.
Eine Standardkomponente erkennt die einzelne Eigenschaft und erstellt die angegebene ack mit der empfangenen Version:
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);
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
Eine geschachtelte Komponente empfängt die gewünschten Eigenschaften, die mit dem Komponentennamen umschlossen sind, und sollte die ack gemeldete Eigenschaft zurückmelden:
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);
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Befehle
Eine Standardkomponente empfängt den Befehlsnamen, wie sie vom Dienst aufgerufen wurde.
Eine geschachtelte Komponente empfängt den Befehlsnamen, der dem Komponentennamen und dem * Trennzeichen vorangestellt ist.
await client.SetMethodHandlerAsync("thermostat*reboot", (MethodRequest req, object ctx) =>
{
Console.WriteLine("REBOOT");
return Task.FromResult(new MethodResponse(200));
},
null);
Anforderungs- und Antwortnutzlasten
Befehle verwenden Typen, um ihre Anforderungs- und Antwortnutzlasten zu definieren. Ein Gerät muss den eingehenden Eingabeparameter deserialisieren und die Antwort serialisieren.
Das folgende Beispiel zeigt, wie Sie einen Befehl mit komplexen Typen implementieren, die in den Nutzlasten definiert sind:
{
"@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"
}
]
}
}
}
Die folgenden Codeausschnitte zeigen, wie ein Gerät diese Befehlsdefinition implementiert, einschließlich der Typen, die zum Aktivieren der Serialisierung und Deserialisierung verwendet werden:
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);
Tipp
Die Anforderungs- und Antwortnamen sind in den serialisierten Nutzlasten, die über die Leitung übertragen werden, nicht vorhanden.
Modell-ID-Ankündigung
Um die Modell-ID ankündigen zu können, muss das Gerät sie in die Verbindungsinformationen aufnehmen.
ClientOptions options = new ClientOptions();
options.setModelId(MODEL_ID);
deviceClient = new DeviceClient(deviceConnectionString, protocol, options);
Die ClientOptions Überladung ist in allen DeviceClient Methoden verfügbar, die zum Initialisieren einer Verbindung verwendet werden.
Tipp
Verwenden Sie ModuleClient für Module und IoT Edge anstelle von DeviceClient.
Tipp
Dies ist das einzige Mal, wenn ein Gerät die Modell-ID festlegen kann, es kann nicht aktualisiert werden, nachdem das Gerät eine Verbindung hergestellt hat.
DPS-Nutzlast
Geräte, die den Device Provisioning Service (DPS) verwenden, können die modelId während des Bereitstellungsprozesses mithilfe der folgenden JSON-Nutzlast verwenden.
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
Verwenden von Komponenten
Wie unter "Grundlegendes zu Komponenten in IoT Plug and Play"-Modellen beschrieben, sollten Sie entscheiden, ob Sie Komponenten verwenden möchten, um Ihre Geräte zu beschreiben. Wenn Sie Komponenten verwenden, müssen Geräte den in den folgenden Abschnitten beschriebenen Regeln entsprechen.
Telemetrie
Eine Standardkomponente erfordert keine spezielle Eigenschaft, die der Telemetrienachricht hinzugefügt wird.
Wenn Sie geschachtelte Komponenten verwenden, muss das Gerät eine Nachrichteneigenschaft mit dem Komponentennamen festlegen:
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);
}
Schreibgeschützte Eigenschaften
Für das Melden einer Eigenschaft aus der Standardkomponente ist kein spezielles Konstrukt erforderlich:
Property reportedProperty = new Property("maxTempSinceLastReboot", 38.7);
deviceClient.sendReportedProperties(Collections.singleton(reportedProperty));
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
Wenn Sie geschachtelte Komponenten verwenden, erstellen Sie Eigenschaften innerhalb des Komponentennamens, und fügen Sie eine Markierung ein:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
Schreibbare Eigenschaften
Diese Eigenschaften können vom Gerät festgelegt oder von der Back-End-Anwendung aktualisiert werden. Wenn die Back-End-Anwendung eine Eigenschaft aktualisiert, erhält der Client eine Benachrichtigung als Callback im DeviceClient oder ModuleClient. Um den IoT Plug and Play-Konventionen zu folgen, muss das Gerät den Service darüber informieren, dass die Eigenschaften erfolgreich empfangen wurden.
Wenn der Eigenschaftentyp lautet Object, muss der Dienst ein vollständiges Objekt an das Gerät senden, auch wenn nur eine Teilmenge der Felder des Objekts aktualisiert wird. Die Bestätigung, die das Gerät sendet, muss ebenfalls ein vollständiges Objekt sein.
Melden einer beschreibbaren Eigenschaft
Wenn ein Gerät eine schreibbare Eigenschaft meldet, muss es die ack in den Konventionen definierten Werte enthalten.
So melden Sie eine beschreibbare Eigenschaft aus der Standardkomponente:
@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));
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
Um eine beschreibbare Eigenschaft aus einer geschachtelten Komponente zu melden, muss der Zwilling eine Markierung enthalten:
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);
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Abonnieren Sie die gewünschten Aktualisierungen zu Immobilien.
Dienste können die gewünschten Eigenschaften aktualisieren, die eine Benachrichtigung auf den verbundenen Geräten auslösen. Diese Benachrichtigung enthält die aktualisierten gewünschten Eigenschaften, einschließlich der Versionsnummer, die das Update identifiziert. Geräte müssen diese Versionsnummer in die ack an den Dienst zurückgesendete Nachricht enthalten.
Eine Standardkomponente erkennt die einzelne Eigenschaft und erstellt die angegebene ack mit der empfangenen Version:
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);
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
Eine geschachtelte Komponente empfängt die gewünschten Eigenschaften, die mit dem Komponentennamen umschlossen sind, und sollte die ack gemeldete Eigenschaft zurückmelden:
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);
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Befehle
Eine Standardkomponente empfängt den Befehlsnamen, wie sie vom Dienst aufgerufen wurde.
Eine geschachtelte Komponente empfängt den Befehlsnamen, der dem Komponentennamen und dem * Trennzeichen vorangestellt ist.
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);
}
}
}
Anforderungs- und Antwortnutzlasten
Befehle verwenden Typen, um ihre Anforderungs- und Antwortnutzlasten zu definieren. Ein Gerät muss den eingehenden Eingabeparameter deserialisieren und die Antwort serialisieren.
Das folgende Beispiel zeigt, wie Sie einen Befehl mit komplexen Typen implementieren, die in den Nutzlasten definiert sind:
{
"@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"
}
]
}
}
}
Die folgenden Codeausschnitte zeigen, wie ein Gerät diese Befehlsdefinition implementiert, einschließlich der Typen, die zum Aktivieren der Serialisierung und Deserialisierung verwendet werden:
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);
}
}
Tipp
Die Anforderungs- und Antwortnamen sind in den serialisierten Nutzlasten, die über die Leitung übertragen werden, nicht vorhanden.
Beispielcode
Sie finden den Beispielcode für viele der in diesem Artikel beschriebenen IoT Plug and Play-Konstrukte im Microsoft Azure IoT-SDKs für Node.js GitHub-Repository.
Modell-ID-Ankündigung
Um die Modell-ID ankündigen zu können, muss das Gerät sie in die Verbindungsinformationen aufnehmen.
const modelIdObject = { modelId: 'dtmi:com:example:Thermostat;1' };
const client = Client.fromConnectionString(deviceConnectionString, Protocol);
await client.setOptions(modelIdObject);
await client.open();
Tipp
Verwenden Sie ModuleClient für Module und IoT Edge anstelle von Client.
Tipp
Dies ist das einzige Mal, wenn ein Gerät die Modell-ID festlegen kann, es kann nicht aktualisiert werden, nachdem das Gerät eine Verbindung hergestellt hat.
DPS-Nutzlast
Geräte, die den Device Provisioning Service (DPS) verwenden, können die modelId während des Bereitstellungsprozesses mithilfe der folgenden JSON-Nutzlast verwenden.
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
Verwenden von Komponenten
Wie unter "Grundlegendes zu Komponenten in IoT Plug and Play"-Modellen beschrieben, müssen Sie entscheiden, ob Sie Komponenten verwenden möchten, um Ihre Geräte zu beschreiben. Wenn Sie Komponenten verwenden, müssen Geräte den in den folgenden Abschnitten beschriebenen Regeln entsprechen.
Telemetrie
Eine Standardkomponente erfordert keine spezielle Eigenschaft, die der Telemetrienachricht hinzugefügt wird.
Wenn Sie geschachtelte Komponenten verwenden, müssen Geräte eine Nachrichteneigenschaft mit dem Komponentennamen festlegen:
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);
}
Schreibgeschützte Eigenschaften
Für das Melden einer Eigenschaft aus der Standardkomponente ist kein spezielles Konstrukt erforderlich:
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;
});
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
Wenn Sie geschachtelte Komponenten verwenden, müssen Eigenschaften innerhalb des Komponentennamens erstellt werden und einen Marker enthalten.
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;
});
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTempSinceLastReboot" : 38.7
}
}
}
Schreibbare Eigenschaften
Diese Eigenschaften können vom Gerät festgelegt oder von der Back-End-Anwendung aktualisiert werden. Wenn die Back-End-Anwendung eine Eigenschaft aktualisiert, erhält der Client eine Benachrichtigung als Callback im Client oder ModuleClient. Um den IoT Plug and Play-Konventionen zu folgen, muss das Gerät den Service darüber informieren, dass die Eigenschaften erfolgreich empfangen wurden.
Wenn der Eigenschaftentyp lautet Object, muss der Dienst ein vollständiges Objekt an das Gerät senden, auch wenn nur eine Teilmenge der Felder des Objekts aktualisiert wird. Die Bestätigung, die das Gerät sendet, muss ebenfalls ein vollständiges Objekt sein.
Melden einer beschreibbaren Eigenschaft
Wenn ein Gerät eine schreibbare Eigenschaft meldet, muss es die ack in den Konventionen definierten Werte enthalten.
So melden Sie eine beschreibbare Eigenschaft aus der Standardkomponente:
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;
});
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "reported default value"
}
}
}
Um eine beschreibbare Eigenschaft aus einer geschachtelten Komponente zu melden, muss der Zwilling eine Markierung enthalten:
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;
});
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "complete"
}
}
}
}
Abonnieren Sie die gewünschten Aktualisierungen zu Immobilien.
Dienste können die gewünschten Eigenschaften aktualisieren, die eine Benachrichtigung auf den verbundenen Geräten auslösen. Diese Benachrichtigung enthält die aktualisierten gewünschten Eigenschaften, einschließlich der Versionsnummer, die das Update identifiziert. Geräte müssen diese Versionsnummer in die ack an den Dienst zurückgesendete Nachricht enthalten.
Eine Standardkomponente erkennt die einzelne Eigenschaft und erstellt die angegebene ack mit der empfangenen Version:
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);
}
});
});
};
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
Eine geschachtelte Komponente empfängt die gewünschten Eigenschaften, die mit dem Komponentennamen umschlossen sind, und sollte die ack gemeldete Eigenschaft zurückmelden:
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);
}
});
});
};
Der Geräte-Twin für Komponenten zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Befehle
Eine Standardkomponente empfängt den Befehlsnamen, wie sie vom Dienst aufgerufen wurde.
Eine geschachtelte Komponente empfängt den Befehlsnamen, der dem Komponentennamen und dem * Trennzeichen vorangestellt ist.
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);
Anforderungs- und Antwortnutzlasten
Befehle verwenden Typen, um ihre Anforderungs- und Antwortnutzlasten zu definieren. Ein Gerät muss den eingehenden Eingabeparameter deserialisieren und die Antwort serialisieren.
Das folgende Beispiel zeigt, wie Sie einen Befehl mit komplexen Typen implementieren, die in den Nutzlasten definiert sind:
{
"@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"
}
]
}
}
}
Die folgenden Codeausschnitte zeigen, wie ein Gerät diese Befehlsdefinition implementiert, einschließlich der Typen, die zum Aktivieren der Serialisierung und Deserialisierung verwendet werden:
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;
}
};
Tipp
Die Anforderungs- und Antwortnamen sind in den serialisierten Nutzlasten, die über die Leitung übertragen werden, nicht vorhanden.
Beispielcode
Sie finden den Beispielcode für viele der in diesem Artikel beschriebenen IoT Plug and Play-Konstrukte im Microsoft Azure IoT SDKs für Python GitHub-Repository.
Modell-ID-Ankündigung
Um die Modell-ID ankündigen zu können, muss das Gerät sie in die Verbindungsinformationen aufnehmen.
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,
)
Tipp
Verwenden Sie IoTHubModuleClient für Module und IoT Edge anstelle von IoTHubDeviceClient.
Tipp
Dies ist das einzige Mal, wenn ein Gerät die Modell-ID festlegen kann, es kann nicht aktualisiert werden, nachdem das Gerät eine Verbindung hergestellt hat.
DPS-Nutzlast
Geräte, die den Device Provisioning Service (DPS) verwenden, können die modelId während des Bereitstellungsprozesses mithilfe der folgenden JSON-Nutzlast verwenden.
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
Verwenden von Komponenten
Wie unter "Grundlegendes zu Komponenten in IoT Plug and Play"-Modellen beschrieben, müssen Sie entscheiden, ob Sie Komponenten verwenden möchten, um Ihre Geräte zu beschreiben. Wenn Sie Komponenten verwenden, müssen Geräte den in den folgenden Abschnitten beschriebenen Regeln entsprechen.
Telemetrie
Eine Standardkomponente erfordert keine spezielle Eigenschaft, die der Telemetrienachricht hinzugefügt wird.
Wenn Sie geschachtelte Komponenten verwenden, müssen Geräte eine Nachrichteneigenschaft mit dem Komponentennamen festlegen:
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)
Schreibgeschützte Eigenschaften
Für das Melden einer Eigenschaft aus der Standardkomponente ist kein spezielles Konstrukt erforderlich:
await device_client.patch_twin_reported_properties({"maxTempSinceLastReboot": 38.7})
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
Wenn Sie geschachtelte Komponenten verwenden, müssen Eigenschaften im Namen der Komponente erstellt werden und einen Marker enthalten.
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)
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTempSinceLastReboot" : 38.7
}
}
}
Schreibbare Eigenschaften
Diese Eigenschaften können vom Gerät festgelegt oder von der Back-End-Anwendung aktualisiert werden. Wenn die Back-End-Anwendung eine Eigenschaft aktualisiert, erhält der Client eine Benachrichtigung als Callback im IoTHubDeviceClient oder IoTHubModuleClient. Um den IoT Plug and Play-Konventionen zu folgen, muss das Gerät den Service darüber informieren, dass die Eigenschaften erfolgreich empfangen wurden.
Wenn der Eigenschaftentyp lautet Object, muss der Dienst ein vollständiges Objekt an das Gerät senden, auch wenn nur eine Teilmenge der Felder des Objekts aktualisiert wird. Die Bestätigung, die das Gerät sendet, muss ebenfalls ein vollständiges Objekt sein.
Melden einer beschreibbaren Eigenschaft
Wenn ein Gerät eine schreibbare Eigenschaft meldet, muss es die ack in den Konventionen definierten Werte enthalten.
So melden Sie eine beschreibbare Eigenschaft aus der Standardkomponente:
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)
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "reported default value"
}
}
}
Um eine beschreibbare Eigenschaft aus einer geschachtelten Komponente zu melden, muss der Zwilling eine Markierung enthalten:
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)
Das Gerät Twin wird mit der folgenden gemeldeten Eigenschaft aktualisiert:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "complete"
}
}
}
}
Abonnieren Sie die gewünschten Aktualisierungen zu Immobilien.
Dienste können die gewünschten Eigenschaften aktualisieren, die eine Benachrichtigung auf den verbundenen Geräten auslösen. Diese Benachrichtigung enthält die aktualisierten gewünschten Eigenschaften, einschließlich der Versionsnummer, die das Update identifiziert. Geräte müssen diese Versionsnummer in die ack an den Dienst zurückgesendete Nachricht enthalten.
Eine Standardkomponente erkennt die einzelne Eigenschaft und erstellt die angegebene ack mit der empfangenen Version:
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)
Der Geräte-Twin für eine geschachtelte Komponente zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
Eine geschachtelte Komponente empfängt die gewünschten Eigenschaften, die mit dem Komponentennamen umschlossen sind, und sollte die ack gemeldete Eigenschaft zurückmelden:
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)
Der Geräte-Twin für Komponenten zeigt die gewünschten und gemeldeten Abschnitte wie folgt an:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
Befehle
Eine Standardkomponente empfängt den Befehlsnamen, wie sie vom Dienst aufgerufen wurde.
Eine geschachtelte Komponente empfängt den Befehlsnamen, der dem Komponentennamen und dem * Trennzeichen vorangestellt ist.
command_request = await device_client.receive_method_request("thermostat1*reboot")
Anforderungs- und Antwortnutzlasten
Befehle verwenden Typen, um ihre Anforderungs- und Antwortnutzlasten zu definieren. Ein Gerät muss den eingehenden Eingabeparameter deserialisieren und die Antwort serialisieren.
Das folgende Beispiel zeigt, wie Sie einen Befehl mit komplexen Typen implementieren, die in den Nutzlasten definiert sind:
{
"@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"
}
]
}
}
}
Die folgenden Codeausschnitte zeigen, wie ein Gerät diese Befehlsdefinition implementiert, einschließlich der Typen, die zum Aktivieren der Serialisierung und Deserialisierung verwendet werden:
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
Tipp
Die Anforderungs- und Antwortnamen sind in den serialisierten Nutzlasten, die über die Leitung übertragen werden, nicht vorhanden.
Nächste Schritte
Nachdem Sie sich mit der Entwicklung von IoT Plug and Play-Geräten beschäftigt haben, finden Sie hier einige weitere Ressourcen: