diff --git a/wiki_ru.md b/wiki_ru.md new file mode 100644 index 0000000..2a8a923 --- /dev/null +++ b/wiki_ru.md @@ -0,0 +1,196 @@ +# Описание компонента zh_ota_server + +## Общие сведения + +**zh_ota_server** — это компонент для ESP-IDF, реализующий сервер обновлений прошивки по воздуху (Over-The-Air, OTA) для микроконтроллеров ESP32. + +Компонент предоставляет веб-интерфейс для загрузки бинарных файлов прошивки через браузер и автоматически записывает их в свободный OTA-раздел ESP32 с последующей активацией и перезагрузкой устройства. + +## Основные возможности + +1. **Веб-интерфейс для загрузки** — встроенная HTML-страница для выбора и отправки файла прошивки через браузер. +2. **Автоматическое определение раздела** — компонент сам находит следующий доступный OTA-раздел через API ESP-IDF. +3. **Построчная запись прошивки** — данные принимаются блоками по 1000 байт для экономии оперативной памяти. +4. **Автоматическая активация и перезагрузка** — после успешной записи прошивки раздел становится главным, и устройство перезагружается. +5. **Интеграция с ESP-IDF HTTP server** — компонент использует встроенный HTTP-сервер ESP-IDF и регистрирует свои обработчики как стандартные URI-хендлеры. +6. **Встроенная HTML-страница** — веб-интерфейс хранится как встроенный массив байт (EMBED_FILES), не требуя внешней файловой системы. +7. **Детальное логирование ошибок** — при ошибках логируются файл, строка и код возврата 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`) + +1. Выделение буфера размером **1000 байт** для приёма данных. +2. Определение следующего доступного OTA-раздела через `esp_ota_get_next_update_partition(NULL)`. +3. Начало записи в раздел через `esp_ota_begin()` с размером `OTA_SIZE_UNKNOWN`. +4. **Цикл приёма данных:** + - Приём данных из 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`). +5. Завершение записи через `esp_ota_end()` (валидация образа). +6. Установка раздела как загрузочного через `esp_ota_set_boot_partition()`. +7. Отправка HTTP-ответа `"Firmware update complete, rebooting now!\n"`. +8. Задержка **500 мс** через `vTaskDelay()` и вызов `esp_restart()` для перезагрузки. + +## Примеры использования + +### Базовый пример + +```c +#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 в режиме точки доступа + +```c +#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. + +```c +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 или установки загрузочного раздела | + +## Ограничения + +1. **Однократная инициализация** — `zh_ota_server_init()` можно вызвать только один раз за время работы приложения. Повторный вызов вернёт `ESP_ERR_INVALID_STATE`. +2. **Утечка памяти URI-пути** — память, выделенная для `_ota_path`, никогда не освобождается. +3. **Риск «окирпичивания» устройства** — запись невалидного образа прошивки сделает устройство не загружаемым. Необходимо иметь механизм восстановления (например, USB-прошивку). +4. **Отсутствие потокобезопасности** — компонент не защищён мьютексами; одновременный доступ из нескольких потоков может привести к неопределённому поведению. +5. **Размер буфера 1000 байт** — фиксированный размер буфера приёма может быть недостаточен для оптимальной производительности при больших объёмах данных. +6. **Отсутствие контрольной суммы** — валидация образа выполняется только через `esp_ota_end()`, что не гарантирует корректность прошивки на уровне содержимого. + +## Требования для сборки + +- ESP-IDF (фреймворк для ESP32) +- `esp_http_server` — встроенный HTTP-сервер ESP-IDF +- `app_update` — модуль ESP-IDF для управления OTA-разделами и загрузкой +- FreeRTOS (входит в состав ESP-IDF) + +## Установка + +```bash +cd ваш_проект/components +git clone https://github.com/aZholtikov/zh_ota_server +``` + +```c +#include "zh_ota_server.h" +```