1. مقدمة
OpenThread هو بروتوكول شبكة مفتوح المصدر أطلقته شركة Nest، وهو يستند إلى بروتوكول Thread®. أصدرت 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
- كيفية تعديل ملفات Makefile
المتطلبات
الأجهزة:
- 3 لوحات تطوير nRF52840 من Nordic Semiconductor
- 3 كابلات USB إلى منفذ USB صغير لتوصيل اللوحات
- جهاز Linux مزوّد بثلاثة منافذ USB على الأقل
البرامج:
- مجموعة أدوات GNU
- أدوات سطر الأوامر في Nordic nRF5x
- برنامج Segger J-Link
- OpenThread
- Git
باستثناء ما هو موضّح بخلاف ذلك، يخضع محتوى هذا الدليل التعليمي للترميز لترخيص بموجب Creative Commons Attribution 3.0 License، كما تخضع عيّنات الرموز البرمجية لترخيص بموجب Apache 2.0 License.
2. الخطوات الأولى
إكمال الدرس التطبيقي حول الترميز في الأجهزة
قبل بدء هذا الدرس التطبيقي، عليك إكمال الدرس التطبيقي إنشاء شبكة Thread باستخدام لوحات nRF52840 وOpenThread، الذي:
- تفاصيل جميع البرامج التي تحتاجها لإنشاء البرامج القابلة للتشغيل وفلاشها
- يشرح لك كيفية إنشاء OpenThread وفلاشه على لوحات Nordic nRF52840
- توضيح أساسيات شبكة Thread
لا يوضّح هذا الدليل التعليمي أيًا من إعدادات البيئة المطلوبة لإنشاء OpenThread وفلاش اللوحات، بل يقدّم فقط تعليمات أساسية لفلاش اللوحات. يُفترض أنّك قد أكملت دورة Codelab لإنشاء شبكة رسائل سلاسل محادثات.
جهاز Linux
تم تصميم هذا الدرس التطبيقي لاستخدام جهاز Linux المستنِد إلى i386 أو x86 لفلاش جميع لوحات تطوير Thread. تم اختبار جميع الخطوات على نظام التشغيل Ubuntu 14.04.5 LTS (Trusty Tahr).
لوحات Nordic Semiconductor nRF52840
يستخدم هذا الدرس التطبيقي ثلاث لوحات nRF52840 PDK.
تثبيت البرنامج
لإنشاء OpenThread وفلاشه، عليك تثبيت SEGGER J-Link وأدوات سطر أوامر nRF5x ومجموعة ARM GNU Toolchain وحِزم Linux المختلفة. إذا كنت قد أكملت "إنشاء شبكة سلاسل محادثات" في 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.
استخدام واجهة برمجة تطبيقات
لاستخدام واجهة برمجة تطبيقات، يجب تضمين ملف الرأس الخاص بها في أحد ملفات تطبيقك. بعد ذلك، استدعي الدالة المطلوبة.
على سبيل المثال، يستخدم مثال تطبيق سطر الأوامر المضمّن في 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 API.
على سبيل المثال، يتمّ إعداد مثيل 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 (ملف جديد)
الإجراء: إضافة تعريفات
تُستخدَم هذه التعريفات كعمليات تجريد بين القيم والمتغيّرات الخاصة بوحدة المعالجة 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
هي دالة ردّ الاتصال التي يتم تسجيلها عند بدء وظيفة الضغط على الزر (في وقت لاحق من هذا الملف).
لاحظ كيف يستخدم هذا المُعلِم الداعم رمز macro 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
الإجراء: إضافة عناصر الرأس
في قسم "العناصر المضمّنة" في ملف 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) إلى جميع الأجهزة الأخرى في الشبكة عند الضغط على الزر 1 (Button1) على لوحة واحدة. لتأكيد استلام الرسالة، سنبدِّل ضوء LED4 على اللوحات الأخرى استجابةً لذلك.
لتفعيل هذه الوظيفة، يجب أن يستوفي التطبيق الشروط التالية:
- بدء اتصال UDP عند بدء التشغيل
- أن يكون بإمكانه إرسال رسالة بروتوكول بيانات المستخدم (UDP) إلى عنوان البث المتعدد على مستوى الشبكة
- معالجة رسائل UDP الواردة
- تبديل LED4 استجابةً لرسائل UDP الواردة
افتح ملف ./openthread/examples/apps/cli/main.c
في محرِّر النصوص المفضّل لديك.
./openthread/examples/apps/cli/main.c
الإجراء: إضافة عناصر الرأس
في قسم "العناصر المضمّنة" في أعلى ملف main.c
، أضِف ملفات رؤوس واجهة برمجة التطبيقات التي ستحتاج إليها لاستخدام ميزة "بروتوكول حزم بيانات المستخدمين المتعدّدين (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
هو عنوان البث المتعدد على مستوى الشبكة. سيتم إرسال أي رسائل يتم إرسالها إلى هذا العنوان إلى جميع أجهزة "سلسلة المحادثات الكاملة" في الشبكة. اطّلِع على البث المتعدد على 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.
الإجراء: تنفيذ معالجة رسائل 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 = 0x2222
- رقم تعريف PAN الموسّع = C0DE1AB5C0DE1AB5
- مفتاح الشبكة = 1234C0DE1AB51234C0DE1AB51234C0DE
- اسم الشبكة = OTCodelab
بالإضافة إلى ذلك، نحدّ من التقلّبات في اختيار جهاز التوجيه، ما يتيح لأجهزة الاختبار تغيير الأدوار بشكل أسرع لأغراض العرض التقديمي. يُرجى العلم أنّه لا يتم إجراء ذلك إلا إذا كانت العقدة جهاز FTD (جهاز سلسلة محادثات كاملة). سنوضّح لك المزيد من المعلومات في الخطوة التالية.
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. إعداد الأجهزة
بعد الانتهاء من جميع تعديلات الرموز البرمجية، تكون مستعدًا لإنشاء التطبيق وبرمجته على جميع لوحات تطوير Nordic nRF52840 الثلاث. سيعمل كل جهاز كجهاز مزوّد بتقنية Full Thread Device (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
فلاش اللوحات
برمِّج ملف ot-cli-ftd.hex
على كل لوحة nRF52840.
اربط كابل USB بمنفذ تصحيح الأخطاء Micro-USB بجانب دبوس الطاقة الخارجي على لوحة nRF52840، ثم وصِّله بجهاز Linux. ضبط الإعدادات بشكل صحيح، LED5 مفعَّل
كما في السابق، يُرجى تدوين الرقم التسلسلي للوحة nRF52840:
انتقِل إلى موقع أدوات سطر أوامر nRFx، وفلشِش ملف OpenThread CLI 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 بالطريقة نفسها، ويكون الأمر نفسه لفلاش BIOS، باستثناء الرقم التسلسلي للوحة. احرص على استخدام الرقم التسلسلي الفريد لكل لوحة في
nrfjprog
أمر الفلاش
في حال نجاح العملية، سيتم إضاءة مصباح LED1 أو LED2 أو LED3 على كل لوحة. قد تلاحظ أيضًا تبديل مصباح LED المضاء من 3 إلى 2 (أو من 2 إلى 1) بعد وميضه بوقت قصير (ميزة تغيير دور الجهاز).
12. وظائف التطبيق
من المفترض أن تكون جميع لوحات nRF52840 الثلاث مفعَّلة الآن وتعمل بتطبيق OpenThread. كما هو موضّح سابقًا، يتضمّن هذا التطبيق ميزتَين أساسيتَين.
مؤشرات أدوار الأجهزة
يشير مصباح LED المضاء على كل لوحة إلى الدور الحالي لعقدة Thread:
- LED1 = القائد
- LED2 = جهاز التوجيه
- LED3 = الجهاز النهائي
وتغيّر ضوء مصباح LED أيضًا مع تغيّر الدور. من المفترض أن تكون قد لاحظت هذه التغييرات على لوحة أو لوحتَين خلال 20 ثانية من تشغيل كل جهاز.
البث المتعدد عبر بروتوكول UDP
عند الضغط على الزر 1 على اللوحة، يتم إرسال رسالة بروتوكول بيانات المستخدم (UDP) إلى عنوان البث المتعدد على شبكة النسيج المحلي، والذي يتضمّن جميع العقد الأخرى في شبكة Thread. استجابةً لتلقّي هذه الرسالة، يتم تفعيل مصباح LED4 أو إيقافه على جميع اللوحات الأخرى. ويظل مصباح LED4 مفعَّلاً أو غير مفعَّل في كل لوحة إلى أن تتلقّى رسالة UDP أخرى.
13. عرض توضيحي: رصد تغييرات أدوار الأجهزة
إنّ الأجهزة التي تم فلاشها هي نوع محدّد من أجهزة Thread الكاملة (FTD) تُعرف باسم "الأجهزة النهائية المؤهَّلة للاتصال بجهاز توجيه" (REED). وهذا يعني أنّه يمكن أن يعمل كجهاز توجيه أو جهاز طرفي، ويمكنه ترقية نفسه من جهاز طرفي إلى جهاز توجيه.
يمكن أن يتضمّن الخيط ما يصل إلى 32 جهاز توجيه، ولكنّه يحاول إبقاء عدد أجهزة التوجيه بين 16 و23. إذا تم ربط جهاز REED كجهاز طرفي وكان عدد أجهزة التوجيه أقل من 16 جهازًا، سيُرقي نفسه تلقائيًا إلى جهاز توجيه. من المفترض أن يحدث هذا التغيير في وقت عشوائي خلال عدد الثواني التي ضبطت فيها قيمة otThreadSetRouterSelectionJitter
في التطبيق (20 ثانية).
تحتوي كل شبكة Thread أيضًا على جهاز توجيه رئيسي مسؤول عن إدارة مجموعة أجهزة التوجيه في شبكة Thread. بعد 20 ثانية من تشغيل جميع الأجهزة، يجب أن يصبح أحدها قائدًا (يكون مصباح LED1 مضاءً) ويجب أن يصبح الجهازان الآخران جهازَي توجيه (يكون مصباح LED2 مضاءً).
إزالة القائد
إذا تمت إزالة الجهاز الرئيسي من شبكة Thread، يُعلِي جهاز توجيه مختلف رتبته إلى جهاز رئيسي، لضمان استمرار توفُّر جهاز رئيسي في الشبكة.
أوقِف لوحة الصدارة (اللوحة التي يضيء فيها مصباح LED1) باستخدام مفتاح الطاقة. انتظِر لمدة 20 ثانية تقريبًا. في إحدى اللوحتَين المتبقيتَين، سيتم إطفاء مصباح LED2 (جهاز التوجيه) وسيتم تشغيل مصباح LED1 (المصباح الرئيسي). أصبح هذا الجهاز الآن قائد شبكة Thread.
أعِد تفعيل لوحة الصدارة الأصلية. من المفترض أن يعاود الجهاز الانضمام تلقائيًا إلى شبكة Thread كجهاز طرفي (يكون مصباح LED3 مضاءً). خلال 20 ثانية (الوقت المتغير لاختيار جهاز التوجيه)، يُعلِن الجهاز عن نفسه كجهاز توجيه (يضيء مصباح LED2).
إعادة ضبط اللوحات
أوقِف تشغيل اللوحات الثلاث، ثم أعِد تشغيلها مرة أخرى وراقِب مصابيح LED. من المفترض أن يبدأ تشغيل اللوحة الأولى التي تم توصيلها في دور "القائد" (يكون مصباح LED1 مضاءً)، ويصبح جهاز التوجيه الأول في شبكة Thread هو "القائد" تلقائيًا.
تتصل اللوحتان الأخريان بالشبكة في البداية بصفتهما جهازَي طرف (يكون مصباح LED3 مضاءً)، ولكن من المفترض أن تصبحا جهازَي توجيه (يكون مصباح LED2 مضاءً) في غضون 20 ثانية.
أقسام الشبكة
إذا لم تتلقّ لوحاتك طاقة كافية أو كان الاتصال اللاسلكي بينها ضعيفًا، قد تنقسم شبكة Thread إلى أقسام وقد يظهر أكثر من جهاز واحد على أنّه "قائد".
إنّ سلسلة المحادثات ذاتية الاسترداد، لذا من المفترض أن يتم دمج الأقسام في النهاية مرة أخرى في قسم واحد يتضمّن قائدًا واحدًا.
14. عرض توضيحي: إرسال بث UDP المتعدد
في حال المتابعة من التمرين السابق، يجب ألا يضيء مصباح LED4 على أي جهاز.
اختَر أي لوحة واضغط على الزر 1. من المفترض أن يؤدي تشغيل التطبيق على جميع اللوحات الأخرى في شبكة Thread إلى تبديل حالة مصباح LED4. في حال المتابعة من التمرين السابق، من المفترض أن تكون هذه الإعدادات مفعّلة الآن.
اضغط على الزر 1 للوحة نفسها مرة أخرى. من المفترض أن يتم تبديل مصباح LED4 على جميع اللوحات الأخرى مرة أخرى.
اضغط على الزر 1 (Button1) في لوحة مختلفة وراقِب كيفية تبديل مصباح LED4 (LED4) في اللوحات الأخرى. اضغط على الزر 1 (Button1) في إحدى اللوحات التي يكون فيها مصباح LED4 مضاءً حاليًا. يظل مصباح LED4 مضاءً في تلك اللوحة، ولكن يتم تشغيله وإيقافه في اللوحات الأخرى.
أقسام الشبكة
إذا تم تقسيم لوحاتك وكان هناك أكثر من قائد واحد بينها، ستختلف نتيجة رسالة البث المتعدد بين اللوحات. في حال الضغط على الزر 1 (Button1) في لوحة تم تقسيمها (وبالتالي هي العضو الوحيد في شبكة Thread المقسّمة)، لن يضيء مصباح LED4 في اللوحات الأخرى استجابةً لذلك. في حال حدوث ذلك، عليك إعادة ضبط اللوحات، ومن المفترض أن تعيد اللوحات إنشاء شبكة Thread واحدة وأن تعمل رسائل بروتوكول UDP بشكل صحيح.
15. تهانينا!
لقد أنشأت تطبيقًا يستخدم واجهات برمجة تطبيقات OpenThread.
تعرَّفت الآن على ما يلي:
- كيفية برمجة الأزرار ومصابيح LED على لوحات تطوير Nordic nRF52840
- كيفية استخدام واجهات برمجة تطبيقات OpenThread الشائعة وفئتها
otInstance
- كيفية رصد تغييرات حالة OpenThread والتفاعل معها
- كيفية إرسال رسائل بروتوكول حزم بيانات المستخدم (UDP) إلى جميع الأجهزة في شبكة Thread
- كيفية تعديل ملفات Makefile
الخطوات التالية
استنادًا إلى هذا الدرس التطبيقي حول الترميز، جرِّب التمارين التالية:
- تعديل وحدة GPIO لاستخدام دبابيس GPIO بدلاً من مصابيح LED المدمجة، وربط مصابيح LED خارجية بألوان الأحمر والأخضر والأزرق (RGB) تتغيّر ألوانها استنادًا إلى دور جهاز التوجيه
- إضافة دعم GPIO لنظام أساسي مختلف
- بدلاً من استخدام البث المتعدد لاختبار الاتصال بجميع الأجهزة من خلال الضغط على زر، استخدِم Router/Leader API لتحديد موقع جهاز فردي واختبار الاتصال به.
- عليك توصيل شبكتك المتداخلة بالإنترنت باستخدام جهاز توجيه OpenThread Border Router وبثها بشكل جماعي من خارج شبكة Thread لإضاءة مصابيح LED.
مراجع إضافية
يمكنك الانتقال إلى openthread.io وGitHub للاطّلاع على مجموعة متنوعة من موارد OpenThread، بما في ذلك:
- الأنظمة الأساسية المتوافقة: يمكنك الاطّلاع على جميع الأنظمة الأساسية المتوافقة مع OpenThread.
- إنشاء OpenThread: مزيد من التفاصيل حول إنشاء OpenThread وضبط إعداداته
- مقدّمة عن سلاسل المحادثات: مرجع رائع عن مفاهيم سلاسل المحادثات
مرجع: