OpenThread الذي تم إصداره بواسطة Nest هو تطبيق مفتوح المصدر لبروتوكول شبكة Thread® . أصدرت نيست OpenThread لإتاحة التكنولوجيا المستخدمة في منتجات Nest على نطاق واسع للمطورين لتسريع تطوير المنتجات للمنزل المتصل.
تحدد مواصفات الخيط بروتوكول اتصال لاسلكي من جهاز إلى جهاز يعتمد على IPv6 موثوق وآمن ومنخفض الطاقة للتطبيقات المنزلية. ينفذ OpenThread جميع طبقات شبكة الخيط بما في ذلك IPv6 و 6LoWPAN و IEEE 802.15.4 مع أمان MAC وإنشاء ارتباط الشبكة وتوجيه الشبكة.
في Codelab هذا ، ستستخدم OpenThread APIs لبدء شبكة Thread ، ومراقبة التغييرات في أدوار الجهاز والرد عليها ، وإرسال رسائل UDP ، بالإضافة إلى ربط هذه الإجراءات بأزرار ومصابيح LED على أجهزة حقيقية.
ماذا ستتعلم
- كيفية برمجة الأزرار ومصابيح LED على لوحات تطوير Nordic nRF52840
- كيفية استخدام واجهات برمجة تطبيقات OpenThread الشائعة وفئة
otInstance
- كيفية مراقبة تغيرات حالة OpenThread والرد عليها
- كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة الموضوع
- كيفية تعديل ملفات Makefiles
ماذا ستحتاج
المعدات:
- 3 لوحات تطوير NRF52840 من Nordic Semiconductor
- 3 كبلات USB إلى Micro-USB لتوصيل اللوحات
- جهاز Linux به 3 منافذ USB على الأقل
برمجة:
- سلسلة أدوات جنو
- أدوات سطر الأوامر nRF5x الاسكندنافية
- برنامج Segger J-Link
- OpenThread
- شخص سخيف
باستثناء ما هو مذكور خلافًا لذلك ، يتم ترخيص محتوى Codelab هذا بموجب ترخيص Creative Commons Attribution 3.0 ، ويتم ترخيص عينات التعليمات البرمجية بموجب ترخيص Apache 2.0 .
أكمل برنامج Codelab للجهاز
قبل البدء في Codelab هذا ، يجب عليك إكمال إنشاء شبكة سلاسل رسائل باستخدام لوحات nRF52840 و OpenThread Codelab ، والتي:
- تفاصيل كل البرامج التي تحتاجها للبناء والوميض
- يعلمك كيفية إنشاء OpenThread ووميضه على لوحات Nordic nRF52840
- يوضح أساسيات شبكة الموضوع
لم يتم تفصيل أي من البيئة التي تم إعدادها لإنشاء OpenThread ووميض اللوحات في Codelab هذا - فقط الإرشادات الأساسية لوميض اللوحات. من المفترض أنك قد أكملت بالفعل بناء مخطط تشفير شبكة مؤشر ترابط.
آلة لينكس
تم تصميم Codelab هذا لاستخدام جهاز Linux يستند إلى i386 أو x86 لفلاش جميع لوحات تطوير Thread. تم اختبار جميع الخطوات على Ubuntu 14.04.5 LTS (Trusty Tahr).
لوحات nRF52840 الشمال أشباه الموصلات
يستخدم Codelab هذا ثلاث لوحات nRF52840 PDK .
تثبيت نظام التشغيل
لإنشاء OpenThread وفلاشها ، تحتاج إلى تثبيت SEGGER J-Link وأدوات nRF5x Command Line و ARM GNU Toolchain وحزم Linux المختلفة. إذا كنت قد أكملت إنشاء برنامج ترميز شبكة مؤشر ترابط كما هو مطلوب ، فسيكون لديك بالفعل كل ما تحتاجه مثبتًا. إذا لم يكن الأمر كذلك ، فأكمل Codelab قبل المتابعة للتأكد من أنه يمكنك إنشاء لوحات OpenThread ووميضها إلى nRF52840 dev board.
يأتي OpenThread مع مثال لرمز التطبيق الذي يمكنك استخدامه كنقطة بداية لـ Codelab هذا.
استنساخ وتثبيت OpenThread:
$ cd ~ $ git clone --recursive https://github.com/openthread/openthread.git $ cd openthread $ ./bootstrap
توجد واجهات برمجة التطبيقات العامة لـ OpenThread في include/openthread
في مستودع OpenThread. توفر واجهات برمجة التطبيقات هذه إمكانية الوصول إلى مجموعة متنوعة من ميزات ووظائف OpenThread على مستوى كل من Thread- وعلى مستوى النظام الأساسي لاستخدامها في تطبيقاتك:
- معلومات وتحكم مثيل OpenThread
- خدمات التطبيقات مثل IPv6 و UDP و CoAP
- إدارة بيانات اعتماد الشبكة ، جنبًا إلى جنب مع أدوار المفوض والنجار
- إدارة الموجهات الحدودية
- ميزات محسّنة مثل مراقبة الأطفال واكتشاف الانحشار
تتوفر المعلومات المرجعية حول جميع واجهات برمجة تطبيقات OpenThread على العنوان openthread.io/reference .
باستخدام API
لاستخدام API ، قم بتضمين ملف الرأس الخاص بها في أحد ملفات التطبيق الخاص بك. ثم اتصل بالوظيفة المطلوبة.
على سبيل المثال ، يستخدم تطبيق مثال CLI المضمن في OpenThread رؤوس API التالية:
أمثلة / تطبيقات / 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 APIs. بمجرد التهيئة ، يمثل هذا الهيكل مثيلًا ثابتًا لمكتبة OpenThread ويسمح للمستخدم بإجراء مكالمات OpenThread API.
على سبيل المثال ، تتم تهيئة مثيل OpenThread في الوظيفة main()
لتطبيق مثال CLI:
أمثلة / تطبيقات / 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 ، examples/platforms/openthread-system.h
رأس examples/platforms/openthread-system.h
، باستخدام مساحة اسم otSys
لجميع الوظائف. ثم قم بتنفيذها في ملف مصدر خاص بالنظام الأساسي. مستخلصًا بهذه الطريقة ، يمكنك استخدام رؤوس الوظائف نفسها لمنصات أمثلة أخرى.
على سبيل المثال ، وظائف GPIO التي سنستخدمها للربط بأزرار nRF52840 ويجب الإعلان عن مصابيح LED في نظام openthread-system.h
.
افتح ملف examples/platforms/openthread-system.h
في محرر النصوص المفضل لديك.
أمثلة / منصات / openthread-system.h
الإجراء: أضف إعلانات وظائف GPIO الخاصة بالمنصة
أضف إقرارات الوظائف هذه في أي مكان داخل الملف:
/** * 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
الدالة otSysButtonProcess
يستخدم otInstance
. بهذه الطريقة يمكن للتطبيق الوصول إلى معلومات حول مثيل OpenThread عند الضغط على زر ، إذا لزم الأمر. كل هذا يتوقف على احتياجات التطبيق الخاص بك. إذا لم تكن بحاجة إليها في تنفيذ الوظيفة ، فيمكنك استخدام الماكرو OT_UNUSED_VARIABLE
من OpenThread API لمنع أخطاء OT_UNUSED_VARIABLE
حول المتغيرات غير المستخدمة لبعض سلاسل الأدوات. سنرى أمثلة على ذلك لاحقًا.
في الخطوة السابقة ، examples/platforms/openthread-system.h
إعلانات الوظائف الخاصة بالمنصة في examples/platforms/openthread-system.h
التي يمكن استخدامها لـ GPIO. من أجل الوصول إلى الأزرار ومصابيح LED على لوحات التطوير nRF52840 ، فإنك تحتاج إلى تنفيذ هذه الوظائف لمنصة nRF52840. في هذا الرمز ، ستضيف وظائف:
- تهيئة دبابيس وأوضاع GPIO
- السيطرة على الجهد على دبوس
- قم بتمكين مقاطعات GPIO وتسجيل معاودة الاتصال
في دليل examples/platforms/nrf528xx/src
، أنشئ ملفًا يسمى gpio.c
في هذا الملف الجديد ، أضف المحتوى التالي.
أمثلة / منصات / nrf528xx / 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 ، راجع Nordic Semiconductor Infocenter .
الإجراء: إضافة رأس يشمل
بعد ذلك ، قم بإضافة الرأس الذي سوف تحتاجه لوظيفة 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 أثناء التهيئة.
/** * @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
الملف.
في تطبيقنا ، نريد أن تضيء مصابيح LED مختلفة اعتمادًا على دور الجهاز. دعنا نتتبع الأدوار التالية: القائد ، جهاز التوجيه ، الجهاز النهائي. يمكننا تخصيصها لمصابيح LED مثل:
- LED1 = القائد
- LED2 = جهاز التوجيه
- LED3 = الجهاز النهائي
لتمكين هذه الوظيفة ، يحتاج التطبيق إلى معرفة متى تغير دور الجهاز وكيفية تشغيل مؤشر LED الصحيح استجابة لذلك. سنستخدم مثيل OpenThread للجزء الأول ، وتجريد منصة GPIO للجزء الثاني.
افتح ملف examples/apps/cli/main.c
في محرر النصوص المفضل لديك.
أمثلة / تطبيقات / cli / main.c
الإجراء: إضافة رأس يشمل
في قسم main.c
ملف main.c
، أضف ملفات رأس API التي ستحتاجها لميزة تغيير الدور.
#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()
بعد استدعاء otCliUartInit
. يخبر تسجيل رد الاتصال هذا OpenThread باستدعاء وظيفة handleNetifStateChange
كلما تغيرت حالة مثيل OpenThread.
الإجراء: أضف تنفيذ تغيير الحالة
في 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; } } }
في تطبيقنا ، نريد أيضًا إرسال رسائل UDP إلى جميع الأجهزة الأخرى في الشبكة عند الضغط على Button1 في لوحة واحدة. لتأكيد استلام الرسالة ، سنقوم بتبديل LED4 على اللوحات الأخرى استجابةً لذلك.
لتمكين هذه الوظيفة ، يحتاج التطبيق إلى:
- قم بتهيئة اتصال UDP عند بدء التشغيل
- كن قادرًا على إرسال رسالة UDP إلى عنوان الإرسال المتعدد المحلي
- معالجة رسائل UDP الواردة
- قم بتبديل مؤشر LED4 استجابةً لرسائل UDP الواردة
افتح ملف examples/apps/cli/main.c
في محرر النصوص المفضل لديك.
أمثلة / تطبيقات / cli / main.c
الإجراء: إضافة رأس يشمل
في قسم main.c
في الجزء العلوي من ملف 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
وقبل أي عبارات #if
، أضف الثوابت الخاصة بـ UDP وحدد:
#define UDP_PORT 1212 static const char UDP_DEST_ADDR[] = "ff03::1"; static const char UDP_PAYLOAD[] = "Hello OpenThread World!";
ff03::1
هو عنوان الإرسال المتعدد المحلي. سيتم إرسال أي رسائل يتم إرسالها إلى هذا العنوان إلى جميع أجهزة السلسلة الكاملة في الشبكة. راجع Multicast على 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
في 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); }
UDP_PORT
هو المنفذ الذي حددته مسبقًا (1212). تفتح وظيفة otUdpOpen
المقبس وتقوم بتسجيل وظيفة رد الاتصال ( handleUdpReceive
) عند تلقي رسالة 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); }
لسهولة العرض ، نريد أن تبدأ أجهزتنا على الفور في تشغيل Thread والانضمام إلى شبكة عند تشغيلها. للقيام بذلك ، otOperationalDataset
بنية otOperationalDataset
. تحتوي هذه البنية على جميع المعلمات اللازمة لنقل بيانات اعتماد شبكة مؤشر الترابط إلى جهاز.
سيؤدي استخدام هذه البنية إلى تجاوز الإعدادات الافتراضية للشبكة المضمنة في OpenThread ، لجعل تطبيقنا أكثر أمانًا وقصر عقد Thread في شبكتنا على أولئك الذين يقومون بتشغيل التطبيق فقط.
مرة أخرى ، افتح ملف examples/apps/cli/main.c
في محرر النصوص المفضل لديك.
أمثلة / تطبيقات / cli / main.c
الإجراء: إضافة رأس تضمين
ضمن قسم main.c
في الجزء العلوي من ملف main.c
، أضف ملف رأس واجهة برمجة التطبيقات الذي ستحتاجه لتكوين شبكة الموضوع:
#include <openthread/dataset_ftd.h>
الإجراء: إضافة إعلان وظيفة لضبط تكوين الشبكة
أضف هذا الإعلان إلى main.c
، بعد العنوان يتضمن وقبل أي عبارات #if
. سيتم تحديد هذه الوظيفة بعد وظيفة التطبيق الرئيسية.
static void setNetworkConfiguration(otInstance *aInstance);
الإجراء: أضف مكالمة تكوين الشبكة
في main.c
، أضف استدعاء الوظيفة هذا إلى الوظيفة main()
بعد استدعاء otSetStateChangedCallback
. تعمل هذه الوظيفة على تكوين مجموعة بيانات شبكة الموضوع.
/* Override default network credentials */ setNetworkConfiguration(instance);
الإجراء: أضف مكالمات لتمكين واجهة شبكة مؤشر الترابط والمكدس
في 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);
الإجراء: تنفيذ تكوين شبكة الموضوع
في 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 Master Key, PSKc, Security Policy */ aDataset.mActiveTimestamp = 1; 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 master key to 1234C0DE1AB51234C0DE1AB51234C0DE */ uint8_t key[OT_MASTER_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE}; memcpy(aDataset.mMasterKey.m8, key, sizeof(aDataset.mMasterKey)); aDataset.mComponents.mIsMasterKeyPresent = 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; #if OPENTHREAD_FTD 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); #else OT_UNUSED_VARIABLE(aInstance); #endif }
كما هو مفصل في الوظيفة ، معلمات شبكة مؤشر الترابط التي نستخدمها لهذا التطبيق هي:
- القناة = 15
- معرف PAN = 0x2222
- معرف PAN الممتد = C0DE1AB5C0DE1AB5
- المفتاح الرئيسي للشبكة = 1234C0DE1AB51234C0DE1AB51234C0DE
- اسم الشبكة = OTCodelab
بالإضافة إلى ذلك ، هذا هو المكان الذي نقوم فيه بتقليل اهتزاز تحديد جهاز التوجيه ، لذلك تقوم أجهزتنا بتغيير الأدوار بشكل أسرع لأغراض العرض. لاحظ أن هذا يتم فقط إذا كانت العقدة عبارة عن FTD (جهاز خيط كامل). المزيد عن ذلك في الخطوة التالية.
تقوم بعض واجهات برمجة تطبيقات OpenThread بتعديل الإعدادات التي يجب تعديلها فقط لأغراض العرض التجريبي أو الاختبار. لا ينبغي استخدام واجهات برمجة التطبيقات هذه في نشر إنتاج لتطبيق باستخدام OpenThread.
على سبيل المثال ، تعمل وظيفة otThreadSetRouterSelectionJitter
ضبط الوقت (بالثواني) الذي يستغرقه الجهاز النهائي لترقية نفسه إلى جهاز توجيه. القيمة الافتراضية لهذه القيمة هي 120 ، وفقًا لمواصفات مؤشر الترابط. لسهولة الاستخدام في Codelab هذا ، سنقوم بتغييره إلى 20 ، لذلك لا يتعين عليك الانتظار طويلاً حتى تغير عقدة مؤشر الترابط الأدوار.
ومع ذلك ، ربما لاحظت أن هذه الوظيفة تسمى ضمن توجيه ما قبل المعالج:
#if OPENTHREAD_FTD 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); #else OT_UNUSED_VARIABLE(aInstance); #endif
هذا لمنع إضافته إلى MTD (جهاز الخيط الأدنى). لا تصبح أجهزة MTD أجهزة توجيه ، ولا يتم تضمين دعم وظيفة مثل otThreadSetRouterSelectionJitter
في إصدار MTD.
يمكنك تأكيد ذلك من خلال النظر في تعريف وظيفة otThreadSetRouterSelectionJitter
، والذي يتم otThreadSetRouterSelectionJitter
أيضًا في توجيه ما قبل المعالج لـ OPENTHREAD_FTD
:
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
قبل إنشاء التطبيق الخاص بك ، هناك بعض التحديثات الطفيفة اللازمة لثلاثة ملفات Makefiles. يتم استخدام هذه بواسطة نظام الإنشاء لترجمة وربط تطبيقك.
أمثلة / Makefile-nrf52840
أضف الآن بعض العلامات إلى nrf52840 Makefile ، للتأكد من تحديد وظائف GPIO في التطبيق.
الإجراء: أضف الأعلام إلى nrf52840 Makefile
افتح examples/Makefile-nrf52840
في محرر النصوص المفضل لديك ، وأضف COMMONCFLAGS
التالية بعد سطر include ... common-switches.mk
:
... include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/common-switches.mk # Defined in third_party/NordicSemiconductor/nrfx/templates/nRF52840/nrfx_config.h COMMONCFLAGS += -DGPIOTE_ENABLED=1 COMMONCFLAGS += -DGPIOTE_CONFIG_IRQ_PRIORITY=7 COMMONCFLAGS += -DGPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS=1 ...
أمثلة / منصات / nrf528xx / nrf52840 / Makefile.am
قم الآن بإضافة ملف gpio.c
الجديد إلى ملف النظام الأساسي nrf52840 Makefile.am
.
الإجراء: أضف مصدر gpio إلى منصة Makefile nrf52840
افتح examples/platforms/nrf528xx/nrf52840/Makefile.am
في محرر النصوص المفضل لديك ، وأضف الملف في نهاية قسم PLATFORM_COMMON_SOURCES
، قبل العبارة $(NULL)
. لا تنسَ وضع شرطة مائلة للخلف في نهاية السطر!
... PLATFORM_COMMON_SOURCES ... src/gpio.c \ $(NULL) ...
3rd_party / NordicSemiconductor / Makefile.am
أخيرًا ، أضف ملف برنامج التشغيل nrfx_gpiote.c
إلى Makefile التابع لجهة خارجية NordicSemiconductor ، بحيث يتم تضمينه في مكتبة بناء برامج تشغيل Nordic.
الإجراء: أضف برنامج تشغيل gpio إلى منصة Makefile nrf52840
افتح third_party/NordicSemiconductor/Makefile.am
في محرر النصوص المفضل لديك ، وأضف الملف في نهاية قسم NORDICSEMI_COMMON_SOURCES
، قبل العبارة $(NULL)
. لا تنسَ وضع شرطة مائلة للخلف في نهاية السطر!
... NORDICSEMI_COMMON_SOURCES ... nrfx/drivers/src/nrfx_gpiote.c \ $(NULL) ...
بعد الانتهاء من جميع تحديثات التعليمات البرمجية ، فأنت جاهز لإنشاء التطبيق وفلاشه لجميع لوحات تطوير Nordic nRF52840 الثلاثة. سيعمل كل جهاز كجهاز خيط كامل (FTD).
بناء OpenThread
قم ببناء مثال النظام الأساسي nRF52840. نظرًا لأنه تم تغيير ملفات Makefiles في الخطوة السابقة ، فأنت بحاجة إلى تشغيل البرنامج النصي bootstrap
قبل إنشاء:
$ cd ~/openthread $ ./bootstrap $ make -f examples/Makefile-nrf52840
انتقل إلى الدليل باستخدام ثنائي OpenThread FTD CLI ، وقم بتحويله إلى تنسيق سداسي عشري باستخدام ARM Embedded Toolchain:
$ cd ~/openthread/output/nrf52840/bin $ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex
فلاش اللوحات
قم ot-cli-ftd.hex
ملف ot-cli-ftd.hex
لكل لوحة nRF52840.
قم بتوصيل كبل USB بمنفذ تصحيح أخطاء Micro-USB بجوار دبوس الطاقة الخارجي على لوحة nRF52840 ، ثم قم بتوصيله بجهاز Linux الخاص بك. اضبط بشكل صحيح ، LED5 قيد التشغيل.
كما في السابق ، لاحظ الرقم التسلسلي للوحة nRF52840:
انتقل إلى موقع أدوات سطر أوامر nRFx ، وقم بوميض ملف OpenThread CLI FTD السداسي على لوحة nRF52840 ، باستخدام الرقم التسلسلي للوحة:
0 بفة 1030سينطفئ مؤشر 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
.
إذا نجحت ، فسيتم إضاءة إما LED1 أو LED2 أو LED3 على كل لوحة. قد ترى حتى مفتاح LED المضاء من 3 إلى 2 (أو 2 إلى 1) بعد وقت قصير من الوميض (ميزة تغيير دور الجهاز).
يجب الآن تشغيل جميع لوحات nRF52840 الثلاثة وتشغيل تطبيق OpenThread الخاص بنا. كما هو مفصل سابقًا ، يحتوي هذا التطبيق على ميزتين أساسيتين.
مؤشرات دور الجهاز
يعكس مصباح LED المضاء على كل لوحة الدور الحالي لعقدة مؤشر الترابط:
- LED1 = القائد
- LED2 = جهاز التوجيه
- LED3 = الجهاز النهائي
مع تغير الدور ، يتغير مصباح LED المضاء. يجب أن تكون قد شاهدت هذه التغييرات بالفعل على لوحة أو اثنتين في غضون 20 ثانية من تشغيل كل جهاز.
الإرسال المتعدد UDP
عند الضغط على زر 1 على لوحة ، يتم إرسال رسالة UDP إلى عنوان الإرسال المتعدد المحلي المتشابك ، والذي يتضمن جميع العقد الأخرى في شبكة الخيط. استجابةً لتلقي هذه الرسالة ، يقوم مؤشر LED4 على جميع اللوحات الأخرى بالتبديل بين التشغيل أو الإيقاف . يظل LED4 قيد التشغيل أو الإيقاف لكل لوحة حتى يتلقى رسالة UDP أخرى.
الأجهزة التي قمت بوميضها هي نوع معين من أجهزة الخيط الكامل (FTD) تسمى الجهاز النهائي المؤهل لجهاز التوجيه (REED). هذا يعني أنه يمكنهم العمل إما كموجه أو جهاز نهائي ، ويمكنهم الترويج لأنفسهم من جهاز طرفي إلى جهاز توجيه.
يمكن أن يدعم الخيط ما يصل إلى 32 جهاز توجيه ، ولكنه يحاول الاحتفاظ بعدد أجهزة التوجيه بين 16 و 23. إذا تم إرفاق REED كجهاز نهائي وكان عدد أجهزة التوجيه أقل من 16 جهاز توجيه ، فإنه يقوم تلقائيًا بترقية نفسه إلى جهاز توجيه. يجب أن يحدث هذا التغيير في وقت عشوائي خلال عدد الثواني التي قمت بتعيين قيمة otThreadSetRouterSelectionJitter
إليها في التطبيق (20 ثانية).
تحتوي كل شبكة من شبكات الخيط أيضًا على زعيم ، وهو جهاز توجيه مسؤول عن إدارة مجموعة أجهزة التوجيه في شبكة خيط. مع تشغيل جميع الأجهزة ، بعد 20 ثانية ، يجب أن يكون أحدها رائدًا (LED1 قيد التشغيل) والآخران يجب أن يكونا موجهات (LED2 قيد التشغيل).
أزل القائد
إذا تمت إزالة Leader من شبكة Thread ، يقوم جهاز توجيه مختلف بترقية نفسه إلى زعيم ، للتأكد من أن الشبكة لا يزال لديها قائد.
قم بإيقاف تشغيل لوحة Leader (اللوحة التي تحتوي على LED1) باستخدام مفتاح الطاقة . انتظر حوالي 20 ثانية. في إحدى اللوحتين المتبقيتين ، سيتم إيقاف تشغيل LED2 (جهاز التوجيه) وسيتم تشغيل LED1 (القائد). هذا الجهاز هو الآن الرائد في شبكة الموضوع.
أعد تشغيل لوحة Leader الأصلية. يجب أن ينضم تلقائيًا إلى شبكة مؤشر الترابط كجهاز نهائي (يضيء مؤشر LED3). في غضون 20 ثانية (Router Selection Jitter) ، يقوم بترقية نفسه إلى جهاز توجيه (يضيء مؤشر LED2).
أعد ضبط المجالس
قم بإيقاف تشغيل الألواح الثلاثة ، ثم أعد تشغيلها مرة أخرى ولاحظ مصابيح LED. يجب أن تبدأ اللوحة الأولى التي تم تشغيلها في دور القائد (يُضيء مؤشر LED1) - يصبح الموجه الأول في شبكة سلسلة الرسائل تلقائيًا هو القائد.
تتصل اللوحان الأخريان في البداية بالشبكة كأجهزة طرفية (إضاءة LED3) ولكن يجب أن تروج نفسها إلى أجهزة التوجيه (يُضيء مؤشر LED2) في غضون 20 ثانية.
أقسام الشبكة
إذا كانت اللوحات الخاصة بك لا تتلقى طاقة كافية ، أو كان الاتصال اللاسلكي بينهما ضعيفًا ، فقد تنقسم شبكة Thread إلى أقسام وقد يكون لديك أكثر من جهاز يظهر كقائد.
الخيط هو الشفاء الذاتي ، لذلك يجب أن تدمج الأقسام في النهاية مرة أخرى في قسم واحد مع قائد واحد.
في حالة الاستمرار من التمرين السابق ، يجب عدم إضاءة LED4 على أي جهاز.
اختر أي لوحة واضغط على زر 1. يجب أن يقوم مؤشر LED4 الموجود على جميع اللوحات الأخرى في شبكة Thread التي تشغل التطبيق بتبديل حالتها. في حالة الاستمرار من التمرين السابق ، يجب أن يكونوا الآن قيد التشغيل.
اضغط على Button1 لنفس اللوحة مرة أخرى. يجب تبديل مؤشر LED4 الموجود على جميع اللوحات الأخرى مرة أخرى.
اضغط على زر 1 على لوحة مختلفة ولاحظ كيفية تبديل LED4 على اللوحات الأخرى. اضغط على Button1 على إحدى اللوحات حيث يعمل LED4 حاليًا. يظل مؤشر LED4 قيد التشغيل لهذه اللوحة ولكنه يبدل على الألواح الأخرى.
أقسام الشبكة
إذا كانت لوحاتك مقسمة وكان هناك أكثر من قائد واحد بينهم ، فستختلف نتيجة رسالة الإرسال المتعدد بين اللوحات. إذا ضغطت على زر 1 على لوحة مقسمة (وبالتالي فهي العضو الوحيد في شبكة الخيط المقسمة) ، فلن يضيء مؤشر LED4 على اللوحات الأخرى استجابةً لذلك. إذا حدث هذا ، قم بإعادة تعيين اللوحات - من الناحية المثالية سيقومون بإصلاح شبكة خيط واحدة ويجب أن تعمل رسائل UDP بشكل صحيح.
لقد قمت بإنشاء تطبيق يستخدم OpenThread APIs!
أنت تعرف الآن:
- كيفية برمجة الأزرار ومصابيح LED على لوحات تطوير Nordic nRF52840
- كيفية استخدام واجهات برمجة تطبيقات OpenThread الشائعة وفئة
otInstance
- كيفية مراقبة تغيرات حالة OpenThread والرد عليها
- كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة الموضوع
- كيفية تعديل ملفات Makefiles
الخطوات التالية
بناء على Codelab هذا ، جرب التمارين التالية:
- قم بتعديل وحدة GPIO لاستخدام دبابيس GPIO بدلاً من مصابيح LED المدمجة ، وقم بتوصيل مصابيح RGB LED الخارجية التي تغير اللون بناءً على دور جهاز التوجيه
- أضف دعم GPIO لمنصة نموذجية مختلفة
- بدلاً من استخدام البث المتعدد لإجراء اختبار ping لجميع الأجهزة من خلال الضغط على الزر ، استخدم Router / Leader API لتحديد موقع جهاز فردي وإجراء اختبار ping
- قم بتوصيل شبكتك المتداخلة بالإنترنت باستخدام OpenThread Border Router وبثها متعددًا من خارج شبكة Thread لإضاءة مصابيح LED
قراءة متعمقة
تحقق من openthread.io و GitHub للحصول على مجموعة متنوعة من موارد OpenThread ، بما في ذلك:
- المنصات المدعومة - اكتشف جميع المنصات التي تدعم OpenThread
- إنشاء OpenThread - مزيد من التفاصيل حول إنشاء OpenThread وتكوينه
- البرايمر الخيط - مرجع رائع لمفاهيم الخيوط
المرجعي: