۱. مقدمه

OpenThread منتشر شده توسط Nest یک پیادهسازی متنباز از پروتکل شبکه Thread® است. Nest OpenThread را منتشر کرده است تا فناوری مورد استفاده در محصولات Nest را به طور گسترده در دسترس توسعهدهندگان قرار دهد تا توسعه محصولات برای خانههای متصل را تسریع کند.
مشخصات Thread یک پروتکل ارتباطی بیسیم دستگاه به دستگاه مبتنی بر IPv6، قابل اعتماد، امن و کممصرف را برای کاربردهای خانگی تعریف میکند. OpenThread تمام لایههای شبکه Thread شامل IPv6، 6LoWPAN، IEEE 802.15.4 را با امنیت MAC، ایجاد لینک مش و مسیریابی مش پیادهسازی میکند.
در این Codelab، شما از APIهای OpenThread برای راهاندازی یک شبکه Thread، نظارت و واکنش به تغییرات در نقشهای دستگاه، و ارسال پیامهای UDP و همچنین مرتبط کردن این اقدامات با دکمهها و LEDها روی سختافزار واقعی استفاده خواهید کرد.

آنچه یاد خواهید گرفت
- نحوه برنامه ریزی دکمه ها و LED ها روی بردهای توسعه Nordic nRF52840
- نحوه استفاده از API های رایج OpenThread و کلاس
otInstance - نحوه نظارت و واکنش به تغییرات وضعیت OpenThread
- نحوه ارسال پیامهای UDP به تمام دستگاههای موجود در یک شبکه Thread
- نحوه تغییر Makefiles
آنچه نیاز دارید
سختافزار:
- ۳ برد توسعهی nRF52840 از شرکت Nordic Semiconductor
- ۳ کابل USB به Micro-USB برای اتصال بردها
- یک دستگاه لینوکس با حداقل ۳ پورت USB
نرمافزار:
- زنجیره ابزار گنو
- ابزارهای خط فرمان Nordic nRF5x
- نرمافزار Segger J-Link
- باز کردن
- گیت
به جز مواردی که خلاف آن ذکر شده است، محتوای این Codelab تحت مجوز Creative Commons Attribution 3.0 و نمونههای کد تحت مجوز Apache 2.0 منتشر شدهاند .
۲. شروع کار
تکمیل آزمایشگاه کد سختافزار
قبل از شروع این Codelab، باید Build a Thread Network with nRF52840 Boards و OpenThread Codelab را تکمیل کنید، که شامل موارد زیر است:
- تمام نرمافزارهایی که برای ساخت و فلش کردن نیاز دارید را با جزئیات شرح میدهد
- به شما آموزش میدهد که چگونه OpenThread را بسازید و آن را روی بردهای Nordic nRF52840 فلش کنید.
- اصول اولیه یک شبکه Thread را نشان میدهد.
هیچ یک از تنظیمات محیطی مورد نیاز برای ساخت OpenThread و فلش کردن بردها در این Codelab به تفصیل شرح داده نشده است - فقط دستورالعملهای اولیه برای فلش کردن بردها. فرض بر این است که شما قبلاً Codelab ساخت شبکه Thread را تکمیل کردهاید.
دستگاه لینوکس
این Codelab طوری طراحی شده است که از یک دستگاه لینوکس مبتنی بر i386 یا x86 برای فلش کردن تمام بردهای توسعه Thread استفاده کند. تمام مراحل روی Ubuntu 14.04.5 LTS (Trusty Tahr) آزمایش شدهاند.
بردهای نیمههادی نوردیک nRF52840
این Codelab از سه برد nRF52840 PDK استفاده میکند.

نصب نرمافزار
برای ساخت و فلش کردن OpenThread، باید SEGGER J-Link، ابزارهای خط فرمان nRF5x، ARM GNU Toolchain و بستههای مختلف لینوکس را نصب کنید. اگر Codelab ساخت شبکه Thread را طبق نیاز تکمیل کرده باشید، از قبل همه چیز مورد نیاز را نصب کردهاید. در غیر این صورت، قبل از ادامه، Codelab را تکمیل کنید تا مطمئن شوید که میتوانید OpenThread را روی بردهای توسعه nRF52840 بسازید و فلش کنید.
۳. مخزن را کلون کنید
OpenThread همراه با کد برنامه نمونه ارائه میشود که میتوانید به عنوان نقطه شروع برای این Codelab از آن استفاده کنید.
مخزن نمونههای OpenThread Nordic nRF528xx را کلون کنید و OpenThread را بسازید:
$ git clone --recursive https://github.com/openthread/ot-nrf528xx $ cd ot-nrf528xx $ ./script/bootstrap
۴. اصول اولیه API در OpenThread
APIهای عمومی OpenThread در مسیر ./openthread/include/openthread در مخزن OpenThread قرار دارند. این APIها دسترسی به طیف وسیعی از ویژگیها و قابلیتهای OpenThread را در سطح Thread و Platform برای استفاده در برنامههای شما فراهم میکنند:
- اطلاعات و کنترل نمونه 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 را نشان میدهد و به کاربر اجازه میدهد تا فراخوانیهای API OpenThread را انجام دهد.
برای مثال، نمونه OpenThread در تابع main() از برنامه نمونه CLI مقداردهی اولیه میشود:
./openthread/examples/apps/cli/main.c
int main(int argc, char *argv[])
{
otInstance *instance
...
#if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES
// Call to query the buffer size
(void)otInstanceInit(NULL, &otInstanceBufferLength);
// Call to allocate the buffer
otInstanceBuffer = (uint8_t *)malloc(otInstanceBufferLength);
assert(otInstanceBuffer);
// Initialize OpenThread with the buffer
instance = otInstanceInit(otInstanceBuffer, &otInstanceBufferLength);
#else
instance = otInstanceInitSingle();
#endif
...
return 0;
}
توابع خاص پلتفرم
اگر میخواهید توابع مختص پلتفرم را به یکی از برنامههای نمونه موجود در OpenThread اضافه کنید، ابتدا آنها را در هدر ./openthread/examples/platforms/openthread-system.h با استفاده از فضای نام otSys برای همه توابع، تعریف کنید. سپس آنها را در یک فایل منبع مختص پلتفرم پیادهسازی کنید. با این روش خلاصهسازی، میتوانید از هدرهای تابع مشابه برای سایر پلتفرمهای نمونه استفاده کنید.
برای مثال، توابع GPIO که قرار است برای اتصال به دکمهها و LEDهای nRF52840 استفاده کنیم، باید در openthread-system.h تعریف شوند.
فایل ./openthread/examples/platforms/openthread-system.h را در ویرایشگر متن دلخواه خود باز کنید.
./openthread/examples/platforms/openthread-system.h
اقدام: تعریف توابع GPIO مختص پلتفرم را اضافه کنید.
این اعلانهای تابع را بعد از #include برای هدر openthread/instance.h اضافه کنید:
/** * Init LED module. * */ void otSysLedInit(void); void otSysLedSet(uint8_t aLed, bool aOn); void otSysLedToggle(uint8_t aLed); /** * A callback will be called when GPIO interrupts occur. * */ typedef void (*otSysButtonCallback)(otInstance *aInstance); void otSysButtonInit(otSysButtonCallback aCallback); void otSysButtonProcess(otInstance *aInstance);
ما این موارد را در مرحله بعدی پیادهسازی خواهیم کرد.
توجه داشته باشید که اعلان تابع otSysButtonProcess از یک otInstance استفاده میکند. به این ترتیب، برنامه میتواند در صورت نیاز، هنگام فشردن یک دکمه، به اطلاعات مربوط به نمونه OpenThread دسترسی پیدا کند. همه چیز به نیازهای برنامه شما بستگی دارد. اگر در پیادهسازی تابع به آن نیازی ندارید، میتوانید از ماکرو OT_UNUSED_VARIABLE از API OpenThread برای سرکوب خطاهای ساخت در اطراف متغیرهای استفاده نشده برای برخی از زنجیره ابزارها استفاده کنید. بعداً نمونههایی از این مورد را خواهیم دید.
۵. پیادهسازی انتزاع پلتفرم GPIO
در مرحله قبل، به تعریف توابع مختص پلتفرم در ./openthread/examples/platforms/openthread-system.h که میتوانند برای GPIO استفاده شوند، پرداختیم. برای دسترسی به دکمهها و LEDها در بردهای توسعه nRF52840، باید آن توابع را برای پلتفرم nRF52840 پیادهسازی کنید. در این کد، توابعی اضافه خواهید کرد که:
- مقداردهی اولیه پینها و حالتهای GPIO
- کنترل ولتاژ روی یک پین
- فعال کردن وقفههای GPIO و ثبت یک callback
در پوشهی ./src/src ، یک فایل جدید با نام gpio.c ایجاد کنید. در این فایل جدید، محتوای زیر را اضافه کنید.
./src/src/gpio.c (فایل جدید)
اقدام: تعریفها را اضافه کنید.
این تعاریف به عنوان انتزاع بین مقادیر خاص nRF52840 و متغیرهای مورد استفاده در سطح برنامه OpenThread عمل میکنند.
/** * @file * This file implements the system abstraction for GPIO and GPIOTE. * */ #define BUTTON_GPIO_PORT 0x50000300UL #define BUTTON_PIN 11 // button #1 #define GPIO_LOGIC_HI 0 #define GPIO_LOGIC_LOW 1 #define LED_GPIO_PORT 0x50000300UL #define LED_1_PIN 13 // turn on to indicate leader role #define LED_2_PIN 14 // turn on to indicate router role #define LED_3_PIN 15 // turn on to indicate child role #define LED_4_PIN 16 // turn on to indicate UDP receive
برای اطلاعات بیشتر در مورد دکمهها و LEDهای nRF52840، به مرکز اطلاعات نیمهرسانای Nordic مراجعه کنید.
اقدام: اضافه کردن موارد شامل در هدر.
در مرحله بعد، هدرهای مورد نیاز برای عملکرد 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"
اقدام: توابع فراخوانی و وقفه را برای دکمه ۱ اضافه کنید.
این کد را در مرحله بعد اضافه کنید. تابع in_pin1_handler تابع فراخوانی است که هنگام مقداردهی اولیه عملکرد فشردن دکمه (در ادامه این فایل) ثبت میشود.
توجه داشته باشید که این تابع فراخوانی مجدد چگونه از ماکروی OT_UNUSED_VARIABLE استفاده میکند، زیرا متغیرهای ارسالی به in_pin1_handler در واقع در تابع استفاده نمیشوند.
/* Declaring callback function for button 1. */
static otSysButtonCallback sButtonHandler;
static bool sButtonPressed;
/**
* @brief Function to receive interrupt and call back function
* set by the application for button 1.
*
*/
static void in_pin1_handler(uint32_t pin, nrf_gpiote_polarity_t action)
{
OT_UNUSED_VARIABLE(pin);
OT_UNUSED_VARIABLE(action);
sButtonPressed = true;
}
اقدام: یک تابع برای پیکربندی LEDها اضافه کنید.
این کد را برای پیکربندی حالت و وضعیت همه LEDها در هنگام مقداردهی اولیه اضافه کنید.
/**
* @brief Function for configuring: PIN_IN pin for input, PIN_OUT pin for output,
* and configures GPIOTE to give an interrupt on pin change.
*/
void otSysLedInit(void)
{
/* Configure GPIO mode: output */
nrf_gpio_cfg_output(LED_1_PIN);
nrf_gpio_cfg_output(LED_2_PIN);
nrf_gpio_cfg_output(LED_3_PIN);
nrf_gpio_cfg_output(LED_4_PIN);
/* Clear all output first */
nrf_gpio_pin_write(LED_1_PIN, GPIO_LOGIC_LOW);
nrf_gpio_pin_write(LED_2_PIN, GPIO_LOGIC_LOW);
nrf_gpio_pin_write(LED_3_PIN, GPIO_LOGIC_LOW);
nrf_gpio_pin_write(LED_4_PIN, GPIO_LOGIC_LOW);
/* Initialize gpiote for button(s) input.
Button event handlers are set in the application (main.c) */
ret_code_t err_code;
err_code = nrfx_gpiote_init();
APP_ERROR_CHECK(err_code);
}
اقدام: یک تابع برای تنظیم حالت یک LED اضافه کنید.
این تابع زمانی استفاده میشود که نقش دستگاه تغییر کند.
/**
* @brief Function to set the mode of an LED.
*/
void otSysLedSet(uint8_t aLed, bool aOn)
{
switch (aLed)
{
case 1:
nrf_gpio_pin_write(LED_1_PIN, (aOn == GPIO_LOGIC_HI));
break;
case 2:
nrf_gpio_pin_write(LED_2_PIN, (aOn == GPIO_LOGIC_HI));
break;
case 3:
nrf_gpio_pin_write(LED_3_PIN, (aOn == GPIO_LOGIC_HI));
break;
case 4:
nrf_gpio_pin_write(LED_4_PIN, (aOn == GPIO_LOGIC_HI));
break;
}
}
اقدام: یک تابع برای تغییر حالت یک LED اضافه کنید.
این تابع برای تغییر وضعیت LED4 هنگام دریافت پیام UDP چندپخشی توسط دستگاه استفاده میشود.
/**
* @brief Function to toggle the mode of an LED.
*/
void otSysLedToggle(uint8_t aLed)
{
switch (aLed)
{
case 1:
nrf_gpio_pin_toggle(LED_1_PIN);
break;
case 2:
nrf_gpio_pin_toggle(LED_2_PIN);
break;
case 3:
nrf_gpio_pin_toggle(LED_3_PIN);
break;
case 4:
nrf_gpio_pin_toggle(LED_4_PIN);
break;
}
}
اقدام: توابعی را برای مقداردهی اولیه و پردازش فشرده شدن دکمهها اضافه کنید.
تابع اول، برد را برای فشردن دکمه مقداردهی اولیه میکند و تابع دوم، پیام UDP چندپخشی را هنگام فشردن دکمه ۱ ارسال میکند.
/**
* @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 را ذخیره کرده و ببندید .
۶. API: واکنش به تغییرات نقش دستگاه
در برنامه ما، میخواهیم بسته به نقش دستگاه، LEDهای مختلفی روشن شوند. بیایید نقشهای زیر را ردیابی کنیم: رهبر، روتر، دستگاه نهایی. میتوانیم آنها را به LEDها به این صورت اختصاص دهیم:
- LED1 = رهبر
- LED2 = روتر
- LED3 = دستگاه پایانی
برای فعال کردن این قابلیت، برنامه باید بداند چه زمانی نقش دستگاه تغییر کرده است و چگونه LED صحیح را در پاسخ روشن کند. ما برای بخش اول از نمونه OpenThread و برای بخش دوم از انتزاع پلتفرم GPIO استفاده خواهیم کرد.
فایل ./openthread/examples/apps/cli/main.c را در ویرایشگر متن دلخواه خود باز کنید.
./openthread/examples/apps/cli/main.c
اقدام: اضافه کردن موارد شامل در هدر.
در بخش includes فایل main.c ، فایلهای هدر API مورد نیاز برای ویژگی تغییر نقش را اضافه کنید.
#include <openthread/instance.h> #include <openthread/thread.h> #include <openthread/thread_ftd.h>
اقدام: برای تغییر وضعیت نمونه OpenThread، اعلان تابع کنترلکننده را اضافه کنید.
این تعریف را به main.c ، بعد از header includes و قبل از هرگونه دستور #if اضافه کنید. این تابع بعد از برنامه اصلی تعریف خواهد شد.
void handleNetifStateChanged(uint32_t aFlags, void *aContext);
اقدام: یک ثبت فراخوانی برای تابع مدیریت تغییر وضعیت اضافه کنید.
در main.c ، این تابع را به تابع main() بعد از فراخوانی otAppCliInit اضافه کنید. این ثبت فراخوانی به OpenThread میگوید که هر زمان وضعیت نمونه OpenThread تغییر کرد، تابع handleNetifStateChange را فراخوانی کند.
/* Register Thread state change handler */ otSetStateChangedCallback(instance, handleNetifStateChanged, instance);
اقدام: پیادهسازی تغییر وضعیت را اضافه کنید.
در main.c ، پس از تابع main() ، تابع handleNetifStateChanged را پیادهسازی کنید. این تابع پرچم OT_CHANGED_THREAD_ROLE از نمونه OpenThread را بررسی میکند و اگر تغییر کرده باشد، LEDها را در صورت نیاز روشن/خاموش میکند.
void handleNetifStateChanged(uint32_t aFlags, void *aContext)
{
if ((aFlags & OT_CHANGED_THREAD_ROLE) != 0)
{
otDeviceRole changedRole = otThreadGetDeviceRole(aContext);
switch (changedRole)
{
case OT_DEVICE_ROLE_LEADER:
otSysLedSet(1, true);
otSysLedSet(2, false);
otSysLedSet(3, false);
break;
case OT_DEVICE_ROLE_ROUTER:
otSysLedSet(1, false);
otSysLedSet(2, true);
otSysLedSet(3, false);
break;
case OT_DEVICE_ROLE_CHILD:
otSysLedSet(1, false);
otSysLedSet(2, false);
otSysLedSet(3, true);
break;
case OT_DEVICE_ROLE_DETACHED:
case OT_DEVICE_ROLE_DISABLED:
/* Clear LED4 if Thread is not enabled. */
otSysLedSet(4, false);
break;
}
}
}
۷. API: استفاده از multicast برای روشن کردن یک LED
در برنامه ما، ما همچنین میخواهیم وقتی دکمه ۱ روی یک برد فشرده میشود، پیامهای UDP را به تمام دستگاههای دیگر در شبکه ارسال کنیم. برای تأیید دریافت پیام، LED4 را در بردهای دیگر در پاسخ فعال یا غیرفعال خواهیم کرد.
برای فعال کردن این قابلیت، برنامه باید:
- مقداردهی اولیه اتصال UDP هنگام راهاندازی
- قادر به ارسال پیام UDP به آدرس چندپخشی محلی مش باشید
- مدیریت پیامهای UDP ورودی
- تغییر وضعیت LED4 در پاسخ به پیامهای UDP دریافتی
فایل ./openthread/examples/apps/cli/main.c را در ویرایشگر متن دلخواه خود باز کنید.
./openthread/examples/apps/cli/main.c
اقدام: اضافه کردن موارد شامل در هدر.
در بخش includes در بالای فایل main.c ، فایلهای هدر API مورد نیاز برای ویژگی multicast 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 ، پس از بخش includes و قبل از هرگونه دستور #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 Devices) در شبکه ارسال خواهد شد. برای اطلاعات بیشتر در مورد پشتیبانی چندپخشی در OpenThread، به Multicast در openthread.io مراجعه کنید.
اقدام: تعریف توابع را اضافه کنید.
در فایل main.c ، پس از تعریف otTaskletsSignalPending و قبل از تابع main() ، توابع مختص UDP و همچنین یک متغیر استاتیک برای نمایش سوکت UDP اضافه کنید:
static void initUdp(otInstance *aInstance);
static void sendUdp(otInstance *aInstance);
static void handleButtonInterrupt(otInstance *aInstance);
void handleUdpReceive(void *aContext, otMessage *aMessage,
const otMessageInfo *aMessageInfo);
static otUdpSocket sUdpSocket;
اقدام: فراخوانیهایی برای مقداردهی اولیه LEDهای GPIO و دکمه اضافه کنید.
در main.c ، این فراخوانیهای تابع را به تابع main() پس از فراخوانی otSetStateChangedCallback اضافه کنید. این توابع پینهای GPIO و GPIOTE را مقداردهی اولیه میکنند و یک کنترلکننده دکمه برای مدیریت رویدادهای فشردن دکمه تنظیم میکنند.
/* init GPIO LEDs and button */ otSysLedInit(); otSysButtonInit(handleButtonInterrupt);
اقدام: فراخوانی مقداردهی اولیه UDP را اضافه کنید.
در main.c ، این تابع را به تابع main() بعد از فراخوانی otSysButtonInit که اضافه کردید، اضافه کنید:
initUdp(instance);
این فراخوانی تضمین میکند که یک سوکت UDP هنگام شروع برنامه راهاندازی میشود. بدون این، دستگاه نمیتواند پیامهای UDP ارسال یا دریافت کند.
اقدام: فراخوانی برای پردازش رویداد دکمه GPIO اضافه کنید.
در main.c ، این فراخوانی تابع را به تابع main() پس از فراخوانی otSysProcessDrivers ، در حلقه while اضافه کنید. این تابع که در gpio.c تعریف شده است، بررسی میکند که آیا دکمه فشرده شده است یا خیر، و در صورت فشرده شدن، handler ( 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 به رابط شبکه Thread متصل میکند. برای سایر گزینههای رابط شبکه، به شمارش otNetifIdentifier در مرجع API 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 ، جایی که بافر را آزاد میکند، خطاها را به طرز ماهرانهای مدیریت میکند.
برای اطلاعات بیشتر در مورد توابع مورد استفاده برای مقداردهی اولیه 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);
}
۸. API: پیکربندی شبکه Thread
برای سهولت نمایش، میخواهیم دستگاههای ما بلافاصله پس از روشن شدن، Thread را اجرا کرده و به یک شبکه متصل شوند. برای انجام این کار، از ساختار otOperationalDataset استفاده خواهیم کرد. این ساختار تمام پارامترهای مورد نیاز برای انتقال اعتبارنامههای شبکه Thread به یک دستگاه را در خود جای داده است.
استفاده از این ساختار، پیشفرضهای شبکهی تعبیهشده در OpenThread را نادیده میگیرد تا برنامهی ما امنتر شود و گرههای Thread در شبکهی ما فقط به آنهایی که برنامه را اجرا میکنند محدود شوند.
دوباره، فایل ./openthread/examples/apps/cli/main.c را در ویرایشگر متن دلخواه خود باز کنید.
./openthread/examples/apps/cli/main.c
اقدام: اضافه کردن بخش هدر
در بخش includes در بالای فایل main.c ، فایل هدر API مورد نیاز برای پیکربندی شبکه Thread را اضافه کنید:
#include <openthread/dataset_ftd.h>
اقدام: تعریف تابع برای تنظیم پیکربندی شبکه را اضافه کنید.
این تعریف را به main.c ، بعد از header includes و قبل از هرگونه دستور #if اضافه کنید. این تابع بعد از تابع main برنامه تعریف خواهد شد.
static void setNetworkConfiguration(otInstance *aInstance);
اقدام: فراخوانی پیکربندی شبکه را اضافه کنید.
در main.c ، این فراخوانی تابع را به تابع main() پس از فراخوانی otSetStateChangedCallback اضافه کنید. این تابع مجموعه دادههای شبکه Thread را پیکربندی میکند.
/* Override default network credentials */ setNetworkConfiguration(instance);
اقدام: فراخوانیهایی برای فعال کردن رابط شبکه و پشته Thread اضافه کنید.
در main.c ، این فراخوانیهای تابع را به تابع main() بعد از فراخوانی otSysButtonInit اضافه کنید.
/* Start the Thread network interface (CLI cmd > ifconfig up) */ otIp6SetEnabled(instance, true); /* Start the Thread stack (CLI cmd > thread start) */ otThreadSetEnabled(instance, true);
اقدام: پیکربندی شبکه Thread را پیادهسازی کنید.
در main.c ، پیادهسازی تابع setNetworkConfiguration را بعد از تابع main() اضافه کنید:
/**
* Override default network settings, such as panid, so the devices can join a
network
*/
void setNetworkConfiguration(otInstance *aInstance)
{
static char aNetworkName[] = "OTCodelab";
otOperationalDataset aDataset;
memset(&aDataset, 0, sizeof(otOperationalDataset));
/*
* Fields that can be configured in otOperationDataset to override defaults:
* Network Name, Mesh Local Prefix, Extended PAN ID, PAN ID, Delay Timer,
* Channel, Channel Mask Page 0, Network Key, PSKc, Security Policy
*/
aDataset.mActiveTimestamp.mSeconds = 1;
aDataset.mActiveTimestamp.mTicks = 0;
aDataset.mActiveTimestamp.mAuthoritative = false;
aDataset.mComponents.mIsActiveTimestampPresent = true;
/* Set Channel to 15 */
aDataset.mChannel = 15;
aDataset.mComponents.mIsChannelPresent = true;
/* Set Pan ID to 2222 */
aDataset.mPanId = (otPanId)0x2222;
aDataset.mComponents.mIsPanIdPresent = true;
/* Set Extended Pan ID to C0DE1AB5C0DE1AB5 */
uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xC0, 0xDE, 0x1A, 0xB5, 0xC0, 0xDE, 0x1A, 0xB5};
memcpy(aDataset.mExtendedPanId.m8, extPanId, sizeof(aDataset.mExtendedPanId));
aDataset.mComponents.mIsExtendedPanIdPresent = true;
/* Set network key to 1234C0DE1AB51234C0DE1AB51234C0DE */
uint8_t key[OT_NETWORK_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE};
memcpy(aDataset.mNetworkKey.m8, key, sizeof(aDataset.mNetworkKey));
aDataset.mComponents.mIsNetworkKeyPresent = true;
/* Set Network Name to OTCodelab */
size_t length = strlen(aNetworkName);
assert(length <= OT_NETWORK_NAME_MAX_SIZE);
memcpy(aDataset.mNetworkName.m8, aNetworkName, length);
aDataset.mComponents.mIsNetworkNamePresent = true;
otDatasetSetActive(aInstance, &aDataset);
/* Set the router selection jitter to override the 2 minute default.
CLI cmd > routerselectionjitter 20
Warning: For demo purposes only - not to be used in a real product */
uint8_t jitterValue = 20;
otThreadSetRouterSelectionJitter(aInstance, jitterValue);
}
همانطور که در تابع به تفصیل توضیح داده شده است، پارامترهای شبکه Thread که ما برای این برنامه استفاده میکنیم عبارتند از:
- کانال = ۱۵
- شناسه PAN = 0x2222
- شناسه PAN توسعهیافته = C0DE1AB5C0DE1AB5
- کلید شبکه = 1234C0DE1AB51234C0DE1AB51234C0DE
- نام شبکه = OTCodelab
علاوه بر این، در اینجا ما لرزش انتخاب روتر (Router Selection Jitter) را کاهش میدهیم، بنابراین دستگاههای ما برای اهداف نمایشی سریعتر نقشها را تغییر میدهند. توجه داشته باشید که این کار فقط در صورتی انجام میشود که گره یک FTD (دستگاه تمام رشتهای) باشد. در مرحله بعدی اطلاعات بیشتری در مورد آن ارائه خواهیم داد.
۹. API: توابع محدود
برخی از APIهای OpenThread تنظیماتی را تغییر میدهند که فقط باید برای اهداف نمایشی یا آزمایشی اصلاح شوند. این APIها نباید در استقرار نهایی یک برنامه با استفاده از OpenThread استفاده شوند.
برای مثال، تابع otThreadSetRouterSelectionJitter زمان (بر حسب ثانیه) لازم برای تبدیل شدن یک دستگاه انتهایی به یک روتر را تنظیم میکند. مقدار پیشفرض این مقدار، طبق مشخصات Thread، ۱۲۰ است. برای سهولت استفاده در این Codelab، ما آن را به ۲۰ تغییر میدهیم، بنابراین لازم نیست مدت زیادی منتظر تغییر نقش یک گره 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
۱۰. بهروزرسانیهای CMake
قبل از ساخت برنامه، چند بهروزرسانی جزئی برای سه فایل CMake مورد نیاز است. این فایلها توسط سیستم ساخت برای کامپایل و پیوند برنامه شما استفاده میشوند.
./third_party/NordicSemiconductor/CMakeLists.txt
حالا چند پرچم به NordicSemiconductor CMakeLists.txt اضافه کنید تا مطمئن شوید توابع GPIO در برنامه تعریف شدهاند.
اقدام: اضافه کردن پرچمها به فایل CMakeLists.txt .
فایل ./third_party/NordicSemiconductor/CMakeLists.txt را در ویرایشگر متن دلخواه خود باز کنید و خطوط زیر را در بخش COMMON_FLAG اضافه کنید.
...
set(COMMON_FLAG
-DSPIS_ENABLED=1
-DSPIS0_ENABLED=1
-DNRFX_SPIS_ENABLED=1
-DNRFX_SPIS0_ENABLED=1
...
# Defined in ./third_party/NordicSemiconductor/nrfx/templates/nRF52840/nrfx_config.h
-DGPIOTE_ENABLED=1
-DGPIOTE_CONFIG_IRQ_PRIORITY=7
-DGPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS=1
)
...
./src/CMakeLists.txt
فایل ./src/CMakeLists.txt را ویرایش کنید تا فایل منبع جدید gpio.c اضافه شود:
اقدام: منبع gpio را به فایل ./src/CMakeLists.txt اضافه کنید .
./src/CMakeLists.txt را در ویرایشگر متن دلخواه خود باز کنید و فایل را به بخش NRF_COMM_SOURCES اضافه کنید.
... set(NRF_COMM_SOURCES ... src/gpio.c ... ) ...
./third_party/NordicSemiconductor/CMakeLists.txt
در نهایت، فایل درایور nrfx_gpiote.c را به فایل NordicSemiconductor CMakeLists.txt اضافه کنید تا در کتابخانهی درایورهای Nordic گنجانده شود.
اقدام: درایور gpio را به فایل NordicSemiconductor CMakeLists.txt اضافه کنید .
فایل ./third_party/NordicSemiconductor/CMakeLists.txt را در ویرایشگر متن دلخواه خود باز کنید و فایل را به بخش COMMON_SOURCES اضافه کنید.
... set(COMMON_SOURCES ... nrfx/drivers/src/nrfx_gpiote.c ... ) ...
۱۱. دستگاهها را تنظیم کنید
با انجام تمام بهروزرسانیهای کد، شما آماده ساخت و فلش کردن برنامه روی هر سه برد توسعه 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 را به درستی تنظیم کنید، روشن میشود.

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

به محل ابزارهای خط فرمان nRFx بروید و فایل hex FTD OpenThread CLI را با استفاده از شماره سریال برد، روی برد 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.
این مرحلهی «فلش کردن بردها» را برای دو برد دیگر تکرار کنید. هر برد باید به همان روش به دستگاه لینوکس متصل شود و دستور فلش کردن نیز یکسان است، به جز شماره سریال برد. حتماً از شماره سریال منحصر به فرد هر برد در ... استفاده کنید.
دستور چشمک زن nrfjprog .
در صورت موفقیت، LED1، LED2 یا LED3 روی هر برد روشن خواهند شد. حتی ممکن است کمی پس از چشمک زدن (ویژگی تغییر نقش دستگاه)، سوئیچ LED روشن را از ۳ به ۲ (یا ۲ به ۱) مشاهده کنید.
۱۲. عملکرد برنامه
اکنون هر سه برد nRF52840 باید روشن شده و برنامه OpenThread ما را اجرا کنند. همانطور که قبلاً توضیح داده شد، این برنامه دو ویژگی اصلی دارد.
نشانگرهای نقش دستگاه
چراغ LED روشن روی هر برد، نقش فعلی گره Thread را نشان میدهد:
- LED1 = رهبر
- LED2 = روتر
- LED3 = دستگاه پایانی
با تغییر نقش، LED روشن نیز تغییر میکند. شما باید این تغییرات را روی یک یا دو برد، ظرف 20 ثانیه پس از روشن شدن هر دستگاه، مشاهده کرده باشید.
چندپخشی UDP
وقتی دکمهی ۱ روی یک برد فشرده میشود، یک پیام UDP به آدرس چندپخشی محلی مش ارسال میشود که شامل تمام گرههای دیگر در شبکهی Thread میشود. در پاسخ به دریافت این پیام، LED4 روی تمام بردهای دیگر روشن یا خاموش میشود . LED4 برای هر برد روشن یا خاموش میماند تا زمانی که یک پیام UDP دیگر دریافت کند.


۱۳. نسخه آزمایشی: مشاهده تغییرات نقش دستگاه
دستگاههایی که فلش کردهاید نوع خاصی از دستگاه تمامریسمانی (FTD) هستند که دستگاه انتهایی واجد شرایط روتر (REED) نامیده میشوند. این بدان معناست که آنها میتوانند به عنوان یک روتر یا دستگاه انتهایی عمل کنند و میتوانند خود را از یک دستگاه انتهایی به یک روتر ارتقا دهند.
Thread میتواند تا ۳۲ روتر را پشتیبانی کند، اما سعی میکند تعداد روترها را بین ۱۶ تا ۲۳ نگه دارد. اگر یک REED به عنوان یک دستگاه انتهایی متصل شود و تعداد روترها کمتر از ۱۶ باشد، به طور خودکار خود را به یک روتر ارتقا میدهد. این تغییر باید در یک زمان تصادفی در تعداد ثانیههایی که مقدار otThreadSetRouterSelectionJitter را در برنامه روی آن تنظیم کردهاید (۲۰ ثانیه) رخ دهد.
هر شبکه Thread همچنین دارای یک Leader است که یک روتر است و وظیفه مدیریت مجموعه روترها را در یک شبکه Thread بر عهده دارد. با روشن بودن همه دستگاهها، پس از 20 ثانیه یکی از آنها باید Leader (LED1 روشن) و دو دستگاه دیگر باید Router (LED2 روشن) باشند.

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

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

تابلوها را دوباره تنظیم کنید
هر سه برد را خاموش کنید، سپس دوباره آنها را روشن کنید و LEDها را مشاهده کنید. اولین بردی که روشن شده است باید در نقش Leader شروع به کار کند (LED1 روشن است) - اولین روتر در یک شبکه Thread به طور خودکار Leader میشود.
دو برد دیگر در ابتدا به عنوان دستگاههای انتهایی (LED3 روشن است) به شبکه متصل میشوند، اما باید ظرف 20 ثانیه خود را به روترها (LED2 روشن است) ارتقا دهند.
پارتیشنهای شبکه
اگر بردهای شما برق کافی دریافت نمیکنند، یا اتصال رادیویی بین آنها ضعیف است، شبکه Thread ممکن است به پارتیشنهایی تقسیم شود و ممکن است بیش از یک دستگاه به عنوان Leader نمایش داده شود.
رشته (Thread) خودبهخود ترمیم میشود، بنابراین پارتیشنها در نهایت باید دوباره در یک پارتیشن واحد با یک Leader ادغام شوند.
۱۴. نسخه آزمایشی: ارسال چندپخشی UDP
اگر از تمرین قبلی ادامه میدهید، LED4 نباید روی هیچ دستگاهی روشن باشد.
هر بردی را انتخاب کنید و دکمهی ۱ را فشار دهید. LED4 روی تمام بردهای دیگر در شبکهی Thread که برنامه در آن اجرا میشود، باید وضعیت خود را تغییر دهد. اگر از تمرین قبلی ادامه دهید، اکنون باید روشن باشند.

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

پارتیشنهای شبکه
اگر بردهای شما پارتیشنبندی شدهاند و بیش از یک Leader در بین آنها وجود دارد، نتیجه پیام چندپخشی بین بردها متفاوت خواهد بود. اگر دکمه ۱ را روی بردی که پارتیشنبندی شده است (و بنابراین تنها عضو شبکه Thread پارتیشنبندی شده است) فشار دهید، LED4 در بردهای دیگر در پاسخ روشن نمیشود. اگر این اتفاق بیفتد، بردها را مجدداً تنظیم کنید - در حالت ایدهآل، آنها یک شبکه Thread واحد را تشکیل میدهند و پیامرسانی UDP باید به درستی کار کند.
۱۵. تبریک میگویم!
شما برنامهای ایجاد کردهاید که از APIهای OpenThread استفاده میکند!
حالا میدانید:
- نحوه برنامه ریزی دکمه ها و LED ها روی بردهای توسعه Nordic nRF52840
- نحوه استفاده از API های رایج OpenThread و کلاس
otInstance - نحوه نظارت و واکنش به تغییرات وضعیت OpenThread
- نحوه ارسال پیامهای UDP به تمام دستگاههای موجود در یک شبکه Thread
- نحوه تغییر Makefiles
مراحل بعدی
با تکیه بر این Codelab، تمرینهای زیر را امتحان کنید:
- ماژول GPIO را طوری تغییر دهید که به جای LEDهای روی برد، از پینهای GPIO استفاده کند و LEDهای RGB خارجی را که بر اساس نقش روتر تغییر رنگ میدهند، متصل کنید.
- پشتیبانی از GPIO را برای یک پلتفرم نمونه متفاوت اضافه کنید
- به جای استفاده از multicast برای پینگ کردن همه دستگاهها از طریق فشردن یک دکمه، از Router/Leader API برای مکانیابی و پینگ کردن یک دستگاه خاص استفاده کنید.
- شبکه مش خود را با استفاده از یک روتر مرزی OpenThread به اینترنت متصل کنید و آنها را از خارج از شبکه Thread به صورت چندپخشی ارسال کنید تا LEDها روشن شوند.
مطالعه بیشتر
برای دسترسی به منابع متنوع OpenThread، از جمله موارد زیر، به openthread.io و GitHub مراجعه کنید:
- پلتفرمهای پشتیبانیشده - تمام پلتفرمهایی که از OpenThread پشتیبانی میکنند را کشف کنید
- ساخت OpenThread — جزئیات بیشتر در مورد ساخت و پیکربندی OpenThread
- Thread Primer - مرجعی عالی در مورد مفاهیم Thread
مرجع: