🔬 Node-RED Filterノード ガイド

このガイドでは、Node-REDのFilterノード(RBE: Report by Exception)について、初心者の方でも理解できるように詳しく説明します。

📚 1. Filterノードとは?

🤔 「Filter」って何?

Filterノードは、値が変化したときだけメッセージを通過させるノードです。 日常生活で例えると、変化を検知するセンサーのようなものです。

🚨 動体検知センサーに例えると:

📦 基本的な動作

Inject Split →→→ Filter Debug

Filterノードは、連続して同じ値が来た場合に重複を除去してくれます。 センサーデータの処理やイベント通知の最適化に非常に有効です。

📊 フィルタリングの例(値の変化のみ通過):

入力:
1 2 2 3 3 3 1
↓ Filter ↓
出力:
1 2 2 3 3 3 1

✅ 通過(変化あり) / ❌ ブロック(前回と同じ)

⚙️ 2. Filterノードの4つのモード

rbe 変化時のみ通過

前回と異なる値のみ通過させる

入力: 1, 2, 2, 3 出力: 1, 2, 3 (重複する2を除去)

rbei 初期値を無視

最初のメッセージを無視し、以降の変化のみ通過

入力: 1, 2, 2, 3 出力: 2, 3 (最初の1を無視)

deadband デッドバンド

指定した範囲を超える変化のみ通過

設定: 50%以上の変化 入力: 10, 12, 20, 21 出力: 10, 20 (微小変化を無視)

narrowband ナローバンド

特定の値(範囲)のみ通過

設定: 値2、範囲±1 入力: 1, 2, 2, 3, 4 出力: 1, 2, 2, 3 (範囲内の値のみ)

📋 設定項目一覧

設定項目 説明 対応モード
モード フィルタリング方式を選択 全モード共通
プロパティ 比較対象のプロパティ(デフォルト: payload) 全モード共通
トピックごとに比較 msg.topicごとに個別に前回値を保持 全モード共通
トピックプロパティ トピックとして使用するプロパティ名(デフォルト: topic) 全モード共通
値の範囲/変化量 数値または%で指定(例: 10, 50%) デッドバンド/ナローバンド
初期値 比較の基準値(ナローバンドの中心値) ナローバンド
出力タイミング 値が範囲に入った時 / 出た時 デッドバンド/ナローバンド
名前 ノードの表示名 全モード共通

🎯 モード詳細比較

モード 動作 主な用途
rbe 値が変化したときのみ通過 状態変化の検知、重複排除
rbei 最初の値を無視、以降の変化のみ通過 起動時の初期値を無視したい場合
deadband 指定範囲を超える変化のみ通過 センサーのノイズ除去、微小変化の無視
narrowband 指定範囲内の値のみ通過 特定の値域の監視、閾値アラート

🎯 3. 実用的な使用パターン

📥 サンプルフローのインポート方法:

  1. 下のサンプルフローJSONをコピー
  2. Node-REDエディタで メニュー → 読み込み を選択
  3. JSONをペーストして「読み込み」をクリック

このサンプルフローには、以下で説明する全パターンの実例が含まれています。

📋 サンプルフロー(クリックで展開)

参照元:NodeREDエディター内サンプルフロー

[ { "id": "1780d8815c9004cf", "type": "tab", "label": "filter", "disabled": false, "info": "", "env": [] }, { "id": "9573fa4b057fe7b2", "type": "comment", "z": "1780d8815c9004cf", "name": "1 Report all changes", "info": "フィルタノードに3つのメッセージ(1、2、2)が送信された場合、最初のメッセージ(1と2)はそれぞれ順に通過します。最後のメッセージ(2)は、前回のメッセージ送信以降に変更が確認されなかったため、フィルタリングされます。", "x": 210, "y": 80, "wires": [] }, { "id": "5adf6b757e2a7bb2", "type": "rbe", "z": "1780d8815c9004cf", "name": "Only report changes", "func": "rbe", "gap": "", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 500, "y": 140, "wires": [["6682a9e8826ad09b"]] }, { "id": "f60dc26f8d634312", "type": "inject", "z": "1780d8815c9004cf", "name": "[1,2,2]", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[1,2,2]", "payloadType": "json", "x": 210, "y": 140, "wires": [["5b2b61bb112b23e7"]] }, { "id": "6682a9e8826ad09b", "type": "debug", "z": "1780d8815c9004cf", "name": "Print changes", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 700, "y": 140, "wires": [] }, { "id": "5b2b61bb112b23e7", "type": "split", "z": "1780d8815c9004cf", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 330, "y": 140, "wires": [["5adf6b757e2a7bb2"]] }, { "id": "79e041e499ad21e9", "type": "comment", "z": "1780d8815c9004cf", "name": "2 Report changes, ignore the initial value", "info": "初期値がセンティネル値として使用され、その後の変更が転送される。", "x": 280, "y": 260, "wires": [] }, { "id": "d5c20441fc01294b", "type": "rbe", "z": "1780d8815c9004cf", "name": "Ignore first, report changes", "func": "rbei", "gap": "", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 520, "y": 320, "wires": [["c5845cc63c81f81d"]] }, { "id": "eef24d9b8c3f98c7", "type": "inject", "z": "1780d8815c9004cf", "name": "[1,2,2]", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[1,2,2]", "payloadType": "json", "x": 210, "y": 320, "wires": [["9e8e79801228c3bb"]] }, { "id": "c5845cc63c81f81d", "type": "debug", "z": "1780d8815c9004cf", "name": "Print changes", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 740, "y": 320, "wires": [] }, { "id": "9e8e79801228c3bb", "type": "split", "z": "1780d8815c9004cf", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 330, "y": 320, "wires": [["d5c20441fc01294b"]] }, { "id": "c6ddaefee2877538", "type": "comment", "z": "1780d8815c9004cf", "name": "3 Report changes larger than 50% (Deadband)", "info": "50%以上の変化のみを報告するデッドバンドモード", "x": 300, "y": 440, "wires": [] }, { "id": "af9390a840a0b28a", "type": "rbe", "z": "1780d8815c9004cf", "name": "Deadband 50%", "func": "deadbandEq", "gap": "50%", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 500, "y": 500, "wires": [["f76f04bfb73ad8db"]] }, { "id": "2a28d48e44fd6450", "type": "inject", "z": "1780d8815c9004cf", "name": "[1,2,2,1]", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[1,2,2,1]", "payloadType": "json", "x": 210, "y": 500, "wires": [["9155ade283e76ab6"]] }, { "id": "f76f04bfb73ad8db", "type": "debug", "z": "1780d8815c9004cf", "name": "Print changes", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 700, "y": 500, "wires": [] }, { "id": "9155ade283e76ab6", "type": "split", "z": "1780d8815c9004cf", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 330, "y": 500, "wires": [["af9390a840a0b28a"]] }, { "id": "9867760eef458ee6", "type": "comment", "z": "1780d8815c9004cf", "name": "4 Report only specific value (Narrowband)", "info": "特定の値(2)のみを報告するナローバンドモード", "x": 290, "y": 620, "wires": [] }, { "id": "6b70bed3e7360f58", "type": "rbe", "z": "1780d8815c9004cf", "name": "Only send 2's (±1)", "func": "narrowbandEq", "gap": "1", "start": "2", "inout": "out", "septopics": false, "property": "payload", "topi": "topic", "x": 510, "y": 680, "wires": [["5f02aa2b43499ca8"]] }, { "id": "a5b05fcbe6fdf53e", "type": "inject", "z": "1780d8815c9004cf", "name": "[1,2,2,3,4]", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[1,2,2,3,4]", "payloadType": "json", "x": 210, "y": 680, "wires": [["e58f98ecb220d7f3"]] }, { "id": "5f02aa2b43499ca8", "type": "debug", "z": "1780d8815c9004cf", "name": "Print values", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 710, "y": 680, "wires": [] }, { "id": "e58f98ecb220d7f3", "type": "split", "z": "1780d8815c9004cf", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 330, "y": 680, "wires": [["6b70bed3e7360f58"]] } ]

パターン1: 変化時のみ通過(rbe)

用途: 連続する同じ値を除去し、変化があったときだけ通過

Inject
[1,2,2]
Split →→→ Filter
(rbe)
Debug
入力:
1 2 2
出力:
1 2 2

設定例:

モード: 値が変化した時にのみブロック(rbe) プロパティ: msg.payload トピックごとに比較: ✓ 入力: 1 → 2 → 2 出力: 1 → 2(最後の2は前回と同じなのでブロック)

パターン2: 初期値を無視(rbei)

用途: 起動時の最初の値を無視し、以降の変化のみ検知

Inject
[1,2,2]
Split →→→ Filter
(rbei)
Debug
入力:
1 2 2
出力:
1 2 2

📌 rbeiの使い所:

設定例:

モード: 初期値を無視、変化時のみ(rbei) プロパティ: msg.payload 入力: 1 → 2 → 2 出力: 2(最初の1は無視、最後の2は前回と同じ)

パターン3: デッドバンド(大きな変化のみ)

用途: 指定した範囲を超える変化のみ通過(ノイズ除去)

Inject
[1,2,2,1]
Split →→→ Filter
(50%)
Debug

デッドバンド 50% の動作イメージ:

前回の値を基準に、±50%の変化は無視

前回値
-50%以下
→通過
±50%以内
→ブロック
+50%以上
→通過

50%デッドバンドの例:

入力:
1 2 2 1
出力:
1 2 2 1

1→2: 100%変化 ✅ | 2→2: 0%変化 ❌ | 2→1: 50%変化 ✅

設定例:

モード: デッドバンド(deadband) 値の範囲: 50% 比較: 値が範囲外に出た時に送信 入力: 1 → 2 → 2 → 1 1→2: 100%変化(50%超)→ 通過 2→2: 0%変化 → ブロック 2→1: 50%変化(境界)→ 通過 出力: 1 → 2 → 1

パターン4: ナローバンド(特定値のみ)

用途: 特定の値(範囲)に入ったときのみ通過

Inject
[1,2,2,3,4]
Split →→→ Filter
(値2, ±1)
Debug

ナローバンド(中心値: 2, 範囲: ±1)の例:

入力:
1 2 2 3 4
出力:
1 2 2 3 4

範囲: 2±1 = 1〜3 → 通過 | 4は範囲外 → ブロック

設定例:

モード: ナローバンド(narrowband) 初期値(中心): 2 値の範囲: 1 入力: 1, 2, 2, 3, 4 1: 範囲内(2±1=1〜3) → 通過 2: 範囲内 → 通過 2: 範囲内 → 通過 3: 範囲内 → 通過 4: 範囲外 → ブロック 出力: 1, 2, 2, 3

🏋️ 4. 実践演習

演習1: 基本的な重複除去初級

📝 課題:

スイッチのON/OFF状態の変化のみを検知するフローを作成してください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Injectノードの設定:

  • payload: JSON型で ["ON", "ON", "OFF", "OFF", "ON"]

Splitノードの設定:

  • デフォルト設定でOK(配列を分割)

Filterノードの設定:

  • モード: 値が変化した時にのみブロック(rbe)
  • プロパティ: msg.payload
✅ 解答例フロー
[ { "id": "ex1_inject", "type": "inject", "name": "スイッチ状態", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[\"ON\", \"ON\", \"OFF\", \"OFF\", \"ON\"]", "payloadType": "json", "x": 160, "y": 100, "wires": [["ex1_split"]] }, { "id": "ex1_split", "type": "split", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 310, "y": 100, "wires": [["ex1_filter"]] }, { "id": "ex1_filter", "type": "rbe", "name": "変化検知", "func": "rbe", "gap": "", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 460, "y": 100, "wires": [["ex1_debug"]] }, { "id": "ex1_debug", "type": "debug", "name": "状態変化", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 620, "y": 100, "wires": [] } ]

演習2: センサーノイズの除去中級

📝 課題:

温度センサーの微小な変動(±2度以内)を無視し、大きな変化のみ検知してください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Injectノードの設定:

  • payload: JSON型で [20, 21, 22, 25, 24, 30]

Filterノードの設定:

  • モード: デッドバンド
  • 値の範囲: 2(数値、%なし)
  • 比較対象外なら: 値を出力しない

動作の確認:

  • 20→21: 1度差 → ブロック
  • 20→22: 2度差 → ブロック(境界)
  • 20→25: 5度差 → 通過
✅ 解答例フロー
[ { "id": "ex2_inject", "type": "inject", "name": "温度データ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[20, 21, 22, 25, 24, 30]", "payloadType": "json", "x": 160, "y": 100, "wires": [["ex2_split"]] }, { "id": "ex2_split", "type": "split", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 310, "y": 100, "wires": [["ex2_filter"]] }, { "id": "ex2_filter", "type": "rbe", "name": "ノイズ除去(±2)", "func": "deadbandEq", "gap": "2", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 480, "y": 100, "wires": [["ex2_debug"]] }, { "id": "ex2_debug", "type": "debug", "name": "有効な変化", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 650, "y": 100, "wires": [] } ]

演習3: 閾値アラート中級

📝 課題:

温度が25〜30度の範囲に入ったときだけアラートを出すフローを作成してください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Filterノードの設定:

  • モード: ナローバンド
  • 初期値(中心): 27.5
  • 値の範囲: 2.5

範囲計算:

  • 27.5 - 2.5 = 25(下限)
  • 27.5 + 2.5 = 30(上限)
✅ 解答例フロー
[ { "id": "ex3_inject", "type": "inject", "name": "温度データ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[20, 25, 27, 30, 35, 28]", "payloadType": "json", "x": 160, "y": 100, "wires": [["ex3_split"]] }, { "id": "ex3_split", "type": "split", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 310, "y": 100, "wires": [["ex3_filter"]] }, { "id": "ex3_filter", "type": "rbe", "name": "25-30度アラート", "func": "narrowbandEq", "gap": "2.5", "start": "27.5", "inout": "out", "septopics": false, "property": "payload", "topi": "topic", "x": 480, "y": 100, "wires": [["ex3_debug"]] }, { "id": "ex3_debug", "type": "debug", "name": "アラート範囲", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "x": 660, "y": 100, "wires": [] } ]

演習4: マルチセンサーの変化検知上級

📝 課題:

複数のセンサー(温度、湿度)からのデータを、センサーごとに独立して変化検知してください。

🎯 要求仕様:

📊 期待される動作:

✅ 成功の条件:

💡 ヒント

Injectノードの設定:

  • JSON配列で複数センサーデータをシミュレート
  • 各データに topic を設定

Changeノードの設定:

  • msg.topic = msg.payload.sensor
  • msg.payload = msg.payload.value

Filterノードの設定:

  • モード: rbe
  • トピックごとに比較: ✓(重要!)
✅ 解答例フロー
[ { "id": "ex4_inject", "type": "inject", "name": "センサーデータ", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "[{\"sensor\":\"temp\",\"value\":25},{\"sensor\":\"humidity\",\"value\":60},{\"sensor\":\"temp\",\"value\":25},{\"sensor\":\"humidity\",\"value\":65},{\"sensor\":\"temp\",\"value\":26},{\"sensor\":\"humidity\",\"value\":65}]", "payloadType": "json", "x": 160, "y": 100, "wires": [["ex4_split"]] }, { "id": "ex4_split", "type": "split", "name": "", "splt": "\\n", "spltType": "str", "arraySplt": 1, "arraySpltType": "len", "stream": false, "addname": "", "x": 310, "y": 100, "wires": [["ex4_change"]] }, { "id": "ex4_change", "type": "change", "name": "topic設定", "rules": [ {"t": "set", "p": "topic", "pt": "msg", "to": "payload.sensor", "tot": "msg"}, {"t": "set", "p": "payload", "pt": "msg", "to": "payload.value", "tot": "msg"} ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 460, "y": 100, "wires": [["ex4_filter"]] }, { "id": "ex4_filter", "type": "rbe", "name": "センサー別変化検知", "func": "rbe", "gap": "", "start": "", "inout": "out", "septopics": true, "property": "payload", "topi": "topic", "x": 640, "y": 100, "wires": [["ex4_debug"]] }, { "id": "ex4_debug", "type": "debug", "name": "変化あり", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "true", "targetType": "full", "x": 820, "y": 100, "wires": [] } ]

🎓 5. まとめ

Filterノードの重要ポイント

⚠️ よくある間違い

📚 次のステップ

Filterノードをマスターしたら、以下のノードも学んでみましょう:

🔧 6. トラブルシューティング

よくある問題と解決方法

問題 原因 解決方法
最初のメッセージが通過しない rbeiモードになっている rbeモードに変更
異なるセンサーの値が混同される トピック別比較が無効 「トピックごとに比較」を有効化
デッドバンドが期待通りに動かない %と絶対値の混同 設定値を確認(「50」vs「50%」)
再起動後に最初の値が通過する 前回値がリセットされた 仕様です(または永続化を検討)
オブジェクトの比較がうまくいかない プロパティの順序が異なる 特定のプロパティのみを比較対象に
ナローバンドで範囲外も通過する 設定の誤解 「範囲外に出た時に送信」をチェック

💡 7. 実務での活用例

ケース1: IoTセンサーのノイズ除去

温度センサー(0.1度単位で変動) ↓ Filterノード(デッドバンド: 0.5度) ↓ 0.5度以上の変化のみデータベースに保存 ↓ ストレージ容量の大幅削減

ケース2: スマートホームの状態管理

ドアセンサー(OPEN/CLOSE連続送信) ↓ Filterノード(rbeモード) ↓ 状態変化時のみ通知 ↓ 「ドアが開きました」「ドアが閉まりました」

ケース3: 工場の異常検知

複数の機械(マシンA, B, C)からの振動データ ↓ Filterノード(トピック別、デッドバンド10%) ↓ 各機械で10%以上の変動があればアラート ↓ 予防保全システムへ連携

ケース4: 株価モニタリング

リアルタイム株価データ(1秒ごと) ↓ Filterノード(デッドバンド: 1%) ↓ 1%以上の変動時のみ通知 ↓ トレーダーへプッシュ通知

📖 8. モード比較早見表

入力データ rbe rbei deadband 50% narrowband (2±1)
1 ✅ 1 ✅ 1 ✅ 1
2 ✅ 2 ✅ 2 ✅ 2 ✅ 2
2 ✅ 2
3 ✅ 3 ✅ 3 ✅ 3 ✅ 3
4 ✅ 4 ✅ 4

🔗 9. 追加リソース


このガイドが役に立ちましたら、実際のプロジェクトで練習してみてください!
Filterノードはセンサーデータ処理の必須ツールです。

参照元:NodeREDエディター内サンプルフロー

🏠