12 KiB
Описание компонента zh_ota_server
Общие сведения
zh_ota_server — это компонент для ESP-IDF, реализующий сервер обновлений прошивки по воздуху (Over-The-Air, OTA) для микроконтроллеров ESP32.
Компонент предоставляет веб-интерфейс для загрузки бинарных файлов прошивки через браузер и автоматически записывает их в свободный OTA-раздел ESP32 с последующей активацией и перезагрузкой устройства.
Основные возможности
- Веб-интерфейс для загрузки — встроенная HTML-страница для выбора и отправки файла прошивки через браузер.
- Автоматическое определение раздела — компонент сам находит следующий доступный OTA-раздел через API ESP-IDF.
- Построчная запись прошивки — данные принимаются блоками по 1000 байт для экономии оперативной памяти.
- Автоматическая активация и перезагрузка — после успешной записи прошивки раздел становится главным, и устройство перезагружается.
- Интеграция с ESP-IDF HTTP server — компонент использует встроенный HTTP-сервер ESP-IDF и регистрирует свои обработчики как стандартные URI-хендлеры.
- Встроенная HTML-страница — веб-интерфейс хранится как встроенный массив байт (EMBED_FILES), не требуя внешней файловой системы.
- Детальное логирование ошибок — при ошибках логируются файл, строка и код возврата ESP-IDF.
Внутреннее устройство
Глобальные переменные
| Переменная | Тип | Описание |
|---|---|---|
_ota_path |
char * |
Выделенная динамически память для URI-пути OTA-сервера (не освобождается) |
_ota_ws_path |
const char * |
URI-путь для POST-обработчика ("/zh_ota_server") |
_is_initialized |
volatile bool |
Флаг инициализации компонента, предотвращающий повторный вызов zh_ota_server_init() |
Структуры обработчиков
| Поле | Тип | Описание |
|---|---|---|
_ota_page.uri |
char * |
URI-путь для GET-запроса (устанавливается при инициализации) |
_ota_page.method |
httpd_method_t |
HTTP-метод: HTTP_GET |
_ota_page.handler |
httpd_req_handler_t |
Указатель на _ota_page_handler() |
_ota_page_ws.uri |
char * |
URI-путь для POST-запроса: "/zh_ota_server" |
_ota_page_ws.method |
httpd_method_t |
HTTP-метод: HTTP_POST |
_ota_page_ws.handler |
httpd_req_handler_t |
Указатель на _ota_ws_page_handler() |
Встроенные ресурсы
HTML-страница zh_ota_server.html встраивается в прошивку через механизм EMBED_FILES CMake. Доступна через внешние символы:
| Символ | Описание |
|---|---|
zh_ota_server_html_start[] |
Начало встроенного HTML-массива |
zh_ota_server_html_end[] |
Конец встроенного HTML-массива |
Размер страницы вычисляется как zh_ota_server_html_end - zh_ota_server_html_start.
Управление памятью
При инициализации zh_ota_path выделяется через heap_caps_calloc() с флагом MALLOC_CAP_8BIT. Память не освобождается никогда — это осознанный выбор, так как функция инициализации может быть вызвана только один раз за время работы приложения.
API (интерфейс)
Инициализация
| Функция | Описание |
|---|---|
zh_ota_server_init() |
Инициализирует и регистрирует обработчики OTA-сервера в предоставленном экземпляре HTTP-сервера. Принимает handle сервера (httpd_handle_t) и URI-путь (например, "/ota"). Возвращает ESP_OK при успехе, ESP_ERR_INVALID_ARG при некорректных аргументах, ESP_ERR_NO_MEM при нехватке памяти, ESP_FAIL при ошибке регистрации обработчика. |
Потокобезопасность
Компонент не является потокобезопасным. Глобальные переменные _ota_path и _is_initialized защищены только проверкой на повторную инициализацию (_is_initialized == false), но не мьютексами. Обработчики HTTP-запросов вызываются в контексте потока HTTP-сервера и не используют механизмы синхронизации.
Алгоритмы
Обработка OTA-обновления (_ota_ws_page_handler)
- Выделение буфера размером 1000 байт для приёма данных.
- Определение следующего доступного OTA-раздела через
esp_ota_get_next_update_partition(NULL). - Начало записи в раздел через
esp_ota_begin()с размеромOTA_SIZE_UNKNOWN. - Цикл приёма данных:
- Приём данных из HTTP-запроса через
httpd_req_recv()блоками доsizeof(buf)(1000 байт). - При таймауте сокета (
HTTPD_SOCK_ERR_TIMEOUT) цикл продолжается без действия. - При ошибке приёма отправляется HTTP 500 и вызывается
esp_ota_abort(). - Запись данных в flash через
esp_ota_write(); при ошибке отправляется HTTP 500 и вызываетсяesp_ota_abort(). - Уменьшение счётчика оставшихся байт (
remaining -= recv_len).
- Приём данных из HTTP-запроса через
- Завершение записи через
esp_ota_end()(валидация образа). - Установка раздела как загрузочного через
esp_ota_set_boot_partition(). - Отправка HTTP-ответа
"Firmware update complete, rebooting now!\n". - Задержка 500 мс через
vTaskDelay()и вызовesp_restart()для перезагрузки.
Примеры использования
Базовый пример
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "zh_ota_server.h"
static httpd_handle_t http_server_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_ota_server", ESP_LOG_ERROR);
nvs_flash_init();
esp_netif_init();
esp_event_loop_run(NULL, NULL);
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_start(&http_server_handle, &config);
zh_ota_server_init(http_server_handle, "/ota");
}
Пример с Wi-Fi в режиме точки доступа
#include "nvs_flash.h"
#include "esp_wifi.h"
#include "zh_ota_server.h"
#define WIFI_SSID "ESP32-OTA"
#define WIFI_PASS "password123"
static httpd_handle_t http_server_handle = NULL;
void app_main(void)
{
esp_log_level_set("zh_ota_server", ESP_LOG_DEBUG);
nvs_flash_init();
esp_netif_init();
esp_event_loop_create_default();
wifi_init_config_t wifi_config = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&wifi_config);
wifi_config_t ap_config = {
.ap = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.max_connection = 4,
.authmode = WIFI_AUTH_WPA2_PSK,
},
};
esp_wifi_set_mode(WIFI_MODE_AP);
esp_wifi_set_config(WIFI_IF_AP, &ap_config);
esp_wifi_start();
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_start(&http_server_handle, &config);
zh_ota_server_init(http_server_handle, "/ota");
}
Логирование
Компонент использует ESP-IDF логирование с тегом "zh_ota_server".
Макрос ZH_LOGI — для информационных сообщений. Макрос ZH_LOGE — для ошибок, автоматически добавляет имя файла, номер строки и строковое представление кода ошибки ESP-IDF.
esp_log_level_set("zh_ota_server", ESP_LOG_DEBUG);
Ошибки
| Код | Описание |
|---|---|
ESP_OK |
Успешное выполнение |
ESP_ERR_INVALID_ARG |
Неверный аргумент: NULL-указатель на сервер или путь |
ESP_ERR_INVALID_STATE |
Повторная инициализация: компонент уже был инициализирован |
ESP_ERR_NO_MEM |
Нехватка памяти при выделении для URI-пути |
ESP_FAIL |
Ошибка регистрации URI-обработчика, записи в flash, завершения OTA или установки загрузочного раздела |
Ограничения
- Однократная инициализация —
zh_ota_server_init()можно вызвать только один раз за время работы приложения. Повторный вызов вернётESP_ERR_INVALID_STATE. - Утечка памяти URI-пути — память, выделенная для
_ota_path, никогда не освобождается. - Риск «окирпичивания» устройства — запись невалидного образа прошивки сделает устройство не загружаемым. Необходимо иметь механизм восстановления (например, USB-прошивку).
- Отсутствие потокобезопасности — компонент не защищён мьютексами; одновременный доступ из нескольких потоков может привести к неопределённому поведению.
- Размер буфера 1000 байт — фиксированный размер буфера приёма может быть недостаточен для оптимальной производительности при больших объёмах данных.
- Отсутствие контрольной суммы — валидация образа выполняется только через
esp_ota_end(), что не гарантирует корректность прошивки на уровне содержимого.
Требования для сборки
- ESP-IDF (фреймворк для ESP32)
esp_http_server— встроенный HTTP-сервер ESP-IDFapp_update— модуль ESP-IDF для управления OTA-разделами и загрузкой- FreeRTOS (входит в состав ESP-IDF)
Установка
cd ваш_проект/components
git clone https://github.com/aZholtikov/zh_ota_server
#include "zh_ota_server.h"