blob: 56731dfc4542dc722a0627acdd86b3968a12d547 [file] [log] [blame]
/*
* Copyright (c) 2020 Manivannan Sadhasivam <mani@kernel.org>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/init.h>
#include <errno.h>
#include <zephyr/lorawan/lorawan.h>
#include "lw_priv.h"
#include <LoRaMac.h>
#include <Region.h>
#include "nvm/lorawan_nvm.h"
#ifdef CONFIG_LORAMAC_REGION_AS923
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_AS923
#elif CONFIG_LORAMAC_REGION_AU915
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_AU915
#elif CONFIG_LORAMAC_REGION_CN470
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_CN470
#elif CONFIG_LORAMAC_REGION_CN779
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_CN779
#elif CONFIG_LORAMAC_REGION_EU433
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_EU433
#elif CONFIG_LORAMAC_REGION_EU868
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_EU868
#elif CONFIG_LORAMAC_REGION_KR920
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_KR920
#elif CONFIG_LORAMAC_REGION_IN865
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_IN865
#elif CONFIG_LORAMAC_REGION_US915
#define DEFAULT_LORAWAN_REGION LORAMAC_REGION_US915
#elif CONFIG_LORAMAC_REGION_RU864
#define DEFAULT_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 <zephyr/logging/log.h>
LOG_MODULE_REGISTER(lorawan);
K_SEM_DEFINE(mlme_confirm_sem, 0, 1);
K_SEM_DEFINE(mcps_confirm_sem, 0, 1);
K_MUTEX_DEFINE(lorawan_join_mutex);
K_MUTEX_DEFINE(lorawan_send_mutex);
/* We store both the default datarate requested through lorawan_set_datarate
* and the current datarate so that we can use the default datarate for all
* join requests, even as the current datarate changes due to ADR.
*/
static enum lorawan_datarate default_datarate;
static enum lorawan_datarate current_datarate;
static bool lorawan_adr_enable;
static sys_slist_t dl_callbacks;
static LoRaMacPrimitives_t mac_primitives;
static LoRaMacCallback_t mac_callbacks;
static LoRaMacEventInfoStatus_t last_mcps_confirm_status;
static LoRaMacEventInfoStatus_t last_mlme_confirm_status;
static LoRaMacEventInfoStatus_t last_mcps_indication_status;
static LoRaMacEventInfoStatus_t last_mlme_indication_status;
static LoRaMacRegion_t selected_region = DEFAULT_LORAWAN_REGION;
static lorawan_battery_level_cb_t battery_level_cb;
static lorawan_dr_changed_cb_t dr_changed_cb;
/* implementation required by the soft-se (software secure element) */
void BoardGetUniqueId(uint8_t *id)
{
/* Do not change the default value */
}
static uint8_t get_battery_level(void)
{
if (battery_level_cb != NULL) {
return battery_level_cb();
} else {
return 255;
}
}
static void mac_process_notify(void)
{
LoRaMacProcess();
}
static void datarate_observe(bool force_notification)
{
MibRequestConfirm_t mib_req;
mib_req.Type = MIB_CHANNELS_DATARATE;
LoRaMacMibGetRequestConfirm(&mib_req);
if ((mib_req.Param.ChannelsDatarate != current_datarate) ||
(force_notification)) {
current_datarate = mib_req.Param.ChannelsDatarate;
if (dr_changed_cb != NULL) {
dr_changed_cb(current_datarate);
}
LOG_INF("Datarate changed: DR_%d", current_datarate);
}
}
static void mcps_confirm_handler(McpsConfirm_t *mcps_confirm)
{
LOG_DBG("Received McpsConfirm (for McpsRequest %d)",
mcps_confirm->McpsRequest);
if (mcps_confirm->Status != LORAMAC_EVENT_INFO_STATUS_OK) {
LOG_ERR("McpsRequest failed : %s",
lorawan_eventinfo2str(mcps_confirm->Status));
} else {
LOG_DBG("McpsRequest success!");
}
/* Datarate may have changed due to a missed ADRACK */
if (lorawan_adr_enable) {
datarate_observe(false);
}
last_mcps_confirm_status = mcps_confirm->Status;
k_sem_give(&mcps_confirm_sem);
}
static void mcps_indication_handler(McpsIndication_t *mcps_indication)
{
struct lorawan_downlink_cb *cb;
LOG_DBG("Received McpsIndication %d", mcps_indication->McpsIndication);
if (mcps_indication->Status != LORAMAC_EVENT_INFO_STATUS_OK) {
LOG_ERR("McpsIndication failed : %s",
lorawan_eventinfo2str(mcps_indication->Status));
return;
}
/* Datarate can change as result of ADR command from server */
if (lorawan_adr_enable) {
datarate_observe(false);
}
/* Iterate over all registered downlink callbacks */
SYS_SLIST_FOR_EACH_CONTAINER(&dl_callbacks, cb, node) {
if ((cb->port == LW_RECV_PORT_ANY) ||
(cb->port == mcps_indication->Port)) {
cb->cb(mcps_indication->Port,
/* IsUplinkTxPending also indicates pending downlinks */
mcps_indication->IsUplinkTxPending == 1,
mcps_indication->Rssi, mcps_indication->Snr,
mcps_indication->BufferSize,
mcps_indication->Buffer);
}
}
last_mcps_indication_status = mcps_indication->Status;
}
static void mlme_confirm_handler(MlmeConfirm_t *mlme_confirm)
{
MibRequestConfirm_t mib_req;
LOG_DBG("Received MlmeConfirm (for MlmeRequest %d)",
mlme_confirm->MlmeRequest);
if (mlme_confirm->Status != LORAMAC_EVENT_INFO_STATUS_OK) {
LOG_ERR("MlmeConfirm failed : %s",
lorawan_eventinfo2str(mlme_confirm->Status));
goto out_sem;
}
switch (mlme_confirm->MlmeRequest) {
case MLME_JOIN:
mib_req.Type = MIB_DEV_ADDR;
LoRaMacMibGetRequestConfirm(&mib_req);
LOG_INF("Joined network! DevAddr: %08x", mib_req.Param.DevAddr);
break;
case MLME_LINK_CHECK:
/* Not implemented */
LOG_INF("Link check not implemented yet!");
break;
default:
break;
}
out_sem:
last_mlme_confirm_status = mlme_confirm->Status;
k_sem_give(&mlme_confirm_sem);
}
static void mlme_indication_handler(MlmeIndication_t *mlme_indication)
{
LOG_DBG("Received MlmeIndication %d", mlme_indication->MlmeIndication);
last_mlme_indication_status = mlme_indication->Status;
}
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 = default_datarate;
mlme_req.Req.Join.NetworkActivation = ACTIVATION_TYPE_OTAA;
if (IS_ENABLED(CONFIG_LORAWAN_NVM_NONE)) {
/* Retrieve the NVM context to store device nonce */
mib_req.Type = MIB_NVM_CTXS;
if (LoRaMacMibGetRequestConfirm(&mib_req) !=
LORAMAC_STATUS_OK) {
LOG_ERR("Could not get NVM context");
return -EINVAL;
}
mib_req.Param.Contexts->Crypto.DevNonce =
join_cfg->otaa.dev_nonce;
}
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.AppKey = 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_set_region(enum lorawan_region region)
{
switch (region) {
#if defined(CONFIG_LORAMAC_REGION_AS923)
case LORAWAN_REGION_AS923:
selected_region = LORAMAC_REGION_AS923;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_AU915)
case LORAWAN_REGION_AU915:
selected_region = LORAMAC_REGION_AU915;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_CN470)
case LORAWAN_REGION_CN470:
selected_region = LORAMAC_REGION_CN470;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_CN779)
case LORAWAN_REGION_CN779:
selected_region = LORAMAC_REGION_CN779;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_EU433)
case LORAWAN_REGION_EU433:
selected_region = LORAMAC_REGION_EU433;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_EU868)
case LORAWAN_REGION_EU868:
selected_region = LORAMAC_REGION_EU868;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_KR920)
case LORAWAN_REGION_KR920:
selected_region = LORAMAC_REGION_KR920;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_IN865)
case LORAWAN_REGION_IN865:
selected_region = LORAMAC_REGION_IN865;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_US915)
case LORAWAN_REGION_US915:
selected_region = LORAMAC_REGION_US915;
break;
#endif
#if defined(CONFIG_LORAMAC_REGION_RU864)
case LORAWAN_REGION_RU864:
selected_region = LORAMAC_REGION_RU864;
break;
#endif
default:
LOG_ERR("No support for region %d!", region);
return -ENOTSUP;
}
LOG_DBG("Selected region %d", region);
return 0;
}
int lorawan_join(const struct lorawan_join_config *join_cfg)
{
MibRequestConfirm_t mib_req;
LoRaMacStatus_t status;
int ret = 0;
k_mutex_lock(&lorawan_join_mutex, K_FOREVER);
/* MIB_PUBLIC_NETWORK powers on the radio and does not turn it off */
mib_req.Type = MIB_PUBLIC_NETWORK;
mib_req.Param.EnablePublicNetwork = IS_ENABLED(CONFIG_LORAWAN_PUBLIC_NETWORK);
LoRaMacMibSetRequestConfirm(&mib_req);
if (join_cfg->mode == LORAWAN_ACT_OTAA) {
status = lorawan_join_otaa(join_cfg);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("OTAA join failed: %s",
lorawan_status2str(status));
ret = lorawan_status2errno(status);
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(&mlme_confirm_sem, K_FOREVER);
if (last_mlme_confirm_status != LORAMAC_EVENT_INFO_STATUS_OK) {
ret = lorawan_eventinfo2errno(last_mlme_confirm_status);
goto out;
}
} 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",
lorawan_status2str(status));
ret = lorawan_status2errno(status);
goto out;
}
} else {
ret = -EINVAL;
}
out:
/* If the join succeeded */
if (ret == 0) {
/*
* Several regions (AS923, AU915, US915) overwrite the
* datarate as part of the join process. Reset the datarate
* to the value requested (and validated) in
* lorawan_set_datarate so that the MAC layer is aware of the
* set datarate for LoRaMacQueryTxPossible. This is only
* performed when ADR is disabled as it the network servers
* responsibility to increase datarates when ADR is enabled.
*/
if (!lorawan_adr_enable) {
MibRequestConfirm_t mib_req2;
mib_req2.Type = MIB_CHANNELS_DATARATE;
mib_req2.Param.ChannelsDatarate = default_datarate;
LoRaMacMibSetRequestConfirm(&mib_req2);
}
/*
* Force a notification of the datarate on network join as the
* user may not have explicitly set a datarate to use.
*/
datarate_observe(true);
}
k_mutex_unlock(&lorawan_join_mutex);
return ret;
}
int lorawan_set_class(enum lorawan_class dev_class)
{
MibRequestConfirm_t mib_req;
DeviceClass_t current_class;
LoRaMacStatus_t status;
mib_req.Type = MIB_DEVICE_CLASS;
LoRaMacMibGetRequestConfirm(&mib_req);
current_class = mib_req.Param.Class;
switch (dev_class) {
case LORAWAN_CLASS_A:
mib_req.Param.Class = CLASS_A;
break;
case LORAWAN_CLASS_B:
LOG_ERR("Class B not supported yet!");
return -ENOTSUP;
case LORAWAN_CLASS_C:
mib_req.Param.Class = CLASS_C;
break;
default:
return -EINVAL;
}
if (mib_req.Param.Class != current_class) {
status = LoRaMacMibSetRequestConfirm(&mib_req);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("Failed to set device class: %s",
lorawan_status2str(status));
return lorawan_status2errno(status);
}
}
return 0;
}
int lorawan_set_datarate(enum lorawan_datarate dr)
{
MibRequestConfirm_t mib_req;
/* Bail out if using ADR */
if (lorawan_adr_enable) {
return -EINVAL;
}
/* Notify MAC layer of the requested datarate */
mib_req.Type = MIB_CHANNELS_DATARATE;
mib_req.Param.ChannelsDatarate = dr;
if (LoRaMacMibSetRequestConfirm(&mib_req) != LORAMAC_STATUS_OK) {
/* Datarate is invalid for this region */
return -EINVAL;
}
default_datarate = dr;
current_datarate = dr;
return 0;
}
void lorawan_get_payload_sizes(uint8_t *max_next_payload_size,
uint8_t *max_payload_size)
{
LoRaMacTxInfo_t tx_info;
/* QueryTxPossible cannot fail */
(void) LoRaMacQueryTxPossible(0, &tx_info);
*max_next_payload_size = tx_info.MaxPossibleApplicationDataSize;
*max_payload_size = tx_info.CurrentPossiblePayloadSize;
}
enum lorawan_datarate lorawan_get_min_datarate(void)
{
MibRequestConfirm_t mib_req;
mib_req.Type = MIB_CHANNELS_MIN_TX_DATARATE;
LoRaMacMibGetRequestConfirm(&mib_req);
return mib_req.Param.ChannelsMinTxDatarate;
}
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)
{
MibRequestConfirm_t mib_req;
mib_req.Type = MIB_CHANNELS_NB_TRANS;
mib_req.Param.ChannelsNbTrans = tries;
if (LoRaMacMibSetRequestConfirm(&mib_req) != LORAMAC_STATUS_OK) {
return -EINVAL;
}
return 0;
}
int lorawan_send(uint8_t port, uint8_t *data, uint8_t len,
enum lorawan_message_type type)
{
LoRaMacStatus_t status;
McpsReq_t mcps_req;
LoRaMacTxInfo_t tx_info;
int ret = 0;
bool empty_frame = false;
if (data == NULL) {
return -EINVAL;
}
k_mutex_lock(&lorawan_send_mutex, K_FOREVER);
status = LoRaMacQueryTxPossible(len, &tx_info);
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",
lorawan_status2str(status));
empty_frame = true;
mcps_req.Type = MCPS_UNCONFIRMED;
mcps_req.Req.Unconfirmed.fBuffer = NULL;
mcps_req.Req.Unconfirmed.fBufferSize = 0;
mcps_req.Req.Unconfirmed.Datarate = DR_0;
} else {
switch (type) {
case LORAWAN_MSG_UNCONFIRMED:
mcps_req.Type = MCPS_UNCONFIRMED;
break;
case LORAWAN_MSG_CONFIRMED:
mcps_req.Type = MCPS_CONFIRMED;
break;
}
mcps_req.Req.Unconfirmed.fPort = port;
mcps_req.Req.Unconfirmed.fBuffer = data;
mcps_req.Req.Unconfirmed.fBufferSize = len;
mcps_req.Req.Unconfirmed.Datarate = current_datarate;
}
status = LoRaMacMcpsRequest(&mcps_req);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("LoRaWAN Send failed: %s", lorawan_status2str(status));
ret = lorawan_status2errno(status);
goto out;
}
/*
* Always wait for MAC operations to complete.
* 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(&mcps_confirm_sem, K_FOREVER);
if (last_mcps_confirm_status != LORAMAC_EVENT_INFO_STATUS_OK) {
ret = lorawan_eventinfo2errno(last_mcps_confirm_status);
}
/*
* Indicate to the application that the provided data was not sent and
* it has to resend the packet.
*/
if (empty_frame) {
ret = -EAGAIN;
}
out:
k_mutex_unlock(&lorawan_send_mutex);
return ret;
}
void lorawan_register_battery_level_callback(lorawan_battery_level_cb_t cb)
{
battery_level_cb = cb;
}
void lorawan_register_downlink_callback(struct lorawan_downlink_cb *cb)
{
sys_slist_append(&dl_callbacks, &cb->node);
}
void lorawan_register_dr_changed_callback(lorawan_dr_changed_cb_t cb)
{
dr_changed_cb = cb;
}
int lorawan_start(void)
{
LoRaMacStatus_t status;
MibRequestConfirm_t mib_req;
GetPhyParams_t phy_params;
PhyParam_t phy_param;
status = LoRaMacInitialization(&mac_primitives, &mac_callbacks,
selected_region);
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("LoRaMacInitialization failed: %s",
lorawan_status2str(status));
return -EINVAL;
}
LOG_DBG("LoRaMAC Initialized");
if (!IS_ENABLED(CONFIG_LORAWAN_NVM_NONE)) {
lorawan_nvm_init();
lorawan_nvm_data_restore();
}
status = LoRaMacStart();
if (status != LORAMAC_STATUS_OK) {
LOG_ERR("Failed to start the LoRaMAC stack: %s",
lorawan_status2str(status));
return -EINVAL;
}
/* Retrieve the default TX datarate for selected region */
phy_params.Attribute = PHY_DEF_TX_DR;
phy_param = RegionGetPhyParam(selected_region, &phy_params);
default_datarate = phy_param.Value;
current_datarate = default_datarate;
/* TODO: Move these to a proper location */
mib_req.Type = MIB_SYSTEM_MAX_RX_ERROR;
mib_req.Param.SystemMaxRxError = CONFIG_LORAWAN_SYSTEM_MAX_RX_ERROR;
LoRaMacMibSetRequestConfirm(&mib_req);
return 0;
}
static int lorawan_init(void)
{
sys_slist_init(&dl_callbacks);
mac_primitives.MacMcpsConfirm = mcps_confirm_handler;
mac_primitives.MacMcpsIndication = mcps_indication_handler;
mac_primitives.MacMlmeConfirm = mlme_confirm_handler;
mac_primitives.MacMlmeIndication = mlme_indication_handler;
mac_callbacks.GetBatteryLevel = get_battery_level;
mac_callbacks.GetTemperatureLevel = NULL;
if (IS_ENABLED(CONFIG_LORAWAN_NVM_NONE)) {
mac_callbacks.NvmDataChange = NULL;
} else {
mac_callbacks.NvmDataChange = lorawan_nvm_data_mgmt_event;
}
mac_callbacks.MacProcessNotify = mac_process_notify;
return 0;
}
SYS_INIT(lorawan_init, POST_KERNEL, 0);