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, bağlı ev için ürünlerin geliştirilmesini hızlandırmaları amacıyla Nest ürünlerinde kullanılan teknolojiyi geliştiricilerin geniş çapta kullanmasını sağlamak için OpenThread kullanıma sunuldu.

İleti dizisi spesifikasyonu, ev uygulamaları için IPv6 tabanlı güvenilir, güvenli ve düşük güçlü kablosuzdan 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 İleti Dizisi ağ katmanını uygular.

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

2a6db2e258c32237.png

Neler öğreneceksiniz?

  • Nordic nRF52840 geliştirici kartlarındaki düğmeleri ve LED'leri programlama
  • Ortak OpenThread API'lerini ve otInstance sınıfını kullanma
  • OpenThread durumundaki değişiklikleri izleme ve bunlara tepki verme
  • Thread ağındaki tüm cihazlara UDP mesajları gönderme
  • Makefile dosyalarında değişiklik yapma

Gerekenler

Donanım:

  • 3 Nordic Semiconductor nRF52840 geliştirici panosu
  • 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. Başlarken

Donanım Codelab'ini tamamlayın

Bu Codelab'i başlatmadan önce nRF52840 Boards and OpenThread Codelab'i ile bir Thread Network oluşturma:

  • Derleme ve yanıp sönme için ihtiyacınız olan tüm yazılımları belirtir
  • OpenThread yapımını ve Nordic nRF52840 panolarında hızlıca işaretlemeyi öğretir
  • Bir İş Parçacığı ağıyla ilgili temel bilgileri gösterir

OpenThread derlemek ve panoları yanıp söndürmek için gerekli olan ortam ayarlarının hiçbiri bu Codelab'de ayrıntılı olarak açıklanmıştır. Yalnızca panoların yanıp sönmesi için temel talimatlar verilmiştir. Thread Network Codelab'i zaten tamamlamış olduğunuz varsayılıyor.

Linux makinesi

Bu Codelab, tüm Thread geliştirme panolarını tetiklemek için i386 veya x86 tabanlı bir Linux makinesi kullanmak üzere tasarlanmıştır. Tüm adımlar Ubuntu 14.04.5 LTS'de (Trusty Tahr) test edildi.

Nordic Semiconductor nRF52840 panolar

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

a6693da3ce213856.png

Yazılımı Yükle

OpenThread oluşturmak ve flash oluşturmak için SEGGER J-Link, nRF5x Komut Satırı araçlarını, ARM GNU Toolchain'i ve çeşitli Linux paketlerini yüklemeniz gerekir. Bir İş Parçacığı Ağ Kodu Derleme işlemini tamamladıysanız, ihtiyacınız olan her şeyi zaten yüklemiş olursunuz. Açık değilse Thread'i nRF52840 geliştirici panolarına oluşturup uygulayabileceğinizden emin olmak için devam etmeden önce bu Codelab'i tamamlayın.

3. Depoyu klonlama

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

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

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

4. OpenThread API ile İlgili Temel Bilgiler

OpenThread' uygulamasının genel API'leri OpenThread deposunda ./openthread/include/openthread konumunda bulunmaktadır. Bu API'ler, uygulamalarınızda kullanmak için hem Thread hem de platform düzeyinde çeşitli OpenThread özelliklerine ve işlevlerine erişim sağlar:

  • OpenThread örneği bilgileri ve denetimi
  • IPv6, UDP ve CoAP gibi uygulama hizmetleri
  • Yetkili ve Düzenleyen 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 bilgilere openthread.io/reference adresinden ulaşabilirsiniz.

API kullanma

Bir API'yı 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 içeren CLI örnek uygulaması aşağıdaki API başlıklarını kullanır:

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

#include <openthread/config.h>
#include <openthread/cli.h>
#include <openthread/diag.h>
#include <openthread/tasklet.h>
#include <openthread/platform/logging.h>

OpenThread örneği

otInstance yapısı, OpenThread API'leriyle çalışırken sıklıkla kullanacağınız bir şeydir. Bu yapı, başlatıldıktan sonra OpenThread kitaplığının statik örneğini temsil eder ve kullanıcının OpenThread API çağrılarını 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 içeren ö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 başlığında belirtin. Daha sonra bunları platforma özgü bir kaynak dosyasına uygulayın. Bu sayede, diğer örnek platformlar için aynı işlev başlıklarını kullanabilirsiniz.

Örneğin, nRF52840 düğmelerine bağlanmak için kullanacağımız GPIO işlevleri ve LED'ler openthread-system.h'da beyan edilmelidir.

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

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

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

openthread/instance.h üst bilgisi için #include işlevinden sonra şu işlev bildirimlerini ekleyin:

/**
 * Init LED module.
 *
 */
void otSysLedInit(void);
void otSysLedSet(uint8_t aLed, bool aOn);
void otSysLedToggle(uint8_t aLed);

/**
* A callback will be called when GPIO interrupts occur.
*
*/
typedef void (*otSysButtonCallback)(otInstance *aInstance);
void otSysButtonInit(otSysButtonCallback aCallback);
void otSysButtonProcess(otInstance *aInstance);

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

otSysButtonProcess işlev beyanının otInstance kullandığını unutmayın. Bu şekilde uygulama, bir düğmeye basıldığında gerektiğinde OpenThread örneğiyle ilgili 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 derleme hatalarını önlemek için OpenThread API'deki OT_UNUSED_VARIABLE makrosunu kullanabilirsiniz. Bunun örneklerini daha sonra göreceğiz.

5. GPIO platform soyutlamasını uygulayın

Önceki adımda, ./openthread/examples/platforms/openthread-system.h uygulamasında GPIO için kullanılabilecek platforma özgü işlev tanımlarını inceledik. nRF52840 geliştirici kartlarındaki düğmelere ve LED'lere erişmek için nRF52840 platformunda bu işlevleri uygulamanız gerekir. Bu kodda şu işlevleri eklersiniz:

  • GPIO raptiyelerini ve modlarını başlatma
  • Raptiyedeki voltajı kontrol edin
  • GPIO kesintilerini etkinleştirin ve geri arama 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ım 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üğmeler ve LED'ler hakkında daha fazla bilgi için Nordic Semiconductor Infocenter hakkındaki makaleyi inceleyin.

İŞLEM: Başlık ekleyin.

Ardından, üstbilgide GPIO işlevselliği için gereken 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 kesme işlevleri ekleyin.

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

in_pin1_handler işlevine aktarılan değişkenler gerçekte işlevde kullanılmadığı için bu geri çağırmanın OT_UNUSED_VARIABLE makrosunu nasıl kullandığına dikkat edin.

/* 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 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 modunu açma/kapatma işlevi eklemek için bir işlev ekleyin.

Bu işlev, cihaz, çoklu yayın UDP mesajı aldığında LED4'ü açmak veya 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 basışlarını başlatmak ve işlemek için işlevler ekleyin.

İlk işlev, tuşa basarak tahtayı başlatır ve ikinci işlev, Düğme 1'e 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.cdosyasını kaydedin ve kapatın.

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

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

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

Uygulamanın bu işlevi etkinleştirmek için cihaz rolünün ne zaman değiştiğini ve buna göre doğru LED'in nasıl açılacağını bilmesi gerekir. Birinci bölüm için OpenThread örneğini, ikincisi için GPIO platformu soyutlamasını kullanacağız.

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

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

İŞLEM: Başlık ekleyin.

main.c dosyasının sahipler bölümünde, rol değişikliği özelliği için ihtiyaç duyacağınız API başlık dosyalarını ekleyin.

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

İŞLEM: OpenThread örneği durumu değişikliği için işleyici işlevi beyanı ekleyin.

Bu beyanı, başlık ve #if ifadesinden sonra main.c ifadesine ekleyin. Bu işlev ana uygulamadan sonra tanımlanacaktır.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

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

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

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

İŞLEM: Eyalet 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ştiyse LED'leri gerektiğinde 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

Uygulamamızda, bir düğmede Düğme1'e basıldığında ağdaki diğer tüm cihazlara UDP mesajları göndermek de istiyoruz. Mesajın alındığını onaylamak için yanıtta diğer panolarda LED4'ü açacağız.

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

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

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

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

İŞLEM: Başlık ekleyin.

main.c dosyasının üst kısmındaki"includes" (eklemeler) bölümünde, çoklu yayın UDP özelliği için ihtiyaç duyacağınız 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 başlığı, çalışma zamanı koşullarını doğrulayan ve hataları sorunsuzca işleyen otEXPECT ve otEXPECT_ACTION makroları için kullanılır.

İŞLEM: Tanım ve sabit değer ekleyin:

main.c dosyasında, içerme bölümünden sonra ve #if ifadelerinin önüne UDP'ye özel sabitler ekleyip şunları 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 örgü yerel çoklu yayın adresidir. Bu adrese gönderilen tüm mesajlar, ağdaki tüm Mesaj Dizisi Cihazları'na gönderilir. OpenThread'te çoklu yayın desteği hakkında daha fazla bilgi edinmek için openthread.io'da çoklu yayın bölümüne bakın.

İŞLEM: İşlev bildirimleri ekleyin.

main.c dosyasında, otTaskletsSignalPending tanımından sonra ve main() işlevinden önce, UDP soketini temsil eden statik 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'lerini ve düğmesini başlatmak için arama ekleyin.

main.c içinde, bu işlev çağrılarını otSetStateChangedCallback çağrıdan sonra main() işlevine ekleyin. Bu işlevler GPIO ve GPIOTE raptiyelerini başlatır ve düğme aktarma etkinliklerini yönetmek için bir düğme işleyici ayarlar.

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

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

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

initUdp(instance);

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

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

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

otSysButtonProcess(instance);

İŞLEM: Düğmeyi Kesecek İşleyiciyi 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, 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, daha önce 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şlevini (handleUdpReceive) kaydeder. otUdpBind, OT_NETIF_THREAD cihazını aktararak yuvayı Thread ağ arayüzüne bağlar. Diğer ağ arayüzü seçenekleri için UDP API Reference (UDP API Referansı) sayfasındaki otNetifIdentifier numaralandırmasına bakın.

İŞLEM: UDP mesajlarını uygulayın.

main.c içinde, 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ğe alan exit blokuna atlayarak hataları kontrol eder.

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

İŞLEM: UDP mesajı işlemeyi uygulayın.

main.c içinde, handleUdpReceive işlevinin uygulamasını, yeni eklediğiniz sendUdp işlevinden sonra ekleyin. Bu işlev yalnızca LED4'ü açar.

/**
 * 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ırın

Tanıtımın daha kolay olması için cihazlarımızın ileti dizisini hemen başlatmasını ve etkinleştirildiğinde bir ağa katılmasını istiyoruz. Bunu yapmak için otOperationalDataset yapısını kullanacağız. Bu yapı, İleti dizisi ağı kimlik bilgilerini bir cihaza aktarmak için gereken tüm parametreleri barındırır.

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.

Yine ./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 "includes" (dahil et) bölümünde, 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 bildirimi ekleyin.

Bu beyanı, başlık ve #if ifadesinden sonra main.c ifadesine ekleyin. Bu işlev, ana uygulama işlevinden sonra tanımlanır.

static void setNetworkConfiguration(otInstance *aInstance);

İŞLEM: Ağ yapılandırma ç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 Dizisi ağ veri kümesini yapılandırır.

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

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

main.c içinde, bu işlev çağrılarını otSysButtonInit çağrıdan 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ığı üzere bu uygulama için kullandığımız İleti dizisi ağ parametreleri şöyledir:

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

Ayrıca, Yönlendirici Seçimi Dalgalaması'nı azalttığımız için cihazlarımız demo açısından 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. Bir sonraki adımda bu konu hakkında daha fazla bilgi edineceksiniz.

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

Bazı OpenThread API'leri 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 kendisini bir Yönlendirici'ye tanıtması için geçen süreyi (saniye cinsinden) ayarlar. İleti dizisi spesifikasyonuna göre bu değer için varsayılan değer 120'dir. Bu Codelab'de kullanım kolaylığı için kodu 20 olarak değiştireceğimiz için bir İş Parçacığı düğümünün rollerini değiştirmesi için çok uzun bir süre beklemeniz gerekmez.

Not: MTD cihazları yönlendirici olmaz ve otThreadSetRouterSelectionJitter gibi bir işleve yönelik destek, MTD derlemesine dahil değildir. Daha sonra CMake seçeneğinin (-DOT_MTD=OFF) belirtilmesi gerekir. Aksi takdirde derleme hatasıyla karşılaşırız.

Bunu, OPENTHREAD_FTD ön işleyici 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. CMake güncellemeleri

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

./third_party/NordicSemiconductor/CMakeLists.txt

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

İŞLEM: CMakeLists.txt dosyasına bayrak ekleyin.

Tercih ettiğiniz metin düzenleyicide ./third_party/NordicSemiconductor/CMakeLists.txt uygulaması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: ./src/CMakeLists.txt dosyasına gpio kaynağını 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. Bu dosya, Nordic sürücülerin kitaplık yapısına dahildir.

İŞLEM: NordicSemiconductor 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ığında uygulamayı Nordic nRF52840 geliştirici kartlarının üçüne de oluşturmaya hazır hale gelirsiniz. Her cihaz, Tam Dizi Cihaz (FTD) işlevi görür.

OpenThread İçeriğini Derleme

nRF52840 platformu için OpenThread FTD ikili programları 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 bu değeri 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ı kazanın

ot-cli-ftd.hex dosyasını her bir nRF52840 panosuna sabitleyin.

USB kablosunu nRF52840 kartındaki harici güç PIN'inin yanındaki Mikro USB hata ayıklama bağlantı noktasına takın ve ardından Linux makinenize bağlayın. Doğru şekilde ayarlanmış olduğundan LED5 açık.

20a3b4b480356447.png

Daha önce olduğu gibi, nRF52840 panosunun seri numarasına dikkat edin:

c00d519ebec7e5f0.jpeg

nRFx Komut Satırı Araçları'nın konumuna gidin ve \&33; panosunun seri numarasını kullanarak OpenThread CLI FTD onaltılık dosyasını nRF52840 panosuna yükseltin:

$ 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. Başarı sonucunda 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 pano için "board board'ları" adımını tekrarlayın. Her panonun Linux makinesine aynı şekilde bağlanması gerekir ve Jamboard'un seri numarası hariç olmak üzere, yanıp sönme komutu da aynıdır. Her panonun benzersiz seri numarasını kullandığınızdan emin olun.

nrfjprog yanıp sönen komut.

Başarılı olursa her panoda LED1, LED2 veya LED3 yanar. Yanıp söndükten kısa bir süre sonra ışığın yandığını 3'ten 2'ye (veya 2'den 1'e) görebilirsiniz (cihaz rolü değişikliği).

12. Uygulama işlevselliği

Artık üç nRF52840 panosunun da açık olması ve OpenThread uygulamamızı çalıştırması gerekir. Daha önce de ayrıntılı olarak belirttiğimiz gibi, bu uygulamanın iki birincil özelliği vardır.

Cihaz rolü göstergeleri

Her bir panodaki ışığın LED'i, İş Parçacığı düğümünün mevcut rolünü yansıtır:

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

Rol değiştikçe ışıklı LED de değişir. Bu değişiklikleri, her cihazın açılmasından itibaren 20 saniye içinde bir tahtada zaten görmüş olabilirsiniz.

UDP Çoklu Yayın

Bir Jamboard'da Düğme1 tuşuna basıldığında, iş parçacığı yerel çoklu yayın adresine Thread ağındaki diğer tüm düğümleri içeren bir UDP mesajı gönderilir. Bu mesaja yanıt olarak diğer tüm panolarda LED4 açılır veya kapanır. Başka bir UDP mesajı alınana kadar LED4 her pano için açık veya kapalı kalır.

203dd094acca1f97.png

9bbd96d9b1c63504.png

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

Flaşını oluşturduğunuz cihazlar, Yönlendirici Uygun Son Cihazı (REED) olarak adlandırılan belirli bir Tam İş Parçacığı Cihazıdır (FTD). Yani, bu cihazlar bir Yönlendirici veya Son Cihaz olarak çalışabilir ve Son Cihaz'dan Yönlendirici'ye kendilerini tanıtabilir.

Mesaj dizisi en fazla 32 Yönlendirici'yi destekleyebilir, ancak 16 ile 23 arasında yönlendirici sayısını korumaya çalışır. Bir REED Son Cihaz olarak eklenirse ve Yönlendirici sayısı 16'nın altındaysa otomatik olarak kendisini bir Yönlendirici'ye tanıtır. Bu değişiklik, otThreadSetRouterSelectionJitter değerini uygulamada ayarladığınız (20 saniye) saniye içinde rastgele bir zamanda gerçekleşmelidir.

Her İleti Dizisi ağının bir Lideri vardır. Bu da bir İş Parçacığı ağındaki Yönlendiriciler grubunu yönetmekten sorumlu Yönlendiricidir. Tüm cihazlar açıkken 20 saniye sonra içlerinden biri Lider (LED1 açık), diğeri ise Yönlendirici (LED2 açık) olmalıdır.

4e1e885861a66570.png

Lideri kaldırma

Lider, Mesaj Dizisi ağından kaldırılırsa ağın hâlâ lider olduğundan emin olmak için farklı bir Yönlendirici kendi kendine Lidere yükseltilir.

Güç anahtarını kullanarak Lider panosunu (LED1 ışıklı anakart) kapatın. 20 saniye kadar bekleyin. Diğer iki karttan birinde LED2 (Yönlendirici) kapanır ve LED1 (Lider) açılır. Bu cihaz artık Thread ağının lideridir.

4c57c87adb40e0e3.png

Orijinal Lider panosunu tekrar açın. Son Cihaz olarak (LED3 ışıklandırmalı) Mesaj Dizisi ağına otomatik olarak yeniden katılmalıdır. 20 saniye içinde (Yönlendirici Seçimi Dalgalanması) kendini bir Yönlendirici'ye (LED2 ışıklı) tanıtır.

5f40afca2dcc4b5b.png

Kartları sıfırla

Üç panosu da kapatıp açın ve LED'leri kontrol edin. Güçlendirilmiş ilk pano, Lider rolünde (LED1 ışıklıdır) başlamalıdır. Bir mesaj dizisi ağındaki ilk Yönlendirici otomatik olarak Lider olur.

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

Ağ bölümleri

Kartlarınız yeterli güç kaynağına sahip değilse veya aralarındaki radyo bağlantısı zayıfsa, Mesaj Dizisi ağı, bölümlere ayrılabilir ve lider olarak gösterilen birden fazla cihazınız olabilir.

Mesaj dizisi kendi kendine iyileşir, dolayısıyla bölümler tek bir Liderle tek bir bölümde birleştirilir.

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

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

Pano seçin ve Düğme1'e basın. Uygulamayı çalıştıran İş Parçacığı ağındaki diğer tüm panolarda LED4'lerin açma/kapatma durumları gerekir. Önceki egzersize devam ediliyorsa şu anda açık olmalıdır.

f186a2618fdbe3fd.png

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

Başka bir panoda Düğme1'e basıp LED4'in diğer panolarda nasıl geçiş yaptığını gözlemleyin. LED4'ün açık olduğu panolardan birinde Düğme1'e basın. LED4 bu tahta için açık kalır, ancak diğerinde açılır.

f5865ccb8ab7aa34.png

Ağ bölümleri

Jamboard'larınız bölümlendirilmişse ve aralarında birden fazla lider varsa, çoklu yayın mesajının sonucu panolar arasında farklılık gösterecektir. Bölümlendirilmiş bir tahtada Düğme1'e basarsanız (bölünmüş İş parçacığı ağının tek üyesi buysa) diğer panolarda LED4 yanıt vermez. Bu durumda panoları sıfırlayın. İdeal olarak tek bir Thread ağı yeniden biçimlendirilir ve UDP mesajlaşması düzgün çalışır.

15. Tebrikler!

OpenThread API'leri kullanan bir uygulama oluşturdunuz.

Artık şunları biliyorsunuz:

  • Nordic nRF52840 geliştirici kartlarındaki düğmeleri ve LED'leri programlama
  • Ortak OpenThread API'lerini ve otInstance sınıfını kullanma
  • OpenThread durumundaki değişiklikleri izleme ve bunlara tepki verme
  • Thread ağındaki tüm cihazlara UDP mesajları gönderme
  • Makefile dosyalarında değişiklik yapma

Sonraki adımlar

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

  • GPIO modülünü, yerleşik LED'ler yerine GPIO raptiyeleri kullanacak şekilde değiştirin 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 ekleyin
  • Tüm cihazları bir düğmeye basarak pinglemek için çoklu yayın özelliğini kullanmak yerine, bir cihazı bulup pinglemek için Router/Leader API'yi kullanın.
  • Bir OpenThread Sınır Yönlendirici kullanarak örgü ağınızı internete bağlayın ve LED'leri ışıklandırmak için Thread ağının dışına ç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: