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

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


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

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

role: — смысловое имя пункта меню. Прошивка говорит «у меня есть iheater.heat_start», а не «у меня есть кнопка номер 35». Это стабильный контракт между устройством и порталом — внутренние имена прошивки могут меняться, role: остаётся неизменным.

Виджет — как портал отображает этот пункт: кнопка, слайдер, переключатель, или сложный компонент (выбор цвета, редактор профиля). Определяется контрактом по role:, не прошивкой.

Пункт меню с role: виден порталу. Без role: — приватный, только на дисплее устройства.


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

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

Проверка встроена в сборку — написать несуществующую роль физически невозможно.

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

mqtt_contract.yamlgen_ts_types.py генерирует mqtt-api.types.ts → файл копируется в frontend-v2/src/contracts/

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

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

Устройство подключается → публикует меню в MQTT топик config → портал читает каждый item с полем r: → смотрит CanonicalRoles[r].widget → рендерит виджет из WIDGET_REGISTRY.

Параметры (min, max, val) берутся из самого menu item — прошивка знает актуальные значения.


Как добавить новое действие на дашборд портала

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

1. Подобрать подходящую роль из контракта. Если подходящей нет — сначала добавить в mqtt_contract.yamlcanonical_roles и запустить regen.sh:

canonical_roles:
  my.action: { type: action, widget: button }

2. Добавить пункт в menu.yaml:

- id: my_action
  type: action
  role: my.action
  title: { ru: "МОЁ ДЕЙСТВИЕ", en: "MY ACTION" }

3. Обработать в прошивке (main.cpp):

if (action == "my.action") { /* делаем что нужно */ }

pio run → валидация → C++ → прошивка публикует r: "my.action" → портал рендерит кнопку.


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

- id: my_param
  type: value
  role: my.param        # если нужен на портале; без role: — только на дисплее
  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 = значение сохраняется после перезагрузки. Портал меняет значение через commands/set { "id": <id>, "val": <value> }.


Что НЕ нужно делать

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