IoT 隨插即用 裝置開發人員指南
IoT 隨插即用 可讓您建置IoT裝置,向Azure IoT應用程式公告其功能。 當客戶將裝置連線到 IoT 隨插即用 啟用的應用程式,例如IoT Central時,IoT 隨插即用裝置不需要手動設定。
您可以使用模組,或使用IoT Edge模組直接實作IoT裝置。
本指南說明建立遵循 IoT 隨插即用 慣例的裝置、模組或IoT Edge模組所需的基本步驟。
若要建置 IoT 隨插即用 裝置、模組或IoT Edge模組,請遵循下列步驟:
- 請確定您的裝置使用 MQTT 或 MQTT over WebSockets 通訊協議來連線到 Azure IoT 中樞。
- 建立 數字對應項定義語言 (DTDL) 模型來描述您的裝置。 若要深入瞭解,請參閱瞭解 IoT 隨插即用 模型中的元件。
- 更新您的裝置或模組,以宣告
model-id
作為裝置連線的一部分。 - 實作遵循 IoT 隨插即用 慣例的遙測、屬性和命令
一旦裝置或模組實作準備就緒,請使用 Azure IoT 總管來驗證裝置是否遵循 IoT 隨插即用 慣例。
模型標識碼公告
若要宣告模型標識碼,裝置必須將其包含在連線資訊中:
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);
提示
針對模組和 IoT Edge 請使用 IoTHubModuleClient_LL
取代 IoTHubDeviceClient_LL
。
提示
這是裝置唯一可以設定模型標識碼的時間,裝置連線之後就無法更新。
DPS 承載
使用 裝置佈建服務 (DPS) 的裝置,可以使用下列 JSON 承載,包含在 modelId
佈建程式期間要使用的 :
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
使用元件
如瞭解 IoT 隨插即用 模型中的元件中所述,您應該決定是否要使用元件來描述您的裝置。 當您使用元件時,裝置必須遵循下列各節所述的規則:
遙測
默認元件不需要新增至遙測訊息的任何特殊屬性。
當您使用巢狀元件時,裝置必須使用元件名稱來設定訊息屬性:
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);
唯讀屬性
從預設元件報告屬性並不需要任何特殊的建構:
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));
裝置對應項會更新為下列報告屬性:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
當您使用巢狀元件時,請在元件名稱內建立屬性,並包含標記:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
可寫入的屬性
這些屬性可由裝置設定,或由後端應用程式更新。 如果後端應用程式更新 屬性,用戶端會在 或 ModuleClient
中DeviceClient
收到回呼通知。 若要遵循 IoT 隨插即用 慣例,裝置必須通知服務已成功接收屬性。
如果屬性類型為 Object
,則服務必須將完整的物件傳送至裝置,即使它只會更新物件的字段子集。 裝置傳送的通知也可以是完整的物件。
報告可寫入的屬性
當裝置報告可寫入的屬性時,它必須包含 ack
慣例中定義的值。
若要從預設元件報告可寫入的屬性:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
若要從巢狀元件報告可寫入的屬性,對應項必須包含標記:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
訂閱所需的屬性更新
服務可以更新在連線裝置上觸發通知的所需屬性。 此通知包含更新所需的屬性,包括識別更新的版本號碼。 裝置必須在傳回給服務的訊息中包含 ack
此版本號碼。
預設元件會看到單一屬性,並使用收到的版本建立報告 ack
:
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))
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
巢狀元件會接收以元件名稱包裝的所需屬性,而且應該回報 ack
報告屬性:
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);
}
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
命令
默認元件會接收由服務叫用的命令名稱。
巢狀元件會接收前面加上元件名稱和分隔符的 *
命令名稱。
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);
要求和響應承載
命令會使用類型來定義其要求和響應承載。 裝置必須還原串行化傳入輸入參數,並串行化回應。
下列範例示範如何使用裝載中定義的複雜類型來實作命令:
{
"@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"
}
]
}
}
}
下列代碼段顯示裝置如何實作此命令定義,包括用來啟用串行化和還原串行化的型別:
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;
}
提示
要求和回應名稱不存在於透過網路傳輸的串行化承載中。
SDK
本文中的代碼段是以使用適用於 Eclipse ThreadX 的 Azure IoT 中間件附加元件範例為基礎。 附加元件是 Eclipse ThreadX 與 Azure SDK for Embedded C 之間的系結層。
本文中的代碼段是以下列範例為基礎:
模型標識碼公告
若要宣告模型標識碼,裝置必須將其包含在連線資訊中:
#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);
提示
這是裝置唯一可以設定模型標識碼的時間,裝置連線之後就無法更新。
DPS 承載
使用裝置佈建服務 (DPS) 的裝置,可以使用下列 JSON 承載,在modelId
佈建程式期間包含 要使用的 :
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
此範例會使用下列程式代碼來傳送此承載:
#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);
使用元件
如瞭解 IoT 隨插即用 模型中的元件中所述,您必須決定是否要使用元件來描述您的裝置。 當您使用元件時,裝置必須遵循下列各節中所述的規則。 為了簡化使用元件的 IoT 隨插即用 慣例,範例會使用 nx_azure_iot_hub_client.h 中的協助程式函式。
遙測
默認元件不需要新增至遙測訊息的任何特殊屬性。
當您使用巢狀元件時,裝置必須使用元件名稱來設定訊息屬性。 在下列代碼段中, component_name_ptr
是元件的名稱,例如 thermostat1
。 nx_azure_iot_pnp_helpers.h 中定義的協助程式函nx_azure_iot_pnp_helper_telemetry_message_create
式會新增具有元件名稱的訊息屬性:
#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);
}
唯讀屬性
從預設元件報告屬性並不需要任何特殊的建構:
#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))))
{
// ...
}
裝置對應項會更新為下列報告屬性:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
當您使用巢狀元件時,必須在元件名稱內建立屬性,並包含標記。 在下列代碼段中, component_name_ptr
是元件的名稱,例如 thermostat1
。 nx_azure_iot_pnp_helpers.h 中定義的協助程式函nx_azure_iot_pnp_helper_build_reported_property
式會以正確的格式建立報告屬性:
#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))))
{
// ...
}
// ...
}
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
可寫入的屬性
這些屬性可由裝置設定,或由後端應用程式更新。 若要遵循 IoT 隨插即用 慣例,裝置必須通知服務已成功接收 屬性。
報告可寫入的屬性
當裝置報告可寫入的屬性時,它必須包含 ack
慣例中定義的值。
若要從預設元件報告可寫入的屬性:
#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
// ...
}
裝置對應項會更新為下列報告屬性:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "success"
}
}
}
若要從巢狀元件報告可寫入的屬性,對應項必須包含標記,而且必須在元件名稱內建立屬性。 在下列代碼段中, component_name_ptr
是元件的名稱,例如 thermostat1
。 nx_azure_iot_pnp_helpers.h 中定義的協助程式函nx_azure_iot_pnp_helper_build_reported_property_with_status
式會建立報告的屬性承載:
#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
{
// ...
}
// ...
}
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "success"
}
}
}
}
訂閱所需的屬性更新
服務可以更新在連線裝置上觸發通知的所需屬性。 此通知包含更新所需的屬性和識別更新的版本號碼。 裝置必須在傳回給服務的訊息中包含 ack
此版本號碼。
預設元件會看到單一屬性,並使用收到的版本建立報告 ack
:
#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);
// ...
}
巢狀元件會接收以元件名稱包裝的所需屬性,並使用收到的版本建立報告 ack
:
#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);
// ...
}
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "success"
}
}
}
}
命令
默認元件會接收由服務叫用的命令名稱。
巢狀元件會接收前面加上元件名稱和分隔符的 *
命令名稱。 在下列代碼段中,nx_azure_iot_pnp_helpers.h 中定義的協助程式函nx_azure_iot_pnp_helper_command_name_parse
式會從裝置從服務接收的訊息中剖析元件名稱和命令名稱:
#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;
}
// ...
}
}
要求和響應承載
命令會使用類型來定義其要求和響應承載。 裝置必須還原串行化傳入輸入參數,並串行化回應。
下列範例示範如何使用裝載中定義的複雜類型來實作命令:
{
"@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"
}
]
}
}
}
下列代碼段顯示裝置如何實作此命令定義,包括用來啟用串行化和還原串行化的型別:
#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);
}
提示
要求和回應名稱不存在於透過網路傳輸的串行化承載中。
模型標識碼公告
若要宣告模型標識碼,裝置必須將其包含在連線資訊中:
DeviceClient.CreateFromConnectionString(
connectionString,
TransportType.Mqtt,
new ClientOptions() { ModelId = modelId })
新的 ClientOptions
多載可用於初始化連接的所有 DeviceClient
方法。
提示
針對模組和 IoT Edge 請使用 ModuleClient
取代 DeviceClient
。
提示
這是裝置唯一可以設定模型標識碼的時間,裝置連線之後就無法更新。
DPS 承載
使用裝置佈建服務 (DPS) 的裝置,可以使用下列 JSON 承載,在modelId
佈建程式期間包含 要使用的 :
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
使用元件
如瞭解 IoT 隨插即用 模型中的元件中所述,您必須決定是否要使用元件來描述您的裝置。 當您使用元件時,裝置必須遵循下列各節中所述的規則。
遙測
默認元件不需要新增至遙測訊息的任何特殊屬性。
當您使用巢狀元件時,裝置必須使用元件名稱來設定訊息屬性:
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);
}
唯讀屬性
從預設元件報告屬性並不需要任何特殊的建構:
TwinCollection reportedProperties = new TwinCollection();
reportedProperties["maxTemperature"] = 38.7;
await client.UpdateReportedPropertiesAsync(reportedProperties);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"maxTemperature" : 38.7
}
}
當您使用巢狀元件時,請在元件名稱內建立屬性,並包含標記:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
可寫入的屬性
這些屬性可由裝置設定,或由後端應用程式更新。 如果後端應用程式更新 屬性,用戶端會在 或 ModuleClient
中DeviceClient
收到回呼通知。 若要遵循 IoT 隨插即用 慣例,裝置必須通知服務已成功接收 屬性。
如果屬性類型為 Object
,則服務必須將完整的物件傳送至裝置,即使它只會更新物件的字段子集。 裝置傳送的通知也必須是完整的物件。
報告可寫入的屬性
當裝置報告可寫入的屬性時,它必須包含 ack
慣例中定義的值。
若要從預設元件報告可寫入的屬性:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
若要從巢狀元件報告可寫入的屬性,對應項必須包含標記:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
訂閱所需的屬性更新
服務可以更新在連線裝置上觸發通知的所需屬性。 此通知包含更新所需的屬性,包括識別更新的版本號碼。 裝置必須在傳回給服務的訊息中包含 ack
此版本號碼。
預設元件會看到單一屬性,並使用收到的版本建立報告 ack
:
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);
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
巢狀元件會接收以元件名稱包裝的所需屬性,而且應該回報 ack
報告屬性:
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);
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
命令
默認元件會接收由服務叫用的命令名稱。
巢狀元件會接收前面加上元件名稱和分隔符的 *
命令名稱。
await client.SetMethodHandlerAsync("themostat*reboot", (MethodRequest req, object ctx) =>
{
Console.WriteLine("REBOOT");
return Task.FromResult(new MethodResponse(200));
},
null);
要求和響應承載
命令會使用類型來定義其要求和響應承載。 裝置必須還原串行化傳入輸入參數,並串行化回應。
下列範例示範如何使用裝載中定義的複雜類型來實作命令:
{
"@type": "Command",
"name": "start",
"request": {
"name": "startRequest",
"schema": {
"@type": "Object",
"fields": [
{
"name": "startPriority",
"schema": "integer"
},
{
"name": "startMessage",
"schema" : "string"
}
]
}
},
"response": {
"name": "startReponse",
"schema": {
"@type": "Object",
"fields": [
{
"name": "startupTime",
"schema": "integer"
},
{
"name": "startupMessage",
"schema": "string"
}
]
}
}
}
下列代碼段顯示裝置如何實作此命令定義,包括用來啟用串行化和還原串行化的型別:
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);
提示
要求和回應名稱不存在於透過網路傳輸的串行化承載中。
模型標識碼公告
若要宣告模型標識碼,裝置必須將其包含在連線資訊中:
ClientOptions options = new ClientOptions();
options.setModelId(MODEL_ID);
deviceClient = new DeviceClient(deviceConnectionString, protocol, options);
多 ClientOptions
載可用於初始化連接的所有 DeviceClient
方法。
提示
針對模組和 IoT Edge 請使用 ModuleClient
取代 DeviceClient
。
提示
這是裝置唯一可以設定模型標識碼的時間,裝置連線之後就無法更新。
DPS 承載
使用裝置佈建服務 (DPS) 的裝置,可以使用下列 JSON 承載,包含在modelId
佈建程式期間要使用的 。
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
使用元件
如瞭解 IoT 隨插即用 模型中的元件中所述,您應該決定是否要使用元件來描述您的裝置。 當您使用元件時,裝置必須遵循下列各節中所述的規則。
遙測
默認元件不需要新增至遙測訊息的任何特殊屬性。
當您使用巢狀元件時,裝置必須使用元件名稱來設定訊息屬性:
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);
}
唯讀屬性
從預設元件報告屬性並不需要任何特殊的建構:
Property reportedProperty = new Property("maxTempSinceLastReboot", 38.7);
deviceClient.sendReportedProperties(Collections.singleton(reportedProperty));
裝置對應項會更新為下列報告屬性:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
當您使用巢狀元件時,請在元件名稱內建立屬性,並包含標記:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTemperature" : 38.7
}
}
}
可寫入的屬性
這些屬性可由裝置設定,或由後端應用程式更新。 如果後端應用程式更新 屬性,用戶端會在 或 ModuleClient
中DeviceClient
收到回呼通知。 若要遵循 IoT 隨插即用 慣例,裝置必須通知服務已成功接收 屬性。
如果屬性類型為 Object
,則服務必須將完整的物件傳送至裝置,即使它只會更新物件的字段子集。 裝置傳送的通知也必須是完整的物件。
報告可寫入的屬性
當裝置報告可寫入的屬性時,它必須包含 ack
慣例中定義的值。
若要從預設元件報告可寫入的屬性:
@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));
裝置對應項會更新為下列報告屬性:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
若要從巢狀元件報告可寫入的屬性,對應項必須包含標記:
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);
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
訂閱所需的屬性更新
服務可以更新在連線裝置上觸發通知的所需屬性。 此通知包含更新所需的屬性,包括識別更新的版本號碼。 裝置必須在傳回給服務的訊息中包含 ack
此版本號碼。
預設元件會看到單一屬性,並使用收到的版本建立報告 ack
:
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);
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "Successfully updated target temperature"
}
}
}
巢狀元件會接收以元件名稱包裝的所需屬性,而且應該回報 ack
報告屬性:
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);
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
命令
默認元件會接收由服務叫用的命令名稱。
巢狀元件會接收前面加上元件名稱和分隔符的 *
命令名稱。
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);
}
}
}
要求和響應承載
命令會使用類型來定義其要求和響應承載。 裝置必須還原串行化傳入輸入參數,並串行化回應。
下列範例示範如何使用裝載中定義的複雜類型來實作命令:
{
"@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"
}
]
}
}
}
下列代碼段顯示裝置如何實作此命令定義,包括用來啟用串行化和還原串行化的型別:
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);
}
}
提示
要求和回應名稱不存在於透過網路傳輸的串行化承載中。
模型標識碼公告
若要宣告模型標識碼,裝置必須將其包含在連線資訊中:
const modelIdObject = { modelId: 'dtmi:com:example:Thermostat;1' };
const client = Client.fromConnectionString(deviceConnectionString, Protocol);
await client.setOptions(modelIdObject);
await client.open();
提示
針對模組和 IoT Edge 請使用 ModuleClient
取代 Client
。
提示
這是裝置唯一可以設定模型標識碼的時間,裝置連線之後就無法更新。
DPS 承載
使用裝置佈建服務 (DPS) 的裝置,可以使用下列 JSON 承載,包含在modelId
佈建程式期間要使用的 。
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
使用元件
如瞭解 IoT 隨插即用 模型中的元件中所述,您必須決定要使用元件來描述您的裝置。 當您使用元件時,裝置必須遵循下列各節中所述的規則。
遙測
默認元件不需要新增至遙測訊息的任何特殊屬性。
當您使用巢狀元件時,裝置必須使用元件名稱來設定訊息屬性:
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);
}
唯讀屬性
從預設元件報告屬性並不需要任何特殊的建構:
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;
});
裝置對應項會更新為下列報告屬性:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
當您使用巢狀元件時,必須在元件名稱內建立屬性,並包含標記:
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;
});
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTempSinceLastReboot" : 38.7
}
}
}
可寫入的屬性
這些屬性可由裝置設定,或由後端應用程式更新。 如果後端應用程式更新 屬性,用戶端會在 或 ModuleClient
中Client
收到回呼通知。 若要遵循 IoT 隨插即用 慣例,裝置必須通知服務已成功接收屬性。
如果屬性類型為 Object
,則服務必須將完整的物件傳送至裝置,即使它只會更新物件的字段子集。 裝置傳送的通知也必須是完整的物件。
報告可寫入的屬性
當裝置報告可寫入的屬性時,它必須包含 ack
慣例中定義的值。
若要從預設元件報告可寫入的屬性:
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;
});
裝置對應項會更新為下列報告屬性:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "reported default value"
}
}
}
若要從巢狀元件報告可寫入的屬性,對應項必須包含標記:
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;
});
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "complete"
}
}
}
}
訂閱所需的屬性更新
服務可以更新在連線裝置上觸發通知的所需屬性。 此通知包含更新所需的屬性,包括識別更新的版本號碼。 裝置必須在傳回給服務的訊息中包含 ack
此版本號碼。
預設元件會看到單一屬性,並使用收到的版本建立報告 ack
:
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);
}
});
});
};
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
巢狀元件會接收以元件名稱包裝的所需屬性,而且應該回報 ack
報告屬性:
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);
}
});
});
};
元件的裝置對應項會顯示所需和回報的區段,如下所示:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
命令
默認元件會接收由服務叫用的命令名稱。
巢狀元件會接收前面加上元件名稱和分隔符的 *
命令名稱。
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);
要求和響應承載
命令會使用類型來定義其要求和響應承載。 裝置必須還原串行化傳入輸入參數,並串行化回應。
下列範例示範如何使用裝載中定義的複雜類型來實作命令:
{
"@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"
}
]
}
}
}
下列代碼段顯示裝置如何實作此命令定義,包括用來啟用串行化和還原串行化的型別:
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;
}
};
提示
要求和回應名稱不存在於透過網路傳輸的串行化承載中。
模型標識碼公告
若要宣告模型標識碼,裝置必須將其包含在連線資訊中:
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,
)
提示
針對模組和 IoT Edge 請使用 IoTHubModuleClient
取代 IoTHubDeviceClient
。
提示
這是裝置唯一可以設定模型標識碼的時間,裝置連線之後就無法更新。
DPS 承載
使用裝置佈建服務 (DPS) 的裝置,可以使用下列 JSON 承載,包含在modelId
佈建程式期間要使用的 。
{
"modelId" : "dtmi:com:example:Thermostat;1"
}
使用元件
如瞭解 IoT 隨插即用 模型中的元件中所述,您必須決定是否要使用元件來描述您的裝置。 當您使用元件時,裝置必須遵循下列各節中所述的規則。
遙測
默認元件不需要新增至遙測訊息的任何特殊屬性。
當您使用巢狀元件時,裝置必須使用元件名稱來設定訊息屬性:
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)
唯讀屬性
從預設元件報告屬性並不需要任何特殊的建構:
await device_client.patch_twin_reported_properties({"maxTempSinceLastReboot": 38.7})
裝置對應項會更新為下列報告屬性:
{
"reported": {
"maxTempSinceLastReboot" : 38.7
}
}
使用巢狀元件時,必須在元件名稱內建立屬性,並包含標記:
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)
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1" : {
"__t" : "c",
"maxTempSinceLastReboot" : 38.7
}
}
}
可寫入的屬性
這些屬性可由裝置設定,或由後端應用程式更新。 如果後端應用程式更新 屬性,用戶端會在 或 IoTHubModuleClient
中IoTHubDeviceClient
收到回呼通知。 若要遵循 IoT 隨插即用 慣例,裝置必須通知服務已成功接收屬性。
如果屬性類型為 Object
,則服務必須將完整的物件傳送至裝置,即使它只會更新物件的字段子集。 裝置傳送的通知也必須是完整的物件。
報告可寫入的屬性
當裝置報告可寫入的屬性時,它必須包含 ack
慣例中定義的值。
若要從預設元件報告可寫入的屬性:
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)
裝置對應項會更新為下列報告屬性:
{
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "reported default value"
}
}
}
若要從巢狀元件報告可寫入的屬性,對應項必須包含標記:
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)
裝置對應項會更新為下列報告屬性:
{
"reported": {
"thermostat1": {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 0,
"ad": "complete"
}
}
}
}
訂閱所需的屬性更新
服務可以更新在連線裝置上觸發通知的所需屬性。 此通知包含更新所需的屬性,包括識別更新的版本號碼。 裝置必須在傳回給服務的訊息中包含 ack
此版本號碼。
預設元件會看到單一屬性,並使用收到的版本建立報告 ack
:
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)
巢狀元件的裝置對應項會顯示所需的和回報區段,如下所示:
{
"desired" : {
"targetTemperature": 23.2,
"$version" : 3
},
"reported": {
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
巢狀元件會接收以元件名稱包裝的所需屬性,而且應該回報 ack
報告屬性:
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)
元件的裝置對應項會顯示所需和回報的區段,如下所示:
{
"desired" : {
"thermostat1" : {
"__t" : "c",
"targetTemperature": 23.2,
}
"$version" : 3
},
"reported": {
"thermostat1" : {
"__t" : "c",
"targetTemperature": {
"value": 23.2,
"ac": 200,
"av": 3,
"ad": "complete"
}
}
}
}
命令
默認元件會接收由服務叫用的命令名稱。
巢狀元件會接收前面加上元件名稱和分隔符的 *
命令名稱。
command_request = await device_client.receive_method_request("thermostat1*reboot")
要求和響應承載
命令會使用類型來定義其要求和響應承載。 裝置必須還原串行化傳入輸入參數,並串行化回應。
下列範例示範如何使用裝載中定義的複雜類型來實作命令:
{
"@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"
}
]
}
}
}
下列代碼段顯示裝置如何實作此命令定義,包括用來啟用串行化和還原串行化的型別:
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
提示
要求和回應名稱不存在於透過網路傳輸的串行化承載中。
下一步
既然您已瞭解 IoT 隨插即用 裝置開發,以下是一些其他資源: