1. Pengantar
OpenThread yang dirilis oleh Nest merupakan implementasi open source dari protokol jaringan Thread®. Nest telah merilis OpenThread untuk membuat teknologi yang digunakan dalam produk Nest tersedia secara luas bagi developer guna mempercepat pengembangan produk untuk rumah yang terhubung.
Spesifikasi Thread mendefinisikan protokol komunikasi perangkat-ke-perangkat berbasis IPv6 yang andal, aman, dan rendah daya untuk aplikasi rumah. OpenThread menerapkan 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, dan mengirim pesan UDP, serta mengaitkan tindakan ini ke tombol dan LED pada hardware sungguhan.
Yang akan Anda pelajari
- Cara memprogram tombol dan LED pada board dev 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
Perangkat keras:
- 3 papan dev Nordic Semiconductor nRF52840
- 3 kabel USB ke USB Mikro untuk menghubungkan board
- Komputer Linux dengan minimal 3 port USB
Software:
- Toolchain GNU
- Alat command line nRF5x Nordic
- 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
Menyelesaikan Codelab Hardware
Sebelum memulai Codelab ini, Anda harus menyelesaikan Codelab Membangun Jaringan Thread dengan Boards dan OpenThread nRF52840, yang:
- Detail semua software yang Anda perlukan untuk mem-build dan melakukan flash
- Mengajari Anda cara membangun OpenThread dan mem-flash-nya pada board nRF52840 Nordic
- Mendemonstrasikan dasar-dasar jaringan Thread
Tidak ada penyiapan lingkungan yang diperlukan untuk membuat OpenThread dan flash board yang dijelaskan dalam Codelab ini—hanya petunjuk dasar untuk mem-flash board. Anda dianggap telah menyelesaikan Codelab Membuat Jaringan Thread.
Mesin Linux
Codelab ini dirancang untuk menggunakan mesin Linux berbasis i386 atau x86 untuk mem-flash semua board pengembangan Thread. Semua langkah diuji di Ubuntu 14.04.5 LTS (Trusty Tahr).
Papan nRF52840 Nordic Semiconductor
Codelab ini menggunakan tiga papan nRF52840 PDK.
Instal Software
Untuk membangun 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 sudah memiliki semua yang perlu diinstal. Jika tidak, selesaikan Codelab tersebut sebelum melanjutkan untuk memastikan Anda dapat membangun dan mem-flash OpenThread ke dev board nRF52840.
3. Melakukan cloning repositori
OpenThread dilengkapi dengan contoh kode aplikasi yang dapat Anda gunakan sebagai titik awal untuk Codelab ini.
Buat clone untuk repositori contoh OpenThread Nordic nRF528xx dan buat OpenThread:
$ git clone --recursive https://github.com/openthread/ot-nrf528xx $ cd ot-nrf528xx $ ./script/bootstrap
4. Dasar-Dasar OpenThread API
API publik OpenThread terletak di ./openthread/include/openthread
dalam repositori OpenThread. API ini memberikan akses ke berbagai fitur dan fungsi OpenThread pada tingkat Thread dan platform untuk digunakan di aplikasi Anda:
- Informasi dan kontrol instance OpenThread
- Layanan aplikasi seperti IPv6, UDP, dan CoAP
- Pengelolaan kredensial jaringan, beserta peran Commissioner dan Joiner
- Pengelolaan Router Batas
- Fitur yang ditingkatkan seperti Pengawasan Anak dan Deteksi Jam
Informasi referensi tentang semua API OpenThread tersedia di openthread.io/reference.
Menggunakan API
Untuk menggunakan API, sertakan file header-nya di salah satu file aplikasi Anda. Lalu, 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 menggunakan OpenThread API. Setelah diinisialisasi, struktur ini akan mewakili instance statis library OpenThread dan memungkinkan pengguna melakukan panggilan OpenThread API.
Misalnya, instance OpenThread diinisialisasi dalam fungsi main()
pada 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, deklarasikan terlebih dahulu di header ./openthread/examples/platforms/openthread-system.h
, menggunakan namespace otSys
untuk semua fungsi. Kemudian terapkan dalam file sumber khusus platform. Diabstraksikan seperti ini, Anda dapat menggunakan header fungsi yang sama untuk contoh platform lainnya.
Misalnya, fungsi GPIO yang akan kita gunakan untuk mengaitkan tombol nRF52840 dan LED 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. Itu semua tergantung pada kebutuhan aplikasi Anda. Jika tidak memerlukannya dalam implementasi fungsi, Anda dapat menggunakan makro OT_UNUSED_VARIABLE
dari OpenThread API untuk menyembunyikan error build terkait variabel yang tidak digunakan untuk beberapa toolchain. Kita akan melihat contohnya nanti.
5. Mengimplementasikan abstraksi platform GPIO
Pada langkah sebelumnya, kita membahas deklarasi fungsi khusus platform di ./openthread/examples/platforms/openthread-system.h
yang dapat digunakan untuk GPIO. Untuk mengakses tombol dan LED pada board dev nRF52840, Anda perlu menerapkan fungsi tersebut untuk platform nRF52840. Dalam kode ini, Anda akan menambahkan fungsi yang:
- Melakukan inisialisasi pin dan mode GPIO
- Mengontrol voltase pada pin
- Mengaktifkan interupsi GPIO dan mendaftarkan 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: Tambahkan definisi.
Ini mendefinisikan berfungsi sebagai abstraksi antara nilai dan variabel khusus nRF52840 yang digunakan pada 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 Pusat Info Nordic Semiconductor.
TINDAKAN: Tambahkan header.
Selanjutnya, tambahkan {i>header<i} yang Anda perlukan untuk fungsionalitas 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: Menambahkan fungsi callback dan interupsi untuk Tombol 1.
Berikutnya, tambahkan kode ini. Fungsi in_pin1_handler
adalah callback yang didaftarkan saat fungsi penekanan tombol diinisialisasi (kemudian dalam file ini).
Perhatikan cara callback ini menggunakan makro OT_UNUSED_VARIABLE
, karena variabel yang diteruskan ke in_pin1_handler
tidak benar-benar 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 mengaktifkan/menonaktifkan mode LED.
Fungsi ini akan digunakan untuk mengaktifkan/menonaktifkan 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 melakukan inisialisasi dan memproses penekanan tombol.
Fungsi pertama melakukan inisialisasi board untuk penekanan tombol, dan yang kedua mengirimkan pesan UDP multicast ketika 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 file gpio.c
.
6. API: Merespons perubahan peran perangkat
Di aplikasi, kita ingin LED yang berbeda menyala tergantung pada peran perangkat. Mari 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 saat peran perangkat telah berubah dan cara menyalakan 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: Tambahkan header.
Di bagian penyertaan pada file main.c
, tambahkan file header API yang diperlukan untuk fitur perubahan peran.
#include <openthread/instance.h> #include <openthread/thread.h> #include <openthread/thread_ftd.h>
TINDAKAN: Tambahkan deklarasi fungsi pengendali untuk perubahan status instance OpenThread.
Tambahkan deklarasi ini ke main.c
, setelah header menyertakan 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()
, implementasikan fungsi handleNetifStateChanged
. Fungsi ini memeriksa flag OT_CHANGED_THREAD_ROLE
dari instance OpenThread dan jika telah berubah, LED akan aktif/nonaktif 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 menyalakan LED
Di aplikasi, kita juga ingin mengirim pesan UDP ke semua perangkat lain di jaringan ketika Button1 ditekan pada satu papan. Untuk mengonfirmasi penerimaan pesan, kami akan mengaktifkan/menonaktifkan LED4 di board lain sebagai respons.
Untuk mengaktifkan fungsi ini, aplikasi harus:
- Melakukan inisialisasi koneksi UDP saat memulai
- Dapat mengirim pesan UDP ke alamat multicast mesh-local
- Menangani pesan UDP yang masuk
- Aktifkan/nonaktifkan LED4 sebagai respons terhadap pesan UDP yang masuk
Buka file ./openthread/examples/apps/cli/main.c
di editor teks pilihan Anda.
./openthread/examples/apps/cli/main.c
TINDAKAN: Tambahkan header.
Di bagian penyertaan di bagian atas file main.c
, tambahkan file header API yang diperlukan 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:
Di file main.c
, setelah bagian penyertaan dan sebelum pernyataan #if
, tambahkan konstanta khusus UDP dan tentukan:
#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 mesh-local. Setiap pesan yang dikirim ke alamat ini akan dikirim ke semua Perangkat Rangkaian Pesan Lengkap di jaringan. Lihat Multicast pada openthread.io untuk 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 mewakili 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 tombol dan LED GPIO.
Di main.c
, tambahkan panggilan fungsi ini ke fungsi main()
setelah panggilan otSetStateChangedCallback
. Fungsi ini menginisialisasi pin GPIO dan GPIOTE serta menetapkan pengendali tombol untuk menangani peristiwa push 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. Tanpanya, 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
, di loop while
. Fungsi ini, yang dideklarasikan dalam gpio.c
, memeriksa apakah tombol ditekan, dan jika demikian, akan memanggil pengendali (handleButtonInterrupt
) yang ditetapkan pada langkah di atas.
otSysButtonProcess(instance);
TINDAKAN: Terapkan Pengendali Interupsi Tombol.
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 API UDP.
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. Jika tidak, fungsi ini akan menangani error dengan baik dengan melompat ke blok exit
, tempat fungsi tersebut mengosongkan buffer.
Lihat Referensi IPv6 dan UDP di openthread.io untuk mengetahui informasi selengkapnya tentang fungsi yang digunakan untuk melakukan inisialisasi UDP.
TINDAKAN: Terapkan penanganan pesan UDP.
Di main.c
, tambahkan implementasi fungsi handleUdpReceive
setelah fungsi sendUdp
yang baru saja Anda tambahkan. Fungsi ini hanya mengaktifkan 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 memudahkan demonstrasi, kami ingin perangkat kami segera memulai Thread dan bergabung ke dalam jaringan saat dinyalakan. 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 mengganti default jaringan yang terdapat pada OpenThread, sehingga aplikasi kita menjadi lebih aman dan membatasi node Thread di jaringan kita hanya untuk node yang menjalankan aplikasi tersebut.
Sekali lagi, buka file ./openthread/examples/apps/cli/main.c
di editor teks pilihan Anda.
./openthread/examples/apps/cli/main.c
TINDAKAN: Tambahkan header.
Dalam bagian penyertaan di bagian atas file main.c
, tambahkan file header API yang diperlukan untuk mengonfigurasi jaringan Thread:
#include <openthread/dataset_ftd.h>
TINDAKAN: Tambahkan deklarasi fungsi untuk menyetel konfigurasi jaringan.
Tambahkan deklarasi ini ke main.c
, setelah header menyertakan 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 stack dan antarmuka 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); }
Sebagaimana dijelaskan dalam fungsi, parameter jaringan Thread yang kami gunakan untuk aplikasi ini adalah:
- Saluran = 15
- ID PAN = 0x2222
- ID PAN yang Diperluas = C0DE1AB5C0DE1AB5
- Kunci Jaringan = 1234C0DE1AB51234C0DE1AB51234C0DE
- Nama Jaringan = OTCodelab
Selain itu, di sinilah kita mengurangi Jitter Pilihan Router, sehingga perangkat mengubah peran lebih cepat untuk tujuan demo. Perhatikan bahwa ini hanya dilakukan jika node merupakan FTD (Full Thread Device). Selengkapnya tentang hal itu di langkah berikutnya.
9. API: Fungsi yang dibatasi
Beberapa API OpenThread mengubah setelan yang hanya boleh dimodifikasi untuk tujuan demo atau pengujian. API ini tidak boleh digunakan dalam deployment produksi aplikasi menggunakan OpenThread.
Misalnya, fungsi otThreadSetRouterSelectionJitter
menyesuaikan waktu (dalam detik) yang diperlukan Perangkat Akhir untuk mempromosikan dirinya ke Router. Default untuk nilai ini adalah 120, sesuai Spesifikasi Thread. Untuk kemudahan penggunaan dalam Codelab ini, kita akan mengubahnya menjadi 20, sehingga Anda tidak perlu menunggu lama sampai node Thread mengubah peran.
Catatan: Perangkat MTD tidak menjadi router, dan dukungan untuk fungsi seperti otThreadSetRouterSelectionJitter
tidak disertakan dalam build MTD. Nantinya, 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 perintah 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 membangun aplikasi, ada beberapa update minor yang diperlukan untuk tiga file CMake. Ini digunakan oleh sistem build untuk mengompilasi dan menautkan aplikasi Anda.
./third_party/NordicSemiconductor/CMakeLists.txt
Sekarang tambahkan beberapa flag ke CMakeLists.txt
NordicSemiconductor 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, dan 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
yang 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 menyelesaikan semua pembaruan kode, Anda siap membangun dan melakukan flash aplikasi ke ketiga board dev Nordic nRF52840. Setiap perangkat akan berfungsi sebagai Perangkat Thread Penuh (FTD).
Membuat 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 OpenThread FTD CLI, lalu konversikan ke format hex dengan ARM Embedded Toolchain:
$ cd build/bin $ arm-none-eabi-objcopy -O ihex ot-cli-ftd ot-cli-ftd.hex
Flash papan
Lakukan flash file ot-cli-ftd.hex
ke setiap board nRF52840.
Pasang kabel USB ke port debug Micro-USB di samping pin daya eksternal pada board nRF52840, lalu colokkan ke mesin Linux. Setel dengan benar, LED5 sudah aktif.
Seperti sebelumnya, perhatikan nomor seri board nRF52840:
Arahkan ke lokasi Alat Baris Perintah nRFx, dan flash file hex OpenThread CLI FTD 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 saat berkedip. Output berikut akan dibuat setelah 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 "Flash board" ini melangkah ke dua papan lainnya. Setiap board harus terhubung ke komputer Linux dengan cara yang sama, dan perintah untuk melakukan flash sama, kecuali untuk nomor seri board. Pastikan untuk menggunakan nomor seri unik setiap board di
nrfjprog
perintah flash.
Jika berhasil, LED1, LED2, atau LED3 akan menyala di setiap papan. Anda bahkan mungkin melihat saklar LED yang menyala dari 3 ke 2 (atau 2 ke 1) segera setelah berkedip (fitur perubahan peran perangkat).
12. Fungsi aplikasi
Ketiga board nRF52840 sekarang seharusnya diaktifkan dan menjalankan aplikasi OpenThread. Seperti yang dijelaskan sebelumnya, aplikasi ini memiliki dua fitur utama.
Indikator peran perangkat
LED yang menyala di setiap board mencerminkan peran node Thread saat ini:
- LED1 = Pemimpin
- LED2 = Router
- LED3 = Perangkat Akhir
Saat peran berubah, begitu pula LED yang menyala. Anda seharusnya sudah melihat perubahan ini di satu atau dua board dalam waktu 20 detik setelah setiap perangkat dinyalakan.
Multicast UDP
Jika Button1 ditekan pada board, pesan UDP akan dikirim ke alamat multicast mesh-local, yang mencakup semua node lainnya dalam jaringan Thread. Sebagai respons atas menerima pesan ini, LED4 di semua board lainnya akan diaktifkan atau dinonaktifkan. LED4 tetap menyala atau mati untuk setiap board hingga menerima pesan UDP lainnya.
13. Demo: Mengamati perubahan peran perangkat
Perangkat yang telah di-flash adalah jenis Perangkat Thread Penuh (FTD) spesifik yang disebut Perangkat Akhir yang Memenuhi Syarat Router (REED). Artinya, perangkat dapat berfungsi sebagai Router atau Perangkat Akhir, dan dapat mempromosikan diri dari Perangkat Akhir ke Router.
Thread dapat mendukung hingga 32 Router, tetapi mencoba mempertahankan jumlah Router antara 16 dan 23. Jika REED terpasang sebagai Perangkat Akhir dan jumlah Router kurang dari 16, REED akan otomatis mempromosikan dirinya ke Router. Perubahan ini akan terjadi pada waktu acak dalam jumlah detik saat Anda menetapkan nilai otThreadSetRouterSelectionJitter
di aplikasi (20 detik).
Setiap jaringan Thread juga memiliki Leader, yaitu Router yang bertanggung jawab untuk mengelola kumpulan Router dalam jaringan Thread. Dengan semua perangkat menyala, setelah 20 detik salah satu dari mereka harus Pemimpin (LED1 menyala) dan dua lainnya harus Router (LED2 menyala).
Hapus Pemimpin
Jika Pemimpin dihapus dari jaringan Thread, Router lain akan mempromosikan dirinya menjadi Pemimpin, untuk memastikan jaringan masih memiliki Pemimpin.
Nonaktifkan Papan pemimpin (yang menyala LED1) menggunakan tombol Daya. Tunggu sekitar 20 detik. Pada salah satu dari dua papan yang tersisa, LED2 (Router) akan mati dan LED1 (Ketua) akan menyala. Perangkat ini sekarang menjadi Pemimpin jaringan Thread.
Aktifkan kembali Papan pemimpin asli. Panel ini akan otomatis bergabung kembali ke jaringan Thread sebagai Perangkat Akhir (LED3 menyala). Dalam waktu 20 detik (Router Selection Jitter) ia mempromosikan dirinya ke Router (LED2 menyala).
Reset board
Matikan ketiga papan, lalu nyalakan kembali dan amati LEDnya. Board pertama yang dinyalakan harus dimulai dalam peran Pemimpin (LED1 menyala). Router pertama di jaringan Thread akan otomatis menjadi Pemimpin.
Dua papan lainnya awalnya terhubung ke jaringan sebagai Perangkat Akhir (LED3 menyala) tetapi harus mempromosikan diri ke Router (LED2 menyala) dalam 20 detik.
Partisi jaringan
Jika board Anda tidak menerima daya yang cukup, atau koneksi radio di antara keduanya lemah, jaringan Thread dapat dibagi menjadi beberapa partisi dan Anda mungkin memiliki lebih dari satu perangkat yang ditampilkan sebagai Pemimpin.
Thread adalah pemulihan mandiri, jadi partisi pada akhirnya harus bergabung kembali menjadi satu partisi dengan satu Pemimpin.
14. Demo: Mengirim multicast UDP
Jika melanjutkan dari latihan sebelumnya, LED4 tidak boleh menyala di perangkat apa pun.
Pilih papan dan tekan Button1. LED4 pada semua board lain di jaringan Thread yang menjalankan aplikasi harus beralih statusnya. Jika melanjutkan dari latihan sebelumnya, seharusnya sekarang sudah aktif.
Tekan lagi Tombol1 untuk board yang sama. LED4 di semua papan lain harus beralih lagi.
Tekan Button1 pada papan yang berbeda dan amati bagaimana LED4 beralih di papan lain. Tekan Button1 pada salah satu board tempat LED4 saat ini menyala. LED4 tetap aktif untuk papan itu tetapi beralih di papan lainnya.
Partisi jaringan
Jika board Anda telah dipartisi dan ada lebih dari satu Pemimpin di antara mereka, hasil pesan multicast antar-board akan berbeda. Jika Anda menekan Button1 pada board yang telah dipartisi (dan dengan demikian adalah satu-satunya anggota jaringan Thread yang dipartisi), LED4 pada board lain tidak akan menyala sebagai respons. Jika hal ini terjadi, reset board—idealnya mereka akan mereformasi satu jaringan Thread dan pesan UDP seharusnya berfungsi dengan benar.
15. Selamat!
Anda telah membuat aplikasi yang menggunakan OpenThread API.
Anda sekarang mengetahui:
- Cara memprogram tombol dan LED pada board dev 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
Berdasarkan Codelab ini, coba latihan berikut:
- Memodifikasi modul GPIO untuk menggunakan pin GPIO, bukan LED onboard, dan menghubungkan LED RGB eksternal yang berubah warna berdasarkan peran Router
- Menambahkan dukungan GPIO untuk platform contoh yang berbeda
- Daripada menggunakan multicast untuk melakukan ping ke semua perangkat dengan menekan tombol, gunakan Router/Leader API untuk menemukan dan melakukan ping ke satu perangkat
- Hubungkan jaringan mesh Anda ke internet menggunakan Router Pembatas OpenThread dan lakukan transmisi multicast dari luar jaringan Thread untuk menyalakan LED
Bacaan lebih lanjut
Lihat openthread.io dan GitHub untuk berbagai resource OpenThread, termasuk:
- Platform yang Didukung — temukan semua platform yang mendukung OpenThread
- Membuat OpenThread — detail lebih lanjut tentang cara membuat dan mengonfigurasi OpenThread
- Thread Primer — referensi yang bagus tentang konsep Thread
Referensi: