در حال توسعه با API های OpenThread

1. معرفی

26b7f4f6b3ea0700.png

OpenThread منتشر شده توسط Nest یک پیاده سازی متن باز از پروتکل شبکه Thread® است. Nest OpenThread را منتشر کرده است تا فناوری مورد استفاده در محصولات Nest را به طور گسترده در دسترس توسعه دهندگان قرار دهد تا توسعه محصولات برای خانه متصل را تسریع بخشد.

مشخصات Thread یک پروتکل ارتباطی دستگاه به دستگاه بی سیم قابل اعتماد، ایمن و کم مصرف مبتنی بر IPv6 را برای برنامه های خانگی تعریف می کند. OpenThread تمام لایه های شبکه Thread از جمله IPv6، 6LoWPAN، IEEE 802.15.4 را با امنیت MAC، Mesh Link Establishment و Mesh Routing پیاده سازی می کند.

در این Codelab، شما از OpenThread API برای راه اندازی شبکه Thread، نظارت و واکنش به تغییرات در نقش های دستگاه، ارسال پیام های UDP، و همچنین گره زدن این اقدامات به دکمه ها و LED ها در سخت افزار واقعی استفاده خواهید کرد.

2a6db2e258c32237.png

چیزی که یاد خواهید گرفت

  • نحوه برنامه ریزی دکمه ها و LED ها در بردهای توسعه دهنده Nordic nRF52840
  • نحوه استفاده از APIهای OpenThread معمول و کلاس otInstance
  • نحوه نظارت و واکنش به تغییرات حالت OpenThread
  • نحوه ارسال پیام های UDP به همه دستگاه های موجود در شبکه Thread
  • نحوه اصلاح Makefiles

آنچه شما نیاز دارید

سخت افزار:

  • 3 برد توسعه دهنده Nordic Semiconductor nRF52840
  • 3 کابل USB به Micro-USB برای اتصال بردها
  • یک دستگاه لینوکس با حداقل 3 پورت USB

نرم افزار:

  • زنجیره ابزار گنو
  • ابزارهای خط فرمان Nordic nRF5x
  • نرم افزار Segger J-Link
  • OpenThread
  • Git

به جز موارد ذکر شده، محتوای این Codelab تحت مجوز Creative Commons Attribution 3.0 و نمونه کد تحت مجوز Apache 2.0 مجوز دارند .

2. شروع به کار

Hardware Codelab را کامل کنید

قبل از شروع این Codelab، باید ساخت شبکه موضوعی را با تابلوهای nRF52840 و OpenThread Codelab تکمیل کنید که:

  • جزئیات تمام نرم افزارهای مورد نیاز برای ساخت و فلش کردن
  • به شما یاد می دهد که چگونه OpenThread بسازید و آن را روی بردهای Nordic nRF52840 فلش کنید.
  • اصول اولیه یک شبکه Thread را نشان می دهد

هیچ یک از محیط های تنظیم شده مورد نیاز برای ساخت OpenThread و فلش کردن بردها در این Codelab به تفصیل ذکر نشده است - فقط دستورالعمل های اولیه برای فلش کردن بردها. فرض بر این است که شما قبلاً Build a Thread Network Codelab را تکمیل کرده اید.

ماشین لینوکس

این Codelab برای استفاده از یک ماشین لینوکس مبتنی بر i386 یا x86 برای فلش کردن همه بردهای توسعه Thread طراحی شده است. همه مراحل در اوبونتو 14.04.5 LTS (Trusty Tahr) آزمایش شدند.

بردهای Nordic Semiconductor nRF52840

این Codelab از سه برد nRF52840 PDK استفاده می کند.

a6693da3ce213856.png

نصب نرم افزار

برای ساخت و فلش OpenThread، باید SEGGER J-Link، ابزارهای خط فرمان nRF5x، زنجیره ابزار ARM GNU و بسته های مختلف لینوکس را نصب کنید. اگر طبق نیاز Build a Thread Network Codelab را تکمیل کرده باشید، همه چیزهایی را که نیاز دارید نصب کرده اید. در غیر این صورت، قبل از ادامه این کار، Codelab را تکمیل کنید تا مطمئن شوید که می‌توانید OpenThread را روی بردهای توسعه‌دهنده nRF52840 بسازید و فلش کنید.

3. مخزن را شبیه سازی کنید

OpenThread همراه با کد برنامه‌ای است که می‌توانید به عنوان نقطه شروع برای این Codelab استفاده کنید.

مخزن نمونه OpenThread Nordic nRF528xx را کلون کنید و OpenThread را بسازید:

$ git clone --recursive https://github.com/openthread/ot-nrf528xx
$ cd ot-nrf528xx
$ ./script/bootstrap

4. مبانی OpenThread API

APIهای عمومی OpenThread در ./openthread/include/openthread در مخزن OpenThread قرار دارند. این APIها دسترسی به انواع ویژگی‌ها و عملکردهای OpenThread را هم در سطح Thread و هم در سطح پلتفرم برای استفاده در برنامه‌های شما فراهم می‌کنند:

  • اطلاعات و کنترل نمونه OpenThread
  • خدمات کاربردی مانند IPv6، UDP و CoAP
  • مدیریت اعتبار شبکه، همراه با نقش های کمیسیونر و جوینر
  • مدیریت روتر مرزی
  • ویژگی های پیشرفته مانند نظارت بر کودک و تشخیص جم

اطلاعات مرجع در مورد همه APIهای 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 چیزی است که هنگام کار با APIهای OpenThread اغلب از آن استفاده خواهید کرد. پس از شروع اولیه، این ساختار نمونه ایستا از کتابخانه OpenThread را نشان می دهد و به کاربر اجازه می دهد تا فراخوانی های OpenThread API را انجام دهد.

به عنوان مثال، نمونه OpenThread در تابع main() برنامه نمونه CLI مقداردهی اولیه می شود:

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

int main(int argc, char *argv[])
{
    otInstance *instance

...

#if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES
    // Call to query the buffer size
    (void)otInstanceInit(NULL, &otInstanceBufferLength);

    // Call to allocate the buffer
    otInstanceBuffer = (uint8_t *)malloc(otInstanceBufferLength);
    assert(otInstanceBuffer);

    // Initialize OpenThread with the buffer
    instance = otInstanceInit(otInstanceBuffer, &otInstanceBufferLength);
#else
    instance = otInstanceInitSingle();
#endif

...

    return 0;
}

توابع خاص پلت فرم

اگر می‌خواهید توابع خاص پلتفرم را به یکی از برنامه‌های کاربردی نمونه موجود با OpenThread اضافه کنید، ابتدا آنها را در هدر ./openthread/examples/platforms/openthread-system.h با استفاده از فضای نام otSys برای همه توابع اعلام کنید. سپس آنها را در یک فایل منبع خاص پلتفرم پیاده سازی کنید. با انتزاع از این طریق، می توانید از همان هدر تابع برای پلتفرم های نمونه دیگر استفاده کنید.

برای مثال، توابع GPIO که قرار است از آنها برای اتصال به دکمه‌ها و LED‌های nRF52840 استفاده کنیم، باید در openthread-system.h اعلان شوند.

فایل ./openthread/examples/platforms/openthread-system.h را در ویرایشگر متن دلخواه خود باز کنید.

./openthread/examples/platforms/openthread-system.h

ACTION: اعلان‌های عملکرد GPIO مخصوص پلتفرم را اضافه کنید.

این اعلان‌های تابع را بعد از #include برای سربرگ openthread/instance.h اضافه کنید:

/**
 * Init LED module.
 *
 */
void otSysLedInit(void);
void otSysLedSet(uint8_t aLed, bool aOn);
void otSysLedToggle(uint8_t aLed);

/**
* A callback will be called when GPIO interrupts occur.
*
*/
typedef void (*otSysButtonCallback)(otInstance *aInstance);
void otSysButtonInit(otSysButtonCallback aCallback);
void otSysButtonProcess(otInstance *aInstance);

در مرحله بعد اینها را اجرا خواهیم کرد.

توجه داشته باشید که اعلان تابع otSysButtonProcess از یک otInstance استفاده می کند. به این ترتیب برنامه می تواند در صورت نیاز به اطلاعات مربوط به نمونه OpenThread در صورت فشار دادن یک دکمه دسترسی پیدا کند. این همه به نیازهای برنامه شما بستگی دارد. اگر در اجرای تابع به آن نیاز ندارید، می‌توانید از ماکرو OT_UNUSED_VARIABLE از OpenThread API برای سرکوب خطاهای ساخت پیرامون متغیرهای استفاده نشده برای برخی از زنجیره‌های ابزار استفاده کنید. نمونه هایی از این را در ادامه خواهیم دید.

5. انتزاع پلتفرم GPIO را پیاده سازی کنید

در مرحله قبل، به بررسی اعلان‌های تابع خاص پلتفرم در ./openthread/examples/platforms/openthread-system.h پرداختیم که می‌تواند برای GPIO استفاده شود. برای دسترسی به دکمه‌ها و LED‌های روی بردهای توسعه‌دهنده nRF52840، باید آن عملکردها را برای پلتفرم nRF52840 پیاده‌سازی کنید. در این کد، توابعی را اضافه خواهید کرد که:

  • پین ها و حالت های GPIO را راه اندازی کنید
  • ولتاژ یک پین را کنترل کنید
  • وقفه های GPIO را فعال کنید و یک تماس پاسخ ثبت کنید

در پوشه ./src/src ، یک فایل جدید با نام gpio.c ایجاد کنید. در این فایل جدید مطالب زیر را اضافه کنید.

./src/src/gpio.c (فایل جدید)

ACTION: تعریف را اضافه کنید.

این تعریف‌ها به‌عنوان انتزاع‌هایی بین مقادیر خاص nRF52840 و متغیرهای مورد استفاده در سطح برنامه OpenThread عمل می‌کنند.

/**
 * @file
 *   This file implements the system abstraction for GPIO and GPIOTE.
 *
 */

#define BUTTON_GPIO_PORT 0x50000300UL
#define BUTTON_PIN 11 // button #1

#define GPIO_LOGIC_HI 0
#define GPIO_LOGIC_LOW 1

#define LED_GPIO_PORT 0x50000300UL
#define LED_1_PIN 13 // turn on to indicate leader role
#define LED_2_PIN 14 // turn on to indicate router role
#define LED_3_PIN 15 // turn on to indicate child role
#define LED_4_PIN 16 // turn on to indicate UDP receive

برای اطلاعات بیشتر در مورد دکمه ها و LED های nRF52840، به مرکز اطلاعات نیمه هادی نوردیک مراجعه کنید.

ACTION: اضافه کردن هدر شامل.

در مرحله بعد، هدر شامل موارد مورد نیاز برای عملکرد 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"

ACTION: برای دکمه 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;
}

ACTION: یک تابع برای پیکربندی 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);
}

ACTION: یک تابع برای تنظیم حالت 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;
    }
}

ACTION: برای تغییر حالت LED یک تابع اضافه کنید.

هنگامی که دستگاه یک پیام UDP چندپخشی دریافت می کند، از این عملکرد برای تغییر حالت LED4 استفاده می شود.

/**
 * @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;
    }
}

ACTION: برای مقداردهی اولیه و پردازش فشار دکمه، توابعی را اضافه کنید.

تابع اول برد را برای فشار دادن دکمه مقدار دهی اولیه می کند و دومی با فشار دادن دکمه 1 پیام UDP چندپخشی را ارسال می کند.

/**
 * @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. API: به تغییرات نقش دستگاه واکنش نشان دهید

در برنامه ما، ما می خواهیم LED های مختلف بسته به نقش دستگاه روشن شوند. بیایید نقش های زیر را دنبال کنیم: رهبر، روتر، دستگاه پایان. می‌توانیم آنها را به LED‌هایی مانند زیر اختصاص دهیم:

  • LED1 = لیدر
  • LED2 = روتر
  • LED3 = دستگاه پایان

برای فعال کردن این عملکرد، برنامه باید بداند که نقش دستگاه چه زمانی تغییر کرده است و چگونه LED صحیح را در پاسخ روشن کند. برای قسمت اول از نمونه OpenThread و برای قسمت دوم از انتزاع پلتفرم GPIO استفاده خواهیم کرد.

فایل ./openthread/examples/apps/cli/main.c را در ویرایشگر متن دلخواه خود باز کنید.

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

ACTION: اضافه کردن هدر شامل.

در بخش شامل فایل main.c ، فایل‌های هدر API را که برای ویژگی تغییر نقش نیاز دارید، اضافه کنید.

#include <openthread/instance.h>
#include <openthread/thread.h>
#include <openthread/thread_ftd.h>

ACTION: برای تغییر حالت نمونه OpenThread، اعلان تابع handler را اضافه کنید.

این اعلان را بعد از هدر و قبل از هر عبارت #if به main.c اضافه کنید. این تابع بعد از برنامه اصلی تعریف می شود.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

ACTION: برای عملکرد کنترل کننده تغییر حالت یک ثبت نام برگشت به تماس اضافه کنید.

در main.c ، این تابع را پس از فراخوانی otAppCliInit به تابع main() اضافه کنید. این ثبت تماس برگشتی به OpenThread می گوید که هر زمان که وضعیت نمونه OpenThread تغییر کرد، تابع handleNetifStateChange را فراخوانی کند.

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

ACTION: اجرای تغییر حالت را اضافه کنید.

در 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. API: از Multicast برای روشن کردن LED استفاده کنید

در برنامه ما، ما همچنین می خواهیم پیام های UDP را به تمام دستگاه های دیگر در شبکه ارسال کنیم، زمانی که دکمه 1 روی یک برد فشار داده می شود. برای تأیید دریافت پیام، در پاسخ، LED4 را روی سایر بردها تغییر می دهیم.

برای فعال کردن این قابلیت، برنامه باید:

  • هنگام راه اندازی، اتصال UDP را راه اندازی کنید
  • قادر به ارسال یک پیام UDP به آدرس چندپخشی mesh-local باشید
  • مدیریت پیام های UDP دریافتی
  • LED4 را در پاسخ به پیام‌های UDP دریافتی روشن کنید

فایل ./openthread/examples/apps/cli/main.c را در ویرایشگر متن دلخواه خود باز کنید.

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

ACTION: اضافه کردن هدر شامل.

در قسمت شامل در بالای فایل 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 استفاده می‌شود که شرایط زمان اجرا را تأیید می‌کنند و خطاها را به خوبی مدیریت می‌کنند.

ACTION: تعریف ها و ثابت ها را اضافه کنید:

در فایل main.c ، بعد از بخش include و قبل از هر دستور #if ، ثابت های خاص UDP را اضافه کنید و تعریف کنید:

#define UDP_PORT 1212

static const char UDP_DEST_ADDR[] = "ff03::1";
static const char UDP_PAYLOAD[]   = "Hello OpenThread World!";

ff03::1 آدرس چندپخشی محلی مش است. هر پیامی که به این آدرس ارسال شود به تمام دستگاه های Full Thread در شبکه ارسال می شود. برای اطلاعات بیشتر در مورد پشتیبانی چندپخشی در OpenThread به Multicast در openthread.io مراجعه کنید.

ACTION: اعلان های تابع را اضافه کنید.

در فایل 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;

ACTION: برای مقداردهی اولیه LED ها و دکمه GPIO تماس ها را اضافه کنید.

در main.c ، این فراخوانی های تابع را پس از فراخوانی otSetStateChangedCallback به تابع main() اضافه کنید. این توابع پین‌های GPIO و GPIOTE را مقداردهی اولیه می‌کنند و یک کنترل‌کننده دکمه را برای مدیریت رویدادهای فشار دکمه تنظیم می‌کنند.

/* init GPIO LEDs and button */
otSysLedInit();
otSysButtonInit(handleButtonInterrupt);

ACTION: فراخوان اولیه UDP را اضافه کنید.

در main.c ، این تابع را پس از فراخوانی otSysButtonInit که به تازگی اضافه کردید به تابع main() اضافه کنید:

initUdp(instance);

این تماس تضمین می‌کند که یک سوکت UDP پس از راه‌اندازی برنامه، مقداردهی اولیه می‌شود. بدون این، دستگاه نمی تواند پیام های UDP را ارسال یا دریافت کند.

اقدام: برای پردازش رویداد دکمه GPIO، تماس را اضافه کنید.

در main.c ، پس از فراخوانی otSysProcessDrivers ، در حلقه while ، این فراخوانی تابع را به تابع main() اضافه کنید. این تابع، که در gpio.c اعلام شده است، بررسی می کند که آیا دکمه فشرده شده است یا خیر، و در این صورت، کنترل کننده ( handleButtonInterrupt ) را که در مرحله بالا تنظیم شده است، فراخوانی می کند.

otSysButtonProcess(instance);

ACTION: اجرای کنترل کننده وقفه دکمه.

در main.c ، اجرای تابع handleButtonInterrupt را بعد از تابع handleNetifStateChanged که در مرحله قبل اضافه کردید اضافه کنید:

/**
 * Function to handle button push event
 */
void handleButtonInterrupt(otInstance *aInstance)
{
    sendUdp(aInstance);
}

ACTION: پیاده سازی مقداردهی اولیه 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 ، سوکت را به رابط شبکه 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 ، جایی که بافر را آزاد می کند، خطاها را به خوبی مدیریت می کند.

برای اطلاعات بیشتر در مورد توابع مورد استفاده برای مقداردهی اولیه UDP به مراجع IPv6 و UDP در openthread.io مراجعه کنید.

اقدام: مدیریت پیام UDP را پیاده سازی کنید.

در main.c ، اجرای تابع handleUdpReceive را بعد از تابع sendUdp که به تازگی اضافه کرده اید اضافه کنید. این عملکرد به سادگی LED4 را تغییر می دهد.

/**
 * Function to handle UDP datagrams received on the listening socket
 */
void handleUdpReceive(void *aContext, otMessage *aMessage,
                      const otMessageInfo *aMessageInfo)
{
    OT_UNUSED_VARIABLE(aContext);
    OT_UNUSED_VARIABLE(aMessage);
    OT_UNUSED_VARIABLE(aMessageInfo);

    otSysLedToggle(4);
}

8. API: شبکه Thread را پیکربندی کنید

برای سهولت نمایش، می‌خواهیم دستگاه‌های ما بلافاصله Thread را راه‌اندازی کنند و هنگامی که روشن می‌شوند به یک شبکه بپیوندند. برای این کار از ساختار otOperationalDataset استفاده می کنیم. این ساختار تمام پارامترهای مورد نیاز برای انتقال اعتبار شبکه Thread به یک دستگاه را در خود جای می دهد.

استفاده از این ساختار، پیش‌فرض‌های شبکه تعبیه‌شده در OpenThread را لغو می‌کند تا برنامه ما امن‌تر شود و گره‌های Thread در شبکه ما فقط به آنهایی که برنامه را اجرا می‌کنند محدود کند.

دوباره فایل ./openthread/examples/apps/cli/main.c را در ویرایشگر متن دلخواه خود باز کنید.

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

ACTION: اضافه کردن هدر شامل.

در قسمت شامل در بالای فایل main.c ، فایل هدر API را که برای پیکربندی شبکه Thread به آن نیاز دارید اضافه کنید:

#include <openthread/dataset_ftd.h>

ACTION: برای تنظیم پیکربندی شبکه، اعلان تابع را اضافه کنید.

این اعلان را بعد از هدر و قبل از هر عبارت #if به main.c اضافه کنید. این تابع بعد از تابع اصلی برنامه تعریف می شود.

static void setNetworkConfiguration(otInstance *aInstance);

اقدام: تماس پیکربندی شبکه را اضافه کنید.

در main.c ، این فراخوانی تابع را پس از فراخوانی otSetStateChangedCallback به تابع main() اضافه کنید. این تابع مجموعه داده شبکه Thread را پیکربندی می کند.

/* Override default network credentials */
setNetworkConfiguration(instance);

ACTION: برای فعال کردن رابط شبکه Thread و پشته تماس ها را اضافه کنید.

در main.c ، پس از فراخوانی otSysButtonInit ، این فراخوانی های تابع را به تابع main() اضافه کنید.

/* Start the Thread network interface (CLI cmd > ifconfig up) */
otIp6SetEnabled(instance, true);

/* Start the Thread stack (CLI cmd > thread start) */
otThreadSetEnabled(instance, true);

اقدام: پیکربندی شبکه Thread را پیاده سازی کنید.

در main.c ، اجرای تابع setNetworkConfiguration را بعد از تابع main() اضافه کنید:

/**
 * Override default network settings, such as panid, so the devices can join a
 network
 */
void setNetworkConfiguration(otInstance *aInstance)
{
    static char          aNetworkName[] = "OTCodelab";
    otOperationalDataset aDataset;

    memset(&aDataset, 0, sizeof(otOperationalDataset));

    /*
     * Fields that can be configured in otOperationDataset to override defaults:
     *     Network Name, Mesh Local Prefix, Extended PAN ID, PAN ID, Delay Timer,
     *     Channel, Channel Mask Page 0, Network Key, PSKc, Security Policy
     */
    aDataset.mActiveTimestamp.mSeconds             = 1;
    aDataset.mActiveTimestamp.mTicks               = 0;
    aDataset.mActiveTimestamp.mAuthoritative       = false;
    aDataset.mComponents.mIsActiveTimestampPresent = true;

    /* Set Channel to 15 */
    aDataset.mChannel                      = 15;
    aDataset.mComponents.mIsChannelPresent = true;

    /* Set Pan ID to 2222 */
    aDataset.mPanId                      = (otPanId)0x2222;
    aDataset.mComponents.mIsPanIdPresent = true;

    /* Set Extended Pan ID to C0DE1AB5C0DE1AB5 */
    uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xC0, 0xDE, 0x1A, 0xB5, 0xC0, 0xDE, 0x1A, 0xB5};
    memcpy(aDataset.mExtendedPanId.m8, extPanId, sizeof(aDataset.mExtendedPanId));
    aDataset.mComponents.mIsExtendedPanIdPresent = true;

    /* Set network key to 1234C0DE1AB51234C0DE1AB51234C0DE */
    uint8_t key[OT_NETWORK_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE};
    memcpy(aDataset.mNetworkKey.m8, key, sizeof(aDataset.mNetworkKey));
    aDataset.mComponents.mIsNetworkKeyPresent = true;

    /* Set Network Name to OTCodelab */
    size_t length = strlen(aNetworkName);
    assert(length <= OT_NETWORK_NAME_MAX_SIZE);
    memcpy(aDataset.mNetworkName.m8, aNetworkName, length);
    aDataset.mComponents.mIsNetworkNamePresent = true;

    otDatasetSetActive(aInstance, &aDataset);
    /* Set the router selection jitter to override the 2 minute default.
       CLI cmd > routerselectionjitter 20
       Warning: For demo purposes only - not to be used in a real product */
    uint8_t jitterValue = 20;
    otThreadSetRouterSelectionJitter(aInstance, jitterValue);
}

همانطور که در تابع توضیح داده شده است، پارامترهای شبکه Thread که ما برای این برنامه استفاده می کنیم عبارتند از:

  • کانال = 15
  • PAN ID = 0x2222
  • شناسه PAN توسعه یافته = C0DE1AB5C0DE1AB5
  • کلید شبکه = 1234C0DE1AB51234C0DE1AB51234C0DE
  • نام شبکه = OTCodelab

علاوه بر این، این جایی است که ما لرزش انتخاب مسیریاب را کاهش می‌دهیم، بنابراین دستگاه‌های ما برای اهداف نمایشی سریع‌تر نقش‌ها را تغییر می‌دهند. توجه داشته باشید که این کار تنها در صورتی انجام می شود که گره یک FTD (دستگاه تمام نخ) باشد. در مرحله بعد در مورد آن بیشتر توضیح دهید.

9. API: توابع محدود

برخی از APIهای OpenThread تنظیماتی را تغییر می دهند که فقط باید برای اهداف آزمایشی یا آزمایشی اصلاح شوند. این APIها نباید در استقرار تولید یک برنامه با استفاده از OpenThread استفاده شوند.

به عنوان مثال، تابع otThreadSetRouterSelectionJitter زمان (بر حسب ثانیه) را تنظیم می کند که یک دستگاه End Device خود را به یک روتر ارتقا دهد. پیش‌فرض برای این مقدار 120 است، بر اساس مشخصات موضوع. برای سهولت استفاده در این Codelab، ما آن را به 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. C به روز رسانی کنید

قبل از اینکه برنامه خود را بسازید، چند به روز رسانی جزئی برای سه فایل CMake مورد نیاز است. اینها توسط سیستم ساخت برای کامپایل و پیوند دادن برنامه شما استفاده می شود.

./third_party/NordicSemiconductor/CMakeLists.txt

اکنون چند پرچم را به NordicSemiconductor CMakeLists.txt اضافه کنید تا مطمئن شوید که توابع GPIO در برنامه تعریف شده اند.

ACTION: پرچم ها را به فایل 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 را اضافه کنید:

ACTION: منبع gpio را به فایل ./src/CMakeLists.txt اضافه کنید .

./src/CMakeLists.txt در ویرایشگر متن دلخواه خود باز کنید و فایل را به بخش NRF_COMM_SOURCES اضافه کنید.

...

set(NRF_COMM_SOURCES
  ...
  src/gpio.c
  ...
)

...

./third_party/NordicSemiconductor/CMakeLists.txt

در نهایت، فایل درایور nrfx_gpiote.c را به فایل NordicSemiconductor CMakeLists.txt اضافه کنید تا با ساخت کتابخانه درایورهای Nordic همراه شود.

اقدام: درایور gpio را به فایل NordicSemiconductor CMakeLists.txt اضافه کنید .

./third_party/NordicSemiconductor/CMakeLists.txt را در ویرایشگر متن دلخواه خود باز کنید و فایل را به بخش COMMON_SOURCES اضافه کنید.

...

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

11. دستگاه ها را راه اندازی کنید

با انجام تمام به‌روزرسانی‌های کد، آماده ساختن و فلش کردن برنامه روی هر سه برد توسعه‌دهنده Nordic nRF52840 هستید. هر دستگاه به عنوان یک دستگاه تمام نخ (FTD) عمل خواهد کرد.

OpenThread را بسازید

باینری های OpenThread FTD را برای پلتفرم nRF52840 بسازید.

$ cd ~/ot-nrf528xx
$ ./script/build nrf52840 UART_trans -DOT_MTD=OFF -DOT_APP_RCP=OFF -DOT_RCP=OFF

با باینری OpenThread FTD CLI به دایرکتوری بروید و با ARM Embedded Toolchain آن را به فرمت هگز تبدیل کنید:

$ cd build/bin
$ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex

تخته ها را فلش کنید

فایل ot-cli-ftd.hex را روی هر برد nRF52840 فلش کنید.

کابل USB را به پورت اشکال زدایی Micro-USB در کنار پین پاور خارجی روی برد nRF52840 وصل کنید و سپس آن را به دستگاه لینوکس خود وصل کنید. به درستی تنظیم کنید، LED5 روشن است.

20a3b4b480356447.png

مانند قبل به شماره سریال برد nRF52840 توجه کنید:

c00d519ebec7e5f0.jpeg

به محل nRFx Command Line Tools بروید و با استفاده از شماره سریال برد، فایل هگز 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.

این مرحله "Flash the boards" را برای دو تخته دیگر تکرار کنید. هر برد باید به همین ترتیب به ماشین لینوکس متصل شود و دستور فلش کردن به جز شماره سریال برد یکسان است. اطمینان حاصل کنید که از شماره سریال منحصر به فرد هر برد در صفحه استفاده کنید

دستور چشمک زن nrfjprog .

در صورت موفقیت آمیز بودن، LED1، LED2 یا LED3 روی هر برد روشن می شود. حتی ممکن است بلافاصله پس از چشمک زدن سوئیچ LED روشن از 3 به 2 (یا 2 به 1) را ببینید (ویژگی تغییر نقش دستگاه).

12. عملکرد برنامه

هر سه برد nRF52840 اکنون باید تغذیه شده و برنامه OpenThread ما را اجرا کنند. همانطور که قبلا توضیح داده شد، این برنامه دارای دو ویژگی اصلی است.

نشانگرهای نقش دستگاه

LED روشن روی هر برد نقش فعلی گره Thread را منعکس می کند:

  • LED1 = لیدر
  • LED2 = روتر
  • LED3 = دستگاه پایان

با تغییر نقش، LED روشن نیز تغییر می کند. شما باید قبلاً این تغییرات را در یک یا دو برد در عرض 20 ثانیه پس از روشن شدن هر دستگاه مشاهده کرده باشید.

UDP Multicast

هنگامی که دکمه 1 روی یک برد فشار داده می شود، یک پیام UDP به آدرس چندپخشی محلی mesh ارسال می شود که شامل تمام گره های دیگر در شبکه Thread می شود. در پاسخ به دریافت این پیام، LED4 در تمام بردهای دیگر روشن یا خاموش می شود . LED4 برای هر برد تا زمانی که پیام UDP دیگری دریافت کند روشن یا خاموش می ماند.

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. دمو: تغییرات نقش دستگاه را مشاهده کنید

دستگاه هایی که فلش کرده اید نوع خاصی از دستگاه تمام رشته (FTD) به نام دستگاه پایان واجد شرایط روتر (REED) هستند. این بدان معناست که آنها می توانند به عنوان یک روتر یا دستگاه پایان کار کنند و می توانند خود را از یک دستگاه پایانی به یک روتر ارتقا دهند.

Thread می تواند تا 32 روتر را پشتیبانی کند، اما سعی می کند تعداد روترها را بین 16 تا 23 نگه دارد. اگر یک REED به عنوان دستگاه پایانی متصل شود و تعداد روترها کمتر از 16 باشد، به طور خودکار خود را به یک روتر ارتقا می دهد. این تغییر باید در یک زمان تصادفی در عرض چند ثانیه که مقدار otThreadSetRouterSelectionJitter را در برنامه تنظیم کرده اید (20 ثانیه) رخ دهد.

هر شبکه Thread یک Leader نیز دارد که یک روتر است که مسئول مدیریت مجموعه روترها در یک شبکه Thread است. با روشن بودن همه دستگاه ها، پس از 20 ثانیه یکی از آنها باید یک Leader (LED1 روشن) و دو مورد دیگر باید Router (LED2 روشن) باشد.

4e1e885861a66570.png

رهبر را حذف کنید

اگر Leader از شبکه Thread حذف شود، روتر دیگری خود را به یک Leader معرفی می کند تا اطمینان حاصل شود که شبکه همچنان یک رهبر دارد.

با استفاده از کلید پاور ، برد Leader (آنی که LED1 روشن دارد) را خاموش کنید. حدود 20 ثانیه صبر کنید. در یکی از دو برد باقیمانده، LED2 (روتر) خاموش و LED1 (لیدر) روشن می شود. این دستگاه اکنون رهبر شبکه Thread است.

4c57c87adb40e0e3.png

برد اصلی Leader را دوباره روشن کنید. باید به‌طور خودکار به‌عنوان دستگاه پایانی دوباره به شبکه Thread ملحق شود (LED3 روشن است). در عرض 20 ثانیه (روتر انتخاب مسیریاب) خود را به روتر ارتقا می دهد (LED2 روشن است).

5f40afca2dcc4b5b.png

بردها را ریست کنید

هر سه برد را خاموش کنید، سپس دوباره روشن کنید و LED ها را مشاهده کنید. اولین بردی که روشن شد باید در نقش رهبر شروع شود (LED1 روشن است) - اولین روتر در یک شبکه Thread به طور خودکار به رهبر تبدیل می شود.

دو برد دیگر ابتدا به عنوان دستگاه پایانی به شبکه متصل می شوند (LED3 روشن است) اما باید در عرض 20 ثانیه خود را به روترها معرفی کنند (LED2 روشن است).

پارتیشن های شبکه

اگر بردهای شما برق کافی دریافت نمی کنند یا اتصال رادیویی بین آنها ضعیف است، شبکه Thread ممکن است به پارتیشن تقسیم شود و ممکن است بیش از یک دستگاه به عنوان Leader نشان داده شود.

Thread خود درمان می شود، بنابراین پارتیشن ها باید در نهایت در یک پارتیشن با یک Leader ادغام شوند.

14. نسخه ی نمایشی: ارسال چندپخشی UDP

اگر تمرین قبلی را ادامه دهید، LED4 نباید روی هیچ دستگاهی روشن شود.

هر تابلویی را انتخاب کنید و دکمه 1 را فشار دهید. LED4 در تمام بردهای دیگر در شبکه Thread که برنامه را اجرا می کنند باید وضعیت آنها را تغییر دهد. اگر تمرین قبلی را ادامه دهید، اکنون باید فعال باشند.

f186a2618fdbe3fd.png

دوباره دکمه 1 را برای همان برد فشار دهید. LED4 در همه بردهای دیگر باید دوباره تغییر کند.

دکمه 1 را روی یک برد دیگر فشار دهید و مشاهده کنید که چگونه LED4 روی سایر بردها تغییر می کند. دکمه 1 را روی یکی از بردهایی که LED4 در حال حاضر روشن است فشار دهید. LED4 برای آن برد روشن باقی می ماند اما روی بقیه جابجا می شود.

f5865ccb8ab7aa34.png

پارتیشن های شبکه

اگر بردهای شما پارتیشن بندی شده باشند و بیش از یک Leader در بین آنها وجود داشته باشد، نتیجه پیام چندپخشی بین بردها متفاوت خواهد بود. اگر دکمه 1 را روی بردی فشار دهید که پارتیشن بندی شده است (و بنابراین تنها عضو شبکه Thread پارتیشن بندی شده است)، LED4 در سایر بردها در پاسخ روشن نمی شود. اگر این اتفاق افتاد، بردها را بازنشانی کنید – در حالت ایده‌آل آنها یک شبکه Thread را اصلاح می‌کنند و پیام‌رسانی UDP باید به درستی کار کند.

15. تبریک می گویم!

شما برنامه ای ایجاد کرده اید که از OpenThread API استفاده می کند!

اکنون می دانید:

  • نحوه برنامه ریزی دکمه ها و LED ها در بردهای توسعه دهنده Nordic nRF52840
  • نحوه استفاده از APIهای OpenThread معمول و کلاس otInstance
  • نحوه نظارت و واکنش به تغییرات حالت OpenThread
  • نحوه ارسال پیام های UDP به همه دستگاه های موجود در شبکه Thread
  • نحوه اصلاح Makefiles

مراحل بعدی

با استفاده از این Codelab، تمرینات زیر را امتحان کنید:

  • ماژول GPIO را تغییر دهید تا از پین‌های GPIO به جای LEDهای داخلی استفاده کنید و LED‌های RGB خارجی را که بر اساس نقش روتر تغییر رنگ می‌دهند وصل کنید.
  • پشتیبانی GPIO را برای پلتفرم نمونه دیگری اضافه کنید
  • به جای استفاده از Multicast برای پینگ کردن همه دستگاه ها با فشار دادن دکمه، از Router/Leader API برای مکان یابی و پینگ کردن یک دستگاه استفاده کنید.
  • شبکه مش خود را با استفاده از یک مسیریاب مرزی OpenThread به اینترنت وصل کنید و آنها را از خارج از شبکه Thread چند پخش کنید تا LED ها روشن شوند.

بیشتر خواندن

برای انواع منابع OpenThread از جمله: openthread.io و GitHub را بررسی کنید:

ارجاع: