Files
ESP32_COMM/main/utils/ota_compressor.cpp
T
小龙虾 c4d78bdfbc feat(ota): 临时版本,添加zlib压缩支持和SPI通信优化
添加了zlib压缩功能以减少固件传输时间,特别是在弱网环境下
优化了SPI通信参数和错误处理机制

BREAKING CHANGE: 固件版本从0.3.0升级到0.3.1,增加了压缩传输协议

- 在WiFi OTA中集成zlib压缩/解压缩功能
- 添加压缩选项UI界面和前端压缩逻辑
- 修复SPI引脚配置错误(MOSI/MISO接反)
- 优化SPI通信参数设置增加CS时序控制
- 改进OTA重试机制和超时处理
- 扩展ota_relay组件栈大小从8K到12K
- 添加解压缩回调函数和上下文管理
- 更新固件版本号到0.3.1
- 添加更多调试日志和错误检查
2026-05-31 19:09:11 +08:00

225 lines
8.1 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* ota_compressor.cpp - OTA 流式解压实现
*
* 使用 miniz tinfl API 做分块 zlib/deflate 解压。
* 采用 TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF 模式,
* 输出缓冲尽可能大(最大 32KB),满了就 flush 到 Flash 然后重置。
* 这是 tinfl_decompress_mem_to_callback 的流式版本。
*
* 注意:如果 deflate 流中存在 > out_buf_size 的 back-reference,
* 解压会失败(理论上可能,实际浏览器 CompressionStream 生成的流几乎不会出现)。
*/
#include "ota_compressor.h"
#include <cstdlib>
#include <cstring>
#include "esp_log.h"
#include "esp_system.h"
// tinfl declarations are in ota_compressor.h
static const char *TAG = "OTA_DECOMP";
/* tinfl LZ 字典大小 = 32KB,输出缓冲的理想大小 */
static constexpr size_t TINFL_LZ_DICT = 32768; /* back-ref window, not alloc target */
/* 最小可用输出缓冲 */
static constexpr size_t TINFL_MIN_BUF = 512; /* 流式解压够用,每 flush 后重置 */
/* 为系统预留的堆空间(WiFi/TCP 栈需要一些余量) */
static constexpr size_t HEAP_MARGIN = 4096; /* WiFi+TCP 最低保障 */
esp_err_t ota_decomp_init(OtaDecompCtx *ctx, OtaCompress type, size_t out_buf_size)
{
if (!ctx) return ESP_ERR_INVALID_ARG;
memset(ctx, 0, sizeof(*ctx));
ctx->type = type;
if (type == OtaCompress::NONE) {
ESP_LOGI(TAG, "passthrough mode (no compression)");
return ESP_OK;
}
/* ① 先分配 tinfl 状态(~12KB),它不可压缩 */
size_t decomp_size = sizeof(tinfl_decompressor);
ctx->tinfl = (tinfl_decompressor *)calloc(1, decomp_size);
if (!ctx->tinfl) {
ESP_LOGE(TAG, "tinfl alloc failed: need %u B, free heap=%u",
(unsigned)decomp_size, (unsigned)esp_get_free_heap_size());
return ESP_ERR_NO_MEM;
}
tinfl_init((tinfl_decompressor *)ctx->tinfl);
/* ② 根据剩余堆自适应分配输出缓冲 */
size_t free_now = esp_get_free_heap_size();
size_t max_alloc = (free_now > HEAP_MARGIN) ? (free_now - HEAP_MARGIN) : 0;
size_t buf_size = out_buf_size;
if (buf_size > TINFL_LZ_DICT) buf_size = TINFL_LZ_DICT; /* 超过 32KB 没意义 */
if (buf_size > max_alloc) buf_size = max_alloc; /* 受限于可用堆 */
if (buf_size < TINFL_MIN_BUF) buf_size = TINFL_MIN_BUF; /* 最低保障 */
ctx->out_buf = (uint8_t *)malloc(buf_size);
if (!ctx->out_buf) {
ESP_LOGE(TAG, "out_buf alloc failed (%u B), free heap=%u",
(unsigned)buf_size, (unsigned)free_now);
free(ctx->tinfl);
ctx->tinfl = nullptr;
return ESP_ERR_NO_MEM;
}
ctx->out_buf_size = buf_size;
ctx->out_write_pos = 0;
ctx->finished = false;
ESP_LOGI(TAG, "zlib decompressor ready: tinfl=%u B, out_buf=%u B, free_heap=%u B",
(unsigned)decomp_size, (unsigned)buf_size,
(unsigned)esp_get_free_heap_size());
return ESP_OK;
}
esp_err_t ota_decomp_process(OtaDecompCtx *ctx,
const uint8_t *chunk, size_t chunk_len,
ota_decomp_write_fn write_cb, void *user_arg,
size_t *out_bytes)
{
if (!ctx || !chunk || chunk_len == 0) return ESP_ERR_INVALID_ARG;
if (out_bytes) *out_bytes = 0;
/* ---- passthrough ---- */
if (ctx->type == OtaCompress::NONE) {
if (write_cb) {
esp_err_t err = write_cb(chunk, chunk_len, user_arg);
if (err != ESP_OK) return err;
}
ctx->total_in += chunk_len;
ctx->total_out += chunk_len;
if (out_bytes) *out_bytes = chunk_len;
return ESP_OK;
}
/* ---- ZLIB decompression ---- */
if (ctx->finished) {
ESP_LOGW(TAG, "decomp already finished, ignoring %u extra bytes", (unsigned)chunk_len);
return ESP_OK;
}
const uint8_t *in_ptr = chunk;
size_t in_left = chunk_len;
/*
* 非包装模式 (TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF):
* - pOut_buf_start = pOut_buf_next = out_buf + out_write_pos
* - tinfl 将缓冲视为线性空间,写满时返回 HAS_MORE_OUTPUT
* - 我们 flush 已写数据到 Flash,然后将写指针重置为 0 继续
* - 每次 flush 后旧数据从缓冲中消失;如果后续 deflate 流
* 引用了已 flush 的数据(back-reference > buf_size),会失败
* - buf_size 越大越安全,32KB 覆盖所有情况
*/
while (in_left > 0 && !ctx->finished) {
size_t in_bytes = in_left;
size_t out_space = ctx->out_buf_size - ctx->out_write_pos;
size_t out_written = out_space;
tinfl_status status = tinfl_decompress(
(tinfl_decompressor *)ctx->tinfl,
(const mz_uint8 *)in_ptr, &in_bytes,
ctx->out_buf + ctx->out_write_pos, /* pOut_buf_start */
ctx->out_buf + ctx->out_write_pos, /* pOut_buf_next */
&out_written,
TINFL_FLAG_HAS_MORE_INPUT |
TINFL_FLAG_PARSE_ZLIB_HEADER |
TINFL_FLAG_COMPUTE_ADLER32 |
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
/* advance input cursor */
in_ptr += in_bytes;
in_left -= in_bytes;
ctx->total_in += in_bytes;
/* flush newly decompressed data to flash */
if (out_written > 0 && write_cb) {
esp_err_t err = write_cb(ctx->out_buf + ctx->out_write_pos, out_written, user_arg);
if (err != ESP_OK) return err;
}
ctx->total_out += out_written;
if (out_bytes) *out_bytes += out_written;
/* advance write pointer; reset to 0 when segment is full */
ctx->out_write_pos += out_written;
if (ctx->out_write_pos >= ctx->out_buf_size) {
ctx->out_write_pos = 0; /* flush-complete: reset for next block */
}
/* status dispatch */
if (status == TINFL_STATUS_DONE) {
ctx->finished = true;
ESP_LOGI(TAG, "decomp done: in=%u out=%u",
(unsigned)ctx->total_in, (unsigned)ctx->total_out);
} else if (status == TINFL_STATUS_HAS_MORE_OUTPUT) {
/* segment full → reset and continue */
ctx->out_write_pos = 0;
continue;
} else if (status == TINFL_STATUS_NEEDS_MORE_INPUT) {
/* consumed all input, wait for next chunk */
break;
} else {
ESP_LOGE(TAG, "tinfl failed, status=%d, in_left=%u, total_in=%u",
(int)status, (unsigned)in_left, (unsigned)ctx->total_in);
return ESP_FAIL;
}
}
return ESP_OK;
}
esp_err_t ota_decomp_finish(OtaDecompCtx *ctx)
{
if (!ctx) return ESP_ERR_INVALID_ARG;
if (ctx->type == OtaCompress::NONE) {
ESP_LOGI(TAG, "passthrough done: %u bytes", (unsigned)ctx->total_out);
return ESP_OK;
}
if (!ctx->finished) {
ESP_LOGW(TAG, "zlib stream not finished, attempting final flush...");
size_t out_space = ctx->out_buf_size - ctx->out_write_pos;
size_t out_written = out_space;
tinfl_status status = tinfl_decompress(
(tinfl_decompressor *)ctx->tinfl,
nullptr, nullptr,
ctx->out_buf + ctx->out_write_pos,
ctx->out_buf + ctx->out_write_pos,
&out_written,
TINFL_FLAG_PARSE_ZLIB_HEADER |
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
if (out_written > 0) {
ctx->total_out += out_written;
}
if (status == TINFL_STATUS_DONE) {
ctx->finished = true;
} else {
ESP_LOGE(TAG, "zlib verify failed, status=%d", (int)status);
return ESP_FAIL;
}
}
ESP_LOGI(TAG, "decomp verified: compressed=%u B -> decompressed=%u B (%.1f%%)",
(unsigned)ctx->total_in, (unsigned)ctx->total_out,
ctx->total_in > 0 ? (float)ctx->total_out * 100.0f / (float)ctx->total_in : 0.0f);
return ESP_OK;
}
void ota_decomp_free(OtaDecompCtx *ctx)
{
if (!ctx) return;
if (ctx->tinfl) {
free(ctx->tinfl);
ctx->tinfl = nullptr;
}
if (ctx->out_buf) {
free(ctx->out_buf);
ctx->out_buf = nullptr;
}
ctx->type = OtaCompress::NONE;
}