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