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

© Copyright (c) 2017 STMicroelectronics International N.V. * All rights reserved.

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