Продолжаем работу по программированию микроконтроллера ESP8266 с использованием операционной системы реального времени FREEFTOS, а также продолжаем работу с протоколом TCP (Transmission Control Protocol). И на данном уроке мы уже попробуем не просто соединиться с сервером и передать тестовый пакет, но и также, передавая пакеты, мы попробуем такие пакеты ещё и принять. Мы также этим раньше занимались с использованием других контроллеров, поэтому нам будет гораздо легче справиться с нашей задачей.
Схема наша осталась прежняя
А проект мы, соответственно, за основу возьмём из прошлого урока с именем WIFI_STA_TCP_CLIENT_CONNECT_RTOS и дадим ему новое имя WIFI_STA_TCP_CLIENT_RTOS.
Откроем наш проект в Eclipse и в файле wifi.c сначала объявим глобальную структуру для передачи параметров в задачу приёма пакета, а также объявим переменную типа данной структуры
1 2 3 4 5 6 |
} qData; typedef struct struct_recv_socket_t { int sock; unsigned char y_pos; } struct_recv_socket; struct_recv_socket recv_socket01; |
После функции vLCDTask добавим функцию задачи приёма пакетов с сервера, в теле которой объявим указатель на тип только что созданной нами переменной типа структуры, переменную типа структуры передачи параметров в задачу вывода информации на дисплей, а также объявим очередь
1 2 3 4 5 6 7 |
//------------------------------------------------ void ICACHE_FLASH_ATTR recv_task(void *pvParameters) { struct_recv_socket *arg_recv_socket; qData xLCDData; } //------------------------------------------------ |
Присвоим указатель на параметры задачи нашему указателю
1 2 |
qData xLCDData; arg_recv_socket = (struct_recv_socket*) pvParameters; |
Объявим небольшой символьный буфер, также целочисленную переменную и флаг, смысл которого мы поймём чуть позже
1 2 3 4 |
arg_recv_socket = (struct_recv_socket*) pvParameters; char data_buffer[22] = {}; int recv_data; char fl = 0; |
Присвоим указатель на наш буфер соответствующему указателю очереди дисплея, а также присвоим позицию, взятую из параметра, аналогичному полю очереди
1 2 3 |
char fl = 0; xLCDData.y_pos = arg_recv_socket->y_pos; xLCDData.str = data_buffer; |
Объявим переменную с количеством милисекунд, которые будет ждать наш клиент при попытке принять пакет от сервера и, используя соответствующую функцию, применим данный таймаут к нашему сокету
1 2 3 |
xLCDData.str = data_buffer; unsigned int timeout = 1000; setsockopt(arg_recv_socket->sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(unsigned int)); |
Добавим бесконечный цикл, в котором попытаемся принять пакет
1 2 3 4 5 |
setsockopt(arg_recv_socket->sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(unsigned int)); for(;;) { recv_data = recv(arg_recv_socket->sock, data_buffer, sizeof(data_buffer), 0); } |
Здесь мы будем висеть до тех пор, пока не примем пакет либо пока не истекут 1000 милисекунд в соответствии с нашими настройками сокета.
Данная функция возвращает количество принятых байтов в пакете или -1 в случае ошибки.
Для интереса отправим в терминальную программу значение данного параметра
1 2 |
recv_data = recv(arg_recv_socket->sock, data_buffer, sizeof(data_buffer), 0); os_printf("recv_data: %d\n", recv_data); |
В случае ошибки приёма выведем в терминальную программу соответствующее сообщение
1 2 3 4 5 |
os_printf("recv_data: %d\n", recv_data); if(recv_data == -1) { os_printf("Socket error\n"); } |
Добавим глобальную очередь для передачи флага состояния из другой задачи
xQueueHandle xQueue, xQueueClose;
Поработаем пока с задачей создания сокета и передачи пакетов, для чего в её функции tcp_task объявим чистый указатель на задачу приёма пакета
1 2 |
struct sockaddr_in servaddr, cliaddr; xTaskHandle recv_handle = NULL; |
Ниже создадим задачу, проинициализировав сначала параметры
1 2 3 4 5 |
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); |
Подождём 2 секунды
1 2 |
xTaskCreate(recv_task, "recv_task", 2048, (void*)&recv_socket01, 3, &recv_handle); vTaskDelay( 2000 / portTICK_RATE_MS); |
С интервалом в 2 секунды передадим пакет серверу
1 2 3 4 5 6 7 |
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); } |
Выйдем из цикла и из условия и удалим вот эти старые строки
vTaskDelay( 2000 / portTICK_RATE_MS);
snprintf(str1, sizeof(str1), «Hello from ESP!!!\n»);
write(sockfd,(void *) str1,strlen(str1));
vTaskDelay( 2000 / portTICK_RATE_MS);
shutdown(sockfd, 0);
Объявим флаг, проинициализируем его единицей и отправим его с помощью очереди в нашу задачу приёма пакета
1 2 3 4 5 |
vTaskDelay( 2000 / portTICK_RATE_MS); } } char fl = 1; xQueueSendToBack(xQueueClose, &fl, 0); |
Вот теперь нам будет ясен смысл нашего флага. Передача с помощью него единицы даст команду на уничтожение задачи приёма пакетов перед закрытием сокета и разрывом соединения с сервером.
Добавим ещё одну глобальную очередь для передачи флага уничтожения задачи обратно из задачи приёма пакетов, чтобы отследить момент, когда задача будет уже уничтожена
xQueueHandle xQueue, xQueueClose, xQueueCloseAsk;
Вернёмся в функцию tcp_task и добавим бесконечный цикл, в котором, собственно и будем отслеживать данный флаг, и попытаемся получить его из очереди
1 2 3 4 5 |
xQueueSendToBack(xQueueClose, &fl, 0); for(;;) { xQueueReceive(xQueueCloseAsk, &fl, 0); } |
Если вернулась единица, то отправим соответствующее уведомление в терминальную программу и выйдем из цикла
1 2 3 4 5 6 |
xQueueReceive(xQueueCloseAsk, &fl, 0); if(fl==1) { os_printf("task delete\n"); break; } |
А если мы не попали в данное условие, то добавим небольшую задержку
1 2 3 |
break; } vTaskDelay( 10 / portTICK_RATE_MS); |
В функции инициализации init_esp_wifi создадим наши обе очереди
1 2 3 |
xQueue = xQueueCreate(10, sizeof(qData)); xQueueClose = xQueueCreate(10, sizeof(unsigned char)); xQueueCloseAsk = xQueueCreate(10, sizeof(unsigned char)); |
В функции приёма пакетов в самом начале бесконечного цикла узнаем, не пришла ли команда на уничтожение задачи. Если пришла, то отправим флаг в другую очередь и уничтожим задачу
1 2 3 4 5 6 7 8 |
for(;;) { xQueueReceive(xQueueClose, &fl, 0); if(fl==1) { xQueueSendToBack(xQueueCloseAsk, &fl, 0); vTaskDelete(NULL); } |
В принципе, мы можем теперь проверить соединение с сервером, отправку пакетов и разрыв соединения после передачи десяти пакетов.
Запустим сначала wireshark и отфильтруемся по сетевому адресу нашего модуля (как это делать, мы давно знаем), затем в netcat дадим команду на прослушку порта и перезагрузим наш модуль, соответственно перед этим не забыв его прошить.
Посмотрим, как приходят пакеты
Посмотрим, как отобразился процесс в анализаторе трафика
Всё прекрасно соединяется, передаётся и разъединяется.
Осталось нам лишь научить наш клиент принимать пакеты.
В этой же функции добавим ветку else в условие валидности принятого пакета, в теле которой мы соберём в массив из пакета все символы и отправим его на дисплей
1 2 3 4 5 6 7 8 9 |
os_printf("Socket error\n"); } else { for(unsigned char i=recv_data-1;i<20;i++) {data_buffer[i]=' ';} data_buffer[20] = 0; xQueueSendToBack(xQueue, &xLCDData, 0); } |
Соберём код, прошьём контроллер, отправив перед этим команду в netcat, и во время передачи пакетов из командной строки попытаемся что-то передать клиенту
Убедимся, что наш клиент принял пакет, увидев такой же текст на дисплее
В момент разрыва соединения с сервером мы также увидим на дисплее соответствующую надпись
Таким образом, в данном уроке нам удалось немного расширить функционал нашего клиента, научив его также принимать пакеты от сервера в независимом потоке.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Добавить комментарий