KNX

PlayCanvasとNode-REDとKNXでSociety5.0(仮想空間と現実空間の融合)をした話 Vol.2

この記事は、PlayCanvas Advent Calendar 2022のカレンダーの4日目の記事でしたが、ギリギリの25日目にアップしました。

Vol.1では全体のシステムを説明しましたが、Vol.2ではPlayCanvas側とNode-REDのフローの説明をします。

PlayCanvasとNode-REDとKNXでSociety5.0(仮想空間と現実空間の融合)をした話 Vol.1|デジタルライト(Digital-light.jp)

PlayCanvas側

UntitledImage

こちらがPlayCanvasのEditor画面。

mqtt.jsの準備

MQTT.js · GitHub

こちらのサイトからmqtt.jsをダウンロード。

このファイルをPlayCanvasの左下にある
Setting>SCRIPTSLOADING ORDER
にてmqttで通信するためのmqtt.jsを読み込む設定をします。

manage.jsを作成

UntitledImage

ProjectのrootにManage.jsというスクリプトを追加してMQTTの通信を行えるようにします。

manage.jsはこちら

var Manage = pc.createScript('manage');

// initialize code called once per entity
Manage.prototype.initialize = function () {

    let client = mqtt.connect('wss://public:public@public.cloud.shiftr.io:443');

    let topic = "toPlayCanvas";

    let topic2 = "toPlayCanvasSensors";

    // Subscribeする
    client.subscribe(topic);
    client.subscribe(topic2);

    // プロジェクト全体で使えるようにする
    this.app.client = client;

    // 受信したら表示

    client.on('message', (topic2, message) => this.sensors(message));
    client.on('message', (topic, message) => this.control(message));
};


Manage.prototype.control = function (message) {

    // MQTTから受信したデータ(buffer)を文字列にする
    var data = message.toString();

    // JSON形式にパース
    data = JSON.parse(data);

    //console.log(data);

    // どのデバイスから来たのかを確認    
    // fixtureとcommandを取得
    var fixture = data.fixture;
    var command = data.command;

    // fixtureのentityを取るためにappを取得
    var app = this.app;

    // LightのEntityを指定する
    this.spot = app.root.findByName(fixture);

    // commandでOn/Off
    if (command == "on" || command == "On") {

        this.spot.light.enabled = true;
    } else if (command == "off" || command == "Off") {
        this.spot.light.enabled = false;
    }
};

Manage.prototype.sensors = function (message) {

    // MQTTから受信したデータ(buffer)を文字列にする
    var data = message.toString();

    // JSON形式にパース
    data = JSON.parse(data);

    // fixtureとcommandを取得
    var fixture = data.fixture;
    var value = data.value;

    // fixtureのentityを取るためにappを取得
    var app = this.app;


    // LightのEntityを指定する
    app.root.findByName(fixture).findByName("Text").element.text = value;

};

// update code called every frame
Manage.prototype.update = function (dt) {

};

2Dスクリーンのボタン設定

UntitledImage

2Dスクリーンの中に、
– RoleScreen
– Sensors
– Btn-Fixture1
– Btn-Fixture2
というオブジェクトを設置。

RoleScreenとBtn-Fixtureにはbutton.jsというスクリプトを追加。
これで、ボタンを押したときのアクションを設定しています。

UntitledImage

また、scriptのattributeで、ボタンの種類やコマンドを変更できるようにしました。

var Button = pc.createScript('button');

// Btnの器具とOn/Offのどちらか
Button.attributes.add("fixture", { type: "string" });
Button.attributes.add("command", { type: "string" });

// initialize code called once per entity
Button.prototype.initialize = function () {
    this.entity.button.on("click", this.onPress, this);
    
};

// update code called every frame
Button.prototype.update = function (dt) {

};

Button.prototype.onPress = function (dt) {

    var fixture = this.fixture;
    var command = this.command;

    let client = this.app.client;

    let topic = "fromPlayCanvas";
    let metric = '{"fixture":"' + fixture + '", "command":"' + command + '"}';

    // Publishする
    client.publish(topic, metric);


};

