1. Giriş

Nest tarafından yayınlanan OpenThread, Thread® ağ protokolünün açık kaynaklı bir uygulamasıdır. Nest, Nest ürünlerinde kullanılan teknolojinin geliştiriciler tarafından yaygın olarak kullanılabilmesini sağlamak ve böylece bağlantılı ev ürünlerinin geliştirilmesini hızlandırmak için OpenThread'i yayınladı.
Thread spesifikasyonu, ev uygulamaları için IPv6 tabanlı güvenilir, güvenli ve düşük güç tüketen kablosuz cihazdan cihaza iletişim protokolünü tanımlar. OpenThread, IPv6, 6LoWPAN, MAC güvenliği ile IEEE 802.15.4, Mesh Link Establishment ve Mesh Routing dahil olmak üzere tüm Thread ağ katmanlarını uygular.
Bu Codelab'de, OpenThread API'lerini kullanarak bir Thread ağı başlatacak, cihaz rollerindeki değişiklikleri izleyip bunlara tepki verecek ve UDP mesajları göndereceksiniz. Ayrıca bu işlemleri gerçek donanımdaki düğmelere ve LED'lere bağlayacaksınız.

Neler öğreneceksiniz?
- Nordic nRF52840 geliştirme kartlarındaki düğmeleri ve LED'leri programlama
- Yaygın OpenThread API'lerini ve
otInstancesınıfını kullanma - OpenThread durum değişikliklerini izleme ve bunlara tepki verme
- Thread ağındaki tüm cihazlara UDP mesajları gönderme
- Makefiles dosyalarını değiştirme
İhtiyacınız olanlar
Donanım:
- 3 Nordic Semiconductor nRF52840 geliştirme kartı
- Kartları bağlamak için 3 adet USB - Mikro USB kablosu
- En az 3 USB bağlantı noktası olan bir Linux makine
Yazılım:
- GNU Toolchain
- Nordic nRF5x komut satırı araçları
- Segger J-Link yazılımı
- OpenThread
- Git
Aksi belirtilmediği sürece bu Codelab'in içeriği Creative Commons Attribution 3.0 Lisansı, kod örnekleri ise Apache 2.0 Lisansı ile lisanslanmıştır.
2. Başlarken
Donanım Codelab'ini tamamlama
Bu Codelab'e başlamadan önce nRF52840 kartları ve OpenThread ile Thread ağı oluşturma Codelab'ini tamamlamanız gerekir. Bu Codelab'de:
- Derleme ve yükleme için ihtiyacınız olan tüm yazılımları listeler.
- OpenThread'i nasıl oluşturacağınızı ve Nordic nRF52840 kartlarına nasıl yükleyeceğinizi öğretir.
- Thread ağının temellerini gösterir.
OpenThread'i oluşturmak ve kartlara yazılım yüklemek için gereken ortam kurulumunun hiçbiri bu codelab'de ayrıntılı olarak açıklanmamıştır. Yalnızca kartlara yazılım yüklemeyle ilgili temel talimatlar verilmiştir. Build a Thread Network Codelab'i tamamladığınız varsayılır.
Linux makine
Bu Codelab, tüm Thread geliştirme kartlarını yüklemek için i386 veya x86 tabanlı bir Linux makinesi kullanacak şekilde tasarlanmıştır. Tüm adımlar Ubuntu 14.04.5 LTS (Trusty Tahr) üzerinde test edilmiştir.
Nordic Semiconductor nRF52840 kartları
Bu Codelab'de üç nRF52840 PDK kartı kullanılır.

Yazılım yükleme
OpenThread'i oluşturup yüklemek için SEGGER J-Link, nRF5x Command Line Tools, ARM GNU Toolchain ve çeşitli Linux paketlerini yüklemeniz gerekir. Gerekli olduğu şekilde Build a Thread Network Codelab'i tamamladıysanız ihtiyacınız olan her şey zaten yüklüdür. Aksi takdirde, nRF52840 geliştirme kartlarına OpenThread'i oluşturup yükleyebildiğinizden emin olmak için devam etmeden önce bu Codelab'i tamamlayın.
3. Depoyu klonlama
OpenThread, bu Codelab'de başlangıç noktası olarak kullanabileceğiniz örnek uygulama koduyla birlikte gelir.
OpenThread Nordic nRF528xx örnekleri deposunu klonlayın ve OpenThread'i oluşturun:
$ git clone --recursive https://github.com/openthread/ot-nrf528xx $ cd ot-nrf528xx $ ./script/bootstrap
4. OpenThread API'nin Temel Özellikleri
OpenThread'in herkese açık API'leri, OpenThread deposunda ./openthread/include/openthread konumunda bulunur. Bu API'ler, uygulamalarınızda kullanılmak üzere hem Thread hem de platform düzeyinde çeşitli OpenThread özelliklerine ve işlevlerine erişim sağlar:
- OpenThread örneği bilgileri ve kontrolü
- IPv6, UDP ve CoAP gibi uygulama hizmetleri
- Komisyon üyesi ve katılımcı rolleriyle birlikte ağ kimlik bilgisi yönetimi
- Sınır yönlendirici yönetimi
- Çocuk gözetimi ve sıkışma algılama gibi gelişmiş özellikler
Tüm OpenThread API'leriyle ilgili referans bilgilerine openthread.io/reference adresinden ulaşabilirsiniz.
API kullanma
Bir API'yi kullanmak için üst dosyasını uygulama dosyalarınızdan birine ekleyin. Ardından, istediğiniz işlevi çağırın.
Örneğin, OpenThread ile birlikte verilen CLI örnek uygulamasında aşağıdaki API üstbilgileri kullanılır:
./openthread/examples/apps/cli/main.c
#include <openthread/config.h> #include <openthread/cli.h> #include <openthread/diag.h> #include <openthread/tasklet.h> #include <openthread/platform/logging.h>
OpenThread örneği
otInstance yapısı, OpenThread API'leriyle çalışırken sık sık kullanacağınız bir yapıdır. Bu yapı, başlatıldıktan sonra OpenThread kitaplığının statik bir örneğini temsil eder ve kullanıcının OpenThread API çağrıları yapmasına olanak tanır.
Örneğin, OpenThread örneği CLI örnek uygulamasının main() işlevinde başlatılır:
./openthread/examples/apps/cli/main.c
int main(int argc, char *argv[])
{
otInstance *instance
...
#if OPENTHREAD_ENABLE_MULTIPLE_INSTANCES
// Call to query the buffer size
(void)otInstanceInit(NULL, &otInstanceBufferLength);
// Call to allocate the buffer
otInstanceBuffer = (uint8_t *)malloc(otInstanceBufferLength);
assert(otInstanceBuffer);
// Initialize OpenThread with the buffer
instance = otInstanceInit(otInstanceBuffer, &otInstanceBufferLength);
#else
instance = otInstanceInitSingle();
#endif
...
return 0;
}
Platforma özgü işlevler
OpenThread ile birlikte verilen örnek uygulamalardan birine platforma özgü işlevler eklemek istiyorsanız önce bunları ./openthread/examples/platforms/openthread-system.h başlığında tüm işlevler için otSys ad alanını kullanarak tanımlayın. Ardından, bunları platforma özel bir kaynak dosyada uygulayın. Bu şekilde özetlenmiş işlevleri, diğer örnek platformlar için de 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 bildirimleri ekleyin.
openthread/instance.h başlığı için #include ifadesinden sonra şu işlev bildirimlerini ekleyin:
/** * Init LED module. * */ void otSysLedInit(void); void otSysLedSet(uint8_t aLed, bool aOn); void otSysLedToggle(uint8_t aLed); /** * A callback will be called when GPIO interrupts occur. * */ typedef void (*otSysButtonCallback)(otInstance *aInstance); void otSysButtonInit(otSysButtonCallback aCallback); void otSysButtonProcess(otInstance *aInstance);
Bunları bir sonraki adımda uygulayacağız.
otSysButtonProcess fonksiyon bildiriminde otInstance kullanıldığını unutmayın. Bu sayede, gerekirse bir düğmeye basıldığında uygulama OpenThread örneğiyle ilgili bilgilere erişebilir. Bu tamamen uygulamanızın ihtiyaçlarına bağlıdır. İşlevin uygulanmasında buna ihtiyacınız yoksa bazı araç zincirlerinde kullanılmayan değişkenlerle ilgili derleme hatalarını bastırmak için OpenThread API'deki OT_UNUSED_VARIABLE makrosunu kullanabilirsiniz. Bununla ilgili örnekleri daha sonra göreceğiz.
5. GPIO platformu soyutlamasını uygulama
Önceki adımda, ./openthread/examples/platforms/openthread-system.h içinde GPIO için kullanılabilecek platforma özgü işlev bildirimlerini inceledik. nRF52840 geliştirme kartlarındaki düğmelere ve LED'lere erişmek için bu işlevleri nRF52840 platformunda uygulamanız gerekir. Bu koda, aşağıdaki işlevleri ekleyeceksiniz:
- GPIO pinlerini ve modlarını başlatma
- Bir pindeki voltajı kontrol etme
- GPIO kesmelerini etkinleştirme ve geri çağırma kaydetme
./src/src dizininde gpio.c adlı yeni bir dosya oluşturun. Bu yeni dosyaya aşağıdaki içeriği ekleyin.
./src/src/gpio.c (yeni dosya)
İŞLEM: Tanımlar ekleyin.
Bu tanımlar, nRF52840'a özgü değerler ile OpenThread uygulama düzeyinde kullanılan değişkenler arasında soyutlamalar olarak işlev görür.
/** * @file * This file implements the system abstraction for GPIO and GPIOTE. * */ #define BUTTON_GPIO_PORT 0x50000300UL #define BUTTON_PIN 11 // button #1 #define GPIO_LOGIC_HI 0 #define GPIO_LOGIC_LOW 1 #define LED_GPIO_PORT 0x50000300UL #define LED_1_PIN 13 // turn on to indicate leader role #define LED_2_PIN 14 // turn on to indicate mesh extender role #define LED_3_PIN 15 // turn on to indicate child role #define LED_4_PIN 16 // turn on to indicate UDP receive
nRF52840 düğmeleri ve LED'leri hakkında daha fazla bilgi için Nordic Semiconductor Infocenter'ı inceleyin.
İŞLEM: Üstbilgi eklemelerini ekleyin.
Ardından, GPIO işlevselliği için ihtiyacınız olan başlıkları ekleyin.
/* Header for the functions defined here */ #include "openthread-system.h" #include <string.h> /* Header to access an OpenThread instance */ #include <openthread/instance.h> /* Headers for lower-level nRF52840 functions */ #include "platform-nrf5.h" #include "hal/nrf_gpio.h" #include "hal/nrf_gpiote.h" #include "nrfx/drivers/include/nrfx_gpiote.h"
İŞLEM: 1. düğme için geri çağırma ve kesme işlevleri ekleyin.
Ardından bu kodu ekleyin. in_pin1_handler işlevi, düğmeye basma işlevi başlatıldığında (bu dosyanın ilerleyen kısımlarında) kaydedilen geri çağırmadır.
in_pin1_handler makrosuna iletilen değişkenler işlevde kullanılmadığından bu geri çağırmanın OT_UNUSED_VARIABLE makrosunu nasıl kullandığına dikkat edin.
/* 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: Bir LED'in modunu değiştirecek bir işlev ekleyin.
Bu işlev, cihaz çoklu yayın UDP mesajı aldığında LED4'ü açıp kapatmak için kullanılır.
/**
* @brief Function to toggle the mode of an LED.
*/
void otSysLedToggle(uint8_t aLed)
{
switch (aLed)
{
case 1:
nrf_gpio_pin_toggle(LED_1_PIN);
break;
case 2:
nrf_gpio_pin_toggle(LED_2_PIN);
break;
case 3:
nrf_gpio_pin_toggle(LED_3_PIN);
break;
case 4:
nrf_gpio_pin_toggle(LED_4_PIN);
break;
}
}
İŞLEM: Düğme basma işlemlerini başlatmak ve işlemek için işlevler ekleyin.
İlk işlev, bir düğmeye basıldığında kartı başlatır. İkincisi ise 1. düğmeye basıldığında ç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ı kaydedip kapatın.
6. API: Cihaz rolü değişikliklerine tepki verme
Uygulamamızda, cihaz rolüne bağlı olarak farklı LED'lerin yanmasını istiyoruz. Şu rolleri izleyelim: Lider, Mesh Genişletici, Son Cihaz. Bunları LED'lere şu şekilde atayabiliriz:
- LED1 = Lider
- LED2 = Ağ Genişleticisi
- LED3 = Son Cihaz
Bu işlevin etkinleştirilmesi için uygulamanın cihaz rolünün ne zaman değiştiğini ve buna karşılık olarak doğru LED'in nasıl açılacağını bilmesi gerekir. İlk bölüm için OpenThread örneğini, ikinci bölüm için ise GPIO platform soyutlamasını kullanacağız.
./openthread/examples/apps/cli/main.c dosyasını tercih ettiğiniz metin düzenleyicide açın.
./openthread/examples/apps/cli/main.c
İŞLEM: Üstbilgi eklemelerini ekleyin.
main.c dosyasının includes bölümüne, rol değişikliği özelliği için ihtiyacınız olan API başlık dosyalarını ekleyin.
#include <openthread/instance.h> #include <openthread/thread.h> #include <openthread/thread_ftd.h>
İŞLEM: OpenThread örneği durum değişikliği için işleyici işlevi bildirimi ekleyin.
Bu bildirimi main.c öğesine, üstbilgi dahil edildikten sonra ve herhangi bir #if ifadesinden önce ekleyin. Bu işlev, ana uygulamadan sonra tanımlanır.
void handleNetifStateChanged(uint32_t aFlags, void *aContext);
İŞLEM: Durum değişikliği işleyici işlevi için geri çağırma kaydı ekleyin.
main.c içinde, otAppCliInit çağrısından sonra main() işlevine şu işlevi ekleyin. Bu geri çağırma kaydı, OpenThread örneğinin durumu her değiştiğinde OpenThread'in handleNetifStateChange işlevini çağırmasını söyler.
/* Register Thread state change handler */ otSetStateChangedCallback(instance, handleNetifStateChanged, instance);
İŞLEM: Durum değişikliği uygulamasını ekleyin.
main.c içinde, main() işlevinden sonra handleNetifStateChanged işlevini uygulayın. Bu işlev, OpenThread örneğinin OT_CHANGED_THREAD_ROLE işaretini kontrol eder ve değişmişse gerektiği gibi LED'leri 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 etkinleştirmek için çoklu yayın kullanma
Ayrıca uygulamamızda, bir kartta Button1'e basıldığında ağdaki diğer tüm cihazlara UDP mesajları göndermek istiyoruz. İletinin alındığını onaylamak için diğer kartlarda LED4'ü açıp kapatacağız.
Bu işlevin etkinleştirilmesi için uygulamanın:
- Başlangıçta bir UDP bağlantısı başlatma
- Ağ yerel çoklu yayın adresine bir UDP mesajı gönderebilme
- Gelen UDP mesajlarını işleme
- Gelen UDP mesajlarına yanıt olarak LED4'ü açma/kapatma
./openthread/examples/apps/cli/main.c dosyasını tercih ettiğiniz metin düzenleyicide açın.
./openthread/examples/apps/cli/main.c
İŞLEM: Üstbilgi eklemelerini ekleyin.
main.c dosyasının üst kısmındaki includes bölümüne, çoklu yayın UDP özelliği için ihtiyacınız olan API başlık dosyalarını ekleyin.
#include <string.h> #include <openthread/message.h> #include <openthread/udp.h> #include "utils/code_utils.h"
code_utils.h üstbilgisi, çalışma zamanı koşullarını doğrulayan ve hataları düzgün şekilde işleyen otEXPECT ve otEXPECT_ACTION makroları için kullanılır.
İŞLEM: Tanımlar ve sabitler ekleyin:
main.c dosyasında, includes bölümünden sonra ve herhangi bir #if ifadesinden önce UDP'ye özgü sabitler ve tanımlar ekleyin:
#define UDP_PORT 1212 static const char UDP_DEST_ADDR[] = "ff03::1"; static const char UDP_PAYLOAD[] = "Hello OpenThread World!";
ff03::1, ağ yerel çoklu yayın adresidir. Bu adrese gönderilen tüm iletiler ağdaki tüm Tam İleti Dizisi Cihazları'na gönderilir. OpenThread'de çoklu yayın desteği hakkında daha fazla bilgi için Multicast on openthread.io (openthread.io'da çoklu yayın) başlıklı makaleyi inceleyin.
İŞLEM: İşlev bildirimleri ekleyin.
main.c dosyasında, otTaskletsSignalPending tanımından sonra ve main() işlevinden önce UDP'ye özgü işlevlerin yanı sıra bir UDP soketini temsil eden statik bir değişken ekleyin:
static void initUdp(otInstance *aInstance);
static void sendUdp(otInstance *aInstance);
static void handleButtonInterrupt(otInstance *aInstance);
void handleUdpReceive(void *aContext, otMessage *aMessage,
const otMessageInfo *aMessageInfo);
static otUdpSocket sUdpSocket;
İŞLEM: GPIO LED'lerini ve düğmesini başlatmak için aramalar ekleyin.
main.c içinde, otSetStateChangedCallback çağrısından sonra main() işlevine şu işlev çağrılarını ekleyin. Bu işlevler, GPIO ve GPIOTE pinlerini başlatır ve düğme basma etkinliklerini işlemek için bir düğme işleyicisi ayarlar.
/* init GPIO LEDs and button */ otSysLedInit(); otSysButtonInit(handleButtonInterrupt);
İŞLEM: UDP başlatma çağrısını ekleyin.
main.c içinde, yeni eklediğiniz otSysButtonInit çağrısından sonra main() işlevine şu işlevi ekleyin:
initUdp(instance);
Bu çağrı, uygulama başlatıldığında bir UDP soketinin başlatılmasını sağlar. Bu olmadan cihaz, UDP mesajları gönderemez veya alamaz.
İŞLEM: GPIO düğmesi etkinliğini işleme çağrısı ekleyin.
main.c içinde, while döngüsünde, otSysProcessDrivers çağrısından sonra main() işlevine şu işlev çağrısını ekleyin. gpio.c içinde tanımlanan bu işlev, düğmenin basılıp basılmadığını kontrol eder. Basıldıysa yukarıdaki adımda ayarlanan işleyiciyi (handleButtonInterrupt) çağırır.
otSysButtonProcess(instance);
İŞLEM: Düğme kesme işleyicisini uygulayın.
main.c içinde, önceki adımda eklediğiniz handleNetifStateChanged işlevinden sonra handleButtonInterrupt işlevinin uygulamasını ekleyin:
/**
* Function to handle button push event
*/
void handleButtonInterrupt(otInstance *aInstance)
{
sendUdp(aInstance);
}
İŞLEM: UDP başlatma işlemini uygulayın.
main.c içinde, yeni eklediğiniz handleButtonInterrupt işlevinden sonra initUdp işlevinin uygulamasını ekleyin:
/**
* Initialize UDP socket
*/
void initUdp(otInstance *aInstance)
{
otSockAddr listenSockAddr;
memset(&sUdpSocket, 0, sizeof(sUdpSocket));
memset(&listenSockAddr, 0, sizeof(listenSockAddr));
listenSockAddr.mPort = UDP_PORT;
otUdpOpen(aInstance, &sUdpSocket, handleUdpReceive, aInstance);
otUdpBind(aInstance, &sUdpSocket, &listenSockAddr, OT_NETIF_THREAD);
}
UDP_PORT, daha önce tanımladığınız bağlantı noktasıdır (1212). otUdpOpen işlevi, soketi açar ve bir UDP mesajı alındığında geri çağırma işlevini (handleUdpReceive) kaydeder. otUdpBind, OT_NETIF_THREAD değerini ileterek soketi Thread ağ arayüzüne bağlar. Diğer ağ arayüzü seçenekleri için UDP API Referansı'ndaki otNetifIdentifier numaralandırmasına bakın.
İŞLEM: UDP mesajlaşmayı uygulayın.
main.c içinde, yeni eklediğiniz initUdp işlevinden sonra sendUdp işlevinin uygulamasını ekleyin:
/**
* Send a UDP datagram
*/
void sendUdp(otInstance *aInstance)
{
otError error = OT_ERROR_NONE;
otMessage * message;
otMessageInfo messageInfo;
otIp6Address destinationAddr;
memset(&messageInfo, 0, sizeof(messageInfo));
otIp6AddressFromString(UDP_DEST_ADDR, &destinationAddr);
messageInfo.mPeerAddr = destinationAddr;
messageInfo.mPeerPort = UDP_PORT;
message = otUdpNewMessage(aInstance, NULL);
otEXPECT_ACTION(message != NULL, error = OT_ERROR_NO_BUFS);
error = otMessageAppend(message, UDP_PAYLOAD, sizeof(UDP_PAYLOAD));
otEXPECT(error == OT_ERROR_NONE);
error = otUdpSend(aInstance, &sUdpSocket, message, &messageInfo);
exit:
if (error != OT_ERROR_NONE && message != NULL)
{
otMessageFree(message);
}
}
otEXPECT ve otEXPECT_ACTION makroları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 boşalttığı exit bloğuna atlayarak hataları düzgün bir şekilde işler.
UDP'yi başlatmak için kullanılan işlevler hakkında daha fazla bilgi edinmek için openthread.io'daki IPv6 ve UDP Referansları'na bakın.
İŞLEM: UDP mesaj işlemeyi uygulayın.
main.c içinde, az önce 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
Gösterimi kolaylaştırmak için cihazlarımızın açıldığında hemen Thread'i başlatmasını ve bir ağa katılmasını istiyoruz. Bunun için otOperationalDataset yapısını kullanacağız. Bu yapı, Thread ağı kimlik bilgilerini bir cihaza iletmek için gereken tüm parametreleri içerir.
Bu yapının kullanılması, uygulamamızı daha güvenli hale getirmek ve ağımızdaki Thread düğümlerini yalnızca uygulamayı çalıştıranlarla sınırlamak için OpenThread'e yerleştirilmiş ağ varsayılanlarını geçersiz kılar.
./openthread/examples/apps/cli/main.c dosyasını tercih ettiğiniz metin düzenleyicide tekrar açın.
./openthread/examples/apps/cli/main.c
İŞLEM: Üstbilgi ekleme ifadesini ekleyin.
main.c dosyasının üst kısmındaki includes bölümüne, Thread ağını yapılandırmak için ihtiyacınız olan API başlık dosyasını ekleyin:
#include <openthread/dataset_ftd.h>
İŞLEM: Ağ yapılandırmasını ayarlamak için işlev tanımlaması ekleyin.
Bu bildirimi main.c öğesine, üstbilgi dahil edildikten sonra ve herhangi bir #if ifadesinden önce ekleyin. Bu işlev, ana uygulama işlevinden sonra tanımlanır.
static void setNetworkConfiguration(otInstance *aInstance);
İŞLEM: Ağ yapılandırması çağrısını ekleyin.
main.c içinde, otSetStateChangedCallback çağrısından sonra main() işlevine şu işlev çağrısını ekleyin. Bu işlev, Thread ağı veri kümesini yapılandırır.
/* Override default network credentials */ setNetworkConfiguration(instance);
İŞLEM: Thread ağ arayüzünü ve yığınını etkinleştirmek için çağrılar ekleyin.
main.c içinde, otSysButtonInit çağrısından sonra main() işlevine şu işlev çağrılarını ekleyin.
/* Start the Thread network interface (CLI cmd > ifconfig up) */ otIp6SetEnabled(instance, true); /* Start the Thread stack (CLI cmd > thread start) */ otThreadSetEnabled(instance, true);
İŞLEM: Thread ağ yapılandırmasını uygulayın.
main.c içinde, main() işlevinden sonra setNetworkConfiguration işlevinin uygulamasını ekleyin:
/**
* Override default network settings, such as panid, so the devices can join a
network
*/
void setNetworkConfiguration(otInstance *aInstance)
{
static char aNetworkName[] = "OTCodelab";
otOperationalDataset aDataset;
memset(&aDataset, 0, sizeof(otOperationalDataset));
/*
* Fields that can be configured in otOperationDataset to override defaults:
* Network Name, Mesh Local Prefix, Extended PAN ID, PAN ID, Delay Timer,
* Channel, Channel Mask Page 0, Network Key, PSKc, Security Policy
*/
aDataset.mActiveTimestamp.mSeconds = 1;
aDataset.mActiveTimestamp.mTicks = 0;
aDataset.mActiveTimestamp.mAuthoritative = false;
aDataset.mComponents.mIsActiveTimestampPresent = true;
/* Set Channel to 15 */
aDataset.mChannel = 15;
aDataset.mComponents.mIsChannelPresent = true;
/* Set Pan ID to 2222 */
aDataset.mPanId = (otPanId)0x2222;
aDataset.mComponents.mIsPanIdPresent = true;
/* Set Extended Pan ID to C0DE1AB5C0DE1AB5 */
uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xC0, 0xDE, 0x1A, 0xB5, 0xC0, 0xDE, 0x1A, 0xB5};
memcpy(aDataset.mExtendedPanId.m8, extPanId, sizeof(aDataset.mExtendedPanId));
aDataset.mComponents.mIsExtendedPanIdPresent = true;
/* Set network key to 1234C0DE1AB51234C0DE1AB51234C0DE */
uint8_t key[OT_NETWORK_KEY_SIZE] = {0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE, 0x1A, 0xB5, 0x12, 0x34, 0xC0, 0xDE};
memcpy(aDataset.mNetworkKey.m8, key, sizeof(aDataset.mNetworkKey));
aDataset.mComponents.mIsNetworkKeyPresent = true;
/* Set Network Name to OTCodelab */
size_t length = strlen(aNetworkName);
assert(length <= OT_NETWORK_NAME_MAX_SIZE);
memcpy(aDataset.mNetworkName.m8, aNetworkName, length);
aDataset.mComponents.mIsNetworkNamePresent = true;
otDatasetSetActive(aInstance, &aDataset);
/* Set the router selection jitter to override the 2 minute default.
CLI cmd > routerselectionjitter 20
Warning: For demo purposes only - not to be used in a real product */
uint8_t jitterValue = 20;
otThreadSetRouterSelectionJitter(aInstance, jitterValue);
}
İşlevde ayrıntılı olarak açıklandığı gibi, bu uygulama için kullandığımız Thread ağı parametreleri şunlardır:
- Kanal = 15
- PAN ID = 0x2222
- Genişletilmiş kalıcı hesap numarası kimliği = C0DE1AB5C0DE1AB5
- Ağ Anahtarı = 1234C0DE1AB51234C0DE1AB51234C0DE
- Ağ Adı = OTCodelab
Ayrıca, bu bölümde yönlendirici seçimi titremesini azaltırız. Böylece cihazlarımız, demo amacıyla rolleri daha hızlı değiştirir. Bu işlemin yalnızca düğüm bir FTD (Full Thread Device) ise yapıldığını unutmayın. Bu konuyla ilgili daha fazla bilgiyi sonraki adımda bulabilirsiniz.
9. API: Kısıtlanmış işlevler
OpenThread'in bazı API'leri, yalnızca demo veya test amacıyla değiştirilmesi gereken ayarları değiştirir. Bu API'ler, OpenThread kullanan bir uygulamanın üretim dağıtımında kullanılmamalıdır.
Örneğin, otThreadSetRouterSelectionJitter işlevi, bir uç cihazın kendisini Mesh Extender olarak tanıtması için gereken süreyi (saniye cinsinden) ayarlar. Bu değer için varsayılan değer, Thread Spesifikasyonu'na göre 120'dir. Bu Codelab'de kolaylık sağlamak için bu değeri 20 olarak değiştireceğiz. Böylece, Thread düğümünün rol değiştirmesi için çok uzun süre beklemeniz gerekmeyecek.
Not: MTD cihazlar Mesh genişletici olmaz ve otThreadSetRouterSelectionJitter gibi bir işlevin desteği MTD derlemesine dahil edilmez. Daha sonra CMake seçeneğini -DOT_MTD=OFF belirtmemiz gerekir. Aksi takdirde derleme hatasıyla karşılaşırız.
Bunu, otThreadSetRouterSelectionJitter işlev tanımına bakarak doğrulayabilirsiniz. İşlev tanımı, OPENTHREAD_FTD ön işlemci yönergesi içinde yer alır:
./openthread/src/core/api/thread_ftd_api.cpp
#if OPENTHREAD_FTD
#include <openthread/thread_ftd.h>
...
void otThreadSetRouterSelectionJitter(otInstance *aInstance, uint8_t aRouterJitter)
{
Instance &instance = *static_cast<Instance *>(aInstance);
instance.GetThreadNetif().GetMle().SetRouterSelectionJitter(aRouterJitter);
}
...
#endif // OPENTHREAD_FTD
10. CMake güncellemeleri
Uygulamanızı oluşturmadan önce üç CMake dosyasında küçük güncellemeler yapmanız gerekir. Bunlar, uygulamanızı derlemek ve bağlamak için derleme sistemi tarafından kullanılır.
./third_party/NordicSemiconductor/CMakeLists.txt
GPIO işlevlerinin uygulamada tanımlandığından emin olmak için NordicSemiconductor CMakeLists.txt'a bazı işaretler ekleyin.
İŞLEM: CMakeLists.txt dosyasına işaret ekleyin.
./third_party/NordicSemiconductor/CMakeLists.txt dosyasını tercih ettiğiniz metin düzenleyicide açın ve COMMON_FLAG bölümüne aşağıdaki satırları ekleyin.
...
set(COMMON_FLAG
-DSPIS_ENABLED=1
-DSPIS0_ENABLED=1
-DNRFX_SPIS_ENABLED=1
-DNRFX_SPIS0_ENABLED=1
...
# Defined in ./third_party/NordicSemiconductor/nrfx/templates/nRF52840/nrfx_config.h
-DGPIOTE_ENABLED=1
-DGPIOTE_CONFIG_IRQ_PRIORITY=7
-DGPIOTE_CONFIG_NUM_OF_LOW_POWER_EVENTS=1
)
...
./src/CMakeLists.txt
Yeni gpio.c kaynak dosyasını eklemek için ./src/CMakeLists.txt dosyasını düzenleyin:
İŞLEM: gpio kaynağını ./src/CMakeLists.txt dosyasına ekleyin.
./src/CMakeLists.txt dosyasını tercih ettiğiniz metin düzenleyicide açın ve dosyayı NRF_COMM_SOURCES bölümüne ekleyin.
... set(NRF_COMM_SOURCES ... src/gpio.c ... ) ...
./third_party/NordicSemiconductor/CMakeLists.txt
Son olarak, nrfx_gpiote.c sürücü dosyasını Nordic sürücülerinin kitaplık derlemesine dahil edilmesi için NordicSemiconductor CMakeLists.txt dosyasına ekleyin.
İŞLEM: gpio sürücüsünü NordicSemiconductor CMakeLists.txt dosyasına ekleyin.
./third_party/NordicSemiconductor/CMakeLists.txt dosyasını tercih ettiğiniz metin düzenleyicide açın ve dosyayı COMMON_SOURCES bölümüne ekleyin.
... set(COMMON_SOURCES ... nrfx/drivers/src/nrfx_gpiote.c ... ) ...
11. Cihazları kurma
Tüm kod güncellemeleri tamamlandığında uygulamayı oluşturup üç Nordic nRF52840 geliştirme kartına da yükleyebilirsiniz. Her cihaz, Tam Thread Cihazı (FTD) olarak işlev görür.
OpenThread'i derleme
nRF52840 platformu için OpenThread FTD ikili dosyalarını oluşturun.
$ cd ~/ot-nrf528xx $ ./script/build nrf52840 UART_trans -DOT_MTD=OFF -DOT_APP_RCP=OFF -DOT_RCP=OFF
OpenThread FTD KSA ikili programının bulunduğu dizine gidin ve ARM Embedded 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
Panelleri yükleme
ot-cli-ftd.hex dosyasını her nRF52840 kartına yükleyin.
USB kablosunu nRF52840 kartındaki harici güç pininin yanındaki Micro-USB hata ayıklama bağlantı noktasına takın ve ardından Linux makinenize bağlayın. Doğru şekilde ayarlayın. LED5 açık olmalıdır.

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

nRFx Command Line Tools'un bulunduğu konuma gidin ve kartın seri numarasını kullanarak OpenThread CLI FTD onaltılık dosyasını nRF52840 kartına yükleyin:
$ cd ~/nrfjprog
$ ./nrfjprog -f nrf52 -s 683704924 --verify --chiperase --program \
~/openthread/output/nrf52840/bin/ot-cli-ftd.hex --reset
LED5, yanıp sönme sırasında kısa süreliğine kapanır. Başarılı olursa aşağıdaki çıktı oluşturulur:
Parsing hex file. Erasing user available code and UICR flash areas. Applying system reset. Checking that the area to write is not protected. Programing device. Applying system reset. Run.
Bu "kartları yükle" adımını diğer iki kart için de tekrarlayın. Her kart, Linux makinesine aynı şekilde bağlanmalıdır ve kartın seri numarası hariç, yükleme komutu aynıdır. Her kartın benzersiz seri numarasını
nrfjprog yanıp sönme komutu
Başarılı olursa her kartta LED1, LED2 veya LED3 yanar. Hatta yanıp sönme işleminden kısa süre sonra yanan LED'in 3'ten 2'ye (veya 2'den 1'e) değiştiğini görebilirsiniz (cihaz rolü değiştirme özelliği).
12. Uygulama işlevselliği
Üç nRF52840 kartı da artık çalıştırılıyor olmalı ve OpenThread uygulamamızı çalıştırıyor olmalıdır. Daha önce ayrıntılı olarak açıklandığı gibi bu uygulamanın iki temel özelliği vardır.
Cihaz rolü göstergeleri
Her karttaki yanan LED, Thread düğümünün mevcut rolünü yansıtır:
- LED1 = Lider
- LED2 = Ağ Genişleticisi
- LED3 = Son Cihaz
Rol değiştikçe yanan LED de değişir. Bu değişiklikleri, her cihaz açıldıktan sonraki 20 saniye içinde bir veya iki panoda görmüş olmanız gerekir.
UDP Çoklu Yayın
Bir kartta Düğme1'e basıldığında, Thread ağındaki diğer tüm düğümleri içeren mesh-local çoklu yayın adresine bir UDP mesajı gönderilir. Bu mesaj alındığında diğer tüm kartlardaki LED4 açılır veya kapanır. LED4, başka bir UDP mesajı alana kadar her kartta açık veya kapalı kalır.


13. Demo: Cihaz rolü değişikliklerini gözlemleme
Flaşladığınız cihazlar, Extender-Capable Device (ECD) adı verilen belirli bir tür Full Thread Device (FTD) cihazdır. Bu, cihazların Mesh Extender veya yedek ECD olarak işlev görebileceği ve kendilerini yedek ECD'den Mesh Extender'a yükseltebileceği anlamına gelir.
Thread, 32'ye kadar Mesh Extender'ı destekleyebilir ancak Mesh Extender sayısını 16 ile 23 arasında tutmaya çalışır. Bir ECD, uç cihaz olarak bağlanırsa ve Mesh Extender sayısı 16'nın altındaysa otomatik olarak Mesh Extender'a 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.
Her Thread ağında, Thread ağındaki Mesh Extender'ların kümesini yönetmekten sorumlu bir Mesh Extender olan Lider de bulunur. Tüm cihazlar açıkken 20 saniye sonra bunlardan biri Lider (LED1 açık), diğer ikisi ise Mesh Genişletici (LED2 açık) olmalıdır.

Lideri kaldırma
Lider, Thread ağından kaldırılırsa ağda liderin olmaya devam etmesini sağlamak için farklı bir Mesh Extender kendisini lider olarak tanıtır.
Güç anahtarını kullanarak skor tablosunu (LED1'in yandığı) kapatın. 20 saniye kadar bekleyin. Kalan iki karttan birinde LED2 (Mesh Extender) kapanır ve LED1 (Leader) açılır. Bu cihaz artık Thread ağının lideri.

Orijinal skor tablosunu tekrar etkinleştirin. Cihaz, Thread ağına otomatik olarak bir uç cihaz olarak yeniden katılmalıdır (LED3 yanar). 20 saniye içinde (Router Seçimi Titremesi) kendisini Mesh Extender'a (Ağ Genişletici) yükseltir (LED2 yanar).

Panoları sıfırlama
Üç kartı da kapatıp tekrar açın ve LED'leri gözlemleyin. Güç verilen ilk kart, Lider rolünde başlamalıdır (LED1 yanar). Bir Thread ağındaki ilk Mesh Extender otomatik olarak Lider olur.
Diğer iki kart başlangıçta ağa Uç Cihazlar (LED3 yanar) olarak bağlanır ancak 20 saniye içinde kendilerini Mesh Genişleticiler (LED2 yanar) olarak tanıtır.
Ağ bölümleri
Kartlarınız yeterli gücü almıyorsa veya aralarındaki radyo bağlantısı zayıfsa Thread ağı bölümlere ayrılabilir ve birden fazla cihaz Lider olarak gösterilebilir.
Thread kendi kendini iyileştirdiğinden bölümler sonunda tek bir liderle tek bir bölümde birleşir.
14. Demo: UDP çoklu yayını gönderme
Önceki alıştırmadan devam ediyorsanız hiçbir cihazda LED4 yanmamalıdır.
Herhangi bir tahtayı seçip 1. Düğme'ye basın. Uygulamanın çalıştığı Thread ağındaki diğer tüm kartlarda LED4'ün durumu değişmelidir. Önceki alıştırmadan devam ediyorsanız bu ayarların etkin olması gerekir.

Aynı pano için Button1'e tekrar basın. Diğer tüm kartlardaki LED4 tekrar açılıp kapanmalıdır.
Farklı bir kartta Button1'e basın ve LED4'ün diğer kartlarda nasıl açılıp kapandığını gözlemleyin. LED4'ün şu anda açık olduğu kartlardan birinde Button1'e basın. LED4, ilgili kart için açık kalır ancak diğerleri için açılır.

Ağ bölümleri
Panolarınız bölümlere ayrılmışsa ve aralarında birden fazla lider varsa çoklu yayın mesajının sonucu panolar arasında farklılık gösterir. Bölümlendirilmiş (ve dolayısıyla bölümlendirilmiş Thread ağının tek üyesi olan) bir kartta Düğme1'e basarsanız diğer kartlardaki LED4 yanıt olarak yanmaz. Bu durumda, kartları sıfırlayın. İdeal olarak, kartlar tek bir Thread ağı oluşturur ve UDP mesajlaşması düzgün şekilde çalışır.
15. Tebrikler!
OpenThread API'lerini kullanan bir uygulama oluşturdunuz.
Artık şunları biliyorsunuz:
- Nordic nRF52840 geliştirme kartlarındaki düğmeleri ve LED'leri programlama
- Yaygın OpenThread API'lerini ve
otInstancesınıfını kullanma - OpenThread durum değişikliklerini izleme ve bunlara tepki verme
- Thread ağındaki tüm cihazlara UDP mesajları gönderme
- Makefiles dosyalarını değiştirme
Sonraki adımlar
Bu Codelab'den yararlanarak aşağıdaki alıştırmaları deneyin:
- GPIO modülünü, yerleşik LED'ler yerine GPIO pinlerini kullanacak şekilde değiştirin ve Mesh Extender rolüne göre renk değiştiren harici RGB LED'leri bağlayın.
- Farklı bir örnek platform için GPIO desteği ekleme
- Bir düğmeye basarak tüm cihazlara ping atmak için çoklu yayın kullanmak yerine, tek bir cihazı bulup ping atmak için Router/Leader API'yi kullanın.
- OpenThread Sınır Yönlendirici kullanarak bağlantılı ağınızı internete bağlayın ve LED'leri yakmak için Thread ağının dışından çoklu yayın yapın.
Daha fazla bilgi
Aşağıdakiler de dahil olmak üzere çeşitli OpenThread kaynakları için openthread.io ve GitHub'a göz atın:
- Desteklenen Platformlar: OpenThread'i destekleyen tüm platformları keşfedin.
- OpenThread'i derleme: OpenThread'i derleme ve yapılandırma hakkında daha fazla bilgi
- Thread Primer: Thread kavramları hakkında harika bir referans
Referans: