案内ロボットシステムのハイブリッド構成。UIはNode-RED、ロジックはPythonで実装。
nodered_json_with_python/
├── README.md # このファイル
├── manifest.json # UIテンプレート注入マッピング(build_flow.py用)
├── flows/ # Node-REDフロー(ビルド成果物含む)
│ ├── mqtt_broker_flow.json # ★ MQTTブローカー (Aedes)
│ ├── robot_ui_flow.json # エンドユーザー向けUI(手編集禁止・ビルド成果物)
│ └── admin_ui_flow.json # 管理者ダッシュボード
├── src/ # robot_ui_flow.json から外部化したUIテンプレート
│ ├── ui/
│ │ └── main_template.html # UIテンプレート本体(HTML+CSS+JS)
│ ├── i18n.js # 多言語データ(scope.i18n オブジェクトリテラル)
│ └── flows/
│ └── robot_ui_flow.skeleton.json # プレースホルダー入りフロー骨格
├── tools/ # フロービルドツール
│ ├── extract_flow.py # flows/robot_ui_flow.json → src/ へ抽出
│ └── build_flow.py # src/ → flows/robot_ui_flow.json を再生成
├── python/ # Pythonサービス
│ ├── patrol_service.py # パトロールロジック
│ ├── config.py # 設定
│ ├── run.sh # 起動スクリプト
│ ├── requirements.txt # 依存関係
│ ├── mock_temi.py # ★ Temiシミュレーター
│ └── yolo_topic_test.py # ★ YOLOデータシミュレーター
└── docs/
└── mqtt_interface.md # MQTTトピック仕様
本システムは3コンポーネント構成(Node-RED / Python / 外部API server)で動作します。
- MQTTブローカー (Aedes) ← 外部Mosquitto不要!
- 画面表示(広告 / メニュー / 案内(チャット) / 考え中 / リモート通話 / 通話終了 の6状態、
src/ui/main_template.html) - UIモード切り替え
- 管理者ダッシュボード表示
- メニュー選択・チャット送信・音声入力を、下記「外部API server」へHTTPSでリクエスト(
node_http_request=POST /chat、main_template.htmlのfetch(.../voice_chat)) - 外部API serverからの応答を受け取り、Temiへ
speak(発話)コマンドを直接MQTT送信(node_mqtt_out_temi→request/temi01)
- パトロールロジック(状態管理)
- タイマー制御
- Guest検知・接近計算
- Temiへの移動コマンド送信(
gotoLocation/goToPosition/stop)
⚠️ Temiへのコマンド送信経路は2つあります。 移動系コマンドはPython (patrol_service.py) が送信しますが、チャット応答後のspeakコマンドはNode-RED(node_mqtt_out_temi)が直接送信しており、Pythonを経由しません。どちらも同じrequest/temi01トピックにPublishされます。
- 本ディレクトリの外、同一ワークスペース内の
robot_service_api_server(FastAPI + Gemini API)として動作する別プロセスです - Node-RED からのチャット (
POST /chat) ・音声チャット (POST /voice_chat) リクエストを受け、Gemini経由で応答を生成 - 未起動の場合、メニュー選択・チャット送信・音声入力が失敗します(
error_networkまたはerror_ai。src/ui/main_template.htmlのエラーメッセージ参照) - 起動方法は後述の「🚀 起動方法」、URL設定は「🌐 ネットワーク設定」を参照
1. Node-RED ← MQTTブローカーが先に起動
↓
2. mock_temi.py ← Temiシミュレーター(本番ではTemi実機)
↓
3. run.sh ← Patrol Service
↓
4. yolo_topic_test.py ← YOLOシミュレーター(本番ではYOLO)
外部API server(robot_service_api_server)はMQTT起動順序に依存しませんが、
メニュー選択・チャット・音声機能を使うには別途起動しておく必要があります(下記5.)。
node-red- Node-REDエディタを開く(https://192.168.11.7:1880)
- メニュー → インポート → すべてのフローを順番にインポート:
flows/mqtt_broker_flow.json← 最初に!flows/robot_ui_flow.jsonflows/admin_ui_flow.json
- デプロイをクリック
⚠️ mqtt_broker_flow.jsonをインポートしてデプロイすると、Node-RED内でMQTTブローカー(ポート1883)が起動します。
cd python
python mock_temi.pycd python
./run.shcd python
python yolo_topic_test.py本ディレクトリの外、同一ワークスペース内の robot_service_api_server(別リポジトリ、FastAPI + Gemini API)を別途起動します。
cd ../robot_service_api_server
./run.sh- ポート: 8000(HTTPS)
flows/robot_ui_flow.jsonのPOST /chatノード、src/ui/main_template.htmlのfetch(.../voice_chat)がこのサーバーへリクエストします- 未起動の場合、メニュー選択・チャット送信・音声入力が
error_network(通信エラー)またはerror_ai(AIシステムエラー)で失敗します - 詳細は
robot_service_api_server/README.mdを参照
https://192.168.11.7:1880/ui の「管理者コンソール」タブ
| ボタン | 動作 |
|---|---|
| Start Patrol 🟢 | システム開始、パトロール有効化 |
| Stop Patrol 🔴 | その場で停止 |
| System Reset 🟡 | UIリセット |
| Go Home 🔵 | ホームへ帰還 |
| 状況 | 動作 |
|---|---|
| normal検知 | パトロール再開 |
| crowded検知 | その場で停止(ホームに帰らない) |
| ゲスト検知 | 接近して案内開始 |
| ゲスト対応中にcrowded | 停止するがゲスト対応継続 |
| ゲスト対応中 | 音声案内なし |
| 項目 | 値 |
|---|---|
| ブローカー | 192.168.11.7 または localhost |
| ポート | 1883 |
| 認証 | なし (Aedes) |
| 暗号化 | なし |
⚠️ 以前のMosquitto(Docker)は使用しません。Node-RED内のAedesがブローカーです。
⚠️ 未使用の残骸に注意:flows/admin_ui_flow.json内の mqtt-broker ノードは名前がLocal Mosquittoのままで、認証情報(user:test/ password:4701)もハードコードされています。これは旧Mosquitto構成の残骸であり、Aedes自体は認証を行わないためこの設定値は無視されます(実質未使用)。クリーンアップ候補ですが、本READMEの方針上フローJSONは手編集しないため現状は残置しています。
| トピック | 方向 | 用途 |
|---|---|---|
internal/ui_event |
UI → Python | UI操作イベント |
internal/ui_control |
Python → UI | UI制御コマンド |
internal/admin/info |
Python → Admin | 管理者ステータス |
station/sensor/data |
YOLO → Python | 混雑度・Guest座標 |
info/temi01 |
Temi → Python | ロボット状態 |
request/temi01 |
Python / Node-RED → Temi | 移動コマンド(Python)、チャット応答後の speak(Node-RED) |
request/voice |
UI → (なし) | 録音開始/停止イベント({"command": "startRecording"/"stopRecording"})。現状どのコンポーネントも購読しておらず、実際の音声処理はHTTPの /voice_chat 経由で行われるためデッドコードの可能性あり |
# Linux
ip addr show | grep "inet "例: 192.168.11.7
| ファイル | 変更箇所 |
|---|---|
python/config.py |
MQTT_BROKER = "192.168.11.7" |
flows/*.json |
mqtt-broker の broker を変更 |
flows/robot_ui_flow.json(node_http_request / POST /chat)、src/ui/main_template.html(fetch(...)) |
https://192.168.11.7:8000/... を外部API server(robot_service_api_server)の実際のホストに変更。UIテンプレートを編集する場合は src/ui/main_template.html を修正して tools/build_flow.py で再ビルドすること(flows/robot_ui_flow.json は直接編集しない) |
python/mock_temi.py(16行目) |
BROKER_ADDRESS = "192.168.11.7" |
python/yolo_topic_test.py(15行目) |
BROKER_ADDRESS = "192.168.11.7" |
⚠️ python/config.pyのMQTT_BROKERはデフォルトが"localhost"であるのに対し、mock_temi.py/yolo_topic_test.pyは"192.168.11.7"をハードコードしています。単一マシンで動かす場合はmock_temi.pyとyolo_topic_test.pyのBROKER_ADDRESSを"localhost"(またはそのマシン自身のIP)に変更してください。 変更を忘れると、これらのシミュレーターだけ別ホストのブローカーに接続しようとして通信できなくなります。
⚠️ 開発時の暫定設定として、flows/robot_ui_flow.jsonのPOST /chatノードはTLS証明書検証を無効化しています(tls-configノードIgnore Self-Signed Certのverifyservercert: false)。自己署名証明書向けの開発専用設定であり、本番環境ではこのままにしないこと。
https://192.168.11.7:1880/ui
Web Audio API(音声入力)を使うにはHTTPS化が必須です。
証明書ファイル: /home/yutori/Documents/cert/
key.pemcert.pem
settings.js に以下が設定済み:
https: {
key: require("fs").readFileSync('/home/yutori/Documents/cert/key.pem'),
cert: require("fs").readFileSync('/home/yutori/Documents/cert/cert.pem')
},
⚠️ 自己署名証明書のため、ブラウザで「安全ではありません」警告が出ます。
robot_ui_flow.json の node_ui_template_main(画面表示ノード)は19,000文字超のHTML+CSS+JSを1つの文字列フィールドに丸ごと保持しており、フローJSONを直接編集するのは差分レビューも困難でした。パトロールロジックを python/patrol_service.py に外部化したのと同じ考え方で、UIテンプレートも src/ 配下のソースファイルに外部化しています。
- UIテンプレートの編集は
src/ui/main_template.html(多言語文言はsrc/i18n.js)で行う。 flows/robot_ui_flow.jsonは手編集禁止(ビルド成果物)。 直接編集すると次回ビルド時に上書きされます。
src/ を編集したら、以下でフローJSONを再生成します。
python3 tools/build_flow.py--check を付けると、実際にはファイルを書き換えずに現在の flows/robot_ui_flow.json との差分の有無だけを確認できます(CIや編集前チェックに利用)。
python3 tools/build_flow.py --checkNode-REDエディタ側でテンプレートを直接編集してしまった場合は、以下で src/ 側に再抽出して同期を取り直します。
python3 tools/extract_flow.py抽出・ビルドのマッピングは manifest.json に記録されており(対象ノードID・フィールド名・分割元ファイル)、build_flow.py と extract_flow.py はこれを共有します。
Temiロボットの動作をシミュレート。
python mock_temi.py機能:
gotoLocation- ロケーション移動goToPosition- 座標移動speak- 発話stop- 停止- 5秒ごとにステータス送信
YOLOセンサーデータをシミュレート。
python yolo_topic_test.py機能:
- 2.5秒ごとにデータ送信
- ランダムに normal / crowded を切り替え
- ランダムにGuest検知を発生
- Node-RED をインストール (
npm install -g node-red) - node-red-contrib-aedes をインストール (
cd ~/.node-red && npm install node-red-contrib-aedes) - node-red-dashboard をインストール (
cd ~/.node-red && npm install node-red-dashboard) - Python 3.x をインストール
- 全フローをインポート・デプロイ
- Python Patrol Service を起動
- 外部API server(
robot_service_api_server、ポート8000)をセットアップ・起動(メニュー・チャット・音声機能に必須。起動方法5.を参照) - HTTPS 化(設定済み)
- Node-RED ダッシュボード (
https://192.168.11.7:1880/ui) にアクセス - 広告画面が表示されることを確認
- 画面タッチでメニュー画面に遷移
- Python側のログに
UI操作: パトロール中断・案内モードが表示されれば成功 - 管理者コンソールでシステムログが表示されることを確認
- 案内画面(
guide)のチャット欄末尾に、デモ用の架空ダイヤであることを示す注記(demo_notice、日英対応)を追加しました。 src/i18n.jsとsrc/ui/main_template.htmlを編集し、tools/build_flow.pyでflows/robot_ui_flow.jsonを再ビルドしています。
robot_ui_flow.jsonのnode_ui_template_main(19,000文字超のHTML+CSS+JS文字列)をsrc/ui/main_template.html・src/i18n.jsに外部化しました。tools/build_flow.py(src/→flows/robot_ui_flow.jsonの再生成)、tools/extract_flow.py(逆抽出)、manifest.json(抽出・ビルドのマッピング)を追加しました。- 詳細は本READMEの「🧩 UIテンプレートのビルドフロー」を参照してください。
-
到着待機ロジックの導入:
- Temiへの移動コマンド送信後、ステータスが
complete(完了)になるまで次のコマンド送信を待機するように変更しました。 - これにより、移動中に次の目的地への指示が重複して送信される問題を解消しました。
- Temiへの移動コマンド送信後、ステータスが
-
タイムアウト処理の追加:
- Temiからの到着通知が届かない環境(ネットワーク不安定など)を考慮し、移動指示から 20秒 経過しても通知がない場合は、自動的に到着済みとみなして次の動作へ進むタイムアウト処理を追加しました。
- タイムアウト時間は
patrol_service.py内で調整可能です。