/* ----------------------------------------------------------------------------- * Copyright (c) 2019 Arm Limited (or its affiliates). All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the License); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an AS IS BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * * $Date: 12. November 2019 * $Revision: V1.0 * * Project: ESP8266 WiFi Driver * -------------------------------------------------------------------------- */ #include #include #include #include #include "ESP8266.h" #include "ESP8266_Serial.h" #include "WiFi_ESP8266_Os.h" #include "kmdw_console.h" /* Control block */ static AT_PARSER_HANDLE AT_Cb; /* Pointer to parser control block */ #define pCb (&AT_Cb) /* Pointer to parser buffer memory */ #define pMem (&AT_Cb.mem) /* String list definition */ typedef const struct { const char *str; } STRING_LIST_t; /* Static functions */ static int32_t ReceiveData (void); static uint8_t AnalyzeLineData (void); static uint8_t GetCommandCode (BUF_LIST *mem); static uint8_t GetASCIIResponseCode (BUF_LIST *mem); static uint8_t GetGMRResponseCode (BUF_LIST *mem); static uint8_t GetCtrlResponseCode (BUF_LIST *mem); static int32_t GetRespArg (uint8_t *buf, uint32_t sz); static int32_t CmdOpen (uint8_t cmd_code, uint32_t cmd_mode, char *buf); static int32_t CmdSend (uint8_t cmd, char *buf, int32_t num); static const char *CmdString (uint8_t cmd); static int32_t CmdSetWFE (uint8_t cmd); static void AT_Parse_IP (char *buf, uint8_t ip[]); static void AT_Parse_MAC (char *buf, uint8_t mac[]); /* Command list (see also CommandCode_t) */ static STRING_LIST_t List_PlusResp[] = { { "IPD" }, { "CWLAP" }, { "CWJAP_CUR" }, { "CWQAP" }, { "CWSAP_CUR" }, { "CWMODE_CUR" }, { "CIPSTAMAC_CUR" }, { "CIPAPMAC_CUR" }, { "RFPOWER" }, { "CIPSTA_CUR" }, { "CIPAP_CUR" }, { "CIPDNS_CUR" }, { "CWDHCP_CUR" }, { "CWDHCPS_CUR" }, { "CWAUTOCONN" }, { "CWLIF" }, { "UART_CUR" }, #if (AT_VARIANT == AT_VARIANT_ESP) #if (AT_VERSION >= AT_VERSION_1_6_0_0) && (AT_VERSION < AT_VERSION_1_7_0_0) { "SYSMSG" }, #elif (AT_VERSION >= AT_VERSION_1_7_0_0) { "SYSMSG_CUR" }, #endif #else { "SYSMSG_CUR" }, #endif { "CIPSTATUS" }, { "CIPDOMAIN" }, { "CIPSTART" }, { "CIPCLOSE" }, { "PING" }, { "CIPSEND" }, { "CIPMUX" }, { "CIPSERVER" }, { "CIPSERVERMAXCONN" }, { "RST" }, { "GMR" }, { "LINK_CONN" }, { "STA_CONNECTED" }, { "STA_DISCONNECTED" }, { "SLEEP" }, { "E" }, { "" } }; /* Command codes */ typedef enum { CMD_IPD = 0, CMD_CWLAP, CMD_CWJAP_CUR, CMD_CWQAP, CMD_CWSAP_CUR, CMD_CWMODE_CUR, CMD_CIPSTAMAC_CUR, CMD_CIPAPMAC_CUR, CMD_RFPOWER, CMD_CIPSTA_CUR, CMD_CIPAP_CUR, CMD_CIPDNS_CUR, CMD_CWDHCP_CUR, CMD_CWDHCPS_CUR, CMD_CWAUTOCONN, CMD_CWLIF, CMD_UART_CUR, CMD_SYSMSG_CUR, CMD_CIPSTATUS, CMD_CIPDOMAIN, CMD_CIPSTART, CMD_CIPCLOSE, CMD_PING, CMD_CIPSEND, CMD_CIPMUX, CMD_CIPSERVER, CMD_CIPSERVERMAXCONN, CMD_RST, CMD_GMR, CMD_LINK_CONN, CMD_STA_CONNECTED, CMD_STA_DISCONNECTED, CMD_SLEEP, CMD_ECHO = 0xFD, /* Command Echo */ CMD_TEST = 0xFE, /* AT startup (empty command) */ CMD_UNKNOWN = 0xFF /* Unknown or unhandled command */ } CommandCode_t; /* Generic responses (see AT_RESP_x definitions) */ static STRING_LIST_t List_ASCIIResp[] = { { "OK" }, { "ERROR" }, { "FAIL" }, { "SEND OK" }, { "SEND FAIL" }, { "busy p..." }, { "busy s..." }, { "ALREADY CONNECTED" }, /* Generic responses redirected to AT_Notify function */ { "WIFI CONNECTED" }, { "WIFI GOT IP" }, { "WIFI DISCONNECT" }, /* Ignored */ #if 0 { "no change" }, { "DNS Fail" }, { "UNLINK" }, #endif /* Special responses */ { "AT" }, { "ready" }, { "ERR CODE" }, }; /* List of strings received in response to AT+GMR */ static STRING_LIST_t List_Gmr[] = { { "AT version" }, { "SDK version" }, { "compile time" }, { "Bin version" } }; /* GMR codes */ #define AT_GMR_AT_VER 0 #define AT_GMR_SDK_VER 1 #define AT_GMR_COMP_TIME 2 #define AT_GMR_BIN_VER 3 #define AT_GMR_UNKNOWN 0xFF /* Control strings */ static STRING_LIST_t List_Ctrl[] = { { "CONNECT" }, { "CLOSED" } }; /* Control codes */ #define AT_CTRL_CONNECT 0 #define AT_CTRL_CLOSED 1 #define AT_CTRL_UNKNOWN 0xFF /* ------------------------------------------------------------------------- */ /* Get pointer to command string */ static const char *CmdString (uint8_t cmd) { return (List_PlusResp[cmd].str); } /* ------------------------------------------------------------------------- */ /** Serial callback. */ void Serial_Cb (uint32_t cb_event) { if (cb_event & SERIAL_CB_TX_DATA_COMPLETED ) { /* Serial transmit completed */ AT_Notify (AT_NOTIFY_TX_DONE, NULL); } if (cb_event & SERIAL_CB_RX_DATA_AVAILABLE) { /* Serial received data */ AT_Notify (AT_NOTIFY_EXECUTE, NULL); } } /* ------------------------------------------------------------------------- */ /** Initialize parser. */ int32_t AT_Parser_Initialize (void) { osMemoryPoolAttr_t mp_attr; osMemoryPoolId_t mp_id; int32_t ex, stat; stat = -1; mp_attr = AT_Parser_MemPool_Attr; mp_id = osMemoryPoolNew (PARSER_BUFFER_BLOCK_COUNT, PARSER_BUFFER_BLOCK_SIZE, &mp_attr); if (mp_id != NULL) { ex = Serial_Initialize (); if ( ex== 0) { /* Serial initialized ,pooling mode*/ stat = 0; } else { if(ex == 1) { stat =1; } } } if (stat >= 0) { /* Setup memory pool */ BufInit (mp_id, NULL, &pCb->mem); BufInit (mp_id, NULL, &pCb->resp); /* Set initial state */ pCb->state = AT_STATE_ANALYZE; pCb->cmd_sent = CMD_UNKNOWN; pCb->gen_resp = 0U; pCb->msg_code = 0U; pCb->resp_code = CMD_UNKNOWN; pCb->resp_len = 0U; } if (stat < 0) { /* Clean up resources */ Serial_Uninitialize(); if (mp_id != NULL) { osMemoryPoolDelete (mp_id); } } return (stat); } /** Uninitialize parser. */ int32_t AT_Parser_Uninitialize (void) { Serial_Uninitialize(); BufUninit(pMem); osMemoryPoolDelete (pMem->mp_id); pCb->mem.mp_id = NULL; pCb->resp.mp_id = NULL; return (0); } /** Set serial baudrate. */ int32_t AT_Parser_SetBaudrate (uint32_t baudrate) { return Serial_SetBaudrate (baudrate); } /** Reset parser. */ void AT_Parser_Reset (void) { /* Flush parser buffer */ BufFlush (0, pMem); /* Reset state */ pCb->state = AT_STATE_ANALYZE; pCb->cmd_sent = CMD_UNKNOWN; pCb->gen_resp = 0U; pCb->msg_code = 0U; pCb->resp_code = CMD_UNKNOWN; pCb->resp_len = 0U; } /** Execute AT command parser. */ void AT_Parser_Execute (void) { uint8_t crlf[] = {'\r', '\n'}; int32_t n; uint32_t sleep; uint32_t p; sleep = 0U; while (sleep == 0) { /* Receive serial data */ n = ReceiveData(); if ( n == 1U) { /* Out of memory */ AT_Notify (AT_NOTIFY_OUT_OF_MEMORY, pCb->mem.mp_id); } switch (pCb->state) { case AT_STATE_ANALYZE: pCb->state = AnalyzeLineData(); break; case AT_STATE_WAIT: /* Not enough data in buffer to complete operation */ sleep = 1U; /* Next state */ pCb->state = AT_STATE_ANALYZE; break; case AT_STATE_FLUSH: /* Flush current response till first CRLF */ n = BufFind (crlf, 2, pMem); if (n != -1) { /* Flush buffer including crlf */ BufFlush ((uint32_t)n + 2, pMem); } /* Start analyzing again */ pCb->state = AT_STATE_ANALYZE; break; case AT_STATE_RECV_DATA: /* Copy IPD data */ /* Set pointer to source memory buffer */ p = (uint32_t)pMem; /* Call notify using pointer to memory buffer */ AT_Notify (AT_NOTIFY_CONNECTION_RX_DATA, &p); /* On return, p must contain number of bytes left to receive */ if (p == 0) { /* Packet is received */ sleep = 1U; /* Next state */ pCb->state = AT_STATE_ANALYZE; } else { if (p == pCb->ipd_rx) { /* Application did not read anything */ sleep = 1U; } pCb->ipd_rx = p; } break; case AT_STATE_RESP_DATA: /* Received +CMD response */ if (pCb->resp_code == CMD_IPD) { /* Copy response (including ':' character) */ BufCopy (&(pCb->resp), &(pCb->mem), pCb->resp_len+1); /* Receive network data (+IPD) */ pCb->ipd_rx = 0U; AT_Notify (AT_NOTIFY_CONNECTION_RX_INIT, &(pCb->ipd_rx)); if (pCb->ipd_rx == 0) { /* Socket is out of memory */ AT_Notify (AT_NOTIFY_OUT_OF_MEMORY, NULL); } /* Start receiving data */ pCb->state = AT_STATE_RECV_DATA; } else { /* Response data arrived */ if (pCb->resp_code == CMD_PING) { /* Artificially add '+PING:' string */ BufWrite ((uint8_t *)"+PING:", 6, &(pCb->resp)); /* Flush '+' from the original response */ BufFlushByte (&(pCb->mem)); /* Adjust response length for the flushed byte */ pCb->resp_len -= 1U; } BufCopy (&(pCb->resp), &(pCb->mem), pCb->resp_len+2); pCb->state = AT_STATE_ANALYZE; if (pCb->resp_code == CMD_LINK_CONN) { /* Connection established (+LINK_CONN) */ AT_Notify (AT_NOTIFY_CONNECTION_OPEN, NULL); } else if (pCb->resp_code == CMD_STA_CONNECTED) { /* Station connected to local AP (+STA_CONNECTED:) */ AT_Notify (AT_NOTIFY_STATION_CONNECTED, NULL); } else if (pCb->resp_code == CMD_STA_DISCONNECTED) { /* Station disconnected from local AP (+STA_DISCONNECTED:) */ AT_Notify (AT_NOTIFY_STATION_DISCONNECTED, NULL); } else if (pCb->resp_code != CMD_UNKNOWN) { /* Command response (+XXX in buffer) */ pCb->state = AT_STATE_ANALYZE; } else { /* Response unknown/unhandled */ pCb->state = AT_STATE_FLUSH; } } break; case AT_STATE_RESP_GMR: /* +GMR: copy response into response buffer */ BufCopy (&(pCb->resp), &(pCb->mem), pCb->resp_len+2); pCb->state = AT_STATE_ANALYZE; break; case AT_STATE_RESP_GEN: switch (pCb->msg_code) { case AT_RESP_OK: case AT_RESP_ERROR: case AT_RESP_ALREADY_CONNECTED: case AT_RESP_SEND_OK: case AT_RESP_SEND_FAIL: /* Set generic command response */ pCb->gen_resp = pCb->msg_code; /* Application waits for response */ AT_Notify (AT_NOTIFY_RESPONSE_GENERIC, NULL); if(BufGetCount(pMem) == 0) sleep = 1U; /* Set next state */ //pCb->state = AT_STATE_FLUSH; break; case AT_RESP_BUSY_P: case AT_RESP_BUSY_S: /* Busy processing or busy sending */ break; case AT_RESP_WIFI_CONNECTED: AT_Notify (AT_NOTIFY_CONNECTED, NULL); break; case AT_RESP_WIFI_GOT_IP: AT_Notify (AT_NOTIFY_GOT_IP, NULL); break; case AT_RESP_WIFI_DISCONNECT: AT_Notify (AT_NOTIFY_DISCONNECTED, NULL); break; case AT_RESP_READY: pCb->gen_resp = pCb->msg_code; AT_Notify (AT_NOTIFY_READY, NULL); sleep = 1U; break; case AT_RESP_ERR_CODE: /* Error code received */ /* Artificially add '+' character and copy response */ BufWriteByte ('+', &(pCb->resp)); BufCopy (&(pCb->resp), &(pCb->mem), pCb->resp_len+2); AT_Notify (AT_NOTIFY_ERR_CODE, NULL); break; default: case AT_RESP_UNKNOWN: /* Unknown response */ break; } /* Set next state */ pCb->state = AT_STATE_FLUSH; break; case AT_STATE_SEND_DATA: /* Received '>' character */ AT_Notify (AT_NOTIFY_REQUEST_TO_SEND, NULL); sleep = 1U; /* Next state */ pCb->state = AT_STATE_FLUSH; break; case AT_STATE_RESP_CTRL: /* Control code arrived */ if (pCb->ctrl_code == AT_CTRL_CONNECT) { /* ,CONNECT */ AT_Notify (AT_NOTIFY_CONNECTION_OPEN, NULL); } else if (pCb->ctrl_code == AT_CTRL_CLOSED) { /* ,CLOSED */ AT_Notify (AT_NOTIFY_CONNECTION_CLOSED, NULL); } /* Next state */ pCb->state = AT_STATE_FLUSH; break; case AT_STATE_RESP_ECHO: /* Command echo received */ /* Next state */ pCb->state = AT_STATE_FLUSH; break; default: break; } } } /* Retrieve data from the serial interface and copy the data into the buffer. */ static int32_t ReceiveData (void) { static uint32_t sz_buf; //static uint32_t n_prev; BUF_MEM *buf; uint32_t n, cnt, num; int32_t err; if (sz_buf == 0) { sz_buf = BufGetSize(pMem); } err = 0; num = 0U; n = Serial_GetRxCount(); while (num < n) { /* Determine free space in the buffer */ buf = BufGetTail (pMem); if (buf != NULL) { cnt = sz_buf - buf->wr_idx; } else { cnt = 0U; } if (cnt != 0U) { /* We can read cnt bytes in one pass */ if (n < cnt) { /* Number of bytes received is less than we can read */ cnt = n; } /* Read actual data */ cnt = (uint32_t)Serial_ReadBuf (&buf->data[buf->wr_idx], cnt); /*dbg_msg_console("usart read buf :%s,%d\n",&buf->data[buf->wr_idx],buf->wr_idx); if(strstr(&buf->data[buf->wr_idx],"OK")!= NULL) { //dbg_msg_console("usart2 read buf :%s,%d\n",&buf->data[buf->wr_idx],buf->wr_idx); }*/ if (cnt != 0) { buf->wr_idx += cnt; num += cnt; } else { /* Serial buffer empty? */ err = 2U; } } else { /* Out of memory */ err = 1U; } if (err != 0U) { break; } } //memset(RxBuf,0,n); Serial_uart_read(); //Serial_uart_read(); return (err); } #define AT_LINE_NODATA (1U << 0) /* Line is empty */ #define AT_LINE_INCOMPLETE (1U << 1) /* Line contains incomplete response */ #define AT_LINE_PLUS (1U << 2) /* Line starts with '+' response */ #define AT_LINE_COLON (1U << 3) /* Line contains ':' character */ #define AT_LINE_ASCII (1U << 4) /* Line starts with ASCII characters */ #define AT_LINE_CRLF (1U << 5) /* Line contains CRLF characters */ #define AT_LINE_TXREQ (1U << 6) /* Line starts with '>' character */ #define AT_LINE_CTRL (1U << 7) /* Line starts with numeric character */ #define AT_LINE_NUMBER (1U << 8) /* Line contains numeric character */ /** Analyze received data and set AT_LINE_n flags based on the line content. \return AT_LINE flags */ static uint32_t AnalyzeLine (BUF_LIST *mem) { uint8_t crlf[] = {'\r', '\n'}; uint8_t b; /* Received byte */ uint32_t flags; /* Analysis flags */ int32_t val; flags = 0U; do { /* Peek current byte from list buffer */ val = BufPeekByte (mem); if (val < 0) { /* Buffer empty */ flags |= AT_LINE_NODATA; break; } b = (uint8_t)val; if (b == '+') { /* Found: +command response */ flags |= AT_LINE_PLUS; /* Check if colon is received */ val = BufFindByte (':', mem); if (val != -1) { flags |= AT_LINE_COLON; } else { /* Not terminated */ flags |= AT_LINE_INCOMPLETE; /* Check if next character is a number (PING response) */ val = BufPeekOffs(1, mem); if (val != -1) { b = (uint8_t)val; if ((b >= '0') && (b <= '9')) { flags &= ~AT_LINE_INCOMPLETE; flags |= AT_LINE_NUMBER; } } } } else if (b == '>') { /* Found: data input request */ flags |= AT_LINE_TXREQ; } else if (((b >= 'A') && (b <= 'Z')) || ((b >= 'a') && (b <= 'z'))) { /* Found: command ASCII response */ flags |= AT_LINE_ASCII; /* Check if terminated */ val = BufFind (crlf, 2, mem); if (val != -1) { pCb->resp_len = (uint8_t)val; flags |= AT_LINE_CRLF; } else { /* Not terminated */ flags |= AT_LINE_INCOMPLETE; } } else if ((b >= '0') && (b <= '9')) { /* [,] CLOSED response ? */ flags |= AT_LINE_CTRL; /* Check if terminated */ val = BufFind (crlf, 2, mem); if (val != -1) { pCb->resp_len = (uint8_t)val; flags |= AT_LINE_CRLF; } else { /* Not terminated */ flags |= AT_LINE_INCOMPLETE; } } else { /* Unknown character, flush it and continue */ BufFlushByte(mem); } } while (flags == 0U); /* Return analysis result */ return (flags); } /* -------------------------------------------------------------------- */ /** Analyze the received content and decide what to do with it. \return next parser state, see AT_STATE_ definitions. */ bool analyze_fin = true; static uint8_t AnalyzeLineData (void) { uint8_t crlf[] = {'\r', '\n'}; uint8_t code; uint8_t rval; int32_t n; uint32_t flags; flags = AnalyzeLine(pMem); if ((flags & AT_LINE_NODATA) || (flags & AT_LINE_INCOMPLETE)) { /* No data or incomplete response */ rval = AT_STATE_WAIT; } else if (flags & AT_LINE_PLUS) { /* Comand response with data */ if (flags & AT_LINE_COLON) { /* Line contains colon, string compare can be performed */ pCb->resp_code = GetCommandCode (pMem); if (pCb->resp_code == CMD_IPD) { /* Receive network data (+IPD) */ /* Find colon, there is no CRLF after +IPD */ pCb->resp_len = (uint8_t)BufFindByte (':', pMem); rval = AT_STATE_RESP_DATA; } else { /* Check if line is terminated */ n = BufFind (crlf, 2, pMem); if (n == -1) { /* Not terminated, wait for more data */ rval = AT_STATE_WAIT; } else { /* Line terminator found */ pCb->resp_len = (uint8_t)n; rval = AT_STATE_RESP_DATA; } } } else if (flags & AT_LINE_NUMBER) { /* Response contains plus and a number, ping response (+x) */ pCb->resp_code = CMD_PING; /* Check if line is terminated */ n = BufFind (crlf, 2, pMem); if (n == -1) { /* Not terminated, wait for more data */ rval = AT_STATE_WAIT; } else { /* Line terminator found */ pCb->resp_len = (uint8_t)n; rval = AT_STATE_RESP_DATA; } } else { /* No colon, out of sync */ rval = AT_STATE_FLUSH; } } else if (flags & AT_LINE_ASCII) { /* Line contains ascii characters */ if (flags & AT_LINE_CRLF) { /* Line is terminated, string compare can be performed */ code = GetGMRResponseCode(pMem); if (code != AT_GMR_UNKNOWN) { rval = AT_STATE_RESP_GMR; } else { code = GetASCIIResponseCode (pMem); if (code == AT_RESP_ECHO) { /* Command echo received */ rval = AT_STATE_RESP_ECHO; } else if (code != AT_RESP_UNKNOWN) { /* Generic response received */ rval = AT_STATE_RESP_GEN; } else { /* Unknown */ rval = AT_STATE_FLUSH; } } /* Save response code */ pCb->msg_code = code; } else { /* No line termination */ rval = AT_STATE_FLUSH; } } else if (flags & AT_LINE_CTRL) { /* Line contains ascii number */ if (flags & AT_LINE_CRLF) { /* Line is terminated, check content */ code = GetCtrlResponseCode (pMem); pCb->ctrl_code = code; rval = AT_STATE_RESP_CTRL; } else { /* Out of sync */ rval = AT_STATE_FLUSH; } } else if (flags & AT_LINE_TXREQ) { /* Line contains data request character */ rval = AT_STATE_SEND_DATA; } else { /* Unknown */ rval = AT_STATE_FLUSH; } return (rval); } /** Compare received data with predefined strings and return corresponding command code. \return CommandCode_t */ static uint8_t GetCommandCode (BUF_LIST *mem) { uint8_t i, maxi, code; int32_t val; code = CMD_UNKNOWN; maxi = sizeof(List_PlusResp)/sizeof(List_PlusResp[0]); for (i = 0; i < maxi; i++) { val = BufCompareString (List_PlusResp[i].str, 1U, mem); if (val > 0) { /* String matches */ code = i; break; } } return (code); } /** Compare received data with predefined strings and return corresponding response code. \return Generic response code, see AT_RESP_ definitions */ static uint8_t GetASCIIResponseCode (BUF_LIST *mem) { uint8_t i, maxi, code; int32_t val; code = AT_RESP_UNKNOWN; maxi = sizeof(List_ASCIIResp)/sizeof(List_ASCIIResp[0]); for (i = 0; i < maxi; i++) { /* Search for responses (OK, ERROR, FAIL, SEND OK, ...) */ val = BufCompareString (List_ASCIIResp[i].str, 0U, mem); if (val > 0) { /* String matches */ code = i; break; } } return (code); } /** Compare received data with predefined strings and return corresponding response code. \return GMR code, see ESP_GMR_ definitions */ static uint8_t GetGMRResponseCode (BUF_LIST *mem) { uint8_t i, maxi, code; int32_t val; code = AT_GMR_UNKNOWN; maxi = sizeof(List_Gmr)/sizeof(List_Gmr[0]); for (i = 0; i < maxi; i++) { /* Search for responses */ val = BufCompareString (List_Gmr[i].str, 0U, mem); if (val > 0) { /* String matches */ code = i; break; } } return (code); } /** Compare received data with predefined strings and return corresponding control code. Currently supported control strings: ,CONNECT ,CLOSED \return ESP_CTRL_CONNECT, ESP_CTRL_CLOSED */ //static uint32_t GetCtrlResponseCode (BUF_LIST *mem) { static uint8_t GetCtrlResponseCode (BUF_LIST *mem) { uint8_t i, maxi, code; int32_t val; code = AT_CTRL_UNKNOWN; maxi = sizeof(List_Ctrl)/sizeof(List_Ctrl[0]); for (i = 0; i < maxi; i++) { val = BufCompareString (List_Ctrl[i].str, 2U, mem); if (val > 0) { /* String matches */ code = i; break; } } return (code); } /* ------------------------------------------------------------------------- */ /** Get response argument. The return value should indicate continuation pattern. For example when there are multiple responses, as +CWLAP:,,,,,\r\n +CWLAP:,,,,,\r\n +CWLAP:,,,,,\r\n +CWLAP:,,,,,\r\nOK the return value should indicate what follows after \r\n termination: - in case if '+' follows, there is another response to be processed - in case if "OK" follows, response was processed completely. Note that +IPD response format is different and there is no \r\n terminator. \return -2: failed, specified buffer too small (sz of buf) -1: response incomplete, rx buffer empty 0: retrieved, last delimiter: ',' 1: retrieved, last delimiter: ':' 2: retrieved, last delimiter: '\r', response pending ('+') 3: retrieved, last delimiter: '\r', last response ("OK") */ static int32_t GetRespArg (uint8_t *buf, uint32_t sz) { uint32_t i; /* argument size */ uint32_t str; /* string indicator */ int32_t val; uint8_t b; uint8_t d[] = {',', ':', '\r'}; int32_t del; if (BufPeekByte(&(pCb->resp)) == '+') { /* Sync till the first ':' after +command string */ do { val = BufReadByte (&(pCb->resp)); if (val == -1) { return -1; } if (val == ',') { /* Handle "+IPD," response format */ break; } } while (val != ':'); } /* Initialize number of delimiters, string indicator (str) and argument size (i) */ del = sizeof(d); str = 0U; i = 0U; do { if (i == sz) { /* Specified buffer too small */ val = -2; } else { /* Read one byte from response buffer */ val = BufReadByte (&(pCb->resp)); } if (val < 0) { break; } b = (uint8_t)val; if (b == '"') { /* Toggle string indicator */ str ^= 1U; } if ((str == 0U) && ((b == '(') || (b == ')'))) { /* Ignore characters if not within string */ } else { if (str == 0U) { /* Check delimiters (when outside of string) */ for (val = 0; val < del; val++) { if (b == d[val]) { /* Found delimiter, set null terminator */ b = '\0'; break; } } } buf[i] = b; i++; } } while (val >= del); if (val == 2) { /* Check if this is the last response */ /* Clear '\n' character */ BufFlushByte (&(pCb->resp)); /* Peek what is next */ b = (uint8_t)BufPeekByte (&(pCb->resp)); if (b != '+') { val = 3; } } return (val); } /* ------------------------------------------------------------------------- */ /** Retrieve incomming data. Response: +IPD,,[,, ]: This response does not have CRLF terminator, is the number of bytes in . Also note that the format of +IPD is also different in how argument are provided. \param[out] link_id connection ID \param[out] len data length \param[out] remote_ip remote IP (enabled by command AT+CIPDINFO=1) \param[out] remote_port remote port (enabled by command AT+CIPDINFO=1) \return 0: ok, len of data shall be read from buffer negative: buffer empty or packet incomplete */ int32_t AT_Resp_IPD (uint32_t *link_id, uint32_t *len, uint8_t *remote_ip, uint16_t *remote_port) { char *p; uint8_t buf[32]; int32_t val; uint32_t a; /* Argument counter */ uint32_t uval; /* Unsigned value storage */ a = 0U; do { /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val < 0) { break; } p = (char *)&buf[0]; /* Got valid argument */ if (a == 0) { /* Read (buf = integer) */ uval = strtoul (p, &p, 10); *link_id = uval; } else if (a == 1) { /* Read (buf = integer) */ uval = strtoul (p, &p, 10); *len = uval; } else if (a == 2) { /* Read (buf = "xxx.xxx.xxx.xxx") */ if (remote_ip != NULL) { AT_Parse_IP (p, remote_ip); } } else if (a == 3) { /* Read (buf = integer?) */ uval = strtoul (p, &p, 10); if (remote_port != NULL) { *remote_port = (uint16_t)uval; } } else { /* ??? */ break; } /* Increment number of arguments */ a++; if (val == 1) { /* At the ':' delimiter */ val = 0; break; } } while (val >= 0); return (val); } /** Get +LINK_CONN response parameters (see +SYSMSG_CUR). +LINK_CONN:,,"UDP/TCP/SSL",,, , */ int32_t AT_Resp_LinkConn (uint32_t *status, AT_DATA_LINK_CONN *conn) { char *p; uint8_t buf[32]; int32_t val; uint32_t a; /* Argument counter */ uint32_t uval; /* Unsigned value storage */ a = 0U; do { /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val < 0) { break; } /* Ignore ':' delimiter */ if (val != -1) { p = (char *)&buf[0]; /* Got valid argument */ if (a == 0) { /* Read (buf = integer) */ uval = strtoul (p, &p, 10); *status = uval; } else if (a == 1) { /* Read (buf = integer) */ uval = strtoul (p, &p, 10); conn->link_id = (uint8_t)uval; } else if (a == 2) { /* Read type string "UDP/TCP/SSL" */ strcpy (conn->type, p); } else if (a == 3) { /* Read client/server flag */ uval = strtoul (p, &p, 10); conn->c_s = (uint8_t)uval; } else if (a == 4) { /* Read (buf = "xxx.xxx.xxx.xxx") */ AT_Parse_IP (p, conn->remote_ip); } else if (a == 5) { /* Read (buf = integer) */ uval = strtoul (p, &p, 10); conn->remote_port = (uint16_t)uval; } else if (a == 6) { /* Read (buf = integer) */ uval = strtoul (p, &p, 10); conn->local_port = (uint16_t)uval; } else { /* ??? */ break; } /* Increment number of arguments */ a++; } } while ((val != 2) && (val != 3)); if (val == 3) { /* Last response */ val = 0; } else { if (val == 2) { /* Response is pending */ val = 1; } } return (val); } /** Get connection number from the ,CONNECT or ,CLOSED response. \param[in] conn_id connection ID \return execution status: -1: no response (buffer empty) 0: connection number retrieved */ int32_t AT_Resp_CtrlConn (uint32_t *conn_id) { int32_t val; uint8_t b; val = BufReadByte (pMem); if (val != -1) { b = (uint8_t)val; *conn_id = b - '0'; val = 0; } return (val); } /** Get +STA_CONNECTED and +STA_DISCONNECTED response (mac). +STA_CONNECTED:crlf +STA_DISCONNECTED"crlf */ int32_t AT_Resp_StaMac (uint8_t mac[]) { char *p; uint8_t buf[32]; int32_t val; /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val > 1) { p = (char *)&buf[0]; /* Read (buf = "xx:xx:xx:xx:xx:xx") */ AT_Parse_MAC (p, mac); val = 0; } return (val); } /** Get ERR_CODE:0x... response. \param[out] err_code Pointer to 32-bit variable where error code will be stored. \return execution status: -1: no response (buffer empty) 0: error code retrieved */ int32_t AT_Resp_ErrCode (uint32_t *err_code) { char *p; uint8_t buf[32]; int32_t val; uint32_t uval; /* Unsigned value storage */ /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val > 1) { p = (char *)&buf[0]; /* Read error code (buf = hex integer) */ uval = strtoul (p, &p, 16); *err_code = uval; val = 0; } return (val); } /** Get standalone generic response. Standalone generic responses are responses that come without +CMD:data. Parser detect them and deliver them into internal variable. \return generic response code AT_RESP_x */ int32_t AT_Resp_Generic (void) { /* Return generic response */ return (pCb->gen_resp); } /** Test AT startup Generic response is expected. \return 0:OK, -1: error */ int32_t AT_Cmd_TestAT (void) { char out[8]; int32_t n; /* Open AT command (AT+ */ n = sprintf (out, "%s", "AT"); /* Append CRLF and send command */ return (CmdSend(CMD_UART_CUR, out, n)); } /** Restarts the module Generic response is expected. Format: AT+RST \return 0:OK, -1: error */ int32_t AT_Cmd_Reset (void) { char out[16]; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_RST, AT_CMODE_EXEC, out); /* Append CRLF and send command */ return (CmdSend(CMD_RST, out, n)); } /** Check version information Generic response is expected. Format: AT+GMR \return 0:OK, -1: error */ int32_t AT_Cmd_GetVersion (void) { char out[16]; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_GMR, AT_CMODE_EXEC, out); /* Append CRLF and send command */ return (CmdSend(CMD_GMR, out, n)); } /** Get response to GetVersion command \param[out] buf data buffer \param[in] len data buffer size */ int32_t AT_Resp_GetVersion (uint8_t *buf, uint32_t len) { uint8_t crlf[] = {'\r', '\n'}; int32_t cnt = (int32_t)len; int32_t val; int32_t n; /* Initialize total number of read bytes */ val = 0U; while (val < cnt) { /* Check if we can find crlf */ n = BufFind (crlf, 2, &(pCb->resp)); if (n < 0) { break; } /* Add crlf */ n += 2; /* Check if len is ok */ if ((val + n) > cnt) { n = (cnt - val); } val += BufRead(&buf[val], (uint32_t)n, &(pCb->resp)); } /* Flush any leftovers */ do { n = BufReadByte(&(pCb->resp)); } while (n != -1); return (val); } /** Enable or disable command echo. Received commands can be echoed. Generic response is expected. \param[in] enable Echo enable(1) or disable(0) \return 0:OK, -1: error */ int32_t AT_Cmd_Echo (uint32_t enable) { char out[8]; int32_t n; /* Open AT command (AT+ */ n = sprintf (out, "%s%d", "ATE", enable); /* Append CRLF and send command */ return (CmdSend(CMD_UART_CUR, out, n)); } /** Set/Query the current UART configuration Format S: AT+UART_CUR=,,,, Format Q: AT+UART_CUR? Example S: AT+UART_CUR=115200,8,1,0,0\r\n \param[in] at_cmode Command mode (inquiry, set, exec) \param[in] baudrate \param[in] databits \param[in] stop_par_flowc stopbits[5:4], parity[3:2], flow control[1:0] \return 0:OK, -1: error */ int32_t AT_Cmd_ConfigUART (uint32_t at_cmode, uint32_t baudrate, uint32_t databits, uint32_t stop_par_flowc) { char out[32]; uint32_t stopbits, parity, flow_ctrl; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_UART_CUR, AT_CMODE_SET, out); if (at_cmode == AT_CMODE_SET) { stopbits = (stop_par_flowc >> 4) & 0x3; parity = (stop_par_flowc >> 2) & 0x3; flow_ctrl = (stop_par_flowc >> 0) & 0x3; /* Add command arguments */ n += sprintf (&out[n], "%d,%d,%d,%d,%d", baudrate, databits, stopbits, parity, flow_ctrl); } /* Append CRLF and send command */ return (CmdSend(CMD_UART_CUR, out, n)); } /** Get response to ConfigUART command Response Q: +UART_CUR:,,,,\r\n\r\n\OK \param[out] baudrate \param[out] databits \param[out] stop_par_flowc stopbits[5:4], parity[3:2], flow control[1:0] \return */ int32_t AT_Resp_ConfigUART (uint32_t *baudrate, uint32_t *databits, uint32_t *stop_par_flowc) { char *p; uint8_t buf[32]; int32_t val; uint32_t a; /* Argument counter */ uint32_t uval; /* Unsigned value storage */ uint32_t spf; a = 0U; do { /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val < 0) { break; } /* Ignore ':' delimiter */ if (val != -1) { p = (char *)&buf[0]; /* Got valid argument */ if (a == 0) { /* Read */ uval = strtoul (p, &p, 10); /* Note: if S was 115200, Q might return 115273 */ *baudrate = uval; } else if (a == 1) { /* Read */ uval = strtoul (p, &p, 10); *databits = uval; } else if (a == 2) { /* Read */ uval = strtoul (p, &p, 10); spf = (uval & 0x3) << 4; } else if (a == 3) { /* Read */ uval = strtoul (p, &p, 10); spf = (uval & 0x3) << 2; } else if (a == 4) { /* Read */ uval = strtoul (p, &p, 10); spf = (uval & 0x3); *stop_par_flowc = spf; } else { /* Ignore unknown arguments */ break; } /* Increment number of arguments */ a++; } } while ((val != 2) && (val != 3)); if (val == 3) { /* Last response */ val = 0; } else { if (val == 2) { /* Response is pending */ val = 1; } } return (val); } /** Configure the sleep modes. Format: AT+SLEEP= \note Command can be used only in Station mode. Modem-sleep is the default mode. \param[in] sleep_mode sleep mode (0: disabled, 1: Light-sleep, 2: Modem-sleep) \return 0: ok, -1: error */ int32_t AT_Cmd_Sleep (uint32_t at_cmode, uint32_t sleep_mode) { char out[32]; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_SLEEP, AT_CMODE_SET, out); if (at_cmode == AT_CMODE_SET) { /* Add command arguments */ n += sprintf (&out[n], "%d", sleep_mode); } /* Append CRLF and send command */ return (CmdSend(CMD_SLEEP, out, n)); } /** Get response to AutoConnectAP command. Response Q: +SLEEP: Example Q: +SLEEP:2\r\n\r\nOK\r\n\ \param[out] sleep_mode Pointer to variable the where sleep mode is stored \return execution status - negative: error - 0: OK, response retrieved, no more data */ int32_t AT_Resp_Sleep (uint32_t *sleep_mode) { uint8_t buf[32]; int32_t val; char *p; do { /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val < 0) { break; } if (val != 1) { /* Set pointer to extracted value */ p = (char *)&buf[0]; /* Read */ *sleep_mode = p[0] - '0'; break; } } while (val != 3); if (val < 0) { val = -1; } else { val = 0; } return (val); } /** Set maximum value of RF TX power (dBm). Power range for ESP8266: range [0:82], units 0.25dBm ESP32: AT 1.2.0: range[0:11] = {19.5, 19, 18.5, 17, 15, 13, 11, 8.5, 7, 5, 2, -1}dBm AT 2.0.0: range[40,82], units 0.25dBm (value 78 means RF power 78*0.25dBm = 19.5dBm) Response: Generic \param[in] tx_power power value \return 0: ok, -1: error */ int32_t AT_Cmd_TxPower (uint32_t tx_power) { char out[32]; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_RFPOWER, AT_CMODE_SET, out); /* Add command arguments */ n += sprintf (&out[n], "%d", tx_power); /* Append CRLF and send command */ return (CmdSend(CMD_RFPOWER, out, n)); } /** Set current system messages. Command: SYSMSG/SYSMSG_CUR Response: Generic Bit 0: configure the message of quitting passthrough transmission Bit 1: configure the message of establishing a network connection 0 - ,CONNECT 1 - +LINK_CONN:,,"UDP/TCP/SSL",, , \note Only AT set command is available. \param[in] n message configuration bit mask [0:1] */ int32_t AT_Cmd_SysMessages (uint32_t n) { char out[32]; int32_t k; /* Open AT command (AT+ */ k = CmdOpen (CMD_SYSMSG_CUR, AT_CMODE_SET, out); /* Add command arguments */ k += sprintf (&out[k], "%d", n); /* Append CRLF and send command */ return (CmdSend(CMD_SYSMSG_CUR, out, k)); } /** Set/Query the current Wi-Fi mode Format S: AT+CWMODE_CUR= Response Q: AT_Resp_CurrentMode \param[in] at_cmode Command mode (inquiry, set, exec) \param[in] mode Mode, 0: RF disabled (ESP32), 1: station, 2: soft AP, 3: soft AP + station \return 0: OK, -1: error (invalid mode, etc) */ int32_t AT_Cmd_CurrentMode (uint32_t at_cmode, uint32_t mode) { char out[32]; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_CWMODE_CUR, at_cmode, out); if (at_cmode == AT_CMODE_SET) { /* Add command arguments */ n += sprintf (&out[n], "%d", mode); } /* Append CRLF and send command */ return (CmdSend(CMD_CWMODE_CUR, out, n)); } /** Get response to CurrentMode command Response Q: +CWMODE_CUR: Example Q: +CWMODE_CUR:3\r\n\r\n\OK \param[in] mode Mode, 1: station, 2: soft AP, 3: soft AP + station \return 0: OK, -1: error (invalid mode, etc) */ int32_t AT_Resp_CurrentMode (uint32_t *mode) { uint8_t buf[32]; int32_t val; char *p; do { /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val < 0) { break; } if (val != 1) { /* Set pointer to extracted value */ p = (char *)&buf[0]; /* Read */ *mode = p[0] - '0'; break; } } while (val != 2); if (val < 0) { val = -1; } else { val = 0; } return (val); } /** Set/Query connected access point or access point to connect to Format S: AT+CWJAP_CUR=,[,] Format Q: AT+CWJAP_CUR? Response S: "WIFI CONNECTED" "WIFI GOT IP" "" "OK" Response Q: AT_Resp_ConnectAP \param[in] ssid \param[in] pwd \param[in] bssid \return 0: ok, -1: error */ int32_t AT_Cmd_ConnectAP (uint32_t at_cmode, const char *ssid, const char *pwd, const char *bssid) { char out[64]; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_CWJAP_CUR, at_cmode, out); if (at_cmode == AT_CMODE_SET) { /* Add command arguments */ n += sprintf (&out[n], "\"%s\",\"%s\"", ssid, pwd); if (bssid != NULL) { n += sprintf (&out[n], ",\"%s\"", bssid); } } /* Append CRLF and send command */ return (CmdSend(CMD_CWJAP_CUR, out, n)); } /** Response to ESP_ConnectAP command. Response Q: +CWJAP_CUR:,,, Example Q: +CWJAP_CUR:"AP_SSID","xx:xx:xx:xx:xx:xx",6,-60 \todo Handle SET response \return - 1: connection timeout - 2: wrong password - 3: cannot find the target AP - 4: connection failed */ int32_t AT_Resp_ConnectAP (AT_DATA_CWJAP *ap) { char *p; uint8_t buf[32+1]; int32_t val; uint32_t a; /* Argument counter */ uint32_t uval; /* Unsigned value storage */ a = 0U; do { /* Retrieve response argument */ val = GetRespArg (buf, sizeof(buf)); if (val < 0) { break; } if(ap == NULL) { /* Extract and return error code */ return (buf[0] - 0x30); } /* Ignore ':' delimiter */ if (val != -1) { p = (char *)&buf[0]; /* Got valid argument */ if (a == 0) { /* Read (buf = "string") */ strcpy (ap->ssid, (const char *)buf); } else if (a == 1) { /* Read (buf = "xx:xx:xx:xx:xx:xx") */ AT_Parse_MAC (p, ap->bssid); } else if (a == 2) { /* Read */ uval = strtoul (p, &p, 10); ap->ch = (uint8_t)uval; } else if (a == 3) { /* Read */ uval = strtoul (p, &p, 10); ap->rssi = (uint8_t)uval; } else { /* Unknown arguments */ } /* Increment number of arguments */ a++; } } while ((val != 2) && (val != 3)); if (val == 3) { /* Last response */ val = 0; } else { if (val == 2) { /* Response is pending */ val = 1; } } return (val); } /** Disconnect from current Access Point (CWQAP) \return 0:ok, -1: error */ int32_t AT_Cmd_DisconnectAP (void) { char out[32]; int32_t n; /* Open AT command (AT+ */ n = CmdOpen (CMD_CWQAP, AT_CMODE_EXEC, out); /* Append CRLF and send command */ return (CmdSend(CMD_CWQAP, out, n)); } /** Configure local access point (SoftAP must be active) Format: AT+CWSAP_CUR=,,,[,][,