diff --git a/README.md b/README.md index 1c7d888..247efc8 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,11 @@ This module contains the following nodes to provide easy integration of the Xiao The following devices are currently supported: * Temperature/humidity sensor +* Aqara temperature/humidity/pressure sensor * Magnet switch +* Aqara window/door sensor * Button switch +* Aqara smart wireless switch * Motion sensor * Power plug (zigbee) * Power plug (wifi) @@ -29,7 +32,7 @@ npm install node-red-contrib-xiaomi-devices From the Xiaomi configurator screen add your different devices by selecting the type of device and a readable description. The readable discription is used on the different edit screen of the nodes to easily select the device you associate to the node. -Note that the Wifi power plug is not configured through the configurator as it is not connected to the gateway. +Note that the Wifi power plug is not configured through the configurator as it is not connected to the gateway. The Xiaomi configurator screen with ease of use to configure your different devices. @@ -37,23 +40,6 @@ The Xiaomi configurator screen with ease of use to configure your different devi Tip: use the configurator from the side-panel (hamburger menu, configuration nodes) to manage your devices. Node-red doesn't update underlying edit screens if the configuration panel is opened / closed from the edit node screen. (If you do, you need to first close the edit node screen and reopen it by double-clicking the node you want to edit the properties for.) -To receive/send json UDP messages from/to the gateway you need to enable the local LAN mode on the gateway. To receive the json UDP messages in node-red you need to add an udp-node with the correct configuration: - -``` -Listen for: multicast messages -Group: 224.0.0.50 -Local ip: -On port: 9898 ipv4 -Output: String -``` - -If you want to sent messages to the gateway you need to add an UDP sender, here an example configuration: - -``` -Send a: UDP message to port: 9898 -Address: ipv4 -``` -This configuration worked for me however I have seen people using different configuration to make UDP work. Here an example of how to use the different nodes. @@ -63,4 +49,3 @@ Here an example of how to use the different nodes. ## Roadmap * ~~Support for other devices like the smart-socket WiFi~~ Done! * Import (new) devices directly from the gateway - diff --git a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.html b/node-red-contrib-xiaomi-configurator/xiaomi-configurator.html index cca9414..b25aa20 100644 --- a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.html +++ b/node-red-contrib-xiaomi-configurator/xiaomi-configurator.html @@ -3,6 +3,7 @@ category: 'config', defaults: { name: {value: ""}, + ip: {value: ""}, deviceList: {value:[{ sid:"", desc:"", model:"plug"}]}, key: {value: ""} }, @@ -59,6 +60,13 @@ oneditsave: function() { var devices = $("#node-config-input-devices").editableList('items'); var node = this; + RED.nodes.eachNode(function(tmpNode) { + if(tmpNode.type.indexOf("xiaomi-gateway") === 0 && tmpNode.gateway == node.id) { + tmpNode.ip = $("#node-config-input-ip").val(); + tmpNode.changed = true; + console.log(tmpNode); + } + }); var devicesArray = []; devices.each(function(i) { var deviceElement = $(this); @@ -81,7 +89,11 @@ -
+
+ + +
+
diff --git a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.js b/node-red-contrib-xiaomi-configurator/xiaomi-configurator.js index 246d3aa..37825c4 100644 --- a/node-red-contrib-xiaomi-configurator/xiaomi-configurator.js +++ b/node-red-contrib-xiaomi-configurator/xiaomi-configurator.js @@ -5,6 +5,7 @@ module.exports = function(RED) { this.name = n.name; this.deviceList = n.deviceList || []; this.key = n.key; + this.ip = n.ip; var node = this; } diff --git a/node-red-contrib-xiaomi-gateway/xiaomi-gateway.html b/node-red-contrib-xiaomi-gateway/xiaomi-gateway.html new file mode 100644 index 0000000..ba76638 --- /dev/null +++ b/node-red-contrib-xiaomi-gateway/xiaomi-gateway.html @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + diff --git a/node-red-contrib-xiaomi-gateway/xiaomi-gateway.js b/node-red-contrib-xiaomi-gateway/xiaomi-gateway.js new file mode 100644 index 0000000..112e0af --- /dev/null +++ b/node-red-contrib-xiaomi-gateway/xiaomi-gateway.js @@ -0,0 +1,175 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = function(RED) { + "use strict"; + var dgram = require('dgram'); + var udpInputPortsInUse = {}; + + // The Input Node + function GatewayIn(n) { + RED.nodes.createNode(this,n); + this.group = "224.0.0.50"; + this.port = 9898; + this.iface = null; + this.addr = n.ip; + this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4"; + var node = this; + + var opts = {type:node.ipv, reuseAddr:true}; + if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } + var server; + + if (!udpInputPortsInUse.hasOwnProperty(this.port)) { + server = dgram.createSocket(opts); // default to udp4 + udpInputPortsInUse[this.port] = server; + } + else { + node.warn(RED._("udp.errors.alreadyused",node.port)); + server = udpInputPortsInUse[this.port]; // re-use existing + } + + if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } + + server.on("error", function (err) { + if ((err.code == "EACCES") && (node.port < 1024)) { + node.error(RED._("udp.errors.access-error")); + } else { + node.error(RED._("udp.errors.error",{error:err.code})); + } + server.close(); + }); + + server.on('message', function (message, remote) { + var msg; + if(remote.address == node.addr) { + msg = { payload: JSON.parse(message.toString('utf8')) }; + node.send(msg); + } + }); + + server.on('listening', function () { + var address = server.address(); + node.log(RED._("udp.status.listener-at",{host:address.address,port:address.port})); + server.setBroadcast(true); + try { + server.setMulticastTTL(128); + server.addMembership(node.group,node.iface); + node.log(RED._("udp.status.mc-group",{group:node.group})); + } catch (e) { + if (e.errno == "EINVAL") { + node.error(RED._("udp.errors.bad-mcaddress")); + } else if (e.errno == "ENODEV") { + node.error(RED._("udp.errors.interface")); + } else { + node.error(RED._("udp.errors.error",{error:e.errno})); + } + } + }); + + node.on("close", function() { + if (udpInputPortsInUse.hasOwnProperty(node.port)) { + delete udpInputPortsInUse[node.port]; + } + try { + server.close(); + node.log(RED._("udp.status.listener-stopped")); + } catch (err) { + //node.error(err); + } + }); + + try { server.bind(node.port,node.iface); } + catch(e) { } // Don't worry if already bound + } + RED.httpAdmin.get('/udp-ports/:id', RED.auth.needsPermission('udp-ports.read'), function(req,res) { + res.json(Object.keys(udpInputPortsInUse)); + }); + RED.nodes.registerType("xiaomi-gateway in",GatewayIn); + + + // The Output Node + function GatewayOut(n) { + RED.nodes.createNode(this,n); + this.port = 9898; + this.outport = 9898; + this.iface = null; + this.addr = n.ip; + this.ipv = this.ip && this.ip.indexOf(":") >= 0 ? "udp6" : "udp4"; + this.multicast = false; + var node = this; + + var opts = {type:node.ipv, reuseAddr:true}; + if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } + + var sock; + if (udpInputPortsInUse[this.outport]) { + sock = udpInputPortsInUse[this.outport]; + } + else { + sock = dgram.createSocket(opts); // default to udp4 + sock.on("error", function(err) { + // Any async error will also get reported in the sock.send call. + // This handler is needed to ensure the error marked as handled to + // prevent it going to the global error handler and shutting node-red + // down. + }); + udpInputPortsInUse[this.outport] = sock; + } + + if (!udpInputPortsInUse[this.outport]) { + sock.bind(node.outport); + node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port})); + } else { + node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port})); + } + + node.on("input", function(msg) { + if (msg.hasOwnProperty("payload")) { + var add = node.addr || msg.ip || ""; + var por = node.port || msg.port || 0; + if (add === "") { + node.warn(RED._("udp.errors.ip-notset")); + } else if (por === 0) { + node.warn(RED._("udp.errors.port-notset")); + } else if (isNaN(por) || (por < 1) || (por > 65535)) { + node.warn(RED._("udp.errors.port-invalid")); + } else { + var message = Buffer.from(JSON.stringify(msg.payload)); + sock.send(message, 0, message.length, por, add, function(err, bytes) { + if (err) { + node.error("udp : "+err,msg); + } + message = null; + }); + } + } + }); + + node.on("close", function() { + if (udpInputPortsInUse.hasOwnProperty(node.outport)) { + delete udpInputPortsInUse[node.outport]; + } + try { + sock.close(); + node.log(RED._("udp.status.output-stopped")); + } catch (err) { + //node.error(err); + } + }); + } + RED.nodes.registerType("xiaomi-gateway out", GatewayOut); +} diff --git a/package.json b/package.json index ca7f627..c14b777 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-red-contrib-xiaomi-devices", - "version": "1.0.14", + "version": "1.1.0", "description": "A set of nodes to control some of the popular Xiaomi sensors which are connected to the Xiaomi Gateway.", "repository": { "type": "git", @@ -16,6 +16,7 @@ "xiaomi-switch": "node-red-contrib-xiaomi-switch/xiaomi-switch.js", "xiaomi-socket": "node-red-contrib-xiaomi-socket/xiaomi-socket.js", "xiaomi-socket-wifi": "node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.js", + "xiaomi-gateway": "node-red-contrib-xiaomi-gateway/xiaomi-gateway.js", "xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js" } },