If you are working on a battery powered solution and want to minimize the power consumption of the WisBlock, please have a look at Low Power Example. There is a separate example that concentrates on low power consumption.
This example shows how to setup the WisBlock as a LoRaWan® sensor node. There are two examples available, one for nodes with ABP network join function and one for nodes using the OTAA network join function. If you want to use LoRaWan® communication you need to define a region. The defined region tells the library which frequency and which channels should be used. Valid regions are:
- REGION_AS923 -> Asia 923 MHz
- REGION_AU915 -> Australia 915 MHz
- REGION_EU868 -> Europe 868 MHz
- REGION_IN865 -> India 865 MHz
- REGION_KR920 -> Korea 920 MHz
- REGION_US915 -> US 915 MHz
The region can be selected from the tools menu of ArduinoIDE
In addition you need keys to register the device.
Keys required for OTAA
- Device EUI if you want to use ABP registration of the device
- Application EUI
- Application Key, the AES encryption/decryption cipher application key
Keys required for ABP
- Device address
- Network Session Key
- App Session Key
The devices must be registered on your LoRaWan® server before they can send and receive data. The above mentioned EUIs and keys can be obtained from your LoRaWan® server. If you use a RAK gateway with an integrated LoRaWan® server or want to use TheThingsNetwork server you can find a tutorial here
To build the LoRa® system. With just one WisBlock Core RAK4631 plugged into the WisBlock Base RAK5005-O board the system is ready to be used.
- WisBlock Base RAK5005-O
- WisBlock Core RAK4631
The RAK4631 is the WisBlock Core which can be connected to CPU SLOT of WisBlock via pin to pin groove like below. Besides, it provides SWD port to download via Jlink. Two antenna (BLE and LoRa®). Screws of four corners help stabilize connection with WisBlock.
After install Arduino IDE and BSP according to the Quick Start Guide, you can install LoRaWAN® library now. Step as below:
- Start the Arduino IDE
- Open Library Manager, search for sx126x-Arduino, and install
The library is an excellent library, supports below:
- LoRaWAN® protocol V1.0.2
- Class A and Class C
- Region EU868/US915/AU915/KR920/AS923/IN865
- OTAA and ABP
- Dutycycle
- ADR/Datarate/Tx power configuration
- Confirm/Unconfirm
- DevEUI/AppEUI/AppKey/DevAddr/NwsKey/AppsKey configuration
- Send data port
RAKwireless provides a lot of example based on this library. Here choose a simple example to show how to develop a LoRaWAN® application.
Download the example from https://github.com/RAKWireless/WisBlock/tree/master/examples/communications/LoRa/LoRaWAN/LoRaWAN_OTAA/ . The example will communicate with server in EU868, join type in OTAA. And send "hello" to server period.
To test the example, we build node like below in ChirpStack (open-source LoRaWAN® Network Server stack, TTN is also supported and tested). And we can obtain the DeviceEUI, AppEUI, AppKey, copy them. The detailed parameters is below:
- Region: EU868
- Join Type: OTAA
- Class: A
Then open the example in Arduino, fill the DeviceEUI, AppEUI, AppKey in the code like below:
//OTAA keys !!!! ALL KEYS ARE MSB !!!
uint8_t nodeDeviceEUI[8] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x33, 0x33};
uint8_t nodeAppEUI[8] = {0xB8, 0x27, 0xEB, 0xFF, 0xFE, 0x39, 0x00, 0x00};
uint8_t nodeAppKey[16] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x33, 0x33, 0x33, 0x33, 0x33};
There is a USB port on WisBlock. It is a debug port for RAK4631 with three function:
- Power supply
- Download port in Arduino
- Communication with RAK4631, incluing log print and command from PC
Connect WisBlock to PC via USB. Arduino will recognize the board. The red led is on means power up. Go into tools and choose like below.
Then click Upload (arrow button), it will compile and download to RAK4631 automatically. The blue led is on means download ok.
Go into Tools, choose the port (Adafruit Feather nRF52840 Express), open Serial Monitor. Log will show as below. It will join automatically and send 'hello!' to server per 20s :
At the same time, server will show the data from RAK4631.
It is a very easy way for users to finish their first LoRaWAN® trip. But most users want to apply LoRaWAN® to their own application. RAKwireless also provide a efficient way for them.
Here will analyze the example above in details, other examples are based on this.
#include <Arduino.h>
#include <LoRaWan-RAK4630.h>
#include <SPI.h>
Above files are necessary for all LoRaWAN® example.
// RAK4630 supply two LED
#ifndef LED_BUILTIN
#define LED_BUILTIN 35
#endif
#ifndef LED_BUILTIN2
#define LED_BUILTIN2 36
#endif
WisBlock Provides three LEDs, Red for power sign. The rest are for users.
bool doOTAA = true;
#define SCHED_MAX_EVENT_DATA_SIZE APP_TIMER_SCHED_EVENT_DATA_SIZE /**< Maximum size of scheduler events. */
#define SCHED_QUEUE_SIZE 60 /**< Maximum number of events in the scheduler queue. */
#define LORAWAN_DATERATE DR_0 /*LoRaMac datarates definition, from DR_0 to DR_5*/
#define LORAWAN_TX_POWER TX_POWER_5 /*LoRaMac tx power definition, from TX_POWER_0 to TX_POWER_15*/
#define JOINREQ_NBTRIALS 3 /**< Number of trials for the join request. */
DeviceClass_t gCurrentClass = CLASS_A; /* class definition*/
lmh_confirm gCurrentConfirm = LMH_CONFIRMED_MSG; /* confirm/unconfirm packet definition*/
uint8_t gAppPort = LORAWAN_APP_PORT; /* data port*/
static lmh_param_t lora_param_init = {LORAWAN_ADR_ON , LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};
Above is LoRaWAN® software configuration. lora_param_init is struct of user configuration, coordinate with lmh_init. User can change any configuration here. More details is in Section 3.7.
// Foward declaration
static void lorawan_has_joined_handler(void);
static void lorawan_rx_handler(lmh_app_data_t *app_data);
static void lorawan_confirm_class_handler(DeviceClass_t Class);
static void send_lora_frame(void);
/**@brief Structure containing LoRaWan callback functions, needed for lmh_init()
*/
static lmh_callback_t lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler};
lora_callbacks is struct of user callback in LoRaWAN®. There are mainly three callbacks:
- lorawan_rx_handler: when receive data, stack will call this
- lorawan_has_joined_handler: when node join to server successfully, stack will call this
- lorawan_confirm_class_handler: when node changes Class type successfully, stack will call this
//OTAA keys !!!! ALL KEYS ARE MSB !!!
uint8_t nodeDeviceEUI[8] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x22, 0x22};
uint8_t nodeAppEUI[8] = {0xB8, 0x27, 0xEB, 0xFF, 0xFE, 0x39, 0x00, 0x00};
uint8_t nodeAppKey[16] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,0x88, 0x88, 0x88, 0x88, 0x22, 0x22, 0x22, 0x22};
Fill in the keys of node on server in MSB. Only AppKey of TTN is LSB.
void setup()
{
...
lora_rak4630_init();
...
err_code = timers_init();
...
}
hardware initialization, including RTC, sx1262. And create a timer with 20s period which will send test data to server after join successfully.
void setup()
{
...
err_code = lmh_init(&lora_callbacks, lora_param_init,doOTAA);
...
}
This part is LoRaWAN® initialization with user's configuration,join type and callbacks.
void setup()
{
...
lmh_join();
...
}
Final step in setup() is join to server.
void loop()
{
// Handle Radio events
Radio.IrqProcess();
}
The main loop is a handle for LoRaWAN® interrupt. Do not add any time consuming task here or any other code here. To keep LoRaWAN® stack have a swift response. We advise to start timer to finish user's work.
void lorawan_has_joined_handler(void)
{
Serial.println("OTAA Mode, Network Joined!");
lmh_error_status ret = lmh_class_request(gCurrentClass);
if(ret == LMH_SUCCESS)
{
delay(1000);
TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
TimerStart(&appTimer);
}
}
If node joins successfully, the stack will call this. User can add what they need here. For example, call lmh_class_request to change Class type. In this example, it starts the timer created before to send data.
void lorawan_rx_handler(lmh_app_data_t *app_data)
{
Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d, data:%s\n",
app_data->port, app_data->buffsize, app_data->rssi, app_data->snr, app_data->buffer);
}
If node receive data from server, the stack will call this. User can add what they need here. For example, print receive data, RSSI and SNR.
void lorawan_confirm_class_handler(DeviceClass_t Class)
{
Serial.printf("switch to class %c done\n", "ABC"[Class]);
// Informs the server that switch has occurred ASAP
m_lora_app_data.buffsize = 0;
m_lora_app_data.port = gAppPort;
lmh_send(&m_lora_app_data, gCurrentConfirm);
}
This callback coordinates with lmh_class_request in Class C. When node needs change to Class C, this will be called after Class C exchange. And send a uplink to server. Notify server node has changed OK.
void send_lora_frame(void)
{
if (lmh_join_status_get() != LMH_SET)
{
//Not joined, try again later
return;
}
uint32_t i = 0;
memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);
m_lora_app_data.port = gAppPort;
m_lora_app_data.buffer[i++] = 'H';
m_lora_app_data.buffer[i++] = 'e';
m_lora_app_data.buffer[i++] = 'l';
m_lora_app_data.buffer[i++] = 'l';
m_lora_app_data.buffer[i++] = 'o';
m_lora_app_data.buffer[i++] = '!';
m_lora_app_data.buffsize = i;
lmh_error_status error = lmh_send(&m_lora_app_data, gCurrentConfirm);
if (error == LMH_SUCCESS)
{
count++;
Serial.printf("lmh_send ok count %d\n", count);
}
else
{
count_fail++;
Serial.printf("lmh_send fail count %d\n", count_fail);
}
}
This function will fill in the packet and send data to server. m_lora_app_data is from LoRaWAN® library and size is no more than 242 bytes. For example, User can call this in a timer handle.
#include <Arduino.h>
#include <LoRaWan-RAK4630.h>
#include <SPI.h>
// RAK4630 supply two LED
#ifndef LED_BUILTIN
#define LED_BUILTIN 35
#endif
#ifndef LED_BUILTIN2
#define LED_BUILTIN2 36
#endif
bool doOTAA = true;
#define SCHED_MAX_EVENT_DATA_SIZE APP_TIMER_SCHED_EVENT_DATA_SIZE /**< Maximum size of scheduler events. */
#define SCHED_QUEUE_SIZE 60 /**< Maximum number of events in the scheduler queue. */
#define LORAWAN_DATERATE DR_0 /*LoRaMac datarates definition, from DR_0 to DR_5*/
#define LORAWAN_TX_POWER TX_POWER_5 /*LoRaMac tx power definition, from TX_POWER_0 to TX_POWER_15*/
#define JOINREQ_NBTRIALS 3 /**< Number of trials for the join request. */
DeviceClass_t gCurrentClass = CLASS_A; /* class definition*/
lmh_confirm gCurrentConfirm = LMH_CONFIRMED_MSG; /* confirm/unconfirm packet definition*/
uint8_t gAppPort = LORAWAN_APP_PORT; /* data port*/
/**@brief Structure containing LoRaWan parameters, needed for lmh_init()
*/
static lmh_param_t lora_param_init = {LORAWAN_ADR_ON , LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};
// Foward declaration
static void lorawan_has_joined_handler(void);
static void lorawan_rx_handler(lmh_app_data_t *app_data);
static void lorawan_confirm_class_handler(DeviceClass_t Class);
static void send_lora_frame(void);
/**@brief Structure containing LoRaWan callback functions, needed for lmh_init()
*/
static lmh_callback_t lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler};
//OTAA keys !!!! ALL KEYS ARE MSB !!!
uint8_t nodeDeviceEUI[8] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x33, 0x33};
uint8_t nodeAppEUI[8] = {0xB8, 0x27, 0xEB, 0xFF, 0xFE, 0x39, 0x00, 0x00};
uint8_t nodeAppKey[16] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88,0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x33, 0x33};
// Private defination
#define LORAWAN_APP_DATA_BUFF_SIZE 64 /**< buffer size of the data to be transmitted. */
#define LORAWAN_APP_INTERVAL 20000 /**< Defines for user timer, the application data transmission interval. 20s, value in [ms]. */
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE]; //< Lora user application data buffer.
static lmh_app_data_t m_lora_app_data = {m_lora_app_data_buffer, 0, 0, 0, 0}; //< Lora user application data structure.
TimerEvent_t appTimer;
static uint32_t timers_init(void);
static uint32_t count = 0;
static uint32_t count_fail = 0;
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// Initialize LoRa chip.
lora_rak4630_init();
// Initialize Serial for debug output
Serial.begin(115200);
while(!Serial){delay(10);}
Serial.println("=====================================");
Serial.println("Welcome to RAK4630 LoRaWan!!!");
Serial.println("Type: OTAA");
#if defined(REGION_AS923)
Serial.println("Region: AS923");
#elif defined(REGION_AU915)
Serial.println("Region: AU915");
#elif defined(REGION_CN470)
Serial.println("Region: CN470");
#elif defined(REGION_CN779)
Serial.println("Region: CN779");
#elif defined(REGION_EU433)
Serial.println("Region: EU433");
#elif defined(REGION_IN865)
Serial.println("Region: IN865");
#elif defined(REGION_EU868)
Serial.println("Region: EU868");
#elif defined(REGION_KR920)
Serial.println("Region: KR920");
#elif defined(REGION_US915)
Serial.println("Region: US915");
#elif defined(REGION_US915_HYBRID)
Serial.println("Region: US915_HYBRID");
#else
Serial.println("Please define a region in the compiler options.");
#endif
Serial.println("=====================================");
//creat a user timer to send data to server period
uint32_t err_code;
err_code = timers_init();
if (err_code != 0)
{
Serial.printf("timers_init failed - %d\n", err_code);
}
// Setup the EUIs and Keys
lmh_setDevEui(nodeDeviceEUI);
lmh_setAppEui(nodeAppEUI);
lmh_setAppKey(nodeAppKey);
// Initialize LoRaWan
err_code = lmh_init(&lora_callbacks, lora_param_init,doOTAA);
if (err_code != 0)
{
Serial.printf("lmh_init failed - %d\n", err_code);
}
// Start Join procedure
lmh_join();
}
void loop()
{
// Handle Radio events
Radio.IrqProcess();
}
/**@brief LoRa function for handling HasJoined event.
*/
void lorawan_has_joined_handler(void)
{
Serial.println("OTAA Mode, Network Joined!");
lmh_error_status ret = lmh_class_request(gCurrentClass);
if(ret == LMH_SUCCESS)
{
delay(1000);
TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
TimerStart(&appTimer);
}
}
/**@brief Function for handling LoRaWan received data from Gateway
*
* @param[in] app_data Pointer to rx data
*/
void lorawan_rx_handler(lmh_app_data_t *app_data)
{
Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d, data:%s\n",
app_data->port, app_data->buffsize, app_data->rssi, app_data->snr, app_data->buffer);
}
void lorawan_confirm_class_handler(DeviceClass_t Class)
{
Serial.printf("switch to class %c done\n", "ABC"[Class]);
// Informs the server that switch has occurred ASAP
m_lora_app_data.buffsize = 0;
m_lora_app_data.port = gAppPort;
lmh_send(&m_lora_app_data, gCurrentConfirm);
}
void send_lora_frame(void)
{
if (lmh_join_status_get() != LMH_SET)
{
//Not joined, try again later
return;
}
uint32_t i = 0;
memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);
m_lora_app_data.port = gAppPort;
m_lora_app_data.buffer[i++] = 'H';
m_lora_app_data.buffer[i++] = 'e';
m_lora_app_data.buffer[i++] = 'l';
m_lora_app_data.buffer[i++] = 'l';
m_lora_app_data.buffer[i++] = 'o';
m_lora_app_data.buffer[i++] = '!';
m_lora_app_data.buffsize = i;
lmh_error_status error = lmh_send(&m_lora_app_data, gCurrentConfirm);
if (error == LMH_SUCCESS)
{
count++;
Serial.printf("lmh_send ok count %d\n", count);
}
else
{
count_fail++;
Serial.printf("lmh_send fail count %d\n", count_fail);
}
}
/**@brief Function for handling user timerout event.
*/
void tx_lora_periodic_handler(void)
{
TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
TimerStart(&appTimer);
Serial.println("Sending frame now...");
send_lora_frame();
}
/**@brief Function for the Timer initialization.
*
* @details Initializes the timer module. This creates and starts application timers.
*/
uint32_t timers_init(void)
{
TimerInit(&appTimer, tx_lora_periodic_handler);
return 0;
}
For users, application is different and complicated. Although RAKwireless provide abundant application examples, it can't satisfy every people. In allusion to this, RAKwireless provides a efficient way to config all parameters about LoRaWAN®.
As the example shows, its default region is EU868. User can change it in Arduino like below:
In *.ino, LoRaWAN® software configuration part, true means OTAA, false means ABP.
bool doOTAA = true;
In *.ino, fill in with LORAWAN_DUTYCYCLE_OFF or LORAWAN_DUTYCYCLE_ON
static lmh_param_t lora_param_init = {LORAWAN_ADR_ON , LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};
In *.ino, fill in with LORAWAN_PUBLIC_NETWORK or LORAWAN_PRIVAT_NETWORK
static lmh_param_t lora_param_init = {LORAWAN_ADR_ON , LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};
So if want to develop own application, better based on examples of RAKwireless in https://github.com/RAKWireless/WisBlock/tree/master/examples/communications/LoRa/LoRaWAN/ . Open in Arduino, change the OTAA/ABP keys according to user's server configuration (TTN and ChirpStack are same with MSB first, only AppKey of TTN is LSB. This part should be pay more attention):
//OTAA keys !!!! ALL KEYS ARE MSB !!!
uint8_t nodeDeviceEUI[8] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x33, 0x33};
uint8_t nodeAppEUI[8] = {0xB8, 0x27, 0xEB, 0xFF, 0xFE, 0x39, 0x00, 0x00};
uint8_t nodeAppKey[16] = {0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x33, 0x33, 0x33, 0x33, 0x33};
//ABP keys !!!! ALL KEYS ARE MSB !!!
uint32_t nodeDevAddr = 0x11111111;
uint8_t nodeNwsKey[16] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
uint8_t nodeAppsKey[16] = {0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11};
In *.ino, fill in with LORAWAN_ADR_ON or LORAWAN_ADR_OFF
static lmh_param_t lora_param_init = {LORAWAN_ADR_ON , LORAWAN_DATERATE, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_TX_POWER, LORAWAN_DUTYCYCLE_OFF};
Same in Arduino project, *.ino. Datarate can be set from DR_0 to DR_5:
#define LORAWAN_DATERATE DR_0 /LoRaMac datarates definition, from DR_0 to DR_5/
Same in Arduino project, *.ino. Tx power can be set from TX_POWER_0 to TX_POWER_15:
#define LORAWAN_TX_POWER TX_POWER_5 /LoRaMac tx power definition, from TX_POWER_0 to TX_POWER_15/
Same in Arduino project, *.ino. Class type can be set to CLASS_A or CLASS_C:
DeviceClass_t gCurrentClass = CLASS_A; /* class definition*/
Remember to set node application to Class C in the TTN or ChirpStack when set to Class C.
Same in Arduino project, *.ino. Packet type can be set to LMH_CONFIRMED_MSG or LMH_UNCONFIRMED_MSG:
lmh_confirm gCurrentConfirm = LMH_CONFIRMED_MSG; /* confirm/unconfirm packet definition*/
Same in Arduino project, *.ino. LoRaWAN application port can be set from 0~255 (do not use 224. It is reserved for certification):
uint8_t gAppPort = 2; /* data port*/
LoRa® is a registered trademark or service mark of Semtech Corporation or its affiliates. LoRaWAN® is a licensed mark.