OpenThread API'lerle 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 teknolojilerin, bağlı ev için ürün geliştirme sürecini hızlandırmak amacıyla geliştiricilere geniş ölçekte sunulması amacıyla OpenThread'i kullanıma sundu.

İleti dizisi spesifikasyonu, ev uygulamaları için IPv6 tabanlı güvenilir, güvenli ve düşük güçlü kablosuz cihazdan cihaza iletişim protokolü tanımlar. OpenThread; IPv6, 6LoWPAN, IEEE 802.15.4 MAC güvenliği, Örgü Bağlantısı Oluşturma ve Örgü Yönlendirme dahil tüm Thread ağ iletişimi katmanlarını uygular.

Bu Codelab'de bir Thread ağı başlatmak, cihaz rollerindeki değişiklikleri izleyip tepki vermek ve UDP mesajlarını göndermek için OpenThread API'leri kullanacaksınız. Ayrıca bu işlemleri gerçek donanımdaki düğmeler ve LED'lerle ilişkilendirebilirsiniz.

2a6db2e258c32237.png

Neler öğreneceksiniz?

  • Nordic nRF52840 geliştirme kartlarında düğmeleri ve LED'leri programlama
  • Ortak 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
  • Makefile'lar nasıl değiştirilir?

Gerekenler

Donanım:

  • 3 Nordic Semiconatorsor 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 makinesi

Yazılım:

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

Aksi belirtilmedikçe, bu Codelab'in içeriği Creative Commons Attribution 3.0 Lisansı altında, kod örnekleri ise Apache 2.0 Lisansı altında lisanslanmıştır.

2. Kullanmaya başlama

Donanım Codelab'ini tamamlayın

Bu Codelab'i başlatmadan önce nRF52840 Boards and OpenThread içeren bir Thread Ağı oluşturma işlemini tamamlamanız gerekir. Bu laboratuvarlar şunlardır:

  • Derleme ve yanıp sönme için ihtiyacınız olan tüm yazılımlar
  • OpenThread platformunu Nordic nRF52840 panolarında nasıl oluşturacağınızı öğrenin
  • Bir Thread ağıyla ilgili temel bilgileri gösterir

OpenThread'i derlemek ve beyaz tahtaları çalıştırmak için gerekli ortam ayarlarının hiçbiri bu Codelab'de ayrıntılı olarak açıklanmıştır. Yalnızca beyaz tahtaların yanıp sönmesiyle ilgili temel talimatlar verilmiştir. Build a Thread Network Codelab'i tamamladığınızı varsayalım.

Linux makinesi

Bu Codelab, tüm iş parçacığı geliştirme panolarını çalıştırmak için i386 veya x86 tabanlı bir Linux makinesinin kullanılması için tasarlanmıştır. Tüm adımlar Ubuntu 14.04.5 LTS'de (Trusty Tahr) test edildi.

Nordic Semiconatorsor nRF52840 panolar

Bu Codelab'de üç nRF52840 PDK kartı kullanılmıştır.

a6693da3ce213856.png

Yazılım Yükleyin

OpenThread'i oluşturmak ve yüklemek için SEGGER J-Link, nRF5x Komut Satırı araçları, ARM GNU Toolchain ve çeşitli Linux paketlerini yüklemeniz gerekir. Bir Thread Network Codelab'i gerektiği gibi tamamladıysanız ihtiyacınız olan her şeyi zaten yüklemiş olursunuz. Aksi takdirde, OpenThread'i oluşturup nRF52840 dev panolarına flashladığınızdan 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 kodu içerir.

OpenThread Nordic nRF528xx örnek deposunu klonlama ve OpenThread derlemesi:

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

4. OpenThread API ile İlgili Temel Bilgiler

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

  • OpenThread örnek bilgileri ve kontrolü
  • IPv6, UDP ve CoAP gibi uygulama hizmetleri
  • Ağ kimlik bilgisi yönetiminin yanı sıra, yetkili ve birleştirme rolleri
  • 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 bilgilerini 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 dahil edilen KSA ö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ık kullanacağınız bir özelliktir. Başlatıldıktan 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'e dahil olan örnek uygulamalardan birine platforma özel işlevler eklemek isterseniz önce tüm işlevler için otSys ad alanını kullanarak bunları ./openthread/examples/platforms/openthread-system.h üst bilgisinde belirtin. Ardından, bunları platforma özel bir kaynak dosyasına uygulayın. Böylece, diğer örnek platformlar için aynı işlev başlıklarını kullanabilirsiniz.

Örneğin, nRF52840 düğmelerine takmak için kullandığımız GPIO işlevleri ve LED'ler 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 üstbilgisi için #include sonrasına şu işlev tanımları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);

Bunları bir sonraki adımda uygularız.

otSysButtonProcess işlev beyanının otInstance kullandığını unutmayın. Bu şekilde uygulama, gerektiğinde bir düğmeye basıldığında OpenThread örneği hakkındaki bilgilere erişebilir. Bu tamamen başvurunuzun ihtiyaçlarına bağlıdır. İşlevi uygularken buna ihtiyacınız yoksa bazı araç zincirlerinde kullanılmayan değişkenlerle ilgili yapı hatalarını önlemek için OpenThread API'deki OT_UNUSED_VARIABLE makrosunu kullanabilirsiniz. Buin örneklerini daha sonra göreceğiz.

5. GPIO platformu soyutlama yöntemi uygulayın

Önceki adımda, ./openthread/examples/platforms/openthread-system.h içinde GPIO için kullanılabilecek 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 platformunda uygulamanız gerekir. Bu koda aşağıdaki işlevleri eklersiniz:

  • GPIO raptiyelerini ve modlarını başlatma
  • Raptiyenin voltajını kontrol edin
  • GPIO kesintilerini etkinleştirin ve geri çağırmayı kaydedin

./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 ve OpenThread uygulama düzeyinde kullanılan değişkenler arasında soyutlama işlevi 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'ler hakkında daha fazla bilgi için Nordic Semiconatorsor Bilgi Merkezi'ne bakın.

İŞLEM: Başlık ekleyin.

Ardından, GPIO işlevi için ihtiyacınız olacak 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: Düğme 1 için geri çağırma ve kesme işlevleri ekleyin.

Şimdi bu kodu ekleyin. in_pin1_handler işlevi, düğme bastırma işlevi başlatıldığında (bu dosyanın ilerleyen kısımlarında) kaydedilen geri çağırma işlevidir.

in_pin1_handler işlevine aktarılan 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 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 açmak veya kapatmak için bir işlev ekleyin.

Cihaz, çoklu yayın UDP mesajı aldığında LED4 arasında geçiş yapmak için bu işlev 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 basışlarını başlatmak ve işlemek için işlevler ekleyin.

İlk işlev, bir düğmeye basılması için 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 dosyayı kaydedin ve kapatın.

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

Uygulamamızda, cihaz rolüne bağlı olarak farklı LED'lerin yanıp sönmesini istiyoruz. Şu rolleri takip edelim: lider, yönlendirici, son cihaz. Bunları aşağıdaki gibi LED'lere atayabiliriz:

  • LED1 = Lider
  • LED2 = Yönlendirici
  • LED3 = Cihazı Sonlandır

Uygulamanın bu işlevi etkinleştirmek için cihaz rolünün ne zaman değiştiğini ve yanıt olarak doğru LED'i nasıl açacağını bilmesi gerekir. İlk kısmı için OpenThread örneğini, ikinci bölümü için GPIO platformu soyutunu 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: Başlık ekleyin.

main.c dosyasının içermeler bölümüne, rol değişikliği özelliği için ihtiyacınız olan API üstbilgisi 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 beyanı ekleyin.

Bu beyanı main.c, üstbilgiden sonra ve #if ifadesinden önce ekleyin. Bu işlev, ana uygulamadan sonra tanımlanır.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

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

main.c işlevinde, 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şlevinde 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şirse 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 kullanın

Uygulamamızda, bir kartta Button1 tuşuna basıldığında ağdaki diğer tüm cihazlara UDP mesajlarını da göndermek istiyoruz. Mesajın alındığını onaylamak için diğer Jamboard'larda LED4'i açıp kapatırız.

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

  • Başlangıçta UDP bağlantısını başlat
  • Örgü yerel çoklu yayın adresine UDP mesajı gönderebilme
  • Gelen UDP mesajlarını işleme
  • Gelen UDP mesajlarına yanıt olarak LED4'ü aç/kapat

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

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

İŞLEM: Başlık ekleyin.

main.c dosyasının üst tarafındaki içermeler bölümünde, çoklu yayın UDP özelliği için ihtiyacınız olan API başlığı dosyalarını 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ı gerektiği gibi işleyen otEXPECT ve otEXPECT_ACTION makroları için kullanılır.

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

main.c dosyasına, "includes" bölümünden sonra ve herhangi bir #if ifadesinden önce, UDP'ye özel sabit değerler ekleyin ve aşağıdakileri tanımlayın:

#define UDP_PORT 1212

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

ff03::1, bağlantılı yerel çoklu yayın adresidir. Bu adrese gönderilen mesajlar, ağdaki tüm Tam Mesaj Dizisi Cihazlarına gönderilir. OpenThread'te çoklu yayın desteği hakkında daha fazla bilgi için openthread.io'da çoklu yayın bölümüne bakın.

İŞLEM: İşlev bildirimleri ekleyin.

main.c dosyasına, otTaskletsSignalPending tanımından sonra ve main() işlevinden önce, UDP soketini temsil eden statik bir değişkenin yanı sıra UDP'ye özel işlevler 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'leri ve düğmesini başlatmak için arama ekleyin.

main.c işlevinde, bu işlev çağrılarını otSetStateChangedCallback çağrısından sonra main() işlevine ekleyin. Bu işlevler, GPIO ve GPIOTE pimlerini başlatır ve düğme aktarma 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şlevinde, 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 ayar olmadan, cihaz UDP iletilerini gönderemez veya alamaz.

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

main.c işlevinde, bu işlev çağrısını otSysProcessDrivers çağrısından sonra while döngüsünde 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 belirtilen işleyiciyi (handleButtonInterrupt) çağırır.

otSysButtonProcess(instance);

YAPILACAKLAR: Düğmenin İşlevselliğini Sonlandırın.

main.c işlevinde, 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'yi ilk kullanıma hazırlayın.

main.c işlevinde, initUdp işlevinin uygulamasını, yeni 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, önceden tanımladığınız bağlantı noktasıdır (1212). otUdpOpen işlevi, yuvayı açar ve UDP mesajı alındığında bir geri çağırma işlevi (handleUdpReceive) kaydeder. otUdpBind, OT_NETIF_THREAD soketini Thread ağ arayüzüne bağlar. Diğer ağ arayüzü seçenekleri için UDP API referansı bölümündeki otNetifIdentifier numaralandırmasına bakın.

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

main.c işlevinde, sendUdp işlevinin uygulamasını, yeni 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 arabellekte doğru şekilde ayrıldığından emin olur. Aksi takdirde işlev, arabelleği serbest bıraktığı exit bloğuna atlayarak hataları zarif bir şekilde işler.

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

İŞLEM: UDP mesaj işleme yöntemini uygulayın.

main.c işlevinde, handleUdpReceive işlevinin uygulamasını, yeni eklediğiniz sendUdp işlevinden sonra ekleyin. Bu işlev, LED 4'ü açar/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

Kısaca göstermek için cihazlarımızın Thread uygulamasını hemen başlatmasını ve açıldığında bir ağa katılmasını istiyoruz. Bunun için otOperationalDataset yapısını kullanırı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'te yerleşik olan ağ varsayılanlarını geçersiz kılarak uygulamamızı daha güvenli hale getirir ve ağımızdaki Thread düğümlerini yalnızca uygulamayı çalıştıranlarla sınırlandırır.

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

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

İŞLEM: Başlık ekleyin.

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

#include <openthread/dataset_ftd.h>

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

Bu beyanı main.c, üstbilgiden sonra ve #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şlevinde, 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ı etkinleştirmek için çağrı ekleyin.

main.c işlevinde, 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şlevinde setNetworkConfiguration işlevinin uygulamasını main() işlevinden sonra 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 Thread ağ parametreleri şunlardır:

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

Buna ek olarak, buradaki yönlendirici seçim ses dalgalanmasını azaltıyoruz. Böylece cihazlarımız, demo amaçları için rolleri daha hızlı değiştiriyor. Bunun yalnızca düğümün FTD (Tam İş Parçacığı Cihazı) olması durumunda yapıldığını unutmayın. Bu konuyla ilgili daha fazla bilgiyi bir sonraki adımda bulabilirsiniz.

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

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

Örneğin, otThreadSetRouterSelectionJitter işlevi, bir Son Cihazın kendini bir Yönlendirici'ye tanıtması için gereken süreyi (saniye cinsinden) ayarlar. Mesaj dizisi spesifikasyonuna göre bu değerin varsayılan değeri 120'dir. Bu Codelab'de kullanım kolaylığı için 20 olarak değiştireceğiz. Bu nedenle, bir Thread düğümünün rolleri değiştirmesi için çok uzun süre beklemeniz gerekmiyor.

Not: MTD cihazları yönlendirici olmaz ve otThreadSetRouterSelectionJitter gibi bir işlev için destek, MTD derlemesine dahil edilmez. Daha sonra -DOT_MTD=OFFCMake seçeneğini belirlememiz gerekir. Aksi takdirde yapı hatasıyla karşılaşırız.

Bunu, OPENTHREAD_FTD ön işlemci yönergesinde bulunan otThreadSetRouterSelectionJitter işlev tanımına bakarak 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. CUpdate

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

./third_party/NordicSemiconORSor/CMakeLists.txt

Şimdi GPIO işlevlerinin uygulamada tanımlandığından emin olmak için bazı NordicSemiconsentor CMakeLists.txt işaretlemelerini ekleyin.

İŞLEM: CMakeLists.txt dosyaya işaretler ekleyin.

Tercih ettiğiniz metin düzenleyicide ./third_party/NordicSemiconductor/CMakeLists.txt öğesini 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/NordicSemiconORSor/CMakeLists.txt

Son olarak, nrfx_gpiote.c sürücü dosyasını NordicSemiconORSor CMakeLists.txt dosyasına ekleyin. Böylece, bu dosya Nordic sürücülerinin kitaplık yapısına dahil edilir.

İŞLEM: NordicSemiconsentor CMakeLists.txt dosyasına gpio sürücüsünü 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ıktan sonra uygulamayı Nordic nRF52840 geliştirme platformlarının üçünde de oluşturup kullanabilirsiniz. Her cihaz tam ileti dizisi cihazı (FTD) olarak çalışır.

OpenThread

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 dosyası ile dizine gidin ve ARM Yerleşik 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

Kartları yansıtmak

ot-cli-ftd.hex dosyasını her bir nRF52840 Jamboard'a aktarın.

USB kablosunu nRF52840 kartındaki harici güç PIN'inin yanındaki mikro USB hata ayıklama bağlantı noktasına takın ve Linux makinenize takın. Doğru şekilde ayarladığınızdan LED5 açık olmalıdır.

20a3b4b480356447.png

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

c00d519ebec7e5f0.jpeg

nRFx Komut Satırı Araçları'nın konumuna gidin ve tahtanın seri numarasını kullanarak OpenThread CLI FTD onaltılık dosyasını nRF52840 panosuna yapıştırı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. Aşağıdaki sonuç başarılı olduğunda 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 beyaz tahta için bu "Jamboard'ları yanıp sön" adımını tekrarlayın. Her kart, Linux makinesine de aynı şekilde bağlanmalıdır. Jamboard'un seri numarası hariç, yanıp sönme komutu da aynıdır. Her kartın benzersiz bir seri numarasını kullandığınızdan emin olun

nrfjprog yanıp sönen komut.

İşlem başarılı olursa her kartta LED1, LED2 veya LED3 yanar. Işık yanıp sönen LED'in yanıp sönmesinin hemen ardından 3'ten 2'ye (veya 2'den 1'e) dönebileceğini de görebilirsiniz (cihaz rolü değişikliği özelliği).

12. Uygulama işlevselliği

Artık üç nRF52840 panosunun da güçlenmesi ve OpenThread uygulamamızı çalıştırması gerekir. Daha önce belirtildiği gibi, bu uygulamanın iki temel özelliği vardır.

Cihaz rolü göstergeleri

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

  • LED1 = Lider
  • LED2 = Yönlendirici
  • LED3 = Cihazı Sonlandır

Rol değiştikçe yanan LED de değişir. Bu değişiklikleri, her bir cihazın açılmasından sonraki 20 saniye içinde bir kartta görmüşsünüzdür.

UDP Çoklu Yayını

Bir Jamboard'da Düğme1'e basıldığında, Thread yerel ağındaki tüm düğümleri içeren bağlantılı örgü yerel çoklu yayın adresine bir UDP mesajı gönderilir. Bu mesaja yanıt olarak diğer tüm Jamboard'lardaki LED4 seçenekleri açılır veya kapanır. LED4, başka bir UDP mesajı alana kadar her pano için açık veya kapalı kalır.

203dd094acca1f97.png

9bbd96d9b1c63504.png

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

Flaş ettiğiniz cihazlar, Yönlendirici Uygun Bitiş Cihazı (REED) olarak adlandırılan belirli bir Tam İş Parçacığı Cihazı (FTD) türüdür. Yani ya yönlendirici ya da son cihaz olarak çalışabilirler? Kendini son cihazdan yönlendiriciye tanıtabilir.

Mesaj dizisi, 32'ye kadar yönlendiriciyi destekleyebilir, ancak yönlendiricilerin sayısını 16 ile 23 arasında tutmaya çalışır. REED bir son cihaz olarak eklenirse ve yönlendirici sayısı 16'nın altındaysa otomatik olarak yönlendiriciye yükseltilir. Bu değişiklik, uygulamada otThreadSetRouterSelectionJitter değerini ayarladığınız saniye sayısı (20 saniye) içinde rastgele bir zamanda gerçekleşmelidir.

Ayrıca, her Thread ağı bir Lidere sahiptir. Bu da bir Thread ağındaki Yönlendirici grubunu yönetmekten sorumlu bir Yönlendiricidir. Tüm cihazlar açıkken 20 saniye sonra ikisinden 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 çıkarılırsa farklı bir yönlendirici, ağın hâlâ bir lidere sahip olduğundan emin olmak için kendini lidere yönlendirir.

Güç anahtarını kullanarak Lider panosu'nu (LED1'in aydınlatıldığı kontrol paneli) kapatın. Yaklaşık 20 saniye bekleyin. Kalan iki karttan birinde LED2 (Yönlendirici) kapanır ve LED1 (Lider) açılır. Bu cihaz artık Thread ağının Lideri.

4c57c87adb40e0e3.png

Orijinal ilk beyaz tahtayı tekrar etkinleştirin. Mesaj dizisi, Son Cihaz olarak Thread ağına otomatik olarak yeniden katılmalıdır (LED3 yanar). 20 saniye içinde (Yönlendirici Seçim Dalgalanması), kendisini bir Yönlendirici'ye (LED2 yanar) tanıtır.

5f40afca2dcc4b5b.png

Jamboard'ları sıfırlayın

Jamboard'un üçünü de kapatıp tekrar açın ve LED'leri kontrol edin. Açılan ilk Jamboard'un, Rol rolünde başlaması gerekir (LED1 yanar). Bir Thread ağındaki ilk yönlendirici otomatik olarak lider olur.

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

Ağ bölümleri

Jamboard'larınız yeterli güç almıyorsa veya aralarındaki radyo bağlantısı zayıfsa Thread ağı, parçalara ayrılabilir ve lider olarak gösterilen birden fazla cihazınız olabilir.

İleti dizisi kendi kendine iyileşir. Bu nedenle bölümlendirmeler, tek bir Liderle tek bir bölümde tekrar birleştirilmelidir.

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

Önceki egzersize devam ediliyorsa LED4 hiçbir cihazda aydınlatılmamalıdır.

Herhangi bir beyaz tahta seçip Button1'e basın. Uygulamayı çalıştıran Thread ağındaki diğer tüm Jamboard'larda yer alan LED4, durumlarını açma/kapatmalıdır. Önceki alıştırmaya devam ediliyorsa şu anda etkin durumda olması gerekir.

f186a2618fdbe3fd.png

Aynı beyaz tahta için Düğme1'e tekrar basın. Diğer tüm Jamboard'lardaki LED4 tekrar açılmalıdır.

Farklı bir panoda Düğme1'e basın ve LED'in diğer panolarda nasıl geçiş yaptığını gözlemleyin. LED4'ün şu anda açık olduğu panolardan birinde Düğme1'e basın. LED4 o kart için açık kalır, ancak diğerleri arasında geçiş yapılır.

f5865ccb8ab7aa34.png

Ağ bölümleri

Jamboard'larınız bölümlendirilmişse ve aralarında birden fazla öncü varsa çoklu yayın mesajının sonucu panolar arasında farklılık gösterir. Bölümlendirilmiş bir Jamboard'da Düğme1'e basarsanız (ve bölümlendirilmiş Thread ağının tek üyesiyse), diğer Jamboard'lardaki LED4 buna yanıt olarak yanmaz. Böyle bir durumda Jamboard'ları sıfırlayın. İdeal olarak tek bir mesaj dizisi ağını yeniden oluşturmak ve UDP'den gelen mesajların doğru şekilde çalışması gerekir.

15. Tebrikler!

OpenThread API'leri kullanan bir uygulama oluşturdunuz.

Artık aşağıdaki bilgileri biliyorsunuz:

  • Nordic nRF52840 geliştirme kartlarında düğmeleri ve LED'leri programlama
  • Ortak 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
  • Makefile'lar nasıl değiştirilir?

Sonraki adımlar

Bu Codelab'i oluştururken aşağıdaki alıştırmaları deneyin:

  • GPIO modülünü değiştirmek için yerleşik LED'ler yerine GPIO pimleri kullanın ve yönlendirici rolüne göre rengi değiştiren harici RGB LED'leri bağlayın
  • Farklı bir örnek platform için GPIO desteği ekleme
  • Tüm cihazlara tek bir düğmeyle ping atmak 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 aydınlatmak için Thread ağının dışından çoklu yayın yapın

Daha fazla bilgi

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

  • Desteklenen Platformlar - OpenThread'i destekleyen tüm platformları keşfedin
  • OpenThread oluşturma: OpenThread'i oluşturma ve yapılandırma hakkında daha fazla ayrıntı
  • Thread Primer: Thread kavramları için mükemmel bir referans

Referans: