/* -----------------------------------------------------------------------------
 * 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 <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>

#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:<sta_mac>) */
            AT_Notify (AT_NOTIFY_STATION_CONNECTED, NULL);
          }
          else if (pCb->resp_code == CMD_STA_DISCONNECTED) {
            /* Station disconnected from local AP (+STA_DISCONNECTED:<sta_mac>) */
            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) {
          /* <conn_id>,CONNECT */
          AT_Notify (AT_NOTIFY_CONNECTION_OPEN, NULL);
        }
        else if (pCb->ctrl_code == AT_CTRL_CLOSED) {
          /* <conn_id>,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')) {
      /* [<link ID>,] 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:
  <conn id>,CONNECT
  <conn id>,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:<ecn>,<ssid>,<rssi>,<mac>,<ch>,<freq offset>\r\n
  +CWLAP:<ecn>,<ssid>,<rssi>,<mac>,<ch>,<freq offset>\r\n
  +CWLAP:<ecn>,<ssid>,<rssi>,<mac>,<ch>,<freq offset>\r\n
  +CWLAP:<ecn>,<ssid>,<rssi>,<mac>,<ch>,<freq offset>\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,<link ID>,<len>[,<remote IP>, <remote port>]:<data>

  This response does not have CRLF terminator, <len> is the number of bytes in <data>.
  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 <link ID> (buf = integer) */
      uval = strtoul (p, &p, 10);

      *link_id = uval;
    }
    else if (a == 1) {
      /* Read <len> (buf = integer) */
      uval = strtoul (p, &p, 10);

      *len = uval;
    }
    else if (a == 2) {
      /* Read <remote_ip> (buf = "xxx.xxx.xxx.xxx") */
      if (remote_ip != NULL) {
        AT_Parse_IP (p, remote_ip);
      }
    }
    else if (a == 3) {
      /* Read <remote_port> (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:<status_type>,<link_id>,"UDP/TCP/SSL",<c/s>,<remote_ip>,
                          <remote_port>,<local_port>
*/
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 <status_type> (buf = integer) */
        uval = strtoul (p, &p, 10);

        *status = uval;
      }
      else if (a == 1) {
        /* Read <link_id> (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 <remote_ip> (buf = "xxx.xxx.xxx.xxx") */
        AT_Parse_IP (p, conn->remote_ip);
      }
      else if (a == 5) {
        /* Read <remote_port> (buf = integer) */
        uval = strtoul (p, &p, 10);

        conn->remote_port = (uint16_t)uval;
      }
      else if (a == 6) {
        /* Read <local_port> (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 <conn_id>,CONNECT or <conn_id>,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:<sta_mac>crlf
  +STA_DISCONNECTED"<sta_mac>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 <sta_mac> (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+<cmd><mode> */
  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+<cmd><mode> */
  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+<cmd><mode> */
  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+<cmd><mode> */
  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=<baudrate>,<databits>,<stopbits>,<parity>,<flow control>
  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+<cmd><mode> */
  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:<baudrate>,<databits>,<stopbits>,<parity>,<flow control>\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 <baudrate> */
        uval = strtoul (p, &p, 10);

        /* Note: if S was 115200, Q might return 115273 */
        *baudrate = uval;
      }
      else if (a == 1) {
        /* Read <databits> */
        uval = strtoul (p, &p, 10);

        *databits = uval;
      }
      else if (a == 2) {
        /* Read <stopbits> */
        uval = strtoul (p, &p, 10);

        spf = (uval & 0x3) << 4;
      }
      else if (a == 3) {
        /* Read <parity> */
        uval = strtoul (p, &p, 10);

        spf = (uval & 0x3) << 2;
      }
      else if (a == 4) {
        /* Read <flow control> */
        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=<sleep mode>

  \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+<cmd><mode> */
  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:<sleep mode>
  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> */
      *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+<cmd><mode> */
  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 - <Link ID>,CONNECT
         1 - +LINK_CONN:<status_type>,<link_id>,"UDP/TCP/SSL",<remote_ip>,
                        <remote_port>,<local_port>

  \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+<cmd><mode> */
  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=<mode>

  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+<cmd><mode> */
  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:<mode>
  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> */
      *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=<ssid>,<pwd>[,<bssid>]
  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+<cmd><mode> */
  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:<ssid>,<bssid>,<channel>,<rssi>
  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 <ssid> (buf = "string") */
        strcpy (ap->ssid, (const char *)buf);
      }
      else if (a == 1) {
        /* Read <bssid> (buf = "xx:xx:xx:xx:xx:xx") */
        AT_Parse_MAC (p, ap->bssid);
      }
      else if (a == 2) {
        /* Read <ch> */
        uval = strtoul (p, &p, 10);

        ap->ch = (uint8_t)uval;
      }
      else if (a == 3) {
        /* Read <rssi> */
        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+<cmd><mode> */
  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=<ssid>,<pwd>,<chl>,<ecn>[,<max conn>][,<ssid hidden>]
*/
int32_t AT_Cmd_ConfigureAP (uint32_t at_cmode, AT_DATA_CWSAP *cfg) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CWSAP_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "\"%s\",\"%s\",%d,%d", cfg->ssid, cfg->pwd, cfg->ch, cfg->ecn);

    /* Add optional arguments */
    n += sprintf (&out[n], ",%d,%d", cfg->max_conn, cfg->ssid_hide);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CWSAP_CUR, out, n));
}

/**
  Get response to ConfigureAP command

  Format: AT+CWSAP_CUR=<ssid>,<pwd>,<chl>,<ecn>,<max conn>,<ssid hidden>

  \param[in]  cfg       AP configuration structure
  \return
*/
int32_t AT_Resp_ConfigureAP (AT_DATA_CWSAP *cfg) {
  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;
    }

    /* Ignore ':' delimiter */
    if (val != -1) {
      p = (char *)&buf[0];

      /* Got valid argument */
      if (a == 0) {
      /* Read <ssid> */
        strcpy (cfg->ssid, (const char *)buf);
      }
      else if (a == 1) {
        /* Read <pwd> */
        strcpy (cfg->pwd, (const char *)buf);
      }
      else if (a == 2) {
        /* Read <ch> */
        uval = strtoul (p, &p, 10);

        cfg->ch = (uint8_t)uval;
      }
      else if (a == 3) {
        /* Read <ecn> */
        cfg->ecn = p[0] - '0';
      }
      else if (a == 4) {
        /* Read <max conn> */
        cfg->max_conn = p[0] - '0';

      }
      else if (a == 5) {
        /* Read <ssid hidden> */
        cfg->ssid_hide = p[0] - '0';
      }
      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);
}


/**
  List available Access Points (CWLAP)
*/
int32_t AT_Cmd_ListAP (void) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CWLAP, AT_CMODE_EXEC, out);

  /* Append CRLF and send command */
  return (CmdSend(CMD_CWLAP, out, n));
}

/**
  Process input string

  Format: +CWLAP:<ecn>,<ssid>,<rssi>,<mac>,<ch>,<freq offset>\r\nOK (AT 0.30)
  Format: +CWLAP:<ecn>,<ssid>,<rssi>,<mac>,<ch>,<freq offset>,
                 <freq cali>,<pairwise_cipher>,<group_cipher>,<bgn>,<wps>\r\n\OK (AT 2.2.0)

  \return execution status
          - negative: error
          - 0: access point list is empty
          - 1: access point list contains more data
*/
int32_t AT_Resp_ListAP (AT_DATA_CWLAP *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;
    }

    /* Ignore ':' delimiter */
    if (val != 1) {
      p = (char *)&buf[0];

      /* Got valid argument */
      if (a == 0) {
        /* Read <ecn> */
        ap->ecn = p[0] - '0';
      }
      else if (a == 1) {
        /* Read <ssid> */
        strcpy (ap->ssid, (const char *)buf);
      }
      else if (a == 2) {
        /* Read <rssi> */
        uval = strtoul (p, &p, 10);

        ap->rssi = (int8_t)uval;
      }
      else if (a == 3) {
        /* Read <mac> (p == "xx:xx:xx:xx:xx:xx") */
        AT_Parse_MAC (p, ap->mac);
      }
      else if (a == 4) {
        /* Read <ch> */
        uval = strtoul (p, &p, 10);

        ap->ch = (uint8_t)uval;
      }
      else if (a == 5) {
        /* Get <freq offset> */
        uval = strtoul (p, &p, 10);

        ap->freq_offs = (uint16_t)uval;
      }
      else {
        /* Ignore 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);
}


/**
  Set station MAC address

  Do not set the same MAC address for Station and SoftAP.

  Format: AT+CIPSTAMAC_CUR=<mac>

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  mac       Pointer to 6 byte array containing MAC address
*/
int32_t AT_Cmd_StationMAC (uint32_t at_cmode, const uint8_t mac[]) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSTAMAC_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "\"%02X:%02X:%02X:%02X:%02X:%02X\"",
                            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSTAMAC_CUR, out, n));
}

/**
  Get response to StationMAC command

  String form: +CIPSTAMAC_CUR:<mac>\r\n\r\nOK

  \param[out]   mac   Pointer to 6 byte array where MAC address will be stored
  \return execution status
          -1 : error
           0 : MAC retrieved
*/
int32_t AT_Resp_StationMAC (uint8_t mac[]) {
  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 MAC string: "xx:xx:xx:xx:xx:xx" */
      p = (char *)&buf[0];

      /* Parse MAC string */
      AT_Parse_MAC (p, mac);
    }
  }
  while (val != 3);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}


/**
  Set/Query the Access Point MAC address

  Do not set the same MAC address for Station and SoftAP.

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  mac       Pointer to 6 byte array containing MAC address
  \return 
*/
int32_t AT_Cmd_AccessPointMAC (uint32_t at_cmode, uint8_t mac[]) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPAPMAC_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "\"%02X:%02X:%02X:%02X:%02X:%02X\"",
                            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPAPMAC_CUR, out, n));
}

