إشعار
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تسجيل الدخول أو تغيير الدلائل.
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تغيير الدلائل.
هام
تتضمن هذه المقالة خطوات لتوصيل جهاز باستخدام توقيع وصول مشترك، يسمى أيضا مصادقة المفتاح المتماثل. طريقة المصادقة هذه ملائمة للاختبار والتقييم، ولكن مصادقة جهاز باستخدام شهادات X.509 هي نهج أكثر أمانا. لمعرفة المزيد، راجع أفضل ممارسات الأمان لأمان الاتصال لحلول > IoT.
يوضح لك هذا البرنامج التعليمي كيفية توصيل تطبيق عميل بتطبيق Azure IoT Central. يحاكي التطبيق سلوك جهاز التحكم في درجة الحرارة. عندما يتصل التطبيق ب IoT Central، فإنه يرسل معرف الطراز لطراز جهاز التحكم في درجة الحرارة. يستخدم IoT Central معرف النموذج لاسترداد طراز الجهاز وإنشاء قالب جهاز لك. يمكنك إضافة طرق عرض إلى قالب الجهاز لتمكين المشغل من التفاعل مع جهاز.
في هذا البرنامج التعليمي، تتعلم كيفية:
- قم بإنشاء وتشغيل رمز الجهاز ورؤيته متصلا بتطبيق IoT Central الخاص بك.
- عرض محاكاة بيانات تتبع الاستخدام المرسلة من الجهاز.
- إضافة طرق عرض مخصصة إلى قالب جهاز.
- انشر قالب الجهاز.
- استخدم طريقة عرض لإدارة خصائص الجهاز.
- استدعاء أمر للتحكم في الجهاز.
هام
تتضمن هذه المقالة خطوات لتوصيل جهاز باستخدام توقيع وصول مشترك، يسمى أيضا مصادقة المفتاح المتماثل. طريقة المصادقة هذه ملائمة للاختبار والتقييم، ولكن مصادقة جهاز باستخدام شهادات X.509 هي نهج أكثر أمانا. لمعرفة المزيد، راجع أفضل ممارسات الأمان لأمان الاتصال لحلول > IoT.
المتطلبات الأساسية
لإكمال الخطوات الواردة في هذا البرنامج التعليمي، تحتاج إلى:
اشتراك Azure نشط. في حال لم يكن لديك اشتراك Azure، فأنشئ حساباً مجانيّاً قبل البدء.
تطبيق IoT Central تم إنشاؤه من قالب التطبيق المخصص. لمعرفة المزيد، راجع إنشاء تطبيق IoT Centralوكيف يمكنني الحصول على معلومات حول التطبيق الخاص بي؟.
يمكنك تشغيل هذا البرنامج التعليمي على Linux أو Windows. تتبع أوامر shell في هذا البرنامج التعليمي اصطلاح Linux لفواصل المسار '/' ، إذا كنت تتابع Windows ، فتأكد من تبديل هذه الفواصل ب '\".
تختلف المتطلبات الأساسية حسب نظام التشغيل:
Linux
يفترض هذا البرنامج التعليمي أنك تستخدم Ubuntu Linux. تم اختبار الخطوات الواردة في هذا البرنامج التعليمي باستخدام Ubuntu 18.04.
لإكمال هذا البرنامج التعليمي على Linux، قم بتثبيت البرنامج التالي على بيئة Linux المحلية:
قم بتثبيت GCC و 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
تحقق من أن إصدار GCC cmake أكبر من 2.8.12 وأن إصدار GCC أكبر من 4.4.7.
cmake --version
gcc --version
بالنسبة لنظام التشغيل
لإكمال هذا البرنامج التعليمي على Windows، قم بتثبيت البرنامج التالي على بيئة Windows المحلية:
- Visual Studio (مجتمع أو محترف أو مؤسسي) - تأكد من تضمين تطوير سطح المكتب مع حمل عمل C++ عند تثبيت Visual Studio.
- جيت.
- جماكي.
قم بتنزيل الكود
في هذا البرنامج التعليمي، يمكنك إعداد بيئة تطوير يمكنك استخدامها لاستنساخ Azure IoT Hub Device C SDK وإنشائه.
افتح موجه الأوامر في الدليل الذي تختاره. قم بتنفيذ الأمر التالي لاستنساخ Azure IoT C SDKs ومستودع GitHub للمكتبات في هذا الموقع:
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 في محرر نصوص.
ينفذ العينة نموذج لغة تعريف التوأم الرقمي لوحدة التحكم في درجة الحرارة متعددة المكونات.
عند تشغيل النموذج للاتصال ب IoT Central، فإنه يستخدم خدمة توفير الجهاز (DPS) لتسجيل الجهاز وإنشاء سلسلة اتصال. يسترد النموذج معلومات اتصال DPS التي يحتاجها من بيئة سطر الأوامر.
في pnp_temperature_controller.c، تستدعي mainCreateDeviceClientAndAllocateComponents الدالة أولا إلى:
- قم بتعيين
dtmi:com:example:Thermostat;1معرف الطراز. يستخدم IoT Central معرف النموذج لتحديد قالب الجهاز لهذا الجهاز أو إنشائه. لمعرفة المزيد، راجع تعيين جهاز إلى قالب جهاز. - استخدم DPS لتوفير الجهاز وتسجيله.
- قم بإنشاء مقبض عميل جهاز، واتصل بتطبيق IoT Central.
- ينشئ معالجا للأوامر في مكون وحدة التحكم في درجة الحرارة.
- إنشاء معالج لتحديثات الخصائص في مكون وحدة التحكم في درجة الحرارة.
- يخلق مكوني منظم الحرارة.
الوظيفة main التالية:
- الإبلاغ عن بعض قيم الخصائص الأولية لجميع المكونات.
- بدء حلقة لإرسال بيانات تتبع الاستخدام من جميع المكونات.
ثم تبدأ الدالة main في مؤشر ترابط لإرسال بيانات تتبع الاستخدام بشكل دوري.
int main(void)
{
IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient = NULL;
g_pnpDeviceConfiguration.modelId = g_temperatureControllerModelId;
g_pnpDeviceConfiguration.enableTracing = g_hubClientTraceEnabled;
// First determine the IoT Hub / credentials / device to use.
if (GetConnectionSettingsFromEnvironment(&g_pnpDeviceConfiguration) == false)
{
LogError("Cannot read required environment variable(s)");
}
// Creates the thermostat subcomponents defined by this model. Since everything
// is simulated, this setup stage just creates simulated objects in memory.
else if (AllocateThermostatComponents() == false)
{
LogError("Failure allocating thermostat components");
}
// Create a handle to device client handle. Note that this call may block
// for extended periods of time when using DPS.
else if ((deviceClient = CreateAndConfigureDeviceClientHandleForPnP()) == NULL)
{
LogError("Failure creating IoT Hub device client");
PnP_ThermostatComponent_Destroy(g_thermostatHandle1);
PnP_ThermostatComponent_Destroy(g_thermostatHandle2);
}
else
{
LogInfo("Successfully created device client. Hit Control-C to exit program\n");
int numberOfIterations = 0;
// During startup, send what DTDLv2 calls "read-only properties" to indicate initial device state.
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_SendCurrentTemperature(g_thermostatHandle1, deviceClient);
PnP_ThermostatComponent_SendCurrentTemperature(g_thermostatHandle2, deviceClient);
}
IoTHubDeviceClient_LL_DoWork(deviceClient);
ThreadAPI_Sleep(g_sleepBetweenPollsMs);
numberOfIterations++;
}
// The remainder of the code is used for cleaning up our allocated resources. It won't be executed in this
// sample (because the loop above is infinite and is only broken out of by Control-C of the program), but
// it is included for reference.
// Free the memory allocated to track simulated thermostat.
PnP_ThermostatComponent_Destroy(g_thermostatHandle1);
PnP_ThermostatComponent_Destroy(g_thermostatHandle2);
// Clean up the IoT Hub SDK handle.
IoTHubDeviceClient_LL_Destroy(deviceClient);
// Free all IoT Hub subsystem.
IoTHub_Deinit();
}
return 0;
}
في pnp_thermostat_component.c، توضح الدالة PnP_ThermostatComponent_SendCurrentTemperature كيف يرسل الجهاز بيانات تتبع الاستخدام لدرجة الحرارة من مكون إلى IoT Central:
void PnP_ThermostatComponent_SendCurrentTemperature(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient)
{
PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
IOTHUB_MESSAGE_HANDLE messageHandle = NULL;
IOTHUB_MESSAGE_RESULT messageResult;
IOTHUB_CLIENT_RESULT iothubClientResult;
char temperatureStringBuffer[CURRENT_TEMPERATURE_BUFFER_SIZE];
// Create the telemetry message body to send.
if (snprintf(temperatureStringBuffer, sizeof(temperatureStringBuffer), g_temperatureTelemetryBodyFormat, pnpThermostatComponent->currentTemperature) < 0)
{
LogError("snprintf of current temperature telemetry failed");
}
// Create the message handle and specify its metadata.
else if ((messageHandle = IoTHubMessage_CreateFromString(temperatureStringBuffer)) == NULL)
{
LogError("IoTHubMessage_PnP_CreateFromString failed");
}
else if ((messageResult = IoTHubMessage_SetContentTypeSystemProperty(messageHandle, g_jsonContentType)) != IOTHUB_MESSAGE_OK)
{
LogError("IoTHubMessage_SetContentTypeSystemProperty failed, error=%d", messageResult);
}
else if ((messageResult = IoTHubMessage_SetContentEncodingSystemProperty(messageHandle, g_utf8EncodingType)) != IOTHUB_MESSAGE_OK)
{
LogError("IoTHubMessage_SetContentEncodingSystemProperty failed, error=%d", messageResult);
}
else if ((messageResult = IoTHubMessage_SetComponentName(messageHandle, pnpThermostatComponent->componentName)) != IOTHUB_MESSAGE_OK)
{
LogError("IoTHubMessage_SetContentEncodingSystemProperty failed, error=%d", messageResult);
}
// Send the telemetry message.
else if ((iothubClientResult = IoTHubDeviceClient_LL_SendTelemetryAsync(deviceClient, messageHandle, NULL, NULL)) != IOTHUB_CLIENT_OK)
{
LogError("Unable to send telemetry message, error=%d", iothubClientResult);
}
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 deviceClient)
{
PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
char maximumTemperatureAsString[MAX_TEMPERATURE_SINCE_REBOOT_BUFFER_SIZE];
IOTHUB_CLIENT_RESULT iothubClientResult;
if (snprintf(maximumTemperatureAsString, sizeof(maximumTemperatureAsString), g_maxTempSinceLastRebootPropertyFormat, pnpThermostatComponent->maxTemperature) < 0)
{
LogError("Unable to create max temp since last reboot string for reporting result");
}
else
{
IOTHUB_CLIENT_PROPERTY_REPORTED maxTempProperty;
maxTempProperty.structVersion = IOTHUB_CLIENT_PROPERTY_REPORTED_STRUCT_VERSION_1;
maxTempProperty.name = g_maxTempSinceLastRebootPropertyName;
maxTempProperty.value = maximumTemperatureAsString;
unsigned char* propertySerialized = NULL;
size_t propertySerializedLength;
// The first step of reporting properties is to serialize IOTHUB_CLIENT_PROPERTY_WRITABLE_RESPONSE into JSON for sending.
if ((iothubClientResult = IoTHubClient_Properties_Serializer_CreateReported(&maxTempProperty, 1, pnpThermostatComponent->componentName, &propertySerialized, &propertySerializedLength)) != IOTHUB_CLIENT_OK)
{
LogError("Unable to serialize reported state, error=%d", iothubClientResult);
}
// The output of IoTHubClient_Properties_Serializer_CreateReported is sent to IoTHubDeviceClient_LL_SendPropertiesAsync to perform network I/O.
else if ((iothubClientResult = IoTHubDeviceClient_LL_SendPropertiesAsync(deviceClient, propertySerialized, propertySerializedLength, NULL, NULL)) != IOTHUB_CLIENT_OK)
{
LogError("Unable to send reported state, error=%d", iothubClientResult);
}
else
{
LogInfo("Sending %s property to IoTHub for component %s", g_maxTempSinceLastRebootPropertyName, pnpThermostatComponent->componentName);
}
IoTHubClient_Properties_Serializer_Destroy(propertySerialized);
}
}
في pnp_thermostat_component.c، تتعامل الدالة PnP_ThermostatComponent_ProcessPropertyUpdate مع تحديثات الخصائص القابلة للكتابة من IoT Central:
void PnP_ThermostatComponent_ProcessPropertyUpdate(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, IOTHUB_DEVICE_CLIENT_LL_HANDLE deviceClient, const char* propertyName, const char* 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
{
char* next;
double targetTemperature = strtod(propertyValue, &next);
if ((propertyValue == next) || (targetTemperature == HUGE_VAL) || (targetTemperature == (-1*HUGE_VAL)))
{
LogError("Property %s is not a valid number", propertyValue);
SendTargetTemperatureResponse(pnpThermostatComponent, deviceClient, propertyValue, PNP_STATUS_BAD_FORMAT, version, g_temperaturePropertyResponseDescriptionNotInt);
}
else
{
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, deviceClient, propertyValue, PNP_STATUS_SUCCESS, version, NULL);
if (maxTempUpdated)
{
// If the maximum temperature has been updated, we also report this as a property.
PnP_TempControlComponent_Report_MaxTempSinceLastReboot_Property(pnpThermostatComponent, deviceClient);
}
}
}
}
في pnp_thermostat_component.c، تتعامل الدالة PnP_ThermostatComponent_ProcessCommand مع الأوامر التي يتم استدعاؤها من IoT Central:
void PnP_ThermostatComponent_ProcessCommand(PNP_THERMOSTAT_COMPONENT_HANDLE pnpThermostatComponentHandle, const char *pnpCommandName, JSON_Value* commandJsonValue, IOTHUB_CLIENT_COMMAND_RESPONSE* commandResponse)
{
PNP_THERMOSTAT_COMPONENT* pnpThermostatComponent = (PNP_THERMOSTAT_COMPONENT*)pnpThermostatComponentHandle;
const char* sinceStr;
if (strcmp(pnpCommandName, g_getMaxMinReportCommandName) != 0)
{
LogError("Command %s is not supported on thermostat component", pnpCommandName);
commandResponse->statusCode = 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");
commandResponse->statusCode = PNP_STATUS_BAD_FORMAT;
}
else if (BuildMaxMinCommandResponse(pnpThermostatComponent, commandResponse) == false)
{
LogError("Unable to build response for component %s", pnpThermostatComponent->componentName);
commandResponse->statusCode = PNP_STATUS_INTERNAL_ERROR;
}
else
{
LogInfo("Returning success from command request for component %s", pnpThermostatComponent->componentName);
commandResponse->statusCode = PNP_STATUS_SUCCESS;
}
}
بناء الكود
يمكنك استخدام SDK للجهاز لإنشاء نموذج التعليمات البرمجية المضمنة:
قم بإنشاء دليل فرعي cmake في المجلد الجذر لحزمة تطوير البرامج (SDK) للجهاز، وانتقل إلى هذا المجلد:
cd azure-iot-sdk-c mkdir cmake cd cmakeقم بتشغيل الأوامر التالية لإنشاء SDK والعينات:
cmake -Duse_prov_client=ON -Dhsm_type_symm_key=ON -Drun_e2e_tests=OFF .. cmake --build .
الحصول على معلومات الاتصال
عند تشغيل نموذج تطبيق الجهاز لاحقا في هذا البرنامج التعليمي، تحتاج إلى قيم التكوين التالية:
- نطاق المعرف: في تطبيق IoT Central، انتقل إلى مجموعات اتصال جهاز الأذونات>. قم بتدوين قيمة نطاق المعرف .
- المفتاح الأساسي للمجموعة: في تطبيق 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-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، يمكنك:
اعرض بيانات تتبع الاستخدام المرسلة بواسطة مكوني منظم الحرارة في صفحة نظرة عامة :
عرض خصائص الجهاز في صفحة حول . تعرض هذه الصفحة الخصائص من مكون معلومات الجهاز ومكوني منظم الحرارة:
تخصيص قالب الجهاز
بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي تم إنشاؤه IoT Central تلقائيا عند توصيل جهاز التحكم في درجة الحرارة.
لإضافة خاصية سحابية لتخزين اسم العميل المقترن بالجهاز:
في تطبيق IoT Central، انتقل إلى قالب جهاز وحدة التحكم في درجة الحرارة في صفحة قوالب الجهاز .
في نموذج وحدة التحكم في درجة الحرارة ، حدد +إضافة قدرة.
أدخل اسم العميلكاسم العرض، وحدد خاصية مجموعة النظراءكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم حدد حفظ.
لتخصيص كيفية عرض أوامر تقرير الحصول على Max-Min في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى getMaxMinReport (thermostat1)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat1.
بالنسبة إلى getMaxMinReport (thermostat2)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat2.
حدد حفظ.
لتخصيص كيفية عرض الخصائص القابلة للكتابة في درجة الحرارة المستهدفة في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى targetTemperature (ترموستات 1) ، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (1).
بالنسبة إلى targetTemperature (thermostat2)، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (2).
حدد حفظ.
تتضمن مكونات منظم الحرارة في نموذج وحدة التحكم في درجة الحرارة الخاصية القابلة للكتابة درجة الحرارة المستهدفة ، ويتضمن قالب الجهاز خاصية السحابة اسم العميل . قم بإنشاء طريقة عرض يمكن لمشغل التشغيل استخدامها لتحرير هذه الخصائص:
حدد طرق العرض ثم حدد لوحة تحرير الجهاز وبيانات السحابة .
أدخل الخصائص كاسم النموذج.
حدد خصائص درجة الحرارة المستهدفة (1)ودرجة الحرارة المستهدفة (2)واسم العميل . ثم حدد إضافة قسم.
احفظ تغييراتك.
نشر قالب الجهاز
قبل أن يتمكن المشغل من رؤية التخصيصات التي أجريتها واستخدامها، يجب عليك نشر قالب الجهاز.
من قالب جهاز منظم الحرارة ، حدد نشر. في لوحة نشر قالب هذا الجهاز إلى التطبيق ، حدد نشر.
يمكن للمشغل الآن استخدام طريقة عرض الخصائص لتحديث قيم الخصائص، واستدعاء أوامر المسماة الحصول على تقرير حالة منظم الحرارة 1والحصول على تقرير حالة منظم الحرارة 2 في صفحة أوامر الجهاز:
قم بتحديث قيم الخصائص القابلة للكتابة في صفحة الخصائص :
استدعاء الأوامر من صفحة الأوامر . إذا قمت بتشغيل الأمر تقرير الحالة، فحدد تاريخا ووقتا للمعلمة 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
هام
تتضمن هذه المقالة خطوات لتوصيل جهاز باستخدام توقيع وصول مشترك، يسمى أيضا مصادقة المفتاح المتماثل. طريقة المصادقة هذه ملائمة للاختبار والتقييم، ولكن مصادقة جهاز باستخدام شهادات X.509 هي نهج أكثر أمانا. لمعرفة المزيد، راجع أفضل ممارسات الأمان لأمان الاتصال لحلول > IoT.
المتطلبات الأساسية
لإكمال الخطوات الواردة في هذه المقالة، تحتاج إلى الموارد التالية:
اشتراك Azure نشط. في حال لم يكن لديك اشتراك Azure، فأنشئ حساباً مجانيّاً قبل البدء.
تطبيق IoT Central تم إنشاؤه من قالب التطبيق المخصص. لمعرفة المزيد، راجع إنشاء تطبيق IoT Centralوكيف يمكنني الحصول على معلومات حول التطبيق الخاص بي؟.
جهاز تطوير مع Visual Studio (مجتمع أو محترف أو مؤسسة).
نسخة محلية من مستودع GitHub ل Microsoft Azure IoT SDK ل C# (.NET) الذي يحتوي على نموذج التعليمات البرمجية. استخدم هذا الرابط لتنزيل نسخة من المستودع: تنزيل ZIP. ثم قم بفك ضغط الملف إلى موقع مناسب على جهازك المحلي.
مراجعة الرمز
في نسخة مستودع Microsoft Azure IoT SDK for C# الذي قمت بتنزيله مسبقا، افتح ملف الحل azure-iot-sdk-csharp-main\azureiot.sln في Visual Studio. في "مستكشف الحلول"، قم بتوسيع المجلد PnpDeviceSamples > TemperatureController وافتح الملفات Program.cs و TemperatureControllerSample.cs لعرض التعليمات البرمجية لهذا النموذج.
ينفذ العينة نموذج لغة تعريف التوأم الرقمي لوحدة التحكم في درجة الحرارة متعددة المكونات.
عند تشغيل النموذج للاتصال ب IoT Central، فإنه يستخدم خدمة توفير الجهاز (DPS) لتسجيل الجهاز وإنشاء سلسلة اتصال. يسترد النموذج معلومات اتصال 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 :
- لتعيين معالج لأمر إعادة التشغيل على المكون الافتراضي.
- لتعيين معالجات أوامر 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، انتقل إلى مجموعات اتصال جهاز الأذونات>. قم بتدوين قيمة نطاق المعرف .
- المفتاح الأساسي للمجموعة: في تطبيق 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:
في مستكشف الحلول، حدد ملف مشروع PnpDeviceSamples > TemperatureController .
انتقل إلى > تصحيح أخطاء خصائص > Project TemperatureController. ثم أضف متغيرات البيئة التالية إلى المشروع:
الاسم القيمة IOTHUB_DEVICE_SECURITY_TYPE DPS IOTHUB_DEVICE_DPS_ENDPOINT global.azure-devices-provisioning.net IOTHUB_DEVICE_DPS_ID_SCOPE قيمة نطاق المعرف التي قمت بتدوينها سابقا. IOTHUB_DEVICE_DPS_DEVICE_ID عينة الجهاز 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، يمكنك:
اعرض بيانات تتبع الاستخدام المرسلة بواسطة مكوني منظم الحرارة في صفحة نظرة عامة :
عرض خصائص الجهاز في صفحة حول . تعرض هذه الصفحة الخصائص من مكون معلومات الجهاز ومكوني منظم الحرارة:
تخصيص قالب الجهاز
بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي تم إنشاؤه IoT Central تلقائيا عند توصيل جهاز التحكم في درجة الحرارة.
لإضافة خاصية سحابية لتخزين اسم العميل المقترن بالجهاز:
في تطبيق IoT Central، انتقل إلى قالب جهاز وحدة التحكم في درجة الحرارة في صفحة قوالب الجهاز .
في نموذج وحدة التحكم في درجة الحرارة ، حدد +إضافة قدرة.
أدخل اسم العميلكاسم العرض، وحدد خاصية مجموعة النظراءكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم حدد حفظ.
لتخصيص كيفية عرض أوامر تقرير الحصول على Max-Min في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى getMaxMinReport (thermostat1)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat1.
بالنسبة إلى getMaxMinReport (thermostat2)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat2.
حدد حفظ.
لتخصيص كيفية عرض الخصائص القابلة للكتابة في درجة الحرارة المستهدفة في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى targetTemperature (ترموستات 1) ، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (1).
بالنسبة إلى targetTemperature (thermostat2)، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (2).
حدد حفظ.
تتضمن مكونات منظم الحرارة في نموذج وحدة التحكم في درجة الحرارة الخاصية القابلة للكتابة درجة الحرارة المستهدفة ، ويتضمن قالب الجهاز خاصية السحابة اسم العميل . قم بإنشاء طريقة عرض يمكن لمشغل التشغيل استخدامها لتحرير هذه الخصائص:
حدد طرق العرض ثم حدد لوحة تحرير الجهاز وبيانات السحابة .
أدخل الخصائص كاسم النموذج.
حدد خصائص درجة الحرارة المستهدفة (1)ودرجة الحرارة المستهدفة (2)واسم العميل . ثم حدد إضافة قسم.
احفظ تغييراتك.
نشر قالب الجهاز
قبل أن يتمكن المشغل من رؤية التخصيصات التي أجريتها واستخدامها، يجب عليك نشر قالب الجهاز.
من قالب جهاز منظم الحرارة ، حدد نشر. في لوحة نشر قالب هذا الجهاز إلى التطبيق ، حدد نشر.
يمكن للمشغل الآن استخدام طريقة عرض الخصائص لتحديث قيم الخصائص، واستدعاء أوامر المسماة الحصول على تقرير حالة منظم الحرارة 1والحصول على تقرير حالة منظم الحرارة 2 في صفحة أوامر الجهاز:
قم بتحديث قيم الخصائص القابلة للكتابة في صفحة الخصائص :
استدعاء الأوامر من صفحة الأوامر . إذا قمت بتشغيل الأمر تقرير الحالة، فحدد تاريخا ووقتا للمعلمة 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.
هام
تتضمن هذه المقالة خطوات لتوصيل جهاز باستخدام توقيع وصول مشترك، يسمى أيضا مصادقة المفتاح المتماثل. طريقة المصادقة هذه ملائمة للاختبار والتقييم، ولكن مصادقة جهاز باستخدام شهادات X.509 هي نهج أكثر أمانا. لمعرفة المزيد، راجع أفضل ممارسات الأمان لأمان الاتصال لحلول > IoT.
المتطلبات الأساسية
لإكمال الخطوات الواردة في هذه المقالة، تحتاج إلى الموارد التالية:
اشتراك Azure نشط. في حال لم يكن لديك اشتراك Azure، فأنشئ حساباً مجانيّاً قبل البدء.
تطبيق IoT Central تم إنشاؤه من قالب التطبيق المخصص. لمعرفة المزيد، راجع إنشاء تطبيق IoT Centralوكيف يمكنني الحصول على معلومات حول التطبيق الخاص بي؟.
جهاز تطوير مزود ب Java SE Development Kit 8 أو أحدث. لمزيد من المعلومات، راجع تثبيت JDK.
نسخة محلية من Microsoft Azure IoT SDK لمستودع Java GitHub الذي يحتوي على نموذج التعليمات البرمجية. استخدم هذا الرابط لتنزيل نسخة من المستودع: تنزيل ZIP. ثم قم بفك ضغط الملف إلى موقع مناسب على جهازك المحلي.
مراجعة الرمز
في نسخة Microsoft Azure IoT SDK ل Java التي قمت بتنزيلها مسبقا، افتح ملف azure-iot-sdk-java/iothub/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معرف النموذج، واستخدام DPS لتوفير الجهاز وتسجيله، وإنشاء مثيل DeviceClient ، والاتصال بتطبيق IoT Central. يستخدم IoT Central معرف النموذج لتحديد قالب الجهاز لهذا الجهاز أو إنشائه. لمعرفة المزيد، راجع تعيين جهاز إلى قالب جهاز. - ينشئ معالجات الأوامر للأوامر
getMaxMinReportوالأمرreboot. - إنشاء معالجات تحديث الخصائص للخصائص القابلة
targetTemperatureللكتابة. - يرسل القيم الأولية للخصائص الموجودة في واجهة معلومات الجهاز وخصائص ذاكرة الجهاز والرقم التسلسلي .
- يبدأ مؤشر ترابط لإرسال قياس درجة الحرارة عن بعد من منظمي الحرارة وتحديث الخاصية
maxTempSinceLastRebootكل خمس ثوان.
public static void main(String[] args) throws Exception {
// ...
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.subscribeToMethods(new MethodCallback(), null);
deviceClient.subscribeToDesiredPropertiesAsync(
{
(twin, context) ->
TwinCollection desiredProperties = twin.getDesiredProperties();
for (String desiredPropertyKey : desiredProperties.keySet())
{
TargetTemperatureUpdateCallback.onPropertyChanged(new Property(desiredPropertyKey, desiredProperties.get(desiredPropertyKey)), null);
}
},
null,
(exception, context) ->
{
if (exception == null)
{
log.info("Successfully subscribed to desired properties. Getting initial state");
deviceClient.getTwinAsync(
(twin, getTwinException, getTwinContext) ->
{
log.info("Initial twin state received");
log.info(twin.toString());
},
null);
}
else
{
log.info("Failed to subscribe to desired properties. Error code {}", exception.getStatusCode());
System.exit(-1);
}
},
null);
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 Exception {
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));
ProvisioningDeviceClientRegistrationResult registrationResult = provisioningDeviceClient.registerDeviceSync(additionalData);
ClientOptions options = ClientOptions.builder().modelId(MODEL_ID).build();
if (registrationResult.getProvisioningDeviceClientStatus() == ProvisioningDeviceClientStatus.PROVISIONING_DEVICE_STATUS_ASSIGNED) {
System.out.println("IotHUb Uri : " + registrationResult.getIothubUri());
System.out.println("Device ID : " + registrationResult.getDeviceId());
String iotHubUri = registrationResult.getIothubUri();
String deviceId = registrationResult.getDeviceId();
log.debug("Opening the device client.");
deviceClient = new DeviceClient(iotHubUri, deviceId, securityClientSymmetricKey, IotHubClientProtocol.MQTT, options);
deviceClient.open(true);
}
}
توضح الطريقة 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);
TwinCollection reportedProperty = PnpConvention.createComponentPropertyPatch(propertyName, maxTemp, componentName);
deviceClient.updateReportedPropertiesAsync(reportedProperty, sendReportedPropertiesResponseCallback, null);
log.debug("Property: Update - {\"{}\": {}°C} is {}.", propertyName, maxTemp, StatusCode.COMPLETED);
}
تحتوي الفئة TargetTemperatureUpdateCallback على onPropertyChanged طريقة التعامل مع تحديثات الخصائص القابلة للكتابة لمكون من IoT Central. تستخدم هذه الطريقة الفئة PnpConvention لإنشاء الاستجابة:
private static class TargetTemperatureUpdateCallback
{
final static String propertyName = "targetTemperature";
@SneakyThrows(InterruptedException.class)
public static void onPropertyChanged(Property property, Object context) {
String componentName = (String) context;
if (property.getKey().equalsIgnoreCase(componentName)) {
double targetTemperature = (double) ((TwinCollection) property.getValue()).get(propertyName);
log.debug("Property: Received - component=\"{}\", {\"{}\": {}°C}.", componentName, propertyName, targetTemperature);
TwinCollection pendingPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
propertyName,
targetTemperature,
componentName,
StatusCode.IN_PROGRESS.value,
property.getVersion().longValue(),
null);
deviceClient.updateReportedPropertiesAsync(pendingPropertyPatch, sendReportedPropertiesResponseCallback, null);
log.debug("Property: Update - component=\"{}\", {\"{}\": {}°C} is {}", componentName, propertyName, targetTemperature, StatusCode.IN_PROGRESS);
// 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);
}
TwinCollection completedPropertyPatch = PnpConvention.createComponentWritablePropertyResponse(
propertyName,
temperature.get(componentName),
componentName,
StatusCode.COMPLETED.value,
property.getVersion().longValue(),
"Successfully updated target temperature.");
deviceClient.updateReportedPropertiesAsync(completedPropertyPatch, sendReportedPropertiesResponseCallback, null);
log.debug("Property: Update - {\"{}\": {}°C} is {}", propertyName, temperature.get(componentName), StatusCode.COMPLETED);
} else {
log.debug("Property: Received an unrecognized property update from service.");
}
}
}
تحتوي الفئة MethodCallback على onMethodInvoked طريقة معالجة أوامر المكونات التي يتم استدعاؤها من IoT Central:
private static class MethodCallback implements com.microsoft.azure.sdk.iot.device.twin.MethodCallback
{
final String reboot = "reboot";
final String getMaxMinReport1 = "thermostat1*getMaxMinReport";
final String getMaxMinReport2 = "thermostat2*getMaxMinReport";
@SneakyThrows(InterruptedException.class)
@Override
public DirectMethodResponse onMethodInvoked(String methodName, DirectMethodPayload methodData, Object context) {
String jsonRequest = methodData.getPayload(String.class);
switch (methodName) {
case reboot:
int delay = getCommandRequestValue(jsonRequest, Integer.class);
log.debug("Command: Received - Rebooting thermostat (resetting temperature reading to 0°C after {} seconds).", delay);
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 DirectMethodResponse(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);
log.debug("Command: Received - component=\"{}\", generating min, max, avg temperature report since {}", componentName, since);
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);
log.debug("Command: MaxMinReport since {}: \"maxTemp\": {}°C, \"minTemp\": {}°C, \"avgTemp\": {}°C, \"startTime\": {}, \"endTime\": {}",
since,
maxTemp,
minTemp,
avgTemp,
startTime,
endTime);
return new DirectMethodResponse(StatusCode.COMPLETED.value, responsePayload);
}
log.debug("Command: component=\"{}\", no relevant readings found since {}, cannot generate any report.", componentName, since);
return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
}
log.debug("Command: component=\"{}\", no temperature readings sent yet, cannot generate any report.", componentName);
return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
default:
log.debug("Command: command=\"{}\" is not implemented, no action taken.", methodName);
return new DirectMethodResponse(StatusCode.NOT_FOUND.value, null);
}
}
}
الحصول على معلومات الاتصال
عند تشغيل نموذج تطبيق الجهاز لاحقا في هذا البرنامج التعليمي، تحتاج إلى قيم التكوين التالية:
- نطاق المعرف: في تطبيق IoT Central، انتقل إلى مجموعات اتصال جهاز الأذونات>. قم بتدوين قيمة نطاق المعرف .
- المفتاح الأساسي للمجموعة: في تطبيق 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 for Java الذي قمت بتنزيله.
قم بتشغيل الأمر التالي لإنشاء نموذج التطبيق:
mvn install -T 2C -DskipTests
تشغيل التعليمات البرمجية
لتشغيل نموذج التطبيق، افتح بيئة سطر الأوامر وانتقل إلى المجلد azure-iot-sdk-java/iothub/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 [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] 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 [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] 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 [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] 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 [eeee4444-ff55-6666-77aa-888888bbbbbb] Message Id [ffff5555-aa66-7777-88bb-999999cccccc] 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 [aaaa6666-bb77-8888-99cc-000000dddddd] Message Id [aaaa0000-bb11-2222-33cc-444444dddddd] 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 [cccc2222-dd33-4444-55ee-666666ffffff] Message Id [dddd3333-ee44-5555-66ff-777777aaaaaa] 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 [bbbb1111-cc22-3333-44dd-555555eeeeee] Message Id [cccc2222-dd33-4444-55ee-666666ffffff] 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 [dddd3333-ee44-5555-66ff-777777aaaaaa] Message Id [eeee4444-ff55-6666-77aa-888888bbbbbb] )
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 [ffff5555-aa66-7777-88bb-999999cccccc] Message Id [aaaa6666-bb77-8888-99cc-000000dddddd] 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 [aaaa0000-bb11-2222-33cc-444444dddddd] Message Id [bbbb1111-cc22-3333-44dd-555555eeeeee] )
2021-03-30 15:33:44.927 DEBUG TemperatureController:461 - Telemetry: Sent - {"temperature": 5.8°C} with message Id bbbb1111-cc22-3333-44dd-555555eeeeee.
بصفتك مشغلا في تطبيق Azure IoT Central، يمكنك:
اعرض بيانات تتبع الاستخدام المرسلة بواسطة مكوني منظم الحرارة في صفحة نظرة عامة :
عرض خصائص الجهاز في صفحة حول . تعرض هذه الصفحة الخصائص من مكون معلومات الجهاز ومكوني منظم الحرارة:
تخصيص قالب الجهاز
بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي تم إنشاؤه IoT Central تلقائيا عند توصيل جهاز التحكم في درجة الحرارة.
لإضافة خاصية سحابية لتخزين اسم العميل المقترن بالجهاز:
في تطبيق IoT Central، انتقل إلى قالب جهاز وحدة التحكم في درجة الحرارة في صفحة قوالب الجهاز .
في نموذج وحدة التحكم في درجة الحرارة ، حدد +إضافة قدرة.
أدخل اسم العميلكاسم العرض، وحدد خاصية مجموعة النظراءكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم حدد حفظ.
لتخصيص كيفية عرض أوامر تقرير الحصول على Max-Min في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى getMaxMinReport (thermostat1)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat1.
بالنسبة إلى getMaxMinReport (thermostat2)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat2.
حدد حفظ.
لتخصيص كيفية عرض الخصائص القابلة للكتابة في درجة الحرارة المستهدفة في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى targetTemperature (ترموستات 1) ، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (1).
بالنسبة إلى targetTemperature (thermostat2)، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (2).
حدد حفظ.
تتضمن مكونات منظم الحرارة في نموذج وحدة التحكم في درجة الحرارة الخاصية القابلة للكتابة درجة الحرارة المستهدفة ، ويتضمن قالب الجهاز خاصية السحابة اسم العميل . قم بإنشاء طريقة عرض يمكن لمشغل التشغيل استخدامها لتحرير هذه الخصائص:
حدد طرق العرض ثم حدد لوحة تحرير الجهاز وبيانات السحابة .
أدخل الخصائص كاسم النموذج.
حدد خصائص درجة الحرارة المستهدفة (1)ودرجة الحرارة المستهدفة (2)واسم العميل . ثم حدد إضافة قسم.
احفظ تغييراتك.
نشر قالب الجهاز
قبل أن يتمكن المشغل من رؤية التخصيصات التي أجريتها واستخدامها، يجب عليك نشر قالب الجهاز.
من قالب جهاز منظم الحرارة ، حدد نشر. في لوحة نشر قالب هذا الجهاز إلى التطبيق ، حدد نشر.
يمكن للمشغل الآن استخدام طريقة عرض الخصائص لتحديث قيم الخصائص، واستدعاء أوامر المسماة الحصول على تقرير حالة منظم الحرارة 1والحصول على تقرير حالة منظم الحرارة 2 في صفحة أوامر الجهاز:
قم بتحديث قيم الخصائص القابلة للكتابة في صفحة الخصائص :
استدعاء الأوامر من صفحة الأوامر . إذا قمت بتشغيل الأمر تقرير الحالة، فحدد تاريخا ووقتا للمعلمة 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
هام
تتضمن هذه المقالة خطوات لتوصيل جهاز باستخدام توقيع وصول مشترك، يسمى أيضا مصادقة المفتاح المتماثل. طريقة المصادقة هذه ملائمة للاختبار والتقييم، ولكن مصادقة جهاز باستخدام شهادات X.509 هي نهج أكثر أمانا. لمعرفة المزيد، راجع أفضل ممارسات الأمان لأمان الاتصال لحلول > IoT.
المتطلبات الأساسية
لإكمال الخطوات الواردة في هذه المقالة، تحتاج إلى الموارد التالية:
اشتراك Azure نشط. في حال لم يكن لديك اشتراك Azure، فأنشئ حساباً مجانيّاً قبل البدء.
تطبيق IoT Central تم إنشاؤه من قالب التطبيق المخصص. لمعرفة المزيد، راجع إنشاء تطبيق IoT Centralوكيف يمكنني الحصول على معلومات حول التطبيق الخاص بي؟.
جهاز تطوير مثبت عليه الإصدار 6Node.js أو أحدث. يمكنك التشغيل
node --versionفي سطر الأوامر للتحقق من الإصدار الخاص بك. تفترض الإرشادات الواردة في هذا البرنامج التعليمي أنك تقوم بتشغيل أمر العقدة في موجه أوامر Windows. ومع ذلك ، يمكنك استخدام Node.js على العديد من أنظمة التشغيل الأخرى.نسخة محلية من Microsoft Azure IoT SDK لمستودع GitHub Node.js الذي يحتوي على نموذج التعليمات البرمجية. استخدم هذا الرابط لتنزيل نسخة من المستودع: تنزيل 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 occurred when sending a method response:\n' + err.toString());
}
};
الحصول على معلومات الاتصال
عند تشغيل نموذج تطبيق الجهاز لاحقا في هذا البرنامج التعليمي، تحتاج إلى قيم التكوين التالية:
- نطاق المعرف: في تطبيق IoT Central، انتقل إلى مجموعات اتصال جهاز الأذونات>. قم بتدوين قيمة نطاق المعرف .
- المفتاح الأساسي للمجموعة: في تطبيق 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، يمكنك:
اعرض بيانات تتبع الاستخدام المرسلة بواسطة مكوني منظم الحرارة في صفحة نظرة عامة :
عرض خصائص الجهاز في صفحة حول . تعرض هذه الصفحة الخصائص من مكون معلومات الجهاز ومكوني منظم الحرارة:
تخصيص قالب الجهاز
بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي تم إنشاؤه IoT Central تلقائيا عند توصيل جهاز التحكم في درجة الحرارة.
لإضافة خاصية سحابية لتخزين اسم العميل المقترن بالجهاز:
في تطبيق IoT Central، انتقل إلى قالب جهاز وحدة التحكم في درجة الحرارة في صفحة قوالب الجهاز .
في نموذج وحدة التحكم في درجة الحرارة ، حدد +إضافة قدرة.
أدخل اسم العميلكاسم العرض، وحدد خاصية مجموعة النظراءكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم حدد حفظ.
لتخصيص كيفية عرض أوامر تقرير الحصول على Max-Min في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى getMaxMinReport (thermostat1)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat1.
بالنسبة إلى getMaxMinReport (thermostat2)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat2.
حدد حفظ.
لتخصيص كيفية عرض الخصائص القابلة للكتابة في درجة الحرارة المستهدفة في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى targetTemperature (ترموستات 1) ، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (1).
بالنسبة إلى targetTemperature (thermostat2)، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (2).
حدد حفظ.
تتضمن مكونات منظم الحرارة في نموذج وحدة التحكم في درجة الحرارة الخاصية القابلة للكتابة درجة الحرارة المستهدفة ، ويتضمن قالب الجهاز خاصية السحابة اسم العميل . قم بإنشاء طريقة عرض يمكن لمشغل التشغيل استخدامها لتحرير هذه الخصائص:
حدد طرق العرض ثم حدد لوحة تحرير الجهاز وبيانات السحابة .
أدخل الخصائص كاسم النموذج.
حدد خصائص درجة الحرارة المستهدفة (1)ودرجة الحرارة المستهدفة (2)واسم العميل . ثم حدد إضافة قسم.
احفظ تغييراتك.
نشر قالب الجهاز
قبل أن يتمكن المشغل من رؤية التخصيصات التي أجريتها واستخدامها، يجب عليك نشر قالب الجهاز.
من قالب جهاز منظم الحرارة ، حدد نشر. في لوحة نشر قالب هذا الجهاز إلى التطبيق ، حدد نشر.
يمكن للمشغل الآن استخدام طريقة عرض الخصائص لتحديث قيم الخصائص، واستدعاء أوامر المسماة الحصول على تقرير حالة منظم الحرارة 1والحصول على تقرير حالة منظم الحرارة 2 في صفحة أوامر الجهاز:
قم بتحديث قيم الخصائص القابلة للكتابة في صفحة الخصائص :
استدعاء الأوامر من صفحة الأوامر . إذا قمت بتشغيل الأمر تقرير الحالة، فحدد تاريخا ووقتا للمعلمة 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
هام
تتضمن هذه المقالة خطوات لتوصيل جهاز باستخدام توقيع وصول مشترك، يسمى أيضا مصادقة المفتاح المتماثل. طريقة المصادقة هذه ملائمة للاختبار والتقييم، ولكن مصادقة جهاز باستخدام شهادات X.509 هي نهج أكثر أمانا. لمعرفة المزيد، راجع أفضل ممارسات الأمان لأمان الاتصال لحلول > IoT.
المتطلبات الأساسية
لإكمال الخطوات الواردة في هذه المقالة، تحتاج إلى الموارد التالية:
اشتراك Azure نشط. في حال لم يكن لديك اشتراك Azure، فأنشئ حساباً مجانيّاً قبل البدء.
تطبيق IoT Central تم إنشاؤه من قالب التطبيق المخصص. لمعرفة المزيد، راجع إنشاء تطبيق IoT Centralوكيف يمكنني الحصول على معلومات حول التطبيق الخاص بي؟.
جهاز تطوير مثبت عليه Python . تحقق من Azure IoT Python SDK للحصول على متطلبات إصدار Python الحالية. يمكنك الركض
python --versionفي سطر الأوامر للتحقق من الإصدار الخاص بك. Python متاح لمجموعة متنوعة من أنظمة التشغيل. تفترض الإرشادات الواردة في هذا البرنامج التعليمي أنك تقوم بتشغيل الأمر python في موجه أوامر Windows.نسخة محلية من Microsoft Azure IoT SDK لمستودع Python GitHub الذي يحتوي على نموذج التعليمات البرمجية. استخدم هذا الرابط لتنزيل نسخة من المستودع: تنزيل ZIP. ثم قم بفك ضغط الملف إلى موقع مناسب على جهازك المحلي.
مراجعة الرمز
في نسخة Microsoft Azure IoT SDK for Python التي قمت بتنزيلها مسبقا، افتح ملف azure-iot-sdk-python/samples/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، انتقل إلى مجموعات اتصال جهاز الأذونات>. قم بتدوين قيمة نطاق المعرف .
- المفتاح الأساسي للمجموعة: في تطبيق 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-python-2/samples/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، يمكنك:
اعرض بيانات تتبع الاستخدام المرسلة بواسطة مكوني منظم الحرارة في صفحة نظرة عامة :
عرض خصائص الجهاز في صفحة حول . تعرض هذه الصفحة الخصائص من مكون معلومات الجهاز ومكوني منظم الحرارة:
تخصيص قالب الجهاز
بصفتك مطور حلول، يمكنك تخصيص قالب الجهاز الذي تم إنشاؤه IoT Central تلقائيا عند توصيل جهاز التحكم في درجة الحرارة.
لإضافة خاصية سحابية لتخزين اسم العميل المقترن بالجهاز:
في تطبيق IoT Central، انتقل إلى قالب جهاز وحدة التحكم في درجة الحرارة في صفحة قوالب الجهاز .
في نموذج وحدة التحكم في درجة الحرارة ، حدد +إضافة قدرة.
أدخل اسم العميلكاسم العرض، وحدد خاصية مجموعة النظراءكنوع القدرة، وقم بتوسيع الإدخال واختر سلسلةكمخطط. ثم حدد حفظ.
لتخصيص كيفية عرض أوامر تقرير الحصول على Max-Min في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى getMaxMinReport (thermostat1)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat1.
بالنسبة إلى getMaxMinReport (thermostat2)، استبدل تقرير Get Max-Min. باستخدام تقرير حالة Get thermostat2.
حدد حفظ.
لتخصيص كيفية عرض الخصائص القابلة للكتابة في درجة الحرارة المستهدفة في تطبيق IoT Central:
انتقل إلى قالب جهاز "وحدة التحكم في درجة الحرارة " في صفحة قوالب الجهاز .
بالنسبة إلى targetTemperature (ترموستات 1) ، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (1).
بالنسبة إلى targetTemperature (thermostat2)، استبدل درجة الحرارة المستهدفةبدرجة الحرارة المستهدفة (2).
حدد حفظ.
تتضمن مكونات منظم الحرارة في نموذج وحدة التحكم في درجة الحرارة الخاصية القابلة للكتابة درجة الحرارة المستهدفة ، ويتضمن قالب الجهاز خاصية السحابة اسم العميل . قم بإنشاء طريقة عرض يمكن لمشغل التشغيل استخدامها لتحرير هذه الخصائص:
حدد طرق العرض ثم حدد لوحة تحرير الجهاز وبيانات السحابة .
أدخل الخصائص كاسم النموذج.
حدد خصائص درجة الحرارة المستهدفة (1)ودرجة الحرارة المستهدفة (2)واسم العميل . ثم حدد إضافة قسم.
احفظ تغييراتك.
نشر قالب الجهاز
قبل أن يتمكن المشغل من رؤية التخصيصات التي أجريتها واستخدامها، يجب عليك نشر قالب الجهاز.
من قالب جهاز منظم الحرارة ، حدد نشر. في لوحة نشر قالب هذا الجهاز إلى التطبيق ، حدد نشر.
يمكن للمشغل الآن استخدام طريقة عرض الخصائص لتحديث قيم الخصائص، واستدعاء أوامر المسماة الحصول على تقرير حالة منظم الحرارة 1والحصول على تقرير حالة منظم الحرارة 2 في صفحة أوامر الجهاز:
قم بتحديث قيم الخصائص القابلة للكتابة في صفحة الخصائص :
استدعاء الأوامر من صفحة الأوامر . إذا قمت بتشغيل الأمر تقرير الحالة، فحدد تاريخا ووقتا للمعلمة 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 الخاص بك:
- في تطبيق IoT Central، انتقل إلى إدارة التطبيقات>.
- حدد حذف ثم قم بتأكيد الإجراء.
الخطوات التالية
إذا كنت تفضل المتابعة من خلال مجموعة البرامج التعليمية ل IoT Central ومعرفة المزيد حول إنشاء حل IoT Central، فراجع: