1. مقدمة

OpenThread هو تطبيق مفتوح المصدر لبروتوكول الشبكة Thread®، وقد أطلقته شركة Nest. أصدرت Nest بروتوكول OpenThread لإتاحة التكنولوجيا المستخدَمة في منتجات Nest على نطاق واسع للمطوّرين بهدف تسريع تطوير المنتجات الخاصة بالشبكة المنزلية المتصلة.
يحدّد مواصفات Thread بروتوكول اتصال لاسلكي موثوق وآمن ومنخفض الطاقة بين الأجهزة يستند إلى IPv6 لتطبيقات المنزل. تنفّذ OpenThread جميع طبقات شبكة Thread، بما في ذلك الإصدار السادس من بروتوكول الإنترنت (IPv6) و6LoWPAN وIEEE 802.15.4 مع أمان رمز مصادقة الرسائل (MAC) وإنشاء روابط الشبكة المتداخلة وتوجيه الشبكة المتداخلة.
في هذا الدرس التطبيقي حول الترميز، ستستخدم واجهات برمجة تطبيقات OpenThread لبدء شبكة Thread ورصد التغييرات في أدوار الأجهزة والتفاعل معها وإرسال رسائل UDP، بالإضافة إلى ربط هذه الإجراءات بالأزرار ومؤشرات LED على أجهزة فعلية.

أهداف الدورة التعليمية
- كيفية برمجة الأزرار ومؤشرات LED على لوحات تطوير Nordic nRF52840
- كيفية استخدام واجهات برمجة تطبيقات OpenThread الشائعة وفئة
otInstance - كيفية تتبُّع تغييرات حالة OpenThread والتعامل معها
- كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة Thread
- كيفية تعديل ملفات Makefiles
المتطلبات
الأجهزة:
- 3 لوحات تطوير Nordic Semiconductor nRF52840
- 3 كابلات USB إلى Micro-USB لتوصيل اللوحات
- جهاز Linux مزوّد بـ 3 منافذ USB على الأقل
البرامج:
- GNU Toolchain
- أدوات سطر الأوامر Nordic nRF5x
- برنامج Segger J-Link
- OpenThread
- Git
يخضع محتوى هذا الدرس التطبيقي حول الترميز لترخيص بموجب المشاع الإبداعي مع نسب العمل إلى مؤلفه 3.0، كما تخضع عيّنات التعليمات البرمجية لترخيص بموجب Apache 2.0، ما لم يُذكر خلاف ذلك.
2. الخطوات الأولى
إكمال الدرس التطبيقي حول الترميز الخاص بالأجهزة
قبل البدء في هذا الدرس التطبيقي حول الترميز، عليك إكمال الدرس التطبيقي حول الترميز إنشاء شبكة Thread باستخدام لوحات nRF52840 وOpenThread، الذي يتضمّن ما يلي:
- تفاصيل جميع البرامج التي تحتاج إليها لإنشاء التطبيق وتثبيته
- تعرّفك هذه السلسلة على كيفية إنشاء OpenThread وتثبيته على لوحات Nordic nRF52840
- عرض أساسيات شبكة Thread
لا يتضمّن هذا الدرس التطبيقي حول الترميز أي تفاصيل حول إعداد البيئة المطلوبة لإنشاء OpenThread وتثبيت ذاكرة ROM على اللوحات، بل يتضمّن فقط تعليمات أساسية لتثبيت ذاكرة ROM على اللوحات. من المفترض أنّك أكملت برنامج Build a Thread Network Codelab.
جهاز Linux
تم تصميم هذا الدرس التطبيقي حول الترميز لاستخدام جهاز Linux يستند إلى i386 أو x86 لتثبيت ذاكرة ROM على جميع لوحات تطوير Thread. تم اختبار جميع الخطوات على نظام التشغيل Ubuntu 14.04.5 LTS (Trusty Tahr).
لوحات Nordic Semiconductor nRF52840
يستخدم هذا الدرس التطبيقي ثلاث لوحات nRF52840 PDK.

تثبيت البرامج
لإنشاء OpenThread وتثبيت ذاكرة ROM، عليك تثبيت SEGGER J-Link وأدوات سطر الأوامر nRF5x وARM GNU Toolchain وحِزم Linux المختلفة. إذا أكملت Codelab "إنشاء شبكة Thread" على النحو المطلوب، ستكون قد ثبّت كل ما تحتاج إليه. إذا لم يكن الأمر كذلك، أكمل Codelab قبل المتابعة للتأكّد من إمكانية إنشاء OpenThread وتثبيته على لوحات تطوير nRF52840.
3- إنشاء نسخة طبق الأصل من المستودع
يتضمّن OpenThread مثالاً على الرمز البرمجي للتطبيق يمكنك استخدامه كنقطة بداية لهذا الدرس العملي.
استنسِخ مستودع أمثلة OpenThread Nordic nRF528xx وأنشئ OpenThread:
$ git clone --recursive https://github.com/openthread/ot-nrf528xx $ cd ot-nrf528xx $ ./script/bootstrap
4. أساسيات OpenThread API
تتوفّر واجهات برمجة التطبيقات العامة في OpenThread على ./openthread/include/openthread في مستودع OpenThread. توفّر واجهات برمجة التطبيقات هذه إمكانية الوصول إلى مجموعة متنوعة من ميزات ووظائف OpenThread على مستوى Thread والمنصة لاستخدامها في تطبيقاتك:
- معلومات مثيل OpenThread والتحكّم فيه
- خدمات التطبيقات، مثل IPv6 وUDP وCoAP
- إدارة بيانات اعتماد الشبكة، بالإضافة إلى أدوار "المفوّض" و"المنضم"
- إدارة جهاز توجيه الحدود
- ميزات محسّنة، مثل "إشراف الأهل" و"رصد التشويش"
تتوفّر معلومات مرجعية عن جميع واجهات برمجة التطبيقات OpenThread على openthread.io/reference.
استخدام واجهة برمجة تطبيقات
لاستخدام واجهة برمجة تطبيقات، عليك تضمين ملف العنوان الخاص بها في أحد ملفات تطبيقك. بعد ذلك، استدعِ الدالة المطلوبة.
على سبيل المثال، يستخدم تطبيق CLI النموذجي المضمّن في OpenThread عناوين واجهة برمجة التطبيقات التالية:
./openthread/examples/apps/cli/main.c
#include <openthread/config.h> #include <openthread/cli.h> #include <openthread/diag.h> #include <openthread/tasklet.h> #include <openthread/platform/logging.h>
مثيل OpenThread
بنية otInstance هي بنية ستستخدمها كثيرًا عند العمل باستخدام واجهات برمجة تطبيقات OpenThread. بعد تهيئتها، يمثّل هذا البنية مثيلاً ثابتًا لمكتبة OpenThread ويسمح للمستخدم بإجراء طلبات إلى واجهة برمجة تطبيقات OpenThread.
على سبيل المثال، يتم إعداد مثيل OpenThread في الدالة main() لتطبيق مثال CLI:
./openthread/examples/apps/cli/main.c
int main(int argc, char *argv[])
{
otInstance *instance
...
#if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES
// Call to query the buffer size
(void)otInstanceInit(NULL, &otInstanceBufferLength);
// Call to allocate the buffer
otInstanceBuffer = (uint8_t *)malloc(otInstanceBufferLength);
assert(otInstanceBuffer);
// Initialize OpenThread with the buffer
instance = otInstanceInit(otInstanceBuffer, &otInstanceBufferLength);
#else
instance = otInstanceInitSingle();
#endif
...
return 0;
}
الدوال الخاصة بالمنصة
إذا كنت تريد إضافة وظائف خاصة بنظام التشغيل إلى أحد التطبيقات النموذجية المضمّنة في OpenThread، عليك أولاً تعريفها في العنوان ./openthread/examples/platforms/openthread-system.h، باستخدام مساحة الاسم otSys لجميع الوظائف. بعد ذلك، نفِّذها في ملف مصدر خاص بمنصة معيّنة. بهذه الطريقة، يمكنك استخدام عناوين الدوال نفسها لمنصات الأمثلة الأخرى.
على سبيل المثال، يجب تعريف دوال GPIO التي سنستخدمها للربط بأزرار ومصابيح LED في nRF52840 في openthread-system.h.
افتح ملف ./openthread/examples/platforms/openthread-system.h في محرِّر النصوص الذي تفضّله.
./openthread/examples/platforms/openthread-system.h
الإجراء: أضِف تعريفات دوال GPIO خاصة بالنظام الأساسي.
أضِف تعريفات الدوال التالية بعد #include لعنوان openthread/instance.h:
/** * Init LED module. * */ void otSysLedInit(void); void otSysLedSet(uint8_t aLed, bool aOn); void otSysLedToggle(uint8_t aLed); /** * A callback will be called when GPIO interrupts occur. * */ typedef void (*otSysButtonCallback)(otInstance *aInstance); void otSysButtonInit(otSysButtonCallback aCallback); void otSysButtonProcess(otInstance *aInstance);
سننفّذ هذه الخطوات في الخطوة التالية.
يُرجى العِلم أنّ تعريف الدالة otSysButtonProcess يستخدم otInstance. بهذه الطريقة، يمكن للتطبيق الوصول إلى معلومات حول مثيل OpenThread عند الضغط على زر، إذا لزم الأمر. يعتمد كل ذلك على احتياجات تطبيقك. إذا لم تكن بحاجة إلى ذلك في تنفيذ الدالة، يمكنك استخدام الماكرو OT_UNUSED_VARIABLE من OpenThread API لإيقاف أخطاء الإنشاء المتعلقة بالمتغيرات غير المستخدَمة لبعض سلاسل الأدوات. سنرى أمثلة على ذلك لاحقًا.
5. تنفيذ تجريد النظام الأساسي لوحدة GPIO
في الخطوة السابقة، استعرضنا تعريفات الدوال الخاصة بالنظام الأساسي في ./openthread/examples/platforms/openthread-system.h التي يمكن استخدامها في GPIO. للوصول إلى الأزرار ومؤشرات LED على لوحات تطوير nRF52840، عليك تنفيذ هذه الوظائف لمنصة nRF52840. في هذا الرمز، ستضيف دوالاً تنفّذ ما يلي:
- تهيئة دبابيس GPIO وأوضاعها
- التحكّم في الجهد الكهربائي على أحد الدبابيس
- تفعيل مقاطعات GPIO وتسجيل دالة ردّ الاتصال
في الدليل ./src/src، أنشئ ملفًا جديدًا باسم gpio.c. في هذا الملف الجديد، أضِف المحتوى التالي.
./src/src/gpio.c (new file)
الإجراء: إضافة تعريفات
تعمل هذه التعريفات كعمليات تجريد بين القيم والمتغيرات الخاصة بشريحة nRF52840 والمستخدَمة على مستوى تطبيق OpenThread.
/** * @file * This file implements the system abstraction for GPIO and GPIOTE. * */ #define BUTTON_GPIO_PORT 0x50000300UL #define BUTTON_PIN 11 // button #1 #define GPIO_LOGIC_HI 0 #define GPIO_LOGIC_LOW 1 #define LED_GPIO_PORT 0x50000300UL #define LED_1_PIN 13 // turn on to indicate leader role #define LED_2_PIN 14 // turn on to indicate router role #define LED_3_PIN 15 // turn on to indicate child role #define LED_4_PIN 16 // turn on to indicate UDP receive
لمزيد من المعلومات حول أزرار ومؤشرات LED في nRF52840، يُرجى الاطّلاع على مركز معلومات Nordic Semiconductor.
الإجراء: إضافة ملفات رأس مضمّنة
بعد ذلك، أضِف عمليات تضمين العناوين التي ستحتاج إليها لوظيفة GPIO.
/* Header for the functions defined here */ #include "openthread-system.h" #include <string.h> /* Header to access an OpenThread instance */ #include <openthread/instance.h> /* Headers for lower-level nRF52840 functions */ #include "platform-nrf5.h" #include "hal/nrf_gpio.h" #include "hal/nrf_gpiote.h" #include "nrfx/drivers/include/nrfx_gpiote.h"
الإجراء: أضِف دالتَي رد الاتصال والمقاطعة للزر 1.
أضِف هذا الرمز بعد ذلك. الدالة in_pin1_handler هي دالة ردّ الاتصال التي يتم تسجيلها عند تهيئة وظيفة الضغط على الزر (في وقت لاحق من هذا الملف).
لاحظ كيف تستخدم دالة الاستدعاء هذه الماكرو OT_UNUSED_VARIABLE، لأنّ المتغيّرات التي يتم تمريرها إلى in_pin1_handler لا تُستخدَم فعليًا في الدالة.
/* Declaring callback function for button 1. */
static otSysButtonCallback sButtonHandler;
static bool sButtonPressed;
/**
* @brief Function to receive interrupt and call back function
* set by the application for button 1.
*
*/
static void in_pin1_handler(uint32_t pin, nrf_gpiote_polarity_t action)
{
OT_UNUSED_VARIABLE(pin);
OT_UNUSED_VARIABLE(action);
sButtonPressed = true;
}
الإجراء: أضِف دالة لضبط مصابيح LED.
أضِف هذا الرمز البرمجي لضبط وضع جميع مؤشرات LED وحالتها أثناء عملية التهيئة.
/**
* @brief Function for configuring: PIN_IN pin for input, PIN_OUT pin for output,
* and configures GPIOTE to give an interrupt on pin change.
*/
void otSysLedInit(void)
{
/* Configure GPIO mode: output */
nrf_gpio_cfg_output(LED_1_PIN);
nrf_gpio_cfg_output(LED_2_PIN);
nrf_gpio_cfg_output(LED_3_PIN);
nrf_gpio_cfg_output(LED_4_PIN);
/* Clear all output first */
nrf_gpio_pin_write(LED_1_PIN, GPIO_LOGIC_LOW);
nrf_gpio_pin_write(LED_2_PIN, GPIO_LOGIC_LOW);
nrf_gpio_pin_write(LED_3_PIN, GPIO_LOGIC_LOW);
nrf_gpio_pin_write(LED_4_PIN, GPIO_LOGIC_LOW);
/* Initialize gpiote for button(s) input.
Button event handlers are set in the application (main.c) */
ret_code_t err_code;
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
الإجراء: إضافة دالة لضبط وضع مصباح LED
سيتم استخدام هذه الدالة عند تغيُّر دور الجهاز.
/**
* @brief Function to set the mode of an LED.
*/
void otSysLedSet(uint8_t aLed, bool aOn)
{
switch (aLed)
{
case 1:
nrf_gpio_pin_write(LED_1_PIN, (aOn == GPIO_LOGIC_HI));
break;
case 2:
nrf_gpio_pin_write(LED_2_PIN, (aOn == GPIO_LOGIC_HI));
break;
case 3:
nrf_gpio_pin_write(LED_3_PIN, (aOn == GPIO_LOGIC_HI));
break;
case 4:
nrf_gpio_pin_write(LED_4_PIN, (aOn == GPIO_LOGIC_HI));
break;
}
}
الإجراء: إضافة دالة لتبديل وضع مؤشر LED
سيتم استخدام هذه الوظيفة لتفعيل أو إيقاف LED4 عندما يتلقّى الجهاز رسالة UDP متعددة الإرسال.
/**
* @brief Function to toggle the mode of an LED.
*/
void otSysLedToggle(uint8_t aLed)
{
switch (aLed)
{
case 1:
nrf_gpio_pin_toggle(LED_1_PIN);
break;
case 2:
nrf_gpio_pin_toggle(LED_2_PIN);
break;
case 3:
nrf_gpio_pin_toggle(LED_3_PIN);
break;
case 4:
nrf_gpio_pin_toggle(LED_4_PIN);
break;
}
}
الإجراء: أضِف دوالاً لتهيئة عمليات الضغط على الأزرار ومعالجتها.
تعمل الدالة الأولى على تهيئة اللوحة عند الضغط على أحد الأزرار، بينما ترسل الدالة الثانية رسالة UDP للبث المتعدد عند الضغط على الزر 1.
/**
* @brief Function to initialize the button.
*/
void otSysButtonInit(otSysButtonCallback aCallback)
{
nrfx_gpiote_in_config_t in_config = NRFX_GPIOTE_CONFIG_IN_SENSE_LOTOHI(true);
in_config.pull = NRF_GPIO_PIN_PULLUP;
ret_code_t err_code;
err_code = nrfx_gpiote_in_init(BUTTON_PIN, &in_config, in_pin1_handler);
APP_ERROR_CHECK(err_code);
sButtonHandler = aCallback;
sButtonPressed = false;
nrfx_gpiote_in_event_enable(BUTTON_PIN, true);
}
void otSysButtonProcess(otInstance *aInstance)
{
if (sButtonPressed)
{
sButtonPressed = false;
sButtonHandler(aInstance);
}
}
الإجراء: احفظ gpio.c الملف وأغلقه.
6. واجهة برمجة التطبيقات: التفاعل مع تغييرات دور الجهاز
في تطبيقنا، نريد أن تضيء مصابيح LED مختلفة حسب دور الجهاز. لنتابع الأدوار التالية: "الجهاز الرئيسي" و"جهاز التوجيه" و"الجهاز النهائي". يمكننا تعيينها إلى مصابيح LED على النحو التالي:
- LED1 = القائد
- LED2 = جهاز التوجيه
- LED3 = جهاز نهائي
لتفعيل هذه الوظيفة، يحتاج التطبيق إلى معرفة وقت تغيير دور الجهاز وكيفية تشغيل مؤشر LED الصحيح استجابةً لذلك. سنستخدم مثيل OpenThread للجزء الأول، وتجريد منصة GPIO للجزء الثاني.
افتح ملف ./openthread/examples/apps/cli/main.c في محرِّر النصوص الذي تفضّله.
./openthread/examples/apps/cli/main.c
الإجراء: إضافة ملفات رأس مضمّنة
في قسم "عمليات التضمين" (includes) في ملف main.c، أضِف ملفات عناوين واجهة برمجة التطبيقات التي تحتاج إليها لميزة تغيير الأدوار.
#include <openthread/instance.h> #include <openthread/thread.h> #include <openthread/thread_ftd.h>
الإجراء: أضِف تعريف دالة معالجة لتغيير حالة مثيل OpenThread.
أضِف هذا التعريف إلى main.c، بعد تضمين العناوين وقبل أي عبارات #if. سيتم تحديد هذه الدالة بعد التطبيق الرئيسي.
void handleNetifStateChanged(uint32_t aFlags, void *aContext);
الإجراء: أضِف تسجيل دالة رد الاتصال لدالة معالجة تغيير الحالة.
في main.c، أضِف هذه الدالة إلى الدالة main() بعد استدعاء otAppCliInit. يطلب تسجيل دالة ردّ الاتصال هذا من OpenThread استدعاء الدالة handleNetifStateChange كلما تغيّرت حالة مثيل OpenThread.
/* Register Thread state change handler */ otSetStateChangedCallback(instance, handleNetifStateChanged, instance);
الإجراء: أضِف عملية تنفيذ تغيير الحالة.
في main.c، بعد الدالة main()، نفِّذ الدالة handleNetifStateChanged. تتحقّق هذه الدالة من العلامة OT_CHANGED_THREAD_ROLE لمثيل OpenThread، وإذا تغيّرت، يتم تشغيل/إيقاف مصابيح LED حسب الحاجة.
void handleNetifStateChanged(uint32_t aFlags, void *aContext)
{
if ((aFlags & OT_CHANGED_THREAD_ROLE) != 0)
{
otDeviceRole changedRole = otThreadGetDeviceRole(aContext);
switch (changedRole)
{
case OT_DEVICE_ROLE_LEADER:
otSysLedSet(1, true);
otSysLedSet(2, false);
otSysLedSet(3, false);
break;
case OT_DEVICE_ROLE_ROUTER:
otSysLedSet(1, false);
otSysLedSet(2, true);
otSysLedSet(3, false);
break;
case OT_DEVICE_ROLE_CHILD:
otSysLedSet(1, false);
otSysLedSet(2, false);
otSysLedSet(3, true);
break;
case OT_DEVICE_ROLE_DETACHED:
case OT_DEVICE_ROLE_DISABLED:
/* Clear LED4 if Thread is not enabled. */
otSysLedSet(4, false);
break;
}
}
}
7. واجهة برمجة التطبيقات: استخدام البث المتعدد لتشغيل مصباح LED
في تطبيقنا، نريد أيضًا إرسال رسائل UDP إلى جميع الأجهزة الأخرى في الشبكة عند الضغط على Button1 على لوحة واحدة. لتأكيد استلام الرسالة، سنفعّل مؤشر LED4 على اللوحات الأخرى ردًا على ذلك.
لتفعيل هذه الوظيفة، يجب أن يستوفي التطبيق الشروط التالية:
- تهيئة اتصال UDP عند بدء التشغيل
- إمكانية إرسال رسالة UDP إلى عنوان البث المتعدد المحلي على الشبكة
- معالجة رسائل UDP الواردة
- تبديل LED4 استجابةً لرسائل UDP الواردة
افتح ملف ./openthread/examples/apps/cli/main.c في محرِّر النصوص الذي تفضّله.
./openthread/examples/apps/cli/main.c
الإجراء: إضافة ملفات رأس مضمّنة
في قسم "التضمينات" (includes) أعلى ملف main.c، أضِف ملفات عناوين API التي ستحتاج إليها لميزة UDP للبث المتعدد.
#include <string.h> #include <openthread/message.h> #include <openthread/udp.h> #include "utils/code_utils.h"
يُستخدَم العنوان code_utils.h لوحدتَي الماكرو otEXPECT وotEXPECT_ACTION اللتين تتحقّقان من صحة شروط وقت التشغيل وتعالجان الأخطاء بشكل سليم.
الإجراء: إضافة تعريفات وثوابت:
في الملف main.c، بعد قسم التضمينات وقبل أي عبارات #if، أضِف الثوابت والتعريفات الخاصة ببروتوكول UDP:
#define UDP_PORT 1212 static const char UDP_DEST_ADDR[] = "ff03::1"; static const char UDP_PAYLOAD[] = "Hello OpenThread World!";
ff03::1 هو عنوان البث المتعدد المحلي على الشبكة. سيتم إرسال أي رسائل إلى هذا العنوان إلى جميع أجهزة Full Thread في الشبكة. يمكنك الاطّلاع على البث المتعدد على openthread.io للحصول على مزيد من المعلومات حول إمكانية استخدام البث المتعدد في OpenThread.
الإجراء: أضِف تعريفات الدوال.
في الملف main.c، بعد تعريف otTaskletsSignalPending وقبل الدالة main()، أضِف دوال خاصة ببروتوكول UDP، بالإضافة إلى متغير ثابت لتمثيل مقبس UDP:
static void initUdp(otInstance *aInstance);
static void sendUdp(otInstance *aInstance);
static void handleButtonInterrupt(otInstance *aInstance);
void handleUdpReceive(void *aContext, otMessage *aMessage,
const otMessageInfo *aMessageInfo);
static otUdpSocket sUdpSocket;
الإجراء: أضِف طلبات لتهيئة مصابيح LED وأزرار GPIO.
في main.c، أضِف استدعاءات الدوال هذه إلى الدالة main() بعد استدعاء otSetStateChangedCallback. تعمل هذه الدوال على تهيئة دبابيس GPIO وGPIOTE وتعيين معالج أزرار للتعامل مع أحداث الضغط على الأزرار.
/* init GPIO LEDs and button */ otSysLedInit(); otSysButtonInit(handleButtonInterrupt);
الإجراء: أضِف طلب تهيئة UDP.
في main.c، أضِف هذه الدالة إلى الدالة main() بعد استدعاء otSysButtonInit الذي أضفته للتو:
initUdp(instance);
يضمن هذا الاستدعاء تهيئة مقبس UDP عند بدء تشغيل التطبيق. وبدون ذلك، لا يمكن للجهاز إرسال رسائل UDP أو تلقّيها.
الإجراء: أضِف طلبًا لمعالجة حدث زر GPIO.
في main.c، أضِف استدعاء الدالة هذا إلى الدالة main() بعد استدعاء otSysProcessDrivers، في حلقة while. تتحقّق هذه الدالة، المعرَّفة في gpio.c، مما إذا تم الضغط على الزر، وإذا كان الأمر كذلك، فإنّها تستدعي المعالج (handleButtonInterrupt) الذي تم ضبطه في الخطوة أعلاه.
otSysButtonProcess(instance);
الإجراء: تنفيذ معالج مقاطعة الزر.
في main.c، أضِف تنفيذ الدالة handleButtonInterrupt بعد الدالة handleNetifStateChanged التي أضفتها في الخطوة السابقة:
/**
* Function to handle button push event
*/
void handleButtonInterrupt(otInstance *aInstance)
{
sendUdp(aInstance);
}
الإجراء: تنفيذ عملية تهيئة UDP.
في main.c، أضِف تنفيذ الدالة initUdp بعد الدالة handleButtonInterrupt التي أضفتها للتو:
/**
* Initialize UDP socket
*/
void initUdp(otInstance *aInstance)
{
otSockAddr listenSockAddr;
memset(&sUdpSocket, 0, sizeof(sUdpSocket));
memset(&listenSockAddr, 0, sizeof(listenSockAddr));
listenSockAddr.mPort = UDP_PORT;
otUdpOpen(aInstance, &sUdpSocket, handleUdpReceive, aInstance);
otUdpBind(aInstance, &sUdpSocket, &listenSockAddr, OT_NETIF_THREAD);
}
UDP_PORT هو المنفذ الذي حدّدته سابقًا (1212). تفتح الدالة otUdpOpen المقبس وتسجّل دالة رد اتصال (handleUdpReceive) عند تلقّي رسالة UDP. يربط otUdpBind المقبس بواجهة شبكة Thread من خلال تمرير OT_NETIF_THREAD. للاطّلاع على خيارات أخرى لبطاقة الشبكة، يُرجى الرجوع إلى التعداد otNetifIdentifier في مرجع واجهة برمجة تطبيقات UDP.
الإجراء: تنفيذ ميزة المراسلة عبر بروتوكول UDP
في main.c، أضِف تنفيذ الدالة sendUdp بعد الدالة initUdp التي أضفتها للتو:
/**
* Send a UDP datagram
*/
void sendUdp(otInstance *aInstance)
{
otError error = OT_ERROR_NONE;
otMessage * message;
otMessageInfo messageInfo;
otIp6Address destinationAddr;
memset(&messageInfo, 0, sizeof(messageInfo));
otIp6AddressFromString(UDP_DEST_ADDR, &destinationAddr);
messageInfo.mPeerAddr = destinationAddr;
messageInfo.mPeerPort = UDP_PORT;
message = otUdpNewMessage(aInstance, NULL);
otEXPECT_ACTION(message != NULL, error = OT_ERROR_NO_BUFS);
error = otMessageAppend(message, UDP_PAYLOAD, sizeof(UDP_PAYLOAD));
otEXPECT(error == OT_ERROR_NONE);
error = otUdpSend(aInstance, &sUdpSocket, message, &messageInfo);
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
otMessageFree(message);
}
}
لاحظ وحدتَي الماكرو otEXPECT وotEXPECT_ACTION. تضمن هذه التعليمات أنّ رسالة UDP صالحة وتم تخصيصها بشكل صحيح في المخزن المؤقت، وإذا لم يكن الأمر كذلك، تعالج الدالة الأخطاء بشكل سليم من خلال الانتقال إلى الحظر exit، حيث يتم تحرير المخزن المؤقت.
راجِع مراجع IPv6 وUDP على openthread.io لمزيد من المعلومات حول الدوال المستخدَمة لتهيئة بروتوكول بيانات المستخدم.
الإجراء: تنفيذ معالجة رسائل UDP
في main.c، أضِف تنفيذ الدالة handleUdpReceive بعد الدالة sendUdp التي أضفتها للتو. تعمل هذه الدالة ببساطة على تبديل حالة LED4.
/**
* Function to handle UDP datagrams received on the listening socket
*/
void handleUdpReceive(void *aContext, otMessage *aMessage,
const otMessageInfo *aMessageInfo)
{
OT_UNUSED_VARIABLE(aContext);
OT_UNUSED_VARIABLE(aMessage);
OT_UNUSED_VARIABLE(aMessageInfo);
otSysLedToggle(4);
}
8. واجهة برمجة التطبيقات: ضبط شبكة Thread
لتسهيل عملية العرض التوضيحي، نريد أن تبدأ أجهزتنا شبكة Thread على الفور وأن تنضم إلى شبكة واحدة عند تشغيلها. لإجراء ذلك، سنستخدم بنية otOperationalDataset. يحتوي هذا البنية على جميع المَعلمات اللازمة لنقل بيانات اعتماد شبكة Thread إلى جهاز.
سيؤدي استخدام هذه البنية إلى إلغاء الإعدادات التلقائية للشبكة المضمّنة في OpenThread، وذلك لجعل تطبيقنا أكثر أمانًا وقصر استخدام عقد Thread في شبكتنا على تلك التي تشغّل التطبيق فقط.
افتح ملف ./openthread/examples/apps/cli/main.c مرة أخرى في محرِّر النصوص المفضّل لديك.
./openthread/examples/apps/cli/main.c
الإجراء: إضافة ملف تضمين الرأس
ضمن قسم "عمليات التضمين" في أعلى ملف main.c، أضِف ملف عنوان واجهة برمجة التطبيقات الذي ستحتاج إليه لإعداد شبكة Thread:
#include <openthread/dataset_ftd.h>
الإجراء: أضِف تعريف الدالة لإعداد ضبط الشبكة.
أضِف هذا التعريف إلى main.c، بعد تضمين العناوين وقبل أي عبارات #if. سيتم تحديد هذه الدالة بعد دالة التطبيق الرئيسية.
static void setNetworkConfiguration(otInstance *aInstance);
الإجراء: أضِف طلب إعدادات الشبكة.
في main.c، أضِف استدعاء الدالة هذا إلى الدالة main() بعد استدعاء otSetStateChangedCallback. تضبط هذه الدالة مجموعة بيانات شبكة Thread.
/* Override default network credentials */ setNetworkConfiguration(instance);
الإجراء: أضِف طلبات لتفعيل واجهة ومجموعة بروتوكولات شبكة Thread.
في main.c، أضِف استدعاءات الدوال هذه إلى الدالة main() بعد استدعاء otSysButtonInit.
/* Start the Thread network interface (CLI cmd > ifconfig up) */ otIp6SetEnabled(instance, true); /* Start the Thread stack (CLI cmd > thread start) */ otThreadSetEnabled(instance, true);
الإجراء: تنفيذ إعدادات شبكة Thread
في main.c، أضِف تنفيذ الدالة setNetworkConfiguration بعد الدالة main():
/**
* Override default network settings, such as panid, so the devices can join a
network
*/
void setNetworkConfiguration(otInstance *aInstance)
{
static char aNetworkName[] = "OTCodelab";
otOperationalDataset aDataset;
memset(&aDataset, 0, sizeof(otOperationalDataset));
/*
* Fields that can be configured in otOperationDataset to override defaults:
* Network Name, Mesh Local Prefix, Extended PAN ID, PAN ID, Delay Timer,
* Channel, Channel Mask Page 0, Network Key, PSKc, Security Policy
*/
aDataset.mActiveTimestamp.mSeconds = 1;
aDataset.mActiveTimestamp.mTicks = 0;
aDataset.mActiveTimestamp.mAuthoritative = false;
aDataset.mComponents.mIsActiveTimestampPresent = true;
/* Set Channel to 15 */
aDataset.mChannel = 15;
aDataset.mComponents.mIsChannelPresent = true;
/* Set Pan ID to 2222 */
aDataset.mPanId = (otPanId)0x2222;
aDataset.mComponents.mIsPanIdPresent = true;
/* Set Extended Pan ID to C0DE1AB5C0DE1AB5 */
uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xC0, 0xDE, 0x1A, 0xB5, 0xC0, 0xDE, 0x1A, 0xB5};
memcpy(aDataset.mExtendedPanId.m8, extPanId, sizeof(aDataset.mExtendedPanId));
aDataset.mComponents.mIsExtendedPanIdPresent = true;
/* Set network key to 1234C0DE1AB51234C0DE1AB51234C0DE */
uint8_t key[OT_NETWORK_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE};
memcpy(aDataset.mNetworkKey.m8, key, sizeof(aDataset.mNetworkKey));
aDataset.mComponents.mIsNetworkKeyPresent = true;
/* Set Network Name to OTCodelab */
size_t length = strlen(aNetworkName);
assert(length <= OT_NETWORK_NAME_MAX_SIZE);
memcpy(aDataset.mNetworkName.m8, aNetworkName, length);
aDataset.mComponents.mIsNetworkNamePresent = true;
otDatasetSetActive(aInstance, &aDataset);
/* Set the router selection jitter to override the 2 minute default.
CLI cmd > routerselectionjitter 20
Warning: For demo purposes only - not to be used in a real product */
uint8_t jitterValue = 20;
otThreadSetRouterSelectionJitter(aInstance, jitterValue);
}
كما هو موضّح بالتفصيل في الدالة، فإنّ معلَمات شبكة Thread التي نستخدمها لهذا التطبيق هي:
- القناة = 15
- PAN ID = 0x2222
- رقم تعريف PAN الموسّع = C0DE1AB5C0DE1AB5
- مفتاح الشبكة = 1234C0DE1AB51234C0DE1AB51234C0DE
- Network Name = OTCodelab
بالإضافة إلى ذلك، يتم هنا تقليل تفاوت اختيار جهاز التوجيه، وبالتالي يمكن لأجهزتنا تغيير الأدوار بشكل أسرع لأغراض العرض التوضيحي. يُرجى العِلم أنّه لا يتم ذلك إلا إذا كانت العقدة عبارة عن جهاز FTD (جهاز Thread كامل). سنتحدّث عن ذلك بالتفصيل في الخطوة التالية.
9- واجهة برمجة التطبيقات: الوظائف المحظورة
تعدّل بعض واجهات برمجة التطبيقات في OpenThread الإعدادات التي يجب تعديلها فقط لأغراض العرض التوضيحي أو الاختبار. يجب عدم استخدام واجهات برمجة التطبيقات هذه في عملية نشر تطبيق يستخدم OpenThread في مرحلة الإنتاج.
على سبيل المثال، تعدّل الدالة otThreadSetRouterSelectionJitter الوقت (بالثواني) الذي يستغرقه جهاز طرفي لترقية نفسه إلى جهاز توجيه. القيمة التلقائية هي 120، وفقًا لمواصفات سلسلة المحادثات. لتسهيل الاستخدام في هذا الدرس التطبيقي حول الترميز، سنغيّر القيمة إلى 20، حتى لا تضطر إلى الانتظار طويلاً حتى تغيّر عقدة Thread أدوارها.
ملاحظة: لا تتحوّل أجهزة MTD إلى أجهزة توجيه، ولا يتم تضمين دعم وظيفة مثل otThreadSetRouterSelectionJitter في إصدار MTD. في وقت لاحق، يجب تحديد خيار CMake -DOT_MTD=OFF، وإلا سيحدث خطأ في الإنشاء.
يمكنك التأكّد من ذلك من خلال الاطّلاع على تعريف الدالة otThreadSetRouterSelectionJitter، والذي يتضمّنه توجيه معالج أولي OPENTHREAD_FTD:
./openthread/src/core/api/thread_ftd_api.cpp
#if OPENTHREAD_FTD
#include <openthread/thread_ftd.h>
...
void otThreadSetRouterSelectionJitter(otInstance *aInstance, uint8_t aRouterJitter)
{
Instance &instance = *static_cast<Instance *>(aInstance);
instance.GetThreadNetif().GetMle().SetRouterSelectionJitter(aRouterJitter);
}
...
#endif // OPENTHREAD_FTD
10. تحديثات CMake
قبل إنشاء تطبيقك، يجب إجراء بعض التعديلات البسيطة على ثلاثة ملفات CMake. يستخدم نظام الإنشاء هذه الملفات لتجميع تطبيقك وربطه.
./third_party/NordicSemiconductor/CMakeLists.txt
الآن، أضِف بعض العلامات إلى NordicSemiconductor CMakeLists.txt، للتأكّد من تحديد وظائف GPIO في التطبيق.
الإجراء: أضِف أعلامًا إلى CMakeLists.txt الملف.
افتح ملف ./third_party/NordicSemiconductor/CMakeLists.txt في محرِّر النصوص الذي تفضّله، وأضِف الأسطر التالية في قسم COMMON_FLAG.
...
set(COMMON_FLAG
-DSPIS_ENABLED=1
-DSPIS0_ENABLED=1
-DNRFX_SPIS_ENABLED=1
-DNRFX_SPIS0_ENABLED=1
...
# Defined in ./third_party/NordicSemiconductor/nrfx/templates/nRF52840/nrfx_config.h
-DGPIOTE_ENABLED=1
-DGPIOTE_CONFIG_IRQ_PRIORITY=7
-DGPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS=1
)
...
./src/CMakeLists.txt
عدِّل الملف ./src/CMakeLists.txt لإضافة ملف المصدر الجديد gpio.c:
الإجراء: أضِف مصدر gpio إلى ./src/CMakeLists.txt الملف.
افتح ./src/CMakeLists.txt في محرِّر النصوص الذي تفضّله، وأضِف الملف إلى القسم NRF_COMM_SOURCES.
... set(NRF_COMM_SOURCES ... src/gpio.c ... ) ...
./third_party/NordicSemiconductor/CMakeLists.txt
أخيرًا، أضِف ملف برنامج التشغيل nrfx_gpiote.c إلى ملف NordicSemiconductor CMakeLists.txt، ليتم تضمينه في إصدار مكتبة برامج التشغيل Nordic.
الإجراء: أضِف برنامج تشغيل gpio إلى ملف NordicSemiconductor CMakeLists.txt.
افتح ./third_party/NordicSemiconductor/CMakeLists.txt في محرِّر النصوص الذي تفضّله، وأضِف الملف إلى القسم COMMON_SOURCES.
... set(COMMON_SOURCES ... nrfx/drivers/src/nrfx_gpiote.c ... ) ...
11. إعداد الأجهزة
بعد الانتهاء من جميع تعديلات الرموز البرمجية، يمكنك إنشاء التطبيق وتثبيت ذاكرة ROM على جميع لوحات التطوير الثلاث من Nordic nRF52840. سيعمل كل جهاز كجهاز Thread كامل (FTD).
إنشاء OpenThread
أنشئ ملفات OpenThread FTD الثنائية لمنصة nRF52840.
$ cd ~/ot-nrf528xx $ ./script/build nrf52840 UART_trans -DOT_MTD=OFF -DOT_APP_RCP=OFF -DOT_RCP=OFF
انتقِل إلى الدليل الذي يحتوي على برنامج OpenThread FTD CLI الثنائي، وحوِّله إلى تنسيق سداسي عشري باستخدام ARM Embedded Toolchain:
$ cd build/bin $ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex
تثبيت ذاكرة ROM على اللوحات
نفِّذ عملية التثبيت السريع لملف ot-cli-ftd.hex على كل لوحة nRF52840.
وصِّل كابل USB بمنفذ تصحيح الأخطاء Micro-USB بجانب دبوس الطاقة الخارجي على لوحة nRF52840، ثم وصِّله بجهاز Linux. إذا تم ضبطه بشكل صحيح، سيضيء LED5.

كما في السابق، سجِّل الرقم التسلسلي للوحة nRF52840:

انتقِل إلى موقع "أدوات سطر الأوامر" nRFx، ومرِّر ملف hex الخاص بأداة سطر الأوامر OpenThread FTD إلى لوحة nRF52840 باستخدام الرقم التسلسلي للوحة:
$ cd ~/nrfjprog
$ ./nrfjprog -f nrf52 -s 683704924 --verify --chiperase --program \
~/openthread/output/nrf52840/bin/ot-cli-ftd.hex --reset
سيتم إيقاف مؤشر LED5 لفترة وجيزة أثناء الوميض. يتم إنشاء النتيجة التالية عند النجاح:
Parsing hex file. Erasing user available code and UICR flash areas. Applying system reset. Checking that the area to write is not protected. Programing device. Applying system reset. Run.
كرِّر خطوة "عرض اللوحات" هذه مع اللوحتَين الأخريَين. يجب ربط كل لوحة بجهاز Linux بالطريقة نفسها، ويكون الأمر المطلوب لتثبيت ذاكرة ROM هو نفسه، باستثناء الرقم التسلسلي للوحة. احرص على استخدام الرقم التسلسلي الفريد لكل لوحة في
nrfjprog أمر التحديث
في حال نجاح العملية، سيضيء أحد مصابيح LED1 أو LED2 أو LED3 على كل لوحة. قد تلاحظ حتى أنّ ضوء LED ينتقل من 3 إلى 2 (أو من 2 إلى 1) بعد وقت قصير من الوميض (ميزة تغيير دور الجهاز).
12. وظائف التطبيق
من المفترض أن تكون جميع لوحات nRF52840 الثلاثة تعمل الآن وتشغّل تطبيق OpenThread. كما هو موضّح بالتفصيل سابقًا، يتضمّن هذا التطبيق ميزتَين أساسيتَين.
مؤشرات دور الجهاز
تعكس مصابيح LED المضاءة على كل لوحة الدور الحالي لعقدة "سلسلة المحادثات":
- LED1 = القائد
- LED2 = جهاز التوجيه
- LED3 = جهاز نهائي
مع تغيُّر الدور، يتغيّر مؤشر LED المضاء. من المفترض أن تكون قد رأيت هذه التغييرات على لوحة أو لوحتين في غضون 20 ثانية من تشغيل كل جهاز.
البث المتعدد عبر بروتوكول UDP
عند الضغط على Button1 على لوحة، يتم إرسال رسالة UDP إلى عنوان البث المتعدد المحلي للشبكة، والذي يتضمّن جميع العُقد الأخرى في شبكة Thread. عند تلقّي هذه الرسالة، يتم تفعيل أو إيقاف مصباح LED4 على جميع اللوحات الأخرى. يبقى مصباح LED4 مفعّلاً أو متوقفًا على كل لوحة إلى أن تتلقّى رسالة UDP أخرى.


13. عرض توضيحي: مراقبة تغييرات دور الجهاز
الأجهزة التي تم تثبيت البرامج عليها هي نوع محدّد من أجهزة Thread الكاملة (FTD) يُعرف باسم جهاز طرفي مؤهَّل كجهاز توجيه (REED). وهذا يعني أنّه يمكن أن تعمل إما كجهاز توجيه أو جهاز طرفي، ويمكنها الترقية من جهاز طرفي إلى جهاز توجيه.
يمكن أن تتوافق شبكة Thread مع ما يصل إلى 32 جهاز توجيه، ولكنها تحاول الحفاظ على عدد أجهزة التوجيه بين 16 و23. إذا تم ربط جهاز REED كجهاز طرفي وكان عدد أجهزة التوجيه أقل من 16، تتم ترقيته تلقائيًا إلى جهاز توجيه. يجب أن يحدث هذا التغيير في وقت عشوائي خلال عدد الثواني الذي ضبطت قيمة otThreadSetRouterSelectionJitter عليه في التطبيق (20 ثانية).
تحتوي كل شبكة Thread أيضًا على جهاز توجيه رئيسي، وهو جهاز توجيه مسؤول عن إدارة مجموعة أجهزة التوجيه في شبكة Thread. بعد تشغيل جميع الأجهزة، يجب أن يصبح أحدها جهازًا رئيسيًا (يضيء مؤشر LED1) ويصبح الجهازان الآخران جهازَي توجيه (يضيء مؤشر LED2) بعد 20 ثانية.

إزالة القائد
إذا تمت إزالة "الجهاز الرئيسي" من شبكة Thread، سيتم ترقية جهاز توجيه آخر ليصبح "الجهاز الرئيسي"، وذلك لضمان استمرار توفّر "جهاز رئيسي" في الشبكة.
أطفِئ لوحة النتائج (التي يضيء فيها مؤشر LED1) باستخدام مفتاح التشغيل. انتظِر لمدة 20 ثانية تقريبًا. على إحدى اللوحتين المتبقيتين، سيتم إيقاف تشغيل LED2 (جهاز التوجيه) وتشغيل LED1 (الجهاز الرئيسي). أصبح هذا الجهاز الآن الجهاز الرئيسي في شبكة Thread.

أعِد تفعيل "قائمة الصدارة" الأصلية. من المفترض أن يعيد الانضمام تلقائيًا إلى شبكة Thread كجهاز طرفي (يضيء مؤشر LED3). في غضون 20 ثانية (الحد الأدنى لتأخير اختيار جهاز التوجيه)، يتم ترقية الجهاز إلى جهاز توجيه (يضيء مؤشر LED2).

إعادة ضبط اللوحات
أوقِف تشغيل جميع اللوحات الثلاث، ثم أعِد تشغيلها مرة أخرى وراقِب مؤشرات LED. يجب أن يبدأ اللوح الأول الذي تم تشغيله بدور "القائد" (يضيء LED1)، إذ يصبح أول جهاز توجيه في شبكة Thread هو "القائد" تلقائيًا.
تتصل اللوحتان الأخريان بالشبكة في البداية كأجهزة طرفية (يضيء مؤشر LED3)، ولكن من المفترض أن تتم ترقيتهما إلى أجهزة توجيه (يضيء مؤشر LED2) في غضون 20 ثانية.
تقسيم موارد الشبكة
إذا كانت لوحاتك لا تتلقّى طاقة كافية أو كان الاتصال اللاسلكي بينها ضعيفًا، قد تنقسم شبكة Thread إلى أقسام وقد يظهر لك أكثر من جهاز واحد على أنّه الجهاز الرئيسي.
تتم إصلاح سلاسل المحادثات ذاتيًا، لذا من المفترض أن يتم دمج الأقسام مرة أخرى في قسم واحد مع قائد واحد.
14. العرض التوضيحي: إرسال بث متعدّد عبر UDP
في حال المتابعة من التمرين السابق، يجب ألا يكون مؤشر LED4 مضاءً على أي جهاز.
اختَر أي لوحة واضغط على الزر 1. يجب أن يؤدي ذلك إلى تبديل حالة مؤشر LED4 على جميع اللوحات الأخرى في شبكة Thread التي تشغّل التطبيق. إذا كنت ستتابع من التمرين السابق، يجب أن تكون هذه الميزة مفعّلة الآن.

اضغط على الزر 1 للوحة نفسها مرة أخرى. يجب أن يومض مؤشر LED4 على جميع اللوحات الأخرى مرة أخرى.
اضغط على Button1 على لوحة مختلفة ولاحظ كيف يتم تبديل LED4 على اللوحات الأخرى. اضغط على الزر 1 في إحدى اللوحات التي يكون فيها مؤشر LED4 مضاءً حاليًا. يبقى مؤشر LED4 مضاءً على اللوحة، ولكن يتم إيقاف تشغيله على اللوحات الأخرى.

تقسيم موارد الشبكة
إذا تم تقسيم لوحاتك وكان هناك أكثر من قائد واحد بينها، سيختلف نتيجة رسالة البث المتعدد بين اللوحات. إذا ضغطت على Button1 في لوحة تم تقسيمها (وبالتالي هي العضو الوحيد في شبكة Thread المقسّمة)، لن يضيء مؤشر LED4 في اللوحات الأخرى استجابةً لذلك. في حال حدوث ذلك، أعِد ضبط اللوحات، ومن المفترض أن تعيد تشكيل شبكة Thread واحدة وأن تعمل مراسلة UDP بشكل صحيح.
15. تهانينا!
لقد أنشأت تطبيقًا يستخدم واجهات برمجة تطبيقات OpenThread.
أنت تعرف الآن:
- كيفية برمجة الأزرار ومؤشرات LED على لوحات تطوير Nordic nRF52840
- كيفية استخدام واجهات برمجة تطبيقات OpenThread الشائعة وفئة
otInstance - كيفية تتبُّع تغييرات حالة OpenThread والتعامل معها
- كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة Thread
- كيفية تعديل ملفات Makefiles
الخطوات التالية
استنادًا إلى هذا الدرس التطبيقي حول الترميز، جرِّب التمارين التالية:
- عدِّل وحدة GPIO لاستخدام دبابيس GPIO بدلاً من مصابيح LED المدمجة، ووصِّل مصابيح LED خارجية بألوان الأحمر والأخضر والأزرق تتغير ألوانها استنادًا إلى دور جهاز التوجيه
- إضافة إمكانية استخدام GPIO لمنصة مثال مختلفة
- بدلاً من استخدام البث المتعدد لإجراء اختبار ping على جميع الأجهزة عند الضغط على أحد الأزرار، استخدِم واجهة برمجة التطبيقات Router/Leader لتحديد موقع جهاز فردي وإجراء اختبار ping عليه.
- وصِّل شبكتك المتداخلة بالإنترنت باستخدام جهاز توجيه حدود شبكة OpenThread وأرسِلها عبر البث المتعدد من خارج شبكة Thread لإضاءة مصابيح LED
محتوى إضافي للقراءة
يمكنك الاطّلاع على openthread.io وGitHub للحصول على مجموعة متنوعة من مراجع OpenThread، بما في ذلك:
- المنصّات المتوافقة: يمكنك الاطّلاع على جميع المنصّات المتوافقة مع OpenThread
- إنشاء OpenThread: مزيد من التفاصيل حول إنشاء OpenThread وإعداده
- مقدمة حول شبكة Thread: مرجع رائع حول مفاهيم شبكة Thread
مرجع: