التطوير باستخدام واجهات برمجة تطبيقات OpenThread

1 المقدمة

26b7f4f6b3ea0700.png

OpenThread الصادرة عن عش هو تنفيذ المصدر المفتوح من Thread® بروتوكول الشبكات. أصدرت شركة Nest OpenThread لإتاحة التكنولوجيا المستخدمة في منتجات Nest على نطاق واسع للمطورين لتسريع تطوير المنتجات للمنزل المتصل.

و مواصفات الموضوع يحدد بروتوكول يستند الإصدار IPv6، موثوقة وآمنة ومنخفضة الطاقة لاسلكية جهاز إلى جهاز اتصال لتطبيقات المنزل. ينفذ OpenThread جميع طبقات شبكة الخيط بما في ذلك IPv6 و 6LoWPAN و IEEE 802.15.4 مع أمان MAC وإنشاء ارتباط الشبكة وتوجيه الشبكة.

في Codelab هذا ، ستستخدم OpenThread APIs لبدء شبكة Thread ، ومراقبة التغييرات في أدوار الجهاز والرد عليها ، وإرسال رسائل UDP ، بالإضافة إلى ربط هذه الإجراءات بأزرار ومصابيح LED على أجهزة حقيقية.

2a6db2e258c32237.png

ماذا ستتعلم

  • كيفية برمجة الأزرار ومصابيح LED على لوحات تطوير Nordic nRF52840
  • كيفية استخدام واجهات برمجة التطبيقات OpenThread المشتركة و otInstance الطبقة
  • كيفية مراقبة تغيرات حالة OpenThread والرد عليها
  • كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة الموضوع
  • كيفية تعديل ملفات Makefiles

ماذا ستحتاج

المعدات:

  • 3 لوحات ديف نورديك لأشباه الموصلات nRF52840
  • 3 كبلات USB إلى Micro-USB لتوصيل اللوحات
  • جهاز Linux به 3 منافذ USB على الأقل

برمجة:

  • سلسلة أدوات جنو
  • أدوات سطر الأوامر nRF5x الاسكندنافية
  • برنامج Segger J-Link
  • OpenThread
  • شخص سخيف

باستثناء ما ذكر غير ذلك، تم ترخيص محتوى هذا Codelab تحت 3.0 رخصة المشاع المبدع ، ونماذج الشفرات مرخصة بموجب ترخيص Apache 2.0 .

2. البدء

أكمل برنامج Codelab للجهاز

قبل البدء في هذا Codelab، يجب استكمال بناء شبكة الموضوع مع المجالس nRF52840 وOpenThread Codelab، منها:

  • تفاصيل كل البرامج التي تحتاجها للبناء والوميض
  • يعلمك كيفية إنشاء OpenThread ووميضه على لوحات Nordic nRF52840
  • يوضح أساسيات شبكة الموضوع

لم يتم تفصيل أي من البيئة التي تم إعدادها لإنشاء OpenThread ووميض اللوحات في Codelab هذا - فقط الإرشادات الأساسية لوميض اللوحات. من المفترض أنك قد أكملت بالفعل مختبر Codelab الخاص ببناء شبكة مؤشرات الترابط.

أكمل معمل Codelab الخاص ببناء شبكة موضوع

آلة لينكس

تم تصميم Codelab هذا لاستخدام جهاز Linux يستند إلى i386 أو x86 لفلاش جميع لوحات تطوير Thread. تم اختبار جميع الخطوات على Ubuntu 14.04.5 LTS (Trusty Tahr).

لوحات الشمال أشباه الموصلات nRF52840

يستخدم هذا Codelab ثلاثة مجالس nRF52840 PDK .

a6693da3ce213856.png

تثبيت نظام التشغيل

لإنشاء OpenThread وفلاشها ، تحتاج إلى تثبيت SEGGER J-Link وأدوات nRF5x Command Line و ARM GNU Toolchain وحزم Linux المختلفة. إذا كنت قد أكملت إنشاء برنامج ترميز شبكة مؤشر ترابط كما هو مطلوب ، فسيكون لديك بالفعل كل ما تحتاجه مثبتًا. إذا لم يكن الأمر كذلك ، فأكمل Codelab قبل المتابعة للتأكد من أنه يمكنك إنشاء لوحات OpenThread ووميضها إلى nRF52840 dev board.

أكمل معمل Codelab الخاص ببناء شبكة سلاسل الرسائل

3. استنساخ المستودع

يأتي OpenThread مع مثال لرمز التطبيق الذي يمكنك استخدامه كنقطة بداية لـ Codelab هذا.

استنساخ 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 .

باستخدام API

لاستخدام API ، قم بتضمين ملف الرأس الخاص بها في أحد ملفات التطبيق الخاص بك. ثم اتصل بالوظيفة المطلوبة.

على سبيل المثال ، يستخدم تطبيق مثال CLI المضمن مع OpenThread رؤوس API التالية:

./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 نحن ذاهبون الى استخدامها لربط إلى أزرار 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 ماكرو من API OpenThread إلى أخطاء بناء قمع حول المتغيرات غير المستخدمة لبعض toolchains. سنرى أمثلة على ذلك لاحقًا.

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 والمصابيح، راجع الشمال أشباه الموصلات مركز المعلومات .

الإجراء: إضافة رأس يشمل

بعد ذلك ، قم بإضافة الرأس الذي سوف تحتاجه لوظيفة 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);
    }
}

ACTION: حفظ وإغلاق gpio.c الملف.

6. API: الرد على تغييرات دور الجهاز

في تطبيقنا ، نريد أن تضيء مصابيح LED مختلفة اعتمادًا على دور الجهاز. دعنا نتتبع الأدوار التالية: القائد ، جهاز التوجيه ، الجهاز النهائي. يمكننا تخصيصها لمصابيح LED مثل:

  • LED1 = القائد
  • LED2 = جهاز التوجيه
  • LED3 = الجهاز النهائي

لتمكين هذه الوظيفة ، يحتاج التطبيق إلى معرفة متى تغير دور الجهاز وكيفية تشغيل مؤشر LED الصحيح استجابة لذلك. سنستخدم مثيل OpenThread للجزء الأول ، وتجريد منصة GPIO للجزء الثاني.

فتح ./openthread/examples/apps/cli/main.c الملف في محرر النص الخاص بك المفضل.

./openthread/examples/apps/cli/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() وظيفة بعد otAppCliInit المكالمة. يروي هذا التسجيل الاستدعاء OpenThread لاستدعاء handleNetifStateChange وظيفة عند تغيير حالة المثال OpenThread.

/* Register Thread state change handler */
otSetStateChangedCallback(instance, handleNetifStateChanged, instance);

الإجراء: أضف تنفيذ تغيير الحالة

في main.c ، بعد main() وظيفة، وتنفيذ handleNetifStateChanged وظيفة. الشيكات هذه الوظيفة OT_CHANGED_THREAD_ROLE علم المثال OpenThread وإذا كان قد تغير، تتحول الأضواء على / قبالة حسب الحاجة.

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. API: استخدم البث المتعدد لتشغيل LED

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

لتمكين هذه الوظيفة ، يحتاج التطبيق إلى:

  • قم بتهيئة اتصال UDP عند بدء التشغيل
  • تكون قادرًا على إرسال رسالة UDP إلى عنوان الإرسال المتعدد المحلي
  • معالجة رسائل UDP الواردة
  • قم بتبديل LED4 استجابةً لرسائل UDP الواردة

فتح ./openthread/examples/apps/cli/main.c الملف في محرر النص الخاص بك المفضل.

./openthread/examples/apps/cli/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 البيانات، إضافة الثوابت ويعرف 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;

الإجراء: أضف مكالمات لتهيئة مصابيح وزر 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 يربط المقبس إلى واجهة شبكة الموضوع عن طريق تمرير OT_NETIF_THREAD . للحصول على خيارات واجهة الشبكة الأخرى، تشير إلى otNetifIdentifier التعداد في UDP API المرجعي .

الإجراء: تنفيذ رسائل 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. API: تكوين شبكة الموضوع

لتسهيل العرض التوضيحي ، نريد أن تبدأ أجهزتنا على الفور في تشغيل Thread والانضمام إلى شبكة عند تشغيلها. للقيام بذلك، ونحن سوف تستخدم otOperationalDataset هيكل. تحتوي هذه البنية على جميع المعلمات اللازمة لإرسال بيانات اعتماد شبكة مؤشر الترابط إلى جهاز.

سيؤدي استخدام هذه البنية إلى تجاوز الإعدادات الافتراضية للشبكة المضمنة في OpenThread ، لجعل تطبيقنا أكثر أمانًا وقصر عقد Thread في شبكتنا على أولئك الذين يقومون بتشغيل التطبيق فقط.

مرة أخرى، فتح ./openthread/examples/apps/cli/main.c الملف في محرر النص الخاص بك المفضل.

./openthread/examples/apps/cli/main.c

الإجراء: إضافة رأس تضمين

داخل في الجزء العلوي من يشمل main.c الملف، إضافة ملف رأس API ستحتاج إلى تكوين شبكة الموضوع:

#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 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 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);
}

كما هو مفصل في الوظيفة ، معلمات شبكة مؤشر الترابط التي نستخدمها لهذا التطبيق هي:

  • القناة = 15
  • معرف PAN = 0x2222
  • معرف PAN الممتد = C0DE1AB5C0DE1AB5
  • مفتاح الشبكة = 1234C0DE1AB51234C0DE1AB51234C0DE
  • اسم الشبكة = OTCodelab

بالإضافة إلى ذلك ، هذا هو المكان الذي نقوم فيه بتقليل اهتزاز تحديد جهاز التوجيه ، لذلك تقوم أجهزتنا بتغيير الأدوار بشكل أسرع لأغراض العرض التوضيحي. لاحظ أن هذا يتم فقط إذا كانت العقدة عبارة عن FTD (جهاز خيط كامل). المزيد عن ذلك في الخطوة التالية.

9. API: وظائف مقيدة

تقوم بعض واجهات برمجة تطبيقات OpenThread بتعديل الإعدادات التي يجب تعديلها فقط لأغراض العرض التجريبي أو الاختبار. لا ينبغي استخدام واجهات برمجة التطبيقات هذه في نشر إنتاج لتطبيق يستخدم OpenThread.

على سبيل المثال، otThreadSetRouterSelectionJitter وظيفة ضبط الوقت (بالثواني) الذي يستغرقه لجهاز النهاية للترويج لنفسها لراوتر. القيمة الافتراضية لهذه القيمة هي 120 ، وفقًا لمواصفات مؤشر الترابط. لسهولة الاستخدام في Codelab هذا ، سنقوم بتغييره إلى 20 ، لذلك لا يتعين عليك الانتظار طويلاً حتى تغير عقدة مؤشر الترابط الأدوار.

لا تصبح الأجهزة MTD أجهزة التوجيه، ودعم وظيفة مثل: مذكرة otThreadSetRouterSelectionJitter لا يتم تضمين في بناء مليون دينار. في وقت لاحق ونحن بحاجة إلى تحديد 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. يتم استخدام هذه من قبل نظام البناء لترجمة وربط التطبيق الخاص بك.

./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

الآن إضافة جديدة gpio.c الملف إلى ./src/CMakeLists.txt الملف.

الإجراء: أضف مصدر 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 الملف، لذلك يتم تضمينه مع بناء مكتبة للسائقين الشمال.

الإجراء: أضف برنامج تشغيل gpio إلى NordicSemiconductor CMakeLists.txt

مفتوحة ./third_party/NordicSemiconductor/CMakeLists.txt في محرر النص الخاص بك المفضل، وإضافة الملف إلى COMMON_SOURCES القسم.

...

set(COMMON_SOURCES
  ...
  nrfx/drivers/src/nrfx_gpiote.c
  ...
)
...

11. إعداد الأجهزة

بعد الانتهاء من جميع تحديثات التعليمات البرمجية ، فأنت جاهز لإنشاء التطبيق وفلاشته لجميع لوحات تطوير Nordic nRF52840 الثلاثة. سيعمل كل جهاز كجهاز خيط كامل (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 إلى منفذ التصحيح الجزئي USB بجانب دبوس الطاقة الخارجية في مجلس nRF52840، ومن ثم توصيله إلى جهاز لينكس الخاص بك. تعيين بشكل صحيح، LED5 على.

20a3b4b480356447.png

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

c00d519ebec7e5f0.jpeg

انتقل إلى موقع أدوات سطر أوامر nRFx ، وقم بوميض ملف OpenThread CLI FTD السداسي على لوحة nRF52840 ، باستخدام الرقم التسلسلي للوحة:

$ cd ~/nrfjprog
$ ./nrfjprog -f nrf52 -s 683704924 --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 قيادة وامض.

إذا نجحت ، فسيتم إضاءة إما LED1 أو LED2 أو LED3 على كل لوحة. قد ترى حتى مفتاح LED المضاء من 3 إلى 2 (أو 2 إلى 1) بعد وقت قصير من الوميض (ميزة تغيير دور الجهاز).

12. وظائف التطبيق

يجب الآن تشغيل جميع لوحات nRF52840 الثلاثة وتشغيل تطبيق OpenThread الخاص بنا. كما هو مفصل سابقًا ، يحتوي هذا التطبيق على ميزتين أساسيتين.

مؤشرات دور الجهاز

يعكس مصباح LED المضاء على كل لوحة الدور الحالي لعقدة مؤشر الترابط:

  • LED1 = القائد
  • LED2 = جهاز التوجيه
  • LED3 = الجهاز النهائي

مع تغير الدور ، يتغير مصباح LED المضاء. يجب أن تكون قد شاهدت هذه التغييرات بالفعل على لوحة أو اثنتين خلال 20 ثانية من تشغيل كل جهاز.

الإرسال المتعدد UDP

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

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. العرض التوضيحي: لاحظ تغييرات دور الجهاز

الأجهزة التي قمت بوميضها هي نوع معين من أجهزة الخيط الكامل (FTD) تسمى الجهاز النهائي المؤهل لجهاز التوجيه (REED). هذا يعني أنه يمكنهم العمل إما كموجه أو جهاز نهائي ، ويمكنهم الترويج لأنفسهم من جهاز طرفي إلى جهاز توجيه.

يمكن أن يدعم الخيط ما يصل إلى 32 جهاز توجيه ، ولكنه يحاول الاحتفاظ بعدد أجهزة التوجيه بين 16 و 23. إذا تم إرفاق REED كجهاز نهائي وكان عدد أجهزة التوجيه أقل من 16 جهاز توجيه ، فإنه يقوم تلقائيًا بترقية نفسه إلى جهاز توجيه. يجب أن يحدث هذا التغيير في وقت عشوائي ضمن عدد الثواني التي قمت بتعيين otThreadSetRouterSelectionJitter قيمة لفي التطبيق (20 ثانية).

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

4e1e885861a66570.png

أزل القائد

إذا تمت إزالة Leader من شبكة Thread ، يقوم جهاز توجيه مختلف بترقية نفسه إلى زعيم ، للتأكد من أن الشبكة لا يزال لديها قائد.

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

4c57c87adb40e0e3.png

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

5f40afca2dcc4b5b.png

أعد ضبط المجالس

قم بإيقاف تشغيل الألواح الثلاثة ، ثم أعد تشغيلها مرة أخرى ولاحظ المصابيح. يجب أن تبدأ اللوحة الأولى التي تم تشغيلها في دور القائد (يُضيء مؤشر LED1) - يصبح الموجه الأول في شبكة سلسلة الرسائل تلقائيًا هو القائد.

تتصل اللوحان الأخريان في البداية بالشبكة كأجهزة طرفية (يُضيء مؤشر LED3) ولكن يجب أن يروجوا لأنفسهم إلى أجهزة التوجيه (يُضيء مؤشر LED2) في غضون 20 ثانية.

أقسام الشبكة

إذا كانت اللوحات الخاصة بك لا تتلقى طاقة كافية ، أو كان الاتصال اللاسلكي بينهما ضعيفًا ، فقد تنقسم شبكة Thread إلى أقسام وقد يكون لديك أكثر من جهاز واحد يظهر كقائد.

الخيط هو شفاء ذاتي ، لذلك يجب أن تدمج الأقسام في النهاية مرة أخرى في قسم واحد مع قائد واحد.

14. العرض التوضيحي: إرسال UDP المتعدد

في حالة استمرار التمرين السابق ، يجب ألا يضيء مؤشر LED4 على أي جهاز.

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

f186a2618fdbe3fd.png

اضغط على Button1 لنفس اللوحة مرة أخرى. يجب تبديل مؤشر LED4 الموجود على جميع اللوحات الأخرى مرة أخرى.

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

f5865ccb8ab7aa34.png

أقسام الشبكة

إذا كانت لوحاتك مقسمة وكان هناك أكثر من قائد بينهم ، فستختلف نتيجة رسالة الإرسال المتعدد بين اللوحات. إذا ضغطت على زر 1 على لوحة مقسمة (وبالتالي فهي العضو الوحيد في شبكة الخيط المقسمة) ، فلن يضيء مؤشر LED4 على اللوحات الأخرى استجابةً لذلك. إذا حدث هذا ، قم بإعادة تعيين اللوحات - من الناحية المثالية سيقومون بإصلاح شبكة خيط واحدة ويجب أن تعمل رسائل UDP بشكل صحيح.

15. مبروك!

لقد قمت بإنشاء تطبيق يستخدم OpenThread APIs!

أنت تعرف الآن:

  • كيفية برمجة الأزرار ومصابيح LED على لوحات تطوير Nordic nRF52840
  • كيفية استخدام واجهات برمجة التطبيقات OpenThread المشتركة و otInstance الطبقة
  • كيفية مراقبة التغييرات في حالة OpenThread والرد عليها
  • كيفية إرسال رسائل UDP إلى جميع الأجهزة في شبكة الموضوع
  • كيفية تعديل ملفات Makefiles

الخطوات التالية

بناء على Codelab هذا ، جرب التمارين التالية:

  • قم بتعديل وحدة GPIO لاستخدام دبابيس GPIO بدلاً من مصابيح LED المدمجة ، وقم بتوصيل مصابيح RGB LED الخارجية التي تغير اللون بناءً على دور جهاز التوجيه
  • أضف دعم GPIO لمنصة نموذجية مختلفة
  • بدلا من استخدام المتعدد لبينغ جميع الأجهزة من زر الصحافة، واستخدام API التوجيه / قائد لتحديد مكان وبينغ عبوة الفردي
  • ربط شبكة سلكية إلى شبكة الإنترنت باستخدام راوتر الحدود OpenThread والمتعدد منهم من خارج الشبكة الموضوع إلى ضوء المصابيح

قراءة متعمقة

تحقق من openthread.io و جيثب لمجموعة متنوعة من الموارد OpenThread، بما في ذلك:

المرجعي: