RAKwireless предоставил образец для обзора универсального комплекта разработки LPWAN IoT WisTrio Link.ONE с поддержкой подключений LTE-M, NB-IoT и LoRaWAN, программируемого через Arduino IDE. Комплект был протестирован во влагозащищенном корпусе WisBlock Unify с использованием LoRaWAN и пакетов открытого ПО, включая ChipStark, Node-Red, InfluxDB и Grafana.
Ключевые особенности комплекта Link.ONE
В полученном комплекте присутствуют корпус WisBlock Unify (100 x 75 x 38 мм) и литий-ионный аккумулятор 3200 мАч/3.7В, которого достаточно при основном использовании системы в спящем режиме для приема данных без частой передачи информации.
Распаковка «Link.ONE with BOX»
В плотно упакованной коробке находились:
- Плата разработки WisTrio LTE-M, NB-IoT и LoRaWAN
- Литий-ионный аккумулятор
- Внешняя антенна для сотовой связи
- Кабель USB Type-C
- Круглый кабель M8 female
Корпус прочен, а крышка оснащена водостойким уплотнителем, обеспечивающим степень защиты IP65 при закрытии.
После извлечения аккумулятора можно детально рассмотреть плату разработки WisTrio Link.ONE.
Модули в составе комплекта разработки Link.ONE

Комплект включает три основных модуля:
- RAK4631 WisBlock core module с микроконтроллером Nordic Semi nRF52840 (BLE) и RF-трансивером Semtech SX1262 (LoRa/LoRaWAN)
- RAK5860 WisBlock NB-IoT interface module на базе Quectel BG77 с поддержкой NB-IoT, LTE-M и GPS
- RAK19007 WisBlock baseboard с портом USB Type-C и контроллером заряда Li-ion
Сборка компонентов показана на схеме ниже.
В комплект также входит SIM-карта Monogoto с пакетом 500 МБ, действительным до 10 лет.
SIM-карта работает глобально , но в Таиланде (место тестирования) функционирует в сетях 2G/3G/4G операторов AIS и TrueMove без поддержки LTE Cat M1 (LTE-M). Данные о совместимости с NB-IoT отсутствуют.
Дополнительные сенсорные модули доступны при заказе, но в тестовом комплекте отсутствовали.
LPWAN (Low Power Wide Area Network)
Link.ONE поддерживает три типа энергоэффективных широкополосных сетей (LPWAN): LTE-M, NB-IoT и LoRaWAN.
- NB-IoT (Narrow Band Internet of Things) — технология на базе 4G LTE для подключения IoT-устройств через сотовые сети. Оптимальна для приложений без требований к высокой скорости передачи данных (Smart Parking, Smart Metering).
- LTE-M (Long Term Evolution of Machines) — аналогична NB-IoT, но с повышенной скоростью передачи при сохранении энергоэффективности. Подходит для отслеживания местоположения (Smart Transportation, Asset Tracking).
- LoRaWAN (Long Range Wide Area Network) — радиотехнология на протоколе LoRa для энергоэффективных устройств с дальним радиусом действия. Поддерживает частные и публичные сети (первые позволяют развертывать собственные шлюзы без операторов).
Примечание 1: Тестирование NB-IoT не проводилось из-за ежегодной платы за облачный Network Server у операторов Таиланда.
Примечание 2: LTE-M не тестировался из-за отсутствия поддержки SIM-картой Monogoto в Таиланде.
Частная локальная платформа LoRaWAN IoT
Для тестов развернута частная платформа LoRaWAN IoT, обеспечивающая полный контроль управления. Использованы следующие решения с открытым исходным кодом:
- ChirpStack — сервер сети и приложений LoRaWAN, регистрирующий устройства и расшифровывающий данные в формате AES128 через MQTT-брокер (отправка publish).
- Node-RED — инструмент flow-based разработки для программирования. Получает данные (subscribe) от ChirpStack по MQTT, декодирует полезную нагрузку из BASE64, сохраняет показания сенсоров в InfluxDB и настраивает уведомления через LINE Notify.
- InfluxDB — база данных временных рядов для хранения показаний сенсоров и данных шлюза LoRaWAN с автоматической сортировкой по времени для анализа.
- Grafana — дашборд реального времени для визуализации данных из InfluxDB.
- LINE Notify — при превышении заданных пороговых значений отправляет однократное уведомление через API LINE Notify (без дублирования).
Подготовка оборудования и ПО
Необходимое оборудование: комплект Link.ONE, кабель USB Type-C, шлюз LoRaWAN и компьютер.
Требуется установка Arduino IDE с настройкой для Link.ONE:
- Установка Arduino IDE
- Добавление устройства Link.ONE. В меню Arduino IDE File -> Preferences вставить URL https://raw.githubusercontent.com/RAKwireless/RAKwireless-Arduino-BSP-Index/main/package_rakwireless_index.json в поле Additional Boards Manager URLs.
- Затем выбрать Tools -> Board -> Board Manager, найти «RAKwireless nRF Boards» и установить пакет для WisBlock RAK4631.
- Выбрать плату через Tools -> Board -> Board Manager -> RAKwireless nRF Boards -> WisBlock RAK4631 .
- Добавить библиотеку SX126x через Sketch -> Include Library -> Library Manager , найти «SX126x-Arduino» и установить.
Тестирование LoRaWAN на devkit Link.ONE
Для отправки сообщения «Hello World» по LoRaWAN установлен частотный диапазон AS923 (Таиланд) и режим подключения OTAA со значениями:
- DevEUI = 88 88 88 88 88 88 33 33
- AppKey = 88 88 88 88 88 88 88 88 88 88 88 88 88 88 88 88
- AppEUI = B8 27 E B FF FE 39 00 00
Примечание: Поддерживаются два типа активации — ABP (Activation By Personalization) и OTAA (Over The Air Activation).
/**
* @file LoRaWAN_OTAA_ABP.ino
* @author rakwireless.com
* @brief LoRaWan node example with OTAA/ABP registration
* @version 0.1
* @date 2020-08-21
*
* @copyright Copyright (c) 2020
*
* @note RAK4631 GPIO mapping to nRF52840 GPIO ports
RAK4631 <-> nRF52840
WB_IO1 <-> P0.17 (GPIO 17)
WB_IO2 <-> P1.02 (GPIO 34)
WB_IO3 <-> P0.21 (GPIO 21)
WB_IO4 <-> P0.04 (GPIO 4)
WB_IO5 <-> P0.09 (GPIO 9)
WB_IO6 <-> P0.10 (GPIO 10)
WB_SW1 <-> P0.01 (GPIO 1)
WB_A0 <-> P0.04/AIN2 (AnalogIn A2)
WB_A1 <-> P0.31/AIN7 (AnalogIn A7)
*/
#include <Arduino.h>
#include <LoRaWan-RAK4630.h> //http://librarymanager/All#SX126x
#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; // OTAA is used by default.
#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 g_CurrentClass = CLASS_A; /* class definition*/
LoRaMacRegion_t g_CurrentRegion = LORAMAC_REGION_AS923; /* Region: AS923 */
lmh_confirm g_CurrentConfirm = LMH_UNCONFIRMED_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 g_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_join_failed_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 g_lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler, lorawan_join_failed_handler
};
//OTAA keys !!!! 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, 0x88, 0x88};
// ABP keys
uint32_t nodeDevAddr = 0x260116F8;
uint8_t nodeNwsKey[16] = {0x7E, 0xAC, 0xE2, 0x55, 0xB8, 0xA5, 0xE2, 0x69, 0x91, 0x51, 0x96, 0x06, 0x47, 0x56, 0x9D, 0x23};
uint8_t nodeAppsKey[16] = {0xFB, 0xAC, 0xB6, 0x47, 0xF3, 0x58, 0x45, 0xC7, 0x50, 0x7D, 0xBF, 0x16, 0x8B, 0xA8, 0xC1, 0x7C};
// 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 Serial for debug output
time_t timeout = millis();
Serial.begin(115200);
while (!Serial)
{
if ((millis() - timeout) < 5000)
{
delay(100);
}
else
{
break;
}
}
// Initialize LoRa chip.
lora_rak4630_init();
Serial.println("=====================================");
Serial.println("Welcome to RAK4630 LoRaWan!!!");
if (doOTAA)
{
Serial.println("Type: OTAA");
}
else
{
Serial.println("Type: ABP");
}
switch (g_CurrentRegion)
{
case LORAMAC_REGION_AS923:
Serial.println("Region: AS923");
break;
case LORAMAC_REGION_AU915:
Serial.println("Region: AU915");
break;
case LORAMAC_REGION_CN470:
Serial.println("Region: CN470");
break;
case LORAMAC_REGION_CN779:
Serial.println("Region: CN779");
break;
case LORAMAC_REGION_EU433:
Serial.println("Region: EU433");
break;
case LORAMAC_REGION_IN865:
Serial.println("Region: IN865");
break;
case LORAMAC_REGION_EU868:
Serial.println("Region: EU868");
break;
case LORAMAC_REGION_KR920:
Serial.println("Region: KR920");
break;
case LORAMAC_REGION_US915:
Serial.println("Region: US915");
break;
case LORAMAC_REGION_RU864:
Serial.println("Region: RU864");
break;
case LORAMAC_REGION_AS923_2:
Serial.println("Region: AS923-2");
break;
case LORAMAC_REGION_AS923_3:
Serial.println("Region: AS923-3");
break;
case LORAMAC_REGION_AS923_4:
Serial.println("Region: AS923-4");
break;
}
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);
return;
}
// Setup the EUIs and Keys
if (doOTAA)
{
lmh_setDevEui(nodeDeviceEUI);
lmh_setAppEui(nodeAppEUI);
lmh_setAppKey(nodeAppKey);
}
else
{
lmh_setNwkSKey(nodeNwsKey);
lmh_setAppSKey(nodeAppsKey);
lmh_setDevAddr(nodeDevAddr);
}
// Initialize LoRaWan
err_code = lmh_init(&g_lora_callbacks, g_lora_param_init, doOTAA, g_CurrentClass, g_CurrentRegion);
if (err_code != 0)
{
Serial.printf("lmh_init failed - %d\n", err_code);
return;
}
// Start Join procedure
lmh_join();
}
void loop()
{
// Put your application tasks here, like reading of sensors,
// Controlling actuators and/or other functions.
}
/**@brief LoRa function for handling HasJoined event.
*/
void lorawan_has_joined_handler(void)
{
if(doOTAA == true)
{
Serial.println("OTAA Mode, Network Joined!");
}
else
{
Serial.println("ABP Mode");
}
lmh_error_status ret = lmh_class_request(g_CurrentClass);
if (ret == LMH_SUCCESS)
{
delay(1000);
TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
TimerStart(&appTimer);
}
}
/**@brief LoRa function for handling OTAA join failed
*/
static void lorawan_join_failed_handler(void)
{
Serial.println("OTAA join failed!");
Serial.println("Check your EUI's and Keys's!");
Serial.println("Check if a Gateway is in range!");
}
/**@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, g_CurrentConfirm);
}
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++] = 'N';
m_lora_app_data.buffer[i++] = 'i';
m_lora_app_data.buffer[i++] = 'n';
m_lora_app_data.buffer[i++] = 'e';
m_lora_app_data.buffer[i++] = 'P';
m_lora_app_data.buffer[i++] = 'h';
m_lora_app_data.buffer[i++] = 'o';
m_lora_app_data.buffer[i++] = 'n';
m_lora_app_data.buffsize = i;
lmh_error_status error = lmh_send(&m_lora_app_data, g_CurrentConfirm);
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;
}
Код компилируется в Arduino IDE и загружается на плату Link.ONE без необходимости нажатия кнопок — преимущество для разработчиков.
При запуске обрабатываются два типа сообщений:
- «Join Request» — запрос Link.ONE на подключение к LoRaWAN Network Server через шлюз.
- «Join Accept» — подтверждение подключения после проверки DevEUI сервером.
Полезная нагрузка «TmluZVBob24=» (см. скриншот) декодируется в «NinePhon» (имя автора) через Base64.
Из-за отсутствия сенсорного модуля написан демо-код для считывания напряжения аккумулятора, уровня заряда в % и «battery value».
/**
* @file LoRaWAN_LinkONE.ino
* @author Mr.Suraphon Kongangkab
* @eMail ninephon9@gmail.com
* @brief Link.ONE LoRaWan node with OTAA join and send Battery Voltage data payload.
* @date 2023-06-20
*
* @note RAK4631 GPIO mapping to nRF52840 GPIO ports
RAK4631 <-> nRF52840
WB_IO1 <-> P0.17 (GPIO 17)
WB_IO2 <-> P1.02 (GPIO 34)
WB_IO3 <-> P0.21 (GPIO 21)
WB_IO4 <-> P0.04 (GPIO 4)
WB_IO5 <-> P0.09 (GPIO 9)
WB_IO6 <-> P0.10 (GPIO 10)
WB_SW1 <-> P0.01 (GPIO 1)
WB_A0 <-> P0.04/AIN2 (AnalogIn A2)
WB_A1 <-> P0.31/AIN7 (AnalogIn A7)
*/
#include <Arduino.h>
#include <LoRaWan-RAK4630.h> //http://librarymanager/All#SX126x
#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; // OTAA is used by default.
#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 g_CurrentClass = CLASS_A; /* class definition*/
LoRaMacRegion_t g_CurrentRegion = LORAMAC_REGION_AS923; /* Region: AS923 */
lmh_confirm g_CurrentConfirm = LMH_UNCONFIRMED_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 g_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_join_failed_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 g_lora_callbacks = {BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler, lorawan_join_failed_handler
};
//OTAA keys !!!! 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, 0x88, 0x88};
// ABP keys
uint32_t nodeDevAddr = 0x260116F8;
uint8_t nodeNwsKey[16] = {0x7E, 0xAC, 0xE2, 0x55, 0xB8, 0xA5, 0xE2, 0x69, 0x91, 0x51, 0x96, 0x06, 0x47, 0x56, 0x9D, 0x23};
uint8_t nodeAppsKey[16] = {0xFB, 0xAC, 0xB6, 0x47, 0xF3, 0x58, 0x45, 0xC7, 0x50, 0x7D, 0xBF, 0x16, 0x8B, 0xA8, 0xC1, 0x7C};
// Private defination
#define LORAWAN_APP_DATA_BUFF_SIZE 64 /**< buffer size of the data to be transmitted. */
#define LORAWAN_APP_INTERVAL 60000 /**< Defines for user timer, the application data transmission interval. 60s, 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;
// Read Battery
#define PIN_VBAT WB_A0
uint32_t vbat_pin = PIN_VBAT;
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12 - bit ADC resolution = 3000mV / 4096
#define VBAT_DIVIDER_COMP (1.73) // Compensation factor for the VBAT divider, depend on the board
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
float readVBAT(void)
{
float raw;
// Get the raw 12-bit, 0..3000mV ADC value
raw = analogRead(vbat_pin);
return raw * REAL_VBAT_MV_PER_LSB;
}
uint8_t mvToPercent(float mvolts)
{
if (mvolts < 3300)
return 0;
if (mvolts < 3600)
{
mvolts -= 3300;
return mvolts / 30;
}
mvolts -= 3600;
return 10 + (mvolts * 0.15F); // thats mvolts /6.66666666
}
uint8_t mvToLoRaWanBattVal(float mvolts)
{
if (mvolts < 3300)
return 0;
if (mvolts < 3600)
{
mvolts -= 3300;
return mvolts / 30 * 2.55;
}
mvolts -= 3600;
return (10 + (mvolts * 0.15F)) * 2.55;
}
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// Initialize Serial for debug output
time_t timeout = millis();
Serial.begin(115200);
while (!Serial)
{
if ((millis() - timeout) < 5000)
{
delay(100);
}
else
{
break;
}
}
// Initialize LoRa chip.
lora_rak4630_init();
Serial.println("=====================================");
Serial.println("Welcome to RAK4630 LoRaWan!!!");
if (doOTAA)
{
Serial.println("Type: OTAA");
}
else
{
Serial.println("Type: ABP");
}
switch (g_CurrentRegion)
{
case LORAMAC_REGION_AS923:
Serial.println("Region: AS923");
break;
case LORAMAC_REGION_AU915:
Serial.println("Region: AU915");
break;
case LORAMAC_REGION_CN470:
Serial.println("Region: CN470");
break;
case LORAMAC_REGION_CN779:
Serial.println("Region: CN779");
break;
case LORAMAC_REGION_EU433:
Serial.println("Region: EU433");
break;
case LORAMAC_REGION_IN865:
Serial.println("Region: IN865");
break;
case LORAMAC_REGION_EU868:
Serial.println("Region: EU868");
break;
case LORAMAC_REGION_KR920:
Serial.println("Region: KR920");
break;
case LORAMAC_REGION_US915:
Serial.println("Region: US915");
break;
case LORAMAC_REGION_RU864:
Serial.println("Region: RU864");
break;
case LORAMAC_REGION_AS923_2:
Serial.println("Region: AS923-2");
break;
case LORAMAC_REGION_AS923_3:
Serial.println("Region: AS923-3");
break;
case LORAMAC_REGION_AS923_4:
Serial.println("Region: AS923-4");
break;
}
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);
return;
}
// Setup the EUIs and Keys
if (doOTAA)
{
lmh_setDevEui(nodeDeviceEUI);
lmh_setAppEui(nodeAppEUI);
lmh_setAppKey(nodeAppKey);
}
else
{
lmh_setNwkSKey(nodeNwsKey);
lmh_setAppSKey(nodeAppsKey);
lmh_setDevAddr(nodeDevAddr);
}
// Initialize LoRaWan
err_code = lmh_init(&g_lora_callbacks, g_lora_param_init, doOTAA, g_CurrentClass, g_CurrentRegion);
if (err_code != 0)
{
Serial.printf("lmh_init failed - %d\n", err_code);
return;
}
// Start Join procedure
lmh_join();
// Read Battery
// Set the analog reference to 3.0V (default = 3.6V)
analogReference(AR_INTERNAL_3_0);
// Set the resolution to 12-bit (0..4095)
analogReadResolution(12); // Can be 8, 10, 12 or 14
// Let the ADC settle
delay(1);
// Get a single ADC sample and throw it away
readVBAT();
}
void loop()
{
// Put your application tasks here, like reading of sensors,
// Controlling actuators and/or other functions.
}
/**@brief LoRa function for handling HasJoined event.
*/
void lorawan_has_joined_handler(void)
{
if(doOTAA == true)
{
Serial.println("OTAA Mode, Network Joined!");
}
else
{
Serial.println("ABP Mode");
}
lmh_error_status ret = lmh_class_request(g_CurrentClass);
if (ret == LMH_SUCCESS)
{
delay(1000);
TimerSetValue(&appTimer, LORAWAN_APP_INTERVAL);
TimerStart(&appTimer);
}
}
/**@brief LoRa function for handling OTAA join failed
*/
static void lorawan_join_failed_handler(void)
{
Serial.println("OTAA join failed!");
Serial.println("Check your EUI's and Keys's!");
Serial.println("Check if a Gateway is in range!");
}
/**@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, g_CurrentConfirm);
}
void send_lora_frame(void)
{
if (lmh_join_status_get() != LMH_SET)
{
//Not joined, try again later
return;
}
memset(m_lora_app_data.buffer, 0, LORAWAN_APP_DATA_BUFF_SIZE);
m_lora_app_data.port = gAppPort;
// Get a raw ADC reading
float vbat_mv = readVBAT();
float vbat_v = vbat_mv/1000;
// Convert from raw mv to percentage (based on LIPO chemistry)
uint8_t vbat_per = mvToPercent(vbat_mv);
m_lora_app_data.buffer[0] = vbat_v;
m_lora_app_data.buffer[4] = vbat_per;
m_lora_app_data.buffer[5] = mvToLoRaWanBattVal(vbat_mv);
m_lora_app_data.buffsize = 6;
lmh_error_status error = lmh_send(&m_lora_app_data, g_CurrentConfirm);
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;
}
Link.ONE передает данные по LoRaWAN на шлюз, который перенаправляет их в сетевой сервер ChirpStack.
Node-RED подключается к ChirpStack по MQTT и декодирует полезную нагрузку из Base64.
Node-RED автоматически сохраняет данные в InfluxDB.
Дашборд Grafana отображает напряжение аккумулятора, уровень заряда и энергопотребление (мВт) в реальном времени.
LINE Notify отслеживает напряжение: при падении ниже 3.3 В отправляется оповещение.
Заключение
Комплект WisTrio Link.ONE подходит разработчикам IoT-устройств, интегрирующим сенсоры, пишущим код для различных LPWAN (LTE-M, NB-IoT, LoRaWAN). Совместимость с Arduino IDE упрощает использование. SIM-карта Monogoto с пакетом 500 МБ на 10 лет — преимущество при наличии покрытия.
Благодарность RAKwireless за предоставленный образец. Комплект Link.ONE доступен от $56 , версия с корпусом WisBlock Unify и аккумулятором — $107. Рекомендуется заказывать сенсоры отдельно. Из-за веса батареи возможен рост стоимости доставки.
Выражаем свою благодарность источнику, с которого взята и переведена статья, сайту cnx-software.com.
Оригинал статьи вы можете прочитать здесь.