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

Dependencies:   mbed

LoRa/hxc_client.c

Committer:
fahadmirza
Date:
2018-10-09
Revision:
21:f1d561ed31a1
Parent:
18:24c232951ff8
Child:
32:2d0678039a09

File content as of revision 21:f1d561ed31a1:

/*
  _    _            _____   _______
 | |  | |          |_   _| |__   __|
 | |__| | __ ___  __ | |  ___ | |
 |  __  |/ _` \ \/ / | | / _ \| |
 | |  | | (_| |>  < _| || (_) | |
 |_|  |_|\__,_/_/\_\_____\___/|_|
    (C)2017 HaxIoT
*/
/*******************************************************************************
  * File    : hxc_client.c
  * Author  : Fahad (Haxiot)
  * Version : V1.0.0
  * Modified: 15-July-2018
  * Brief   : HXC AT command API
  ******************************************************************************
  * @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 <stdbool.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include "hw_usart.h"
#include "utilities.h"
#include "tiny_vsnprintf.h"
#include "hxc_client.h"
#include "hw_conf.h"
#include "delay.h"
#include "time_server.h"
#include "hw_gpio.h"

/* Private typedef -----------------------------------------------------------*/
// Type definition for return code analysis
typedef  char* AtErrorStr_t;

typedef struct sAtRetCode
{
	AtErrorStr_t RetCodeStr;
	uint8_t SizeRetCodeStr;
	eAtStatus_t RetCode;
}sAtRetCode_t;


/* Private functions ---------------------------------------------------------*/
static uint16_t at_cmd_format(ATGroup_t at_group, ATCmd_t Cmd, const void *ptr);
static uint16_t at_set_cmd_format(ATCmd_t Cmd, const void *ptr);
static eAtStatus_t at_cmd_send(uint16_t len);
static eAtStatus_t at_cmd_receive(void *pdata);
static eAtStatus_t at_cmd_responseAnalysing(const char *ReturnResp);
static uint16_t at_cmd_vprintf(const char *format, ...);


/* Private macro -------------------------------------------------------------*/
#define AT_VPRINTF(...)         at_cmd_vprintf(__VA_ARGS__)
#define AT_VSSCANF(...)         tiny_sscanf(__VA_ARGS__)

#define BAUD_RATE               9600
#define ARRAY_SIZE(a)           (sizeof(a) / sizeof(a[0]))

#define DATA_RX_MAX_BUFF_SIZE   64       /* Max size of the received buffer.
                                          * To optimize we can match with device key sizeof */

#define DATA_TX_MAX_BUFF_SIZE   78       // Max size of the transmit buffer
#define HXC_TIMEOUT				2000     // 2 seconds

/*
 * RetCode used to compare the return code from HXC modem.
 * In direct relation with sAtRetCode_t
 */
#define RET_OK                 "OK"
#define RET_ERROR              "AT_ERROR"
#define RET_PARAM_ERROR        "AT_PARAM_ERROR"
#define RET_BUSY_ERROR         "AT_BUSY_ERROR"
#define RET_PARAM_OVERFLOW     "AT_PARAM_OVERFLOW"
#define RET_INVALID_MODE       "AT_INVALID_MODE"
#define RET_NO_NETWORK_JOINED  "AT_NO_NETWORK_JOINED"
#define RET_PAYLOAD_SIZE_ERROR "AT_PAYLOAD_SIZE_ERROR"


/* Private Variables --------------------------------------------------------*/
static const sAtRetCode_t AT_RetCode[] = {
{RET_OK,                 sizeof(RET_OK),                 AT_OK},
{RET_ERROR,              sizeof(RET_ERROR),              AT_ERROR},
{RET_PARAM_ERROR,        sizeof(RET_PARAM_ERROR),        AT_PARAM_ERROR},
{RET_NO_NETWORK_JOINED,  sizeof(RET_NO_NETWORK_JOINED),  AT_NO_NET_JOINED},
{RET_BUSY_ERROR,         sizeof(RET_BUSY_ERROR),         AT_BUSY_ERROR},
{RET_PARAM_OVERFLOW,     sizeof(RET_PARAM_OVERFLOW),     AT_PARAM_OVERFLOW},
{RET_INVALID_MODE,       sizeof(RET_INVALID_MODE),       AT_INVALID_MODE},
{RET_PAYLOAD_SIZE_ERROR, sizeof(RET_PAYLOAD_SIZE_ERROR), AT_PAYLOAD_SIZE_ERROR}};


/*
 * List of AT cmd supported by the HXC Client Module:
 * HXC900 and HXC400
 */
static const char *CmdTab[] =
{
	"",            // AT
	"+RESET",      // Reset modem
	"+FD",         // Factory default
	"+DEVEUI",     // Device identifier
	"+DEVADR",     // Device address
	"+APPKEY",     // Application key
	"+NWKSKEY",    // Network session key
	"+APPSKEY",    // Application session key
	"+APPEUI",     // Application identifier
	"+ADR",        // Adaptive data rate
	"+TXP",        // Transmit power
	"+DR",         // Data rate
	"+DCS",        // DCS duty cycle settings
	"+PNM",        // Public/Private network
	"+RX2WND",     // Rx2 window frequency and datarate
	"+RX1DL",      // Delay of the Rx1 window
	"+RX2DL",      // Delay of the Rx2 window
	"+JN1DL",      // Join delay on Rx1 window
	"+JN2DL",      // Join delay on Rx2 window
	"+NJM",        // Network join mode
	"+NWKID",      // Network ID
	"+FCU",        // Uplink frame counter
	"+FCD",        // Downlink frame counter
	"+CLASS",      // LoRa class
	"+CH",         // Channel configuration
	"+JOIN",       // Join network server
	"+NJS",        // Network join mode
	"+SENDB",      // Send binary formatted data
	"+SEND",       // Send data in ASCII format
	"+RECVB",      // Get the last received data
	"+CFS",        // Confirm status
	"+SNR",        // Signal to noise ratio
	"+RSSI",       // Signal strength indicator on received radio signal
	"+MODE",       // Modem mode
	"+RFCFG",      // LoRa only configuration
	"+TXCW",       // Continuous Tx
	"+TX",         // Send LoRa only data in raw format
	"+RX",         // Continuous Rx
	"+BAT",        /// Battery level
	"+VER",        // Firmware version of the HXC Client
};


char LoRa_AT_Cmd_Buff[DATA_TX_MAX_BUFF_SIZE];// Buffer used for AT cmd transmission

static uint16_t Offset = 0;                  // Write position needed for LoRa_AT_Cmd_Buff[] during AT_SET

/* Has to be the largest of the response.
 * Not only for return code but also for
 * return value e.g. APPKEY.
 */
static char response[DATA_RX_MAX_BUFF_SIZE];


/* Exported functions ------------------------------------------------------- */

/*******************************************************************************
 * @Brief  : Configures HXC UART interface, Reset Pin
 * @Param  : None
 * @Return : AT_OK in case of success
 *           AT_UART_LINK_ERROR in case of UART init failure
*******************************************************************************/
eAtStatus_t Modem_Init( void )
{
	if (HW_UART_Modem_Init(BAUD_RATE) == false)
    {
	   return AT_UART_LINK_ERROR;
    }

	// Reset pin initialization
	HXC_RESET_GPIO_CLK_ENABLE();
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Mode  = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull  = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HW_GPIO_Init(HXC_RESET_PORT, HXC_RESET_PIN, &GPIO_InitStruct);

	return AT_OK;
}


/*******************************************************************************
 * @Brief  : Deinitialize modem UART interface.
 * @Param  : None
 * @Return : None
*******************************************************************************/
void Modem_IO_DeInit( void )
{
	HW_UART_Modem_DeInit();
}


/*******************************************************************************
 * @Brief  : Handle the AT cmd following their Group type
 * @Param  : at_group AT group (control, set , get)
 *           Cmd AT command
 *           pdata pointer to the IN/OUT buffer
 * @Return : Module status
 ******************************************************************************/
eAtStatus_t  Modem_AT_Cmd(ATGroup_t at_group, ATCmd_t atCmd, void *pdata )
{
	eAtStatus_t atStatus = AT_END_ERROR;
	uint16_t atCmdLen;

	// Reset At_cmd buffer for each transmission
	memset1((uint8_t *)LoRa_AT_Cmd_Buff, 0x00, sizeof(LoRa_AT_Cmd_Buff));
	
	/* Reset the UART circular buffer for each transmission to make sure
	 * the responses we will get are for the current AT cmd. */
	HW_UART_ResetBuffer();

	// Format AT cmd
	atCmdLen = at_cmd_format(at_group, atCmd, pdata);
	if(atCmdLen == 0)
	{
		/*
		 * You are trying to use a command behavior that HXC Client doesn't
		 * support. Check manual for possible AT command behaviors.
		 */
		return AT_CMD_ERROR;
	}

	// Send AT cmd string
	if(at_cmd_send(atCmdLen) != AT_OK)
	{
        // Problem on UART transmission
	    return AT_UART_LINK_ERROR;
	}

	// Read response from HXC client for the AT cmd
	if (at_group == AT_GET)
	{
		// Get the value
		atStatus = at_cmd_receive(pdata);
	}
	else
	{
		// Check for the return status
		atStatus = at_cmd_receive(NULL);
	}

    return atStatus;
}


/*******************************************************************************
 * @Brief : Format the cmd in order to be send
 * @Param : at_group - the behavior of AT cmd
 *          Cmd - AT command
 *          ptr - generic pointer to the IN/OUT buffer
 * @Return: Length of the formated frame to be send
 ******************************************************************************/
static uint16_t at_cmd_format(ATGroup_t at_group, ATCmd_t Cmd, const void *ptr)
{
	uint16_t len = 0;  /*length of the formated command*/

	switch (at_group)
	{
		case AT_CTRL:
		{
			len = AT_VPRINTF("AT%s\r\n", CmdTab[Cmd]);
			break;
		}
		case AT_GET:
		{
			len = AT_VPRINTF("AT%s=?\r\n", CmdTab[Cmd]);
			break;
		}
		case AT_SET:
		{
			len = at_set_cmd_format(Cmd, ptr);
			break;
		}
		default:
		{
			break;
		}
	}

   return len;
}


/*******************************************************************************
 * @Brief : Format the at set cmd
 * @Param : Cmd - AT command
 *          ptr - generic pointer to the IN/OUT buffer
 * @Return: Length of the formated frame to be send
 ******************************************************************************/
static uint16_t at_set_cmd_format(ATCmd_t Cmd, const void *ptr)
{
	uint32_t value;    /*for 32_02X and 32_D*/


	Offset = AT_VPRINTF("AT%s=", CmdTab[Cmd]);
	switch (Cmd)
	{
		case  AT_SEND:
		{
			sSendDataString_t *SendData = (sSendDataString_t *)ptr;
			Offset += AT_VPRINTF("%d,%d:%s", SendData->Ack, SendData->Port, SendData->Buffer);
			break;
		}
		case  AT_SENDB:
		{
			sSendDataBinary_t *SendData = (sSendDataBinary_t *)ptr;

			Offset += AT_VPRINTF("%d,%d:", SendData->Ack, SendData->Port);
			unsigned i;
			for (i = 0; i < SendData->DataSize; i++)
			{
				Offset+=AT_VPRINTF("%02x", SendData->Buffer[i]);
			}

			break;
		}
		case AT_APPKEY:
		case AT_NWKSKEY:
		case AT_APPSKEY:
		case AT_DEVADR:
		case AT_APPEUI:
		case AT_DEVEUI:
		{
			char *key = (char*) ptr;
			Offset += AT_VPRINTF("%s", key);
			
			break;
		}
		case  AT_RX1DL:
		case  AT_RX2DL:
		case  AT_JN1DL:
		case  AT_JN2DL:
		case  AT_FCU:
		case  AT_FCD:
		{
			value = *(uint32_t*)ptr;
			Offset += AT_VPRINTF("%u", value);
			break;
		}
		case  AT_DR:
		case  AT_TXP:
		case  AT_PNM:
		case  AT_DCS:
		case  AT_ADR:
		case  AT_BAT:
		{
			uint8_t value_8 =  *(uint8_t*)ptr;
			Offset += AT_VPRINTF("%d", value_8);
			break;
		}
		case  AT_CLASS:
		{
			char value_c =  *(char*)ptr;
			Offset += AT_VPRINTF("%c", value_c);
			break;
		}
		case  AT_NJM:
		{
			Offset += AT_VPRINTF("%s", (char*)ptr);
			break;
		}
		default:
		{
			//DBG_PRINTF ("Not supported\r\n");
			break;
		}
	}

	Offset += AT_VPRINTF("\r\n");
	uint16_t len = Offset;
	Offset = 0;

	return len;
}


/*******************************************************************************
  * @Brief : This function sends an AT cmd to the slave device
  * @Param : len - length of the AT cmd to be sent
  * @Return: eAtStatus_t return code
*******************************************************************************/
static eAtStatus_t at_cmd_send(uint16_t len)
{
  /*transmit the command from master to slave*/
  if( HW_UART_Modem_SendBytes(LoRa_AT_Cmd_Buff, len) == false)
  {
	  return AT_UART_LINK_ERROR;
  }

  return AT_OK;
}


