برنامج تعليمي: إنشاء وتوصيل تطبيق عميل إلى التطبيق المركزي Azure IoT

هذا البرنامج التعليمي يوضح لك كيفية توصيل تطبيق عميل لتطبيق Azure IoT المركزي. يحاكي التطبيق سلوك جهاز تحكم درجة الحرارة. عندما يتصل التطبيق إلى IoT Central، فإنه يرسل معرف ID الطراز لنموذج جهاز التحكم في درجة الحرارة. يستخدم IoT Central معرف ID الطراز لاسترداد قالب الجهاز وإنشاء قالب جهاز لك. يمكنك إضافة طرق عرض إلى قالب الجهاز لتمكين عامل التشغيل من التفاعل مع جهاز.

في هذا البرنامج التعليمي، تتعلم كيفية:

  • إنشاء وتشغيل رمز الجهاز ورؤيتها تتصل بتطبيق IoT المركزي.
  • عرض القياس عن بُعد محاكاة تم إرسالها من الجهاز.
  • إضافة طرق عرض مخصصة إلى قالب جهاز.
  • انشر قالب الجهاز.
  • استخدم طريقة عرض لإدارة خصائص الجهاز.
  • استدعاء أمر للتحكم في الجهاز.

استعراض التعليمة البرمجية

المتطلبات الأساسية

لإكمال الخطوات في هذا البرنامج التعليمي، تحتاج إلى:

يمكنك إكمال هذا البرنامج التعليمي على Linux أو Windows. أوامر shell في هذا البرنامج التعليمي تتبع اصطلاح Linux لفواصل المسار ''،/إذا كنت تتابع على Windows فتأكد من تبديل هذه الفواصل بـ '\'.

تختلف المتطلبات الأساسية تبعًا لنظام التشغيل:

Linux

يفترض هذا البرنامج التعليمي أنك تستخدم Ubuntu Linux. تم اختبار الخطوات في هذا البرنامج التعليمي باستخدام Ubuntu 18.04.

لإكمال هذا البرنامج التعليمي على Linux، قم بتثبيت البرنامج التالي على بيئة Linux المحلية لديك:

تثبيت سحابة القطاع الحكومي،Git، cmake، وكافة التبعيات المطلوبة باستخدام الأمر apt-get:

sudo apt-get update
sudo apt-get install -y git cmake build-essential curl libcurl4-openssl-dev libssl-dev uuid-dev

تحقق من أن إصدار ⁧cmake⁩ أعلى من ⁧⁩2.8.12⁧⁩ وإصدار ⁧⁩GCC ⁧⁩ أعلى من ⁧⁩4.4.7⁧⁩.

cmake --version
gcc --version

Windows

لإكمال هذا البرنامج التعليمي على Windows، قم بتثبيت البرنامج التالي على بيئة Windows المحلية لديك:

قم بتنزيل الرمز

في هذا البرنامج التعليمي، تستطيع إعداد بيئة تطوير يمكنك استخدامها لاستنساخ وبناء جهاز مركز Azure IoT C SDK.

افتح موجه الأوامر في الدليل الذي تختاره. نفذ الأمر التالي لاستنساخ مستودع GitHub⁧⁩Azure IoT C SDKs and Libraries⁧⁩ في هذا الموقع:

git clone https://github.com/Azure/azure-iot-sdk-c.git
cd azure-iot-sdk-c
git submodule update --init

توقع أن تستغرق هذه العملية دقائق عدة لإكمالها.

مراجعة الرمز

في نسخة من Microsoft Azure IoT SDK لـ C قمت بتنزيلها مسبقًا، افتح azure-iot-sdk-c/iothub_client/samples/pnp/pnp_temperature_controller/pnp_temperature_controller.c و azure-iot-sdk-c/iothub_client/samples/pnp/pnp_temperature_controller/pnp_thermostat_component.c الملفات في محرر نص.

أثناء تشغيل النموذج للاتصال بإنترنت الأجهزة المركزية يستخدم "خدمة توفير الأجهزة" (DPS) لتسجيل الجهاز وإنشاء سلسلة اتصال. يسترد النموذج معلومات اتصال DPS التي يحتاجها من بيئة سطر الأوامر.

في pnp_temperature_controller.c، main تستدعي الدالة أولًا CreateDeviceClientAndAllocateComponents إلى:

  • تعيين dtmi:com:example:Thermostat;1 مُعرف الطراز. يستخدم IoT Central معرف الطراز لتحديد أو إنشاء قالب الجهاز لهذا الجهاز. لمعرفة المزيد، راجع تعيين جهاز إلى قالب جهاز.
  • استخدم DPS لتوفير وتسجيل الجهاز.
  • أنشئ مقبض عميل جهاز والاتصال بتطبيق IoT المركزي.
  • أنشئ معالجًا للأوامر في مكون وحدة تحكم درجة الحرارة.
  • أنشئ معالجًا لتحديثات الخصائص في مكون وحدة تحكم درجة الحرارة.
  • أنشئ مكوني منظم الحرارة.

mainالدالة التالية:

  • تقارير بعض قيم الخصائص الأوليةِ لكافة المكونات.
  • يبدأ حلقة لإرسال القياس عن بُعد من كافة المكونات.

تبدأmain الدالة بعد ذلك في سلسلة رسائل لإرسال القياس عن بُعد دوريًا.

int main(void)
{
  IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient = NULL;

  if ((deviceClient = CreateDeviceClientAndAllocateComponents()) == NULL)
  {
    LogError("Failure creating IotHub device client");
  }
  else
  {
    LogInfo("Successfully created device client.  Hit Control-C to exit program\n");

    int numberOfIterations = 0;

    // During startup, send the non-"writable" properties.
    PnP_TempControlComponent_ReportSerialNumber_Property(deviceClient);
    PnP_DeviceInfoComponent_Report_All_Properties(g_deviceInfoComponentName, deviceClient);
    PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(g_thermostatHandle1, deviceClient);
    PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(g_thermostatHandle2, deviceClient);

    while (true)
    {
      // Wake up periodically to poll.  Even if we do not plan on sending telemetry, we still need to poll periodically in order to process
      // incoming requests from the server and to do connection keep alives.
      if ((numberOfIterations % g_sendTelemetryPollInterval) == 0)
      {
          PnP_TempControlComponent_SendWorkingSet(deviceClient);
          PnP_ThermostatComponent_SendTelemetry(g_thermostatHandle1, deviceClient);
          PnP_ThermostatComponent_SendTelemetry(g_thermostatHandle2, deviceClient);
      }

      IoTHubDeviceClient_LL_DoWork(deviceClient);
      ThreadAPI_Sleep(g_sleepBetweenPollsMs);
      numberOfIterations++;
    }

    // Free the memory allocated to track simulated thermostat.
    PnP_ThermostatComponent_Destroy(g_thermostatHandle2);
    PnP_ThermostatComponent_Destroy(g_thermostatHandle1);

    // Clean up the iothub sdk handle
    IoTHubDeviceClient_LL_Destroy(deviceClient);
    // Free all the sdk subsystem
    IoTHub_Deinit();
  }

  return 0;
}

فيpnp_thermostat_component.c، توضح الدالةPnP_ThermostatComponent_SendTelemetryكيفية إرسال الجهاز قياس درجة الحرارة عن بُعد من أحد المكونات إلى IoT Central:

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_thermostat_component.c، PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Propertyترسل الدالةmaxTempSinceLastReboot تحديث خاصية من المكون إلى IoT Central:

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), g_maxTempSinceLastRebootPropertyFormat, 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_thermostat_component.c تتعامل الدالةPnP_ThermostatComponent_ProcessPropertyUpdateمع تحديثات الخصائص القابلة للكتابة من IoT Central:

void PnP_ThermostatComponent_ProcessPropertyUpdate(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClientLL, const char* propertyName, JSON_Value* propertyValue, int version)
{
  PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;

  if (strcmp(propertyName, g_targetTemperaturePropertyName) != 0)
  {
    LogError("Property=%s was requested to be changed but is not part of the thermostat interface definition", propertyName);
  }
  else if (json_value_get_type(propertyValue) != JSONNumber)
  {
    LogError("JSON field %s is not a number", g_targetTemperaturePropertyName);
  }
  else
  {
    double targetTemperature = json_value_get_number(propertyValue);

    LogInfo("Received targetTemperature=%f for component=%s", targetTemperature, pnpThermostatComponent->componentName);
    
    bool maxTempUpdated = false;
    UpdateTemperatureAndStatistics(pnpThermostatComponent, targetTemperature, &maxTempUpdated);

    // The device needs to let the service know that it has received the targetTemperature desired property.
    SendTargetTemperatureResponse(pnpThermostatComponent, deviceClientLL, version);
    
    if (maxTempUpdated)
    {
      // If the Maximum temperature has been updated, we also report this as a property.
        PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(pnpThermostatComponent, deviceClientLL);
    }
  }
}

فيpnp_thermostat_component.c، تتعامل الدالةPnP_ThermostatComponent_ProcessCommand مع الأوامر التي يتم استدعاؤها من IoT Central:

int PnP_ThermostatComponent_ProcessCommand(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, const char *pnpCommandName, JSON_Value* commandJsonValue, unsigned char** response, size_t* responseSize)
{
  PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
  const char* sinceStr;
  int result;

  if (strcmp(pnpCommandName, g_getMaxMinReport) != 0)
  {
    LogError("PnP command=%s is not supported on thermostat component", pnpCommandName);
    result = PNP_STATUS_NOT_FOUND;
  }
  // See caveats section in ../readme.md; we don't actually respect this sinceStr to keep the sample simple,
  // but want to demonstrate how to parse out in any case.
  else if ((sinceStr = json_value_get_string(commandJsonValue)) == NULL)
  {
    LogError("Cannot retrieve JSON string for command");
    result = PNP_STATUS_BAD_FORMAT;
  }
  else if (BuildMaxMinCommandResponse(pnpThermostatComponent, response, responseSize) == false)
  {
    LogError("Unable to build response for component=%s", pnpThermostatComponent->componentName);
    result = PNP_STATUS_INTERNAL_ERROR;
  }
  else
  {
    LogInfo("Returning success from command request for component=%s", pnpThermostatComponent->componentName);
    result = PNP_STATUS_SUCCESS;
  }

  return result;
}

إنشاء التعليمات البرمجية

يمكنك استخدام الجهاز SDK لإنشاء نموذج التعليمات البرمجية المضمنة:

  1. أنشئ مجلدًا فرعيًا ⁧⁩cmake⁧⁩ في المجلد الجذر من الجهاز SDK المستنسخ ثم انتقل إلى هذا المجلد:

    cd azure-iot-sdk-c
    mkdir cmake
    cd cmake
    
  2. شغل الأوامر التالية لإنشاء SDK ونماذج:

    cmake -Duse_prov_client=ON -Dhsm_type_symm_key=ON -Drun_e2e_tests=OFF ..
    cmake --build .
    

معلومات الاتصال

عند تشغيل تطبيق الجهاز العينة لاحقًا في هذا البرنامج التعليمي، تحتاج قيم التكوين التالية:

  • نطاق المعرف: في تطبيق IoT Central، انتقل إلى Permissions > Device connection groups. دوّن قيمة نطاق ID.
  • المفتاح الأساسي للمجموعة: في تطبيق IoT Central، انتقل إلى Permissions > Device connection groups > SAS-IoT-Devices. دوّن قيمة المفتاح الأساسي لتوقيع الوصول المشترك.

استخدم Azure Cloud Shell لإنشاء مفتاح جهاز من المفتاح الأساسي للمجموعة الذي قمت باسترداده:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

دوّن ملاحظة عن مفتاح الجهاز الذي تم إنشاؤه، يمكنك استخدامها لاحقًا في هذا البرنامج التعليمي.

ملاحظة

لتشغيل هذه العينة، لا تحتاج إلى تسجيل الجهاز مسبقا في تطبيق IoT Central. يستخدم النموذج إمكانية IoT Central لتسجيل الأجهزة تلقائيا عند الاتصال لأول مرة.

تشغيل التعليمات البرمجية

لتشغيل نموذج التطبيق، فم بفتح بيئة سطر الأوامر وانتقل إلى المجلد azure-iot-sdk-c\cmake.

يُرجى تعيين متغيرات البيئة لتكوين العينة. توضح القصاصة البرمجية التالية كيفية تعيين متغيرات البيئة في موجه أوامر Windows. إذا كنت تستخدم ⁧⁩bash⁧⁩ shell⁧، استبدل الأوامر ⁧set⁩ بالأوامر export⁩:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

لتشغيل النموذج:

# Bash
cd iothub_client/samples/pnp/pnp_temperature_controller/
./pnp_temperature_controller
REM Windows
cd iothub_client\samples\pnp\pnp_temperature_controller\Debug
.\pnp_temperature_controller.exe

يوضح الإخراج التالي تسجيل الجهاز والاتصال بـ IoT Central. يبدأ النموذج بإرسال القياس عن بُعد:

Info: Initiating DPS client to retrieve IoT Hub connection information
-> 09:43:27 CONNECT | VER: 4 | KEEPALIVE: 0 | FLAGS: 194 | USERNAME: 0ne0026656D/registrations/sample-device-01/api-version=2019-03-31&ClientVersion=1.6.0 | PWD: XXXX | CLEAN: 1
<- 09:43:28 CONNACK | SESSION_PRESENT: false | RETURN_CODE: 0x0
-> 09:43:29 SUBSCRIBE | PACKET_ID: 1 | TOPIC_NAME: $dps/registrations/res/# | QOS: 1
<- 09:43:30 SUBACK | PACKET_ID: 1 | RETURN_CODE: 1
-> 09:43:30 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/PUT/iotdps-register/?$rid=1 | PAYLOAD_LEN: 102
<- 09:43:31 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/202/?$rid=1&retry-after=3 | PACKET_ID: 2 | PAYLOAD_LEN: 94
-> 09:43:31 PUBACK | PACKET_ID: 2
-> 09:43:33 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/GET/iotdps-get-operationstatus/?$rid=2&operationId=4.2f792ade0a5c3e68.baf0e879-d88a-4153-afef-71aff51fd847 | PAYLOAD_LEN: 102
<- 09:43:34 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/202/?$rid=2&retry-after=3 | PACKET_ID: 2 | PAYLOAD_LEN: 173
-> 09:43:34 PUBACK | PACKET_ID: 2
-> 09:43:36 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $dps/registrations/GET/iotdps-get-operationstatus/?$rid=3&operationId=4.2f792ade0a5c3e68.baf0e879-d88a-4153-afef-71aff51fd847 | PAYLOAD_LEN: 102
<- 09:43:37 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: $dps/registrations/res/200/?$rid=3 | PACKET_ID: 2 | PAYLOAD_LEN: 478
-> 09:43:37 PUBACK | PACKET_ID: 2
Info: Provisioning callback indicates success.  iothubUri=iotc-60a....azure-devices.net, deviceId=sample-device-01
-> 09:43:37 DISCONNECT
Info: DPS successfully registered.  Continuing on to creation of IoTHub device client handle.
Info: Successfully created device client.  Hit Control-C to exit program

Info: Sending serialNumber property to IoTHub
Info: Sending device information property to IoTHub.  propertyName=swVersion, propertyValue="1.0.0.0"
Info: Sending device information property to IoTHub.  propertyName=manufacturer, propertyValue="Sample-Manufacturer"
Info: Sending device information property to IoTHub.  propertyName=model, propertyValue="sample-Model-123"
Info: Sending device information property to IoTHub.  propertyName=osName, propertyValue="sample-OperatingSystem-name"
Info: Sending device information property to IoTHub.  propertyName=processorArchitecture, propertyValue="Contoso-Arch-64bit"
Info: Sending device information property to IoTHub.  propertyName=processorManufacturer, propertyValue="Processor Manufacturer(TM)"
Info: Sending device information property to IoTHub.  propertyName=totalStorage, propertyValue=10000
Info: Sending device information property to IoTHub.  propertyName=totalMemory, propertyValue=200
Info: Sending maximumTemperatureSinceLastReboot property to IoTHub for component=thermostat1
Info: Sending maximumTemperatureSinceLastReboot property to IoTHub for component=thermostat2
-> 09:43:44 CONNECT | VER: 4 | KEEPALIVE: 240 | FLAGS: 192 | USERNAME: iotc-60a576a2-eec7-48e2-9306-9e7089a79995.azure-devices.net/sample-device-01/?api-version=2020-09-30&DeviceClientType=iothubclient%2f1.6.0%20(native%3b%20Linux%3b%20x86_64)&model-id=dtmi%3acom%3aexample%3aTemperatureController%3b1 | PWD: XXXX | CLEAN: 0
<- 09:43:44 CONNACK | SESSION_PRESENT: false | RETURN_CODE: 0x0
-> 09:43:44 SUBSCRIBE | PACKET_ID: 2 | TOPIC_NAME: $iothub/twin/res/# | QOS: 0 | TOPIC_NAME: $iothub/methods/POST/# | QOS: 0
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/ | PACKET_ID: 3 | PAYLOAD_LEN: 19
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/%24.sub=thermostat1 | PACKET_ID: 4 | PAYLOAD_LEN: 21
-> 09:43:44 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_LEAST_ONCE | TOPIC_NAME: devices/sample-device-01/messages/events/%24.sub=thermostat2 | PACKET_ID: 5 | PAYLOAD_LEN: 21

كمشغل في تطبيق Azure IoT Central، يمكنك:

  • عرض تتبع الاستخدام بواسطة مكوني الحرارة في صفحة Overview:

    لقطة شاشة تعرض صفحة نظرة عامة على الجهاز.

  • عرض خصائص الجهاز في صفحة About. تعرض هذه الصفحة الخصائص من معلومات مكون الجهاز ومكونات منظمي الحرارة:

    لقطة شاشة تعرض طريقة عرض خصائص الجهاز.

تخصيص قالب الجهاز

بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي أنشأه IoT Central بصورة تلقائية عندما يتم توصيل جهاز التحكم في درجة الحرارة.

لكي تتم إضافة خاصية السحابة لتخزين اسم العميل المقترن بالجهاز:

  1. في تطبيق IoT Central، انتقل إلى قالب الجهاز Temperature Controller في صفحة Device templates.

  2. في نموذج وحدة تحكم درجة الحرارة ، حدد +إضافة إمكانية.

  3. أدخل اسم العميل كاسم العرض، وحدد خاصية السحابةكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم اختر ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض أوامر Get Max-Min report في تطبيق IoT Central الخاص بك:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. للحصول على getMaxMinReport (thermostat1)، استبدل Get Max-Min report. ب Get thermostat1 status report.

  3. للحصول على getMaxMinReport (thermostat2)، استبدل Get Max-Min report. ب Get thermostat2 status report.

  4. حدد ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض خصائص درجة الحرارة المستهدفة القابلة للكتابة في تطبيق IoT Central:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. بالنسبة إلى درجة الحرارة المستهدفة (thermostat1) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (1) .

  3. بالنسبة إلى درجة الحرارة المستهدفة (thermostat2) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (2) .

  4. حدد ⁧⁩حفظ⁧⁩.

تشتمل مكونات مكون الحرارة في نموذج وحدة التحكم في درجة الحرارة على خاصية قابلة كتابة درجة الحرارة المستهدفة، ويتضمن نموذج الجهاز خاصية السحابة الإلكترونية اسم العميل. قم بإنشاء عرض يمكن أن يستخدمه المشغل في تحرير هذه الخصائص:

  1. حدد Views ثم حدد التجانب Editing device and cloud data.

  2. أدخل Properties كاسم النموذج.

  3. حدد خصائص درجة الحرارة الهدف (1)ودرجة الحرارة الهدف (2)واسم العميل . ثم حدد إضافة مقطع.

  4. حفظ التغييرات.

لقطة شاشة تعرض طريقة عرض لتحديث قيم الخصائص.

نشر قالب الجهاز

قبل أن يتمكن المشغل من رؤية واستخدام التخصيصات التي أجريتها، يجب عليك نشر قالب الجهاز.

من قالب جهاز Thermostat حدد Publish. في لوحة انشر قالب هذا الجهاز إلى التطبيق اخترPublish.

يمكن لعامل التشغيل الآن استخدام عرض Properties لتحديث قيم الخصائص، واستدعاء أوامر تسمى Get thermostat1 status report وGet thermostat2 status report في صفحة أوامر الجهاز:

  • تحديث قيم الخاصية القابلة للكتابة على صفحة Properties:

    لقطة شاشة تعرض تحديث خصائص الجهاز.

  • استدعاء الأوامر من صفحة Commands. إذا قمت بتشغيل أمر تقرير الحالة، فحدد تاريخ ووقت المعلمة Since قبل تشغيله:

    لقطة شاشة تعرض استدعاء أمر.

    لقطة شاشة تعرض استجابة الأمر.

يُمكنك مشاهدة كيفية استجابة الجهاز للأوامر وتحديثات الخصائص:

<- 09:49:03 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/methods/POST/thermostat1*getMaxMinReport/?$rid=1 | PAYLOAD_LEN: 26
Info: Received PnP command for component=thermostat1, command=getMaxMinReport
Info: Returning success from command request for component=thermostat1
-> 09:49:03 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/methods/res/200/?$rid=1 | PAYLOAD_LEN: 117

...

<- 09:50:04 PUBLISH | IS_DUP: false | RETAIN: 0 | QOS: DELIVER_AT_MOST_ONCE | TOPIC_NAME: $iothub/twin/PATCH/properties/desired/?$version=2 | PAYLOAD_LEN: 63
Info: Received targetTemperature=67.000000 for component=thermostat2
Info: Sending acknowledgement of property to IoTHub for component=thermostat2

استعراض التعليمة البرمجية

المتطلبات الأساسية

لإكمال الخطوات الواردة في هذه المقالة، ستحتاج إلى الموارد التالية:

مراجعة التعليمة البرمجية

في نسخة مستودع Microsoft Azure IoT SDK ل C# الذي قمت بتنزيله مسبقا، افتح ملف الحل azure-iot-sdk-csharp-main\azureiot.sln" في Visual Studio. في مستكشف الحلول، قم بتوسيع المجلد PnpDeviceSamples > TemperatureController وافتح ملفات Program.cs و TemperatureControllerSample.cs لعرض التعليمات البرمجية لهذا النموذج.

عند تشغيل النموذج للاتصال بـ IoT Central، فإنها تستخدم خدمة توفير الأجهزة (DPS) لتسجيل الجهاز، وإنشاء سلسلة اتصال. تقوم العينة باسترداد معلومات اتصال خدمة توفير الأجهزة التي يحتاجها من البيئة.

في Program.cs، يستدعي الأسلوب MainSetupDeviceClientAsync من أجل:

  • استخدم معرف النموذج dtmi:com:example:TemperatureController;2 عندما يقوم بتوفير الجهاز مع DPS. يستخدم IoT Central معرف النموذج لتحديد أو إنشاء قالب الجهاز لهذا الجهاز. لمعرفة المزيد، راجع تعيين جهاز إلى قالب جهاز.
  • أنشئ مثيل DeviceClient للاتصال بـ IoT Central.
private static async Task<DeviceClient> SetupDeviceClientAsync(Parameters parameters, CancellationToken cancellationToken)
{
  DeviceClient deviceClient;
  switch (parameters.DeviceSecurityType.ToLowerInvariant())
  {
    case "dps":
      DeviceRegistrationResult dpsRegistrationResult = await ProvisionDeviceAsync(parameters, cancellationToken);
      var authMethod = new DeviceAuthenticationWithRegistrySymmetricKey(dpsRegistrationResult.DeviceId, parameters.DeviceSymmetricKey);
      deviceClient = InitializeDeviceClient(dpsRegistrationResult.AssignedHub, authMethod);
      break;

    case "connectionstring":
      // ...

    default:
      // ...
  }
  return deviceClient;
}

الأسلوب الرئيسي بعد ذلك يقوم بإنشاء مثيل TemperatureControllerSample ثم استدعاء الأسلوب PerformOperationsAsync لمعالجة التفاعلات مع IoT Central.

في TemperatureControllerSample.cs، يقوم الأسلوب PerformOperationsAsync بما يلي:

  • تعيين معالج لأمر reboot على المكون الافتراضي.
  • تعيين معالجات لأوامر getMaxMinReport على مكوني منظم الحرارة.
  • يضبط المعالجات لتلقي تحديثات خصائص درجة الحرارة المستهدفة على مكوني منظم الحرارة.
  • إرسال تحديثات خصائص معلومات الجهاز الأولية.
  • ترسل دورياً قياس درجة الحرارة عن بعد من اثنين من مكونات منظم الحرارة.
  • يرسل بشكل دوري القياس عن بعد لمجموعة العمل من المكون الافتراضي.
  • يرسل أقصى درجة حرارة منذ آخر إعادة تشغيل عندما يتم الوصول إلى درجة حرارة قصوى جديدة في مكوني منظم الحرارة.
public async Task PerformOperationsAsync(CancellationToken cancellationToken)
{
  await _deviceClient.SetMethodHandlerAsync("reboot", HandleRebootCommandAsync, _deviceClient, cancellationToken);

  // For a component-level command, the command name is in the format "<component-name>*<command-name>".
  await _deviceClient.SetMethodHandlerAsync("thermostat1*getMaxMinReport", HandleMaxMinReportCommand, Thermostat1, cancellationToken);
  await _deviceClient.SetMethodHandlerAsync("thermostat2*getMaxMinReport", HandleMaxMinReportCommand, Thermostat2, cancellationToken);

  await _deviceClient.SetDesiredPropertyUpdateCallbackAsync(SetDesiredPropertyUpdateCallback, null, cancellationToken);
  _desiredPropertyUpdateCallbacks.Add(Thermostat1, TargetTemperatureUpdateCallbackAsync);
  _desiredPropertyUpdateCallbacks.Add(Thermostat2, TargetTemperatureUpdateCallbackAsync);

  await UpdateDeviceInformationAsync(cancellationToken);
  await SendDeviceSerialNumberAsync(cancellationToken);

  bool temperatureReset = true;
  _maxTemp[Thermostat1] = 0d;
  _maxTemp[Thermostat2] = 0d;

  while (!cancellationToken.IsCancellationRequested)
  {
    if (temperatureReset)
    {
      // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component.
      _temperature[Thermostat1] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1);
      _temperature[Thermostat2] = Math.Round(s_random.NextDouble() * 40.0 + 5.0, 1);
    }

    await SendTemperatureAsync(Thermostat1, cancellationToken);
    await SendTemperatureAsync(Thermostat2, cancellationToken);
    await SendDeviceMemoryAsync(cancellationToken);

    temperatureReset = _temperature[Thermostat1] == 0 && _temperature[Thermostat2] == 0;
    await Task.Delay(5 * 1000);
  }
}

الأسلوب SendTemperatureAsync يعرض كيفية إرسال الجهاز قياس الحرارة عن بُعد من مكون إلى IoT Central. يستخدم الأسلوب SendTemperatureTelemetryAsync الفئة PnpConvention لإنشاء الرسالة:

private async Task SendTemperatureAsync(string componentName, CancellationToken cancellationToken)
{
  await SendTemperatureTelemetryAsync(componentName, cancellationToken);

  double maxTemp = _temperatureReadingsDateTimeOffset[componentName].Values.Max<double>();
  if (maxTemp > _maxTemp[componentName])
  {
    _maxTemp[componentName] = maxTemp;
    await UpdateMaxTemperatureSinceLastRebootAsync(componentName, cancellationToken);
  }
}

private async Task SendTemperatureTelemetryAsync(string componentName, CancellationToken cancellationToken)
{
  const string telemetryName = "temperature";
  double currentTemperature = _temperature[componentName];
  using Message msg = PnpConvention.CreateMessage(telemetryName, currentTemperature, componentName);

  await _deviceClient.SendEventAsync(msg, cancellationToken);

  if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName))
  {
    _temperatureReadingsDateTimeOffset[componentName].TryAdd(DateTimeOffset.UtcNow, currentTemperature);
  }
  else
  {
    _temperatureReadingsDateTimeOffset.TryAdd(
      componentName,
      new Dictionary<DateTimeOffset, double>
      {
        { DateTimeOffset.UtcNow, currentTemperature },
      });
  }
}

يرسل الأسلوب UpdateMaxTemperatureSinceLastRebootAsync تحديث الخاصية maxTempSinceLastReboot إلى IoT Central. يستخدم هذا الأسلوب الفئة PnpConvention لإنشاء تصحيح:

private async Task UpdateMaxTemperatureSinceLastRebootAsync(string componentName, CancellationToken cancellationToken)
{
  const string propertyName = "maxTempSinceLastReboot";
  double maxTemp = _maxTemp[componentName];
  TwinCollection reportedProperties = PnpConvention.CreateComponentPropertyPatch(componentName, propertyName, maxTemp);

  await _deviceClient.UpdateReportedPropertiesAsync(reportedProperties, cancellationToken);
}

يعالج الأسلوب TargetTemperatureUpdateCallbackAsyncتحديث الخاصية درجة الحرارة الهدف القابل للكتابة من IoT Central. يستخدم هذا الأسلوب الفئة PnpConvention لقراءة رسالة تحديث الخاصية وإنشاء الاستجابة:

private async Task TargetTemperatureUpdateCallbackAsync(TwinCollection desiredProperties, object userContext)
{
  const string propertyName = "targetTemperature";
  string componentName = (string)userContext;

  bool targetTempUpdateReceived = PnpConvention.TryGetPropertyFromTwin(
    desiredProperties,
    propertyName,
    out double targetTemperature,
    componentName);
  if (!targetTempUpdateReceived)
  {
      return;
  }

  TwinCollection pendingReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse(
      componentName,
      propertyName,
      targetTemperature,
      (int)StatusCode.InProgress,
      desiredProperties.Version);

  await _deviceClient.UpdateReportedPropertiesAsync(pendingReportedProperty);

  // Update Temperature in 2 steps
  double step = (targetTemperature - _temperature[componentName]) / 2d;
  for (int i = 1; i <= 2; i++)
  {
      _temperature[componentName] = Math.Round(_temperature[componentName] + step, 1);
      await Task.Delay(6 * 1000);
  }

  TwinCollection completedReportedProperty = PnpConvention.CreateComponentWritablePropertyResponse(
      componentName,
      propertyName,
      _temperature[componentName],
      (int)StatusCode.Completed,
      desiredProperties.Version,
      "Successfully updated target temperature");

  await _deviceClient.UpdateReportedPropertiesAsync(completedReportedProperty);
}

الأسلوب HandleMaxMinReportCommand يعالج الأوامر للمكونات التي تم استدعاؤها من IoT Central:

private Task<MethodResponse> HandleMaxMinReportCommand(MethodRequest request, object userContext)
{
    try
    {
        string componentName = (string)userContext;
        DateTime sinceInUtc = JsonConvert.DeserializeObject<DateTime>(request.DataAsJson);
        var sinceInDateTimeOffset = new DateTimeOffset(sinceInUtc);

        if (_temperatureReadingsDateTimeOffset.ContainsKey(componentName))
        {

            Dictionary<DateTimeOffset, double> allReadings = _temperatureReadingsDateTimeOffset[componentName];
            Dictionary<DateTimeOffset, double> filteredReadings = allReadings.Where(i => i.Key > sinceInDateTimeOffset)
                .ToDictionary(i => i.Key, i => i.Value);

            if (filteredReadings != null && filteredReadings.Any())
            {
                var report = new
                {
                    maxTemp = filteredReadings.Values.Max<double>(),
                    minTemp = filteredReadings.Values.Min<double>(),
                    avgTemp = filteredReadings.Values.Average(),
                    startTime = filteredReadings.Keys.Min(),
                    endTime = filteredReadings.Keys.Max(),
                };

                byte[] responsePayload = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report));
                return Task.FromResult(new MethodResponse(responsePayload, (int)StatusCode.Completed));
            }

            return Task.FromResult(new MethodResponse((int)StatusCode.NotFound));
        }

        return Task.FromResult(new MethodResponse((int)StatusCode.NotFound));
    }
    catch (JsonReaderException ex)
    {
        // ...
    }
}

احصل على معلومات الاتصال

عند تشغيل تطبيق الجهاز العينة لاحقًا في هذا البرنامج التعليمي، تحتاج قيم التكوين التالية:

  • نطاق المعرف: في تطبيق IoT Central، انتقل إلى مجموعات اتصال جهاز الأذونات>. دوّن قيمة نطاق ID.
  • المفتاح الأساسي للمجموعة: في تطبيق IoT Central، انتقل إلى مجموعات > اتصال جهاز الأذونات > SAS-IoT-Devices. دوّن قيمة المفتاح الأساسي لتوقيع الوصول المشترك.

استخدم Azure Cloud Shell لإنشاء مفتاح جهاز من المفتاح الأساسي للمجموعة الذي قمت باسترداده:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

دوّن ملاحظة عن مفتاح الجهاز الذي تم إنشاؤه، يمكنك استخدامها لاحقًا في هذا البرنامج التعليمي.

ملاحظة

لتشغيل هذه العينة، لا تحتاج إلى تسجيل الجهاز مسبقا في تطبيق IoT Central. يستخدم النموذج إمكانية IoT Central لتسجيل الأجهزة تلقائيا عند الاتصال لأول مرة.

تشغيل التعليمات البرمجية

ملاحظة

إعداد TemperatureController كمشروع بدء التشغيل قبل تشغيل التعليمات البرمجية.

لتشغيل نموذج التطبيق في Visual Studio:

  1. في مستكشف الحلول، حدد ملف مشروع PnpDeviceSamples > TemperatureController.

  2. انتقل إلى Project > TemperatureController Properties > Debug. ثم قم بإضافة متغيرات البيئة التالية للمشروع:

    الاسم القيمة
    IOTHUB_DEVICE_SECURITY_TYPE DPS
    IOTHUB_DEVICE_DPS_ENDPOINT global.azure-devices-provisioning.net
    IOTHUB_DEVICE_DPS_ID_SCOPE قيمة نطاق المعرف التي قمت بتدوينها مسبقًا.
    IOTHUB_DEVICE_DPS_DEVICE_ID sample-device-01
    IOTHUB_DEVICE_DPS_DEVICE_KEY قيمة مفتاح الجهاز التي تم إنشاؤها والتي قمت بتدوينها مسبقًا.

يمكنك الآن تشغيل وتصحيح العينة في Visual Studio.

يظهر الإخراج التالي تسجيل الجهاز والاتصال بـ IoT Central. يبدأ النموذج بإرسال القياس عن بُعد:

[03/31/2021 14:43:17]info: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Press Control+C to quit the sample.
[03/31/2021 14:43:17]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set up the device client.
[03/31/2021 14:43:18]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Initializing via DPS
[03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler for 'reboot' command.
[03/31/2021 14:43:27]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Connection status change registered - status=Connected, reason=Connection_Ok.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler for "getMaxMinReport" command.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Set handler to receive 'targetTemperature' updates.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component = 'deviceInformation', properties update is complete.
[03/31/2021 14:43:28]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - { "serialNumber": "SR-123456" } is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat1", { "temperature": 34.2 } in °C.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", { "maxTempSinceLastReboot": 34.2 } in °C is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat2", { "temperature": 25.1 } in °C.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat2", { "maxTempSinceLastReboot": 25.1 } in °C is complete.
[03/31/2021 14:43:29]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - {"workingSet":31412} in KB.

كمشغل في تطبيق Azure IoT Central، يمكنك:

  • عرض تتبع الاستخدام بواسطة مكوني الحرارة في صفحة Overview:

    لقطة شاشة تعرض صفحة نظرة عامة على الجهاز.

  • عرض خصائص الجهاز في صفحة About. تعرض هذه الصفحة الخصائص من معلومات مكون الجهاز ومكونات منظمي الحرارة:

    لقطة شاشة تعرض طريقة عرض خصائص الجهاز.

تخصيص قالب الجهاز

بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي أنشأه IoT Central بصورة تلقائية عندما يتم توصيل جهاز التحكم في درجة الحرارة.

لكي تتم إضافة خاصية السحابة لتخزين اسم العميل المقترن بالجهاز:

  1. في تطبيق IoT Central، انتقل إلى قالب الجهاز Temperature Controller في صفحة Device templates.

  2. في نموذج وحدة تحكم درجة الحرارة ، حدد +Add capability.

  3. أدخل اسم العميلكاسم العرض، وحدد خاصية Cloudكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم اختر ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض أوامر Get Max-Min report في تطبيق IoT Central الخاص بك:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. للحصول على getMaxMinReport (thermostat1)، استبدل Get Max-Min report. ب Get thermostat1 status report.

  3. للحصول على getMaxMinReport (thermostat2)، استبدل Get Max-Min report. ب Get thermostat2 status report.

  4. حدد ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض خصائص درجة الحرارة المستهدفة القابلة للكتابة في تطبيق IoT Central:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. بالنسبة إلى درجة الحرارة المستهدفة (thermostat1) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (1) .

  3. بالنسبة إلى درجة الحرارة المستهدفة (thermostat2) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (2) .

  4. حدد ⁧⁩حفظ⁧⁩.

تشتمل مكونات مكون الحرارة في نموذج وحدة التحكم في درجة الحرارة على خاصية قابلة كتابة درجة الحرارة المستهدفة، ويتضمن نموذج الجهاز خاصية السحابة الإلكترونية اسم العميل. قم بإنشاء عرض يمكن أن يستخدمه المشغل في تحرير هذه الخصائص:

  1. حدد Views ثم حدد التجانب Editing device and cloud data.

  2. أدخل Properties كاسم النموذج.

  3. حدد خصائص درجة الحرارة الهدف (1)ودرجة الحرارة الهدف (2)واسم العميل . ثم حدد إضافة مقطع.

  4. حفظ التغييرات.

لقطة شاشة تعرض طريقة عرض لتحديث قيم الخصائص.

نشر قالب الجهاز

قبل أن يتمكن المشغل من رؤية واستخدام التخصيصات التي أجريتها، يجب عليك نشر قالب الجهاز.

من قالب جهاز Thermostat حدد Publish. في لوحة انشر قالب هذا الجهاز إلى التطبيق اخترPublish.

يمكن لعامل التشغيل الآن استخدام عرض Properties لتحديث قيم الخصائص، واستدعاء أوامر تسمى Get thermostat1 status report وGet thermostat2 status report في صفحة أوامر الجهاز:

  • تحديث قيم الخاصية القابلة للكتابة على صفحة Properties:

    لقطة شاشة تعرض تحديث خصائص الجهاز.

  • استدعاء الأوامر من صفحة Commands. إذا قمت بتشغيل أمر تقرير الحالة، فحدد تاريخ ووقت المعلمة Since قبل تشغيله:

    لقطة شاشة تعرض استدعاء أمر.

    لقطة شاشة تعرض استجابة الأمر.

يُمكنك مشاهدة كيفية استجابة الجهاز للأوامر وتحديثات الخصائص:

[03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Command: Received - component="thermostat2", generating max, min and avg temperature report since 31/03/2021 06:00:00.
[03/31/2021 14:47:00]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Command: component="thermostat2", MaxMinReport since 31/03/2021 06:00:00: maxTemp=36.4, minTemp=36.4, avgTemp=36.4, startTime=31/03/2021 14:46:33, endTime=31/03/2021 14:46:55

...

[03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Received - component="thermostat1", { "targetTemperature": 67°C }.
[03/31/2021 14:46:36]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is InProgress.
[03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Property: Update - component="thermostat1", {"targetTemperature": 67 } in °C is Completed
[03/31/2021 14:46:49]dbug: Microsoft.Azure.Devices.Client.Samples.TemperatureControllerSample[0]
      Telemetry: Sent - component="thermostat1", { "temperature": 67 } in °C.

استعراض التعليمة البرمجية

المتطلبات الأساسية

لإكمال الخطوات الواردة في هذه المقالة، ستحتاج إلى الموارد التالية:

  • آلة تطوير مع Java SE Development Kit 8 أو أحدث. لمزيد من المعلومات، راجع ⁧⁩تثبيت JDK⁧⁩.

  • ⁩Apache Maven 3⁧⁩.

  • نسخة محلية من ⁧⁩Microsoft Azure IoT SDK لـ Java⁧⁩ مستودع GitHub الذي يحتوي على عينة تعليمات برمجية. استخدم هذا الرابط لتنزيل نسخة من المستودع: ⁧⁩Download ZIP⁧⁩. ثم فك الملف إلى موقع مناسب على جهازك المحلي.

مراجعة التعليمة البرمجية

في نسخة Microsoft Azure IoT SDK لـ Java قمت سابقًا بتنزيل، افتح ملف ⁧⁩azure-iot-sdk-java/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample/src/main/java/samples/com/microsoft/azure/sdk/iot/device/TemperatureController.java⁧⁩ في محرر نص.

عند تشغيل النموذج للاتصال بـ IoT Central، فإنها تستخدم خدمة توفير الأجهزة (DPS) لتسجيل الجهاز، وإنشاء سلسلة اتصال. يقوم النموذج باسترداد معلومات اتصال DPS التي يحتاجها من بيئة شريط الأوامر.

طريقة ⁧main⁩:

  • تستعدي ⁧initializeAndProvisionDevice⁩ لتعيين معرف نموذج ⁧dtmi:com:example:TemperatureController;2⁩، استخدم خدمة توفير الأجهزة لتوفير وتسجيل الجهاز، أنشئ المثيل ⁧⁩DeviceClient⁧⁩، وقم بتوصيل تطبيق IoT Central الخاص بك. يستخدم IoT Central معرف النموذج لتحديد أو إنشاء قالب الجهاز لهذا الجهاز. لمعرفة المزيد، راجع تعيين جهاز إلى قالب جهاز.
  • إنشاء معالجات أوامر لأوامر ⁧getMaxMinReport⁩ و⁧reboot⁩.
  • ينشئ معالج تحديث الخاصية للخصائص ⁧targetTemperature⁩ القابلة للكتابة عليها.
  • يرسل القيم الأولية للخصائص في واجهة ⁧⁩معلومات الجهاز⁧⁩، وخصائص ⁧⁩ذاكرة الجهاز⁧⁩ و⁧⁩رقم المسلسل⁧⁩.
  • يبدأ مؤشر ترابط لإرسال قياس درجة الحرارة عن بُعد من منظمين اثنين للحرارة، وتحديث الخاصية ⁧maxTempSinceLastReboot⁩ كل خمسة ثواني.
public static void main(String[] args) throws IOException, URISyntaxException, ProvisioningDeviceClientException, InterruptedException {

  // ...
  
  switch (deviceSecurityType.toLowerCase())
  {
    case "dps":
    {
      if (validateArgsForDpsFlow())
      {
        initializeAndProvisionDevice();
        break;
      }
      throw new IllegalArgumentException("Required environment variables are not set for DPS flow, please recheck your environment.");
    }
    case "connectionstring":
    {
      // ...
    }
    default:
    {
      // ...
    }
  }
  
  deviceClient.subscribeToDeviceMethod(new MethodCallback(), null, new MethodIotHubEventCallback(), null);
  
  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(THERMOSTAT_1, null),
          new Pair<>(new TargetTemperatureUpdateCallback(), THERMOSTAT_1)),
      new AbstractMap.SimpleEntry<Property, Pair<TwinPropertyCallBack, Object>>(
          new Property(THERMOSTAT_2, null),
          new Pair<>(new TargetTemperatureUpdateCallback(), THERMOSTAT_2))
  ).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
  
  deviceClient.subscribeToTwinDesiredProperties(desiredPropertyUpdateCallback);
  
  updateDeviceInformation();
  sendDeviceMemory();
  sendDeviceSerialNumber();
  
  final AtomicBoolean temperatureReset = new AtomicBoolean(true);
  maxTemperature.put(THERMOSTAT_1, 0.0d);
  maxTemperature.put(THERMOSTAT_2, 0.0d);
  
  new Thread(new Runnable() {
    @SneakyThrows({InterruptedException.class, IOException.class})
    @Override
    public void run() {
      while (true) {
        if (temperatureReset.get()) {
          // Generate a random value between 5.0°C and 45.0°C for the current temperature reading for each "Thermostat" component.
          temperature.put(THERMOSTAT_1, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue());
          temperature.put(THERMOSTAT_2, BigDecimal.valueOf(random.nextDouble() * 40 + 5).setScale(1, RoundingMode.HALF_UP).doubleValue());
        }

        sendTemperatureReading(THERMOSTAT_1);
        sendTemperatureReading(THERMOSTAT_2);

        temperatureReset.set(temperature.get(THERMOSTAT_1) == 0 && temperature.get(THERMOSTAT_2) == 0);
        Thread.sleep(5 * 1000);
      }
    }
  }).start();
}

الطريقة ⁧initializeAndProvisionDevice⁩ تظهر كيفية استخدام الجهاز لـ DPS للتسجيل والاتصال بـ IoT Central. تتضمن الحمولة معرف النموذج الذي يستخدمه IoT Central لتعيين جهاز إلى قالب جهاز:

private static void initializeAndProvisionDevice() throws ProvisioningDeviceClientException, IOException, URISyntaxException, InterruptedException {
  SecurityProviderSymmetricKey securityClientSymmetricKey = new SecurityProviderSymmetricKey(deviceSymmetricKey.getBytes(), registrationId);
  ProvisioningDeviceClient provisioningDeviceClient;
  ProvisioningStatus provisioningStatus = new ProvisioningStatus();

  provisioningDeviceClient = ProvisioningDeviceClient.create(globalEndpoint, scopeId, provisioningProtocol, securityClientSymmetricKey);

  AdditionalData additionalData = new AdditionalData();
  additionalData.setProvisioningPayload(com.microsoft.azure.sdk.iot.provisioning.device.plugandplay.PnpHelper.createDpsPayload(MODEL_ID));

  provisioningDeviceClient.registerDevice(new ProvisioningDeviceClientRegistrationCallbackImpl(), provisioningStatus, additionalData);

  while (provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getProvisioningDeviceClientStatus() != ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED)
  {
    if (provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ERROR ||
        provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_DISABLED ||
        provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_FAILED)
    {
      provisioningStatus.exception.printStackTrace();
      System.out.println("Registration error, bailing out");
      break;
    }
    System.out.println("Waiting for Provisioning Service to register");
    Thread.sleep(MAX_TIME_TO_WAIT_FOR_REGISTRATION);
  }

  ClientOptions options = new ClientOptions();
  options.setModelId(MODEL_ID);

  if (provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) {
    System.out.println("IotHUb Uri : " + provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getIothubUri());
    System.out.println("Device ID : " + provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getDeviceId());

    String iotHubUri = provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getIothubUri();
    String deviceId = provisioningStatus.provisioningDeviceClientRegistrationInfoClient.getDeviceId();

    deviceClient = DeviceClient.createFromSecurityProvider(iotHubUri, deviceId, securityClientSymmetricKey, IotHubClientProtocol.MQTT, options);
    deviceClient.open();
  }
}

الطريقة ⁧sendTemperatureTelemetry⁩ تظهر كيفية إرسال الجهاز قياس الحرارة عن بُعد من مكون إلى IoT Central. تستخدم هذه الطريقة الفئة ⁧PnpConvention⁩ لإنشاء الرسالة:

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

    Message message = PnpConvention.createIotHubMessageUtf8(telemetryName, currentTemperature, componentName);
    deviceClient.sendEventAsync(message, new MessageIotHubEventCallback(), message);

    // Add the current temperature entry to the list of temperature readings.
    Map<Date, Double> currentReadings;
    if (temperatureReadings.containsKey(componentName)) {
      currentReadings = temperatureReadings.get(componentName);
    } else {
      currentReadings = new HashMap<>();
    }
    currentReadings.put(new Date(), currentTemperature);
    temperatureReadings.put(componentName, currentReadings);
  }

الطريقة ⁧updateMaxTemperatureSinceLastReboot⁩ ترسل خاصية ⁧maxTempSinceLastReboot⁩ للتحديث من مكون إلى IoT Central. هذه الطريقة تستخدم الفئة ⁧PnpConvention⁩ لإنشاء تصحيح:

private static void updateMaxTemperatureSinceLastReboot(String componentName) throws IOException {
  String propertyName = "maxTempSinceLastReboot";
  double maxTemp = maxTemperature.get(componentName);

  Set<Property> reportedProperty = PnpConvention.createComponentPropertyPatch(propertyName, maxTemp, componentName);
  deviceClient.sendReportedProperties(reportedProperty);
}

تحتوي الفئة ⁧TargetTemperatureUpdateCallback⁩ على الطريقة ⁧TwinPropertyCallBack⁩ لمعالجة تحديثات الخاصية القابلة للكتابة لمكون من IoT Central. هذه الطريقة تستخدم الفئة ⁧PnpConvention⁩ لإنشاء استجابة:

private static class TargetTemperatureUpdateCallback implements TwinPropertyCallBack {

  final String propertyName = "targetTemperature";

  @SneakyThrows({IOException.class, InterruptedException.class})
  @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);

      Set<Property> pendingPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
          propertyName,
          targetTemperature,
          componentName,
          StatusCode.IN_PROGRESS.value,
          property.getVersion().longValue(),
          null);
      deviceClient.sendReportedProperties(pendingPropertyPatch);

      // Update temperature in 2 steps
      double step = (targetTemperature - temperature.get(componentName)) / 2;
      for (int i = 1; i <=2; i++) {
        temperature.put(componentName, BigDecimal.valueOf(temperature.get(componentName) + step).setScale(1, RoundingMode.HALF_UP).doubleValue());
        Thread.sleep(5 * 1000);
      }

      Set<Property> completedPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
          propertyName,
          temperature.get(componentName),
          componentName,
          StatusCode.COMPLETED.value,
          property.getVersion().longValue(),
          "Successfully updated target temperature.");
      deviceClient.sendReportedProperties(completedPropertyPatch);
    } else {
        // ...
    }
  }
}

الفئة ⁧MethodCallback⁩ تحتوي على ⁧call⁩ لمعالجة أوامر المكون المستدعاة من IoT Central:

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

  @SneakyThrows(InterruptedException.class)
  @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 = getCommandRequestValue(jsonRequest, Integer.class);
        Thread.sleep(delay * 1000L);

        temperature.put(THERMOSTAT_1, 0.0d);
        temperature.put(THERMOSTAT_2, 0.0d);

        maxTemperature.put(THERMOSTAT_1, 0.0d);
        maxTemperature.put(THERMOSTAT_2, 0.0d);

        temperatureReadings.clear();
        return new DeviceMethodData(StatusCode.COMPLETED.value, null);

        case getMaxMinReport1:
        case getMaxMinReport2:
          String[] words = methodName.split("\\*");
          String componentName = words[0];

          if (temperatureReadings.containsKey(componentName)) {
            Date since = getCommandRequestValue(jsonRequest, Date.class);

            Map<Date, Double> allReadings = temperatureReadings.get(componentName);
            Map<Date, Double> filteredReadings = allReadings.entrySet().stream()
                .filter(map -> map.getKey().after(since))
                .collect(Collectors.toMap(Entry::getKey, Entry::getValue));

            if (!filteredReadings.isEmpty()) {
              SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
              double maxTemp = Collections.max(filteredReadings.values());
              double minTemp = Collections.min(filteredReadings.values());
              double avgTemp = filteredReadings.values().stream().mapToDouble(Double::doubleValue).average().orElse(Double.NaN);
              String startTime =  sdf.format(Collections.min(filteredReadings.keySet()));
              String endTime =  sdf.format(Collections.max(filteredReadings.keySet()));

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

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

            return new DeviceMethodData(StatusCode.NOT_FOUND.value, null);
          }

          return new DeviceMethodData(StatusCode.NOT_FOUND.value, null);

        default:
            return new DeviceMethodData(StatusCode.NOT_FOUND.value, null);
    }
  }
}

الحصول على معلومات الاتصال

عند تشغيل تطبيق الجهاز العينة لاحقًا في هذا البرنامج التعليمي، تحتاج قيم التكوين التالية:

  • نطاق المعرف: في تطبيق IoT Central، انتقل إلى مجموعات اتصال جهاز الأذونات>. دوّن قيمة نطاق ID.
  • المفتاح الأساسي للمجموعة: في تطبيق IoT Central، انتقل إلى مجموعات > اتصال جهاز الأذونات > SAS-IoT-Devices. دوّن قيمة المفتاح الأساسي لتوقيع الوصول المشترك.

استخدم Azure Cloud Shell لإنشاء مفتاح جهاز من المفتاح الأساسي للمجموعة الذي قمت باسترداده:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

دوّن ملاحظة عن مفتاح الجهاز الذي تم إنشاؤه، يمكنك استخدامها لاحقًا في هذا البرنامج التعليمي.

ملاحظة

لتشغيل هذه العينة، لا تحتاج إلى تسجيل الجهاز مسبقا في تطبيق IoT Central. يستخدم النموذج إمكانية IoT Central لتسجيل الأجهزة تلقائيا عند الاتصال لأول مرة.

في Windows، انتقل إلى المجلد الجذر من Azure IoT SDK لمستودع Java التي قمت تنزيلها.

قم بتشغيل الأمر التالي لإنشاء تطبيق العينة:

mvn install -T 2C -DskipTests

تشغيل التعليمات البرمجية

لتشغيل عينة التطبيق، افتح بيئة سطر الأوامر، وانتقل إلى المجلد ⁧⁩azure-iot-sdk-java/device/iot-device-samples/pnp-device-sample/temperature-controller-device-sample⁧⁩ الذي يحتوي على المجلد ⁧⁩src⁧⁩ مع ⁧⁩TemperatureController.java⁧⁩ عينة الملف.

يُرجى تعيين متغيرات البيئة لتكوين العينة. توضح القصاصة البرمجية التالية كيفية تعيين متغيرات البيئة في موجه أوامر Windows. إذا كنت تستخدم ⁧⁩bash⁧⁩ shell⁧، استبدل الأوامر ⁧set⁩ بالأوامر export⁩:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

تشغيل العينة:

mvn exec:java -Dexec.mainClass="samples.com.microsoft.azure.sdk.iot.device.TemperatureController"

يظهر الإخراج التالي تسجيل الجهاز والاتصال بـ IoT Central. يبدأ النموذج بإرسال القياس عن بُعد:

2021-03-30 15:33:25.138 DEBUG TemperatureController:123 - Initialize the device client.
Waiting for Provisioning Service to register
Waiting for Provisioning Service to register
IotHUb Uri : iotc-60a.....azure-devices.net
Device ID : sample-device-01
2021-03-30 15:33:38.294 DEBUG TemperatureController:247 - Opening the device client.
2021-03-30 15:33:38.307 INFO  ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true
2021-03-30 15:33:38.321 INFO  ExponentialBackoffWithJitter:98 - NOTE: A new instance of ExponentialBackoffWithJitter has been created with the following properties. Retry Count: 2147483647, Min Backoff Interval: 100, Max Backoff Interval: 10000, Max Time Between Retries: 100, Fast Retry Enabled: true
2021-03-30 15:33:38.427 DEBUG MqttIotHubConnection:274 - Opening MQTT connection...
2021-03-30 15:33:38.427 DEBUG Mqtt:123 - Sending MQTT CONNECT packet...
2021-03-30 15:33:44.628 DEBUG Mqtt:126 - Sent MQTT CONNECT packet was acknowledged
2021-03-30 15:33:44.630 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/#
2021-03-30 15:33:44.731 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic devices/sample-device-01/messages/devicebound/# was acknowledged
2021-03-30 15:33:44.733 DEBUG MqttIotHubConnection:279 - MQTT connection opened successfully
2021-03-30 15:33:44.733 DEBUG IotHubTransport:302 - The connection to the IoT Hub has been established
2021-03-30 15:33:44.734 INFO  IotHubTransport:1429 - Updating transport status to new status CONNECTED with reason CONNECTION_OK
2021-03-30 15:33:44.735 DEBUG IotHubTransport:1439 - Invoking connection status callbacks with new status details
2021-03-30 15:33:44.739 DEBUG IotHubTransport:394 - Client connection opened successfully
2021-03-30 15:33:44.740 INFO  DeviceClient:438 - Device client opened successfully
2021-03-30 15:33:44.740 DEBUG TemperatureController:152 - Set handler for "reboot" command.
2021-03-30 15:33:44.742 DEBUG TemperatureController:153 - Set handler for "getMaxMinReport" command.
2021-03-30 15:33:44.774 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [029d30d4-acbd-462d-b155-82d53ce7786c] Message Id [1b2adf93-ba81-41e4-b8c7-7c90c8b0d6a1] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] )
2021-03-30 15:33:44.774 DEBUG TemperatureController:156 - Set handler to receive "targetTemperature" updates.
2021-03-30 15:33:44.775 INFO  IotHubTransport:1344 - Sending message ( Message details: Correlation Id [029d30d4-acbd-462d-b155-82d53ce7786c] Message Id [1b2adf93-ba81-41e4-b8c7-7c90c8b0d6a1] Device Operation Type [DEVICE_OPERATION_METHOD_SUBSCRIBE_REQUEST] )
2021-03-30 15:33:44.779 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/methods/POST/#
2021-03-30 15:33:44.793 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [f2f9ed95-9778-44f2-b9ec-f60c84061251] Message Id [0d5abdb2-6460-414c-a10e-786ee24cacff] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.794 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [417d659a-7324-43fa-84eb-8a3f3d07963c] Message Id [55532cad-8a5a-489f-9aa8-8f0e5bc21541] Request Id [0] Device Operation Type [DEVICE_OPERATION_TWIN_GET_REQUEST] )
2021-03-30 15:33:44.819 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [d46a0d8a-8a18-4014-abeb-768bd9b17ad2] Message Id [780abc81-ce42-4e5f-aa80-e4785883604e] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.881 DEBUG Mqtt:261 - Sent MQTT SUBSCRIBE packet for topic $iothub/methods/POST/# was acknowledged
2021-03-30 15:33:44.882 INFO  IotHubTransport:1344 - Sending message ( Message details: Correlation Id [f2f9ed95-9778-44f2-b9ec-f60c84061251] Message Id [0d5abdb2-6460-414c-a10e-786ee24cacff] Device Operation Type [DEVICE_OPERATION_TWIN_SUBSCRIBE_DESIRED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.882 DEBUG Mqtt:256 - Sending MQTT SUBSCRIBE packet for topic $iothub/twin/res/#
2021-03-30 15:33:44.893 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [a77b1c02-f043-4477-b610-e31a774772c0] Message Id [2e2f6bee-c480-42cf-ac31-194118930846] Request Id [1] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.904 DEBUG TemperatureController:423 - Property: Update - component = "deviceInformation" is COMPLETED.
2021-03-30 15:33:44.915 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [bbb7e3cf-3550-4fdf-90f9-0787740f028a] Message Id [e06ac385-ae0d-46dd-857a-d9725707527a] )
2021-03-30 15:33:44.915 DEBUG TemperatureController:434 - Telemetry: Sent - {"workingSet": 1024.0KiB }
2021-03-30 15:33:44.915 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [6dbef765-cc9a-4e72-980a-2fe5b0cd77e1] Message Id [49bbad33-09bf-417a-9d6e-299ba7b7c562] Request Id [2] Device Operation Type [DEVICE_OPERATION_TWIN_UPDATE_REPORTED_PROPERTIES_REQUEST] )
2021-03-30 15:33:44.916 DEBUG TemperatureController:442 - Property: Update - {"serialNumber": SR-123456} is COMPLETED
2021-03-30 15:33:44.927 INFO  IotHubTransport:489 - Message was queued to be sent later ( Message details: Correlation Id [86787c32-87a5-4c49-9083-c7f2b17446a7] Message Id [0a45fa0c-a467-499d-b214-9bb5995772ba] )
2021-03-30 15:33:44.927 DEBUG TemperatureController:461 - Telemetry: Sent - {"temperature": 5.8°C} with message Id 0a45fa0c-a467-499d-b214-9bb5995772ba.

كمشغل في تطبيق Azure IoT Central، يمكنك:

  • عرض تتبع الاستخدام بواسطة مكوني الحرارة في صفحة Overview:

    لقطة شاشة تعرض صفحة نظرة عامة على الجهاز.

  • عرض خصائص الجهاز في صفحة About. تعرض هذه الصفحة الخصائص من معلومات مكون الجهاز ومكونات منظمي الحرارة:

    لقطة شاشة تعرض طريقة عرض خصائص الجهاز.

تخصيص قالب الجهاز

بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي أنشأه IoT Central بصورة تلقائية عندما يتم توصيل جهاز التحكم في درجة الحرارة.

لكي تتم إضافة خاصية السحابة لتخزين اسم العميل المقترن بالجهاز:

  1. في تطبيق IoT Central، انتقل إلى قالب الجهاز Temperature Controller في صفحة Device templates.

  2. في نموذج وحدة تحكم درجة الحرارة ، حدد +Add capability.

  3. أدخل اسم العميلكاسم العرض، وحدد خاصية Cloudكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم اختر ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض أوامر Get Max-Min report في تطبيق IoT Central الخاص بك:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. للحصول على getMaxMinReport (thermostat1)، استبدل Get Max-Min report. ب Get thermostat1 status report.

  3. للحصول على getMaxMinReport (thermostat2)، استبدل Get Max-Min report. ب Get thermostat2 status report.

  4. حدد ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض خصائص درجة الحرارة المستهدفة القابلة للكتابة في تطبيق IoT Central:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. بالنسبة إلى درجة الحرارة المستهدفة (thermostat1) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (1) .

  3. بالنسبة إلى درجة الحرارة المستهدفة (thermostat2) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (2) .

  4. حدد ⁧⁩حفظ⁧⁩.

تشتمل مكونات مكون الحرارة في نموذج وحدة التحكم في درجة الحرارة على خاصية قابلة كتابة درجة الحرارة المستهدفة، ويتضمن نموذج الجهاز خاصية السحابة الإلكترونية اسم العميل. قم بإنشاء عرض يمكن أن يستخدمه المشغل في تحرير هذه الخصائص:

  1. حدد Views ثم حدد التجانب Editing device and cloud data.

  2. أدخل Properties كاسم النموذج.

  3. حدد خصائص درجة الحرارة الهدف (1)ودرجة الحرارة الهدف (2)واسم العميل . ثم حدد إضافة مقطع.

  4. حفظ التغييرات.

لقطة شاشة تعرض طريقة عرض لتحديث قيم الخصائص.

نشر قالب الجهاز

قبل أن يتمكن المشغل من رؤية واستخدام التخصيصات التي أجريتها، يجب عليك نشر قالب الجهاز.

من قالب جهاز Thermostat حدد Publish. في لوحة انشر قالب هذا الجهاز إلى التطبيق اخترPublish.

يمكن لعامل التشغيل الآن استخدام عرض Properties لتحديث قيم الخصائص، واستدعاء أوامر تسمى Get thermostat1 status report وGet thermostat2 status report في صفحة أوامر الجهاز:

  • تحديث قيم الخاصية القابلة للكتابة على صفحة Properties:

    لقطة شاشة تعرض تحديث خصائص الجهاز.

  • استدعاء الأوامر من صفحة Commands. إذا قمت بتشغيل أمر تقرير الحالة، فحدد تاريخ ووقت المعلمة Since قبل تشغيله:

    لقطة شاشة تعرض استدعاء أمر.

    لقطة شاشة تعرض استجابة الأمر.

يُمكنك مشاهدة كيفية استجابة الجهاز للأوامر وتحديثات الخصائص:

2021-03-30 15:43:57.133 DEBUG TemperatureController:309 - Command: Received - component="thermostat1", generating min, max, avg temperature report since Tue Mar 30 06:00:00 BST 2021
2021-03-30 15:43:57.153 DEBUG TemperatureController:332 - Command: MaxMinReport since Tue Mar 30 06:00:00 BST 2021: "maxTemp": 35.6°C, "minTemp": 35.6°C, "avgTemp": 35.6°C, "startTime": 2021-03-30T15:43:41Z, "endTime": 2021-03-30T15:43:56Z
2021-03-30 15:43:57.394 DEBUG TemperatureController:502 - Command - Response from IoT Hub: command name=null, status=OK_EMPTY


...

2021-03-30 15:48:47.808 DEBUG TemperatureController:372 - Property: Received - component="thermostat2", {"targetTemperature": 67.0°C}.
2021-03-30 15:48:47.837 DEBUG TemperatureController:382 - Property: Update - component="thermostat2", {"targetTemperature": 67.0°C} is IN_PROGRESS

استعراض التعليمة البرمجية

المتطلبات الأساسية

لإكمال الخطوات الواردة في هذه المقالة، ستحتاج إلى الموارد التالية:

  • جهاز تطوير مع ⁧⁩Node.js⁧⁩ تثبيت الإصدار 6 أو الإصدارات اللاحقة. يمكنك استخدام ⁧node --version⁩ في شريط الأوامر للتحقق من الإصدار الخاص بك. تفترض الإرشادات الواردة في هذا البرنامج التعليمي أنك تقوم بتشغيل الأمر ⁧⁩node⁧⁩ في موجه الأوامر Windows. ومع ذلك، يمكنك استخدام Node.js على العديد من أنظمة التشغيل الأخرى.

  • نسخة محلية من ⁧⁩Microsoft Azure IoT SDK Node.js⁧⁩ GitHub المستودع الذي يحتوي على نموذج التعليمات البرمجية. استخدم هذا الرابط لتنزيل نسخة من المستودع: ⁧⁩Download ZIP⁧⁩. ثم فك الملف إلى موقع مناسب على جهازك المحلي.

مراجعة التعليمة البرمجية

في نسخة Microsoft Azure IoT SDK Node.js قمت بتنزيلها مسبقا، افتح ملف azure-iot-sdk-node/device/samples/javascript/pnp_temperature_controller.js في محرر نص.

عند تشغيل النموذج للاتصال بـ IoT Central، فإنها تستخدم خدمة توفير الأجهزة (DPS) لتسجيل الجهاز، وإنشاء سلسلة اتصال. يقوم النموذج باسترداد معلومات اتصال DPS التي يحتاجها من بيئة شريط الأوامر.

main⁩الطريقة:

  • إنشاء ⁧client⁩ كائن ثم تعيين ⁧dtmi:com:example:TemperatureController;2⁩ معرف الطراز قبل أن يفتح الاتصال. يستخدم IoT Central معرف الطراز لتحديد قالب الجهاز لهذا الجهاز أو إنشائه. لمعرفة المزيد، راجع تعيين جهاز إلى قالب جهاز.
  • إنشاء معالجات أوامر لثلاثة أوامر.
  • ابدأ حلقة لكل مكون الحرارة لإرسال القياس عن بعد درجة الحرارة كل 5 ثوان.
  • ابدأ حلقة للمكون الافتراضي لإرسال قياس حجم مجموعة العمل عن بعد كل 6 ثوان.
  • قُم بإرسال⁧maxTempSinceLastReboot⁩ الخاصية لكل مكون الحرارة.
  • إرسال خصائص معلومات الجهاز.
  • إنشاء معالجات خصائص قابل للكتابة للمكونات الثلاثة.
async function main() {
  // ...

  // fromConnectionString must specify a transport, coming from any transport package.
  const client = Client.fromConnectionString(deviceConnectionString, Protocol);
  console.log('Connecting using connection string: ' + deviceConnectionString);
  let resultTwin;

  try {
    // Add the modelId here
    await client.setOptions(modelIdObject);
    await client.open();
    console.log('Enabling the commands on the client');
    client.onDeviceMethod(commandNameGetMaxMinReport1, commandHandler);
    client.onDeviceMethod(commandNameGetMaxMinReport2, commandHandler);
    client.onDeviceMethod(commandNameReboot, commandHandler);

    // Send Telemetry after some interval
    let index1 = 0;
    let index2 = 0;
    let index3 = 0;
    intervalToken1 = setInterval(() => {
      const data = JSON.stringify(thermostat1.updateSensor().getCurrentTemperatureObject());
      sendTelemetry(client, data, index1, thermostat1ComponentName).catch((err) => console.log('error ', err.toString()));
      index1 += 1;
    }, 5000);

    intervalToken2 = setInterval(() => {
      const data = JSON.stringify(thermostat2.updateSensor().getCurrentTemperatureObject());
      sendTelemetry(client, data, index2, thermostat2ComponentName).catch((err) => console.log('error ', err.toString()));
      index2 += 1;
    }, 5500);


    intervalToken3 = setInterval(() => {
      const data = JSON.stringify({ workingset: 1 + (Math.random() * 90) });
      sendTelemetry(client, data, index3, null).catch((err) => console.log('error ', err.toString()));
      index3 += 1;
    }, 6000);

    // attach a standard input exit listener
    exitListener(client);

    try {
      resultTwin = await client.getTwin();
      // Only report readable properties
      const patchRoot = helperCreateReportedPropertiesPatch({ serialNumber: serialNumber }, null);
      const patchThermostat1Info = helperCreateReportedPropertiesPatch({
        maxTempSinceLastReboot: thermostat1.getMaxTemperatureValue(),
      }, thermostat1ComponentName);

      const patchThermostat2Info = helperCreateReportedPropertiesPatch({
        maxTempSinceLastReboot: thermostat2.getMaxTemperatureValue(),
      }, thermostat2ComponentName);

      const patchDeviceInfo = helperCreateReportedPropertiesPatch({
        manufacturer: 'Contoso Device Corporation',
        model: 'Contoso 47-turbo',
        swVersion: '10.89',
        osName: 'Contoso_OS',
        processorArchitecture: 'Contoso_x86',
        processorManufacturer: 'Contoso Industries',
        totalStorage: 65000,
        totalMemory: 640,
      }, deviceInfoComponentName);

      // the below things can only happen once the twin is there
      updateComponentReportedProperties(resultTwin, patchRoot, null);
      updateComponentReportedProperties(resultTwin, patchThermostat1Info, thermostat1ComponentName);
      updateComponentReportedProperties(resultTwin, patchThermostat2Info, thermostat2ComponentName);
      updateComponentReportedProperties(resultTwin, patchDeviceInfo, deviceInfoComponentName);
      desiredPropertyPatchListener(resultTwin, [thermostat1ComponentName, thermostat2ComponentName, deviceInfoComponentName]);
    } catch (err) {
      console.error('could not retrieve twin or report twin properties\n' + err.toString());
    }
  } catch (err) {
    console.error('could not connect Plug and Play client or could not attach interval function for telemetry\n' + err.toString());
  }
}

provisionDevice⁩تظهر الدالة كيفية استخدام الجهاز لـ DPS للتسجيل والاتصال ب IoT Central. تتضمن الحمولة معرف النموذج الذي يستخدمه IoT Central لتعيين جهاز إلى قالب جهاز:

async function provisionDevice(payload) {
  var provSecurityClient = new SymmetricKeySecurityClient(registrationId, symmetricKey);
  var provisioningClient = ProvisioningDeviceClient.create(provisioningHost, idScope, new ProvProtocol(), provSecurityClient);

  if (!!(payload)) {
    provisioningClient.setProvisioningPayload(payload);
  }

  try {
    let result = await provisioningClient.register();
    deviceConnectionString = 'HostName=' + result.assignedHub + ';DeviceId=' + result.deviceId + ';SharedAccessKey=' + symmetricKey;
    console.log('registration succeeded');
    console.log('assigned hub=' + result.assignedHub);
    console.log('deviceId=' + result.deviceId);
    console.log('payload=' + JSON.stringify(result.payload));
  } catch (err) {
    console.error("error registering device: " + err.toString());
  }
}

sendTelemetry⁩توضح الوظيفة كيف يرسل الجهاز القياس عن بعد لدرجة الحرارة إلى IoT Central. للقياس عن بعد من المكونات، فإنه يضيف خاصية تسمى ⁧$.sub⁩ باسم المكون:

async function sendTelemetry(deviceClient, data, index, componentName) {
  if (!!(componentName)) {
    console.log('Sending telemetry message %d from component: %s ', index, componentName);
  } else {
    console.log('Sending telemetry message %d from root interface', index);
  }
  const msg = new Message(data);
  if (!!(componentName)) {
    msg.properties.add(messageSubjectProperty, componentName);
  }
  msg.contentType = 'application/json';
  msg.contentEncoding = 'utf-8';
  await deviceClient.sendEvent(msg);
}

main⁩يستخدم الأسلوب أسلوب مساعد يسمى ⁧helperCreateReportedPropertiesPatch⁩لإنشاء رسائل تحديث الخاصية. يأخذ هذا الأسلوب معلمة اختيارية لتحديد المكون الذي يرسل الخاصية.:

const helperCreateReportedPropertiesPatch = (propertiesToReport, componentName) => {
  let patch;
  if (!!(componentName)) {
    patch = { };
    propertiesToReport.__t = 'c';
    patch[componentName] = propertiesToReport;
  } else {
    patch = { };
    patch = propertiesToReport;
  }
  if (!!(componentName)) {
    console.log('The following properties will be updated for component: ' + componentName);
  } else {
    console.log('The following properties will be updated for root interface.');
  }
  console.log(patch);
  return patch;
};

main⁩يستخدم الأسلوب التالي لمعالجة التحديثات إلى خصائص قابلة للكتابة من IoT Central. لاحظ كيف يبني الأسلوب الاستجابة مع الإصدار ورمز الحالة:

const desiredPropertyPatchListener = (deviceTwin, componentNames) => {
  deviceTwin.on('properties.desired', (delta) => {
    console.log('Received an update for device with value: ' + JSON.stringify(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') {
            console.log('Will update property: ' + propertyName + ' to value: ' + propertyValue + ' of component: ' + componentName);
            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 = { };
        console.log('Will update property: ' + key + ' to value: ' + values + ' for root');
        const propertyContent = { value: values };
        propertyContent.ac = 200;
        propertyContent.ad = 'Successfully executed patch';
        propertyContent.av = version;
        patchForRoot[key] = propertyContent;
        updateComponentReportedProperties(deviceTwin, patchForRoot, null);
      }
    });
  });
};

main⁩يستخدم الأسلوب الطرق التالية لمعالجة الأوامر من IoT Central:

const commandHandler = async (request, response) => {
  helperLogCommandRequest(request);
  switch (request.methodName) {
  case commandNameGetMaxMinReport1: {
    await sendCommandResponse(request, response, 200, thermostat1.getMaxMinReportObject());
    break;
  }
  case commandNameGetMaxMinReport2: {
    await sendCommandResponse(request, response, 200, thermostat2.getMaxMinReportObject());
    break;
  }
  case commandNameReboot: {
    await sendCommandResponse(request, response, 200, 'reboot response');
    break;
  }
  default:
    await sendCommandResponse(request, response, 404, 'unknown method');
    break;
  }
};

const sendCommandResponse = async (request, response, status, payload) => {
  try {
    await response.send(status, payload);
    console.log('Response to method: ' + request.methodName + ' sent successfully.' );
  } catch (err) {
    console.error('An error ocurred when sending a method response:\n' + err.toString());
  }
};

احصل على معلومات الاتصال

عند تشغيل تطبيق الجهاز العينة لاحقًا في هذا البرنامج التعليمي، تحتاج قيم التكوين التالية:

  • نطاق المعرف: في تطبيق IoT Central، انتقل إلى مجموعات اتصال جهاز الأذونات>. دوّن قيمة نطاق ID.
  • المفتاح الأساسي للمجموعة: في تطبيق IoT Central، انتقل إلى مجموعات > اتصال جهاز الأذونات > SAS-IoT-Devices. دوّن قيمة المفتاح الأساسي لتوقيع الوصول المشترك.

استخدم Azure Cloud Shell لإنشاء مفتاح جهاز من المفتاح الأساسي للمجموعة الذي قمت باسترداده:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

دوّن ملاحظة عن مفتاح الجهاز الذي تم إنشاؤه، يمكنك استخدامها لاحقًا في هذا البرنامج التعليمي.

ملاحظة

لتشغيل هذه العينة، لا تحتاج إلى تسجيل الجهاز مسبقا في تطبيق IoT Central. يستخدم النموذج إمكانية IoT Central لتسجيل الأجهزة تلقائيا عند الاتصال لأول مرة.

تشغيل التعليمات البرمجية

لتشغيل نموذج التطبيق، افتح بيئة سطر الأوامر وانتقل إلى المجلد azure-iot-sdk-node/device/samples/javascript الذي يحتوي على ملف عينة pnp_temperature_controller.js .

يُرجى تعيين متغيرات البيئة لتكوين العينة. توضح القصاصة البرمجية التالية كيفية تعيين متغيرات البيئة في موجه أوامر Windows. إذا كنت تستخدم ⁧⁩bash⁧⁩ shell⁧، استبدل الأوامر ⁧set⁩ بالأوامر export⁩:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

قم بتثبيت الحزم المطلوبة:

npm install

تشغيل العينة:

node pnp_temperature_controller.js

يظهر الإخراج التالي تسجيل الجهاز والاتصال بـ IoT Central. ثم ترسل العينة ⁧maxTempSinceLastReboot⁩ الخاصية من مكوني منظم الحرارة قبل أن تبدأ في إرسال القياس عن بعد:

registration succeeded
assigned hub=iotc-....azure-devices.net
deviceId=sample-device-01
payload=undefined
Connecting using connection string: HostName=iotc-....azure-devices.net;DeviceId=sample-device-01;SharedAccessKey=qdv...IpAo=
Enabling the commands on the client
Please enter q or Q to exit sample.
The following properties will be updated for root interface.
{ serialNumber: 'alwinexlepaho8329' }
The following properties will be updated for component: thermostat1
{ thermostat1: { maxTempSinceLastReboot: 1.5902294191855972, __t: 'c' } }
The following properties will be updated for component: thermostat2
{ thermostat2: { maxTempSinceLastReboot: 16.181771928614545, __t: 'c' } }
The following properties will be updated for component: deviceInformation
{ deviceInformation:
   { manufacturer: 'Contoso Device Corporation',
     model: 'Contoso 47-turbo',
     swVersion: '10.89',
     osName: 'Contoso_OS',
     processorArchitecture: 'Contoso_x86',
     processorManufacturer: 'Contoso Industries',
     totalStorage: 65000,
     totalMemory: 640,
     __t: 'c' } }
executed sample
Received an update for device with value: {"$version":1}
Properties have been reported for component: thermostat1
Properties have been reported for component: thermostat2
Properties have been reported for component: deviceInformation
Properties have been reported for root interface.
Sending telemetry message 0 from component: thermostat1 
Sending telemetry message 0 from component: thermostat2 
Sending telemetry message 0 from root interface

كمشغل في تطبيق Azure IoT Central، يمكنك:

  • عرض تتبع الاستخدام بواسطة مكوني الحرارة في صفحة Overview:

    لقطة شاشة تعرض صفحة نظرة عامة على الجهاز.

  • عرض خصائص الجهاز في صفحة About. تعرض هذه الصفحة الخصائص من معلومات مكون الجهاز ومكونات منظمي الحرارة:

    لقطة شاشة تعرض طريقة عرض خصائص الجهاز.

تخصيص قالب الجهاز

بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي أنشأه IoT Central بصورة تلقائية عندما يتم توصيل جهاز التحكم في درجة الحرارة.

لكي تتم إضافة خاصية السحابة لتخزين اسم العميل المقترن بالجهاز:

  1. في تطبيق IoT Central، انتقل إلى قالب الجهاز Temperature Controller في صفحة Device templates.

  2. في نموذج وحدة تحكم درجة الحرارة ، حدد +إضافة إمكانية.

  3. أدخل اسم العميل كاسم العرض، وحدد خاصية السحابةكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم اختر ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض أوامر Get Max-Min report في تطبيق IoT Central الخاص بك:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. للحصول على getMaxMinReport (thermostat1)، استبدل Get Max-Min report. ب Get thermostat1 status report.

  3. للحصول على getMaxMinReport (thermostat2)، استبدل Get Max-Min report. ب Get thermostat2 status report.

  4. حدد ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض خصائص درجة الحرارة المستهدفة القابلة للكتابة في تطبيق IoT Central:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. بالنسبة إلى درجة الحرارة المستهدفة (thermostat1) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (1) .

  3. بالنسبة إلى درجة الحرارة المستهدفة (thermostat2) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (2) .

  4. حدد ⁧⁩حفظ⁧⁩.

تشتمل مكونات مكون الحرارة في نموذج وحدة التحكم في درجة الحرارة على خاصية قابلة كتابة درجة الحرارة المستهدفة، ويتضمن نموذج الجهاز خاصية السحابة الإلكترونية اسم العميل. قم بإنشاء عرض يمكن أن يستخدمه المشغل في تحرير هذه الخصائص:

  1. حدد Views ثم حدد التجانب Editing device and cloud data.

  2. أدخل Properties كاسم النموذج.

  3. حدد خصائص درجة الحرارة الهدف (1)ودرجة الحرارة الهدف (2)واسم العميل . ثم حدد إضافة مقطع.

  4. حفظ التغييرات.

لقطة شاشة تعرض طريقة عرض لتحديث قيم الخصائص.

نشر قالب الجهاز

قبل أن يتمكن المشغل من رؤية واستخدام التخصيصات التي أجريتها، يجب عليك نشر قالب الجهاز.

من قالب جهاز Thermostat حدد Publish. في لوحة انشر قالب هذا الجهاز إلى التطبيق اخترPublish.

يمكن لعامل التشغيل الآن استخدام عرض Properties لتحديث قيم الخصائص، واستدعاء أوامر تسمى Get thermostat1 status report وGet thermostat2 status report في صفحة أوامر الجهاز:

  • تحديث قيم الخاصية القابلة للكتابة على صفحة Properties:

    لقطة شاشة تعرض تحديث خصائص الجهاز.

  • استدعاء الأوامر من صفحة Commands. إذا قمت بتشغيل أمر تقرير الحالة، فحدد تاريخ ووقت المعلمة Since قبل تشغيله:

    لقطة شاشة تعرض استدعاء أمر.

    لقطة شاشة تعرض استجابة أمر.

يمكنك مشاهدة كيفية استجابة الجهاز للأوامر وتحديثات الخصائص. ⁧getMaxMinReport⁩الأمر في ⁧thermostat2⁩ المكون، ⁧reboot⁩الأمر في المكون الافتراضي. targetTemperature تم تعيين الخاصية القابلة للكتابة للمكونthermostat2:

Received command request for command name: thermostat2*getMaxMinReport
The command request payload is:
2021-03-26T06:00:00.000Z
Response to method: thermostat2*getMaxMinReport sent successfully.

...

Received command request for command name: reboot
The command request payload is:
10
Response to method: reboot sent successfully.

...

Received an update for device with value: {"thermostat2":{"targetTemperature":76,"__t":"c"},"$version":2}
Will update property: targetTemperature to value: 76 of component: thermostat2
Properties have been reported for component: thermostat2

استعراض التعليمة البرمجية

المتطلبات الأساسية

لإكمال الخطوات الواردة في هذه المقالة، ستحتاج إلى الموارد التالية:

  • جهاز تطوير مع Python تثبيت الإصدار 3.7 أو الإصدارات اللاحقة. يمكنك تشغيل python --version في شريط الأوامر للتحقق من الإصدار الخاص بك. Python متاح لمجموعة متنوعة من أنظمة التشغيل. تفترض الإرشادات الواردة في هذا البرنامج التعليمي أنك تقوم بتشغيل الأمر ⁧⁩python⁧⁩ في موجه الأوامر Windows.

  • نسخة محلية من Microsoft Azure IoT SDK لـ python مستودع GitHub الذي يحتوي على عينة تعليمات برمجية. استخدم هذا الرابط لتنزيل نسخة من المستودع: Download ZIP. ثم فك الملف إلى موقع مناسب على جهازك المحلي.

استعراض التعليمات البرمجية

في نسخة Microsoft Azure IoT SDK لـ Python التي قمت بتنزيلها مسبقًا، افتح ملف azure-iot-sdk-python / azure-iot-device / sample / pnp / temp_controller_with_thermostats.py في محرر نصوص.

عند تشغيل النموذج للاتصال بـ IoT Central، فإنها تستخدم خدمة توفير الأجهزة (DPS) لتسجيل الجهاز وإنشاء سلسلة اتصال. يقوم النموذج باسترداد معلومات اتصال DPS التي يحتاجها من بيئة شريط الأوامر.

main الوظيفة:

  • يستخدم DPS لتوفير الجهاز. تتضمن معلومات التوفير معرف النموذج. يستخدم IoT Central معرف الطراز لتحديد قالب الجهاز لهذا الجهاز أو إنشائه. لمعرفة المزيد، راجع تعيين جهاز إلى قالب جهاز.
  • إنشاء Device_client كائن ثم تعيين dtmi:com:example:TemperatureController;2 معرف الطراز قبل أن يفتح الاتصال.
  • إرسال قيم الخصائص الأولية إلى IoT Central. ويستخدم pnp_helper لإنشاء بقع.
  • إنشاء المستمعين للأوامر getMaxMinReportوreboot. كل مكون الحرارة له أمر خاص getMaxMinReport به.
  • إنشاء خاصية الإصغاء، للاستماع إلى تحديثات الخاصية القابلة للكتابة.
  • يبدأ حلقة لإرسال قياس درجة الحرارة عن بعد من مكوني منظم الحرارة وقياس مجموعة العمل من المكون الافتراضي كل 8 ثوانٍ.
async def main():
    switch = os.getenv("IOTHUB_DEVICE_SECURITY_TYPE")
    if switch == "DPS":
        provisioning_host = (
            os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            if os.getenv("IOTHUB_DEVICE_DPS_ENDPOINT")
            else "global.azure-devices-provisioning.net"
        )
        id_scope = os.getenv("IOTHUB_DEVICE_DPS_ID_SCOPE")
        registration_id = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_ID")
        symmetric_key = os.getenv("IOTHUB_DEVICE_DPS_DEVICE_KEY")

        registration_result = await provision_device(
            provisioning_host, id_scope, registration_id, symmetric_key, model_id
        )

        if registration_result.status == "assigned":
            print("Device was assigned")
            print(registration_result.registration_state.assigned_hub)
            print(registration_result.registration_state.device_id)
            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,
            )
        else:
            raise RuntimeError(
                "Could not provision device. Aborting Plug and Play device connection."
            )

    elif switch == "connectionString":
        # ...

    # Connect the client.
    await device_client.connect()

    ################################################
    # Update readable properties from various components

    properties_root = pnp_helper.create_reported_properties(serialNumber=serial_number)
    properties_thermostat1 = pnp_helper.create_reported_properties(
        thermostat_1_component_name, maxTempSinceLastReboot=98.34
    )
    properties_thermostat2 = pnp_helper.create_reported_properties(
        thermostat_2_component_name, maxTempSinceLastReboot=48.92
    )
    properties_device_info = pnp_helper.create_reported_properties(
        device_information_component_name,
        swVersion="5.5",
        manufacturer="Contoso Device Corporation",
        model="Contoso 4762B-turbo",
        osName="Mac Os",
        processorArchitecture="x86-64",
        processorManufacturer="Intel",
        totalStorage=1024,
        totalMemory=32,
    )

    property_updates = asyncio.gather(
        device_client.patch_twin_reported_properties(properties_root),
        device_client.patch_twin_reported_properties(properties_thermostat1),
        device_client.patch_twin_reported_properties(properties_thermostat2),
        device_client.patch_twin_reported_properties(properties_device_info),
    )

    ################################################
    # Get all the listeners running
    print("Listening for command requests and property updates")

    global THERMOSTAT_1
    global THERMOSTAT_2
    THERMOSTAT_1 = Thermostat(thermostat_1_component_name, 10)
    THERMOSTAT_2 = Thermostat(thermostat_2_component_name, 10)

    listeners = asyncio.gather(
        execute_command_listener(
            device_client, method_name="reboot", user_command_handler=reboot_handler
        ),
        execute_command_listener(
            device_client,
            thermostat_1_component_name,
            method_name="getMaxMinReport",
            user_command_handler=max_min_handler,
            create_user_response_handler=create_max_min_report_response,
        ),
        execute_command_listener(
            device_client,
            thermostat_2_component_name,
            method_name="getMaxMinReport",
            user_command_handler=max_min_handler,
            create_user_response_handler=create_max_min_report_response,
        ),
        execute_property_listener(device_client),
    )

    ################################################
    # Function to send telemetry every 8 seconds

    async def send_telemetry():
        print("Sending telemetry from various components")

        while True:
            curr_temp_ext = random.randrange(10, 50)
            THERMOSTAT_1.record(curr_temp_ext)

            temperature_msg1 = {"temperature": curr_temp_ext}
            await send_telemetry_from_temp_controller(
                device_client, temperature_msg1, thermostat_1_component_name
            )

            curr_temp_int = random.randrange(10, 50)  # Current temperature in Celsius
            THERMOSTAT_2.record(curr_temp_int)

            temperature_msg2 = {"temperature": curr_temp_int}

            await send_telemetry_from_temp_controller(
                device_client, temperature_msg2, thermostat_2_component_name
            )

            workingset_msg3 = {"workingSet": random.randrange(1, 100)}
            await send_telemetry_from_temp_controller(device_client, workingset_msg3)

    send_telemetry_task = asyncio.ensure_future(send_telemetry())

    # ...

provision_deviceتستخدم الوظيفة DPS لتوفير الجهاز وتسجيله مع IoT Central. تتضمن الدالة معرف نموذج الجهاز، الذي يستخدمه IoT Central لتعيين جهاز إلى قالب جهاز، في حمولة التوفير:

async def provision_device(provisioning_host, id_scope, registration_id, symmetric_key, model_id):
    provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key(
        provisioning_host=provisioning_host,
        registration_id=registration_id,
        id_scope=id_scope,
        symmetric_key=symmetric_key,
    )

    provisioning_device_client.provisioning_payload = {"modelId": model_id}
    return await provisioning_device_client.register()

execute_command_listenerتعالج الوظيفة طلبات الأوامر، تشغيل max_min_handler الوظيفة عندما يتلقى الجهاز getMaxMinReport الأمر لمكونات الحرارة reboot_handler والوظيفة عندما يتلقى الجهاز reboot الأمر. ويستخدم pnp_helper الوحدة النمطية لإنشاء الاستجابة:

async def execute_command_listener(
    device_client,
    component_name=None,
    method_name=None,
    user_command_handler=None,
    create_user_response_handler=None,
):
    while True:
        if component_name and method_name:
            command_name = component_name + "*" + method_name
        elif method_name:
            command_name = method_name
        else:
            command_name = None

        command_request = await device_client.receive_method_request(command_name)
        print("Command request received with payload")
        values = command_request.payload
        print(values)

        if user_command_handler:
            await user_command_handler(values)
        else:
            print("No handler provided to execute")

        (response_status, response_payload) = pnp_helper.create_response_payload_with_status(
            command_request, method_name, create_user_response=create_user_response_handler
        )

        command_response = MethodResponse.create_from_method_request(
            command_request, response_status, response_payload
        )

        try:
            await device_client.send_method_response(command_response)
        except Exception:
            print("responding to the {command} command failed".format(command=method_name))

async def execute_property_listenerيعالج تحديثات الخاصية القابلة للكتابة مثل مكونات targetTemperature الحرارة ويولد استجابة JSON. ويستخدم pnp_helper الوحدة النمطية لإنشاء الاستجابة:

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

        await device_client.patch_twin_reported_properties(properties_dict)

send_telemetry_from_temp_controllerترسل الوظيفة رسائل القياس عن بُعد من مكونات منظم الحرارة إلى IoT Central. ويستخدم pnp_helper الوحدة النمطية لإنشاء الرسائل:

async def send_telemetry_from_temp_controller(device_client, telemetry_msg, component_name=None):
    msg = pnp_helper.create_telemetry(telemetry_msg, component_name)
    await device_client.send_message(msg)
    print("Sent message")
    print(msg)
    await asyncio.sleep(5)

الحصول على معلومات الاتصال

عند تشغيل تطبيق الجهاز العينة لاحقًا في هذا البرنامج التعليمي، تحتاج قيم التكوين التالية:

  • نطاق المعرف: في تطبيق IoT Central، انتقل إلى Permissions > Device connection groups. دوّن قيمة نطاق ID.
  • المفتاح الأساسي للمجموعة: في تطبيق IoT Central، انتقل إلى Permissions > Device connection groups > SAS-IoT-Devices. دوّن قيمة المفتاح الأساسي لتوقيع الوصول المشترك.

استخدم Azure Cloud Shell لإنشاء مفتاح جهاز من المفتاح الأساسي للمجموعة الذي قمت باسترداده:

az extension add --name azure-iot
az iot central device compute-device-key --device-id sample-device-01 --pk <the group primary key value>

دوّن ملاحظة عن مفتاح الجهاز الذي تم إنشاؤه، يمكنك استخدامها لاحقًا في هذا البرنامج التعليمي.

ملاحظة

لتشغيل هذه العينة، لا تحتاج إلى تسجيل الجهاز مسبقا في تطبيق IoT Central. يستخدم النموذج إمكانية IoT Central لتسجيل الأجهزة تلقائيا عند الاتصال لأول مرة.

تشغيل التعليمات البرمجية

لتشغيل التطبيق النموذجي، افتح بيئة سطر أوامر وانتقل إلى المجلد azure-iot-sdk-python / azure-iot-device / sample / pnp الذي يحتوي على ملف نموذج temp_controller_with_thermostats.py.

يُرجى تعيين متغيرات البيئة لتكوين العينة. توضح القصاصة البرمجية التالية كيفية تعيين متغيرات البيئة في موجه أوامر Windows. إذا كنت تستخدم ⁧⁩bash⁧⁩ shell⁧، استبدل الأوامر ⁧set⁩ بالأوامر export⁩:

set IOTHUB_DEVICE_SECURITY_TYPE=DPS
set IOTHUB_DEVICE_DPS_ID_SCOPE=<The ID scope you made a note of previously>
set IOTHUB_DEVICE_DPS_DEVICE_ID=sample-device-01
set IOTHUB_DEVICE_DPS_DEVICE_KEY=<The generated device key you made a note of previously>
set IOTHUB_DEVICE_DPS_ENDPOINT=global.azure-devices-provisioning.net

قم بتثبيت الحزم المطلوبة:

pip install azure-iot-device

تشغيل العينة:

python temp_controller_with_thermostats.py

يظهر الإخراج التالي تسجيل الجهاز والاتصال بـ IoT Central. ترسل العينة maxTempSinceLastReboot الخاصية من مكوني منظم الحرارة قبل أن تبدأ في إرسال القياس عن بُعد:

Device was assigned
iotc-60a.....azure-devices.net
sample-device-01
Updating pnp properties for root interface
{'serialNumber': 'alohomora'}
Updating pnp properties for thermostat1
{'thermostat1': {'maxTempSinceLastReboot': 98.34, '__t': 'c'}}
Updating pnp properties for thermostat2
{'thermostat2': {'maxTempSinceLastReboot': 48.92, '__t': 'c'}}
Updating pnp properties for deviceInformation
{'deviceInformation': {'swVersion': '5.5', 'manufacturer': 'Contoso Device Corporation', 'model': 'Contoso 4762B-turbo', 'osName': 'Mac Os', 'processorArchitecture': 'x86-64', 'processorManufacturer': 'Intel', 'totalStorage': 1024, 'totalMemory': 32, '__t': 'c'}}
Listening for command requests and property updates
Press Q to quit
Sending telemetry from various components
Sent message
{"temperature": 27}
Sent message
{"temperature": 17}
Sent message
{"workingSet": 13}

كمشغل في تطبيق Azure IoT Central، يمكنك:

  • عرض تتبع الاستخدام بواسطة مكوني الحرارة في صفحة Overview:

    لقطة شاشة تعرض صفحة نظرة عامة على الجهاز.

  • عرض خصائص الجهاز في صفحة About. تعرض هذه الصفحة الخصائص من معلومات مكون الجهاز ومكونات منظمي الحرارة:

    لقطة شاشة تعرض طريقة عرض خصائص الجهاز.

تخصيص قالب الجهاز

بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي أنشأه IoT Central بصورة تلقائية عندما يتم توصيل جهاز التحكم في درجة الحرارة.

لكي تتم إضافة خاصية السحابة لتخزين اسم العميل المقترن بالجهاز:

  1. في تطبيق IoT Central، انتقل إلى قالب الجهاز Temperature Controller في صفحة Device templates.

  2. في نموذج وحدة تحكم درجة الحرارة ، حدد +إضافة إمكانية.

  3. أدخل اسم العميل كاسم العرض، وحدد خاصية السحابةكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم اختر ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض أوامر Get Max-Min report في تطبيق IoT Central الخاص بك:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. للحصول على getMaxMinReport (thermostat1)، استبدل Get Max-Min report. ب Get thermostat1 status report.

  3. للحصول على getMaxMinReport (thermostat2)، استبدل Get Max-Min report. ب Get thermostat2 status report.

  4. حدد ⁧⁩حفظ⁧⁩.

لتخصيص كيفية عرض خصائص درجة الحرارة المستهدفة القابلة للكتابة في تطبيق IoT Central:

  1. انتقل إلى قالب جهاز وحدة تحكم درجة الحرارة في صفحة قوالب الجهاز .

  2. بالنسبة إلى درجة الحرارة المستهدفة (thermostat1) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (1) .

  3. بالنسبة إلى درجة الحرارة المستهدفة (thermostat2) ، استبدل درجة الحرارة المستهدفة بـ درجة الحرارة المستهدفة (2) .

  4. حدد ⁧⁩حفظ⁧⁩.

تشتمل مكونات مكون الحرارة في نموذج وحدة التحكم في درجة الحرارة على خاصية قابلة كتابة درجة الحرارة المستهدفة، ويتضمن نموذج الجهاز خاصية السحابة الإلكترونية اسم العميل. قم بإنشاء عرض يمكن أن يستخدمه المشغل في تحرير هذه الخصائص:

  1. حدد Views ثم حدد التجانب Editing device and cloud data.

  2. أدخل Properties كاسم النموذج.

  3. حدد خصائص درجة الحرارة الهدف (1)ودرجة الحرارة الهدف (2)واسم العميل . ثم حدد إضافة مقطع.

  4. حفظ التغييرات.

لقطة شاشة تعرض طريقة عرض لتحديث قيم الخصائص.

نشر قالب الجهاز

قبل أن يتمكن المشغل من رؤية واستخدام التخصيصات التي أجريتها، يجب عليك نشر قالب الجهاز.

من قالب جهاز Thermostat حدد Publish. في لوحة انشر قالب هذا الجهاز إلى التطبيق اخترPublish.

يمكن لعامل التشغيل الآن استخدام عرض Properties لتحديث قيم الخصائص، واستدعاء أوامر تسمى Get thermostat1 status report وGet thermostat2 status report في صفحة أوامر الجهاز:

  • تحديث قيم الخاصية القابلة للكتابة على صفحة Properties:

    لقطة شاشة تعرض تحديث خصائص الجهاز.

  • استدعاء الأوامر من صفحة Commands. إذا قمت بتشغيل أمر تقرير الحالة، فحدد تاريخ ووقت المعلمة Since قبل تشغيله:

    لقطة شاشة تعرض استدعاء أمر.

    لقطة شاشة تعرض استجابة أمر.

يُمكنك مشاهدة كيفية استجابة الجهاز للأوامر وتحديثات الخصائص:

{'thermostat1': {'targetTemperature': 67, '__t': 'c'}, '$version': 2}
the data in the desired properties patch was: {'thermostat1': {'targetTemperature': 67, '__t': 'c'}, '$version': 2}
Values received are :-
{'targetTemperature': 67, '__t': 'c'}
Sent message

...

Command request received with payload
2021-03-31T05:00:00.000Z
Will return the max, min and average temperature from the specified time 2021-03-31T05:00:00.000Z to the current time
Done generating
{"avgTemp": 4.0, "endTime": "2021-03-31T12:29:48.322427", "maxTemp": 18, "minTemp": null, "startTime": "2021-03-31T12:28:28.322381"}

عرض البيانات الأولية

يمكنك استخدام عرض⁧⁩البيانات الأولية⁧⁩لفحص البيانات الأولية التي يرسلها جهازك إلى IoT Central:

لقطة شاشة تعرض طريقة عرض البيانات الأولية.

في طريقة العرض هذه، يمكنك تحديد الأعمدة لعرض نطاق زمني لعرضه وتعيينه. يعرض عمود⁧⁩البيانات غير النموذجية⁧⁩بيانات الجهاز التي لا تتطابق مع أي خاصية أو تعريفات تتبع عن بُعد في قالب الجهاز.

تنظيف الموارد

إذا كنت لا تخطط لإكمال أي تشغيل سريع أو برامج تعليمية أخرى في IoT Central، فإنه يمكنك حذف تطبيق IoT Central الخاص بك:

  1. في تطبيق IoT Central، انتقل إلى إدارة التطبيقات>.
  2. حدد Delete ثم قم بتأكيد الإجراء الخاص بك.

الخطوات التالية

إذا كنت تفضل الاستمرار في مجموعة من الدروس IoT المركزية ومعرفة المزيد عن بناء حل IoT المركزية، فانظر: