6#include <ArduinoJson.h>
7#include <mbedtls/sha256.h>
8#include <esp_ota_ops.h>
12#include <esp_task_wdt.h>
30static void _printGuard(
const char* name,
const char* tag,
const char* current) {
31 logError(
"[GUARD] Повреждение (%s) после %s: '%s'", name, tag, current);
35 if (strncmp(
guardGap,
"BEFORE", 6) != 0) {
47void setupOTA(
const char* manifestUrl, WiFiClient& client)
52 logWarn(
"[OTA] [SETUP DEBUG] ⚠️ OTA уже инициализирован, пропускаем повторную инициализацию");
57 logSystem(
"[OTA] [SETUP DEBUG] Инициализация OTA 2.0...");
58 logSystem(
"[OTA] [SETUP DEBUG] Входные параметры:");
59 logSystem(
"[OTA] [SETUP DEBUG] manifestUrl: %s", manifestUrl ? manifestUrl :
"NULL");
60 logSystem(
"[OTA] [SETUP DEBUG] client: %s", &client ?
"OK" :
"NULL");
63 if (!manifestUrl || strlen(manifestUrl) < 20 || strstr(manifestUrl,
"github.com") ==
nullptr) {
64 logError(
"[OTA] [SETUP DEBUG] ❌ Неверный URL манифеста!");
75 logError(
"[OTA] [SETUP DEBUG] ❌ URL поврежден при копировании!");
90 logSystem(
"[OTA] [SETUP DEBUG] Глобальные переменные установлены:");
96 logSuccess(
"[OTA] [SETUP DEBUG] ✅ OTA инициализирован успешно с защитой памяти");
100static bool verifySha256(
const uint8_t* calcDigest,
const char* expectedHex)
103 for (
int i = 0; i < 32; ++i)
104 sprintf(&calcHex[i * 2],
"%02x", calcDigest[i]);
105 return strcasecmp(calcHex, expectedHex) == 0;
111 esp_task_wdt_reset();
115 size_t freeHeap = ESP.getFreeHeap();
116 logSystem(
"[OTA] Свободная память перед HTTP: %d байт", freeHeap);
119 if (freeHeap < 70000) {
121 logError(
"[OTA] Недостаточно памяти для HTTP: %d байт", freeHeap);
126 static char urlBuffer[256];
127 strlcpy(urlBuffer, binUrl.c_str(),
sizeof(urlBuffer));
128 logSystem(
"[OTA] Загрузка: %s", urlBuffer);
131 if (strlen(urlBuffer) < 10 || strstr(urlBuffer,
"github.com") ==
nullptr) {
133 logError(
"[OTA] URL поврежден или некорректен: %s", urlBuffer);
138 logSystem(
"[OTA] Инициализация HTTP клиента...");
141 logError(
"[OTA] Не удалось инициализировать HTTP клиент");
145 http.setTimeout(65000);
146 http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
148 logSystem(
"[OTA] Выполняем HTTP GET запрос...");
149 esp_task_wdt_reset();
151 int code = http.GET();
152 esp_task_wdt_reset();
156 if (code != HTTP_CODE_OK)
159 logError(
"[OTA] HTTP ошибка %d", code);
163 contentLen = http.getSize();
164 if (contentLen == -1)
166 logSystem(
"[OTA] Chunked transfer, размер неизвестен");
167 contentLen = UPDATE_SIZE_UNKNOWN;
171 logSystem(
"[OTA] Размер файла: %d байт", contentLen);
174 if (!Update.begin(contentLen))
177 logError(
"[OTA] Update.begin() failed");
178 Update.printError(Serial);
186static bool downloadData(HTTPClient& http,
int contentLen, mbedtls_sha256_context& shaCtx)
191 size_t heapBeforeDownload = ESP.getFreeHeap();
192 logSystem(
"[OTA] Память перед загрузкой: %d байт", heapBeforeDownload);
195 if (heapBeforeDownload < 60000) {
196 strcpy(
statusBuf,
"Мало памяти для загрузки");
197 logError(
"[OTA] Недостаточно памяти для загрузки: %d байт", heapBeforeDownload);
201 WiFiClient* stream = http.getStreamPtr();
204 logError(
"[OTA] Не удалось получить поток данных");
210 size_t totalDownloaded = 0;
211 unsigned long lastProgress = millis();
212 unsigned long lastActivity = millis();
213 const unsigned long TIMEOUT_MS = 120000;
214 bool isChunked = (contentLen == UPDATE_SIZE_UNKNOWN);
216 while (http.connected())
218 esp_task_wdt_reset();
220 size_t avail = stream->available();
223 lastActivity = millis();
224 size_t toRead = (avail >
sizeof(buf)) ?
sizeof(buf) : avail;
225 int readBytes = stream->readBytes(buf, toRead);
230 logError(
"[OTA] Ошибка чтения данных");
235 if (Update.write(buf, readBytes) != (
size_t)readBytes)
238 logError(
"[OTA] Ошибка записи во flash");
239 Update.printError(Serial);
244 mbedtls_sha256_update_ret(&shaCtx, buf, readBytes);
245 totalDownloaded += readBytes;
248 if (millis() - lastProgress > 3000)
252 snprintf(
statusBuf,
sizeof(
statusBuf),
"Загружено %dКБ", (
int)(totalDownloaded / 1024));
256 int percent = (totalDownloaded * 100) / contentLen;
259 logSystem(
"[OTA] Загружено: %d байт", totalDownloaded);
260 lastProgress = millis();
266 if (!isChunked && totalDownloaded == (
size_t)contentLen)
268 logSystem(
"[OTA] Загрузка завершена, получено %d байт", totalDownloaded);
272 if (millis() - lastActivity > TIMEOUT_MS)
275 logError(
"[OTA] Таймаут загрузки (нет данных %lu мс)", TIMEOUT_MS);
280 if (isChunked && !http.connected())
282 logSystem(
"[OTA] Chunked transfer завершён, загружено %d байт", totalDownloaded);
286 esp_task_wdt_reset();
291 if (!isChunked && totalDownloaded != (
size_t)contentLen)
293 snprintf(
statusBuf,
sizeof(
statusBuf),
"Неполная загрузка %d/%d", totalDownloaded, contentLen);
294 logError(
"[OTA] Неполная загрузка: %d из %d байт", totalDownloaded, contentLen);
305 logSystem(
"[OTA] Начинаем загрузку и обновление");
308 size_t initialHeap = ESP.getFreeHeap();
309 logSystem(
"[OTA] Начальная память: %d байт", initialHeap);
312 if (initialHeap < 80000) {
313 strcpy(
statusBuf,
"Критически мало памяти");
314 logError(
"[OTA] Критически мало памяти: %d байт", initialHeap);
319 HTTPClient* http =
new HTTPClient();
321 strcpy(
statusBuf,
"Ошибка создания HTTP клиента");
322 logError(
"[OTA] Не удалось создать HTTP клиент");
329 logSystem(
"[OTA] Инициализация загрузки...");
332 logError(
"[OTA] Ошибка инициализации загрузки");
338 logSystem(
"[OTA] Инициализация успешна, размер контента: %d", contentLen);
341 mbedtls_sha256_context* shaCtx =
new mbedtls_sha256_context();
343 strcpy(
statusBuf,
"Ошибка создания SHA256 контекста");
344 logError(
"[OTA] Не удалось создать SHA256 контекст");
350 mbedtls_sha256_init(shaCtx);
351 mbedtls_sha256_starts_ret(shaCtx, 0);
354 bool downloadSuccess =
downloadData(*http, contentLen, *shaCtx);
358 if (!downloadSuccess)
360 mbedtls_sha256_free(shaCtx);
368 mbedtls_sha256_finish_ret(shaCtx, digest);
369 mbedtls_sha256_free(shaCtx);
375 strcpy(
statusBuf,
"Неверная контрольная сумма");
376 logError(
"[OTA] SHA256 не совпадает");
382 strcpy(
statusBuf,
"Завершение установки");
383 if (!Update.end(
true))
386 logError(
"[OTA] Update.end() failed");
387 Update.printError(Serial);
392 strcpy(
statusBuf,
"✅ Обновление завершено!");
393 logSystem(
"[OTA] ✅ Обновление успешно завершено. Перезагрузка через 3 секунды...");
409 static bool isChecking =
false;
412 logWarn(
"[OTA] Проверка уже выполняется, пропускаем");
417 logSystem(
"[OTA] Принудительная проверка OTA запущена");
427 logError(
"[OTA] Нет доступных обновлений для установки");
437 strcpy(
statusBuf,
"Обновление успешно!");
445 logError(
"[OTA] Установка обновления не удалась");
458 static unsigned long debugCallCount = 0;
462 esp_task_wdt_reset();
466 logError(
"[OTA] [DEBUG] OTA не инициализирован или URL пуст - выходим");
473 logError(
"[OTA] [DEBUG] Переинициализируем OTA...");
478 logSystem(
"[OTA] [DEBUG] handleOTA() вызов #%lu, URL проверен: %.50s...",
483 logError(
"[OTA] [DEBUG] clientPtr не задан - выходим");
487 logSystem(
"[OTA] [DEBUG] Начинаем проверку обновлений...");
489 strcpy(
statusBuf,
"Проверка обновлений");
492 logSystem(
"[OTA] [DEBUG] Инициализируем HTTP клиент...");
494 http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
495 http.setTimeout(15000);
497 logSystem(
"[OTA] [DEBUG] Отправляем GET запрос...");
498 int code = http.GET();
499 esp_task_wdt_reset();
501 logSystem(
"[OTA] [DEBUG] HTTP ответ: %d", code);
503 if (code != HTTP_CODE_OK)
506 logError(
"[OTA] [DEBUG] Ошибка загрузки манифеста: HTTP %d", code);
509 if (code == HTTP_CODE_NOT_FOUND) {
510 logError(
"[OTA] [DEBUG] 404 - файл манифеста не найден на сервере");
511 }
else if (code == HTTP_CODE_MOVED_PERMANENTLY || code == HTTP_CODE_FOUND) {
512 logError(
"[OTA] [DEBUG] %d - редирект, но followRedirects включен", code);
513 }
else if (code == -1) {
514 logError(
"[OTA] [DEBUG] -1 - ошибка подключения/DNS");
515 }
else if (code == -11) {
516 logError(
"[OTA] [DEBUG] -11 - таймаут подключения");
523 String manifestContent = http.getString();
524 int contentLength = manifestContent.length();
527 logSystem(
"[OTA] [DEBUG] Манифест получен: %d байт", contentLength);
530 String preview = manifestContent.substring(0, min(200, contentLength));
531 logSystem(
"[OTA] [DEBUG] Начало манифеста: '%s'%s",
532 preview.c_str(), contentLength > 200 ?
"..." :
"");
535 if (!manifestContent.startsWith(
"{")) {
536 logError(
"[OTA] [DEBUG] Манифест не начинается с '{' - возможно HTML ошибка");
541 const size_t capacity = JSON_OBJECT_SIZE(3) + 300;
542 StaticJsonDocument<capacity> doc;
543 DeserializationError err = deserializeJson(doc, manifestContent);
547 logError(
"[OTA] [DEBUG] Ошибка парсинга JSON: %s", err.c_str());
548 logError(
"[OTA] [DEBUG] JSON содержимое: '%s'", manifestContent.c_str());
552 const char* newVersion = doc[
"version"] |
"";
553 const char* binUrl = doc[
"url"] |
"";
554 const char* sha256 = doc[
"sha256"] |
"";
556 logSystem(
"[OTA] [DEBUG] Парсинг JSON успешен:");
557 logSystem(
"[OTA] [DEBUG] version: '%s'", newVersion);
558 logSystem(
"[OTA] [DEBUG] url: '%s'", binUrl);
559 logSystem(
"[OTA] [DEBUG] sha256: '%s'", sha256);
563 if (strlen(newVersion) == 0) {
564 logError(
"[OTA] [DEBUG] Поле 'version' пустое или отсутствует");
565 strcpy(
statusBuf,
"Нет версии в манифесте");
568 if (strlen(binUrl) == 0) {
569 logError(
"[OTA] [DEBUG] Поле 'url' пустое или отсутствует");
570 strcpy(
statusBuf,
"Нет URL в манифесте");
573 if (strlen(sha256) != 64) {
574 logError(
"[OTA] [DEBUG] Поле 'sha256' неверной длины: %d (ожидается 64)", strlen(sha256));
589 logSystem(
"[OTA] [DEBUG] Версии совпадают - обновление не требуется");
600 logSystem(
"[OTA] [DEBUG] ✅ ОБНОВЛЕНИЕ НАЙДЕНО!");
602 logSystem(
"[OTA] [DEBUG] Доступна: %s", newVersion);
603 logSystem(
"[OTA] [DEBUG] URL: %s", binUrl);
604 logSystem(
"[OTA] [DEBUG] SHA256: %.16s...", sha256);
605 logSystem(
"[OTA] [DEBUG] Ожидаем подтверждения установки через веб-интерфейс");
void logWarn(const char *format,...)
void logSuccess(const char *format,...)
void logError(const char *format,...)
void logSystem(const char *format,...)
Система логгирования с красивым форматированием
static bool updateAvailable
static bool verifySha256(const uint8_t *calcDigest, const char *expectedHex)
static String pendingUpdateVersion
static String pendingUpdateSha256
const char * getOtaStatus()
static char manifestUrlGlobal[512]
static char statusBuf[128]
static bool downloadData(HTTPClient &http, int contentLen, mbedtls_sha256_context &shaCtx)
static WiFiClient * clientPtr
static void _printGuard(const char *name, const char *tag, const char *current)
static bool downloadAndUpdate(const String &binUrl, const char *expectedSha256)
void setupOTA(const char *manifestUrl, WiFiClient &client)
static bool initializeDownload(HTTPClient &http, const String &binUrl, int &contentLen)
static String pendingUpdateUrl
void checkGuard(const char *tag)
static char guardSentinel[8]
static bool urlInitialized
#define JXCT_VERSION_STRING