本記事ではMinecraftにKNX-DALIゲートウェイにつないで、部屋に備わっている設備照明を制御する方法を記載しています。
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-REDをRaspberry Piで動かしています。Windowsで動かしていれば不要です。
使用機器

- KNX Power Supply・・・KNXを使用するための電源
- KNX-DALI Gateway・・・KNXからDALI信号をやり取りする場所
- KNX IP Interface 731・・・IPとKNXをつなぎLANで制御できるようにするもの
※事前にKNX、DALIの準備が必要です。詳しくはこちら
完成像
金色の上:明るさ100%、黒色の上:明るさ0%
間の部分も位置に応じた光の強さで照明が光ります。
フローの概要

Minecraftからのデータ取得は以前の記事で紹介しています。
取得したデータを条件分岐させていく
今回、Switchノードで多くの条件分岐をさせています。分岐の順は下記の通りです。
- イベント名
- 実行範囲選択 : x (z)軸
- 実行範囲選択 : z (x)軸
- 直前の軸位置に合わせて出力値を決定
- 調光データをpayloadにコピー
イベント名
msg.payload.header.eventName に格納されたイベント名から
PlayerTravelled のみのデータを抽出します。
実行範囲選択 : x (z)軸

今回は、Minecraft内で明るさを操作するとき、PlayerTravelledから得た座標情報を元に操作しました。動作させたい座標範囲のうち、調光に必要のない軸を予め範囲を絞っておきます。今回はx座標-38~-34の範囲が操作盤として扱われます。
実行範囲選択 : z (x)軸

実際に明るさを決定する座標軸です。今回は-11~-3の座標間で0%~100%間、
-13~-11の座標間で0%、-3~-1の座標間で100%としています。
直前の軸位置に合わせて出力値を決定①

0%~100%間を制御する範囲です。座標幅の-11~-3の値を
rangeノードで0~100の値幅に変更しています。
直前の軸位置に合わせて出力値を決定②③

0%、100%を出力するために、payloadに値を挿入するだけのノードです。
KNXモジュール
パレットの管理から、KNX と検索し、少しスクロールしたところの
node-red-contrib-knx-ultimate を追加します。

KNX DEVICEノードを選択し、ゲートウェイのIPアドレスを入力後、
下記画像のような設定にします。

また、事前にETSで割り当てたデータを下記のようにコピペしておきます。

設定を完了したらもう一度開き、Universal modeにチェックを入れ、
STATUSのReact to response telegramsにチェックを入れることで、
他デバイスからの操作でも、照明の状態を受け取ることができます。

また、KNX DEVICEノードのヘルプの通り、destinationに対して割り当てたアドレスを出力することで、簡単に制御対象を変更することができます。

そこで、destinationを出力するためのfunctionをノード間に挟んでいます。
コードは下記の通りです。
var destination = "0/1/5";
msg.destination = destination;
return msg;
Minecraft内での設定

ノードを見てきてわかる通り、今回の照明制御に利用したMinecraftからの受信データは、プレイヤーの座標情報だけです。動画のニワトリ調光バー等は視覚的にわかりやすくした言わば装飾品です。しかし、実行範囲はわかりやすいように囲っておくことをおすすめします。
今回のニワトリ調光バーのコマンドも一応張っておきます。
※設定はリピート、無条件、tick遅延0です。
barという名前のニワトリの動きを固定
/execute as @e[name=bar,type=chicken] at @s run tp ~~~
今回の実行範囲内のプレイヤーの動きにニワトリを連動
/execute as @p if entity @p[z=-11,dz=7,dx=3,dy=3] run execute as @s at @s run tp @e[name=bar,type=chicken] -39 -59 ~
ノードデータ
[ { "id": "3f41cb14d88ded7c", "type": "websocket in", "z": "713734672c0780c0", "name": "", "server": "c838b41f45982027", "client": "", "x": 210, "y": 360, "wires": [ [ "c3ed757774490478" ] ] }, { "id": "2c84d87239f3ba36", "type": "change", "z": "713734672c0780c0", "name": "payloadにコピー", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "payload.body.player.position.z", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1170, "y": 320, "wires": [ [ "7a2d1d2b2d6f5334" ] ] }, { "id": "03bf603dfb374bcf", "type": "change", "z": "713734672c0780c0", "name": "payloadに挿入:100", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "100", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1010, "y": 400, "wires": [ [ "7a2d1d2b2d6f5334" ] ] }, { "id": "7a049a314b5dd479", "type": "change", "z": "713734672c0780c0", "name": "payloadに挿入:0", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "0", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1000, "y": 360, "wires": [ [ "7a2d1d2b2d6f5334" ] ] }, { "id": "89b9dd95ddd31f18", "type": "range", "z": "713734672c0780c0", "minin": "-11", "maxin": "-3", "minout": "0", "maxout": "100", "action": "scale", "round": false, "property": "payload.body.player.position.z", "name": "", "x": 980, "y": 320, "wires": [ [ "2c84d87239f3ba36" ] ] }, { "id": "7a2d1d2b2d6f5334", "type": "function", "z": "713734672c0780c0", "name": "destination指定", "func": "\nvar destination = \"0/1/5\";\nmsg.destination = destination;\n\nreturn msg;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1400, "y": 360, "wires": [ [ "a170f69dfea20c69" ] ] }, { "id": "c728612a5565c773", "type": "switch", "z": "713734672c0780c0", "name": "", "property": "payload.body.player.position.z", "propertyType": "msg", "rules": [ { "t": "btwn", "v": "-11.00", "vt": "num", "v2": "-3.00", "v2t": "num" }, { "t": "btwn", "v": "-13", "vt": "num", "v2": "-11.00", "v2t": "num" }, { "t": "btwn", "v": "-3.00", "vt": "num", "v2": "-1", "v2t": "num" } ], "checkall": "true", "repair": false, "outputs": 3, "x": 770, "y": 360, "wires": [ [ "89b9dd95ddd31f18" ], [ "7a049a314b5dd479" ], [ "03bf603dfb374bcf" ] ] }, { "id": "e1527db4043983f1", "type": "switch", "z": "713734672c0780c0", "name": "", "property": "payload.body.player.position.x", "propertyType": "msg", "rules": [ { "t": "btwn", "v": "-38", "vt": "num", "v2": "-34", "v2t": "num" } ], "checkall": "true", "repair": false, "outputs": 1, "x": 630, "y": 360, "wires": [ [ "c728612a5565c773" ] ] }, { "id": "b4e967d02fa82a64", "type": "switch", "z": "713734672c0780c0", "name": "", "property": "payload.header.eventName", "propertyType": "msg", "rules": [ { "t": "eq", "v": "PlayerTravelled", "vt": "str" } ], "checkall": "true", "repair": false, "outputs": 1, "x": 490, "y": 360, "wires": [ [ "e1527db4043983f1" ] ] }, { "id": "c3ed757774490478", "type": "json", "z": "713734672c0780c0", "name": "", "property": "payload", "action": "", "pretty": true, "x": 350, "y": 360, "wires": [ [ "b4e967d02fa82a64" ] ] }, { "id": "f53b8d28775551e4", "type": "function", "z": "713734672c0780c0", "name": "PlayerTravelledCmd", "func": "const uuid = msg.payload;\n\nconst subscribeMessageJSON = {\n \"header\": {\n \"version\": 1, // プロトコルのバージョンを指定。1.18.2の時点では1で問題ない\n \"requestId\": uuid, // UUIDv4を指定\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;", "outputs": 1, "timeout": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 680, "y": 200, "wires": [ [ "763b74f7971701dc" ] ] }, { "id": "763b74f7971701dc", "type": "websocket out", "z": "713734672c0780c0", "name": "", "server": "c838b41f45982027", "client": "", "x": 900, "y": 200, "wires": [] }, { "id": "53db2e6fd229bc42", "type": "uuid", "z": "713734672c0780c0", "uuidVersion": "v4", "namespaceType": "", "namespace": "", "namespaceCustom": "", "name": "", "field": "payload", "fieldType": "msg", "x": 470, "y": 200, "wires": [ [ "f53b8d28775551e4" ] ] }, { "id": "496bb17f2755b902", "type": "inject", "z": "713734672c0780c0", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 260, "y": 200, "wires": [ [ "53db2e6fd229bc42" ] ] }, { "id": "15da87d6d6758730", "type": "debug", "z": "713734672c0780c0", "name": "デバッグ", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "payload", "targetType": "msg", "statusVal": "", "statusType": "auto", "x": 1720, "y": 360, "wires": [] }, { "id": "706691a4bca8f6a2", "type": "comment", "z": "713734672c0780c0", "name": "Minecraftからデータ取得:PlayerTravelled", "info": "", "x": 320, "y": 140, "wires": [] }, { "id": "4c60247c0a5f530d", "type": "comment", "z": "713734672c0780c0", "name": "KNXに座標データを変換して送信", "info": "", "x": 300, "y": 300, "wires": [] }, { "id": "a170f69dfea20c69", "type": "knxUltimate", "z": "713734672c0780c0", "server": "9943cbe8f16f8422", "topic": "0/1/1", "outputtopic": "", "dpt": "5.001", "initialread": 0, "notifyreadrequest": false, "notifyresponse": true, "notifywrite": true, "notifyreadrequestalsorespondtobus": false, "notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized": "0", "listenallga": true, "name": "", "outputtype": "write", "outputRBE": false, "inputRBE": false, "formatmultiplyvalue": 1, "formatnegativevalue": "leave", "formatdecimalsvalue": 999, "passthrough": "no", "x": 1570, "y": 360, "wires": [ [ "15da87d6d6758730" ] ] }, { "id": "c838b41f45982027", "type": "websocket-listener", "path": "ws", "wholemsg": "false" }, { "id": "9943cbe8f16f8422", "type": "knxUltimate-config", "host": "224.0.23.12", "port": "3671", "physAddr": "15.15.22", "hostProtocol": "Multicast", "suppressACKRequest": false, "csv": "", "KNXEthInterface": "Auto", "KNXEthInterfaceManuallyInput": "", "stopETSImportIfNoDatapoint": "stop", "loglevel": "error", "name": "KNX Gateway", "localEchoInTunneling": true, "delaybetweentelegrams": "50", "delaybetweentelegramsfurtherdelayREAD": "1", "ignoreTelegramsWithRepeatedFlag": false, "autoReconnect": "yes" } ]