LoRaWAN demo application using grove peripherals generating Cayenne LPP

Dependencies:   lorawan1v1

radio chip selection

Radio chip driver is not included, because two options are available.
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 NAmote72 or Murata discovery, then you must import only sx127x driver.

Grove peripherals -> Cayenne demo

Read LoRaWAN-1.1 page for configuration instructions.

This project adds support for Murata discovery board, in addition to LoRa shields on NUCLEO boards.

Use with sx1272 shield with grove peripherals connected:

D8 D9: ButtonRX TXA3 A4: TempSense
D6 D7:SCL SDA : LEDA1 A2: Pot

Button

Sends two different payload types: short press (under 1 sec)
long press: held down > 1 sec.

serial console keys

115200bps, 8N1
Enter key not used
Keys '0' to '3': cayenne channel number
'0': pot (rotary sensor)
'1': temperature
'2': digital out
'3': analog out

DevEUI configuration

For use on networks which force you to use DevEUI defined by network, comment out HardwareIDtoDevEUI().
HardwareIDtoDevEUI() obtains DevEUI from the CPU unique hardware serial number. However, some networks may force you to use their DevEUI value.

Revision:
0:62e456e60083
Child:
1:3c1d13a0489e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sensorDemoVT100.cpp	Wed Feb 28 14:06:17 2018 -0800
@@ -0,0 +1,1238 @@
+/*
+ / _____)             _              | |
+( (____  _____ ____ _| |_ _____  ____| |__
+ \____ \| ___ |    (_   _) ___ |/ ___)  _ \
+ _____) ) ____| | | || |_| ____( (___| | | |
+(______/|_____)_|_|_| \__)_____)\____)_| |_|
+    (C)2018 Semtech
+
+Description: LoRaMac classB device implementation
+
+License: Revised BSD License, see LICENSE.TXT file include in the project
+
+*/
+
+#include "LoRaMac.h"
+#include "SerialDisplay.h"
+#include "LoRaMacString.h"
+#ifdef ENABLE_VT100
+
+/*!
+ * Defines the application data transmission duty cycle. 5s, value in [ms].
+ */
+#define APP_TX_DUTYCYCLE_us                            8000000
+
+/*!
+ * Defines a random delay for application data transmission duty cycle. 1s,
+ * value in [ms].
+ */
+#define APP_TX_DUTYCYCLE_RND_us                        2000000
+
+/*!
+ * Default datarate
+ */
+
+#if defined( USE_BAND_ARIB_8CH )
+    #define LORAWAN_DEFAULT_DATARATE                    DR_3
+#else
+    #define LORAWAN_DEFAULT_DATARATE                    DR_0
+#endif
+
+/*!
+ * LoRaWAN confirmed messages
+ */
+#define LORAWAN_CONFIRMED_MSG_ON                    true
+
+/*!
+ * LoRaWAN Adaptive Data Rate
+ *
+ * \remark Please note that when ADR is enabled the end-device should be static
+ */
+#define LORAWAN_ADR_ON                              1
+
+#if defined( USE_BAND_868 )
+
+/*!
+ * LoRaWAN ETSI duty cycle control enable/disable
+ *
+ * \remark Please note that ETSI mandates duty cycled transmissions. Use only for test purposes
+ */
+#define LORAWAN_DUTYCYCLE_ON                        false
+
+#define USE_SEMTECH_DEFAULT_CHANNEL_LINEUP          1
+
+#if( USE_SEMTECH_DEFAULT_CHANNEL_LINEUP == 1 ) 
+
+#define LC4                { 867100000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 }
+#define LC5                { 867300000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 }
+#define LC6                { 867500000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 }
+#define LC7                { 867700000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 }
+#define LC8                { 867900000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 }
+#define LC9                { 868800000, { ( ( DR_7 << 4 ) | DR_7 ) }, 2 }
+#define LC10               { 868300000, { ( ( DR_6 << 4 ) | DR_6 ) }, 1 }
+
+#endif
+
+#endif
+
+/*!
+ * LoRaWAN application port
+ */
+#define LORAWAN_APP_PORT                            2
+
+/*!
+ * User application data buffer size
+ */
+#define LORAWAN_APP_DATA_SIZE                       3
+
+
+#ifdef LORAWAN_JOIN_EUI
+    static uint8_t DevEui[] = LORAWAN_DEVICE_EUI;
+    static const uint8_t JoinEui[] = LORAWAN_JOIN_EUI;
+    static const uint8_t NwkKey[] = LORAWAN_ROOT_NWKKEY;
+    #ifdef LORAWAN_ROOT_APPKEY
+        static const uint8_t AppKey[] = LORAWAN_ROOT_APPKEY;
+    #endif
+#else
+    static const uint8_t FNwkSIntKey[] = LORAWAN_FNwkSIntKey;
+    static const uint8_t AppSKey[] = LORAWAN_APPSKEY;
+    static const uint32_t DevAddr = LORAWAN_DEVICE_ADDRESS;
+    #if defined(LORAWAN_SNwkSIntKey) && defined(LORAWAN_NwkSEncKey)
+        static const uint8_t SNwkSIntKey[] = LORAWAN_SNwkSIntKey;
+        static const uint8_t NwkSEncKey[] = LORAWAN_NwkSEncKey;
+    #endif
+#endif
+
+/*!
+ * Application port
+ */
+static uint8_t AppPort = LORAWAN_APP_PORT;
+
+/*!
+ * User application data size
+ */
+static uint8_t gAppDataSize = LORAWAN_APP_DATA_SIZE;
+
+/*!
+ * User application data buffer size
+ */
+#define LORAWAN_APP_DATA_MAX_SIZE                           64
+
+/*!
+ * User application data
+ */
+static uint8_t AppData[LORAWAN_APP_DATA_MAX_SIZE];
+
+/*!
+ * Indicates if the node is sending confirmed or unconfirmed messages
+ */
+static uint8_t gIsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
+
+/*!
+ * Timer to handle the application data transmission duty cycle
+ *
+ */
+LowPowerTimeout tx_timeout;
+
+/*!
+ * Indicates if a new packet can be sent
+ */
+static volatile struct {
+    uint8_t gmi : 1;
+    uint8_t gmc : 1;
+} flags;
+
+/*!
+ * Device states
+ */
+volatile enum eDevicState
+{
+    /* 0 */ DEVICE_STATE_INIT = 0,
+    /* 1 */ DEVICE_STATE_SEND, 
+    /* 2 */ DEVICE_STATE_TRIGGER,
+    /* 3 */ DEVICE_STATE_SLEEP,
+#ifdef LORAWAN_JOIN_EUI
+    /* 4 */ DEVICE_STATE_JOIN,
+    /* 5 */ DEVICE_STATE_JOIN_OK
+#endif /* LORAWAN_JOIN_EUI */
+} DeviceState, WakeUpState;
+
+#if defined(TARGET_MOTE_L152RC) && !defined(TARGET_FF_ARDUINO)
+    #define TARGET_FF_ARDUINO
+#endif
+
+#if defined(TARGET_FF_ARDUINO)
+DigitalIn d8(D8);
+DigitalOut extLed(D15);
+#endif /* TARGET_FF_ARDUINO */
+
+#if defined(TARGET_FF_MORPHO) && !defined(TARGET_DISCO_L072CZ_LRWAN1)
+    #define JUMPER_ENABLE
+#endif /* */
+
+#ifdef JUMPER_ENABLE
+    #define TX_INTERVAL_US      15000000 
+    DigitalOut jumper_out(PC_10);
+    InterruptIn jumper_in(PC_12);
+#endif /* JUMPER_ENABLE */
+
+uint8_t c_ch;
+us_timestamp_t buttonStartAt;
+#ifdef TARGET_DISCO_L072CZ_LRWAN1
+PwmOut pwm(PA_0);
+#elif defined(TARGET_FF_ARDUINO)
+PwmOut pwm(PB_11);
+#endif /* TARGET_DISCO_L072CZ_LRWAN1 */
+volatile int cayenne_ack_ch;
+
+/*!
+ * LoRaWAN compliance tests support data
+ */
+struct ComplianceTest_s
+{
+    bool Running;
+    uint8_t State;
+    bool IsTxConfirmed;
+    uint8_t AppPort;
+    uint8_t AppDataSize;
+    uint8_t *AppDataBuffer;
+    uint16_t DownLinkCounter;
+    bool LinkCheck;
+    uint8_t DemodMargin;
+    uint8_t NbGateways;
+}ComplianceTest;
+
+McpsIndication_t gmi;
+
+McpsConfirm_t gmc;
+
+#ifdef JUMPER_ENABLE
+void autoUplink()
+{
+    if (jumper_in.read() == 1) {
+        tx_timeout.attach_us(autoUplink, TX_INTERVAL_US);
+    }
+
+    c_ch = 0xff;
+    DeviceState = DEVICE_STATE_SEND;
+}
+
+void jumper_callback()
+{
+    tx_timeout.attach_us(autoUplink, TX_INTERVAL_US);
+}
+#endif /* JUMPER_ENABLE */
+
+static void
+clearIndications()
+{
+    vt.SetCursorPos(ROW_MCPS_CONF, 1);
+    vt.printf("\e[K");
+    vt.SetCursorPos(ROW_MCPS_IND, 1);
+    vt.printf("\e[K");
+    vt.SetCursorPos(ROW_MLME_IND, 1);
+    vt.printf("\e[K");
+    vt.SetCursorPos(ROW_MLME_CONF, 1);
+    vt.printf("\e[K");
+
+    vt.SetCursorPos(ROW_MIC+3, 1);
+    vt.printf("\e[K");
+}
+
+#define LPP_DIGITAL_INPUT       0       // 1 byte
+#define LPP_DIGITAL_OUTPUT      1       // 1 byte
+#define LPP_ANALOG_INPUT        2       // 2 bytes, 0.01 signed
+#define LPP_ANALOG_OUTPUT       3       // 2 bytes, 0.01 signed
+#define LPP_LUMINOSITY          101     // 2 bytes, 1 lux unsigned
+#define LPP_PRESENCE            102     // 1 byte, 1
+#define LPP_TEMPERATURE         103     // 2 bytes, 0.1°C signed
+#define LPP_RELATIVE_HUMIDITY   104     // 1 byte, 0.5% unsigned
+#define LPP_ACCELEROMETER       113     // 2 bytes per axis, 0.001G
+#define LPP_BAROMETRIC_PRESSURE 115     // 2 bytes 0.1 hPa Unsigned
+#define LPP_GYROMETER           134     // 2 bytes per axis, 0.01 °/s
+#define LPP_GPS                 136     // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01m
+
+
+// Data ID + Data Type + Data Size
+#define LPP_DIGITAL_INPUT_SIZE       3
+#define LPP_DIGITAL_OUTPUT_SIZE      3
+#define LPP_ANALOG_INPUT_SIZE        4
+#define LPP_ANALOG_OUTPUT_SIZE       4
+#define LPP_LUMINOSITY_SIZE          4
+#define LPP_PRESENCE_SIZE            3
+#define LPP_TEMPERATURE_SIZE         4
+#define LPP_RELATIVE_HUMIDITY_SIZE   3
+#define LPP_ACCELEROMETER_SIZE       8
+#define LPP_BAROMETRIC_PRESSURE_SIZE 4
+#define LPP_GYROMETER_SIZE           8
+#define LPP_GPS_SIZE                 11
+
+#define CAYENNE_CH_DOUT     2
+#define CAYENNE_CH_AOUT     3
+#define CAYENNE_CH_TEMP     0
+#define CAYENNE_CH_POT      1
+
+AnalogIn a1(A1);
+AnalogIn a3(A3);
+
+const unsigned R0 = 100000;
+const unsigned B = 4275;
+
+/*!
+ * \brief   Prepares the payload of the frame
+ */
+static void PrepareTxFrame( uint8_t port )
+{
+    uint16_t u16, rot;
+    float t, f, R;
+
+    if (c_ch != 0xff) {
+        gAppDataSize = 0;
+        AppData[gAppDataSize++] = c_ch;
+        switch (c_ch) {
+            case CAYENNE_CH_TEMP:
+                AppData[gAppDataSize++] = LPP_TEMPERATURE;
+                u16 = a3.read_u16() >> 4;
+                R = 4096.0 / u16 - 1.0;
+                R = R0 * R;
+                t = 1.0/(log(R/R0)/B+1/298.15)-273.15;
+                u16 = t * 10;  // 0.1C per bit
+                AppData[gAppDataSize++] = u16 >> 8;
+                AppData[gAppDataSize++] = u16;
+                break;
+            case CAYENNE_CH_POT:
+                AppData[gAppDataSize++] = LPP_ANALOG_INPUT;
+                u16 = a1.read_u16();    // pot (rotary angle)
+                f = u16 / 198.6;    // scale 65535/3.3 to 0.01v per bit
+                rot = (uint16_t) f;
+                AppData[gAppDataSize++] = rot >> 8;
+                AppData[gAppDataSize++] = rot;
+                break;
+            case CAYENNE_CH_DOUT:
+                AppData[gAppDataSize++] = LPP_DIGITAL_OUTPUT;
+                AppData[gAppDataSize++] = extLed.read();
+                break;
+            case CAYENNE_CH_AOUT:
+                AppData[gAppDataSize++] = LPP_ANALOG_OUTPUT;
+                u16 = pwm.read() * 100;
+                AppData[gAppDataSize++] = u16 >> 8;
+                AppData[gAppDataSize++] = u16;
+                break;
+        }
+        return;
+    } else if (cayenne_ack_ch != -1) {
+        switch (cayenne_ack_ch) {
+            case CAYENNE_CH_DOUT:
+                AppData[gAppDataSize++] = LPP_DIGITAL_OUTPUT;
+                AppData[gAppDataSize++] = extLed.read();
+                break;
+            case CAYENNE_CH_AOUT:
+                AppData[gAppDataSize++] = LPP_ANALOG_OUTPUT;
+                u16 = pwm.read() * 100;
+                AppData[gAppDataSize++] = u16 >> 8;
+                AppData[gAppDataSize++] = u16;
+                break;
+        }
+        cayenne_ack_ch = -1;
+    }
+
+    while (d8.read() == 1) {
+        us_timestamp_t duration = LoRaMacReadTimer() - buttonStartAt;
+        if (duration > 1000000) {
+            gAppDataSize = 0;
+            AppData[gAppDataSize++] = CAYENNE_CH_DOUT;
+            AppData[gAppDataSize++] = LPP_DIGITAL_OUTPUT;
+            AppData[gAppDataSize++] = extLed.read();
+            return;
+        }
+    }
+
+    switch( port ) {
+        case LORAWAN_APP_PORT:
+            gAppDataSize = 0;
+            AppData[gAppDataSize++] = CAYENNE_CH_TEMP;
+            AppData[gAppDataSize++] = LPP_TEMPERATURE;
+            u16 = a3.read_u16() >> 4;
+            R = 4096.0 / u16 - 1.0;
+            R = R0 * R;
+            t = 1.0/(log(R/R0)/B+1/298.15)-273.15;
+            u16 = t * 10;  // 0.1C per bit
+            AppData[gAppDataSize++] = u16 >> 8;
+            AppData[gAppDataSize++] = u16;
+            AppData[gAppDataSize++] = CAYENNE_CH_POT;
+            AppData[gAppDataSize++] = LPP_ANALOG_INPUT;
+            u16 = a1.read_u16();    // pot (rotary angle)
+            f = u16 / 198.6;    // scale 65535/3.3 to 0.01v per bit
+            rot = (uint16_t) f;
+            AppData[gAppDataSize++] = rot >> 8;
+            AppData[gAppDataSize++] = rot;
+
+            AppData[gAppDataSize++] = CAYENNE_CH_DOUT;
+            AppData[gAppDataSize++] = LPP_DIGITAL_OUTPUT;
+            AppData[gAppDataSize++] = extLed.read();
+            break;
+        case 224:
+            if( ComplianceTest.LinkCheck == true ) {
+                ComplianceTest.LinkCheck = false;
+                gAppDataSize = 3;
+                AppData[0] = 5;
+                AppData[1] = ComplianceTest.DemodMargin;
+                AppData[2] = ComplianceTest.NbGateways;
+                ComplianceTest.State = 1;
+            } else {
+                switch( ComplianceTest.State ) {
+                    case 4:
+                        ComplianceTest.State = 1;
+                        break;
+                    case 1:
+                        gAppDataSize = 2;
+                        AppData[0] = ComplianceTest.DownLinkCounter >> 8;
+                        AppData[1] = ComplianceTest.DownLinkCounter;
+                        break;
+                }
+            }
+            break;
+        default:
+            break;
+    }
+}
+
+/*!
+ * \brief   Prepares the payload of the frame
+ *
+ * \retval  [0: frame could be send, 1: error]
+ */
+static LoRaMacStatus_t SendFrame(bool IsTxConfirmed, uint8_t AppDataSize)
+{
+    LoRaMacStatus_t status;
+    char str[64];
+    McpsReq_t mcpsReq;
+    LoRaMacTxInfo_t txInfo;
+    
+    if( LoRaMacQueryTxPossible( AppDataSize, &txInfo ) != LORAMAC_STATUS_OK )
+    {
+        // Send empty frame in order to flush MAC commands
+        mcpsReq.Type = MCPS_UNCONFIRMED;
+        mcpsReq.Req.fBuffer = NULL;
+        mcpsReq.Req.fBufferSize = 0;
+        mcpsReq.Req.Datarate = LORAWAN_DEFAULT_DATARATE;
+    }
+    else
+    {
+        SerialDisplayUpdateFrameType(IsTxConfirmed);
+        if( IsTxConfirmed == false )
+        {
+            mcpsReq.Type = MCPS_UNCONFIRMED;
+            mcpsReq.Req.fPort = AppPort;
+            mcpsReq.Req.fBuffer = AppData;
+            mcpsReq.Req.fBufferSize = AppDataSize;
+            mcpsReq.Req.Datarate = LORAWAN_DEFAULT_DATARATE;
+        }
+        else
+        {
+            mcpsReq.Type = MCPS_CONFIRMED;
+            mcpsReq.Req.fPort = AppPort;
+            mcpsReq.Req.fBuffer = AppData;
+            mcpsReq.Req.fBufferSize = AppDataSize;
+            mcpsReq.Req.NbTrials = 8;
+            mcpsReq.Req.Datarate = LORAWAN_DEFAULT_DATARATE;
+        }
+    }
+
+    clearIndications();
+    status = LoRaMacMcpsRequest( &mcpsReq );
+    if (status == LORAMAC_STATUS_OK) {
+        SerialDisplayUplink(mcpsReq.Req.fPort, AppData, mcpsReq.Req.fBufferSize);
+        vt.SetCursorPos( ROW_END, 1 );
+        vt.printf("sendFrame() OK %u\e[K", AppDataSize);
+    } else {
+        LoRaMacStatus_to_string(status, str);
+        vt.SetCursorPos( ROW_END, 1 );
+        vt.printf("sendFrame() %s rx%d\e[K", str, LoRaMacGetRxSlot());
+    }
+
+
+    return status;
+} // ..SendFrame()
+
+/*!
+ * \brief   MCPS-Confirm event function
+ *
+ * \param   [IN] mcpsConfirm - Pointer to the confirm structure,
+ *               containing confirm attributes.
+ */
+static void McpsConfirm( const McpsConfirm_t *mcpsConfirm )
+{
+    char str[64];
+
+    vt.SetCursorPos( ROW_MCPS_CONF, 1);
+    vt.printf("McpsConfirm up:%uhz ", mcpsConfirm->UpLinkFreqHz);
+    LoRaMacEventInfoStatus_to_string(mcpsConfirm->Status, str);
+    if (mcpsConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK)
+        vt.printf("%s \e[K", str);
+    else
+        vt.printf("\e[31m%s\e[0m \e[K", str);
+
+    if (mcpsConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK)
+    {
+        switch( mcpsConfirm->McpsRequest )
+        {
+            case MCPS_UNCONFIRMED:
+            {
+                // Check Datarate
+                // Check TxPower
+                break;
+            }
+            case MCPS_CONFIRMED:
+            {
+                // Check Datarate
+                // Check TxPower
+                // Check AckReceived
+                // Check NbTrials
+                //LoRaMacUplinkStatus.Acked = mcpsConfirm->AckReceived;
+                break;
+            }
+            case MCPS_PROPRIETARY:
+            {
+                break;
+            }
+            default:
+                break;
+        }
+    } else {
+        /* fail */
+    }
+    memcpy(&gmc, mcpsConfirm, sizeof(McpsConfirm_t));
+    flags.gmc = true;
+
+    DeviceState = DEVICE_STATE_TRIGGER;
+} // ..McpsConfirm()
+
+
+/*!
+ * \brief   MCPS-Indication event function
+ *
+ * \param   [IN] mcpsIndication - Pointer to the indication structure,
+ *               containing indication attributes.
+ */
+static void McpsIndication( const McpsIndication_t *mcpsIndication )
+{
+    char str[64];
+
+    memcpy(&gmi, mcpsIndication, sizeof(McpsIndication_t));
+    flags.gmi = true;
+
+    vt.SetCursorPos(ROW_MCPS_CONF, 1);
+    vt.printf("\e[K");  // clear stale mcpsconf if retrying
+
+    vt.SetCursorPos( ROW_MCPS_IND, 0);
+    vt.printf("McpsIndication rx%d ", mcpsIndication->RxSlot);
+    if (mcpsIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK)
+    {
+        LoRaMacEventInfoStatus_to_string(mcpsIndication->Status, str);
+        vt.printf("\e[31m%s attempt%u\e[0m\e[K", str, mcpsIndication->attempt);
+        return;
+    }
+    vt.printf("OK \e[K");
+
+    switch( mcpsIndication->McpsIndication )
+    {
+        case MCPS_UNCONFIRMED:
+        {
+            break;
+        }
+        case MCPS_CONFIRMED:
+        {
+            /* ack sent by mac layer */
+            break;
+        }
+        case MCPS_PROPRIETARY:
+        {
+            break;
+        }
+        case MCPS_MULTICAST:
+        {
+            break;
+        }
+        default:
+            break;
+    }
+
+    // Check Multicast
+    // Check Port
+    // Check Datarate
+    // Check FramePending
+    // Check Buffer
+    // Check BufferSize
+    // Check Rssi
+    // Check Snr
+    // Check RxSlot
+
+
+    if( ComplianceTest.Running == true )
+    {
+        ComplianceTest.DownLinkCounter++;
+    }
+
+    if( mcpsIndication->RxData == true )
+    {
+        unsigned n;
+        for (n = 0; n < mcpsIndication->BufferSize; n += 4) {
+            uint16_t val = mcpsIndication->Buffer[n+1] << 8;
+            val += mcpsIndication->Buffer[n+2];
+            cayenne_ack_ch = mcpsIndication->Buffer[n];
+            switch (mcpsIndication->Buffer[n]) {
+                case CAYENNE_CH_DOUT:
+                    extLed.write(val);
+                    break;
+                case CAYENNE_CH_AOUT:
+                    pwm.write(val / 100.0);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        switch( mcpsIndication->Port )
+        {
+        case 1: // The application LED can be controlled on port 1 or 2
+        case 2:
+            break;
+        case 224:
+            if( ComplianceTest.Running == false )
+            {
+                // Check compliance test enable command (i)
+                if( ( mcpsIndication->BufferSize == 4 ) &&
+                    ( mcpsIndication->Buffer[0] == 0x01 ) &&
+                    ( mcpsIndication->Buffer[1] == 0x01 ) &&
+                    ( mcpsIndication->Buffer[2] == 0x01 ) &&
+                    ( mcpsIndication->Buffer[3] == 0x01 ) )
+                {
+                    gIsTxConfirmed = false;
+                    AppPort = 224;
+                    gAppDataSize = 2;
+                    ComplianceTest.DownLinkCounter = 0;
+                    ComplianceTest.LinkCheck = false;
+                    ComplianceTest.DemodMargin = 0;
+                    ComplianceTest.NbGateways = 0;
+                    ComplianceTest.Running = true;
+                    ComplianceTest.State = 1;
+                    
+                    MibRequestConfirm_t mibReq;
+                    mibReq.Type = MIB_ADR;
+                    mibReq.Param.AdrEnable = true;
+                    LoRaMacMibSetRequestConfirm( &mibReq );
+
+#if defined( USE_BAND_868 )
+                    DutyCycleOn = false;
+#endif
+                }
+            }
+            else
+            {
+                ComplianceTest.State = mcpsIndication->Buffer[0];
+                switch( ComplianceTest.State )
+                {
+                case 0: // Check compliance test disable command (ii)
+                    gIsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
+                    AppPort = LORAWAN_APP_PORT;
+                    gAppDataSize = LORAWAN_APP_DATA_SIZE;
+                    ComplianceTest.DownLinkCounter = 0;
+                    ComplianceTest.Running = false;
+
+                    MibRequestConfirm_t mibReq;
+                    mibReq.Type = MIB_ADR;
+                    mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
+                    LoRaMacMibSetRequestConfirm( &mibReq );
+#if defined( USE_BAND_868 )
+                    DutyCycleOn = LORAWAN_DUTYCYCLE_ON;
+#endif
+                    break;
+                case 1: // (iii, iv)
+                    gAppDataSize = 2;
+                    break;
+                case 2: // Enable confirmed messages (v)
+                    gIsTxConfirmed = true;
+                    ComplianceTest.State = 1;
+                    break;
+                case 3:  // Disable confirmed messages (vi)
+                    gIsTxConfirmed = false;
+                    ComplianceTest.State = 1;
+                    break;
+                case 4: // (vii)
+                    gAppDataSize = mcpsIndication->BufferSize;
+
+                    AppData[0] = 4;
+                    for( uint8_t i = 1; i < gAppDataSize; i++ )
+                    {
+                        AppData[i] = mcpsIndication->Buffer[i] + 1;
+                    }
+                    break;
+                case 5: // (viii)
+                    {
+                        MlmeReq_t mlmeReq;
+                        mlmeReq.Type = MLME_LINK_CHECK;
+                        LoRaMacMlmeRequest( &mlmeReq );
+                    }
+                    break;
+                case 6: // (ix)
+                    {
+#ifdef LORAWAN_JOIN_EUI
+                        MlmeReq_t mlmeReq = {};
+
+                        // Disable TestMode and revert back to normal operation
+                        gIsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
+                        AppPort = LORAWAN_APP_PORT;
+                        gAppDataSize = LORAWAN_APP_DATA_SIZE;
+                        ComplianceTest.DownLinkCounter = 0;
+                        ComplianceTest.Running = false;
+
+                        MibRequestConfirm_t mibReq;
+                        mibReq.Type = MIB_ADR;
+                        mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
+                        LoRaMacMibSetRequestConfirm( &mibReq );
+#if defined( USE_BAND_868 )
+                        DutyCycleOn = LORAWAN_DUTYCYCLE_ON;
+#endif
+
+                        mlmeReq.Type = MLME_JOIN;
+
+                        mlmeReq.Req.Join.DevEui = DevEui;
+                        mlmeReq.Req.Join.JoinEui = JoinEui;
+                        mlmeReq.Req.Join.NwkKey = NwkKey;
+    #ifdef LORAWAN_ROOT_APPKEY
+                        mlmeReq.Req.Join.AppKey = AppKey;
+    #endif /* LORAWAN_ROOT_APPKEY */
+
+                        LoRaMacMlmeRequest( &mlmeReq );
+#endif /* LORAWAN_JOIN_EUI */
+                        DeviceState = DEVICE_STATE_SLEEP;
+                    }
+                    break;
+                case 7: // Switch end device Class
+                    {
+                        MlmeReq_t mlmeReq;
+
+                        mlmeReq.Type = MLME_SWITCH_CLASS;
+
+                        // CLASS_A = 0, CLASS_B = 1, CLASS_C = 2
+                        mlmeReq.Req.SwitchClass.Class = ( DeviceClass_t )mcpsIndication->Buffer[1];
+
+                        LoRaMacMlmeRequest( &mlmeReq );
+
+                        PrepareTxFrame( AppPort );
+                        /*status =*/ SendFrame(gIsTxConfirmed, gAppDataSize);
+                    }
+                    break;
+                case 8: // Send PingSlotInfoReq
+                    {
+                        MlmeReq_t mlmeReq;
+
+                        mlmeReq.Type = MLME_PING_SLOT_INFO;
+
+                        mlmeReq.Req.PingSlotInfo.Value = mcpsIndication->Buffer[1];
+
+                        LoRaMacMlmeRequest( &mlmeReq );
+                        PrepareTxFrame( AppPort );
+                        /*status =*/ SendFrame(gIsTxConfirmed, gAppDataSize);
+                    }
+                    break;
+                case 9: // Send BeaconTimingReq
+                    {
+                        MlmeReq_t mlmeReq;
+
+                        mlmeReq.Type = MLME_BEACON_TIMING;
+
+                        LoRaMacMlmeRequest( &mlmeReq );
+                        PrepareTxFrame( AppPort );
+                        /*status =*/ SendFrame(gIsTxConfirmed, gAppDataSize);
+                    }
+                    break;
+                default:
+                    break;
+                }
+            }
+            break;
+        default:
+            break;
+        }
+
+    }
+
+} // ..McpsIndication()
+
+#ifdef LORAWAN_JOIN_EUI 
+void
+join(uint8_t tries)
+{
+    char str[64];
+    LoRaMacStatus_t status;
+    MlmeReq_t mlmeReq = { };
+
+    mlmeReq.Type = MLME_JOIN;
+
+    clearIndications();
+#ifdef LORAWAN_ROOT_APPKEY
+    mlmeReq.Req.Join.AppKey = AppKey;
+#endif
+    mlmeReq.Req.Join.DevEui = DevEui;
+    mlmeReq.Req.Join.JoinEui = JoinEui;
+    mlmeReq.Req.Join.NwkKey = NwkKey;
+    mlmeReq.Req.Join.NbTrials = tries;
+    status = LoRaMacMlmeRequest( &mlmeReq );
+    if (status != LORAMAC_STATUS_OK) {
+        LoRaMacStatus_to_string(status, str);
+    } else
+        extLed = 1;
+}
+#endif /* LORAWAN_JOIN_EUI */
+
+/*!
+ * \brief   MLME-Confirm event function
+ *
+ * \param   [IN] mlmeConfirm - Pointer to the confirm structure,
+ *               containing confirm attributes.
+ */
+static void MlmeConfirm( const MlmeConfirm_t *mlmeConfirm )
+{
+    char str[64];
+    static uint8_t failCnt = 0;
+
+    vt.SetCursorPos(ROW_MLME_CONF, 1);
+    Mlme_to_string(mlmeConfirm->MlmeRequest, str);
+    vt.printf("MlmeConfirm %s ", str);
+    LoRaMacEventInfoStatus_to_string(mlmeConfirm->Status, str);
+    if (mlmeConfirm->Status != LORAMAC_EVENT_INFO_STATUS_OK)
+        vt.printf("\e[31m%s \e[0m \e[K", str);
+    else
+        vt.printf("%s \e[K", str);
+
+#if defined(LORAWAN_ROOT_APPKEY) && defined(LORAWAN_JOIN_EUI)
+    /* 1v1 joinNonce is incrementing non-volatile value */
+    if (mlmeConfirm->MlmeRequest == MLME_JOIN) {
+        vt.printf(" rxJoinNonce:%u vs %u", 
+            mlmeConfirm->fields.join.rxJoinNonce,
+            mlmeConfirm->fields.join.myJoinNonce
+        );
+    }
+#endif /* LORAWAN_ROOT_APPKEY */
+
+    if (mlmeConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK)
+    {
+        failCnt = 0;
+        switch (mlmeConfirm->MlmeRequest)
+        {
+#ifdef LORAWAN_JOIN_EUI
+            case MLME_JOIN:
+            {
+                // Status is OK, node has joined the network
+                /* collect any mac cmds from server until expected channel mask */
+                extLed = 0;
+                DeviceState = DEVICE_STATE_JOIN_OK;
+                break;
+            }
+#endif /* LORAWAN_JOIN_EUI*/
+            case MLME_LINK_CHECK:
+            {
+                // Check DemodMargin
+                // Check NbGateways
+                if( ComplianceTest.Running == true )
+                {
+                    ComplianceTest.LinkCheck = true;
+                    ComplianceTest.DemodMargin = mlmeConfirm->fields.link.DemodMargin;
+                    ComplianceTest.NbGateways = mlmeConfirm->fields.link.NbGateways;
+                }
+                break;
+            }
+            case MLME_TIME_REQ:
+                break;
+            default:
+                /* TODO: handle unknown MLME request */
+                DeviceState = DEVICE_STATE_SLEEP;
+                break;
+        }
+    }
+    else    // not ok...
+    {
+        failCnt++;
+
+#ifdef LORAWAN_JOIN_EUI
+        if (failCnt > 5) {
+            join(1);
+            return;
+        }
+#endif
+
+        switch( mlmeConfirm->MlmeRequest )
+        {
+#ifdef LORAWAN_JOIN_EUI
+            case MLME_JOIN:
+            {
+                // Join failed, restart join procedure
+                break;
+            }
+#endif /* LORAWAN_JOIN_EUI */
+            case MLME_LINK_CHECK:
+                DeviceState = DEVICE_STATE_SLEEP;
+                break;
+#ifdef LORAWAN_JOIN_EUI
+            case MLME_REJOIN_0:
+                break;
+            case MLME_REJOIN_2:
+                break;
+            case MLME_TIME_REQ:
+                break;
+#endif /* LORAWAN_JOIN_EUI */
+            default:
+                DeviceState = DEVICE_STATE_SLEEP;
+                break;
+        }
+    }
+} // ..MlmeConfirm
+
+static void MlmeIndication( const MlmeIndication_t *MlmeIndication )
+{
+    char str[48];
+    MibRequestConfirm_t mibReq;
+
+    vt.SetCursorPos(ROW_MLME_IND, 1);
+    LoRaMacEventInfoStatus_to_string(MlmeIndication->Status, str);
+    Mlme_to_string(MlmeIndication->MlmeIndication, str);
+    vt.printf("MlmeIndication %s ", str);
+    if (MlmeIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK)
+        vt.printf("\e[31m%s \e[0m \e[K", str);
+    else
+        vt.printf("%s \e[K", str);
+
+    switch( MlmeIndication->MlmeIndication )
+    {
+        case MLME_SWITCH_CLASS:
+        {
+            /* mac gave up on beacon */
+            mibReq.Type = MIB_DEVICE_CLASS;
+            mibReq.Param.Class = CLASS_A;
+            LoRaMacMibSetRequestConfirm( &mibReq );
+
+            // Switch to class A again
+            DeviceState = DEVICE_STATE_SLEEP;    // class-B manual switch
+            break;
+        }
+        case MLME_BEACON:
+        {
+            LoRaMacEventInfoStatus_to_string(MlmeIndication->Status, str);
+            break;
+
+        }
+#ifdef LORAWAN_JOIN_EUI
+        case MLME_JOIN:
+            vt.printf("%uhz try%u", MlmeIndication->freqHz, MlmeIndication->JoinRequestTrials);
+            break;
+#endif /* !LORAWAN_JOIN_EUI  */
+        default:
+            break;
+    }
+
+} // ..MlmeIndication()
+
+uint8_t periodicity;
+
+void SerialDisplayRefresh( void )
+{
+#ifdef LORAWAN_JOIN_EUI
+    MibRequestConfirm_t mibReq;
+#endif
+
+    SerialDisplayInit( );
+#ifdef LORAWAN_JOIN_EUI
+    SerialDisplayUpdateActivationMode(true);
+    SerialDisplayUpdateEui( ROW_DEVEUI, DevEui);
+    SerialDisplayUpdateEui( ROW_JOINEUI, JoinEui);
+    SerialDisplayUpdateKey( ROW_NWKKEY, NwkKey);
+
+    #ifdef LORAWAN_ROOT_APPKEY
+    SerialDisplayUpdateKey(ROW_APPKEY, AppKey);
+    #endif
+
+    mibReq.Type = MIB_NETWORK_JOINED;
+    LoRaMacMibGetRequestConfirm( &mibReq );
+    SerialDisplayUpdateNetworkIsJoined( mibReq.Param.IsNetworkJoined );
+#else
+    //SerialDisplayUpdateNwkId( LORAWAN_NETWORK_ID );
+    SerialDisplayUpdateDevAddr( DevAddr );
+    SerialDisplayUpdateKey( ROW_FNwkSIntKey, FNwkSIntKey);
+    SerialDisplayUpdateKey( ROW_AppSKey, AppSKey );
+    #if defined(LORAWAN_SNwkSIntKey) && defined(LORAWAN_NwkSEncKey)
+    SerialDisplayUpdateKey(ROW_NwkSEncKey, NwkSEncKey);
+    SerialDisplayUpdateKey(ROW_SNwkSIntKey, SNwkSIntKey);
+    #endif /* 1v1 ABP */
+
+    vt.SetCursorPos( ROW_END, 1 );
+    vt.printf("FCntUp:%08x", eeprom_read(EEPROM_FCNTUP));
+    vt.printf(" AFCntDown:%08x", get_fcntdwn(true));
+    vt.printf(" NFCntDown:%08x", get_fcntdwn(false));
+#endif
+
+
+    SerialDisplayUpdateAdr( LORAWAN_ADR_ON );
+#if defined( USE_BAND_868 )
+    SerialDisplayUpdateDutyCycle( LORAWAN_DUTYCYCLE_ON );
+#else
+    SerialDisplayUpdateDutyCycle( false );
+#endif
+    SerialDisplayUpdatePublicNetwork( LORAWAN_PUBLIC_NETWORK );
+
+    //SerialDisplayUpdateLedState( 3, AppLedStateOn );
+}
+
+void SerialRxProcess( void )
+{
+    LoRaMacStatus_t status;
+    MlmeReq_t mlmeReq;
+#ifndef LORAWAN_JOIN_EUI
+    static uint8_t icnt = 0;
+#endif
+    
+    if( SerialDisplayReadable( ) == true ) {
+        char ch = SerialDisplayGetChar();
+#ifndef LORAWAN_JOIN_EUI
+        if (ch == 'I') {
+            if (++icnt == 3) {
+                vt.SetCursorPos( ROW_END, 1 );
+                vt.printf("reset-fcnts\e[K");
+                eeprom_clear(EEPROM_AFCNTDWN);
+                eeprom_clear(EEPROM_NFCNTDWN);
+                eeprom_clear(EEPROM_FCNTUP);
+            }
+        } else
+            icnt = 0;
+#endif /* !LORAWAN_JOIN_EUI */
+
+        if ( ch >= '0' && ch <= '9') {
+            c_ch = ch - '0';
+            DeviceState = DEVICE_STATE_SEND;
+            return;
+        }
+        switch( ch ) {
+            case 'R':
+            case 'r':
+                // Refresh Serial screen
+                SerialDisplayRefresh( );
+                break;
+            case 'L':
+                clearIndications();
+                mlmeReq.Type = MLME_LINK_CHECK;
+                status = LoRaMacMlmeRequest( &mlmeReq );
+                if (status == LORAMAC_STATUS_OK)
+                    SendFrame(0, false);
+                break;
+#ifdef LORAWAN_JOIN_EUI
+            case 'j':
+                DeviceState = DEVICE_STATE_JOIN;
+                break;
+#endif
+            default:
+                break;
+        }
+    }
+}
+
+
+static const LoRaMacPrimitives_t LoRaMacPrimitives = {
+    McpsConfirm,
+    McpsIndication,
+    MlmeConfirm,
+    MlmeIndication
+};
+
+static const LoRaMacCallback_t LoRaMacCallbacks = {
+    BoardGetBatteryLevel,
+    NULL
+};
+
+/**
+ * Main application entry point.
+ */
+int main()
+{
+    MibRequestConfirm_t mibReq;
+
+    DeviceState = DEVICE_STATE_INIT;
+
+    if (sleep_manager_can_deep_sleep())
+        sleep_manager_lock_deep_sleep();    // prevent deep sleep
+
+#ifdef JUMPER_ENABLE
+    jumper_out = 1;
+    jumper_in.mode(PullDown);
+    jumper_in.rise(jumper_callback);
+#endif /* JUMPER_ENABLE */
+
+    while( 1 )
+    {
+        SerialRxProcess( );
+
+        if (flags.gmi) {
+            flags.gmi = false;
+            SerialDisplayMcpsIndication(&gmi);
+        }
+
+        if (flags.gmc) {
+            flags.gmc = false;
+            SerialDisplayMcpsConfirm(&gmc);
+        }
+
+        switch( DeviceState )
+        {
+            case DEVICE_STATE_INIT:
+            {
+                pwm.period(1.0 / 60);
+                cayenne_ack_ch = -1;
+                c_ch = 0xff;
+                d8.mode(PullDown);
+                if (LORAMAC_STATUS_OK != LoRaMacInitialization( &LoRaMacPrimitives, &LoRaMacCallbacks )) {
+                    return -1;
+                }
+
+                mibReq.Type = MIB_ADR;
+                mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+
+                mibReq.Type = MIB_PUBLIC_NETWORK;
+                mibReq.Param.EnablePublicNetwork = LORAWAN_PUBLIC_NETWORK;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+
+#if defined( USE_BAND_868 )
+                DutyCycleOn = LORAWAN_DUTYCYCLE_ON ;
+#if( USE_SEMTECH_DEFAULT_CHANNEL_LINEUP == 1 ) 
+                LoRaMacChannelAdd( 3, ( ChannelParams_t )LC4 );
+                LoRaMacChannelAdd( 4, ( ChannelParams_t )LC5 );
+                LoRaMacChannelAdd( 5, ( ChannelParams_t )LC6 );
+                LoRaMacChannelAdd( 6, ( ChannelParams_t )LC7 );
+                LoRaMacChannelAdd( 7, ( ChannelParams_t )LC8 );
+                LoRaMacChannelAdd( 8, ( ChannelParams_t )LC9 );
+                LoRaMacChannelAdd( 9, ( ChannelParams_t )LC10 );
+
+                mibReq.Type = MIB_RX2_CHANNEL;
+                mibReq.Param.Rx2Channel = ( Rx2ChannelParams_t ){ 869525000, DR_3 };
+                LoRaMacMibSetRequestConfirm( &mibReq );
+#endif
+
+#endif
+
+                SerialDisplayRefresh();
+#ifdef LORAWAN_JOIN_EUI
+
+    #ifndef SENETCO /* for senet, use network provided DevEUI */
+                // Initialize LoRaMac device unique ID
+                HardwareIDtoDevEUI(DevEui);
+    #ifdef LORAWAN_ROOT_APPKEY
+                // inverted DevEui provisioned as v1.1 on server (non-inv = lorawan1v0)
+                for (int i = 0; i < 8; i++)
+                    DevEui[i] ^= 0xff;
+    #endif /* LORAWAN_ROOT_APPKEY */
+    #endif /* !SENETCO */
+                SerialDisplayUpdateEui( 5, DevEui );
+                DeviceState = DEVICE_STATE_JOIN;
+#else   /* ABP... */
+
+                mibReq.Type = MIB_DEV_ADDR;
+                mibReq.Param.DevAddr = DevAddr;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+                SerialDisplayUpdateDevAddr(DevAddr);
+
+                mibReq.Type = MIB_APP_SKEY;
+                mibReq.Param.key = AppSKey;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey(ROW_AppSKey, AppSKey);
+
+    #if defined(LORAWAN_SNwkSIntKey) && defined(LORAWAN_NwkSEncKey)
+                /* lorawan 1v1 ABP */
+                mibReq.Type = MIB_NwkSEncKey;
+                mibReq.Param.NwkSEncKey = NwkSEncKey;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey(ROW_NwkSEncKey, NwkSEncKey);
+
+                mibReq.Type = MIB_SNwkSIntKey;
+                mibReq.Param.SNwkSIntKey = SNwkSIntKey;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey(ROW_SNwkSIntKey, SNwkSIntKey);
+
+                mibReq.Type = MIB_FNwkSIntKey;
+                mibReq.Param.key = FNwkSIntKey;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey(ROW_FNwkSIntKey, mibReq.Param.key);
+    #else
+                /* lorawan 1v0 ABP */
+                mibReq.Type = MIB_NwkSKey;
+                mibReq.Param.key = FNwkSIntKey;
+                LoRaMacMibSetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey(ROW_FNwkSIntKey, mibReq.Param.key);
+    #endif
+
+                DeviceState = DEVICE_STATE_TRIGGER;
+#endif /* !LORAWAN_JOIN_EUI */
+                break;
+            }
+#ifdef LORAWAN_JOIN_EUI
+            case DEVICE_STATE_JOIN:
+            {
+                join(8);
+                DeviceState = DEVICE_STATE_SLEEP;
+                break;
+            }
+            case DEVICE_STATE_JOIN_OK:
+                MibRequestConfirm_t mibReq;
+                mibReq.Type = MIB_NETWORK_JOINED;
+                LoRaMacMibGetRequestConfirm( &mibReq );
+                SerialDisplayUpdateNetworkIsJoined( mibReq.Param.IsNetworkJoined );
+                mibReq.Type = MIB_DEV_ADDR;
+                LoRaMacMibGetRequestConfirm( &mibReq );
+                SerialDisplayUpdateDevAddr(mibReq.Param.DevAddr);
+                mibReq.Type = MIB_FNwkSIntKey;
+                LoRaMacMibGetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey( ROW_FNwkSIntKey, mibReq.Param.key );
+                mibReq.Type = MIB_APP_SKEY;
+                LoRaMacMibGetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey( ROW_AppSKey,  mibReq.Param.key );
+
+    #ifdef LORAWAN_ROOT_APPKEY
+                mibReq.Type = MIB_SNwkSIntKey;
+                LoRaMacMibGetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey(ROW_SNwkSIntKey,  mibReq.Param.key);
+                mibReq.Type = MIB_NwkSEncKey;
+                LoRaMacMibGetRequestConfirm( &mibReq );
+                SerialDisplayUpdateKey(ROW_NwkSEncKey,  mibReq.Param.key);
+    #endif /* LORAWAN_ROOT_APPKEY */
+                DeviceState = DEVICE_STATE_TRIGGER;
+                break;
+#endif /* LORAWAN_JOIN_EUI */
+            case DEVICE_STATE_SEND:
+                SerialDisplayUpdateUplinkAcked( false );
+                SerialDisplayUpdateDonwlinkRxData( false );
+                PrepareTxFrame( AppPort );
+                /*status =*/ SendFrame(gIsTxConfirmed, gAppDataSize);
+                /* McpsConfirm or McpsIndication callback will continue */
+                DeviceState = DEVICE_STATE_SLEEP;
+                break;
+            case DEVICE_STATE_SLEEP:
+            {
+                // Wake up through events
+                sleep_manager_sleep_auto();
+                break;
+            }
+            case DEVICE_STATE_TRIGGER:
+                sleep_manager_sleep_auto();
+                if (d8.read() == 1) {
+                    c_ch = 0xff;
+                    DeviceState = DEVICE_STATE_SEND;
+                    buttonStartAt = LoRaMacReadTimer();
+                }
+                break;
+            default:
+                DeviceState = DEVICE_STATE_INIT;
+                break;
+        } // ..switch( DeviceState )
+
+    } // ..while( 1 )
+}
+#endif /* ENABLE_VT100 */