Перейти к содержанию

Меню как протокол: menu.yaml ↔ mqtt_contract.yaml ↔ портал


Три файла — три роли

Файл Владелец Что описывает
src/menu/menu.yaml ваш продукт меню устройства: параметры, действия, структура
contracts/mqtt_contract.yaml idryer-core список известных смыслов: canonical_roles с подписями на нескольких языках
frontend-v2/src/contracts/mqtt-api.types.ts генерируется TypeScript-типы для портала

role: — семантическое имя пункта меню. Прошивка говорит «у меня есть iheater.heat_temp», а не «у меня пункт номер 35». Внутренние имена прошивки могут меняться, role: остаётся постоянным.

Меню — зеркало настроек прибора. Прошивка публикует все пункты menu.yaml — с role: и без. Портал рисует каждый пункт по его типу: значение, переключатель, действие, подменю. role: даёт пункту подпись из контракта на языке пользователя; без role: портал показывает название, которое прислало устройство.

Карточку устройства меню не строит. Что показывает карточка и какие операции запускает, описывает card-манифест — см. Карточка устройства: card-манифест. Действие карточки может взять пределы и значение по умолчанию своего параметра из пункта меню.


1. Сборка прошивки (pio run)

menu.yaml → menu_gen.py сверяет каждый role: с canonical_roles в контракте → если роль неизвестна, сборка падает с ошибкой и списком допустимых ролей → генератор пишет C++-файлы в src/menu/.

2. Обновление TypeScript для портала (regen.sh)

mqtt_contract.yaml → gen_ts_types.py генерирует mqtt-api.types.ts и подписи ролей roles.{lang}.json → файлы копируются в портал.

Запускается при изменении контракта. Результат коммитится.

3. Runtime: устройство ↔ портал

Прошивка публикует меню в топик config (это делает код продукта по команде get_config; продукты iDryer — ещё и при выходе в онлайн) → бэкенд портала сохраняет его → портал получает меню через GET /devices/:id/menu-config → каждый пункт рисуется по типу t (val, tog, act, sub), подпись — canonical_roles[r].labels[lang], затем английская, затем название n от устройства.

Параметры (min, max, val) приходят из самого пункта меню — текущие значения знает прошивка.

Портал меняет значение командой commands/set { "id": <id>, "val": <value> }.


Как добавить настройку (параметр в NVS)

- id: my_param
  type: value
  role: my.param        # необязательно: подпись из контракта
  title: { ru: "ПАРАМЕТР", en: "PARAM" }
  unit: { ru: "°C", en: "°C" }
  vtype: uint16
  min: 0
  max: 100
  step: 1
  bind: my_param        # ключ NVS (≤ 15 символов)
  persist: true
  scope: global
  default: 50

bind = ключ NVS. persist: true = значение переживает перезагрузку.

role: — не произвольное поле: значение должно быть из canonical_roles в контракте, иначе сборка упадёт. Список — в contracts/mqtt_contract.yaml → canonical_roles или в menu.template.yaml. Новая роль сначала добавляется в контракт, затем regen.sh.


Как добавить операцию на карточку устройства

Операции (запуск, остановка, нагрев, подсветка) объявляются действиями карточки, а не пунктами меню. Пределы можно взять из пункта меню:

idryer::card_menu::attach(s_link.card());
s_link.card().action("storage", "STORAGE", onStorage)
    .param("temperature", "target_temperature", MENU_TARGET_TEMP);
s_link.card().action("stop", "IDLE", onStop);

Подробно — Карточка устройства: card-манифест.


Чего НЕ делать

  • Не добавлять widget: в menu.yaml. Поле widget в canonical_roles справочное: портал и приложение его не читают.
  • Не править mqtt-api.types.ts руками — его генерирует regen.sh.
  • Не трогать флаги Config.hasXxx ради новых действий — они только для телеметрии (сенсоры, состояния).