دليل مطور جهاز IoT أجهزة التوصيل و التشغيل

يتيح لك IoT أجهزة التوصيل و التشغيل إنشاء أجهزة IoT تعلن عن قدراتها لتطبيقات Azure IoT. لا تتطلب أجهزة IoT أجهزة التوصيل و التشغيل تكوينا يدويا عندما يقوم العميل بتوصيلها بالتطبيقات الممكنة أجهزة التوصيل و التشغيل IoT مثل IoT Central.

يمكنك تنفيذ جهاز IoT مباشرة باستخدام الوحدات النمطية، أو باستخدام وحدات IoT Edge النمطية.

يصف هذا الدليل الخطوات الأساسية المطلوبة لإنشاء جهاز أو وحدة نمطية أو وحدة IoT Edge تتبع اصطلاحات أجهزة التوصيل و التشغيل IoT.

لإنشاء جهاز أجهزة التوصيل و التشغيل أو وحدة نمطية أو وحدة نمطية أو وحدة IoT Edge، اتبع الخطوات التالية:

  1. تأكد من أن جهازك يستخدم إما بروتوكول MQTT أو MQTT عبر WebSockets للاتصال ب Azure IoT Hub.
  2. إنشاء نموذج لغة تعريف التوائم الرقمية (DTDL) لوصف جهازك. لمعرفة المزيد، راجع فهم المكونات في نماذج أجهزة التوصيل و التشغيل IoT.
  3. قم بتحديث جهازك أو الوحدة النمطية للإعلان عن model-id كجزء من اتصال الجهاز.
  4. تنفيذ بيانات تتبع الاستخدام والخصائص والأوامر التي تتبع اصطلاحات أجهزة التوصيل و التشغيل 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

يمكن أن تتضمن modelId الأجهزة التي تستخدم خدمة تزويد الأجهزة (DPS) لاستخدامها أثناء عملية التزويد باستخدام حمولة JSON التالية:

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

خصائص قابلة للكتابة

يمكن تعيين هذه الخصائص بواسطة الجهاز أو تحديثها بواسطة التطبيق الخلفي. إذا حدث التطبيق الخلفي خاصية، يتلقى العميل إشعارا كرد اتصال في DeviceClient أو ModuleClient. لاتباع اصطلاحات أجهزة التوصيل و التشغيل 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

تستند قصاصات التعليمات البرمجية في هذه المقالة إلى العينات التي تستخدم ملحق Azure IoT Middleware ل Eclipse ThreadX. الملحق هو طبقة ربط بين Eclipse ThreadX وAzure SDK ل 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

يمكن أن تتضمن modelId الأجهزة التي تستخدم خدمة تزويد الأجهزة (DPS) التي سيتم استخدامها أثناء عملية التوفير باستخدام حمولة JSON التالية:

{
    "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_helper_telemetry_message_create المساعد المعرفة في nx_azure_iot_pnp_helpers.h خاصية الرسالة باسم المكون:

#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_helper_build_reported_property المساعد المعرفة في nx_azure_iot_pnp_helpers.h الخاصية التي تم الإبلاغ عنها بالتنسيق الصحيح:

#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_helper_build_reported_property_with_status المساعد المعرفة في nx_azure_iot_pnp_helpers.h حمولة الخاصية التي تم الإبلاغ عنها:

#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(&copy_json_reader,
            (UCHAR *)desired_version_property_name,
            sizeof(desired_version_property_name) - 1) ||
        nx_azure_iot_json_reader_token_int32_get(&copy_json_reader, (int32_t *)&version))
    {
        // ...
    }

    // ...

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

    // ...
}

يتلقى المكون المتداخل الخصائص المطلوبة المغلفة باسم المكون وينشئ ما تم الإبلاغ عنه 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_helper_command_name_parse المساعد المحددة في nx_azure_iot_pnp_helpers.h بتحليل اسم المكون واسم الأمر من الرسالة التي يتلقاها الجهاز من الخدمة:

#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

يمكن أن تتضمن modelId الأجهزة التي تستخدم خدمة تزويد الأجهزة (DPS) التي سيتم استخدامها أثناء عملية التوفير باستخدام حمولة JSON التالية:

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

خصائص قابلة للكتابة

يمكن تعيين هذه الخصائص بواسطة الجهاز أو تحديثها بواسطة التطبيق الخلفي. إذا حدث التطبيق الخلفي خاصية، يتلقى العميل إشعارا كرد اتصال في DeviceClient أو ModuleClient. لاتباع اصطلاحات أجهزة التوصيل و التشغيل 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

يمكن أن تتضمن modelId الأجهزة التي تستخدم خدمة تزويد الأجهزة (DPS) التي سيتم استخدامها أثناء عملية التزويد باستخدام حمولة JSON التالية.

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

خصائص قابلة للكتابة

يمكن تعيين هذه الخصائص بواسطة الجهاز أو تحديثها بواسطة التطبيق الخلفي. إذا حدث التطبيق الخلفي خاصية، يتلقى العميل إشعارا كرد اتصال في DeviceClient أو ModuleClient. لاتباع اصطلاحات أجهزة التوصيل و التشغيل 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

يمكن أن تتضمن modelId الأجهزة التي تستخدم خدمة تزويد الأجهزة (DPS) التي سيتم استخدامها أثناء عملية التزويد باستخدام حمولة JSON التالية.

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

خصائص قابلة للكتابة

يمكن تعيين هذه الخصائص بواسطة الجهاز أو تحديثها بواسطة التطبيق الخلفي. إذا حدث التطبيق الخلفي خاصية، يتلقى العميل إشعارا كرد اتصال في Client أو ModuleClient. لاتباع اصطلاحات أجهزة التوصيل و التشغيل 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

يمكن أن تتضمن modelId الأجهزة التي تستخدم خدمة تزويد الأجهزة (DPS) التي سيتم استخدامها أثناء عملية التزويد باستخدام حمولة JSON التالية.

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

خصائص قابلة للكتابة

يمكن تعيين هذه الخصائص بواسطة الجهاز أو تحديثها بواسطة التطبيق الخلفي. إذا حدث التطبيق الخلفي خاصية، يتلقى العميل إشعارا كرد اتصال في IoTHubDeviceClient أو IoTHubModuleClient. لاتباع اصطلاحات أجهزة التوصيل و التشغيل 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 أجهزة التوصيل و التشغيل، إليك بعض الموارد الأخرى: