application layer with: button, LED, pot, tempSense

Dependencies:   mbed SX1272Lib

Fork of LoRaWAN-demo-72-bootcamp by Semtech

Use with sx1272 shield with grove peripherals connected:

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

Button

Sends to different payloads: 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

app/main.cpp

Committer:
dudmuck
Date:
2018-02-03
Revision:
11:018e7e28161d
Parent:
10:52810ecbd83b
Child:
12:662ff73ce484

File content as of revision 11:018e7e28161d:

/*
 / _____)             _              | |
( (____  _____ ____ _| |_ _____  ____| |__
 \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 _____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
    (C)2015 Semtech

Description: LoRaMac classA device implementation

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

Maintainer: Miguel Luis and Gregory Cristian
*/
#include "mbed.h"
#include "board.h"
#include "radio.h"

#include "LoRaMac.h"
#include "Commissioning.h"
#include "SerialDisplay.h"

/*!
 * Defines the application data transmission duty cycle. 5s, value in [ms].
 */
#define APP_TX_DUTYCYCLE                            5000

/*!
 * Defines a random delay for application data transmission duty cycle. 1s,
 * value in [ms].
 */
#define APP_TX_DUTYCYCLE_RND                        1000

/*!
 * Default datarate
 */
#define LORAWAN_DEFAULT_DATARATE                    DR_5

/*!
 * LoRaWAN confirmed messages
 */
#define LORAWAN_CONFIRMED_MSG_ON                    false

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

#include "LoRaMacTest.h"

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

/*!
 * User application data buffer size
 */
#if ( LORAWAN_CONFIRMED_MSG_ON == 1 )
#define LORAWAN_APP_DATA_SIZE                       6

#else
#define LORAWAN_APP_DATA_SIZE                       5

#endif

static uint8_t DevEui[] = LORAWAN_DEVICE_EUI;
static uint8_t AppEui[] = LORAWAN_APPLICATION_EUI;
static uint8_t AppKey[] = LORAWAN_APPLICATION_KEY;

#if( OVER_THE_AIR_ACTIVATION == 0 )

static uint8_t NwkSKey[] = LORAWAN_NWKSKEY;
static uint8_t AppSKey[] = LORAWAN_APPSKEY;

/*!
 * Device address
 */
static uint32_t DevAddr = LORAWAN_DEVICE_ADDRESS;

#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;

/*!
 * Device states
 */
static enum eDeviceState {
    DEVICE_STATE_INIT = 0,
    DEVICE_STATE_JOIN,
    DEVICE_STATE_JOIN_OK,
    DEVICE_STATE_SEND,
    DEVICE_STATE_TRIGGER,
    DEVICE_STATE_SLEEP
} DeviceState;

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

/*
 * SerialDisplay managment variables
 */

/*!
 * Strucure containing the Uplink status
 */
struct sLoRaMacUplinkStatus {
    uint8_t Acked;
    int8_t Datarate;
    uint16_t UplinkCounter;
    uint8_t Port;
    uint8_t *Buffer;
    uint8_t BufferSize;
} LoRaMacUplinkStatus;

/*!
 * Strucure containing the Downlink status
 */
struct sLoRaMacDownlinkStatus {
    int16_t Rssi;
    int8_t Snr;
    uint16_t DownlinkCounter;
    bool RxData;
    uint8_t Port;
    uint8_t *Buffer;
    uint8_t BufferSize;
} LoRaMacDownlinkStatus;
volatile bool DownlinkStatusUpdated = false;

