Перейти к содержимому

Шаблоны 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, heap
firmwares/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_typepower_supply или battery (при интерактиве/сценариях с батареей).
use_wifi_secretsfalse, если --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 не конфликтуют

  1. {{ ... }} — Jinja. После рендера на этом месте должна быть корректная строка/число для YAML, например name: "{{ device_name }}"name: esp-123-456.
  2. ${name} в ESPHome — это substitutions, не Jinja. Не смешивай: version: ${firmware_version} без объявления substitutions даст предупреждение; для версии из шаблона используй version: "{{ firmware_version }}" после {% set firmware_version = ... %}.
  3. !secret key переживает рендер как есть — секреты не проходят через Jinja, их читает ESPHome с диска.
  4. Многострочные 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 config
python 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.pyfirmwares-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.pyrender_jinja2_template, normalize_rendered_yaml.

Дальше: Быстрый старт, Каталог прошивок, GUI и команды в README на GitHub.