/*******************************************************************************
  * @Brief : This function receives response from the slave device
  * @Param : pdata - pointer to the value returned by the slave
  * @Return: Return code coming from HXC slave
*******************************************************************************/
static eAtStatus_t at_cmd_receive(void *pdata)
{
	bool ResponseComplete = false;
	uint8_t i = 0;
	eAtStatus_t RetCode = AT_END_ERROR;
  
	// Cleanup the response buffer
	memset1((uint8_t *)response, 0x00, sizeof(response));

	uint32_t currentTime = TimerGetCurrentTime();
    while(ResponseComplete != true)
    {
    	if(HW_UART_Modem_IsNewCharReceived() == false)
        {
    		if(TimerGetElapsedTime(currentTime) > HXC_TIMEOUT)
    		{
    			ResponseComplete = true;
    			RetCode = AT_TIMEOUT;
    		}
        }
    	else
    	{
    		// Process the response
            response[i++] = HW_UART_Modem_GetNewChar();
   
            // Wait up to line feed marker
            if (response[i - 1] == '\n')
            {
            	// Last two bytes are <CR><LF>, set CR as NULL byte.
    	        response[i - 2] = '\0';
    	        // Reset index
    	        i = 0;

    	        // Compare response with AT statuses
		        RetCode = at_cmd_responseAnalysing(response);
		        if(RetCode != AT_END_ERROR)
		        {
			        // We received a status. Response complete.
			        ResponseComplete = true;
		        }
                else if(pdata != NULL)
                {
        	        // If pdata isn't null that means we are using GET cmd to get
        	        // return value. Copy the return value into pdata.

			        strcpy(pdata, response);
			        memset1((uint8_t *)response, 0x00, 16);
			        // Now, let's get the status
		        }
            }
            else
            {
                if (i ==  (DATA_RX_MAX_BUFF_SIZE-1))
                {
        	        // Frame overflow. Reset index and stop reading.
                	i = 0;
                	RetCode = AT_PARAM_OVERFLOW;
                	ResponseComplete = true;
                }
            }
    	}
    } // End while(ResponseComplete != true)

    HW_UART_Modem_Ready();          
    return RetCode;
}


/*******************************************************************************
  * @Brief : Analyze the response received by the device
  * @Param : response: pointer to the received response
  * @Return: eAtStatus_t error type
*******************************************************************************/
static eAtStatus_t at_cmd_responseAnalysing(const char *ReturnResp)
{
	int i;

    for (i = 0; i < ARRAY_SIZE(AT_RetCode); i++)
    {   
		if (strncmp(ReturnResp, AT_RetCode[i].RetCodeStr, (AT_RetCode[i].SizeRetCodeStr-1)) == 0)
		{
			/* Command has been found */
			return AT_RetCode[i].RetCode;
		}
    }
    return AT_END_ERROR;
}


/*******************************************************************************
  * @Brief : Format the AT frame to be sent to the modem (slave)
  * @Param : Pointer to the format string
  * @Return: Length of the string to be sent
*******************************************************************************/
uint16_t at_cmd_vprintf(const char *format, ...)
{
	va_list args;
    uint16_t len;
   
    va_start(args, format);
  
    len = tiny_vsnprintf_like(LoRa_AT_Cmd_Buff+Offset, sizeof(LoRa_AT_Cmd_Buff)-Offset, format, args);
  
    va_end(args);
  
    return len;
}

/*******************************************************************************
 * @Brief  : Reset the HXC client modem using HXC RESET pin
 * @Param  : None
 * @Return : Module status
 ******************************************************************************/
eAtStatus_t Modem_HardReset(void)
{
	HW_GPIO_Write(HXC_RESET_PORT, HXC_RESET_PIN, GPIO_PIN_RESET);
	DelayMs(200);
	HW_GPIO_Write(HXC_RESET_PORT, HXC_RESET_PIN, GPIO_PIN_SET);

	// Check for the return status - OK
	return at_cmd_receive(NULL);
}

/*******************************************************************************
 * @Brief : Check if any downlink packet is received
 * @Param : None
 * @Return: TRUE or FALSE
 ******************************************************************************/
bool Modem_IsNewDataReceived(void)
{
	if(HW_UART_Modem_IsNewCharReceived() == false)
	{
		return false;
	}
	// Cleanup the response buffer
	memset1((uint8_t *)response, 0x00, sizeof(response));
	HW_UART_Modem_GetCharactersUntilNewLine(response, sizeof(response), HXC_TIMEOUT);
	if(strncmp("rxdata", response, 6) == 0)
	{
		return true;
	}
	return false;
}
/************************ (C) COPYRIGHT Haxiot *****END OF FILE*****/