/**
  Get response to AccessPointMAC command
  
  \param[out]   mac   Pointer to 6 byte array where MAC address will be stored
  \return execution status
*/
int32_t AT_Resp_AccessPointMAC (uint8_t mac[]) {
  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 MAC string: "xx:xx:xx:xx:xx:xx" */
      p = (char *)&buf[0];

      /* Parse MAC string, skip initial quote */
      AT_Parse_MAC (p, mac);
    }
  }
  while (val != 3);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}


/**
  Set/Query current IP address of the local station

  Format S: AT+CIPSTA_CUR=<ip>,<gateway>,<netmask>
  Format Q: AT+CIPSTA_CUR?

  Example: AT+CIPSTA_CUR="192.168.1.100","192.168.6.1","255.255.255.0"
  
  Response: AT_Resp_StationIP
*/
int32_t AT_Cmd_StationIP (uint32_t at_cmode, uint8_t ip[], uint8_t gw[], uint8_t mask[]) {
  char out[70];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSTA_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "\"%d.%d.%d.%d\"", ip[0], ip[1], ip[2], ip[3]);
    
    if (gw != NULL) {
      /* Add gateway */
      n += sprintf (&out[n], ",\"%d.%d.%d.%d\"", gw[0], gw[1], gw[2], gw[3]);

      if (mask != NULL) {
        /* Add netmask */
        n += sprintf (&out[n], ",\"%d.%d.%d.%d\"", mask[0], mask[1], mask[2], mask[3]);
      }
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSTA_CUR, out, n));
}

/**
  Get response to StationIP command

  Response: +CIPSTA_CUR:<ip>
            +CIPSTA_CUR:<gateway>
            +CIPSTA_CUR:<netmask>

  Example:  +CIPSTA_CUR:ip:"192.168.1.155"

  \param[out]   addr    Pointer to 4 byte array where the address is stored
  \return execution status
          - negative: error
          - 0: address list is empty
          - 1: address list contains more data
*/
int32_t AT_Resp_StationIP (uint8_t addr[]) {
  uint8_t  buf[32]; /* Argument buffer */
  int32_t  val;     /* Control value */
  char *p;

  do {
    /* Retrieve response argument */
    val = GetRespArg (buf, sizeof(buf));

    if (val < 0) {
      break;
    }

    /* Ignore ':' delimiter */
    if (val != 1) {
      /* Set pointer to start of string */
      p = (char *)&buf[0];

      /* Parse IP address (xxx.xxx.xxx.xxx) */
      AT_Parse_IP (p, addr);
    }
  }
  while ((val != 2) && (val != 3));

  if (val == 3) {
    /* Last response */
    val = 0;
  }
  else {
    if (val == 2) {
      /* Response is pending */
      val = 1;
    }
  }

  return (val);
}

/**
  Set/Query current IP address of the local access point

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  ip    IP address
  \param[in]  gw    Gateway address
  \param[in]  mask  Netmask
  \return 
*/
int32_t AT_Cmd_AccessPointIP (uint32_t at_cmode, uint8_t ip[], uint8_t gw[], uint8_t mask[]) {
  char out[70];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPAP_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "\"%d.%d.%d.%d\"", ip[0], ip[1], ip[2], ip[3]);
    
    if (gw != NULL) {
      /* Add gateway */
      n += sprintf (&out[n], ",\"%d.%d.%d.%d\"", gw[0], gw[1], gw[2], gw[3]);

      if (mask != NULL) {
        /* Add netmask */
        n += sprintf (&out[n], ",\"%d.%d.%d.%d\"", mask[0], mask[1], mask[2], mask[3]);
      }
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPAP_CUR, out, n));
}

int32_t AT_Resp_AccessPointIP (uint8_t addr[]) {
  return AT_Resp_StationIP (addr);
}


/**
  Set user defined DNS servers

  Format S: AT+CIPDNS_CUR=<enable>[,<DNS server0>,<DNS server1>]
  Format S: AT+CIPDNS_CUR?

  Example: AT+CIPDNS_CUR=1,"192.168.0.1"
  
  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  enable    Enable (1) or disable (0) user defined DNS servers
  \param[in]  dns0      Primary DNS server
  \param[in]  dns1      Secondary DNS server
*/
int32_t AT_Cmd_DNS (uint32_t at_cmode, uint32_t enable, uint8_t dns0[], uint8_t dns1[]) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPDNS_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d", enable);
    
    if (dns0 != NULL) {
      /* Add DNS 0 */
      n += sprintf (&out[n], ",\"%d.%d.%d.%d\"", dns0[0], dns0[1], dns0[2], dns0[3]);

      if (dns1 != NULL) {
        /* Add DNS 1 */
        n += sprintf (&out[n], ",\"%d.%d.%d.%d\"", dns1[0], dns1[1], dns1[2], dns1[3]);
      }
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPDNS_CUR, out, n));
}

#if (AT_VARIANT == AT_VARIANT_ESP32) && (AT_VERSION >= AT_VERSION_2_0_0_0)
/**
  Get response to DNS command

  Example: +CIPDNS=1,"192.168.0.1","192.168.0.2"\r\n

  \param[out]   enable  Pointer to variable where enable flag will be stored
  \param[out]   dns0    Pointer to 4 byte array where DNS 0 address will be stored
  \param[out]   dns1    Pointer to 4 byte array where DNS 1 address will be stored
  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_DNS (uint32_t *enable, uint8_t dns0[], uint8_t dns1[]) {
  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 <lease time> (buf = integer) */
        uval = strtoul (&p[0], &p, 10);

        if (enable != NULL) {
          *enable = uval;
        }
      }
      else if (a == 1) {
        /* Parse DNS0 */
        AT_Parse_IP (p, dns0);
      }
      else if (a == 2) {
        /* Parse DNS1 */
        AT_Parse_IP (p, dns1);
      }
      else {
        /* ??? */
        break;
      }

      /* Increment number of arguments */
      a++;
    }
  }
  while (val != 3);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}
#else
/**
  Get response to DNS command

  Example: +CIPDNS_CUR=192.168.0.1

  \param[out]   addr    Pointer to 4 byte array where the address is stored
  \return execution status
          - negative: error
          - 0: address list is empty
          - 1: address list contains more data
*/
int32_t AT_Resp_DNS (uint8_t addr[]) {
  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 start of string */
      p = (char *)&buf[0];
      
      /* Parse IP address */
      AT_Parse_IP (p, addr);
      break;
    }
  }
  while ((val != 2) && (val != 3));

  if (val == 3) {
    /* Last response */
    val = 0;
  }
  else {
    if (val == 2) {
      /* Response is pending */
      val = 1;
    }
  }

  return (val);
}
#endif


#if (AT_VARIANT == AT_VARIANT_ESP32)
/**
  Set/Query DHCP state

  Query: AT+CWDHCP?
  Set:   AT+CWDHCP=<operate>,<mode>

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  operate   0: disable DHCP, 1: enable DHCP
  \param[in]  mode      Bit0: set station, Bit1: set soft-ap
*/
int32_t AT_Cmd_DHCP (uint32_t at_cmode, uint32_t operate, uint32_t mode) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CWDHCP_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d,%d", operate, mode);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CWDHCP_CUR, out, n));
}
#else
/**
  Set/Query DHCP state

  Format S: AT+CWDHCP_CUR=<mode>,<en>
  Format Q: AT+CWDHCP_CUR?

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  mode      0: set soft-ap, 1: set station, 2: set both (soft-ap and station)
  \param[in]  enable    0: disable DHCP, 1: enable DHCP
*/
int32_t AT_Cmd_DHCP (uint32_t at_cmode, uint32_t mode, uint32_t enable) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CWDHCP_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d,%d", mode, enable);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CWDHCP_CUR, out, n));
}
#endif


/**
  Get response to DHCP command

  Response Q: +CWDHCP_CUR:<mode>
  Example  Q: +CWDHCP_CUR:3\r\n\r\n\OK

  \param[out]   mode    Pointer to variable the DHCP mode is stored
  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_DHCP (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> */
      *mode = (p[0] - '0') & 0x03U;
      break;
    }
  }
  while (val != 3);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}


/**
  Set/Query DHCP IP address lease time and range for local access point

  AT command is enabled when device runs as SoftAP, and when DHCP is enabled.
  The IP address should be in the same network segment as the IP address of device 
  SoftAP. SoftAP IP address must be different than <start IP> or <end IP>.

  Format S: AT+CWDHCPS_CUR=<enable>,<lease time>,<start IP>,<end IP>
  Format Q: AT+CWDHCPS_CUR?

  Example: AT+CWDHCPS_CUR=1,3,"192.168.4.10","192.168.4.15"

  \param[in]  at_cmode    Command mode (inquiry, set, exec)
  \param[in]  en_tlease   bit[16] 0: disable setting, 1: enable setting the IP range
                          bits[15:0] lease time in minutes, range [1, 2880]
  \param[in]  ip_start    first IP in range that can be obtained from DHCP server
  \param[in]  ip_end      last IP in range that can be obtained from DHCP server
*/
int32_t AT_Cmd_RangeDHCP (uint32_t at_cmode, uint32_t en_tlease, uint8_t ip_start[], uint8_t ip_end[]) {
  char out[64];
  int32_t n;
  uint32_t en      = en_tlease >> 16;
  uint32_t t_lease = en_tlease & 0xFFFF;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CWDHCPS_CUR, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    if (en != 0) {
      n += sprintf (&out[n], "%d,%d,\"%d.%d.%d.%d\",\"%d.%d.%d.%d\"",
                             en,
                             t_lease,
                             ip_start[0], ip_start[1], ip_start[2], ip_start[3],
                             ip_end[0],   ip_end[1],   ip_end[2],   ip_end[3]);
    }
    else {
     /* AT+CWDHCPS_CUR=0 */
     n += sprintf (&out[n], "%d", en);
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CWDHCPS_CUR, out, n));
}

/**
  Get response to RangeDHCP command

  Response Q: +CWDHCPS_CUR=<lease time>,<start IP>,<end IP>
  Example  Q: +CWDHCPS_CUR:???

  \param[out]  t_lease  lease time in minutes, range[1, 2880]
  \param[out]  ip_start first IP in range that can be obtained from DHCP server
  \param[out]  ip_end   last IP in range that can be obtained from DHCP server

  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_RangeDHCP (uint32_t *t_lease, uint8_t ip_start[], uint8_t ip_end[]) {
  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;
    }

    /* Ignore ':' delimiter */
    if (val != -1) {
      p = (char *)&buf[0];

      /* Got valid argument */
      if (a == 0) {
      /* Read <lease time> (buf = integer) */
        uval = strtoul (&p[0], &p, 10);

        *t_lease = uval;
      }
      else if (a == 1) {
        /* Read <start IP> (buf = "xxx.xxx.xxx.xxx") */
        AT_Parse_IP (p, ip_start);
      }
      else if (a == 2) {
        /* Read <end IP> (buf = "xxx.xxx.xxx.xxx") */
        AT_Parse_IP (p, ip_end);
      }
      else {
        /* ??? */
        break;
      }

      /* Increment number of arguments */
      a++;
    }
  }
  while (val != 3);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}


/**
  Set/Query Auto-Connect to the AP

  Format: AT+CWAUTOCONN=<enable>

  Generic response is expected.

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  enable    0:disable, 1:enable auto-connect on power-up

  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_AutoConnectAP (uint32_t at_cmode, uint32_t enable) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CWAUTOCONN, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command argument */
    n += sprintf (&out[n], "%d", enable);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CWAUTOCONN, out, n));
}

/**
  Get response to AutoConnectAP command

  Response Q: +CWAUTOCONN:<enable>
  Example  Q: +CWAUTOCONN:0\r\n\OK\r\n\

  \param[out]   enable  Pointer to variable the enable status is stored
  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_AutoConnectAP (uint32_t *enable) {
  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> */
      *enable = p[0] - '0';
      break;
    }
  }
  while (val != 3);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}


/**
  Retrieve the list of IP (stations) connected to access point

  Format: AT+CWLIF
*/
int32_t AT_Cmd_ListIP (void) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CWLIF, AT_CMODE_EXEC, out);

  /* Append CRLF and send command */
  return (CmdSend(CMD_CWLIF, out, n));
}

/**
  Get response to ListIP command

  Format: +CWLIF=<IP addr>,<mac>
  
  Example: +CWLIF:192.168.4.2,xx:xx:xx:xx:xx:xx

  \param[out] ip  Pointer to 4 byte array where IP address will be stored
  \param[out] mac Pointer to 6 byte array where MAC address will be stored
  \return execution status
          - negative: error
          - 0: list is empty
          - 1: list contains more data
*/
int32_t AT_Resp_ListIP (uint8_t ip[], uint8_t mac[]) {
  uint8_t  buf[32]; /* Argument buffer */
  int32_t  val;     /* Control value */
  uint32_t a;       /* Argument counter */
  char *p;

  a = 0U;

  do {
    /* Retrieve response argument */
    val = GetRespArg (&buf[1], sizeof(buf)-1);

    if (val < 0) {
      break;
    }

    /* Ignore ':' delimiter */
    if (val != 1) {
      /* Set pointer to start of string */
      p = (char *)&buf[0];

      if (a == 0) {
        /* Parse IP address (xxx.xxx.xxx.xxx) */
        AT_Parse_IP (p, ip);
      }
      else {
        /* Parse MAC string (xx:xx:xx:xx:xx:xx) */
        AT_Parse_MAC (p, mac);
      }

      /* Increment number of arguments */
      a++;
    }
  }
  while (val != 2);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}

/* TCP/IP Related AT Commands ---------------------- */

/**
  Get the connection status.

  Format: AT+CIPSTATUS

  \note Only AT command execute mode is available (AT_CMODE_EXEC)

  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_GetStatus (uint32_t at_cmode) {
  char out[64];
  int32_t n;

  if (at_cmode != AT_CMODE_EXEC) {
    return -1;
  }

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSTATUS, at_cmode, out);

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSTATUS, out, n));
}

/**
  Get response to GetStatus command

  Format:  +CIPSTATUS:<link ID>,<type>,<remote IP>,<remote port>,<local port>,<tetype>
  Example: +CIPSTATUS:0,"TCP","192.168.4.2",54600,80,1

  \param[out] conn         connection info

  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_GetStatus (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 <link_id> (buf = integer) */
        uval = strtoul (p, &p, 10);

        conn->link_id = (uint8_t)uval;
      }
      else if (a == 1) {
        /* Read type string "UDP/TCP/SSL" */
        strcpy (conn->type, &p[1]);
      }
      else if (a == 2) {
        /* Read <remote_ip> (buf = "xxx.xxx.xxx.xxx") */
        AT_Parse_IP (p, conn->remote_ip);
      }
      else if (a == 3) {
        /* Read <remote_port> (buf = integer?) */
        uval = strtoul (p, &p, 10);

        conn->remote_port = (uint16_t)uval;
      }
      else if (a == 4) {
        /* Read <local_port> (buf = integer?) */
        uval = strtoul (p, &p, 10);

        conn->local_port = (uint16_t)uval;
      }
      else if (a == 5) {
        /* Read client/server flag */
        uval = strtoul (p, &p, 10);

        conn->c_s = (uint8_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);
}


/**
  Resolve IP address from host name.

  Format S: AT+CIPDOMAIN=<domain name>

  Example S: AT+CIPDOMAIN="iot.espressif.cn"

  \param[in]  at_cmode    Command mode (inquiry, set, exec)
  \param[in]  domain      domain name string (www.xxx.com)
  
  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_DnsFunction (uint32_t at_cmode, const char *domain) {
  char out[280];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPDOMAIN, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "\"%s\"", domain);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPDOMAIN, out, n));
}

/**
  Get response to DnsFunction command

  Format: +CIPDOMAIN:<IP address>

  Example: +CIPDOMAIN:192.168.4.2   (ESP)
  Example: +CIPDOMAIN:"192.168.4.2" (WIZ)

  \param[out] ip          IP address

  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_DnsFunction (uint8_t ip[]) {
  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 start of string */
      p = (char *)&buf[0];

      /* Parse IP address */
      AT_Parse_IP (p, ip);
      break;
    }
  }
  while (val != 2);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}


/**
  Establish TCP connection.

  Format S: AT+CIPSTART=<link ID>,<type>,<remote IP>,<remote port>[,<TCP keep alive>] (AT+CIPMUX=1)

  Example S: AT+CIPSTART=1,"TCP","192.168.1.100",8000

  Generic response is expected.

  \param[in]  at_cmode    Command mode (inquiry, set, exec)
  \param[in]  link_id     ID of the connection
  \param[in]  ip          remote ip number
  \param[in]  port        remote port number
  \param[in]  keep_alive  TCP keep alive, 0:disable or [1:7200] in seconds to enable keep alive
\param[in]	mode				TCP mode (0: normal tcp connect 1:SSL connect)
  
  \return execution status:
          0: OK, -1 on error
	\note Supports the establishment of up to 1 SSL connection
*/
int32_t AT_Cmd_ConnOpenTCP (uint32_t at_cmode, uint32_t link_id, const uint8_t ip[], uint16_t port, uint16_t keep_alive, uint32_t mode) {
  char out[64];
  int32_t n;
	char mode_str[][3]= {"TCP","SSL"};
	
  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSTART, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d,\"%s\",\"%d.%d.%d.%d\",%d",
                           link_id,
                           mode_str[mode],
                           ip[0], ip[1], ip[2], ip[3],
                           port);
    if (keep_alive != 0U) {
      n += sprintf (&out[n], ",%d", keep_alive);
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSTART, out, n));
}


/**
  Establish UDP transmission.

  Format S: AT+CIPSTART=<link ID>,<type>,<remote IP>,<remote port>[,<UDP local port>, <UDP mode>] (AT+CIPMUX=1)

  Example S: AT+CIPSTART=1,"UDP","192.168.1.100",8000

  Generic response is expected.

  \param[in]  at_cmode    Command mode (inquiry, set, exec)
  \param[in]  link_id     ID of the connection
  \param[in]  r_ip        remote ip number
  \param[in]  r_port      remote port number
  \param[in]  l_port      local port (ESP8266 rejects zero)
  \param[in]  mode        UDP mode (0:dst peer entity will not change, 1:will change once, 2:allowed to change)
  
  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_ConnOpenUDP (uint32_t at_cmode, uint32_t link_id, const uint8_t r_ip[], uint16_t r_port, uint16_t l_port, uint32_t mode) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSTART, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d,\"%s\",\"%d.%d.%d.%d\",%d",
                           link_id,
                           "UDP",
                           r_ip[0], r_ip[1], r_ip[2], r_ip[3],
                           r_port);
    if (l_port != 0U) {
      /* Add optional arguments */
      n += sprintf (&out[n], ",%d,%d", l_port, mode);
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSTART, out, n));
}

/**
  Establish SSL connection.

  Format S: AT+CIPSTART=<link ID>,<type>,<remote IP>,<remote port>[,<TCP keep alive>] (AT+CIPMUX=1)

  Example S: AT+CIPSTART=0,"SSL","192.168.1.100",8000

  Generic response is expected.

  \param[in]  at_cmode    Command mode (inquiry, set, exec)
  \param[in]  link_id     ID of the connection
  \param[in]  ip          remote ip number
  \param[in]  port        remote port number
  \param[in]  keep_alive  TCP keep alive, 0:disable or [1:7200] in seconds to enable keep alive
  
  \return execution status:
          0: OK, -1 on error
	\note Supports the establishment of up to 1 SSL connection
*/

/**
  Close the TCP/UDP/SSL connection.

  Format S: AT+CIPCLOSE=<link_id>
  Format E: AT+CIPCLOSE
  
  Example: AT+CIPCLOSE=4;

  If ID of the connection is 5, all connections will be closed.
  Generic response is expected.

  \param[in]  at_cmode    Command mode (inquiry, set, exec)
  \param[in]  link_id     ID of the connection

  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_ConnectionClose (uint32_t at_cmode, uint32_t link_id) {
  char out[32];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPCLOSE, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d", link_id);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPCLOSE, out, n));
}

/**
  Get ping response time.

  Command: PING
  Format:  AT+PING=<IP>
  Example: AT+PING="192.168.1.1"
  Example: AT+PING="www.xxx.com"

  Domain can be specified either by IP or by domain name string - only one should be
  specified.

  \param[in]  at_cmode    Command mode (inquiry, set, exec)
  \param[in]  ip          IP address (xxx.xxx.xxx.xxx)
  \param[in]  domain      domain name string (www.xxx.com)
  
  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_Ping (uint32_t at_cmode, const uint8_t ip[], const char *domain) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_PING, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    if (ip != NULL) {
      n += sprintf (&out[n], "\"%d.%d.%d.%d\"", ip[0], ip[1], ip[2], ip[3]);
    }
    if (domain != NULL) {
      n += sprintf (&out[n], "\"%s\"", domain);
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_PING, out, n));
}

/**
  Get response to Ping command.
  
  Response: +<time>
  Response: +timeout
  Example:  +5

  \param[out] time        ping response time in ms

  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data

  \return execution status
          - negative: error
          - 0: list is empty
          - 1: list contains more data
*/
int32_t AT_Resp_Ping (uint32_t *time) {
  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];

      if ((p[0] >= '0') && (p[0] <= '9')) {
        /* Integer value */
        *time = strtoul (&p[0], &p, 10);
      } else {
        /* Got "timeout" string */
        *time |= 0x80000000U;
      }
      break;
    }
  }
  while (val != 2);

//  if (val < 0) {
//    val = -1;
//  } else {
    val = 0;
//  }

  return (val);
}


/**
  Set send data command.

  Command: CIPSEND
  Format:  AT+CIPSEND=<link_id>,<length>[,<remote IP>,<remote port>]

  \param[in]  at_cmode    command mode (inquiry, set, exec)
  \param[in]  link_id     connection id
  \param[in]  length      number of bytes to send
  \param[in]  remote_ip   remote IP (UDP transmission)
  \param[in]  remote_port remote port (UDP transmission)
  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_SendData (uint32_t at_cmode, uint32_t link_id, uint32_t length, const uint8_t remote_ip[], uint16_t remote_port) {
  char out[64];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSEND, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d,%d", link_id, length);

    if (remote_ip != 0U) {
      /* Add optional arguments */
      n += sprintf (&out[n], ",\"%d.%d.%d.%d\",%d",
                              remote_ip[0], remote_ip[1], remote_ip[2], remote_ip[3],
                              remote_port);
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSEND, out, n));
}


/**
  Set/Query connection type (single, multiple connections)

  Command: CIPMUX

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  mode      0:single connection, 1:multiple connections
  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_ConnectionMux (uint32_t at_cmode, uint32_t mode) {
  char out[32];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPMUX, 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_CIPMUX, out, n));
}

/**
  Get response to ConnectionMux command

  Response Q: +CIPMUX:<mode>
  Example  Q: +CIPMUX:1

  \param[out] mode      0:single connection, 1:multiple connections

  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_ConnectionMux (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> */
      *mode = p[0] - '0';
      break;
    }
  }
  while (val < 2);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}

/**
  Create or delete TCP server.

  Command: CIPSERVER
  Format:  AT+CIPSERVER=<mode>[,<port>]

  Generic response is expected.

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  mode      0:delete server, 1:create server
  \param[in]  port      port number
  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_TcpServer (uint32_t at_cmode, uint32_t mode, uint16_t port) {
  char out[32];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSERVER, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d", mode);
    
    if (port != 0U) {
      /* Add optional port number */
      n += sprintf (&out[n], ",%d", port);
    }
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSERVER, out, n));
}

/**
  Set the maximum connection allowed by server.

  Command: CIPSERVERMAXCONN
  Format:  AT+CIPSERVERMAXCONN=<num>

  \param[in]  at_cmode  Command mode (inquiry, set, exec)
  \param[in]  num       maximum number of clients allowed to connect
  \return execution status:
          0: OK, -1 on error
*/
int32_t AT_Cmd_TcpServerMaxConn (uint32_t at_cmode, uint32_t num) {
  char out[32];
  int32_t n;

  /* Open AT command (AT+<cmd><mode> */
  n = CmdOpen (CMD_CIPSERVERMAXCONN, at_cmode, out);

  if (at_cmode == AT_CMODE_SET) {
    /* Add command arguments */
    n += sprintf (&out[n], "%d", num);
  }

  /* Append CRLF and send command */
  return (CmdSend(CMD_CIPSERVERMAXCONN, out, n));
}

/**
  Get response to TcpServerMaxConn command

  Response Q: +CIPSERVERMAXCONN:<num>
  Example  Q: +CIPSERVERMAXCONN:1

  \param[in]  num       maximum number of clients allowed to connect

  \return execution status
          - negative: error
          - 0: OK, response retrieved, no more data
*/
int32_t AT_Resp_TcpServerMaxConn (uint32_t *num) {
  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 <num> */
      *num = p[0] - '0';
      break;
    }
  }
  while (val < 2);

  if (val < 0) {
    val = -1;
  } else {
    val = 0;
  }

  return (val);
}

/* ------------------------------------------------------------------------- */

/**
  Open AT command string (construct string: AT+<cmd><mode>)

  \param[in]  cmd_code  command code
  \param[in]  cmd_mode  command mode (AT_CMODE_QUERY, AT_CMODE_SET)
  \param[out] buf       AT command string output buffer
*/
static int32_t CmdOpen (uint8_t cmd_code, uint32_t cmd_mode, char *buf) {
  const char *Ctrl_AT = "AT+";
  const char *at;
  const char *cmd;
  char  chm;
  int32_t n;

  at  = Ctrl_AT;
  cmd = CmdString (cmd_code);

  if (cmd_mode == AT_CMODE_QUERY) {
    chm = '?';
  }
  else if (cmd_mode == AT_CMODE_SET) {
    chm = '=';
  }
  else {
    chm = '\0';
  }

  n = sprintf (buf, "%s%s%c", at, cmd, chm);

  if (chm == '\0') {
    n -= 1;
  }

  return (n);
}


/**
  Send AT command string (append crlf to command string and send)

  \param[in]  cmd   command code 
  \param[in]  buf   string buffer
  \param[in]  num   number of bytes from buf to send

  \return 0:OK, -1: error
*/
static int32_t CmdSend (uint8_t cmd, char *buf, int32_t num) {
  const char *Ctrl_CRLF = "\r\n";
  int32_t rval;
  int32_t sent;

  rval = -1;

  if (CmdSetWFE(cmd) == 0) {
    /* Command registered, append CRLF */
    num += sprintf (&buf[num], "%s", Ctrl_CRLF);

    /* Send out the command data */
    sent = Serial_SendBuf ((uint8_t *)buf, (uint32_t)num);
		
    if (sent == num) {
      rval = 0;
    }
  }
  return (rval);
}


/**
  Register command that waits for the response.
  
  \param[in]  cmd   Command code (see command list definition)
*/
static int32_t CmdSetWFE (uint8_t cmd) {

  /* Store last command sent */
  pCb->cmd_sent = cmd;

  return (0);//OK
}


/**
  Determine maximum number of bytes to be sent using AT_Send_Data.

  \return number of bytes
*/
uint32_t AT_Send_GetFree (void) {
  uint32_t cnt;

  cnt = Serial_GetTxFree();

  return (cnt);
}


/**
  Send data via currently active connection.

  \parma[in]  buf   data buffer
  \param[in]  len   number of bytes in buf to send

  \return number of bytes sent
*/
uint32_t AT_Send_Data (const uint8_t *buf, uint32_t len) {
  int32_t rval;
  uint32_t n;

  /* Send out the command data */
  rval = Serial_SendBuf (buf, len);
  
  if (rval < 0) {
    n = 0U;
  } else {
    n = (uint32_t)rval;
  }

  /* Return number of bytes actually sent */
  return (n);
}

/* ------------------------------------------------------------------------- */

/**
  Parse IP address from string to byte value.
*/
static void AT_Parse_IP (char *buf, uint8_t ip[]) {
  char *p;

  /* Set pointer to start of string */
  p = (char *)&buf[0];

  if (p[0] == '"') {
    /* Strip out the first quotation mark */
    p++;
  }

  /* Parse IP address (xxx.xxx.xxx.xxx or "xxx.xxx.xxx.xxx") */
  ip[0] = (uint8_t)strtoul (&p[0], &p, 10);
  ip[1] = (uint8_t)strtoul (&p[1], &p, 10);
  ip[2] = (uint8_t)strtoul (&p[1], &p, 10);
  ip[3] = (uint8_t)strtoul (&p[1], &p, 10);
}

/**
  Parse MAC address from (hex) string to byte value.
*/
static void AT_Parse_MAC (char *buf, uint8_t mac[]) {
  char *p;

  /* Set pointer to start of string */
  p = (char *)&buf[0];

  if (p[0] == '"') {
    /* Strip out the first quotation mark */
    p++;
  }

  /* Parse MAC address (xx:xx:xx:xx:xx:xx or "xx:xx:xx:xx:xx:xx") */
  mac[0] = (uint8_t)strtoul (&p[0], &p, 16);
  mac[1] = (uint8_t)strtoul (&p[1], &p, 16);
  mac[2] = (uint8_t)strtoul (&p[1], &p, 16);
  mac[3] = (uint8_t)strtoul (&p[1], &p, 16);
  mac[4] = (uint8_t)strtoul (&p[1], &p, 16);
  mac[5] = (uint8_t)strtoul (&p[1], &p, 16);
}
