A demo application for HXC900 LoRaWAN module using Nucleo-L053R8.

Dependencies:   mbed

LoRa/lora_driver.c

Committer:
fahadmirza
Date:
2018-11-01
Revision:
26:176e648c03f6
Parent:
25:50414f44a431
Child:
27:517ca3a30ad7

File content as of revision 26:176e648c03f6:

/*
  _    _            _____   _______
 | |  | |          |_   _| |__   __|
 | |__| | __ ___  __ | |  ___ | |
 |  __  |/ _` \ \/ / | | / _ \| |
 | |  | | (_| |>  < _| || (_) | |
 |_|  |_|\__,_/_/\_\_____\___/|_|
    (C)2017 HaxIoT
*/
/*******************************************************************************
  * @File    : lora_driver.c
  * @Author  : Fahad Mirza (Haxiot)
  * @Version : V1.0.0
  * @Modified: 18 October, 2018
  * @Brief   : LoRa Driver
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; COPYRIGHT(c) 2017 Haxiot</center></h2>
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of Haxiot nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include <stdio.h>
#include "hxc_client.h"
#include "debug.h"
#include "stm32l0xx_nucleo.h"
#include "time_server.h"
#include "lora_driver.h"
#include "tiny_sscanf.h"
#include "utilities.h"

/* Private Macros ------------------------------------------------------------*/
#define JOIN_SEND_DELAY_MAX   (10000U) // Randomization range - 10s
#define JOIN_STATUS_REQ_DELAY (7000U)  // milliseconds. Join req takes 6s.

// Re Authenticate every REJOIN_TIME
#define REJOIN_TIME           (1 * 60 * 60 * 1000U) // 1 hour

/* Private global variables --------------------------------------------------*/
static sLoraConfig_t *LoraConfigParam;
static sLoraDriverParam_t *LoraDriverParam;
static volatile eDeviceState_t DeviceState = CLIENT_INIT;

static TimerEvent_t JoinRequestTimer;
static TimerEvent_t RejoinTimer;
static TimerEvent_t SensorMeasureTimer;
static TimerEvent_t NucleoLedTimer;
static TimerEvent_t JoinStatusDelayTimer;

// Object definition for data to be sent to loRa application server
static uint8_t DataBinaryBuff[64];
static sSendDataBinary_t SendDataBinary = {DataBinaryBuff, 0 , 0, 0};

// RNG handler declaration
RNG_HandleTypeDef RngHandle = {.Instance = RNG};

/* Private functions ---------------------------------------------------------*/
static void OnRejoinTimerEvent(void);
static void OnLedTimerEvent(void);
static void OnJoinRequestTimerEvt(void);
static void OnJoinStatusDelayTimerEvt(void);
static void OnSensorMeasureTimerEvt(void);
static void setJoinRequestTimer(void);


/* Function definitions ------------------------------------------------------*/
/******************************************************************************
  * @Brief  : Initialize LoRa Modem
  * @Param  : sLoraConfig_t
  * @Return : None
******************************************************************************/
void Lora_init(sLoraConfig_t *loraConfig, sLoraDriverParam_t *loraDriverParam)
{

	LoraConfigParam = loraConfig;
	LoraDriverParam = loraDriverParam;

	if(Modem_Init() != AT_OK)
	{
		DBG_PRINTF("Modem_Init failed\r\n");
	}

	// Initialize RNG for join request send randomization
	HAL_RNG_Init(&RngHandle);


	// Timer for join request send
	TimerInit(&JoinRequestTimer, OnJoinRequestTimerEvt);
	// Timer for join status check
	TimerInit(&JoinStatusDelayTimer, OnJoinStatusDelayTimerEvt);
	// Timer for sensor occurrence measure
	TimerInit(&SensorMeasureTimer, OnSensorMeasureTimerEvt);
	// Timer for Nucleo LED
	TimerInit(&NucleoLedTimer, OnLedTimerEvent);
	// Timer for Re Authenticate / Join
	TimerInit(&RejoinTimer, OnRejoinTimerEvent);
}

/******************************************************************************
  * @Brief  : Check if the modem responds
  * @Param  : void
  * @Return : AT_OK or other eAtStatus_t
******************************************************************************/
static eAtStatus_t is_modem_working(void)
{
	return Modem_AT_Cmd(AT_CTRL, AT, NULL);
}

/******************************************************************************
  * @Brief  : Set Device EUI
  * @Param  : Pointer to Device EUI
  * @Return : AT_OK or other eAtStatus_t
******************************************************************************/
static eAtStatus_t Lora_setDevEui(char *devEui)
{
	return Modem_AT_Cmd(AT_SET, AT_DEVEUI, devEui);
}

/******************************************************************************
  * @Brief  : Turn on or off ADR
  * @Param  : ADR_ON or ADR_OFF
  * @Return : AT_OK or other eAtStatus_t
******************************************************************************/
static eAtStatus_t Lora_setAdr(eAdrStatus_t adrStatus)
{
	//char adr = (adrStatus == ADR_ON ? '1' : '0');
	return Modem_AT_Cmd(AT_SET, AT_ADR, (uint8_t *)(&adrStatus));
}

/******************************************************************************
  * @Brief  : Set Application EUI
  * @Param  : Pointer to Application EUI
  * @Return : AT_OK or other eAtStatus_t
******************************************************************************/
static eAtStatus_t Lora_setAppEui(char *appEui)
{
	return Modem_AT_Cmd(AT_SET, AT_APPEUI, appEui);
}

/******************************************************************************
  * @Brief  : Set Application Key
  * @Param  : Pointer to Application Key
  * @Return : AT_OK or other eAtStatus_t
******************************************************************************/
static eAtStatus_t Lora_setAppKey(char *appKey)
{
	return Modem_AT_Cmd(AT_SET, AT_APPKEY, appKey);
}

/******************************************************************************
  * @Brief  : Set join mode
  * @Param  : OTAA or ABP
  * @Retval : AT_OK if successful, otherwise other eAtStatus_t
******************************************************************************/
static eAtStatus_t Lora_setJoinMode(eJoinMode_t joinMode)
{
	if(joinMode == OTAA)
	{
		return Modem_AT_Cmd(AT_SET, AT_NJM, "OTAA");
	}

	return Modem_AT_Cmd(AT_SET, AT_NJM, "ABP");
}

/******************************************************************************
  * @Brief  : Set Class
  * @Param  : CLASS_A or CLASS_C
  * @Retval : AT_OK if successful, otherwise other eAtStatus_t
******************************************************************************/
static eAtStatus_t Lora_setClass(char class)
{
	return Modem_AT_Cmd(AT_SET, AT_CLASS, &class);
}

/******************************************************************************
  * @Brief  : Join network and initiate join sleep transition timer
  * @Param  : None
  * @Retval : AT_OK or other eAtStatus_t
******************************************************************************/
static eAtStatus_t Lora_Join(void)
{
	return Modem_AT_Cmd(AT_CTRL, AT_JOIN, NULL);
}

/******************************************************************************
  * @Brief  : Check JOIN status
  * @Param  : None
  * @Retval : JOINED or NOT_JOINED
******************************************************************************/
static eJoinStatus_t Lora_getJoinStatus(void)
{
	char joinStatus[12]; // "NOT JOINED" is 10 characters + 1 Null char

	Modem_AT_Cmd(AT_GET, AT_NJS, joinStatus);

    if(strncmp("JOINED", joinStatus, 6) == 0)
    {
    	return JOINED;
    }

	return NOT_JOINED;
}

/******************************************************************************
  * @Brief  : Send uplink packet using binary payload
  * @Param  : Pointer of sSendDataBinary_t variable
  * @Retval : AT_OK or other eAtStatus_t statuses
******************************************************************************/
static eAtStatus_t Lora_SendDataBinary(sSendDataBinary_t *binaryData)
{
	return Modem_AT_Cmd(AT_SET, AT_SENDB, binaryData);
}

/******************************************************************************
 * @Brief  : Read the received downlink packet
 * @Param  : Pointer to sRecvDataBinary_t variable
 * @Return : None
******************************************************************************/
static void Lora_ReadData(sRecvDataBinary_t *receivedData)
{
	char receiveString[64];

	Modem_AT_Cmd(AT_GET, AT_RECVB, receiveString);

	tiny_sscanf(receiveString, "%hhu,%hhu", &(receivedData->Ack), &(receivedData->Port));

	// Find the position after the second comma
	char *hexString = strchr((strchr(receiveString,',') + 1),',') + 1;

	receivedData->DataSize = stringHexToByteArray(hexString, receivedData->Buffer, 64);
}

/******************************************************************************
  * @Brief  : Change Lora_fsm() DeviceState
  * @Param  : Any of eDeviceState_t type
  * @Return : None
******************************************************************************/
void Lora_ChangeDeviceState(eDeviceState_t newDeviceState)
{
	//ToDo: Check for valid eDeviceState_t states
	DeviceState = newDeviceState;
}

/******************************************************************************
  * @Brief  : LoRa Modem state machine
  * @Param  : Void
  * @Return : None
******************************************************************************/
void Lora_fsm(void)
{
    switch(DeviceState)
    {
        case CLIENT_INIT:
        {
    	    if(is_modem_working() != AT_OK)
    	    {
    	    	DBG_PRINTF("AT failed. Resetting HW...\r\n");
    	        // Modem isn't responding. Execute hard reset.
                Modem_HardReset();
                // We stay in DEVICE_INIT state and try again.
    	    }
    	    else
    	    {
    	    	DeviceState = CLIENT_CONFIG;
    	    }

            break;
        }
        case CLIENT_CONFIG:
        {
        	eAtStatus_t loraModemRetCode = Lora_setDevEui(LoraConfigParam->DevEui);
			loraModemRetCode |= Lora_setAppEui(LoraConfigParam->AppEui);
			loraModemRetCode |= Lora_setAppKey(LoraConfigParam->AppKey);
			loraModemRetCode |= Lora_setJoinMode(LoraConfigParam->JoinMode);
			loraModemRetCode |= Lora_setClass(LoraConfigParam->Class);
			loraModemRetCode |= Lora_setAdr(LoraConfigParam->AdrStatus);
			// ToDo: Add FW version checking

			if(loraModemRetCode == AT_OK)
			{
				setJoinRequestTimer();
				DeviceState = CLIENT_SLEEP;
			}
			else if(loraModemRetCode == AT_TIMEOUT)
			{
				DeviceState = CLIENT_INIT;
			}
			else
			{
				DBG_PRINTF("Check your keys\r\n");
				// We stay in DEVICE_CONFIG and try again
			}

			break;
        }
        case CLIENT_JOIN:
        {
            DBG_PRINTF("Joining...\r\n");
            // Indicate a Join request using Nucleo LED
            BSP_LED_On(LED_GREEN);
            TimerStart(&NucleoLedTimer, 200);

            switch(Lora_Join())
            {
                case AT_OK:
                {
                	// Start the Join status request timer and go to sleep
                	TimerStart(&JoinStatusDelayTimer, JOIN_STATUS_REQ_DELAY);
                    DeviceState = CLIENT_SLEEP;
                    break;
                }
                case AT_TIMEOUT:
                {
                	// The modem isn't responding. Execute hard reset
                	DeviceState = CLIENT_INIT;
                	break;
                }
                default:
                {
                	DBG_PRINTF("Join cmd failed\n");
                    // We stay in DEVICE_JOIN state and redo Lora_Join()
                	break;
                }
            }

            break;
        }
        case CLIENT_JOIN_STATUS_CHECK:
        {
        	if(Lora_getJoinStatus() == JOINED)
        	{
        		// Inidicate Join status using LED
				GPIO_InitTypeDef GPIO_InitStruct;
    			
    			GPIO_InitStruct.Pin = GPIO_PIN_7;
    			GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    			GPIO_InitStruct.Pull = GPIO_NOPULL;
    			HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    			
    			GPIO_InitStruct.Pin = GPIO_PIN_6;
    			GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    			GPIO_InitStruct.Pull = GPIO_NOPULL;
    			HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    			
    			HAL_GPIO_WritePin(GPIOA, GPIO_PIN_7, GPIO_PIN_SET);
    			HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
    			 
        		DBG_PRINTF("Nwk Joined\n");
        		
        		// Start timer for Re-Authentication
        		TimerStart(&RejoinTimer, REJOIN_TIME);
        		
				// Start timer for uplink transmission for sensor-data
				TimerStart(&SensorMeasureTimer, LoraDriverParam->SensorMeasureTime);
				DeviceState = CLIENT_SLEEP;
        	}
        	else
        	{
        		// Try joining again
        		setJoinRequestTimer();
                DeviceState = CLIENT_SLEEP;
        	}
        	break;
        }
		case CLIENT_SLEEP:
        {
            /* Wake up through RTC events or asynchronous event coming from HXC modem*/
        	if(Modem_IsNewDataReceived() == true)
        	{
        		DeviceState = CLIENT_DATA_RECEIVED;
        	}
            break;
        }
		case CLIENT_SEND:
		{
			// Read sensor data and populate payload
			LoraDriverParam->SendDataHandler(SendDataBinary.Buffer, &SendDataBinary.DataSize, &SendDataBinary.Ack, &SendDataBinary.Port);

			// Initiate uplink transmission
			eAtStatus_t status = Lora_SendDataBinary(&SendDataBinary);
			if (status == AT_OK)
			{
				DBG_PRINTF("Uplink sent\n");
				// Schedule the next packet
				TimerStart(&SensorMeasureTimer, LoraDriverParam->SensorMeasureTime);
				DeviceState = CLIENT_SLEEP;
			}
			else if(status == AT_TIMEOUT)
			{
				// Device isn't responding. Go to init.
				DeviceState = CLIENT_INIT;
			}
			else
			{
				DBG_PRINTF("Uplink Failed (Error: %d)\n", (uint8_t)status);
			}

		    break;
		}
		case CLIENT_DATA_RECEIVED:
		{
			uint8_t rBuffer[64];
			sRecvDataBinary_t rxPacket = {.Buffer = rBuffer};

			Lora_ReadData(&rxPacket);

			// Execute users ReceivedPacketHandler function
			LoraDriverParam->ReceiveDataHandler(rxPacket.Buffer, rxPacket.DataSize, rxPacket.Ack, rxPacket.Port);

			DeviceState = CLIENT_SLEEP;
			break;
		}
		default:
		{
			DeviceState = CLIENT_INIT;
		    break;
		}
    }
}

/******************************************************************************
 * @Brief  : Set Join request timer
 * @Param  : none
 * @Return : none
******************************************************************************/
static void setJoinRequestTimer(void)
{
	// Use a random delay to avoid synchronized join request from all LoRa node after power up
	uint32_t joinDelay = (HAL_RNG_GetRandomNumber(&RngHandle) % JOIN_SEND_DELAY_MAX) + 1;
	
	TimerStart(&JoinRequestTimer, joinDelay);
}

/******************************************************************************
 * @Brief  : Function executed on JoinStatusDelayTimer Timeout event
 * @Param  : none
 * @Return : none
******************************************************************************/
static void OnJoinRequestTimerEvt(void)
{
	TimerStop(&JoinRequestTimer);
	DeviceState = CLIENT_JOIN;
}

/******************************************************************************
 * @Brief  : Function executed on NucleoLedTimer Timeout event
 * @Param  : none
 * @Return : none
******************************************************************************/
static void OnLedTimerEvent(void)
{
	TimerStop(&NucleoLedTimer);
	BSP_LED_Off(LED_GREEN);
}

/******************************************************************************
 * @Brief  : Function executed on JoinStatusDelayTimer Timeout event
 * @Param  : none
 * @Return : none
******************************************************************************/
static void OnJoinStatusDelayTimerEvt(void)
{
	TimerStop(&JoinStatusDelayTimer);
	DeviceState = CLIENT_JOIN_STATUS_CHECK;
}

/******************************************************************************
 * @Brief  : Function executed on SensorMeasureTimer Timeout event
 * @Param  : none
 * @Return : none
******************************************************************************/
static void OnSensorMeasureTimerEvt(void)
{
	TimerStop(&SensorMeasureTimer);
    DeviceState = CLIENT_SEND;
}

/******************************************************************************
 * @Brief  : Function executed on RejoinTimer Timeout event
 * @Param  : none
 * @Return : none
******************************************************************************/
static void OnRejoinTimerEvent(void)
{
	setJoinRequestTimer();
}

/******************************************************************************
  * @brief RNG MSP Initialization
  *        This function configures the hardware resources used in this example:
  *           - Peripheral's clock enable
  * @param hrng: RNG handle pointer
  * @retval None
******************************************************************************/
void HAL_RNG_MspInit(RNG_HandleTypeDef *hrng)
{
	/* RNG Peripheral clock enable */
    __HAL_RCC_RNG_CLK_ENABLE();
}