ESP8266 Урок 29. FreeRTOS. Wi-Fi. STA. TCP Server

 

 

 

Продолжаем работу по программированию микроконтроллера ESP8266 с использованием операционной системы реального времени FREEFTOS, а также продолжаем работу с протоколом TCP (Transmission Control Protocol). И на данном уроке мы уже попытаемся создать простенький TCP сервер, который позволит нам обрабатывать пришедшие пакеты от клиентов, а также отвечать на них. Напомню также, что мы также этим раньше подобные задачи решали с использованием других контроллеров, поэтому нам будет гораздо легче справиться с нашей задачей.

Схема наша осталась прежняя

 

 

А проект мы, как обычно, за основу возьмём из прошлого урока с именем WIFI_STA_TCP_CLIENT_RTOS и дадим ему новое имя WIFI_STA_TCP_SERVER_RTOS. Откроем наш проект в Eclipse.

Я думаю, что не для кого не секрет, что работа любого сервера связана с затратами ресурсов, в том числе памяти, поэтому давайте будем это дело отслеживать, чтобы знать, какие у нас имеются возможности. Для этого перейдём в файл main.c и в функции дополнительной задачи task1 добавим следующую строку, которая нам покажет в терминальной программе, сколько у нас осталось памяти в куче, а строку с версией SDK можно будет удалить

 

os_printf("SDK version:%s\n", system_get_sdk_version());

os_printf("Heap free size: %d\n", xPortGetFreeHeapSize());

 

Вполне возможно, что для работы функции xPortGetFreeHeapSize потребуется внести некоторые изменения в настройках FreeRTOS в конфигурационных файлах. Что требуется для какого функционала, мы изучали, когда работали с контроллерами STM32 и, думаю, что возвращаться к этому сейчас – дополнительная трата времени.

Давайте сразу проверим, работает ли наша функция, для чего запустим терминальную программу и прошьём наш контроллер.

Мы должны получить примерно следующее

 

 

Также предлагаю здесь смотреть сетевой адрес нашей платы, так как не всегда можно подключиться из терминальной программы сразу, а он нам будет нужен, чтобы отфильтровать по нему пакеты затем в программе анализатора сетевого трафика.

В этой же функции объявим несколько переменных

 

 

Пропишем в строковый массив сначала хотя бы какой-то адрес, так как вдруг мы его сразу не получим

 

 

Пропишем его в переменную структуры

 

 

В бесконечном цикле получим адрес нашей станции и отобразим его в терминальной программе

 

 

Проверим отображение адреса, собрав код и прошив контроллер

 

 

Займёмся теперь собственно сервером.

Перейдём в файл wifi.c и удалим для начала функцию recv_task вместе с её телом.

Вместо неё добавим другую функцию, для задачи, которая будет заниматься отдельным клиентом, подключенным к серверу

 

 

В функции tcp_task удалим следующие строки

 

//Заполнение информации о клиенте

cliaddr.sin_family = AF_INET; // IPv4

cliaddr.sin_addr.s_addr = INADDR_ANY;

cliaddr.sin_port = htons(CLIENT_PORT);

 

А четыре строки, где инициализируется информация о сервере, перенесём выше, до установки связи с сокета с адресом сервера

 

 

Изменим адрес сервера в данной строке, так как данный адрес может быть разным и это адрес нашего узла

 

servaddr.sin_addr.s_addr = INADDR_ANY;

 

Исправим также здесь, изменив в параметрах структуру клиента на структуру сервера

 

if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)) < 0 )

 

Удалим следующие строки:

 

if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(struct sockaddr_in)) >= 0)

{

  snprintf(str1, sizeof(str1), "Connected");

  xQueueSendToBack(xQueue, &xLCDData, 0);

  recv_socket01.y_pos = 2;

  recv_socket01.sock = sockfd;

  xTaskCreate(recv_task, "recv_task", 2048, (void*)&recv_socket01, 3, &recv_handle);

  vTaskDelay( 2000 / portTICK_RATE_MS);

  for(int i=1; i<10; i++)

  {

    snprintf(str1, sizeof(str1), "Hello from ESP!!!\n");

    write(sockfd,(void *) str1,strlen(str1));

    vTaskDelay( 2000 / portTICK_RATE_MS);

  }

}

char fl = 1;

xQueueSendToBack(xQueueClose, &fl, 0);

for(;;)

{

  xQueueReceive(xQueueCloseAsk, &fl, 0);

  if(fl==1)

  {

    os_printf("task delete\n");

    break;

  }

  vTaskDelay( 10 / portTICK_RATE_MS);

}

 

После связи сокета с адресом сервера начнём слушать наш сокет

 

 

Надеюсь, мы все помним, что даёт нам цифра во втором параметре. Так и быть, напомню тем, кто забыл. Эта цифра – максимальное количество попыток соединений в очереди.

Объявим локальную переменную для идентификации сокета, который будет использоваться для клиента, пытающегося соединиться с нашим сервером

 

int sockfd, accept_sock;

 

Объявим также переменную для хранения размера адреса сокета

 

 

Также нам будет нужна переменная для хранения сетевого адреса клиента

 

struct sockaddr_in servaddr, cliaddr, remotehost;

 

Добавим бесконечный цикл, в котором начнём ожидать подключения клиента, пока клиент не подключится, мы будем висеть в этом месте

 

 

Затем нам надо будет создать задачу для работы с сокетом клиента, подключившегося к нам, передав туда ряд параметров. Для этого объявим глобальную структуру и сразу же объявим и переменную тип данной структуры

 

 

Вернёмся в нашу задачу tcp_task и в бесконечном цикле покажем в терминальной программе значение переменной идентификатора сокета

 

 

Проинициализируем поля переменной и создадим задачу, передав в качестве параметра нашу переменную

 

 

Теперь займёмся задачей клиента, перейдя в функцию client_socket_task и объявим символьный массив, а также некоторые переменные

 

 

Присвоим адрес параметров задачи объявленному указателю

 

 

Объявим переменную структуры адреса сокета, а также размера данного адреса

 

 

Объявим переменную длины буфера и сразу инициализируем её

 

 

Объявим и инициализируем массив для хранения данных буфера

 

 

 

Инициализируем поля переменной структуры для задачи дисплея

 

 

Присвоим идентификатор сокета локальной переменной

 

 

Аналогично инициализируем другие переменные

 

 

Добавим бесконечный цикл, в котором попытаемся принять пакет от клиента

 

 

Если пакет валидный, то есть, если мы провалились не по ошибке, в том числе не по истечению таймаута, который можно также настроить, то обнулим сначала строку в буфере

 

 

Если пришел пакет с определённой строкой, которая будет служить командой разрыва соединения от клиента, то закроем наш сокет и удалим задачу

 

 

А если пришел непустой пакет, то скопируем данные из буфера в строковую переменную, остаток до 20 забьём пробелами, завершим нулём и отправим на дисплей в нужную позицию, соответствующую идентификатору (номеру) сокета. Также отправим строку с данными в терминальную программу и покажем в терминальной программе порт и сетевой адрес клиента

 

 

В функции инициализации init_esp_wifi удалим создание следующих очередей, которые не используются в проекте, для экономии памяти

 

xQueueClose = xQueueCreate(10, sizeof(unsigned char));

xQueueCloseAsk = xQueueCreate(10, sizeof(unsigned char));

 

Также можно удалить и их объявление

 

xQueueHandle xQueue, xQueueClose, xQueueCloseAsk;

 

Ну, вроде всё. Наконец-то настал час испытания нашего проекта.

Соберём его и прошьём контроллер, запустим терминальную программу.

В качестве программы-клиента будем использовать программу Putty на компьютере.

Также запустим анализатор пакетов WireShark, в котором отфильтруемся по адресу нашей платы, который мы теперь постоянно видим в терминальной программе.

Попытаемся соединиться с нашим сервером

 

 

Увидим в Wireshark, что соединение удалось

 

 

В терминальной программе мы также видим, что сокет валидный и после соединения у нас просела память.

Попробуем что-нибудь передать нашей плате

 

 

Мы видим, что пакет успешно передан

 

 

Также мы видим всю информацию в терминальной программе

 

 

На дисплее мы также видим нашу строку

 

 

Можно попытаться передать ещё какие-нибудь строки. Всё нормально передаётся.

Запустим ещё один клиент, запустив для этого ещё раз программу Putty и соединившись с сервером. Мы увидим, что создался ещё один сокет и память просела ещё

 

 

Попробуем передать строку из этого клиента

 

 

В терминальной программе мы увидим данную строку и заметим ещё то, что IP-адрес клиента не изменился, а порт уже другой

 

 

На дисплей наша строка также пришла в соответствующую позицию второго сокета

 

 

Попытаемся разъединиться с сервером с обеих клиентов, отправив строки -c, увидим следующее в терминальной программе

 

 

Мы видим, что объём памяти опять увеличился. Таким образом, мы можем быть уверены, что задачи клиентов успешно удалились.

Теперь посмотрим разъединение узлов в анализаторе трафика

 

 

Всё происходит корректно. Не обращайте внимание на разницу в номерах портов при соединении и разъединении. Статья писалась не сразу, происходили коллизии, разъединения с виртуальным портом и т.д.

Можете также попробовать запустить клиенты ещё раз, всё должно работать корректно, я проверял.

Таким образом, на данном занятии нам удалось создать простенький сервер, работающий по протоколу TCP с несколькими клиентами. Мы увидели, что количество таких клиентов ограничено в связи с небольшим размером кучи. Можно поэкспериментировать с размером этой кучи, также с размерами стеков для задач клиентов и других задач и увеличить практическое количество клиентов.

Всем спасибо за внимание!

 

Предыдущий урок Программирование МК ESP8266 Следующий урок

 

Исходный код

 

 

Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU

Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266

Дисплей LCD 20×4 можно приобрести здесь Дисплей LCD 20×4

Дисплей LCD 16×2

Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

ESP8266 FreeRTOS. Wi-Fi. STA. TCP Server

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*