📺 Node-RED Dashboard 2.0 表示系ウィジェット ガイド

このガイドでは、Dashboard 2.0の表示系ウィジェットについて詳しく解説します。これらのウィジェットは、データや情報をユーザーに視覚的に伝えるために使用します。

📌 このガイドで学べること:

📑 目次

  1. 表示系ウィジェットの概要
  2. サンプルフロー
  3. text(テキスト表示)
  4. markdown(Markdownビューア)
  5. notification(通知)
  6. audio(オーディオ)
  7. 実践演習
  8. まとめ
  9. トラブルシューティング
  10. 実務での活用例
  11. 追加リソース

🎯 1. 表示系ウィジェットの概要

🤔 表示系ウィジェットとは?

表示系ウィジェットは、ユーザーに情報を見せる(出力する)ためのウィジェットです。 日常生活で例えると、掲示板や案内表示のようなものです。

🏢 オフィスビルに例えると:

📊 表示系ウィジェット一覧

ウィジェット 用途 HTML対応 動的更新
text シンプルなテキスト表示
markdown Markdown/Mermaid表示 ✅(Markdown経由)
notification ポップアップ通知
audio 音声再生・TTS -

🔥 2. サンプルフロー

📥 まずサンプルフローをインポートしましょう!

以下のサンプルフローには、このガイドで説明する全パターンの実例が含まれています。
先にインポートして、実際に動作を確認しながら読み進めると理解が深まります。

  1. 下のサンプルフローJSONをコピー
  2. Node-REDエディタで メニュー → 読み込み を選択
  3. JSONをペーストして「読み込み」をクリック
  4. 各ui-*ノードでGroupを選択してデプロイ
📋 表示系ウィジェット サンプルフロー(クリックで展開)

このサンプルには text, markdown, notification, audio の全パターンが含まれています

[ { "id": "289c4fb42bfdacf9", "type": "tab", "label": "表示系ウィジェット", "disabled": false, "info": "Dashboard 2.0 表示系ウィジェットのサンプル" }, { "id": "8a360052a6ac4aa9", "type": "comment", "z": "289c4fb42bfdacf9", "name": "━━━ 📝 ui-text サンプル ━━━", "info": "", "x": 160, "y": 40, "wires": [] }, { "id": "491423b783369936", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン1: シンプルなテキスト表示", "info": "", "x": 180, "y": 80, "wires": [] }, { "id": "cba24325da99bcbf", "type": "inject", "z": "289c4fb42bfdacf9", "name": "センサー値", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "現在の温度: 25.5℃", "payloadType": "str", "x": 130, "y": 120, "wires": [ [ "ad4de93c829d850f" ] ] }, { "id": "ad4de93c829d850f", "type": "ui-text", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_text", "order": 1, "width": 0, "height": 0, "name": "シンプルテキスト", "label": "ステータス:", "format": "{{msg.payload}}", "layout": "row-spread", "style": false, "font": "", "fontSize": 16, "color": "#000000", "wrapText": false, "className": "", "value": "payload", "valueType": "msg", "x": 340, "y": 120, "wires": [] }, { "id": "5ab02164fa917931", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン2: HTML装飾付きテキスト(Template経由)", "info": "", "x": 220, "y": 180, "wires": [] }, { "id": "b196d12c5d24f7bd", "type": "inject", "z": "289c4fb42bfdacf9", "name": "データ送信", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "FlowFuse", "payloadType": "str", "x": 130, "y": 220, "wires": [ [ "567e1f367c19f46b" ] ] }, { "id": "567e1f367c19f46b", "type": "template", "z": "289c4fb42bfdacf9", "name": "HTML生成", "field": "payload", "fieldType": "msg", "format": "handlebars", "syntax": "mustache", "template": "FlowFuseへようこそ!\n
\n重要: 警告メッセージ", "output": "str", "x": 300, "y": 220, "wires": [ [ "f4ba7b48dedccb2f" ] ] }, { "id": "f4ba7b48dedccb2f", "type": "ui-text", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_text", "order": 2, "width": 0, "height": 0, "name": "HTMLテキスト", "label": "", "format": "{{msg.payload}}", "layout": "row-left", "style": false, "font": "", "fontSize": 16, "color": "#000000", "wrapText": false, "className": "", "value": "payload", "valueType": "msg", "x": 480, "y": 220, "wires": [] }, { "id": "c6aa3f00cb2154ce", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン3: JSONata式で値をフォーマット", "info": "", "x": 200, "y": 280, "wires": [] }, { "id": "af07ce26dd544d63", "type": "inject", "z": "289c4fb42bfdacf9", "name": "温度値", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "25.678", "payloadType": "num", "x": 120, "y": 320, "wires": [ [ "ca66755d1cc0bede" ] ] }, { "id": "ca66755d1cc0bede", "type": "ui-text", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_text", "order": 3, "width": 0, "height": 0, "name": "JSONataフォーマット", "label": "温度:", "format": "{{msg.payload}}", "layout": "row-spread", "style": false, "font": "", "fontSize": 16, "color": "#000000", "wrapText": false, "className": "", "value": "$round(payload, 1)", "valueType": "jsonata", "x": 360, "y": 320, "wires": [] }, { "id": "8939939a8777513a", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン4: プレフィックス/サフィックスの追加", "info": "", "x": 210, "y": 380, "wires": [] }, { "id": "2b2ed53f581d2a99", "type": "inject", "z": "289c4fb42bfdacf9", "name": "温度値", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "25.5", "payloadType": "num", "x": 120, "y": 420, "wires": [ [ "39d1630f4220d208" ] ] }, { "id": "39d1630f4220d208", "type": "template", "z": "289c4fb42bfdacf9", "name": "単位追加", "field": "payload", "fieldType": "msg", "format": "handlebars", "syntax": "mustache", "template": "{{payload}} ℃", "output": "str", "x": 290, "y": 420, "wires": [ [ "3f23b4684076a184" ] ] }, { "id": "3f23b4684076a184", "type": "ui-text", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_text", "order": 4, "width": 0, "height": 0, "name": "単位付き表示", "label": "温度:", "format": "{{msg.payload}}", "layout": "row-spread", "style": false, "font": "", "fontSize": 16, "color": "#000000", "wrapText": false, "className": "", "value": "payload", "valueType": "msg", "x": 480, "y": 420, "wires": [] }, { "id": "09b1386036e9f9fd", "type": "comment", "z": "289c4fb42bfdacf9", "name": "━━━ 📰 ui-markdown サンプル ━━━", "info": "", "x": 180, "y": 500, "wires": [] }, { "id": "f644e84296d5bd65", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン1: 静的ドキュメント表示", "info": "", "x": 180, "y": 540, "wires": [] }, { "id": "ebe7dc4c54c38776", "type": "ui-markdown", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_markdown", "name": "操作説明", "order": 1, "width": 0, "height": 0, "content": "## 📖 操作説明\n\n### 基本操作\n1. **開始ボタン**をクリックして処理を開始\n2. ステータスを確認\n3. 完了後、**停止ボタン**で終了\n\n### 注意事項\n- 処理中は画面を閉じないでください\n- エラー時は管理者に連絡\n\n---\n*詳細は[マニュアル](https://example.com)を参照*", "className": "", "x": 130, "y": 580, "wires": [ [] ] }, { "id": "dc8e3ab53f9fa6d5", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン2: 動的レポート生成", "info": "", "x": 170, "y": 640, "wires": [] }, { "id": "3ddf9ce3aa2880c8", "type": "inject", "z": "289c4fb42bfdacf9", "name": "センサーデータ", "props": [ { "p": "payload.temperature", "v": "25.5", "vt": "num" }, { "p": "payload.humidity", "v": "60", "vt": "num" }, { "p": "payload.timestamp", "v": "", "vt": "date" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 140, "y": 680, "wires": [ [ "d16f4b82d6161953" ] ] }, { "id": "d16f4b82d6161953", "type": "ui-markdown", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_markdown", "name": "センサーレポート", "order": 2, "width": 0, "height": 0, "content": "## 📊 センサーレポート\n\n| 項目 | 値 |\n|------|----|\n| 🌡️ 温度 | **{{ msg?.payload?.temperature || '--' }}** ℃ |\n| 💧 湿度 | **{{ msg?.payload?.humidity || '--' }}** % |\n\n---\n\n*最終更新: {{ msg?.payload?.timestamp || '取得中...' }}*", "className": "", "x": 380, "y": 680, "wires": [ [] ] }, { "id": "83cc66b8b8f069e7", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン3: Mermaidチャート表示", "info": "", "x": 180, "y": 740, "wires": [] }, { "id": "86dc89394e7a12aa", "type": "ui-markdown", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_markdown", "name": "システム構成図", "order": 3, "width": 0, "height": 0, "content": "## 🔧 システム構成図\n\n```mermaid\ngraph TD;\n A[センサー] --> B[Node-RED];\n B --> C[Dashboard];\n B --> D[データベース];\n C --> E[ユーザー];\n```", "className": "", "x": 150, "y": 780, "wires": [ [] ] }, { "id": "3d78ef1f081d306c", "type": "comment", "z": "289c4fb42bfdacf9", "name": "━━━ 🔔 ui-notification サンプル ━━━", "info": "", "x": 190, "y": 860, "wires": [] }, { "id": "2d48715fd01e8025", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン1: シンプルな通知", "info": "", "x": 160, "y": 900, "wires": [] }, { "id": "b8e4a95adb057146", "type": "inject", "z": "289c4fb42bfdacf9", "name": "完了通知", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "処理が完了しました", "payloadType": "str", "x": 130, "y": 940, "wires": [ [ "e36825aeb356a81a" ] ] }, { "id": "e36825aeb356a81a", "type": "ui-notification", "z": "289c4fb42bfdacf9", "ui": "6692685697ac2af1", "position": "top right", "colorDefault": true, "color": "#000000", "displayTime": "3", "showCountdown": true, "outputs": 1, "allowDismiss": true, "dismissText": "OK", "allowConfirm": false, "confirmText": "", "raw": false, "className": "", "name": "シンプル通知", "x": 350, "y": 940, "wires": [ [] ] }, { "id": "c2bcf1928674b982", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン2: HTMLを含むリッチ通知", "info": "", "x": 180, "y": 1000, "wires": [] }, { "id": "fb417647e0580698", "type": "inject", "z": "289c4fb42bfdacf9", "name": "警告データ", "props": [ { "p": "payload", "v": "85", "vt": "num" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 130, "y": 1040, "wires": [ [ "a74fa659a6bdb19c" ] ] }, { "id": "a74fa659a6bdb19c", "type": "function", "z": "289c4fb42bfdacf9", "name": "HTML生成", "func": "msg.payload = `\n⚠️ 警告\nセンサー値が閾値を超えました\n現在値: ${msg.payload}℃\n`;\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 290, "y": 1040, "wires": [ [ "313db1b819a9b152" ] ] }, { "id": "313db1b819a9b152", "type": "ui-notification", "z": "289c4fb42bfdacf9", "ui": "6692685697ac2af1", "position": "top center", "colorDefault": false, "color": "#ff9800", "displayTime": "5", "showCountdown": true, "outputs": 1, "allowDismiss": true, "dismissText": "閉じる", "allowConfirm": false, "confirmText": "", "raw": true, "className": "", "name": "リッチ通知", "x": 470, "y": 1040, "wires": [ [] ] }, { "id": "3e84d02bf90fc690", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン3: 確認ダイアログと結果の処理分岐", "info": "", "x": 210, "y": 1100, "wires": [] }, { "id": "89633e5bf0977894", "type": "inject", "z": "289c4fb42bfdacf9", "name": "確認要求", "props": [ { "p": "payload" }, { "p": "ui_update", "v": "{\"allowConfirm\":true,\"confirmText\":\"実行\",\"allowDismiss\":true,\"dismissText\":\"キャンセル\",\"displayTime\":0,\"position\":\"center center\",\"color\":\"#2196f3\"}", "vt": "json" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "この操作を実行しますか?", "payloadType": "str", "x": 130, "y": 1140, "wires": [ [ "4441429339a04176" ] ] }, { "id": "4441429339a04176", "type": "ui-notification", "z": "289c4fb42bfdacf9", "ui": "6692685697ac2af1", "position": "center center", "colorDefault": false, "color": "#2196f3", "displayTime": "0", "showCountdown": false, "outputs": 1, "allowDismiss": true, "dismissText": "キャンセル", "allowConfirm": true, "confirmText": "実行", "raw": false, "className": "", "name": "確認ダイアログ", "x": 360, "y": 1140, "wires": [ [ "pattern3_switch" ] ] }, { "id": "pattern3_switch", "type": "switch", "z": "289c4fb42bfdacf9", "name": "実行/キャンセル判定", "property": "payload", "propertyType": "msg", "rules": [ { "t": "eq", "v": "実行", "vt": "str" }, { "t": "eq", "v": "キャンセル", "vt": "str" } ], "checkall": "false", "repair": false, "outputs": 2, "x": 570, "y": 1140, "wires": [ [ "pattern3_debug_exec" ], [ "pattern3_debug_cancel" ] ] }, { "id": "pattern3_debug_exec", "type": "debug", "z": "289c4fb42bfdacf9", "name": "実行処理", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "'実行されました'", "statusType": "jsonata", "x": 770, "y": 1120, "wires": [] }, { "id": "pattern3_debug_cancel", "type": "debug", "z": "289c4fb42bfdacf9", "name": "キャンセル処理", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "payload", "targetType": "msg", "statusVal": "'キャンセルされました'", "statusType": "jsonata", "x": 780, "y": 1160, "wires": [] }, { "id": "5f776d012acd26d3", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン4: 全クライアントへの一斉通知", "info": "", "x": 200, "y": 1200, "wires": [] }, { "id": "faf9ea5cbf24b8b9", "type": "inject", "z": "289c4fb42bfdacf9", "name": "緊急通知", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "🚨 システムメンテナンスを開始します", "payloadType": "str", "x": 130, "y": 1240, "wires": [ [ "220bb51fa0b8dd39" ] ] }, { "id": "220bb51fa0b8dd39", "type": "change", "z": "289c4fb42bfdacf9", "name": "_client削除", "rules": [ { "t": "delete", "p": "_client", "pt": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 310, "y": 1240, "wires": [ [ "2929a71cada84ecd" ] ] }, { "id": "2929a71cada84ecd", "type": "ui-notification", "z": "289c4fb42bfdacf9", "ui": "6692685697ac2af1", "position": "top center", "colorDefault": false, "color": "#f44336", "displayTime": "10", "showCountdown": true, "outputs": 1, "allowDismiss": true, "dismissText": "確認", "allowConfirm": false, "confirmText": "", "raw": false, "className": "", "name": "全員通知", "x": 490, "y": 1240, "wires": [ [] ] }, { "id": "3511b9fdece8deef", "type": "comment", "z": "289c4fb42bfdacf9", "name": "━━━ 🔊 ui-audio サンプル(⚠️音が鳴ります)━━━", "info": "", "x": 170, "y": 1320, "wires": [] }, { "id": "00e5d1548bef800c", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン1: BGM再生(Audio Player)", "info": "", "x": 190, "y": 1360, "wires": [] }, { "id": "audio_reset_inject", "type": "inject", "z": "289c4fb42bfdacf9", "name": "起動時リセット", "props": [ { "p": "playback", "v": "stop", "vt": "str" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.5, "topic": "", "x": 140, "y": 1400, "wires": [ [ "e403d7f609744706" ] ] }, { "id": "7330d4e44b92a815", "type": "inject", "z": "289c4fb42bfdacf9", "name": "URL設定", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3", "payloadType": "str", "x": 110, "y": 1440, "wires": [ [ "e403d7f609744706" ] ] }, { "id": "e403d7f609744706", "type": "ui-audio", "z": "289c4fb42bfdacf9", "group": "sample_ui_group_audio", "name": "Audio Player", "order": 1, "width": 0, "height": 0, "mode": "src", "voice": "", "src": "", "autoplay": false, "loop": false, "muted": false, "className": "", "x": 330, "y": 1420, "wires": [ [] ] }, { "id": "da23772057ce8f26", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン2: アラート音声(TTS)", "info": "", "x": 170, "y": 1500, "wires": [] }, { "id": "0a0e894c02267584", "type": "inject", "z": "289c4fb42bfdacf9", "name": "温度データ", "props": [ { "p": "payload.temperature", "v": "85", "vt": "num" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 130, "y": 1540, "wires": [ [ "85188f3a9cd9d566" ] ] }, { "id": "85188f3a9cd9d566", "type": "function", "z": "289c4fb42bfdacf9", "name": "閾値判定", "func": "if (msg.payload.temperature > 80) {\n msg.payload = {\n text: \"警告!温度が80度を超えました\",\n rate: 1.2,\n volume: 100\n };\n return msg;\n}\nreturn null;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 300, "y": 1540, "wires": [ [ "935347c615c04902" ] ] }, { "id": "935347c615c04902", "type": "ui-audio", "z": "289c4fb42bfdacf9", "group": "", "ui": "6692685697ac2af1", "name": "TTS", "order": "", "width": "", "height": "", "mode": "tts", "voice": "urn:moz-tts:osx:com.apple.voice.compact.ja-JP.Kyoko", "src": "", "autoplay": false, "loop": false, "muted": false, "className": "", "x": 470, "y": 1540, "wires": [ [] ] }, { "id": "e534c25ce7de35f9", "type": "comment", "z": "289c4fb42bfdacf9", "name": "パターン3: 多言語音声案内", "info": "", "x": 160, "y": 1600, "wires": [] }, { "id": "8101307a1fecb87c", "type": "inject", "z": "289c4fb42bfdacf9", "name": "日本語", "props": [ { "p": "payload", "v": "{\"text\":\"ようこそ、ダッシュボードへ\",\"lang\":\"ja-JP\"}", "vt": "json" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 120, "y": 1640, "wires": [ [ "176c70581fe252da" ] ] }, { "id": "e7fbfe3e98ddb96b", "type": "inject", "z": "289c4fb42bfdacf9", "name": "英語", "props": [ { "p": "payload", "v": "{\"text\":\"Welcome to the dashboard\",\"lang\":\"en-US\"}", "vt": "json" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 110, "y": 1680, "wires": [ [ "176c70581fe252da" ] ] }, { "id": "176c70581fe252da", "type": "ui-audio", "z": "289c4fb42bfdacf9", "group": "", "ui": "6692685697ac2af1", "name": "多言語TTS", "order": "", "width": "", "height": "", "mode": "tts", "voice": "", "src": "", "autoplay": false, "loop": false, "muted": false, "className": "", "x": 290, "y": 1660, "wires": [ [] ] }, { "id": "sample_ui_group_text", "type": "ui-group", "name": "📝 テキスト表示", "page": "sample_ui_page", "width": "6", "height": "-1", "order": 1, "showTitle": true, "className": "", "visible": true, "disabled": false, "groupType": "default" }, { "id": "sample_ui_group_markdown", "type": "ui-group", "name": "📰 Markdown表示", "page": "sample_ui_page", "width": "6", "height": "-1", "order": 2, "showTitle": true, "className": "", "visible": true, "disabled": false, "groupType": "default" }, { "id": "6692685697ac2af1", "type": "ui-base", "name": "My Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": [ "ui-notification", "ui-control" ], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false }, { "id": "sample_ui_group_audio", "type": "ui-group", "name": "🔊 オーディオ(⚠️音が鳴ります)", "page": "sample_ui_page", "width": "6", "height": "-1", "order": 3, "showTitle": true, "className": "", "visible": true, "disabled": false, "groupType": "default" }, { "id": "sample_ui_page", "type": "ui-page", "name": "表示系サンプル", "ui": "6692685697ac2af1", "path": "/display-widgets", "icon": "mdi-monitor", "layout": "grid", "theme": "sample_ui_theme", "breakpoints": [ { "name": "Default", "px": 0, "cols": 3 }, { "name": "Tablet", "px": 576, "cols": 6 }, { "name": "Small Desktop", "px": 768, "cols": 9 }, { "name": "Desktop", "px": 1024, "cols": 12 } ], "order": 1, "className": "", "visible": "true", "disabled": "false" }, { "id": "sample_ui_theme", "type": "ui-theme", "name": "Default Theme", "colors": { "surface": "#ffffff", "primary": "#b92d5d", "bgPage": "#eeeeee", "groupBg": "#ffffff", "groupOutline": "#cccccc" }, "sizes": { "pagePadding": "12px", "groupGap": "12px", "groupBorderRadius": "4px", "widgetGap": "12px", "density": "default" } }, { "id": "b39caf71bb018d4b", "type": "global-config", "env": [], "modules": { "@flowfuse/node-red-dashboard": "1.29.0" } } ]

📝 3. text(テキスト表示)

表示 text ウィジェット

編集不可のテキストフィールドを表示します。msg.payloadを受け取るたびに表示内容が更新されます。

📌 サンプルフロー参照: 上記サンプルフローの「📝 ui-text サンプル」セクションに、以下のパターン1〜4の実例が含まれています。

⚙️ 基本設定

設定項目 説明 動的変更
Group 表示するグループを選択 -
Size ウィジェットの幅(グループ幅が上限) -
Label ラベルテキスト(HTML可) ✅ msg.ui_update.label
Layout ラベルと値の配置方法 ✅ msg.ui_update.layout
Style カスタムスタイルの有効/無効 -
Font フォント指定(Style有効時) ✅ msg.ui_update.font
Text Size 文字サイズ(Style有効時) ✅ msg.ui_update.fontSize
Text Color 文字色(Style有効時) ✅ msg.ui_update.color
Value 表示する値(msg.payloadなど)
Value Type 値の取得元タイプ(msg等) -
Format 表示フォーマット(デフォルト: {{msg.payload}} -
Wrap Text テキストの折り返し有効/無効 -
Class CSSクラス名 ✅ msg.class

📐 Layout(配置)オプション

row-left

ラベルと値を左寄せで横並び

[Label] [Value ]

row-center

ラベルと値を中央寄せで横並び

[Label] [Value]

row-right

ラベルと値を右寄せで横並び

[ Label] [Value]

row-spread

ラベルを左、値を右に配置

[Label] [Value]

col-center

ラベルと値を縦に中央配置

[Label] [Value]

🎨 使用パターン

パターン1: シンプルなテキスト表示

Inject text

用途: センサー値やステータスの表示

msg.payload = "現在の温度: 25.5℃"

パターン2: HTML装飾付きテキスト

Inject Template text

用途: リンクや強調表示を含むテキスト

// TemplateノードでHTML生成 <a href="https://flowfuse.com" target="_blank">FlowFuse</a>へようこそ! <br> <strong>重要:</strong> <span style="color:red">警告メッセージ</span>

パターン3: JSONata式で値をフォーマット

ui-textのValue設定で「JSONata」を選択すると、値を動的にフォーマットできます。

// 小数点1桁に丸める $round(payload, 1) // 単位を付ける payload & " ℃" // 条件分岐 payload > 30 ? "高温注意" : "正常"

パターン4: プレフィックス/サフィックスの追加

⚠️ Dashboard 1.0との違い:

Dashboard 1.0のvalueFormatオプションはセキュリティ上の理由から廃止されました。代わりにNode-REDのTemplateノードを使用します。

Inject
(温度値)
Template
(単位追加)
text
// Templateノードの内容 {{payload}} ℃

🔄 動的プロパティ

msg.ui_updateオブジェクトを使用して、実行時にプロパティを変更できます。

msg.ui_update = { label: "新しいラベル", layout: "row-spread", font: "monospace", fontSize: "24px", color: "#ff0000" }; msg.class = "my-custom-class";

📰 4. markdown(Markdownビューア)

表示 リッチテキスト markdown ウィジェット

Markdown記法で書式付きテキストを表示します。Mermaidチャートにも対応しています。

📌 サンプルフロー参照: 上記サンプルフローの「📰 ui-markdown サンプル」セクションに、以下のパターン1〜3の実例が含まれています。

⚙️ 基本設定

設定項目 説明 動的変更
Group 表示するグループを選択 -
Name ノードの名前 -
Size ウィジェットの幅と高さ(グループ幅が上限) -
Order グループ内の表示順序 -
Content Markdownコンテンツ ✅(mustache構文)
Class CSSクラス ✅ msg.class

📝 Markdown記法サポート

# 見出し1 ## 見出し2 ### 見出し3 **太字** と *斜体* `インラインコード` とコードブロック: ```js function hello() { console.log('Hello World'); } ``` - リスト項目1 - リスト項目2 [リンク](https://flowfuse.com) > 引用文 | ヘッダー1 | ヘッダー2 | |----------|----------| | セル1 | セル2 |

🔗 mustache構文でmsg値を埋め込み

二重中括弧 {{ }} を使用してmsgの値を埋め込めます。

## センサーデータ 現在の温度: **{{ msg?.payload?.temperature || '取得中...' }}** ℃ 更新時刻: {{ msg?.payload?.timestamp || 'N/A' }}

💡 プレースホルダーの設定:

{{ msg?.payload || 'プレースホルダー' }} の形式で、メッセージ受信前のデフォルト値を設定できます。

📊 Mermaidチャート対応

Markdownのコードブロック内でmermaidタイプを指定すると、Mermaidチャートを描画できます。

# システム構成図 ```mermaid graph TD; A[センサー] --> B[Node-RED]; B --> C[Dashboard]; B --> D[データベース]; C --> E[ユーザー]; ```

動的なMermaidチャート

# 売上比率 ```mermaid pie title 売上構成 "製品A" : {{ msg?.payload?.productA || 30 }} "製品B" : {{ msg?.payload?.productB || 25 }} "製品C" : {{ msg?.payload?.productC || 45 }} ```

🎨 使用パターン

パターン1: ドキュメント表示

markdown
(静的コンテンツ)

用途: ヘルプページ、操作説明、利用規約など

パターン2: 動的レポート生成

Inject Function
(データ整形)
markdown

用途: センサーデータのレポート、システム状態表示

パターン3: フローチャート/図の表示

markdown
(Mermaid)

用途: システム構成図、ステートマシン、シーケンス図

🔔 5. notification(通知)

表示 一時的 notification ウィジェット

Dashboard 1.0では「Toast」と呼ばれていた機能です。画面上に一時的なポップアップ通知を表示します。

📌 サンプルフロー参照: 上記サンプルフローの「🔔 ui-notification サンプル」セクションに、以下のパターン1〜4の実例が含まれています。

⚙️ 基本設定

設定項目 説明 動的変更
UI 所属するUI(ui-base) -
Name ノードの名前 -
Position 画面上の表示位置 ✅ msg.ui_update.position
Color Default デフォルトカラーを使用するか -
Color 通知の枠線の色 ✅ msg.ui_update.color
Timeout 自動で閉じるまでの秒数 ✅ msg.ui_update.displayTime
Show Countdown Bar 残り時間のプログレスバー表示 ✅ msg.ui_update.showCountdown
Allow Manual Dismissal 閉じるボタンを表示 ✅ msg.ui_update.allowDismiss
Dismiss Text 閉じるボタンのテキスト -
Allow Manual Confirmation 確認ボタンを表示 ✅ msg.ui_update.allowConfirm
Confirm Text 確認ボタンのテキスト -
Accept Raw HTMLをそのまま処理 ✅ msg.ui_update.raw
Outputs 出力ポート数 -
Class CSSクラス名 ✅ msg.class

📍 Position(表示位置)オプション

top left
top center
top right
center left
center center
center right
bottom left
bottom center
bottom right

💡 無期限表示:

Timeoutを0に設定すると通知は自動で閉じません。この場合、allowDismissまたはallowConfirmを有効にしないとユーザーが閉じられません。

🎨 使用パターン

パターン1: シンプルな通知

Inject notification
msg.payload = "処理が完了しました";

パターン2: HTMLを含むリッチ通知

Inject Function notification
msg.payload = ` <h3>⚠️ 警告</h3> <p>センサー値が閾値を超えました</p> <p><strong>現在値:</strong> 85℃</p> `; return msg;

パターン3: 確認ダイアログと結果の処理分岐

確認ダイアログ(Confirm/Dismiss付き通知)を表示し、ユーザーの選択に応じて下流の処理を分岐させることができます。ui-notificationノードの出力を使用すると、ボタンがクリックされたときにメッセージが送信されます。

Inject notification Switch Debug×2

📌 動作の流れ:

  1. Injectノードで確認ダイアログを表示
  2. ユーザーが「実行」または「キャンセル」をクリック
  3. ui-notificationはクリックされたボタンのテキストをmsg.payloadとして出力
  4. Switchノードで「実行」「キャンセル」を判定して処理を分岐
// Injectノードの設定 msg.payload = "この操作を実行しますか?"; msg.ui_update = { allowConfirm: true, confirmText: "実行", // ← 確認時にこの文字列が出力される allowDismiss: true, dismissText: "キャンセル", // ← 却下時にこの文字列が出力される displayTime: 0, // 手動で閉じるまで表示 position: "center center", color: "#2196f3" }; return msg;
// Switchノードの設定 プロパティ: msg.payload 条件1: == "実行" → 出力1(実行処理へ) 条件2: == "キャンセル" → 出力2(キャンセル処理へ)

パターン4: 全クライアントへの一斉通知

⚠️ 重要:

通知はデフォルトで単一クライアントにのみ送信されます(msg._clientによる制約)。全クライアントに送信するには、Changeノードでmsg._clientを削除します。

Inject Change
(delete _client)
notification

🔄 動的プロパティ

msg.ui_update = { position: "top right", color: "#ff5722", displayTime: 10, showCountdown: true, allowDismiss: true, dismissText: "閉じる", allowConfirm: false, raw: true }; msg.show = true; // 表示/非表示の制御

🔊 6. audio(オーディオ)

表示 audio ウィジェット

ダッシュボードにオーディオ機能を追加します。音声ファイルの再生とText-to-Speech(TTS)の2つのモードがあります。

📌 サンプルフロー参照: 上記サンプルフローの「🔊 ui-audio サンプル」セクションに、以下のパターン1〜3の実例が含まれています。

🎚️ モード選択

🎵 Audio Player モード

URLから音声ファイルを再生するプレイヤーをダッシュボードに表示します。

  • Group設定が必要(画面に表示)
  • 再生コントロール付き
  • 自動再生、ループ、ミュート対応

🗣️ Text-to-Speech モード

ブラウザ内蔵のTTS機能でテキストを音声で読み上げます。

  • UI設定が必要(画面には非表示)
  • 音声・速度・ピッチ指定可能
  • ⚠️ ユーザージェスチャー後のみ動作

⚙️ 基本設定(Audio Player)

設定項目 説明 動的変更
Mode Audio Player / Text-to-Speech -
Group 表示するグループ(Audio Playerのみ) -
UI 所属するUI(Text-to-Speechモード時) -
Name ノードの名前 -
Size プレイヤーの幅 -
Order グループ内の表示順序 -
Source 音声ファイルのURL ✅ msg.ui_update.source
Autoplay 自動再生の有効/無効 ✅ msg.ui_update.autoplay
Loop ループ再生の有効/無効 ✅ msg.ui_update.loop
Muted ミュートの有効/無効 ✅ msg.ui_update.muted
Voice TTS音声の選択(Text-to-Speechモード時) -
Class CSSクラス名 ✅ msg.class

🎮 再生制御

msg.playbackを使用して再生を制御できます。

// 再生開始/再開 msg.playback = "play"; // 一時停止 msg.playback = "pause"; // 停止(最初に戻る) msg.playback = "stop"; // resume は play のエイリアス msg.playback = "resume";

🗣️ Text-to-Speech の使い方

シンプルな読み上げ

msg.payload = "こんにちは、Node-REDダッシュボードへようこそ";

詳細設定付き読み上げ

msg.payload = { text: "Hello World", voice: "Google US English", // 音声名またはインデックス rate: 1.1, // 速度(0.1〜10) pitch: 0.9, // ピッチ(0〜2) volume: 88 // 音量(0〜100) };

💡 利用可能な音声の確認:

ブラウザのコンソールで speechSynthesis.getVoices() を実行すると、利用可能な音声のリストを確認できます。

⚠️ ブラウザのセキュリティ制限:

TTSはブラウザのセキュリティ制限により、ユーザーが最初にダッシュボードをクリックした後でないと動作しません。これは自動音声再生を防ぐためのブラウザ仕様です。

🎨 使用パターン

パターン1: BGM再生(Audio Playerモード)

Audio Playerモードでは、ダッシュボード上に標準のオーディオプレイヤーUIが表示され、ユーザーが再生/一時停止/シーク/音量を操作できます。

Inject
(起動時リセット)
audio
(Audio Player)
Inject
(URL設定)
audio
(Audio Player)

💡 ポイント:

⚠️ フロー再起動時の自動再生を防ぐ:

Audio Playerはブラウザ側で状態を保持するため、フローの再起動(デプロイ)時に前回の音声が自動再生されることがあります。これを防ぐには、起動時リセット用のInjectノードを追加します:

// 音声URLを送信(自動再生・ループ有効) msg.payload = "https://example.com/bgm.mp3"; msg.ui_update = { autoplay: "on", loop: "on" };

パターン2: アラート音声

Function
(閾値判定)
audio
(TTS)
if (msg.payload.temperature > 80) { msg.payload = { text: "警告!温度が80度を超えました", rate: 1.2, volume: 100 }; return msg; } return null;

パターン3: 多言語音声案内

// 日本語 msg.payload = { text: "ようこそ", lang: "ja-JP" }; // 英語 msg.payload = { text: "Welcome", lang: "en-US" };

🏋️ 7. 実践演習

各表示系ウィジェットの理解を深めるため、ウィジェット別の演習問題を用意しました。初級→中級→上級の順に挑戦してください。

💡 演習の進め方:

📝 text ウィジェット演習

演習 T-1: シンプルなステータス表示初級

📝 課題:

「稼働中」というステータスをダッシュボードに表示するフローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • msg.payload: 「稼働中」(文字列)

ui-textノードの設定:

  • Group: 選択(必須)
  • Label: 「システム状態:」
  • Layout: row-spread
✅ 解答例フロー
[{"id":"t1_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false},{"id":"t1_ui_page","type":"ui-page","name":"演習T-1","ui":"t1_ui_base","path":"/t1","icon":"home","layout":"grid","theme":"t1_ui_theme","order":1,"visible":true,"disabled":false},{"id":"t1_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}},{"id":"t1_ui_group","type":"ui-group","name":"ステータス","page":"t1_ui_page","width":"6","height":"1","order":1,"showTitle":true},{"id":"t1_inject","type":"inject","name":"稼働中","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"稼働中","payloadType":"str","x":130,"y":100,"wires":[["t1_ui_text"]]},{"id":"t1_ui_text","type":"ui-text","group":"t1_ui_group","order":1,"width":0,"height":0,"name":"ステータス表示","label":"システム状態:","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#000000","x":330,"y":100,"wires":[]}]

演習 T-2: 現在時刻の表示中級

📝 課題:

現在時刻を「HH:MM:SS」形式で表示するフローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • 繰り返し: 指定した時間間隔 → 1秒
  • msg.payload: タイムスタンプ(timestamp)

Functionノード:

const now = new Date(msg.payload); const h = String(now.getHours()).padStart(2, '0'); const m = String(now.getMinutes()).padStart(2, '0'); const s = String(now.getSeconds()).padStart(2, '0'); msg.payload = `${h}:${m}:${s}`; return msg;

ui-textノード: Label「現在時刻:」、Layout「row-spread」を設定

✅ 解答例フロー
[ {"id":"t2_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"t2_ui_page","type":"ui-page","name":"演習T-2","ui":"t2_ui_base","path":"/t2","icon":"home","layout":"grid","theme":"t2_ui_theme","order":1,"visible":true,"disabled":false}, {"id":"t2_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}, {"id":"t2_ui_group","type":"ui-group","name":"時刻表示","page":"t2_ui_page","width":"6","height":"1","order":1,"showTitle":true}, {"id":"t2_inject","type":"inject","name":"1秒間隔","props":[{"p":"payload"}],"repeat":"1","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":100,"wires":[["t2_function"]]}, {"id":"t2_function","type":"function","name":"時刻フォーマット","func":"const now = new Date(msg.payload);\nconst h = String(now.getHours()).padStart(2, '0');\nconst m = String(now.getMinutes()).padStart(2, '0');\nconst s = String(now.getSeconds()).padStart(2, '0');\nmsg.payload = `${h}:${m}:${s}`;\nreturn msg;","outputs":1,"x":320,"y":100,"wires":[["t2_ui_text"]]}, {"id":"t2_ui_text","type":"ui-text","group":"t2_ui_group","order":1,"width":0,"height":0,"name":"時刻表示","label":"現在時刻:","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#000000","wrapText":false,"className":"","value":"payload","valueType":"msg","x":510,"y":100,"wires":[]} ]

演習 T-3: 温度による動的スタイル変更中級

📝 課題:

温度値に応じて文字色を変更するフローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • 高温テスト用: msg.payload に数値 32 を設定
  • 通常テスト用: msg.payload に数値 25 を設定

Functionノード:

msg.ui_update = { color: msg.payload >= 30 ? "#ff0000" : "#4caf50", fontSize: "20px" }; msg.payload = msg.payload + " ℃"; return msg;

ui-textノード: Styleチェックボックスを有効化(動的スタイル変更に必須)

✅ 解答例フロー
[ {"id":"t3_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"t3_ui_page","type":"ui-page","name":"演習T-3","ui":"t3_ui_base","path":"/t3","icon":"home","layout":"grid","theme":"t3_ui_theme","order":1,"visible":true,"disabled":false}, {"id":"t3_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}, {"id":"t3_ui_group","type":"ui-group","name":"温度モニター","page":"t3_ui_page","width":"6","height":"1","order":1,"showTitle":true}, {"id":"t3_inject_hot","type":"inject","name":"高温 (32℃)","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"32","payloadType":"num","x":130,"y":100,"wires":[["t3_function"]]}, {"id":"t3_inject_normal","type":"inject","name":"通常 (25℃)","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25","payloadType":"num","x":130,"y":160,"wires":[["t3_function"]]}, {"id":"t3_function","type":"function","name":"色設定","func":"msg.ui_update = {\n color: msg.payload >= 30 ? \"#ff0000\" : \"#4caf50\",\n fontSize: \"20px\"\n};\nmsg.payload = msg.payload + \" ℃\";\nreturn msg;","outputs":1,"x":320,"y":130,"wires":[["t3_ui_text"]]}, {"id":"t3_ui_text","type":"ui-text","group":"t3_ui_group","order":1,"width":0,"height":0,"name":"温度表示","label":"温度:","format":"{{msg.payload}}","layout":"row-spread","style":true,"font":"","fontSize":16,"color":"#000000","wrapText":false,"className":"","value":"payload","valueType":"msg","x":490,"y":130,"wires":[]} ]

演習 T-4: HTMLリンク付きステータス表示上級

📝 課題:

システムステータスをHTMLリンク付きで表示するフローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • msg.payload.name: "サーバーA"
  • msg.payload.status: "正常"(または"エラー")
  • msg.payload.url: "https://example.com/serverA"

Templateノード:

<a href="{{payload.url}}" target="_blank">{{payload.name}}</a>: <span style="color: {{#payload.status}}正常{{/payload.status}}{{^payload.status}}エラー{{/payload.status}} == '正常' ? 'green' : 'red'"> {{payload.status}} </span>
✅ 解答例フロー
[ {"id":"t4_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"t4_ui_page","type":"ui-page","name":"演習T-4","ui":"t4_ui_base","path":"/t4","icon":"home","layout":"grid","theme":"t4_ui_theme","order":1,"visible":true,"disabled":false}, {"id":"t4_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}, {"id":"t4_ui_group","type":"ui-group","name":"サーバーステータス","page":"t4_ui_page","width":"6","height":"1","order":1,"showTitle":true}, {"id":"t4_inject_ok","type":"inject","name":"正常ステータス","props":[{"p":"payload.name","v":"サーバーA","vt":"str"},{"p":"payload.status","v":"正常","vt":"str"},{"p":"payload.url","v":"https://example.com/serverA","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":100,"wires":[["t4_template"]]}, {"id":"t4_inject_err","type":"inject","name":"エラーステータス","props":[{"p":"payload.name","v":"サーバーB","vt":"str"},{"p":"payload.status","v":"エラー","vt":"str"},{"p":"payload.url","v":"https://example.com/serverB","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":160,"wires":[["t4_template"]]}, {"id":"t4_template","type":"template","name":"HTML生成","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"{{payload.name}}: {{payload.status}}","output":"str","x":340,"y":130,"wires":[["t4_ui_text"]]}, {"id":"t4_ui_text","type":"ui-text","group":"t4_ui_group","order":1,"width":0,"height":0,"name":"ステータス表示","label":"","format":"{{msg.payload}}","layout":"row-left","style":false,"font":"","fontSize":16,"color":"#000000","wrapText":false,"className":"","value":"payload","valueType":"msg","x":530,"y":130,"wires":[]} ]

📰 markdown ウィジェット演習

演習 M-1: 静的ヘルプページ初級

📝 課題:

ダッシュボードの操作説明をMarkdown形式で表示するヘルプページを作成してください。

🎯 要求仕様:

💡 ヒント

ui-markdownノードの設定:

  • Groupを選択(必須)
  • Contentに直接Markdownを記述

Markdown記法:

## 見出し2 ### 見出し3 - 箇条書き項目 - **太字のテキスト** [リンクテキスト](https://example.com)
✅ 解答例フロー
[ {"id":"m1_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"m1_ui_page","type":"ui-page","name":"演習M-1","ui":"m1_ui_base","path":"/m1","icon":"home","layout":"grid","theme":"m1_ui_theme","order":1,"visible":true,"disabled":false}, {"id":"m1_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}, {"id":"m1_ui_group","type":"ui-group","name":"ヘルプ","page":"m1_ui_page","width":"6","height":"1","order":1,"showTitle":true}, {"id":"m1_markdown","type":"ui-markdown","group":"m1_ui_group","name":"ヘルプページ","order":1,"width":0,"height":0,"content":"## 📖 操作ガイド\n\n### 基本操作\n1. **開始ボタン**をクリックして処理を開始\n2. ステータス表示で進捗を確認\n3. **停止ボタン**で終了\n\n### 注意事項\n- 処理中は画面を閉じないでください\n- エラー時は管理者に連絡\n\n---\n*詳細は [公式ドキュメント](https://dashboard.flowfuse.com/) を参照*","className":"","x":150,"y":100,"wires":[[]]} ]

演習 M-2: 動的センサーレポート初級

📝 課題:

複数のセンサーデータをMarkdown形式のテーブルで表示してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • msg.payload.temperature: 25.5 (数値)
  • msg.payload.humidity: 60 (数値)
  • msg.payload.pressure: 1013 (数値)

ui-markdownのContent:

## 📊 環境レポート | 項目 | 値 | |------|----| | 温度 | {{ msg?.payload?.temperature || '--' }} ℃ | | 湿度 | {{ msg?.payload?.humidity || '--' }} % |
✅ 解答例フロー
[ {"id":"m2_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"m2_ui_page","type":"ui-page","name":"演習M-2","ui":"m2_ui_base","path":"/m2","icon":"home","layout":"grid","theme":"m2_ui_theme","order":1,"visible":true,"disabled":false}, {"id":"m2_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}, {"id":"m2_ui_group","type":"ui-group","name":"センサーデータ","page":"m2_ui_page","width":"6","height":"1","order":1,"showTitle":true}, {"id":"m2_inject","type":"inject","name":"センサーデータ","props":[{"p":"payload.temperature","v":"25.5","vt":"num"},{"p":"payload.humidity","v":"60","vt":"num"},{"p":"payload.pressure","v":"1013","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":100,"wires":[["m2_markdown"]]}, {"id":"m2_markdown","type":"ui-markdown","group":"m2_ui_group","order":1,"width":0,"height":0,"name":"センサーレポート","content":"## 📊 環境モニタリングレポート\n\n| センサー | 値 | 単位 |\n|---------|-----|-----|\n| 🌡️ 温度 | **{{ msg?.payload?.temperature || '--' }}** | ℃ |\n| 💧 湿度 | **{{ msg?.payload?.humidity || '--' }}** | % |\n| 🌀 気圧 | **{{ msg?.payload?.pressure || '--' }}** | hPa |\n\n---\n*データ受信時に自動更新*","className":"","x":360,"y":100,"wires":[[]]} ]

演習 M-3: Mermaidシステム構成図上級

📝 課題:

Mermaidを使ってシステム構成図を動的に生成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • msg.payload.serverStatus: "正常"または"異常"
  • msg.payload.dbStatus: "正常"または"異常"

Functionノード:

const serverStyle = msg.payload.serverStatus === "正常" ? "fill:#4caf50,color:#fff" : "fill:#f44336,color:#fff"; const dbStyle = msg.payload.dbStatus === "正常" ? "fill:#4caf50,color:#fff" : "fill:#f44336,color:#fff"; msg.payload = { serverStyle: serverStyle, dbStyle: dbStyle, serverStatus: msg.payload.serverStatus, dbStatus: msg.payload.dbStatus }; return msg;

ui-markdownのContent:

## システム状態 ```mermaid graph TD; A[センサー] --> B[サーバー]; B --> C[データベース]; style B {{ msg?.payload?.serverStyle || '' }}; style C {{ msg?.payload?.dbStyle || '' }}; ```
✅ 解答例フロー
[ {"id":"m3_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"m3_ui_page","type":"ui-page","name":"演習M-3","ui":"m3_ui_base","path":"/m3","icon":"home","layout":"grid","theme":"m3_ui_theme","order":1,"visible":true,"disabled":false}, {"id":"m3_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}, {"id":"m3_ui_group","type":"ui-group","name":"システム構成","page":"m3_ui_page","width":"6","height":"1","order":1,"showTitle":true}, {"id":"m3_inject_ok","type":"inject","name":"全正常","props":[{"p":"payload.serverStatus","v":"正常","vt":"str"},{"p":"payload.dbStatus","v":"正常","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":100,"wires":[["m3_function"]]}, {"id":"m3_inject_err","type":"inject","name":"DB異常","props":[{"p":"payload.serverStatus","v":"正常","vt":"str"},{"p":"payload.dbStatus","v":"異常","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":110,"y":160,"wires":[["m3_function"]]}, {"id":"m3_function","type":"function","name":"スタイル生成","func":"const serverStyle = msg.payload.serverStatus === '正常' ? 'fill:#4caf50,color:#fff' : 'fill:#f44336,color:#fff';\nconst dbStyle = msg.payload.dbStatus === '正常' ? 'fill:#4caf50,color:#fff' : 'fill:#f44336,color:#fff';\nmsg.payload = { serverStyle, dbStyle, serverStatus: msg.payload.serverStatus, dbStatus: msg.payload.dbStatus };\nreturn msg;","outputs":1,"x":300,"y":130,"wires":[["m3_markdown"]]}, {"id":"m3_markdown","type":"ui-markdown","group":"m3_ui_group","order":1,"width":0,"height":0,"name":"システム構成図","content":"## 🔧 システム状態\n\n```mermaid\ngraph TD;\n A[センサー] --> B[サーバー];\n B --> C[データベース];\n B --> D[Dashboard];\n style B {{ msg?.payload?.serverStyle || '' }};\n style C {{ msg?.payload?.dbStyle || '' }};\n```\n\n| コンポーネント | 状態 |\n|---------------|------|\n| サーバー | {{ msg?.payload?.serverStatus || '不明' }} |\n| データベース | {{ msg?.payload?.dbStatus || '不明' }} |","x":490,"y":130,"wires":[[]]} ]

🔔 notification ウィジェット演習

演習 N-1: シンプルな通知表示初級

📝 課題:

ボタンをクリックすると「処理が完了しました」という通知を表示するフローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • msg.payload: 「処理が完了しました」(文字列)

ui-notificationノードの設定:

  • UI: 適切なui-baseを選択(必須)
  • Position: top right
  • Timeout: 3
  • Show Countdown Bar: チェック
✅ 解答例フロー
[ {"id":"n1_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"n1_inject","type":"inject","name":"通知送信","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"処理が完了しました","payloadType":"str","x":130,"y":100,"wires":[["n1_notify"]]}, {"id":"n1_notify","type":"ui-notification","ui":"n1_ui_base","position":"top right","colorDefault":true,"color":"#000000","displayTime":"3","showCountdown":true,"allowDismiss":true,"dismissText":"OK","allowConfirm":false,"confirmText":"","raw":false,"name":"完了通知","x":330,"y":100,"wires":[[]]} ]

演習 N-2: 確認ダイアログの結果分岐中級

📝 課題:

確認ダイアログを表示し、ユーザーの選択(OK/NG)に応じて処理を分岐させるフローを作成してください。

🎯 要求仕様:

📌 ポイント: サンプルフローの「パターン3: 確認ダイアログと結果の処理分岐」を参考にしてください。

💡 ヒント

Injectノードの設定:

  • msg.payload: 「処理を実行しますか?」(文字列)

ui-notificationの出力:

  • 確認ボタンがクリックされると、confirmTextに設定した文字列(この場合「OK」)がmsg.payloadに出力されます
  • 却下ボタンがクリックされると、dismissTextに設定した文字列(この場合「NG」)がmsg.payloadに出力されます

Switchノードの設定:

  • プロパティ: msg.payload
  • 条件: == "OK"(確認ボタンが押された場合のみ出力)

Changeノードの設定:

  • msg.payloadを「処理を実行しました」に設定
✅ 解答例フロー
[ {"id":"n2_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"n2_inject","type":"inject","name":"確認要求","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"処理を実行しますか?","payloadType":"str","x":140,"y":100,"wires":[["n2_notification"]]}, {"id":"n2_notification","type":"ui-notification","ui":"n2_ui_base","position":"center center","colorDefault":false,"color":"#2196f3","displayTime":"0","showCountdown":false,"outputs":1,"allowDismiss":true,"dismissText":"NG","allowConfirm":true,"confirmText":"OK","raw":false,"className":"","name":"確認ダイアログ","x":350,"y":100,"wires":[["n2_switch"]]}, {"id":"n2_switch","type":"switch","name":"OK判定","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"OK","vt":"str"}],"checkall":"false","repair":false,"outputs":1,"x":520,"y":100,"wires":[["n2_change"]]}, {"id":"n2_change","type":"change","name":"メッセージ設定","rules":[{"t":"set","p":"payload","pt":"msg","to":"処理を実行しました","tot":"str"}],"x":680,"y":100,"wires":[["n2_debug"]]}, {"id":"n2_debug","type":"debug","name":"結果表示","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload","statusType":"auto","x":850,"y":100,"wires":[]} ]

演習 N-3: 条件付き通知システム上級

📝 課題:

温度値に応じて異なる種類の通知を表示するシステムを作成してください。

🎯 要求仕様:

💡 ヒント

Switchノードの設定:

  • プロパティ: msg.payload
  • 条件1: >= 30 → 出力1
  • 条件2: >= 20 → 出力2
  • 条件3: その他 → 出力3

各ui-notificationの設定を変える:

  • Position、Color、表示メッセージを条件ごとに設定
✅ 解答例フロー
[ { "id": "ex3_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false }, { "id": "ex3_inject_high", "type": "inject", "name": "高温 (35℃)", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "35", "payloadType": "num", "x": 130, "y": 100, "wires": [["ex3_switch"]] }, { "id": "ex3_inject_normal", "type": "inject", "name": "通常 (25℃)", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "25", "payloadType": "num", "x": 130, "y": 160, "wires": [["ex3_switch"]] }, { "id": "ex3_inject_low", "type": "inject", "name": "低温 (15℃)", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "15", "payloadType": "num", "x": 130, "y": 220, "wires": [["ex3_switch"]] }, { "id": "ex3_switch", "type": "switch", "name": "温度判定", "property": "payload", "propertyType": "msg", "rules": [ {"t": "gte", "v": "30", "vt": "num"}, {"t": "gte", "v": "20", "vt": "num"}, {"t": "else"} ], "checkall": "false", "repair": false, "outputs": 3, "x": 320, "y": 160, "wires": [["ex3_func_high"], ["ex3_func_normal"], ["ex3_func_low"]] }, { "id": "ex3_func_high", "type": "function", "name": "高温警告", "func": "msg.ui_update = {\n color: \"#f44336\",\n position: \"top center\"\n};\nmsg.payload = `⚠️ 高温警告: ${msg.payload}℃`;\nreturn msg;", "outputs": 1, "x": 490, "y": 100, "wires": [["ex3_notify"]] }, { "id": "ex3_func_normal", "type": "function", "name": "通常通知", "func": "msg.ui_update = {\n color: \"#4caf50\",\n position: \"top right\"\n};\nmsg.payload = `✓ 正常: ${msg.payload}℃`;\nreturn msg;", "outputs": 1, "x": 490, "y": 160, "wires": [["ex3_notify"]] }, { "id": "ex3_func_low", "type": "function", "name": "低温注意", "func": "msg.ui_update = {\n color: \"#2196f3\",\n position: \"bottom right\"\n};\nmsg.payload = `❄️ 低温注意: ${msg.payload}℃`;\nreturn msg;", "outputs": 1, "x": 490, "y": 220, "wires": [["ex3_notify"]] }, { "id": "ex3_notify", "type": "ui-notification", "ui": "ex3_ui_base", "position": "top right", "colorDefault": true, "color": "#000000", "displayTime": "3", "showCountdown": true, "allowDismiss": true, "dismissText": "OK", "allowConfirm": false, "confirmText": "", "raw": false, "outputs": 1, "className": "", "name": "通知", "x": 670, "y": 160, "wires": [] } ]

🔊 audio ウィジェット演習

演習 A-1: Audio Playerの基本操作初級

📝 課題:

ダッシュボード上にオーディオプレイヤーを表示し、BGMを再生できるようにしてください。

🎯 要求仕様:

💡 Audio Playerモードについて:

Audio Playerモードでは、ダッシュボードに標準のオーディオプレイヤーUIが表示されます。ユーザーはこのUIで再生/一時停止/シーク/音量調整を操作できます。Injectノードは音声URLを設定するためのテスト用です。

💡 ヒント

起動時リセット用Injectノードの設定:

  • 繰り返し: なし(once: true - 起動後1回だけ実行)
  • msg.playback: "stop"(文字列)

URL設定用Injectノードの設定:

  • msg.payload: 音声ファイルのURL(例: https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3)
  • msg.ui_update: {"autoplay":"on", "loop":"on"}

ui-audioノードの設定:

  • Mode: Audio Player
  • Group: 選択(必須 - ダッシュボードにUIを表示するため)
  • Size: 適切な幅を設定(6など)

❗ ポイント:

  • 「起動時リセット」はフロー再起動時の自動再生を防ぎます
  • URL設定Injectをクリック → URLが送信され自動再生開始
  • 一時停止/再開 → ダッシュボードのプレイヤーUIで操作
✅ 解答例フロー
[ {"id":"a1_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"a1_ui_page","type":"ui-page","name":"演習A-1","ui":"a1_ui_base","path":"/a1","icon":"home","layout":"grid","theme":"a1_ui_theme","order":1,"visible":true,"disabled":false}, {"id":"a1_ui_theme","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}, {"id":"a1_ui_group","type":"ui-group","name":"Audio Player","page":"a1_ui_page","width":"6","height":"1","order":1,"showTitle":true}, {"id":"a1_reset","type":"inject","name":"起動時リセット","props":[{"p":"playback","v":"stop","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.5,"topic":"","x":140,"y":80,"wires":[["a1_audio"]]}, {"id":"a1_inject_url","type":"inject","name":"URL設定&再生","props":[{"p":"payload"},{"p":"ui_update","v":"{\"autoplay\":\"on\",\"loop\":\"on\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3","payloadType":"str","x":150,"y":120,"wires":[["a1_audio"]]}, {"id":"a1_audio","type":"ui-audio","group":"a1_ui_group","name":"BGM Player","order":1,"width":"6","height":"1","mode":"src","voice":"","src":"","autoplay":false,"loop":false,"muted":false,"className":"","x":360,"y":100,"wires":[[]]} ]

※ 再生/一時停止はダッシュボード上のプレイヤーUIで操作します。「起動時リセット」はフロー再起動時の自動再生を防ぎます。

演習 A-2: TTS音声読み上げ初級

📝 課題:

テキストを音声で読み上げるTTS(Text-to-Speech)フローを作成してください。

🎯 要求仕様:

💡 ヒント

Injectノードの設定:

  • msg.payloadをJSONオブジェクトとして設定:
{ "text": "こんにちは、ダッシュボードへようこそ", "rate": 1.0, "volume": 100 }

ui-audioノード:

  • Mode: Text-to-Speech
  • UI: 選択(必須)

❗ 注意: TTSはユーザーがダッシュボードをクリックした後でないと動作しません

✅ 解答例フロー
[ {"id":"a2_ui_base","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false}, {"id":"a2_inject","type":"inject","name":"読み上げ","props":[{"p":"payload","v":"{\"text\":\"こんにちは、ダッシュボードへようこそ\",\"rate\":1.0,\"volume\":100}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":100,"wires":[["a2_tts"]]}, {"id":"a2_tts","type":"ui-audio","ui":"a2_ui_base","name":"TTS","mode":"tts","voice":"","src":"","autoplay":false,"loop":false,"muted":false,"className":"","x":310,"y":100,"wires":[[]]} ]

演習 A-3: 音声アラートシステム上級

📝 課題:

異常検知時に音声と視覚の両方でアラートを出すシステムを作成してください。

🎯 要求仕様:

💡 ヒント

並列処理の実現:

Functionノードから複数の出力を使用するか、同じメッセージを両方のノードに接続します。

ui-audio(TTS)の設定:

  • Mode: Text-to-Speech
  • UI: 適切なui-baseを選択

Functionノードの出力:

// 通知用と音声用の両方を出力 const notifyMsg = { payload: "<h3>⚠️ 異常検知</h3><p>システムに異常が発生しました</p>", ui_update: { color: "#f44336", position: "center center" } }; const audioMsg = { payload: { text: "警告、異常を検知しました", rate: 1.0, volume: 100 } }; return [notifyMsg, audioMsg];
✅ 解答例フロー
[ { "id": "ex4_ui_base", "type": "ui-base", "name": "Dashboard", "path": "/dashboard", "appIcon": "", "includeClientData": true, "acceptsClientConfig": ["ui-notification", "ui-control"], "showPathInSidebar": false, "headerContent": "page", "navigationStyle": "default", "titleBarStyle": "default", "showPageTitle": true, "showReconnectNotification": true, "notificationDisplayTime": 1, "showDisconnectNotification": true, "allowInstall": false }, { "id": "ex4_inject", "type": "inject", "name": "異常検知", "props": [{"p": "payload"}], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "異常", "payloadType": "str", "x": 120, "y": 100, "wires": [["ex4_function"]] }, { "id": "ex4_function", "type": "function", "name": "アラート生成", "func": "const notifyMsg = {\n payload: \"

⚠️ 異常検知

システムに異常が発生しました

\",\n ui_update: {\n color: \"#f44336\",\n position: \"center center\",\n displayTime: 0,\n allowDismiss: true,\n dismissText: \"確認\",\n raw: true\n }\n};\n\nconst audioMsg = {\n payload: {\n text: \"警告、異常を検知しました\",\n rate: 1.0,\n volume: 100\n }\n};\n\nreturn [notifyMsg, audioMsg];", "outputs": 2, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 310, "y": 100, "wires": [["ex4_notify"], ["ex4_audio"]] }, { "id": "ex4_notify", "type": "ui-notification", "ui": "ex4_ui_base", "position": "center center", "colorDefault": false, "color": "#f44336", "displayTime": "0", "showCountdown": false, "allowDismiss": true, "dismissText": "確認", "allowConfirm": false, "confirmText": "", "raw": true, "outputs": 1, "className": "", "name": "警告通知", "x": 510, "y": 80, "wires": [] }, { "id": "ex4_audio", "type": "ui-audio", "name": "警告音声", "ui": "ex4_ui_base", "mode": "tts", "src": "", "autoplay": false, "loop": false, "muted": false, "voice": "", "className": "", "x": 510, "y": 120, "wires": [] } ]

🎓 8. まとめ

表示系ウィジェットの重要ポイント

⚠️ よくある間違い

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

問題 原因 解決方法
textが更新されない Groupが未選択 textノードでGroupを選択
HTMLが表示されない エスケープされている ui-notificationの場合はAccept Rawを有効化
Mermaidが描画されない コードブロックの指定ミス ```mermaidで始めているか確認
通知が表示されない UIが未選択 ui-notificationでUIを選択
TTSが動作しない ユーザージェスチャーなし ダッシュボードを一度クリックしてから再試行
動的スタイルが反映されない Styleが無効 ui-textでStyleチェックボックスを有効化
全クライアントに通知されない msg._clientが残っている Changeノードでmsg._clientを削除
フロー再起動時に音が自動再生される Audio Playerがブラウザ側で状態を保持 起動時にmsg.playback="stop"を送信するInjectノードを追加(once: true)

💡 10. 実務での活用例

ケース1: IoTセンサーモニタリング

【フロー構成】 MQTTセンサー → Function(データ整形) → ui-text(リアルタイム値) ↘ ui-markdown(統計レポート) ↘ Switch → ui-notification(閾値アラート) ↘ ui-audio(音声警告)

ケース2: 工場ライン監視

【フロー構成】 PLCデータ → ui-markdown(Mermaidでライン状態図) → ui-text(各工程のステータス) × 複数 → 異常検知 → ui-notification + ui-audio(全クライアント通知)

ケース3: スマートホーム

【フロー構成】 各種センサー → ui-markdown(家全体の状態サマリー) → ui-notification(来客通知、異常通知) → ui-audio(音声での状態報告)

ケース4: ヘルプ・ドキュメント表示

【フロー構成】 ui-button(ヘルプ) → ui-notification(クイックヒント) ui-markdown(操作マニュアル、利用規約) ui-markdown(Mermaidでワークフロー図)

📚 11. 追加リソース


このガイドが役に立ちましたら、実際のプロジェクトで練習してみてください!
表示系ウィジェットはダッシュボードの情報伝達の基盤となります。

参照元:FlowFuse Dashboard 2.0 公式ドキュメント

🏠