text: /*!
   * \file      main.c
   *
   * \brief     LoRaMac classA device implementation
   *
   * \copyright Revised BSD License, see section \ref LICENSE.
   *
   * \code
   *                ______                              _
   *               / _____)             _              | |
   *              ( (____  _____ ____ _| |_ _____  ____| |__
   *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
   *               _____) ) ____| | | || |_| ____( (___| | | |
   *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
   *              (C)2013-2017 Semtech
   *
   * \endcode
   *
   * \author    Miguel Luis ( Semtech )
   *
   * \author    Gregory Cristian ( Semtech )
   */
  
  /*! \file classA/NucleoL152/main.c */
  
  #include <stdio.h>
  #include "utilities.h"
  #include "board.h"
  #include "gpio.h"
  #include "LoRaMac.h"
  #include "Commissioning.h"
  #include "NvmCtxMgmt.h"
  
  #ifndef ACTIVE_REGION
  
  #warning "No active region defined, LORAMAC_REGION_EU868 will be used as default."
  
  #define ACTIVE_REGION LORAMAC_REGION_EU868
  
  #endif
  
  /*!
   * 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                    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( REGION_EU868 )
  
  #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                        true
  
  #endif
  
  /*!
   * LoRaWAN application port
   */
  #define LORAWAN_APP_PORT                            2
  
  static uint8_t DevEui[] = LORAWAN_DEVICE_EUI;
  static uint8_t JoinEui[] = LORAWAN_JOIN_EUI;
  static uint8_t AppKey[] = LORAWAN_APP_KEY;
  static uint8_t NwkKey[] = LORAWAN_NWK_KEY;
  
  #if( OVER_THE_AIR_ACTIVATION == 0 )
  
  static uint8_t FNwkSIntKey[] = LORAWAN_F_NWK_S_INT_KEY;
  static uint8_t SNwkSIntKey[] = LORAWAN_S_NWK_S_INT_KEY;
  static uint8_t NwkSEncKey[] = LORAWAN_NWK_S_ENC_KEY;
  static uint8_t AppSKey[] = LORAWAN_APP_S_KEY;
  
  /*!
   * 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 = 1;
  static uint8_t AppDataSizeBackup = 1;
  
  /*!
   * User application data buffer size
   */
  #define LORAWAN_APP_DATA_MAX_SIZE                           242
  
  /*!
   * User application data
   */
  static uint8_t AppDataBuffer[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;
  
  /*!
   * Specifies the state of the application LED
   */
  static bool AppLedStateOn = false;
  
  /*!
   * Timer to handle the state of LED1
   */
  static TimerEvent_t Led1Timer;
  
  /*!
   * Timer to handle the state of LED2
   */
  static TimerEvent_t Led2Timer;
  
  /*!
   * Indicates if a new packet can be sent
   */
  static bool NextTx = true;
  
  /*!
   * Indicates if LoRaMacProcess call is pending.
   * 
   * \warning If variable is equal to 0 then the MCU can be set in low power mode
   */
  static uint8_t IsMacProcessPending = 0;
  
  /*!
   * Device states
   */
  static enum eDeviceState
  {
      DEVICE_STATE_RESTORE,
      DEVICE_STATE_START,
      DEVICE_STATE_JOIN,
      DEVICE_STATE_SEND,
      DEVICE_STATE_CYCLE,
      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;
  
  /*!
   *
   */
  typedef enum
  {
      LORAMAC_HANDLER_UNCONFIRMED_MSG = 0,
      LORAMAC_HANDLER_CONFIRMED_MSG = !LORAMAC_HANDLER_UNCONFIRMED_MSG
  }LoRaMacHandlerMsgTypes_t;
  
  /*!
   * Application data structure
   */
  typedef struct LoRaMacHandlerAppData_s
  {
      LoRaMacHandlerMsgTypes_t MsgType;
      uint8_t Port;
      uint8_t BufferSize;
      uint8_t *Buffer;
  }LoRaMacHandlerAppData_t;
  
  LoRaMacHandlerAppData_t AppData =
  {
      .MsgType = LORAMAC_HANDLER_UNCONFIRMED_MSG,
      .Buffer = NULL,
      .BufferSize = 0,
      .Port = 0
  };
  
  /*!
   * LED GPIO pins objects
   */
  extern Gpio_t Led1; // Tx
  extern Gpio_t Led2; // Rx
  
  /*!
   * MAC status strings
   */
  const char* MacStatusStrings[] =
  {
      "OK", "Busy", "Service unknown", "Parameter invalid", "Frequency invalid",
      "Datarate invalid", "Frequency or datarate invalid", "No network joined",
      "Length error", "Device OFF", "Region not supported", "Skipped APP data",
      "Duty-cycle restricted", "No channel found", "No free channel found",
      "Busy beacon reserved time", "Busy ping-slot window time",
      "Busy uplink collision", "Crypto error", "FCnt handler error",
      "MAC command error", "ERROR"
  };
  
  /*!
   * MAC event info status strings.
   */
  const char* EventInfoStatusStrings[] =
  { 
      "OK", "Error", "Tx timeout", "Rx 1 timeout",
      "Rx 2 timeout", "Rx1 error", "Rx2 error",
      "Join failed", "Downlink repeated", "Tx DR payload size error",
      "Downlink too many frames loss", "Address fail", "MIC fail",
      "Multicast fail", "Beacon locked", "Beacon lost", "Beacon not found"
  };
  
  /*!
   * Prints the provided buffer in HEX
   * 
   * \param buffer Buffer to be printed
   * \param size   Buffer size to be printed
   */
  void PrintHexBuffer( uint8_t *buffer, uint8_t size )
  {
      uint8_t newline = 0;
  
      for( uint8_t i = 0; i < size; i++ )
      {
          if( newline != 0 )
          {
              printf( "\r\n" );
              newline = 0;
          }
  
          printf( "%02X ", buffer[i] );
  
          if( ( ( i + 1 ) % 16 ) == 0 )
          {
              newline = 1;
          }
      }
      printf( "\r\n" );
  }
  
  /*!
   * Executes the network Join request
   */
  static void JoinNetwork( void )
  {
      LoRaMacStatus_t status;
      MlmeReq_t mlmeReq;
      mlmeReq.Type = MLME_JOIN;
      mlmeReq.Req.Join.DevEui = DevEui;
      mlmeReq.Req.Join.JoinEui = JoinEui;
      mlmeReq.Req.Join.Datarate = LORAWAN_DEFAULT_DATARATE;
  
      // Starts the join procedure
      status = LoRaMacMlmeRequest( &mlmeReq );
      printf( "\r\n###### ===== MLME-Request - MLME_JOIN ==== ######\r\n" );
      printf( "STATUS      : %s\r\n", MacStatusStrings[status] );
  
      if( status == LORAMAC_STATUS_OK )
      {
          printf( "###### ===== JOINING ==== ######\r\n" );
          DeviceState = DEVICE_STATE_SLEEP;
      }
      else
      {
          DeviceState = DEVICE_STATE_CYCLE;
      }
  }
  
  /*!
   * \brief   Prepares the payload of the frame
   */
  static void PrepareTxFrame( uint8_t port )
  {
      switch( port )
      {
      case 2:
          {
              AppDataSizeBackup = AppDataSize = 1;
              AppDataBuffer[0] = AppLedStateOn;
          }
          break;
      case 224:
          if( ComplianceTest.LinkCheck == true )
          {
              ComplianceTest.LinkCheck = false;
              AppDataSize = 3;
              AppDataBuffer[0] = 5;
              AppDataBuffer[1] = ComplianceTest.DemodMargin;
              AppDataBuffer[2] = ComplianceTest.NbGateways;
              ComplianceTest.State = 1;
          }
          else
          {
              switch( ComplianceTest.State )
              {
              case 4:
                  ComplianceTest.State = 1;
                  break;
              case 1:
                  AppDataSize = 2;
                  AppDataBuffer[0] = ComplianceTest.DownLinkCounter >> 8;
                  AppDataBuffer[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;
      }
      else
      {
          if( IsTxConfirmed == false )
          {
              mcpsReq.Type = MCPS_UNCONFIRMED;
              mcpsReq.Req.Unconfirmed.fPort = AppPort;
              mcpsReq.Req.Unconfirmed.fBuffer = AppDataBuffer;
              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 = AppDataBuffer;
              mcpsReq.Req.Confirmed.fBufferSize = AppDataSize;
              mcpsReq.Req.Confirmed.NbTrials = 8;
              mcpsReq.Req.Confirmed.Datarate = LORAWAN_DEFAULT_DATARATE;
          }
      }
  
      // Update global variable
      AppData.MsgType = ( mcpsReq.Type == MCPS_CONFIRMED ) ? LORAMAC_HANDLER_CONFIRMED_MSG : LORAMAC_HANDLER_UNCONFIRMED_MSG;
      AppData.Port = mcpsReq.Req.Unconfirmed.fPort;
      AppData.Buffer = mcpsReq.Req.Unconfirmed.fBuffer;
      AppData.BufferSize = mcpsReq.Req.Unconfirmed.fBufferSize;
  
      LoRaMacStatus_t status;
      status = LoRaMacMcpsRequest( &mcpsReq );
      printf( "\r\n###### ===== MCPS-Request ==== ######\r\n" );
      printf( "STATUS      : %s\r\n", MacStatusStrings[status] );
  
      if( status == LORAMAC_STATUS_OK )
      {
          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_ACTIVATION;
      status = LoRaMacMibGetRequestConfirm( &mibReq );
  
      if( status == LORAMAC_STATUS_OK )
      {
          if( mibReq.Param.NetworkActivation == ACTIVATION_TYPE_NONE )
          {
              // Network not joined yet. Try to join again
              JoinNetwork( );
          }
          else
          {
              DeviceState = DEVICE_STATE_SEND;
              NextTx = true;
          }
      }
  }
  
  /*!
   * \brief Function executed on Led 1 Timeout event
   */
  static void OnLed1TimerEvent( void )
  {
      TimerStop( &Led1Timer );
      // Switch LED 1 OFF
      GpioWrite( &Led1, 0 );
  }
  
  /*!
   * \brief Function executed on Led 2 Timeout event
   */
  static void OnLed2TimerEvent( void )
  {
      TimerStop( &Led2Timer );
      // Switch LED 2 OFF
      GpioWrite( &Led2, 0 );
  }
  
  /*!
   * \brief   MCPS-Confirm event function
   *
   * \param   [IN] mcpsConfirm - Pointer to the confirm structure,
   *               containing confirm attributes.
   */
  static void McpsConfirm( McpsConfirm_t *mcpsConfirm )
  {
      printf( "\r\n###### ===== MCPS-Confirm ==== ######\r\n" );
      printf( "STATUS      : %s\r\n", EventInfoStatusStrings[mcpsConfirm->Status] );
      if( mcpsConfirm->Status != LORAMAC_EVENT_INFO_STATUS_OK )
      {
      }
      else
      {
          switch( mcpsConfirm->McpsRequest )
          {
              case MCPS_UNCONFIRMED:
              {
                  // Check Datarate
                  // Check TxPower
                  break;
              }
              case MCPS_CONFIRMED:
              {
                  // Check Datarate
                  // Check TxPower
                  // Check AckReceived
                  // Check NbTrials
                  break;
              }
              case MCPS_PROPRIETARY:
              {
                  break;
              }
              default:
                  break;
          }
  
          // Switch LED 1 ON
          GpioWrite( &Led1, 1 );
          TimerStart( &Led1Timer );
      }
      MibRequestConfirm_t mibGet;
      MibRequestConfirm_t mibReq;
  
      mibReq.Type = MIB_DEVICE_CLASS;
      LoRaMacMibGetRequestConfirm( &mibReq );
  
      printf( "\r\n###### ===== UPLINK FRAME %lu ==== ######\r\n", mcpsConfirm->UpLinkCounter );
      printf( "\r\n" );
  
      printf( "CLASS       : %c\r\n", "ABC"[mibReq.Param.Class] );
      printf( "\r\n" );
      printf( "TX PORT     : %d\r\n", AppData.Port );
  
      if( AppData.BufferSize != 0 )
      {
          printf( "TX DATA     : " );
          if( AppData.MsgType == LORAMAC_HANDLER_CONFIRMED_MSG )
          {
              printf( "CONFIRMED - %s\r\n", ( mcpsConfirm->AckReceived != 0 ) ? "ACK" : "NACK" );
          }
          else
          {
              printf( "UNCONFIRMED\r\n" );
          }
          PrintHexBuffer( AppData.Buffer, AppData.BufferSize );
      }
  
      printf( "\r\n" );
      printf( "DATA RATE   : DR_%d\r\n", mcpsConfirm->Datarate );
  
      mibGet.Type  = MIB_CHANNELS;
      if( LoRaMacMibGetRequestConfirm( &mibGet ) == LORAMAC_STATUS_OK )
      {
          printf( "U/L FREQ    : %lu\r\n", mibGet.Param.ChannelList[mcpsConfirm->Channel].Frequency );
      }
  
      printf( "TX POWER    : %d\r\n", mcpsConfirm->TxPower );
  
      mibGet.Type  = MIB_CHANNELS_MASK;
      if( LoRaMacMibGetRequestConfirm( &mibGet ) == LORAMAC_STATUS_OK )
      {
          printf("CHANNEL MASK: ");
  #if defined( REGION_AS923 ) || defined( REGION_CN779 ) || \
      defined( REGION_EU868 ) || defined( REGION_IN865 ) || \
      defined( REGION_KR920 ) || defined( REGION_EU433 ) || \
      defined( REGION_RU864 )
  
          for( uint8_t i = 0; i < 1; i++)
  
  #elif defined( REGION_AU915 ) || defined( REGION_US915 ) || defined( REGION_CN470 )
  
          for( uint8_t i = 0; i < 5; i++)
  #else
  
  #error "Please define a region in the compiler options."
  
  #endif
          {
              printf("%04X ", mibGet.Param.ChannelsMask[i] );
          }
          printf("\r\n");
      }
  
      printf( "\r\n" );
  }
  
  /*!
   * \brief   MCPS-Indication event function
   *
   * \param   [IN] mcpsIndication - Pointer to the indication structure,
   *               containing indication attributes.
   */
  static void McpsIndication( McpsIndication_t *mcpsIndication )
  {
      printf( "\r\n###### ===== MCPS-Indication ==== ######\r\n" );
      printf( "STATUS      : %s\r\n", EventInfoStatusStrings[mcpsIndication->Status] );
      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
      if( mcpsIndication->FramePending == true )
      {
          // The server signals that it has pending data to be sent.
          // We schedule an uplink as soon as possible to flush the server.
          OnTxNextPacketTimerEvent( );
      }
      // Check Buffer
      // Check BufferSize
      // Check Rssi
      // Check Snr
      // Check RxSlot
  
      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;
              }
              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;
                      AppDataSizeBackup = AppDataSize;
                      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( REGION_EU868 )
                      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 = AppDataSizeBackup;
                      ComplianceTest.DownLinkCounter = 0;
                      ComplianceTest.Running = false;
  
                      MibRequestConfirm_t mibReq;
                      mibReq.Type = MIB_ADR;
                      mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
                      LoRaMacMibSetRequestConfirm( &mibReq );
  #if defined( REGION_EU868 )
                      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;
  
                      AppDataBuffer[0] = 4;
                      for( uint8_t i = 1; i < MIN( AppDataSize, LORAWAN_APP_DATA_MAX_SIZE ); i++ )
                      {
                          AppDataBuffer[i] = mcpsIndication->Buffer[i] + 1;
                      }
                      break;
                  case 5: // (viii)
                      {
                          MlmeReq_t mlmeReq;
                          mlmeReq.Type = MLME_LINK_CHECK;
                          LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );
                          printf( "\r\n###### ===== MLME-Request - MLME_LINK_CHECK ==== ######\r\n" );
                          printf( "STATUS      : %s\r\n", MacStatusStrings[status] );
                      }
                      break;
                  case 6: // (ix)
                      {
                          // Disable TestMode and revert back to normal operation
                          IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
                          AppPort = LORAWAN_APP_PORT;
                          AppDataSize = AppDataSizeBackup;
                          ComplianceTest.DownLinkCounter = 0;
                          ComplianceTest.Running = false;
  
                          MibRequestConfirm_t mibReq;
                          mibReq.Type = MIB_ADR;
                          mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
                          LoRaMacMibSetRequestConfirm( &mibReq );
  #if defined( REGION_EU868 )
                          LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );
  #endif
  
                          JoinNetwork( );
                      }
                      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] );
                              LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );
                              printf( "\r\n###### ===== MLME-Request - MLME_TXCW ==== ######\r\n" );
                              printf( "STATUS      : %s\r\n", MacStatusStrings[status] );
                          }
                          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];
                              LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );
                              printf( "\r\n###### ===== MLME-Request - MLME_TXCW1 ==== ######\r\n" );
                              printf( "STATUS      : %s\r\n", MacStatusStrings[status] );
                          }
                          ComplianceTest.State = 1;
                      }
                      break;
                  default:
                      break;
                  }
              }
              break;
          default:
              break;
          }
      }
  
      // Switch LED 2 ON for each received downlink
      GpioWrite( &Led2, 1 );
      TimerStart( &Led2Timer );
  
      const char *slotStrings[] = { "1", "2", "C", "Ping-Slot", "Multicast Ping-Slot" };
      int32_t snr = 0;
      if( mcpsIndication->Snr & 0x80 ) // The SNR sign bit is 1
      {
          // Invert and divide by 4
          snr = ( ( ~mcpsIndication->Snr + 1 ) & 0xFF ) >> 2;
          snr = -snr;
      }
      else
      {
          // Divide by 4
          snr = ( mcpsIndication->Snr & 0xFF ) >> 2;
      }
  
      printf( "\r\n###### ===== DOWNLINK FRAME %lu ==== ######\r\n", mcpsIndication->DownLinkCounter );
  
      printf( "RX WINDOW   : %s\r\n", slotStrings[mcpsIndication->RxSlot] );
      
      printf( "RX PORT     : %d\r\n", mcpsIndication->Port );
  
      if( mcpsIndication->BufferSize != 0 )
      {
          printf( "RX DATA     : \r\n" );
          PrintHexBuffer( mcpsIndication->Buffer, mcpsIndication->BufferSize );
      }
  
      printf( "\r\n" );
      printf( "DATA RATE   : DR_%d\r\n", mcpsIndication->RxDatarate );
      printf( "RX RSSI     : %d\r\n", mcpsIndication->Rssi );
      printf( "RX SNR      : %ld\r\n", snr );
  
      printf( "\r\n" );
  }
  
  /*!
   * \brief   MLME-Confirm event function
   *
   * \param   [IN] mlmeConfirm - Pointer to the confirm structure,
   *               containing confirm attributes.
   */
  static void MlmeConfirm( MlmeConfirm_t *mlmeConfirm )
  {
      printf( "\r\n###### ===== MLME-Confirm ==== ######\r\n" );
      printf( "STATUS      : %s\r\n", EventInfoStatusStrings[mlmeConfirm->Status] );
      if( mlmeConfirm->Status != LORAMAC_EVENT_INFO_STATUS_OK )
      {
      }
      switch( mlmeConfirm->MlmeRequest )
      {
          case MLME_JOIN:
          {
              if( mlmeConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK )
              {
                  MibRequestConfirm_t mibGet;
                  printf( "###### ===== JOINED ==== ######\r\n" );
                  printf( "\r\nOTAA\r\n\r\n" );
  
                  mibGet.Type = MIB_DEV_ADDR;
                  LoRaMacMibGetRequestConfirm( &mibGet );
                  printf( "DevAddr     : %08lX\r\n", mibGet.Param.DevAddr );
  
                  printf( "\n\r\n" );
                  mibGet.Type = MIB_CHANNELS_DATARATE;
                  LoRaMacMibGetRequestConfirm( &mibGet );
                  printf( "DATA RATE   : DR_%d\r\n", mibGet.Param.ChannelsDatarate );
                  printf( "\r\n" );
                  // Status is OK, node has joined the network
                  DeviceState = DEVICE_STATE_SEND;
              }
              else
              {
                  // Join was not successful. Try to join again
                  JoinNetwork( );
              }
              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;
      }
  }
  
  /*!
   * \brief   MLME-Indication event function
   *
   * \param   [IN] mlmeIndication - Pointer to the indication structure.
   */
  static void MlmeIndication( MlmeIndication_t *mlmeIndication )
  {
      if( mlmeIndication->Status != LORAMAC_EVENT_INFO_STATUS_BEACON_LOCKED )
      {
          printf( "\r\n###### ===== MLME-Indication ==== ######\r\n" );
          printf( "STATUS      : %s\r\n", EventInfoStatusStrings[mlmeIndication->Status] );
      }
      if( mlmeIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK )
      {
      }
      switch( mlmeIndication->MlmeIndication )
      {
          case MLME_SCHEDULE_UPLINK:
          {// The MAC signals that we shall provide an uplink as soon as possible
              OnTxNextPacketTimerEvent( );
              break;
          }
          default:
              break;
      }
  }
  
  void OnMacProcessNotify( void )
  {
      IsMacProcessPending = 1;
  }
  
  /**
   * Main application entry point.
   */
  int main( void )
  {
      LoRaMacPrimitives_t macPrimitives;
      LoRaMacCallback_t macCallbacks;
      MibRequestConfirm_t mibReq;
      LoRaMacStatus_t status;
  
      BoardInitMcu( );
      BoardInitPeriph( );
  
      macPrimitives.MacMcpsConfirm = McpsConfirm;
      macPrimitives.MacMcpsIndication = McpsIndication;
      macPrimitives.MacMlmeConfirm = MlmeConfirm;
      macPrimitives.MacMlmeIndication = MlmeIndication;
      macCallbacks.GetBatteryLevel = BoardGetBatteryLevel;
      macCallbacks.GetTemperatureLevel = NULL;
      macCallbacks.NvmContextChange = NvmCtxMgmtEvent;
      macCallbacks.MacProcessNotify = OnMacProcessNotify;
  
      LoRaMacInitialization( &macPrimitives, &macCallbacks, ACTIVE_REGION );
  
      DeviceState = DEVICE_STATE_RESTORE;
  
      printf( "###### ===== ClassA demo application v1.0.RC1 ==== ######\r\n\r\n" );
  
      while( 1 )
      {
          // Process Radio IRQ
          if( Radio.IrqProcess != NULL )
          {
              Radio.IrqProcess( );
          }
          // Processes the LoRaMac events
          LoRaMacProcess( );
  
          switch( DeviceState )
          {
              case DEVICE_STATE_RESTORE:
              {
                  // Try to restore from NVM and query the mac if possible.
                  if( NvmCtxMgmtRestore( ) == NVMCTXMGMT_STATUS_SUCCESS )
                  {
                      printf( "\r\n###### ===== CTXS RESTORED ==== ######\r\n\r\n" );
                  }
                  else
                  {
                      mibReq.Type = MIB_APP_KEY;
                      mibReq.Param.AppKey = AppKey;
                      LoRaMacMibSetRequestConfirm( &mibReq );
  
                      mibReq.Type = MIB_NWK_KEY;
                      mibReq.Param.NwkKey = NwkKey;
                      LoRaMacMibSetRequestConfirm( &mibReq );
  
                      // Initialize LoRaMac device unique ID if not already defined in Commissioning.h
                      if( ( DevEui[0] == 0 ) && ( DevEui[1] == 0 ) &&
                          ( DevEui[2] == 0 ) && ( DevEui[3] == 0 ) &&
                          ( DevEui[4] == 0 ) && ( DevEui[5] == 0 ) &&
                          ( DevEui[6] == 0 ) && ( DevEui[7] == 0 ) )
                      {
                          BoardGetUniqueId( DevEui );
                      }
  
  #if( OVER_THE_AIR_ACTIVATION == 0 )
                      // Choose a random device address if not already defined in Commissioning.h
                      if( DevAddr == 0 )
                      {
                          // Random seed initialization
                          srand1( BoardGetRandomSeed( ) );
  
                          // Choose a random device address
                          DevAddr = randr( 0, 0x01FFFFFF );
                      }
  
                      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_F_NWK_S_INT_KEY;
                      mibReq.Param.FNwkSIntKey = FNwkSIntKey;
                      LoRaMacMibSetRequestConfirm( &mibReq );
  
                      mibReq.Type = MIB_S_NWK_S_INT_KEY;
                      mibReq.Param.SNwkSIntKey = SNwkSIntKey;
                      LoRaMacMibSetRequestConfirm( &mibReq );
  
                      mibReq.Type = MIB_NWK_S_ENC_KEY;
                      mibReq.Param.NwkSEncKey = NwkSEncKey;
                      LoRaMacMibSetRequestConfirm( &mibReq );
  
                      mibReq.Type = MIB_APP_S_KEY;
                      mibReq.Param.AppSKey = AppSKey;
                      LoRaMacMibSetRequestConfirm( &mibReq );
  #endif
                  }
                  DeviceState = DEVICE_STATE_START;
                  break;
              }
  
              case DEVICE_STATE_START:
              {
                  TimerInit( &TxNextPacketTimer, OnTxNextPacketTimerEvent );
  
                  TimerInit( &Led1Timer, OnLed1TimerEvent );
                  TimerSetValue( &Led1Timer, 25 );
  
                  TimerInit( &Led2Timer, OnLed2TimerEvent );
                  TimerSetValue( &Led2Timer, 25 );
  
                  mibReq.Type = MIB_PUBLIC_NETWORK;
                  mibReq.Param.EnablePublicNetwork = LORAWAN_PUBLIC_NETWORK;
                  LoRaMacMibSetRequestConfirm( &mibReq );
  
                  mibReq.Type = MIB_ADR;
                  mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
                  LoRaMacMibSetRequestConfirm( &mibReq );
  
  #if defined( REGION_EU868 )
                  LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );
  #endif
                  mibReq.Type = MIB_SYSTEM_MAX_RX_ERROR;
                  mibReq.Param.SystemMaxRxError = 20;
                  LoRaMacMibSetRequestConfirm( &mibReq );
  
                  LoRaMacStart( );
  
                  mibReq.Type = MIB_NETWORK_ACTIVATION;
                  status = LoRaMacMibGetRequestConfirm( &mibReq );
  
                  if( status == LORAMAC_STATUS_OK )
                  {
                      if( mibReq.Param.NetworkActivation == ACTIVATION_TYPE_NONE )
                      {
                          DeviceState = DEVICE_STATE_JOIN;
                      }
                      else
                      {
                          DeviceState = DEVICE_STATE_SEND;
                          NextTx = true;
                      }
                  }
                  break;
              }
              case DEVICE_STATE_JOIN:
              {
                  printf( "DevEui      : %02X", DevEui[0] );
                  for( int i = 1; i < 8; i++ )
                  {
                      printf( "-%02X", DevEui[i] );
                  }
                  printf( "\r\n" );
                  printf( "AppEui      : %02X", JoinEui[0] );
                  for( int i = 1; i < 8; i++ )
                  {
                      printf( "-%02X", JoinEui[i] );
                  }
                  printf( "\r\n" );
                  printf( "AppKey      : %02X", NwkKey[0] );
                  for( int i = 1; i < 16; i++ )
                  {
                      printf( " %02X", NwkKey[i] );
                  }
                  printf( "\n\r\n" );
  #if( OVER_THE_AIR_ACTIVATION == 0 )
                  printf( "###### ===== JOINED ==== ######\r\n" );
                  printf( "\r\nABP\r\n\r\n" );
                  printf( "DevAddr     : %08lX\r\n", DevAddr );
                  printf( "NwkSKey     : %02X", FNwkSIntKey[0] );
                  for( int i = 1; i < 16; i++ )
                  {
                      printf( " %02X", FNwkSIntKey[i] );
                  }
                  printf( "\r\n" );
                  printf( "AppSKey     : %02X", AppSKey[0] );
                  for( int i = 1; i < 16; i++ )
                  {
                      printf( " %02X", AppSKey[i] );
                  }
                  printf( "\n\r\n" );
  
                  // Tell the MAC layer which network server version are we connecting too.
                  mibReq.Type = MIB_ABP_LORAWAN_VERSION;
                  mibReq.Param.AbpLrWanVersion.Value = ABP_ACTIVATION_LRWAN_VERSION;
                  LoRaMacMibSetRequestConfirm( &mibReq );
  
                  mibReq.Type = MIB_NETWORK_ACTIVATION;
                  mibReq.Param.NetworkActivation = ACTIVATION_TYPE_ABP;
                  LoRaMacMibSetRequestConfirm( &mibReq );
  
                  DeviceState = DEVICE_STATE_SEND;
  #else
                  JoinNetwork( );
  #endif
                  break;
              }
              case DEVICE_STATE_SEND:
              {
                  if( NextTx == true )
                  {
                      PrepareTxFrame( AppPort );
  
                      NextTx = SendFrame( );
                  }
                  DeviceState = DEVICE_STATE_CYCLE;
                  break;
              }
              case DEVICE_STATE_CYCLE:
              {
                  DeviceState = DEVICE_STATE_SLEEP;
                  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 );
                  }
  
                  // Schedule next packet transmission
                  TimerSetValue( &TxNextPacketTimer, TxDutyCycleTime );
                  TimerStart( &TxNextPacketTimer );
                  break;
              }
              case DEVICE_STATE_SLEEP:
              {
                  if( NvmCtxMgmtStore( ) == NVMCTXMGMT_STATUS_SUCCESS )
                  {
                      printf( "\r\n###### ===== CTXS STORED ==== ######\r\n" );
                  }
  
                  CRITICAL_SECTION_BEGIN( );
                  if( IsMacProcessPending == 1 )
                  {
                      // Clear flag and prevent MCU to go into low power modes.
                      IsMacProcessPending = 0;
                  }
                  else
                  {
                      // The MCU wakes up through events
                      BoardLowPowerHandler( );
                  }
                  CRITICAL_SECTION_END( );
                  break;
              }
              default:
              {
                  DeviceState = DEVICE_STATE_START;
                  break;
              }
          }
      }
  }
  