今回はNode-REDを通して、MinecraftからIoTデバイスを制御する方法を紹介します。
本記事ではWindowsでの制御方法を紹介しております。
Contents
開発環境
PC:Windows 11
サーバー:Raspberry Pi 4 Model B (Linux)
開発ツール:Node.js (v18.19.0)
Node-RED (v.3.1.3)
インターネット:社内の無線LAN
Minecraft:v1.20.51 (執筆時点)
Node.js / Node-REDの導入方法はこちら
Windows:(公式サイト) https://nodered.jp/docs/getting-started/windows
Mac:https://blog.smartlight.co.jp/?p=3059
PCをRaspberry Piと接続する
検索バーから コマンドプロンプト を立ち上げ、SSHで接続します。
<code>ssh ユーザー名@ホスト名 もしくは IPアドレス</code>
ssh pi@raspberrypi.local もしくは ssh pi@xxx.xxx.x.xx
パスワードを求められるので入力します。
接続完了後はこのような画面になります。

Node-REDを立ち上げる
続いて、コマンドプロンプトからNode-REDを立ち上げます。
node-red
エディターへアクセスするには、『http://127.0.0.1:1880/』または『http://localhost:1880』をChrome等のブラウザに入力します。
するとこのような画面が開きます。

フローの概要
ある程度フローの形が決まっているので、先述します。

Websocketノードは待ち受けに設定する。

Node-REDがMinecraftに送信するフロー
MinecraftとNode-REDの通信は、アクションのリクエストが必要です。Websocketサーバーにリクエストを送るが、それにはUUIDが必要となる。UUIDを生成するノードはパレットから入手できる。UUIDのプロパティはVersion 4 (random)である。

Requestには下記のようなJavascriptコードを入れる。
//Minecraft規定のイベントから情報を取得
const uuid = msg.payload; //uuidの保管
const subscribeMessageJSON = {
"header": {
"version": 1, // プロトコルのバージョンを指定。1.20.51の時点では1で問題ない
"requestId": uuid, // msg.payloadに保管したUUIDを与える
"messageType": "commandRequest", // "commandRequest" を指定
"messagePurpose": "subscribe", // "subscribe" を指定
},
"body": {
"eventName": "PlayerTravelled" // イベント名を指定。今回はプレイヤーの位置情報
},
};
// イベント購読用のJSONをシリアライズ(文字列化)して送信
msg.payload= JSON.stringify(subscribeMessageJSON);
return msg; //msg.payloadで返す
Minecraftからの返答をNode-REDが受信するフロー
受信したオブジェクト情報をJSONへ変換するため、間にJSONノードをデフォルト設定のまま入れる。うまく動作すれば、デバッグメニューに取得したデータが送信されるはず。
MinecraftへCommandRequestするフロー

実際にMinecraftへCommandを送りたい場合、以下のようなコードを使用します。
今回は前述のPlayerTravelledイベントの情報をSwitchで条件分岐させ、プレイヤーが動くとチャットに流れるフローを作りました。
const pName = msg.payload.body.player.name;
const uuid = msg.pay;
const commandRequestMessageJSON = {
"header": {
"version": 1, // プロトコルのバージョン1.18.2時点では1でOK
"requestId": uuid, // UUIDv4を生成して指定
"messageType": "commandRequest", // commandRequestを指定
"messagePurpose": "commandRequest", // commandRequestを指定
},
"body": {
"origin": {
"type": "player" // 誰がコマンドを実行するかを指定(ただし、Player以外にどの値が利用可能かは要調査)
},
"version": 1, // プロトコルのバージョン1.18.2時点では1でOK
"commandLine": `say いま${pName}が動いたよ`, // マイクラで実行したいコマンドを指定(ここではニワトリをスポーンさせるコマンドを指定)
}
};
msg.payload = JSON.stringify(commandRequestMessageJSON);
return msg;
フローを作る
今回はEnOcean EASYFIT (BLE)とLutron電動ロールスクリーンを動作させます。
完成したノードがこちら。(画面外にLutronのノードあり)

今回Minecraft規定のイベント3種類のデータを取得し、Switchノードで条件分岐させ、動作を検証した。その結果、下記の通信すべてが可能だった。
・ゲームからのアクション ➞ IoTデバイスを動かす
・IoT デバイスのアクション ➞ ゲーム内でアクション
・ゲームからのアクション ➞ ゲーム内でアクション
Minecraftと接続する
前述したとおりMinecraftはWebsocketという通信でサーバーと接続することができます。
チャットを開き、コマンドを入力します。
/connect 127.0.0.1:1880 または /connect xxx.xx.x.xx:1880
Websocketの設定でパスを使用した場合はこちら。(本記事では/wsのパスを使用)
/connect 127.0.0.1:1880/ws または /connect xxx.xx.x.xx:1880/ws
接続完了のメッセージを確認したら、Node-REDで再度デプロイし、デバッグ画面から動作を確認します。
実行結果
Minecraft ➞ IoTデバイス
チャットで「fullopen」「fullclose」と打ってロールスクリーンを全開・全閉している。
IoTデバイス ➞ Minecraft
2か所のスイッチにsummonコマンドを割り当てている。
Minecraft ➞ Minecraft
「oak_planks」を置いたときにだけchickenをsummonしている。
ノード読み取り用
概要で紹介したフロー
[ { "id": "8cc4b187bead8951", "type": "inject", "z": "aa9a062666553723", "name": "inject", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 150, "y": 160, "wires": [ [ "60e1eddb9aba7be4" ] ] }, { "id": "60e1eddb9aba7be4", "type": "uuid", "z": "aa9a062666553723", "uuidVersion": "v4", "namespaceType": "", "namespace": "", "namespaceCustom": "", "name": "", "field": "payload", "fieldType": "msg", "x": 310, "y": 160, "wires": [ [ "dd0b8342d558242a" ] ] }, { "id": "dd0b8342d558242a", "type": "function", "z": "aa9a062666553723", "name": "Request", "func": "//Minecraft規定のイベントから情報を取得\nconst uuid = msg.payload; //uuidの保管\n\nconst subscribeMessageJSON = {\n \"header\": {\n \"version\": 1, // プロトコルのバージョンを指定。1.20.51の時点では1で問題ない\n \"requestId\": uuid, // msg.payloadに保管したUUIDを与える\n \"messageType\": \"commandRequest\", // \"commandRequest\" を指定\n \"messagePurpose\": \"subscribe\", // \"subscribe\" を指定\n },\n \"body\": {\n \"eventName\": \"PlayerTravelled\" // イベント名を指定。今回はプレイヤーの位置情報\n },\n};\n\n// イベント購読用のJSONをシリアライズ(文字列化)して送信\nmsg.payload= JSON.stringify(subscribeMessageJSON);\n\nreturn msg; //msg.payloadで返す", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 480, "y": 160, "wires": [ [ "f7745a07751ec403" ] ] }, { "id": "8f7ebd96829d0294", "type": "comment", "z": "aa9a062666553723", "name": "Minecraftからの返答をNode-REDが受信するフロー", "info": "", "x": 230, "y": 260, "wires": [] }, { "id": "d967d5e0e9b95592", "type": "comment", "z": "aa9a062666553723", "name": "Node-REDがMinecraftに送信するフロー", "info": "", "x": 200, "y": 100, "wires": [] }, { "id": "7a16220640cb9dfe", "type": "json", "z": "aa9a062666553723", "name": "", "property": "payload", "action": "", "pretty": true, "x": 290, "y": 320, "wires": [ [ "0d00e47b393541d4" ] ] }, { "id": "0d00e47b393541d4", "type": "debug", "z": "aa9a062666553723", "name": "debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 450, "y": 320, "wires": [] }, { "id": "eca095910be51b0c", "type": "websocket in", "z": "aa9a062666553723", "name": "", "server": "c838b41f45982027", "client": "", "x": 130, "y": 320, "wires": [ [ "7a16220640cb9dfe" ] ] }, { "id": "f7745a07751ec403", "type": "websocket out", "z": "aa9a062666553723", "name": "", "server": "c838b41f45982027", "client": "", "x": 660, "y": 160, "wires": [] }, { "id": "c838b41f45982027", "type": "websocket-listener", "path": "ws", "wholemsg": "false" } ]
歩くとチャットに流れるフロー
[ { "id": "8f7ebd96829d0294", "type": "comment", "z": "aa9a062666553723", "name": "Minecraftからの返答をNode-REDが受信するフロー", "info": "", "x": 230, "y": 260, "wires": [] }, { "id": "7a16220640cb9dfe", "type": "json", "z": "aa9a062666553723", "name": "", "property": "payload", "action": "", "pretty": false, "x": 270, "y": 320, "wires": [ [ "11385e13a39b0439" ] ] }, { "id": "0d00e47b393541d4", "type": "debug", "z": "aa9a062666553723", "name": "debug", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 610, "y": 320, "wires": [] }, { "id": "eca095910be51b0c", "type": "websocket in", "z": "aa9a062666553723", "name": "", "server": "c838b41f45982027", "client": "", "x": 130, "y": 320, "wires": [ [ "7a16220640cb9dfe" ] ] }, { "id": "b1cfb07ba083724d", "type": "uuid", "z": "aa9a062666553723", "uuidVersion": "v4", "namespaceType": "", "namespace": "", "namespaceCustom": "", "name": "", "field": "pay", "fieldType": "msg", "x": 390, "y": 440, "wires": [ [ "faf461d6426fa4e7" ] ] }, { "id": "faf461d6426fa4e7", "type": "function", "z": "aa9a062666553723", "name": "CommandRequest", "func": "const pName = msg.payload.body.player.name;\nconst uuid = msg.pay;\n\nconst commandRequestMessageJSON = {\n \"header\": {\n \"version\": 1, // プロトコルのバージョン1.18.2時点では1でOK\n \"requestId\": uuid, // UUIDv4を生成して指定\n \"messageType\": \"commandRequest\", // commandRequestを指定\n \"messagePurpose\": \"commandRequest\", // commandRequestを指定\n },\n \"body\": {\n \"origin\": {\n \"type\": \"player\" // 誰がコマンドを実行するかを指定(ただし、Player以外にどの値が利用可能かは要調査)\n },\n \"version\": 1, // プロトコルのバージョン1.18.2時点では1でOK\n \"commandLine\": `say いま${pName}が動いたよ`, // マイクラで実行したいコマンドを指定(ここではニワトリをスポーンさせるコマンドを指定)\n }\n};\n\nmsg.payload = JSON.stringify(commandRequestMessageJSON);\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 570, "y": 440, "wires": [ [ "ce675db671d6d12e" ] ] }, { "id": "ce675db671d6d12e", "type": "websocket out", "z": "aa9a062666553723", "name": "", "server": "c838b41f45982027", "client": "", "x": 760, "y": 440, "wires": [] }, { "id": "11385e13a39b0439", "type": "switch", "z": "aa9a062666553723", "name": "", "property": "payload.header.eventName", "propertyType": "msg", "rules": [ { "t": "eq", "v": "PlayerTravelled", "vt": "str" }, { "t": "eq", "v": "OtherEventName2", "vt": "str" }, { "t": "eq", "v": "OtherEventName3", "vt": "str" } ], "checkall": "true", "repair": false, "outputs": 3, "x": 410, "y": 320, "wires": [ [ "b1cfb07ba083724d", "0d00e47b393541d4" ], [], [] ] }, { "id": "7816db0a7f789d1c", "type": "comment", "z": "aa9a062666553723", "name": "動くとチャットが流れる", "info": "", "x": 590, "y": 400, "wires": [] }, { "id": "c838b41f45982027", "type": "websocket-listener", "path": "ws", "wholemsg": "false" } ]
参考
WebSocket(Node.js)でサーバーからマインクラフト統合版にコマンドを発行する【概要編】 – マイクラの泉 (lflab.work)