OpenThread API'leriyle geliştirme

1. Giriş

26b7f4f6b3ea0700.png

Nest tarafından yayınlanan OpenThread, Thread® ağ protokolünün açık kaynaklı bir uygulamasıdır. Nest, bağlı evlere yönelik ürünlerin geliştirilmesini hızlandırmak amacıyla, Nest ürünlerinde kullanılan teknolojiyi geliştiricilerin kullanımına sunmak için OpenThread'i yayınladı.

Thread spesifikasyonu, ev uygulamaları için IPv6 tabanlı güvenilir, güvenli ve düşük güçlü bir kablosuz cihazlar arası iletişim protokolü tanımlar. OpenThread; MAC güvenliği, Örgü Bağlantı Oluşturma ve Örgü Yönlendirme ile IPv6, 6LoWPAN, IEEE 802.15.4 dahil olmak üzere tüm Thread ağ katmanlarını uygular.

Bu Codelab'de, Thread ağı başlatmak, cihaz rollerindeki değişiklikleri izleyip bunlara tepki vermek, UDP mesajları göndermek ve bu işlemleri gerçek donanımdaki düğmelere ve LED'lere bağlamak için OpenThread API'lerini kullanacaksınız.

2a6db2e258c32237.png

Neler öğreneceksiniz?

  • Nordic nRF52840 geliştirici kartlarındaki düğmeleri ve LED'leri programlama
  • Yaygın OpenThread API'leri ve otInstance sınıfı nasıl kullanılır?
  • OpenThread durum değişikliklerini izleme ve bu değişikliklere tepki verme
  • Thread ağındaki tüm cihazlara UDP mesajları gönderme
  • Makefiles'i değiştirme

Gerekenler

Donanım:

  • 3 Nordic Semiconctionor nRF52840 geliştirici kartı
  • Kartları bağlamak için 3 adet USB - Mikro USB kablosu
  • En az 3 USB bağlantı noktasına sahip bir Linux makinesi

Yazılım:

  • GNU Araç Zinciri
  • İskandinav nRF5x komut satırı araçları
  • Segger J-Link yazılımı
  • OpenThread
  • Git

Aksi belirtilmediği sürece bu Codelab'in içeriği Creative Commons Attribution 3.0 Lisansı ile, kod örnekleri ise Apache 2.0 Lisansı ile lisanslanmıştır.

2. Başlarken

Donanım Codelab'ini tamamlama

Bu Codelab'e başlamadan önce nRF52840 Boards ve OpenThread ile Thread Ağı Oluşturma Codelab'ini tamamlamanız gerekir:

  • Bina oluşturmak ve yanıp söndürmek için ihtiyacınız olan tüm yazılımları ayrıntılarıyla gösterir
  • OpenThread oluşturmayı ve Nordic nRF52840 kartlarına yüklemeyi öğretir.
  • Thread ağının temel bilgilerini gösterir

OpenThread oluşturmak ve panoları yüklemek için gerekli olan ortam ayarlarının hiçbiri bu Codelab'de ayrıntılı olarak anlatılmıştır. Yalnızca kartların yanıp sönmesiyle ilgili temel talimatlar verilmiştir. Build a Thread Network Codelab'i tamamladığınız varsayılır.

Linux makinesi

Bu Codelab, tüm Thread geliştirme kartlarının yanıp sönmesi için i386 veya x86 tabanlı bir Linux makinesi kullanacak şekilde tasarlanmıştır. Tüm adımlar Ubuntu 14.04.5 LTS (Trusty Tahr) sürümünde test edilmiştir.

Nordic yarı iletken nRF52840 kart

Bu Codelab'de üç nRF52840 PDK kartı kullanılır.

a6693da3ce213856.png

Yazılımı Yükle

OpenThread oluşturmak ve yüklemek için SEGGER J-Link, nRF5x Komut Satırı araçlarını, ARM GNU Araç Zinciri'ni ve çeşitli Linux paketlerini yüklemeniz gerekir. Build a Thread Network Codelab'i gerektiği gibi tamamladıysanız ihtiyacınız olan her şey zaten yüklüdür. Aksi takdirde, OpenThread’i derleyip nRF52840 geliştirme kartlarına yükleyebildiğinizden emin olmak için devam etmeden önce bu Codelab'i tamamlayın.

3. Depoyu klonlama

OpenThread, bu Codelab için başlangıç noktası olarak kullanabileceğiniz örnek uygulama koduyla birlikte gelir.

OpenThread Nordic nRF528xx örnek deposunu klonlayın ve OpenThread oluşturun:

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

4. OpenThread API ile İlgili Temel Bilgiler

OpenThread'in herkese açık API'leri OpenThread deposunda ./openthread/include/openthread konumunda bulunur. Bu API'ler, uygulamalarınızda kullanılmak üzere hem Thread hem de platform düzeyinde çeşitli OpenThread özelliklerine ve işlevlerine erişim sağlar:

  • OpenThread örneği bilgileri ve kontrolü
  • IPv6, UDP ve CoAP gibi uygulama hizmetleri
  • Denetçi ve Birleştirici rolleriyle birlikte ağ kimlik bilgisi yönetimi
  • Sınır Yönlendirici yönetimi
  • Çocuk Gözetimi ve Sıkışma Algılama gibi gelişmiş özellikler

Tüm OpenThread API'leriyle ilgili referans bilgileri openthread.io/reference adresinde bulabilirsiniz.

API kullanma

API kullanmak için başlık dosyasını uygulama dosyalarınızdan birine ekleyin. Ardından istediğiniz işlevi çağırın.

Örneğin, OpenThread'e eklenen CLI örnek uygulaması aşağıdaki API başlıklarını kullanır:

./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 örneği

otInstance yapısı, OpenThread API'leriyle çalışırken sıklıkla kullanacağınız bir yapıdır. Başlatılan bu yapı, OpenThread kitaplığının statik örneğini temsil eder ve kullanıcının OpenThread API çağrıları yapmasına olanak tanır.

Örneğin OpenThread örneği, CLI örnek uygulamasının main() işlevinde başlatılır:

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

Platforma özgü işlevler

OpenThread'deki örnek uygulamalardan birine platforma özel işlevler eklemek istiyorsanız öncelikle tüm işlevler için otSys ad alanını kullanarak bunları ./openthread/examples/platforms/openthread-system.h başlığında bildirin. Ardından bunları platforma özel bir kaynak dosyaya uygulayın. Bu şekilde özetlendiğinde, aynı işlev başlıklarını diğer örnek platformlar için kullanabilirsiniz.

Örneğin, nRF52840 düğmelerini bağlamak için kullanacağımız GPIO işlevleri ve LED'ler, openthread-system.h dilinde belirtilmelidir.

./openthread/examples/platforms/openthread-system.h dosyasını tercih ettiğiniz metin düzenleyicide açın.

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

İŞLEM: Platforma özel GPIO işlev bildirimleri ekleyin.

openthread/instance.h başlığı için #include öğesinden sonra şu işlev bildirimlerini ekleyin:

/**
 * 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);

Bunları bir sonraki adımda uygulayacağız.

otSysButtonProcess işlev bildiriminin otInstance kullandığını unutmayın. Bu şekilde, uygulama gerekirse bir düğmeye basıldığında OpenThread örneğiyle ilgili bilgilere erişebilir. Her şey uygulamanızın ihtiyaçlarına bağlıdır. İşlevi uygulamanızda bu koda ihtiyaç duymuyorsanız bazı araç zincirlerinde kullanılmayan değişkenlerle ilgili derleme hatalarını engellemek için OpenThread API'den OT_UNUSED_VARIABLE makrosu kullanabilirsiniz. Buna daha sonra örnekler vereceğiz.

5. GPIO platform soyutlamasını uygulama

Önceki adımda, ./openthread/examples/platforms/openthread-system.h ürünündeki GPIO için kullanılabilecek platforma özel işlev bildirimlerini gözden geçirdik. nRF52840 geliştirici kartlarındaki düğmelere ve LED'lere erişmek için bu işlevleri nRF52840 platformunda uygulamanız gerekir. Bu kodda, şu işlevleri ekleyeceksiniz:

  • GPIO PIN'lerini ve modlarını başlatma
  • Pim üzerindeki voltajı kontrol etme
  • GPIO kesintilerini etkinleştir ve geri arama kaydetme

./src/src dizininde gpio.c adlı yeni bir dosya oluşturun. Bu yeni dosyaya aşağıdaki içeriği ekleyin.

./src/src/gpio.c (yeni dosya)

İŞLEM: Tanımları ekleyin.

Bunlar, nRF52840'a özgü değerler ile OpenThread uygulama düzeyinde kullanılan değişkenler arasında soyutlama görevi görür.

/**
 * @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 düğmeleri ve LED'leri hakkında daha fazla bilgi için Nordic Semiconuctionor Infocenter'a bakın.

İŞLEM: Üstbilgi şunları ekle.

Ardından, GPIO işlevi için ihtiyacınız olan bilgileri içeren üstbilgiyi ekleyin.

/* 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"

İŞLEM: Düğme 1 için geri çağırma ve kesinti işlevleri ekleyin.

Sonra bu kodu ekleyin. in_pin1_handler işlevi, düğmeye basma işlevi ilk kullanıma hazırlandığında (bu dosyanın ilerleyen kısımlarında) kaydedilen geri çağırmadır.

in_pin1_handler işlevine iletilen değişkenler işlevde kullanılmadığından bu geri çağırmanın OT_UNUSED_VARIABLE makrosunu nasıl kullandığını unutmayın.

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

İŞLEM: LED'leri yapılandırmak için bir işlev ekleyin.

Başlatma sırasında tüm LED'lerin modunu ve durumunu yapılandırmak için bu kodu ekleyin.

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

İŞLEM: LED'in modunu ayarlamak için bir işlev ekleyin.

Cihazın rolü değiştiğinde bu işlev kullanılır.

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

İŞLEM: LED'in modunu açıp kapatmak için bir işlev ekleyin.

Bu işlev, cihaz çoklu yayın UDP mesajı aldığında LED4'ü açmak/kapatmak için kullanılır.

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

İŞLEM: Başlatma ve düğmelere basma işlemlerini başlatmak için işlevler ekleyin.

İlk işlev, düğmeye basmak için panoyu başlatır, ikincisi ise 1. Düğmeye basıldığında çoklu yayın UDP mesajını gönderir.

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

İŞLEM: gpio.c dosyasını kaydedin ve kapatın.

6. API: Cihaz rolü değişikliklerine tepki verme

Uygulamamızda, cihaz rolüne bağlı olarak farklı LED'lerin yanmasını istiyoruz. Şu rolleri izleyelim: Lider, Yönlendirici, Son Cihaz. Bunları aşağıdaki şekilde LED'lere atayabiliriz:

  • LED1 = Lider
  • LED2 = Yönlendirici
  • LED3 = Son Cihaz

Bu işlevi etkinleştirmek için uygulamanın, cihaz rolünün ne zaman değiştiğini ve yanıt olarak doğru LED'in nasıl açılacağını bilmesi gerekir. İlk kısım için OpenThread örneğini, ikincisi için GPIO platform soyutlamasını kullanacağız.

./openthread/examples/apps/cli/main.c dosyasını tercih ettiğiniz metin düzenleyicide açın.

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

İŞLEM: Üstbilgi şunları ekle.

main.c dosyasının dahil edilenler bölümüne, rol değiştirme özelliği için ihtiyaç duyacağınız API başlığı dosyalarını ekleyin.

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

İŞLEM: OpenThread örnek durumu değişikliği için işleyici işlevi bildirimi ekleyin.

Bu beyanı, başlık #if ifadelerinden sonra ve bu ifadelerin önüne gelecek şekilde main.c öğesine ekleyin. Bu işlev, ana uygulamadan sonra tanımlanacak.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

İŞLEM: Durum değişikliği işleyici işlevi için bir geri çağırma kaydı ekleyin.

main.c içinde, bu işlevi otAppCliInit çağrısından sonra main() işlevine ekleyin. Bu geri çağırma kaydı, OpenThread örneği durumu her değiştiğinde OpenThread'e handleNetifStateChange işlevini çağırmasını bildirir.

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

İŞLEM: Durum değişikliği uygulamasını ekleyin.

main.c içinde, main() işlevinden sonra handleNetifStateChanged işlevini uygulayın. Bu işlev, OpenThread örneğinin OT_CHANGED_THREAD_ROLE işaretini kontrol eder ve değiştiyse LED'leri gerektiği şekilde açar/kapatır.

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'i açmak için çoklu yayın özelliğini kullanma

Uygulamamızda, kartlardan birinde Button1 tuşuna basıldığında ağdaki diğer tüm cihazlara UDP mesajları da göndermek istiyoruz. Mesajın alındığını onaylamak için yanıt olarak diğer panoların LED4'ünü etkinleştireceğiz.

Bu işlevi etkinleştirmek için uygulamanın şunları yapması gerekir:

  • Başlatma sırasında UDP bağlantısını başlat
  • Bağlantılı yerel çoklu yayın adresine UDP mesajı gönderilebilmelidir
  • Gelen UDP mesajlarını işleme
  • Gelen UDP mesajlarına yanıt olarak LED4'ü açar/kapatır

./openthread/examples/apps/cli/main.c dosyasını tercih ettiğiniz metin düzenleyicide açın.

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

İŞLEM: Üstbilgi şunları ekle.

Çoklu yayın UDP özelliği için ihtiyaç duyacağınız API başlığı dosyalarını main.c dosyasının üst kısmındaki içerir bölümünde ekleyin.

#include <string.h>

#include <openthread/message.h>
#include <openthread/udp.h>

#include "utils/code_utils.h"

code_utils.h başlığı, çalışma zamanı koşullarını doğrulayan ve hataları ayrıntılı bir şekilde ele alan otEXPECT ve otEXPECT_ACTION makroları için kullanılır.

İŞLEM: Tanımlar ve sabit değerler ekleyin:

main.c dosyasında, dahil et bölümünden sonra ve tüm #if ifadelerinden önce UDP'ye özel sabit değerleri ekleyin ve şunları tanımlar:

#define UDP_PORT 1212

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

ff03::1, ağ-yerel çoklu yayın adresidir. Bu adrese gönderilen tüm iletiler, ağdaki tüm tam ileti dizisi cihazlarına gönderilir. OpenThread'de çoklu yayın desteği hakkında daha fazla bilgi için openthread.io'da çoklu yayın sayfasına bakın.

İŞLEM: İşlev bildirimleri ekleyin.

main.c dosyasında, otTaskletsSignalPending tanımından sonra ve main() işlevinden önce UDP'ye özel işlevler ve bir UDP soketini temsil eden statik bir değişken ekleyin:

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;

İŞLEM: GPIO LED'lerini ve düğmesini başlatmak için çağrı ekleyin.

main.c ürününde bu işlev çağrılarını, otSetStateChangedCallback çağrısından sonra main() işlevine ekleyin. Bu işlevler GPIO ve GPIOTE PIN'lerini başlatır ve düğme push etkinliklerini işlemek için bir düğme işleyici ayarlar.

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

İŞLEM: UDP başlatma çağrısını ekleyin.

main.c içinde, az önce eklediğiniz otSysButtonInit çağrısından sonra bu işlevi main() işlevine ekleyin:

initUdp(instance);

Bu çağrı, uygulama başlatıldığında bir UDP soketinin başlatılmasını sağlar. Bu olmadan cihaz UDP mesajlarını gönderemez veya alamaz.

İŞLEM: GPIO düğmesi etkinliğini işlemek için çağrı ekleyin.

main.c işlevinde bu işlev çağrısını main() işlevine, otSysProcessDrivers çağrısından sonra, while döngüsüne ekleyin. gpio.c öğesinde belirtilen bu işlev, düğmeye basılıp basılmadığını kontrol eder. Basılmışsa yukarıdaki adımda ayarlanan işleyiciyi (handleButtonInterrupt) çağırır.

otSysButtonProcess(instance);

İŞLEM: Düğme Kesme İşleyicisini uygulayın.

main.c öğesinde, handleButtonInterrupt işlevinin uygulamasını önceki adımda eklediğiniz handleNetifStateChanged işlevinden sonra ekleyin:

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

İŞLEM: UDP ilk kullanıma hazırlamayı uygulayın.

main.c içinde, initUdp işlevinin uygulamasını az önce eklediğiniz handleButtonInterrupt işlevinden sonra ekleyin:

/**
 * 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, daha önce tanımladığınız bağlantı noktasıdır (1212). otUdpOpen işlevi soketi açar ve UDP mesajı alındığında bir geri çağırma işlevi (handleUdpReceive) kaydeder. otUdpBind, OT_NETIF_THREAD öğesini ileterek soketi Thread ağ arayüzüne bağlar. Diğer ağ arayüzü seçenekleri için UDP API Referansı bölümündeki otNetifIdentifier sıralamasına bakın.

İŞLEM: UDP mesajlaşmasını uygulayın.

main.c içinde, sendUdp işlevinin uygulamasını az önce eklediğiniz initUdp işlevinden sonra ekleyin:

/**
 * 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 ve otEXPECT_ACTION makrolarına dikkat edin. Bunlar, UDP mesajının geçerli olduğundan ve arabelleğe doğru bir şekilde ayrılmasını sağlar. Aksi takdirde, işlev arabelleği boşalttığı exit blokuna atlayarak hataları zarif bir şekilde ele alır.

UDP'yi başlatmak için kullanılan işlevler hakkında daha fazla bilgi için openthread.io sitesindeki IPv6 ve UDP Referansları bölümüne bakın.

İŞLEM: UDP ileti işlemesini uygulayın.

main.c işlevinde, handleUdpReceive işlevinin uygulamasını az önce eklediğiniz sendUdp işlevinden sonra ekleyin. Bu işlev yalnızca LED4'ü açıp kapatır.

/**
 * 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 ağını yapılandırma

Örnekte kolaylık sağlaması için, cihazlarımızın Thread'i hemen başlatmasını ve açık olduklarında bir ağa katılmasını istiyoruz. Bunun için otOperationalDataset yapısını kullanacağız. Bu yapı, Thread ağ kimlik bilgilerini bir cihaza iletmek için gereken tüm parametreleri barındırır.

Bu yapının kullanılması, uygulamamızı daha güvenli hale getirmek ve ağımızdaki Thread düğümlerini yalnızca uygulamayı çalıştıranlarla sınırlandırmak için OpenThread'de yerleşik olarak bulunan ağ varsayılanlarını geçersiz kılar.

./openthread/examples/apps/cli/main.c dosyasını tercih ettiğiniz metin düzenleyicide tekrar açın.

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

İŞLEM: Üstbilgi dahil et ekleyin.

main.c dosyasının üst kısmındaki içerirler bölümünde, Thread ağını yapılandırmak için ihtiyaç duyacağınız API başlığı dosyasını ekleyin:

#include <openthread/dataset_ftd.h>

İŞLEM: Ağ yapılandırmasını ayarlamak için işlev bildirimi ekleyin.

Bu beyanı, başlık #if ifadelerinden sonra ve bu ifadelerin önüne gelecek şekilde main.c öğesine ekleyin. Bu işlev, ana uygulama işlevinden sonra tanımlanacak.

static void setNetworkConfiguration(otInstance *aInstance);

İŞLEM: Ağ yapılandırması çağrısı ekleyin.

main.c ürününde, bu işlev çağrısını otSetStateChangedCallback çağrısından sonra main() işlevine ekleyin. Bu işlev, Thread ağ veri kümesini yapılandırır.

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

İŞLEM: Thread ağ arayüzünü ve yığınını etkinleştirmek için çağrı ekleyin.

main.c ürününde bu işlev çağrılarını, otSysButtonInit çağrısından sonra main() işlevine ekleyin.

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

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

İŞLEM: Thread ağ yapılandırmasını uygulayın.

main.c içinde, main() işlevinden sonra setNetworkConfiguration işlevinin uygulamasını ekleyin:

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

İşlevde ayrıntılı olarak belirtildiği üzere, bu uygulama için kullandığımız Thread ağ parametreleri şunlardır:

  • Kanal = 15
  • PAN Kimliği = 0x2222
  • Genişletilmiş PAN kimliği = C0DE1AB5C0DE1AB5
  • Ağ Anahtarı = 1234C0DE1AB51234C0DE1AB51234C0DE
  • Ağ Adı = OTCodelab

Ayrıca, bu şekilde Yönlendirici Seçimi Ses dalgalanmasını azaltıyoruz. Böylece, cihazlarımız demo yapmak için rolleri daha hızlı değiştiriyor. Bunun yalnızca düğüm bir FTD (tam iş parçacığı cihazı) olması durumunda yapıldığını unutmayın. Bir sonraki adımda bu konu hakkında daha fazla bilgi edineceksiniz.

9. API: Kısıtlanmış işlevler

OpenThread'in bazı API'leri yalnızca demo veya test amacıyla değiştirilmesi gereken ayarları değiştirir. Bu API'ler, OpenThread kullanan bir uygulamanın üretim dağıtımında kullanılmamalıdır.

Örneğin, otThreadSetRouterSelectionJitter işlevi, Son Cihazın kendisini bir Yönlendirici'ye tanıtması için gereken süreyi (saniye cinsinden) ayarlar. İleti dizisi spesifikasyonuna göre bu değer için varsayılan değer 120'dir. Bu Codelab'de kullanım kolaylığı sağlamak amacıyla kodu 20 olarak değiştireceğiz. Böylece, Thread düğümünün rollerini değiştirmesi için çok fazla beklemenize gerek yok.

Not: MTD cihazları yönlendiriciler olmaz ve otThreadSetRouterSelectionJitter gibi bir işlev için destek, MTD derlemesine dahil değildir. Daha sonra CMake seçeneği -DOT_MTD=OFF belirtmemiz gerekecek. Aksi takdirde derleme hatasıyla karşılaşacağız.

OPENTHREAD_FTD ön işlemci yönergesinde yer alan otThreadSetRouterSelectionJitter işlevi tanımına bakarak bunu doğrulayabilirsiniz:

./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 güncellemeleri

Uygulamanızı oluşturmadan önce, üç CMake dosyası için birkaç küçük güncelleme yapılması gerekiyor. Bunlar, derleme sistemi tarafından uygulamanızı derlemek ve bağlamak için kullanılır.

./third_party/NordicSemiconductor/CMakeLists.txt

Şimdi GPIO işlevlerinin uygulamada tanımlandığından emin olmak için NordicSemiconctionor CMakeLists.txt öğesine birkaç işaretler ekleyin.

İŞLEM: CMakeLists.txt dosyasına işaret ekleyin.

./third_party/NordicSemiconductor/CMakeLists.txt sayfasını tercih ettiğiniz metin düzenleyicide açın ve COMMON_FLAG bölümüne aşağıdaki satırları ekleyin.

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

Yeni gpio.c kaynak dosyasını eklemek için ./src/CMakeLists.txt dosyasını düzenleyin:

İŞLEM: Gpio kaynağını ./src/CMakeLists.txt dosyasına ekleyin.

./src/CMakeLists.txt dosyasını tercih ettiğiniz metin düzenleyicide açın ve dosyayı NRF_COMM_SOURCES bölümüne ekleyin.

...

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

...

./third_party/NordicSemiconductor/CMakeLists.txt

Son olarak, nrfx_gpiote.c sürücü dosyasını NordicSemiconctionor CMakeLists.txt dosyasına ekleyin. Böylece bu dosya, İskandinav sürücülerinin kitaplık derlemesine dahil edilir.

İŞLEM: gpio sürücüsünü NordicSemiconctionor CMakeLists.txt dosyasına ekleyin.

./third_party/NordicSemiconductor/CMakeLists.txt dosyasını tercih ettiğiniz metin düzenleyicide açın ve dosyayı COMMON_SOURCES bölümüne ekleyin.

...

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

11. Cihazları kurma

Tüm kod güncellemelerinizi yaptıktan sonra, uygulamayı derlemek ve üç Nordic nRF52840 geliştirme panosuna yüklemek için hazırsınız. Her cihaz, tam işlevsel cihaz (FTD) olarak çalışır.

OpenThread derleyin

nRF52840 platformu için OpenThread FTD ikili programlarını oluşturun.

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

OpenThread FTD CLI ikili programıyla dizine gidin ve bunu, ARM Katıştırılmış Araç Zinciri ile onaltılık biçime dönüştürün:

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

Panoları fırlatın

ot-cli-ftd.hex dosyasını her bir nRF52840 kartına Flash ile yükleyin.

USB kablosunu nRF52840 kartındaki harici güç pimininin yanında bulunan Mikro USB hata ayıklama bağlantı noktasına takın ve ardından Linux makinenize takın. Doğru şekilde ayarlayın, LED5 açık olmalıdır.

20a3b4b480356447.png

Önceki gibi nRF52840 kartının seri numarasını not edin:

c00d519ebec7e5f0.jpeg

nRFx Komut Satırı Araçlarının bulunduğu yere gidin ve kartın seri numarasını kullanarak OpenThread CLI FTD onaltılık dosyasını nRF52840 kartına yükleyin:

$ cd ~/nrfjprog
$ ./nrfjprog -f nrf52 -s 683704924 --verify --chiperase --program \
       ~/openthread/output/nrf52840/bin/ot-cli-ftd.hex --reset

LED5 yanıp sönerken kısa süreliğine söner. İşlem başarılı olduktan sonra aşağıdaki çıkış oluşturulur:

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.

"Oyun tahtalarını yakıp söndür" komutunu tekrar edin iki pano için de aynı işlemi yapıyor. Her bir kart Linux makinesine aynı şekilde bağlanmalıdır. Ayrıca, kartın seri numarası dışında flash komutu aynıdır. Kartların benzersiz seri numarasını

nrfjprog yanıp sönen komut.

Başarılı olursa her kartta LED1, LED2 veya LED3 yanar. Hatta yanıp söndükten kısa bir süre sonra (cihaz rolü değiştirme özelliği) yanan LED anahtarını 3'ten 2'ye (veya 2'ye 1'e) görebilirsiniz.

12. Uygulama işlevselliği

Artık üç nRF52840 anakartına da güç ve OpenThread uygulamamız çalışıyor olmalıdır. Daha önce ayrıntılı bir şekilde açıklandığı gibi, bu uygulamanın iki temel özelliği vardır.

Cihaz rolü göstergeleri

Her kartta yanan LED, Thread düğümünün mevcut rolünü yansıtır:

  • LED1 = Lider
  • LED2 = Yönlendirici
  • LED3 = Son Cihaz

Rol değiştikçe yanan LED de değişir. Bu değişiklikleri her bir cihaz açıldıktan sonraki 20 saniye içinde panoda veya ikisinde görmüş olmalısınız.

UDP Çoklu Yayın

Kart üzerinde Button1 tuşuna basıldığında, Thread ağındaki diğer tüm düğümleri içeren bağlantılı yerel çoklu yayın adresine bir UDP mesajı gönderilir. Bu mesaja yanıt olarak, diğer tüm kartlardaki LED4 açılır veya kapatılır. LED4, başka bir UDP mesajı alana kadar her kart için açık veya kapalı kalır.

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. Demo: Cihaz rolü değişikliklerini gözlemleme

Flash yüklediğiniz cihazlar, Yönlendirici Uygun Son Cihaz (REED) olarak adlandırılan belirli bir Tam İş Parçacıklı Cihaz (FTD) türüne sahiptir. Yani ya bir Yönlendirici ya da Son Cihaz olarak çalışabilir ve kendilerini bir Son Cihazdan Yönlendiriciye tanıtabilirler.

Thread, en fazla 32 Yönlendirici destekleyebilir ancak Yönlendirici sayısını 16 ile 23 arasında tutmaya çalışır. Bir REED, Son Cihaz olarak bağlanırsa ve Yönlendirici sayısı 16'dan azsa, kendisini otomatik olarak bir Yönlendirici'ye yükseltir. Bu değişiklik, uygulamada otThreadSetRouterSelectionJitter değerini ayarladığınız saniye sayısı içinde rastgele bir zamanda (20 saniye) gerçekleşmelidir.

Her Thread ağının aynı zamanda bir Lideri vardır. Bu lider, Thread ağındaki Yönlendirici grubunu yönetmekten sorumlu bir Yönlendiricidir. Tüm cihazlar açıkken 20 saniye sonunda bu cihazlardan biri Lider (LED1 açık), diğer ikisi ise Yönlendirici (LED2 açık) olmalıdır.

4e1e885861a66570.png

Lideri kaldır

Lider, Thread ağından kaldırılırsa farklı bir Yönlendirici, ağın hâlâ bir Lider'e sahip olduğundan emin olmak için kendisini bir Lidere yükseltir.

Güç anahtarını kullanarak Lider panosunu (LED1 ışıklı) kapatın. Yaklaşık 20 saniye bekleyin. Kalan iki karttan birinde LED2 (Yönlendirici) söner ve LED1 (Lider) yanar. Bu cihaz artık Thread ağının lideri.

4c57c87adb40e0e3.png

Orijinal Lider panosu'nu tekrar etkinleştirin. Cihaz, Thread ağına otomatik olarak Son Cihaz olarak yeniden katılır (LED3 yanar). 20 saniye içinde (Freç Seçimi Ses dalgalanması) kendini bir Yönlendiriciye yükseltir (LED2 yanar).

5f40afca2dcc4b5b.png

Kartları sıfırla

Üç kartı da kapatıp tekrar açın ve LED'leri kontrol edin. Gücü verilen ilk kart Lider rolünde (LED1 yanar) başlamalıdır. Thread ağındaki ilk Yönlendirici otomatik olarak Lider olur.

Diğer iki kart, ağa başlangıçta Son Cihazlar (LED3 yanar) olarak bağlanır ancak kendilerini 20 saniye içinde Yönlendiricilere tanıtmaları gerekir (LED2 yanar).

Ağ bölümleri

Panelleriniz yeterli güç almıyorsa veya aralarında radyo bağlantısı zayıfsa Thread ağı bölümlere ayrılabilir ve lider olarak gösterilen birden fazla cihazınız olabilir.

İş parçacığı kendi kendine iyileştirilir, bu nedenle bölümlerin sonunda tek bir lider içeren tek bir bölüm halinde birleştirilmesi gerekir.

14. Demo: UDP çoklu yayın gönder

Önceki alıştırmaya devam ediyorsanız hiçbir cihazda LED4 yanmamalıdır.

Herhangi bir pano seçin ve Button1'e basın. Thread ağındaki uygulamayı çalıştıran diğer tüm kartlarda bulunan LED4, durumunu değiştirmelidir. Önceki alıştırmadan devam ediyorsanız şimdi etkin durumda olması gerekir.

f186a2618fdbe3fd.png

Aynı kart için Düğme1'e tekrar basın. Diğer tüm kartlardaki LED4 tekrar geçiş yapar.

Farklı bir karttaki Button1 düğmesine basın ve LED4'ün diğer kartlardaki açma/kapatma durumunu inceleyin. LED4'ün şu anda açık olduğu kartlardan birindeki Düğme1'e basın. LED4 bu kart için açık kalır ancak diğer kartları açar.

f5865ccb8ab7aa34.png

Ağ bölümleri

Panelleriniz bölümlendiyse ve aralarında birden fazla lider varsa çoklu yayın mesajının sonucu panolar arasında farklılık gösterir. Bölümlendirilmiş (ve bu nedenle bölümlendirilmiş Thread ağının tek üyesi olan) bir kartta Button1 düğmesine basarsanız diğer kartlardaki LED4 yanıt olarak yanmaz. Böyle bir durumda kartları sıfırlayın. İdeal olarak, tek bir Thread ağı yeniden oluşturulur ve UDP mesajlaşması düzgün çalışır.

15. Tebrikler!

OpenThread API'leri kullanan bir uygulama oluşturdunuz.

Artık şunları biliyorsunuz:

  • Nordic nRF52840 geliştirici kartlarındaki düğmeleri ve LED'leri programlama
  • Yaygın OpenThread API'leri ve otInstance sınıfı nasıl kullanılır?
  • OpenThread durum değişikliklerini izleme ve bu değişikliklere tepki verme
  • Thread ağındaki tüm cihazlara UDP mesajları gönderme
  • Makefiles'i değiştirme

Sonraki adımlar

Bu Codelab'den yola çıkarak aşağıdaki alıştırmaları deneyin:

  • GPIO modülünü, yerleşik LED'ler yerine GPIO pin'leri kullanacak şekilde değiştirin ve Yönlendirici rolüne göre rengi değişen harici RGB LED'leri bağlayın
  • Farklı bir örnek platform için GPIO desteği ekleme
  • Bir düğmeye basarak tüm cihazları pinglemek için çoklu yayın özelliğini kullanmak yerine, tek bir cihazı bulup pinglemek için Router/Leader API'yi kullanın.
  • OpenThread Sınır Yönlendirici kullanarak örgü ağınızı internete bağlayın ve LED'leri yakmak için Thread ağının dışından çoklu yayın yapın

Daha fazla bilgi

Aşağıdakiler de dahil olmak üzere çeşitli OpenThread kaynakları için openthread.io ve GitHub'a göz atın:

Referans: