👁️ Node-RED Watch ノードガイド

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

📚 1. Watchノードとは?

🤔 「Watch」って何?

Watchノードは、ファイルやディレクトリの変更を監視し、変更があった際にメッセージを出力するノードです。

🔔 警備員に例えると:

📦 基本的な動作

Watch Debug

Watchノードは、指定したファイルやディレクトリを常時監視し、ファイルの追加・変更・削除があると自動的にメッセージを送信します。

💡 主な用途:

⚙️ 2. Watchノードの設定

📋 設定項目

設定項目説明
ファイル監視するファイル/ディレクトリのパス(カンマ区切りで複数可)/home/pi/uploads, /tmp/logs
サブディレクトリを再帰的に監視サブディレクトリも再帰的に監視true / false
名前ノードの表示名アップロード監視

🔧 パス指定のルール

OSパス形式
Linux / Macスラッシュ区切り/home/pi/data
WindowsダブルバックスラッシュC:\\Users\\data
スペースを含むパスダブルクォートで囲む"/home/pi/my folder"

📤 出力メッセージ

ファイルやディレクトリに変更があると、以下のプロパティを持つメッセージが出力されます:

プロパティ説明
msg.payload変更されたファイルのフルパス/home/pi/uploads/image.jpg
msg.filenamemsg.payload と同じ(フルパス)/home/pi/uploads/image.jpg
msg.fileファイル名のみ(短縮形)image.jpg
msg.type変更されたものの種類file / directory
msg.sizeファイルサイズ(バイト)1024
msg.topic監視リスト(文字列化)/home/pi/uploads

⚠️ 重要な制約:

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

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

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

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

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

※ 使用前に /tmp/uploads, /tmp/config.json, /tmp/data ディレクトリを作成してください

[ { "id": "watch_sample_tab", "type": "tab", "label": "Watch サンプル", "disabled": false, "info": "" }, { "id": "watch_comment1", "type": "comment", "z": "watch_sample_tab", "name": "━━━ パターン1: 基本的なファイル監視 ━━━", "info": "", "x": 220, "y": 40, "wires": [] }, { "id": "watch_basic", "type": "watch", "z": "watch_sample_tab", "name": "uploads監視", "files": "/tmp/uploads", "recursive": true, "x": 140, "y": 100, "wires": [["watch_debug1"]] }, { "id": "watch_debug1", "type": "debug", "z": "watch_sample_tab", "name": "変更検知", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "true", "targetType": "full", "statusVal": "file", "statusType": "msg", "x": 330, "y": 100, "wires": [] }, { "id": "watch_comment2", "type": "comment", "z": "watch_sample_tab", "name": "━━━ パターン2: ファイル変更 → 内容読み込み ━━━", "info": "", "x": 240, "y": 180, "wires": [] }, { "id": "watch_read", "type": "watch", "z": "watch_sample_tab", "name": "設定ファイル監視", "files": "/tmp/config.json", "recursive": false, "x": 160, "y": 240, "wires": [["watch_file_read"]] }, { "id": "watch_file_read", "type": "file in", "z": "watch_sample_tab", "name": "設定読み込み", "filename": "", "filenameType": "msg", "format": "utf8", "chunk": false, "sendError": false, "encoding": "utf8", "allProps": false, "x": 360, "y": 240, "wires": [["watch_json_parse"]] }, { "id": "watch_json_parse", "type": "json", "z": "watch_sample_tab", "name": "JSON解析", "property": "payload", "action": "", "pretty": false, "x": 530, "y": 240, "wires": [["watch_debug2"]] }, { "id": "watch_debug2", "type": "debug", "z": "watch_sample_tab", "name": "設定内容", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 690, "y": 240, "wires": [] }, { "id": "watch_comment3", "type": "comment", "z": "watch_sample_tab", "name": "━━━ パターン3: ファイル種別による振り分け ━━━", "info": "", "x": 240, "y": 320, "wires": [] }, { "id": "watch_filter", "type": "watch", "z": "watch_sample_tab", "name": "データフォルダ監視", "files": "/tmp/data", "recursive": true, "x": 170, "y": 380, "wires": [["watch_switch"]] }, { "id": "watch_switch", "type": "switch", "z": "watch_sample_tab", "name": "拡張子で振り分け", "property": "file", "propertyType": "msg", "rules": [ {"t": "regex", "v": "\\.csv$", "vt": "str", "case": true}, {"t": "regex", "v": "\\.(jpg|png|gif)$", "vt": "str", "case": true}, {"t": "regex", "v": "\\.json$", "vt": "str", "case": true}, {"t": "else"} ], "checkall": "false", "repair": false, "outputs": 4, "x": 370, "y": 380, "wires": [["watch_debug_csv"], ["watch_debug_img"], ["watch_debug_json"], ["watch_debug_other"]] }, { "id": "watch_debug_csv", "type": "debug", "z": "watch_sample_tab", "name": "CSV処理", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "file", "targetType": "msg", "statusVal": "\"CSV: \" & file", "statusType": "jsonata", "x": 560, "y": 340, "wires": [] }, { "id": "watch_debug_img", "type": "debug", "z": "watch_sample_tab", "name": "画像処理", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "file", "targetType": "msg", "statusVal": "\"IMG: \" & file", "statusType": "jsonata", "x": 560, "y": 380, "wires": [] }, { "id": "watch_debug_json", "type": "debug", "z": "watch_sample_tab", "name": "JSON処理", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "file", "targetType": "msg", "statusVal": "\"JSON: \" & file", "statusType": "jsonata", "x": 560, "y": 420, "wires": [] }, { "id": "watch_debug_other", "type": "debug", "z": "watch_sample_tab", "name": "その他", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "file", "targetType": "msg", "statusVal": "\"OTHER: \" & file", "statusType": "jsonata", "x": 550, "y": 460, "wires": [] }, { "id": "watch_comment4", "type": "comment", "z": "watch_sample_tab", "name": "━━━ パターン4: ファイルサイズでフィルタ ━━━", "info": "", "x": 230, "y": 540, "wires": [] }, { "id": "watch_size", "type": "watch", "z": "watch_sample_tab", "name": "サイズ監視", "files": "/tmp/uploads", "recursive": false, "x": 140, "y": 600, "wires": [["watch_size_check"]] }, { "id": "watch_size_check", "type": "switch", "z": "watch_sample_tab", "name": "1MB以上?", "property": "size", "propertyType": "msg", "rules": [ {"t": "gte", "v": "1048576", "vt": "num"}, {"t": "else"} ], "checkall": "false", "repair": false, "outputs": 2, "x": 310, "y": 600, "wires": [["watch_debug_large"], ["watch_debug_small"]] }, { "id": "watch_debug_large", "type": "debug", "z": "watch_sample_tab", "name": "大きいファイル", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"大: \" & $string(size) & \" bytes\"", "statusType": "jsonata", "x": 500, "y": 580, "wires": [] }, { "id": "watch_debug_small", "type": "debug", "z": "watch_sample_tab", "name": "小さいファイル", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"小: \" & $string(size) & \" bytes\"", "statusType": "jsonata", "x": 500, "y": 620, "wires": [] }, { "id": "watch_comment5", "type": "comment", "z": "watch_sample_tab", "name": "━━━ テスト用: ファイル作成 ━━━", "info": "Injectボタンを押すとテストファイルが作成されます", "x": 200, "y": 700, "wires": [] }, { "id": "watch_test_inject", "type": "inject", "z": "watch_sample_tab", "name": "テストファイル作成", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "テストデータ: ", "payloadType": "str", "x": 170, "y": 760, "wires": [["watch_test_func"]] }, { "id": "watch_test_func", "type": "function", "z": "watch_sample_tab", "name": "タイムスタンプ追加", "func": "msg.payload = msg.payload + new Date().toISOString();\nmsg.filename = \"/tmp/uploads/test_\" + Date.now() + \".txt\";\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 760, "wires": [["watch_test_write"]] }, { "id": "watch_test_write", "type": "file", "z": "watch_sample_tab", "name": "ファイル書き込み", "filename": "", "filenameType": "msg", "appendNewline": true, "createDir": true, "overwriteFile": "true", "encoding": "utf8", "x": 570, "y": 760, "wires": [["watch_test_debug"]] }, { "id": "watch_test_debug", "type": "debug", "z": "watch_sample_tab", "name": "作成完了", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "filename", "targetType": "msg", "statusVal": "filename", "statusType": "msg", "x": 740, "y": 760, "wires": [] } ]

パターン1: 基本的なファイル監視

用途: 特定のディレクトリに新しいファイルが追加されたことを検知

Watch
(/tmp/uploads)
Debug

📌 動作の流れ:

  1. /tmp/uploads フォルダを常時監視
  2. ファイルが追加/変更されると検知
  3. ファイル情報(パス、名前、サイズ)をDebugに出力

出力例:

msg.payload = "/tmp/uploads/image.jpg" msg.file = "image.jpg" msg.type = "file" msg.size = 1024

パターン2: ファイル変更 → 内容読み込み

用途: 設定ファイルが更新されたら内容を自動的に読み込む

Watch
(config.json)
Read File JSON Debug

📌 動作の流れ:

  1. Watch が config.json の変更を検知
  2. Read File が msg.filename を使ってファイル内容を読み込み
  3. JSON ノードでオブジェクトに変換
  4. Debugで設定内容を確認

※ Watch が出力する msg.filename を Read File ノードで使用するのがポイント

パターン3: ファイル種別による振り分け

用途: 拡張子によって異なる処理を実行

Watch
(/tmp/data)
Switch
(拡張子)
CSV / 画像 / JSON / その他

📌 動作の流れ:

  1. Watch が /tmp/data フォルダを監視
  2. Switch ノードで msg.file を正規表現でマッチング
  3. 拡張子に応じて4つの出力に振り分け

Switchノードの設定:

プロパティ: msg.file ルール1: 正規表現 \.csv$ → CSV処理へ ルール2: 正規表現 \.(jpg|png|gif)$ → 画像処理へ ルール3: 正規表現 \.json$ → JSON処理へ ルール4: その他 → その他処理へ

パターン4: ファイルサイズでフィルタ

用途: 大きなファイルと小さなファイルで処理を分ける

Watch Switch
(size >= 1MB)
大 / 小

📌 動作の流れ:

  1. Watch が msg.size にファイルサイズを出力
  2. Switch ノードで 1MB (1048576 bytes) 以上かを判定
  3. 大きいファイルと小さいファイルで別々の処理へ

🏋️ 4. 実践演習

演習1: 基本的なディレクトリ監視初級

📝 課題:

/tmp/mywatch ディレクトリを監視し、ファイルが追加されたらファイル名とサイズをDebugに表示してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

準備:

  • 事前に mkdir /tmp/mywatch でディレクトリを作成

Watchノードの設定:

  • Files: /tmp/mywatch

Debugノードの設定:

  • 出力: メッセージ全体
✅ 解答例フロー
[ {"id": "ex1_tab", "type": "tab", "label": "演習1", "disabled": false, "info": ""}, {"id": "ex1_watch", "type": "watch", "name": "mywatch監視", "files": "/tmp/mywatch", "recursive": false, "x": 160, "y": 100, "wires": [["ex1_debug"]], "z": "ex1_tab"}, {"id": "ex1_debug", "type": "debug", "name": "ファイル情報", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "true", "targetType": "full", "statusVal": "file & \": \" & $string(size) & \" bytes\"", "statusType": "jsonata", "x": 370, "y": 100, "wires": [], "z": "ex1_tab"} ]

演習2: 設定ファイルの自動リロード中級

📝 課題:

JSONの設定ファイルを監視し、変更があったら内容を読み込んでグローバル変数に保存してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

Read Fileノードの設定:

  • ファイル名: msg.filename を使用(Watch が出力するパス)

Functionノード:

  • global.set("config", msg.payload);
✅ 解答例フロー
[ {"id": "ex2_tab", "type": "tab", "label": "演習2", "disabled": false, "info": ""}, {"id": "ex2_watch", "type": "watch", "name": "設定監視", "files": "/tmp/app_config.json", "recursive": false, "x": 140, "y": 100, "wires": [["ex2_read"]], "z": "ex2_tab"}, {"id": "ex2_read", "type": "file in", "name": "読み込み", "filename": "", "filenameType": "msg", "format": "utf8", "chunk": false, "sendError": false, "encoding": "utf8", "allProps": false, "x": 290, "y": 100, "wires": [["ex2_json"]], "z": "ex2_tab"}, {"id": "ex2_json", "type": "json", "name": "JSON解析", "property": "payload", "action": "", "pretty": false, "x": 430, "y": 100, "wires": [["ex2_func"]], "z": "ex2_tab"}, {"id": "ex2_func", "type": "function", "name": "グローバル保存", "func": "global.set(\"config\", msg.payload);\nnode.status({fill:\"green\", shape:\"dot\", text:\"設定更新: \" + new Date().toLocaleTimeString()});\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 590, "y": 100, "wires": [["ex2_debug"]], "z": "ex2_tab"}, {"id": "ex2_debug", "type": "debug", "name": "新設定", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 750, "y": 100, "wires": [], "z": "ex2_tab"} ]

演習3: 画像ファイルの自動検出中級

📝 課題:

アップロードフォルダを監視し、JPG/PNG画像のみを検出してファイル情報をログに記録してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

Switchノードの設定:

  • プロパティ: msg.file
  • 正規表現: \.(jpg|jpeg|png)$

Changeノードの設定:

  • JSONata式を使って msg.payload にログ文字列を設定
  • ログ形式: タイムスタンプ,ファイル名,サイズ
  • 式の例: $now('[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01].[f001]Z') & "," & file & "," & $string(size)
✅ 解答例フロー
[ {"id": "ex3_tab", "type": "tab", "label": "演習3", "disabled": false, "info": ""}, {"id": "ex3_watch", "type": "watch", "name": "画像フォルダ監視", "files": "/tmp/images", "recursive": true, "x": 170, "y": 100, "wires": [["ex3_switch"]], "z": "ex3_tab"}, {"id": "ex3_switch", "type": "switch", "name": "画像のみ", "property": "file", "propertyType": "msg", "rules": [{"t": "regex", "v": "\\.(jpg|jpeg|png)$", "vt": "str", "case": true}], "checkall": "true", "repair": false, "outputs": 1, "x": 350, "y": 100, "wires": [["ex3_change"]], "z": "ex3_tab"}, {"id": "ex3_change", "type": "change", "name": "ログ整形", "rules": [{"t": "set", "p": "payload", "pt": "msg", "to": "$now('[Y0001]-[M01]-[D01]T[H01]:[m01]:[s01].[f001]Z') & \",\" & file & \",\" & $string(size)", "tot": "jsonata"}], "x": 510, "y": 100, "wires": [["ex3_write", "ex3_debug"]], "z": "ex3_tab"}, {"id": "ex3_write", "type": "file", "name": "ログ追記", "filename": "/tmp/image_log.csv", "filenameType": "str", "appendNewline": true, "createDir": true, "overwriteFile": "false", "encoding": "utf8", "x": 680, "y": 80, "wires": [[]], "z": "ex3_tab"}, {"id": "ex3_debug", "type": "debug", "name": "画像検出", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "payload", "statusType": "msg", "x": 680, "y": 120, "wires": [], "z": "ex3_tab"} ]

演習4: FTPアップロード処理システム上級

📝 課題:

FTPフォルダを監視し、CSVファイルがアップロードされたら内容を解析してJSON形式に変換、処理結果を保存してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

なぜDelayが必要?

  • FTPアップロード中にWatchが検知することがある
  • 書き込み完了を待ってから読み込むと安全

CSVノードの設定:

  • 1行目をヘッダーとして使用: ON
✅ 解答例フロー
[ {"id": "ex4_tab", "type": "tab", "label": "演習4", "disabled": false, "info": ""}, {"id": "ex4_watch", "type": "watch", "name": "FTPフォルダ", "files": "/tmp/ftp_incoming", "recursive": false, "x": 140, "y": 100, "wires": [["ex4_filter"]], "z": "ex4_tab"}, {"id": "ex4_filter", "type": "switch", "name": "CSVのみ", "property": "file", "propertyType": "msg", "rules": [{"t": "regex", "v": "\\.csv$", "vt": "str", "case": true}], "checkall": "true", "repair": false, "outputs": 1, "x": 290, "y": 100, "wires": [["ex4_delay"]], "z": "ex4_tab"}, {"id": "ex4_delay", "type": "delay", "name": "2秒待機", "pauseType": "delay", "timeout": "2", "timeoutUnits": "seconds", "rate": "1", "nbRateUnits": "1", "rateUnits": "second", "randomFirst": "1", "randomLast": "5", "randomUnits": "seconds", "drop": false, "allowrate": false, "outputs": 1, "x": 420, "y": 100, "wires": [["ex4_read"]], "z": "ex4_tab"}, {"id": "ex4_read", "type": "file in", "name": "CSV読み込み", "filename": "", "filenameType": "msg", "format": "utf8", "chunk": false, "sendError": false, "encoding": "utf8", "allProps": false, "x": 560, "y": 100, "wires": [["ex4_csv"]], "z": "ex4_tab"}, {"id": "ex4_csv", "type": "csv", "name": "CSV解析", "sep": ",", "hdrin": true, "hdrout": "none", "multi": "mult", "ret": "\\n", "temp": "", "skip": "0", "strings": true, "include_empty_strings": false, "include_null_values": false, "x": 700, "y": 100, "wires": [["ex4_func"]], "z": "ex4_tab"}, {"id": "ex4_func", "type": "function", "name": "JSON変換", "func": "var baseName = msg.file.replace('.csv', '');\nvar result = {\n originalFile: msg.filename,\n processedAt: new Date().toISOString(),\n recordCount: msg.payload.length,\n data: msg.payload\n};\nmsg.payload = JSON.stringify(result, null, 2);\nmsg.outputFile = \"/tmp/ftp_processed/\" + baseName + \".json\";\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 150, "y": 180, "wires": [["ex4_write_json"]], "z": "ex4_tab"}, {"id": "ex4_write_json", "type": "file", "name": "JSON保存", "filename": "", "filenameType": "msg", "appendNewline": false, "createDir": true, "overwriteFile": "true", "encoding": "utf8", "x": 310, "y": 180, "wires": [["ex4_debug"]], "z": "ex4_tab"}, {"id": "ex4_debug", "type": "debug", "name": "処理完了", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "outputFile", "targetType": "msg", "statusVal": "\"処理完了: \" & file", "statusType": "jsonata", "x": 470, "y": 180, "wires": [], "z": "ex4_tab"} ]

🎓 5. まとめ

Watchノードの重要ポイント

⚠️ よくある間違い

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

問題原因解決方法
監視が開始されない ファイル/ディレクトリが存在しない 事前にファイル/ディレクトリを作成する
変更が検知されない NFS/SMBマウント上のファイル ローカルファイルシステムを使用
削除後に監視復活しない Watchノードの仕様 フローを再デプロイする
読み込みエラー ファイル書き込み完了前に読み込み Delayノードで待機してから読み込み
Windowsでパスが認識されない シングルバックスラッシュ使用 ダブルバックスラッシュ(\\)を使用

💡 7. 実務での活用例

ケース1: IoTセンサーデータの自動取り込み

センサー機器がCSVファイルを定期出力 ↓ Watch で検知 ↓ データ解析・DB登録

ケース2: 監視カメラ画像の処理

動体検知カメラが画像を保存 ↓ Watch で検知 ↓ 画像解析 → 異常時にアラート

ケース3: ログファイル監視

アプリケーションログを監視 ↓ エラーキーワード検出 ↓ Slack/メールで通知

ケース4: 設定ファイルのホットリロード

設定ファイル変更を検知 ↓ 新設定を読み込み ↓ アプリケーションに適用(再起動不要)

📗 8. 追加リソース


このガイドが役に立ちましたら、実際のプロジェクトで練習してみてください!
Watchノードはファイル監視の強力なツールです。

🏠