Статьи

Учебник: веб-сервер с WiFi-модулем ESP8266

Со времени моего первого поста о ESP8266 прошло много времени (см. « Дешевый и простой WiFi с ESP8266 для платы FRDM »). ESP8266 — это новый недорогой ($ 4,50) WiFi-модуль, который облегчает подключение к сети или Интернету. Наконец, на этих выходных я нашел время написать учебное пособие: как реализовать веб-сервер WiFi для модуля WiFi ESP8266 и платы Freescale FRDM-KL25Z:

WSP8266 веб-сервер

WSP8266 веб-сервер

FRDM-KL25Z с WiFi-модулем ESP8266

FRDM-KL25Z с WiFi-модулем ESP8266

Контур

В этом уроке я использую  плату Freescale FRDM-KL25Z  в качестве веб-сервера, используя  плату ESP8266 . ESP8266 — это беспроводная WiFi-плата стоимостью менее $ 4,5, которая становится все более популярной в качестве платы IoT. Есть даже способ запустить ESP8266 в автономном режиме (поскольку на этой плате установлен полноценный процессор). Однако это развитие все еще находится в движении и довольно нестабильно. Вместо этого я использую последовательное соединение с ESP8266 вместо этого. При этом любой маленький микроконтроллер может отправлять и получать данные из Интернета: подключите эту плату к микроконтроллеру с напряжением 3,3 В, GND, Tx и Rx, и у вас будет соединение W-LAN!

В этом уроке я использую Eclipse с GNU / GDB с  Processor Expert , но с помощью шагов из этого урока вы также сможете использовать любой другой набор инструментов.

Так как в будущем все может измениться с другой прошивкой на ESP8266: у меня установлена ​​прошивка на плате версии  00160901 .

Соединения с платой

Со времени моего первого поста на ESP8266 я почистил проводку. Пины, как показано ниже для ESP8266:

ESP8266 Штырьки

ESP8266 Штырьки

Поскольку ESP8266 может потреблять> 200 мА, я использую преобразователь постоянного тока 5–3,3 В. Я измерял от 70 до 90 мА, так что это (пока) действительно не нужно, но я хотел использовать его для защиты на плате. ESP8266 Rx и Tx подключены к выводам Tx и Rx микроконтроллера. Общее разочарование для модуля ESP8266 — это подключение оставшихся контактов. Что мне помогло, так это подключить  CH_PD  к 3,3 В и оставить  RST , GPIO0  и  GPIO2  не подключенными / плавающими.

Настройка проводки с FRDM-KL25Z и ESP8266

Настройка проводки с FRDM-KL25Z и ESP8266

Протокол связи

Я рекомендую использовать логический анализатор для проверки связи между ESP8266 и микроконтроллером. Мой модуль связывается с 115200, но я вижу сообщения о том, что другие модули (другие прошивки) могут использовать другую скорость передачи данных.

Модуль использует отправку команды AT. Самая простая команда — отправить  «AT \ r \ n»,  и она отвечает  «AT \ r \ r \ n \ r \ nOK \ r \ n»:

AT команда отправлена ​​в ESP8266

AT команда отправлена ​​в ESP8266

В этом уроке я использую оболочку командной строки (см. « Оболочку для платы Freedom KL25Z »), чтобы иметь ручной режим для отправки команд модулю. Подробнее об этом позже.

Создание проекта

Вы можете использовать мой проект и исходные файлы, доступные на GitHub (см. Ссылку в конце этой статьи). Или создайте свой собственный проект. Мой проект использует  Kinetis Design Studio  и для платы FRDM-KL25Z (MKL25Z128VLK4).

Я создал проект для Processor Expert, так как использую несколько его компонентов:

Процессор Эксперт Проект

Процессор Эксперт Проект

Для проекта у меня есть несколько добавленных файлов:

ESP8266 Проект в Eclipse

ESP8266 Проект в Eclipse

Со следующими исходными файлами:

  • Application.c / .h : запускает программу приложения и веб-сервера
  • ESP8266.c / .h : Драйвер для ESP8266
  • Events.c / .h : обработчики событий Processor Expert
  • main.c : главная точка входа
  • Shell.c / .h : интерфейс командной строки

источники

Файлы проекта и исходные файлы доступны на GitHub здесь:
https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-KL25Z/FRDM-KL25Z_ESP8266

Пожалуйста, проверьте последние исходные файлы на GitHub. На момент написания этой статьи я использовал следующее:

Shell.h  является интерфейсом командной оболочки:

01.
/*
02.
 * Shell.h
03.
 *
04.
 *      Author: Erich Styger
05.
 */
06.
  
07.
#ifndef SHELL_H_
08.
#define SHELL_H_
09.
  
10.
/*!
11.
 * \brief Shell parse routine
12.
 */
13.
voidSHELL_Parse(void);
14.
  
15.
/*!
16.
 * \brief Shell initialization
17.
 */
18.
voidSHELL_Init(void);
19.
  
20.
#endif /* SHELL_H_ */

Shell.c  реализует прикладную часть оболочки:

01.
/*
02.
 * Shell.c
03.
 *
04.
 *      Author: Erich Styger
05.
 */
06.
  
07.
#include "Shell.h"
08.
#include "CLS1.h"
09.
#include "ESP8266.h"
10.
  
11.
/* table with shell parser/handler */
12.
staticconstCLS1_ParseCommandCallback CmdParserTable[] =
13.
{
14.
  CLS1_ParseCommand,
15.
  ESP_ParseCommand,
16.
  NULL /* sentinel */
17.
};
18.
  
19.
staticunsigned charlocalConsole_buf[48]; /* buffer for command line */
20.
  
21.
voidSHELL_Parse(void) {
22.
  (void)CLS1_ReadAndParseWithCommandTable(localConsole_buf, sizeof(localConsole_buf), CLS1_GetStdio(), CmdParserTable);
23.
}
24.
  
25.
voidSHELL_Init(void) {
26.
  localConsole_buf[0] = '\0'; /* initialize buffer */
27.
}

ESP8266.h  — это интерфейс к модулю WiFi:

001.
/*
002.
 * ESP8266.h
003.
 *
004.
 *      Author: Erich Styger
005.
 */
006.
  
007.
#ifndef ESP8266_H_
008.
#define ESP8266_H_
009.
  
010.
#include "CLS1.h"
011.
  
012.
#define ESP_DEFAULT_TIMEOUT_MS    (100)
013.
  /*!< Default timeout value in milliseconds */
014.
  
015.
/*!
016.
 * \brief Command line parser routine
017.
 * \param cmd Pointer to command line string
018.
 * \param handled Return value if command has been handled
019.
 * \param io Standard Shell I/O handler
020.
 * \return Error code, ERR_OK for no failure
021.
 */
022.
uint8_t ESP_ParseCommand(constunsigned char*cmd, bool *handled, constCLS1_StdIOType *io);
023.
  
024.
/*!
025.
 * \brief Send a string to th ESP8266 module
026.
 * \param str String to send, "\r\n" will be appended
027.
 * \param io Shell I/O handler or NULL if not used
028.
 * \return Error code, ERR_OK for no failure
029.
 */
030.
uint8_t ESP_SendStr(constuint8_t *str, CLS1_ConstStdIOType *io);
031.
  
032.
/*!
033.
 * \brief Used to send an AT command to the ESP8266 module
034.
 * \param cmd Command string to send
035.
 * \param rxBuf Buffer for the response, can be NULL
036.
 * \param rxBufSize Size of response buffer
037.
 * \param expectedTailStr Expected response from the module, can be NULL
038.
 * \param msTimeout Timeout time in milliseconds
039.
 * \param io Shell I/O handler or NULL if not used
040.
 * \return Error code, ERR_OK for no failure
041.
 */
042.
uint8_t ESP_SendATCommand(uint8_t *cmd, uint8_t *rxBuf, size_t rxBufSize, uint8_t *expectedTailStr, uint16_t msTimeout, constCLS1_StdIOType *io);
043.
  
044.
/*!
045.
 * \brief Read from the serial line from the module until a sentinel char is received
046.
 * \param buf
047.
 * \param bufSize
048.
 * \param sentinelChar
049.
 * \param timeoutMs Timeout time in milliseconds
050.
 * \return Error code, ERR_OK for no failure
051.
 */
052.
uint8_t ESP_ReadCharsUntil(uint8_t *buf, size_t bufSize, uint8_t sentinelChar, uint16_t timeoutMs);
053.
  
054.
/*!
055.
 * \brief Sends an AT command to test the connection
056.
 * \return Error code, ERR_OK for no failure
057.
 */
058.
uint8_t ESP_TestAT(void);
059.
  
060.
/*!
061.
 * \brief Restarts the ESP8266 module
062.
 * \param io Shell I/O handler or NULL if not used
063.
 * \param timeoutMs Timeout time in milliseconds
064.
 * \return Error code, ERR_OK for no failure
065.
 */
066.
uint8_t ESP_Restart(constCLS1_StdIOType *io, uint16_t timeoutMs);
067.
  
068.
/*!
069.
 * \brief Set the current mode of the module
070.
 * \param mode Where <mode> is 1=Sta, 2=AP or 3=both
071.
 * \return Error code, ERR_OK for no failure
072.
 */
073.
uint8_t ESP_SelectMode(uint8_t mode);
074.
  
075.
/*!
076.
 * \Brief returns the firmware version string
077.
 * \param fwBuf Buffer for the string
078.
 * \param fwBufSize Size of buffer in bytes
079.
 * \return Error code, ERR_OK for no failure
080.
 */
081.
uint8_t ESP_GetFirmwareVersionString(uint8_t *fwBuf, size_t fwBufSize);
082.
  
083.
/*!
084.
 * \brief Join an access point.
085.
 * \param ssid SSID of access point
086.
 * \param pwd Password of access point
087.
 * \param nofRetries Number of connection retries
088.
 * \param io Shell I/O or NULL if not used
089.
 * \return Error code, ERR_OK for no failure
090.
 */
091.
uint8_t ESP_JoinAP(constuint8_t *ssid, constuint8_t *pwd, intnofRetries, CLS1_ConstStdIOType *io);
092.
  
093.
/*!
094.
 * \brief Scans for an IPD message sent by the module
095.
 * \param msgBuf Pointer to the message buffer where to store the message
096.
 * \param msgBufSize Size of message buffer
097.
 * \param ch_id Pointer to where to store the channel/id
098.
 * \param size Pointer where to store the size of the message
099.
 * \param isGet TRUE if it is a GET message, FALSE for a POST message
100.
 * \param timeoutMs Error code, ERR_OK for no failure
101.
 * \param io
102.
 * \return Error code, ERR_OK for no failure
103.
 */
104.
uint8_t ESP_GetIPD(uint8_t *msgBuf, size_t msgBufSize, uint8_t *ch_id, uint16_t *size, bool *isGet, uint16_t timeoutMs, constCLS1_StdIOType *io);
105.
  
106.
/*!
107.
 * \brief Closes a connection
108.
 * \param channel Channel ID
109.
 * \param io Error code, ERR_OK for no failure
110.
 * \param timeoutMs Error code, ERR_OK for no failure
111.
 * \return Error code, ERR_OK for no failure
112.
 */
113.
uint8_t ESP_CloseConnection(uint8_t channel, constCLS1_StdIOType *io, uint16_t timeoutMs);
114.
  
115.
/*!
116.
 * \brief Used to determine if the web server is running or not.
117.
 * \return TRUE if web server has beens started
118.
 */
119.
bool ESP_IsServerOn(void);
120.
  
121.
/*!
122.
 * \brief Driver initialization
123.
 */
124.
voidESP_Init(void);
125.
  
126.
/*!
127.
 * \brief Driver de-initialization
128.
 */
129.
voidESP_Deinit(void);
130.
  
131.
#endif /* ESP8266_H_ */

А драйвер ESP8266 находится в  ESP8266.c,  который реализует все функции доступа SPI низкого уровня, функциональную реализацию и интерфейс оболочки командной строки:

001.
/*
002.
 * ESP8266.c
003.
 *
004.
 *      Author: Erich Styger
005.
 */
006.
  
007.
#include "ESP8266.h"
008.
#include "Shell.h"
009.
#include "UTIL1.h"
010.
#include "CLS1.h"
011.
#include "AS2.h"
012.
#include "WAIT1.h"
013.
  
014.
staticbool ESP_WebServerIsOn = FALSE;
015.
  
016.
bool ESP_IsServerOn(void) {
017.
  returnESP_WebServerIsOn;
018.
}
019.
  
020.
staticvoidSend(unsigned char*str) {
021.
  while(*str!='\0') {
022.
    AS2_SendChar(*str);
023.
    str++;
024.
  }
025.
}
026.
  
027.
staticvoidSkipNewLines(constunsigned char**p) {
028.
  while(**p=='\n'|| **p=='\r') {
029.
    (*p)++; /* skip new lines */
030.
  }
031.
}
032.
  
033.
uint8_t ESP_ReadCharsUntil(uint8_t *buf, size_t bufSize, uint8_t sentinelChar, uint16_t timeoutMs) {
034.
  uint8_t ch;
035.
  uint8_t res = ERR_OK;
036.
  
037.
  if(bufSize<=1) {
038.
    returnERR_OVERRUN; /* buffer to small */
039.
  }
040.
  buf[0] = '\0'; buf[bufSize-1] = '\0'; /* always terminate */
041.
  bufSize--;
042.
  for(;;) { /* breaks */
043.
    if(bufSize==0) {
044.
      res = ERR_OVERRUN;
045.
      break;
046.
    }
047.
    if(AS2_GetCharsInRxBuf()>0) {
048.
      (void)AS2_RecvChar(&ch);
049.
      *buf = ch;
050.
      buf++;
051.
      bufSize--;
052.
      if(ch==sentinelChar) {
053.
        *buf = '\0'; /* terminate string */
054.
        break; /* sentinel found */
055.
      }
056.
    } else{
057.
      if(timeoutMs>10) {
058.
        WAIT1_WaitOSms(5);
059.
        timeoutMs -= 5;
060.
      } else{
061.
        res = ERR_NOTAVAIL; /* timeout */
062.
        break;
063.
      }
064.
    }
065.
  }
066.
  returnres;
067.
}
068.
  
069.
staticuint8_t RxResponse(unsigned char*rxBuf, size_t rxBufLength, unsigned char*expectedTail, uint16_t msTimeout) {
070.
  unsigned charch;
071.
  uint8_t res = ERR_OK;
072.
  unsigned char*p;
073.
  
074.
  if(rxBufLength < sizeof("x\r\n")) {
075.
    returnERR_OVERFLOW; /* not enough space in buffer */
076.
  }
077.
  p = rxBuf;
078.
  p[0] = '\0';
079.
  for(;;) { /* breaks */
080.
    if(msTimeout == 0) {
081.
      break; /* will decide outside of loop if it is a timeout. */
082.
    } elseif(rxBufLength == 0) {
083.
      res = ERR_OVERFLOW; /* not enough space in buffer */
084.
      break;
085.
    } elseif(AS2_GetCharsInRxBuf() > 0) {
086.
#if0
087.
      if(AS2_RecvChar(&ch) != ERR_OK) {
088.
        res = ERR_RXEMPTY;
089.
        break;
090.
      }
091.
#else
092.
      /* might get an overrun OVERRUN_ERR error here? Ignoring error for now */
093.
      (void)AS2_RecvChar(&ch);
094.
#endif
095.
      *p++ = ch;
096.
      *p = '\0'; /* always terminate */
097.
      rxBufLength--;
098.
    } elseif(expectedTail!=NULL && expectedTail[0]!='\0'
099.
          && UTIL1_strtailcmp(rxBuf, expectedTail) == 0) {
100.
      break; /* finished */
101.
    } else{
102.
      WAIT1_WaitOSms(1);
103.
      msTimeout--;
104.
    }
105.
  } /* for */
106.
  if(msTimeout==0) { /* timeout! */
107.
    if(expectedTail[0] != '\0'/* timeout, and we expected something: an error for sure */
108.
        || rxBuf[0] == '\0'/* timeout, did not know what to expect, but received nothing? There has to be a response. */
109.
       )
110.
    {
111.
      res = ERR_FAULT;
112.
    }
113.
  }
114.
  returnres;
115.
}
116.
  
117.
uint8_t ESP_SendATCommand(uint8_t *cmd, uint8_t *rxBuf, size_t rxBufSize, uint8_t *expectedTailStr, uint16_t msTimeout, constCLS1_StdIOType *io) {
118.
  uint16_t snt;
119.
  uint8_t res;
120.
  
121.
  if(rxBuf!=NULL) {
122.
    rxBuf[0] = '\0';
123.
  }
124.
  if(io!=NULL) {
125.
    CLS1_SendStr("sending>>:\r\n", io->stdOut);
126.
    CLS1_SendStr(cmd, io->stdOut);
127.
  }
128.
  if(AS2_SendBlock(cmd, (uint16_t)UTIL1_strlen((char*)cmd), &snt) != ERR_OK) {
129.
    returnERR_FAILED;
130.
  }
131.
  if(rxBuf!=NULL) {
132.
    res = RxResponse(rxBuf, rxBufSize, expectedTailStr, msTimeout);
133.
    if(io!=NULL) {
134.
      CLS1_SendStr("received<<:\r\n", io->stdOut);
135.
      CLS1_SendStr(rxBuf, io->stdOut);
136.
    }
137.
  }
138.
  returnres;
139.
}
140.
  
141.
uint8_t ESP_TestAT(void) {
142.
  /* AT */
143.
  uint8_t rxBuf[sizeof("AT\r\r\n\r\nOK\r\n")];
144.
  uint8_t res;
145.
  
146.
  res = ESP_SendATCommand("AT\r\n", rxBuf, sizeof(rxBuf), "AT\r\r\n\r\nOK\r\n", ESP_DEFAULT_TIMEOUT_MS, NULL);
147.
  returnres;
148.
}
149.
  
150.
uint8_t ESP_Restart(constCLS1_StdIOType *io, uint16_t timeoutMs) {
151.
  /* AT+RST */
152.
  uint8_t rxBuf[sizeof("AT+RST\r\r\n\r\nOK\r\n")];
153.
  uint8_t res;
154.
  uint8_t buf[64];
155.
  
156.
  AS2_ClearRxBuf(); /* clear buffer */
157.
  res = ESP_SendATCommand("AT+RST\r\n", rxBuf, sizeof(rxBuf), "AT+RST\r\r\n\r\nOK\r\n", ESP_DEFAULT_TIMEOUT_MS, io);
158.
  if(res==ERR_OK) {
159.
    for(;;) {
160.
      ESP_ReadCharsUntil(buf, sizeof(buf), '\n', 1000);
161.
      if(io!=NULL) {
162.
        CLS1_SendStr(buf, io->stdOut);
163.
      }
164.
      if(UTIL1_strncmp(buf, "ready", sizeof("ready")-1)==0) { /* wait until ready message from module */
165.
        break; /* module has restarted */
166.
      }
167.
    }
168.
  }
169.
  AS2_ClearRxBuf(); /* clear buffer */
170.
  returnres;
171.
}
172.
  
173.
uint8_t ESP_CloseConnection(uint8_t channel, constCLS1_StdIOType *io, uint16_t timeoutMs) {
174.
  /* AT+CIPCLOSE=<channel> */
175.
  uint8_t res;
176.
  uint8_t cmd[64];
177.
  
178.
  UTIL1_strcpy(cmd, sizeof(cmd), "AT+CIPCLOSE=");
179.
  UTIL1_strcatNum8u(cmd, sizeof(cmd), channel);
180.
  UTIL1_strcat(cmd, sizeof(cmd), "\r\n");
181.
  res = ESP_SendATCommand(cmd, NULL, 0, "Unlink\r\n", timeoutMs, io);
182.
  returnres;
183.
}
184.
  
185.
uint8_t ESP_SetNumberOfConnections(uint8_t nof, constCLS1_StdIOType *io, uint16_t timeoutMs) {
186.
  /* AT+CIPMUX=<nof>, 0: single connection, 1: multiple connections */
187.
  uint8_t res;
188.
  uint8_t cmd[sizeof("AT+CIPMUX=12\r\n")];
189.
  uint8_t rxBuf[sizeof("AT+CIPMUX=12\r\n\r\nOK\r\n")+10];
190.
  
191.
  if(nof>1) { /* only 0 and 1 allowed */
192.
    if(io!=NULL) {
193.
      CLS1_SendStr("Wrong number of connection parameter!\r\n", io->stdErr);
194.
    }
195.
    returnERR_FAILED;
196.
  }
197.
  UTIL1_strcpy(cmd, sizeof(cmd), "AT+CIPMUX=");
198.
  UTIL1_strcatNum8u(cmd, sizeof(cmd), nof);
199.
  UTIL1_strcat(cmd, sizeof(cmd), "\r\n");
200.
  res = ESP_SendATCommand(cmd, rxBuf, sizeof(rxBuf), "OK\r\n", timeoutMs, io);
201.
  returnres;
202.
}
203.
  
204.
uint8_t ESP_SetServer(bool startIt, uint16_t port, constCLS1_StdIOType *io, uint16_t timeoutMs) {
205.
  /* AT+CIPSERVER=<en>,<port>, where <en>: 0: stop, 1: start */
206.
  uint8_t res;
207.
  uint8_t cmd[sizeof("AT+CIPSERVER=1,80\r\n\r\nOK\r\n")+sizeof("no change")];
208.
  uint8_t rxBuf[sizeof("AT+CIPSERVER=1,80\r\n\r\nOK\r\n")+sizeof("no change")];
209.
  
210.
  UTIL1_strcpy(cmd, sizeof(cmd), "AT+CIPSERVER=");
211.
  if(startIt) {
212.
    UTIL1_strcat(cmd, sizeof(cmd), "1,");
213.
  } else{
214.
    UTIL1_strcat(cmd, sizeof(cmd), "0,");
215.
  }
216.
  UTIL1_strcatNum16u(cmd, sizeof(cmd), port);
217.
  UTIL1_strcat(cmd, sizeof(cmd), "\r\n");
218.
  res = ESP_SendATCommand(cmd, rxBuf, sizeof(rxBuf), "OK\r\n", timeoutMs, io);
219.
  if(res!=ERR_OK) { /* accept "no change" too */
220.
    UTIL1_strcpy(cmd, sizeof(cmd), "AT+CIPSERVER=");
221.
    if(startIt) {
222.
      UTIL1_strcat(cmd, sizeof(cmd), "1,");
223.
    } else{
224.
      UTIL1_strcat(cmd, sizeof(cmd), "0,");
225.
    }
226.
    UTIL1_strcatNum16u(cmd, sizeof(cmd), port);
227.
    UTIL1_strcat(cmd, sizeof(cmd), "\r\r\nno change\r\n");
228.
    if(UTIL1_strcmp(rxBuf, cmd)==0) {
229.
      res = ERR_OK;
230.
    }
231.
  }
232.
  returnres;
233.
}
234.
  
235.
uint8_t ESP_SelectMode(uint8_t mode) {
236.
  /* AT+CWMODE=<mode>, where <mode> is 1=Sta, 2=AP or 3=both */
237.
  uint8_t txBuf[sizeof("AT+CWMODE=x\r\n")];
238.
  uint8_t rxBuf[sizeof("AT+CWMODE=x\r\r\nno change\r\n")];
239.
  uint8_t expected[sizeof("AT+CWMODE=x\r\r\nno change\r\n")];
240.
  uint8_t res;
241.
  
242.
  if(mode<1|| mode>3) {
243.
    returnERR_RANGE; /* only 1, 2 or 3 */
244.
  }
245.
  UTIL1_strcpy(txBuf, sizeof(txBuf), "AT+CWMODE=");
246.
  UTIL1_strcatNum16u(txBuf, sizeof(txBuf), mode);
247.
  UTIL1_strcat(txBuf, sizeof(txBuf), "\r\n");
248.
  UTIL1_strcpy(expected, sizeof(expected), "AT+CWMODE=");
249.
  UTIL1_strcatNum16u(expected, sizeof(expected), mode);
250.
  UTIL1_strcat(expected, sizeof(expected), "\r\r\n\n");
251.
  res = ESP_SendATCommand(txBuf, rxBuf, sizeof(rxBuf), expected, ESP_DEFAULT_TIMEOUT_MS, NULL);
252.
  if(res!=ERR_OK) {
253.
    /* answer could be as well "AT+CWMODE=x\r\r\nno change\r\n"!! */
254.
    UTIL1_strcpy(txBuf, sizeof(txBuf), "AT+CWMODE=");
255.
    UTIL1_strcatNum16u(txBuf, sizeof(txBuf), mode);
256.
    UTIL1_strcat(txBuf, sizeof(txBuf), "\r\n");
257.
    UTIL1_strcpy(expected, sizeof(expected), "AT+CWMODE=");
258.
    UTIL1_strcatNum16u(expected, sizeof(expected), mode);
259.
    UTIL1_strcat(expected, sizeof(expected), "\r\r\nno change\r\n");
260.
    if(UTIL1_strcmp(rxBuf, expected)==0) {
261.
      res = ERR_OK;
262.
    }
263.
  }
264.
  returnres;
265.
}
266.
  
267.
uint8_t ESP_GetFirmwareVersionString(uint8_t *fwBuf, size_t fwBufSize) {
268.
  /* AT+GMR */
269.
  uint8_t rxBuf[32];
270.
  uint8_t res;
271.
  constunsigned char*p;
272.
  
273.
  res = ESP_SendATCommand("AT+GMR\r\n", rxBuf, sizeof(rxBuf), "\r\n\r\nOK\r\n", ESP_DEFAULT_TIMEOUT_MS, NULL);
274.
  if(res!=ERR_OK) {
275.
    if(UTIL1_strtailcmp(rxBuf, "\r\n\r\nOK\r\n")) {
276.
      res = ERR_OK;
277.
    }
278.
  }
279.
  if(res==ERR_OK) {
280.
    if(UTIL1_strncmp(rxBuf, "AT+GMR\r\r\n", sizeof("AT+GMR\r\r\n")-1)==0) { /* check for beginning of response */
281.
      UTIL1_strCutTail(rxBuf, "\r\n\r\nOK\r\n"); /* cut tailing response */
282.
      p = rxBuf+sizeof("AT+GMR\r\r\n")-1/* skip beginning */
283.
      UTIL1_strcpy(fwBuf, fwBufSize, p); /* copy firmware information string */
284.
    } else{
285.
      res = ERR_FAILED;
286.
    }
287.
  }
288.
  if(res!=ERR_OK) {
289.
    UTIL1_strcpy(fwBuf, fwBufSize, "ERROR"); /* default error */
290.
  }
291.
  returnres;
292.
}
293.
  
294.
uint8_t ESP_GetIPAddrString(uint8_t *ipBuf, size_t ipBufSize) {
295.
  /* AT+CIFSR */
296.
  uint8_t rxBuf[32];
297.
  uint8_t res;
298.
  constunsigned char*p;
299.
  
300.
  res = ESP_SendATCommand("AT+CIFSR\r\n", rxBuf, sizeof(rxBuf), NULL, ESP_DEFAULT_TIMEOUT_MS, NULL);
301.
  if(res!=ERR_OK) {
302.
    if(UTIL1_strtailcmp(rxBuf, "\r\n")) {
303.
      res = ERR_OK;
304.
    }
305.
  }
306.
  if(res==ERR_OK) {
307.
    if(UTIL1_strncmp(rxBuf, "AT+CIFSR\r\r\n", sizeof("AT+CIFSR\r\r\n")-1)==0) { /* check for beginning of response */
308.
      UTIL1_strCutTail(rxBuf, "\r\n"); /* cut tailing response */
309.
      p = rxBuf+sizeof("AT+CIFSR\r\r\n")-1; /* skip beginning */
310.
      SkipNewLines(&p);
311.
      UTIL1_strcpy(ipBuf, ipBufSize, p); /* copy IP information string */
312.
    } else{
313.
      res = ERR_FAILED;
314.
    }
315.
  }
316.
  if(res!=ERR_OK) {
317.
    UTIL1_strcpy(ipBuf, ipBufSize, "ERROR");
318.
  }
319.
  returnres;
320.
}
321.
  
322.
uint8_t ESP_GetModeString(uint8_t *buf, size_t bufSize) {
323.
  /* AT+CWMODE? */
324.
  uint8_t rxBuf[32];
325.
  uint8_t res;
326.
  constunsigned char*p;
327.
  
328.
  res = ESP_SendATCommand("AT+CWMODE?\r\n", rxBuf, sizeof(rxBuf), "\r\n\r\nOK\r\n", ESP_DEFAULT_TIMEOUT_MS, NULL);
329.
  if(res==ERR_OK) {
330.
    if(UTIL1_strncmp(rxBuf, "AT+CWMODE?\r\r\n+CWMODE:", sizeof("AT+CWMODE?\r\r\n+CWMODE:")-1)==0) { /* check for beginning of response */
331.
      UTIL1_strCutTail(rxBuf, "\r\n\r\nOK\r\n"); /* cut tailing response */
332.
      p = rxBuf+sizeof("AT+CWMODE?\r\r\n+CWMODE:")-1; /* skip beginning */
333.
      UTIL1_strcpy(buf, bufSize, p); /* copy information string */
334.
    } else{
335.
      res = ERR_FAILED;
336.
    }
337.
  }
338.
  if(res!=ERR_OK) {
339.
    UTIL1_strcpy(buf, bufSize, "ERROR");
340.
  }
341.
  returnres;
342.
}
343.
  
344.
uint8_t ESP_GetCIPMUXString(uint8_t *cipmuxBuf, size_t cipmuxBufSize) {
345.
  /* AT+CIPMUX? */
346.
  uint8_t rxBuf[32];
347.
  uint8_t res;
348.
  constunsigned char*p;
349.
  
350.
  res = ESP_SendATCommand("AT+CIPMUX?\r\n", rxBuf, sizeof(rxBuf), "\r\n\r\nOK\r\n", ESP_DEFAULT_TIMEOUT_MS, NULL);
351.
  if(res==ERR_OK) {
352.
    if(UTIL1_strncmp(rxBuf, "AT+CIPMUX?\r\r\n+CIPMUX:", sizeof("AT+CIPMUX?\r\r\n+CIPMUX:")-1)==0) { /* check for beginning of response */
353.
      UTIL1_strCutTail(rxBuf, "\r\n\r\nOK\r\n"); /* cut tailing response */
354.
      p = rxBuf+sizeof("AT+CIPMUX?\r\r\n+CIPMUX:")-1; /* skip beginning */
355.
      UTIL1_strcpy(cipmuxBuf, cipmuxBufSize, p); /* copy IP information string */
356.
    } else{
357.
      res = ERR_FAILED;
358.
    }
359.
  }
360.
  if(res!=ERR_OK) {
361.
    UTIL1_strcpy(cipmuxBuf, cipmuxBufSize, "ERROR");
362.
  }
363.
  returnres;
364.
}
365.
  
366.
uint8_t ESP_GetConnectedAPString(uint8_t *apBuf, size_t apBufSize) {
367.
  /* AT+CWJAP? */
368.
  uint8_t rxBuf[48];
369.
  uint8_t res;
370.
  constunsigned char*p;
371.
  
372.
  res = ESP_SendATCommand("AT+CWJAP?\r\n", rxBuf, sizeof(rxBuf), "\r\n\r\nOK\r\n", ESP_DEFAULT_TIMEOUT_MS, NULL);
373.
  if(res==ERR_OK) {
374.
    if(UTIL1_strncmp(rxBuf, "AT+CWJAP?\r\r\n+CWJAP:\"", sizeof("AT+CWJAP?\r\r\n+CWJAP:\"")-1)==0) { /* check for beginning of response */
375.
      UTIL1_strCutTail(rxBuf, "\"\r\n\r\nOK\r\n"); /* cut tailing response */
376.
      p = rxBuf+sizeof("AT+CWJAP?\r\r\n+CWJAP:\"")-1; /* skip beginning */
377.
      UTIL1_strcpy(apBuf, apBufSize, p); /* copy IP information string */
378.
    } else{
379.
      res = ERR_FAILED;
380.
    }
381.
  }
382.
  if(res!=ERR_OK) {
383.
    UTIL1_strcpy(apBuf, apBufSize, "ERROR");
384.
  }
385.
  returnres;
386.
  
387.
}
388.
  
389.
staticuint8_t JoinAccessPoint(constuint8_t *ssid, constuint8_t *pwd, CLS1_ConstStdIOType *io) {
390.
  /* AT+CWJAP="<ssid>","<pwd>" */
391.
  uint8_t txBuf[48];
392.
  uint8_t rxBuf[64];
393.
  uint8_t expected[48];
394.
  
395.
  UTIL1_strcpy(txBuf, sizeof(txBuf), "AT+CWJAP=\"");
396.
  UTIL1_strcat(txBuf, sizeof(txBuf), ssid);
397.
  UTIL1_strcat(txBuf, sizeof(txBuf), "\",\"");
398.
  UTIL1_strcat(txBuf, sizeof(txBuf), pwd);
399.
  UTIL1_strcat(txBuf, sizeof(txBuf), "\"\r\n");
400.
  
401.
  UTIL1_strcpy(expected, sizeof(expected), "AT+CWJAP=\"");
402.
  UTIL1_strcat(expected, sizeof(expected), ssid);
403.
  UTIL1_strcat(expected, sizeof(expected), "\",\"");
404.
  UTIL1_strcat(expected, sizeof(expected), pwd);
405.
  UTIL1_strcat(expected, sizeof(expected), "\"\r\r\n\r\nOK\r\n");
406.
  
407.
  returnESP_SendATCommand(txBuf, rxBuf, sizeof(rxBuf), expected, ESP_DEFAULT_TIMEOUT_MS, io);
408.
}
409.
  
410.
uint8_t ESP_JoinAP(constuint8_t *ssid, constuint8_t *pwd, intnofRetries, CLS1_ConstStdIOType *io) {
411.
  uint8_t buf[32];
412.
  uint8_t res;
413.
  
414.
  do{
415.
    res = JoinAccessPoint(ssid, pwd, io);
416.
    if(res==ERR_OK) {
417.
      break;
418.
    }
419.
    WAIT1_WaitOSms(1000);
420.
    nofRetries--;
421.
  } while(nofRetries>0);
422.
  returnres;
423.
}
424.
  
425.
staticuint8_t ReadIntoIPDBuffer(uint8_t *buf, size_t bufSize, uint8_t *p, uint16_t msgSize, uint16_t msTimeout, constCLS1_StdIOType *io) {
426.
  uint8_t ch;
427.
  size_t nofInBuf;
428.
  inttimeout;
429.
  
430.
  nofInBuf = p-buf;
431.
  bufSize -= nofInBuf; /* take into account what we already have in buffer */
432.
  timeout = msTimeout;
433.
  while(msgSize>0&& bufSize>0) {
434.
    if(AS2_GetCharsInRxBuf()>0) {
435.
      (void)AS2_RecvChar(&ch);
436.
      *p = ch;
437.
      if(io!=NULL) { /* copy on console */
438.
        io->stdOut(ch);
439.
      }
440.
      p++;
441.
      *p = '\0'; /* terminate */
442.
      nofInBuf++; msgSize--; bufSize--;
443.
    } else{
444.
      /* check in case we recveive less characters than expected, happens for POST? */
445.
      if(nofInBuf>6&& UTIL1_strncmp(&p[-6], "\r\nOK\r\n", sizeof("\r\nOK\r\n")-1)==0) {
446.
        break;
447.
      } else{
448.
        timeout -= 10;
449.
        WAIT1_WaitOSms(10);
450.
        if(timeout<0) {
451.
          returnERR_BUSY;
452.
        }
453.
      }
454.
    }
455.
  }
456.
  returnERR_OK;
457.
}
458.
  
459.
uint8_t ESP_GetIPD(uint8_t *msgBuf, size_t msgBufSize, uint8_t *ch_id, uint16_t *size, bool *isGet, uint16_t timeoutMs, constCLS1_StdIOType *io) {
460.
  /* scan e.g. for
461.
   * +IPD,0,404:POST / HTTP/1.1
462.
   * and return ch_id (0), size (404)
463.
   */
464.
  uint8_t res = ERR_OK;
465.
  constuint8_t *p;
466.
  bool isIPD = FALSE;
467.
  uint8_t cmd[24], rxBuf[48];
468.
  uint16_t ipdSize;
469.
  
470.
  *ch_id = 0; *size = 0; *isGet = FALSE; /* init */
471.
  for(;;) { /* breaks */
472.
    res = ESP_ReadCharsUntil(msgBuf, msgBufSize, '\n', timeoutMs);
473.
    if(res!=ERR_OK) {
474.
      break; /* timeout */
475.
    }
476.
    if(res==ERR_OK) { /* line read */
477.
      if(io!=NULL) {
478.
        CLS1_SendStr(msgBuf, io->stdOut); /* copy on console */
479.
      }
480.
      isIPD = UTIL1_strncmp(msgBuf, "+IPD,", sizeof("+IPD,")-1)==0;
481.
      if(isIPD) { /* start of IPD message */
482.
        p = msgBuf+sizeof("+IPD,")-1;
483.
        if(UTIL1_ScanDecimal8uNumber(&p, ch_id)!=ERR_OK) {
484.
          if(io!=NULL) {
485.
            CLS1_SendStr("ERR: wrong channel?\r\n", io->stdErr); /* error on console */
486.
          }
487.
          res = ERR_FAILED;
488.
          break;
489.
        }
490.
        if(*p!=',') {
491.
          res = ERR_FAILED;
492.
          break;
493.
        }
494.
        p++; /* skip comma */
495.
        if(UTIL1_ScanDecimal16uNumber(&p, size)!=ERR_OK) {
496.
          if(io!=NULL) {
497.
            CLS1_SendStr("ERR: wrong size?\r\n", io->stdErr); /* error on console */
498.
          }
499.
          res = ERR_FAILED;
500.
          break;
501.
        }
502.
        if(*p!=':') {
503.
          res = ERR_FAILED;
504.
          break;
505.
        }
506.
        ipdSize = p-msgBuf; /* length of "+IPD,<channel>,<size>" string */
507.
        p++; /* skip ':' */
508.
        if(UTIL1_strncmp(p, "GET", sizeof("GET")-1)==0) {
509.
          *isGet = TRUE;
510.
        } elseif(UTIL1_strncmp(p, "POST", sizeof("POST")-1)==0) {
511.
          *isGet = FALSE;
512.
        } else{
513.
          res = ERR_FAILED;
514.
        }
515.
        while(*p!='\0') {
516.
          p++; /* skip to the end */
517.
        }
518.
        /* read the rest of the message */
519.
        res = ReadIntoIPDBuffer(msgBuf, msgBufSize, (uint8_t*)p, (*size)-ipdSize, ESP_DEFAULT_TIMEOUT_MS, io);
520.
        break;
521.
      }
522.
    }
523.
  }
524.
  returnres;
525.
}
526.
  
527.
uint8_t ESP_StartWebServer(constCLS1_StdIOType *io) {
528.
  uint8_t buf[32];
529.
  uint8_t res;
530.
  
531.
  res = ESP_SetNumberOfConnections(1, io, ESP_DEFAULT_TIMEOUT_MS);
532.
  if(res!=ERR_OK) {
533.
    CLS1_SendStr("ERR: failed to set multiple connections.\r\n", io->stdErr);
534.
    returnres;
535.
  }
536.
  res = ESP_SetServer(TRUE, 80, io, ESP_DEFAULT_TIMEOUT_MS);
537.
  if(res!=ERR_OK) {
538.
    CLS1_SendStr("ERR: failed to set server.\r\n", io->stdErr);
539.
    returnres;
540.
  }
541.
  CLS1_SendStr("INFO: Web Server started, waiting for connection on ", io->stdOut);
542.
  if(ESP_GetIPAddrString(buf, sizeof(buf))==ERR_OK) {
543.
    CLS1_SendStr(buf, io->stdOut);
544.
    CLS1_SendStr(":80", io->stdOut);
545.
  } else{
546.
    CLS1_SendStr("(ERROR!)", io->stdOut);
547.
  }
548.
  CLS1_SendStr("\r\n", io->stdOut);
549.
  
550.
  returnERR_OK;
551.
}
552.
  
553.
uint8_t ESP_SendStr(constuint8_t *str, CLS1_ConstStdIOType *io) {
554.
  uint8_t buf[32];
555.
  uint8_t rxBuf[48];
556.
  uint8_t res;
557.
  uint16_t timeoutMs;
558.
  #define RX_TIMEOUT_MS 3000
559.
  AS2_TComData ch;
560.
  
561.
  UTIL1_strcpy(buf, sizeof(buf), str);
562.
  UTIL1_strcat(buf, sizeof(buf), "\r\n");
563.
  res = ESP_SendATCommand(buf, rxBuf, sizeof(rxBuf), NULL, ESP_DEFAULT_TIMEOUT_MS, io);
564.
  timeoutMs = 0;
565.
  while(timeoutMs<RX_TIMEOUT_MS) {
566.
    WAIT1_WaitOSms(100);
567.
    timeoutMs += 100;
568.
    while(AS2_GetCharsInRxBuf()>0) {
569.
      (void)AS2_RecvChar(&ch);
570.
      CLS1_SendChar(ch);
571.
    }
572.
  }
573.
  returnERR_OK;
574.
}
575.
  
576.
staticuint8_t ESP_PrintHelp(constCLS1_StdIOType *io) {
577.
  CLS1_SendHelpStr("ESP", "ESP8200 commands\r\n", io->stdOut);
578.
  CLS1_SendHelpStr("  help|status", "Print help or status information\r\n", io->stdOut);
579.
  CLS1_SendHelpStr("  send <str>", "Sends a string to the module\r\n", io->stdOut);
580.
  CLS1_SendHelpStr("  test", "Sends a test AT command\r\n", io->stdOut);
581.
  CLS1_SendHelpStr("  restart", "Restart module\r\n", io->stdOut);
582.
  CLS1_SendHelpStr("  listAP", "List available Access Points\r\n", io->stdOut);
583.
  CLS1_SendHelpStr("  connectAP \"ssid\",\"pwd\"", "Connect to an Access Point\r\n", io->stdOut);
584.
  CLS1_SendHelpStr("  server (start|stop)", "Start or stop web server\r\n", io->stdOut);
585.
  returnERR_OK;
586.
}
587.
  
588.
staticuint8_t ESP_PrintStatus(constCLS1_StdIOType *io) {
589.
  uint8_t buf[48];
590.
  
591.
  CLS1_SendStatusStr("ESP8266", "\r\n", io->stdOut);
592.
  
593.
  CLS1_SendStatusStr("  Webserver", ESP_WebServerIsOn?"ON\r\n":"OFF\r\n", io->stdOut);
594.
  
595.
  if(ESP_GetFirmwareVersionString(buf, sizeof(buf)) != ERR_OK) {
596.
    UTIL1_strcpy(buf, sizeof(buf), "FAILED\r\n");
597.
  } else{
598.
    UTIL1_strcat(buf, sizeof(buf), "\r\n");
599.
  }
600.
  CLS1_SendStatusStr("  AT+GMR", buf, io->stdOut);
601.
  
602.
  if(ESP_GetModeString(buf, sizeof(buf)) != ERR_OK) {
603.
    UTIL1_strcpy(buf, sizeof(buf), "FAILED\r\n");
604.
  } else{
605.
    if(UTIL1_strcmp(buf, "1")==0) {
606.
      UTIL1_strcat(buf, sizeof(buf), " (device)");
607.
    } elseif(UTIL1_strcmp(buf, "2")==0) {
608.
      UTIL1_strcat(buf, sizeof(buf), " (AP)");
609.
    } elseif(UTIL1_strcmp(buf, "3")==0) {
610.
      UTIL1_strcat(buf, sizeof(buf), " (device+AP)");
611.
    } else{
612.
      UTIL1_strcat(buf, sizeof(buf), " (ERROR)");
613.
    }
614.
    UTIL1_strcat(buf, sizeof(buf), "\r\n");
615.
  }
616.
  CLS1_SendStatusStr("  AT+CWMODE?", buf, io->stdOut);
617.
  
618.
  if(ESP_GetIPAddrString(buf, sizeof(buf)) != ERR_OK) {
619.
    UTIL1_strcpy(buf, sizeof(buf), "FAILED\r\n");
620.
  } else{
621.
    UTIL1_strcat(buf, sizeof(buf), "\r\n");
622.
  }
623.
  CLS1_SendStatusStr("  AT+CIFSR", buf, io->stdOut);
624.
  
625.
  if(ESP_GetConnectedAPString(buf, sizeof(buf)) != ERR_OK) {
626.
    UTIL1_strcpy(buf, sizeof(buf), "FAILED\r\n");
627.
  } else{
628.
    UTIL1_strcat(buf, sizeof(buf), "\r\n");
629.
  }
630.
  CLS1_SendStatusStr("  AT+CWJAP?", buf, io->stdOut);
631.
  
632.
  if(ESP_GetCIPMUXString(buf, sizeof(buf)) != ERR_OK) {
633.
    UTIL1_strcpy(buf, sizeof(buf), "FAILED\r\n");
634.
  } else{
635.
    if(UTIL1_strcmp(buf, "0")==0) {
636.
      UTIL1_strcat(buf, sizeof(buf), " (single connection)");
637.
    } elseif(UTIL1_strcmp(buf, "1")==0) {
638.
      UTIL1_strcat(buf, sizeof(buf), " (multiple connections)");
639.
    } else{
640.
      UTIL1_strcat(buf, sizeof(buf), " (ERROR)");
641.
    }
642.
    UTIL1_strcat(buf, sizeof(buf), "\r\n");
643.
  }
644.
  CLS1_SendStatusStr("  CIPMUX", buf, io->stdOut);
645.
  returnERR_OK;
646.
}
647.
  
648.
uint8_t ESP_ParseCommand(constunsigned char*cmd, bool *handled, constCLS1_StdIOType *io) {
649.
  uint32_t val;
650.
  uint8_t res;
651.
  constunsigned char*p;
652.
  uint8_t pwd[24], ssid[24];
653.
  
654.
  if(UTIL1_strcmp((char*)cmd, CLS1_CMD_HELP)==0|| UTIL1_strcmp((char*)cmd, "ESP help")==0) {
655.
    *handled = TRUE;
656.
    res = ESP_PrintHelp(io);
657.
  } elseif(UTIL1_strcmp((char*)cmd, CLS1_CMD_STATUS)==0|| UTIL1_strcmp((char*)cmd, "ESP status")==0) {
658.
    *handled = TRUE;
659.
    res = ESP_PrintStatus(io);
660.
  } elseif(UTIL1_strncmp((char*)cmd, "ESP send ", sizeof("ESP send ")-1)==0) {
661.
    *handled = TRUE;
662.
    p = cmd+sizeof("ESP send ")-1;
663.
  
664.
    (void)ESP_SendStr(p, io);
665.
  } elseif(UTIL1_strcmp((char*)cmd, "ESP test")==0) {
666.
    *handled = TRUE;
667.
    if(ESP_TestAT()!=ERR_OK) {
668.
      CLS1_SendStr("TEST failed!\r\n", io->stdErr);
669.
      res = ERR_FAILED;
670.
    } else{
671.
      CLS1_SendStr("TEST ok!\r\n", io->stdOut);
672.
    }
673.
  } elseif(UTIL1_strcmp((char*)cmd, "ESP listAP")==0) {
674.
    *handled = TRUE;
675.
    (void)ESP_SendStr("AT+CWLAP", io);
676.
    /* AT + CWLAP
677.
    response
678.
    + CWLAP: <ecn>, <ssid>, <rssi> [, <mode>]
679.
    OK Or Fails, the return ERROR
680.
    <Ecn> 0 OPEN
681.
    1 WEP
682.
    2 WPA_PSK
683.
    3 WPA2_PSK
684.
    4 WPA_WPA2_PSK
685.
    <Ssid> string parameter, the access point name
686.
    <Rssi> signal strength
687.
    <Mode> 0: manually connect 1: An automatic connection
688.
     */
689.
    returnERR_OK;
690.
  } elseif(UTIL1_strncmp((char*)cmd, "ESP connectAP ", sizeof("ESP connectAP ")-1)==0) {
691.
    *handled = TRUE;
692.
    p = cmd+sizeof("ESP connectAP ")-1;
693.
    ssid[0] = '\0'; pwd[0] = '\0';
694.
    res = UTIL1_ScanDoubleQuotedString(&p, ssid, sizeof(ssid));
695.
    if(res==ERR_OK && *p!='\0'&& *p==',') {
696.
      p++; /* skip comma */
697.
      res = UTIL1_ScanDoubleQuotedString(&p, pwd, sizeof(pwd));
698.
    } else{
699.
      CLS1_SendStr("Comma expected between strings!\r\n", io->stdErr);
700.
      res = ERR_FAILED;
701.
    }
702.
    if(res==ERR_OK) {
703.
      res = ESP_JoinAP(ssid, pwd, 3, io);
704.
    } else{
705.
      CLS1_SendStr("Wrong command format!\r\n", io->stdErr);
706.
      res = ERR_FAILED;
707.
    }
708.
  } elseif(UTIL1_strcmp((char*)cmd, "ESP server start")==0) {
709.
    *handled = TRUE;
710.
    res = ESP_StartWebServer(io);
711.
    ESP_WebServerIsOn = res==ERR_OK;
712.
  } elseif(UTIL1_strcmp((char*)cmd, "ESP server stop")==0) {
713.
    *handled = TRUE;
714.
    ESP_WebServerIsOn = FALSE;
715.
  } elseif(UTIL1_strcmp((char*)cmd, "ESP restart")==0) {
716.
    *handled = TRUE;
717.
    ESP_Restart(io, 2000);
718.
  }
719.
  returnres;
720.
}
721.
  
722.
voidESP_Deinit(void) {
723.
  /* nothing to do */
724.
}
725.
  
726.
voidESP_Init(void) {
727.
  AS2_ClearRxBuf(); /* clear buffer */
728.
}