2DスクリーンのSensors

UntitledImage

2DスクリーンにあるSensorsには、KNXのセンサーから来た情報(温度、湿度、CO2濃度)が表示されます。

これは、manage.jsにある下記のスクリプトにてアップデートしました。

Manage.prototype.sensors = function (message) {

    // MQTTから受信したデータ(buffer)を文字列にする
    var data = message.toString();

    // JSON形式にパース
    data = JSON.parse(data);

    // fixtureとcommandを取得
    var fixture = data.fixture;
    var value = data.value;

    // fixtureのentityを取るためにappを取得
    var app = this.app;


    // LightのEntityを指定する
    app.root.findByName(fixture).findByName("Text").element.text = value;

};

照明器具のOn/Off

UntitledImage

PlayCanvas内にある照明器具のOn/Offについてもmanage.jsにある下記スクリプト部分で操作しています。

Manage.prototype.control = function (message) {

    // MQTTから受信したデータ(buffer)を文字列にする
    var data = message.toString();

    // JSON形式にパース
    data = JSON.parse(data);

    //console.log(data);

    // どのデバイスから来たのかを確認    
    // fixtureとcommandを取得
    var fixture = data.fixture;
    var command = data.command;

    // fixtureのentityを取るためにappを取得
    var app = this.app;

    // LightのEntityを指定する
    this.spot = app.root.findByName(fixture);

    // commandでOn/Off
    if (command == "on" || command == "On") {

        this.spot.light.enabled = true;
    } else if (command == "off" || command == "Off") {
        this.spot.light.enabled = false;
    }
};

start.jsは使っていない

UntitledImage

プロジェクトにstart.jsというスクリプトがありますが、これは使っていません。消していないだけ。

Node-REDのフロー

UntitledImage

まずはこちらがNode-REDのフローです。

[{"id":"5a24fa3c7c901b55","type":"knxUltimate","z":"6b88292094289bed","server":"7512ccc946517e75","topic":"0/1/0","outputtopic":"","dpt":"1.001","initialread":0,"notifyreadrequest":false,"notifyresponse":false,"notifywrite":true,"notifyreadrequestalsorespondtobus":false,"notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized":"0","listenallga":false,"name":"","outputtype":"write","outputRBE":true,"inputRBE":false,"formatmultiplyvalue":1,"formatnegativevalue":"leave","formatdecimalsvalue":999,"passthrough":"no","x":640,"y":480,"wires":[["4ea0c1e1bf81f84f"]]},{"id":"4ea0c1e1bf81f84f","type":"debug","z":"6b88292094289bed","name":"KNX Msg","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":920,"y":580,"wires":[]},{"id":"9e28efce84fbeed4","type":"knxUltimate","z":"6b88292094289bed","server":"7512ccc946517e75","topic":"0/1/2","outputtopic":"","dpt":"1.001","initialread":0,"notifyreadrequest":false,"notifyresponse":false,"notifywrite":true,"notifyreadrequestalsorespondtobus":false,"notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized":"0","listenallga":false,"name":"Fixture1","outputtype":"write","outputRBE":true,"inputRBE":false,"formatmultiplyvalue":1,"formatnegativevalue":"leave","formatdecimalsvalue":999,"passthrough":"no","x":190,"y":720,"wires":[["8efe420c30794133"]]},{"id":"45c57ba18466255a","type":"knxUltimate","z":"6b88292094289bed","server":"7512ccc946517e75","topic":"0/1/1","outputtopic":"","dpt":"1.001","initialread":0,"notifyreadrequest":false,"notifyresponse":false,"notifywrite":true,"notifyreadrequestalsorespondtobus":false,"notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized":"0","listenallga":false,"name":"","outputtype":"write","outputRBE":true,"inputRBE":false,"formatmultiplyvalue":1,"formatnegativevalue":"leave","formatdecimalsvalue":999,"passthrough":"no","x":640,"y":540,"wires":[["4ea0c1e1bf81f84f"]]},{"id":"73a957b59ffa2844","type":"knxUltimate","z":"6b88292094289bed","server":"7512ccc946517e75","topic":"0/1/3","outputtopic":"","dpt":"1.001","initialread":0,"notifyreadrequest":false,"notifyresponse":false,"notifywrite":true,"notifyreadrequestalsorespondtobus":false,"notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized":"0","listenallga":false,"name":"Fixture2","outputtype":"write","outputRBE":true,"inputRBE":false,"formatmultiplyvalue":1,"formatnegativevalue":"leave","formatdecimalsvalue":999,"passthrough":"no","x":190,"y":780,"wires":[["8efe420c30794133"]]},{"id":"ab949fb04cf74de4","type":"mqtt out","z":"6b88292094289bed","name":"","topic":"toPlayCanvas","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"48db53855e8c1474","x":640,"y":820,"wires":[]},{"id":"8efe420c30794133","type":"function","z":"6b88292094289bed","name":"fixForMQTT","func":"var fixture = msg.devicename;\nvar value = msg.payload;\nvar command = msg.payloadsubtypevalue;\n\nmsg.payload = {\n    \"device\" : \"light\",\n    \"fixture\": fixture,\n    \"value\" : value,\n    \"command\" : command\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":470,"y":760,"wires":[["ab949fb04cf74de4","1c447979aa784221"]]},{"id":"e71b07318646d1cc","type":"mqtt in","z":"6b88292094289bed","name":"","topic":"fromPlayCanvas","qos":"0","datatype":"json","broker":"48db53855e8c1474","nl":false,"rap":true,"rh":0,"x":140,"y":220,"wires":[["f7c77fe62bf3b5ae"]]},{"id":"bf6c5bf401bfdb16","type":"switch","z":"6b88292094289bed","name":"Fixtures","property":"payload.fixture","propertyType":"msg","rules":[{"t":"eq","v":"Fixture1","vt":"str"},{"t":"eq","v":"Fixture2","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":280,"y":420,"wires":[["3fdce79917961677"],["4f4f0571766b89a5"]]},{"id":"e1ac979c383b71ba","type":"change","z":"6b88292094289bed","name":"TrueFalse","rules":[{"t":"change","p":"payload.command","pt":"msg","from":"off","fromt":"str","to":"false","tot":"bool"},{"t":"change","p":"payload.command","pt":"msg","from":"on","fromt":"str","to":"true","tot":"bool"}],"action":"","property":"","from":"","to":"","reg":false,"x":240,"y":360,"wires":[["bf6c5bf401bfdb16"]]},{"id":"3fdce79917961677","type":"change","z":"6b88292094289bed","name":"Fixture1","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.command","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":400,"wires":[["5a24fa3c7c901b55"]]},{"id":"4f4f0571766b89a5","type":"change","z":"6b88292094289bed","name":"Fixture2","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.command","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":500,"wires":[["45c57ba18466255a"]]},{"id":"54bdc424c639aadd","type":"inject","z":"6b88292094289bed","name":"Login","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payloadType":"date","x":750,"y":100,"wires":[["ad69445f95fb6587"]]},{"id":"ad69445f95fb6587","type":"function","z":"6b88292094289bed","name":"nwk/n/r","func":"msg.payload = '';\nnode.send(msg);\nmsg.payload = 'nwk\\n\\r';\nnode.send(msg);","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":900,"y":100,"wires":[["affdc43c79df2f38"]]},{"id":"affdc43c79df2f38","type":"tcp request","z":"6b88292094289bed","server":"192.168.1.231","port":"23","out":"sit","splitc":" ","name":"Lutron","x":1130,"y":280,"wires":[["219c3c858dffadc3"]]},{"id":"219c3c858dffadc3","type":"function","z":"6b88292094289bed","name":"buffer.toString","func":"var b=Buffer.from(msg.payload);\nvar s=b.toString();\nvar out=s;\nmsg.payload = out;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1240,"y":360,"wires":[["71279b9cbda1cb32"]]},{"id":"71279b9cbda1cb32","type":"debug","z":"6b88292094289bed","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1310,"y":420,"wires":[]},{"id":"912ad7bccb564438","type":"inject","z":"6b88292094289bed","name":"Morning","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":760,"y":140,"wires":[["18997bd976b0decc"]]},{"id":"5ddde5d4caf017e1","type":"inject","z":"6b88292094289bed","name":"Afternoon","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":760,"y":220,"wires":[["ce280085f9f01071"]]},{"id":"cdba7d9bd1156775","type":"inject","z":"6b88292094289bed","name":"Night","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payloadType":"date","x":770,"y":300,"wires":[["d8bf841334352da9"]]},{"id":"18997bd976b0decc","type":"function","z":"6b88292094289bed","name":"#DEVICE,TESTQS,70,3","func":"msg.payload = '#DEVICE,TESTQS,70,3\\r\\n';\nnode.send(msg);","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":180,"wires":[["affdc43c79df2f38"]]},{"id":"ce280085f9f01071","type":"function","z":"6b88292094289bed","name":"#DEVICE,TESTQS,71,3","func":"msg.payload = '#DEVICE,TESTQS,71,3\\r\\n';\nnode.send(msg);","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":260,"wires":[["affdc43c79df2f38"]]},{"id":"d8bf841334352da9","type":"function","z":"6b88292094289bed","name":"#DEVICE,TESTQS,76,3","func":"msg.payload = '#DEVICE,TESTQS,76,3\\r\\n';\nnode.send(msg);","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":340,"wires":[["affdc43c79df2f38"]]},{"id":"f7c77fe62bf3b5ae","type":"switch","z":"6b88292094289bed","name":"Facilities","property":"payload.fixture","propertyType":"msg","rules":[{"t":"eq","v":"Role","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":200,"y":300,"wires":[["34306b8e7462e4aa"],["e1ac979c383b71ba"]]},{"id":"34306b8e7462e4aa","type":"switch","z":"6b88292094289bed","name":"Scenes","property":"payload.command","propertyType":"msg","rules":[{"t":"eq","v":"morning","vt":"str"},{"t":"eq","v":"afternoon","vt":"str"},{"t":"eq","v":"night","vt":"str"}],"checkall":"true","repair":false,"outputs":3,"x":440,"y":280,"wires":[["18997bd976b0decc"],["ce280085f9f01071"],["d8bf841334352da9"]]},{"id":"ed9c3ecc9694318c","type":"comment","z":"6b88292094289bed","name":"FromPlayCanvas","info":"","x":140,"y":160,"wires":[]},{"id":"61a361abf5d6a139","type":"comment","z":"6b88292094289bed","name":"Lutron RoleScreen","info":"","x":770,"y":60,"wires":[]},{"id":"f78842da7bb201ea","type":"comment","z":"6b88292094289bed","name":"toPlayCanvas","info":"","x":650,"y":780,"wires":[]},{"id":"aa13c38c67551606","type":"comment","z":"6b88292094289bed","name":"toKNX","info":"","x":630,"y":440,"wires":[]},{"id":"b7803c48257d8ad3","type":"comment","z":"6b88292094289bed","name":"FromKNX","info":"","x":180,"y":660,"wires":[]},{"id":"1c447979aa784221","type":"debug","z":"6b88292094289bed","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":650,"y":680,"wires":[]},{"id":"37529ea891b14a1a","type":"knxUltimate","z":"6b88292094289bed","server":"7512ccc946517e75","topic":"0/2/1","outputtopic":"","dpt":"9.007","initialread":0,"notifyreadrequest":false,"notifyresponse":false,"notifywrite":true,"notifyreadrequestalsorespondtobus":false,"notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized":"0","listenallga":false,"name":"Humidity","outputtype":"write","outputRBE":true,"inputRBE":false,"formatmultiplyvalue":1,"formatnegativevalue":"leave","formatdecimalsvalue":999,"passthrough":"no","x":200,"y":940,"wires":[["70e19065c3e24fd1"]]},{"id":"60f01d488e29c326","type":"knxUltimate","z":"6b88292094289bed","server":"7512ccc946517e75","topic":"0/2/0","outputtopic":"","dpt":"9.001","initialread":0,"notifyreadrequest":false,"notifyresponse":false,"notifywrite":true,"notifyreadrequestalsorespondtobus":false,"notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized":"0","listenallga":false,"name":"Temprature","outputtype":"write","outputRBE":true,"inputRBE":false,"formatmultiplyvalue":1,"formatnegativevalue":"leave","formatdecimalsvalue":999,"passthrough":"no","x":200,"y":880,"wires":[["70e19065c3e24fd1"]]},{"id":"92ec77e0723f9b33","type":"knxUltimate","z":"6b88292094289bed","server":"7512ccc946517e75","topic":"0/2/2","outputtopic":"","dpt":"9.008","initialread":0,"notifyreadrequest":false,"notifyresponse":false,"notifywrite":true,"notifyreadrequestalsorespondtobus":false,"notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized":"0","listenallga":false,"name":"CO2","outputtype":"write","outputRBE":true,"inputRBE":false,"formatmultiplyvalue":1,"formatnegativevalue":"leave","formatdecimalsvalue":999,"passthrough":"no","x":180,"y":1000,"wires":[["70e19065c3e24fd1"]]},{"id":"70e19065c3e24fd1","type":"function","z":"6b88292094289bed","name":"fixForMQTT","func":"var fixture = msg.devicename;\nvar value = msg.payload;\nvar command = msg.payloadsubtypevalue;\n\nmsg.payload = {\n    \"device\" : \"light\",\n    \"fixture\": fixture,\n    \"value\" : value,\n    \"command\" : command\n}\n\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":450,"y":960,"wires":[["96f90508771c3e0b","fb7bd903b682592a"]]},{"id":"fb7bd903b682592a","type":"mqtt out","z":"6b88292094289bed","name":"","topic":"toPlayCanvasSensors","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"48db53855e8c1474","x":680,"y":960,"wires":[]},{"id":"96f90508771c3e0b","type":"debug","z":"6b88292094289bed","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":580,"y":1060,"wires":[]},{"id":"190ad6297698f57c","type":"inject","z":"6b88292094289bed","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":510,"y":900,"wires":[["fb7bd903b682592a"]]},{"id":"7512ccc946517e75","type":"knxUltimate-config","host":"192.168.1.102","port":"3671","physAddr":"15.15.22","hostProtocol":"TunnelUDP","suppressACKRequest":false,"csv":"","KNXEthInterface":"Auto","KNXEthInterfaceManuallyInput":"","statusDisplayLastUpdate":true,"statusDisplayDeviceNameWhenALL":true,"statusDisplayDataPoint":false,"stopETSImportIfNoDatapoint":"stop","loglevel":"error","name":"KNX Gateway","localEchoInTunneling":true,"delaybetweentelegrams":"50","delaybetweentelegramsfurtherdelayREAD":"1","ignoreTelegramsWithRepeatedFlag":false,"autoReconnect":"yes"},{"id":"48db53855e8c1474","type":"mqtt-broker","name":"MQTT","broker":"mqtt://public:public@public.cloud.shiftr.io","port":"1883","clientid":"","usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"sessionExpiry":""}]

* ごめんなさい。詳細はフローみてください。

まとめ

PlayCanvasアドベントカレンダーもやる気まんまんだったのですが、11月後半から仕事が激増し、この記事を含め2記事しかアップできませんでした。

仕事が増えて嬉しい悲鳴ですが、引き続きいろいろ勉強しながらがんばります。

ABOUT ME
中畑 隆拓
スマートライト㈱ 代表取締役。IoTソリューションの開発、スマートホーム&オフィスのコンサルティング、DALI,KNX,EnOceanなどのインテグレーションを行っています。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です