├── .gitignore ├── xiaomi-configurator.png ├── xiaomi-devices-overview.png ├── node-red-contrib-xiaomi-magnet ├── icons │ └── door-icon.png ├── xiaomi-magnet.js └── xiaomi-magnet.html ├── node-red-contrib-xiaomi-switch ├── icons │ └── light-icon.png ├── xiaomi-switch.js └── xiaomi-switch.html ├── node-red-contrib-xiaomi-ht ├── icons │ └── thermometer-icon.png ├── xiaomi-ht.js └── xiaomi-ht.html ├── node-red-contrib-xiaomi-motion ├── icons │ └── motion-icon.png ├── xiaomi-motion.js └── xiaomi-motion.html ├── node-red-contrib-xiaomi-socket ├── icons │ └── outlet-icon.png ├── xiaomi-socket.js └── xiaomi-socket.html ├── node-red-contrib-xiaomi-configurator ├── icons │ ├── magnet-tw-icon.png │ ├── motion-tw-icon.png │ ├── plug-tw-icon.png │ ├── switch-tw-icon.png │ └── sensor-ht-tw-icon.png ├── xiaomi-configurator.js └── xiaomi-configurator.html ├── node-red-contrib-xiaomi-socket-wifi ├── icons │ └── outlet-wifi-icon.png ├── xiaomi-socket-wifi.html └── xiaomi-socket-wifi.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | 3 | *.iml 4 | 5 | /node_modules 6 | .log 7 | -------------------------------------------------------------------------------- /xiaomi-configurator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/xiaomi-configurator.png -------------------------------------------------------------------------------- /xiaomi-devices-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/xiaomi-devices-overview.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-magnet/icons/door-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-magnet/icons/door-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-switch/icons/light-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-switch/icons/light-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-ht/icons/thermometer-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-ht/icons/thermometer-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-motion/icons/motion-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-motion/icons/motion-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-socket/icons/outlet-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-socket/icons/outlet-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-configurator/icons/magnet-tw-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-configurator/icons/magnet-tw-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-configurator/icons/motion-tw-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-configurator/icons/motion-tw-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-configurator/icons/plug-tw-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-configurator/icons/plug-tw-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-configurator/icons/switch-tw-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-configurator/icons/switch-tw-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-socket-wifi/icons/outlet-wifi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-socket-wifi/icons/outlet-wifi-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-configurator/icons/sensor-ht-tw-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/HEAD/node-red-contrib-xiaomi-configurator/icons/sensor-ht-tw-icon.png -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-configurator/xiaomi-configurator.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | 3 | function XiaomiConfiguratorNode(n) { 4 | RED.nodes.createNode(this, n); 5 | this.name = n.name; 6 | this.deviceList = n.deviceList || []; 7 | this.key = n.key; 8 | 9 | var node = this; 10 | } 11 | 12 | RED.nodes.registerType("xiaomi-configurator", XiaomiConfiguratorNode); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-contrib-xiaomi-devices", 3 | "version": "1.0.14", 4 | "description": "A set of nodes to control some of the popular Xiaomi sensors which are connected to the Xiaomi Gateway.", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+ssh://git@github.com:hrietman/node-red-contrib-xiaomi-devices.git" 8 | }, 9 | "license" : "MIT", 10 | "keywords": ["Xiaomi", "node-red"], 11 | "node-red" : { 12 | "nodes": { 13 | "xiaomi-ht": "node-red-contrib-xiaomi-ht/xiaomi-ht.js", 14 | "xiaomi-magnet": "node-red-contrib-xiaomi-magnet/xiaomi-magnet.js", 15 | "xiaomi-motion": "node-red-contrib-xiaomi-motion/xiaomi-motion.js", 16 | "xiaomi-switch": "node-red-contrib-xiaomi-switch/xiaomi-switch.js", 17 | "xiaomi-socket": "node-red-contrib-xiaomi-socket/xiaomi-socket.js", 18 | "xiaomi-socket-wifi": "node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.js", 19 | "xiaomi-configurator": "node-red-contrib-xiaomi-configurator/xiaomi-configurator.js" 20 | } 21 | }, 22 | "author": "Harald Rietman", 23 | "bugs": { 24 | "url": "https://github.com/hrietman/node-red-contrib-xiaomi-devices/issues" 25 | }, 26 | "dependencies": { 27 | "cryptojs": "^2.5.3", 28 | "mustache": "^2.3.0", 29 | "miio": "0.13.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-switch/xiaomi-switch.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var mustache = require("mustache"); 4 | 5 | function XiaomiSwitchNode(config) { 6 | RED.nodes.createNode(this, config); 7 | this.gateway = RED.nodes.getNode(config.gateway); 8 | this.sid = config.sid; 9 | this.output = config.output; 10 | this.outmsg = config.outmsg; 11 | this.outmsgdbcl = config.outmsgdbcl; 12 | 13 | var node = this; 14 | 15 | node.status({fill:"grey",shape:"ring",text:"battery"}); 16 | 17 | if (this.gateway) { 18 | node.on('input', function(msg) { 19 | // var payload = JSON.parse(msg); 20 | var payload = msg.payload; 21 | 22 | if (payload.sid == node.sid && payload.model == "switch") { 23 | var data = JSON.parse(payload.data) 24 | 25 | if (data.voltage) { 26 | if (data.voltage < 2500) { 27 | node.status({fill:"red",shape:"dot",text:"battery"}); 28 | } else if (data.voltage < 2900) { 29 | node.status({fill:"yellow",shape:"dot",text:"battery"}); 30 | } else { 31 | node.status({fill:"green",shape:"dot",text:"battery"}); 32 | } 33 | } 34 | 35 | if (node.output == "0") { 36 | msg.payload = payload; 37 | node.send([msg]); 38 | } else if (node.output == "1") { 39 | var status = null; 40 | 41 | if (data.status) { 42 | status = {"payload": data.status}; 43 | } 44 | node.send([status]); 45 | } else if (node.output == "2") { 46 | var status = null; 47 | 48 | if (data.status && data.status == "click") { 49 | status = {"payload": mustache.render(node.outmsg, data)} 50 | node.send([[status],[]]); 51 | } 52 | 53 | if (data.status && data.status == "double_click") { 54 | status = {"payload": mustache.render(node.outmsgdbcl, data)} 55 | node.send([[],[status]]); 56 | } 57 | } 58 | } 59 | }); 60 | 61 | node.on("close", function() { 62 | }); 63 | 64 | } else { 65 | // no gateway configured 66 | } 67 | 68 | } 69 | 70 | RED.nodes.registerType("xiaomi-switch", XiaomiSwitchNode); 71 | 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-red-contrib-xiaomi-devices 2 | 3 | This module contains the following nodes to provide easy integration of the Xiaomi devices into node-red. 4 | 5 | The following devices are currently supported: 6 | 7 | * Temperature/humidity sensor 8 | * Magnet switch 9 | * Button switch 10 | * Motion sensor 11 | * Power plug (zigbee) 12 | * Power plug (wifi) 13 | 14 | ## Preperation 15 | To receive the gateway json messages on your network you need to enable the developer mode, aka LAN mode in the gateway. Refer to [openHAB](http://docs.openhab.org/addons/bindings/mihome/readme.html#setup) or [domoticz.com](https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz) for the necessary steps. 16 | 17 | A UDP input node is needed to receive the json messages. An UDP output node to send command's to the gateway. 18 | 19 | To control the Wifi-Plug, extensive use is made of the miio library created by [Andreas Holstenson](https://github.com/aholstenson/miio). Make sure to check his page for compatible devices. 20 | 21 | ## Install 22 | 23 | ``` 24 | cd ~\.node-red 25 | npm install node-red-contrib-xiaomi-devices 26 | ``` 27 | 28 | ## Usage 29 | 30 | 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. 31 | 32 | Note that the Wifi power plug is not configured through the configurator as it is not connected to the gateway. 33 | 34 | The Xiaomi configurator screen with ease of use to configure your different devices. 35 | 36 | ![Xiaomi configurator in node-red](https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/master/xiaomi-configurator.png) 37 | 38 | 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.) 39 | 40 | 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: 41 | 42 | ``` 43 | Listen for: multicast messages 44 | Group: 224.0.0.50 45 | Local ip: 46 | On port: 9898 ipv4 47 | Output: String 48 | ``` 49 | 50 | If you want to sent messages to the gateway you need to add an UDP sender, here an example configuration: 51 | 52 | ``` 53 | Send a: UDP message to port: 9898 54 | Address: ipv4 55 | ``` 56 | This configuration worked for me however I have seen people using different configuration to make UDP work. 57 | 58 | Here an example of how to use the different nodes. 59 | 60 | ![Xiaomi devices example in node-red](https://raw.githubusercontent.com/hrietman/node-red-contrib-xiaomi-devices/master/xiaomi-devices-overview.png) 61 | 62 | 63 | ## Roadmap 64 | * ~~Support for other devices like the smart-socket WiFi~~ Done! 65 | * Import (new) devices directly from the gateway 66 | 67 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-magnet/xiaomi-magnet.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var mustache = require("mustache"); 4 | 5 | function XiaomiMagnetNode(config) { 6 | RED.nodes.createNode(this, config); 7 | this.gateway = RED.nodes.getNode(config.gateway); 8 | this.sid = config.sid; 9 | this.output = config.output; 10 | this.openmsg = config.openmsg; 11 | this.closemsg = config.closemsg; 12 | 13 | var node = this; 14 | var state = ""; 15 | 16 | // node.status({fill:"yellow", shape:"dot", text:"unknown state"}); 17 | node.status({fill:"grey",shape:"ring",text:"battery"}); 18 | 19 | if (this.gateway) { 20 | node.on('input', function(msg) { 21 | // var payload = JSON.parse(msg); 22 | var payload = msg.payload; 23 | 24 | if (payload.sid == node.sid && payload.model == "magnet") { 25 | var data = JSON.parse(payload.data) 26 | 27 | // if (data.status && data.status == "open") { 28 | // node.status({fill:"green", shape:"dot", text:"open"}); 29 | // state = "open"; 30 | // } else if (data.status && data.status == "close") { 31 | // node.status({fill:"red", shape:"dot", text:"closed"}); 32 | // state = "closed"; 33 | // } 34 | 35 | if (data.voltage) { 36 | if (data.voltage < 2500) { 37 | node.status({fill:"red",shape:"dot",text:"battery"}); 38 | } else if (data.voltage < 2900) { 39 | node.status({fill:"yellow",shape:"dot",text:"battery"}); 40 | } else { 41 | node.status({fill:"green",shape:"dot",text:"battery"}); 42 | } 43 | } 44 | 45 | 46 | if (node.output == "0") { 47 | msg.payload = payload; 48 | node.send([msg]); 49 | } else if (node.output == "1") { 50 | var status = null; 51 | 52 | if (data.status) { 53 | status = {"payload": data.status}; 54 | } 55 | node.send([status]); 56 | } else if (node.output == "2") { 57 | var status = null; 58 | 59 | if (data.status === 'open' || data.no_close) { 60 | status = {"payload": mustache.render(node.openmsg, data)} 61 | } else { 62 | status = {"payload": mustache.render(node.closemsg, data)} 63 | } 64 | node.send([status]); 65 | } 66 | } 67 | }); 68 | 69 | node.on("close", function() { 70 | }); 71 | 72 | } else { 73 | // no gateway configured 74 | } 75 | 76 | } 77 | 78 | RED.nodes.registerType("xiaomi-magnet", XiaomiMagnetNode); 79 | 80 | } 81 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-ht/xiaomi-ht.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var mustache = require("mustache"); 4 | var dgram = require('dgram'); 5 | 6 | function XiaomiHtNode(config) { 7 | RED.nodes.createNode(this, config); 8 | this.gateway = RED.nodes.getNode(config.gateway); 9 | this.sid = config.sid; 10 | this.output = config.output; 11 | this.temperature = config.temperature; 12 | this.humidity = config.humidity; 13 | this.divide = config.divide; 14 | 15 | var node = this; 16 | 17 | node.status({fill:"grey",shape:"ring",text:"battery"}); 18 | 19 | if (this.gateway) { 20 | node.on('input', function(msg) { 21 | // var payload = JSON.parse(msg); 22 | var payload = msg.payload; 23 | node.log("Received message from: " + payload.model + " sid: " + payload.sid + " payload: " + payload.data); 24 | 25 | if (payload.sid == node.sid && payload.model == "sensor_ht") { 26 | var data = JSON.parse(payload.data) 27 | 28 | if (data.voltage) { 29 | if (data.voltage < 2500) { 30 | node.status({fill:"red",shape:"dot",text:"battery"}); 31 | } else if (data.voltage < 2900) { 32 | node.status({fill:"yellow",shape:"dot",text:"battery"}); 33 | } else { 34 | node.status({fill:"green",shape:"dot",text:"battery"}); 35 | } 36 | } 37 | 38 | if (node.output == "0") { 39 | msg.payload = payload; 40 | node.send([msg]); 41 | } else if (node.output == "1") { 42 | var temp = null; 43 | var humidity = null; 44 | 45 | if (data.temperature) { 46 | temp = {"payload": data.temperature}; 47 | } 48 | 49 | if (data.humidity) { 50 | humidity = {"payload": data.humidity}; 51 | } 52 | node.send([temp, humidity]); 53 | } else if (node.output == "2") { 54 | var temp = null; 55 | var humidity = null; 56 | 57 | if (data.temperature) { 58 | if (this.divide) { 59 | data.temperature = String(data.temperature / 100); 60 | } 61 | temp = {"payload": mustache.render(node.temperature, data)} 62 | } 63 | 64 | if (data.humidity) { 65 | if (this.divide) { 66 | data.humidity = String(data.humidity / 100); 67 | } 68 | humidity = {"payload": mustache.render(node.humidity, data)} 69 | } 70 | node.send([temp, humidity]); 71 | } 72 | } 73 | }); 74 | 75 | node.on("close", function() { 76 | }); 77 | 78 | } else { 79 | // no gateway configured 80 | } 81 | 82 | } 83 | 84 | RED.nodes.registerType("xiaomi-ht", XiaomiHtNode); 85 | 86 | } 87 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-motion/xiaomi-motion.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var mustache = require("mustache"); 4 | 5 | function XiaomiMotionNode(config) { 6 | RED.nodes.createNode(this, config); 7 | this.gateway = RED.nodes.getNode(config.gateway); 8 | this.sid = config.sid; 9 | this.output = config.output; 10 | this.motionmsg = config.motionmsg; 11 | this.nomotionmsg = config.nomotionmsg; 12 | 13 | var node = this; 14 | var state = ""; 15 | 16 | // node.status({fill:"yellow", shape:"dot", text:"unknown state"}); 17 | node.status({fill:"grey",shape:"ring",text:"battery"}); 18 | 19 | if (this.gateway) { 20 | node.on('input', function(msg) { 21 | // var payload = JSON.parse(msg); 22 | var payload = msg.payload; 23 | 24 | if (payload.sid == node.sid && payload.model == "motion") { 25 | var data = JSON.parse(payload.data) 26 | 27 | // if (data.status && data.status == "open") { 28 | // node.status({fill:"green", shape:"dot", text:"open"}); 29 | // state = "open"; 30 | // } else if (data.status && data.status == "close") { 31 | // node.status({fill:"red", shape:"dot", text:"closed"}); 32 | // state = "closed"; 33 | // } 34 | 35 | if (data.voltage) { 36 | if (data.voltage < 2500) { 37 | node.status({fill:"red",shape:"dot",text:"battery"}); 38 | } else if (data.voltage < 2900) { 39 | node.status({fill:"yellow",shape:"dot",text:"battery"}); 40 | } else { 41 | node.status({fill:"green",shape:"dot",text:"battery"}); 42 | } 43 | } 44 | 45 | 46 | if (node.output == "0") { 47 | msg.payload = payload; 48 | node.send([msg]); 49 | } else if (node.output == "1") { 50 | var status = null; 51 | var duration = null; 52 | 53 | if (data.status) { 54 | status = {"payload": data.status}; 55 | } 56 | if (data.no_motion) { 57 | status = {"payload": "no_motion"}; 58 | duration = {"payload": {"no_motion": data.no_motion}}; 59 | } 60 | 61 | node.send([[status], [duration]]); 62 | } else if (node.output == "2") { 63 | var status = null; 64 | 65 | if (data.status === 'motion') { 66 | status = {"payload": mustache.render(node.motionmsg, data)} 67 | } else { 68 | status = {"payload": mustache.render(node.nomotionmsg, data)} 69 | } 70 | node.send([status]); 71 | } 72 | } 73 | }); 74 | 75 | node.on("close", function() { 76 | }); 77 | 78 | } else { 79 | // no gateway configured 80 | } 81 | 82 | } 83 | 84 | RED.nodes.registerType("xiaomi-motion", XiaomiMotionNode); 85 | 86 | } 87 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.html: -------------------------------------------------------------------------------- 1 | 37 | 38 | 64 | 65 | 120 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-socket/xiaomi-socket.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var mustache = require("mustache"); 4 | var crypto = require("crypto"); 5 | 6 | function XiaomiPlugNode(config) { 7 | RED.nodes.createNode(this, config); 8 | this.gateway = RED.nodes.getNode(config.gateway); 9 | this.sid = config.sid; 10 | this.output = config.output; 11 | this.onmsg = config.onmsg; 12 | this.offmsg = config.offmsg; 13 | this.key = this.gateway.key; 14 | 15 | var node = this; 16 | var currentToken = ""; 17 | var state = ""; 18 | 19 | node.status({fill:"yellow", shape:"ring", text:"waiting for key"}); 20 | 21 | if (this.gateway && this.key != "") { 22 | node.on('input', function(msg) { 23 | // var payload = JSON.parse(msg); 24 | var payload = msg.payload; 25 | 26 | if (payload.cmd == "heartbeat" && payload.model == "gateway") { 27 | var token = payload.token; 28 | 29 | if (token) { 30 | var cipher = crypto.createCipheriv('aes128', node.key, (new Buffer("17996d093d28ddb3ba695a2e6f58562e", "hex"))); 31 | var encoded_string = cipher.update(token, 'utf8', 'hex'); 32 | 33 | encoded_string += cipher.final('hex'); 34 | currentToken = encoded_string.substring(0,32); 35 | if (state == "") { 36 | node.status({fill:"yellow", shape:"dot", text:"unknown state"}); 37 | } 38 | } 39 | } 40 | if (payload == 'on') { 41 | var cmd = 42 | { "cmd":"write", 43 | "sid": node.sid, 44 | "model": "plug", 45 | "data": JSON.stringify({"status":"on", "key": currentToken }) 46 | } 47 | msg.payload = JSON.stringify(cmd); 48 | node.send([[],[msg]]); 49 | 50 | } else if (payload == "off") { 51 | var cmd = 52 | { "cmd":"write", 53 | "sid": node.sid, 54 | "model": "plug", 55 | "data": JSON.stringify({"status":"off", "key": currentToken }) 56 | } 57 | msg.payload = JSON.stringify(cmd); 58 | node.send([[],[msg]]); 59 | 60 | } else if (payload.sid == node.sid && payload.model == "plug") { 61 | var data = JSON.parse(payload.data) 62 | 63 | if (currentToken == "") { 64 | node.status({fill:"yellow", shape:"ring", text:"waiting for key"}); 65 | } else if (data.status && data.status == "on") { 66 | node.status({fill:"green", shape:"dot", text:"on"}); 67 | state = "on"; 68 | } else if (data.status && data.status == "off") { 69 | node.status({fill:"red", shape:"dot", text:"off"}); 70 | state = "off"; 71 | } 72 | 73 | if (node.output == "0") { 74 | msg.payload = payload; 75 | node.send([msg]); 76 | } else if (node.output == "1") { 77 | var status = null; 78 | 79 | if (data.status) { 80 | status = {"payload": data.status}; 81 | } 82 | node.send([status]); 83 | } else if (node.output == "2") { 84 | var status = null; 85 | 86 | if (data.status === 'on') { 87 | status = {"payload": mustache.render(node.onmsg, data)} 88 | } else { 89 | status = {"payload": mustache.render(node.offmsg, data)} 90 | } 91 | node.send([status]); 92 | } 93 | } 94 | }); 95 | 96 | node.on("close", function() { 97 | }); 98 | 99 | } else { 100 | // no gateway configured 101 | if (this.key == "") { 102 | node.status({fill:"red", shape:"dot", text:"no key configured"}); 103 | } 104 | } 105 | 106 | } 107 | 108 | RED.nodes.registerType("xiaomi-plug", XiaomiPlugNode); 109 | 110 | } 111 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-magnet/xiaomi-magnet.html: -------------------------------------------------------------------------------- 1 | 55 | 56 | 86 | 87 | 141 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-motion/xiaomi-motion.html: -------------------------------------------------------------------------------- 1 | 55 | 56 | 86 | 87 | 146 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-switch/xiaomi-switch.html: -------------------------------------------------------------------------------- 1 | 58 | 59 | 89 | 90 | 149 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-configurator/xiaomi-configurator.html: -------------------------------------------------------------------------------- 1 | 78 | 79 | 92 | 93 | 116 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-socket-wifi/xiaomi-socket-wifi.js: -------------------------------------------------------------------------------- 1 | module.exports = function(RED) { 2 | "use strict"; 3 | var mustache = require("mustache"); 4 | var crypto = require("crypto"); 5 | var miio = require("miio"); 6 | var connectionState = "timeout"; 7 | var retryTimer; 8 | var delayedStatusMsgTimer; 9 | 10 | 11 | function XiaomiPlugWifiNode(config) { 12 | RED.nodes.createNode(this, config); 13 | this.ip = config.ip; 14 | this.output = config.output; 15 | this.onmsg = config.onmsg; 16 | this.offmsg = config.offmsg; 17 | this.plug = null; 18 | 19 | var node = this; 20 | 21 | node.status({fill: "yellow", shape: "dot", text: "connecting"}); 22 | 23 | miio.device({address: node.ip}) 24 | .then(function (plug) { 25 | node.plug = plug; 26 | node.status({fill:"green", shape:"dot", text:"connected"}); 27 | connectionState = "connected"; 28 | delayedStatusMsgUpdate(node); 29 | 30 | node.plug.on('propertyChanged', function(e) { 31 | if (e.property === "power") { 32 | if (e.value['0']) { 33 | setState("on"); 34 | } else { 35 | setState("off"); 36 | } 37 | } 38 | }); 39 | watchdog(); 40 | }) 41 | .catch(function (error) { 42 | connectionState = "reconnecting"; 43 | watchdog(); 44 | }) 45 | 46 | node.on('input', function (msg) { 47 | var payload = msg.payload; 48 | if (connectionState === "connected") { 49 | if (payload == 'on') { 50 | node.plug.setPower(true); 51 | } 52 | 53 | if (payload == 'off') { 54 | node.plug.setPower(false); 55 | } 56 | } 57 | }); 58 | 59 | node.on('close', function (done) { 60 | if (retryTimer) { 61 | clearTimeout(retryTimer); 62 | } 63 | if (delayedStatusMsgTimer) { 64 | clearTimeout(delayedStatusMsgTimer); 65 | } 66 | if (node.plug) { 67 | node.plug.destroy(); 68 | } 69 | done(); 70 | }); 71 | 72 | var setState = function(state) { 73 | if (node.plug) { 74 | var status = null; 75 | var info = {"payload": { 76 | "id": node.plug.id, 77 | "type": node.plug.type, 78 | "model": node.plug.model, 79 | "capabilities": node.plug.capabilities, 80 | "address": node.plug.address, 81 | "port": node.plug.port, 82 | "power": node.plug.power() 83 | }}; 84 | 85 | if (state === "on") { 86 | node.status({fill:"green", shape:"dot", text:"on"}); 87 | status = {"payload": mustache.render(node.onmsg, info.payload)} 88 | } 89 | if (state === "off") { 90 | node.status({fill:"red", shape:"dot", text:"off"}); 91 | status = {"payload": mustache.render(node.offmsg, info.payload)} 92 | } 93 | 94 | if (node.output == 0) { 95 | status = info; 96 | } else if (node.output == "1") { 97 | status = {"payload": state} 98 | } else if (node.output == "2") { 99 | // do nothing, just send status parsed with mustache 100 | } 101 | node.send([status]); 102 | } 103 | }; 104 | 105 | var delayedStatusMsgUpdate = function() { 106 | delayedStatusMsgTimer = setTimeout(function() { 107 | if (node.plug.power()['0']) { 108 | setState("on"); 109 | } else { 110 | setState("off"); 111 | } 112 | }, 1500); 113 | }; 114 | 115 | var discoverDevice = function() { 116 | miio.device({address: node.ip}) 117 | .then(function (plug) { 118 | if (node.plug == null) { 119 | node.plug = plug; 120 | node.plug.on('propertyChanged', function(e) { 121 | if (e.property === "power") { 122 | if (e.value['0']) { 123 | setState("on"); 124 | } else { 125 | setState("off"); 126 | } 127 | } 128 | }); 129 | } 130 | if (connectionState === "reconnecting") { 131 | node.status({fill:"green", shape:"dot", text:"connected"}); 132 | connectionState = "connected"; 133 | delayedStatusMsgUpdate(); 134 | } 135 | }) 136 | .catch(function (error) { 137 | connectionState = "reconnecting"; 138 | if (node.plug) { 139 | node.plug.destroy(); 140 | node.plug = null; 141 | } 142 | }) 143 | }; 144 | 145 | var watchdog = function() { 146 | setTimeout(function retryTimer() { 147 | discoverDevice(); 148 | if (connectionState === "reconnecting") { 149 | node.status({fill: "red", shape: "dot", text: "reconnecting"}); 150 | } 151 | setTimeout(retryTimer, 30000); 152 | }, 30000); 153 | } 154 | } 155 | 156 | RED.nodes.registerType("xiaomi-plug-wifi", XiaomiPlugWifiNode); 157 | 158 | process.on('unhandledRejection', function(reason, p) { 159 | // console.log("Possibly Unhandled Rejection at: Promise ", p, " reason: ", reason); 160 | var message = reason + ""; 161 | if (message.indexOf("Call to device timed out") >= 0) { 162 | if (this.plug) { 163 | console.log("Issue with miio package; discard plug and reconnect."); 164 | this.plug.destroy(); 165 | this.plug = null; 166 | } 167 | } 168 | }); 169 | } 170 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-socket/xiaomi-socket.html: -------------------------------------------------------------------------------- 1 | 55 | 56 | 86 | 87 | 156 | -------------------------------------------------------------------------------- /node-red-contrib-xiaomi-ht/xiaomi-ht.html: -------------------------------------------------------------------------------- 1 | 59 | 60 | 95 | 96 | 164 | --------------------------------------------------------------------------------