Implemented LED Indicator Patterns

Dependencies:   mbed

app/main.cpp

Committer:
uss1994
Date:
2019-01-25
Revision:
19:6f88d1cabaca
Parent:
18:0a4de80c02a6
Child:
20:e75b3340d1c7

File content as of revision 19:6f88d1cabaca:

/*
 / _____)             _              | |
( (____  _____ ____ _| |_ _____  ____| |__
 \____ \| ___ |    (_   _) ___ |/ ___)  _ \
 _____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
    (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_0

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

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

/*!
 * User application data buffer size
 */
#define LORAWAN_APP_DATA_SIZE                       41

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 AppDataSize = 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 IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;

/*!
 * Defines the application data transmission duty cycle
 */
static uint32_t TxDutyCycleTime;

/*!
 * Timer to handle the application data transmission duty cycle
 */
static TimerEvent_t TxNextPacketTimer;

/*!
 * SOME APPLICATION PARAMETERS
 */
 
DigitalOut myled(D7);

DigitalOut relayPin(D6);
AnalogIn BAT_PIN(A1);
AnalogIn LIGHT_1_PIN(A2);
AnalogIn LIGHT_2_PIN(A5);
AnalogIn RH_PIN(PC_2);
AnalogIn VCE_PIN(PB_1);

InterruptIn button1(USER_BUTTON);
volatile bool button1_pressed = false; // Used in the main loop
volatile bool button1_enabled = true; // Used for debouncing
Timeout button1_timeout; // Used for debouncing
int press_count;
Timeout buttonOptions_timeout; // Used for determining number of presses

unsigned int time_window = 5; // Default 3600 seconds in an hour
unsigned long long_interval = 604800; // Default 31557600 seconds in a year
unsigned int hb_interval = 3600; // Default 86400 seconds in a day
unsigned long test_interval = 86400; // Default 2592000 seconds in a month

time_t next_stest;
time_t next_ltest;

uint8_t hb_data[5];
uint8_t tdata[10];

uint8_t *data;
uint8_t data_len;

bool running_test;
bool joining;
// bool received_downlink;

time_t current_time;

unsigned long last_hb;
unsigned long test_start;

uint8_t measurements[5];

unsigned long test_duration;

/*!
 * Specifies the state of the application LED
 */
static bool AppLedStateOn = false;
volatile bool Led3StateChanged = false;
/*!
 * Timer to handle the state of LED1
 */
static TimerEvent_t Led1Timer;
volatile bool Led1State = false;
volatile bool Led1StateChanged = false;
/*!
 * Timer to handle the state of LED2
 */
static TimerEvent_t Led2Timer;
volatile bool Led2State = false;
volatile bool Led2StateChanged = false;

/*!
 * Indicates if a new packet can be sent
 */
static bool NextTx = true;

/*!
 * Device states
 */
static enum eDeviceState
{
    DEVICE_STATE_INIT,
    DEVICE_STATE_JOIN,
    DEVICE_STATE_SEND,
    DEVICE_STATE_CYCLE,
    DEVICE_STATE_SLEEP
}DeviceState;

static enum eMessageType
{
    MESSAGE_TYPE_HB,
    MESSAGE_TYPE_SHORT_TEST,
    MESSAGE_TYPE_LONG_TEST
}MessageType;

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

/*!
 * Indicates if the MAC layer network join status has changed.
 */
static bool IsNetworkJoinedStatusUpdate = false;

/*!
 * 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;
volatile bool UplinkStatusUpdated = false;

/*!
 * 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 SerialRxProcess( void )
{
    if( SerialDisplayReadable( ) == true )
    {
        switch( SerialDisplayGetChar( ) )
        {
            case 'R':
            case 'r':
                // Refresh Serial screen
                SerialDisplayRefresh( );
                break;
            default:
                break;
        }
    }
}

// Enables button when bouncing is over
void button1_enabled_cb(void)
{
    button1_enabled = true;
}

// ISR handling button pressed event
void button1_onpressed_cb(void)
{
    if (button1_enabled) { // Disabled while the button is bouncing
        button1_enabled = false;
        button1_pressed = true; // To be read by the main loop
        button1_timeout.attach(callback(button1_enabled_cb), 0.3); // Debounce time 300 ms
    }
}

// ****************************    COMMUNICATION PACKET DEFINITION METHODS   ********************************   //

void heartbeat_message(uint8_t *hb_data, uint8_t *current_date) {
    hb_data[0] = 0xD2;
    memcpy(hb_data+1, current_date, 4);
}

void short_test_result(uint8_t *tdata, uint8_t *test_start_date, uint8_t *measurements) {
    tdata[0] = 0xD7;
    memcpy(tdata+1, test_start_date, 4);
    memcpy(tdata+5, measurements, 5);
}

void long_test_result(uint8_t *tdata, uint8_t *test_start_date, uint8_t *measurements) {
    tdata[0] = 0xD8;
    memcpy(tdata+1, test_start_date, 4);
    memcpy(tdata+5, measurements, 5);
}

/*!
 * \brief   Prepares the payload of the frame
 */
static void PrepareTxFrame( uint8_t port )
{
    switch( port )
    {
    case 15:
        {   
            switch ( MessageType ) 
            {
                case MESSAGE_TYPE_HB:
                {
                    AppDataSize = 5;
                    heartbeat_message(AppData, (uint8_t *)&current_time);
                    break;
                }
                case MESSAGE_TYPE_SHORT_TEST:
                {
                    AppDataSize = 10;
                    short_test_result(AppData, (uint8_t *)&current_time, measurements);
                    break;
                }
                case MESSAGE_TYPE_LONG_TEST:
                {
                    AppDataSize = 10;
                    long_test_result(AppData, (uint8_t *)&current_time, measurements);
                    break;
                }
                default:
                {
                    AppDataSize = 5;
                    heartbeat_message(AppData, (uint8_t *)&current_time);
                    break;
                }
            }
        }
        break;
    case 224:
        if( ComplianceTest.LinkCheck == true )
        {
            ComplianceTest.LinkCheck = false;
            AppDataSize = 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:
                AppDataSize = 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 bool SendFrame( void )
{
    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.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 );
        // SerialDisplayPrintDebugStatus( LoRaMacQueryTxPossible( AppDataSize, &txInfo ) );
        
    }
    else
    {
        LoRaMacUplinkStatus.Acked = false;
        LoRaMacUplinkStatus.Port = AppPort;
        LoRaMacUplinkStatus.Buffer = AppData;
        LoRaMacUplinkStatus.BufferSize = AppDataSize;
        SerialDisplayUpdateFrameType( IsTxConfirmed );

        if( IsTxConfirmed == false )
        {
            mcpsReq.Type = MCPS_UNCONFIRMED;
            mcpsReq.Req.Unconfirmed.fPort = AppPort;
            mcpsReq.Req.Unconfirmed.fBuffer = AppData;
            mcpsReq.Req.Unconfirmed.fBufferSize = AppDataSize;
            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 = AppDataSize;
            mcpsReq.Req.Confirmed.NbTrials = 8;
            mcpsReq.Req.Confirmed.Datarate = LORAWAN_DEFAULT_DATARATE;
        }
    }

    if( LoRaMacMcpsRequest( &mcpsReq ) == LORAMAC_STATUS_OK )
    {
        if( LoRaMacQueryTxPossible( AppDataSize, &txInfo ) != LORAMAC_STATUS_OK )
        {
            return true;
        }
        return false;
    }
    return true;
}

/*!
 * \brief Function executed on TxNextPacket Timeout event
 */
static void OnTxNextPacketTimerEvent( void )
{
    MibRequestConfirm_t mibReq;
    LoRaMacStatus_t status;

    TimerStop( &TxNextPacketTimer );

    mibReq.Type = MIB_NETWORK_JOINED;
    status = LoRaMacMibGetRequestConfirm( &mibReq );

    if( status == LORAMAC_STATUS_OK )
    {
        if( mibReq.Param.IsNetworkJoined == true )
        {
            DeviceState = DEVICE_STATE_SEND;
            NextTx = true;
        }
        else
        {
            DeviceState = DEVICE_STATE_JOIN;
        }
    }
}

/*!
 * \brief Function executed on Led 1 Timeout event
 */
static void OnLed1TimerEvent( void )
{
    TimerStop( &Led1Timer );
    // Switch LED 1 OFF
    Led1State = false;
    Led1StateChanged = true;
}

/*!
 * \brief Function executed on Led 2 Timeout event
 */
static void OnLed2TimerEvent( void )
{
    TimerStop( &Led2Timer );
    // Switch LED 2 OFF
    Led2State = false;
    Led2StateChanged = true;
}

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

        // Switch LED 1 ON
        Led1State = true;
        Led1StateChanged = true;
        TimerStart( &Led1Timer );

        UplinkStatusUpdated = true;
    }
    NextTx = true;
}

/*!
 * \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 )
    {
        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 )
    {
        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 ) )
                {
                    IsTxConfirmed = false;
                    AppPort = 224;
                    AppDataSize = 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)
                    IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
                    AppPort = LORAWAN_APP_PORT;
                    AppDataSize = 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)
                    AppDataSize = 2;
                    break;
                case 2: // Enable confirmed messages (v)
                    IsTxConfirmed = true;
                    ComplianceTest.State = 1;
                    break;
                case 3:  // Disable confirmed messages (vi)
                    IsTxConfirmed = false;
                    ComplianceTest.State = 1;
                    break;
                case 4: // (vii)
                    AppDataSize = mcpsIndication->BufferSize;

                    AppData[0] = 4;
                    for( uint8_t i = 1; i < AppDataSize; 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
                        IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
                        AppPort = LORAWAN_APP_PORT;
                        AppDataSize = 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;
        }
    }

    // Switch LED 2 ON for each received downlink
    Led2State = true;
    Led2StateChanged = true;
    TimerStart( &Led2Timer );
    DownlinkStatusUpdated = true;
}

/*!
 * \brief   MLME-Confirm event function
 *
 * \param   [IN] mlmeConfirm - Pointer to the confirm structure,
 *               containing confirm attributes.
 */
static void MlmeConfirm( MlmeConfirm_t *mlmeConfirm )
{
    switch( mlmeConfirm->MlmeRequest )
    {
        case MLME_JOIN:
        {
            if( mlmeConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK )
            {
                // Status is OK, node has joined the network
                IsNetworkJoinedStatusUpdate = true;
                DeviceState = DEVICE_STATE_SEND;
                
            
                MessageType = MESSAGE_TYPE_HB;
                joining = false;
                next_stest = current_time + 5;
                next_ltest = current_time + long_interval;
            }
            else
            {
                // Join was not successful. Try to join again
                DeviceState = DEVICE_STATE_JOIN;
            }
            break;
        }
        case MLME_LINK_CHECK:
        {
            if( mlmeConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK )
            {
                // Check DemodMargin
                // Check NbGateways
                if( ComplianceTest.Running == true )
                {
                    ComplianceTest.LinkCheck = true;
                    ComplianceTest.DemodMargin = mlmeConfirm->DemodMargin;
                    ComplianceTest.NbGateways = mlmeConfirm->NbGateways;
                }
            }
            break;
        }
        default:
            break;
    }
    NextTx = true;
    UplinkStatusUpdated = true;
}


// ****************************    TEST METHODS   ********************************   //

void startTest(uint8_t *measurements) {
    
    // Determine test length
    switch (MessageType)
    {
        case MESSAGE_TYPE_SHORT_TEST:
            test_duration = 30;
            break;
        case MESSAGE_TYPE_LONG_TEST:
            test_duration = 5400;
            break;
        default:
            test_duration = 0;
            break;
    }
    
    // Measure voltage preval
    measurements[0] = (uint8_t) (BAT_PIN.read()*1000)/10;
    
    // Start test
    relayPin = 1;
    AppLedStateOn = 1;
    Led3StateChanged = true;
}

void endTest(uint8_t *measurements) {
    // Measure endvals
    int battery_reading = BAT_PIN.read()*1000;
    int vce_reading = VCE_PIN.read()*1000;
    int light_1_reading = LIGHT_1_PIN.read()*1000 - vce_reading;
    int light_2_reading = LIGHT_2_PIN.read()*1000 - vce_reading;
    int rh_reading = RH_PIN.read()*1000 - vce_reading;
    
    measurements[1] = (uint8_t) (battery_reading/10);
    measurements[2] = (uint8_t) light_1_reading;
    measurements[3] = (uint8_t) light_2_reading;
    measurements[4] = (uint8_t) rh_reading;
    
    // End test
    relayPin = 0;
    AppLedStateOn = 0;
    Led3StateChanged = true;
}

void button_options_handler(void)
{
    if (press_count >= 3) {
        // Run long test
        MessageType = MESSAGE_TYPE_LONG_TEST;
        
        startTest(measurements);
                    
        running_test = true;
        test_start = current_time;
    }
    if (press_count == 2) {
        // Run short test
        MessageType = MESSAGE_TYPE_SHORT_TEST;
        
        startTest(measurements);
                    
        running_test = true;
        test_start = current_time;
    }
    
    // Reset count
    press_count = 0;
}

/**
 * Main application entry point.
 */
Serial pc(SERIAL_TX, SERIAL_RX,115200);
int MY_SetSysClock_PLL_HSE(void)
{
    RCC_ClkInitTypeDef RCC_ClkInitStruct;
    RCC_OscInitTypeDef RCC_OscInitStruct;

    /* Enable HSE and activate PLL with HSE as source */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
    RCC_OscInitStruct.HSEState       = RCC_HSE_ON; /* External 8 MHz xtal on OSC_IN/OSC_OUT */

    // PLLCLK = (8 MHz * 8)/2 = 32 MHz
    RCC_OscInitStruct.PLL.PLLState        = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource       = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLMUL          = RCC_PLLMUL_8;
    RCC_OscInitStruct.PLL.PLLDIV          = RCC_PLLDIV_2;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        return (-1); // FAIL
    }

    /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */
    RCC_ClkInitStruct.ClockType      = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);
    RCC_ClkInitStruct.SYSCLKSource   = RCC_SYSCLKSOURCE_PLLCLK; // 32 MHz
    RCC_ClkInitStruct.AHBCLKDivider  = RCC_SYSCLK_DIV1;         // 32 MHz
    RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;           // 32 MHz
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;           // 32 MHz
    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK) {
        return (-2); // FAIL
    }

    /* Enable HSE and activate PLL with HSE as source */
    RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_MSI;
    RCC_OscInitStruct.HSIState       = RCC_HSI_OFF;
    RCC_OscInitStruct.MSIState       = RCC_MSI_OFF;
    RCC_OscInitStruct.HSI48State     = RCC_HSI48_OFF;
    RCC_OscInitStruct.PLL.PLLState   = RCC_PLL_NONE;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
        return (-3); // FAIL
    }
        
    return 0; // OK
}

void my_patch(void)
{
    int retVal;
    
    // Put device into default clock, i.e using MSI = 2MHz
    HAL_RCC_DeInit();
    
    // Enable HSE clock
    retVal = MY_SetSysClock_PLL_HSE();
    if(retVal< 0)
    {
        // fail
        //pc.printf("Failed to start HSE, ERR= %d\r\n", retVal);
        
        // indicate error
        while(1)
        {
            myled = 1;
            wait(0.2);
            myled = 0;
            wait(0.5);
        }
    }    
}




