- C 67.4%
- HTML 21%
- TypeScript 5.2%
- Svelte 3.9%
- CMake 2%
- Other 0.4%
|
All checks were successful
System | Сборка всех прошивок / Сборка всех устройств (push) Has been skipped
|
||
|---|---|---|
| .forgejo/workflows | ||
| .framework | ||
| .vscode/extensions/poe-esp-tools | ||
| catalog | ||
| components | ||
| tests | ||
| WebApp | ||
| .clang-format | ||
| .clangd | ||
| .gitignore | ||
| AGENTS.md | ||
| CMakeLists.txt | ||
| LICENSE.md | ||
| README.md | ||
🚀 ProdFactory-ESP
Профессиональный фреймворк для разработки прошивок на базе ESP32 (ESP32/S2/S3/C2/C3/C6/H2/P4), совмещённый с каталогом устройств компании.
Модульная архитектура: MCU-независимые компоненты (core/drivers/utilities) подключаются конфигурацией config.yaml, без правки CMake вручную. Все внешние данные (CAN, UART-консоль, HTTP/WebSocket) проходят через единый маршрутизатор api_service.
Устройство имеет код производителя DEVICE_CODE, пример: 10.00.000-00.
Лицензия: закрытая — см. LICENSE.md в корне репозитория.
🧠 Идея фреймворка коротко
Это не библиотека кода, а конвейер производства прошивок. Один набор компонентов перечисляется в текстовом config.yaml — и собирается прошивка под конкретное устройство, без переписывания системного кода под каждый новый прибор и без ручного связывания периферии в CMake.
Всё начинается с двух обязательных компонентов
Любое устройство на фреймворке подключает api_service и config_service — без них не работает вообще ничего остальное, это фундамент, а не опциональный «сервис».
api_service— единственная точка входа и выхода для любых данных. Транспорт (UART-консоль, HTTP/WebSocket, CAN) разбирает только протокол своего канала, собирает унифицированный пакет и отдаёт его сюда.api_serviceсам находит обработчик, вызывает его и отправляет ответ обратно тем же путём. Бизнес-логика никогда не разбирает байты канала напрямую — читайте подробнее в README api_service.config_service— единственный механизм настроек для чего угодно: системные параметры устройства (включая WiFi-креды), настройки конкретного драйвера, коэффициенты алгоритма — регистрируются одинаково и бесплатно получают чтение/запись по CBOR-запросу извне, сброс к заводским значениям и сохранение в NVS (износостойкость Flash и защита от обрыва питания уже решены платформой ESP-IDF) — подробнее в README config_service.
Дальше всё строится поверх них
api_service + config_service ← обязательны в любом устройстве
│
┌───────────────────┼────────────────────┐
▼ ▼ ▼
Периферия (шины) Драйверы Утилиты
spi/i2c/uart/can/ RM3100, ICM42688… ahrs, poe_nmea…
wifi/http/…
- Периферия (
spi_service,i2c_service,uart_service,can_service,wifi_service,http_server…) даёт драйверам физический доступ к шинам и сети. Часть из неё сама является транспортом дляapi_service(can_service,system_console,http_server); собственные настройки периферии, если они есть, — тоже черезconfig_service. - Драйверы (
RM3100,ICM42688…) используют периферию, чтобы говорить с датчиком или модулем по шине — но как только речь заходит о настройке или команде извне, каждый драйвер делает это ровно так же, как всё остальное во фреймворке: регистрирует свои поля вconfig_service, получает и отдаёт их черезapi_service. - Утилиты (
ahrs,poe_nmea…) — чистые алгоритмы поверх данных от драйверов (например,ahrsобъединяет показания IMU и магнитометра в ориентацию в пространстве); их параметры настраиваются так же, черезconfig_service.
Один и тот же паттерн работает на любом уровне — от WiFi-кредов устройства до одного поля одного конкретного датчика. Добавляя новую сущность, никогда не нужно придумывать новый способ передать команду или сохранить настройку — только повторить уже существующий.
Остальное — автоматизация вокруг этого ядра
- Периферия подключается суффиксом — драйвер с несколькими интерфейсами указывается одной строкой (
RM3100_I2C,ICM42688_SPI); главныйCMakeLists.txtсам разбираетconfig.yamlи подставляет нужныйREQUIRES— редактироватьCMakeLists.txtкомпонентов вручную не нужно - Управление в одну кнопку — расширение VS Code
poe-esp-tools: выбор устройства, сохранение изменений вcatalog/, очисткаbuild/; сборка/прошивка/монитор — штатными кнопками расширения ESP-IDF - Один код — любой чип линейки ESP32 — компоненты написаны поверх переносимого ESP-IDF API;
config.yamlлишь выбираетmcuи набор компонентов, физически совместимых с этим чипом
⚡ Чем это лучше «голого» ESP-IDF (или Arduino)
ProdFactory-ESP — это слой поверх ESP-IDF: низкоуровневый доступ к периферии и сборка остаются штатными средствами ESP-IDF, фреймворк добавляет сверху организацию кода нескольких устройств, единый обмен данными с внешним миром и декларативное хранение конфигурации.
С ProdFactory-ESP |
Голый ESP-IDF / Arduino | |
|---|---|---|
| 🗂️ Новый проект | Новый каталог в catalog/ + config.yaml со списком компонентов — инфраструктура (сервис конфигов, консоль, API) уже готова |
Новый проект idf.py create-project (или новый .ino) — инфраструктуру логирования, конфигов и протокола обмена нужно писать заново |
| 🔁 Переиспользование кода | Общие core/drivers/utilities-компоненты — правка в одном месте применяется сразу ко всем устройствам |
Копирование кода драйверов между проектами или подключение сторонних библиотек с разным качеством и стилем |
| ⚙️ Подключение периферии | Суффикс в config.yaml (RM3100_I2C) — нужный bus-сервис подставляется в сборку автоматически, без ручной правки CMakeLists.txt/REQUIRES |
Ручная правка CMakeLists.txt/REQUIRES (ESP-IDF) или подбор совместимой Arduino-библиотеки под конкретный чип |
| 🧵 Обмен данными с внешним миром | Готовый api_service — транспорт разбирает только свои байты, бизнес-логика не знает, откуда пришёл пакет |
Каждый проект пишет свой протокол и диспетчеризацию команд с нуля |
| 🗄️ Хранение настроек | Единый config_service — декларативная регистрация полей, сериализация и сброс к дефолту поверх NVS |
Прямая работа с NVS API (ESP-IDF) или Preferences/EEPROM (Arduino) в каждом проекте по отдельности |
| 🖥️ Управление проектом | Кнопки статус-бара VS Code (poe-esp-tools) поверх штатного расширения ESP-IDF |
Переключение между проектами/платами вручную через CLI или настройки IDE |
| 🧩 Несколько устройств в одном репозитории | Один репозиторий, один main/, устройства — просто каталоги в catalog/, переключаются в одну команду |
Отдельный репозиторий или отдельная ветка на каждое устройство |
Итог: гибкость (любой чип линейки ESP32, любой набор периферии — вопрос конфигурации), скорость (не нужно каждый раз писать сервис конфигов и маршрутизацию заново) и простота (вся специфика конкретного устройства — это один config.yaml и один маленький main.c).
📑 Содержание
- Назначение
- Архитектура
- Структура проекта
- Быстрый старт
- Управление проектом (VS Code)
- Создание нового устройства
- Создание нового компонента
- Каталог компонентов
- API протокол
- Оптимизация памяти
- CRC: канонические алгоритмы
- Примечания
🎯 Назначение
Централизованная разработка прошивок всего парка IoT-устройств компании на ESP32.
| Особенность | Описание |
|---|---|
| 📦 Монолитная кодовая база | Все устройства в одном проекте |
| 🗂️ Каталог устройств | Все проекты хранятся в catalog/ |
| 🔄 Рабочая директория | Выбранное устройство копируется в main/ для сборки |
| 🧩 Модульные компоненты | MCU-независимые (core, drivers, utilities) |
| ⚙️ Конфигурация без кода | Набор компонентов и MCU задаются в config.yaml |
| 🔀 Единый API-хаб | api_service маршрутизирует все внешние пакеты (CAN/UART/HTTP/WebSocket) |
| 🧵📶🔗🚌 Общие шины | spi_service/wifi_service/i2c_service/can_service/uart_service — один драйвер для всех устройств |
| 🔢 Единый формат данных | Только CBOR (RFC 8949) через poe_cbor, без поля формата в протоколе |
Точка входа: catalog/{DEVICE_CODE}/ (пример: 10.00.000-00) → копируется в main/ при выборе устройства.
🏗️ Архитектура
┌──────────────────────────────────────────────────────────────────────────┐
│ ProdFactory-ESP Framework │
├──────────────────────────────────────────────────────────────────────────┤
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Business Logic Layer │ │
│ │ main.c + код конкретного устройства │ │
│ └─────────────────────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ API Service (единый маршрутизатор) │ │
│ │ UART-консоль │ HTTP/WebSocket │ CAN (poe_cbor_pkt) │ Другие IFace │ │
│ └─────────────────────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Core Services │ │
│ │ Config │ File System │ WiFi │ SPI/I2C/UART/CAN │ System Console │ │
│ └─────────────────────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Drivers & Utilities │ │
│ │ RM3100 │ ICM42688 │ ICP20100 │ SGP41 │ TAU951M │ MAVLink │ … │ │
│ └─────────────────────────────────┬──────────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Hardware Abstraction — ESP-IDF + периферия │ │
│ └────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────┘
Это тот же фундамент, что и в разделе Идея фреймворка коротко выше, только в разрезе рантайма: can_service/system_console/http_server разбирают свой транспортный протокол и передают готовый api_packet_t в api_service, ответ уходит обратно через колбэк, зарегистрированный API_Service_RegIFace. Бизнес-логика (main.c) не знает, откуда физически пришла команда.
📁 Структура проекта
📁 ProdFactory-ESP/
│
├─ 📁 .framework/ # Служебные файлы фреймворка
│ ├─ 📁 partitions/ # Таблицы разделов Flash (файловая система имеет такой же размер что и app0, app1 для поддержки OTA)
│ ├─ 📄 fw_includes.h # Автоподключение заголовков всех компонентов (__has_include)
│ ├─ 📄 sdkconfig.defaults # Глобальные настройки сборки ESP-IDF
│ └─ 📄 Dictionary.yaml # Словарь для импорта в SYP-DevTerminal (совпадает с ProdFactory-STM), сборкой прошивок не используется
├─ 📁 WebApp/ # Svelte приложение для серверной части ESP
│
├─ 📁 catalog/ # Хранилище всех устройств компании
│ ├─ 📁 00.00.000-00/ # Базовое устройство (шаблон)
│ │ ├─ 📄 config.yaml # MCU, зависимости, core/drivers/utilities
│ │ ├─ 📄 main.c / main.h # Точка входа и агрегирующий заголовок
│ │ ├─ 📄 sdkconfig.defaults # Настройки конкретного устройства (таблица разделов и т.п.)
│ │ └─ 📚 ... # Остальная бизнес-логика устройства
│ └─ 📁 {DEVICE_CODE}/ # Очередное устройство
│
├─ 📁 components/ # Библиотека компонентов (MCU-независимые)
│ ├─ 📁 core/ # api_service, can_service, config_service, poe_cbor, file_system...
│ ├─ 📁 drivers/ # BMP180, ICM42688, ICP20100, MCP2518FD, RM3100...
│ └─ 📁 utilities/ # ahrs, poe_nmea...
│
├─ 📁 main/ # Рабочая директория (копия из catalog/{DEVICE_CODE})
│
├─ 📁 tests/ # Директория для тестов фреймворка
├─ 📜 CMakeLists.txt # Главный файл сборки — парсит main/config.yaml
├─ 🔒 LICENSE.md # Лицензионное соглашение
└─ 📝 README.md # Этот файл
Примечание: список компонентов выше — снимок на момент написания, актуальный полный список — в разделе Каталог компонентов (там же ссылки на README каждого).
🏁 Быстрый старт
В системе уже должны быть установлены VS Code, ESP-IDF (через штатный установщик Espressif) и расширение ESP-IDF для VS Code.
1️⃣ Клонирование репозитория
git clone <repo-url> ProdFactory-ESP
cd ProdFactory-ESP
2️⃣ Установка расширения фреймворка
В .vscode/extensions/poe-esp-tools/ — собственное расширение выбора/сохранения устройства (см. README расширения).
3️⃣ Выбор устройства и сборка
Используйте кнопки статус-бара VS Code (см. раздел ниже) или встроенные команды расширения ESP-IDF для сборки/прошивки/монитора.
🎮 Управление проектом (VS Code)
Кнопки в статус-баре (poe-esp-tools):
| Кнопка | Команда | Действие |
|---|---|---|
| 🔹 Select Device | poe-esp.selectDevice |
Выбор устройства из catalog/ → копирование в main/ |
| 💾 Save | poe-esp.saveProject |
Сохранение main/ обратно в catalog/{DEVICE_CODE} |
| 🗑 Clear | poe-esp.clearProject |
Очистка build/ |
Сборка/прошивка/монитор — стандартными кнопками расширения ESP-IDF. Выбранное устройство хранится в .project_state (создаётся автоматически).
Рабочий процесс:
Select Device → Build (ESP-IDF) → Flash & Monitor → Save (при изменениях)
🆕 Создание нового устройства
1️⃣ Создайте каталог устройства
mkdir catalog/12.34.567-89
2️⃣ Создайте config.yaml
# catalog/12.34.567-89/config.yaml
device_code: 12.34.567-89
name: "ESP32 Базовый модуль"
description: "Простейшая плата"
dependencies:
mcu: esp32
max_ext_modules: 1
poe_cbor_upload_chunk_buf_size: 1536
core:
- api_service
- common
- config_service
- file_system
- http_client
- http_server
- main_config
- poe_cbor
- status_led
- system_console
- upgrade_service
- wifi_service
drivers:
utilities:
⚠️ Все поля из примера выше обязательны — при конфигурации CMake останавливается с перечнем
отсутствующих полей, если хоть одно не заполнено (даже dependencies:/drivers: без единого элемента
— сам ключ обязан быть в файле).
Драйверы с несколькими интерфейсами (SPI/I2C/UART) указываются с суффиксом (RM3100_I2C, ICM42688_SPI) — реальная папка компонента без суффикса, суффикс превращается в define и подставляет нужный core-сервис в REQUIRES автоматически (см. корневой CMakeLists.txt).
3️⃣ Создайте main.c/main.h
/* catalog/12.34.567-89/main.c */
#include "main.h"
#include "api_handlers.h"
/* ********************************************************** */
/* API Модуля */
extern const api_entry_t api_device[];
/* ********************************************************** */
/* Главные параметры продукта */
static const device_meta_data_t device_meta_data = {
.cat_dev_id = "12.34.567-89",
.dev_fw = 1,
.model_description = "POE-Device for IoT",
.server_domain = "devcloud.xpoe.pro",
.manufacturer = "DevCloud",
.manufacturer_url = "https://devcloud.xpoe.pro/",
.device_url = "https://devcloud.xpoe.pro/products/12.34.567-89",
.system_user = "root", /* 15 символов + \0 */
.system_password = "password", /* 15 символов + \0 */
.server_certificate_path = "/storage/Certificate.pem",
.backup_config_path = "/storage/BackupConfig.json",
};
/* ********************************************************** */
/* Задача бизнес-логики */
static void Task_APP(void* arg) {
(void) arg;
while(1) { vTaskDelay(pdMS_TO_TICKS(5000)); }
}
/* ********************************************************** */
/* Задача инициализации (самоуничтожающаяся) */
static void Task_InitApp(void* pvParameters) {
(void) pvParameters;
if(DefaultEventLoop_Init() != ESP_OK) goto fail; /* Цикл стандартных событий */
if(SC_Init(115200) != ESP_OK) goto fail; /* Системная консоль */
if(API_Service_Init(api_device, 3, 6144, 6144, 4096, configMAX_PRIORITIES - 4) != ESP_OK) goto fail; /* API сервис */
if(API_Service_RegIFace(IFACE_UART0_SC, SC_Tx_API_Package) != ESP_OK) goto fail; /* Регистрация интерфейса UART0 */
if(CS_Init(&device_meta_data) != ESP_OK) goto fail; /* Сервис конфигураций */
if(Main_Config_Init() != ESP_OK) goto fail; /* Основной конфиг */
if(FS_Init() != ESP_OK) goto fail; /* Встроенная файловая система */
if(WiFi_Service_Init(6144, 15) != ESP_OK) goto fail; /* WiFi */
if(HTTP_Client_Init(6144, 14, NULL) != ESP_OK) goto fail; /* HTTP-клиент */
if(Upgrade_Service_Init() != ESP_OK) goto fail; /* Обновление прошивки */
if(HTTP_Server_Init(2048, 12) != ESP_OK) goto fail; /* HTTP-сервер */
/* Инициализация периферии и драйверов устройства... */
/* ... */
/* Создаем задачу App */
if(xTaskCreate(Task_APP, "APP", 2048, NULL, 5, NULL) != pdPASS) {
SC_LOG_E("App Task Init", FAILED_TASK_CREATE);
goto fail;
}
SC_LOG_I("App Started", "");
/* Уничтожаем задачу инициализации */
vTaskDelete(NULL);
return;
fail:
while(1) {
vTaskDelay(pdMS_TO_TICKS(30000));
SC_LOG_FE("Init", "Restarting...");
esp_restart();
}
}
/* Точка входа */
void app_main(void) {
vTaskDelay(pdMS_TO_TICKS(2500));
xTaskCreate(Task_InitApp, "InitApp", 20480, NULL, 10, NULL);
vTaskDelete(NULL);
}
/* catalog/12.34.567-89/main.h */
/* Системные библиотеки */
#include <freertos/FreeRTOS.h>
#include <stdint.h>
/* Файлы бизнес-логики */
#include "api.h"
#include "fw_includes.h"
4️⃣ Задайте настройки SDK (sdkconfig.defaults)
# catalog/12.34.567-89/sdkconfig.defaults
#
CONFIG_IDF_TARGET="esp32"
# Таблица разделов флеш памяти
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=".framework/partitions/F4R0.csv"
CONFIG_PARTITION_TABLE_FILENAME=".framework/partitions/F4R0.csv"
CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
# TLS-буферы выделяются на время операции и освобождаются сразу после
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
# Освобождать разобранные сертификат/ключ/DHM после хендшейка (держим их только на момент установки сессии)
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT=y
5️⃣ Выберите устройство в VS Code и соберите
🧩 Создание нового компонента
Для драйверов начните с копирования components/drivers/Template/ — это предписывающий эталон (нейминг {Name}_Init/CB_ResetConfig/CB_GetConfig/CB_OnConfigUpdate, паттерн config_pending, чек-лист создания драйвера) и уже готовая структура файлов. Для core/utilities — по образцу существующих компонентов той же категории.
1️⃣ Создайте каталог компонента
mkdir components/{core|drivers|utilities}/component_name
2️⃣ CMakeLists.txt
idf_component_register(
SRCS "component_name.c"
INCLUDE_DIRS "."
REQUIRES config_service # другие core-сервисы, от которых зависит компонент
PRIV_REQUIRES common system_console
)
3️⃣ Заголовок и реализация
/* components/{category}/component_name/component_name.h */
#pragma once
#include <esp_err.h>
esp_err_t ComponentName_Init(/* ... */);
4️⃣ Подключите в config.yaml устройства
core: # или drivers / utilities
- component_name
5️⃣ Добавьте README.md компонента
По образцу существующих (см. Каталог компонентов) — обзор, зависимости, API, конфигурация, особенности/нюансы (обязательно, включая известные ограничения).
📚 Каталог компонентов
Полный API, конфигурация через config_service и нюансы каждого компонента — в его собственном README, здесь только карта.
🧠 Ядро (core)
| Компонент | Описание |
|---|---|
| 🔀 api_service | Единый маршрутизатор API-пакетов (CBOR), диспетчеризация по интерфейсам |
| 🚌 can_service | CAN/CAN FD с фрагментацией, приём разбирается напрямую в API Service |
| 🧰 common | Битовые маски интерфейсов, строковые константы ошибок |
| 🗄️ config_service | Группы конфигурации в NVS, CBOR-получение/установка |
| 🗃️ file_system | LittleFS на разделе storage |
| 🔌 gpio_service | Управляемые GPIO-выходы + цифровые/аналоговые входы общего назначения |
| 🌐 http_client | HTTP-клиент + WebSocket-клиент, асинхронная загрузка прошивки |
| 🌐 http_server | HTTP/WebSocket-сервер, Captive Portal, mDNS, DNS, UPnP |
| 🔗 i2c_service | I2C шины, регистрация устройств |
| 🛠️ main_config | Системная конфигурация устройства |
| 🔢 poe_cbor | CBOR (RFC 8949) + пакетный протокол шинных интерфейсов |
| 🧵 spi_service | SPI шины, регистрация устройств |
| 🚦 status_led | Индикация состояния устройства |
| 🖥️ system_console | UART-консоль: свой протокол с CRC8, уровни логирования |
| 📟 uart_service | UART шины общего назначения |
| 📶 wifi_service | WiFi (STA/AP/APSTA), SNTP, сканирование сетей |
🔌 Драйверы (drivers)
| Компонент | Интерфейс | Описание |
|---|---|---|
| 🌡️ BMP180 | I2C | Датчик давления/температуры |
| 🌀 ICM42688 | SPI/I2C | IMU (акселерометр + гироскоп) |
| 🌡️ ICP20100 | I2C | Барометр |
| ✈️ MAVLink | UART | Протокол MAVLink v2 (диалект ArduPilotMega) — приём и передача |
| 🚌 MCP2518FD | SPI | Внешний контроллер CAN FD (backend для can_service) |
| 🧭 RM3100 | SPI/I2C | Геомагнитный датчик (магнитометр) |
| 💨 SGP41 | I2C | Датчик VOC/NOx |
| 🛰️ TAU951M | UART | GNSS-модуль ALLYSTAR TAU951M-P2 (позиция из NMEA, конфигурация — бинарный протокол) |
| 💾 W25N01 | SPI | NAND Flash (Winbond W25N01GV) |
| 🌈 RGB_Strip | RMT/SPI | Адресные светодиодные ленты (WS2812 и т.п.) |
🧮 Утилиты (utilities)
| Компонент | Описание |
|---|---|
| 🧮 ahrs | Определение пространственного положения (несколько алгоритмов слияния) |
| 🛰️ poe_nmea | Framing/чексумма/разбор полей NMEA-0183 (вендоронезависимо) |
🧩 API протокол
Компоненты-транспорты (system_console, http_server, can_service) сами разбирают свой канал и передают готовый api_packet_t в api_service — см. Архитектуру. Формат конкретного канала — в README этого компонента:
- UART-консоль — байтовый кадр
[SOH]HEADER[US]ARGUMENT[STX]VALUE[ETX]CRC8[US]FHS[EOT],VALUE— сырые CBOR-байты, см. README system_console - CAN —
poe_cbor_pkt:{ TrgBusID: [RetBusID, HA_Code, Payload] }, см. README can_service и README poe_cbor - HTTP/WebSocket — только бинарные CBOR-кадры в теле запроса, см. README http_server
Протокол не несёт отдельного поля формата — весь фреймворк работает только с CBOR (poe_cbor), на всех интерфейсах.
Пример команды (CBOR, показан в диагностической нотации RFC 8949 §8):
{"Payload":{"CfgGroup":"CFG"}}
Словарь заголовков (H_GET/H_SET/H_OK/H_ER) и аргументов (A_*) — общий для ESP и STM, см. components/core/api_service/api_ha.h (побайтово одинаков в обоих фреймворках). .framework/Dictionary.yaml для сборки прошивок не используется — тот же словарь в YAML для импорта в SYP-DevTerminal.
🗃️ Оптимизация памяти
Статические буферы вместо стека
/* ✅ Правильно — статический буфер для больших данных */
static uint8_t s_buffer[8192];
void task(void* arg) {
cbor_enc_ctx_t enc;
CBOR_EncoderInit(&enc, s_buffer, sizeof(s_buffer));
}
/* ❌ Неправильно — буфер на стеке */
void task(void* arg) {
uint8_t buffer[8192]; /* 8KB на стеке задачи! */
}
Рекомендованные размеры стеков задач
| Компонент | Стек |
|---|---|
| API Service | ≥ 6144 |
| WiFi Service | ≥ 5120 |
| HTTP Server | ≥ 12288 |
| HTTP Client | ≥ 8192 |
| App Task | ≥ 4096 |
Точные значения для конкретного компонента — см. сигнатуру его _Init в README.
max_ext_modules — размер списка модулей на шине под конкретное устройство
ModList (common.h, mod_list_t ModList[MAX_EXT_MODULES] внутри main_cfg_t) — список модулей,
обнаруженных на шине через ModFind. Поле max_ext_modules в config.yaml задаёт размер этого
массива per-device (default в common.h — 16, поле обязательно, сборка остановится без него):
max_ext_modules: 1 # устройство не отслеживает другие модули на шине
max_ext_modules: 16 # хаб, которому нужно видеть много модулей одновременно
POE_CBOR_UPLOAD_CHUNK_BUF_SIZE — размер чанка загрузки файлов
Константа используется только file_system/upload_file.c — запас под конверт запроса UploadProcess
(заголовки + CurrentID/ChunkIndex + сам Data, нативная CBOR byte string, без base64), размер
чанка вычисляется от неё, а не задан независимой константой:
#define CHUNK_SIZE (POE_CBOR_UPLOAD_CHUNK_BUF_SIZE - 256)
Data пишется в файл напрямую из буфера запроса (CBOR_DecodeBytes), без промежуточного
декодирования и без отдельного буфера под чанк.
Размер обязателен в config.yaml каждого устройства (poe_cbor.h держит #ifndef-дефолт 4096Б
только как страховку):
poe_cbor_upload_chunk_buf_size: 4096 # 1536 на 00.00.000-00 — устройство без PSRAM, см. ниже
Уменьшать poe_cbor_upload_chunk_buf_size на устройстве с upgrade_service/загрузкой файлов можно свободно,
чанк просто станет меньше (и закачка медленнее). _Static_assert(CHUNK_SIZE >= 128, ...) в
upload_file.c защищает от совсем непригодного значения.
%lld/%llu не используются нигде во фреймворке
CFG_TYPE_I64/CFG_TYPE_U64 убраны из config_service целиком (оба фреймворка) — ни одно устройство
в catalog/ их не регистрировало, 64-битные значения в конфигурации не нужны, т.к. nano-формат
printf/scanf (STM32: --specs=nano.specs) не поддерживает 64-битные целочисленные форматы
вообще, ни при каком линкер-флаге (в отличие от float, который восстанавливается через
-u _printf_float) — так исторически собран сам newlib-nano.
Почему nano-формат здесь не включён (ESP-IDF)
На STM32 фреймворк линкует --specs=nano.specs (newlib-nano) для экономии RAM/Flash. У классического
newlib в ESP-IDF есть аналог — CONFIG_LIBC_NEWLIB_NANO_FORMAT — но эта версия ESP-IDF по умолчанию
собирает проект на Picolibc (CONFIG_LIBC_PICOLIBC=y), не на newlib — опция зависит от
LIBC_NEWLIB и просто неактивна (disabled dependency), собственного аналога для Picolibc в Kconfig
нет.
TLS: CONFIG_MBEDTLS_CERTIFICATE_BUNDLE + динамические буферы
Все 4 ESP-устройства держат CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y — полный набор корневых сертификатов
вместо одного зашитого ISRG_ROOT_X1_PEM. Один захардкоженный корень ломается при ротации CA на
сервере (Let's Encrypt в 2026м перешёл на цепочку из 4 ECDSA-сертификатов с кросс-подписью через
ISRG Root X2 — mbedtls_ssl_handshake падал с -0x2700/MBEDTLS_ERR_X509_CERT_VERIFY_FAILED), бандл
устойчив к такой смене. Стоимость на 00.00.000-00 (без PSRAM): +70КБ flash, +1КБ DRAM — по флешу
запас всегда проверять по факту (idf.py size), но обычно не критично.
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y — TLS TX/RX буферы выделяются на время активной операции и
освобождаются сразу после, а не держатся все 16+4КБ на весь срок жизни сессии. Критично, когда
одновременно активны две TLS-сессии (постоянный WS + разовый HTTPS для метаданных/прошивки при
апгрейде) — на 00.00.000-00 реально ловился esp-x509-crt-bundle: PSA signature verification failed ... PSA_ERROR_INSUFFICIENT_MEMORY (-141) при проверке ECDSA-подписи именно из-за одновременного
резервирования буферов двух TLS-сессий.
⚠️ CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN уменьшать нельзя — держит 16384 (дефолт ESP-IDF). Полная
цепочка сертификатов devcloud.xpoe.pro укладывается в ~3.5КБ DER, но входящий буфер ограничивает
максимальный размер ОДНОЙ TLS-записи целиком, а не только рукопожатие — при реальной закачке файла
(прошивка, мегабайты) сервер шлёт полноразмерные записи вплоть до 16КБ, и без согласования Max Fragment
Length клиент не может заставить сервер резать их мельче. Уменьшенный до 4096 буфер ловил mbedtls_ssl_read
read error :-0x0087 на первой же более крупной записи — закачка обрывалась почти сразу после старта.
DYNAMIC_BUFFER всё равно даёт реальную экономию (буфер существует только на время чтения), даже с
полным 16384-байтным размером.
PSRAM есть не на всех устройствах — DRAM бюджет проверять по факту, не по чипу
20.00.000-01/10.00.000-00/50.00.000-00 (ESP32-S3) собраны с PSRAM (CONFIG_SPIRAM=y в
sdkconfig.defaults устройства) — там DRAM запас большой. 00.00.000-00 (классический ESP32, флеш
без PSRAM, партиция F4R0) PSRAM не имеет вообще — на нём DRAM реально ограничена (~50% занято уже
на базовой сборке с WiFi). Перед тем как считать RAM некритичной для конкретного устройства — проверить
sdkconfig.defaults этого устройства на CONFIG_SPIRAM=y, а не полагаться на то, что у ESP32 "всегда
есть PSRAM".
Крупные статические буферы — в PSRAM, если она есть
config_service_nvs.c (s_compare_buf) размечен EXT_RAM_BSS_ATTR — размещается во внешней PSRAM
на устройствах, где она включена (CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY), и прозрачно
деградирует до обычной DRAM там, где PSRAM нет (атрибут превращается в no-op, сборка не ломается)
— на 00.00.000-00 этот буфер реально занимает внутреннюю DRAM. Этот же приём (EXT_RAM_BSS_ATTR)
стоит использовать для любых новых крупных (от ~1КБ) статических буферов, не требующих DMA-доступа —
но для устройств без PSRAM единственный реальный рычаг экономии — уменьшать сам буфер (см.
poe_cbor_upload_chunk_buf_size выше).
🧮 CRC: канонические алгоритмы
Единый набор для всей экосистемы (ProdFactory-STM, ProdFactory-ESP, SYP-DevCloud, SYP-DevTerminal).
ROM-функция esp_crc32_le на ESP32 не ведёт себя как линейный аккумулятор при ненулевом стартовом
значении — расходится с собственной документацией ESP-IDF. Поэтому CRC нигде в стеке не считается
через ROM/SDK-функции — только через собственные таблицы, реализованные и проверенные в каждом
проекте независимо.
Все три алгоритма — стандартные именованные варианты (каталог reveng CRC Catalogue), не собственное изобретение — их параметры проверяемы любым внешним инструментом.
| Ширина | Алгоритм | poly | init | refin/refout | xorout | check("123456789") | Где |
|---|---|---|---|---|---|---|---|
| 8 бит | CRC-8/ROHC |
0x07 |
0xFF |
true/true | 0x00 |
0xD0 |
Framing консоли (ESP/STM system_console, Terminal poe_serial.rs) |
| 16 бит | CRC-16/KERMIT |
0x1021 |
0x0000 |
true/true | 0x0000 |
0x2189 |
Серийник/DevBusID, поблочная проверка при OTA по шине |
| 32 бит | CRC-32/ISO-HDLC |
0x04C11DB7 |
0xFFFFFFFF |
true/true | 0xFFFFFFFF |
0xCBF43926 |
Целостность файла прошивки (Cloud-метаданные, скачивание, загрузчик STM) |
Функции в этом проекте (components/core/common/common.c/common.h):
uint8_t crc8_rohc(uint8_t crc, const uint8_t* buf, size_t len); /* init=0xFF на первый вызов */
uint16_t crc16_kermit(uint16_t crc, const uint8_t* buf, size_t len); /* init=0x0000 на первый вызов */
uint32_t crc32_iso_hdlc_file(const char* file_path); /* init/xorout внутри, файл целиком */
crc8_rohc/crc16_kermit — chainable: crc — это init на первый вызов или результат предыдущего
вызова при многосегментном расчёте (например system_console считает CRC8 по трём кускам кадра подряд).
Обе таблицы сгенерированы из отражённого полинома (0xE0 и 0x8408 соответственно) и проверены на
check-значении из таблицы выше — при любых изменениях в файле сверяться с этим значением, а не
полагаться на совпадение по названию.
⚠️ Не путать с общей стилевой унификацией "любой CRC = один из трёх канонических". Специфичные CRC,
завязанные на протокол/чип, где выбора алгоритма нет — не трогать: MT6835/SGP41 (алгоритм диктует
даташит производителя), CAN FD (аппаратный CRC внутри контроллера MCP2518FD/стандарт ISO 11898-1).
WebApp/src/lib/Utils/Common.ts::crc32() использует тот же стандартный CRC-32/ISO-HDLC — обязательное
условие для загрузки файла на устройство через WebSocket (GUIPreview.svelte → FUpD), которое
проверяет CRC32 через upload_file.c.
📌 Примечания
- Компоненты MCU-независимы по возможности — общая логика в
core/utilities, аппаратно-зависимый код изолирован вdrivers/board-специфичных файлах - Каждый компонент документирует нюансы и известные ограничения в своём README (обязательное требование при добавлении/изменении компонента)
ProdFactory-STM— аналогичный фреймворк для STM32, многие компоненты (can_service,poe_cbor,ahrs,RM3100,ICM42688,SGP41...) реализованы зеркально по API и идеологии
📧 Контакты
© 2026 Олег Перевышин E-Mail: oleg.perevyshin@gmail.com