1. مقدمة
OpenThread الذي طرحته Nest هو تطبيق مفتوح المصدر لبروتوكول شبكة Thread®. أطلقت Nest أداة OpenThread لإتاحة التكنولوجيا المستخدَمة في منتجات Nest على نطاق واسع للمطوّرين، ما يساعد في تسريع عملية تطوير المنتجات للشبكات المنزلية المتصلة.
تحدِّد مواصفات سلسلة المحادثات بروتوكول اتصال من جهاز إلى جهاز لاسلكي موثوق به وآمن ومنخفض الطاقة يستند إلى بروتوكول IPv6، وذلك في التطبيقات المنزلية. ينفِّذ OpenThread جميع طبقات شبكات Thread، بما في ذلك IPv6 و6LoWPAN وIEEE 802.15.4 مع أمان MAC و"إنشاء رابط متداخل" و"توجيه الشبكة المتداخلة".
في هذا الدرس التطبيقي حول الترميز، ستستخدم واجهات برمجة تطبيقات OpenThread لبدء شبكة Thread، ومراقبة التغييرات في أدوار الأجهزة والتفاعل معها، وإرسال رسائل بروتوكول مخطط بيانات المستخدم، بالإضافة إلى ربط هذه الإجراءات بالأزرار ومصابيح LED على الأجهزة الحقيقية.
ما ستتعرَّف عليه
- طريقة برمجة الأزرار ومصابيح LED على لوحات مطوري Nordic nRF52840
- طريقة استخدام واجهات برمجة تطبيقات OpenThread الشائعة وفئة
otInstance
- كيفية مراقبة تغييرات حالة OpenThread والتفاعل معها
- كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة Thread
- كيفية تعديل ملفات Makefiles
المتطلبات
الأجهزة:
- 3 لوحات Nordic تعليقconductor nRF52840 لمطوِّري التطبيقات
- 3 كابلات USB إلى USB مصغّر لتوصيل اللوحات
- جهاز Linux مزوَّد بثلاثة منافذ USB على الأقل
البرامج:
- سلسلة أدوات GNU
- أدوات سطر أوامر nRF5x الشمالي
- برنامج Segger J-Link
- OpenThread
- Git
إنّ محتوى هذا الدرس التطبيقي حول الترميز مرخَّص بموجب ترخيص Creative Commons Attribution 3.0، كما تخضع عيّنات التعليمات البرمجية لترخيص بموجب Apache 2.0، ما لم يُذكر خلاف ذلك.
2. الخطوات الأولى
إكمال الدرس التطبيقي حول ترميز الأجهزة
قبل البدء في هذا الدرس التطبيقي حول الترميز، عليك إكمال الدرس التطبيقي حول الترميز Build a Thread Network with nRF52840 Boards and OpenThread، والذي:
- تفاصيل جميع البرامج التي تحتاجها لإنشاء ووميض
- تعليمك كيفية إنشاء OpenThread ووميضه على لوحات Nordic nRF52840
- توضيح أساسيات شبكة Thread
في هذا الدرس التطبيقي حول الترميز، لم يتم توضيح أي من البيئات التي تم إعدادها لإنشاء مكتبة OpenThread وإضاءة الألواح، وهم يقدّم تعليمات أساسية فقط حول تثبيت اللوحات. من المفترض أنّك أكملت بالفعل الدرس التطبيقي حول ترميز شبكة Thread.
جهاز Linux
صُمِّم هذا الدرس التطبيقي حول الترميز لاستخدام جهاز Linux يستند إلى i386 أو x86 لتثبيت جميع لوحات تطوير Thread. تم اختبار جميع الخطوات على نظام التشغيل Ubuntu 14.04.5 LTS (Trusty Tahr).
لوحات Nordic شبهconductor nRF52840
يستخدم هذا الدرس التطبيقي حول الترميز ثلاث لوحات nRF52840 PDK.
تثبيت البرنامج
لإنشاء أداة OpenThread والتحديث إليها، يجب تثبيت أدوات سطر الأوامر SEGGER J-Link وأدوات سطر الأوامر nRF5x وسلسلة أدوات ARM GNU وحزم Linux المتنوعة. إذا كنت قد أكملت الدرس التطبيقي حول ترميز شبكة Thread Network على النحو المطلوب، سيكون لديك كل ما تحتاج إليه مثبّتًا. إذا لم يكن الأمر كذلك، عليك إكمال الدرس التطبيقي حول الترميز هذا قبل المتابعة للتأكّد من أنّه يمكنك إنشاء لوحات OpenThread وإضافتها إلى لوحات المطوّرين nRF52840.
3- استنساخ المستودع
يتضمّن موقع OpenThread مثالاً لرمز التطبيق الذي يمكنك استخدامه كنقطة بداية لهذا الدرس التطبيقي حول الترميز.
استنساخ أمثلة OpenThread Nordic nRF528xx وتصميم منصة OpenThread:
$ git clone --recursive https://github.com/openthread/ot-nrf528xx $ cd ot-nrf528xx $ ./script/bootstrap
4. أساسيات واجهة برمجة تطبيقات OpenThread
تتوفّر واجهات برمجة التطبيقات العامة في OpenThread على الرابط ./openthread/include/openthread
في مستودع OpenThread. تتيح واجهات برمجة التطبيقات هذه الوصول إلى مجموعة متنوعة من ميزات ووظائف OpenThread على مستوى كل من Thread والنظام الأساسي للاستخدام في تطبيقاتك:
- معلومات مثيل OpenThread والتحكم فيه
- خدمات التطبيق، مثل IPv6 وUDP وCoAP
- إدارة بيانات اعتماد الشبكة، إلى جانب دور "المفوض" و"الانضمام"
- إدارة أجهزة توجيه الحدود
- ميزات محسَّنة، مثل الإشراف على الأطفال و"رصد محتوى Jam"
تتوفّر معلومات مرجعية حول جميع واجهات برمجة تطبيقات 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()
ضمن مثال تطبيق واجهة سطر الأوامر:
./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 التي سنستخدمها في الربط بأزرار nRF52840، ويجب تعريف مصابيح LED في 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 لمنع أخطاء الإنشاء حول المتغيّرات غير المستخدَمة لبعض سلاسل الأدوات. سنطّلع على أمثلة على ذلك لاحقًا.
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
لمزيد من المعلومات عن أزرار nRF52840 ومصابيح LED، يمكنك الاطّلاع على مركز معلومات أشباه الموصلات الشمالي.
الإجراء: تضمين العنوان
بعد ذلك، أضف العنوان يتضمن أنك ستحتاج إلى وظيفة 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
الإجراء: تضمين العنوان
في قسم "تضمين" لملف "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
الإجراء: تضمين العنوان
في قسم "تضمين" أعلى ملف 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
هو عنوان البث المتعدد المحلي في الشبكة المحلية. أي رسائل يتم إرسالها إلى هذا العنوان سيتم إرسالها إلى جميع أجهزة Threads في الشبكة. راجِع قسم البث المتعدد على 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;
الإجراء: يجب إضافة طلبات لبدء إعداد مصابيح GPIO LED والزر
في 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
الإجراء: إضافة العنوان "تضمين"
ضمن قسم "includes" (تضمين) في أعلى ملف 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، وفقًا لمواصفات Thread. لتيسير الاستخدام في هذا التمرين التطبيقي حول الترميز، سنغيّرها إلى 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
أضف الآن بعض العلامات إلى Nordic تعليقconductor 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
إلى ملف Nordicomeconductor CMakeLists.txt
، لكي يتم تضمينه في إصدار المكتبة لبرامج تشغيل Nordic.
الإجراء: إضافة برنامج تشغيل gpio إلى ملف Nordicsomeconductor CMakeLists.txt
.
افتح "./third_party/NordicSemiconductor/CMakeLists.txt
" في محرِّر النصوص المفضَّل لديك، وأضِف الملف إلى القسم COMMON_SOURCES
.
... set(COMMON_SOURCES ... nrfx/drivers/src/nrfx_gpiote.c ... ) ...
11. إعداد الأجهزة
بعد الانتهاء من جميع تحديثات الرموز، يصبح بإمكانك إنشاء التطبيق وتحديثه على جميع لوحات تطوير nRF52840 Nordic الثلاثة. سيعمل كل جهاز كجهاز سلسلة محادثات كاملة (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 inline Toolchain:
$ cd build/bin $ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex
فلاش الألواح
يجب مسح الملف ot-cli-ftd.hex
بكل لوحة nRF52840.
وصِّل كابل USB بمنفذ تصحيح أخطاء 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 بالطريقة نفسها، ويكون أمر الفلاش هو نفسه، باستثناء الرقم التسلسلي للوحة. احرص على استخدام الرقم التسلسلي الفريد لكل لوحة في
nrfjprog
أمر Flash.
في حال نجاح تركيبها، ستتم إضاءة LED1 أو LED2 أو LED3 على كل لوح. قد ترى أيضًا مفتاح إضاءة LED المضاء من 3 إلى 2 (أو 2 إلى 1) بعد وميض مباشرة (ميزة تغيير دور الجهاز).
12. وظائف التطبيق
من المفترض أن يتم تشغيل جميع لوحات nRF52840 الثلاث وتشغيل تطبيق OpenThread التابع لنا. وكما هو موضح سابقًا، يشتمل هذا التطبيق على ميزتين أساسيتين.
مؤشرات دور الجهاز
يعكس ضوء LED المضاء على كل لوحة الدور الحالي لعقدة Thread:
- LED1 = القائد
- LED2 = جهاز توجيه
- LED3 = الجهاز النهائي
مع تغيُّر الدور، يتغيّر ضوء LED المضاء. من المفترض أن تكون هذه التغييرات قد ظهرت على لوح أو لوحتَين خلال 20 ثانية من تشغيل كل جهاز.
البث المتعدد عبر بروتوكول UDP
عند الضغط على Button1 في لوحة، يتم إرسال رسالة UDP إلى عنوان البث المتعدد المحلي على شبكة متداخلة، والذي يتضمن جميع العُقد الأخرى في شبكة Thread. ردًا على تلقّي هذه الرسالة، يمكن تفعيل أو إيقاف مؤشر LED4 على جميع اللوحات الأخرى. يظل مصباح LED4 يعمل أو متوقفًا لكل لوحة إلى أن يتلقّى رسالة UDP أخرى.
13. عرض توضيحي: ملاحظة التغييرات في أدوار الجهاز
إنّ الأجهزة التي شغّلتها هي نوع معيّن من الأجهزة التي تتضمّن مؤشر ترابط كامل (FTD) يُطلق عليها اسم "جهاز التوجيه النهائي المؤهَّل لجهاز التوجيه" (REED). وهذا يعني أنّه بإمكانها العمل كجهاز توجيه أو جهاز نهائي، ويمكنه الترويج لنفسه من جهاز نهائي إلى جهاز توجيه.
يمكن أن تدعم سلسلة التعليمات ما يصل إلى 32 جهاز توجيه، ولكنها تحاول جعل عدد أجهزة التوجيه بين 16 و23 جهاز توجيه. إذا تم توصيل حزمة REED كجهاز نهائي وكان عدد أجهزة التوجيه أقل من 16 جهاز توجيه، ستتم ترقيتها تلقائيًا إلى جهاز توجيه. يجب أن يحدث هذا التغيير في وقت عشوائي ضمن عدد الثواني التي تم ضبط القيمة otThreadSetRouterSelectionJitter
عليها في التطبيق (20 ثانية).
تتضمّن كل شبكة في Thread أيضًا "قائدًا"، وهو جهاز توجيه مسؤول عن إدارة مجموعة أجهزة التوجيه في شبكة Thread. عند تشغيل جميع الأجهزة، من المفترض أن يصبح أحدها مزوّدًا (LED1 قيد التشغيل) بعد 20 ثانية، ومن المفترض أن يكون الجهازان الآخران جهاز توجيه (LED2 قيد التشغيل).
إزالة القائد
في حال إزالة القائد من شبكة Thread، يتولى جهاز توجيه مختلف ترقية نفسه إلى قائد، لضمان استمرار وجود قائد للشبكة.
أوقِف لوحة الصدارة (التي تتضمّن إضاءة LED1) باستخدام مفتاح التبديل التشغيل. انتظِر لمدة 20 ثانية تقريبًا. على إحدى اللوحتين المتبقيتين، سيتم إطفاء LED2 (جهاز التوجيه) وتشغيل LED1 (القائد). أصبح هذا الجهاز الآن قائد شبكة Thread.
أعِد تفعيل "لوحة الصدارة" الأصلية. من المفترض أن ينضم تلقائيًا إلى شبكة Thread كجهاز نهائي (مضاء LD3). وفي غضون 20 ثانية (في حال عدم استقرار اختيار جهاز التوجيه)، ينتقل الجهاز إلى جهاز توجيه (ضوء LD2).
إعادة ضبط اللوحات
عليك إيقاف اللوحات الثلاث، ثم إعادة تشغيلها مرة أخرى ومراقبة مصابيح LED. يجب أن تبدأ أول لوحة تم تشغيلها في دور القائد (LED1 مضاءة)، وسيصبح أول جهاز توجيه في شبكة Thread تلقائيًا هو القائد.
وتتصل اللوحتان الأخريان بالشبكة في البداية عند تشغيل الأجهزة النهائية (LED3 مضاءة)، لكن من المفترض أن يتم الترويج لهما على أجهزة التوجيه (LED2) في غضون 20 ثانية.
أقسام الشبكة
إذا كانت لوحاتك لا تتلقّى طاقة كافية أو كان الاتصال اللاسلكي بينها ضعيفًا، قد تنقسم شبكة Thread إلى أقسام وقد يكون لديك أكثر من جهاز واحد يظهر كـ "قائد".
تخضع سلسلة المحادثات للمعالجة الذاتية، لذا من المفترض أن يتم دمج الأقسام مرة أخرى في قسم واحد مع قائد واحد.
14. عرض توضيحي: إرسال بث متعدد عبر UDP
في حال المتابعة من التمرين السابق، يجب عدم إضاءة LED4 على أي جهاز.
اختر أي لوحة واضغط على Button1. يجب أن يبدّل LED4 على جميع اللوحات الأخرى في شبكة Thread التي تشغّل التطبيق حالتها. إذا كانت المتابعة من التمرين السابق، فينبغي أن تكون قيد التشغيل الآن.
اضغط على Button1 لنفس اللوحة مرة أخرى. ومن المفترض أن يتم تبديل مصباح LED4 على جميع الألواح الأخرى مجددًا.
اضغط على Button1 على لوحة مختلفة ولاحظ كيفية تبديل LED4 على اللوحات الأخرى. يجب الضغط على Button1 في إحدى اللوحتين حيث يعمل مصباح LED4 حاليًا. يظلّ LED4 قيد التشغيل لتلك اللوحة، ولكنه يبدِّل اللوحة على الأخرى.
أقسام الشبكة
في حال تقسيم لوحاتك وكان هناك أكثر من قائد واحد منها، ستختلف نتيجة رسالة البث المتعدد بين اللوحات. إذا ضغطت على Button1 على لوح مقسّم (وبالتالي هو العضو الوحيد في شبكة Thread المقسَّمة)، لن يضيء مصباح LED4 على الألواح الأخرى استجابةً. وفي حال حدوث ذلك، يجب إعادة ضبط اللوحات، ومن المفترض أن تعدِّل هذه اللوحات شبكة Thread واحدة، ومن المفترض أن تعمل رسائل بروتوكول مخطط بيانات المستخدم بشكل صحيح.
15. تهانينا!
لقد أنشأت تطبيقًا يستخدم واجهات برمجة تطبيقات OpenThread.
أنت تعرف الآن:
- طريقة برمجة الأزرار ومصابيح LED على لوحات مطوري Nordic nRF52840
- طريقة استخدام واجهات برمجة تطبيقات OpenThread الشائعة وفئة
otInstance
- كيفية مراقبة تغييرات حالة OpenThread والتفاعل معها
- كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة Thread
- كيفية تعديل ملفات Makefiles
الخطوات التالية
استنادًا إلى هذا الدرس التطبيقي حول الترميز، جرِّب التمارين التالية:
- عدِّل وحدة GPIO لاستخدام دبابيس GPIO بدلاً من مصابيح LED المُدمَجة، ووصِّل مصابيح RGB خارجية التي يتغير لونها بناءً على دور جهاز التوجيه
- إضافة دعم GPIO لنظام أساسي مختلف
- بدلاً من استخدام البث المتعدد لاختبار الاتصال بجميع الأجهزة من خلال الضغط على أحد الأزرار، يمكنك استخدام Router/Leader API لتحديد موقع جهاز فردي وفحص الاتصال به.
- يمكنك توصيل الشبكة المتداخلة بالإنترنت باستخدام جهاز توجيه الحدود OpenThread وبثها من خارج شبكة Thread وإضاءة مصابيح LED.
محتوى إضافي للقراءة
راجع openthread.io وGitHub للحصول على مجموعة متنوعة من موارد OpenThread، بما في ذلك:
- الأنظمة الأساسية المتوافقة: اكتشاف جميع الأنظمة الأساسية التي تتيح استخدام OpenThread
- إنشاء OpenThread: مزيد من التفاصيل حول إنشاء أداة OpenThread وضبطها
- Thread Primer - مرجع رائع لمفاهيم Thread
مرجع: