Mengembangkan dengan API OpenThread

1. Pengantar

26b7f4f6b3ea0700.png

OpenThread yang dirilis oleh Nest adalah implementasi open source dari protokol jaringan Thread®. Nest telah merilis OpenThread untuk membuat teknologi yang digunakan dalam produk Nest tersedia secara luas bagi developer untuk mempercepat pengembangan produk untuk rumah yang terhubung.

Spesifikasi Thread menentukan protokol komunikasi nirkabel device-to-device berbasis IPv6 yang andal, aman, dan berdaya rendah untuk aplikasi rumah. OpenThread mengimplementasikan semua lapisan jaringan Thread, termasuk IPv6, 6LoWPAN, IEEE 802.15.4 dengan keamanan MAC, Pembentukan Link Mesh, dan Perutean Mesh.

Dalam Codelab ini, Anda akan menggunakan OpenThread API untuk memulai jaringan Thread, memantau dan bereaksi terhadap perubahan peran perangkat, serta mengirim pesan UDP, dan mengikat tindakan ini ke tombol dan LED pada hardware asli.

2a6db2e258c32237.png

Yang akan Anda pelajari

  • Cara memprogram tombol dan LED di papan pengembangan Nordic nRF52840
  • Cara menggunakan API OpenThread umum dan class otInstance
  • Cara memantau dan bereaksi terhadap perubahan status OpenThread
  • Cara mengirim pesan UDP ke semua perangkat dalam jaringan Thread
  • Cara mengubah Makefile

Yang Anda butuhkan

Hardware:

  • 3 papan pengembangan Nordic Semiconductor nRF52840
  • 3 kabel USB ke Micro-USB untuk menghubungkan papan
  • Mesin Linux dengan minimal 3 port USB

Software:

  • GNU Toolchain
  • Alat command line Nordic nRF5x
  • Software Segger J-Link
  • OpenThread
  • Git

Kecuali dinyatakan lain, konten Codelab ini dilisensikan berdasarkan Lisensi Creative Commons Attribution 3.0, dan contoh kode dilisensikan berdasarkan Lisensi Apache 2.0.

2. Memulai

Selesaikan Codelab Hardware

Sebelum memulai Codelab ini, Anda harus menyelesaikan Codelab Membangun Jaringan Thread dengan Board nRF52840 dan OpenThread, yang:

  • Mencantumkan semua software yang Anda butuhkan untuk membangun dan mem-flash
  • Mengajari Anda cara membangun OpenThread dan mem-flash-nya di papan Nordic nRF52840
  • Menunjukkan dasar-dasar jaringan Thread

Tidak ada penyiapan lingkungan yang diperlukan untuk membangun OpenThread dan mem-flash board yang dijelaskan dalam Codelab ini—hanya petunjuk dasar untuk mem-flash board. Diasumsikan Anda telah menyelesaikan Codelab Membangun Jaringan Thread.

Mesin Linux

Codelab ini dirancang untuk menggunakan mesin Linux berbasis i386 atau x86 untuk mem-flash semua board pengembangan Thread. Semua langkah telah diuji di Ubuntu 14.04.5 LTS (Trusty Tahr).

Board Nordic Semiconductor nRF52840

Codelab ini menggunakan tiga board PDK nRF52840.

a6693da3ce213856.png

Menginstal Software

Untuk membuat dan mem-flash OpenThread, Anda perlu menginstal SEGGER J-Link, alat Command Line nRF5x, ARM GNU Toolchain, dan berbagai paket Linux. Jika telah menyelesaikan Codelab Membangun Jaringan Thread seperti yang diperlukan, Anda akan memiliki semua yang perlu diinstal. Jika belum, selesaikan Codelab tersebut sebelum melanjutkan untuk memastikan Anda dapat membuat dan mem-flash OpenThread ke papan pengembangan nRF52840.

3. Melakukan cloning repositori

OpenThread dilengkapi dengan kode aplikasi contoh yang dapat Anda gunakan sebagai titik awal untuk Codelab ini.

Clone repositori contoh OpenThread Nordic nRF528xx dan bangun OpenThread:

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

4. Dasar-Dasar OpenThread API

API publik OpenThread berada di ./openthread/include/openthread di repositori OpenThread. API ini menyediakan akses ke berbagai fitur dan fungsi OpenThread di tingkat Thread dan platform untuk digunakan dalam aplikasi Anda:

  • Informasi dan kontrol instance OpenThread
  • Layanan aplikasi seperti IPv6, UDP, dan CoAP
  • Pengelolaan kredensial jaringan, beserta peran Commissioner dan Joiner
  • Pengelolaan Router Pembatas
  • Fitur yang ditingkatkan seperti Pengawasan Anak dan Deteksi Kemacetan

Informasi referensi tentang semua OpenThread API tersedia di openthread.io/reference.

Menggunakan API

Untuk menggunakan API, sertakan file header-nya di salah satu file aplikasi Anda. Kemudian, panggil fungsi yang diinginkan.

Misalnya, aplikasi contoh CLI yang disertakan dengan OpenThread menggunakan header API berikut:

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

Instance OpenThread

Struktur otInstance adalah sesuatu yang akan sering Anda gunakan saat bekerja dengan OpenThread API. Setelah diinisialisasi, struktur ini merepresentasikan instance statis library OpenThread dan memungkinkan pengguna melakukan panggilan OpenThread API.

Misalnya, instance OpenThread diinisialisasi dalam fungsi main() aplikasi contoh CLI:

./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;
}

Fungsi khusus platform

Jika Anda ingin menambahkan fungsi khusus platform ke salah satu aplikasi contoh yang disertakan dengan OpenThread, nyatakan terlebih dahulu fungsi tersebut di header ./openthread/examples/platforms/openthread-system.h, menggunakan namespace otSys untuk semua fungsi. Kemudian, terapkan di file sumber khusus platform. Dengan cara ini, Anda dapat menggunakan header fungsi yang sama untuk platform contoh lainnya.

Misalnya, fungsi GPIO yang akan kita gunakan untuk terhubung ke tombol dan LED nRF52840 harus dideklarasikan di openthread-system.h.

Buka file ./openthread/examples/platforms/openthread-system.h di editor teks pilihan Anda.

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

TINDAKAN: Tambahkan deklarasi fungsi GPIO khusus platform.

Tambahkan deklarasi fungsi ini setelah #include untuk header openthread/instance.h:

/**
 * 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);

Kita akan menerapkannya di langkah berikutnya.

Perhatikan bahwa deklarasi fungsi otSysButtonProcess menggunakan otInstance. Dengan begitu, aplikasi dapat mengakses informasi tentang instance OpenThread saat tombol ditekan, jika diperlukan. Semuanya bergantung pada kebutuhan aplikasi Anda. Jika Anda tidak memerlukannya dalam penerapan fungsi, Anda dapat menggunakan makro OT_UNUSED_VARIABLE dari OpenThread API untuk menekan error build terkait variabel yang tidak digunakan untuk beberapa toolchain. Kita akan melihat contohnya nanti.

5. Menerapkan abstraksi platform GPIO

Pada langkah sebelumnya, kita telah membahas deklarasi fungsi khusus platform di ./openthread/examples/platforms/openthread-system.h yang dapat digunakan untuk GPIO. Untuk mengakses tombol dan LED di papan pengembangan nRF52840, Anda harus menerapkan fungsi tersebut untuk platform nRF52840. Dalam kode ini, Anda akan menambahkan fungsi yang:

  • Menginisialisasi pin dan mode GPIO
  • Mengontrol voltase pada pin
  • Aktifkan interupsi GPIO dan daftarkan callback

Di direktori ./src/src, buat file baru bernama gpio.c. Di file baru ini, tambahkan konten berikut.

./src/src/gpio.c (file baru)

TINDAKAN: Menambahkan definisi.

Definisi ini berfungsi sebagai abstraksi antara nilai khusus nRF52840 dan variabel yang digunakan di tingkat aplikasi OpenThread.

/**
 * @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

Untuk mengetahui informasi selengkapnya tentang tombol dan LED nRF52840, lihat Nordic Semiconductor Infocenter.

TINDAKAN: Menambahkan header mencakup.

Selanjutnya, tambahkan header yang akan Anda perlukan untuk fungsi GPIO.

/* 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"

TINDAKAN: Tambahkan fungsi callback dan interupsi untuk Tombol 1.

Tambahkan kode ini berikutnya. Fungsi in_pin1_handler adalah callback yang didaftarkan saat fungsi penekanan tombol diinisialisasi (nanti dalam file ini).

Perhatikan cara callback ini menggunakan makro OT_UNUSED_VARIABLE, karena variabel yang diteruskan ke in_pin1_handler sebenarnya tidak digunakan dalam fungsi.

/* 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;
}

TINDAKAN: Tambahkan fungsi untuk mengonfigurasi LED.

Tambahkan kode ini untuk mengonfigurasi mode dan status semua LED selama inisialisasi.

/**
 * @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);
}

TINDAKAN: Tambahkan fungsi untuk menyetel mode LED.

Fungsi ini akan digunakan saat peran perangkat berubah.

/**
 * @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;
    }
}

TINDAKAN: Tambahkan fungsi untuk mengganti mode LED.

Fungsi ini akan digunakan untuk mengalihkan LED4 saat perangkat menerima pesan UDP multicast.

/**
 * @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;
    }
}

TINDAKAN: Tambahkan fungsi untuk menginisialisasi dan memproses penekanan tombol.

Fungsi pertama menginisialisasi papan untuk penekanan tombol, dan fungsi kedua mengirim pesan UDP multicast saat Tombol 1 ditekan.

/**
 * @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);
    }
}

TINDAKAN: Simpan dan tutup gpio.c file.

6. API: Bereaksi terhadap perubahan peran perangkat

Dalam aplikasi, kita ingin LED yang berbeda menyala bergantung pada peran perangkat. Mari kita lacak peran berikut: Pemimpin, Router, Perangkat Akhir. Kita dapat menetapkannya ke LED seperti berikut:

  • LED1 = Pemimpin
  • LED2 = Router
  • LED3 = Perangkat Akhir

Untuk mengaktifkan fungsi ini, aplikasi perlu mengetahui kapan peran perangkat telah berubah dan cara mengaktifkan LED yang benar sebagai respons. Kita akan menggunakan instance OpenThread untuk bagian pertama, dan abstraksi platform GPIO untuk bagian kedua.

Buka file ./openthread/examples/apps/cli/main.c di editor teks pilihan Anda.

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

TINDAKAN: Menambahkan header mencakup.

Di bagian include file main.c, tambahkan file header API yang akan Anda perlukan untuk fitur perubahan peran.

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

TINDAKAN: Tambahkan deklarasi fungsi handler untuk perubahan status instance OpenThread.

Tambahkan deklarasi ini ke main.c, setelah menyertakan header dan sebelum pernyataan #if. Fungsi ini akan ditentukan setelah aplikasi utama.

void handleNetifStateChanged(uint32_t aFlags, void *aContext);

TINDAKAN: Tambahkan pendaftaran callback untuk fungsi pengendali perubahan status.

Di main.c, tambahkan fungsi ini ke fungsi main() setelah panggilan otAppCliInit. Pendaftaran callback ini memberi tahu OpenThread untuk memanggil fungsi handleNetifStateChange setiap kali status instance OpenThread berubah.

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

TINDAKAN: Tambahkan implementasi perubahan status.

Di main.c, setelah fungsi main(), terapkan fungsi handleNetifStateChanged. Fungsi ini memeriksa tanda OT_CHANGED_THREAD_ROLE instance OpenThread dan jika telah berubah, akan menyalakan/mematikan LED sesuai kebutuhan.

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: Menggunakan multicast untuk mengaktifkan LED

Dalam aplikasi kita, kita juga ingin mengirim pesan UDP ke semua perangkat lain dalam jaringan saat Button1 ditekan di satu papan. Untuk mengonfirmasi penerimaan pesan, kita akan mengalihkan LED4 di papan lain sebagai respons.

Untuk mengaktifkan fungsi ini, aplikasi harus:

  • Melakukan inisialisasi koneksi UDP saat memulai
  • Dapat mengirim pesan UDP ke alamat multicast lokal mesh
  • Menangani pesan UDP masuk
  • Mengalihkan LED4 sebagai respons terhadap pesan UDP masuk

Buka file ./openthread/examples/apps/cli/main.c di editor teks pilihan Anda.

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

TINDAKAN: Menambahkan header mencakup.

Di bagian include di bagian atas file main.c, tambahkan file header API yang akan Anda perlukan untuk fitur UDP multicast.

#include <string.h>

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

#include "utils/code_utils.h"

Header code_utils.h digunakan untuk makro otEXPECT dan otEXPECT_ACTION yang memvalidasi kondisi runtime dan menangani error dengan baik.

TINDAKAN: Tambahkan definisi dan konstanta:

Dalam file main.c, setelah bagian include dan sebelum pernyataan #if, tambahkan konstanta dan definisi khusus UDP:

#define UDP_PORT 1212

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

ff03::1 adalah alamat multicast lokal mesh. Semua pesan yang dikirim ke alamat ini akan dikirim ke semua Perangkat Rangkaian Pesan Lengkap di jaringan. Lihat Multicast di openthread.io untuk mengetahui informasi selengkapnya tentang dukungan multicast di OpenThread.

TINDAKAN: Tambahkan deklarasi fungsi.

Di file main.c, setelah definisi otTaskletsSignalPending dan sebelum fungsi main(), tambahkan fungsi khusus UDP, serta variabel statis untuk merepresentasikan soket UDP:

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;

TINDAKAN: Tambahkan panggilan untuk menginisialisasi LED dan tombol GPIO.

Di main.c, tambahkan panggilan fungsi ini ke fungsi main() setelah panggilan otSetStateChangedCallback. Fungsi ini menginisialisasi pin GPIO dan GPIOTE serta menetapkan handler tombol untuk menangani peristiwa penekanan tombol.

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

TINDAKAN: Tambahkan panggilan inisialisasi UDP.

Di main.c, tambahkan fungsi ini ke fungsi main() setelah panggilan otSysButtonInit yang baru saja Anda tambahkan:

initUdp(instance);

Panggilan ini memastikan soket UDP diinisialisasi saat aplikasi dimulai. Tanpa ini, perangkat tidak dapat mengirim atau menerima pesan UDP.

TINDAKAN: Tambahkan panggilan untuk memproses peristiwa tombol GPIO.

Di main.c, tambahkan panggilan fungsi ini ke fungsi main() setelah panggilan otSysProcessDrivers, dalam loop while. Fungsi ini, yang dideklarasikan di gpio.c, memeriksa apakah tombol ditekan, dan jika ya, memanggil handler (handleButtonInterrupt) yang ditetapkan pada langkah di atas.

otSysButtonProcess(instance);

TINDAKAN: Terapkan Button Interrupt Handler.

Di main.c, tambahkan implementasi fungsi handleButtonInterrupt setelah fungsi handleNetifStateChanged yang Anda tambahkan di langkah sebelumnya:

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

TINDAKAN: Terapkan inisialisasi UDP.

Di main.c, tambahkan implementasi fungsi initUdp setelah fungsi handleButtonInterrupt yang baru saja Anda tambahkan:

/**
 * 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 adalah port yang Anda tentukan sebelumnya (1212). Fungsi otUdpOpen membuka soket dan mendaftarkan fungsi callback (handleUdpReceive) saat pesan UDP diterima. otUdpBind mengikat soket ke antarmuka jaringan Thread dengan meneruskan OT_NETIF_THREAD. Untuk opsi antarmuka jaringan lainnya, lihat enumerasi otNetifIdentifier di Referensi UDP API.

TINDAKAN: Terapkan pesan UDP.

Di main.c, tambahkan implementasi fungsi sendUdp setelah fungsi initUdp yang baru saja Anda tambahkan:

/**
 * 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);
    }
}

Perhatikan makro otEXPECT dan otEXPECT_ACTION. Hal ini memastikan bahwa pesan UDP valid dan dialokasikan dengan benar dalam buffer, dan jika tidak, fungsi akan menangani error dengan baik dengan melompat ke blok exit, tempat buffer dibebaskan.

Lihat Referensi IPv6 dan UDP di openthread.io untuk mengetahui informasi selengkapnya tentang fungsi yang digunakan untuk menginisialisasi UDP.

TINDAKAN: Terapkan penanganan pesan UDP.

Di main.c, tambahkan penerapan fungsi handleUdpReceive setelah fungsi sendUdp yang baru saja Anda tambahkan. Fungsi ini hanya mengalihkan LED4.

/**
 * 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: Mengonfigurasi jaringan Thread

Untuk mempermudah demonstrasi, kami ingin perangkat kami segera memulai Thread dan bergabung ke jaringan saat diaktifkan. Untuk melakukannya, kita akan menggunakan struktur otOperationalDataset. Struktur ini menyimpan semua parameter yang diperlukan untuk mengirimkan kredensial jaringan Thread ke perangkat.

Penggunaan struktur ini akan menggantikan default jaringan yang ada di OpenThread, untuk membuat aplikasi kita lebih aman dan membatasi node Thread di jaringan kita hanya pada node yang menjalankan aplikasi.

Buka kembali file ./openthread/examples/apps/cli/main.c di editor teks pilihan Anda.

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

TINDAKAN: Tambahkan header include.

Dalam bagian includes di bagian atas file main.c, tambahkan file header API yang akan Anda perlukan untuk mengonfigurasi jaringan Thread:

#include <openthread/dataset_ftd.h>

TINDAKAN: Tambahkan deklarasi fungsi untuk menyetel konfigurasi jaringan.

Tambahkan deklarasi ini ke main.c, setelah menyertakan header dan sebelum pernyataan #if. Fungsi ini akan ditentukan setelah fungsi aplikasi utama.

static void setNetworkConfiguration(otInstance *aInstance);

TINDAKAN: Tambahkan panggilan konfigurasi jaringan.

Di main.c, tambahkan panggilan fungsi ini ke fungsi main() setelah panggilan otSetStateChangedCallback. Fungsi ini mengonfigurasi set data jaringan Thread.

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

TINDAKAN: Tambahkan panggilan untuk mengaktifkan antarmuka dan stack jaringan Thread.

Di main.c, tambahkan panggilan fungsi ini ke fungsi main() setelah panggilan otSysButtonInit.

/* Start the Thread network interface (CLI cmd > ifconfig up) */
otIp6SetEnabled(instance, true);

/* Start the Thread stack (CLI cmd > thread start) */
otThreadSetEnabled(instance, true);

TINDAKAN: Terapkan konfigurasi jaringan Thread.

Di main.c, tambahkan implementasi fungsi setNetworkConfiguration setelah fungsi main():

/**
 * 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);
}

Seperti yang dijelaskan dalam fungsi, parameter jaringan Thread yang kita gunakan untuk aplikasi ini adalah:

  • Channel = 15
  • PAN ID = 0x2222
  • ID PAN yang Diperluas = C0DE1AB5C0DE1AB5
  • Kunci Jaringan = 1234C0DE1AB51234C0DE1AB51234C0DE
  • Network Name = OTCodelab

Selain itu, di sinilah kita mengurangi Jitter Pemilihan Router, sehingga perangkat kita mengubah peran lebih cepat untuk tujuan demo. Perhatikan bahwa hal ini hanya dilakukan jika node adalah FTD (Full Thread Device). Selengkapnya akan dibahas di langkah berikutnya.

9. API: Fungsi yang dibatasi

Beberapa API OpenThread mengubah setelan yang hanya boleh diubah untuk tujuan demo atau pengujian. API ini tidak boleh digunakan dalam deployment produksi aplikasi yang menggunakan OpenThread.

Misalnya, fungsi otThreadSetRouterSelectionJitter menyesuaikan waktu (dalam detik) yang diperlukan Perangkat Akhir untuk mempromosikan dirinya menjadi Router. Nilai default untuk nilai ini adalah 120, sesuai dengan Spesifikasi Thread. Untuk mempermudah penggunaan dalam Codelab ini, kita akan mengubahnya menjadi 20, sehingga Anda tidak perlu menunggu terlalu lama agar peran node Thread berubah.

Catatan: Perangkat MTD tidak menjadi router, dan dukungan untuk fungsi seperti otThreadSetRouterSelectionJitter tidak disertakan dalam build MTD. Nanti kita perlu menentukan opsi CMake -DOT_MTD=OFF, jika tidak, kita akan mengalami kegagalan build.

Anda dapat mengonfirmasi hal ini dengan melihat definisi fungsi otThreadSetRouterSelectionJitter, yang terdapat dalam direktif praprosesor OPENTHREAD_FTD:

./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. Update CMake

Sebelum Anda membangun aplikasi, ada beberapa update kecil yang diperlukan untuk tiga file CMake. File ini digunakan oleh sistem build untuk mengompilasi dan menautkan aplikasi Anda.

./third_party/NordicSemiconductor/CMakeLists.txt

Sekarang tambahkan beberapa flag ke NordicSemiconductor CMakeLists.txt, untuk memastikan fungsi GPIO ditentukan dalam aplikasi.

TINDAKAN: Tambahkan tanda ke file CMakeLists.txt .

Buka ./third_party/NordicSemiconductor/CMakeLists.txt di editor teks pilihan Anda, lalu tambahkan baris berikut di bagian COMMON_FLAG.

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

Edit file ./src/CMakeLists.txt untuk menambahkan file sumber gpio.c baru:

TINDAKAN: Tambahkan sumber gpio ke file ./src/CMakeLists.txt.

Buka ./src/CMakeLists.txt di editor teks pilihan Anda, lalu tambahkan file ke bagian NRF_COMM_SOURCES.

...

set(NRF_COMM_SOURCES
  ...
  src/gpio.c
  ...
)

...

./third_party/NordicSemiconductor/CMakeLists.txt

Terakhir, tambahkan file driver nrfx_gpiote.c ke file CMakeLists.txt NordicSemiconductor, sehingga disertakan dengan build library driver Nordic.

TINDAKAN: Tambahkan driver gpio ke file CMakeLists.txt NordicSemiconductor.

Buka ./third_party/NordicSemiconductor/CMakeLists.txt di editor teks pilihan Anda, lalu tambahkan file ke bagian COMMON_SOURCES.

...

set(COMMON_SOURCES
  ...
  nrfx/drivers/src/nrfx_gpiote.c
  ...
)
...

11. Menyiapkan perangkat

Setelah semua pembaruan kode selesai, Anda siap untuk membuat dan mem-flash aplikasi ke ketiga papan pengembangan Nordic nRF52840. Setiap perangkat akan berfungsi sebagai Perangkat Thread Lengkap (FTD).

Bangun OpenThread

Bangun biner FTD OpenThread untuk platform nRF52840.

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

Buka direktori dengan biner CLI FTD OpenThread, dan konversikan ke format hex dengan ARM Embedded Toolchain:

$ cd build/bin
$ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex

Menyala-nyalakan papan

Flash file ot-cli-ftd.hex ke setiap board nRF52840.

Hubungkan kabel USB ke port debug Micro-USB di samping pin daya eksternal pada papan nRF52840, lalu colokkan ke mesin Linux Anda. Setel dengan benar, LED5 akan menyala.

20a3b4b480356447.png

Seperti sebelumnya, catat nomor seri board nRF52840:

c00d519ebec7e5f0.jpeg

Buka lokasi nRFx Command Line Tools, lalu flash file hex FTD CLI OpenThread ke board nRF52840 menggunakan nomor seri board:

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

LED5 akan mati sebentar selama flashing. Output berikut akan dihasilkan jika berhasil:

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.

Ulangi langkah "Flash the boards" ini untuk dua papan lainnya. Setiap papan harus terhubung ke mesin Linux dengan cara yang sama, dan perintah untuk mem-flash juga sama, kecuali untuk nomor seri papan. Pastikan untuk menggunakan nomor seri unik setiap papan di

nrfjprog perintah flashing.

Jika berhasil, LED1, LED2, atau LED3 akan menyala di setiap papan. Anda bahkan mungkin melihat LED yang menyala berubah dari 3 menjadi 2 (atau 2 menjadi 1) segera setelah berkedip (fitur perubahan peran perangkat).

12. Fungsi aplikasi

Ketiga papan nRF52840 kini harus ditenagai dan menjalankan aplikasi OpenThread kita. Seperti yang dijelaskan sebelumnya, aplikasi ini memiliki dua fitur utama.

Indikator peran perangkat

LED yang menyala di setiap papan mencerminkan peran node Thread saat ini:

  • LED1 = Pemimpin
  • LED2 = Router
  • LED3 = Perangkat Akhir

Saat peran berubah, LED yang menyala juga akan berubah. Anda seharusnya sudah melihat perubahan ini di satu atau dua papan dalam waktu 20 detik setelah setiap perangkat diaktifkan.

UDP Multicast

Saat Button1 ditekan di papan, pesan UDP akan dikirim ke alamat multicast lokal mesh, yang mencakup semua node lain di jaringan Thread. Sebagai respons terhadap penerimaan pesan ini, LED4 di semua papan lainnya akan diaktifkan atau dinonaktifkan. LED4 tetap aktif atau nonaktif untuk setiap papan hingga menerima pesan UDP lain.

203dd094acca1f97.png

9bbd96d9b1c63504.png

13. Demo: Mengamati perubahan peran perangkat

Perangkat yang telah Anda flash adalah jenis Perangkat Thread Lengkap (FTD) tertentu yang disebut Perangkat Akhir yang Memenuhi Syarat sebagai Router (REED). Artinya, perangkat ini dapat berfungsi sebagai Router atau Perangkat Akhir, dan dapat mempromosikan dirinya dari Perangkat Akhir menjadi Router.

Thread dapat mendukung hingga 32 Router, tetapi mencoba mempertahankan jumlah Router antara 16 dan 23. Jika REED terhubung sebagai Perangkat Akhir dan jumlah Router kurang dari 16, REED akan otomatis dipromosikan menjadi Router. Perubahan ini akan terjadi pada waktu acak dalam jumlah detik yang Anda tetapkan nilai otThreadSetRouterSelectionJitter-nya di aplikasi (20 detik).

Setiap jaringan Thread juga memiliki Pemimpin, yaitu Router yang bertanggung jawab untuk mengelola kumpulan Router dalam jaringan Thread. Dengan semua perangkat menyala, setelah 20 detik, salah satu perangkat akan menjadi Pemimpin (LED1 menyala) dan dua perangkat lainnya akan menjadi Router (LED2 menyala).

4e1e885861a66570.png

Menghapus Pemimpin

Jika Pemimpin dihapus dari jaringan Thread, Router lain akan mempromosikan dirinya menjadi Pemimpin, untuk memastikan jaringan tetap memiliki Pemimpin.

Nonaktifkan papan Peringkat (yang LED1-nya menyala) menggunakan tombol Daya. Tunggu sekitar 20 detik. Di salah satu dari dua papan yang tersisa, LED2 (Router) akan nonaktif dan LED1 (Leader) akan aktif. Perangkat ini kini menjadi Pemimpin jaringan Thread.

4c57c87adb40e0e3.png

Aktifkan kembali Papan peringkat asli. Perangkat akan otomatis bergabung kembali ke jaringan Thread sebagai Perangkat Akhir (LED3 menyala). Dalam waktu 20 detik (Jitter Pemilihan Router), perangkat akan mempromosikan dirinya menjadi Router (LED2 menyala).

5f40afca2dcc4b5b.png

Mereset papan

Nonaktifkan ketiga papan, lalu aktifkan kembali dan amati LED. Board pertama yang diaktifkan akan dimulai dalam peran Pemimpin (LED1 menyala)—Router pertama dalam jaringan Thread akan otomatis menjadi Pemimpin.

Dua papan lainnya awalnya terhubung ke jaringan sebagai Perangkat Akhir (LED3 menyala), tetapi akan mempromosikan diri mereka menjadi Router (LED2 menyala) dalam waktu 20 detik.

Partisi jaringan

Jika papan Anda tidak menerima daya yang cukup, atau koneksi radio di antara papan tersebut lemah, jaringan Thread dapat terbagi menjadi beberapa partisi dan Anda mungkin memiliki lebih dari satu perangkat yang ditampilkan sebagai Pemimpin.

Thread dapat memulihkan dirinya sendiri, sehingga partisi pada akhirnya akan digabungkan kembali menjadi satu partisi dengan satu Pemimpin.

14. Demo: Mengirim multicast UDP

Jika melanjutkan dari latihan sebelumnya, LED4 tidak boleh menyala di perangkat mana pun.

Pilih papan mana saja, lalu tekan Tombol1. LED4 di semua papan lain dalam jaringan Thread yang menjalankan aplikasi harus mengubah statusnya. Jika melanjutkan dari latihan sebelumnya, perangkat kini harus aktif.

f186a2618fdbe3fd.png

Tekan Button1 untuk papan yang sama lagi. LED4 di semua papan lainnya akan berganti lagi.

Tekan Button1 di papan yang berbeda dan amati bagaimana LED4 berganti-ganti di papan lainnya. Tekan Button1 di salah satu papan yang saat ini menyalakan LED4. LED4 tetap menyala untuk papan tersebut, tetapi berganti-ganti pada papan lainnya.

f5865ccb8ab7aa34.png

Partisi jaringan

Jika papan Anda telah dipartisi dan terdapat lebih dari satu Pemimpin di antaranya, hasil pesan multicast akan berbeda di antara papan. Jika Anda menekan Button1 di papan yang telah dipartisi (dan dengan demikian menjadi satu-satunya anggota jaringan Thread yang dipartisi), LED4 di papan lain tidak akan menyala sebagai respons. Jika hal ini terjadi, reset papan—idealnya papan akan membentuk kembali jaringan Thread tunggal dan pesan UDP akan berfungsi dengan benar.

15. Selamat!

Anda telah membuat aplikasi yang menggunakan OpenThread API.

Sekarang Anda tahu:

  • Cara memprogram tombol dan LED di papan pengembangan Nordic nRF52840
  • Cara menggunakan API OpenThread umum dan class otInstance
  • Cara memantau dan bereaksi terhadap perubahan status OpenThread
  • Cara mengirim pesan UDP ke semua perangkat dalam jaringan Thread
  • Cara mengubah Makefile

Langkah berikutnya

Dari Codelab ini, coba latihan berikut:

  • Ubah modul GPIO untuk menggunakan pin GPIO, bukan LED bawaan, dan hubungkan LED RGB eksternal yang berubah warna berdasarkan peran Router
  • Menambahkan dukungan GPIO untuk platform contoh yang berbeda
  • Daripada menggunakan multicast untuk mengirim ping ke semua perangkat dari penekanan tombol, gunakan Router/Leader API untuk menemukan dan mengirim ping ke perangkat tertentu
  • Hubungkan jaringan mesh Anda ke internet menggunakan Router Pembatas OpenThread dan kirimkan secara multicast dari luar jaringan Thread untuk menyalakan LED

Bacaan lebih lanjut

Lihat openthread.io dan GitHub untuk berbagai referensi OpenThread, termasuk:

Referensi: