Продолжая тему передачи данных по беспроводной сети посредством протокола UDP, на данном уроке мы попробуем создать простой сервер, который будет слушать постоянно какой-нибудь порт, и если вдруг получит на него пакет с числом, то должен будет ответить отправителю подобным пакетом с числом, составляющим разность числа 32767 с данным числом. То есть это та же задача, которую выполняли приложения, которые мы использовали на смартфоне и ПК. Соответственно к сети WiFi мы будем подключаться с использованием библиотеки написанной в прошлом уроке.
Схема урока будет с использованием дисплея, как и в уроке 21
И проект также был сделан из проекта урока 21 с именем WIFI_STA_UDP_CLIENT_RX и назван был WIFI_STA_UDP_SERVER.
Прежде чем открыть данный проект в среде, давайте кое-что с ним сделаем, чтобы библиотека соединения с точкой доступа у нас была уже новая. Поэтому из проекта прошлого урока с именем WIFI_STA_SMART скопируем с заменой файлы wifi.h и wifi.c и откроем наш проект в среде Espressif IDE.
В файле Kconfig.projbuild удалим пункт меню ESP_MAXIMUM_RETRY, и также в том месте где был данный пункт добавим вместо некоторые нужные пункты
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
choice WIFI_SCAN_METHOD prompt "WiFi Scan Method" default WIFI_SCAN_METHOD_ALL_CHANNEL help WiFi scan method: If "Fast" is selected, scan will end after find SSID match AP. If "All Channel" is selected, scan will end after scan all the channel. config WIFI_SCAN_METHOD_FAST bool "Fast" config WIFI_SCAN_METHOD_ALL_CHANNEL bool "All Channel" endchoice choice WIFI_CONNECT_AP_SORT_METHOD prompt "WiFi Connect AP Sort Method" default WIFI_CONNECT_AP_BY_SIGNAL help WiFi connect AP sort method: If "Signal" is selected, Sort matched APs in scan list by RSSI. If "Security" is selected, Sort matched APs in scan list by security mode. config WIFI_CONNECT_AP_BY_SIGNAL bool "Signal" config WIFI_CONNECT_AP_BY_SECURITY bool "Security" endchoice config WIFI_SCAN_RSSI_THRESHOLD int "WiFi minimum rssi" range -127 0 default -127 help The minimum rssi to accept in the scan mode. choice WIFI_SCAN_AUTH_MODE_THRESHOLD prompt "WiFi Scan auth mode threshold" default WIFI_AUTH_OPEN help The weakest authmode to accept in the scan mode. config WIFI_AUTH_OPEN bool "OPEN" config WIFI_AUTH_WEP bool "WEP" config WIFI_AUTH_WPA_PSK bool "WPA PSK" config WIFI_AUTH_WPA2_PSK bool "WPA2 PSK" config WIFI_AUTH_WPA_WPA2_PSK bool "WPA WPA2 PSK" config WIFI_AUTH_WPA2_ENTERPRISE bool "WPA2 ENTERPRISE" config WIFI_AUTH_WPA3_PSK bool "WPA3 PSK" config WIFI_AUTH_WPA2_WPA3_PSK bool "WPA2 WPA3 PSK" config WIFI_AUTH_WAPI_PSK bool "WAPI PSK" endchoice |
Проверим все параметры в конфигураторе и при необходимости настроим их, в том числе поменяем алгоритм шифрования и назначим порт нашему серверу
Порт клиента может быть любой, кто подключится, с тем и работаем.
Сохраним конфигурацию, попробуем собрать проект, который скорей всего теперь соберётся, и перейдём в файл udp.c, в котором функцию recv_task удалим вместе с телом.
В функции udp_task удалим переменную для задачи приёма пакетов
TaskHandle_t xLCDTaskHandle = NULL, xRecvTask = NULL;
Также удалим объявление данной переменной
portTickType xLastWakeTime;
Объявим небольшой буфер, целочисленную знаковую 16-разрядную переменную, объявим переменную структуры очереди, а также проинициализируем позицию и указатель на строку
1 2 3 4 5 6 |
int sockfd; char buf[10] = {}; qLCDData xLCDData; char str1[10]; xLCDData.y_pos = 1; xLCDData.str = str1; |
После строки
xTaskCreate(vLCDTask, "vLCDTask", 2048, NULL, 2, &xLCDTaskHandle);
предлагаю удалить оставшийся код тела нашей функции, так как изменений будет много и мы можем запутаться.
Далее организуем бесконечный цикл, в котором попытаемся создать сокет, если данная операция пройдёт неудачно, то выведем в терминале соответствующее сообщение и выйдем из цикла, а при успешном создании выведем другое сообщение
1 2 3 4 5 6 7 8 9 10 |
xTaskCreate(vLCDTask, "vLCDTask", 2048, NULL, 2, &xLCDTaskHandle); while(1) { ESP_LOGI(TAG, "Create socket...\n"); if ( (sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0 ) { ESP_LOGE(TAG, "socket not created\n"); break; } ESP_LOGI(TAG, "Socket created"); } |
Очистим память полей переменных типов структур и проинициализируем переменную для адреса сервера
1 2 3 4 5 6 7 8 |
ESP_LOGI(TAG, "Socket created"); memset(&servaddr, 0, sizeof(servaddr)); memset(&cliaddr, 0, sizeof(cliaddr)); uint32_t client_addr_len = sizeof(cliaddr); //Заполнение информации о сервере servaddr.sin_family = AF_INET; // IPv4 servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(CONFIG_SERVER_PORT); |
Попытаемся связать сокет с адресом сервера
1 2 3 4 5 6 7 8 9 10 |
servaddr.sin_port = htons(CONFIG_SERVER_PORT); //Свяжем сокет с адресом сервера if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(struct sockaddr_in)) < 0 ) { ESP_LOGI(TAG,"socket not binded"); shutdown(sockfd, 0); close(sockfd); break; } ESP_LOGI(TAG,"socket binded"); |
Организуем ещё один бесконечный цикл, в котором попытаемся принять пакет от от клиента, заодно узнав информацию о нём и записав её в переменную соответствующей структуры, сформируем строку с числом, являющейся разностью числа 32767 и пришедшего от клиента числа, отправим данное число клиенту в виде пакета UDP, а также отобразим принятое число на нашем дисплее при помощи очереди
1 2 3 4 5 6 7 8 9 10 |
ESP_LOGI(TAG,"socket binded"); while(1) { recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&cliaddr, &client_addr_len); snprintf(str1, sizeof(str1), "%6d", *(short*)buf); *(short*)buf = 32767 - *(short*)buf; sendto(sockfd, buf, 2, 0, (struct sockaddr*) &cliaddr, sizeof(cliaddr)); xQueueSendToBack(lcd_string_queue, &xLCDData, 0); } |
Выйдем из данного бесконечного цикла (хотя реально, скорее всего, мы из него никогда не выйдем) и, если сокет существует, то попробуем его закрыть и, так как на верхнем уровне у нас также бесконечный цикл, то будет предпринята попытка заново создать сокет
1 2 3 4 5 6 7 |
xQueueSendToBack(lcd_string_queue, &xLCDData, 0); } if (sockfd != -1) { ESP_LOGE(TAG, "Shutting down socket and restarting..."); shutdown(sockfd, 0); close(sockfd); } |
Выйдем из всех циклов, удалим задачу дисплея, текущую задачу и очередь (вот сюда мы вполне можем попасть так как в цикле присутствует break
1 2 3 4 5 6 |
close(sockfd); } } vTaskDelete(xLCDTaskHandle); vQueueDelete(lcd_string_queue); vTaskDelete(NULL); |
В файле wifi.c в функции wifi_stop удалим одну строку, а то почему-то их получилось две одинаковые
ESP_LOGI(TAG, "esp_wifi_clear_default_wifi_driver_and_handlers : %d", ret);
Также в функции wifi_init_sta в цикле, котором перебираются интерфейсы и выводится информация о них, уберём вот эту скобку
for (int i = 0; i < esp_netif_get_nr_of_ifs(); ++i) {
netif = esp_netif_next(netif);
}
А поставим её ниже, иначе дальнейший код по выводу информации в цикл не войдёт и попадёт в него только последний интерфейс, вследствие чего у нас и не было ошибки, так как у нас интерфейс был только один
1 2 3 |
ESP_LOGI(TAG, "- IPv4 address: " IPSTR, IP2STR(&ip.ip)); } } |
В функции app_main файла main.c удалим объявление данной переменной
wifi_ap_record_t info;
Также полностью удалим бесконечный цикл со всем телом.
Произведём инициализацию сетевого интерфейса и создадим цикл событий по умолчанию
1 2 3 4 5 |
ESP_LOGI(TAG, "nvs_flash_init: 0x%04x", ret); ret = esp_netif_init(); ESP_LOGI(TAG, "esp_netif_init: %d", ret); ret = esp_event_loop_create_default(); ESP_LOGI(TAG, "esp_event_loop_create_default: %d", ret); |
Добавим возврат параметра в вызов функции инициализации wifi
ret = wifi_init_sta();
Ниже выведем в терминал код ошибки
1 2 |
ret = wifi_init_sta(); ESP_LOGI(TAG, "wifi_init_sta: %d", ret); |
Соберём код, прошьём контроллер, запустим программу на смартфоне и внесём там настройки нашего сервера, для этого нужно знать кроме порта также IP-адрес нашего ESP32, который можно посмотреть в терминале при старте программы
Применим настройки и запустим клиент
И мы увидим, как начнут изменяться цифры — верхняя будет декрементироваться, а нижняя — инкрементироваться
На дисплее, который подключен к нашей плате, мы также увидим приходящие с клиента числа
Остановим наш клиент на смартфоне и запустим программу для Windows (ссылка внизу страницы), зайдём там в настройки и также настроим программу на адрес и порт нашего сервера
Нажмём кнопку Start, тем самым запустив клиент
И если всё нормально, пойдёт тот же самый процесс, что и со смартфоном
Я не стал заморачиваться с отображением отправляемых чисел на экране в программе, а отобразил только принятые, так как отправленные мы видим на экране дисплея, подключенного к плате ESP.
Таким образом, на данном уроке нам удалось создать нехитрый сервер, работающий по протоколу UDP, который слушает определённый порт, и, приняв пакет, отправляет отправившему клиенту обратный.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP32 Следующий урок
TCP UDP client-server for Android
QR-код на скачивание приложения:
Недорогие отладочные платы ESP32 можно купить здесь Недорогие отладочные платы ESP32
Недорогие отладочные платы ESP32/ESP32-C3/ESP32-S3 можно купить здесь Недорогие отладочные платы ESP32
Логический анализатор 16 каналов можно приобрести здесь
Переходник I2C to LCD можно приобрести здесьI2C to LCD1602 2004
Смотреть ВИДЕОУРОК в RuTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в YouTube (нажмите на картинку)
Смотреть ВИДЕОУРОК в Дзен (нажмите на картинку)
Добавить комментарий