Интерфейс приложения в  Application.h  довольно короткий :-):

01.
/*
02.
 * Application.h
03.
 *
04.
 *      Author: Erich Styger
05.
 */
06.
  
07.
#ifndef APPLICATION_H_
08.
#define APPLICATION_H_
09.
  
10.
/*!
11.
 * \brief Application main routine
12.
 */
13.
voidAPP_Run(void);
14.
  
15.
#endif /* APPLICATION_H_ */

The main loop of the application is Application.c, along with the application specific web server code.

As the SendWebPage function contains HTML code, I’m posting it here separately:

01.
staticuint8_t SendWebPage(uint8_t ch_id, bool ledIsOn, uint8_t temperature, constCLS1_StdIOType *io) {
02.
  staticuint8_t http[1024];
03.
  uint8_t cmd[24], rxBuf[48], expected[48];
04.
  uint8_t buf[16];
05.
  uint8_t res = ERR_OK;
06.
  
07.
  /* construct web page content */
08.
  UTIL1_strcpy(http, sizeof(http), (uint8_t*)"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n");
09.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<html>\r\n<body>\r\n");
10.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<title>ESP8266 Web Server</title>\r\n");
11.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<h2>Web Server using ESP8266</h2>\r\n");
12.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"
13.
<hr>\r\n");
14.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<p><form method=\"POST\"><strong>Temp: <input type=\"text\" size=2 value=\"");
15.
  UTIL1_strcatNum8s(http, sizeof(http), temperature);
16.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"\"> <sup>O</sup>C");
17.
  if(ledIsOn) {
18.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"<p><input type=\"radio\" name=\"radio\" value=\"0\" >Red LED off");
19.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"
20.
<input type=\"radio\" name=\"radio\" value=\"1\" checked>Red LED on");
21.
  } else{
22.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"<p><input type=\"radio\" name=\"radio\" value=\"0\" checked>Red LED off");
23.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"
24.
<input type=\"radio\" name=\"radio\" value=\"1\" >Red LED on");
25.
  }
26.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"</strong><p><input type=\"submit\"></form></span>");
27.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"</body>\r\n</html>\r\n");
28.
  
29.
  UTIL1_strcpy(cmd, sizeof(cmd), "AT+CIPSEND="); /* parameters are <ch_id>,<size> */
30.
  UTIL1_strcatNum8u(cmd, sizeof(cmd), ch_id);
31.
  UTIL1_chcat(cmd, sizeof(cmd), ',');
32.
  UTIL1_strcatNum16u(cmd, sizeof(cmd), UTIL1_strlen(http));
33.
  UTIL1_strcpy(expected, sizeof(expected), cmd); /* we expect the echo of our command */
34.
  UTIL1_strcat(expected, sizeof(expected), "\r\r\n> "); /* expect "> " */
35.
  UTIL1_strcat(cmd, sizeof(cmd), "\r\n");
36.
  res = ESP_SendATCommand(cmd, rxBuf, sizeof(rxBuf), expected, ESP_DEFAULT_TIMEOUT_MS, io);
37.
  if(res!=ERR_OK) {
38.
    if(io!=NULL) {
39.
      CLS1_SendStr("INFO: TIMEOUT, closing connection!\r\n", io->stdOut);
40.
    }
41.
  } else{
42.
    if(io!=NULL) {
43.
      CLS1_SendStr("INFO: Sending http page...\r\n", io->stdOut);
44.
    }
45.
    UTIL1_strcat(http, sizeof(http), "\r\n\r\n"); /* need to add this to end the command! */
46.
    res = ESP_SendATCommand(http, NULL, 0, NULL, ESP_DEFAULT_TIMEOUT_MS, io);
47.
    if(res!=ERR_OK) {
48.
      CLS1_SendStr("Sending page failed!\r\n", io->stdErr); /* copy on console */
49.
    } else{
50.
      for(;;) { /* breaks */
51.
        res = ESP_ReadCharsUntil(buf, sizeof(buf), '\n', 1000);
52.
        if(res==ERR_OK) { /* line read */
53.
          if(io!=NULL) {
54.
            CLS1_SendStr(buf, io->stdOut); /* copy on console */
55.
          }
56.
        }
57.
        if(UTIL1_strncmp(buf, "SEND OK\r\n", sizeof("SEND OK\r\n")-1)==0) { /* ok from module */
58.
          break;
59.
        }
60.
      }
61.
    }
62.
  }
63.
  returnres;
64.
}

The rest of Application.c is rather simple:

01.
/*
02.
 * Application.c
03.
 *
04.
 *      Author: Erich Styger
05.
 */
06.
#include "PE_Types.h"
07.
#include "CLS1.h"
08.
#include "WAIT1.h"
09.
#include "Shell.h"
10.
#include "UTIL1.h"
11.
#include "ESP8266.h"
12.
#include "LEDR.h"
13.
#include "LEDG.h"
14.
#include "AS2.h"
15.
  
16.
staticuint8_t APP_EspMsgBuf[512]; /* buffer for messages from ESP8266 */
17.
  
18.
staticvoidWebProcess(void) {
19.
  uint8_t res=ERR_OK;
20.
  bool isGet;
21.
  uint8_t ch_id=0;
22.
  uint16_t size=0;
23.
  constuint8_t *p;
24.
  constCLS1_StdIOType *io;
25.
  
26.
  if(ESP_IsServerOn()) {
27.
    io = CLS1_GetStdio();
28.
    res = ESP_GetIPD(APP_EspMsgBuf, sizeof(APP_EspMsgBuf), &ch_id, &size, &isGet, 1000, io);
29.
    if(res==ERR_OK) {
30.
      if(isGet) { /* GET: put web page */
31.
        res = SendWebPage(ch_id, LEDR_Get()!=FALSE, 21/*dummy temperature*/, io);
32.
        if(res!=ERR_OK && io!=NULL) {
33.
          CLS1_SendStr("Sending page failed!\r\n", io->stdErr); /* copy on console */
34.
        }
35.
      } else{ /* POST: received info */
36.
        intpos;
37.
  
38.
        pos = UTIL1_strFind(APP_EspMsgBuf, "radio=");
39.
        if(pos!=-1) { /* found */
40.
          if(UTIL1_strncmp(&APP_EspMsgBuf[pos], "radio=0", sizeof("radio=0")-1)) {
41.
            LEDR_On();
42.
          } elseif(UTIL1_strncmp(&APP_EspMsgBuf[pos], "radio=1", sizeof("radio=1")-1)) {
43.
            LEDR_Off();
44.
          }
45.
        }
46.
        res = SendWebPage(ch_id, LEDR_Get()!=FALSE, 20/*dummy temperature*/, io);
47.
        if(res!=ERR_OK && io!=NULL) {
48.
          CLS1_SendStr("Sending page failed!\r\n", io->stdErr); /* copy on console */
49.
        }
50.
      }
51.
      CLS1_SendStr("INFO: Closing connection...\r\n", io->stdOut);
52.
      res = ESP_CloseConnection(ch_id, io, ESP_DEFAULT_TIMEOUT_MS);
53.
    }
54.
  } else{ /* copy messages we receive to console */
55.
    while(AS2_GetCharsInRxBuf()>0) {
56.
      uint8_t ch;
57.
  
58.
      (void)AS2_RecvChar(&ch);
59.
      CLS1_SendChar(ch);
60.
    }
61.
  }
62.
}
63.
  
64.
voidAPP_Run(void) {
65.
  CLS1_ConstStdIOType *io;
66.
  
67.
  WAIT1_Waitms(1000); /* wait after power-on */
68.
  ESP_Init();
69.
  SHELL_Init();
70.
  io = CLS1_GetStdio();
71.
  CLS1_SendStr("\r\n------------------------------------------\r\n", io->stdOut);
72.
  CLS1_SendStr("ESP8266 with FRDM-KL25Z\r\n", io->stdOut);
73.
  CLS1_SendStr("------------------------------------------\r\n", io->stdOut);
74.
  CLS1_PrintPrompt(io);
75.
  for(;;) {
76.
    WebProcess();
77.
    SHELL_Parse();
78.
    WAIT1_Waitms(10);
79.
    LEDG_Neg();
80.
  }
81.
}

Imain.c I call the application part:

01.
/* ###################################################################
02.
**     Filename    : main.c
03.
**     Project     : FRDM-KL25Z_ESP8266
04.
**     Processor   : MKL25Z128VLK4
05.
**     Version     : Driver 01.01
06.
**     Compiler    : GNU C Compiler
07.
**     Date/Time   : 2014-10-15, 14:28, # CodeGen: 0
08.
**     Abstract    :
09.
**         Main module.
10.
**         This module contains user's application code.
11.
**     Settings    :
12.
**     Contents    :
13.
**         No public methods
14.
**
15.
** ###################################################################*/
16.
/*!
17.
** @file main.c
18.
** @version 01.01
19.
** @brief
20.
**         Main module.
21.
**         This module contains user's application code.
22.
*/        
23.
/*!
24.
**  @addtogroup main_module main module documentation
25.
**  @{
26.
*/        
27.
/* MODULE main */
28.
  
29.
/* Including needed modules to compile this module/procedure */
30.
#include "Cpu.h"
31.
#include "Events.h"
32.
#include "WAIT1.h"
33.
#include "UTIL1.h"
34.
#include "AS1.h"
35.
#include "ASerialLdd1.h"
36.
#include "CLS1.h"
37.
#include "CS1.h"
38.
#include "AS2.h"
39.
#include "ASerialLdd2.h"
40.
#include "LEDR.h"
41.
#include "LEDpin1.h"
42.
#include "BitIoLdd1.h"
43.
#include "LEDG.h"
44.
#include "LEDpin2.h"
45.
#include "BitIoLdd2.h"
46.
#include "LEDB.h"
47.
#include "LEDpin3.h"
48.
#include "BitIoLdd3.h"
49.
/* Including shared modules, which are used for whole project */
50.
#include "PE_Types.h"
51.
#include "PE_Error.h"
52.
#include "PE_Const.h"
53.
#include "IO_Map.h"
54.
/* User includes (#include below this line is not maintained by Processor Expert) */
55.
#include "Application.h"
56.
  
57.
/*lint -save  -e970 Disable MISRA rule (6.3) checking. */
58.
intmain(void)
59.
/*lint -restore Enable MISRA rule (6.3) checking. */
60.
{
61.
  /* Write your local variable definition here */
62.
  
63.
  /*** Processor Expert internal initialization. DON'T REMOVE THIS CODE!!! ***/
64.
  PE_low_level_init();
65.
  /*** End of Processor Expert internal initialization.                    ***/
66.
  
67.
  APP_Run();
68.
  
69.
  /*** Don't write any code pass this line, or it will be deleted during code generation. ***/
70.
  /*** RTOS startup code. Macro PEX_RTOS_START is defined by the RTOS component. DON'T MODIFY THIS CODE!!! ***/
71.
  #ifdef PEX_RTOS_START
72.
    PEX_RTOS_START();                  /* Startup of the selected RTOS. Macro is defined by the RTOS component. */
73.
  #endif
74.
  /*** End of RTOS startup code.  ***/
75.
  /*** Processor Expert end of main routine. DON'T MODIFY THIS CODE!!! ***/
76.
  for(;;){}
77.
  /*** Processor Expert end of main routine. DON'T WRITE CODE BELOW!!! ***/
78.
} /*** End of main routine. DO NOT MODIFY THIS TEXT!!! ***/
79.
  
80.
/* END main */
81.
/*!
82.
** @}
83.
*/
84.
/*
85.
** ###################################################################
86.
**
87.
**     This file was created by Processor Expert 10.4 [05.10]
88.
**     for the Freescale Kinetis series of microcontrollers.
89.
**
90.
** ###################################################################
91.
*/

Processor Expert Components

In addition, I’m using several Processor Expert component which are available fromSourceForge.

Компоненты Процессор Эксперт

Processor Expert Components

  • Wait: Busy waiting component, e.g. to wait for a few milliseconds.
  • Utility: string manipulation and utility functions.
  • AsynchroSerial (AS1): serial interface to the host for the shell command line interface
  • Shell: command line shell implementation
  • CriticalSection: for creating critical sections
  • AsynchroSerial (AS2): serial interface to the ESP8266 module
  • LEDRLEDG and LEDB: Red, Green and Blue LED on the FRDM-KL25Z board

AS1 is configured as UART connection (over OpenSDA) for the shell:

Настройки оболочки UART

Shell UART Settings

There are no special settings for the Shell component:

Настройки оболочки

Shell Settings

Important are the correct settings to the ESP8266 UART: 115200 baud and using the correct pins on the board connected to the Rx and Tx lines of the ESP8266. I’m using rather large input and output buffers:

Соединение UART с ESP8266

UART connection to ESP8266

The LED components are configured for the pins used on the board: PTB18 for red, PTB19 for green and PTD1 for blue LED.

Красный светодиод для FRDM-KL25Z

Red LED for FRDM-KL25Z

Sending Commands

The shell implements the command ESP send which I can use to send a string or command to the module:

ESP send <str>

Note that for every command a trailing “\r\n” will be sent.

So instead of using the programmatic way, the shell can be used to ‘manually’ drive a web server, at least most of the part. So I’m using command line commands below to explore how the ESP8266 module works.

Using the Shell

With the project (link to GitHub below), I have a serial connection and command line shell interface to the module. Compile the project and download it to the FRDM-KL25Z board and use a terminal program (I use Termite) to talk with the module.

It power-up, the program shows a greeting message:

Приветственное сообщение

Greeting Message

With ‘help‘ I get a list of the available commands:

Команда помощи

Help Command

The ‘status‘ command gives a system status:

Вывод команды состояния

Status Command Output

With this, I’m ready to send commands to the module :-).

Connection Test

To test the connection I send a simple ‘AT’ command

ESP send AT
Выход AT-команд

AT Command Output

and the module should respond with

AT\r\r\n\r\nOK\r\n

Module Restart

Sometimes the module gets stuck. What helps is a power-on reset of the module. Another way is to send the

AT+RST

command to reset the module. The module will boot up and print a ‘ready’ message:

Сброс ESP8266

Reset of the ESP8266

Access Point or Device

First I need to configure if the ESP is either a device or an access point. For this, theCWMODE command is used:

AT+CWMODE=

where <mode> is one of:

  • 1: ‘Sta’, ESP8266 is a device, it connects to an existing access point
  • 2: ‘AP’, ESP8266 is an access point, so other devices can connect to it
  • 3: ‘both’. Not really clear to me, but it seems that in this mode the device is in a hybrid mode?

To have the ESP as device so it can connect to an existing access point I use

AT+CWMODE=1

and the module should answer with

AT+CWMODE=1\r\r\n\r\nOK\r\n

or with a ‘no change’:

AT+CWMODE=1\r\r\nno change\r\n

With

AT+CWMODE?

I can ask for the current mode:

Retrieving Current Mode

Retrieving Current Mode

List of Access Points

With

AT+CWLAP

I get a list of access points. It reports a list like this:

AT+CWLAP




+CWLAP:(0,"",0)
+CWLAP:(4,"APforESP",-39)
+CWLAP:(4,"iza-97497",-94)




OK

❗ I experienced problems with that command in an environment with lots of access points visible. In this case it seems the module hands up. Try first in a place with only a few access points.

For this tutorial I have configured an access point with SSID “APforESP” which shows up in my list.

The list is formatted like this

+ CWLAP: , , [, ]

With following encoding:

<ecn>:

  • 0: OPEN
  • 1: WPA_PSK
  • 2: WPA2_PSK
  • 4: WPA_WPA2_PSK

<ssid>: the SSID (string) of the access point.

<rssi>: Signal strength.

<mode>:

  • 0: manually connect
  • 1: automatic connect

Connecting to Access Point

To connect to an access point I use the command

AT+CWJAP="<ssid>","<pwd>"

Of course replace and with your setup. The module should report back an “OK” message, and you are connected :-).

❗ The module stores the ssid and password. After power-up, the module will automatically reconnect to the Access Point.

IP Address

Once connected I can check the IP address I have been assigned to with

AT+CIFSR

which should give something like

AT+CIFSR




192.168.0.111

So now I know my module IP address :-). With this I can ping my module:

Pinging my ESP Module

Pinging my ESP Module

Building a Web Server

Now as we hav a connection, it is time to use it to run a web server :-).What I want to serve a web page which I can use to turn on or off the LEDs on the board.

Number of Connections: CIPMUX

Before I start the server I need to make sure it accepts multiple connections. For this I use the following command:

AT+CIPMUX=1

The parameter is either 0 (single connection), or 1 (multiple connections). For a web server I need to set it up for multiple connections.

The ESP module should respond with

AT+CIPMUX=1\r\n\r\nOK\r\n

:info: To make it clear, I have included the ‘\r’ and ‘\n’ in the responses.

Starting the Server: CIPSERVER

I start the server with

AT+CIPSERVER=1,80

The first parameter is either 0 (close connection) or 1 (open connection), followed by the port. I use here the standard http port (80).

The module should answer with:

AT+CIPSERVER=1,80\r\r\n\r\nOK\r\n

or if it is already running the server with a ‘no change’:

AT+CIPSERVER=1,80\r\r\nno change\r\n

No I have a connection open on my IP address (see above: 192.168.0.111), listening to the port I have specified (80).

Connecting to the Server with Browser

I enter the IP address in a web browser:

http://192.168.0.111:80

For clarity I have specified the standard HTTP port (80). So if you are using a different port, make sure you specify it in the address line.

Connection from FireFox

Connection from FireFox

The browser now sends a GET request to the module, and I will see this from the message printed out from the module:

First response from Module

First response from Module

The ‘Link’ indicates that it has established a link. IPD (IP Data?) is followed by the channel number (this will the one we have to respond to), plus the size of the following data (296 bytes in that case).

As I’m not responding (yet), there will be a timeout (after about 1 minute or so), with an ‘Unlink’ message from the module:

Link




+IPD,0,296:GET / HTTP/1.1
Host: 192.168.0.111
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:33.0) Gecko/20100101 Firefox/33.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: de,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
OK
Unlink
Unlink message

Unlink message

Sending Data to Server: CIPSEND

Now I need to respond and send data to the browser. For this I need to know the channel number, and this is provided in the IPD message from above, right after the comma:

 +IPD,0

To send data, I use the command

AT+CIPSEND=<channel>,<size>

So I connect again with the browser, and I send 5 bytes (“hello”) with:

AT+CIPSEND=0,5

The ESP8266 responds with

AT+CIPSEND=0,5\r\n>

Notice the ‘>’ at the end: this is my signal to send the actual data (“hello” in my case):

hello

The ESP8266 now resonds with a SEND OK:

Data Sent

Data Sent

However, the browser is still busy and spins around. I already thought that I did something wrong, but after the browser run into a timeout (after about one minute), my data is there! 🙂

Hello in Browser

Hello in Browser

Closing Connection: CIPCLOSE

So things *are* working :-). The trick is that I have to close the connection after I have sent the data. There is a CIPCLOSE command I can use:

AT+CIPCLOSE=<channel>

which I can use to close a channel. So I close the connection with

AT+CIPCLOSE=0

and now the browser shows the content right away :-).

Web Server Implementation

So far I have used the module in command line and manual mode. This is great for exploration of the protocol, but for building the web server I need to do this programmatically. For this I run my ‘main’ loop in APP_Run().  After printing a greeting message and initializing the sub modules, it processes the web/module responses, parses the shell command line interfaces and blinks the green (LEDG) LED.

01.
voidAPP_Run(void) {
02.
  CLS1_ConstStdIOType *io;
03.
  
04.
  WAIT1_Waitms(1000); /* wait after power-on */
05.
  ESP_Init();
06.
  SHELL_Init();
07.
  io = CLS1_GetStdio();
08.
  CLS1_SendStr("\r\n------------------------------------------\r\n", io->stdOut);
09.
  CLS1_SendStr("ESP8266 with FRDM-KL25Z\r\n", io->stdOut);
10.
  CLS1_SendStr("------------------------------------------\r\n", io->stdOut);
11.
  CLS1_PrintPrompt(io);
12.
  for(;;) {
13.
    WebProcess();
14.
    SHELL_Parse();
15.
    WAIT1_Waitms(10);
16.
    LEDG_Neg();
17.
  }
18.
}

With

ESP server start

I start the web server:

Starting the Web Server

Starting the Web Server

It sends the AT+CIPMUX command followed by the AT+CIPSERVER to start the server, and then listens to the port. Reading and responding messages is done in WebProcess():

01.
staticvoidWebProcess(void) {
02.
  uint8_t res=ERR_OK;
03.
  bool isGet;
04.
  uint8_t ch_id=0;
05.
  uint16_t size=0;
06.
  constuint8_t *p;
07.
  constCLS1_StdIOType *io;
08.
  
09.
  if(ESP_IsServerOn()) {
10.
    io = CLS1_GetStdio();
11.
    res = ESP_GetIPD(APP_EspMsgBuf, sizeof(APP_EspMsgBuf), &ch_id, &size, &isGet, 1000, io);
12.
    if(res==ERR_OK) {
13.
      if(isGet) { /* GET: put web page */
14.
        res = SendWebPage(ch_id, LEDR_Get()!=FALSE, 21/*dummy temperature*/, io);
15.
        if(res!=ERR_OK && io!=NULL) {
16.
          CLS1_SendStr("Sending page failed!\r\n", io->stdErr); /* copy on console */
17.
        }
18.
      } else{ /* POST: received info */
19.
        intpos;
20.
  
21.
        pos = UTIL1_strFind(APP_EspMsgBuf, "radio=");
22.
        if(pos!=-1) { /* found */
23.
          if(UTIL1_strncmp(&APP_EspMsgBuf[pos], "radio=0", sizeof("radio=0")-1)) {
24.
            LEDR_On();
25.
          } elseif(UTIL1_strncmp(&APP_EspMsgBuf[pos], "radio=1", sizeof("radio=1")-1)) {
26.
            LEDR_Off();
27.
          }
28.
        }
29.
        res = SendWebPage(ch_id, LEDR_Get()!=FALSE, 20/*dummy temperature*/, io);
30.
        if(res!=ERR_OK && io!=NULL) {
31.
          CLS1_SendStr("Sending page failed!\r\n", io->stdErr); /* copy on console */
32.
        }
33.
      }
34.
      CLS1_SendStr("INFO: Closing connection...\r\n", io->stdOut);
35.
      res = ESP_CloseConnection(ch_id, io, ESP_DEFAULT_TIMEOUT_MS);
36.
    }
37.
  } else{ /* copy messages we receive to console */
38.
    while(AS2_GetCharsInRxBuf()>0) {
39.
      uint8_t ch;
40.
  
41.
      (void)AS2_RecvChar(&ch);
42.
      CLS1_SendChar(ch);
43.
    }
44.
  }
45.
}

If the server is not enabled, it simply copies the received messages to the console:

1.
} else{ /* copy messages we receive to console */
2.
   while(AS2_GetCharsInRxBuf()>0) {
3.
     uint8_t ch;
4.
 
5.
     (void)AS2_RecvChar(&ch);
6.
     CLS1_SendChar(ch);
7.
   }
8.
 }

Otherwise it scans for an IPD message (ESP_GetIPD()). This function returns the whole message, the channel, the message size and if it is a GET or POST message:

1
res = ESP_GetIPD(APP_EspMsgBuf, sizeof(APP_EspMsgBuf), &ch_id, &size, &isGet, 1000, io);

If it is a GET message, then it sends a HTML page to the module:

1
res = SendWebPage(ch_id, LEDR_Get()!=FALSE, 21 /*dummy temperature*/, io);

This web page shows the status of the red LED on the board, a (dummy) temperature value and a button to submit new LED values:

WSP8266 Web Server

WSP8266 Web Server

The HTML code for this page is constructed in SendWebPage() and sent withAT+CIPSEND:

01.
staticuint8_t SendWebPage(uint8_t ch_id, bool ledIsOn, uint8_t temperature, constCLS1_StdIOType *io) {
02.
  staticuint8_t http[1024];
03.
  uint8_t cmd[24], rxBuf[48], expected[48];
04.
  uint8_t buf[16];
05.
  uint8_t res = ERR_OK;
06.
  
07.
  /* construct web page content */
08.
  UTIL1_strcpy(http, sizeof(http), (uint8_t*)"HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n");
09.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<html>\r\n<body>\r\n");
10.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<title>ESP8266 Web Server</title>\r\n");
11.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<h2>Web Server using ESP8266</h2>\r\n");
12.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"
13.
<hr>\r\n");
14.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"<p><form method=\"POST\"><strong>Temp: <input type=\"text\" size=2 value=\"");
15.
  UTIL1_strcatNum8s(http, sizeof(http), temperature);
16.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"\"> <sup>O</sup>C");
17.
  if(ledIsOn) {
18.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"<p><input type=\"radio\" name=\"radio\" value=\"0\" >Red LED off");
19.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"
20.
<input type=\"radio\" name=\"radio\" value=\"1\" checked>Red LED on");
21.
  } else{
22.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"<p><input type=\"radio\" name=\"radio\" value=\"0\" checked>Red LED off");
23.
    UTIL1_strcat(http, sizeof(http), (uint8_t*)"
24.
<input type=\"radio\" name=\"radio\" value=\"1\" >Red LED on");
25.
  }
26.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"</strong><p><input type=\"submit\"></form></span>");
27.
  UTIL1_strcat(http, sizeof(http), (uint8_t*)"</body>\r\n</html>\r\n");
28.
  
29.
  UTIL1_strcpy(cmd, sizeof(cmd), "AT+CIPSEND="); /* parameters are <ch_id>,<size> */
30.
  UTIL1_strcatNum8u(cmd, sizeof(cmd), ch_id);
31.
  UTIL1_chcat(cmd, sizeof(cmd), ',');
32.
  UTIL1_strcatNum16u(cmd, sizeof(cmd), UTIL1_strlen(http));
33.
  UTIL1_strcpy(expected, sizeof(expected), cmd); /* we expect the echo of our command */
34.
  UTIL1_strcat(expected, sizeof(expected), "\r\r\n> "); /* expect "> " */
35.
  UTIL1_strcat(cmd, sizeof(cmd), "\r\n");
36.
  res = ESP_SendATCommand(cmd, rxBuf, sizeof(rxBuf), expected, ESP_DEFAULT_TIMEOUT_MS, io);
37.
  if(res!=ERR_OK) {
38.
    if(io!=NULL) {
39.
      CLS1_SendStr("INFO: TIMEOUT, closing connection!\r\n", io->stdOut);
40.
    }
41.
  } else{
42.
    if(io!=NULL) {
43.
      CLS1_SendStr("INFO: Sending http page...\r\n", io->stdOut);
44.
    }
45.
    UTIL1_strcat(http, sizeof(http), "\r\n\r\n"); /* need to add this to end the command! */
46.
    res = ESP_SendATCommand(http, NULL, 0, NULL, ESP_DEFAULT_TIMEOUT_MS, io);
47.
    if(res!=ERR_OK) {
48.
      CLS1_SendStr("Sending page failed!\r\n", io->stdErr); /* copy on console */
49.
    } else{
50.
      for(;;) { /* breaks */
51.
        res = ESP_ReadCharsUntil(buf, sizeof(buf), '\n', 1000);
52.
        if(res==ERR_OK) { /* line read */
53.
          if(io!=NULL) {
54.
            CLS1_SendStr(buf, io->stdOut); /* copy on console */
55.
          }
56.
        }
57.
        if(UTIL1_strncmp(buf, "SEND OK\r\n", sizeof("SEND OK\r\n")-1)==0) { /* ok from module */
58.
          break;
59.
        }
60.
      }
61.
    }
62.
  }
63.
  returnres;
64.
}

In case of a POST message (user has pressed the button), I scan for the radio element string and turn on/off the LED accordingly, and re-submit the new web page:

01.
} else{ /* POST: received info */
02.
      intpos;
03.
 
04.
      pos = UTIL1_strFind(APP_EspMsgBuf, "radio=");
05.
      if(pos!=-1) { /* found */
06.
        if(UTIL1_strncmp(&APP_EspMsgBuf[pos], "radio=0", sizeof("radio=0")-1)) {
07.
          LEDR_On();
08.
        } elseif(UTIL1_strncmp(&APP_EspMsgBuf[pos], "radio=1", sizeof("radio=1")-1)) {
09.
          LEDR_Off();
10.
        }
11.
      }
12.
      res = SendWebPage(ch_id, LEDR_Get()!=FALSE, 20/*dummy temperature*/, io);
13.
      if(res!=ERR_OK && io!=NULL) {
14.
        CLS1_SendStr("Sending page failed!\r\n", io->stdErr); /* copy on console */
15.
      }
16.
    }

Finally, it closes the connection at the end:

1.
CLS1_SendStr("INFO: Closing connection...\r\n", io->stdOut);
2.
      res = ESP_CloseConnection(ch_id, io, ESP_DEFAULT_TIMEOUT_MS);

With this, I handle GET and POST messages and can toggle the LED on my board 🙂 :-).

Summary

It is amazing what is possible with this tiny and inexpensive ($4.50) WiFi module. The simple AT interface allows small and tiny microprocesors to connect to the internet or the local network. With all the hype around ‘Internet of Things’ this is where things very likely will end up: small nodes connecting in an easy way to the network. The processor on that ESP8266 is probably more powerful than the KL25Z (the specs and data sheets of that ESP8266 are still evolving). Or it is possible to run that module in standalone mode too which is a very interesting approach too, see the links at the end of this article. But still having an UART way to connect to the network is very useful and powerful. Other modules costs multiple times more. I expect that many vendors will come up with similar integrated modules e.g. to combine an ARM processor with the WiFi radio, similar that ESP8266 module. For sure that ESP8266 has a head start and paved the way how WiFi connectivity should work. We all will see what the future brings. Until then, that ESP8266 module is something I can use in many projects :-).

The sources and project files can be found on GitHub:
https://github.com/ErichStyger/mcuoneclipse/tree/master/Examples/KDS/FRDM-KL25Z/FRDM-KL25Z_ESP8266

Happy Web-Serving 🙂

Useful Links: