Общие сведения:
В этом проекте мы соединим две Piranha ESP32 по WiFi и будем передавать массивы данных с одной на другую при помощи протокола WebSocket.
WebSocket — протокол связи поверх TCP-соединения, предназначенный для обмена сообщениями между браузером и веб-сервером в режиме реального времени.
В данных примерах будет использоваться сторонняя библиотека WebSocketClient. Её необходимо скачать и установить. О том как это сделать можно прочитать в этой статье
Фомат массива
При передаче и получении информации используются байты. Одним байтом возможно передать число от 0 до 255, что соответствует типу данных byte и uint8_t, а так же от -128 до 127, что соответствует типам char и int8_t. При передаче больших чисел используется трюк с преобразованием типов адресов (указателей). Например, если это массив с типом данных int (что соответствует четырём байтам для esp32), то каждый элемент массива отсылается побайтово. Таким образом число типа int i = -1 будет передано как четыре байта со значениями 0xff. Далее принимающая сторона преобразует байты обратно в int. Этот приём работает только при приёме/передаче данных представляющих непрерывные структуры в памяти, таких как массивы. Со связанными списками это приём не работает. При таком подходе очень легко выйти за границы массива, поэтому при передаче данных передаётся и их размер в байтах полученный при помощи оператора sizeof. Принимающая сторона вычисляет количество элементов приводя количество полученных байтов к размеру типа ожидаемых данных, так же при помощи оператора sizeof.
При передаче и приёме данных используйте одинаковые типы данных! В примерах используется проверка размера данных, но всё равно что-то может пойти не так. Если принимающая сторона будет ожидать int, а передающая сторона передаст char, то может произойти чтение или запись в ненадлежащую область памяти, от чего ESP32 может зависнуть, перезагружаться или работать непредсказуемо.
Кратко о параметре type для callback функций
Возможны разные варианты событий WebSocket, исходя их которых можно определить действия для выполнения в функции, которая вызывается при получении любого события. Значение события автоматически передаётся в callback функцию. Полезные варианты значений события:
WStype_CONNECTED- соединение установлено;WStype_DISCONNECTED- соединение прервано;WStype_TEXT- передача в текстовом формате;WStype_BIN- передача в двоичном формате.
Для передачи текста по WebSocket можно использовать функцию webSocket.sendTXT(). В данных примерах осуществляется только передача двоичных данных.
Видео:
редактируется ...
Нам понадобится:
Подключение:

Скетчи проекта для прямого подключения (esp32 - esp32):
Скетч для ESP32 в качестве WS сервера
// Подключаем библиотеки
#include <WiFi.h>
#include <WiFiClient.h>
#include <WiFiAP.h>
#include <WebSocketsServer.h>
// Определяем название и пароль точки доступа
const char* ssid = "esp32asAP";
const char* password = "password12345";
// Массив для отправки
unsigned long sdata[3] {1984, 1968, 0};
// Массив для получения
int rdata[2] {0};
// Кол-во элементов массива для получения
const size_t glen = sizeof(rdata) / sizeof(rdata[0]);
// Создаём объект сервера
WebSocketsServer webSocket = WebSocketsServer(81);
/* Callback функция события WebSocket.
* Парамтетры:
* num - номер клиента
* type - тип событыя
* payload - указатель на данные
* length - размер данных
*/
void webSocketServerEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
// Записываем текущие millis в третий элемент массива отправки
sdata[2] = millis();
// Если тип данных двоичный и их размер не нулевой
if (type == WStype_BIN && length > 0) {
// Преобразуем тип данных
const int* tmp = (int*) payload;
// Вычисляем размер данных нового типа
size_t len = length / sizeof(&rdata);
// Записываем в глобальную переменную
for (size_t i = 0; i < len && i < glen; i++) {
rdata[i] = tmp[i];
}
// Отвечаем клиенту
webSocket.sendBIN(num, (uint8_t*)sdata, sizeof(sdata));
}
}
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);
// Инициируем сервер
webSocket.begin();
// Метод событий WebSocket
webSocket.onEvent(webSocketServerEvent);
Serial.println("Сервер запущен.");
}
void loop() {
// Цикл WebSocket
webSocket.loop();
// Если прошло 5 секунд...
if (millis() % 1000 == 0) {
// Если данные были получены хотя бы один раз
if (rdata[0] != 0) {
Serial.println("Текущие данные от клиента: ");
// Выводим данные в последовательный порт
for (size_t i = 0; i < glen; i++) {
Serial.println(rdata[i]);
}
Serial.println();
}
}
}
Скетч для ESP32 в качестве WS клиента
// Подключаем библиотеки
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsClient.h>
// Определяем название и пароль точки доступа
const char* ssid = "esp32asAP";
const char* password = "password12345";
// Определяем адрес сервера
const char* ADDR = "192.168.4.1";
// Определяем url подключения
const char* URL = "/";
// Определяем порт
const uint16_t PORT = 81;
// Создаём массив для отправки
int sdata[2] {1984, 11276};
// Создаём массив для получения
unsigned long rdata[3] {0};
// Кол-во элементов массива для получения
const size_t glen = sizeof(rdata) / sizeof(rdata[0]);
// Создаём экземпляр класса клиента
WebSocketsClient webSocket;
/* Callback функция события WebSocket.
* Парамтетры:
* type - тип событыя
* payload - указатель на данные
* length - размер данных
*/
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
// Если тип данных двоичный и размер больше нуля
if (type == WStype_BIN && length > 0) {
// Преобразуем данные из байтов в десятичные числа без знака
const unsigned long* tmp = (unsigned long*) payload;
// Вычисляем размер данных в новом формате
const size_t len = length / sizeof(*rdata);
// Записываем в глобальную переменную
for (size_t i = 0; i < len && i < glen; i++) {
rdata[i] = tmp[i];
}
Serial.println();
}
}
void setup() {
// Инициируем последовательный порт
Serial.begin(115200);
// Устанавливаем режим работы в качестве клиента
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Ждём подключения WiFi
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println();
Serial.print("IP адрес: ");
Serial.println(WiFi.localIP());
// Подключаемся к серверу
webSocket.begin(ADDR, PORT, URL);
// Метод событий WebSocket
webSocket.onEvent(webSocketEvent);
// Если соединение прервано, повторить попытку через 5 сек.
webSocket.setReconnectInterval(5000);
}
void loop() {
// Цикл WebSocket
webSocket.loop();
// Если прошла одна секунда и сервер хотя бы раз прислал данные
if (millis() % 1000 == 0) {
// Отправляем данные в двоичном формате серверу
webSocket.sendBIN((uint8_t*)sdata, sizeof(sdata));
Serial.println("Данные отправлены.");
// Если данные сервера были получены хотябы один раз
if (rdata[0] != 0) {
Serial.println("Текущие данные сервера:");
// Выводим массив данных в последовательный порт
for (size_t i = 0; i < glen; i++)
Serial.println(rdata[i]);
}
}
// Если соединение WiFi прервано
if (WiFi.status() != WL_CONNECTED)
// Вызываем функцию setup(), для повторного подключения
setup();
}
Скетчи проекта для подключения через WiFi роутер (esp32 - роутер - esp32):
При таком подключении необходимо сначала загрузить скетч с сервером WebSocket. В последовательный порт будет выведен IP-адрес сервера, который необходимо указать в скетче клиента. Так же в обоих сетчах необходимо указать название точки доступа и пароль. Обе ESP32 должны находиться в одной сети. При таком подключении передача данных немного медленее, чем примое подключение (оверхед роутера на маршрутизацию пакетв).
Скетч для ESP32 в качестве WS сервера
// Подключаем библиотеки
#include <WiFi.h>
#include <WiFiClient.h>
#include <WebSocketsServer.h>
// Определяем название и пароль точки доступа
const char* ssid = "название WiFi";
const char* password = "пароль WiFi";
// Массив для отправки
unsigned long sdata[3] {1984, 1968, 0};
// Массив для получения
int rdata[2] {0};
// Кол-во элементов массива для получения
const size_t glen = sizeof(rdata) / sizeof(rdata[0]);
// Создаём объект сервера
WebSocketsServer webSocket = WebSocketsServer(81);
/* Callback функция события WebSocket.
* Парамтетры:
* num - номер клиента
* type - тип событыя
* payload - указатель на данные
* length - размер данных
*/
void webSocketServerEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
// Записываем текущие millis в третий элемент массива отправки
sdata[2] = millis();
// Если тип данных двоичный и их размер не нулевой
if (type == WStype_BIN && length > 0) {
// Преобразуем тип данных
const int* tmp = (int*) payload;
// Вычисляем размер данных нового типа
size_t len = length / sizeof(&rdata);
// Записываем в глобальную переменную
for (size_t i = 0; i < len && i < glen; i++) {
rdata[i] = tmp[i];
}
// Отвечаем клиенту
webSocket.sendBIN(num, (uint8_t*)sdata, sizeof(sdata));
}
}
void setup() {
// Инициируем последовательный порт
Serial.begin(115200);
Serial.println();
Serial.println("Подключаемся к WiFi...");
// Подключаемся к WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Ждём подключения WiFi
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println();
// Выводим IP-адрес Веб-сервера
IPAddress myIP = WiFi.localIP();
Serial.print("IP-адрес точки доступа: ");
Serial.println(myIP);
// Инициируем сервер
webSocket.begin();
// Метод событий WebSocket
webSocket.onEvent(webSocketServerEvent);
Serial.println("Сервер запущен.");
}
void loop() {
// Цикл WebSocket
webSocket.loop();
// Если прошло 5 секунд...
if (millis() % 1000 == 0) {
// Если данные были получены хотя бы один раз
if (rdata[0] != 0) {
Serial.println("Текущие данные от клиента: ");
// Выводим данные в последовательный порт
for (size_t i = 0; i < glen; i++) {
Serial.println(rdata[i]);
}
Serial.println();
}
}
}
Скетч для ESP32 в качестве WS клиента
// Подключаем библиотеки
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsClient.h>
// Определяем название и пароль точки доступа
const char* ssid = "Название WiFi";
const char* password = "Пароль WiFi";
// Определяем адрес сервера (например "192.168.1.3")
const char* ADDR = "n.n.n.n";
// Определяем url подключения
const char* URL = "/";
// Определяем порт
const uint16_t PORT = 81;
// Создаём массив для отправки
int sdata[2] {1984, 11276};
// Создаём массив для получения
unsigned long rdata[3] {0};
// Кол-во элементов массива для получения
const size_t glen = sizeof(rdata) / sizeof(rdata[0]);
// Создаём экземпляр класса клиента
WebSocketsClient webSocket;
/* Callback функция события WebSocket.
* Парамтетры:
* type - тип событыя
* payload - указатель на данные
* length - размер данных
*/
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
// Если тип данных двоичный и размер больше нуля
if (type == WStype_BIN && length > 0) {
// Преобразуем данные из байтов в десятичные числа без знака
const unsigned long* tmp = (unsigned long*) payload;
// Вычисляем размер данных в новом формате
const size_t len = length / sizeof(*rdata);
// Записываем в глобальную переменную
for (size_t i = 0; i < len && i < glen; i++) {
rdata[i] = tmp[i];
}
Serial.println();
}
}
void setup() {
// Инициируем последовательный порт
Serial.begin(115200);
// Подключаемся к WiFi
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Ждём подключения WiFi
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
Serial.println();
Serial.print("IP адрес: ");
Serial.println(WiFi.localIP());
// Подключаемся к серверу
webSocket.begin(ADDR, PORT, URL);
// Метод событий WebSocket
webSocket.onEvent(webSocketEvent);
// Если соединение прервано, повторить попытку через 5 сек.
webSocket.setReconnectInterval(5000);
}
void loop() {
// Цикл WebSocket
webSocket.loop();
// Если прошла одна секунда и сервер хотя бы раз прислал данные
if (millis() % 1000 == 0) {
// Отправляем данные в двоичном формате серверу
webSocket.sendBIN((uint8_t*)sdata, sizeof(sdata));
Serial.println("Данные отправлены.");
// Если данные сервера были получены хотябы один раз
if (rdata[0] != 0) {
Serial.println("Текущие данные сервера:");
// Выводим массив данных в последовательный порт
for (size_t i = 0; i < glen; i++)
Serial.println(rdata[i]);
}
}
// Если соединение WiFi прервано
if (WiFi.status() != WL_CONNECTED)
// Вызываем функцию setup(), для повторного подключения
setup();
}

Обсуждение