Ottawa Bootcamp Publish

Dependencies:   SX127x lorawan1v1

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 */