Шаблоны Jinja2 для ESPHome
Шаблоны Jinja2 для ESPHome
Краткий рабочий гайд под цепочку репозиторий esphomeFlasher → scripts/flasher.py → ESPHome. Базовые блоки обычного YAML без Jinja — в Структура обычного конфига ESPHome. Дальше — только Jinja и flasher; полная документация Jinja — jinja.palletsprojects.com.
Зачем .yaml.j2
| Подход | Когда уместен |
|---|---|
Обычный .yaml | Одна плата, фиксированные пины, без параметров. |
.yaml.j2 + flasher | Несколько плат, серийники, Wi‑Fi/Zigbee-флаги, OTA-версия, вынесенные блоки в _includes/. |
Рендер даёт валидный ESPHome YAML (часто файл с точкой в имени, например .device_esp-…__yaml-….yaml). Его компилирует ESPHome; в репозитории обычно хранят только шаблон, а не каждый сгенерированный артефакт.
Каркас репозитория прошивки
Рекомендуемая схема (как у espHome_HumidifierSystem):
firmwares/ci-overrides/<project>/ # канон в git espDevice.yaml.j2 # точка входа: {% include %} блоков _includes/ runtime.yaml.j2 # esphome, wifi, api, update, chip logic.yaml.j2 # сенсоры, реле, скрипты (бизнес) maintenance.yaml.j2 # OTA:, NET:, SETUP:, globals, button diagnostics.yaml.j2 # uptime, RSSI, heapfirmwares/ci-overrides/_shared/ # общие фрагменты для нескольких прошивокПосле правки ci-overrides выполните python scripts/apply_ci_overrides.py, затем рендер из firmwares-external/<repo>/.
Разделение: сеть и OTA — в runtime; поведение устройства — в logic; сервисные сущности — в maintenance. В итоговом YAML не должно быть двух корневых interval: / globals: — только один блок каждого типа (остальное через {% include %} фрагментов).
Что подставляет flasher.py
Загрузчик Jinja2 (scripts/flasher.py): каталог файла шаблона, плюс firmwares/ci-overrides/_shared, плюс firmwares-external/_shared (после apply_ci_overrides.py).
{% include "_includes/foo.yaml.j2" %} — относительно папки основного .j2; {% include "labels_common.yaml.j2" %} — из _shared.
Переменные в template.render(...) (всегда или почти всегда)
| Переменная | Смысл |
|---|---|
serial_number | Имя узла ESPHome и идентификатор устройства в цепочке flasher. |
power_type | power_supply или battery (при интерактиве/сценариях с батареей). |
use_wifi_secrets | false, если --wifi-provisioning (без STA из secrets). |
yaml_build_version | Метка сборки YAML (шапка и суффикс имени файла). |
zigbee_enabled, zigbee_router, zigbee_as_generic | Из env FLASHER_* (см. ниже). |
friendly_name_override, zigbee_vendor_name, zigbee_product_prefix | Из env, для HA/Zigbee без правки шаблона. |
Если задан FIRMWARE_VERSION_OVERRIDE, в контекст попадает build_version — в шаблоне обычно делают {% set firmware_version = build_version | default("…") %}.
Пины и плата (если передан --board-profile)
Читается firmwares/boards/<profile>.yaml; в Jinja попадают, среди прочего: board, chip_family, framework_type, status_led_pin, пины увлажнителя/реле и т.д. (см. render_jinja2_template в scripts/flasher.py).
Реле и батарея
При соответствующем сценарии flasher добавляет relay_* или battery_* — имеет смысл только если шаблон эти ключи реально использует.
Переменные окружения (CLI / CI)
| Env | Эффект |
|---|---|
FLASHER_ENABLE_ZIGBEE=1 | Включает Zigbee-блок в шаблоне (ESP32-C6/H2 + zigbee_esphome). |
FLASHER_ZIGBEE_ROUTER, FLASHER_ZIGBEE_AS_GENERIC | Доп. флаги Zigbee. |
FLASHER_FRIENDLY_NAME, FLASHER_ZIGBEE_VENDOR, FLASHER_ZIGBEE_PRODUCT | Строки для HA / Zigbee Basic. |
FLASHER_ESPHOME_CMD | Явная команда ESPHome (например py -3.13 -m esphome). |
FIRMWARE_VERSION_OVERRIDE | Подставляется как build_version (версия прошивки в шаблоне). |
ESPHOME_YAML_BUILD_VERSION | Метка артефакта YAML, если не задан --yaml-version. |
Правила разметки: Jinja и ESPHome не конфликтуют
{{ ... }}— Jinja. После рендера на этом месте должна быть корректная строка/число для YAML, напримерname: "{{ device_name }}"→name: esp-123-456.${name}в ESPHome — это substitutions, не Jinja. Не смешивай:version: ${firmware_version}без объявленияsubstitutionsдаст предупреждение; для версии из шаблона используйversion: "{{ firmware_version }}"после{% set firmware_version = ... %}.!secret keyпереживает рендер как есть — секреты не проходят через Jinja, их читает ESPHome с диска.- Многострочные lambda в
|-блоках: внутри C++ не оставляй сырой{{без экранирования, если это не должно обрабатываться Jinja (редкий случай — чаще lambda статичен, а динамика — черезid(...)).
Паттерны в .j2
Значения по умолчанию и один источник правды для версии:
{% set firmware_version = build_version | default("0.1.0") %}{% set device_name = serial_number | default("my-device") %}Условные блоки целиком (Zigbee, батарея):
{% if zigbee_enabled | default(false) %}zigbee: id: zb ...{% endif %}Include:
{% include "_includes/runtime.yaml.j2" %}Пины с платы:
pin: {{ relay_pump_pin | default("GPIO7") }}Команды flasher для шаблонов
Из корня esphomeFlasher:
# Только сгенерировать и проверить esphome configpython scripts/flasher.py -f path/to/device.yaml.j2 -a yaml --local --board-profile esp32c6-supermini -y
# Сборка + прошивкаpython scripts/flasher.py -f path/to/device.yaml.j2 -a run --port COM5 --board-profile esp32c6-superminiПолезные флаги:
--board-profile— профиль изfirmwares/boards/*.yaml.--wifi-provisioning— первичная настройка Wi‑Fi без SSID/пароля из secrets в прошивке.--yaml-version VER— явная метка в имени сгенерированного файла.--clean-rendered-yaml— удалить старые сгенерированные YAML вfirmwares/иfirmwares-external/.
secrets.yaml для внешних репо flasher при рендере копирует из firmwares/secrets.yaml рядом с артефактом (см. код flasher) — удобно для локального esphome config.
OTA и project.version
- В шаблоне задай
esphome.project.versionиз той же строки, что публикуется вmanifest.jsonна OTA-сервере. - Обновление на устройстве: компонент
update.http_requestи URL вида…/firmware/<owner>.<project>.<hw>/<serial>/manifest.json(см._shared/runtime_network_ota.yaml.j2). - После рендера flasher нормализует артефакт (
);#→ перенос строки), чтобы lambda в YAML не сливалась с комментарием следующегоinclude.
Общая схема и пути скриптов — Обзор OTA; чеклист публикации — GitHub OTA — быстрый старт.
Типичные ошибки
| Симптом | Причина |
|---|---|
firmware_version is undefined и ${firmware_version} | Перепутаны substitutions и Jinja; нужна {% set %} + {{ firmware_version }} или литерал после рендера. |
| Сборка C++ с «голым» токеном / строкой | Подстановка секрета в globals.initial_value через ${...} — опасно; для заголовков HTTP предпочитай !secret или отказ от лишнего auth для публичного JSON. |
| HA собирает не то устройство | В YAML и имя файла в ESPHome Dashboard должно совпадать esphome.name с целевым железом. |
include не находит файл | Путь относительно папки основного .j2, не корня монорепо. |
Ориентиры в этом проекте
- Канон шаблонов:
firmwares/ci-overrides/→ копия:python scripts/apply_ci_overrides.py→firmwares-external/. - Увлажнитель:
mihazzzold.espHome_HumidifierSystem/(runtime,logic,maintenance,diagnostics). - Шторы:
mihazzzold.espHome_SmartCurtains/(curtain_logic,curtain_entities). - Станция:
mihazzzold.espHome-SmartStation/(station_logic,station_entities). - Пример в монорепо:
firmwares/example/example.yaml.j2(только платформа OTA). - Профили пинов:
firmwares/boards/*.yaml. - Рендер:
scripts/flasher.py→render_jinja2_template,normalize_rendered_yaml.
Дальше: Быстрый старт, Каталог прошивок, GUI и команды в README на GitHub.