КОРЗИНА
магазина
8 (499) 500-14-56 | ПН. - ПТ. 12:00-18:00
ЛЕСНОРЯДСКИЙ ПЕРЕУЛОК, 18С2, БЦ "ДМ-ПРЕСС"

Соединяем две Piranha ESP32 по WebSocket и передаём массив данных

Общие сведения:

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

Ссылки




Обсуждение

Гарантии и возврат Используя сайт Вы соглашаетесь с условями