void SerialDisplayRefresh( void )
{
    MibRequestConfirm_t mibReq;

    SerialDisplayInit( );
    SerialDisplayUpdateActivationMode( OVER_THE_AIR_ACTIVATION );

#if( OVER_THE_AIR_ACTIVATION == 0 )
    SerialDisplayUpdateNwkId( LORAWAN_NETWORK_ID );
    SerialDisplayUpdateDevAddr( DevAddr );
    SerialDisplayUpdateKey( 12, NwkSKey );
    SerialDisplayUpdateKey( 13, AppSKey );
#endif
    SerialDisplayUpdateEui( 5, DevEui );
    SerialDisplayUpdateEui( 6, AppEui );
    SerialDisplayUpdateKey( 7, AppKey );

    mibReq.Type = MIB_NETWORK_JOINED;
    LoRaMacMibGetRequestConfirm( &mibReq );
    SerialDisplayUpdateNetworkIsJoined( mibReq.Param.IsNetworkJoined );

    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 LoRaMacStatus_toString(LoRaMacStatus_t s, char* out)
{
    switch (s) {
        case LORAMAC_STATUS_PARAMETER_INVALID:
            strcpy(out, "PARAMTER_INVALID");
            break;
        case LORAMAC_STATUS_BUSY:
            strcpy(out, "BUSY");
            break;
        default:
            sprintf(out, "<%u>", s);
            break;
    }
}

volatile unsigned sendCnt = 0;
/*!
 * \brief   Prepares the payload of the frame
 *
 * \retval  [0: frame could be send, 1: error]
 */
static bool SendFrame( uint8_t len, bool conf )
{
    char str[48];
    LoRaMacStatus_t status;
    McpsReq_t mcpsReq;
    LoRaMacTxInfo_t txInfo;

    if( LoRaMacQueryTxPossible( len, &txInfo ) != LORAMAC_STATUS_OK ) {
        // Send empty frame in order to flush MAC commands
        mcpsReq.Type = MCPS_UNCONFIRMED;
        mcpsReq.Req.Unconfirmed.fBuffer = NULL;
        mcpsReq.Req.Unconfirmed.fBufferSize = 0;
        mcpsReq.Req.Unconfirmed.Datarate = LORAWAN_DEFAULT_DATARATE;

        LoRaMacUplinkStatus.Acked = false;
        LoRaMacUplinkStatus.Port = 0;
        LoRaMacUplinkStatus.Buffer = NULL;
        LoRaMacUplinkStatus.BufferSize = 0;
        SerialDisplayUpdateFrameType( false );
    } else {
        LoRaMacUplinkStatus.Acked = false;
        LoRaMacUplinkStatus.Port = AppPort;
        LoRaMacUplinkStatus.Buffer = AppData;
        LoRaMacUplinkStatus.BufferSize = len;//AppDataSize;
        SerialDisplayUpdateFrameType( conf/*IsTxConfirmed*/ );

        if( conf == false ) {
            mcpsReq.Type = MCPS_UNCONFIRMED;
            mcpsReq.Req.Unconfirmed.fPort = AppPort;
            mcpsReq.Req.Unconfirmed.fBuffer = AppData;
            mcpsReq.Req.Unconfirmed.fBufferSize = len;
            mcpsReq.Req.Unconfirmed.Datarate = LORAWAN_DEFAULT_DATARATE;
        } else {
            mcpsReq.Type = MCPS_CONFIRMED;
            mcpsReq.Req.Confirmed.fPort = AppPort;
            mcpsReq.Req.Confirmed.fBuffer = AppData;
            mcpsReq.Req.Confirmed.fBufferSize = len;
            mcpsReq.Req.Confirmed.NbTrials = 8;
            mcpsReq.Req.Confirmed.Datarate = LORAWAN_DEFAULT_DATARATE;
        }
    }

    sendCnt++;
    status = LoRaMacMcpsRequest( &mcpsReq );
    if (status == LORAMAC_STATUS_OK) {
        vt.SetCursorPos( 44, 1 );
        vt.printf("%u sendFrame() OK\e[K", sendCnt);
        SerialDisplayUpdateUplink(
            LoRaMacUplinkStatus.Acked,
            LoRaMacUplinkStatus.Datarate,
            LoRaMacUplinkStatus.UplinkCounter,
            LoRaMacUplinkStatus.Port,
            LoRaMacUplinkStatus.Buffer,
            LoRaMacUplinkStatus.BufferSize
        );
        return false;
    }
    LoRaMacStatus_toString(status, str);
    vt.SetCursorPos( 44, 1 );
    vt.printf("%u sendFrame() %s\e[K", sendCnt, str);
    return true;
}

uint8_t c_ch;

void SerialRxProcess( void )
{
    LoRaMacStatus_t status;
    MlmeReq_t mlmeReq;
    
    if( SerialDisplayReadable( ) == true ) {
        char ch = SerialDisplayGetChar();
        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':
                mlmeReq.Type = MLME_LINK_CHECK;
                status = LoRaMacMlmeRequest( &mlmeReq );
                if (status == LORAMAC_STATUS_OK)
                    SendFrame(0, false);
                break;
            default:
                break;
        }
    }
}

#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 a2(A2);
AnalogIn a3(A3);
AnalogIn a4(A4);

const unsigned R0 = 100000;
const unsigned B = 4275;


volatile TimerTime_t buttonStartAt;
DigitalIn d8(D8);
DigitalOut extLed(D15);
PwmOut pwm(PB_11);
volatile int cayenne_ack_ch;
/*!
 * \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) {
        TimerTime_t duration = TimerGetCurrentTime() - buttonStartAt;
        vt.SetCursorPos( 41, 1 );
        if (duration > 1000) {
            gAppDataSize = 0;
            AppData[gAppDataSize++] = CAYENNE_CH_DOUT;
            AppData[gAppDataSize++] = LPP_DIGITAL_OUTPUT;
            AppData[gAppDataSize++] = extLed.read();
            vt.printf("send outputs           ", duration);
            return;
        } else
            vt.printf("dur %u           ", duration);
    }

    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();
            vt.SetCursorPos( 41, 1 );
            vt.printf("u16:%u, f:%f, rot:%u t:%.1f\e[K", u16, f, rot, t);
            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;
    }
}


void LoRaMacEventInfoStatus_toString(LoRaMacEventInfoStatus_t s, char* out)
{
    switch (s) {
        case LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL:
            strcpy(out, "JOIN_FAIL");
            break;
        case LORAMAC_EVENT_INFO_STATUS_ERROR:
            strcpy(out, "ERROR");
            break;
        case LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT:
            strcpy(out, "RX2_TIMEOUT");
            break;
        case LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL:
            strcpy(out, "ADDRESS_FAIL");
            break;
        case LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOSS:
            strcpy(out, "FRAMES_LOSS");
            break;
        case LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED:
            strcpy(out, "DOWNLINK_REPEATED");
            break; // confirmed downlink retry?
        case LORAMAC_EVENT_INFO_STATUS_MIC_FAIL:
            strcpy(out, "MIC_FAIL");
            break;
        case LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT:
            strcpy(out, "TX_TIMEOUT");
            break;
        case LORAMAC_EVENT_INFO_STATUS_RX1_ERROR:
            strcpy(out, "RX1_ERROR");
            break;
        case LORAMAC_EVENT_INFO_STATUS_RX2_ERROR:
            strcpy(out, "RX2_ERROR");
            break;
        case LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR:
            strcpy(out, "SIZE");
            break;


        default:
            sprintf(out, "<%d>", s);
            break;
    }
}

/*!
 * \brief   MCPS-Confirm event function
 *
 * \param   [IN] mcpsConfirm - Pointer to the confirm structure,
 *               containing confirm attributes.
 */
static void McpsConfirm( McpsConfirm_t *mcpsConfirm )
{
    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;
        }
        LoRaMacUplinkStatus.Datarate = mcpsConfirm->Datarate;
        LoRaMacUplinkStatus.UplinkCounter = mcpsConfirm->UpLinkCounter;
    } else {
        char str[48];
        LoRaMacEventInfoStatus_toString(mcpsConfirm->Status, str);
        vt.SetCursorPos( 44, 1 );
        vt.printf("mcpsConf %s", str);

    }

    DeviceState = DEVICE_STATE_TRIGGER;
}


/*!
 * \brief   MCPS-Indication event function
 *
 * \param   [IN] mcpsIndication - Pointer to the indication structure,
 *               containing indication attributes.
 */
static void McpsIndication( McpsIndication_t *mcpsIndication )
{
    if( mcpsIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK ) {
        char str[48];
        LoRaMacEventInfoStatus_toString(mcpsIndication->Status, str);
        vt.SetCursorPos( 44, 1 );
        vt.printf("mcpsInd %s\e[K", str);
        return;
    }

    switch( mcpsIndication->McpsIndication ) {
        case MCPS_UNCONFIRMED: {
            break;
        }
        case MCPS_CONFIRMED: {
            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
    LoRaMacDownlinkStatus.Rssi = mcpsIndication->Rssi;
    if( mcpsIndication->Snr & 0x80 ) { // The SNR sign bit is 1
        // Invert and divide by 4
        LoRaMacDownlinkStatus.Snr = ( ( ~mcpsIndication->Snr + 1 ) & 0xFF ) >> 2;
        LoRaMacDownlinkStatus.Snr = -LoRaMacDownlinkStatus.Snr;
    } else {
        // Divide by 4
        LoRaMacDownlinkStatus.Snr = ( mcpsIndication->Snr & 0xFF ) >> 2;
    }
    LoRaMacDownlinkStatus.DownlinkCounter++;
    LoRaMacDownlinkStatus.RxData = mcpsIndication->RxData;
    LoRaMacDownlinkStatus.Port = mcpsIndication->Port;
    LoRaMacDownlinkStatus.Buffer = mcpsIndication->Buffer;
    LoRaMacDownlinkStatus.BufferSize = mcpsIndication->BufferSize;

    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:
                if( mcpsIndication->BufferSize == 1 ) {
                    //AppLedStateOn = mcpsIndication->Buffer[0] & 0x01;
                    //Led3StateChanged = true;
                }
                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 )
                        LoRaMacTestSetDutyCycleOn( 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 )
                            LoRaMacTestSetDutyCycleOn( 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)
                            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 )
                            LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );
#endif

                            mlmeReq.Type = MLME_JOIN;

                            mlmeReq.Req.Join.DevEui = DevEui;
                            mlmeReq.Req.Join.AppEui = AppEui;
                            mlmeReq.Req.Join.AppKey = AppKey;
                            mlmeReq.Req.Join.NbTrials = 3;

                            LoRaMacMlmeRequest( &mlmeReq );
                            DeviceState = DEVICE_STATE_SLEEP;
                        }
                        break;
                        case 7: { // (x)
                            if( mcpsIndication->BufferSize == 3 ) {
                                MlmeReq_t mlmeReq;
                                mlmeReq.Type = MLME_TXCW;
                                mlmeReq.Req.TxCw.Timeout = ( uint16_t )( ( mcpsIndication->Buffer[1] << 8 ) | mcpsIndication->Buffer[2] );
                                LoRaMacMlmeRequest( &mlmeReq );
                            } else if( mcpsIndication->BufferSize == 7 ) {
                                MlmeReq_t mlmeReq;
                                mlmeReq.Type = MLME_TXCW_1;
                                mlmeReq.Req.TxCw.Timeout = ( uint16_t )( ( mcpsIndication->Buffer[1] << 8 ) | mcpsIndication->Buffer[2] );
                                mlmeReq.Req.TxCw.Frequency = ( uint32_t )( ( mcpsIndication->Buffer[3] << 16 ) | ( mcpsIndication->Buffer[4] << 8 ) | mcpsIndication->Buffer[5] ) * 100;
                                mlmeReq.Req.TxCw.Power = mcpsIndication->Buffer[6];
                                LoRaMacMlmeRequest( &mlmeReq );
                            }
                            ComplianceTest.State = 1;
                        }
                        break;
                        default:
                            break;
                    }
                }
                break;
            default:
                break;
        }
    }

    DownlinkStatusUpdated = true;
}



/*!
 * \brief   MLME-Confirm event function
 *
 * \param   [IN] mlmeConfirm - Pointer to the confirm structure,
 *               containing confirm attributes.
 */