int main( void )
{
    pc.printf("mbed-os-rev: %d.%d.%d   lib-rev: %d\r\n", \
            MBED_MAJOR_VERSION, MBED_MINOR_VERSION,MBED_PATCH_VERSION,MBED_LIBRARY_VERSION);
    pc.printf("BUILD= %s, SysClock= %d, RCC= %0X\r\n", __TIME__, SystemCoreClock, RCC->CR);   
    my_patch();
    pc.printf("NEW SysClock= %d, NEW RCC= %0X\r\n", SystemCoreClock, RCC->CR);
    wait(3);
    LoRaMacPrimitives_t LoRaMacPrimitives;
    LoRaMacCallback_t LoRaMacCallbacks;
    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;
    set_time(1514764800);
    relayPin = 0;
    
    running_test = false;
    joining = true;
    //received_downlink = false;
    time_t start_time = time(NULL);
  
    last_hb = start_time;
    
    //button1.mode(PullUp); // Activate pull-up
    button1.fall(callback(button1_onpressed_cb)); // Attach ISR to handle button press event
    AppLedStateOn = 0;
    Led3StateChanged = true;

    while( 1 )
    {
        current_time = time(NULL);
        SerialRxProcess( );
        if( IsNetworkJoinedStatusUpdate == true )
        {
            IsNetworkJoinedStatusUpdate = false;
            mibReq.Type = MIB_NETWORK_JOINED;
            LoRaMacMibGetRequestConfirm( &mibReq );
            SerialDisplayUpdateNetworkIsJoined( mibReq.Param.IsNetworkJoined );
        }
        if( Led1StateChanged == true )
        {
            Led1StateChanged = false;
            SerialDisplayUpdateLedState( 1, Led1State );
        }
        if( Led2StateChanged == true )
        {
            Led2StateChanged = false;
            SerialDisplayUpdateLedState( 2, Led2State );
        }
        if( Led3StateChanged == true )
        {
            Led3StateChanged = false;
            SerialDisplayUpdateLedState( 3, AppLedStateOn );
        }
        if( UplinkStatusUpdated == true )
        {
            UplinkStatusUpdated = false;
            SerialDisplayUpdateUplink( LoRaMacUplinkStatus.Acked, LoRaMacUplinkStatus.Datarate, LoRaMacUplinkStatus.UplinkCounter, LoRaMacUplinkStatus.Port, LoRaMacUplinkStatus.Buffer, LoRaMacUplinkStatus.BufferSize );
        }
        if( DownlinkStatusUpdated == true )
        {
            DownlinkStatusUpdated = false;
            SerialDisplayUpdateLedState( 2, Led2State );
            SerialDisplayUpdateDownlink( LoRaMacDownlinkStatus.RxData, LoRaMacDownlinkStatus.Rssi, LoRaMacDownlinkStatus.Snr, LoRaMacDownlinkStatus.DownlinkCounter, LoRaMacDownlinkStatus.Port, LoRaMacDownlinkStatus.Buffer, LoRaMacDownlinkStatus.BufferSize );
        }
        
        switch( DeviceState )
        {
            case DEVICE_STATE_INIT:
            {
                LoRaMacPrimitives.MacMcpsConfirm = McpsConfirm;
                LoRaMacPrimitives.MacMcpsIndication = McpsIndication;
                LoRaMacPrimitives.MacMlmeConfirm = MlmeConfirm;
                LoRaMacCallbacks.GetBatteryLevel = BoardGetBatteryLevel;
                LoRaMacInitialization( &LoRaMacPrimitives, &LoRaMacCallbacks );

                TimerInit( &TxNextPacketTimer, OnTxNextPacketTimerEvent );

                TimerInit( &Led1Timer, OnLed1TimerEvent );
                TimerSetValue( &Led1Timer, 25 );

                TimerInit( &Led2Timer, OnLed2TimerEvent );
                TimerSetValue( &Led2Timer, 25 );

                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 )
                MlmeReq_t mlmeReq;

                mlmeReq.Type = MLME_JOIN;

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

                if( NextTx == true )
                {
                    LoRaMacMlmeRequest( &mlmeReq );
                }
                DeviceState = DEVICE_STATE_SLEEP;
#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
                IsNetworkJoinedStatusUpdate = true;
                break;
            }
            case DEVICE_STATE_SEND:
            {
                if( NextTx == true )
                {
                    SerialDisplayUpdateUplinkAcked( false );
                    SerialDisplayUpdateDonwlinkRxData( false );
                    PrepareTxFrame( AppPort );

                    NextTx = SendFrame( );
                }
                if( ComplianceTest.Running == true )
                {
                    // Schedule next packet transmission
                    TxDutyCycleTime = 5000; // 5000 ms
                }
                else
                {
                    // Schedule next packet transmission
                    TxDutyCycleTime = APP_TX_DUTYCYCLE + randr( -APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND );
                }
                
                if ( NextTx == true ) 
                {
                    DeviceState = DEVICE_STATE_SLEEP;

                    // Schedule next packet transmission
                    TimerSetValue( &TxNextPacketTimer, TxDutyCycleTime );
                    TimerStart( &TxNextPacketTimer );
                    
                    // char my_str[17] = "Scheduled Resend";
                    // SerialDisplayPrintDebugLine( my_str, 17 );
                }
                else
                {
                    DeviceState = DEVICE_STATE_CYCLE;
                }
                
                break;
            }
            case DEVICE_STATE_CYCLE:
            {
                // DeviceState = DEVICE_STATE_SLEEP;

                // Schedule next packet transmission
                // TimerSetValue( &TxNextPacketTimer, TxDutyCycleTime );
                // TimerStart( &TxNextPacketTimer );
                
                
                
                // Is a test is running
                if (running_test) {
                  // If it is 
              
                  //check if it's time to end the test
                  if (current_time - test_start >= test_duration) {
                    endTest(measurements);
                    // sendResult(measurements, ttype);
                    DeviceState = DEVICE_STATE_SEND;
                    last_hb = current_time;
              
                    // Set the times for the next tests
                    switch ( MessageType )
                    {
                        case MESSAGE_TYPE_SHORT_TEST:
                        {
                            next_stest = test_start + test_interval;
                            break;
                        }
                        default:
                        {
                            // If long test, reset both test types, 
                            // to prevent two tests at the same time
                            next_stest = test_start + test_interval;
                            next_ltest = test_start + long_interval;
                            break;
                        }  
                    }
                    
                    running_test = false;
                  }
                } else {
                  // If it isn't
                  
                  // check if downlink action is required
            
                  //if (received_downlink == true) {
                  //  dl_handler(received_data);
                  //}
                  
                  //check if button is pressed
                    if (button1_pressed) { // Set when button is pressed
                        if (press_count == 0) {
                            buttonOptions_timeout.attach(callback(button_options_handler), 2); // Debounce time 300 ms
                        }
                        button1_pressed = false;
                        press_count++;
                    }
                  
                  //check if it's time to run a test
                  if (current_time >= next_stest || current_time >= next_ltest) {
              
                    // Check what kind of test to run
                    if (current_time >= next_ltest) {
                      MessageType = MESSAGE_TYPE_LONG_TEST;
                    } else {
                      MessageType = MESSAGE_TYPE_SHORT_TEST;
                    }
            
                    startTest(measurements);
                    
                    running_test = true;
                    test_start = current_time;
              
                    
                  //check if it's time for a heartbeat
                  } else if (current_time - last_hb >= hb_interval) { 
                    // sendHb();
                    DeviceState = DEVICE_STATE_SEND;
                    MessageType = MESSAGE_TYPE_HB;
                    last_hb = current_time;
                  }
                }
                
                
                break;
            }
            case DEVICE_STATE_SLEEP:
            {
                // Wake up through events
                break;
            }
            default:
            {
                DeviceState = DEVICE_STATE_INIT;
                break;
            }
        }
    }
}