blob: 4e5bf3f848df1c9710df6bd0f34577570db915f7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <string.h>
#include "os/mynewt.h"
#include "node/lora_priv.h"
#include "node/lora_band.h"
STATS_SECT_DECL(lora_mac_stats) lora_mac_stats;
STATS_NAME_START(lora_mac_stats)
STATS_NAME(lora_mac_stats, join_req_tx)
STATS_NAME(lora_mac_stats, join_accept_rx)
STATS_NAME(lora_mac_stats, link_chk_tx)
STATS_NAME(lora_mac_stats, link_chk_ans_rxd)
STATS_NAME(lora_mac_stats, join_failures)
STATS_NAME(lora_mac_stats, joins)
STATS_NAME(lora_mac_stats, tx_timeouts)
STATS_NAME(lora_mac_stats, unconfirmed_tx)
STATS_NAME(lora_mac_stats, confirmed_tx_fail)
STATS_NAME(lora_mac_stats, confirmed_tx_good)
STATS_NAME(lora_mac_stats, tx_mac_flush)
STATS_NAME(lora_mac_stats, rx_errors)
STATS_NAME(lora_mac_stats, rx_frames)
STATS_NAME(lora_mac_stats, rx_mic_failures)
STATS_NAME(lora_mac_stats, rx_mlme)
STATS_NAME(lora_mac_stats, rx_mcps)
STATS_NAME(lora_mac_stats, rx_dups)
STATS_NAME(lora_mac_stats, rx_invalid)
STATS_NAME(lora_mac_stats, no_bufs)
STATS_NAME(lora_mac_stats, already_joined)
STATS_NAME_END(lora_mac_stats)
/* Device EUI */
uint8_t g_lora_dev_eui[LORA_EUI_LEN];
/* Application EUI */
uint8_t g_lora_app_eui[LORA_EUI_LEN];
/* Application Key */
uint8_t g_lora_app_key[LORA_KEY_LEN];
/* Flag to denote if we last sent a mac command */
uint8_t g_lora_node_last_tx_mac_cmd;
/* MAC task */
#define LORA_MAC_STACK_SIZE (256)
struct os_task g_lora_mac_task;
os_stack_t g_lora_mac_stack[LORA_MAC_STACK_SIZE];
/*
* Lora MAC data object
*/
struct lora_mac_obj g_lora_mac_data;
/* The join event argument */
struct lm_join_ev_arg_obj
{
uint8_t trials;
uint8_t *dev_eui;
uint8_t *app_eui;
uint8_t *app_key;
};
struct lm_join_ev_arg_obj g_lm_join_ev_arg;
/* Debug log */
#if defined(LORA_NODE_DEBUG_LOG)
struct lora_node_debug_log_entry g_lnd_log[LORA_NODE_DEBUG_LOG_ENTRIES];
uint16_t g_lnd_log_index;
void
lora_node_log(uint8_t logid, uint8_t p8, uint16_t p16, uint32_t p32)
{
os_sr_t sr;
OS_ENTER_CRITICAL(sr);
g_lnd_log[g_lnd_log_index].lnd_id = logid;
g_lnd_log[g_lnd_log_index].lnd_p8 = p8;
g_lnd_log[g_lnd_log_index].lnd_p16 = p16;
g_lnd_log[g_lnd_log_index].lnd_p32 = p32;
g_lnd_log[g_lnd_log_index].lnd_cputime =
hal_timer_read(MYNEWT_VAL(LORA_MAC_TIMER_NUM));
++g_lnd_log_index;
if (g_lnd_log_index == LORA_NODE_DEBUG_LOG_ENTRIES) {
g_lnd_log_index = 0;
}
OS_EXIT_CRITICAL(sr);
}
#endif /* if defined(LORA_NODE_DEBUG_LOG) */
/* Allocate a packet for lora transmission. This returns a packet header mbuf */
struct os_mbuf *
lora_pkt_alloc(void)
{
struct os_mbuf *p;
/* XXX: For now just allocate 255 bytes */
p = os_msys_get_pkthdr(255, sizeof(struct lora_pkt_info));
return p;
}
/**
* This is the application to mac layer transmit interface.
*
* @param om Pointer to transmit packet
*/
void
lora_node_mcps_request(struct os_mbuf *om)
{
int rc;
lora_node_log(LORA_NODE_LOG_APP_TX, 0, OS_MBUF_PKTLEN(om), (uint32_t)om);
rc = os_mqueue_put(&g_lora_mac_data.lm_txq, &g_lora_mac_data.lm_evq, om);
assert(rc == 0);
}
/**
* What's the maximum payload which can be sent on next frame
*
* @return int payload length, negative on error.
*/
int
lora_node_mtu(void)
{
struct sLoRaMacTxInfo info;
int rc;
rc = LoRaMacQueryTxPossible(0, &info);
if (rc != LORAMAC_STATUS_MAC_CMD_LENGTH_ERROR) {
return info.MaxPossiblePayload;
}
return -1;
}
#if !MYNEWT_VAL(LORA_NODE_CLI)
static void
lora_node_reset_txq_timer(void)
{
/* XXX: For now, just reset timer to fire off in one second */
os_callout_reset(&g_lora_mac_data.lm_txq_timer, OS_TICKS_PER_SEC);
}
/**
* Posts an event to the lora mac task to check the transmit queue for
* more packets.
*/
void
lora_node_chk_txq(void)
{
os_eventq_put(&g_lora_mac_data.lm_evq, &g_lora_mac_data.lm_txq.mq_ev);
}
bool
lora_node_txq_empty(void)
{
bool rc;
struct os_mbuf_pkthdr *mp;
mp = STAILQ_FIRST(&g_lora_mac_data.lm_txq.mq_head);
if (mp == NULL) {
rc = true;
} else {
rc = false;
}
return rc;
}
/**
* lora node mac mcps indicate
*
* MAC indication handler
*/
void
lora_node_mac_mcps_indicate(void)
{
int rc;
struct os_mbuf *om;
struct lora_pkt_info *lpkt;
/*
* Not sure if this is possible, but port 0 is not a valid application port.
* If the port is 0 do not send indicate
*/
if (g_lora_mac_data.rxpkt.port == 0) {
/* XXX: count a statistic? */
return;
}
om = lora_pkt_alloc();
if (om) {
/* Copy data into mbuf */
rc = os_mbuf_copyinto(om, 0, g_lora_mac_data.rxbuf,
g_lora_mac_data.rxbufsize);
if (rc) {
os_mbuf_free_chain(om);
return;
}
/* Set lora packet info */
lpkt = LORA_PKT_INFO_PTR(om);
memcpy(lpkt, &g_lora_mac_data.rxpkt, sizeof(struct lora_pkt_info));
lora_app_mcps_indicate(om);
} else {
/* XXX: cant do anything until the lower stack gets modified */
STATS_INC(lora_mac_stats, no_bufs);
}
}
static uint8_t
lora_node_get_batt_status(void)
{
/* NOTE: 0 means connected to external power supply. */
return 0;
}
/**
* Process transmit enqueued event
*
* @param ev Pointer to transmit enqueue event
*/
static void
lora_mac_proc_tx_q_event(struct os_event *ev)
{
LoRaMacStatus_t rc;
LoRaMacEventInfoStatus_t evstatus;
LoRaMacTxInfo_t txinfo;
struct lora_pkt_info *lpkt;
struct os_mbuf *om;
struct os_mbuf_pkthdr *mp;
/* Stop the transmit callback because something was just queued */
os_callout_stop(&g_lora_mac_data.lm_txq_timer);
/* If busy just leave */
if (lora_mac_tx_state() == LORAMAC_STATUS_BUSY) {
/* XXX: this should not be needed */
lora_node_reset_txq_timer();
return;
}
/*
* Check if possible to send frame. If a MAC command length error we
* need to send an empty, unconfirmed frame to flush mac commands.
*/
lpkt = NULL;
while (1) {
mp = STAILQ_FIRST(&g_lora_mac_data.lm_txq.mq_head);
if (mp == NULL) {
/* If an ack has been requested, send one */
if (lora_mac_srv_ack_requested()) {
g_lora_node_last_tx_mac_cmd = 0;
goto send_empty_msg;
} else {
/* Check for any mac commands */
if (lora_mac_cmd_buffer_len() != 0) {
g_lora_node_last_tx_mac_cmd = 1;
goto send_empty_msg;
}
}
break;
}
rc = LoRaMacQueryTxPossible(mp->omp_len, &txinfo);
if (rc == LORAMAC_STATUS_MAC_CMD_LENGTH_ERROR) {
/*
* XXX: an ugly hack for now. If the server decides to send MAC
* commands all the time, it could be that we never send the
* data packet enqueued as we cannot add more data to it. For now,
* just alternate between sending the packet on the queue and
* a mac command. Yes, ugly.
*/
if (g_lora_node_last_tx_mac_cmd) {
rc = LORAMAC_STATUS_OK;
goto send_from_txq;
}
g_lora_node_last_tx_mac_cmd = 1;
/* Need to flush MAC commands. Send empty unconfirmed frame */
STATS_INC(lora_mac_stats, tx_mac_flush);
/* NOTE: no need to get a mbuf. */
send_empty_msg:
lpkt = &g_lora_mac_data.txpkt;
g_lora_mac_data.curtx = lpkt;
om = NULL;
memset(lpkt, 0, sizeof(struct lora_pkt_info));
lpkt->pkt_type = MCPS_UNCONFIRMED;
rc = LORAMAC_STATUS_OK;
} else {
send_from_txq:
om = os_mqueue_get(&g_lora_mac_data.lm_txq);
assert(om != NULL);
lpkt = LORA_PKT_INFO_PTR(om);
g_lora_mac_data.curtx = lpkt;
g_lora_node_last_tx_mac_cmd = 0;
}
g_lora_mac_data.cur_tx_mbuf = om;
if (rc != LORAMAC_STATUS_OK) {
/* Check if length error or mac command error */
if (rc == LORAMAC_STATUS_LENGTH_ERROR) {
evstatus = LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR;
} else {
evstatus = LORAMAC_EVENT_INFO_STATUS_ERROR;
}
goto proc_txq_om_done;
}
/* Form MCPS request */
switch (lpkt->pkt_type) {
case MCPS_UNCONFIRMED:
evstatus = LORAMAC_EVENT_INFO_STATUS_OK;
break;
case MCPS_CONFIRMED:
evstatus = LORAMAC_EVENT_INFO_STATUS_OK;
break;
case MCPS_PROPRIETARY:
/* XXX: not allowed */
evstatus = LORAMAC_EVENT_INFO_STATUS_ERROR;
break;
default:
/* XXX: this is an error */
evstatus = LORAMAC_EVENT_INFO_STATUS_ERROR;
break;
}
if (evstatus == LORAMAC_EVENT_INFO_STATUS_OK) {
rc = LoRaMacMcpsRequest(om, lpkt);
switch (rc) {
case LORAMAC_STATUS_OK:
/* Transmission started. */
evstatus = LORAMAC_EVENT_INFO_STATUS_OK;
break;
case LORAMAC_STATUS_NO_NETWORK_JOINED:
evstatus = LORAMAC_EVENT_INFO_STATUS_NO_NETWORK_JOINED;
break;
case LORAMAC_STATUS_LENGTH_ERROR:
evstatus = LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR;
break;
/* XXX: handle BUSY differently here I think. Need to requeue
at head */
default:
evstatus = LORAMAC_EVENT_INFO_STATUS_ERROR;
break;
}
if (evstatus == LORAMAC_EVENT_INFO_STATUS_OK) {
return;
}
}
/*
* If we get here there was an error sending. Send confirm and
* continue processing transmit queue.
*/
proc_txq_om_done:
lpkt->status = evstatus;
lora_app_mcps_confirm(om);
}
}
static void
lora_mac_txq_timer_cb(struct os_event *ev)
{
lora_mac_proc_tx_q_event(NULL);
}
/**
* The LoRa mac task
*
* @param arg Pointer to arg passed to task (currently NULL).
*/
void
lora_mac_task(void *arg)
{
/* Process events */
while (1) {
os_eventq_run(&g_lora_mac_data.lm_evq);
}
}
#endif
#if !MYNEWT_VAL(LORA_APP_AUTO_JOIN)
/**
* Called to check if this device is joined to a network.
*
*
* @return int LORA_APP_STATUS_ALREADY_JOINED if joined.
* LORA_APP_STATUS_NO_NETWORK if not joined
*/
int
lora_node_chk_if_joined(void)
{
int rc;
MibRequestConfirm_t mibReq;
/* Check to see if we are already joined. If so, return an error */
mibReq.Type = MIB_NETWORK_JOINED;
LoRaMacMibGetRequestConfirm(&mibReq);
if (mibReq.Param.IsNetworkJoined == true) {
rc = LORA_APP_STATUS_ALREADY_JOINED;
} else {
rc = LORA_APP_STATUS_NO_NETWORK;
}
return rc;
}
/**
* Called when the application wants to perform the join process.
*
* @return int A lora app return code
*/
int
lora_node_join(uint8_t *dev_eui, uint8_t *app_eui, uint8_t *app_key,
uint8_t trials)
{
int rc;
rc = lora_node_chk_if_joined();
if (rc != LORA_APP_STATUS_ALREADY_JOINED) {
/* Send event to MAC */
g_lm_join_ev_arg.dev_eui = dev_eui;
g_lm_join_ev_arg.app_eui = app_eui;
g_lm_join_ev_arg.app_key = app_key;
g_lm_join_ev_arg.trials = trials;
os_eventq_put(&g_lora_mac_data.lm_evq, &g_lora_mac_data.lm_join_ev);
rc = LORA_APP_STATUS_OK;
}
return rc;
}
/**
* Called when the application wants to perform a link check
*
* @return int A lora app return code
*/
int
lora_node_link_check(void)
{
int rc;
rc = lora_node_chk_if_joined();
if (rc == LORA_APP_STATUS_ALREADY_JOINED) {
os_eventq_put(&g_lora_mac_data.lm_evq, &g_lora_mac_data.lm_link_chk_ev);
rc = LORA_APP_STATUS_OK;
}
return rc;
}
#if !MYNEWT_VAL(LORA_NODE_CLI)
static void
lora_mac_join_event(struct os_event *ev)
{
MlmeReq_t mlmeReq;
LoRaMacStatus_t rc;
LoRaMacEventInfoStatus_t status;
struct lm_join_ev_arg_obj *lmj;
/* XXX: should we check if we are joined here too? Could we have
joined in meantime? */
lmj = (struct lm_join_ev_arg_obj *)ev->ev_arg;
mlmeReq.Type = MLME_JOIN;
mlmeReq.Req.Join.DevEui = lmj->dev_eui;
mlmeReq.Req.Join.AppEui = lmj->app_eui;
mlmeReq.Req.Join.AppKey = lmj->app_key;
mlmeReq.Req.Join.NbTrials = lmj->trials;
rc = LoRaMacMlmeRequest(&mlmeReq);
switch (rc) {
case LORAMAC_STATUS_OK:
status = LORAMAC_EVENT_INFO_STATUS_OK;
break;
/* XXX: for now, just report this generic error. */
default:
status = LORAMAC_EVENT_INFO_STATUS_ERROR;
break;
}
if (status != LORAMAC_EVENT_INFO_STATUS_OK) {
if (lora_join_cb_func) {
lora_join_cb_func(status, 0);
}
}
}
/**
* lora mac link chk event
*
* Called from the Lora MAC task when a link check event has been posted
* to it. This event gets posted when link check API gets called.
*
*
* @param ev
*/
static void
lora_mac_link_chk_event(struct os_event *ev)
{
MlmeReq_t mlmeReq;
LoRaMacStatus_t rc;
LoRaMacEventInfoStatus_t status;
mlmeReq.Type = MLME_LINK_CHECK;
rc = LoRaMacMlmeRequest(&mlmeReq);
switch (rc) {
case LORAMAC_STATUS_OK:
status = LORAMAC_EVENT_INFO_STATUS_OK;
break;
/* XXX: for now, just report this generic error. */
default:
status = LORAMAC_EVENT_INFO_STATUS_ERROR;
break;
}
lora_node_log(LORA_NODE_LOG_LINK_CHK, 0, 0, status);
/* If status is OK */
if (status != LORAMAC_EVENT_INFO_STATUS_OK) {
if (lora_link_chk_cb_func) {
lora_link_chk_cb_func(status, 0, 0);
}
} else {
/*
* If nothing on transmit queue, we need to send event so that link
* check gets sent.
*/
if (lora_node_txq_empty()) {
lora_node_chk_txq();
}
}
}
#endif
#endif
/**
* Helper routine to track measurement averages.
*
* @param orig State variable
* @param sample Latest sample to add to average.
*/
static void
lora_node_calc_avg(int16_t *orig, uint16_t sample)
{
int16_t tmp;
tmp = *orig;
if (tmp) {
/*
* The following magic is equivalent to algorithm
* avg = sample/16 + avg*15/16 in fixed point.
*/
tmp += (sample << LORA_DELTA_SHIFT) - (tmp >> LORA_AVG_SHIFT);
*orig = tmp;
} else {
/*
* No measurement yet.
*/
*orig = sample << (LORA_AVG_SHIFT + LORA_DELTA_SHIFT);
}
}
void
lora_node_qual_sample(int16_t rssi, int16_t snr)
{
lora_node_calc_avg(&g_lora_mac_data.lm_rssi_avg, rssi);
lora_node_calc_avg(&g_lora_mac_data.lm_snr_avg, snr);
}
/**
* Report tracked RSSI/SNR averages
*
* @param rssi Pointer to where to store RSSI average.
* @param snr Pointer to where to store SNR average.
*
* @return 0 if returned data is valid, non-zero otherwise
*/
int
lora_node_link_qual(int16_t *rssi, int16_t *snr)
{
struct lora_mac_obj *lmo = &g_lora_mac_data;
if (lmo->lm_rssi_avg || lmo->lm_snr_avg) {
/*
* Rounds down. XXX
*/
*rssi = g_lora_mac_data.lm_rssi_avg >> (LORA_AVG_SHIFT +
LORA_DELTA_SHIFT);
*snr = g_lora_mac_data.lm_snr_avg >> (LORA_AVG_SHIFT +
LORA_DELTA_SHIFT);
return 0;
} else {
return -1;
}
}
struct os_eventq *
lora_node_mac_evq_get(void)
{
return &g_lora_mac_data.lm_evq;
}
void
lora_node_init(void)
{
int rc;
#if !MYNEWT_VAL(LORA_NODE_CLI)
LoRaMacStatus_t lms;
LoRaMacCallback_t lora_cb;
#endif
rc = stats_init_and_reg(
STATS_HDR(lora_mac_stats),
STATS_SIZE_INIT_PARMS(lora_mac_stats, STATS_SIZE_32),
STATS_NAME_INIT_PARMS(lora_mac_stats), "lora_mac");
SYSINIT_PANIC_ASSERT(rc == 0);
#if MYNEWT_VAL(LORA_NODE_CLI)
lora_cli_init();
#else
/* Init app */
lora_app_init();
#if MYNEWT_VAL(LORA_NODE_LOG_CLI) == 1
lora_cli_init();
#endif
/*--- MAC INIT ---*/
/* Initialize eventq */
os_eventq_init(&g_lora_mac_data.lm_evq);
/* Set up transmit done queue and event */
os_mqueue_init(&g_lora_mac_data.lm_txq, lora_mac_proc_tx_q_event, NULL);
/* Create the mac task */
os_task_init(&g_lora_mac_task, "loramac", lora_mac_task, NULL,
MYNEWT_VAL(LORA_MAC_PRIO), OS_WAIT_FOREVER, g_lora_mac_stack,
LORA_MAC_STACK_SIZE);
/* Initialize join event */
g_lora_mac_data.lm_join_ev.ev_cb = lora_mac_join_event;
g_lora_mac_data.lm_join_ev.ev_arg = &g_lm_join_ev_arg;
/* Initialize link check event */
g_lora_mac_data.lm_link_chk_ev.ev_cb = lora_mac_link_chk_event;
/* Initialize the transmit queue timer */
os_callout_init(&g_lora_mac_data.lm_txq_timer,
&g_lora_mac_data.lm_evq, lora_mac_txq_timer_cb, NULL);
/* Initialize the LoRa mac */
lora_cb.GetBatteryLevel = lora_node_get_batt_status;
lms = LoRaMacInitialization(&lora_cb, LORA_NODE_REGION);
assert(lms == LORAMAC_STATUS_OK);
#endif
}