Продолжаем работать с программированием микроконтроллера ESP8266 с использованием операционной системы реального времени FREEFTOS, и на данном уроке мы уже попытаемся создать простенький HTTP сервер, который позволит нам принять запрос документа от клиента, обработать его и в ответ направить клиенту нужную информацию. Также позволю себе лишний раз напомнить, что мы раньше подобные задачи решали с использованием других контроллеров, поэтому думаю, что с данной задачей мы без особого труда справимся.
Дисплей я решил отключить, так как он нам сильно не нужен, в целях экономии кучи ибо создавать лишнюю задачу под дисплей уже не придётся. По желанию вы можете дисплей оставить. Также плату я тоже решил сменить и подключил другую, подобную, чтобы показать, что мы можем для наших задач использовать любые платы с контроллером ESP8266 на борту
Также у меня есть такая же плата, той же фирмы, но уже с OLED-дисплеем разрешением 128х32, которую мы впоследствии, возможно, также будем использовать в наших уроках.
Ссылку на покупку данной платы и платы с дисплеем я не даю, так как на Aliexpress я таких не нашел, есть в других магазинах и торговых ресурсах, но там они то появляются, то исчезают. Поэтому, если у вас будет возможность найти данные платы, то советую приобрести.
Подключим нашу плату к USP-шине компьютера
Проект мы, как обычно, за основу возьмём из прошлого урока с именем WIFI_STA_TCP_SERVER_RTOS и дадим ему новое имя WIFI_STA_HTTP_SERVER_RTOS. Откроем наш проект в Eclipse.
Так как дисплей мы не используем, мы можем спокойно удалить файлы lcd.h и lcd.c из дерева проекта, а также удалить их и физически из каталога с проектом.
В файлах main.h и wifi.h удалим подключение данной библиотеки
#include «lcd.h»
В функции user_init удалим следующие строки
i2c_mas_gpio_init();
I2C_MASTER_SDA_LOW_SCL_LOW();
LCD_ini();
LCD_SetPos(0,0);
Перейдём в файл wifi.c и удалим функцию vLCDTask вместе с телом.
Функцию client_socket_task мы также удалим вместе с телом ибо мы не будем создавать отдельных задач для работы с подключенными клиентами.
В функции tcp_task удалим вот эти строки объявления очереди и инициализации переменной структуры для дисплея
qData xLCDData;
xLCDData.y_pos = 1;
xLCDData.str = str1;
Все вот эти глобальные объявления также удалим
typedef struct
{
unsigned char y_pos;
char *str;
} qData;
//————————————————
typedef struct struct_recv_socket_t {
int sock;
unsigned char y_pos;
} struct_recv_socket;
struct_recv_socket recv_socket01;
typedef struct struct_client_socket_t {
struct sockaddr_in remotehost;
socklen_t sockaddrsize;
int accept_sock;
uint16_t y_pos;
} struct_client_socket;
struct_client_socket client_socket01;
//————————————————
xQueueHandle xQueue;
Вернёмся в функцию tcp_task и объявим размер буфера, а также массив для него
1 2 3 |
char addr_str[25]; int buflen = 512; uint8_t buf[512] = {}; |
Вот здесь мы пока в очередь будем ставить только одного клиента, так как мы будем передавать в браузер только одну страницу без рисунков, иконок и всего прочего
listen(sockfd, 1);
В теле следующего условия пока также удалим весь код
if(accept_sock >= 0)
{
client_socket01.accept_sock = accept_sock;
client_socket01.remotehost = remotehost;
client_socket01.sockaddrsize = sockaddrsize;
client_socket01.y_pos = accept_sock-1;
xTaskCreate(client_socket_task, «client_socket_task», 4096, (void*)&client_socket01, 5, NULL);
Вот эти строки также удалим
close(sockfd);
snprintf(str1, sizeof(str1), «Disonnected»);
xQueueSendToBack(xQueue, &xLCDData, 0);
В функции инициализации init_esp_wifi удалим также создание очереди и задачи для работы с дисплеем
xQueue = xQueueCreate(10, sizeof(qData));
xTaskCreate(vLCDTask, «vLCDTask», 256, NULL, 2, NULL);
Теперь у нас, по крайней мере, проект будет собираться.
Вернёмся в функцию tcp_task и добавим объявление ещё одной переменной, которая будет хранить результат приёма пакета
int sockfd, accept_sock, ret;
Попытаемся принять пакет с запросом от клиента
1 2 3 |
if(accept_sock >= 0) { ret = recvfrom(accept_sock, buf, buflen, 0, (struct sockaddr *)&remotehost, &sockaddrsize); |
Теперь самое главное. Перед нами встаёт вопрос, где мы будем брать контент для отправки клиенту, то есть веб-страницу, ведь у нас же нет никаких инициализированных накопителей, файловая система SPIFFS ещё в плане к изучению. А сделаем мы так, как делали раньше с AVR. Содержимое страницы и заголовка HTTP мы поместим в глобальные массивы
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
volatile bool wifi_station_static_ip = false; //------------------------------------------------ const char http_header[] = {"HTTP/1.1 200 OK\r\nServer: nginx\r\nContent-Type: text/html\r\nConnection: keep-alive\r\n\r\n"}; const uint8_t index_htm[] = { 0x3c,0x68,0x74,0x6d,0x6c,0x3e,0x3c,0x62,0x6f,0x64,0x79,0x3e,0x3c,0x68,0x31,0x20, 0x73,0x74,0x79,0x6c,0x65,0x3d,0x22,0x74,0x65,0x78,0x74,0x2d,0x61,0x6c,0x69,0x67, 0x6e,0x3a,0x20,0x63,0x65,0x6e,0x74,0x65,0x72,0x3b,0x22,0x3e,0x45,0x73,0x70,0x20, 0x38,0x32,0x36,0x36,0x3c,0x62,0x72,0x3e,0x3c,0x62,0x72,0x3e,0x48,0x54,0x54,0x50, 0x20,0x53,0x65,0x72,0x76,0x65,0x72,0x3c,0x2f,0x68,0x31,0x3e,0x0d,0x0a,0x3c,0x70, 0x3e,0x3c,0x73,0x70,0x61,0x6e,0x20,0x73,0x74,0x79,0x6c,0x65,0x3d,0x22,0x66,0x6f, 0x6e,0x74,0x2d,0x66,0x61,0x6d,0x69,0x6c,0x79,0x3a,0x20,0x54,0x69,0x6d,0x65,0x73, 0x20,0x4e,0x65,0x77,0x20,0x52,0x6f,0x6d,0x61,0x6e,0x2c,0x54,0x69,0x6d,0x65,0x73, 0x2c,0x73,0x65,0x72,0x69,0x66,0x3b,0x22,0x3e,0x57,0x69,0x2d,0x46,0x69,0x20,0x53, 0x6f,0x43,0x3c,0x2f,0x73,0x70,0x61,0x6e,0x3e,0x20,0x3c,0x2f,0x70,0x3e,0x0d,0x0a, 0x3c,0x2f,0x62,0x6f,0x64,0x79,0x3e,0x3c,0x2f,0x68,0x74,0x6d,0x6c,0x3e}; |
Вернёмся в функцию tcp_task и поместим всё это в буфер, перед этим отфильтровав всё ненужное
1 2 3 4 5 6 7 8 9 10 11 12 |
ret = recvfrom(accept_sock, buf, buflen, 0, (struct sockaddr *)&remotehost, &sockaddrsize); if(ret > 0) { if ((ret >=5 ) && (strncmp(buf, "GET /", 5) == 0)) { if ((strncmp((char const *)buf,"GET / ",6)==0)||(strncmp((char const *)buf,"GET /index.html",15)==0)) { strcpy((char*)buf,http_header); memcpy((void*)(buf + strlen(http_header)),(void*)index_htm,sizeof(index_htm)); } } } |
Отправим подготовленный пакет клиенту
1 2 |
memcpy((void*)(buf + strlen(http_header)),(void*)index_htm,sizeof(index_htm)); write(accept_sock, (const unsigned char*)buf, strlen(http_header) + sizeof(index_htm)); |
Не забываем разъединиться с клиентом
1 2 3 4 5 |
write(accept_sock, (const unsigned char*)buf, strlen(http_header) + sizeof(index_htm)); } } } close(accept_sock); |
Также у нас не будет работать приём запросов, если мы не изменим порт.
Поэтому перейдём в файл user_config.h и внесём данные изменения
#define SERVER_PORT 80
Вот и всё. Собираем проект, прошиваем контроллер и узнаём IP адрес нашей платы в терминальной программе
Попытаемся в браузере запросить страницу
Если всё нормально, то мы должны получить вот такую картину
В анализаторе трафика Wireshark мы также можем наблюдать, что обмен пакетами с сервером у нас происходит корректно, в том числе соединение с ним и его разрыв
Итак, на данном уроке нам удалось создать простейший HTTP-сервер, который может обрабатывать запрос страницы от клиента и передавать ему данную страницу в форме пакета HTTP.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Утилита для получения двоичного кода страниц и других файлов: makefsdata
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Поправте ссылку на видео.
Спасибо! Поправил. Не пойму даже, как так получилось, кусок ссылки ушёл в отдельный параграф.
Добрый день! CGI/SSI будет?