static void MlmeConfirm( MlmeConfirm_t *mlmeConfirm )
{
    vt.SetCursorPos( 45, 1 );
    vt.printf("MlmeConfirm() ");
    switch( mlmeConfirm->MlmeRequest ) {
        case MLME_JOIN: {
            vt.printf("MLME_JOIN ");
            if( mlmeConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK ) {
                vt.printf("OK");
                DeviceState = DEVICE_STATE_JOIN_OK;
                extLed = 0;
            } else {
                char str[48];
                LoRaMacEventInfoStatus_toString(mlmeConfirm->Status, str);
                vt.printf(str);
                // Join was not successful. Try to join again
                DeviceState = DEVICE_STATE_JOIN;
            }
            break;
        }
        case MLME_LINK_CHECK: {
            vt.printf("MLME_LINK_CHECK");
            if( mlmeConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK ) {
                vt.printf("OK");
                // Check DemodMargin
                // Check NbGateways
                if( ComplianceTest.Running == true ) {
                    ComplianceTest.LinkCheck = true;
                    ComplianceTest.DemodMargin = mlmeConfirm->DemodMargin;
                    ComplianceTest.NbGateways = mlmeConfirm->NbGateways;
                }
            }
            break;
        }
        default:
            vt.printf("<%d>", mlmeConfirm->MlmeRequest);
            break;
    }
    vt.printf("\e[K");
}

void foo()
{
    float R, temp;
    uint16_t u16;
    
    vt.SetCursorPos( 43, 1 );
    
    u16 = a3.read_u16() >> 4;
    R = 4096.0 / u16 - 1.0;
    R = R0 * R;
    temp = 1.0/(log(R/R0)/B+1/298.15)-273.15;
    
    vt.printf("%u d9:%u (%03x %03x %03x %03x) %.2fC\e[K\r\n", DeviceState, d8.read() >> 4, a1.read_u16() >> 4, a2.read_u16() >> 4, a3.read_u16() >> 4, a4.read_u16(), temp);
}

const LoRaMacPrimitives_t LoRaMacPrimitives = {
    McpsConfirm,
    McpsIndication,
    MlmeConfirm

};
const LoRaMacCallback_t LoRaMacCallbacks = {
    BoardGetBatteryLevel
};

/**
 * Main application entry point.
 */
int main( void )
{
    MibRequestConfirm_t mibReq;

    BoardInit( );
    SerialDisplayInit( );

    SerialDisplayUpdateEui( 5, DevEui );
    SerialDisplayUpdateEui( 6, AppEui );
    SerialDisplayUpdateKey( 7, AppKey );

#if( OVER_THE_AIR_ACTIVATION == 0 )
    SerialDisplayUpdateNwkId( LORAWAN_NETWORK_ID );
    SerialDisplayUpdateDevAddr( DevAddr );
    SerialDisplayUpdateKey( 12, NwkSKey );
    SerialDisplayUpdateKey( 13, AppSKey );
#endif

    DeviceState = DEVICE_STATE_INIT;

    while( 1 ) {
        foo();
        SerialRxProcess( );

        if( DownlinkStatusUpdated == true ) {
            DownlinkStatusUpdated = false;
            SerialDisplayUpdateDownlink( LoRaMacDownlinkStatus.RxData, LoRaMacDownlinkStatus.Rssi, LoRaMacDownlinkStatus.Snr, LoRaMacDownlinkStatus.DownlinkCounter, LoRaMacDownlinkStatus.Port, LoRaMacDownlinkStatus.Buffer, LoRaMacDownlinkStatus.BufferSize );
        }

        switch( DeviceState ) {
            case DEVICE_STATE_INIT: {
                pwm.period(1.0 / 60);
                cayenne_ack_ch = -1;
                c_ch = 0xff;
                d8.mode(PullDown);

                LoRaMacInitialization( &LoRaMacPrimitives, &LoRaMacCallbacks );

                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 )
                LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );
                SerialDisplayUpdateDutyCycle( 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_DEFAULT_CHANNEL;
                mibReq.Param.Rx2DefaultChannel = ( Rx2ChannelParams_t ) {
                    869525000, DR_3
                };
                LoRaMacMibSetRequestConfirm( &mibReq );

                mibReq.Type = MIB_RX2_CHANNEL;
                mibReq.Param.Rx2Channel = ( Rx2ChannelParams_t ) {
                    869525000, DR_3
                };
                LoRaMacMibSetRequestConfirm( &mibReq );
#endif

#endif
                SerialDisplayUpdateActivationMode( OVER_THE_AIR_ACTIVATION );
                SerialDisplayUpdateAdr( LORAWAN_ADR_ON );
                SerialDisplayUpdatePublicNetwork( LORAWAN_PUBLIC_NETWORK );

                LoRaMacDownlinkStatus.DownlinkCounter = 0;

                DeviceState = DEVICE_STATE_JOIN;
                break;
            }
            case DEVICE_STATE_JOIN: {
#if( OVER_THE_AIR_ACTIVATION != 0 )
                LoRaMacStatus_t s;
                MlmeReq_t mlmeReq;
                mlmeReq.Type = MLME_JOIN;

                mlmeReq.Req.Join.DevEui = DevEui;
                mlmeReq.Req.Join.AppEui = AppEui;
                mlmeReq.Req.Join.AppKey = AppKey;

                s = LoRaMacMlmeRequest( &mlmeReq );
                if (s != LORAMAC_STATUS_OK) {
                    char str[48];
                    LoRaMacStatus_toString(s, str);
                    vt.SetCursorPos( 44, 1 );
                    vt.printf("mlmeReq join %s\e[K", str);
                    return -1;                    
                } else
                    DeviceState = DEVICE_STATE_SLEEP;

                extLed = 1;
#else
                mibReq.Type = MIB_NET_ID;
                mibReq.Param.NetID = LORAWAN_NETWORK_ID;
                LoRaMacMibSetRequestConfirm( &mibReq );

                mibReq.Type = MIB_DEV_ADDR;
                mibReq.Param.DevAddr = DevAddr;
                LoRaMacMibSetRequestConfirm( &mibReq );

                mibReq.Type = MIB_NWK_SKEY;
                mibReq.Param.NwkSKey = NwkSKey;
                LoRaMacMibSetRequestConfirm( &mibReq );

                mibReq.Type = MIB_APP_SKEY;
                mibReq.Param.AppSKey = AppSKey;
                LoRaMacMibSetRequestConfirm( &mibReq );

                mibReq.Type = MIB_NETWORK_JOINED;
                mibReq.Param.IsNetworkJoined = true;
                LoRaMacMibSetRequestConfirm( &mibReq );
                DeviceState = DEVICE_STATE_SEND;
#endif
                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_NWK_SKEY;
                LoRaMacMibGetRequestConfirm( &mibReq );
                SerialDisplayUpdateKey( 12, mibReq.Param.NwkSKey );
                mibReq.Type = MIB_APP_SKEY;
                LoRaMacMibGetRequestConfirm( &mibReq );
                SerialDisplayUpdateKey( 13,  mibReq.Param.AppSKey );
                //SerialDisplayUpdateNwkId( uint8_t id );
                DeviceState = DEVICE_STATE_TRIGGER;
                break;
            case DEVICE_STATE_SEND: {
                SerialDisplayUpdateUplinkAcked( false );
                SerialDisplayUpdateDonwlinkRxData( false );
                PrepareTxFrame( AppPort );

                SendFrame(gAppDataSize, gIsTxConfirmed);

                DeviceState = DEVICE_STATE_SLEEP;
                break;
            }
            case DEVICE_STATE_SLEEP: {
                // Wake up through events
                break;
            }
            case DEVICE_STATE_TRIGGER:
                if (d8.read() == 1) {
                    c_ch = 0xff;
                    DeviceState = DEVICE_STATE_SEND;
                    buttonStartAt = TimerGetCurrentTime();
                }
                break;
            default: {
                DeviceState = DEVICE_STATE_INIT;
                break;
            }

        }

    }
}