Профессиональный фреймворк корпоративного уровня для разработки прошивок на базе ESP32 https://git.xpoe.pro/xPOE-Studio/ProdFactory-ESP
  • C 67.4%
  • HTML 21%
  • TypeScript 5.2%
  • Svelte 3.9%
  • CMake 2%
  • Other 0.4%
Find a file
Oleg Perevyshin 0d98505ed6
All checks were successful
System | Сборка всех прошивок / Сборка всех устройств (push) Has been skipped
+
2026-08-11 21:36:41 +03:00
.forgejo/workflows Правка workflows 2026-08-06 12:33:15 +03:00
.framework База WL приостановлена, нужен модуль HT-CT62-HF (868МГц) 2026-08-05 22:54:13 +03:00
.vscode/extensions/poe-esp-tools Аудит README core/drivers/utilities, единый api_service (ретраи CAN и BeginResponse параметром вызывающего) 2026-08-10 11:04:12 +03:00
catalog Аудит README core/drivers/utilities, единый api_service (ретраи CAN и BeginResponse параметром вызывающего) 2026-08-10 11:04:12 +03:00
components + 2026-08-11 21:36:41 +03:00
tests Аудит README core/drivers/utilities, единый api_service (ретраи CAN и BeginResponse параметром вызывающего) 2026-08-10 11:04:12 +03:00
WebApp Аудит README всех core-компонентов против кода, ретраи CAN, пул сессий http_server 2026-08-09 15:39:52 +03:00
.clang-format Add autoformat 2026-07-09 14:27:06 +03:00
.clangd Добавляю устройство 10.00.000-00 2026-07-06 16:12:50 +03:00
.gitignore + 2026-06-22 08:14:27 +03:00
AGENTS.md poe_cbor: независимость от common.h (bool, слияние endian.h, защита SkipValue от рекурсии), CS_AddEntry напрямую без макроса, AGENTS.md, полный тест-сьют tests/, аудит README 2026-08-09 13:20:54 +03:00
CMakeLists.txt Переход на CBOR #01 2026-08-07 18:57:07 +03:00
LICENSE.md + 2026-07-08 22:01:45 +03:00
README.md Аудит README core/drivers/utilities, единый api_service (ретраи CAN и BeginResponse параметром вызывающего) 2026-08-10 11:04:12 +03:00

🚀 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).

⬆️ Наверх


📑 Содержание

  1. Назначение
  2. Архитектура
  3. Структура проекта
  4. Быстрый старт
  5. Управление проектом (VS Code)
  6. Создание нового устройства
  7. Создание нового компонента
  8. Каталог компонентов
  9. API протокол
  10. Оптимизация памяти
  11. CRC: канонические алгоритмы
  12. Примечания

🎯 Назначение

Централизованная разработка прошивок всего парка 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
  • CANpoe_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 X2mbedtls_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.svelteFUpD), которое проверяет 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