OpenThread API'leriyle geliştirme

Bu codelab hakkında
schedule60 dakika
subjectSon güncelleme 5 Mayıs 2025
account_circleYazan: Jeff Bumgardner

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ı ev ürünleri geliştirme sürecini hızlandırmak için Nest ürünlerinde kullanılan teknolojiyi geliştiricilerin kullanımına sunmak amacıyla OpenThread'i kullanıma sundu.

Thread spesifikasyonu, ev uygulamaları için IPv6 tabanlı, güvenilir, güvenli ve düşük güç tüketimine sahip kablosuz cihazlar arası iletişim protokolü tanımlar. OpenThread; IPv6, 6LoWPAN, MAC güvenliğine sahip IEEE 802.15.4, Mesh Bağlantısı Kurma ve Mesh Yönlendirme dahil olmak üzere tüm Thread ağ katmanlarını uygular.

Bu Codelab'de, bir Thread ağı başlatmak, cihaz rollerindeki değişiklikleri izlemek ve 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
  • Bir Thread ağındaki tüm cihazlara UDP mesajı gönderme
  • Makefile'leri değiştirme

İhtiyacınız olanlar

Donanım:

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

Yazılım:

  • GNU Aracı Zinciri
  • Nordic 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ı, kod örnekleri ise Apache 2.0 Lisansı kapsamındadı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 şunlar yer alır:

  • Derleme ve önyükleme için ihtiyaç duyduğunuz tüm yazılımlar
  • OpenThread'i nasıl oluşturacağınızı ve Nordic nRF52840 kartlarına nasıl yükleyeceğinizi öğretir.
  • Mesaj dizisi ağının temel özelliklerini gösterir.

OpenThread'i derlemek ve kartları flaşlamak için gereken ortam ayarlarının hiçbiri bu Codelab'de ayrıntılı olarak açıklanmamıştır. Yalnızca kartları flaşlamaya yönelik temel talimatlar verilmiştir. Bir Konu Ağı Oluşturma Codelab'i tamamladığınız varsayılır.

Linux makinesi

Bu Codelab, tüm Thread geliştirme kartlarını flaşlamak 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ılmaktadır.

a6693da3ce213856.png

Yazılım yükleme

OpenThread'i derleyip flaşlamak için SEGGER J-Link, nRF5x Komut Satırı Araçları, ARM GNU Aracı Zinciri ve çeşitli Linux paketlerini yüklemeniz gerekir. Thread Network Codelab'i gerektiği gibi tamamladıysanız ihtiyacınız olan her şey zaten yüklü olacaktır. Aksi takdirde, OpenThread'i derleyip nRF52840 geliştirme kartlarına yükleyebilmeniz için devam etmeden önce bu Codelab'i tamamlayın.

3. Depoyu klonlama

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

OpenThread Nordic nRF528xx örnekleri deposunu klonlayın ve OpenThread'i derleyin:

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

4. OpenThread API ile İlgili Temel Bilgiler

OpenThread'ın herkese açık API'leri, OpenThread deposundaki ./openthread/include/openthread adresinde bulunur. Bu API'ler, uygulamalarınızda kullanmak ü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ıcı rolleriyle birlikte ağ kimlik bilgisi yönetimi
  • Sınır yönlendirici yönetimi
  • Çocuk Gözetimi ve Parazit Algılama gibi gelişmiş özellikler

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

API kullanma

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

Örneğin, OpenThread'a dahil edilen CLI örnek uygulaması aşağıdaki API üst bilgilerini 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ık sık kullanacağınız bir yapıdır. İlkleştirildikten sonra bu yapı, 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'da yer alan örnek uygulamalardan birine platforma özgü işlevler eklemek istiyorsanız önce tüm işlevler için otSys ad alanını kullanarak bunları ./openthread/examples/platforms/openthread-system.h başlığında tanımlayın. Ardından bunları platforma özel bir kaynak dosyasına uygulayın. Bu şekilde soyutlandığında, diğer örnek platformlar için aynı işlev üstbilgilerini 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 özgü GPIO işlev beyanları ekleyin.

openthread/instance.h başlığı için #include'ten sonra aşağıdaki işlev beyanlarını 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);

Bu bilgileri bir sonraki adımda uygulayacağız.

otSysButtonProcess işlev beyanında otInstance kullanıldığını unutmayın. Bu sayede uygulama, bir düğmeye basıldığında gerekirse OpenThread örneğiyle ilgili bilgilere erişebilir. Bu tamamen uygulamanızın ihtiyaçlarına bağlıdır. İşlev uygulamanızda buna ihtiyacınız yoksa bazı araç zincirlerinde kullanılmayan değişkenlerle ilgili derleme hatalarını önlemek için OpenThread API'deki OT_UNUSED_VARIABLE makrosunu kullanabilirsiniz. Bunun örneklerini daha sonra göreceğiz.

5. GPIO platform soyutlama özelliğini uygulama

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

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

ACTION: Tanım ekleyin.

Bu tanımlar, 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 Semiconductor Infocenter'a bakın.

İŞLEM: Üstbilgi içeriği ekleyin.

Ardından, GPIO işlevi için ihtiyaç duyacağınız başlığı 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 kesinti işlevleri ekleyin.

Ardından bu kodu ekleyin. in_pin1_handler işlevi, düğmeye basma işlevi başlatılırken (bu dosyanın ilerleyen bölümlerinde) kaydedilen geri çağırma işlevidir.

in_pin1_handler işlevine iletilen değişkenler işlevde kullanılmadığı için bu geri çağırma işlevinin 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.

İ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ştirmek 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: Düğme basma işlemlerini başlatmak ve işlemek için işlevler ekleyin.

İlk işlev, düğmeye basıldığında kartı başlatır ve ikinci işlev, 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ı 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. Aşağıdaki 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ştirilebilmesi için uygulamanın, cihaz rolünün ne zaman değiştiğini ve buna karşılık doğru LED'i nasıl açacağını bilmesi gerekir. İlk bölüm için OpenThread örneğini, ikinci bölüm için GPIO platform soyutlama sınıfı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 içeriği 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ğinin durum değişikliği için işleyici işlevi beyanı ekleyin.

Bu beyanı, başlığın dahil edilmesinden sonra ve #if beyanlarının önüne main.c öğesine 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, bu işlevi otAppCliInit çağrısından sonra main() işlevine ekleyin. Bu geri çağırma kaydı, OpenThread örneğinin durumu her değiştiğinde OpenThread'a 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ştiyse 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: LED'i açmak için çoklu yayını kullanma

Uygulamamızda, bir kartta 1. düğmeye basıldığında ağdaki diğer tüm cihazlara UDP mesajları da göndermek istiyoruz. Mesajın alındığını onaylamak için diğer kartlardaki LED4'ü yanıt olarak etkinleştiririz.

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

  • Başlangıçta UDP bağlantısı başlatma
  • Mesh yerel çoklu yayın adresine UDP mesajı gönderebilmelidir.
  • 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 içeriği 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ım ve sabit değerler ekleyin:

main.c dosyasında, includes bölümünün ardından ve tüm #if ifadelerinden ö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, örgülü yerel çoklu yayın adresidir. Bu adrese gönderilen tüm mesajlar, ağdaki tüm Tam İleti Dizisi Cihazlarına gönderilir. OpenThread'daki çoklu yayın desteği hakkında daha fazla bilgi için openthread.io'daki Çoklu Yayın başlıklı makaleyi inceleyin.

İŞLEM: İşlev tanımları 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 edecek 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üğmeyi başlatmak için çağrılar ekleyin.

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

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

İŞLEM: UDP ilklendirme çağrısını ekleyin.

main.c dosyasında, yeni eklediğiniz otSysButtonInit çağrısından sonra şu işlevi main() işlevine ekleyin:

initUdp(instance);

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

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

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

otSysButtonProcess(instance);

İŞLEM: Button Interrupt Handler'ı uygulayın.

main.c dosyasına, önceki adımda eklediğiniz handleNetifStateChanged işlevinin ardından handleButtonInterrupt işlevinin uygulamasını ekleyin:

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

İŞLEM: UDP başlatmayı uygulayın.

main.c dosyasında, yeni eklediğiniz handleButtonInterrupt işlevinin ardından 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 UDP mesajı alındığında bir geri çağırma işlevi (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 listelemesini inceleyin.

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

main.c dosyasına, yeni eklediğiniz initUdp işlevinin ardından 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ı not edin. Bu kontroller, UDP mesajının geçerli olduğundan ve arabelleğe doğru şekilde ayrıldığından emin olur. Aksi takdirde işlev, arabelleği boşalttığı exit bloğuna atlayarak hataları sorunsuz bir şekilde ele alır.

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

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

main.c dosyasına, yeni eklediğiniz sendUdp işlevinden sonra handleUdpReceive işlevinin uygulamasını ekleyin. Bu işlev yalnızca LED4'ü açar veya 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

Açıklamayı kolaylaştırmak için cihazlarımızın, açıldıklarında hemen Thread'i başlatmasını ve bir ağda bir araya gelmesini istiyoruz. Bunun için otOperationalDataset yapısını kullanacağız. Bu yapı, Thread ağı kimlik bilgilerini bir cihaza aktarmak için gereken tüm parametreleri içerir.

Bu yapının kullanılması, OpenThread'e yerleştirilmiş ağ varsayılanlarını geçersiz kılar. Böylece uygulamamız daha güvenli hale gelir ve ağımızdaki Thread düğümleri yalnızca uygulamayı çalıştıranlarla sınırlandırılır.

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

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

Bu beyanı, başlığın dahil edilmesinden sonra ve #if beyanlarının önüne main.c öğesine 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, bu işlev çağrısını otSetStateChangedCallback çağrısından sonra main() işlevine ekleyin. Bu işlev, Mesaj 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ılar ekleyin.

main.c içinde, 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 dosyasında, 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 açıklandığı gibi, bu uygulama için kullandığımız mesaj dizisi ağ parametreleri şunlardır:

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

Ayrıca, burada yönlendirici seçim jitter'ini azaltırız. Böylece cihazlarımız demo amacıyla daha hızlı rol değiştirir. Bunun yalnızca düğüm bir FTD (Tam İleti Dizisi Cihaz) ise yapıldığını unutmayın. Bu konu hakkında daha fazla bilgiyi bir sonraki adımda bulabilirsiniz.

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

OpenThread'ın API'lerinden bazıları 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 bir yönlendiriciye tanıtmasının (saniye cinsinden) süresini ayarlar. Bu değer için varsayılan değer, mesaj dizisi spesifikasyonuna göre 120'dir. Bu Codelab'de kullanım kolaylığı sağlamak için bu değeri 20 olarak değiştireceğiz. Böylece, bir Thread düğümünün rolünü değiştirmesi için çok uzun süre beklemeniz gerekmez.

Not: MTD cihazlar yönlendirici olmaz ve otThreadSetRouterSelectionJitter gibi bir işlev için destek, 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, OPENTHREAD_FTD önişleyici yönergesi içinde bulunan otThreadSetRouterSelectionJitter işlev tanımına bakarak onaylayabilirsiniz:

./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ı derlemeden önce üç CMake dosyası için birkaç küçük güncelleme yapmanız gerekir. Bunlar, derleme sistemi tarafından uygulamanızı derlemek ve bağlamak için kullanılır.

./third_party/NordicSemiconductor/CMakeLists.txt

Ardından, 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.

Tercih ettiğiniz metin düzenleyicide ./third_party/NordicSemiconductor/CMakeLists.txt dosyasını 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.

Tercih ettiğiniz metin düzenleyicide ./src/CMakeLists.txt dosyasını 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.

Tercih ettiğiniz metin düzenleyicide ./third_party/NordicSemiconductor/CMakeLists.txt dosyasını 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ı derleyip üç Nordic nRF52840 geliştirme kartına da yüklemeye hazırsınız demektir. Her cihaz Tam Mesaj Dizisi Cihazı (FTD) olarak çalışır.

OpenThread'i derleme

nRF52840 platformu için OpenThread FTD ikililerini derleyin.

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

OpenThread FTD CLI ikili dosyası içeren 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ı yanıp söndürme

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çıktır.

20a3b4b480356447.png

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

c00d519ebec7e5f0.jpeg

nRFx Komut Satırı Araçları'nın konumuna gidin ve OpenThread CLI FTD onaltılık dosyasını, kartın seri numarasını kullanarak nRF52840 kartına flaşlayın:

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

LED5, yanıp sönme sırasında kısa süreliğine kapanır. İşlem başarılı olduğunda 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.

Diğer iki kart için bu "Kartları flaşlayın" adımını tekrarlayın. Her kart Linux makinesine aynı şekilde bağlanmalıdır ve kartın seri numarası hariç, önyükleme komutu aynıdır. Her kartın benzersiz seri numarasını

nrfjprog yanıp sönen komut

İşlem başarılı olursa her kartta LED1, LED2 veya LED3 yanar. Hatta yanan LED'in yanıp söndükten kısa bir süre sonra 3'ten 2'ye (veya 2'den 1'e) geçtiğini görebilirsiniz (cihaz rolü değiştirme özelliği).

12. Uygulama işlevselliği

Üç nRF52840 kartının tümü artık açık ve OpenThread uygulamamızı çalıştırıyor olmalıdır. Daha önce de belirtildiği gibi, bu uygulamanın iki temel özelliği vardır.

Cihaz rolü göstergeleri

Her karttaki yanan LED, ileti dizisi düğümünün mevcut rolünü gösterir:

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

Rol değiştikçe yanan LED de değişir. Her cihaz açıldıktan sonraki 20 saniye içinde bu değişiklikleri bir veya iki kartta görmüş olmanız gerekir.

UDP Çoklu Yayın

Bir kartta 1. düğmeye basıldığında, Thread ağındaki diğer tüm düğümleri içeren yerel örgülü çoklu yayın adresine bir UDP mesajı gönderilir. Bu mesajı aldıktan sonra 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

Yazılım yüklediğiniz cihazlar, Yönlendirici Uygun Son Cihaz (REED) olarak adlandırılan belirli bir tür Tam Thread Cihazı'dır (FTD). Yani bu cihazlar, Yönlendirici veya Son Cihaz olarak işlev görebilir ve kendilerini Son Cihazdan Yönlendiriciye yükseltebilir.

Thread 32'ye kadar 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 kendisini otomatik olarak yönlendirici olarak 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şir.

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

4e1e885861a66570.png

Lideri kaldırma

Lider, ileti dizisi ağından kaldırılırsa ağın hâlâ bir lideri olduğundan emin olmak için farklı bir Yönlendirici kendisini Lider olarak yükseltir.

Güç anahtarını kullanarak Liderler tablosunu (LED1'in yandığını gösteren) kapatın. Yaklaşık 20 saniye bekleyin. Kalan iki karttan birinde LED2 (Yönlendirici) söner ve LED1 (Lider) açılır. Bu cihaz artık Thread ağının lideridir.

4c57c87adb40e0e3.png

Orijinal lider tablosunu tekrar etkinleştirin. Cihaz, uç cihaz olarak Thread ağına otomatik olarak yeniden katılır (LED3 yanar). 20 saniye içinde (Yönlendirici Seçimi Jitter'i) kendisini bir 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. Açılan ilk kart, Lider rolünde başlar (LED1 ışığı yanar). Thread ağındaki ilk Yönlendirici otomatik olarak Lider olur.

Diğer iki kart başlangıçta ağa son cihaz olarak bağlanır (LED3 yanar) ancak 20 saniye içinde kendilerini yönlendirici olarak tanımlamalıdır (LED2 yanar).

Ağ bölümleri

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

Mesaj dizisi kendi kendini iyileştirir. Bu nedenle, bölümler sonunda tek bir lidere sahip tek bir bölüme geri birleştirilir.

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

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

Herhangi bir tahtayı seçin ve 1. düğmeye basın. Uygulamayı çalıştıran Thread ağındaki diğer tüm kartlardaki LED4'ün durumu değişmelidir. Önceki alıştırmadan devam ediyorsanız bu ayarlar açık olmalıdır.

f186a2618fdbe3fd.png

Aynı pano için 1. düğmeye tekrar basın. Diğer tüm kartlardaki LED4 tekrar açılıp kapanır.

Farklı bir karttaki 1. düğmeye basın ve diğer kartlardaki LED4'ün nasıl açıldığını gözlemleyin. LED4'ün şu anda açık olduğu panolardan birinde 1. düğmeye basın. LED4, ilgili kartta açık kalır ancak diğer kartlarda açılıp kapanır.

f5865ccb8ab7aa34.png

Ağ bölümleri

Panolarınız bölümlenmişse ve aralarında birden fazla lider varsa çoklu yayın mesajının sonucu panolar arasında farklılık gösterir. Bölünmüş bir kartta (ve dolayısıyla bölünmüş Thread ağının tek üyesi olan) 1. düğmeye basarsanız diğer kartlardaki LED4 ışığı yanmaz. Böyle bir durumda panoları sıfırlayın. İdeal olarak, tek bir Thread ağı oluştururlar ve UDP mesajlaşma düzgün ç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
  • Bir Thread ağındaki tüm cihazlara UDP mesajı gönderme
  • Makefile'leri değiştirme

Sonraki adımlar

Bu kod laboratuvarını temel alarak 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'ler 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 yerine, tek bir cihazı bulup pinglemek için Router/Leader API'yi kullanın.
  • LED'leri yakmak için OpenThread Sınır Yönlendiricisi kullanarak örgülü ağınızı internete bağlayın ve 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: