lorawan: Add initial support for LoRaWAN

Add initial support for LoRaWAN based on Semtech's loramac-node
library. Current implementation only supports OTAA config and
sending data to LoRaWAN server like ThingsNetwork.

While at it, this commit also moves the "loramac-node" library
definition from drivers/lora to subsys/lorawan. This is required
because, subsys/lorawan gets processed before drivers/lora and
that creates issue while building.

Signed-off-by: Manivannan Sadhasivam <mani@kernel.org>
This commit is contained in:
Manivannan Sadhasivam 2020-03-04 19:54:09 +05:30 committed by Carles Cufí
parent 88e3ea02ff
commit 3ce8540f3a
6 changed files with 640 additions and 0 deletions

View file

@ -9,6 +9,12 @@ config HAS_SEMTECH_LORAMAC
help
This option enables the use of Semtech's LoRaMac stack
config HAS_SEMTECH_SOFT_SE
bool "Semtech Secure Element software implementation"
help
This option enables the use of Semtech's Secure Element
software implementation
config HAS_SEMTECH_RADIO_DRIVERS
bool "Semtech LoRa Radio Drivers"
help

View file

@ -2,6 +2,7 @@
add_subdirectory(debug)
add_subdirectory(logging)
add_subdirectory_ifdef(CONFIG_LORAWAN lorawan)
add_subdirectory_ifdef(CONFIG_BT bluetooth)
add_subdirectory_ifdef(CONFIG_CONSOLE_SUBSYS console)
add_subdirectory_ifdef(CONFIG_SHELL shell)

View file

@ -27,6 +27,8 @@ source "subsys/jwt/Kconfig"
source "subsys/logging/Kconfig"
source "subsys/lorawan/Kconfig"
source "subsys/mgmt/Kconfig"
source "subsys/net/Kconfig"

View file

@ -0,0 +1,23 @@
# SPDX-License-Identifier: Apache-2.0
# lorawan.c depends on the include directories exposed by the loramac-node
# library. Hence, if it exists then the source files are added to that otherwise
# a library with same name is created.
if(TARGET loramac-node)
set(ZEPHYR_CURRENT_LIBRARY loramac-node)
else()
zephyr_library_named(loramac-node)
endif()
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_AS923 REGION_AS923)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_AU915 REGION_AU915)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_CN470 REGION_CN470)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_CN779 REGION_CN779)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_EU433 REGION_EU433)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_EU868 REGION_EU868)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_KR920 REGION_KR920)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_IN865 REGION_IN865)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_US915 REGION_US915)
zephyr_compile_definitions_ifdef(CONFIG_LORAMAC_REGION_RU864 REGION_RU864)
zephyr_library_sources_ifdef(CONFIG_LORAWAN lorawan.c)

61
subsys/lorawan/Kconfig Normal file
View file

@ -0,0 +1,61 @@
# LoRaWAN configuration options
# Copyright (c) 2020 Manivannan Sadhasivam <mani@kernel.org>
# SPDX-License-Identifier: Apache-2.0
menuconfig LORAWAN
bool "LoRaWAN support [EXPERIMENTAL]"
depends on LORA
select HAS_SEMTECH_LORAMAC
select HAS_SEMTECH_SOFT_SE
help
This option enables LoRaWAN support.
if LORAWAN
module = LORAWAN
module-str = lorawan
source "subsys/logging/Kconfig.template.log_config"
choice
prompt "LoRaWAN Region Selection"
default LORAMAC_REGION_UNKNOWN
help
Select the LoRaWAN region.
config LORAMAC_REGION_UNKNOWN
bool "Unknown region"
config LORAMAC_REGION_AS923
bool "Asia 923MHz Frequency band"
config LORAMAC_REGION_AU915
bool "Australia 915MHz Frequency band"
config LORAMAC_REGION_CN470
bool "China 470MHz Frequency band"
config LORAMAC_REGION_CN779
bool "China 779MHz Frequency band"
config LORAMAC_REGION_EU433
bool "Europe 433MHz Frequency band"
config LORAMAC_REGION_EU868
bool "Europe 868MHz Frequency band"
config LORAMAC_REGION_KR920
bool "South Korea 920MHz Frequency band"
config LORAMAC_REGION_IN865
bool "India 865MHz Frequency band"
config LORAMAC_REGION_US915
bool "North America 915MHz Frequency band"
config LORAMAC_REGION_RU864
bool "Russia 864MHz Frequency band"
endchoice
endif # LORAWAN

547
subsys/lorawan/lorawan.c Normal file
View file

@ -0,0 +1,547 @@
/*
* Copyright (c) 2020 Manivannan Sadhasivam <mani@kernel.org>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <init.h>
#include <errno.h>
#include <lorawan/lorawan.h>
#include <zephyr.h>
#include <LoRaMac.h>
BUILD_ASSERT(!IS_ENABLED(CONFIG_LORAMAC_REGION_UNKNOWN),
"Unknown region specified for LoRaWAN in Kconfig");
#ifdef CONFIG_LORAMAC_REGION_AS923
#define LORAWAN_REGION LORAMAC_REGION_AS923
#elif CONFIG_LORAMAC_REGION_AU915
#define LORAWAN_REGION LORAMAC_REGION_AU915
#elif CONFIG_LORAMAC_REGION_CN470
#define LORAWAN_REGION LORAMAC_REGION_CN470
#elif CONFIG_LORAMAC_REGION_CN779
#define LORAWAN_REGION LORAMAC_REGION_CN779
#elif CONFIG_LORAMAC_REGION_EU433
#define LORAWAN_REGION LORAMAC_REGION_EU433
#elif CONFIG_LORAMAC_REGION_EU868
#define LORAWAN_REGION LORAMAC_REGION_EU868
#elif CONFIG_LORAMAC_REGION_KR920
#define LORAWAN_REGION LORAMAC_REGION_KR920
#elif CONFIG_LORAMAC_REGION_IN865
#define LORAWAN_REGION LORAMAC_REGION_IN865
#elif CONFIG_LORAMAC_REGION_US915
#define LORAWAN_REGION LORAMAC_REGION_US915
#elif CONFIG_LORAMAC_REGION_RU864
#define LORAWAN_REGION LORAMAC_REGION_RU864
#else
#error "At least one LoRaWAN region should be selected"
#endif
/* Use version 1.0.3.0 for ABP */
#define LORAWAN_ABP_VERSION 0x01000300
#define LOG_LEVEL CONFIG_LORAWAN_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(lorawan);
K_SEM_DEFINE(lorawan_config_sem, 0, 1);
K_SEM_DEFINE(lorawan_tx_sem, 0, 1);
K_MUTEX_DEFINE(lorawan_join_mutex);
K_MUTEX_DEFINE(lorawan_send_mutex);
static volatile bool join_status = false;
static volatile bool send_status = false;
static enum lorawan_datarate lorawan_datarate = LORAWAN_DR_0;
static uint8_t lorawan_conf_msg_tries = 1;
static bool lorawan_adr_enable;
const char *status2str(int status)
{
switch (status) {
case LORAMAC_STATUS_OK:
return "OK";
case LORAMAC_STATUS_BUSY:
return "Busy";
case LORAMAC_STATUS_SERVICE_UNKNOWN:
return "Service unknown";
case LORAMAC_STATUS_PARAMETER_INVALID:
return "Parameter invalid";
case LORAMAC_STATUS_FREQUENCY_INVALID:
return "Frequency invalid";
case LORAMAC_STATUS_DATARATE_INVALID:
return "Datarate invalid";
case LORAMAC_STATUS_FREQ_AND_DR_INVALID:
return "Frequency or datarate invalid";
case LORAMAC_STATUS_NO_NETWORK_JOINED:
return "No network joined";
case LORAMAC_STATUS_LENGTH_ERROR:
return "Length error";
case LORAMAC_STATUS_REGION_NOT_SUPPORTED:
return "Region not supported";
case LORAMAC_STATUS_SKIPPED_APP_DATA:
return "Skipped APP data";
case LORAMAC_STATUS_DUTYCYCLE_RESTRICTED:
return "Duty-cycle restricted";
case LORAMAC_STATUS_NO_CHANNEL_FOUND:
return "No channel found";
case LORAMAC_STATUS_NO_FREE_CHANNEL_FOUND:
return "No free channel found";
case LORAMAC_STATUS_BUSY_BEACON_RESERVED_TIME:
return "Busy beacon reserved time";
case LORAMAC_STATUS_BUSY_PING_SLOT_WINDOW_TIME:
return "Busy ping-slot window time";
case LORAMAC_STATUS_BUSY_UPLINK_COLLISION:
return "Busy uplink collision";
case LORAMAC_STATUS_CRYPTO_ERROR:
return "Crypto error";
case LORAMAC_STATUS_FCNT_HANDLER_ERROR:
return "FCnt handler error";
case LORAMAC_STATUS_MAC_COMMAD_ERROR:
return "MAC command error";
case LORAMAC_STATUS_CLASS_B_ERROR:
return "ClassB error";
case LORAMAC_STATUS_CONFIRM_QUEUE_ERROR:
return "Confirm queue error";
case LORAMAC_STATUS_MC_GROUP_UNDEFINED:
return "Multicast group undefined";
case LORAMAC_STATUS_ERROR:
return "Unknown error";
default:
return NULL;
}
}
const char *eventinfo2str(int status)
{
switch (status) {
case LORAMAC_EVENT_INFO_STATUS_OK:
return "OK";
case LORAMAC_EVENT_INFO_STATUS_ERROR:
return "Error";
case LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT:
return "Tx timeout";
case LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT:
return "Rx 1 timeout";
case LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT:
return "Rx 2 timeout";
case LORAMAC_EVENT_INFO_STATUS_RX1_ERROR:
return "Rx1 error";
case LORAMAC_EVENT_INFO_STATUS_RX2_ERROR:
return "Rx2 error";
case LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL:
return "Join failed";
case LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED:
return "Downlink repeated";
case LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR:
return "Tx DR payload size error";
case LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOSS:
return "Downlink too many frames loss";
case LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL:
return "Address fail";
case LORAMAC_EVENT_INFO_STATUS_MIC_FAIL:
return "MIC fail";
case LORAMAC_EVENT_INFO_STATUS_MULTICAST_FAIL:
return "Multicast fail";
case LORAMAC_EVENT_INFO_STATUS_BEACON_LOCKED:
return "Beacon locked";
case LORAMAC_EVENT_INFO_STATUS_BEACON_LOST:
return "Beacon lost";
case LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND:
return "Beacon not found";
default:
return NULL;
}
}
static LoRaMacPrimitives_t macPrimitives;
static LoRaMacCallback_t macCallbacks;
void OnMacProcessNotify(void)
{
LoRaMacProcess();
}
static void McpsConfirm(McpsConfirm_t *mcpsConfirm)
{
if (mcpsConfirm->Status != LORAMAC_EVENT_INFO_STATUS_OK) {
LOG_ERR("McpsRequest failed : %s",
log_strdup(eventinfo2str(mcpsConfirm->Status)));
} else {
LOG_DBG("McpsRequest success!");
send_status = true;
}
k_sem_give(&lorawan_tx_sem);
}
static void McpsIndication(McpsIndication_t *mcpsIndication)
{
if (mcpsIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK) {
LOG_ERR("McpsIndication failed : %s",
log_strdup(eventinfo2str(mcpsIndication->Status)));
return;
}
/* TODO: Check MCPS Indication type */
if (mcpsIndication->RxData == true) {
if (mcpsIndication->BufferSize != 0) {
LOG_DBG("Rx Data: %s",
log_strdup(mcpsIndication->Buffer));
}
}
/* TODO: Compliance test based on FPort value*/
}
static void MlmeConfirm(MlmeConfirm_t *mlmeConfirm)
{
MibRequestConfirm_t mibGet;
if (mlmeConfirm->Status != LORAMAC_EVENT_INFO_STATUS_OK) {
LOG_ERR("MlmeConfirm failed : %s",
log_strdup(eventinfo2str(mlmeConfirm->Status)));
goto out_sem;
}
switch (mlmeConfirm->MlmeRequest) {
case MLME_JOIN:
mibGet.Type = MIB_DEV_ADDR;
LoRaMacMibGetRequestConfirm(&mibGet);
LOG_INF("Joined network! DevAddr: %08x", mibGet.Param.DevAddr);
join_status = true;
break;
case MLME_LINK_CHECK:
/* Not implemented */
LOG_INF("Link check not implemented yet!");
break;
default:
break;
}
out_sem:
k_sem_give(&lorawan_config_sem);
}
static void MlmeIndication(MlmeIndication_t *mlmeIndication)
{
LOG_DBG("%s", __func__);
}
static LoRaMacStatus_t lorawan_join_otaa(
const struct lorawan_join_config *join_cfg)
{
MlmeReq_t mlme_req;
MibRequestConfirm_t mib_req;
mlme_req.Type = MLME_JOIN;
mlme_req.Req.Join.Datarate = lorawan_datarate;
mib_req.Type = MIB_DEV_EUI;
mib_req.Param.DevEui = join_cfg->dev_eui;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_JOIN_EUI;
mib_req.Param.JoinEui = join_cfg->otaa.join_eui;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_NWK_KEY;
mib_req.Param.NwkKey = join_cfg->otaa.nwk_key;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_APP_KEY;
mib_req.Param.JoinEui = join_cfg->otaa.app_key;
LoRaMacMibSetRequestConfirm(&mib_req);
return LoRaMacMlmeRequest(&mlme_req);
}
static LoRaMacStatus_t lorawan_join_abp(
const struct lorawan_join_config *join_cfg)
{
MibRequestConfirm_t mib_req;
mib_req.Type = MIB_ABP_LORAWAN_VERSION;
mib_req.Param.AbpLrWanVersion.Value = LORAWAN_ABP_VERSION;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_NET_ID;
mib_req.Param.NetID = 0;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_DEV_ADDR;
mib_req.Param.DevAddr = join_cfg->abp.dev_addr;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_F_NWK_S_INT_KEY;
mib_req.Param.FNwkSIntKey = join_cfg->abp.nwk_skey;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_S_NWK_S_INT_KEY;
mib_req.Param.SNwkSIntKey = join_cfg->abp.nwk_skey;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_NWK_S_ENC_KEY;
mib_req.Param.NwkSEncKey = join_cfg->abp.nwk_skey;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_APP_S_KEY;
mib_req.Param.AppSKey = join_cfg->abp.app_skey;
LoRaMacMibSetRequestConfirm(&mib_req);
mib_req.Type = MIB_NETWORK_ACTIVATION;
mib_req.Param.NetworkActivation = ACTIVATION_TYPE_ABP;
LoRaMacMibSetRequestConfirm(&mib_req);
return LORAMAC_STATUS_OK;
}
int lorawan_join(const struct lorawan_join_config *join_cfg)
{
LoRaMacStatus_t status;
int ret;
k_mutex_lock(&lorawan_join_mutex, K_FOREVER);
if (join_cfg->mode == LORAWAN_ACT_OTAA) {
join_status = false;
status = lorawan_join_otaa(join_cfg);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("OTAA join failed: %s",
log_strdup(status2str(status)));
ret = -EINVAL;
goto out;
}
LOG_DBG("Network join request sent!");
/*
* We can be sure that the semaphore will be released for
* both success and failure cases after a specific time period.
* So we can use K_FOREVER and no need to check the return val.
*/
k_sem_take(&lorawan_config_sem, K_FOREVER);
} else if (join_cfg->mode == LORAWAN_ACT_ABP) {
status = lorawan_join_abp(join_cfg);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("ABP join failed: %s",
log_strdup(status2str(status)));
ret = -EINVAL;
goto out;
}
} else {
ret = -EINVAL;
goto out;
}
if (join_status) {
ret = 0;
} else {
/* TODO: Return the exact error code */
ret = -EINVAL;
}
out:
k_mutex_unlock(&lorawan_join_mutex);
return ret;
}
int lorawan_set_class(enum lorawan_class dev_class)
{
LoRaMacStatus_t status;
MibRequestConfirm_t mib_req;
mib_req.Type = MIB_DEVICE_CLASS;
switch (dev_class) {
case LORAWAN_CLASS_A:
mib_req.Param.Class = CLASS_A;
break;
case LORAWAN_CLASS_B:
case LORAWAN_CLASS_C:
LOG_ERR("Device class not supported yet!");
return -ENOTSUP;
default:
return -EINVAL;
};
status = LoRaMacMibSetRequestConfirm(&mib_req);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("Failed to set device class: %s",
status2str(status));
return mac_status_to_errno[status];
}
return 0;
}
int lorawan_set_datarate(enum lorawan_datarate dr)
{
/* Bail out if using ADR */
if (lorawan_adr_enable) {
return -EINVAL;
}
lorawan_datarate = dr;
return 0;
}
void lorawan_enable_adr(bool enable)
{
MibRequestConfirm_t mib_req;
if (enable != lorawan_adr_enable) {
lorawan_adr_enable = enable;
mib_req.Type = MIB_ADR;
mib_req.Param.AdrEnable = lorawan_adr_enable;
LoRaMacMibSetRequestConfirm(&mib_req);
}
}
int lorawan_set_conf_msg_tries(uint8_t tries)
{
lorawan_conf_msg_tries = tries;
return 0;
}
int lorawan_send(uint8_t port, uint8_t *data, uint8_t len, uint8_t flags)
{
LoRaMacStatus_t status;
McpsReq_t mcpsReq;
LoRaMacTxInfo_t txInfo;
int ret = 0;
bool empty_frame = false;
if (data == NULL) {
return -EINVAL;
}
k_mutex_lock(&lorawan_send_mutex, K_FOREVER);
status = LoRaMacQueryTxPossible(len, &txInfo);
if (status != LORAMAC_STATUS_OK) {
/*
* If status indicates an error, then most likely the payload
* has exceeded the maximum possible length for the current
* region and datarate. We can't do much other than sending
* empty frame in order to flush MAC commands in stack and
* hoping the application to lower the payload size for
* next try.
*/
LOG_ERR("LoRaWAN Query Tx Possible Failed: %s",
log_strdup(status2str(status)));
empty_frame = true;
mcpsReq.Type = MCPS_UNCONFIRMED;
mcpsReq.Req.Unconfirmed.fBuffer = NULL;
mcpsReq.Req.Unconfirmed.fBufferSize = 0;
mcpsReq.Req.Unconfirmed.Datarate = DR_0;
} else {
if (flags & LORAWAN_MSG_CONFIRMED) {
mcpsReq.Type = MCPS_CONFIRMED;
mcpsReq.Req.Confirmed.fPort = port;
mcpsReq.Req.Confirmed.fBuffer = data;
mcpsReq.Req.Confirmed.fBufferSize = len;
mcpsReq.Req.Confirmed.NbTrials = lorawan_conf_msg_tries;
mcpsReq.Req.Confirmed.Datarate = lorawan_datarate;
send_status = false;
} else {
/* default message type */
mcpsReq.Type = MCPS_UNCONFIRMED;
mcpsReq.Req.Unconfirmed.fPort = port;
mcpsReq.Req.Unconfirmed.fBuffer = data;
mcpsReq.Req.Unconfirmed.fBufferSize = len;
mcpsReq.Req.Unconfirmed.Datarate = lorawan_datarate;
}
}
status = LoRaMacMcpsRequest(&mcpsReq);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("LoRaWAN Send failed: %s",
log_strdup(status2str(status)));
ret = -EINVAL;
goto out;
}
/*
* Indicate to the application that the current packet is not sent and
* it has to resend the packet.
*/
if (empty_frame) {
ret = -EAGAIN;
goto out;
}
/* Wait for send confirmation */
if (flags & LORAWAN_MSG_CONFIRMED) {
/*
* We can be sure that the semaphore will be released for
* both success and failure cases after a specific time period.
* So we can use K_FOREVER and no need to check the return val.
*/
k_sem_take(&lorawan_tx_sem, K_FOREVER);
if (send_status) {
ret = 0;
} else {
/* TODO: Return the exact error code */
ret = -EINVAL;
}
}
out:
k_mutex_unlock(&lorawan_send_mutex);
return ret;
}
int lorawan_start(void)
{
LoRaMacStatus_t status;
MibRequestConfirm_t mib_req;
status = LoRaMacStart();
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("Failed to start the LoRaMAC stack: %s",
status2str(status));
return -EINVAL;
}
/* TODO: Move these to a proper location */
mib_req.Type = MIB_PUBLIC_NETWORK;
mib_req.Param.EnablePublicNetwork = true;
LoRaMacMibSetRequestConfirm(&mib_req);
return 0;
}
static int lorawan_init(const struct device *dev)
{
LoRaMacStatus_t status;
macPrimitives.MacMcpsConfirm = McpsConfirm;
macPrimitives.MacMcpsIndication = McpsIndication;
macPrimitives.MacMlmeConfirm = MlmeConfirm;
macPrimitives.MacMlmeIndication = MlmeIndication;
macCallbacks.GetBatteryLevel = NULL;
macCallbacks.GetTemperatureLevel = NULL;
macCallbacks.NvmContextChange = NULL;
macCallbacks.MacProcessNotify = OnMacProcessNotify;
status = LoRaMacInitialization(&macPrimitives, &macCallbacks,
LORAWAN_REGION);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("LoRaMacInitialization failed: %s",
log_strdup(status2str(status)));
return -EINVAL;
}
LOG_DBG("LoRaMAC Initialized");
return 0;
}
SYS_INIT(lorawan_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEVICE);