Перенаправление сообщений ESP_LOGx через WebSocket Печать
Добавил(а) microsin   

В более новых версиях ESP-IDF (v5.0 и выше) компонент esp_websocket_client больше не входит в состав фреймворка по умолчанию. Он был вынесен в отдельный репозиторий esp-protocols и теперь устанавливается как внешний компонент.

Способ 1: через IDF Component Manager (рекомендуемый)

1. Создайте файл idf_component.yml в корне вашего проекта или в папке main:

dependencies:
espressif/esp_websocket_client: "^1.0.0"

Система сборки автоматически загрузит компонент при следующей сборке.

2. Или используйте команду для добавления зависимости:

idf.py add-dependency "espressif/esp_websocket_client^1.0.0"

Способ 2: клонировать вручную

Вы можете склонировать репозиторий в папку components вашего проекта:

cd components
git clone https://github.com/espressif/esp-protocols.git

Затем используйте компонент из папки esp-protocols/components/esp_websocket_client.

[Важные замечания]

1. Требования к версии IDF: компонент esp_websocket_client требует ESP-IDF версии 5.0 или выше.
2. Зависимости: компонент автоматически подтягивает необходимые зависимости, включая http_parser.

Пример использования:

// Инициализация WebSocket клиента
esp_websocket_client_config_t ws_config = {
.uri = "ws://192.168.1.100:8080",
.disable_auto_reconnect = false, };
esp_websocket_client_handle_t client = esp_websocket_client_init(&ws_config); esp_websocket_client_start(client);

// Инициализация логгера umsg_log_init(client);

[Альтернативный вариант]

Если вы не хотите использовать внешний компонент, вы можете реализовать отправку логов через обычные TCP сокеты или использовать библиотеку libwebsockets, которая также доступна как компонент.

Заголовочный файл main/umsg_log.h:

#ifndef UMSG_LOG_H
#define UMSG_LOG_H

#include "esp_log.h"
#include "esp_websocket_client.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include < string.h>
#include < stdarg.h>

#ifdef __cplusplus
extern "C" {
#endif

// Настройки
#define UMSG_LOG_QUEUE_SIZE 100 // Размер очереди сообщений
#define UMSG_LOG_MAX_MSG_LEN 512 // Максимальная длина сообщения
#define UMSG_LOG_TASK_STACK 4096 // Размер стека задачи
#define UMSG_LOG_TASK_PRIORITY 5 // Приоритет задачи

// Типы сообщений
typedef enum {
UMSG_LOG_INFO,
UMSG_LOG_WARN,
UMSG_LOG_ERROR } umsg_log_level_t;

// Структура сообщения для очереди
typedef struct {
char message[UMSG_LOG_MAX_MSG_LEN];
umsg_log_level_t level;
char tag[32]; } umsg_log_message_t;

// Структура конфигурации логгера
typedef struct {
esp_websocket_client_handle_t ws_client;
bool uart_output_enabled; // Выводить ли также в UART
bool color_enabled; // Использовать ли цвета } umsg_log_config_t;

// Инициализация логгера
void umsg_log_init(esp_websocket_client_handle_t ws_client);

// Деинициализация логгера
void umsg_log_deinit(void);

// Отправка лога через WebSocket (внутренняя функция)
void umsg_log_send(const char *tag, umsg_log_level_t level, const char *format, ...);

// Получение строкового представления уровня лога
const char* umsg_log_level_to_string(umsg_log_level_t level);

// Макросы с таким же интерфейсом как у ESP_LOGx
#define UMSGI(tag, format, ...) \
do { \
umsg_log_send(tag, UMSG_LOG_INFO, format, ##__VA_ARGS__); \
} while(0)

#define UMSGW(tag, format, ...) \
do { \
umsg_log_send(tag, UMSG_LOG_WARN, format, ##__VA_ARGS__); \
} while(0)

#define UMSGE(tag, format, ...) \
do { \
umsg_log_send(tag, UMSG_LOG_ERROR, format, ##__VA_ARGS__); \
} while(0)

// Макросы с условием (аналог ESP_LOGI_IS_ENABLED)
#define UMSGI_IS_ENABLED(level) 1 // Всегда включены
#define UMSGW_IS_ENABLED(level) 1
#define UMSGE_IS_ENABLED(level) 1

// Макросы с проверкой уровня (аналог ESP_LOG_LEVEL_LOCAL)
#define UMSG_LOG_LEVEL_LOCAL ESP_LOG_VERBOSE
#define UMSG_LOG_LEVEL(tag, level, format, ...) \
do { \
if (level >= UMSG_LOG_LEVEL_LOCAL) { \
umsg_log_send(tag, level, format, ##__VA_ARGS__); \
} \
} while(0)

#ifdef __cplusplus }
#endif

#endif // UMSG_LOG_H

Код модуля main/umsg_log.c, где используется подключение WebSocket клиента:

#include "umsg_log.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "esp_err.h"
#include < stdio.h>

// Глобальные переменные
static QueueHandle_t umsg_log_queue = NULL;
static TaskHandle_t umsg_log_task_handle = NULL;
static umsg_log_config_t umsg_log_config = {
.ws_client = NULL,
.uart_output_enabled = true,
.color_enabled = true };
static SemaphoreHandle_t umsg_log_mutex = NULL;

// ANSI цветовые коды
#define COLOR_RESET "\033[0m"
#define COLOR_RED "\033[31m"
#define COLOR_GREEN "\033[32m"
#define COLOR_YELLOW "\033[33m"
#define COLOR_BLUE "\033[34m"
#define COLOR_MAGENTA "\033[35m"
#define COLOR_CYAN "\033[36m"
#define COLOR_WHITE "\033[37m"

// Получение цветового кода для уровня лога
static const char* get_color_for_level(umsg_log_level_t level) {
if (!umsg_log_config.color_enabled) return "";

switch (level) {
case UMSG_LOG_INFO: return COLOR_GREEN;
case UMSG_LOG_WARN: return COLOR_YELLOW;
case UMSG_LOG_ERROR: return COLOR_RED;
default: return COLOR_RESET;
} }

// Получение строкового представления уровня лога
const char* umsg_log_level_to_string(umsg_log_level_t level) {
switch (level) {
case UMSG_LOG_INFO: return "I";
case UMSG_LOG_WARN: return "W";
case UMSG_LOG_ERROR: return "E";
default: return "?";
} }

// Форматирование сообщения для WebSocket
static void format_websocket_message(char *buffer, size_t buffer_size,
const char *tag, umsg_log_level_t level,
const char *message) {
uint32_t timestamp_ms = esp_timer_get_time() / 1000;

snprintf(buffer, buffer_size,
"{\"type\":\"log\",\"level\":\"%s\",\"tag\":\"%s\","
"\"message\":\"%s\",\"timestamp\":%lu,\"core\":%d}",
umsg_log_level_to_string(level),
tag,
message,
timestamp_ms,
xPortGetCoreID()); }

// Форматирование сообщения для UART
static void format_uart_message(char *buffer, size_t buffer_size,
const char *tag, umsg_log_level_t level,
const char *message) {
const char *color = get_color_for_level(level);
const char *level_str = umsg_log_level_to_string(level);

snprintf(buffer, buffer_size,
"%s%s (%llu) %s: %s%s\n",
color,
level_str,
esp_timer_get_time() / 1000,
tag,
message,
umsg_log_config.color_enabled ? COLOR_RESET : ""); }

// Задача для отправки логов через WebSocket
static void umsg_log_task(void *pvParameters) {
umsg_log_message_t log_msg;

while (1) {
if (xQueueReceive(umsg_log_queue, &log_msg, portMAX_DELAY) == pdTRUE) {
// Форматируем сообщение для WebSocket
char ws_buffer[UMSG_LOG_MAX_MSG_LEN + 128];
format_websocket_message(ws_buffer, sizeof(ws_buffer),
log_msg.tag, log_msg.level, log_msg.message);

// Отправляем через WebSocket
if (umsg_log_config.ws_client != NULL) {
if (esp_websocket_client_is_connected(umsg_log_config.ws_client)) {
esp_websocket_client_send_text(umsg_log_config.ws_client,
ws_buffer, strlen(ws_buffer),
pdMS_TO_TICKS(100));
}
}

// Выводим в UART если нужно
if (umsg_log_config.uart_output_enabled) {
char uart_buffer[UMSG_LOG_MAX_MSG_LEN + 128];
format_uart_message(uart_buffer, sizeof(uart_buffer),
log_msg.tag, log_msg.level, log_msg.message);
printf("%s", uart_buffer);
}
}
} }

// Внутренняя функция отправки лога
static void umsg_log_vprintf(const char *tag, umsg_log_level_t level,
const char *format, va_list args) {
if (umsg_log_queue == NULL) return;

umsg_log_message_t log_msg;
strncpy(log_msg.tag, tag, sizeof(log_msg.tag) - 1);
log_msg.tag[sizeof(log_msg.tag) - 1] = '\0';
log_msg.level = level;

// Форматируем сообщение
vsnprintf(log_msg.message, sizeof(log_msg.message) - 1, format, args);
log_msg.message[sizeof(log_msg.message) - 1] = '\0';

// Отправляем в очередь (неблокирующая отправка)
if (xQueueSend(umsg_log_queue, &log_msg, 0) != pdTRUE) {
// Очередь заполнена - можно добавить счетчик потерянных сообщений
static uint32_t dropped_count = 0;
dropped_count++;
if (dropped_count % 100 == 1) {
printf("UMSG: Dropped %lu log messages\n", dropped_count);
}
} }

// Отправка лога (публичная функция)
void umsg_log_send(const char *tag, umsg_log_level_t level, const char *format, ...) {
va_list args;
va_start(args, format);
umsg_log_vprintf(tag, level, format, args);
va_end(args); }

// Инициализация логгера
void umsg_log_init(esp_websocket_client_handle_t ws_client) {
if (umsg_log_queue != NULL) {
ESP_LOGW("UMSG_LOG", "Logger already initialized");
return;
}

// Создаем мьютекс
umsg_log_mutex = xSemaphoreCreateMutex();
// Создаем очередь
umsg_log_queue = xQueueCreate(UMSG_LOG_QUEUE_SIZE, sizeof(umsg_log_message_t));
if (umsg_log_queue == NULL) {
ESP_LOGE("UMSG_LOG", "Failed to create log queue");
return;
}

// Сохраняем конфигурацию
umsg_log_config.ws_client = ws_client;

// Создаем задачу для отправки логов
BaseType_t ret = xTaskCreate(umsg_log_task, "umsg_log",
UMSG_LOG_TASK_STACK, NULL,
UMSG_LOG_TASK_PRIORITY, &umsg_log_task_handle);

if (ret != pdPASS) {
ESP_LOGE("UMSG_LOG", "Failed to create log task");
vQueueDelete(umsg_log_queue);
umsg_log_queue = NULL;
return;
}

ESP_LOGI("UMSG_LOG", "UMSG logger initialized (WebSocket: %s)",
ws_client ? "enabled" : "disabled");

// Тестовая отправка
UMSGI("UMSG_LOG", "Logger initialized successfully"); }

// Деинициализация логгера
void umsg_log_deinit(void) {
if (umsg_log_queue != NULL) {
// Ждем пока очередь опустеет
vTaskDelay(pdMS_TO_TICKS(100));

// Удаляем задачу
if (umsg_log_task_handle != NULL) {
vTaskDelete(umsg_log_task_handle);
umsg_log_task_handle = NULL;
}

// Удаляем очередь
vQueueDelete(umsg_log_queue);
umsg_log_queue = NULL;

// Удаляем мьютекс
if (umsg_log_mutex != NULL) {
vSemaphoreDelete(umsg_log_mutex);
umsg_log_mutex = NULL;
}

printf("UMSG: Logger deinitialized\n");
} }

// Настройка параметров логгера
void umsg_log_set_uart_output(bool enabled) {
if (umsg_log_mutex != NULL) {
xSemaphoreTake(umsg_log_mutex, portMAX_DELAY);
umsg_log_config.uart_output_enabled = enabled;
xSemaphoreGive(umsg_log_mutex);
} }

void umsg_log_set_color_enabled(bool enabled) {
if (umsg_log_mutex != NULL) {
xSemaphoreTake(umsg_log_mutex, portMAX_DELAY);
umsg_log_config.color_enabled = enabled;
xSemaphoreGive(umsg_log_mutex);
} }

Пример использования:

#include "umsg_log.h"
#include "esp_websocket_client.h"

// Глобальный WebSocket клиент
extern esp_websocket_client_handle_t ws_client;

void app_main(void) {
// Инициализация WebSocket клиента
esp_websocket_client_config_t ws_config = {
.uri = "ws://192.168.1.100:8080",
};
ws_client = esp_websocket_client_init(&ws_config);
esp_websocket_client_start(ws_client);

// Инициализация UMSG логгера
umsg_log_init(ws_client);

// Опционально: отключить вывод в UART (если нужно только WebSocket)
// umsg_log_set_uart_output(false);

// Использование макросов (интерфейс как у ESP_LOGI)
UMSGI("MAIN", "Application started");
UMSGW("MAIN", "Warning: low memory");
UMSGE("MAIN", "Error: connection failed");

// Форматированные сообщения
int value = 42;
UMSGI("TAG", "Value = %d, string = %s", value, "test");

// В любой момент можно отправить лог
umsg_log_send("CUSTOM", UMSG_LOG_INFO, "Custom message");

// ... остальной код ...

// При завершении
umsg_log_deinit(); }

Формат сообщения для WebSocket. Сообщения отправляются в JSON формате, так их проще декодировать и анализировать на принимающей стороне:

{
"type": "log",
"level": "I",
"tag": "MAIN",
"message": "Application started",
"timestamp": 12345678,
"core": 0 }

Где:

level: I (Info), W (Warning), E (Error)
tag: текстовый тег сообщения
message: текст сообщения
timestamp: время в миллисекундах
core: номер ядра ESP32 (0 или 1)

Преимущества такого подхода:

● Совместимость: Интерфейс идентичен ESP_LOGx макросам.
● Асинхронность: Используется очередь и отдельная задача, не блокирует основной код.
● Гибкость: Можно одновременно выводить и в UART, и в WebSocket.
● Надежность: Очередь предотвращает потерю сообщений при временных проблемах с WebSocket.
● Расширяемость: Легко добавить дополнительные форматы вывода (SD card, MQTT и т.д.)

Теперь вы можете использовать UMSGI, UMSGW, UMSGE точно так же, как стандартные макросы ESP-IDF, но ваши сообщения будут отправляться через WebSocket.

[Ссылки]

1. ESP-IDF: как сделать интерактивную консоль.
2. ESP-IDF: компонент консоли.