SWPF AUTOMATION 設定と実コードの動き・データの流れ解説
対象読者: ロジック経験はあるが、Drupal / サーバー側フレームワークにはまだ慣れていない開発者向け 前提: この説明は、今回確認した実コードをベースに、「何が起点で、どの順番で、どのデータが、どこへ渡るか」を追えるように整理したものです。
1. まず全体像
SWPF AUTOMATION は、ざっくり言うと次の4層で動きます。
- トリガ層
CRON や LINE受信、SwitchBot受信、Kintone受信など、起動のきっかけを決める層
- TriggerEntity 層
「このユーザーは、このトリガで、どの GROUP_ID を動かすか」を管理する層
- TemplateGroup / TemplateEntity 層
GROUP_ID に属する複数の TEMPLATEENTITY を、weight 順に実行する層
- Executor / Plugin 層
各 TEMPLATEENTITY の PARAMSJSON / RULEJSON / TEMPLATE_JSON を解釈して、 実際のプラグイン呼び出し・式計算・固定値投入を行う層
この関係を先に図で見るとこうです。
2. 重要な考え方
この仕組みを理解するポイントは次の3つです。
2-1. トリガは「処理そのもの」ではなく「起動ポイント」
トリガ定数は、どの処理を呼ぶかを直接書いたものではありません。 「この名前のイベントが起きた」という入口です。
実コード上は swpfcommon 側のトリガ定数を swpfautomation から互換参照しています。
CRON1M,CRON5M,CRON_15MなどLINE_RECEIVEDKINTONE_RECEIVEDSWITCHBOT_RECEIVED- 環境によっては
MQTT_RECEIVEDのようなイベントトリガも使用
つまり、トリガは「関数名」ではなく、イベント名 / 発火名です。
2-2. TriggerEntity は「どの GROUP_ID を起動するか」の対応表
ユーザーは TriggerEntity によって、
- どの
trigger_typeで - どの
templategroupidを - 有効化するか
を設定します。
つまり TriggerEntity は、
イベント名 → 実行グループ
の対応表です。
2-3. TemplateEntity は「単一ステップ」、GROUP_ID は「一連の流れ」
1個の TEMPLATEENTITY は、1つの判断・1つの動作単位です。 ただし実際には RULEJSON により複数 templateid が選ばれたり、template_sequence が組まれたりするので、 1テンプレート = 1行の命令というより、1ステップ分のロジック定義と考えると分かりやすいです。
GROUP_ID は、それを複数まとめて 順番に流すパイプラインです。
3. 起動の入口
3-1. CRON 系トリガの入口
CRON は swpfautomation.module の hookcron() から入ります。
処理の流れはこうです。
ここで重要なのは、CRON 自体は直接 Template を実行しないことです。 CRON はまず 「今回発火すべきトリガ名」 を出し、そこから fireBatch() に渡します。
つまり、CRON 側は
- 時間管理
- 二重起動防止
- 発火タイミング計算
を担当し、実際の自動化ロジックは FireService 側に渡します。
3-2. イベント系トリガの入口
イベント系は、プラグインの中から fire() が呼ばれることがあります。
例えば実コードでは次のような流れがあります。
FetchRawDataFromMqttQueueが MQTT RAW を取得してMQTT_RECEIVEDを発火FetchRawDataFromKintoneが Kintone データを取得してKINTONE_RECEIVEDを発火FetchSensorDataFromSwitchbotが SwitchBot データ取得後にSWITCHBOT_RECEIVEDを発火
つまり SWPF AUTOMATION では、
- 外側のCRONから直接起動する流れ
- いったんプラグインで外部データを取得し、その中で次のトリガを起こす流れ
の両方があります。
これを図にするとこうです。
ここで大事なのは、プラグイン内 fire も通常の GROUP 実行と同じく context/raws/results を持ったまま次の流れへ渡せるということです。
4. FireService の役割
FireService はこの仕組みの「交通整理係」です。
4-1. fireBatch(triggerType, rawData, options)
これは主に CRON 用です。 指定された triggerType に一致する TriggerEntity を全部拾い、対象ユーザーごとに fire() を呼びます。
ポイント:
trigger_typeが一致する TriggerEntity を検索enabled=1のものだけ対象- 通常は 同じ uid について重複発火しないように整理
- ユーザーごとの rawData 上書きも可能
つまり fireBatch() は、
このトリガを受ける人たちを探して、それぞれに個別 fire する
という役目です。
4-2. fire(uid, triggerType, rawData, options)
こちらが実際の本体です。
処理のイメージ:
ここで重要なのは、1つの triggerType に対して TriggerEntity が複数あってもよいということです。 つまり同じトリガで複数 GROUP_ID を起動できます。
ただしそのとき、GROUP_ID が異なればデータは共有されません。
これはかなり重要です。
- 同じ GROUP_ID 内: データは引き継がれる
- 別 GROUP_ID: それぞれ独立した実行になる
5. GROUP_ID 単位の処理
GROUP の実行は SwpfAutomationGroupRunner::run() が担当します。
ここが、あなたの言う
GROUPID は TEMPLATEENTITY(一連の動作のテンプレート)を束ねており、TEMPLATEENTITY に設定された実行順序に従って順番に動作する
をそのまま実装している部分です。
5-1. GROUP 内で行うこと
uid + groupidでactiontemplate_entityを検索enabled=1のみ対象weight ASC,id ASCで順番確定- 1件ずつ Executor に渡して実行
- 実行後の
input.rawsとinput.resultsを次の TEMPLATEENTITY に渡す
図にするとこうです。
5-2. GROUP 内で引き継がれるもの
GroupRunner が次段へ渡す主なものはこれです。
input.rawsinput.resultstracemeta
実コード上、特に明示的に引き継いでいるのは次の2つです。
input.raws
前段プラグインや前段評価結果によって更新された、生データ寄りの作業領域
input.results
式計算や固定値投入などの中間結果置き場
この2つがあるので、GROUP は単なる「順番実行」ではなく、前段の出力を後段が参照できる実行パイプラインになります。
6. 1つの TEMPLATEENTITY の中で何が起こるか
ここからが一番重要です。 1つの TEMPLATEENTITY は、Executor に渡されると次の順で処理されます。
7. ContextBuilder の役割
Executor はいきなり rule_json を評価するのではなく、まず ContextBuilder で周辺情報を集めます。
7-1. ContextBuilder が集めるもの
主に次を組み立てます。
- ActionTemplateEntity 本体
- その Template に紐づく Profile
- その Profile に紐づく DeviceService
params_jsonrule_jsontemplate_json- fallback 用
plugin_id
つまり ContextBuilder は、
実行に必要な設定一式を bundle にまとめる担当
です。
7-2. plugin_id の優先関係
コードから読み取れる優先順位はこうです。
templatejson[対象templateid].plugintemplateentity.pluginidprofileentity.pluginid
つまり TEMPLATEJSON の各項目で plugin を明示した場合、それが最優先です。 なければ TemplateEntity の pluginid、それもなければ ProfileEntity の plugin_id が使われます。
これは、
- 基本は共通プラグインを Profile で持たせる
- 特定の Template だけ別プラグインにしたいときは TEMPLATE_JSON 側で上書きする
という運用ができるようにするためです。
8. PARAMSJSON と PARAMSMAP の意味
ここはサーバーに不慣れな人が最初につまずきやすい箇所です。
8-1. なぜ PARAMS_JSON が必要か
IoT の生データは、送信元ごとにキー名がバラバラになりがちです。
例:
- MQTT は
temp - SwitchBot は
temperature - Kintone は
record.body.temp.value - ChatGPT結果は
result.score.value
このまま RULE_JSON に直接書くと、テンプレートごとに参照方法がバラバラになります。
そこで PARAMS_JSON で、
- ルール側で使いたい名前
- 実際の内部データの場所
を対応付けます。
つまり PARAMS_JSON は、評価用の別名定義です。
8-2. PARAMS_MAP はいつ作られるか
Executor は RULEJSON を評価する前に、必ず paramsmap を作ります。 サービス定義上、この処理は @swpfcommon.paramsmap_builder に委譲されています。
つまり概念上はこうです。
8-3. 何が入るか
params_map には少なくとも次の2系統が入ります。
- raw 由来の値
input.raws から PARAMS_JSON に従って取り出された値
- result 由来の値
前段の EXPRESSION / LITERAL により input.results に入った値を、result.* プレフィックスで追加した値
実コードでは refreshResultsParamsMap() が input.results を input.params_map に再反映します。
つまり後段では、たとえば次のような参照が可能になります。
TEMPraw.weather.jma.forecastresult.score.valueresult.alert_message.value
この設計の利点は、
生データの場所と、ロジックで使う変数名を分離できる
ことです。
9. RULE_JSON の役割
RULEJSON は、PARAMSMAP を見て どの TEMPLATE_ID を使うか決める部分です。
ここで大事なのは、RULEJSON はプラグインそのものを直接呼ぶのではなく、まず templateid の選択を行う点です。
つまり流れはこうです。
PARAMS_MAP を作る
↓
RULE_JSON を評価する
↓
選ばれた TEMPLATE_ID を得る
↓
TEMPLATE_JSON[そのTEMPLATE_ID] を使って実処理する
9-1. AND / OR / ネスト
ご指摘の通り、RULEJSON は複数ネストに対応し、IF判定で AND / OR をサポートする前提の構造です。 今回確認した Executor は評価自体を @swpfcommon.eval_json に委譲しているため、厳密な演算子実装本体は COMMON 側にありますが、Automation 側の使い方としては以下の理解で問題ありません。
- RULE_JSON は単純な1回判定だけではない
- ネストした条件木を持てる
- 結果として
selectedtemplateidまたはselectedtemplateidsが返る
9-2. 複数 TEMPLATE_ID を返せる
Executor は単一の selectedtemplateid だけでなく、selectedtemplateids を前提に template_sequence を組む実装になっています。
つまり RULE_JSON は
- 1個の template_id を返す場合
- 複数 template_id を返して、順番に COMMAND / EXPRESSION / LITERAL を並べる場合
の両方に対応できます。
10. TEMPLATE_JSON の役割
TEMPLATEJSON は、選ばれた TEMPLATEID に対して、実際に何をするかを定義します。
ユーザーの整理どおり、ここには「単一動作の手続き」が入ります。
実コード上、type は主に次の3種類です。
COMMANDEXPRESSIONLITERAL
10-1. COMMAND
COMMAND は最終的にプラグインへ渡される実行命令です。 JSON payload の場合もあれば、文字列テンプレートの場合もあります。
例のイメージ:
{
"type": "COMMAND",
"plugin": "line_message_execute",
"command": {
"REPLYHEADER": "こんにちは{USER_NAME}さん",
"REPLY": "現在温度は{TEMP}です"
}
}
この {TEMP} のようなプレースホルダは、params_map により置換されます。
10-2. EXPRESSION
EXPRESSION は式評価です。 プラグインは実行せず、計算結果を input.results に格納します。
例:
{
"type": "EXPRESSION",
"expr": "({TEMP} * 2) + {HUM}",
"result_key": "score"
}
実行後は概念的にこうなります。
input.results.score = {
"key": "score",
"value": 123,
"type": "EXPRESSION",
"template_id": "SCORE_CALC"
}
そしてその結果が result.score.value のように params_map に再取り込みされ、次のルールやコマンドから参照できるようになります。
10-3. LITERAL
LITERAL は固定値投入です。 式評価せず、そのまま値を input.results に入れます。
例:
{
"type": "LITERAL",
"value": "異常あり",
"result_key": "alert_message"
}
これは「変数に固定値を設定する」に対応します。
11. 1つの TEMPLATEENTITY の中でのデータ変形
ここはデータの流れだけに絞って見ます。
11-1. 初期入力
GroupRunner から Executor へ渡る時点では概ねこうです。
{
"input": {
"raws": { ...前段から来た生データ... },
"results": { ...前段の中間結果... }
},
"action_template_entity_id": 123,
"meta": { ... },
"trace": { ... }
}
11-2. ContextBuilder 後
TemplateEntity / Profile / DeviceService 由来の設定が bundle 化されます。
{
"bundle": {
"params_json": {...},
"rule_json": {...},
"template_json": {...},
"plugin_id": "fallback_plugin",
"config_json": {...},
"capabilities_json": {...}
}
}
11-3. params_map 作成後
{
"input": {
"raws": {...},
"results": {...},
"params_map": {
"TEMP": 27.4,
"HUM": 45.2,
"result.score.value": 82
}
}
}
11-4. RULE_JSON 評価後
{
"meta": {
"selected_template_id": "MAIL_NORMAL",
"used_template_id": "MAIL_NORMAL",
"selected_template_ids": ["CALC_SCORE", "MAIL_NORMAL"],
"used_template_ids": ["CALC_SCORE", "MAIL_NORMAL"]
}
}
11-5. TEMPLATE_JSON 展開後
{
"template_sequence": [
{
"template_id": "CALC_SCORE",
"type": "EXPRESSION",
"expr": "({TEMP}*2)+{HUM}",
"result_key": "score"
},
{
"template_id": "MAIL_NORMAL",
"type": "COMMAND",
"plugin_id": "mail_send_execute",
"command": {
"subject": "温度通知",
"body": "score={result.score.value}"
}
}
]
}
11-6. 実行後
- EXPRESSION / LITERAL は
input.resultsを更新 - COMMAND はプラグインを実行し、その plugin が返した
contextをマージ - 最終
contextが次の TEMPLATEENTITY に渡る
12. プラグイン実行時の流れ
COMMAND がある場合、Executor は plugin.manager.swpf_execute からプラグインを生成して実行します。
しかも今の実装は、template_sequence の各 COMMAND を順番に全部実行します。 以前は最後の1件寄りの扱いでしたが、現在は複数 COMMAND の逐次実行になっています。
これにより、1つの TEMPLATEENTITY の中で
- 先に値を作る
- 次に通知する
- 次に保存する
のような複数段の動作も実現しやすくなっています。
13. GROUP 内のデータ引き継ぎ
あなたが特に重視している点を、そのまま整理すると次の通りです。
13-1. 同一 GROUP_ID 内では引き継がれる
GROUP の中で Template #1 → #2 → #3 と流れるとき、次が引き継がれます。
input.rawsinput.results- プラグインが返した
contextの反映結果
図で見るとこうです。
13-2. 別 GROUP_ID には引き継がれない
同じ triggerType で複数 GROUP_ID が起動されても、GROUP ごとに run() が独立しています。 したがって、GROUP A の途中結果が GROUP B に混ざることはありません。
これは設計上かなり大事で、同一イベントをきっかけに複数の自動化を走らせても相互汚染しないという意味です。
14. プラグイン内 fire のときのデータ引き継ぎ
これも重要です。 プラグインの中で次のトリガを発火する場合、
- 直前までの
context - そのときの
input.raws - 親トレース情報
を使って fire() できます。
例えば Kintone 取得プラグインでは、取得した RAW を context['input']['raws']['kintone'] に積んだあと、それをそのまま fire() の第3引数に渡しています。
つまり概念的にはこうです。
これは、あなたの言う
プラグイン内でトリガポイントが存在する場合は、直前に持つ各種情報をそのままそのプラグインで発火された次の TEMPLATEENTITY に引き継ぐ
に対応しています。
厳密には「同じ GROUP の次段」ではなく、新しい triggerType の fire として別 GROUP が起動する形ですが、データの受け渡し感覚としてはかなり近いです。
15. 開発者目線での「実際の理解のしかた」
IoT 開発者向けにかなり雑に言い換えると、SWPF AUTOMATION は次のように理解すると分かりやすいです。
15-1. TriggerEntity は割り込みベクタ表のようなもの
CRON_15Mが来たらどの GROUP を動かすかLINE_RECEIVEDが来たらどの GROUP を動かすかKINTONE_RECEIVEDが来たらどの GROUP を動かすか
を定義するので、マイコンで言えば イベントディスパッチ表 に近いです。
15-2. GROUP_ID はパイプライン
GROUP は、
- 入力データを受け取り
- 各ステップで加工し
- 中間結果を足しながら
- 後段に流す
ので、シェルのパイプや、Node-RED の直列フローに近いです。
15-3. PARAMS_JSON は配線変換
センサ側のキー名をそのままロジックに使うと壊れやすいので、 PARAMS_JSON は
- 実データの場所
- ロジック側の変数名
を変換する 配線アダプタ と考えると分かりやすいです。
15-4. RULE_JSON は分岐回路
RULE_JSON は
- 温度が高いなら A
- 低いなら B
- さらに score が高いなら C も追加
のような分岐です。 if/else と AND/OR を JSON 化したものと考えればよいです。
15-5. TEMPLATE_JSON は命令本体
最後に実際の「何をやるか」が TEMPLATE_JSON です。
- メール送信
- LINE返信
- ChatGPT 呼び出し
- Queue投入
- SwitchBot制御
- 固定値投入
- 式評価
がここで決まります。
16. 典型例: CRON → Kintone取得 → KINTONE_RECEIVED → ChatGPT → メール
全体を1本の例で見ると分かりやすいです。
この例で押さえるポイントはこうです。
- 最初の CRON はあくまで起動だけ
- 外部データ取得はプラグインが担当
- 取得後に別トリガを fire して次の処理系へ渡せる
- 中間結果は
input.results - 生データや下流に渡したいデータは
input.raws - 最後の通知や保存は COMMAND + plugin 実行
17. input.raws と input.results の使い分け
ここは実運用で重要なので分けて整理します。
| 項目 | 主な用途 | 典型例 |
|---|---|---|
input.raws | 外部から来たデータ、または下流へそのまま渡したい構造体 | MQTT records, Kintone records, weather API応答, plugin取得データ |
input.results | 式計算・固定値投入・中間判定結果 | score, alertmessage, blogtitle, diagnosis_summary |
input.params_map | 評価・置換のために見やすく再編成した参照表 | TEMP, HUM, result.score.value |
使い分けの感覚としては、
raws= 荷物置き場results= 加工結果メモparams_map= 参照しやすい目次
です。
18. 実装上の注意点
18-1. GROUP をまたいで値はつながらない
同じ triggerType で複数 GROUP をぶら下げても、処理は独立です。 そのため、GROUP A の計算結果を GROUP B で使いたいなら、明示的に別の保存先へ書くか、別トリガ fire 時の rawData に入れて渡す必要があります。
18-2. EXPRESSION / LITERAL は plugin を実行しない
これはかなり大事です。 EXPRESSION / LITERAL は データを作るだけ で、通知や保存はしません。 通知や保存したければ、その後段に COMMAND を置く必要があります。
18-3. PARAMSJSON をきちんと作らないと RULEJSON が読みにくくなる
生キーを RULE に直接埋めると、後からデータ元が変わったとき全部壊れます。 この仕組みでは PARAMS_JSON を丁寧に作るほど、ルールとテンプレートが長持ちします。
18-4. plugin 内 fire は便利だが「新しい起動」になる
感覚としては前処理から後処理へ自然につながりますが、内部的には 別トリガの fire です。 したがって、
- どの uid で fire するか
- どの triggerType で fire するか
- rawData に何を積むか
を明確にしないと、意図しないユーザーや GROUP が動く可能性があります。
19. 最終まとめ
SWPF AUTOMATION を一文で言うと、
トリガで GROUP を起動し、GROUP 内の TEMPLATEENTITY を順番に流し、その中で PARAMSMAP → RULEJSON → TEMPLATE_JSON → Plugin/Expression/Literal を通してデータを展開していく仕組み
です。
理解の順番としては、次の順が最も分かりやすいです。
- TriggerType はイベント名
- TriggerEntity はイベント名と GROUP_ID の対応表
- GROUP_ID は TemplateEntity の実行列
- TemplateEntity は
PARAMSJSON + RULEJSON + TEMPLATE_JSONの1ステップ定義 - PARAMSJSON で raw を別名化して PARAMSMAP を作る
- RULEJSON で使う TEMPLATEID を決める
- TEMPLATE_JSON で COMMAND / EXPRESSION / LITERAL を実行する
- 結果は
input.raws/input.resultsに載り、次へ渡る - 同一 GROUP 内では渡るが、別 GROUP には渡らない
- plugin 内 fire を使うと、新しい trigger として次の GROUP を起動できる
20. 実コード上の主な確認対象ファイル
今回の説明を追うときは、次のファイルを見ると理解しやすいです。
swpfautomation/swpfautomation.module
CRON から fireBatch() へ入る入口
swpf_automation/src/Service/SwpfAutomationFireService.php
TriggerEntity を拾って GROUP を起動する本体
swpf_automation/src/Service/SwpfAutomationGroupRunner.php
GROUP_ID 配下の TEMPLATEENTITY を順番実行し、raws/results を引き継ぐ層
swpf_automation/src/Service/SwpfAutomationExecutor.php
PARAMSMAP 作成、RULEJSON 評価、TEMPLATE_JSON 展開、plugin 実行までの中核
swpf_automation/src/Service/SwpfAutomationContextBuilder.php
Template / Profile / DeviceService を集約して bundle を組み立てる層
swpf_automation/src/Plugin/SwpfExecute/*.php
実際の COMMAND 実行先
21. 補足
今回確認できた範囲では、PARAMSMAP の生成ロジック本体と RULEJSON の評価ロジック本体は swpf_common サービスに委譲されています。
@swpfcommon.paramsmap_builder@swpfcommon.evaljson
そのため、演算子の全仕様や params 展開の厳密な末端実装は COMMON 側を見る必要があります。 ただし、AUTOMATION 側の流れとしては本書の理解でほぼそのまま追えます。
21. 実際のサンプルJSON付き版
ここからは、上の説明を実際に設定する人がイメージしやすいように、 できるだけ現実的な JSON 例を付けて説明します。
注意:
- 以下の JSON は 理解用のサンプル です
- 実環境ではフィールド名やラッパ構造が少し異なる可能性があります
- ただし、考え方・責務分担・データの流れ はこのまま理解して問題ありません
22. サンプル全体シナリオ
まずは全体シナリオを固定します。
22-1. 例のシナリオ
「15分ごとの CRON で Kintone データを取得し、異常判定し、必要なら LINE 通知する」
処理の流れ:
CRON_15Mが発火- TriggerEntity が
GROUPIMPORTKINTONEを起動 - その GROUP 内で Kintone データ取得プラグインを実行
- 取得プラグインが
KINTONE_RECEIVEDを fire - TriggerEntity が
GROUPANALYZEKINTONEを起動 PARAMS_JSONでデータを別名化RULE_JSONで異常かどうか判定- 異常なら
EXPRESSIONでメッセージを組み立てる COMMANDで LINE 通知する
図にするとこうです。
23. Trigger 定数のイメージ
まず、起動ポイントはあらかじめ CONST 的に定義されている前提です。
23-1. トリガ定数のイメージ
const CRON_1M = 'CRON_1M';
const CRON_5M = 'CRON_5M';
const CRON_15M = 'CRON_15M';
const LINE_RECEIVED = 'LINE_RECEIVED';
const MQTT_RECEIVED = 'MQTT_RECEIVED';
const KINTONE_RECEIVED = 'KINTONE_RECEIVED';
const SWITCHBOT_RECEIVED = 'SWITCHBOT_RECEIVED';
これは「処理そのもの」ではなく、イベント名の定義です。
マイコンで言えば、
- タイマ割り込み
- GPIO 割り込み
- UART受信完了
のような入口ラベルに近いです。
24. TriggerEntity のサンプル
TriggerEntity は「どのトリガで、どの GROUP_ID を動かすか」の設定です。
24-1. CRON 用 TriggerEntity の例
{
"id": 101,
"uid": 1,
"enabled": 1,
"trigger_type": "CRON_15M",
"template_group_id": "GROUP_IMPORT_KINTONE",
"weight": 10,
"title": "15分ごとにKintone取得開始"
}
意味:
- ユーザー
uid=1 CRON_15Mが来たらGROUPIMPORTKINTONEを起動する
24-2. KINTONE_RECEIVED 用 TriggerEntity の例
{
"id": 102,
"uid": 1,
"enabled": 1,
"trigger_type": "KINTONE_RECEIVED",
"template_group_id": "GROUP_ANALYZE_KINTONE",
"weight": 10,
"title": "Kintone受信後に解析と通知"
}
意味:
- 同じユーザー
uid=1 KINTONE_RECEIVEDが fire されたらGROUPANALYZEKINTONEを起動する
24-3. 同じトリガに複数 GROUP をぶら下げる例
[
{
"id": 201,
"uid": 1,
"enabled": 1,
"trigger_type": "CRON_15M",
"template_group_id": "GROUP_IMPORT_KINTONE",
"weight": 10
},
{
"id": 202,
"uid": 1,
"enabled": 1,
"trigger_type": "CRON_15M",
"template_group_id": "GROUP_IMPORT_WEATHER",
"weight": 20
}
]
この場合、同じ CRON_15M で
GROUPIMPORTKINTONEGROUPIMPORTWEATHER
の両方が動きます。
ただし前述の通り、GROUP が違うのでデータは共有されません。
25. GROUP_ID にぶら下がる TEMPLATEENTITY のサンプル
次に、GROUP に属する実行ステップの例です。
25-1. GROUPIMPORTKINTONE の例
[
{
"id": 301,
"uid": 1,
"group_id": "GROUP_IMPORT_KINTONE",
"weight": 10,
"enabled": 1,
"title": "Kintoneからデータ取得",
"plugin_id": "fetch_raw_data_from_kintone"
}
]
この GROUP は 1ステップだけです。
25-2. GROUPANALYZEKINTONE の例
[
{
"id": 401,
"uid": 1,
"group_id": "GROUP_ANALYZE_KINTONE",
"weight": 10,
"enabled": 1,
"title": "取得データを解析してスコア計算"
},
{
"id": 402,
"uid": 1,
"group_id": "GROUP_ANALYZE_KINTONE",
"weight": 20,
"enabled": 1,
"title": "必要なら通知"
}
]
ここで重要なのは、
weight=10が先weight=20が後
であり、401 の結果を 402 が使えることです。
26. Kintone取得プラグインの入出力イメージ
ここでは最初の GROUP で動くプラグインをイメージ化します。
26-1. プラグイン実行前の context
{
"uid": 1,
"trigger_type": "CRON_15M",
"input": {
"raws": {},
"results": {}
},
"meta": {
"source": "cron"
}
}
26-2. プラグインが Kintone から取得した生データ例
{
"records": [
{
"record_id": "5001",
"device_id": "esp32_C80CABF16E20",
"temp": 31.2,
"hum": 68.5,
"co2": 1024,
"captured_at": "2026-04-19 15:00:00"
},
{
"record_id": "5002",
"device_id": "esp32_C80CABF16E20",
"temp": 32.8,
"hum": 70.1,
"co2": 1180,
"captured_at": "2026-04-19 15:15:00"
}
]
}
26-3. プラグイン実行後の context 例
{
"uid": 1,
"trigger_type": "CRON_15M",
"input": {
"raws": {
"kintone": {
"records": [
{
"record_id": "5001",
"device_id": "esp32_C80CABF16E20",
"temp": 31.2,
"hum": 68.5,
"co2": 1024,
"captured_at": "2026-04-19 15:00:00"
},
{
"record_id": "5002",
"device_id": "esp32_C80CABF16E20",
"temp": 32.8,
"hum": 70.1,
"co2": 1180,
"captured_at": "2026-04-19 15:15:00"
}
]
}
},
"results": {}
},
"meta": {
"fetched_count": 2,
"source": "cron"
}
}
26-4. このあと fire されるデータの例
{
"kintone": {
"records": [
{
"record_id": "5001",
"device_id": "esp32_C80CABF16E20",
"temp": 31.2,
"hum": 68.5,
"co2": 1024,
"captured_at": "2026-04-19 15:00:00"
},
{
"record_id": "5002",
"device_id": "esp32_C80CABF16E20",
"temp": 32.8,
"hum": 70.1,
"co2": 1180,
"captured_at": "2026-04-19 15:15:00"
}
]
}
}
つまり、プラグイン内 fire は概念的にはこうです。
$fire_raws = $context['input']['raws'];
$fireService->fire($uid, 'KINTONE_RECEIVED', $fire_raws, $options);
このため、取得済みデータをそのまま次の自動化へ渡せるわけです。
27. 解析用 TEMPLATEENTITY の実サンプル
ここからは GROUPANALYZEKINTONE の中身を具体化します。
まず 1件目の TEMPLATEENTITY です。
27-1. TEMPLATEENTITY #401 のサンプル
{
"id": 401,
"uid": 1,
"group_id": "GROUP_ANALYZE_KINTONE",
"weight": 10,
"enabled": 1,
"title": "温度とCO2から警告判定",
"params_json": {
"TEMP": "kintone.records.1.temp",
"HUM": "kintone.records.1.hum",
"CO2": "kintone.records.1.co2",
"DEVICE_ID": "kintone.records.1.device_id",
"CAPTURED_AT": "kintone.records.1.captured_at"
},
"rule_json": {
"if": {
"op": "OR",
"conditions": [
{
"left": "TEMP",
"operator": ">=",
"right": 32.0
},
{
"left": "CO2",
"operator": ">=",
"right": 1100
}
]
},
"then": ["CALC_ALERT_LEVEL", "BUILD_ALERT_MESSAGE"],
"else": ["CALC_NORMAL_LEVEL"]
},
"template_json": {
"CALC_ALERT_LEVEL": {
"type": "LITERAL",
"result_key": "alert_level",
"value": "warning"
},
"BUILD_ALERT_MESSAGE": {
"type": "EXPRESSION",
"result_key": "alert_message",
"expr": "'Device=' ~ {DEVICE_ID} ~ ' temp=' ~ {TEMP} ~ ' hum=' ~ {HUM} ~ ' co2=' ~ {CO2}"
},
"CALC_NORMAL_LEVEL": {
"type": "LITERAL",
"result_key": "alert_level",
"value": "normal"
}
}
}
この 1件でやっていることは:
params_jsonで Kintone データを別名にするrule_jsonで異常かどうか判定する- 異常なら
alert_level=warningを入れる - 異常なら通知文
alert_messageも作る - 正常なら
alert_level=normalを入れる
28. PARAMS_JSON のサンプルを分解して理解する
28-1. 例
{
"TEMP": "kintone.records.1.temp",
"HUM": "kintone.records.1.hum",
"CO2": "kintone.records.1.co2",
"DEVICE_ID": "kintone.records.1.device_id",
"CAPTURED_AT": "kintone.records.1.captured_at"
}
28-2. 意味
これは、RULEJSON や TEMPLATEJSON の中では
TEMPHUMCO2DEVICE_IDCAPTURED_AT
という短い名前で使いたい、という宣言です。
もとのデータ構造が深い場合でも、ロジック側を簡潔にできます。
28-3. IoT 開発者向けの感覚
これは、マイコン側でいうと
float t = sensorData.moduleA.temp;
float h = sensorData.moduleA.hum;
のように、ローカル変数へ取り出してから計算する感覚に近いです。
サーバー側ではその「取り出し定義」を JSON 化している、と考えると分かりやすいです。
29. PARAMS_MAP が実際にどう生成されるかの例
上の paramsjson と input.raws から、評価用の paramsmap が作られます。
29-1. 元データ
{
"input": {
"raws": {
"kintone": {
"records": [
{
"record_id": "5001",
"device_id": "esp32_C80CABF16E20",
"temp": 31.2,
"hum": 68.5,
"co2": 1024,
"captured_at": "2026-04-19 15:00:00"
},
{
"record_id": "5002",
"device_id": "esp32_C80CABF16E20",
"temp": 32.8,
"hum": 70.1,
"co2": 1180,
"captured_at": "2026-04-19 15:15:00"
}
]
}
},
"results": {}
}
}
29-2. params_map の生成例
{
"TEMP": 32.8,
"HUM": 70.1,
"CO2": 1180,
"DEVICE_ID": "esp32_C80CABF16E20",
"CAPTURED_AT": "2026-04-19 15:15:00"
}
この時点で RULE_JSON からは、深いパスを意識せずに
TEMPCO2
で判定できるようになります。
30. RULE_JSON のサンプルを詳しく読む
30-1. 単純な OR 判定例
{
"if": {
"op": "OR",
"conditions": [
{
"left": "TEMP",
"operator": ">=",
"right": 32.0
},
{
"left": "CO2",
"operator": ">=",
"right": 1100
}
]
},
"then": ["CALC_ALERT_LEVEL", "BUILD_ALERT_MESSAGE"],
"else": ["CALC_NORMAL_LEVEL"]
}
意味:
TEMP >= 32.0またはCO2 >= 1100なら異常扱い- 異常なら
CALCALERTLEVELとBUILDALERTMESSAGEを実行 - それ以外は
CALCNORMALLEVELを実行
30-2. もっと複雑な AND / OR ネスト例
{
"if": {
"op": "OR",
"conditions": [
{
"op": "AND",
"conditions": [
{
"left": "TEMP",
"operator": ">=",
"right": 30.0
},
{
"left": "HUM",
"operator": ">=",
"right": 75.0
}
]
},
{
"left": "CO2",
"operator": ">=",
"right": 1200
}
]
},
"then": ["CALC_ALERT_LEVEL", "BUILD_ALERT_MESSAGE"],
"else": ["CALC_NORMAL_LEVEL"]
}
意味:
(TEMP >= 30.0 AND HUM >= 75.0)- または
CO2 >= 1200 - なら異常
これは実際の if 文にすると、だいたい次の感覚です。
if ( (
$TEMP >= 30.0 && $HUM >= 75.0
) || $CO2 >= 1200 ) {
// alert
}
31. TEMPLATE_JSON のサンプルを詳しく読む
31-1. LITERAL の例
{
"type": "LITERAL",
"result_key": "alert_level",
"value": "warning"
}
意味:
input.results.alert_levelに- 固定値
warningを入れる
実行後イメージ:
{
"input": {
"results": {
"alert_level": {
"key": "alert_level",
"value": "warning",
"type": "LITERAL",
"template_id": "CALC_ALERT_LEVEL"
}
}
}
}
31-2. EXPRESSION の例
{
"type": "EXPRESSION",
"result_key": "alert_message",
"expr": "'Device=' ~ {DEVICE_ID} ~ ' temp=' ~ {TEMP} ~ ' hum=' ~ {HUM} ~ ' co2=' ~ {CO2}"
}
意味:
DEVICE_ID,TEMP,HUM,CO2を使って文字列を組み立てる- 結果を
input.results.alert_messageに入れる
実行後イメージ:
{
"input": {
"results": {
"alert_message": {
"key": "alert_message",
"value": "Device=esp32_C80CABF16E20 temp=32.8 hum=70.1 co2=1180",
"type": "EXPRESSION",
"template_id": "BUILD_ALERT_MESSAGE"
}
}
}
}
31-3. COMMAND の例
次の TEMPLATEENTITY では、作った値を使って通知することができます。
{
"SEND_LINE_ALERT": {
"type": "COMMAND",
"plugin": "line_message_execute",
"command": {
"REPLYHEADER": "自動診断結果",
"REPLY": "{result.alert_message.value}",
"LABEL": "警告",
"LEVEL": "{result.alert_level.value}"
}
}
}
意味:
- LINE 通知プラグインを呼ぶ
result.alert_message.valueを本文に差し込むresult.alert_level.valueをプラグインへ渡す
32. 結果が params_map に再反映される例
ここが非常に重要です。
先ほどの LITERAL / EXPRESSION 実行後、input.results に入った値は後段のテンプレートから参照できるように、再度 params_map に載せ直されます。
32-1. input.results の例
{
"alert_level": {
"key": "alert_level",
"value": "warning",
"type": "LITERAL",
"template_id": "CALC_ALERT_LEVEL"
},
"alert_message": {
"key": "alert_message",
"value": "Device=esp32_C80CABF16E20 temp=32.8 hum=70.1 co2=1180",
"type": "EXPRESSION",
"template_id": "BUILD_ALERT_MESSAGE"
}
}
32-2. 再構築後の params_map 例
{
"TEMP": 32.8,
"HUM": 70.1,
"CO2": 1180,
"DEVICE_ID": "esp32_C80CABF16E20",
"CAPTURED_AT": "2026-04-19 15:15:00",
"result.alert_level.value": "warning",
"result.alert_message.value": "Device=esp32_C80CABF16E20 temp=32.8 hum=70.1 co2=1180"
}
このため後段では
{result.alert_level.value}{result.alert_message.value}
のように参照できます。
33. 通知用 TEMPLATEENTITY のサンプル
次は 2件目の TEMPLATEENTITY です。
33-1. TEMPLATEENTITY #402 のサンプル
{
"id": 402,
"uid": 1,
"group_id": "GROUP_ANALYZE_KINTONE",
"weight": 20,
"enabled": 1,
"title": "警告時だけLINE通知",
"params_json": {
"ALERT_LEVEL": "result.alert_level.value",
"ALERT_MESSAGE": "result.alert_message.value"
},
"rule_json": {
"if": {
"left": "ALERT_LEVEL",
"operator": "==",
"right": "warning"
},
"then": ["SEND_LINE_ALERT"],
"else": []
},
"template_json": {
"SEND_LINE_ALERT": {
"type": "COMMAND",
"plugin": "line_message_execute",
"command": {
"REPLYHEADER": "自動診断結果",
"REPLY": "{ALERT_MESSAGE}",
"LABEL": "警告",
"LEVEL": "{ALERT_LEVEL}"
}
}
}
}
この TEMPLATEENTITY は、前段 #401 が作った結果だけを見ています。
つまり、同じ GROUP 内では次のように流れます。
34. 実行前から実行後までの context 全体例
ここでは、KINTONERECEIVED で GROUPANALYZE_KINTONE が始まってから、通知まで進んだ場合の流れをまとめます。
34-1. GROUP 開始時の context
{
"uid": 1,
"trigger_type": "KINTONE_RECEIVED",
"input": {
"raws": {
"kintone": {
"records": [
{
"record_id": "5001",
"device_id": "esp32_C80CABF16E20",
"temp": 31.2,
"hum": 68.5,
"co2": 1024,
"captured_at": "2026-04-19 15:00:00"
},
{
"record_id": "5002",
"device_id": "esp32_C80CABF16E20",
"temp": 32.8,
"hum": 70.1,
"co2": 1180,
"captured_at": "2026-04-19 15:15:00"
}
]
}
},
"results": {}
},
"meta": {
"parent_trigger": "CRON_15M"
}
}
34-2. #401 実行後の context
{
"uid": 1,
"trigger_type": "KINTONE_RECEIVED",
"input": {
"raws": {
"kintone": {
"records": [
{
"record_id": "5001",
"device_id": "esp32_C80CABF16E20",
"temp": 31.2,
"hum": 68.5,
"co2": 1024,
"captured_at": "2026-04-19 15:00:00"
},
{
"record_id": "5002",
"device_id": "esp32_C80CABF16E20",
"temp": 32.8,
"hum": 70.1,
"co2": 1180,
"captured_at": "2026-04-19 15:15:00"
}
]
}
},
"results": {
"alert_level": {
"key": "alert_level",
"value": "warning",
"type": "LITERAL",
"template_id": "CALC_ALERT_LEVEL"
},
"alert_message": {
"key": "alert_message",
"value": "Device=esp32_C80CABF16E20 temp=32.8 hum=70.1 co2=1180",
"type": "EXPRESSION",
"template_id": "BUILD_ALERT_MESSAGE"
}
},
"params_map": {
"TEMP": 32.8,
"HUM": 70.1,
"CO2": 1180,
"DEVICE_ID": "esp32_C80CABF16E20",
"CAPTURED_AT": "2026-04-19 15:15:00",
"result.alert_level.value": "warning",
"result.alert_message.value": "Device=esp32_C80CABF16E20 temp=32.8 hum=70.1 co2=1180"
}
}
}
34-3. #402 実行時にプラグインへ渡る command 例
{
"REPLYHEADER": "自動診断結果",
"REPLY": "Device=esp32_C80CABF16E20 temp=32.8 hum=70.1 co2=1180",
"LABEL": "警告",
"LEVEL": "warning"
}
35. MQTT 受信のサンプルJSON版
次は、IoT 開発者向けにより身近な MQTT を例にします。
35-1. ESP32 から来る payload 例
{
"schema": "swpf.env.v1",
"type": "env",
"device_id": "esp32_C80CABF16E20",
"ts": 1776142631,
"temperature": 26.5,
"humidity": 47.05,
"lux": 237.49,
"pressure_hpa": 1013.8,
"co2": 812,
"soil_moisture": 41,
"wifi_rssi": -51,
"free_heap": 208740,
"min_free_heap": 199984,
"uptime_sec": 12
}
35-2. これを受けた最初の rawData イメージ
{
"mqtt": {
"topic": "swpf/user001/esp32_C80CABF16E20/env",
"payload": {
"schema": "swpf.env.v1",
"type": "env",
"device_id": "esp32_C80CABF16E20",
"ts": 1776142631,
"temperature": 26.5,
"humidity": 47.05,
"lux": 237.49,
"pressure_hpa": 1013.8,
"co2": 812,
"soil_moisture": 41,
"wifi_rssi": -51,
"free_heap": 208740,
"min_free_heap": 199984,
"uptime_sec": 12
}
}
}
35-3. これに対する PARAMS_JSON 例
{
"TEMP": "mqtt.payload.temperature",
"HUM": "mqtt.payload.humidity",
"CO2": "mqtt.payload.co2",
"SOIL": "mqtt.payload.soil_moisture",
"RSSI": "mqtt.payload.wifi_rssi",
"FREE_HEAP": "mqtt.payload.free_heap",
"MIN_HEAP": "mqtt.payload.min_free_heap",
"DEVICE_ID": "mqtt.payload.device_id"
}
35-4. ルール例
{
"if": {
"op": "OR",
"conditions": [
{
"left": "SOIL",
"operator": "<=",
"right": 20
},
{
"left": "FREE_HEAP",
"operator": "<=",
"right": 120000
}
]
},
"then": ["BUILD_MQTT_ALERT", "SEND_LINE_ALERT"],
"else": []
}
意味:
- 土壌水分が低すぎる
- またはヒープ残量が少なすぎる
なら警告する。
これは IoT 運用ではかなり実用的です。
36. LITERAL / EXPRESSION / COMMAND を一列で使うサンプル
ここでは、1つの TEMPLATEENTITY 内で 3種類を混ぜるイメージを示します。
36-1. 例
{
"params_json": {
"TEMP": "mqtt.payload.temperature",
"HUM": "mqtt.payload.humidity"
},
"rule_json": {
"if": {
"left": "TEMP",
"operator": ">=",
"right": 30
},
"then": ["SET_STATUS", "MAKE_MESSAGE", "SEND_LINE"],
"else": []
},
"template_json": {
"SET_STATUS": {
"type": "LITERAL",
"result_key": "status",
"value": "high_temp"
},
"MAKE_MESSAGE": {
"type": "EXPRESSION",
"result_key": "message",
"expr": "'温度=' ~ {TEMP} ~ ' 湿度=' ~ {HUM}"
},
"SEND_LINE": {
"type": "COMMAND",
"plugin": "line_message_execute",
"command": {
"REPLYHEADER": "センサー通知",
"REPLY": "{result.message.value}"
}
}
}
}
36-2. 順番の意味
SET_STATUSで固定値を結果へ入れるMAKE_MESSAGEで文字列を組み立てるSEND_LINEでそれを通知する
この並びは、PLC や Node-RED のフロー感覚で理解すると分かりやすいです。
37. GROUP をまたぐとデータが消える例 / 消えない例
ここは誤解しやすいので、サンプルで明確にします。
37-1. 同じ GROUP 内なら使える例
GROUPANALYZEKINTONE の中:
- #401 が
result.alert_message.valueを作る - #402 がそれを使って LINE 送信する
これは OK です。
37-2. 別 GROUP ではそのまま使えない例
CRON_15M に対して次の2つが直接ぶら下がっているとします。
[
{
"trigger_type": "CRON_15M",
"template_group_id": "GROUP_A"
},
{
"trigger_type": "CRON_15M",
"template_group_id": "GROUP_B"
}
]
この場合:
GROUP_Aでresult.score.value=80を作ってもGROUP_Bではその値は見えません
図にするとこうです。
37-3. 別 GROUP へ渡したい場合の例
その場合は、GROUP_A のプラグイン内で次のように fire 時 rawData に積む必要があります。
{
"from_group_a": {
"score": 80,
"message": "warning"
}
}
つまり、別 GROUP に渡したい値は意識的に載せ替える必要があります。
38. プラグイン内 fire の具体例
38-1. 親プラグイン内でのイメージ
$context['input']['raws']['imported'] = [
'score' => 80,
'message' => 'warning',
'source' => 'kintone',
];
$fire_raws = $context['input']['raws'];
$fireService->fire($uid, 'ANALYZE_IMPORTED_DATA', $fire_raws, [
'parent_trigger' => 'KINTONE_RECEIVED',
]);
38-2. 子トリガ側で受ける rawData の例
{
"imported": {
"score": 80,
"message": "warning",
"source": "kintone"
}
}
38-3. 子 GROUP 側 PARAMS_JSON の例
{
"SCORE": "imported.score",
"MESSAGE": "imported.message",
"SOURCE": "imported.source"
}
このようにすると、子 GROUP 側で
SCOREMESSAGESOURCE
として綺麗に参照できます。
39. 実際の開発でよくある構成パターン
ここでは、実用でよくあるパターンを JSON イメージ付きで整理します。
39-1. パターンA: 取得 GROUP と解析 GROUP を分ける
構成:
CRON15M→GROUPIMPORTGROUP_IMPORTの plugin が外部データ取得- plugin 内
fire('DATA_IMPORTED') DATAIMPORTED→GROUPANALYZE
利点:
- 取得処理と解析処理を分離できる
- 解析 GROUP を他トリガからも再利用しやすい
39-2. パターンB: 1 GROUP に全部入れる
構成:
CRON15M→GROUPONEFLOW- Step1: 取得
- Step2: 判定
- Step3: 通知
利点:
- 設定数が少ない
- データ引き継ぎが分かりやすい
欠点:
- GROUP が巨大化しやすい
- 再利用しにくい
39-3. パターンC: デバイス別・通知先別に GROUP を分ける
構成例:
[
{
"trigger_type": "MQTT_RECEIVED",
"template_group_id": "GROUP_SENSOR_SAVE"
},
{
"trigger_type": "MQTT_RECEIVED",
"template_group_id": "GROUP_SENSOR_ALERT"
}
]
意味:
- 受信したら保存系 GROUP を動かす
- 同時に通知系 GROUP も動かす
ただし繰り返しですが、この2つは独立実行です。
40. サンプル: メール送信版 TEMPLATE_JSON
LINE だけでなくメールも同じ考え方で動きます。
40-1. COMMAND 例
{
"SEND_MAIL_ALERT": {
"type": "COMMAND",
"plugin": "mail_send_execute",
"command": {
"subject": "[警告] {DEVICE_ID} のセンサー異常",
"body": "時刻={CAPTURED_AT}\n{result.alert_message.value}",
"to": "admin@example.com"
}
}
}
このように、COMMAND は
- プラグイン種類
- そのプラグインに渡すデータ
を定義する層です。
つまり、TEMPLATE_JSON は「関数呼び出しの引数定義」にかなり近いです。
41. サンプル: ChatGPT 呼び出し版 TEMPLATE_JSON
AI 系の実行も、考え方は同じです。
41-1. 例
{
"SEND_TO_GPT": {
"type": "COMMAND",
"plugin": "chatgpt_execute",
"command": {
"PROMPT": "次の環境データを解析してください。TEMP={TEMP}, HUM={HUM}, CO2={CO2}",
"SYSTEM": "あなたは栽培診断アシスタントです",
"RESULT_KEY": "gpt_diagnosis"
}
}
}
このプラグインが結果を
{
"input": {
"raws": {
"chatgpt": {
"diagnosis": "高温とCO2上昇が見られます。換気を推奨します。"
}
},
"results": {
"gpt_diagnosis": {
"key": "gpt_diagnosis",
"value": "高温とCO2上昇が見られます。換気を推奨します。",
"type": "COMMAND_RESULT"
}
}
}
}
のように返せば、後段のテンプレートからさらに利用できます。
42. 実際に設計するときのおすすめ分割
JSON 例を見たうえで、開発者向けにおすすめの分割を書くとこうです。
42-1. まず TriggerEntity を設計する
最初に決めるもの:
- どのイベントで起動するか
- そのイベントで何グループ動かすか
42-2. 次に GROUP の責務を決める
GROUP は 1つに何でも詰め込まず、たとえば次の責務に分けると分かりやすいです。
- IMPORT_GROUP: 外部取得
- ANALYZE_GROUP: 判定・計算
- NOTIFY_GROUP: 通知
- SAVE_GROUP: 保存
42-3. TEMPLATEENTITY は「1ステップ1責務」にする
たとえば:
- Step1: 必要な値を別名化して判定
- Step2: 中間メッセージを作成
- Step3: 通知
のようにすると、デバッグしやすいです。
43. デバッグ時に JSON をどう見るとよいか
実際にハマりやすい順で確認ポイントを書きます。
43-1. まず input.raws を見る
期待した元データが本当に入っているか確認します。
例:
{
"mqtt": {
"payload": {
"temperature": 26.5
}
}
}
ここが違っていたら、以降全部ずれます。
43-2. 次に params_map を見る
PARAMS_JSON が正しく効いていれば、ここで短い名前に変換されています。
例:
{
"TEMP": 26.5
}
もし無ければ:
- PARAMS_JSON のパス誤り
- rawData の構造認識違い
が疑わしいです。
43-3. 次に input.results を見る
EXPRESSION / LITERAL の結果がここに出ます。
例:
{
"status": {
"value": "warning"
}
}
43-4. 最後に COMMAND へ渡る値を見る
通知文や保存値が展開後にどうなっているかを確認します。
例:
{
"REPLY": "Device=esp32_C80CABF16E20 temp=32.8 hum=70.1 co2=1180"
}
ここまで合っていれば、あとはプラグイン内部の問題に絞れます。
44. よくあるミスと JSON 上の見え方
44-1. PARAMS_JSON のキー先を間違える
誤り例:
{
"TEMP": "mqtt.temperature"
}
実データが mqtt.payload.temperature なら、これは解決できません。
結果:
{
"TEMP": null
}
または TEMP 自体が生成されないことがあります。
44-2. result の参照先を間違える
誤り例:
{
"ALERT_MESSAGE": "result.alert_message"
}
実際には result.alert_message.value であるなら、値ではなく構造体を見てしまう、または空になります。
44-3. GROUP をまたいで値が見えると思い込む
これはかなり多いです。
- GROUP_A で作った
result.score.value - GROUP_B でそのまま参照
は基本的にできません。
必要なら:
- 保存する
- fire 時 rawData に積み替える
のどちらかが必要です。
45. 開発者向けの最終理解モデル
サンプルJSONまで含めて、最終的には次のように理解すると設計しやすいです。
45-1. TriggerEntity はイベントルーティング
trigger_type -> group_id
45-2. GROUP はパイプライン
Template #1 -> Template #2 -> Template #3
45-3. PARAMS_JSON は変数展開のための別名表
実データパス -> ルールで使う短い名前
45-4. RULE_JSON は分岐器
条件成立ならどの template_id を実行するか
45-5. TEMPLATE_JSON は命令辞書
template_id -> 実際の処理内容
45-6. input.raws / results / params_map は役割が違う
raws = 元データ・構造体
results = 中間生成物
params_map = 評価/置換のための見やすい参照表
46. 付録: 最小サンプル一式
最後に、最小構成のサンプルをひとまとめに置きます。
46-1. TriggerEntity
{
"uid": 1,
"enabled": 1,
"trigger_type": "MQTT_RECEIVED",
"template_group_id": "GROUP_SENSOR_ALERT",
"weight": 10
}
46-2. TemplateEntity
{
"uid": 1,
"group_id": "GROUP_SENSOR_ALERT",
"weight": 10,
"enabled": 1,
"params_json": {
"TEMP": "mqtt.payload.temperature",
"CO2": "mqtt.payload.co2",
"DEVICE_ID": "mqtt.payload.device_id"
},
"rule_json": {
"if": {
"op": "OR",
"conditions": [
{
"left": "TEMP",
"operator": ">=",
"right": 30
},
{
"left": "CO2",
"operator": ">=",
"right": 1000
}
]
},
"then": ["BUILD_MSG", "SEND_LINE"],
"else": []
},
"template_json": {
"BUILD_MSG": {
"type": "EXPRESSION",
"result_key": "message",
"expr": "'device=' ~ {DEVICE_ID} ~ ' temp=' ~ {TEMP} ~ ' co2=' ~ {CO2}"
},
"SEND_LINE": {
"type": "COMMAND",
"plugin": "line_message_execute",
"command": {
"REPLYHEADER": "MQTT警告",
"REPLY": "{result.message.value}"
}
}
}
}
46-3. 受信 payload
{
"mqtt": {
"payload": {
"device_id": "esp32_C80CABF16E20",
"temperature": 31.4,
"co2": 1050
}
}
}
46-4. 実行後の主な結果
{
"input": {
"results": {
"message": {
"value": "device=esp32_C80CABF16E20 temp=31.4 co2=1050"
}
}
}
}
これだけでも、SWPF AUTOMATION の本質である
- イベントで起動
- GROUP を選択
- PARAMS_MAP を作成
- RULE で分岐
- TEMPLATE で実処理
- 結果を次へ渡す
という流れが成立しています。
47. 補足まとめ
今回追加したサンプルJSON付き版で見てほしいポイントは次の通りです。
- TriggerEntity は event → group の対応表
- TemplateEntity は 1ステップの設定本体
- PARAMS_JSON は rawData から使いやすい名前へ引き直す定義
- RULEJSON は templateid を選ぶ分岐
- TEMPLATE_JSON は COMMAND / EXPRESSION / LITERAL の命令辞書
- 同一 GROUP 内だけ results を自然に引き継げる
- 別 GROUP に渡すなら fire 時 rawData に載せる必要がある