🔌 Node-RED WebSocket ノード完全ガイド

📚 目次

📖 1. 概要

WebSocket ノードは、双方向リアルタイム通信を実現するためのノードです。HTTPと異なり、一度接続を確立すると、サーバーからクライアントへプッシュ送信が可能になります。

HTTP vs WebSocket

🔄 HTTP(リクエスト-レスポンス)

クライアントが毎回リクエストを送信

  • リクエスト → レスポンス → 接続終了
  • サーバーからプッシュ不可
  • オーバーヘッドが大きい

⚡ WebSocket(双方向通信)

一度接続したら維持

  • 接続確立 → 双方向通信 → 切断
  • サーバーからプッシュ可能
  • 低オーバーヘッド

📞 電話に例えると:HTTPは「毎回電話をかけ直す」、WebSocketは「電話をつなぎっぱなしにしておく」イメージです。つなぎっぱなしなので、相手からも自由に話しかけることができます。

📥 WebSocket In

WebSocket In

役割: WebSocketメッセージを受信

  • サーバーモード: クライアントからの接続受付
  • クライアントモード: サーバーへ接続

📤 WebSocket Out

WebSocket Out

役割: WebSocketメッセージを送信

  • 接続中のクライアント全員に送信
  • 特定クライアントに送信

⚙️ 2. WebSocket設定ノード

WebSocket In/Out ノードは、設定ノード(WebSocket-listener または WebSocket-client)を共有します。

サーバーモード(Listen)

Node-REDがWebSocketサーバーとして動作し、クライアントからの接続を受け付けます。

プロパティ説明
種類Listen at(サーバー)-
パスWebSocketエンドポイント/ws/chat
送受信データ形式entire message / payload
// クライアントからの接続URL ws://localhost:1880/ws/chat

クライアントモード(Connect)

Node-REDがWebSocketクライアントとして、外部サーバーに接続します。

プロパティ説明
種類Connect to(クライアント)-
URL接続先WebSocketサーバーws://example.com/socket
ハートビート送信キープアライブ送信25秒ごと

Send/Receive オプション

オプション説明用途
entire messagemsg全体をJSON化して送受信Node-RED間通信
payload onlymsg.payloadのみ送受信一般的なWebSocket通信

⚙️ 3. WebSocket In / Out ノード

WebSocket In ノード

受信時に設定される msg プロパティ:

プロパティ説明
msg.payload受信データ
msg._sessionセッションID(クライアント識別用)

WebSocket Out ノード

送信時に使用できる msg プロパティ:

プロパティ説明
msg.payload送信データ
msg._session特定クライアントに送信(省略時は全員)

💡 ポイント:

🔧 4. 実用的な使用パターン

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

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

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

⚠️ テスト方法:

WebSocketのテストには以下のツールが便利です:

ブラウザでのテスト方法

// ブラウザの開発者ツール (Console) で実行 var ws = new WebSocket("ws://localhost:1880/ws/echo"); ws.onopen = function() { console.log("接続成功!"); ws.send("Hello WebSocket!"); }; ws.onmessage = function(event) { console.log("受信:", event.data); }; ws.onclose = function() { console.log("切断"); }; // メッセージ送信 ws.send("テストメッセージ");
📥 サンプルフローJSON(クリックで展開)
[ { "id": "ws_sample_tab", "type": "tab", "label": "WebSocket サンプル", "disabled": false, "info": "" }, { "id": "ws_listener_echo", "type": "websocket-listener", "path": "/ws/echo", "wholemsg": "false" }, { "id": "ws_listener_chat", "type": "websocket-listener", "path": "/ws/chat", "wholemsg": "false" }, { "id": "ws_comment1", "type": "comment", "z": "ws_sample_tab", "name": "━━━ エコーサーバー(受信→返信) ━━━", "info": "ws://localhost:1880/ws/echo", "x": 220, "y": 40, "wires": [] }, { "id": "ws_in_echo", "type": "websocket in", "z": "ws_sample_tab", "name": "WS Echo In", "server": "ws_listener_echo", "client": "", "x": 150, "y": 100, "wires": [["ws_func_echo", "ws_debug1"]] }, { "id": "ws_func_echo", "type": "function", "z": "ws_sample_tab", "name": "エコー処理", "func": "// 受信データに時刻を追加して返信\nvar received = msg.payload;\n\nmsg.payload = {\n type: \"echo\",\n original: received,\n timestamp: new Date().toISOString(),\n message: \"あなたは「\" + received + \"」と言いました\"\n};\n\n// JSON文字列に変換\nmsg.payload = JSON.stringify(msg.payload);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 350, "y": 100, "wires": [["ws_out_echo"]] }, { "id": "ws_out_echo", "type": "websocket out", "z": "ws_sample_tab", "name": "WS Echo Out", "server": "ws_listener_echo", "client": "", "x": 550, "y": 100, "wires": [] }, { "id": "ws_debug1", "type": "debug", "z": "ws_sample_tab", "name": "受信ログ", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"受信\"", "statusType": "str", "x": 350, "y": 140, "wires": [] }, { "id": "ws_comment2", "type": "comment", "z": "ws_sample_tab", "name": "━━━ ブロードキャスト(全員に配信) ━━━", "info": "ws://localhost:1880/ws/chat", "x": 220, "y": 220, "wires": [] }, { "id": "ws_in_chat", "type": "websocket in", "z": "ws_sample_tab", "name": "WS Chat In", "server": "ws_listener_chat", "client": "", "x": 150, "y": 280, "wires": [["ws_func_chat", "ws_debug2"]] }, { "id": "ws_func_chat", "type": "function", "z": "ws_sample_tab", "name": "チャット処理", "func": "// 送信者のセッションIDを保存\nvar senderSession = msg._session;\n\ntry {\n var data = JSON.parse(msg.payload);\n} catch(e) {\n var data = {name: \"匿名\", message: msg.payload};\n}\n\n// ブロードキャスト用にメッセージを整形\nmsg.payload = JSON.stringify({\n type: \"chat\",\n name: data.name || \"匿名\",\n message: data.message || data,\n timestamp: new Date().toISOString()\n});\n\n// _sessionを削除して全員に送信\ndelete msg._session;\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 350, "y": 280, "wires": [["ws_out_chat"]] }, { "id": "ws_out_chat", "type": "websocket out", "z": "ws_sample_tab", "name": "WS Chat Out (全員)", "server": "ws_listener_chat", "client": "", "x": 580, "y": 280, "wires": [] }, { "id": "ws_debug2", "type": "debug", "z": "ws_sample_tab", "name": "チャットログ", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"チャット受信\"", "statusType": "str", "x": 370, "y": 320, "wires": [] }, { "id": "ws_comment3", "type": "comment", "z": "ws_sample_tab", "name": "━━━ サーバーからのプッシュ配信 ━━━", "info": "", "x": 210, "y": 400, "wires": [] }, { "id": "ws_inject_push", "type": "inject", "z": "ws_sample_tab", "name": "プッシュ送信", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "{\"type\":\"notification\",\"message\":\"サーバーからのお知らせです\"}", "payloadType": "json", "x": 160, "y": 460, "wires": [["ws_func_push"]] }, { "id": "ws_inject_timer", "type": "inject", "z": "ws_sample_tab", "name": "5秒ごとに時刻配信", "props": [{"p": "payload"}], "repeat": "5", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 180, "y": 520, "wires": [["ws_func_time"]] }, { "id": "ws_func_push", "type": "function", "z": "ws_sample_tab", "name": "プッシュ整形", "func": "msg.payload = JSON.stringify(msg.payload);\n// _sessionがないので全員に送信\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 370, "y": 460, "wires": [["ws_out_chat"]] }, { "id": "ws_func_time", "type": "function", "z": "ws_sample_tab", "name": "時刻整形", "func": "msg.payload = JSON.stringify({\n type: \"time\",\n serverTime: new Date().toISOString()\n});\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 360, "y": 520, "wires": [["ws_out_chat"]] }, { "id": "ws_comment4", "type": "comment", "z": "ws_sample_tab", "name": "━━━ ブラウザテスト用HTMLページ ━━━", "info": "http://localhost:1880/ws-test", "x": 220, "y": 600, "wires": [] }, { "id": "ws_http_in_test", "type": "http in", "z": "ws_sample_tab", "name": "GET /ws-test", "url": "/ws-test", "method": "get", "upload": false, "swaggerDoc": "", "x": 160, "y": 660, "wires": [["ws_template_test"]] }, { "id": "ws_template_test", "type": "template", "z": "ws_sample_tab", "name": "テストページHTML", "field": "payload", "fieldType": "msg", "format": "html", "syntax": "plain", "template": "\n\n\n \n WebSocket テスト\n \n\n\n
\n

🔌 WebSocket テスト

\n \n
切断中
\n \n
\n \n \n \n
\n \n
\n \n
\n \n \n
\n
\n \n \n\n", "output": "str", "x": 390, "y": 660, "wires": [["ws_http_response_test"]] }, { "id": "ws_http_response_test", "type": "http response", "z": "ws_sample_tab", "name": "Response", "statusCode": "200", "headers": {"Content-Type": "text/html; charset=utf-8"}, "x": 600, "y": 660, "wires": [] } ]

使用パターン

パターン1: エコーサーバー(受信→返信)

サンプルフローの「エコーサーバー(受信→返信)」を参照してください。

WS In 処理 WS Out

ポイント:

パターン2: ブロードキャスト(全員に配信)

サンプルフローの「ブロードキャスト(全員に配信)」を参照してください。

データ生成 _session削除 WS Out

ポイント:

パターン3: サーバーからのプッシュ配信

サンプルフローの「サーバーからのプッシュ配信」を参照してください。

定期実行 データ生成 全員に配信

ポイント:

パターン4: ブラウザテスト用HTMLページ

サンプルフローの「ブラウザテスト用HTMLページ」を参照してください。

HTTP In テストHTML HTTP Response

ポイント:

📝 5. 演習問題

演習1: シンプルなエコーサーバー 初級

📋 課題: 受信したメッセージをそのまま送信元に返すエコーサーバーを作成してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

WebSocket In ノード:

  • Type: Listen at
  • Path: /ws/myecho

WebSocket Out ノード: 同じ設定ノードを選択

WebSocket In の出力を直接 WebSocket Out に接続すれば、msg._session が保持されて送信元に返信されます。

✅ 解答例フロー
[ {"id": "ex1_tab", "type": "tab", "label": "演習1"}, {"id": "ex1_ws_listener", "type": "websocket-listener", "path": "/ws/myecho", "wholemsg": "false"}, {"id": "ex1_ws_in", "type": "websocket in", "z": "ex1_tab", "name": "WS In", "server": "ex1_ws_listener", "client": "", "x": 150, "y": 100, "wires": [["ex1_ws_out", "ex1_debug"]]}, {"id": "ex1_ws_out", "type": "websocket out", "z": "ex1_tab", "name": "WS Out", "server": "ex1_ws_listener", "client": "", "x": 350, "y": 100, "wires": []}, {"id": "ex1_debug", "type": "debug", "z": "ex1_tab", "name": "ログ", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"受信\"", "statusType": "str", "x": 350, "y": 140, "wires": []} ]

演習2: 時刻付きレスポンス 初級

📋 課題: 受信したメッセージに現在時刻を追加して返信してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

Function ノード:

msg.payload = JSON.stringify({ message: msg.payload, receivedAt: new Date().toISOString() }); return msg;
✅ 解答例フロー
[ {"id": "ex2_tab", "type": "tab", "label": "演習2"}, {"id": "ex2_ws_listener", "type": "websocket-listener", "path": "/ws/time", "wholemsg": "false"}, {"id": "ex2_ws_in", "type": "websocket in", "z": "ex2_tab", "name": "WS In", "server": "ex2_ws_listener", "client": "", "x": 150, "y": 100, "wires": [["ex2_func"]]}, {"id": "ex2_func", "type": "function", "z": "ex2_tab", "name": "時刻追加", "func": "msg.payload = JSON.stringify({\n message: msg.payload,\n receivedAt: new Date().toISOString()\n});\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 320, "y": 100, "wires": [["ex2_ws_out"]]}, {"id": "ex2_ws_out", "type": "websocket out", "z": "ex2_tab", "name": "WS Out", "server": "ex2_ws_listener", "client": "", "x": 490, "y": 100, "wires": []} ]

演習3: ブロードキャスト配信 中級

📋 課題: Injectノードからメッセージを送信すると、接続中の全クライアントに配信されるフローを作成してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

ブロードキャストのポイント:

  • WebSocket Out に渡す msg に _session がないと全員に送信されます
  • Changeノードの「削除」ルールで msg._session を削除すると、ブロードキャスト送信になります
  • Inject ノードから生成した msg には _session がないので、そのまま送信可能です
✅ 解答例フロー
[ {"id": "ex3_tab", "type": "tab", "label": "演習3"}, {"id": "ex3_ws_listener", "type": "websocket-listener", "path": "/ws/broadcast", "wholemsg": "false"}, {"id": "ex3_inject", "type": "inject", "z": "ex3_tab", "name": "ブロードキャスト", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "{\"type\":\"broadcast\",\"content\":\"全員へのお知らせ\"}", "payloadType": "json", "x": 170, "y": 100, "wires": [["ex3_change"]]}, {"id": "ex3_change", "type": "change", "z": "ex3_tab", "name": "_session削除", "rules": [{"t": "delete", "p": "_session", "pt": "msg"}], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 370, "y": 100, "wires": [["ex3_ws_out"]]}, {"id": "ex3_ws_out", "type": "websocket out", "z": "ex3_tab", "name": "WS Out (全員)", "server": "ex3_ws_listener", "client": "", "x": 560, "y": 100, "wires": []}, {"id": "ex3_ws_in", "type": "websocket in", "z": "ex3_tab", "name": "WS In (接続確認用)", "server": "ex3_ws_listener", "client": "", "x": 190, "y": 180, "wires": [["ex3_debug"]]}, {"id": "ex3_debug", "type": "debug", "z": "ex3_tab", "name": "接続ログ", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "\"クライアント接続\"", "statusType": "str", "x": 410, "y": 180, "wires": []} ]

演習4: 簡易チャットシステム 上級

📋 課題: 複数のクライアントが参加できるチャットルームを作成してください。

🎯 要求仕様:

✅ 成功の条件:

💡 ヒント

処理の流れ:

  1. WebSocket In で受信
  2. Function で JSON パース&整形
  3. delete msg._session でブロードキャスト準備
  4. WebSocket Out で全員に送信
✅ 解答例フロー
[ {"id": "ex4_tab", "type": "tab", "label": "演習4"}, {"id": "ex4_ws_listener", "type": "websocket-listener", "path": "/ws/chatroom", "wholemsg": "false"}, {"id": "ex4_ws_in", "type": "websocket in", "z": "ex4_tab", "name": "WS Chat In", "server": "ex4_ws_listener", "client": "", "x": 160, "y": 100, "wires": [["ex4_func"]]}, {"id": "ex4_func", "type": "function", "z": "ex4_tab", "name": "チャット処理", "func": "try {\n var data = JSON.parse(msg.payload);\n} catch(e) {\n var data = {name: \"匿名\", message: msg.payload};\n}\n\nmsg.payload = JSON.stringify({\n type: \"chat\",\n name: data.name || \"匿名\",\n message: data.message || \"\",\n timestamp: new Date().toISOString()\n});\n\n// 全員に送信するため _session を削除\ndelete msg._session;\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 360, "y": 100, "wires": [["ex4_ws_out", "ex4_debug"]]}, {"id": "ex4_ws_out", "type": "websocket out", "z": "ex4_tab", "name": "WS Chat Out", "server": "ex4_ws_listener", "client": "", "x": 570, "y": 100, "wires": []}, {"id": "ex4_debug", "type": "debug", "z": "ex4_tab", "name": "チャットログ", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "x": 570, "y": 140, "wires": []} ]

✅ 6. まとめ

🎯 重要ポイント:

⚠️ 注意事項:

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

症状原因解決方法
接続できない URLが間違っている ws://(httpsならwss://)を確認
すぐ切断される サーバー側の問題 Node-REDのログを確認
メッセージが届かない 設定ノードが異なる In/Outで同じ設定ノードを使用
全員に送信されない _sessionが残っている delete msg._session
JSONパースエラー データ形式の問題 try-catchでエラーハンドリング

🏭 8. 実務活用例

ケース1: リアルタイムセンサーモニタリング

Raspberry Pi からのセンサーデータをWebSocketで配信し、ダッシュボードでリアルタイム表示

ケース2: 機器の遠隔制御

WebSocket経由でコマンドを受信し、GPIOやアクチュエーターを制御

ケース3: 通知システム

アラートや警告をクライアントに即時プッシュ配信

ケース4: 複数デバイス間の同期

複数のRaspberry Pi間でデータを同期(分散センサーネットワーク)

📚 Node-RED 公式ドキュメント - WebSocket

🏠