crb_lte/Core/Src/eg91.c

1376 lines
37 KiB
C

/**
******************************************************************************
* @file EG91.c
* @author MCD Application Team
* @version V0.0.1
* @date 11-08-2017
* @brief Functions to manage the EG91 module (C2C cellular modem).
******************************************************************************
* @attention
*
* <h2><center>&copy; Copyright (c) 2017 STMicroelectronics International N.V.
* All rights reserved.</center></h2>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted, provided that the following conditions are met:
*
* 1. Redistribution 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 STMicroelectronics nor the names of other
* contributors to this software may be used to endorse or promote products
* derived from this software without specific written permission.
* 4. This software, including modifications and/or derivative works of this
* software, must execute solely and exclusively on microcontroller or
* microprocessor devices manufactured by or for STMicroelectronics.
* 5. Redistribution and use of this software other than as permitted under
* this license is void and will automatically terminate your rights under
* this license.
*
* THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
* RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
* SHALL STMICROELECTRONICS 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 "eg91.h"
#include "main.h"
#define CHARISHEXNUM(x) (((x) >= '0' && (x) <= '9') || \
((x) >= 'a' && (x) <= 'f') || \
((x) >= 'A' && (x) <= 'F'))
#define CHARISNUM(x) ((x) >= '0' && (x) <= '9')
#define CHAR2NUM(x) ((x) - '0')
#define CTRL_Z 26
/* Private variable ---------------------------------------------------------*/
char CmdString[EG91_CMD_SIZE];
/* Exported variable ---------------------------------------------------------*/
/* This should be reworked, _IO should not depend on component */
const EG91_RetKeywords_t ReturnKeywords[] =
{
/* send receive related keywords */
{ RET_SENT, "OK\r\n" },
{ RET_ARROW, ">" },
{ RET_READ, "CIPRXGET" },
{ RET_SEND, "CIPSEND" },
{ RET_POWERED_DOWN, "POWERED DOWN\r\n" },
{ RET_UART_READY, "RDY\r\n"},
{ RET_URC_CLOSED, "closed\"" },
{ RET_URC_RECV, "CIPRXGET:SUCCESS" },
{ RET_URC_IN_FULL, "incoming full\"" },
{ RET_URC_INCOM, "incoming\"" },
{ RET_URC_PDPDEACT, "pdpdeact\"" },
{ RET_URC_DNS, "+MDNSGIP:" },
/* errors keywords */
{ RET_ERROR, "ERROR\r\n" },
{ RET_CME_ERROR, "+CME ERROR: " },
//{ RET_CMS_ERROR, "CMS ERROR:" },
{ RET_BUF_FULL, "ERROR\r\n" },
/* set-up keywords */
{ RET_OK, "OK\r\n" },
{ RET_OPEN, "OPEN:" },
{ RET_SIM_READY, "ready\r\n" },
{ RET_RESP, "\r\n\r\nOK" },
{ RET_NETCLOSE, "\r\n\r\n" },
{ RET_PING, "+QPING: " },
{ RET_RDY, "RDY" },
{ RET_CRLF, "\r\n" }, /* keep RET_CRLF last !!! */
};
/* Private functions ---------------------------------------------------------*/
/**
* @brief Parses and returns number from string.
* @param ptr: pointer to string
* @param cnt: pointer to the number of parsed digit
* @retval integer value.
*/
static int32_t ParseNumber(char *ptr, uint8_t *cnt)
{
uint8_t minus = 0, i = 0;
int32_t sum = 0;
if (*ptr == '-')
{ /* Check for minus character */
minus = 1;
ptr++;
i++;
}
while (CHARISNUM(*ptr))
{ /* Parse number */
sum = 10 * sum + CHAR2NUM(*ptr);
ptr++;
i++;
}
if (cnt != NULL)
{ /* Save number of characters used for number */
*cnt = i;
}
if (minus)
{ /* Minus detected */
return 0 - sum;
}
return sum; /* Return number */
}
/**
* @brief Parses and returns QIRQ query response.
* @param ptr: pointer to string
* @param arr: pointer to IP array
* @retval None.
*/
static void ParseQIRD(char *ptr, uint16_t *arr)
{
uint8_t hexnum = 0, hexcnt;
while (*ptr)
{
hexcnt = 1;
if (*ptr != ',')
{
arr[hexnum++] = (uint16_t) ParseNumber(ptr, &hexcnt);
}
ptr = ptr + hexcnt;
if ((*ptr == '\r') || (hexnum == 3))
{
return;
}
}
}
/**
* @brief Parses and returns .
* @param ptr: pointer to string
* @retval The number of unread data in modem TX buffer.
*/
//static void ParseQISEND(char *ptr, uint32_t *unackedbytes)
//{
// uint8_t hexnum = 0, hexcnt;
// uint32_t temp = 0;
//
// /* QISEND Response contains 3 kind of information
// * <total_send_length>,<ackedbytes>,<unackedbytes>
// * we're only interested to the 3rd one, unackedbytes for now */
// while (*ptr)
// {
// hexcnt = 1;
// if (*ptr != ',')
// {
// temp = (uint32_t) ParseNumber(ptr, &hexcnt);
// /* Count up the number we've retrieved */
// hexnum++;
// if (hexnum == 3)
// {
// *unackedbytes = temp;
// }
// }
// ptr = ptr + hexcnt;
// if ((*ptr == '\r') || (hexnum == 3))
// {
// return;
// }
// }
//}
/**
* @brief Parses and returns IP address.
* @param ptr: pointer to string
* @param arr: pointer to IP array
* @retval None.
*/
static void ParseIP(char *ptr, uint8_t *arr)
{
uint8_t hexnum = 0, hexcnt;
while (*ptr)
{
hexcnt = 1;
if (*ptr != '.')
{
arr[hexnum++] = ParseNumber(ptr, &hexcnt);
}
ptr = ptr + hexcnt;
if (*ptr == '"')
{
return;
}
}
}
/**
* @brief Return the integer difference between 'init + timeout' and 'now'.
* The implementation is robust to uint32_t overflows.
* @param In: init Reference index.
* @param In: now Current index.
* @param In: timeout Target index.
* @retval Number of ms from now to target (init + timeout).
*/
static int32_t TimeLeftFromExpiration(uint32_t init, uint32_t now,
uint32_t timeout)
{
int32_t ret = 0;
uint32_t wrap_end = 0;
if (now < init)
{ /* Timer wrap-around detected */
wrap_end = UINT32_MAX - init;
}
ret = wrap_end - (now - init) + timeout;
return ret;
}
/**
* @brief Retrieve Data from the C2C module over the UART interface.
* This function receives data from the C2C module, the
* data is fetched from a ring buffer that is asynchronously and continuously
filled with the received data.
* @param Obj: pointer to module handle
* @param pData: a buffer inside which the data will be read.
* @param Length: Size of the data to receive.
* @param ScanVals: when param Length = 0 : values to be retrieved in the coming data in order to exit.
* @retval int32_t: if param Length = 0 : the actual RET_CODE found,
if param Length > 0 : the actual data size that has been received
if error (timeout): return -1 (EG91_RETURN_RETRIEVE_ERROR).
*/
static int32_t AT_RetrieveData(EG91Object_t *Obj, uint8_t *pData,
uint16_t Length, uint32_t ScanVals, uint32_t Timeout)
{
uint32_t tickstart = Obj->GetTickCb();
int16_t ReadData = 0;
uint16_t x;
uint16_t index[NUM_RESPONSES];
uint16_t lens[NUM_RESPONSES];
uint16_t pos;
uint8_t c;
int32_t min_requested_time;
min_requested_time = 2 * (Length + 15) * 8 * 1000 / EG91_DEFAULT_BAUDRATE; /* 15 is the max length of the return keyword */
if (Timeout < min_requested_time) /* UART speed 115200 bits per sec */
{
Timeout = min_requested_time;
}
for (x = 0; x < NUM_RESPONSES; x++)
{
index[x] = 0;
lens[x] = strlen(ReturnKeywords[x].retStr);
}
if ((Length == 0) && (ScanVals == RET_NONE))
{
return 0; /* to avoid waiting a RET_VAL in case the parsed_lenth of payload is zero */
/* but no code needs to be retrieved */
}
memset(Obj->CmdResp, 0, EG91_CMD_SIZE);
while (TimeLeftFromExpiration(tickstart, Obj->GetTickCb(), Timeout) > 0)
{
if (Obj->fops.IO_ReceiveOne(&c) == 0) /* Receive one sample from UART */
{
/* serial data available, so return data to user */
pData[ReadData++] = c;
if (Length == 0)
{
/* Check whether we hit an ESP return values */
for (x = 0; x < NUM_RESPONSES; x++)
{
if (c != ReturnKeywords[x].retStr[index[x]])
{
index[x] = 0;
}
if (c == ReturnKeywords[x].retStr[index[x]])
{
pos = ++(index[x]);
if (pos >= lens[x])
{
if (ScanVals & ReturnKeywords[x].retVal)
{
return ReturnKeywords[x].retVal;
}
}
}
}
}
else /* end (Length > 0) */
{
if (ReadData < Length)
{
/* nothing to do except keep reading in the while loop */
}
else /* ReadData >= Length */
{
return ReadData;
}
} /* end (Length == 0) */
}
}
if ((Length > 0) && (ReadData > 0))
{
return ReadData;
}
return EG91_RETURN_RETRIEVE_ERROR;
}
/**
* @brief Execute AT command.
* @param Obj: pointer to module handle
* @param cmd: pointer to command string
* @param resp: expected response
* @retval Operation Status: OK or ERROR.
*/
static int32_t AT_ExecuteCommand(EG91Object_t *Obj, uint32_t timeout,
uint8_t *cmd, uint32_t resp)
{
int32_t ret = EG91_RETURN_SEND_ERROR;
if (timeout == 0)
{
timeout = EG91_TOUT_300;
}
#ifdef EG91_DBG_AT //alecks
printf("AT Request: %s \n", cmd);
#endif
if (Obj->fops.IO_Send(cmd, strlen((char*) cmd)) >= 0)
{
HAL_Delay(200); //return to 200
ret = (AT_RetrieveData(Obj, Obj->CmdResp, 0, resp, timeout));
if (ret < 0)
{
#ifdef EG91_DBG
printf("EG91 AT_ExecuteCommand() rcv TIMEOUT ret=%ld: %s \r\n", ret, cmd);
#endif
}
#ifdef EG91_DBG_AT
else
{
printf("AT Response: %s \n", Obj->CmdResp);
}
#endif
}
else
{
#ifdef EG91_DBG
printf("EG91 AT_ExecuteCommand() send ERROR: %s \r\n", cmd);
#endif
}
return ret;
}
/**
* @brief Execute AT command with data.
* @param Obj: pointer to module handle
* @param pdata: pointer to returned data
* @param len: binary data length
* @param timeout: waiting that the modem confirms confirm that send command has been executed (SEND OK)
* @retval Operation Status.
*/
static EG91_SendRet_t AT_RequestSendData(EG91Object_t *Obj, uint8_t *pdata,
uint16_t len, uint32_t timeout)
{
int32_t confirm;
if (Obj->fops.IO_Send(pdata, len) >= 0)
{
confirm = AT_RetrieveData(Obj, Obj->CmdResp, 0,
(RET_SENT | RET_BUF_FULL | RET_ERROR), timeout);
return (EG91_SendRet_t) confirm;
}
return EG91_SEND_RET_UART_FAIL; /* UART error to transmit */
}
/**
* @brief Retrieve URC header and decode it
* @param Obj: pointer to module handle
* @param Timeout : ms
* @param ParseLength : pointer to return the length parsed from URC info
* @param AccMode : currently only EG91_BUFFER_MODE is used and tested
* @retval Operation Status.
*/
static int32_t AT_RetrieveUrc(EG91Object_t *Obj, uint32_t Timeout,
uint16_t *ParseLength, EG91_AccessMode_t AccMode)
{
int32_t check_resp, ret;
uint32_t expected_resp;
uint8_t parse_ret;
uint8_t parse_count, ip_count;
uint32_t count = 0;
expected_resp = RET_URC_CLOSED | RET_URC_RECV | RET_URC_IN_FULL
| RET_URC_INCOM | RET_URC_PDPDEACT | RET_URC_DNS;
check_resp = AT_RetrieveData(Obj, Obj->CmdResp, 0, expected_resp, Timeout);
switch (check_resp)
{
case RET_URC_CLOSED:
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, EG91_TOUT_SHORT);
parse_ret = ParseNumber((char*) Obj->CmdResp + 1, &parse_count);
break;
case RET_URC_RECV:
/* retrieve contextID */
ret = AT_RetrieveData(Obj, Obj->CmdResp, 3, RET_NONE, EG91_TOUT_SHORT);
if (ret > 0)
{
parse_ret = ParseNumber((char*) Obj->CmdResp + 1, &parse_count);
if (parse_count == 2)
{ /* read next comma */
AT_RetrieveData(Obj, Obj->CmdResp, 1, RET_NONE, EG91_TOUT_SHORT);
}
}
if (AccMode == EG91_BUFFER_MODE)
{
AT_RetrieveData(Obj, Obj->CmdResp, 1, RET_NONE, EG91_TOUT_SHORT);
}
else
{
for (; count < 6; count++)
{
if (ret < 0)
{
break;
}
ret = AT_RetrieveData(Obj, &Obj->CmdResp[count], 1, RET_NONE,EG91_TOUT_SHORT);
if (ret == 1)
{
if (Obj->CmdResp[count] == '\n')
{
*ParseLength = (uint16_t) ParseNumber((char*) Obj->CmdResp, &parse_count);
break;
}
}
}
}
break;
case RET_URC_IN_FULL:
/* nothing to be done */
break;
case RET_URC_INCOM:
/* TBD: to be implemented for SERVER MODE*/
break;
case RET_URC_PDPDEACT:
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, EG91_TOUT_SHORT);
parse_ret = ParseNumber((char*) Obj->CmdResp + 1, &parse_count);
break;
case RET_URC_DNS:
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, Timeout);
parse_ret = ParseNumber((char*) Obj->CmdResp + 1, &parse_count);
if (parse_ret == 0) /* means no errors */
{
ip_count = ParseNumber((char*) Obj->CmdResp + 3, &parse_count);
for (count = 0; count < ip_count; count++)
{
expected_resp = RET_URC_DNS;
check_resp = AT_RetrieveData(Obj, Obj->CmdResp, 0,
expected_resp, Timeout);
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, Timeout);
}
}
else
{
check_resp = -1;
}
break;
default:
check_resp = -1;
break;
}
return check_resp;
}
/**
* @brief Synchronize the modem uart with the STM32 uart (autobauding)
* @param Obj: pointer to module handle
* @retval Operation status.
*/
static int32_t AT_Synchro(EG91Object_t *Obj)
{
int32_t ret = RET_ERROR;
int8_t atSync = 0;
uint32_t tickstart;
/* Init tickstart for timeout management */
tickstart = Obj->GetTickCb();
/* Start AT SYNC: Send AT every 500ms,
if receive OK, SYNC success,
if no OK return after sending AT 10 times, SYNC fail */
do
{
if (TimeLeftFromExpiration(tickstart, Obj->GetTickCb(), EG91_TOUT_ATSYNC) < 0)
{
ret = AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT\r\n", RET_OK | RET_ERROR);
atSync++;
tickstart = Obj->GetTickCb();
}
} while ((atSync < 10) && (ret != RET_OK));
return ret;
}
/* --------------------------------------------------------------------------*/
/* --- Public functions -----------------------------------------------------*/
/* --------------------------------------------------------------------------*/
/**
* @brief Register EG91 BusIO external functions.
* @param Obj: pointer to module handle
* @retval Operation Status.
*/
EG91_Return_t EG91_RegisterBusIO(EG91Object_t *Obj, IO_Init_Func IO_Init,
IO_DeInit_Func IO_DeInit, IO_Baudrate_Func IO_Baudrate,
IO_Send_Func IO_Send, IO_ReceiveOne_Func IO_ReceiveOne,
IO_Flush_Func IO_Flush)
{
if (!Obj || !IO_Init || !IO_DeInit || !IO_Baudrate || !IO_Send
|| !IO_ReceiveOne || !IO_Flush)
{
return EG91_RETURN_ERROR;
}
Obj->fops.IO_Init = IO_Init;
Obj->fops.IO_DeInit = IO_DeInit;
Obj->fops.IO_Baudrate = IO_Baudrate;
Obj->fops.IO_Send = IO_Send;
Obj->fops.IO_ReceiveOne = IO_ReceiveOne;
Obj->fops.IO_FlushBuffer = IO_Flush;
return EG91_RETURN_OK;
}
/**
* @brief Shut down the module.
* @retval Operation Status.
*/
EG91_Return_t EG91_PowerDown(EG91Object_t *Obj)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
if ( RET_OK == (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+POWEROFF\r\n",
RET_OK | RET_ERROR | RET_CME_ERROR))
{
// printf("Please wait, disconnecting and saving data. It may last until 60 s\n");
/* expect for the "POWERED DOWN" */
/* The maximum time for network log-off is 60 seconds */
if (AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_POWERED_DOWN, EG91_TOUT_60000) > 0)
{
printf("Modem is entered in power down\n");
ret = EG91_RETURN_OK;
}
}
return ret;
}
/**
* @brief Initialize EG91 module.
* @param Obj: pointer to module handle
* @retval Operation Status.
*/
EG91_InitRet_t EG91_Init(EG91Object_t *Obj)
{
EG91_InitRet_t fret = EG91_INIT_OTHER_ERR;
int32_t ret = RET_ERROR;
int8_t i;
char *align_ptr, *token;
uint8_t parse_count;
uint32_t tickstart;
Obj->APsActive = 0;
for (i = 0; i < EG91_MAX_SOCKETS; i++)
{
Obj->SocketInfo[i].Type = EG91_TCP_CONNECTION;
Obj->SocketInfo[i].AccessMode = EG91_BUFFER_MODE;
Obj->SocketInfo[i].ComulatedQirdData = 0;
Obj->SocketInfo[i].HaveReadLength = 0;
Obj->SocketInfo[i].UnreadLength = 0;
}
Obj->fops.IO_FlushBuffer(); /* Flush Uart intermediate buffer */
if (Obj->fops.IO_Init() == 0) /* configure and initialize UART */
{
ret = AT_Synchro(Obj);
if (ret != RET_OK)
{
printf("Fail to AT SYNC, after several attempts\r\n");
fret = EG91_INIT_RET_AT_ERR; /* if does not respond to AT command set specific return status */
}
else
{
// /* Retrieve Quectel Factory Default values */
// ret = EG91_ResetToFactoryDefault(Obj);
/* Retrieve Quectel UART baud rate and flow control*/
/* If not aligned to the UART of MCU (_io.h), already previous AT command will fail */
ret = ret | EG91_GetUARTConfig(Obj, &Obj->UART_Config);
/* Use ATV1 to set the response format */
ret = ret | AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "ATV1\r\n", RET_OK | RET_ERROR);
/* Use ATE1 to enable or ATE0 to disable echo mode */
ret = ret | AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "ATE0\r\n", RET_OK | RET_ERROR);
/* Use AT+CMEE=1 to enable result code and use "integer" values */
ret = ret | AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CMEE=1\r\n", RET_OK | RET_ERROR);
}
/* retrieve module info */
if (ret == RET_OK)
{
ret = AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CGMI\r\n", RET_OK | RET_ERROR);
if (ret == RET_OK)
{
align_ptr = strtok((char *)Obj->CmdResp, "\r\n");
strncpy((char*) Obj->Manufacturer, align_ptr, EG91_MFC_SIZE);
}
ret = AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CGMM\r\n", RET_OK | RET_ERROR);
if (ret == RET_OK)
{
align_ptr = strtok((char *)Obj->CmdResp, "\r\n");
strncpy((char*) Obj->ProductID, align_ptr, EG91_PROD_ID_SIZE);
}
ret = AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CGMR\r\n", RET_OK | RET_ERROR);
if (ret == RET_OK)
{
align_ptr = strtok((char *)Obj->CmdResp, "\r\n");
strncpy((char*) Obj->FW_Rev, align_ptr, EG91_FW_REV_SIZE);
}
/* Use AT+GSN to query the IMEI (International Mobile Equipment Identity) of module */
ret = AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CGSN\r\n", RET_OK | RET_ERROR);
if (ret == RET_OK)
{
align_ptr = strtok((char *)Obj->CmdResp, "\r\n");
strncpy((char*) Obj->Imei, align_ptr, EG91_IMEI_SIZE);
}
}
/* retrieve SIM info */
if (ret == RET_OK)
{
/* A tempo is required to get the SIM ready. Set to 2000 ms */
tickstart = Obj->GetTickCb();
while ((Obj->GetTickCb() - tickstart) < 2000)
{
}
ret = AT_ExecuteCommand(Obj, EG91_TOUT_5000, (uint8_t*) "AT+CPIN?\r\n", RET_OK | RET_ERROR | RET_CME_ERROR);
if (RET_OK == ret)
{
align_ptr = strstr((char*) Obj->CmdResp, "+CPIN: READY");
if ( NULL != align_ptr)
{
Obj->SimInfo.SimStatus = EG91_SIM_READY;
if (RET_OK == AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CIMI\r\n", RET_OK | RET_ERROR | RET_CME_ERROR))
{
align_ptr = strtok((char *)Obj->CmdResp, "\r\n");
strncpy((char*) Obj->SimInfo.IMSI, align_ptr, EG91_IMSI_SIZE);
}
if (RET_OK == AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+ICCID\r\n", RET_OK | RET_ERROR))
{
align_ptr = strtok((char *)Obj->CmdResp, "\r\n");
token = strchr(align_ptr, ':');
if (token != NULL)
{
token++;
while(*token == ' ')
{
token++;
}
}
strncpy((char*) Obj->SimInfo.ICCID, token, EG91_ICCID_SIZE);
}
fret = EG91_INIT_RET_OK;
}
}
else
{
if (RET_CME_ERROR == ret)
{
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, EG91_TOUT_SHORT);
Obj->SimInfo.SimStatus = (EG91_SIMState_t) ParseNumber( (char*) Obj->CmdResp + 1, &parse_count);
fret = EG91_INIT_RET_SIM_ERR;
}
}
}
/* Set the radio ON with the full functionality in the modem */
ret = AT_ExecuteCommand(Obj, EG91_TOUT_15000, (uint8_t*) "AT+CFUN=1\r\n", RET_OK | RET_ERROR | RET_CME_ERROR);
if (RET_OK != ret)
{
fret = EG91_INIT_OTHER_ERR;
}
for (int i = 0; i < 30; i++)
{
HAL_Delay(30);
}
}
else
{
fret = EG91_INIT_RET_IO_ERR;
}
return fret;
}
/**
* @brief Get Signal Quality value
* @param Obj: pointer to module handle
* @retval Operation status.
*/
EG91_Return_t EG91_GetSignalQualityStatus(EG91Object_t *Obj, int32_t *Qvalue)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
uint8_t parse_count;
char *align_ptr;
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CSQ\r\n", RET_OK | RET_ERROR | RET_CME_ERROR);
if (RET_OK == ret)
{
align_ptr = strstr((char*) Obj->CmdResp, "+CSQ:") + sizeof("+CSQ:");
*Qvalue = ParseNumber(align_ptr, &parse_count);
// AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+QCSQ\r\n", RET_OK | RET_ERROR | RET_CME_ERROR);
}
return ret;
}
/**
* @brief Attach the MT to the packet domain service
* @param Obj: pointer to module handle
* @retval Operation status.
*/
EG91_Return_t EG91_PSAttach(EG91Object_t *Obj)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
if (Obj->SimInfo.SimStatus == EG91_SIM_READY)
{
if (RET_OK != AT_ExecuteCommand(Obj, EG91_TOUT_150000, (uint8_t*) "AT+CGATT=1\r\n", RET_OK | RET_ERROR | RET_CME_ERROR))
{
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, EG91_TOUT_SHORT);
printf("CME ERROR: %s\r\n", Obj->CmdResp);
ret = EG91_RETURN_ERROR;
}
else
{
ret = EG91_RETURN_OK;
}
}
return ret;
}
/**
* @brief Force an automatic PLMN selection
* @param Obj: pointer to module handle
* @retval Operation status.
*/
EG91_Return_t EG91_AutomaticPlmnSelection(EG91Object_t *Obj)
{
EG91_Return_t ret;
if (RET_OK != AT_ExecuteCommand(Obj, EG91_TOUT_180000, (uint8_t*) "AT+COPS=0\r\n", RET_OK | RET_ERROR | RET_CME_ERROR))
{
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, EG91_TOUT_SHORT);
ret = EG91_RETURN_ERROR;
}
else
{
ret = EG91_RETURN_OK;
}
return ret;
}
EG91_Return_t EG91_SetFullFunctionality(EG91Object_t *Obj)
{
EG91_Return_t ret;
if (RET_OK != AT_ExecuteCommand(Obj, EG91_TOUT_15000, (uint8_t*) "AT+CFUN=1\r\n", RET_OK | RET_ERROR | RET_CME_ERROR))
{
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_CRLF, EG91_TOUT_SHORT);
ret = EG91_RETURN_ERROR;
}
else
{
ret = EG91_RETURN_OK;
}
return ret;
}
/**
* @brief Get Circuit Switch Registration Status
* @param Obj: pointer to module handle
* @retval Registration Status.
*/
EG91_NetworkRegistrationState_t EG91_GetCsNetworkRegistrationStatus(EG91Object_t *Obj)
{
EG91_NetworkRegistrationState_t ret = EG91_NRS_ERROR;
int n = 0; // n: mode
int stat = 0; // stat: registration state
char *cgreg_ptr;
if (Obj->SimInfo.SimStatus == EG91_SIM_READY)
{
if (RET_OK == AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CREG?\r\n", RET_OK | RET_ERROR | RET_CME_ERROR))
{
cgreg_ptr = strstr((char*) Obj->CmdResp, "+CREG:");
if (NULL != cgreg_ptr)
{
if(sscanf(cgreg_ptr, "+CREG: %d,%d", &n, &stat) == 2)
{
ret = (EG91_NetworkRegistrationState_t)stat;
}
}
}
}
return ret;
}
/**
* @brief Get Packet Switch Registration Status
* @param Obj: pointer to module handle
* @retval Registration Status.
*/
EG91_NetworkRegistrationState_t EG91_GetPsNetworkRegistrationStatus(EG91Object_t *Obj)
{
EG91_NetworkRegistrationState_t ret = EG91_NRS_ERROR;
int n = 0; // n: mode
int stat = 0; // stat: registration state
char *cgreg_ptr;
if (Obj->SimInfo.SimStatus == EG91_SIM_READY)
{
if (RET_OK == AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CGREG?\r\n", RET_OK | RET_ERROR | RET_CME_ERROR))
{
cgreg_ptr = strstr((char*) Obj->CmdResp, "+CGREG:");
if (NULL != cgreg_ptr)
{
if(sscanf(cgreg_ptr, "+CGREG: %d,%d", &n, &stat) == 2)
{
ret = (EG91_NetworkRegistrationState_t)stat;
}
}
}
}
return ret;
}
EG91_NetworkRegistrationState_t EG91_GetEpsNetworkRegistrationStatus(EG91Object_t *Obj)
{
EG91_NetworkRegistrationState_t ret = EG91_NRS_ERROR;
int n = 0; // n: mode
int stat = 0; // stat: registration state
char *cgreg_ptr;
if (Obj->SimInfo.SimStatus == EG91_SIM_READY)
{
if (RET_OK == AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+CEREG?\r\n", RET_OK | RET_ERROR | RET_CME_ERROR))
{
cgreg_ptr = strstr((char*) Obj->CmdResp, "+CEREG:");
if (NULL != cgreg_ptr)
{
if(sscanf(cgreg_ptr, "+CEREG: %d,%d", &n, &stat) == 2)
{
ret = (EG91_NetworkRegistrationState_t)stat;
}
}
}
}
return ret;
}
/**
* @brief Get the list of Network Operator available in the area
* @param Obj: pointer to module handle
* @param Operator: pointer to a string
* @retval Operation Status.
*/
EG91_Return_t EG91_ListOperators(EG91Object_t *Obj, char *Operators)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
char *align_ptr;
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_180000, (uint8_t*) "AT+COPS=?\r\n", RET_OK | RET_ERROR | RET_CME_ERROR);
if (RET_OK == ret)
{
align_ptr = strstr((char*) Obj->CmdResp, "+COPS:") + sizeof("+COPS:");
strncpy((char*) Operators, align_ptr, 100);
}
return ret;
}
/**
* @brief Get current Network Operator (by string descriptor).
* @param Obj: pointer to module handle
* @param Operator: pointer to a string
* @param bufsize: max string buffer length
* @retval Operation Status.
*/
EG91_Return_t EG91_GetCurrentOperator(EG91Object_t *Obj, char *Operator,
uint8_t Bufsize)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
char *align_ptr;
const char s[2] = ",";
char *token;
int i;
AT_ExecuteCommand(Obj, EG91_TOUT_180000, (uint8_t*) "AT+COPS=0\r\n", RET_OK | RET_ERROR | RET_CME_ERROR);
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_180000, (uint8_t*) "AT+COPS?\r\n", RET_OK | RET_ERROR | RET_CME_ERROR);
if (RET_OK == ret)
{
align_ptr = strstr((char*) Obj->CmdResp, "+COPS:") + sizeof("+COPS:");
strncpy((char*) Operator, align_ptr, Bufsize);
/* get the first token */
token = strtok(Operator, s);
/* walk through tokens until operator info */
i = 0;
while (token != NULL && i < 2)
{
token = strtok(NULL, s);
i++;
}
if (token != NULL)
{
strncpy((char*) (Operator), token, Bufsize);
}
else
{
ret = EG91_RETURN_ERROR;
}
}
return ret;
}
/**
* @brief Reset To factory defaults.
* @param Obj: pointer to module handle
* @retval Operation Status.
*/
EG91_Return_t EG91_ResetToFactoryDefault(EG91Object_t *Obj)
{
EG91_Return_t ret;
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT&F\r\n", RET_OK | RET_ERROR);
return ret;
}
/**
* @brief Set UART Configuration on the Quectel modem and on STM32.
* @param Obj: pointer to module handle
* @param pconf: pointer to UART config structure
* @retval Operation Status.
*/
EG91_Return_t EG91_SetUARTBaudrate(EG91Object_t *Obj, EG91_UARTConfig_t *pconf)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
/* change the UART baudrate on EG91 module */
snprintf(CmdString, 17, "AT+IPR=%lu\r\n", pconf->BaudRate);
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) CmdString, RET_OK | RET_ERROR);
/* change the UART baudrate on STM32 microcontroller accordingly */
Obj->fops.IO_Baudrate(pconf->BaudRate);
return ret;
}
/**
* @brief Get UART Configuration.
* @param Obj: pointer to module handle
* @param pconf: pointer to UART config structure
* @retval Operation Status.
*/
EG91_Return_t EG91_GetUARTConfig(EG91Object_t *Obj, EG91_UARTConfig_t *pconf)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
char *align_ptr;
uint8_t rts, cts;
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+IPR?\r\n", RET_OK | RET_ERROR);
if (ret == RET_OK)
{
align_ptr = strstr((char*) Obj->CmdResp, "+IPR:") + sizeof("+IPR:");
Obj->UART_Config.BaudRate = ParseNumber(align_ptr, NULL);
}
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) "AT+IFC?\r\n", RET_OK | RET_ERROR);
if (ret == RET_OK)
{
align_ptr = strstr((char*) Obj->CmdResp, "+IFC:") + sizeof("+IPR:");
rts = ParseNumber(align_ptr, NULL);
cts = ParseNumber(align_ptr + 2, NULL);
if (rts == 2)
{
if (cts == 2)
{
pconf->FlowControl = EG91_UART_FLW_CTL_RTS_CTS;
}
else
{
pconf->FlowControl = EG91_UART_FLW_CTL_RTS;
}
}
else
{
if (cts == 2)
{
pconf->FlowControl = EG91_UART_FLW_CTL_CTS;
}
else
{
pconf->FlowControl = EG91_UART_FLW_CTL_NONE;
}
}
}
return ret;
}
/**
* @brief Return Manufacturer.
* @param Obj: pointer to module handle
* @param Manufacturer: pointer to Manufacturer
* @retval None.
*/
void EG91_GetManufacturer(EG91Object_t *Obj, uint8_t *Manufacturer)
{
strncpy((char*) Manufacturer, (char*) Obj->Manufacturer, EG91_MFC_SIZE);
}
/**
* @brief Return Model.
* @param Obj: pointer to module handle
* @param Model: pointer to Model
* @retval None.
*/
void EG91_GetProductID(EG91Object_t *Obj, uint8_t *ProductID)
{
strncpy((char*) ProductID, (char*) Obj->ProductID, EG91_PROD_ID_SIZE);
}
/**
* @brief Return FW revision.
* @param Obj: pointer to module handle
* @param Model: pointer to FW revision
* @retval None.
*/
void EG91_GetFWRevID(EG91Object_t *Obj, uint8_t *Fw_ver)
{
strncpy((char*) Fw_ver, (char*) Obj->FW_Rev, EG91_FW_REV_SIZE);
}
/**
* @brief Retrieve last IP error code
* @param Obj: pointer to module handle
* @param error_string:
* @param error_code
* @retval Operation Status.
*/
EG91_Return_t EG91_RetrieveLastErrorDetails(EG91Object_t *Obj,
char *error_string)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
char *align_ptr;
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_SHORT, (uint8_t*) "AT+QIGETERROR\r\n", RET_OK | RET_ERROR);
align_ptr = strstr((char*) Obj->CmdResp, "+QIGETERROR:") + sizeof("+QIGETERROR:");
strncpy((char*) error_string, align_ptr, EG91_ERROR_STRING_SIZE);
return ret;
}
/**
* @brief Register EG91 Tick external cb functions to be provided by application (e.g. HAL_GetTick)
* @param Obj: pointer to module handle
* @param GetTickCb: pointer to callback function that should provide a Timer Tick in ms
* @retval Operation Status.
*/
EG91_Return_t EG91_RegisterTickCb(EG91Object_t *Obj,
App_GetTickCb_Func GetTickCb)
{
if (!Obj || !GetTickCb)
{
return EG91_RETURN_ERROR;
}
Obj->GetTickCb = GetTickCb;
return EG91_RETURN_OK;
}
/* ==== AP Connection ==== */
EG91_Return_t EG91_ConfigurePDPContext(EG91Object_t *Obj, uint8_t ContextID, const char *apn)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
snprintf(CmdString, strlen("AT+CGDCONT=%d,\"IP\",\"%s\"\r\n") + strlen(apn) + 1, "AT+CGDCONT=%d,\"IP\",\"%s\"\r\n", ContextID, apn);
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*)CmdString, RET_OK | RET_ERROR);
snprintf(CmdString, strlen("AT+CGDCONT?\r\n"), "AT+CGDCONT?\r\n");
AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*)CmdString, RET_OK | RET_ERROR);
if (ret == EG91_RETURN_OK)
{
snprintf(CmdString, EG91_CMD_SIZE, "AT+QICSGP=%d,1,\"%s\",\"%s\",\"%s\",%d\r\n",
ContextID, apn, "", "", 0);
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) CmdString, RET_OK | RET_ERROR);
}
return ret;
}
/**
* @brief Join a PDP Access point.
* @param Obj: pointer to module handle
* @param ContextID : range is 1-20 (max three can be connected simultaneously)
* @retval Operation Status.
*/
EG91_Return_t EG91_Activate(EG91Object_t *Obj, uint8_t ContextID, const char *apnStr)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
snprintf(CmdString, strlen("AT+CGDCONT=1,\"IP\", \"%s\"\r\n") + strlen(apnStr), "AT+CGDCONT=1,\"IP\", \"%s\"\r\n", apnStr);
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) CmdString, RET_OK | RET_ERROR);
if (ret == EG91_RETURN_OK)
{
if (Obj->APsActive < 3)
{
snprintf(CmdString, 24, "AT+CGACT=1,%d\r\n", ContextID);
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_150000, (uint8_t*) CmdString, RET_OK | RET_ERROR);
HAL_Delay(10);
if (ret == EG91_RETURN_OK)
{
snprintf(CmdString, 24, "AT+CGACT?\r\n");
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_150000, (uint8_t*) CmdString, RET_OK | RET_ERROR);
HAL_Delay(10);
if (ret == EG91_RETURN_OK)
{
const char *p = strchr((char *)Obj->CmdResp, ',');
if (!p)
{
return EG91_RETURN_ERROR;
}
p++;
Obj->APContextState[ContextID - 1] = atoi(p);
Obj->APsActive++;
}
}
}
HAL_Delay(500);
}
return ret;
}
/**
* @brief Leave a PDP Access point.
* @param Obj: pointer to module handle
* @param ContextID : range is 1-20 (max three are connected simultaneously)
* @retval Operation Status.
*/
EG91_Return_t EG91_Deactivate(EG91Object_t *Obj, uint8_t ContextID)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
snprintf(CmdString, 24, "AT+CGACT=0,%d\r\n", ContextID);
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_40000, (uint8_t*) CmdString, RET_OK | RET_ERROR);
if (ret == EG91_RETURN_OK)
{
snprintf(CmdString, 24, "AT+CGACT?\r\n");
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_150000, (uint8_t*) CmdString, RET_OK | RET_ERROR);
HAL_Delay(10);
const char *p = strchr((char *)Obj->CmdResp, ',');
if (!p)
{
return EG91_RETURN_ERROR;
}
p++;
Obj->APContextState[ContextID - 1] = atoi(p);
Obj->APsActive--;
}
return ret;
}
/**
* @brief Check whether the contextID is connected to an access point.
* @retval Operation Status.
*/
EG91_APState_t EG91_IsActivated(EG91Object_t *Obj, uint8_t ContextID)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
snprintf(CmdString, strlen("AT+CGACT?\r\n"), "AT+CGACT?\r\n");
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_150000, (uint8_t*) CmdString, RET_OK | RET_ERROR);
if (ret == EG91_RETURN_OK)
{
char *line = strtok((char *)Obj->CmdResp, "\r\n");
while(line != NULL)
{
if (strstr(line, "+CGACT:") != NULL)
{
int ctx_id = 0;
int act_state = 0;
if (sscanf(line, "+CGACT: %d,%d", &ctx_id, &act_state) == 2)
{
if (ctx_id == ContextID)
{
Obj->APContextState[ContextID - 1] = act_state;
return(EG91_APState_t)act_state;
}
}
}
line = strtok(NULL, "\r\n");
}
}
return EG91_AP_ERROR;
}
/**
* @brief Get the list of the current activated context and its IP addresses
* @param Obj: pointer to module handle
* @param IPaddr_string: pointer where to retrieve the string with all active IP info
* @param IPaddr_int: pointer where to retrieve the first active IP address in int_array[] format
* @retval Operation Status.
*/
EG91_Return_t EG91_GetActiveIpAddresses(EG91Object_t *Obj)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
int32_t cmdret;
cmdret = AT_ExecuteCommand(Obj, EG91_TOUT_150000, (uint8_t*) "AT+CGPADDR=1\r\n", RET_OK | RET_ERROR);
if (cmdret == RET_OK)
{
const char* p = (const char*)Obj->CmdResp;
while ((p = strstr(p, "+CGPADDR: ")) != NULL)
{
const char *quote_start = strchr(p, '"');
if (quote_start)
{
const char *quote_end = strchr(quote_start + 1, '"');
if (quote_end)
{
char ip[64];
size_t len = quote_end - quote_start - 1;
if (len >= sizeof(ip))
len = sizeof(ip) - 1;
strncpy(ip, quote_start + 1, len);
ip[len] = '\0';
printf("IP Address: %s\r\n", ip);
}
}
// Move p forward to avoid infinite loop
p += strlen("+CGPADDR: ");
}
ret = EG91_RETURN_OK;
}
return ret;
}
#if (EG91_USE_PING == 1)
int _countCommaStr(const char *str)
{
int count = 0;
while(*str)
{
if (*str == ',')
{
count++;
}
str++;
}
return count;
}
/**
* @brief Test the Internet Protocol reachability of a host
* @param Obj: pointer to module handle
* @param ContextID : range is 1-20 (max three are connected simultaneously)
* @param host_addr_string: domain name (e.g. www.amazon.com) or dotted decimal IP addr
* @param count: PING repetitions (default 4) (max 10)
* @param rep_delay_sec: timeout for each repetition in seconds
* @retval Operation Status.
*/
EG91_Return_t EG91_Ping(EG91Object_t *Obj)
{
EG91_Return_t ret = EG91_RETURN_ERROR;
snprintf(CmdString, strlen("AT+QPING=1,\"8.8.8.8\",10,10\r\n"), "AT+QPING=1,\"8.8.8.8\",10,10\r\n");
ret = (EG91_Return_t) AT_ExecuteCommand(Obj, EG91_TOUT_300, (uint8_t*) CmdString, RET_OK | RET_ERROR);
if(ret == EG91_RETURN_OK)
{
uint32_t startTick = HAL_GetTick();
int res = 0;
int sent = 0;
int rcv = 0;
int loss = 0;
int min = 0;
int max = 0;
int avg = 0;
uint8_t pingOK = 0;
while(((HAL_GetTick() - startTick) < EG91_TOUT_15000))
{
AT_RetrieveData(Obj, Obj->CmdResp, 0, RET_NETCLOSE, EG91_TOUT_SHORT);
if (_countCommaStr((char *)Obj->CmdResp) == 6)
{
sscanf((char *)Obj->CmdResp, " %d,%d,%d,%d,%d,%d,%d", &res, &sent, &rcv, &loss, &min, &max, &avg);
printf("PING Result: %d out of %d\r\n\r\n", rcv, sent);
pingOK = 1;
}
}
if(pingOK == 0)
{
printf("PING Result: NOK\r\n\r\n");
ret = EG91_RETURN_ERROR;
}
}
return ret;
}
#endif
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/