Arduino и ESP8266: веб-сервер
Это более старое руководство (изначально опубликовано 4 января 2015 г., обновлено 7 июля 2017 г.), демонстрирующее создание веб-сервера с использованием Arduino и модуля ESP8266 через AT-команды.
Примечание
Это очень старое руководство, и с тех пор многое изменилось. Одним из главных достижений стало ядро ESP8266 для Arduino IDE, позволяющее программировать ESP8266 напрямую.
Функциональность
Веб-сервер включает:
Счётчик запросов
Текстовое поле ввода
Отправку имени через HTTP GET запрос
Используемые AT-команды
AT+CWMODE=1 – режим станции (ESP8266 подключается как клиент к существующей сети)
AT+CWJAP=»NetworkSSID»,»password» – подключение к сети с учётными данными
AT+CIFSR – возвращает IP-адрес, выделенный роутером
AT+CIPMUX=1 – включение множественных одновременных подключений (необходимо для режима сервера)
AT+CIPSERVER=1,80 – запуск сервера на порту 80
AT+CIPSEND=0,25 – отправка 25 символов на канале 0
AT+CIPCLOSE=0 – закрытие соединения на канале 0
Полный скетч Arduino
// Базовый веб-сервер Arduino и ESP8266
//
// использует AltSoftSerial, скачать с https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
// можно заменить на обычный Software Serial
#include <AltSoftSerial.h>
// Arduino пин 08 для RX
// Arduino пин 09 для TX
AltSoftSerial espSerial;
const bool printReply = true;
const char line[] = "-----\n\r";
int loopCount=0;
char html[50];
char command[20];
char reply[500]; // обычно так не делают
char ipAddress [20];
char name[30];
int lenHtml = 0;
char temp[5];
void setup()
{
Serial.begin(9600);
Serial.println("Start\r\n\r\n");
espSerial.begin(9600); // скорость вашего модуля ESP8266 может отличаться
// сброс ESP8266
Serial.println("reset the module");
espSerial.print("AT+RST\r\n");
getReply( 2000 );
// настройка как станция
Serial.println("Change to station mode");
espSerial.print("AT+CWMODE=1\r\n");
getReply( 1500 );
// подключение к сети. Используется DHCP. IP будет назначен роутером.
Serial.println("Connect to a network ");
// Введите SSID и пароль вашей сети
espSerial.print("AT+CWJAP=\"myNetworkSSID\",\"password\"\r\n");
getReply( 6000 );
// получение IP-адреса
Serial.println("Get the ip address assigned by the router");
espSerial.print("AT+CIFSR\r\n");
getReply( 1000 );
// разбор IP-адреса
int len = strlen( reply );
bool done=false;
bool error = false;
int pos = 0;
while (!done)
{
if ( reply[pos] == 10) { done = true;}
pos++;
if (pos > len) { done = true; error = true;}
}
if (!error)
{
int buffpos = 0;
done = false;
while (!done)
{
if ( reply[pos] == 13 ) { done = true; }
else { ipAddress[buffpos] = reply[pos]; buffpos++; pos++; }
}
ipAddress[buffpos] = 0;
}
else { strcpy(ipAddress,"ERROR"); }
// настройка множественных подключений
Serial.println("Set for multiple connections");
espSerial.print("AT+CIPMUX=1\r\n");
getReply( 1500 );
// запуск сервера на порту 80
Serial.println("Start the server");
espSerial.print("AT+CIPSERVER=1,80\r\n");
getReply( 1500 );
Serial.println("");
Serial.println("Waiting for page request");
Serial.print("Connect to "); Serial.println(ipAddress);
Serial.println("");
}
void loop()
{
if(espSerial.available()) // проверка, отправляет ли ESP8266 данные
{
// это ответ +IPD -- он довольно длинный
// обычно не нужно копировать всё сообщение в переменную
getReply( 2000 );
bool foundIPD = false;
for (int i=0; i<strlen(reply); i++)
{
if ( (reply[i]=='I') && (reply[i+1]=='P') && (reply[i+2]=='D') ) { foundIPD = true; }
}
if ( foundIPD )
{
loopCount++;
// проверка наличия имени -- ищем name=
bool haveName = false;
int nameStartPos = 0;
for (int i=0; i<strlen(reply); i++)
{
if (!haveName)
{
if ( (reply[i]=='n') && (reply[i+1]=='a') && (reply[i+2]=='m') && (reply[i+3]=='e') && (reply[i+4]=='=') )
{
haveName = true;
nameStartPos = i+5;
}
}
}
// получение имени -- копируем всё от nameStartPos до первого пробела
if (haveName)
{
int tempPos = 0;
bool finishedCopying = false;
for (int i=nameStartPos; i<strlen(reply); i++)
{
if ( (reply[i]==' ') && !finishedCopying ) { finishedCopying = true; }
if ( !finishedCopying ) { name[tempPos] = reply[i]; tempPos++; }
}
name[tempPos] = 0;
}
if (haveName) { Serial.print( "name = "); Serial.println(name); Serial.println(""); }
else { Serial.println( "no name entered"); Serial.println(""); }
// начинаем отправку HTML
strcpy(html,"<html><head></head><body>");
strcpy(command,"AT+CIPSEND=0,25\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
strcpy(html,"<h1>ESP8266 Webserver</h1>");
strcpy(command,"AT+CIPSEND=0,26\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
strcpy(html,"<p>Served by Arduino and ESP8266</p>");
strcpy(command,"AT+CIPSEND=0,36\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
strcpy(html,"<p>Request number ");
itoa( loopCount, temp, 10);
strcat(html,temp);
strcat(html,"</p>");
// нужна длина html
int lenHtml = strlen( html );
strcpy(command,"AT+CIPSEND=0,");
itoa( lenHtml, temp, 10);
strcat(command, temp);
strcat(command, "\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
if (haveName)
{
// вывод имени
strcpy(html,"<p>Your name is "); strcat(html, name ); strcat(html,"</p>");
// нужна длина html для команды cipsend
lenHtml = strlen( html );
strcpy(command,"AT+CIPSEND=0,"); itoa( lenHtml, temp, 10); strcat(command, temp); strcat(command, "\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
}
strcpy(html,"<form action=\""); strcat(html, ipAddress); strcat(html, "\" method=\"GET\">");
lenHtml = strlen( html );
itoa( lenHtml, temp, 10);
strcpy(command,"AT+CIPSEND=0,");
itoa( lenHtml, temp, 10);
strcat(command, temp);
strcat(command, "\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
strcpy(html,"Name:<br><input type=\"text\" name=\"name\">");
strcpy(command,"AT+CIPSEND=0,40\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
strcpy(html,"<input type=\"submit\" value=\"Submit\"></form>");
strcpy(command,"AT+CIPSEND=0,43\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
strcpy(html,"</body></html>");
strcpy(command,"AT+CIPSEND=0,14\r\n");
espSerial.print(command);
getReply( 2000 );
espSerial.print(html);
getReply( 2000 );
// закрытие соединения
espSerial.print( "AT+CIPCLOSE=0\r\n" );
getReply( 1500 );
Serial.println("last getReply 1 ");
getReply( 1500 );
Serial.println("last getReply 2 ");
getReply( 1500 );
} // if(espSerial.find("+IPD"))
} //if(espSerial.available())
delay (100);
// ожидание следующего запроса
}
void getReply(int wait)
{
int tempPos = 0;
long int time = millis();
while( (time + wait) > millis())
{
while(espSerial.available())
{
char c = espSerial.read();
if (tempPos < 500) { reply[tempPos] = c; tempPos++; }
}
reply[tempPos] = 0;
}
if (printReply) { Serial.println( reply ); Serial.println(line); }
}
Настройка
Пользователям необходимо изменить учётные данные сети, заменив SSID и пароль в строке espSerial.print на свои реальные данные WiFi.
Известные проблемы
Длительное время отклика из-за 2-секундных задержек на команды
Модуль иногда возвращает ответы «busy», требующие повторной отправки
Лишние сообщения от ESP8266 после передачи страницы
Автор признаёт, что код недостаточно эффективен и мог бы выиграть от рефакторинга