Общие сведения:
В этом проекте мы соединим две Piranha ESP32 по WiFi и будем передавать массивы данных с одной на другую при помощи протокола UDP.
UDP (англ. User Datagram Protocol) — протокол пользовательских датаграмм. UDP предоставляет ненадёжный сервис, и датаграммы могут прийти не по порядку, дублироваться или вовсе исчезнуть без следа. UDP подразумевает, что проверка ошибок и исправление либо не нужны, либо должны исполняться в приложении.
Фомат массива
При передаче и получении информации используются байты. Одним байтом возможно передать число от 0 до 255, что соответствует типу данных byte
и uint8_t
, а так же от -128 до 127, что соответствует типам char
и int8_t
. При передаче б_о_льших чисел используется трюк с преобразованием типов адресов (указателей). Например, если это массив с типом данных int
(что соответствует четырём байтам для esp32), то каждый элемент массива отсылается побайтово. Таким образом число типа int i = -1
будет передано как четыре байта со значениями 0xff
. Далее принимающая сторона преобразует байты обратно в int
. Этот приём работает только при приёме/передаче данных представляющих непрерывные структуры в памяти, таких как массивы. Со связанными списками это приём не работает. При таком подходе очень легко выйти за границы массива, поэтому при передаче данных передаётся и их размер в байтах полученный при помощи оператора sizeof
. Принимающая сторона вычисляет количество элементов приводя количество полученных байтов к размеру типа ожидаемых данных, так же при помощи оператора sizeof
.
При передаче и приёме данных используйте одинаковые типы данных! В примерах используется проверка размера данных, но всё равно что-то может пойти не так. Если принимающая сторона будет ожидать int
, а передающая сторона передаст char
, то может произойти чтение или запись в ненадлежащую область памяти, от чего ESP32 может зависнуть, перезагружаться или работать непредсказуемо.
Видео:
редактируется ...
Нам понадобится:
Подключение:
Скетчи проекта для прямого подключения (esp32 - esp32):
Скетч для ESP32 в качестве UDP сервера
В данном скетче отправка данных осуществляется при помощи функции printf
, а получение при помощи преобразования указателей. Это сделано для демонстрации двух способов. В первом случае получатель должен парсить строку, во втором отправитель должен отсылать только байты необходимых данных.
// Подключаем библиотеки #include <AsyncUDP.h> #include <WiFi.h> #include <WiFiAP.h> // Определяем название и пароль точки доступа const char* ssid = "esp32asAP"; const char* password = "password12345"; // Создаём объект UDP соединения AsyncUDP udp; // Определяем порт const uint16_t PORT = 49152; // Массив данных для отправки int SDATA[2] {1984, 2021}; // Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) { // Записываем адрес начала данных в памяти int* pdata = (int*)packet.data(); // Вычисляем размер данных const size_t len = packet.length() / sizeof(&pdata); // Если адрес данных не равен нулю и размер данных больше нуля... if (pdata != NULL && len > 0) { // Проходим по элементам массива for (size_t i = 0; i < len; i++) { // Выводим каждый элемент в последовательный порт Serial.print(pdata[i]); Serial.print(", "); } Serial.println(); // Отправляем данные клиенту packet.printf("Пакет получен. Ответ: %d, %d\n", SDATA[0], SDATA[1]); } } void setup() { // Инициируем последовательный порт Serial.begin(115200); Serial.println(); Serial.println("Инициируем точку доступа WiFi"); // Инициируем точку доступа WiFi WiFi.softAP(ssid, password); IPAddress myIP = WiFi.softAPIP(); // Выводим IP-адрес Serial.print("IP-адрес точки доступа: "); Serial.println(myIP); // Инициируем сервер if(udp.listen(PORT)) { // При получении пакета вызываем callback функцию udp.onPacket(parsePacket); } Serial.println("Сервер запущен."); } void loop() { delay(10); }
Скетч для ESP32 в качестве UDP клиента
В скетче клиента отправка данных осуществляется побайтово, а приём при помощи парсинга строки.
// Подключаем библиотеки #include "WiFi.h" #include "AsyncUDP.h" // Создаём переменную состояния кнопки bool keyState = false; // Массив данных для отправки int DATA[2] {11276, 1969}; // Определяем название и пароль точки доступа const char *ssid = "esp32asAP"; const char *password = "password12345"; // Определяем порт const uint16_t PORT = 49152; // Создаём объект с IP-адресом точки доступа IPAddress ADDR(192, 168, 4, 1); // Создаём объект UDP соединения AsyncUDP udp; // Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) { String msg = packet.readStringUntil(':'); int first = packet.readStringUntil(',').toInt(); int second = packet.readStringUntil('\n').toInt(); // Выводим преобразованные данные в последовательный порт Serial.printf("%s %d, %d\n", msg.c_str(), first, second); } void setup() { // Инициируем последовательный порт Serial.begin(115200); // Устанавливаем режим работы в качестве клиента WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Ждём подключения WiFi while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.print("."); delay(100); } // Если удалось подключиться по UDP if (udp.connect(ADDR, PORT)) { Serial.println("UDP подключён"); // вызываем callback функцию при получении пакета udp.onPacket(parsePacket); } // Если подключение не удалось else { Serial.println("UDP не подключён"); // Входим в бесконечный цикл while(1) { delay(1000); } } } void loop() { // Отправляем данные серверу побайтово udp.broadcastTo((uint8_t*)&DATA, sizeof(DATA), PORT); // Если соединение WiFi прервано if (WiFi.status() != WL_CONNECTED) { // Вызываем функцию setup(), для повторного подключения setup(); } delay(1000); }
Скетчи проекта для подключения через WiFi роутер (esp32 - роутер - esp32):
При таком подключении необходимо сначала загрузить скетч с сервером UDP. В последовательный порт будет выведен IP-адрес сервера, который необходимо указать в скетче клиента. Так же в обоих сетчах необходимо указать название точки доступа и пароль. Обе ESP32 должны находиться в одной сети. При таком подключении передача данных немного медленее, чем примое подключение (оверхед роутера на маршрутизацию пакетв).
Скетч для ESP32 в качестве UDP сервера
// Подключаем библиотеки #include <AsyncUDP.h> #include <WiFi.h> // Определяем название и пароль точки доступа const char* ssid = "название WiFi"; const char* password = "пароль WiFi"; // Создаём объект UDP соединения AsyncUDP udp; // Определяем порт const uint16_t PORT = 49152; // Массив данных для отправки int SDATA[2] {1984, 2021}; // Массив для получения int rdata[2] {0}; // Кол-во элементов массива для получения const size_t glen = sizeof(rdata) / sizeof(rdata[0]); // Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) { // Записываем адрес начала данных в памяти const int* tmp = (int*)packet.data(); // Вычисляем размер данных const size_t len = packet.length() / sizeof(&rdata); // Если адрес данных не равен нулю и размер данных больше нуля... if (tmp != nullptr && len > 0) { // Проходим по элементам массива for (size_t i = 0; i < len && i < glen; i++) { // Записываем в глобальную переменную rdata[i] = tmp[i]; } // Отправляем данные клиенту packet.printf("Пакет получен. Ответ: %d, %d\n", SDATA[0], SDATA[1]); } } void setup() { // Инициируем последовательный порт Serial.begin(115200); // Устанавливаем режим работы в качестве клиента WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Ждём подключения WiFi while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.print("."); delay(100); } // Выводим IP-адрес IPAddress myIP = WiFi.localIP(); Serial.print("IP-адрес точки доступа: "); Serial.println(myIP); // Инициируем сервер if(udp.listen(PORT)) { // При получении пакета вызываем callback функцию udp.onPacket(parsePacket); } Serial.println("Сервер запущен."); } void loop() { // Выводим значение элементов массива for (size_t i = 0; i < glen; i++) { Serial.println(rdata[i]); } delay(1000); }
Скетч для ESP32 в качестве UDP клиента
// Подключаем библиотеки #include "WiFi.h" #include "AsyncUDP.h" // Создаём переменную состояния кнопки bool keyState = false; // Массив данных для отправки int DATA[2] {11276, 1969}; // Определяем название и пароль точки доступа const char* ssid = "название WiFi"; const char* password = "пароль WiFi"; // Определяем порт const uint16_t PORT = 49152; // Определяем адрес сервера (например "192.168.1.3") const char* SER_IP = "n.n.n.n"; // Создаём объект UDP соединения AsyncUDP udp; // Переменные для получения данных String msg = ""; int first = 0; int second = 0; // Определяем callback функцию обработки пакета void parsePacket(AsyncUDPPacket packet) { msg = packet.readStringUntil(':'); first = packet.readStringUntil(',').toInt(); second = packet.readStringUntil('\n').toInt(); } void setup() { // Инициируем последовательный порт Serial.begin(115200); // Устанавливаем режим работы в качестве клиента WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); // Ждём подключения WiFi while (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.print("."); delay(100); } // Инициируем объект IP-адреса IPAddress addr; // Вычисляем IP-адрес из строки addr.fromString(SER_IP); // Если удалось подключиться по UDP if (udp.connect(addr, PORT)) { Serial.println("UDP подключён"); // вызываем callback функцию при получении пакета udp.onPacket(parsePacket); } // Если подключение не удалось else { Serial.println("UDP не подключён"); // Входим в бесконечный цикл while(1) { delay(1000); } } } void loop() { // Отправляем данные серверу побайтово udp.broadcastTo((uint8_t*)&DATA, sizeof(DATA), PORT); // Выводим полученные данные в последовательный порт Serial.printf("%s %d, %d\n", msg.c_str(), first, second); // Если соединение WiFi прервано if (WiFi.status() != WL_CONNECTED) { // Вызываем функцию setup(), для повторного подключения setup(); } delay(1000); }
Обсуждение