end node on synchronous star LoRa network.

Dependencies:   SX127x sx12xx_hal TSL2561

radio chip selection

Radio chip driver is not included, allowing choice of radio device.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
if you're using SX1280, then import sx1280 driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.

This project for use with LoRaWAN_singlechannel_gateway project.

Alternately gateway running on raspberry pi can be used as gateway.

LoRaWAN on single radio channel

Network description is at gateway project page. Synchronous star network.

Hardware Support

This project supports SX1276 and SX1272, sx126x kit, sx126x shield, and sx128x 2.4GHz. The ST board B-L072Z-LRWAN1 is also supported (TypeABZ module). When B-L072Z-LRWAN1 target is selected, TARGET_DISCO_L072CZ_LRWAN1 is defined by tools, allowing correct radio driver configuration for this platform. Alternately, any mbed board that can use LoRa radio shield board should work, but NUCLEO boards are tested.

End-node Unique ID

DevEUI is created from CPU serial number. AppEUI and AppKey are declared as software constants.

End-node Configuration

Data rate definition LORAMAC_DEFAULT_DATARATE configured in LoRaMac-definitions.h. See gateway project page for configuration of gateway.
LoRaWAN addressing is configured in Comissioning.h; only OTA mode is functional.
Header file board/lora_config.h, selects application layer options (i.e. sensors) to be compiled in.

Serial Interface

Serial port operates at 115200bps.
Application layer single_us915_main.cpp User button triggers uplink (i.e. blue button on nucleo board), or jumper enables continuously sends repeated uplink packets. The MAC layer holds each uplink request until the allocated timeslot.

commandargumentsdescription
?-print available commands
. (period)-print status (DevEUI, DevAddr, etc)
ullength integerset payload length of test uplink packets

sensor demo

Selected grove sensors may be plugged into SX1272 shield.
To enable, edit lora_config.h to define SENSORS.

Sensor connections on SX1272MB2xAS:

D8 D9: buttonRX TX: (unused)A3 A4: Rotary Angle Sensor
D6 D7: RGB LEDSCL SDA: digital light sensorA1 A2: Rotary Angle Sensor

Digital input pin, state reported via uplink: PC8
Digital output pin, controlled via downlink: PC6
PWM out: PB_10

Jumper enables auto-repeated transmit: PC10 and PC12 on NUCLEO board, located on end of morpho headers nearby JP4.

mac/LoRaMac.cpp

Committer:
dudmuck
Date:
2017-08-02
Revision:
18:9ac71c0eb70d
Parent:
17:3215f12051f9
Child:
20:42839629a5dc

File content as of revision 18:9ac71c0eb70d:

/*
 / _____)             _              | |
( (____  _____ ____ _| |_ _____  ____| |__
 \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 _____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
    (C)2013 Semtech
 ___ _____ _   ___ _  _____ ___  ___  ___ ___
/ __|_   _/_\ / __| |/ / __/ _ \| _ \/ __| __|
\__ \ | |/ _ \ (__| ' <| _| (_) |   / (__| _|
|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___|
embedded.connectivity.solutions===============

Description: LoRa MAC layer implementation

License: Revised BSD License, see LICENSE.TXT file include in the project

Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jäckle ( STACKFORCE )
*/
#include <math.h>
#include "board.h"

#include "LoRaMacCrypto.h"
#include "LoRaMac.h"
#include "LoRaMacTest.h"

#define TX_DEBUG

#define PING_SLOT_RESOLUTION_us         30000

/*!
 * Maximum PHY layer payload size
 */
#define LORAMAC_PHY_MAXPAYLOAD                      255

/*!
 * Maximum MAC commands buffer size
 */
#define LORA_MAC_COMMAND_MAX_LENGTH                 15

/*!
 * FRMPayload overhead to be used when setting the Radio.SetMaxPayloadLength
 * in RxWindowSetup function.
 * Maximum PHYPayload = MaxPayloadOfDatarate/MaxPayloadOfDatarateRepeater + LORA_MAC_FRMPAYLOAD_OVERHEAD
 */
#define LORA_MAC_FRMPAYLOAD_OVERHEAD                13 // MHDR(1) + FHDR(7) + Port(1) + MIC(4)

/*!
 * LoRaMac duty cycle for the back-off procedure during the first hour.
 */
#define BACKOFF_DC_1_HOUR                           100

/*!
 * LoRaMac duty cycle for the back-off procedure during the next 10 hours.
 */
#define BACKOFF_DC_10_HOURS                         1000

/*!
 * LoRaMac duty cycle for the back-off procedure during the next 24 hours.
 */
#define BACKOFF_DC_24_HOURS                         10000

/*!
 * Device IEEE EUI
 */
static uint8_t *LoRaMacDevEui;

/*!
 * Application IEEE EUI
 */
static uint8_t *LoRaMacAppEui;

/*!
 * AES encryption/decryption cipher application key
 */
static uint8_t *LoRaMacAppKey;

/*!
 * AES encryption/decryption cipher network session key
 */
static uint8_t LoRaMacNwkSKey[] =
{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/*!
 * AES encryption/decryption cipher application session key
 */
static uint8_t LoRaMacAppSKey[] =
{
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

/*!
 * Device nonce is a random value extracted by issuing a sequence of RSSI
 * measurements
 */
static uint16_t LoRaMacDevNonce;

/*!
 * Network ID ( 3 bytes )
 */
static uint32_t LoRaMacNetID;

/*!
 * Mote Address
 */
static uint32_t LoRaMacDevAddr;

/*!
 * Multicast channels linked list
 */
static MulticastParams_t *MulticastChannels = NULL;

/*!
 * Actual device class
 */
static DeviceClass_t LoRaMacDeviceClass;

/*!
 * Indicates if the node is connected to a private or public network
 */
static bool PublicNetwork;

/*!
 * Buffer containing the data to be sent or received.
 */
static uint8_t LoRaMacBuffer[LORAMAC_PHY_MAXPAYLOAD];

/*!
 * Length of packet in LoRaMacBuffer
 */
static uint16_t LoRaMacBufferPktLen = 0;

/*!
 * Length of the payload in LoRaMacBuffer
 */
static uint8_t LoRaMacTxPayloadLen = 0;

/*!
 * Buffer containing the upper layer data.
 */
static uint8_t LoRaMacRxPayload[LORAMAC_PHY_MAXPAYLOAD];

/*!
 * LoRaMAC frame counter. Each time a packet is sent the counter is incremented.
 * Only the 16 LSB bits are sent
 */
static uint32_t UpLinkCounter = 0;

/*!
 * LoRaMAC frame counter. Each time a packet is received the counter is incremented.
 * Only the 16 LSB bits are received
 */
static uint32_t DownLinkCounter = 0;

/*!
 * IsPacketCounterFixed enables the MIC field tests by fixing the
 * UpLinkCounter value
 */
//static bool IsUpLinkCounterFixed = false;

/*!
 * Used for test purposes. Disables the opening of the reception windows.
 */
static bool IsRxWindowsEnabled = true;
LowPowerTimeout rx_timeout;

/*!
 * Indicates if the MAC layer has already joined a network.
 */
static bool IsLoRaMacNetworkJoined = false;

/*!
 * If the node has sent a FRAME_TYPE_DATA_CONFIRMED_UP this variable indicates
 * if the nodes needs to manage the server acknowledgement.
 */
static bool NodeAckRequested = false;

/*!
 * If the server has sent a FRAME_TYPE_DATA_CONFIRMED_DOWN this variable indicates
 * if the ACK bit must be set for the next transmission
 */
static bool SrvAckRequested = false;

/*!
 * Indicates if the MAC layer wants to send MAC commands
 */
static bool MacCommandsInNextTx = false;

/*!
 * Contains the current MacCommandsBuffer index
 */
static uint8_t MacCommandsBufferIndex = 0;

/*!
 * Contains the current MacCommandsBuffer index for MAC commands to repeat
 */
static uint8_t MacCommandsBufferToRepeatIndex = 0;

/*!
 * Buffer containing the MAC layer commands
 */
static uint8_t MacCommandsBuffer[LORA_MAC_COMMAND_MAX_LENGTH];

/*!
 * Buffer containing the MAC layer commands which must be repeated
 */
static uint8_t MacCommandsBufferToRepeat[LORA_MAC_COMMAND_MAX_LENGTH];

#define BEACON_SIZE                 6   /* bytes */
#define BEACON_CHANNEL_DR           LORAMAC_DEFAULT_DATARATE


#if defined( USE_BAND_915_SINGLE )
    /*!
     * Data rates table definition
     */
    const uint8_t Datarates[]  = { 10, 9, 8,  7,  8,  0,  0, 0, 12, 11, 10, 9, 8, 7, 0, 0 };

    /*!
     * Bandwidths table definition in Hz
     */
    const uint32_t Bandwidths[] = { 125000, 125000, 125000, 125000, 500000, 0, 0, 0, 500000, 500000, 500000, 500000, 500000, 500000, 0, 0 };

    /*!
     * LoRaMac bands
     */
    /*static Band_t Bands[LORA_MAX_NB_BANDS] =
    {
        BAND0,
    };*/

    /*!
     * Tx output powers table definition
     */
    const int8_t TxPowers[]    = { 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 };
    #define LORAMAC_FIRST_CHANNEL           ( (uint32_t)910.0e6 )
    #define LORAMAC_STEPWIDTH_CHANNEL       ( (uint32_t)800e3 )
    #define LORA_MAX_NB_CHANNELS                        8

    #define LORA_BANDWIDTH           2   /* 2=500KHz */

    /* measured beacon duration (all at bw500, 6 byte fixed payload length)
     * latency assigned for correct rx-before-tx measurement */
    #if (LORAMAC_DEFAULT_DATARATE == DR_8)
        #define BEACON_RXDONE_LATENCY_us        6000
        #define BEACON_TOA_us                   209000
    #elif (LORAMAC_DEFAULT_DATARATE == DR_9)
        #define BEACON_RXDONE_LATENCY_us        3500
        #define BEACON_TOA_us                   105000
    #elif (LORAMAC_DEFAULT_DATARATE == DR_10)
        #define BEACON_RXDONE_LATENCY_us        1460
        #define BEACON_TOA_us                   52800
    #elif (LORAMAC_DEFAULT_DATARATE == DR_11)
        #define BEACON_RXDONE_LATENCY_us        2000
        #define BEACON_TOA_us                   26000
    #elif (LORAMAC_DEFAULT_DATARATE == DR_12)
        #define BEACON_RXDONE_LATENCY_us        1500
        #define BEACON_TOA_us                   12800
    #elif (LORAMAC_DEFAULT_DATARATE == DR_13)
        #define BEACON_RXDONE_LATENCY_us        1300
        #define BEACON_TOA_us                   6560
    #else
        #error datarate
    #endif

    /* end us915 */
#elif defined(USE_BAND_433) && defined(ENABLE_SX1276)
    const uint8_t Datarates[]  = { 12, 11, 10, 9, 8, 7, 7, 50 };
    const uint32_t Bandwidths[] = { 125000, 125000, 125000, 125000, 125000, 125000, 250000, 0 };
    const int8_t TxPowers[] = { 10, 7, 4, 1, -2, -5 };
    #define LORA_BANDWIDTH           0   /* 0=125KHz */

    #define LORAMAC_FIRST_CHANNEL           ( (uint32_t)433.32e6 )
    #define LORAMAC_STEPWIDTH_CHANNEL       ( (uint32_t)200e3 )
    #define LORA_MAX_NB_CHANNELS                        7

    #if (LORAMAC_DEFAULT_DATARATE == DR_0)
        #define BEACON_RXDONE_LATENCY_us        27500
        #define BEACON_TOA_us                   828100
    #elif (LORAMAC_DEFAULT_DATARATE == DR_1)
        #define BEACON_RXDONE_LATENCY_us        12500
        #define BEACON_TOA_us                   414400
    #elif (LORAMAC_DEFAULT_DATARATE == DR_2)
        #define BEACON_RXDONE_LATENCY_us        6000
        #define BEACON_TOA_us                   207500
    #elif (LORAMAC_DEFAULT_DATARATE == DR_3)
        #define BEACON_RXDONE_LATENCY_us        3840
        #define BEACON_TOA_us                   103000
    #elif (LORAMAC_DEFAULT_DATARATE == DR_4)
        #define BEACON_RXDONE_LATENCY_us        2800
        #define BEACON_TOA_us                   51000
    #elif (LORAMAC_DEFAULT_DATARATE == DR_5)
        #define BEACON_RXDONE_LATENCY_us        1400
        #define BEACON_TOA_us                   25800
    #else
        #error datarate
    #endif

    /* end USE_BAND_433 */
#else
    #error "Please define a frequency band in the compiler options."
#endif

static ChannelParams_t Channels[LORA_MAX_NB_CHANNELS];  // populated in init

#define BEACON_GUARD_us         2000000     // pre-beacon start
#define BEACON_RESERVED_us      2120000     // post-beacon start

#define BEACON_MIN_SYMBOL_TIMEOUT       8

LowPowerTimer lp_timer;

#ifdef DEBUG_GWTX_JUMPER
InterruptIn gwtx_pin(DEBUG_GWTX_JUMPER);
unsigned int gwtx_rise_us;

void gwtx_pin_callback()
{
    gwtx_rise_us = lp_timer.read_us();
}
#endif

/*!
 * LoRaMac parameters
 */
LoRaMacParams_t LoRaMacParams;

/*!
 * LoRaMac default parameters
 */
LoRaMacParams_t LoRaMacParamsDefaults;

/*!
 * Uplink messages repetitions counter
 */
static uint8_t ChannelsNbRepCounter = 0;

static uint32_t AggregatedLastTxDoneTime_us;

/*!
 * Current channel index
 */
static uint8_t Channel;

/*!
 * Stores the time at LoRaMac initialization.
 *
 * \remark Used for the BACKOFF_DC computation.
 */
//static TimerTime_t LoRaMacInitializationTime = 0;

/*!
 * LoRaMac upper layer event functions
 */
static LoRaMacPrimitives_t *LoRaMacPrimitives;

/*!
 * LoRaMac upper layer callback functions
 */
static LoRaMacCallback_t *LoRaMacCallbacks;

/*!
 * Radio events function pointer
 */
static RadioEvents_t RadioEvents;

/*!
 * LoRaMac duty cycle delayed Tx timer
 */
LowPowerTimeout tx_timeout;


/*!
 * LoRaMac reception windows delay
 * \remark normal frame: RxWindowXDelay = ReceiveDelayX - RADIO_WAKEUP_TIME
 *         join frame  : RxWindowXDelay = JoinAcceptDelayX - RADIO_WAKEUP_TIME
 */
static uint32_t RxWindowDelay_us;

typedef enum {
    BEACON_STATE_NONE = 0,
    BEACON_STATE_FIRST_ACQ,
    BEACON_STATE_ACQ_ERROR,
    BEACON_STATE_LOCKED_,
} beacon_state_e;

static struct beacon_struct {
    int rx_precession_us; // positive: rxing before tx start, negative: rxing after tx start
    unsigned int RxBeaconSetupAt_us;
    unsigned int LastBeaconRx_us;   // updated only at beacon reception
    int last_BeaconRxTimerError_us;
    int known_working_BeaconRxTimerError_us;

    float symbol_period_secs;

    uint8_t Precess_symbols;    // how many symbols we want to start receiver before expected transmitter
    uint16_t SymbolTimeout;
    float SymbolTimeout_sec;
    uint8_t num_missed;

    beacon_state_e state;

    uint16_t tx_slot_offset;
    uint16_t periodicity_slots;

    LowPowerTimeout timeout_rx;
    LowPowerTimeout timeout_guard;
    bool guard;
    unsigned guard_at;
} BeaconCtx;
bool expecting_beacon;

/*!
 * Rx window parameters
 */
typedef struct
{
    int8_t Datarate;
    uint8_t Bandwidth;
    uint32_t RxWindowTimeout;
    int32_t RxOffset;
}RxConfigParams_t;

/*!
 * Rx windows params
 */
static RxConfigParams_t RxWindowsParam;


/*!
 * Acknowledge timeout timer. Used for packet retransmissions.
 */
static LowPowerTimeout AckTimeoutTimer;

uint32_t TxTimeOnAir = 0;

/*!
 * Number of trials for the Join Request
 */
static uint8_t JoinRequestTrials;

/*!
 * Maximum number of trials for the Join Request
 */
static uint8_t MaxJoinRequestTrials;

/*!
 * Structure to hold an MCPS indication data.
 */
static McpsIndication_t McpsIndication;

/*!
 * Structure to hold MCPS confirm data.
 */
static McpsConfirm_t McpsConfirm;

/*!
 * Structure to hold MLME confirm data.
 */
static MlmeConfirm_t MlmeConfirm;

/*!
 * Structure to hold MLME indication data.
 */
static MlmeIndication_t MlmeIndication;

/*!
 * LoRaMac tx/rx operation state
 */
LoRaMacFlags_t LoRaMacFlags;

/*!
 * \brief Function to be executed on Radio Tx Done event
 */
static void OnRadioTxDone( unsigned int tx_done_us );
unsigned int TxDone_us;

/*!
 * \brief This function prepares the MAC to abort the execution of function
 *        OnRadioRxDone in case of a reception error.
 */
static void PrepareRxDoneAbort( void );

/*!
 * \brief Function to be executed on Radio Rx Done event
 */
static void OnRadioRxDone(unsigned rx_us, uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );

/*!
 * \brief Function executed on Radio Tx Timeout event
 */
static void OnRadioTxTimeout( void );

/*!
 * \brief Function executed on Radio Rx error event
 */
static void OnRadioRxError( void );

/*!
 * \brief Function executed on Radio Rx Timeout event
 */
static void OnRadioRxTimeout( void );

static void OnRxWindowTimerEvent( void );

/*!
 * \brief Function executed on AckTimeout timer event
 */
static void OnAckTimeoutTimerEvent( void );


/*!
 * \brief Initializes and opens the reception window
 *
 * \param [IN] freq window channel frequency
 * \param [IN] datarate window channel datarate
 * \param [IN] bandwidth window channel bandwidth
 * \param [IN] timeout window channel timeout
 *
 * \retval status Operation status [true: Success, false: Fail]
 */
static bool RxWindowSetup( uint32_t freq, int8_t datarate, uint32_t bandwidth, uint16_t timeout, bool rxContinuous );

/*!
 * \brief Adds a new MAC command to be sent.
 *
 * \Remark MAC layer internal function
 *
 * \param [in] cmd MAC command to be added
 *                 [MOTE_MAC_LINK_CHECK_REQ,
 *                  MOTE_MAC_LINK_ADR_ANS,
 *                  MOTE_MAC_DUTY_CYCLE_ANS,
 *                  MOTE_MAC_RX2_PARAM_SET_ANS,
 *                  MOTE_MAC_DEV_STATUS_ANS
 *                  MOTE_MAC_NEW_CHANNEL_ANS]
 * \param [in] p1  1st parameter ( optional depends on the command )
 * \param [in] p2  2nd parameter ( optional depends on the command )
 *
 * \retval status  Function status [0: OK, 1: Unknown command, 2: Buffer full]
 */
static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 );

/*!
 * \brief Parses the MAC commands which must be repeated.
 *
 * \Remark MAC layer internal function
 *
 * \param [IN] cmdBufIn  Buffer which stores the MAC commands to send
 * \param [IN] length  Length of the input buffer to parse
 * \param [OUT] cmdBufOut  Buffer which stores the MAC commands which must be
 *                         repeated.
 *
 * \retval Size of the MAC commands to repeat.
 */
static uint8_t ParseMacCommandsToRepeat( uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut );

/*!
 * \brief Verifies, if a value is in a given range.
 *
 * \param value Value to verify, if it is in range
 *
 * \param min Minimum possible value
 *
 * \param max Maximum possible value
 *
 * \retval Returns the maximum valid tx power
 */
static bool ValueInRange( int8_t value, int8_t min, int8_t max );


/*!
 * \brief Decodes MAC commands in the fOpts field and in the payload
 */
static void ProcessMacCommands( uint8_t *payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr );

/*!
 * \brief LoRaMAC layer generic send frame
 *
 * \param [IN] macHdr      MAC header field
 * \param [IN] fPort       MAC payload port
 * \param [IN] fBuffer     MAC data buffer to be sent
 * \param [IN] fBufferSize MAC data buffer size
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t Send( LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer, uint16_t fBufferSize );

/*!
 * \brief LoRaMAC layer frame buffer initialization
 *
 * \param [IN] macHdr      MAC header field
 * \param [IN] fCtrl       MAC frame control field
 * \param [IN] fOpts       MAC commands buffer
 * \param [IN] fPort       MAC payload port
 * \param [IN] fBuffer     MAC data buffer to be sent
 * \param [IN] fBufferSize MAC data buffer size
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize );

/*
 * \brief Schedules the frame according to the duty cycle
 *
 * \retval Status of the operation
 */
static LoRaMacStatus_t ScheduleTx( void );


/*!
 * \brief LoRaMAC layer prepared frame buffer transmission with channel specification
 *
 * \remark PrepareFrame must be called at least once before calling this
 *         function.
 *
 * \param [IN] channel     Channel parameters
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t SendFrameOnChannel( ChannelParams_t channel );

/*!
 * \brief Sets the radio in continuous transmission mode
 *
 * \remark Uses the radio parameters set on the previous transmission.
 *
 * \param [IN] timeout     Time in seconds while the radio is kept in continuous wave mode
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t SetTxContinuousWave( uint16_t timeout );

/*!
 * \brief Sets the radio in continuous transmission mode
 *
 * \remark Uses the radio parameters set on the previous transmission.
 *
 * \param [IN] timeout     Time in seconds while the radio is kept in continuous wave mode
 * \param [IN] frequency   RF frequency to be set.
 * \param [IN] power       RF ouptput power to be set.
 * \retval status          Status of the operation.
 */
LoRaMacStatus_t SetTxContinuousWave1( uint16_t timeout, uint32_t frequency, uint8_t power );

/*!
 * \brief Resets MAC specific parameters to default
 */
static void ResetMacParameters( void );

void
loramac_print_status()
{
    isr_printf("DR%u=sf%u guard:%d\r\n",
        LoRaMacParams.ChannelsDatarate_fixed,
        Datarates[LoRaMacParams.ChannelsDatarate_fixed],
        BeaconCtx.guard
    );
}

/*
 * Rx window precise timing
 *
 * For more details please consult the following document, chapter 3.1.2.
 * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf
 * or
 * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf
 *
 *                 Downlink start: T = Tx + 1s (+/- 20 us)
 *                            |
 *             TRxEarly       |        TRxLate
 *                |           |           |
 *                |           |           +---+---+---+---+---+---+---+---+
 *                |           |           |       Latest Rx window        |
 *                |           |           +---+---+---+---+---+---+---+---+
 *                |           |           |
 *                +---+---+---+---+---+---+---+---+
 *                |       Earliest Rx window      |
 *                +---+---+---+---+---+---+---+---+
 *                            |
 *                            +---+---+---+---+---+---+---+---+
 *Downlink preamble 8 symbols |   |   |   |   |   |   |   |   |
 *                            +---+---+---+---+---+---+---+---+
 *
 *                     Worst case Rx window timings
 *
 * TRxLate  = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME
 * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME
 *
 * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR
 *
 * RxOffset = ( TRxLate + TRxEarly ) / 2
 *
 * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR
 * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME
 *
 * Minimal value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol
 */
/*!
 * Computes the Rx window parameters.
 *
 * \param [IN] datarate     Rx window datarate to be used
 * \param [IN] rxError      Maximum timing error of the receiver. in milliseconds
 *                          The receiver will turn on in a [-rxError : +rxError] ms
 *                          interval around RxOffset
 *
 * \retval rxConfigParams   Returns a RxConfigParams_t structure.
 */
static RxConfigParams_t ComputeRxWindowParameters( int8_t datarate, uint32_t rxError );

static void OnRadioTxDone( unsigned int tx_done_us )
{
    // Setup timers
    if (IsRxWindowsEnabled)
    {
        rx_timeout.attach_us(&OnRxWindowTimerEvent, RxWindowDelay_us);
        if (NodeAckRequested)
        {
            AckTimeoutTimer.attach_us(&OnAckTimeoutTimerEvent, (RxWindowDelay_us/1000) + ACK_TIMEOUT_us + randr(-ACK_TIMEOUT_RND_us, ACK_TIMEOUT_RND_us));
        }
    }
    else
    {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
        MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT;

        if( LoRaMacFlags.Value == 0 )
        {
            LoRaMacFlags.Bits.McpsReq = 1;
        }
        LoRaMacFlags.Bits.MacDone = 1;
    }
    Radio.Sleep( );   
    TxDone_us = tx_done_us; 

    // Update Aggregated last tx done time
    AggregatedLastTxDoneTime_us = tx_done_us;
    // Update Backoff

    if (!NodeAckRequested)
    {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
        ChannelsNbRepCounter++;
    }
    
    MlmeIndication.MlmeIndication = MLME_TXDONE;
    MlmeIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK;
    LoRaMacPrimitives->MacMlmeIndication( &MlmeIndication );  
}


static void application_callbacks()
{
    if (LoRaMacFlags.Bits.McpsInd) {
        LoRaMacFlags.Bits.McpsInd = 0;
        LoRaMacPrimitives->MacMcpsIndication( &McpsIndication );
    }
    
    if (LoRaMacFlags.Bits.McpsReq) {
        LoRaMacPrimitives->MacMcpsConfirm( &McpsConfirm );
        LoRaMacFlags.Bits.McpsReq = 0;
    }    
}

static void PrepareRxDoneAbort( void )
{
    if (NodeAckRequested)
    {
        OnAckTimeoutTimerEvent( );
    }

    LoRaMacFlags.Bits.McpsInd = 1;
    LoRaMacFlags.Bits.MacDone = 1;
    
    application_callbacks();
}

void send_callback()
{      
    if (BeaconCtx.guard) {
        if (LoRaMacFlags.Bits.pending_tx) {
            McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_INCOMPLETE;
            MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_INCOMPLETE;
            LoRaMacFlags.Bits.MacDone = 1;
            LoRaMacPrimitives->MacMcpsConfirm( &McpsConfirm );
        }
        return;
    }
    
    tx_timeout.attach_us(&send_callback, BeaconCtx.periodicity_slots * 30000);
                
    if (!LoRaMacFlags.Bits.pending_tx)
        return;
        
    Radio.SetTxConfig(
        /* RadioModems_t modem */ MODEM_LORA,
        /* int8_t power */ TxPowers[LoRaMacParams.ChannelsTxPower],
        /* uint32_t fdev */ 0,
        /* uint32_t bandwidth */ LORA_BANDWIDTH,
        /* uint32_t datarate */ Datarates[LoRaMacParams.ChannelsDatarate_fixed],
        /* uint8_t coderate */ 1,
        /* uint16_t preambleLen */ 8,
        /* bool fixLen */ false,
        /* bool crcOn */ true,
        /* bool freqHopOn */ 0,
        /* uint8_t hopPeriod */ 0,
        /* bool iqInverted */ false,
        /* uint32_t timeout */ 3e3
    );
    Radio.Send( LoRaMacBuffer, LoRaMacBufferPktLen );        
        
    LoRaMacFlags.Bits.pending_tx = 0;
}

void OnRxBeaconSetup()
{
    unsigned int prevRxBeaconSetupAt_us;
    unsigned now_us = lp_timer.read_us();
    
    prevRxBeaconSetupAt_us = BeaconCtx.RxBeaconSetupAt_us;
    BeaconCtx.RxBeaconSetupAt_us = now_us;
    expecting_beacon = true;
    
    Radio.SetRxConfig(
        /* RadioModems_t */ MODEM_LORA,
        /* uint32_t bandwidth */ LORA_BANDWIDTH,
        /* uint32_t datarate */ Datarates[BEACON_CHANNEL_DR],
        /* uint8_t coderate */ 1,
        /* uint32_t bandwidthAfc */ 0,
        /* uint16_t preambleLen */ 10,
        /* uint16_t symbTimeout */ BeaconCtx.SymbolTimeout,
        /* bool fixLen */ true,
        /* uint8_t payloadLen */ BEACON_SIZE,
        /* bool crcOn */ false,
        /* bool freqHopOn */ 0,
        /* uint8_t hopPeriod */ 0,
        /* bool iqInverted */ false,
        /* bool rxContinuous */false
    );

    Radio.Rx(2000);
    
    unsigned int us_since_last_beacon_start = BeaconCtx.RxBeaconSetupAt_us - prevRxBeaconSetupAt_us;
    isr_printf("OnRxBeaconSetup() %u since-last:%u\r\n", BeaconCtx.SymbolTimeout, us_since_last_beacon_start);
}

void guard_callback()
{
    unsigned now_us = lp_timer.read_us();
    
    /* occurs BEACON_GUARD_us prior to OnRxBeaconSetup() */
    BeaconCtx.guard = true;
    
    isr_printf("guard since-last:%u\r\n", now_us - BeaconCtx.guard_at);
    BeaconCtx.guard_at = now_us;
}

static void set_beacon_symbol_timeout(float secs)
{
    isr_printf("symTo:%.1f ", secs);
    BeaconCtx.SymbolTimeout = secs / BeaconCtx.symbol_period_secs;
    if (BeaconCtx.SymbolTimeout < (BEACON_MIN_SYMBOL_TIMEOUT+BeaconCtx.Precess_symbols)) {
        BeaconCtx.SymbolTimeout = BEACON_MIN_SYMBOL_TIMEOUT+BeaconCtx.Precess_symbols;  
    }
    BeaconCtx.SymbolTimeout_sec = BeaconCtx.SymbolTimeout * BeaconCtx.symbol_period_secs;
    isr_printf("%u\r\n", BeaconCtx.SymbolTimeout);
}

static uint16_t beacon_crc( uint8_t *buffer, uint16_t length )
{
    // The CRC calculation follows CCITT
    const uint16_t polynom = 0x1021;
    // CRC initial value
    uint16_t crc = 0x0000;

    if( buffer == NULL )
    {
        return 0;
    }

    for( uint16_t i = 0; i < length; ++i )
    {
        crc ^= ( uint16_t ) buffer[i] << 8;
        for( uint16_t j = 0; j < 8; ++j )
        {
            crc = ( crc & 0x8000 ) ? ( crc << 1 ) ^ polynom : ( crc << 1 );
        }
    }

    return crc;
}

/* low power timer needs larger value due to poor resolution */
#define TARGET_PRECESSION_US        3000
#define BEACON_RX_TIMEOUT_LOCKED    0.008

void rx_beacon(unsigned int rx_us, uint8_t* payload, uint16_t size)
{
    static bool compensate_precession = false;
    int32_t compensation = 0;
    unsigned ThisBeaconRx_us = rx_us - (BEACON_TOA_us + BEACON_RXDONE_LATENCY_us);

    BeaconCtx.guard = false;
    BeaconCtx.rx_precession_us = ThisBeaconRx_us - BeaconCtx.RxBeaconSetupAt_us;
    if (BeaconCtx.state != BEACON_STATE_FIRST_ACQ) {
        unsigned int us_since_last = ThisBeaconRx_us - BeaconCtx.LastBeaconRx_us;
        unsigned int intervals_since_last = us_since_last / BEACON_INTERVAL_us;
        BeaconCtx.known_working_BeaconRxTimerError_us = BeaconCtx.last_BeaconRxTimerError_us;
        /* get average of error history */
        BeaconCtx.last_BeaconRxTimerError_us = (ThisBeaconRx_us - BeaconCtx.LastBeaconRx_us) % BEACON_INTERVAL_us;
        /* BeaconRxTimerError: positive means our clock is fast
         * negative means our clock is slow */
        if (BeaconCtx.last_BeaconRxTimerError_us > (BEACON_INTERVAL_us/2))
            BeaconCtx.last_BeaconRxTimerError_us -= BEACON_INTERVAL_us; // negative value representing slow crystal

        if (intervals_since_last > 1) {
            /* timer error is measured over more than one beacon period */
            BeaconCtx.last_BeaconRxTimerError_us /= intervals_since_last;
        }

        if (BeaconCtx.state == BEACON_STATE_ACQ_ERROR) {
            isr_printf("-->LOCKED ");
            BeaconCtx.state = BEACON_STATE_LOCKED_;
            compensate_precession = true;
            set_beacon_symbol_timeout(BEACON_RX_TIMEOUT_LOCKED);
        }
    } else {
        /* ignore precession at first acquisition because it has slot resolution added */
        isr_printf("-->ACQ_ERROR ");
        // next beacon will give us our crystal error
        BeaconCtx.state = BEACON_STATE_ACQ_ERROR;
    }

#ifdef DEBUG_GWTX_JUMPER
    isr_printf("rx-before-gwtx:%d ", gwtx_rise_us - BeaconCtx.RxBeaconSetupAt_us);
#endif /* DEBUG_GWTX_JUMPER */    
    isr_printf("err%d=%u-%u ", BeaconCtx.last_BeaconRxTimerError_us, ThisBeaconRx_us, BeaconCtx.LastBeaconRx_us);
    isr_printf(" rx-before-tx:%d ", BeaconCtx.rx_precession_us);
    BeaconCtx.LastBeaconRx_us = ThisBeaconRx_us;

    if (BeaconCtx.state == BEACON_STATE_LOCKED_) {
        if (compensate_precession) {
            compensation = BeaconCtx.rx_precession_us - TARGET_PRECESSION_US + BeaconCtx.last_BeaconRxTimerError_us;
            isr_printf(" comp%ld", compensation);
        }
    }

    unsigned int next_beacon_expected_us = BEACON_INTERVAL_us + compensation;
    unsigned now_us = lp_timer.read_us();
    unsigned us_since_rx_setup = now_us - BeaconCtx.RxBeaconSetupAt_us;
    unsigned timeout_us = next_beacon_expected_us - us_since_rx_setup;
    BeaconCtx.timeout_rx.attach_us(&OnRxBeaconSetup, timeout_us);
    BeaconCtx.timeout_guard.attach_us(&guard_callback, timeout_us - BEACON_GUARD_us);
    
    /***************************************/
    unsigned us_since_beacon_start = now_us - ThisBeaconRx_us;
    timeout_us = BEACON_RESERVED_us + (BeaconCtx.tx_slot_offset * 30000) - us_since_beacon_start;
    tx_timeout.attach_us(&send_callback, timeout_us);
    /***************************************/    
    isr_printf(" nextTx:%u\r\n", timeout_us);

    if (BeaconCtx.num_missed > 0) {
        /* restore rx symbol timeout */
        set_beacon_symbol_timeout(BEACON_RX_TIMEOUT_LOCKED);
    }

    BeaconCtx.num_missed = 0;

    MlmeIndication.MlmeIndication = MLME_BEACON;
    MlmeIndication.Status = LORAMAC_EVENT_INFO_STATUS_BEACON_LOCKED;
    LoRaMacPrimitives->MacMlmeIndication( &MlmeIndication );

    /* check beacon payload */
    uint16_t calc_crc = beacon_crc(payload, 4);
    uint16_t rx_crc = payload[4];
    rx_crc |= payload[5] << 8;
    if (rx_crc == calc_crc) {
        unsigned int rx = payload[0];
        rx |= payload[1] << 8;
        rx |= payload[2] << 16;
        rx |= payload[3] << 24;
        if (rx != 0) {
            //isr_printf("beacon payload:%08x\r\n", rx);
            LoRaMacRxPayload[0] = payload[0];
            LoRaMacRxPayload[1] = payload[1];
            LoRaMacRxPayload[2] = payload[2];
            LoRaMacRxPayload[3] = payload[3];
            McpsIndication.McpsIndication = MCPS_MULTICAST;
            McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK;
            McpsIndication.Buffer = LoRaMacRxPayload;
            McpsIndication.BufferSize = 4;
            McpsIndication.RxData = true;
            LoRaMacPrimitives->MacMcpsIndication( &McpsIndication );
        }
    } else
        isr_printf("calc_crc:%04x rx_crc:%04x\r\n", calc_crc, rx_crc);
}

float get_symbol_period(uint8_t bw, uint8_t sf)
{
        //bw:,   // 0=125kHz, 1=250kHz, 2=500KHz
    float hz;
    switch( bw )
    {
    //case 0: // 7.8 kHz
    //    bw = 78e2;
    //    break;
    //case 1: // 10.4 kHz
    //    bw = 104e2;
    //    break;
    //case 2: // 15.6 kHz
    //    bw = 156e2;
    //    break;
    //case 3: // 20.8 kHz
    //    bw = 208e2;
    //    break;
    //case 4: // 31.2 kHz
    //    bw = 312e2;
    //    break;
    //case 5: // 41.4 kHz
    //    bw = 414e2;
    //    break;
    //case 6: // 62.5 kHz
    //    bw = 625e2;
    //    break;
    case 0: // 125 kHz
        hz = 125e3;
        break;
    case 1: // 250 kHz
        hz = 250e3;
        break;
    case 2: // 500 kHz
        hz = 500e3;
        break;
    default:
        return 0;
    }

    // return symbol period in seconds
    return (1 << sf) / hz;
}

static uint32_t GetRxBandwidth( int8_t datarate )
{
    return 2;   /* always 500KHz */
}

static void OnRadioRxDone(unsigned rx_us, uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
    LoRaMacHeader_t macHdr;
    LoRaMacFrameCtrl_t fCtrl;
    bool skipIndication = false;

    uint8_t pktHeaderLen = 0;
    uint32_t address = 0;
    uint8_t appPayloadStartIndex = 0;
    uint8_t port = 0xFF;
    uint8_t frameLen = 0;
    uint32_t mic = 0;
    uint32_t micRx = 0;

    uint16_t sequenceCounter = 0;
    uint16_t sequenceCounterPrev = 0;
    uint16_t sequenceCounterDiff = 0;
    uint32_t downLinkCounter = 0;

    MulticastParams_t *curMulticastParams = NULL;
    uint8_t *nwkSKey = LoRaMacNwkSKey;
    uint8_t *appSKey = LoRaMacAppSKey;

    uint8_t multicast = 0;

    bool isMicOk = false;

    McpsConfirm.AckReceived = false;
    McpsIndication.Rssi = rssi;
    McpsIndication.Snr = snr;
    McpsIndication.Port = 0;
    McpsIndication.Multicast = 0;
    McpsIndication.FramePending = 0;
    McpsIndication.Buffer = NULL;
    McpsIndication.BufferSize = 0;
    McpsIndication.RxData = false;
    McpsIndication.AckReceived = false;
    McpsIndication.DownLinkCounter = 0;
    McpsIndication.McpsIndication = MCPS_UNCONFIRMED;

    Radio.Sleep( );

    if (expecting_beacon) {
        rx_beacon(rx_us, payload, size);
        expecting_beacon = false;
        return;
    }

    macHdr.Value = payload[pktHeaderLen++];

    switch( macHdr.Bits.MType )
    {
        case FRAME_TYPE_JOIN_ACCEPT:
            if (IsLoRaMacNetworkJoined)
            {
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_JOIN_ACCEPT;
                PrepareRxDoneAbort( );
                return;
            }
            LoRaMacJoinDecrypt( payload + 1, size - 1, LoRaMacAppKey, LoRaMacRxPayload + 1 );

            LoRaMacRxPayload[0] = macHdr.Value;

            LoRaMacJoinComputeMic( LoRaMacRxPayload, size - LORAMAC_MFR_LEN, LoRaMacAppKey, &mic );

            micRx |= ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN];
            micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 1] << 8 );
            micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 2] << 16 );
            micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 3] << 24 );

            if( micRx == mic )
            {
                LoRaMacJoinComputeSKeys( LoRaMacAppKey, LoRaMacRxPayload + 1, LoRaMacDevNonce, LoRaMacNwkSKey, LoRaMacAppSKey );

                LoRaMacNetID = ( uint32_t )LoRaMacRxPayload[4];
                LoRaMacNetID |= ( ( uint32_t )LoRaMacRxPayload[5] << 8 );
                LoRaMacNetID |= ( ( uint32_t )LoRaMacRxPayload[6] << 16 );

                LoRaMacDevAddr = ( uint32_t )LoRaMacRxPayload[7];
                LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[8] << 8 );
                LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[9] << 16 );
                LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[10] << 24 );

                // DLSettings
                LoRaMacParams.Rx1DrOffset = ( LoRaMacRxPayload[11] >> 4 ) & 0x07;

                LoRaMacParams.ReceiveDelay_us = ( LoRaMacRxPayload[12] & 0x0F );
                if( LoRaMacParams.ReceiveDelay_us == 0 )
                    LoRaMacParams.ReceiveDelay_us = RECEIVE_DELAY_us;
                else
                    LoRaMacParams.ReceiveDelay_us *= 10;

                uint16_t beaconTimingDelay = LoRaMacRxPayload[13] & 0xff;
                beaconTimingDelay |= LoRaMacRxPayload[14] << 8;

                isr_printf("%lx slots:%x (rxdelay %lu)", LoRaMacDevAddr, beaconTimingDelay, LoRaMacParams.ReceiveDelay_us);
                unsigned int now_us = lp_timer.read_us();
                unsigned int us_since_TxDone = now_us - TxDone_us;
                unsigned us_to_beacon = ( PING_SLOT_RESOLUTION_us * beaconTimingDelay );
                unsigned timeout_us = us_to_beacon - us_since_TxDone;
                BeaconCtx.timeout_rx.attach_us(&OnRxBeaconSetup, timeout_us);
                BeaconCtx.timeout_guard.attach_us(&guard_callback, timeout_us - BEACON_GUARD_us);
                
                BeaconCtx.tx_slot_offset = LoRaMacRxPayload[15];
                BeaconCtx.tx_slot_offset |= LoRaMacRxPayload[16] << 8;
                BeaconCtx.periodicity_slots = LoRaMacRxPayload[17];
                BeaconCtx.periodicity_slots |= LoRaMacRxPayload[18] << 8;                     
                
                BeaconCtx.LastBeaconRx_us = (us_to_beacon + TxDone_us) - BEACON_INTERVAL_us;
                unsigned us_since_beacon_start = now_us - BeaconCtx.LastBeaconRx_us;
                int now_slot = (us_since_beacon_start - BEACON_RESERVED_us) / 30000;
                int use_slot = BeaconCtx.tx_slot_offset;
                while (use_slot < now_slot)
                    use_slot += BeaconCtx.periodicity_slots;                
                int slots_now_to_next = use_slot - now_slot;
                timeout_us = slots_now_to_next * 30000;
                tx_timeout.attach_us(&send_callback, timeout_us);

                isr_printf(" now_slot:%d use_slot:%d timeout_us:%u\r\n", now_slot, use_slot, timeout_us);
                isr_printf("us_to_beacon:%u, since_txDone:%u\r\n", us_to_beacon, us_since_TxDone);
                BeaconCtx.state = BEACON_STATE_FIRST_ACQ;
                BeaconCtx.guard = false;
                BeaconCtx.num_missed = 0;
                BeaconCtx.rx_precession_us = 0;
                BeaconCtx.last_BeaconRxTimerError_us = -PPM_100_BEACON_INTERVAL;
                BeaconCtx.known_working_BeaconRxTimerError_us = -PPM_100_BEACON_INTERVAL;
                BeaconCtx.symbol_period_secs = get_symbol_period(GetRxBandwidth(LORAMAC_DEFAULT_DATARATE), Datarates[LORAMAC_DEFAULT_DATARATE]);
                // N-ms: slot resolution + minimum for preamble detector + 100ppm fast crystal rxing 12ms early
                BeaconCtx.Precess_symbols = TARGET_PRECESSION_US * (BeaconCtx.symbol_period_secs);
                BeaconCtx.SymbolTimeout_sec = 0.1 + (BEACON_MIN_SYMBOL_TIMEOUT * BeaconCtx.symbol_period_secs);
                BeaconCtx.SymbolTimeout = BeaconCtx.SymbolTimeout_sec / BeaconCtx.symbol_period_secs;

                MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
                IsLoRaMacNetworkJoined = true;
                LoRaMacParams.ChannelsDatarate_fixed = LoRaMacParamsDefaults.ChannelsDatarate_fixed;
            }
            else
            {
                MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
                isr_printf("join-mic-fail\r\n");
                JoinRequestTrials = MaxJoinRequestTrials; // stop trying
            }
            LoRaMacPrimitives->MacMlmeConfirm( &MlmeConfirm );
            LoRaMacFlags.Bits.MlmeReq = 0;  // MacMlmeConfirm() called
            break;
        case FRAME_TYPE_DATA_CONFIRMED_DOWN:
        case FRAME_TYPE_DATA_UNCONFIRMED_DOWN:
            {
                address = payload[pktHeaderLen++];
                address |= ( (uint32_t)payload[pktHeaderLen++] << 8 );
                address |= ( (uint32_t)payload[pktHeaderLen++] << 16 );
                address |= ( (uint32_t)payload[pktHeaderLen++] << 24 );

                if( address != LoRaMacDevAddr )
                {
                    curMulticastParams = MulticastChannels;
                    while( curMulticastParams != NULL )
                    {
                        if( address == curMulticastParams->Address )
                        {
                            multicast = 1;
                            nwkSKey = curMulticastParams->NwkSKey;
                            appSKey = curMulticastParams->AppSKey;
                            downLinkCounter = curMulticastParams->DownLinkCounter;
                            break;
                        }
                        curMulticastParams = curMulticastParams->Next;
                    }
                    if( multicast == 0 )
                    {
                        // We are not the destination of this frame.
                        McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL;
                        PrepareRxDoneAbort( );
                        return;
                    }
                }
                else
                {
                    multicast = 0;
                    nwkSKey = LoRaMacNwkSKey;
                    appSKey = LoRaMacAppSKey;
                    downLinkCounter = DownLinkCounter;
                }

                fCtrl.Value = payload[pktHeaderLen++];

                sequenceCounter = ( uint16_t )payload[pktHeaderLen++];
                sequenceCounter |= ( uint16_t )payload[pktHeaderLen++] << 8;

                appPayloadStartIndex = 8 + fCtrl.Bits.FOptsLen;

                micRx |= ( uint32_t )payload[size - LORAMAC_MFR_LEN];
                micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 1] << 8 );
                micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 2] << 16 );
                micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 3] << 24 );

                sequenceCounterPrev = ( uint16_t )downLinkCounter;
                sequenceCounterDiff = ( sequenceCounter - sequenceCounterPrev );

                if( sequenceCounterDiff < ( 1 << 15 ) )
                {
                    downLinkCounter += sequenceCounterDiff;
                    LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounter, &mic );
                    if( micRx == mic )
                    {
                        isMicOk = true;
                    }
                }
                else
                {
                    // check for sequence roll-over
                    uint32_t  downLinkCounterTmp = downLinkCounter + 0x10000 + ( int16_t )sequenceCounterDiff;
                    LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounterTmp, &mic );
                    if( micRx == mic )
                    {
                        isMicOk = true;
                        downLinkCounter = downLinkCounterTmp;
                    }
                }

                // Check for a the maximum allowed counter difference
                if( sequenceCounterDiff >= MAX_FCNT_GAP )
                {
                    McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOSS;
                    McpsIndication.DownLinkCounter = downLinkCounter;
                    PrepareRxDoneAbort( );
                    return;
                }

                if( isMicOk == true )
                {
                    McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK;
                    McpsIndication.Multicast = multicast;
                    McpsIndication.FramePending = fCtrl.Bits.FPending;
                    McpsIndication.Buffer = NULL;
                    McpsIndication.BufferSize = 0;
                    McpsIndication.DownLinkCounter = downLinkCounter;

                    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;

                    MacCommandsBufferToRepeatIndex = 0;

                    // Update 32 bits downlink counter
                    if( multicast == 1 )
                    {
                        McpsIndication.McpsIndication = MCPS_MULTICAST;

                        if( ( curMulticastParams->DownLinkCounter == downLinkCounter ) &&
                            ( curMulticastParams->DownLinkCounter != 0 ) )
                        {
                            McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
                            McpsIndication.DownLinkCounter = downLinkCounter;
                            PrepareRxDoneAbort( );
                            return;
                        }
                        curMulticastParams->DownLinkCounter = downLinkCounter;
                    }
                    else
                    {
                        if( macHdr.Bits.MType == FRAME_TYPE_DATA_CONFIRMED_DOWN )
                        {
                            SrvAckRequested = true;
                            McpsIndication.McpsIndication = MCPS_CONFIRMED;

                            if( ( DownLinkCounter == downLinkCounter ) &&
                                ( DownLinkCounter != 0 ) )
                            {
                                // Duplicated confirmed downlink. Skip indication.
                                // In this case, the MAC layer shall accept the MAC commands
                                // which are included in the downlink retransmission.
                                // It should not provide the same frame to the application
                                // layer again.
                                skipIndication = true;
                            }
                        }
                        else
                        {
                            SrvAckRequested = false;
                            McpsIndication.McpsIndication = MCPS_UNCONFIRMED;

                            if( ( DownLinkCounter == downLinkCounter ) &&
                                ( DownLinkCounter != 0 ) )
                            {
                                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
                                McpsIndication.DownLinkCounter = downLinkCounter;
                                PrepareRxDoneAbort( );
                                return;
                            }
                        }
                        DownLinkCounter = downLinkCounter;
                    }

                    // This must be done before parsing the payload and the MAC commands.
                    // We need to reset the MacCommandsBufferIndex here, since we need
                    // to take retransmissions and repititions into account. Error cases
                    // will be handled in function OnMacStateCheckTimerEvent.
                    if( McpsConfirm.McpsRequest == MCPS_CONFIRMED )
                    {
                        if( fCtrl.Bits.Ack == 1 )
                        {// Reset MacCommandsBufferIndex when we have received an ACK.
                            MacCommandsBufferIndex = 0;
                        }
                    }
                    else
                    {// Reset the variable if we have received any valid frame.
                        MacCommandsBufferIndex = 0;
                    }

                    // Process payload and MAC commands
                    if( ( ( size - 4 ) - appPayloadStartIndex ) > 0 )
                    {
                        port = payload[appPayloadStartIndex++];
                        frameLen = ( size - 4 ) - appPayloadStartIndex;

                        McpsIndication.Port = port;

                        if( port == 0 )
                        {
                            // Only allow frames which do not have fOpts
                            if( fCtrl.Bits.FOptsLen == 0 )
                            {
                                LoRaMacPayloadDecrypt( payload + appPayloadStartIndex,
                                                       frameLen,
                                                       nwkSKey,
                                                       address,
                                                       DOWN_LINK,
                                                       downLinkCounter,
                                                       LoRaMacRxPayload );

                                // Decode frame payload MAC commands
                                ProcessMacCommands( LoRaMacRxPayload, 0, frameLen, snr );
                            }
                            else
                            {
                                skipIndication = true;
                            }
                        }
                        else
                        {
                            if( fCtrl.Bits.FOptsLen > 0 )
                            {
                                // Decode Options field MAC commands. Omit the fPort.
                                ProcessMacCommands( payload, 8, appPayloadStartIndex - 1, snr );
                            }

                            LoRaMacPayloadDecrypt( payload + appPayloadStartIndex,
                                                   frameLen,
                                                   appSKey,
                                                   address,
                                                   DOWN_LINK,
                                                   downLinkCounter,
                                                   LoRaMacRxPayload );

                            if( skipIndication == false )
                            {
                                McpsIndication.Buffer = LoRaMacRxPayload;
                                McpsIndication.BufferSize = frameLen;
                                McpsIndication.RxData = true;
                            }
                        }
                    }
                    else
                    {
                        if( fCtrl.Bits.FOptsLen > 0 )
                        {
                            // Decode Options field MAC commands
                            ProcessMacCommands( payload, 8, appPayloadStartIndex, snr );
                        }
                    }

                    if( skipIndication == false )
                    {
                        // Check if the frame is an acknowledgement
                        if( fCtrl.Bits.Ack == 1 )
                        {
                            McpsConfirm.AckReceived = true;
                            McpsIndication.AckReceived = true;

                            // Stop the AckTimeout timer as no more retransmissions
                            // are needed.
                        }
                        else
                        {
                            McpsConfirm.AckReceived = false;
                        }
                    }
                    // Provide always an indication, skip the callback to the user application,
                    // in case of a confirmed downlink retransmission.
                    LoRaMacFlags.Bits.McpsInd = 1;
                    LoRaMacFlags.Bits.McpsIndSkip = skipIndication;
                }
                else
                {
                    McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL;
                    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL;
                    PrepareRxDoneAbort( );
                    return;
                }
            }
            break;
        case FRAME_TYPE_PROPRIETARY:
            {
                memcpy1( LoRaMacRxPayload, &payload[pktHeaderLen], size );

                McpsIndication.McpsIndication = MCPS_PROPRIETARY;
                McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK;
                McpsIndication.Buffer = LoRaMacRxPayload;
                McpsIndication.BufferSize = size - pktHeaderLen;

                LoRaMacFlags.Bits.McpsInd = 1;
                break;
            }
        default:
            McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_RX_MTYPE;
            McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_RX_MTYPE;
            PrepareRxDoneAbort( );
            break;
    }
    LoRaMacFlags.Bits.MacDone = 1;

    application_callbacks();

} // ..OnRadioRxDone();



static void OnRadioTxTimeout( void )
{
    Radio.Sleep( );

    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT;
    MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT;
    LoRaMacFlags.Bits.MacDone = 1;
}

static void OnRadioRxError( void )
{
    Radio.Sleep( );

    if (NodeAckRequested)
    {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_ERROR;
    }
    MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_ERROR;

    if ((lp_timer.read_us() - AggregatedLastTxDoneTime_us) >= RxWindowDelay_us )
    {
        LoRaMacFlags.Bits.MacDone = 1;
    }
}

static void
join_send()
{
    if (JoinRequestTrials < MaxJoinRequestTrials) {
        LoRaMacHeader_t macHdr;
        LoRaMacFrameCtrl_t fCtrl;
        
        if (++Channel == LORA_MAX_NB_CHANNELS)
            Channel = 0;
        isr_printf("<join-ch%u>", Channel);
    
        macHdr.Value = 0;
        macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ;
    
        fCtrl.Value = 0;
        fCtrl.Bits.Adr = 0;
    
        /* In case of join request retransmissions, the stack must prepare
         * the frame again, because the network server keeps track of the random
         * LoRaMacDevNonce values to prevent reply attacks. */
        PrepareFrame( &macHdr, &fCtrl, 0, NULL, 0 );
        
        ScheduleTx();
    } else {
        MlmeConfirm.MlmeRequest = MLME_JOIN;
        MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
        LoRaMacPrimitives->MacMlmeConfirm( &MlmeConfirm );
    }
}

static void OnRadioRxTimeout( void )
{
    static unsigned LastBeaconRx_us;
    
    Radio.Sleep( );

    if (expecting_beacon) {
        unsigned next_beacon_expected_us = BEACON_INTERVAL_us;
        if (BeaconCtx.state == BEACON_STATE_FIRST_ACQ) {
            next_beacon_expected_us -= 1000000;
            set_beacon_symbol_timeout(1.000);
        } else {
            next_beacon_expected_us += BeaconCtx.known_working_BeaconRxTimerError_us;
            // for measurement resolution and temperature drift while missing beacons:
            next_beacon_expected_us -= 10000;
            set_beacon_symbol_timeout(BeaconCtx.SymbolTimeout_sec + 0.025);
        }
        unsigned now_us = lp_timer.read_us();
        unsigned us_since_rx_setup = now_us - BeaconCtx.RxBeaconSetupAt_us;
        unsigned b_timeout_us = next_beacon_expected_us - us_since_rx_setup;
        BeaconCtx.timeout_rx.attach_us(&OnRxBeaconSetup, b_timeout_us);
        BeaconCtx.timeout_guard.attach_us(&guard_callback, b_timeout_us - BEACON_GUARD_us);

        if (BeaconCtx.num_missed == 0)  /* first missed beacon, init our local LastBeaconRx_us */
            LastBeaconRx_us = BeaconCtx.LastBeaconRx_us;

        BeaconCtx.num_missed++;
        /***************************************/
        unsigned fake_ThisBeaconRx_us = LastBeaconRx_us + BEACON_INTERVAL_us + BeaconCtx.known_working_BeaconRxTimerError_us;
        unsigned us_since_beacon_start = now_us - fake_ThisBeaconRx_us;
        unsigned timeout_us = BEACON_RESERVED_us + (BeaconCtx.tx_slot_offset * 30000) - us_since_beacon_start;
        tx_timeout.attach_us(&send_callback, timeout_us);
        isr_printf("fake %u\r\n", fake_ThisBeaconRx_us - LastBeaconRx_us);
        LastBeaconRx_us = fake_ThisBeaconRx_us; // update our local for next missed beacon
        /***************************************/         

#ifdef DEBUG_GWTX_JUMPER
        isr_printf("rx-before-gwtx:%d ", gwtx_rise_us - BeaconCtx.RxBeaconSetupAt_us);
#endif /* DEBUG_GWTX_JUMPER */    
            
       isr_printf("beacon-rx-timeout %u %u next in %uus (rxing for %u) nextTx:%u\r\n", BeaconCtx.num_missed, BeaconCtx.SymbolTimeout, b_timeout_us, us_since_rx_setup, timeout_us);

        MlmeIndication.MlmeIndication = MLME_BEACON;
        MlmeIndication.Status = LORAMAC_EVENT_INFO_STATUS_BEACON_LOST;
        LoRaMacPrimitives->MacMlmeIndication( &MlmeIndication );

        BeaconCtx.guard = false;
        expecting_beacon = false;
    } else {
        if (LoRaMacFlags.Bits.MlmeReq && ( MlmeConfirm.MlmeRequest == MLME_JOIN ))
             tx_timeout.attach_us(&join_send, (JoinRequestTrials*20000) + randr(0, 70000));
    }

    if (NodeAckRequested)
    {
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT;
    }
    MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT;
    LoRaMacFlags.Bits.MacDone = 1;
    
    application_callbacks();
} // ..OnRadioRxTimeout();

static void OnRxWindowTimerEvent( void )
{
    Radio.Standby( );
    if (expecting_beacon) {
        isr_printf("rxwin-during-beacon\r\n");
        McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_ERROR;
        MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_ERROR;
        LoRaMacFlags.Bits.MacDone = 1;
        LoRaMacPrimitives->MacMcpsConfirm( &McpsConfirm );
    } else {
        RxWindowSetup( LORAMAC_FIRST_CHANNEL + ( Channel * LORAMAC_STEPWIDTH_CHANNEL), RxWindowsParam.Datarate, RxWindowsParam.Bandwidth, RxWindowsParam.RxWindowTimeout, false );
    }
}

static void OnAckTimeoutTimerEvent( void )
{
}

static bool RxWindowSetup( uint32_t freq, int8_t datarate, uint32_t bandwidth, uint16_t timeout, bool rxContinuous )
{
    uint8_t downlinkDatarate = Datarates[datarate];
    RadioModems_t modem;

    if( Radio.GetStatus( ) == RF_IDLE )
    {
        Radio.SetChannel( freq );

        // Store downlink datarate
        McpsIndication.RxDatarate = ( uint8_t ) datarate;

        modem = MODEM_LORA;
        Radio.SetRxConfig( modem, bandwidth, downlinkDatarate, 1, 0, 8, timeout, false, 0, false, 0, 0, true, rxContinuous );
        Radio.SetMaxPayloadLength( MODEM_LORA, 255 );

        if( rxContinuous == false )
        {
            Radio.Rx( LoRaMacParams.MaxRxWindow );
        }
        else
        {
            Radio.Rx( 0 ); // Continuous mode
        }
        return true;
    }
    return false;
}

static bool ValueInRange( int8_t value, int8_t min, int8_t max )
{
    if( ( value >= min ) && ( value <= max ) )
    {
        return true;
    }
    return false;
}

static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 )
{
    LoRaMacStatus_t status = LORAMAC_STATUS_BUSY;
    // The maximum buffer length must take MAC commands to re-send into account.
    uint8_t bufLen = LORA_MAC_COMMAND_MAX_LENGTH - MacCommandsBufferToRepeatIndex;

    switch( cmd )
    {
        case MOTE_MAC_LINK_CHECK_REQ:
            if( MacCommandsBufferIndex < bufLen )
            {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this command
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_RX_PARAM_SETUP_ANS:
            if( MacCommandsBufferIndex < ( bufLen - 1 ) )
            {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // Status: Datarate ACK, Channel ACK
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_DEV_STATUS_ANS:
            if( MacCommandsBufferIndex < ( bufLen - 2 ) )
            {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // 1st byte Battery
                // 2nd byte Margin
                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
                MacCommandsBuffer[MacCommandsBufferIndex++] = p2;
                status = LORAMAC_STATUS_OK;
            }
            break;
        case MOTE_MAC_RX_TIMING_SETUP_ANS:
            if( MacCommandsBufferIndex < bufLen )
            {
                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
                // No payload for this answer
                status = LORAMAC_STATUS_OK;
            }
            break;
        default:
            return LORAMAC_STATUS_SERVICE_UNKNOWN;
    }
    if( status == LORAMAC_STATUS_OK )
    {
        MacCommandsInNextTx = true;
    }
    return status;
}

static uint8_t ParseMacCommandsToRepeat( uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut )
{
    uint8_t i = 0;
    uint8_t cmdCount = 0;

    if( ( cmdBufIn == NULL ) || ( cmdBufOut == NULL ) )
    {
        return 0;
    }

    for( i = 0; i < length; i++ )
    {
        switch( cmdBufIn[i] )
        {
            // STICKY
            case MOTE_MAC_RX_PARAM_SETUP_ANS:
            {
                cmdBufOut[cmdCount++] = cmdBufIn[i++];
                cmdBufOut[cmdCount++] = cmdBufIn[i];
                break;
            }
            case MOTE_MAC_RX_TIMING_SETUP_ANS:
            {
                cmdBufOut[cmdCount++] = cmdBufIn[i];
                break;
            }
            // NON-STICKY
            case MOTE_MAC_DEV_STATUS_ANS:
            { // 2 bytes payload
                i += 2;
                break;
            }
            case MOTE_MAC_LINK_CHECK_REQ:
            { // 0 byte payload
                break;
            }
            default:
                break;
        }
    }

    return cmdCount;
}

static void ProcessMacCommands( uint8_t *payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr )
{
    while( macIndex < commandsSize )
    {
        // Decode Frame MAC commands
        switch( payload[macIndex++] )
        {
            case SRV_MAC_LINK_CHECK_ANS:
                MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK;
                MlmeConfirm.DemodMargin = payload[macIndex++];
                MlmeConfirm.NbGateways = payload[macIndex++];
                break;
            case SRV_MAC_DEV_STATUS_REQ:
                {
                    uint8_t batteryLevel = BAT_LEVEL_NO_MEASURE;
                    if( ( LoRaMacCallbacks != NULL ) && ( LoRaMacCallbacks->GetBatteryLevel != NULL ) )
                    {
                        batteryLevel = LoRaMacCallbacks->GetBatteryLevel( );
                    }
                    AddMacCommand( MOTE_MAC_DEV_STATUS_ANS, batteryLevel, snr );
                    break;
                }
            case SRV_MAC_RX_TIMING_SETUP_REQ:
                {
                    uint8_t delay = payload[macIndex++] & 0x0F;

                    if( delay == 0 )
                    {
                        delay++;
                    }
                    LoRaMacParams.ReceiveDelay_us = delay * 1e6;
                    AddMacCommand( MOTE_MAC_RX_TIMING_SETUP_ANS, 0, 0 );
                }
                break;
            default:
                // Unknown command. ABORT MAC commands processing
                return;
        }
    }
}

LoRaMacStatus_t Send( LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer, uint16_t fBufferSize )
{
    LoRaMacFrameCtrl_t fCtrl;
    LoRaMacStatus_t status = LORAMAC_STATUS_PARAMETER_INVALID;

    fCtrl.Value = 0;
    fCtrl.Bits.FOptsLen      = 0;
    fCtrl.Bits.FPending      = 0;
    fCtrl.Bits.Ack           = false;
    fCtrl.Bits.AdrAckReq     = false;
    fCtrl.Bits.Adr           = false;

    // Prepare the frame
    status = PrepareFrame( macHdr, &fCtrl, fPort, fBuffer, fBufferSize );

    // Validate status
    if( status != LORAMAC_STATUS_OK )
    {
        return status;
    }

    // Reset confirm parameters
    McpsConfirm.AckReceived = false;
    McpsConfirm.UpLinkCounter = UpLinkCounter;

    return ScheduleTx();
}

static LoRaMacStatus_t ScheduleTx( void )
{
    // Compute Rx1 windows parameters
    if (!IsLoRaMacNetworkJoined)
    {
        RxWindowDelay_us = LoRaMacParams.JoinAcceptDelay_us + RxWindowsParam.RxOffset;  // dont care
    }
    else
    {
        RxWindowDelay_us = LoRaMacParams.ReceiveDelay_us + RxWindowsParam.RxOffset;
    }

    // Schedule transmission of frame

    // Try to send now
    return SendFrameOnChannel( Channels[Channel] );
}

static void ResetMacParameters( void )
{
    IsLoRaMacNetworkJoined = false;

    // Counters
    UpLinkCounter = 0;
    DownLinkCounter = 0;

    ChannelsNbRepCounter = 0;

    MacCommandsBufferIndex = 0;
    MacCommandsBufferToRepeatIndex = 0;

    IsRxWindowsEnabled = true;

    LoRaMacParams.ChannelsTxPower = LoRaMacParamsDefaults.ChannelsTxPower;
    LoRaMacParams.ChannelsDatarate_fixed = LoRaMacParamsDefaults.ChannelsDatarate_fixed;

    LoRaMacParams.Rx1DrOffset = LoRaMacParamsDefaults.Rx1DrOffset;

    NodeAckRequested = false;
    SrvAckRequested = false;
    MacCommandsInNextTx = false;

    // Reset Multicast downlink counters
    MulticastParams_t *cur = MulticastChannels;
    while( cur != NULL )
    {
        cur->DownLinkCounter = 0;
        cur = cur->Next;
    }

}

LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize )
{
    uint16_t i;
    uint8_t pktHeaderLen = 0;
    uint32_t mic = 0;
    const void* payload = fBuffer;
    uint8_t framePort = fPort;

    LoRaMacBufferPktLen = 0;

    NodeAckRequested = false;

    if( fBuffer == NULL )
    {
        fBufferSize = 0;
    }

    LoRaMacTxPayloadLen = fBufferSize;

    LoRaMacBuffer[pktHeaderLen++] = macHdr->Value;

    switch( macHdr->Bits.MType )
    {
        case FRAME_TYPE_JOIN_REQ:
            LoRaMacBufferPktLen = pktHeaderLen;

            memcpyr( LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacAppEui, 8 );
            LoRaMacBufferPktLen += 8;
            memcpyr( LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacDevEui, 8 );
            LoRaMacBufferPktLen += 8;

            LoRaMacDevNonce = Radio.Random( );

            LoRaMacBuffer[LoRaMacBufferPktLen++] = LoRaMacDevNonce & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = ( LoRaMacDevNonce >> 8 ) & 0xFF;

            LoRaMacJoinComputeMic( LoRaMacBuffer, LoRaMacBufferPktLen & 0xFF, LoRaMacAppKey, &mic );

            LoRaMacBuffer[LoRaMacBufferPktLen++] = mic & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 8 ) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 16 ) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 24 ) & 0xFF;

            break;
        case FRAME_TYPE_DATA_CONFIRMED_UP:
            NodeAckRequested = true;
            //Intentional fallthrough
        case FRAME_TYPE_DATA_UNCONFIRMED_UP:
            if (!IsLoRaMacNetworkJoined)
            {
                return LORAMAC_STATUS_NO_NETWORK_JOINED; // No network has been joined yet
            }

            fCtrl->Bits.AdrAckReq = 0;

            if( SrvAckRequested == true )
            {
                SrvAckRequested = false;
                fCtrl->Bits.Ack = 1;
            }

            LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr ) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 8 ) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 16 ) & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 24 ) & 0xFF;

            LoRaMacBuffer[pktHeaderLen++] = fCtrl->Value;

            LoRaMacBuffer[pktHeaderLen++] = UpLinkCounter & 0xFF;
            LoRaMacBuffer[pktHeaderLen++] = ( UpLinkCounter >> 8 ) & 0xFF;

            // Copy the MAC commands which must be re-send into the MAC command buffer
            memcpy1( &MacCommandsBuffer[MacCommandsBufferIndex], MacCommandsBufferToRepeat, MacCommandsBufferToRepeatIndex );
            MacCommandsBufferIndex += MacCommandsBufferToRepeatIndex;

            if( ( payload != NULL ) && ( LoRaMacTxPayloadLen > 0 ) )
            {
                if( ( MacCommandsBufferIndex <= LORA_MAC_COMMAND_MAX_LENGTH ) && ( MacCommandsInNextTx == true ) )
                {
                    fCtrl->Bits.FOptsLen += MacCommandsBufferIndex;

                    // Update FCtrl field with new value of OptionsLength
                    LoRaMacBuffer[0x05] = fCtrl->Value;
                    for( i = 0; i < MacCommandsBufferIndex; i++ )
                    {
                        LoRaMacBuffer[pktHeaderLen++] = MacCommandsBuffer[i];
                    }
                }
            }
            else
            {
                if( ( MacCommandsBufferIndex > 0 ) && ( MacCommandsInNextTx ) )
                {
                    LoRaMacTxPayloadLen = MacCommandsBufferIndex;
                    payload = MacCommandsBuffer;
                    framePort = 0;
                }
            }
            MacCommandsInNextTx = false;
            // Store MAC commands which must be re-send in case the device does not receive a downlink anymore
            MacCommandsBufferToRepeatIndex = ParseMacCommandsToRepeat( MacCommandsBuffer, MacCommandsBufferIndex, MacCommandsBufferToRepeat );
            if( MacCommandsBufferToRepeatIndex > 0 )
            {
                MacCommandsInNextTx = true;
            }

            if( ( payload != NULL ) && ( LoRaMacTxPayloadLen > 0 ) )
            {
                LoRaMacBuffer[pktHeaderLen++] = framePort;

                if( framePort == 0 )
                {
                    LoRaMacPayloadEncrypt( (uint8_t* ) payload, LoRaMacTxPayloadLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen] );
                }
                else
                {
                    LoRaMacPayloadEncrypt( (uint8_t* ) payload, LoRaMacTxPayloadLen, LoRaMacAppSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen] );
                }
            }
            LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen;

            LoRaMacComputeMic( LoRaMacBuffer, LoRaMacBufferPktLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &mic );

            LoRaMacBuffer[LoRaMacBufferPktLen + 0] = mic & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 1] = ( mic >> 8 ) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 2] = ( mic >> 16 ) & 0xFF;
            LoRaMacBuffer[LoRaMacBufferPktLen + 3] = ( mic >> 24 ) & 0xFF;

            LoRaMacBufferPktLen += LORAMAC_MFR_LEN;

            break;
        case FRAME_TYPE_PROPRIETARY:
            if( ( fBuffer != NULL ) && ( LoRaMacTxPayloadLen > 0 ) )
            {
                memcpy1( LoRaMacBuffer + pktHeaderLen, ( uint8_t* ) fBuffer, LoRaMacTxPayloadLen );
                LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen;
            }
            break;
        default:
            return LORAMAC_STATUS_SERVICE_UNKNOWN;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SendFrameOnChannel( ChannelParams_t channel )
{
    int8_t datarate = Datarates[LoRaMacParams.ChannelsDatarate_fixed];
    int8_t txPowerIndex = 0;
    int8_t txPower = 0;

    if (BeaconCtx.guard) {
        unsigned now_us = lp_timer.read_us();
        if (now_us - BeaconCtx.guard_at > 10000000) {
            isr_printf("beacon fault\r\n");
            IsLoRaMacNetworkJoined = false;
            BeaconCtx.timeout_rx.detach();
            BeaconCtx.timeout_guard.detach();
            BeaconCtx.state = BEACON_STATE_NONE;
            BeaconCtx.guard = false;
            return LORAMAC_STATUS_NO_NETWORK_JOINED;
        }
        return LORAMAC_STATUS_BUSY;
    }

    txPowerIndex = LoRaMacParams.ChannelsTxPower;
    txPower = TxPowers[txPowerIndex];

    MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_SEND;
    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_SEND;
    McpsConfirm.TxPower = txPowerIndex;
    McpsConfirm.UpLinkFrequency = channel.Frequency;

    Radio.SetChannel( channel.Frequency );

#if defined( USE_BAND_915_SINGLE )
    if( LoRaMacParams.ChannelsDatarate_fixed >= DR_8 )
    { // High speed LoRa channel BW500 kHz
        Radio.SetTxConfig( MODEM_LORA, txPower, 0, 2, datarate, 1, 8, false, true, 0, 0, false, 3e3 );
        TxTimeOnAir = Radio.TimeOnAir( MODEM_LORA, LoRaMacBufferPktLen );
    } else
        return LORAMAC_STATUS_DATARATE_INVALID;
#elif defined(USE_BAND_433) && defined (ENABLE_SX1276)
    if( LoRaMacParams.ChannelsDatarate_fixed == DR_7 )
    { // High Speed FSK channel
        //Radio.SetMaxPayloadLength( MODEM_FSK, LoRaMacBufferPktLen );
        Radio.SetTxConfig( MODEM_FSK, txPower, 25e3, 0, datarate * 1e3, 0, 5, false, true, 0, 0, false, 3e3 );
        TxTimeOnAir = Radio.TimeOnAir( MODEM_FSK, LoRaMacBufferPktLen );

    }
    else if( LoRaMacParams.ChannelsDatarate_fixed == DR_6 )
    { // High speed LoRa channel
        //Radio.SetMaxPayloadLength( MODEM_LORA, LoRaMacBufferPktLen );
        Radio.SetTxConfig( MODEM_LORA, txPower, 0, 1, datarate, 1, 8, false, true, 0, 0, false, 3e3 );
        TxTimeOnAir = Radio.TimeOnAir( MODEM_LORA, LoRaMacBufferPktLen );
    }
    else
    { // Normal LoRa channel
        //Radio.SetMaxPayloadLength( MODEM_LORA, LoRaMacBufferPktLen );
        Radio.SetTxConfig( MODEM_LORA, txPower, 0, 0, datarate, 1, 8, false, true, 0, 0, false, 3e3 );
        TxTimeOnAir = Radio.TimeOnAir( MODEM_LORA, LoRaMacBufferPktLen );
    }
#endif

    // Store the time on air
    McpsConfirm.TxTimeOnAir = TxTimeOnAir;
    MlmeConfirm.TxTimeOnAir = TxTimeOnAir;


    if (!IsLoRaMacNetworkJoined)
    {
        JoinRequestTrials++;
        isr_printf("join %luhz try%u DR%u\r\n", channel.Frequency, JoinRequestTrials, LoRaMacParams.ChannelsDatarate_fixed);
    }

    /* anything not join request is sent at permitted time slot */
    LoRaMacHeader_t* macHdr = (LoRaMacHeader_t*)&LoRaMacBuffer[0];
    if (macHdr->Bits.MType == FRAME_TYPE_JOIN_REQ) {
        // Send now
        Radio.Send( LoRaMacBuffer, LoRaMacBufferPktLen );
    }
    else {
        LoRaMacFlags.Bits.pending_tx = 1;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SetTxContinuousWave( uint16_t timeout )
{
    int8_t txPowerIndex = 0;
    int8_t txPower = 0;

    txPowerIndex = LoRaMacParams.ChannelsTxPower;
    txPower = TxPowers[txPowerIndex];
    Radio.SetTxContinuousWave( Channels[Channel].Frequency, txPower, timeout );

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t SetTxContinuousWave1( uint16_t timeout, uint32_t frequency, uint8_t power )
{
    Radio.SetTxContinuousWave( frequency, power, timeout );

    return LORAMAC_STATUS_OK;
}

void seconds()
{
    isr_printf("second\r\n");
}

LoRaMacStatus_t LoRaMacInitialization( LoRaMacPrimitives_t *primitives, LoRaMacCallback_t *callbacks )
{
    if( primitives == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    if( ( primitives->MacMcpsConfirm == NULL ) ||
        ( primitives->MacMcpsIndication == NULL ) ||
        ( primitives->MacMlmeConfirm == NULL ) )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    LoRaMacPrimitives = primitives;
    LoRaMacCallbacks = callbacks;

    LoRaMacFlags.Value = 0;

    LoRaMacDeviceClass = CLASS_A;

    JoinRequestTrials = 0;
    MaxJoinRequestTrials = 255;


    // Reset to defaults
    LoRaMacParamsDefaults.ChannelsTxPower = LORAMAC_DEFAULT_TX_POWER;
    LoRaMacParamsDefaults.ChannelsDatarate_fixed = LORAMAC_DEFAULT_DATARATE;

    LoRaMacParamsDefaults.SystemMaxRxError = 10;
    LoRaMacParamsDefaults.MinRxSymbols = 6; // TODO XXX increase
    LoRaMacParamsDefaults.MaxRxWindow = MAX_RX_WINDOW;

    LoRaMacParamsDefaults.ReceiveDelay_us = RECEIVE_DELAY_us;
    LoRaMacParamsDefaults.JoinAcceptDelay_us = JOIN_ACCEPT_DELAY_us;

    LoRaMacParamsDefaults.ChannelsNbRep = 1;
    LoRaMacParamsDefaults.Rx1DrOffset = 0;

    for( uint8_t i = 0; i < LORA_MAX_NB_CHANNELS; i++ )
    {
        Channels[i].Frequency = LORAMAC_FIRST_CHANNEL + (i * LORAMAC_STEPWIDTH_CHANNEL);
        Channels[i].DrRange.Value = (LORAMAC_MAX_DATARATE << 4) | LORAMAC_MIN_DATARATE;
        Channels[i].Band = 0;
    }

    // Init parameters which are not set in function ResetMacParameters
    LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError;
    LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols;
    LoRaMacParams.MaxRxWindow = LoRaMacParamsDefaults.MaxRxWindow;
    LoRaMacParams.ReceiveDelay_us = LoRaMacParamsDefaults.ReceiveDelay_us;
    LoRaMacParams.JoinAcceptDelay_us = LoRaMacParamsDefaults.JoinAcceptDelay_us;
    LoRaMacParams.ChannelsNbRep = LoRaMacParamsDefaults.ChannelsNbRep;

    ResetMacParameters( );

    // Initialize Radio driver
    RadioEvents.TxDone = OnRadioTxDone;
    RadioEvents.RxDone = OnRadioRxDone;
    RadioEvents.RxError = OnRadioRxError;
    RadioEvents.TxTimeout = OnRadioTxTimeout;
    RadioEvents.RxTimeout = OnRadioRxTimeout;
    Radio.Init( &RadioEvents );

    // Random seed initialization
    srand1( Radio.Random( ) );

    PublicNetwork = true;
    Radio.SetPublicNetwork( PublicNetwork );
    Radio.Sleep( );

    lp_timer.start();

    RxWindowsParam = ComputeRxWindowParameters(LORAMAC_DEFAULT_DATARATE, LoRaMacParams.SystemMaxRxError);
    
#ifdef DEBUG_GWTX_JUMPER
    gwtx_pin.rise(&gwtx_pin_callback);
#endif

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacQueryTxPossible( uint8_t size, LoRaMacTxInfo_t* txInfo )
{
    uint8_t fOptLen = MacCommandsBufferIndex + MacCommandsBufferToRepeatIndex;

    if( txInfo == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    txInfo->CurrentPayloadSize = 255;

    if( txInfo->CurrentPayloadSize >= fOptLen )
    {
        txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize - fOptLen;
    }
    else
    {
        return LORAMAC_STATUS_MAC_CMD_LENGTH_ERROR;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMibGetRequestConfirm( MibRequestConfirm_t *mibGet )
{
    LoRaMacStatus_t status = LORAMAC_STATUS_OK;

    if( mibGet == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    switch( mibGet->Type )
    {
        case MIB_DEVICE_CLASS:
        {
            mibGet->Param.Class = LoRaMacDeviceClass;
            break;
        }
        case MIB_NETWORK_JOINED:
        {
            mibGet->Param.IsNetworkJoined = IsLoRaMacNetworkJoined;
            break;
        }
        case MIB_NET_ID:
        {
            mibGet->Param.NetID = LoRaMacNetID;
            break;
        }
        case MIB_DEV_ADDR:
        {
            mibGet->Param.DevAddr = LoRaMacDevAddr;
            break;
        }
        case MIB_NWK_SKEY:
        {
            mibGet->Param.NwkSKey = LoRaMacNwkSKey;
            break;
        }
        case MIB_APP_SKEY:
        {
            mibGet->Param.AppSKey = LoRaMacAppSKey;
            break;
        }
        case MIB_PUBLIC_NETWORK:
        {
            mibGet->Param.EnablePublicNetwork = PublicNetwork;
            break;
        }
        case MIB_CHANNELS_NB_REP:
        {
            mibGet->Param.ChannelNbRep = LoRaMacParams.ChannelsNbRep;
            break;
        }
        case MIB_MAX_RX_WINDOW_DURATION:
        {
            mibGet->Param.MaxRxWindow = LoRaMacParams.MaxRxWindow;
            break;
        }
        case MIB_CHANNELS_DEFAULT_TX_POWER:
        {
            mibGet->Param.ChannelsDefaultTxPower = LoRaMacParamsDefaults.ChannelsTxPower;
            break;
        }
        case MIB_CHANNELS_TX_POWER:
        {
            mibGet->Param.ChannelsTxPower = LoRaMacParams.ChannelsTxPower;
            break;
        }
        case MIB_UPLINK_COUNTER:
        {
            mibGet->Param.UpLinkCounter = UpLinkCounter;
            break;
        }
        case MIB_DOWNLINK_COUNTER:
        {
            mibGet->Param.DownLinkCounter = DownLinkCounter;
            break;
        }
        case MIB_MULTICAST_CHANNEL:
        {
            mibGet->Param.MulticastList = MulticastChannels;
            break;
        }
        case MIB_SYSTEM_MAX_RX_ERROR:
        {
            mibGet->Param.SystemMaxRxError = LoRaMacParams.SystemMaxRxError;
            break;
        }
        case MIB_MIN_RX_SYMBOLS:
        {
            mibGet->Param.MinRxSymbols = LoRaMacParams.MinRxSymbols;
            break;
        }
        default:
            status = LORAMAC_STATUS_SERVICE_UNKNOWN;
            break;
    }

    return status;
}

LoRaMacStatus_t LoRaMacMibSetRequestConfirm( MibRequestConfirm_t *mibSet )
{
    LoRaMacStatus_t status = LORAMAC_STATUS_OK;

    if( mibSet == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    switch( mibSet->Type )
    {
        case MIB_DEVICE_CLASS:
        {
            LoRaMacDeviceClass = mibSet->Param.Class;
            switch( LoRaMacDeviceClass )
            {
                case CLASS_A:
                {
                    // Set the radio into sleep to setup a defined state
                    Radio.Sleep( );
                    break;
                }
                case CLASS_B:
                {
                    break;
                }
                case CLASS_D:
                    isr_printf("TODO MIB_DEVICE_CLASS:D\r\n");
                    break;
            }
            break;
        }
        case MIB_NETWORK_JOINED:
        {
            IsLoRaMacNetworkJoined = mibSet->Param.IsNetworkJoined;
            if (!IsLoRaMacNetworkJoined) {
                BeaconCtx.timeout_rx.detach();
                BeaconCtx.timeout_guard.detach();
                BeaconCtx.state = BEACON_STATE_NONE;
            }
            break;
        }
        case MIB_NET_ID:
        {
            LoRaMacNetID = mibSet->Param.NetID;
            break;
        }
        case MIB_DEV_ADDR:
        {
            LoRaMacDevAddr = mibSet->Param.DevAddr;
            break;
        }
        case MIB_NWK_SKEY:
        {
            if( mibSet->Param.NwkSKey != NULL )
            {
                memcpy1( LoRaMacNwkSKey, mibSet->Param.NwkSKey,
                               sizeof( LoRaMacNwkSKey ) );
            }
            else
            {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_APP_SKEY:
        {
            if( mibSet->Param.AppSKey != NULL )
            {
                memcpy1( LoRaMacAppSKey, mibSet->Param.AppSKey,
                               sizeof( LoRaMacAppSKey ) );
            }
            else
            {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_PUBLIC_NETWORK:
        {
            PublicNetwork = mibSet->Param.EnablePublicNetwork;
            Radio.SetPublicNetwork( PublicNetwork );
            break;
        }
        case MIB_CHANNELS_NB_REP:
        {
            if( ( mibSet->Param.ChannelNbRep >= 1 ) &&
                ( mibSet->Param.ChannelNbRep <= 15 ) )
            {
                LoRaMacParams.ChannelsNbRep = mibSet->Param.ChannelNbRep;
            }
            else
            {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_MAX_RX_WINDOW_DURATION:
        {
            LoRaMacParams.MaxRxWindow = mibSet->Param.MaxRxWindow;
            break;
        }
        case MIB_CHANNELS_DEFAULT_TX_POWER:
        {
            if( ValueInRange( mibSet->Param.ChannelsDefaultTxPower,
                              LORAMAC_MAX_TX_POWER, LORAMAC_MIN_TX_POWER ) )
            {
                LoRaMacParamsDefaults.ChannelsTxPower = mibSet->Param.ChannelsDefaultTxPower;
            }
            else
            {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_CHANNELS_TX_POWER:
        {
            if( ValueInRange( mibSet->Param.ChannelsTxPower,
                              LORAMAC_MAX_TX_POWER, LORAMAC_MIN_TX_POWER ) )
            {
                LoRaMacParams.ChannelsTxPower = mibSet->Param.ChannelsTxPower;
            }
            else
            {
                status = LORAMAC_STATUS_PARAMETER_INVALID;
            }
            break;
        }
        case MIB_UPLINK_COUNTER:
        {
            UpLinkCounter = mibSet->Param.UpLinkCounter;
            break;
        }
        case MIB_DOWNLINK_COUNTER:
        {
            DownLinkCounter = mibSet->Param.DownLinkCounter;
            break;
        }
        case MIB_SYSTEM_MAX_RX_ERROR:
        {
            LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError = mibSet->Param.SystemMaxRxError;
            break;
        }
        case MIB_MIN_RX_SYMBOLS:
        {
            LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols = mibSet->Param.MinRxSymbols;
            break;
        }
        default:
            status = LORAMAC_STATUS_SERVICE_UNKNOWN;
            break;
    }

    return status;
}

LoRaMacStatus_t LoRaMacMulticastChannelLink( MulticastParams_t *channelParam )
{
    if( channelParam == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    // Reset downlink counter
    channelParam->DownLinkCounter = 0;

    if( MulticastChannels == NULL )
    {
        // New node is the fist element
        MulticastChannels = channelParam;
    }
    else
    {
        MulticastParams_t *cur = MulticastChannels;

        // Search the last node in the list
        while( cur->Next != NULL )
        {
            cur = cur->Next;
        }
        // This function always finds the last node
        cur->Next = channelParam;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMulticastChannelUnlink( MulticastParams_t *channelParam )
{
    if( channelParam == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    if( MulticastChannels != NULL )
    {
        if( MulticastChannels == channelParam )
        {
          // First element
          MulticastChannels = channelParam->Next;
        }
        else
        {
            MulticastParams_t *cur = MulticastChannels;

            // Search the node in the list
            while( cur->Next && cur->Next != channelParam )
            {
                cur = cur->Next;
            }
            // If we found the node, remove it
            if( cur->Next )
            {
                cur->Next = channelParam->Next;
            }
        }
        channelParam->Next = NULL;
    }

    return LORAMAC_STATUS_OK;
}

LoRaMacStatus_t LoRaMacMlmeRequest( MlmeReq_t *mlmeRequest )
{
    LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN;
    LoRaMacHeader_t macHdr;

    if( mlmeRequest == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    memset1( ( uint8_t* ) &MlmeConfirm, 0, sizeof( MlmeConfirm ) );

    MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_MLMEREQ;

    switch( mlmeRequest->Type )
    {
        case MLME_JOIN:
        {
            if( ( mlmeRequest->Req.Join.DevEui == NULL ) ||
                ( mlmeRequest->Req.Join.AppEui == NULL ) ||
                ( mlmeRequest->Req.Join.AppKey == NULL ) ||
                ( mlmeRequest->Req.Join.NbTrials == 0 ) )
            {
                return LORAMAC_STATUS_PARAMETER_INVALID;
            }

            // Enables at least the usage of all datarates.
            if( mlmeRequest->Req.Join.NbTrials < 48 )
            {
                mlmeRequest->Req.Join.NbTrials = 48;
            }

            LoRaMacFlags.Bits.MlmeReq = 1;
            MlmeConfirm.MlmeRequest = mlmeRequest->Type;

            LoRaMacDevEui = mlmeRequest->Req.Join.DevEui;
            LoRaMacAppEui = mlmeRequest->Req.Join.AppEui;
            LoRaMacAppKey = mlmeRequest->Req.Join.AppKey;
            MaxJoinRequestTrials = mlmeRequest->Req.Join.NbTrials;

            // Reset variable JoinRequestTrials
            JoinRequestTrials = 0;

            // Setup header information
            macHdr.Value = 0;
            macHdr.Bits.MType  = FRAME_TYPE_JOIN_REQ;

            ResetMacParameters( );
            expecting_beacon = false;
            BeaconCtx.state = BEACON_STATE_NONE;

            Channel = 0;    // start with first channel
            isr_printf("<ch0>");
            isr_printf("mlme-join-send ch%u\r\n", Channel);
            status = Send( &macHdr, 0, NULL, 0 );
            break;
        }
        case MLME_LINK_CHECK:
        {
            LoRaMacFlags.Bits.MlmeReq = 1;
            // LoRaMac will send this command piggy-pack
            MlmeConfirm.MlmeRequest = mlmeRequest->Type;

            status = AddMacCommand( MOTE_MAC_LINK_CHECK_REQ, 0, 0 );
            break;
        }
        case MLME_TXCW:
        {
            MlmeConfirm.MlmeRequest = mlmeRequest->Type;
            LoRaMacFlags.Bits.MlmeReq = 1;
            status = SetTxContinuousWave( mlmeRequest->Req.TxCw.Timeout );
            break;
        }
        case MLME_TXCW_1:
        {
            MlmeConfirm.MlmeRequest = mlmeRequest->Type;
            LoRaMacFlags.Bits.MlmeReq = 1;
            status = SetTxContinuousWave1( mlmeRequest->Req.TxCw.Timeout, mlmeRequest->Req.TxCw.Frequency, mlmeRequest->Req.TxCw.Power );
            break;
        }
        default:
            break;
    }

    if( status != LORAMAC_STATUS_OK )
    {
        NodeAckRequested = false;
        LoRaMacFlags.Bits.MlmeReq = 0;
    }

    return status;
}

LoRaMacStatus_t LoRaMacMcpsRequest( McpsReq_t *mcpsRequest )
{
    LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN;
    LoRaMacHeader_t macHdr;
    uint8_t fPort = 0;
    void *fBuffer;
    uint16_t fBufferSize;
    bool readyToSend = false;

    if( mcpsRequest == NULL )
    {
        return LORAMAC_STATUS_PARAMETER_INVALID;
    }

    macHdr.Value = 0;
    memset1 ( ( uint8_t* ) &McpsConfirm, 0, sizeof( McpsConfirm ) );
    McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_MCPSREQ;

    switch( mcpsRequest->Type )
    {
        case MCPS_UNCONFIRMED:
        {
            readyToSend = true;

            macHdr.Bits.MType = FRAME_TYPE_DATA_UNCONFIRMED_UP;
            fPort = mcpsRequest->Req.Unconfirmed.fPort;
            fBuffer = mcpsRequest->Req.Unconfirmed.fBuffer;
            fBufferSize = mcpsRequest->Req.Unconfirmed.fBufferSize;
            break;
        }
        case MCPS_CONFIRMED:
        {
            readyToSend = true;

            macHdr.Bits.MType = FRAME_TYPE_DATA_CONFIRMED_UP;
            fPort = mcpsRequest->Req.Confirmed.fPort;
            fBuffer = mcpsRequest->Req.Confirmed.fBuffer;
            fBufferSize = mcpsRequest->Req.Confirmed.fBufferSize;
            break;
        }
        case MCPS_PROPRIETARY:
        {
            readyToSend = true;

            macHdr.Bits.MType = FRAME_TYPE_PROPRIETARY;
            fBuffer = mcpsRequest->Req.Proprietary.fBuffer;
            fBufferSize = mcpsRequest->Req.Proprietary.fBufferSize;
            break;
        }
        default:
            break;
    }

    if( readyToSend == true )
    {
        status = Send( &macHdr, fPort, fBuffer, fBufferSize );
        if( status == LORAMAC_STATUS_OK )
        {
            McpsConfirm.McpsRequest = mcpsRequest->Type;
            LoRaMacFlags.Bits.McpsReq = 1;
        }
        else
        {
            NodeAckRequested = false;
        }
    }

    return status;
}

void LoRaMacTestRxWindowsOn( bool enable )
{
    IsRxWindowsEnabled = enable;
}

void LoRaMacTestSetMic( uint16_t txPacketCounter )
{
    UpLinkCounter = txPacketCounter;
    //IsUpLinkCounterFixed = true;
}

void LoRaMacTestSetChannel( uint8_t channel )
{
    isr_printf("set-testch%u\r\n", channel);
    Channel = channel;
}


static RxConfigParams_t ComputeRxWindowParameters( int8_t datarate, uint32_t rxError )
{
    RxConfigParams_t rxConfigParams = { 0, 0, 0, 0 };
    double tSymbol = 0.0;
 
    rxConfigParams.Datarate = datarate;
    switch( Bandwidths[datarate] )
    {
        default:
        case 125000:
            rxConfigParams.Bandwidth = 0;
            break;
        case 250000:
            rxConfigParams.Bandwidth = 1;
            break;
        case 500000:
            rxConfigParams.Bandwidth = 2;
            break;
    }
 
#if defined( USE_BAND_433 ) || defined( USE_BAND_780 ) || defined( USE_BAND_868 )
    if( datarate == DR_7 )
    { // FSK
        tSymbol = ( 1.0 / ( double )Datarates[datarate] ) * 8.0; // 1 symbol equals 1 byte
    }
    else
#endif
    { // LoRa
        tSymbol = ( ( double )( 1 << Datarates[datarate] ) / ( double )Bandwidths[datarate] ) * 1e3;
    }
 
    rxConfigParams.RxWindowTimeout = MAX( ( uint32_t )ceil( ( ( 2 * LoRaMacParams.MinRxSymbols - 8 ) * tSymbol + 2 * rxError ) / tSymbol ), LoRaMacParams.MinRxSymbols ); // Computed number of symbols
 
    rxConfigParams.RxOffset = ( int32_t )ceil( ( 4.0 * tSymbol ) - ( ( rxConfigParams.RxWindowTimeout * tSymbol ) / 2.0 ) - RADIO_WAKEUP_TIME );
 
    return rxConfigParams;
}