Продолжаем развивать тему по приёму и передаче данных по беспроводной сети Wi-Fi.
И на данном уроке мы попытаемся написать простейший клиент UDP. Надеюсь, мы все помним, что это за протокол, если нет, то смотрим уроки по другим контроллерам, где всё это рассмотрено очень глубоко.
Передавать и принимать данные посредством протокола транспортного уровня UDP мы будем посредством Wi-Fi соединения в режиме станции.
Схема наша остаётся та же — отладочная плата, подключенная к USB компьютера
Проект мы за основу возьмём из прошлого урока с именем WIFI_STA и назовём его ESPCONN_UDP_CLIENT.
Для работы с протоколом UDP мы будем использовать библиотеку espconn, также предоставленную с закрытым исходным кодом.
Откроем наш проект в Eclipse и в файле main.c подключим заголовочный файл библиотеки
1 2 |
#include "driver/uart.h" #include "espconn.h" |
Теперь внесём некоторые изменение в создание соединения Wi-Fi для более стабильной его работы, а также для большего быстродействия.
Для начала в функции user_init() разъединим станцию с точкой доступа и отключим DHCP
1 2 3 |
wifi_set_opmode_current(STATION_MODE); wifi_station_disconnect(); wifi_station_dhcpc_stop(); |
Удалим вот эти строки
os_memset(&stationConf, 0, sizeof(struct station_config));
os_sprintf(stationConf.ssid, «%s», WIFI_CLIENTSSID);
os_sprintf(stationConf.password, «%s», WIFI_CLIENTPASSWORD);
wifi_station_set_config_current(&stationConf);
Заберём из FLASH-памяти настройки Wi-Fi-соединения и в случае удачного выполнения данной функции установим в ноль только SSID и пароль, после чего заменим их своими строками
1 2 3 4 5 6 7 8 |
wifi_station_dhcpc_stop(); if(wifi_station_get_config(&stationConf)) { os_memset(stationConf.ssid, 0, sizeof(stationConf.ssid)); os_memset(stationConf.password, 0, sizeof(stationConf.password)); os_sprintf(stationConf.ssid, "%s", WIFI_CLIENTSSID); os_sprintf(stationConf.password, "%s", WIFI_CLIENTPASSWORD); } |
При неудачном конфигурировании выведем соответствующее сообщение в терминальную программу
1 2 3 4 5 |
os_sprintf(stationConf.password, "%s", WIFI_CLIENTPASSWORD); if(!wifi_station_set_config(&stationConf)) { os_printf("Not set station config!\r\n"); } |
Удалим строку соединения с точкой доступа
wifi_station_connect();
И вызовем данную функцию выше, а затем вызовем функцию включения клиента DHCP
1 2 3 |
wifi_set_sleep_type(NONE_SLEEP_T); wifi_station_connect(); wifi_station_dhcpc_start(); |
Включим протокол физического уровня типа 802.11n, если таковой уже не включен
1 2 3 |
wifi_station_dhcpc_start(); if(wifi_get_phy_mode() != PHY_MODE_11N) wifi_set_phy_mode(PHY_MODE_11N); |
Включим автоматическое соединение с точкой доступа в случае разрыва, если не включено
1 2 3 |
wifi_set_phy_mode(PHY_MODE_11N); if(wifi_station_get_auto_connect() == 0) wifi_station_set_auto_connect(1); |
Удалим объявления следующих глобальных переменных
static uint8_t wifiStatus = STATION_IDLE;
static uint8_t connectStatus = 0;
Создадим перечисляемый тип с некоторыми состояниями соединения
1 2 3 4 5 6 7 8 |
static uint8_t led_state=0; //------------------------------------------------------ typedef enum { WIFI_CONNECTING, WIFI_CONNECTING_ERROR, WIFI_CONNECTED } tConnState; //------------------------------------------------------ |
Объявим переменную данного типа и сразу проинициализируем её
1 2 3 4 |
} tConnState; //------------------------------------------------------ static tConnState connState = WIFI_CONNECTING; //------------------------------------------------------ |
Объявим переменную типа структуры соединения и ещё одну типа структуры для хранения параметров соединения UDP
1 2 3 4 5 |
static tConnState connState = WIFI_CONNECTING; //------------------------------------------------------ struct espconn pConn; esp_udp ConnUDP; //------------------------------------------------------ |
Для функции обработки событий таймера создадим прототип, так как доступность её имени потребуется в коде выше
1 2 3 4 |
esp_udp ConnUDP; //------------------------------------------------------ static void ICACHE_FLASH_ATTR wifi_check_ip(void *arg); //------------------------------------------------------ |
В теле данной функции wifi_check_ip следующая переменная станет локальной
uint8_t wifiStatus = wifi_station_get_connect_status();
В данной условной конструкции удалим второе условие
if (wifiStatus == STATION_GOT_IP && ipConfig.ip.addr != 0)
Узнаем информацию о станции
1 2 3 |
if (wifiStatus == STATION_GOT_IP) { wifi_get_ip_info(STATION_IF, &ipConfig); |
Удалим инициализацию и запуск таймера
os_timer_setfn(&os_timer01, (os_timer_func_t *)wifi_check_ip, NULL);
os_timer_arm(&os_timer01, 2000, 0);
Весь код, расположенный ниже в теле условия, перенесём в тело другого условия, которое мы добавим, кое-что в нём изменив
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
os_timer_arm(&os_timer01, 2000, 0); if(ipConfig.ip.addr != 0) { if(connState != WIFI_CONNECTED) { connState = WIFI_CONNECTED; return; } else { led_state = (led_state==0) ? 1 : 0; GPIO_OUTPUT_SET(LED, led_state); } } |
В случае удачного соединения мы из функции выйдем совсем, в этом случае мы таймер позже запустим совсем в другом месте.
В ветке противного случая удалим данную строку
connectStatus = 0;
Вместо этого изменим статус соединения Wi-Fi
1 2 3 |
else { connState = WIFI_CONNECTING; |
Разъединимся с хостом
1 2 |
connState = WIFI_CONNECTING; espconn_delete(&pConn); |
Из тел следующих трёх условий условной конструкции удалим вызов функции соединения с точкой доступа, так как мы настроили автоматическое соединение при разрыве
wifi_station_connect();
А выше вывода сообщения в терминал в данных условиях присвоим переменной статуса значение ошибки соединения. После этого условия примут следующий вид
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if(wifiStatus == STATION_WRONG_PASSWORD) { connState = WIFI_CONNECTING_ERROR; os_printf("STATION_WRONG_PASSWORD\r\n"); } else if(wifi_station_get_connect_status() == STATION_NO_AP_FOUND) { connState = WIFI_CONNECTING_ERROR; os_printf("STATION_NO_AP_FOUND\r\n"); } else if(wifi_station_get_connect_status() == STATION_CONNECT_FAIL) { connState = WIFI_CONNECTING_ERROR; os_printf("STATION_CONNECT_FAIL\r\n"); } |
Противный случай данной условной конструкции также изменится, там будет другое сообщение и присвоение статуса
1 2 3 4 5 6 7 |
os_printf("STATION_CONNECT_FAIL\r\n"); } else { connState = WIFI_CONNECTING; os_printf("WiFi connecting...\r\n"); } |
Инициализацию таймера и запуск здесь мы также удалим
os_timer_setfn(&os_timer01, (os_timer_func_t *)wifi_check_ip, NULL);
os_timer_arm(&os_timer01, 500, 0);
Таймер мы инициализируем и запустим, выйдя из тела противного случая условной конструкции верхнего уровня, иначе у нас светодиод мигать не будет. Время срабатывания таймера мы установим в 1 секунду
1 2 3 4 5 |
os_printf("WiFi connecting...\r\n"); } } os_timer_setfn(&os_timer01, (os_timer_func_t *)wifi_check_ip, NULL); os_timer_arm(&os_timer01, 1000, 0); |
Выше функции wifi_check_ip добавим функцию установки соединения с сервером UDP
1 2 3 4 5 |
//------------------------------------------------------ static void ICACHE_FLASH_ATTR udp_connect() { } //------------------------------------------------------ |
Вызовем данную функцию в функции wifi_check_ip вот здесь
1 2 3 4 5 6 |
if(ipConfig.ip.addr != 0) { if(connState != WIFI_CONNECTED) { connState = WIFI_CONNECTED; udp_connect(); |
В теле функции udp_connect объявим локальную переменную для хранения сетевого адреса
1 2 3 |
static void ICACHE_FLASH_ATTR udp_connect() { uint32_t ip; |
Объявим и проинициализируем символьный массив, который мы передадим затем серверу в случае успешного соединения с ним
1 2 |
uint32_t ip; char data[] = "Hello\r\n"; |
Объявим подобный, но пустой, массив для хранения строкового значения адреса IP сервера
1 2 |
char data[] = "Hello\r\n"; char udpserverip[15] = {}; |
Деинициализируем таймер
1 2 |
char udpserverip[15] = {}; os_timer_disarm(&os_timer01); |
Внесём некоторые настройки соединения: покажем адрес переменной структуры соединения UDP, тип соединения и его начальное состояние
1 2 3 4 |
os_timer_disarm(&os_timer01); pConn.proto.udp = &ConnUDP; pConn.type = ESPCONN_UDP; pConn.state = ESPCONN_NONE; |
В файле user_config.h добавим макросы для адреса и порта сервера, в качестве сервера у нас будет работать привычная нам программа netcat на ПК
1 2 3 |
#define WIFI_CLIENTPASSWORD "12345678" #define UDPSERVERIP "192.168.1.85" #define UDPSERVERPORT 30000 |
Вернёмся в main.c в функцию udp_connect и скопируем адрес сервера в символьную строку, а затем с помощью специальной функции преобразуем его в число
1 2 3 |
pConn.state = ESPCONN_NONE; os_sprintf(udpserverip, "%s", UDPSERVERIP); ip = ipaddr_addr(udpserverip); |
Скопируем его в соответствующее поле переменной pConn
1 2 |
ip = ipaddr_addr(udpserverip); os_memcpy(pConn.proto.udp->remote_ip, &ip, 4); |
Также с помощью функции библиотеки espconn сгенерируем номер порта клиента и запишем его также в соответствующее поле
1 2 |
os_memcpy(pConn.proto.udp->remote_ip, &ip, 4); pConn.proto.udp->local_port = espconn_port(); |
В следующее поле запишем номер порта сервера
1 2 |
pConn.proto.udp->local_port = espconn_port(); pConn.proto.udp->remote_port = UDPSERVERPORT; |
Перед созданием соединения пошлём сообщение в терминальную программу
1 2 |
pConn.proto.udp->remote_port = UDPSERVERPORT; os_printf("UDP espconn create...\r\n"); |
Попытаемся создать соединение UDP
1 2 |
os_printf("UDP espconn create...\r\n"); espconn_create(&pConn); |
Мы, конечно же, прекрасно знаем, что обмен данными посредством протокола UDP не предусматривает создание соединения, всё это виртуально, только для правильной организации работы клиента.
Выше добавим функции обратного вызова передачи и приёма пакетов
1 2 3 4 5 6 7 8 9 |
//------------------------------------------------------ void ICACHE_FLASH_ATTR udp_client_udp_send_cb(void* arg) { } //------------------------------------------------------ void ICACHE_FLASH_ATTR udp_client_udp_recv_cb(void* arg, char* pusrdata, uint16_t length) { } //------------------------------------------------------ |
Зарегистрируем их в функции udp_connect
1 2 3 |
espconn_create(&pConn); espconn_regist_sentcb(&pConn, udp_client_udp_send_cb); espconn_regist_recvcb(&pConn, udp_client_udp_recv_cb); |
Проинициализируем и запустим таймер
1 2 3 |
espconn_regist_recvcb(&pConn, udp_client_udp_recv_cb); os_timer_setfn(&os_timer01, (os_timer_func_t *)wifi_check_ip, NULL); os_timer_arm(&os_timer01, 1000, 0); |
Затем попробуем передать пакет серверу
1 2 |
os_timer_arm(&os_timer01, 1000, 0); espconn_send(&pConn, (uint8_t*)data, strlen(data)); |
Обратный вызов передачи пакета мы обрабатывать не будем, пусть обработчик будет пустотелым, а вот приём пакета обработать нужно.
Поэтому в функции udp_client_udp_recv_cb объявим переменную для отсчёта принятых байтов в пакете, а также выведем в терминал длину пакета в байтах и подготовим строку для вывода в терминальную программу принятых символов, так как на сервер и с сервера мы будем передавать символьные строки
1 2 3 4 5 |
void ICACHE_FLASH_ATTR udp_client_udp_recv_cb(void* arg, char* pusrdata, uint16_t length) { uint16_t idx; os_printf("UDP : DATA RECEIVED : LEN = %d\n", length); os_printf("UDP : DATA: "); |
Выведем принятые с сервера символы в терминальную программу
1 2 3 4 5 |
os_printf("UDP : DATA: "); for(idx=0;idx<length;idx++) { uart_tx_one_char(UART0,pusrdata[idx]); } |
И отправим символьную строку серверу в ответ
1 2 3 |
uart_tx_one_char(UART0,pusrdata[idx]); } espconn_send(&pConn, (uint8_t*)"Hello from ESP8266!!!\r\n", 23); |
Соберём код и прошьём контроллер.
В терминальной программе соединимся с виртуальным портом и увидим, что соединение Wi=Fi благополучно установлено
Только вот дальше у нас пока ничего не выводится, так как мы не запустили наш сервер. Поэтому в командной строке, находясь в каталоге с программой netcat, дадим следующую команду и, если всё нормально, то после перезагрузки отладочной платы сервер примет нашу строку
Попробуем что-нибудь передать обратно клиенту, введя в командной строке какую-нибудь строку
Клиент благополучно ответил.
Также мы видим информацию о принятом пакете клиента в терминальной программе
Давайте посмотрим, как проходят пакеты в программе анализа сетевого трафика Wireshark. Запустим его, в командной строке пока остановим слушание порта, нажав комбинацию клавиш Ctrl+C, запустим netcat заново, так как после перезагрузки клиента порт у него уже будет другой и в старом соединении сервера ничего не примется.
Перезагрузим клиент, примем строку, передадим строку клиента, на что он нам опять ответит своей строкой и уже после этого посмотрим анализ трафика между сервером и клиентом
Мы видим, что пакеты наши передаются в обе стороны безошибочно.
Итак, на данном уроке мы создали простенький, но уверенно работающий клиент UDP, с помощью которого нам благополучно удалось передать и принять пакеты UDP в обе стороны, используя при этом беспроводное соединение Wi-Fi с нашей платой.
Всем спасибо за внимание!
Предыдущий урок Программирование МК ESP8266 Следующий урок
Модуль ESP NodeMCU можно купить здесь: Модуль ESP NodeMCU
Различные модули ЕSP8266 можно приобрести здесь Модули ЕSP8266
Переходник USB to TTL можно приобрести здесь ftdi ft232rl
Многофункциональный переходник CJMCU FT232H USB к JTAG UART FIFO SPI I2C можно приобрести здесь ftdi ft232rl
Логический анализатор 16 каналов можно приобрести здесь
Смотреть ВИДЕОУРОК (нажмите на картинку)
Добрый день. Есть вопрос о сотрудничестве, нужны ваши знания для наших проектов. Киньте мне на почту свой контактный телефон, наберу вам. Спасибо.
Добрый день.
К сожалению, я фрилансерством не занимаюсь.
Начинающие, не забывайте отключать фаерволл !!! )))
Пол дня мучился пока сообразил.
У меня антивирус своим фаерволлом перекрывал
получение пакетов от модуля ESP01 в NetCat.
А WireShark-у хоть бы что!!!
Автору огромное спасибо за сий Труд!
Маленький вопрос, откуда алгоритм действий данной программы?