Общие сведения:
В этом проекте мы соединим две 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);
}

Обсуждение