プロトコルとしてのメニュー: menu.yaml ↔ mqtt_contract.yaml ↔ ポータル¶
3 つのファイル、3 つの役割¶
| ファイル | 所有者 | 記述する内容 |
|---|---|---|
src/menu/menu.yaml |
あなたの製品 | デバイスのメニュー: パラメータ、アクション、構造 |
contracts/mqtt_contract.yaml |
idryer-core | 既知の意味の一覧: 複数言語のラベルを持つ canonical_roles |
frontend-v2/src/contracts/mqtt-api.types.ts |
生成物 | ポータル用の TypeScript 型 |
role: — メニュー項目の意味的な名前です。ファームウェアは「35 番の項目がある」ではなく「iheater.heat_temp がある」と伝えます。ファームウェア内部の名前は変わっても、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. 実行時: デバイス ↔ ポータル¶
ファームウェアがメニューを 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 マニフェスト。
やってはいけないこと¶
menu.yamlにwidget:を追加しない。canonical_rolesのwidgetフィールドは参考情報で、ポータルもアプリも読みません。mqtt-api.types.tsを手で編集しない —regen.shが生成します。- 新しいアクションのために
Config.hasXxxフラグを変更しない — これはテレメトリ(センサー、状態)専用です。