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, Nest ürünlerinde kullanılan teknolojinin geliştiriciler tarafından yaygın olarak kullanılabilmesini sağlamak ve böylece bağlantılı ev ürünlerinin geliştirilmesini hızlandırmak için OpenThread'i yayınladı.

Thread spesifikasyonu, ev uygulamaları için IPv6 tabanlı güvenilir, güvenli ve düşük güç tüketen kablosuz cihazdan cihaza iletişim protokolünü tanımlar. OpenThread, IPv6, 6LoWPAN, MAC güvenliği ile IEEE 802.15.4, Mesh Link Establishment ve Mesh Routing 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ştirme kartlarındaki düğmeleri ve LED'leri programlama
  • Yaygın OpenThread API'lerini ve otInstance sınıfını kullanma
  • OpenThread durum değişikliklerini izleme ve bunlara tepki verme
  • Thread ağındaki tüm cihazlara UDP mesajları gönderme
  • Makefiles dosyalarını değiştirme

İhtiyacınız olanlar

Donanım:

  • 3 Nordic Semiconductor nRF52840 geliştirme kartı
  • Kartları bağlamak için 3 adet USB - Mikro USB kablosu
  • En az 3 USB bağlantı noktası olan bir Linux makine

Yazılım:

  • GNU Toolchain
  • Nordic nRF5x komut satırı araçları
  • Segger J-Link yazılımı
  • OpenThread
  • Git

Aksi belirtilmediği takdirde bu Codelab'in içeriği Creative Commons Attribution 3.0 Lisansı, 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 kartları ve OpenThread ile Thread ağı oluşturma Codelab'ini tamamlamanız gerekir. Bu Codelab'de:

  • Derleme ve yükleme için ihtiyacınız olan tüm yazılımları ayrıntılı olarak açıklar.
  • OpenThread'i nasıl oluşturacağınızı ve Nordic nRF52840 kartlarına nasıl yükleyeceğinizi öğretir.
  • Thread ağının temellerini gösterir.

OpenThread'i oluşturmak ve kartları yüklemek için gereken ortam kurulumunun hiçbiri bu codelab'de ayrıntılı olarak açıklanmamıştır. Yalnızca kartları yüklemeyle ilgili temel talimatlar verilmiştir. Build a Thread Network Codelab'i tamamladığınız varsayılır.

Linux makine

Bu Codelab, tüm Thread geliştirme kartlarına yazılım yüklemek 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) üzerinde test edilmiştir.

Nordic Semiconductor nRF52840 kartları

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

a6693da3ce213856.png

Yazılım yükleme

OpenThread'i oluşturup yüklemek için SEGGER J-Link, nRF5x Command Line Tools, ARM GNU Toolchain ve çeşitli Linux paketlerini yüklemeniz gerekir. Gerekli olduğu şekilde Build a Thread Network Codelab'i tamamladıysanız ihtiyacınız olan her şey zaten yüklüdür. Aksi takdirde, nRF52840 geliştirme kartlarına OpenThread'i oluşturup yükleyebildiğinizden emin olmak için devam etmeden önce bu Codelab'i tamamlayın.

3. Depoyu klonlama

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

OpenThread Nordic nRF528xx örnekleri deposunu klonlayın ve OpenThread'i oluşturun:

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

4. OpenThread API'sinin Temel Bilgileri

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
  • Komisyon üyesi ve katılımcı rolleriyle birlikte ağ kimlik bilgisi yönetimi
  • Sınır yönlendirici yönetimi
  • Çocuk Gözetimi ve Jam Algılama gibi gelişmiş özellikler

Tüm OpenThread API'leriyle ilgili referans bilgilerine openthread.io/reference adresinden ulaşabilirsiniz.

API kullanma

Bir API'yi kullanmak için üst dosyasını uygulama dosyalarınızdan birine ekleyin. Ardından, istediğiniz işlevi çağırın.

Örneğin, OpenThread ile birlikte gelen CLI örnek uygulamasında aşağıdaki API üstbilgileri kullanılı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ık sık kullanacağınız bir yapıdır. Bu yapı, başlatıldıktan sonra OpenThread kitaplığının statik bir ö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 ile birlikte verilen örnek uygulamalardan birine platforma özgü işlevler eklemek istiyorsanız önce bunları ./openthread/examples/platforms/openthread-system.h başlığında, tüm işlevler için otSys ad alanını kullanarak tanımlayın. Ardından, bunları platforma özel bir kaynak dosyada uygulayın. Bu şekilde özetlenmiş olarak, diğer örnek platformlar için aynı işlev başlıklarını kullanabilirsiniz.

Örneğin, nRF52840 düğmelerine ve LED'lerine bağlanmak için kullanacağımız GPIO işlevleri openthread-system.h içinde tanımlanmalıdır.

./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 ifadesinden 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 fonksiyon bildiriminde otInstance kullanıldığını unutmayın. Bu sayede, gerekirse bir düğmeye basıldığında uygulama OpenThread örneğiyle ilgili bilgilere erişebilir. Bu tamamen uygulamanızın ihtiyaçlarına bağlıdır. İşlevin uygulanmasında buna ihtiyacınız yoksa bazı araç zincirlerinde kullanılmayan değişkenlerle ilgili derleme hatalarını bastırmak için OpenThread API'deki OT_UNUSED_VARIABLE makrosunu kullanabilirsiniz. Bunun örneklerini daha sonra göreceğiz.

5. GPIO platformu soyutlamasını uygulama

Önceki adımda, ./openthread/examples/platforms/openthread-system.h içinde GPIO için kullanılabilecek platforma özgü işlev bildirimlerini inceledik. nRF52840 geliştirme kartlarındaki düğmelere ve LED'lere erişmek için bu işlevleri nRF52840 platformunda uygulamanız gerekir. Bu koda, aşağıdaki işlevleri ekleyeceksiniz:

  • GPIO pinlerini ve modlarını başlatma
  • Bir pindeki voltajı kontrol etme
  • GPIO kesmelerini etkinleştirme ve geri çağırma 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.

Bu tanımlar, nRF52840'a özgü değerler ile OpenThread uygulama düzeyinde kullanılan değişkenler arasında soyutlamalar olarak işlev 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 Semiconductor Infocenter'ı inceleyin.

İŞLEM: Üstbilgi eklemelerini ekleyin.

Ardından, GPIO işlevselliği için ihtiyacınız olan başlıkları 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: 1. düğme için geri çağırma ve kesme işlevleri ekleyin.

Ardından bu kodu ekleyin. in_pin1_handler işlevi, düğmeye basma işlevi başlatıldığında (bu dosyanın ilerleyen kısımlarında) kaydedilen geri çağırmadır.

OT_UNUSED_VARIABLE makrosuna iletilen değişkenler işlevde kullanılmadığından bu geri çağırmanın OT_UNUSED_VARIABLE makrosunu nasıl kullandığına dikkat edin.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;
}

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

İlk kullanıma hazırlama 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.

Bu işlev, cihazın rolü değiştiğinde 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 değiştirecek bir işlev ekleyin.

Bu işlev, cihaz çok noktalı UDP mesajı aldığında LED4'ü açıp 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: Düğmeye basma işlemlerini başlatmak ve işlemek için işlevler ekleyin.

İlk işlev, bir düğmeye basıldığında kartı başlatır. İkincisi ise 1. düğmeye basıldığında çok noktalı 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 Dosyayı kaydedip 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ı LED'lere şu şekilde atayabiliriz:

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

Bu işlevin etkinleştirilmesi için uygulamanın cihaz rolünün ne zaman değiştiğini ve buna karşılık olarak doğru LED'in nasıl açılacağını bilmesi gerekir. İlk kısımda OpenThread örneğini, ikinci kısımda ise 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 eklemelerini ekleyin.

main.c dosyasının includes bölümüne, rol değişikliği özelliği için ihtiyacınız olan API başlık dosyalarını ekleyin.

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

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

Bu bildirimi main.c öğesine, üstbilgi dahil edildikten sonra ve herhangi bir #if ifadesinden önce ekleyin. Bu işlev, ana uygulamadan sonra tanımlanır.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

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

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

/* 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şmişse LED'leri gerektiği gibi 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: Bir LED'i açmak için çoklu yayın kullanma

Ayrıca uygulamamızda, bir kartta Button1'e basıldığında ağdaki diğer tüm cihazlara UDP mesajları göndermek istiyoruz. İletinin alındığını onaylamak için diğer kartlarda LED4'ü açıp kapatacağız.

Bu işlevin etkinleştirilmesi için uygulamanın:

  • Başlangıçta bir UDP bağlantısı başlatma
  • Ağ yerel çoklu yayın adresine bir UDP mesajı gönderebilme
  • Gelen UDP mesajlarını işleme
  • Gelen UDP mesajlarına yanıt olarak LED4'ü açma/kapatma

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

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

İŞLEM: Üstbilgi eklemelerini ekleyin.

main.c dosyasının üst kısmındaki includes bölümüne, çoklu yayın UDP özelliği için ihtiyacınız olan API başlık dosyalarını ekleyin.

#include <string.h>

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

#include "utils/code_utils.h"

code_utils.h üstbilgisi, çalışma zamanı koşullarını doğrulayan ve hataları düzgün şekilde işleyen otEXPECT ve otEXPECT_ACTION makroları için kullanılır.

İŞLEM: Tanımlar ve sabitler ekleyin:

main.c dosyasında, includes bölümünden sonra ve herhangi bir #if ifadesinden önce UDP'ye özgü sabitler ve tanımlar ekleyin:

#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 mesajlar ağdaki tüm Tam İleti Dizisi Cihazları'na gönderilir. OpenThread'de çoklu yayın desteği hakkında daha fazla bilgi için Multicast on openthread.io (openthread.io'da çoklu yayın) başlıklı makaleyi inceleyin.

İŞLEM: İşlev bildirimleri ekleyin.

main.c dosyasında, otTaskletsSignalPending tanımından sonra ve main() işlevinden önce UDP'ye özgü işlevlerin yanı sıra 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 aramalar ekleyin.

main.c içinde, otSetStateChangedCallback çağrısından sonra main() işlevine şu işlev çağrılarını ekleyin. Bu işlevler, GPIO ve GPIOTE pinlerini başlatır ve düğmeye basma etkinliklerini işlemek için bir düğme işleyicisi ayarlar.

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

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

main.c içinde, yeni eklediğiniz otSysButtonInit çağrısından sonra main() işlevine şu işlevi 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ı gönderemez veya alamaz.

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

main.c içinde, while döngüsünde, otSysProcessDrivers çağrısından sonra main() işlevine şu işlev çağrısını ekleyin. gpio.c içinde tanımlanan bu işlev, düğmenin basılıp basılmadığını kontrol eder. Basıldıysa yukarıdaki adımda ayarlanan işleyiciyi (handleButtonInterrupt) çağırır.

otSysButtonProcess(instance);

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

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

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

İŞLEM: UDP başlatma işlemini uygulayın.

main.c içinde, yeni eklediğiniz handleButtonInterrupt işlevinden sonra initUdp işlevinin uygulamasını 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 bir UDP mesajı alındığında geri çağırma işlevini (handleUdpReceive) kaydeder. otUdpBind, OT_NETIF_THREAD değerini ileterek soketi Thread ağ arayüzüne bağlar. Diğer ağ arayüzü seçenekleri için UDP API Referansı'ndaki otNetifIdentifier numaralandırmasına bakın.

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

main.c içinde, yeni eklediğiniz initUdp işlevinden sonra sendUdp işlevinin uygulamasını 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ını inceleyin. Bu işlevler, UDP mesajının geçerli olduğundan ve arabellekte doğru şekilde ayrıldığından emin olur. Aksi takdirde, arabelleği boşalttığı exit bloğuna atlayarak hataları düzgün şekilde işler.

UDP'yi başlatmak için kullanılan işlevler hakkında daha fazla bilgi edinmek için openthread.io'daki IPv6 ve UDP Referansları'na bakın.

İŞLEM: UDP mesaj işleme özelliğini uygulayın.

main.c içinde, yeni eklediğiniz sendUdp işlevinden sonra handleUdpReceive işlevinin uygulamasını 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

Gösterimi kolaylaştırmak için cihazlarımızın açıldıklarında hemen Thread'i başlatmasını ve 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 içerir.

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ırlamak için OpenThread'e yerleştirilmiş 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 ekleme ifadesini ekleyin.

main.c dosyasının üst kısmındaki includes bölümüne, Thread ağını yapılandırmak için ihtiyacınız olan API başlık dosyasını ekleyin:

#include <openthread/dataset_ftd.h>

EYLEM: Ağ yapılandırmasını ayarlamak için işlev tanımlaması ekleyin.

Bu bildirimi main.c öğesine, üstbilgi dahil edildikten sonra ve herhangi bir #if ifadesinden önce ekleyin. Bu işlev, ana uygulama işlevinden sonra tanımlanır.

static void setNetworkConfiguration(otInstance *aInstance);

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

main.c içinde, otSetStateChangedCallback çağrısından sonra main() işlevine şu işlev çağrısını 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 aramalar ekleyin.

main.c içinde, otSysButtonInit çağrısından sonra main() işlevine şu işlev çağrılarını 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 gibi, bu uygulama için kullandığımız Thread ağı parametreleri şunlardır:

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

Ayrıca, bu bölümde yönlendirici seçimi titremesini azaltırız. Böylece cihazlarımız, demo amacıyla rolleri daha hızlı değiştirir. Bu işlemin yalnızca düğüm bir FTD (Full Thread Device) ise yapıldığını unutmayın. Bu konuyla ilgili daha fazla bilgiyi sonraki adımda bulabilirsiniz.

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, bir uç cihazın kendisini yönlendiriciye tanıtması için gereken süreyi (saniye cinsinden) ayarlar. Bu değerin varsayılanı, Thread Spesifikasyonu'na göre 120'dir. Bu Codelab'de kolaylık sağlamak için bu değeri 20 olarak değiştireceğiz. Böylece, Thread düğümünün rol değiştirmesi için çok uzun süre beklemeniz gerekmeyecek.

Not: MTD cihazlar yönlendirici olmaz ve otThreadSetRouterSelectionJitter gibi bir işlevin desteği MTD derlemesine dahil edilmez. Daha sonra CMake seçeneğini -DOT_MTD=OFF belirtmemiz gerekir. Aksi takdirde derleme hatasıyla karşılaşırız.

Bunu, otThreadSetRouterSelectionJitter işlev tanımına bakarak doğrulayabilirsiniz. Bu tanım, OPENTHREAD_FTD ön işlemci yönergesi içinde yer alır:

./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ında küçük güncellemeler yapmanız gerekir. Bunlar, uygulamanızı derlemek ve bağlamak için derleme sistemi tarafından kullanılır.

./third_party/NordicSemiconductor/CMakeLists.txt

GPIO işlevlerinin uygulamada tanımlandığından emin olmak için NordicSemiconductor CMakeLists.txt öğesine bazı işaretler ekleyin.

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

./third_party/NordicSemiconductor/CMakeLists.txt dosyası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ı NordicSemiconductor CMakeLists.txt dosyasına ekleyin. Böylece, Nordic sürücülerinin kitaplık derlemesine dahil edilir.

İŞLEM: gpio sürücüsünü NordicSemiconductor 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üncellemeleri tamamlandığında uygulamayı oluşturup üç Nordic nRF52840 geliştirme kartına da yükleyebilirsiniz. Her cihaz, Tam Thread Cihazı (FTD) olarak işlev görür.

OpenThread'i derleme

nRF52840 platformu için OpenThread FTD ikili dosyaları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ının bulunduğu dizine gidin ve ARM Embedded Toolchain 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ı yükleme

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

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

20a3b4b480356447.png

Daha önce olduğu gibi, nRF52840 kartının seri numarasını not edin:

c00d519ebec7e5f0.jpeg

nRFx Command Line Tools'un konumuna gidin ve kartın seri numarasını kullanarak OpenThread CLI FTD hex 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

Yanıp sönme sırasında LED5 kısa süreliğine kapanır. Başarılı olursa aşağıdaki çıktı 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.

Diğer iki kart için de bu "kartları yükleme" adımını tekrarlayın. Her kart, Linux makinesine aynı şekilde bağlanmalıdır ve kartın seri numarası hariç olmak üzere, yükleme komutu aynıdır. Her kartı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önme işleminden kısa süre sonra yanan LED'in 3'ten 2'ye (veya 2'den 1'e) değiştiğini görebilirsiniz (cihaz rolü değiştirme özelliği).

12. Uygulama işlevselliği

Üç nRF52840 kartı da artık çalıştırılıyor olmalı ve OpenThread uygulamamız çalışıyor olmalıdır. Daha önce ayrıntılı olarak açıklandığı gibi bu uygulamanın iki temel özelliği vardır.

Cihaz rolü göstergeleri

Her karttaki 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 cihaz açıldıktan sonraki 20 saniye içinde bir veya iki panoda görmüş olmanız gerekir.

UDP Çoklu Yayın

Bir kartta Button1'e basıldığında, Thread ağındaki diğer tüm düğümleri içeren mesh-local çoklu yayın adresine bir UDP mesajı gönderilir. Bu mesaj alındığında diğer tüm kartlardaki LED4 açılır veya kapanır. LED4, başka bir UDP mesajı alana kadar her kartta açık veya kapalı kalır.

203dd094acca1f97.png

9bbd96d9b1c63504.png

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

Flaşladığınız cihazlar, Yönlendiriciye Uygun Uç Cihaz (REED) adı verilen belirli bir tür Tam Thread Cihazı (FTD) olmalıdır. Bu nedenle, yönlendirici veya uç cihaz olarak işlev görebilir ve kendilerini uç cihazdan yönlendiriciye yükseltebilirler.

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

Her Thread ağında, Thread ağındaki yönlendiricileri yönetmekten sorumlu bir yönlendirici olan Lider de bulunur. Tüm cihazlar açıkken 20 saniye sonra bunlardan biri Lider (LED1 açık), diğer ikisi ise Yönlendirici (LED2 açık) olmalıdır.

4e1e885861a66570.png

Lideri kaldırma

Lider, Thread ağından kaldırılırsa ağda hâlâ bir lider olduğundan emin olmak için farklı bir yönlendirici kendisini lider olarak tanıtır.

Güç anahtarını kullanarak skor tablosunu (LED1'in yandığı) kapatın. 20 saniye kadar bekleyin. Kalan iki karttan birinde LED2 (Yönlendirici) kapanacak ve LED1 (Lider) yanacak. Bu cihaz artık Thread ağının Lideri.

4c57c87adb40e0e3.png

Orijinal skor tablosunu tekrar etkinleştirin. Thread ağına otomatik olarak bir uç cihaz olarak yeniden katılmalıdır (LED3 yanar). 20 saniye içinde (yönlendirici seçimi titremesi) kendisini yönlendirici olarak tanıtır (LED2 yanar).

5f40afca2dcc4b5b.png

Panoları sıfırlama

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

Diğer iki kart başlangıçta ağa Uç Cihazlar olarak bağlanır (LED3 yanar) ancak 20 saniye içinde kendilerini Yönlendiricilere yükseltmelidir (LED2 yanar).

Ağ bölümleri

Kartlarınız yeterli gücü almıyorsa veya aralarındaki radyo bağlantısı zayıfsa Thread ağı bölümlere ayrılabilir ve birden fazla cihaz Lider olarak gösterilebilir.

Thread kendi kendini onarır. Bu nedenle, bölümler sonunda tek bir bölüm halinde birleşerek tek bir lider oluşturur.

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

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

Herhangi bir tahtayı seçip 1. Düğme'ye basın. Uygulamayı çalıştıran Thread ağındaki diğer tüm kartlarda LED4'ün durumu değişmelidir. Önceki alıştırmadan devam ediyorsanız bu ayarların etkin olması gerekir.

f186a2618fdbe3fd.png

Aynı kart için Button1'e tekrar basın. Diğer tüm kartlardaki LED4 tekrar açılıp kapanmalıdır.

Farklı bir kartta Button1'e basın ve LED4'ün diğer kartlarda nasıl açılıp kapandığını gözlemleyin. LED4'ün şu anda açık olduğu kartlardan birinde Button1'e basın. LED4, o kart için açık kalır ancak diğerlerinde açılıp kapanır.

f5865ccb8ab7aa34.png

Ağ bölümleri

Panolarınız bölümlere ayrılmışsa 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ş bir kartta (bölümlendirilmiş Thread ağının tek üyesi) Düğme1'e basarsanız diğer kartlardaki LED4 yanıt olarak yanmaz. Bu durumda, kartları sıfırlayın. İdeal olarak, tek bir Thread ağı oluşturulur ve UDP mesajlaşması düzgün şekilde çalışır.

15. Tebrikler!

OpenThread API'lerini kullanan bir uygulama oluşturdunuz.

Artık şunları biliyorsunuz:

  • Nordic nRF52840 geliştirme kartlarındaki düğmeleri ve LED'leri programlama
  • Yaygın OpenThread API'lerini ve otInstance sınıfını kullanma
  • OpenThread durum değişikliklerini izleme ve bunlara tepki verme
  • Thread ağındaki tüm cihazlara UDP mesajları gönderme
  • Makefiles dosyalarını değiştirme

Sonraki adımlar

Bu Codelab'den yararlanarak aşağıdaki alıştırmaları deneyin:

  • GPIO modülünü, yerleşik LED'ler yerine GPIO pinlerini kullanacak şekilde değiştirin ve yönlendirici rolüne göre renk değiştiren harici RGB LED'leri bağlayın.
  • Farklı bir örnek platform için GPIO desteği ekleme
  • Bir düğmeye basarak tüm cihazlara ping atmak için çoklu yayın kullanmak yerine, tek bir cihazı bulup ping atmak için Router/Leader API'yi kullanın.
  • OpenThread sınır yönlendirici kullanarak bağlantılı 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'u inceleyin:

Referans: