From e088107ae5efb54223b691e0d6a3f6b46b72091c Mon Sep 17 00:00:00 2001 From: Maxmudjon Date: Sat, 23 Nov 2019 21:19:05 +0500 Subject: [PATCH] 1.0.7 version minor fixes, added Mi IR --- .homeychangelog.json | 8 + APPSTORE.md | 57 --- README.md | 9 +- app.json | 129 ++++++- drivers/curtain/device.js | 14 +- drivers/ir_remote/assets/airConditioner.svg | 29 ++ drivers/ir_remote/assets/box.svg | 23 ++ drivers/ir_remote/assets/camera.svg | 47 +++ drivers/ir_remote/assets/dvd.svg | 88 +++++ drivers/ir_remote/assets/purifier.svg | 1 + drivers/ir_remote/assets/satellite.svg | 1 + drivers/ir_remote/assets/unknown.svg | 1 + drivers/ir_remote/device.js | 85 +++-- drivers/ir_remote/driver.js | 93 ++--- drivers/ir_remote/pair/capabilities.js | 59 +++- .../ir_remote/pair/deviceCharacteristics.html | 58 +-- drivers/ir_remote/pair/deviceTypes.html | 31 +- .../img/types/irv2_device_type_tv_normal.png | Bin 7758 -> 9147 bytes drivers/ir_remote/pair/keys.html | 199 +++++------ node_modules/miio/lib/network.js | 222 ++++++------ node_modules/miio/lib/packet.js | 79 +++-- readme.txt | 334 ++++++++++++++++++ 22 files changed, 1107 insertions(+), 460 deletions(-) create mode 100644 .homeychangelog.json delete mode 100644 APPSTORE.md create mode 100644 drivers/ir_remote/assets/airConditioner.svg create mode 100644 drivers/ir_remote/assets/box.svg create mode 100644 drivers/ir_remote/assets/camera.svg create mode 100644 drivers/ir_remote/assets/dvd.svg create mode 100644 drivers/ir_remote/assets/purifier.svg create mode 100644 drivers/ir_remote/assets/satellite.svg create mode 100644 drivers/ir_remote/assets/unknown.svg create mode 100644 readme.txt diff --git a/.homeychangelog.json b/.homeychangelog.json new file mode 100644 index 0000000..0d3ce81 --- /dev/null +++ b/.homeychangelog.json @@ -0,0 +1,8 @@ +{ + "1.0.6": { + "en": "first time publish" + }, + "1.0.7": { + "en": "minor fixes" + } +} diff --git a/APPSTORE.md b/APPSTORE.md deleted file mode 100644 index f87c2ce..0000000 --- a/APPSTORE.md +++ /dev/null @@ -1,57 +0,0 @@ -# Mi Homey - -Added support for Mi Gateways child devices and wifi devices. - -## Supported devices: - -- Double Button Wirelles Remote Switch (DuplexButton86). -- Motion Sensor 1 version (MotionSensor). -- Leak Sensor (WaterDetector). -- Plug zigbee (PlugBase). -- Contact Sensor (MagnetSensor). -- Button Switch (Switch). -- Mi Temperature and Humidity Sensor (Sensor_HT). -- Aqara Smart Light Switch With Neutral (SingleSwitchLN). -- Aqara Wirelles Single Switch (SingleButton86). -- Aqara Wall Outlet (PlugBase86). -- Aqara Smart Light Switch With Neutral (DoubleSwitchLN). -- Aqara Smart Light Switch (DoubleSwitch). -- Aqara Smart Light Switch (SingleSwitch). -- Aqara Temperature and Humidity Sensor (WeatherSensor). -- Aqara Door and Window Sensor (MagnetSensor2). -- Aqara Motion Sensor (MotionSensor2). -- Mi / Aqara Cube (Cube). -- Aqara Curtain Motor (Curtain). -- Aqara Button Switch (SwitchSensor). -- Mijia Gateway (Gateway). -- MiJia Gas Leak Detector (NatgasDetector). -- MiJia Smoke Detector (SmokeDetector). -- Aqara Wireless Remote Switch Double Button (DuplexButton862) (advanced 2018) -- Aqara Button Switch (SwitchSensor) (advanced 2018). -- Aqara Lock (Lock). -- Aqara Vibration sensor (Vibration). -- Yeelight Color Bulb. -- Yeelight White Bulb. -- Yeelight Ceiling Lamp. -- Yeelight Bedside Lamp. -- Yeelight Light Strip. -- Mi LED Desk Lamp. -- Philips Eyecare Desk Lapmp 2. -- Philips Eyecare Ceiling Lamp. -- Philips Light Bulb. -- Mi Smart Plug WiFi. -- Mi Smart Plug With USB WiFi. -- Mi Smart Plug With 2 USB WiFi. -- Mi Smart Power Strip. -- CHINGMI Smart Power Strip. -- Yeelight Jiaoyue 650. -- Mi Air Purifier S2. -- Mi Vacuum Cleaner (gen 1). -- Mi Vacuum Cleaner V2 (gen 2). -- Philips Zhirui Smart LED Bulb E14 Candle Lamp. -- Philips Zhirui Smart LED Bulb E14 Candle Lamp White Crystal. -- Philips Downlight. -- Philips Zhirui Bedside Lamp. -- Mi Air Purifier Pro. -- Mi Humidifier V1. -- Mi Humidifier V2. diff --git a/README.md b/README.md index 3d82432..32b169d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Added support for Mi Gateways child devices. -## Version 1.0.6 - Supported devices: +## Version 1.0.7 - Supported devices: ### Zigbee subdevices Xiaomi Gateway @@ -68,6 +68,7 @@ Added support for Mi Gateways child devices. - Mi Vacuum Cleaner 1S. - Mi Air Purifier (MJXFJ-300-G1). - Mi Roborock Vacuum Cleaner T6. +- Mi IR remote controller. ![](https://raw.githubusercontent.com/Maxmudjon/images/master/DuplexButton86.jpg) ![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiMotionSensor.jpg) @@ -123,6 +124,12 @@ Added support for Mi Gateways child devices. ## Version logs +### 1.0.7 (23.11.2019) + +1. minor fixes. +2. added Mi IR devices (beta). +3. 1.5 version of Homey firmware is no longer supported. + ### 1.0.6 (10.11.2019) 1. added Mi Roborock Vacuum Cleaner T6. diff --git a/app.json b/app.json index b70241a..648b44f 100644 --- a/app.json +++ b/app.json @@ -1,7 +1,7 @@ { "id": "com.maxmudjon.mihomey", - "version": "1.0.6", - "compatibility": ">=1.5.0", + "version": "1.0.7", + "compatibility": ">=2.0.0", "sdk": 2, "name": { "en": "Mi Homey" @@ -41,6 +41,10 @@ "bugs": { "url": "https://github.com/Maxmudjon/com.maxmudjon.mihomey/issues" }, + "homeyCommunityTopicId": 19816, + "source": "https://github.com/Maxmudjon/com.maxmudjon.mihomey", + "homepage": "https://github.com/Maxmudjon/com.maxmudjon.mihomey", + "support": "mailto:bio42@mail.ru", "flow": { "triggers": [ { @@ -4396,6 +4400,22 @@ "value": "AABBCC01LM" } ] + }, + { + "type": "group", + "label": { + "en": "Mode of operation" + }, + "children": [ + { + "id": "reverted", + "type": "checkbox", + "value": true, + "label": { + "en": "Reverted" + } + } + ] } ], "images": { @@ -8876,6 +8896,9 @@ "alarm_filter_work_time", "alarm_sensor_dirty_time" ], + "energy": { + "batteries": ["OTHER"] + }, "capabilitiesOptions": { "onoff": { "title": { @@ -9077,6 +9100,9 @@ "alarm_filter_work_time", "alarm_sensor_dirty_time" ], + "energy": { + "batteries": ["OTHER"] + }, "capabilitiesOptions": { "onoff": { "title": { @@ -9278,6 +9304,9 @@ "alarm_filter_work_time", "alarm_sensor_dirty_time" ], + "energy": { + "batteries": ["OTHER"] + }, "capabilitiesOptions": { "onoff": { "title": { @@ -9480,6 +9509,9 @@ "alarm_sensor_dirty_time", "alarm_motion" ], + "energy": { + "batteries": ["OTHER"] + }, "capabilitiesOptions": { "onoff": { "title": { @@ -10131,6 +10163,99 @@ } } ] + }, + { + "id": "ir_remote", + "name": { + "en": "Mi IR" + }, + "class": "other", + "capabilities": ["onoff"], + "settings": [ + { + "type": "group", + "label": { + "en": "Device settings" + }, + "children": [ + { + "id": "deviceIp", + "type": "text", + "label": { + "en": "Device IP" + }, + "hint": { + "en": "Enter Mi IR remote controller IP address." + }, + "value": "" + }, + { + "id": "deviceToken", + "type": "text", + "label": { + "en": "Device Token" + }, + "hint": { + "en": "Enter Mi IR remote controller token." + }, + "value": "" + } + ] + }, + { + "type": "group", + "label": { + "en": "Configure sending codes" + }, + "children": [ + { + "id": "replay", + "type": "number", + "label": { + "en": "Replay" + }, + "value": 1, + "min": 1, + "max": 10 + } + ] + } + ], + "images": { + "large": "/drivers/ir_remote/assets/images/large.png", + "small": "/drivers/ir_remote/assets/images/small.png" + }, + "pair": [ + { + "id": "deviceTypes" + }, + { + "id": "devices", + "navigation": { + "prev": "deviceTypes" + } + }, + { + "id": "deviceCharacteristics", + "navigation": { + "next": "keys", + "prev": "devices" + } + }, + { + "id": "keys", + "navigation": { + "next": "done", + "prev": "deviceCharacteristics" + } + }, + { + "id": "done", + "navigation": { + "prev": "devices" + } + } + ] } ] } diff --git a/drivers/curtain/device.js b/drivers/curtain/device.js index 3697fcb..03aed67 100644 --- a/drivers/curtain/device.js +++ b/drivers/curtain/device.js @@ -128,9 +128,19 @@ class Curtain extends Homey.Device { registerCovering(name) { let sid = this.data.sid; this.registerCapabilityListener(name, async value => { + const settings = this.getSettings(); const states = { up: "open", idle: "stop", down: "close" }; - const data = { curtain_status: states[value] }; - await Homey.app.mihub.sendWrite(sid, data); + + if (value == "up") { + const data = { curtain_status: states[settings.reverted ? "down" : "up"] }; + await Homey.app.mihub.sendWrite(sid, data); + } else if (value == "down") { + const data = { curtain_status: states[settings.reverted ? "up" : "down"] }; + await Homey.app.mihub.sendWrite(sid, data); + } else { + const data = { curtain_status: states[value] }; + await Homey.app.mihub.sendWrite(sid, data); + } }); } diff --git a/drivers/ir_remote/assets/airConditioner.svg b/drivers/ir_remote/assets/airConditioner.svg new file mode 100644 index 0000000..a49038f --- /dev/null +++ b/drivers/ir_remote/assets/airConditioner.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/drivers/ir_remote/assets/box.svg b/drivers/ir_remote/assets/box.svg new file mode 100644 index 0000000..98559d0 --- /dev/null +++ b/drivers/ir_remote/assets/box.svg @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/drivers/ir_remote/assets/camera.svg b/drivers/ir_remote/assets/camera.svg new file mode 100644 index 0000000..6b85600 --- /dev/null +++ b/drivers/ir_remote/assets/camera.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/drivers/ir_remote/assets/dvd.svg b/drivers/ir_remote/assets/dvd.svg new file mode 100644 index 0000000..78bbe40 --- /dev/null +++ b/drivers/ir_remote/assets/dvd.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/drivers/ir_remote/assets/purifier.svg b/drivers/ir_remote/assets/purifier.svg new file mode 100644 index 0000000..f79daad --- /dev/null +++ b/drivers/ir_remote/assets/purifier.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/drivers/ir_remote/assets/satellite.svg b/drivers/ir_remote/assets/satellite.svg new file mode 100644 index 0000000..c7de34a --- /dev/null +++ b/drivers/ir_remote/assets/satellite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/drivers/ir_remote/assets/unknown.svg b/drivers/ir_remote/assets/unknown.svg new file mode 100644 index 0000000..7191816 --- /dev/null +++ b/drivers/ir_remote/assets/unknown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/drivers/ir_remote/device.js b/drivers/ir_remote/device.js index 9026055..bd7f197 100644 --- a/drivers/ir_remote/device.js +++ b/drivers/ir_remote/device.js @@ -34,6 +34,8 @@ class IRRemote extends Homey.Device { this.registerVolumeUpAction("volume_up"); this.registerVolumeDownAction("volume_down"); this.registerMuteAction("volume_mute"); + this.registerDimAction("dim"); + this.registerThermostatAction("thermostat"); } registerCondition(name, condition) { @@ -42,76 +44,115 @@ class IRRemote extends Homey.Device { registerStandbyAction(name) { this.registerCapabilityListener(name, async value => { - this.sendIrCode(this.data.standby, name); + if (value) { + if (this.data && this.data.onoff1) { + this.sendIrCode(this.data.onoff1, name); + } else { + this.sendIrCode(this.data.onoff, name); + } + } else { + if (this.data && this.data.onoff2) { + this.sendIrCode(this.data.onoff2, name); + } else { + this.sendIrCode(this.data.onoff, name); + } + } }); } registerChannelUpAction(name) { this.registerCapabilityListener(name, async value => { - this.sendIrCode(this.data.channelUp, name); + this.sendIrCode(this.data.channel_up, name); }); } registerChannelDownAction(name) { this.registerCapabilityListener(name, async value => { - this.sendIrCode(this.data.channelDown, name); + this.sendIrCode(this.data.channel_down, name); }); } registerVolumeUpAction(name) { this.registerCapabilityListener(name, async value => { - this.sendIrCode(this.data.volumeUp, name); + this.sendIrCode(this.data.volume_up, name); }); } registerVolumeDownAction(name) { this.registerCapabilityListener(name, async value => { - this.sendIrCode(this.data.volumeDown, name); + this.sendIrCode(this.data.volume_down, name); }); } registerMuteAction(name) { this.registerCapabilityListener(name, async value => { - this.sendIrCode(this.data.mute, name); + this.sendIrCode(this.data.volume_mute, name); + }); + } + + registerDimAction(name) { + this.registerCapabilityListener(name, async value => { + if (this.data && this.data.dim1) { + this.sendIrCode(this.data.dim + value, name); + } else { + this.sendIrCode(this.data.dim, name); + } + }); + } + + registerThermostatAction(name) { + this.registerCapabilityListener(name, async value => { + this.log(value); + // this.sendIrCode(this.data.volume_mute, name); }); } sendIrCodeAction(name, action) { action.code.registerRunListener(async (args, state) => { + let data = { + deviceIp: args.device.getSetting("deviceIP"), + deviceToken: args.device.getSetting("deviceToken") + }; this.sendIrCode(args.code); }); } sendIrCode(code, cababilityName) { const settings = this.getSettings(); - var that = this; miio .device({ address: settings.deviceIp, token: settings.deviceToken }) - .then(device => { - device - .call("miIO.ir_play", { freq: 38400, code: code }) - .then(result => { - if (!cababilityName) { - cababilityName = "custom"; - } - that.log("Sending " + cababilityName + " ir code: " + code); - }) - .catch(function(error) { - that.log("Sending ir code error: ", error); - }); + .then(async device => { + for (let i = 0; i < settings.replay; i++) { + await this.sleep(500); + device + .call("miIO.ir_play", { freq: 38400, code: code }) + .then(result => { + if (!cababilityName) { + cababilityName = "custom"; + } + this.log("Sending " + cababilityName + " ir code: " + code); + }) + .catch(error => { + this.log("Sending ir code error: ", error); + }); + } }) - .catch(function(error) { + .catch(error => { if (error == "Error: Could not connect to device, handshake timeout") { - that.log("Device timeout error: ", error); + this.log("Device timeout error: ", error); } else { - that.log("Device error: ", error); + this.log("Device error: ", error); } }); } + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + onAdded() { this.log("Device added"); } diff --git a/drivers/ir_remote/driver.js b/drivers/ir_remote/driver.js index 4a0deb9..caf1907 100644 --- a/drivers/ir_remote/driver.js +++ b/drivers/ir_remote/driver.js @@ -24,49 +24,33 @@ class IRRemote extends Homey.Driver { }; let type = "other"; - let allCapabilities; - let createdDevice = { - onoffType: { - onoff: 0 - }, - dimSteep: { - dim: 0 - } - }; - socket.on("getCurrentDevice", function(data, callback) { - let device = Object.assign(pairingDevice, createdDevice); - console.log("CURRENT PAIRING DEVICES: ", device); - callback(null, device); + socket.on("getCurrentDevice", (data, callback) => { + callback(null, pairingDevice); }); - socket.on("getCurrentDeviceForCharacteristics", function(data, callback) { - console.log("CURRENT PAIRING DEVICES: ", pairingDevice); - + socket.on("getCurrentDeviceForCharacteristics", (data, callback) => { callback(null, pairingDevice); }); - socket.on("getDevicesList", function(data, callback) { + socket.on("getDevicesList", (data, callback) => { var devices = Homey.ManagerSettings.get("irDevicesList"); callback(null, devices || []); }); - socket.on("newCharacteristics", function(data, callback) { - console.log(data); - if (data.onoff) { - createdDevice.onoffType.onoff = data.onoff; - } + socket.on("newCharacteristics", (data, callback) => { + pairingDevice = data; - if (data.dim) { - createdDevice.dimSteep.dim = data.dim; + if (pairingDevice.capabilitiesOptions && pairingDevice.capabilitiesOptions.dim) { + pairingDevice.capabilitiesOptions.dim.max = parseInt(data.characteristicsSettings.dim); } - console.log(createdDevice); + console.log(pairingDevice.capabilitiesOptions); callback(null, true); }); - socket.on("learnCode", function(data, callback) { + socket.on("learnCode", (data, callback) => { console.log("LEARN CODE: ", data); this.timekey = "123456789012345"; @@ -80,39 +64,23 @@ class IRRemote extends Homey.Driver { device .call("miIO.ir_learn", { key: this.timekey }) .then(result => {}) - .catch(function(error) { + .catch(error => { if (error == "Error: Call to device timed out") { callback(null, "time out"); } else { callback(error, "Error"); } }); - }) - .catch(function(error) { - if (error == "Error: Could not connect to device, handshake timeout") { - callback(null, "offline"); - } else { - callback(error, "Error"); - } - }); - setTimeout(() => { - miio - .device({ - address: data.ip, - token: data.token - }) - .then(device => { + setTimeout(() => { device .call("miIO.ir_read", { key: this.timekey }) .then(result => { if (result["code"] !== "") { let value = { code: result["code"] }; - pairingDevice.data[`${data.key}`] = result["code"]; + pairingDevice.data[data.key] = result["code"]; console.log("KEY KEY: ", data.key); - pairingDevice.capabilities = data.key; - console.log("TAYYORI: ", pairingDevice); callback(null, value); @@ -120,39 +88,36 @@ class IRRemote extends Homey.Driver { callback(null, "timeout"); } }) - .catch(function(err) { - if (err == "Error: Call to device timed out") { + .catch(error => { + if (error == "Error: Call to device timed out") { callback(null, "offline"); } else { - callback(err, "Error"); + callback(error, "Error"); } }); - }) - .catch(function(error) { - if (error == "Error: Could not connect to device, handshake timeout") { - callback(null, "offline"); - } else { - callback(error, "Error"); - } - }); - }, 5000); + }, 5000); + }) + .catch(error => { + if (error == "Error: Could not connect to device, handshake timeout") { + callback(null, "offline"); + } else { + callback(error, "Error"); + } + }); }); - socket.on("selectedType", function(data, callback) { + socket.on("selectedType", (data, callback) => { type = data.type; - console.log("Tanlangan qurilma turi: ", data.type); - pairingDevice.name = data.name; pairingDevice.icon = data.type + ".svg"; pairingDevice.capabilities = data.capabilities; pairingDevice.capabilitiesOptions = data.capabilitiesOptions; - - console.log(pairingDevice); + pairingDevice.characteristicsSettings = data.characteristicsSettings; callback(null, type); }); - socket.on("selectedDevice", function(data, callback) { + socket.on("selectedDevice", (data, callback) => { pairingDevice.settings.deviceIp = data.devices.ip; pairingDevice.settings.deviceToken = data.devices.token; pairingDevice.data.id = @@ -166,7 +131,7 @@ class IRRemote extends Homey.Driver { callback(null, pairingDevice); }); - socket.on("allmostDone", function(data, callback) { + socket.on("allmostDone", (data, callback) => { callback(null, pairingDevice); }); } diff --git a/drivers/ir_remote/pair/capabilities.js b/drivers/ir_remote/pair/capabilities.js index f76e261..77a4968 100644 --- a/drivers/ir_remote/pair/capabilities.js +++ b/drivers/ir_remote/pair/capabilities.js @@ -7,9 +7,37 @@ var devices = { title: { en: "Standby" } + }, + volume_up: { + title: { + en: "Volume UP" + } + }, + volume_down: { + title: { + en: "Volume DOWN" + } + }, + channel_up: { + title: { + en: "Channel UP" + } + }, + channel_down: { + title: { + en: "Channel DOWN" + } + }, + volume_mute: { + title: { + en: "Volume MUTE" + } } }, - defaultCapabilities: ["onoff"] + defaultCapabilities: ["onoff"], + characteristicsSettings: { + onoff: 1 + } }, projector: { name: "Projector", @@ -21,10 +49,14 @@ var devices = { } } }, - defaultCapabilities: ["onoff"] + defaultCapabilities: ["onoff"], + characteristicsSettings: { + onoff: 1 + } }, airConditioner: { name: "Air Conditioner", + class: "thermostat", capabilities: ["onoff", "thermostat"], capabilitiesOptions: { onoff: { @@ -33,7 +65,15 @@ var devices = { } } }, - defaultCapabilities: ["onoff"] + defaultCapabilities: ["onoff"], + characteristicsSettings: { + onoff: 1, + thermostat: { + heat: true, + cold: true, + auto: true + } + } }, amplifier: { name: "Amplifier", @@ -45,7 +85,10 @@ var devices = { } } }, - defaultCapabilities: ["onoff"] + defaultCapabilities: ["onoff"], + characteristicsSettings: { + onoff: 1 + } }, fan: { name: "FAN", @@ -61,10 +104,14 @@ var devices = { en: "Fan speed" }, min: 0, - max: 3, + max: 1, step: 1 } }, - defaultCapabilities: ["onoff"] + defaultCapabilities: ["onoff"], + characteristicsSettings: { + onoff: 1, + dim: 1 + } } }; diff --git a/drivers/ir_remote/pair/deviceCharacteristics.html b/drivers/ir_remote/pair/deviceCharacteristics.html index 3e11016..bba6ce9 100644 --- a/drivers/ir_remote/pair/deviceCharacteristics.html +++ b/drivers/ir_remote/pair/deviceCharacteristics.html @@ -5,7 +5,7 @@ +
-

Coming soon

- +
diff --git a/node_modules/miio/lib/network.js b/node_modules/miio/lib/network.js index 982b81d..21b2c19 100644 --- a/node_modules/miio/lib/network.js +++ b/node_modules/miio/lib/network.js @@ -1,24 +1,23 @@ -'use strict'; +"use strict"; -const EventEmitter = require('events'); -const dgram = require('dgram'); +const EventEmitter = require("events"); +const dgram = require("dgram"); -const debug = require('debug'); +const debug = require("debug"); -const Packet = require('./packet'); -const tokens = require('./tokens'); +const Packet = require("./packet"); +const tokens = require("./tokens"); -const safeishJSON = require('./safeishJSON'); +const safeishJSON = require("./safeishJSON"); const PORT = 54321; const ERRORS = { - '-5001': (method, args, err) => err.message === 'invalid_arg' ? 'Invalid argument' : err.message, - '-5005': (method, args, err) => err.message === 'params error' ? 'Invalid argument' : err.message, - '-10000': (method) => 'Method `' + method + '` is not supported' + "-5001": (method, args, err) => (err.message === "invalid_arg" ? "Invalid argument" : err.message), + "-5005": (method, args, err) => (err.message === "params error" ? "Invalid argument" : err.message), + "-10000": method => "Method `" + method + "` is not supported" }; - /** * Class for keeping track of the current network of devices. This is used to * track a few things: @@ -39,28 +38,28 @@ class Network extends EventEmitter { this.devices = new Map(); this.references = 0; - this.debug = debug('miio:network'); + this.debug = debug("miio:network"); } search() { this.packet.handshake(); const data = Buffer.from(this.packet.raw); - this.socket.send(data, 0, data.length, PORT, '255.255.255.255'); + this.socket.send(data, 0, data.length, PORT, "255.255.255.255"); // Broadcast an extra time in 500 milliseconds in case the first brodcast misses a few devices setTimeout(() => { - this.socket.send(data, 0, data.length, PORT, '255.255.255.255'); + this.socket.send(data, 0, data.length, PORT, "255.255.255.255"); }, 500); } findDevice(id, rinfo) { // First step, check if we know about the device based on id let device = this.devices.get(id); - if(! device && rinfo) { + if (!device && rinfo) { // If we have info about the address, try to resolve again device = this.addresses.get(rinfo.address); - if(! device) { + if (!device) { // No device found, keep track of this one device = new DeviceInfo(this, id, rinfo.address, rinfo.port); this.devices.set(id, device); @@ -74,33 +73,34 @@ class Network extends EventEmitter { } findDeviceViaAddress(options) { - if(! this.socket) { - throw new Error('Implementation issue: Using network without a reference'); + if (!this.socket) { + throw new Error("Implementation issue: Using network without a reference"); } let device = this.addresses.get(options.address); - if(! device) { + if (!device) { // No device was found at the address, try to discover it device = new DeviceInfo(this, null, options.address, options.port || PORT); this.addresses.set(options.address, device); } // Update the token if we have one - if(typeof options.token === 'string') { - device.token = Buffer.from(options.token, 'hex'); - } else if(options.token instanceof Buffer) { + if (typeof options.token === "string") { + device.token = Buffer.from(options.token, "hex"); + } else if (options.token instanceof Buffer) { device.token = options.token; } // Set the model if provided - if(! device.model && options.model) { + if (!device.model && options.model) { device.model = options.model; } // Perform a handshake with the device to see if we can connect - return device.handshake() + return device + .handshake() .catch(err => { - if(err.code === 'missing-token') { + if (err.code === "missing-token") { // Supress missing tokens - enrich should take care of that return; } @@ -108,7 +108,7 @@ class Network extends EventEmitter { throw err; }) .then(() => { - if(! this.devices.has(device.id)) { + if (!this.devices.has(device.id)) { // This is a new device, keep track of it this.devices.set(device.id, device); @@ -130,47 +130,48 @@ class Network extends EventEmitter { } createSocket() { - this._socket = dgram.createSocket('udp4'); + this._socket = dgram.createSocket("udp4"); // Bind the socket and when it is ready mark it for broadcasting this._socket.bind(); - this._socket.on('listening', () => { + this._socket.on("listening", () => { this._socket.setBroadcast(true); const address = this._socket.address(); - this.debug('Network bound to port', address.port); + this.debug("Network bound to port", address.port); }); // On any incoming message, parse it, update the discovery - this._socket.on('message', (msg, rinfo) => { + this._socket.on("message", (msg, rinfo) => { const buf = Buffer.from(msg); try { this.packet.raw = buf; - } catch(ex) { - this.debug('Could not handle incoming message'); + } catch (ex) { + this.debug("Could not handle incoming message"); return; } - if(! this.packet.deviceId) { - this.debug('No device identifier in incoming packet'); + if (!this.packet.deviceId) { + this.debug("No device identifier in incoming packet"); return; } const device = this.findDevice(this.packet.deviceId, rinfo); device.onMessage(buf); - if(! this.packet.data) { - if(! device.enriched) { + if (!this.packet.data) { + if (!device.enriched) { // This is the first time we see this device - device.enrich() + device + .enrich() .then(() => { - this.emit('device', device); + this.emit("device", device); }) .catch(err => { - this.emit('device', device); + this.emit("device", device); }); } else { - this.emit('device', device); + this.emit("device", device); } } }); @@ -184,7 +185,7 @@ class Network extends EventEmitter { * Get a reference to the network. Helps with locking of a socket. */ ref() { - this.debug('Grabbing reference to network'); + this.debug("Grabbing reference to network"); this.references++; this.updateSocket(); @@ -192,9 +193,9 @@ class Network extends EventEmitter { let self = this; return { release() { - if(released) return; + if (released) return; - self.debug('Releasing reference to network'); + self.debug("Releasing reference to network"); released = true; self.references--; @@ -210,23 +211,23 @@ class Network extends EventEmitter { * discovery or device is being used. */ updateSocket() { - if(this.references === 0) { + if (this.references === 0) { // No more references, kill the socket - if(this._socket) { - this.debug('Network no longer active, destroying socket'); + if (this._socket) { + this.debug("Network no longer active, destroying socket"); this._socket.close(); this._socket = null; } - } else if(this.references === 1 && ! this._socket) { + } else if (this.references === 1 && !this._socket) { // This is the first reference, create the socket - this.debug('Making network active, creating socket'); + this.debug("Making network active, creating socket"); this.createSocket(); } } get socket() { - if(! this._socket) { - throw new Error('Network communication is unavailable, device might be destroyed'); + if (!this._socket) { + throw new Error("Network communication is unavailable, device might be destroyed"); } return this._socket; @@ -248,7 +249,7 @@ class DeviceInfo { this.lastId = 0; this.id = id; - this.debug = id ? debug('thing:miio:' + id) : debug('thing:miio:pending'); + this.debug = id ? debug("thing:miio:" + id) : debug("thing:miio:pending"); // Get if the token has been manually changed this.tokenChanged = false; @@ -259,7 +260,7 @@ class DeviceInfo { } set token(t) { - this.debug('Using manual token:', t.toString('hex')); + this.debug("Using manual token:", t.toString("hex")); this.packet.token = t; this.tokenChanged = true; } @@ -269,40 +270,39 @@ class DeviceInfo { * simply call miIO.info. */ enrich() { - if(! this.id) { - throw new Error('Device has no identifier yet, handshake needed'); + if (!this.id) { + console.log("Device has no identifier yet, handshake needed"); } - if(this.model && ! this.tokenChanged && this.packet.token) { + if (this.model && !this.tokenChanged && this.packet.token) { // This device has model info and a valid token return Promise.resolve(); } - if(this.enrichPromise) { + if (this.enrichPromise) { // If enrichment is already happening return this.enrichPromise; } // Check if there is a token available, otherwise try to resolve it let promise; - if(! this.packet.token) { + if (!this.packet.token) { // No automatic token found - see if we have a stored one - this.debug('Loading token from storage, device hides token and no token set via options'); + this.debug("Loading token from storage, device hides token and no token set via options"); this.autoToken = false; - promise = tokens.get(this.id) - .then(token => this.token = Buffer.from(token, 'hex')); + promise = tokens.get(this.id).then(token => (this.token = Buffer.from(token, "hex"))); } else { - if(this.tokenChanged) { + if (this.tokenChanged) { this.autoToken = false; } else { this.autoToken = true; - this.debug('Using automatic token:', this.packet.token.toString('hex')); + this.debug("Using automatic token:", this.packet.token.toString("hex")); } promise = Promise.resolve(); } - return this.enrichPromise = promise - .then(() => this.call('miIO.info')) + return (this.enrichPromise = promise + .then(() => this.call("miIO.info")) .then(data => { this.enriched = true; this.model = data.model; @@ -314,82 +314,82 @@ class DeviceInfo { this.enrichPromise = null; this.enriched = true; - if(err.code === 'missing-token') { + if (err.code === "missing-token") { // Rethrow some errors throw err; } - if(this.packet.token) { + if (this.packet.token) { // Could not call the info method, this might be either a timeout or a token problem - const e = new Error('Could not connect to device, token might be wrong'); - e.code = 'connection-failure'; + const e = new Error("Could not connect to device, token might be wrong"); + e.code = "connection-failure"; e.device = this; throw e; } else { - const e = new Error('Could not connect to device, token needs to be specified'); - e.code = 'missing-token'; + const e = new Error("Could not connect to device, token needs to be specified"); + e.code = "missing-token"; e.device = this; throw e; } - }); + })); } onMessage(msg) { try { this.packet.raw = msg; - } catch(ex) { - this.debug('<- Unable to parse packet', ex); + } catch (ex) { + this.debug("<- Unable to parse packet", ex); return; } let data = this.packet.data; - if(data === null) { - this.debug('<-', 'Handshake reply:', this.packet.checksum); + if (data === null) { + this.debug("<-", "Handshake reply:", this.packet.checksum); this.packet.handleHandshakeReply(); - if(this.handshakeResolve) { + if (this.handshakeResolve) { this.handshakeResolve(); } } else { // Handle null-terminated strings - if(data[data.length - 1] === 0) { + if (data[data.length - 1] === 0) { data = data.slice(0, data.length - 1); } // Parse and handle the JSON message - let str = data.toString('utf8'); + let str = data.toString("utf8"); // Remove non-printable characters to help with invalid JSON from devices - str = str.replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, ''); // eslint-disable-line + str = str.replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F\x7F-\x9F]/g, ""); // eslint-disable-line - this.debug('<- Message: `' + str + '`'); + this.debug("<- Message: `" + str + "`"); try { let object = safeishJSON(str); const p = this.promises.get(object.id); - if(! p) return; - if(typeof object.result !== 'undefined') { + if (!p) return; + if (typeof object.result !== "undefined") { p.resolve(object.result); } else { p.reject(object.error); } - } catch(ex) { - this.debug('<- Invalid JSON', ex); + } catch (ex) { + this.debug("<- Invalid JSON", ex); } } } handshake() { - if(! this.packet.needsHandshake) { + if (!this.packet.needsHandshake) { return Promise.resolve(this.token); } // If a handshake is already in progress use it - if(this.handshakePromise) { + if (this.handshakePromise) { return this.handshakePromise; } - return this.handshakePromise = new Promise((resolve, reject) => { + return (this.handshakePromise = new Promise((resolve, reject) => { // Create and send the handshake data this.packet.handshake(); const data = this.packet.raw; @@ -402,18 +402,18 @@ class DeviceInfo { this.handshakeTimeout = null; this.handshakePromise = null; - if(this.id !== this.packet.deviceId) { + if (this.id !== this.packet.deviceId) { // Update the identifier if needed this.id = this.packet.deviceId; - this.debug = debug('thing:miio:' + this.id); - this.debug('Identifier of device updated'); + this.debug = debug("thing:miio:" + this.id); + this.debug("Identifier of device updated"); } - if(this.packet.token) { + if (this.packet.token) { resolve(); } else { - const err = new Error('Could not connect to device, token needs to be specified'); - err.code = 'missing-token'; + const err = new Error("Could not connect to device, token needs to be specified"); + err.code = "missing-token"; reject(err); } }; @@ -424,15 +424,15 @@ class DeviceInfo { this.handshakeTimeout = null; this.handshakePromise = null; - const err = new Error('Could not connect to device, handshake timeout'); - err.code = 'timeout'; + const err = new Error("Could not connect to device, handshake timeout"); + err.code = "timeout"; reject(err); }, 2000); - }); + })); } call(method, args, options) { - if(typeof args === 'undefined') { + if (typeof args === "undefined") { args = []; } @@ -441,7 +441,7 @@ class DeviceInfo { params: args }; - if(options && options.sid) { + if (options && options.sid) { // If we have a sub-device set it (used by Lumi Smart Home Gateway) request.sid = options.sid; } @@ -461,12 +461,12 @@ class DeviceInfo { resolved = true; this.promises.delete(request.id); - if(! (err instanceof Error) && typeof err.code !== 'undefined') { + if (!(err instanceof Error) && typeof err.code !== "undefined") { const code = err.code; const handler = ERRORS[code]; let msg; - if(handler) { + if (handler) { msg = handler(method, args, err.message); } else { msg = err.message || err.toString(); @@ -481,22 +481,22 @@ class DeviceInfo { let retriesLeft = (options && options.retries) || 5; const retry = () => { - if(retriesLeft-- > 0) { + if (retriesLeft-- > 0) { send(); } else { - const err = new Error('Call to device timed out'); - err.code = 'timeout'; + const err = new Error("Call to device timed out"); + err.code = "timeout"; promise.reject(err); } }; const send = () => { - if(resolved) return; + if (resolved) return; this.handshake() .catch(err => { - if(err.code === 'timeout') { - this.debug('<- Handshake timed out'); + if (err.code === "timeout") { + this.debug("<- Handshake timed out"); retry(); return false; } else { @@ -505,11 +505,11 @@ class DeviceInfo { }) .then(token => { // Token has timed out - handled via retry - if(! token) return; + if (!token) return; // Assign the identifier before each send let id; - if(request.id) { + if (request.id) { /* * This is a failure, increase the last id. Should * increase the chances of the new request to @@ -525,7 +525,7 @@ class DeviceInfo { } // Check that the id hasn't rolled over - if(id >= 10000) { + if (id >= 10000) { this.lastId = id = 1; } else { this.lastId = id; @@ -539,8 +539,8 @@ class DeviceInfo { // Create the JSON and send it const json = JSON.stringify(request); - this.debug('-> (' + retriesLeft + ')', json); - this.packet.data = Buffer.from(json, 'utf8'); + this.debug("-> (" + retriesLeft + ")", json); + this.packet.data = Buffer.from(json, "utf8"); const data = this.packet.raw; diff --git a/node_modules/miio/lib/packet.js b/node_modules/miio/lib/packet.js index d0e2f51..ccbfd55 100644 --- a/node_modules/miio/lib/packet.js +++ b/node_modules/miio/lib/packet.js @@ -1,7 +1,7 @@ -'use strict'; +"use strict"; -const crypto = require('crypto'); -const debug = require('debug')('miio:packet'); +const crypto = require("crypto"); +const debug = require("debug")("miio:packet"); class Packet { constructor(discovery = false) { @@ -11,7 +11,7 @@ class Packet { this.header[0] = 0x21; this.header[1] = 0x31; - for(let i=4; i<32; i++) { + for (let i = 4; i < 32; i++) { this.header[i] = 0xff; } @@ -24,9 +24,9 @@ class Packet { } handleHandshakeReply() { - if(this._token === null) { + if (this._token === null) { const token = this.checksum; - if(token.toString('hex').match(/^[fF0]+$/)) { + if (token.toString("hex").match(/^[fF0]+$/)) { // Device did not return its token so we set our token to null this._token = null; } else { @@ -41,65 +41,63 @@ class Packet { * 1) do not have a token * 2) it has been longer then 120 seconds since last received message */ - return ! this._token || (Date.now() - this._serverStampTime) > 120000; + return !this._token || Date.now() - this._serverStampTime > 120000; } get raw() { - if(this.data) { + if (this.data) { // Send a command to the device - if(! this._token) { - throw new Error('Token is required to send commands'); + if (!this._token) { + throw new Error("Token is required to send commands"); } - for(let i=4; i<8; i++) { + for (let i = 4; i < 8; i++) { this.header[i] = 0x00; } // Update the stamp to match server - if(this._serverStampTime) { + if (this._serverStampTime) { const secondsPassed = Math.floor(Date.now() - this._serverStampTime) / 1000; this.header.writeUInt32BE(this._serverStamp + secondsPassed, 12); } // Encrypt the data - let cipher = crypto.createCipheriv('aes-128-cbc', this._tokenKey, this._tokenIV); - let encrypted = Buffer.concat([ - cipher.update(this.data), - cipher.final() - ]); + let cipher = crypto.createCipheriv("aes-128-cbc", this._tokenKey, this._tokenIV); + let encrypted = Buffer.concat([cipher.update(this.data), cipher.final()]); // Set the length this.header.writeUInt16BE(32 + encrypted.length, 2); // Calculate the checksum - let digest = crypto.createHash('md5') + let digest = crypto + .createHash("md5") .update(this.header.slice(0, 16)) .update(this._token) .update(encrypted) .digest(); digest.copy(this.header, 16); - debug('->', this.header); - return Buffer.concat([ this.header, encrypted ]); + debug("->", this.header); + return Buffer.concat([this.header, encrypted]); } else { // Handshake this.header.writeUInt16BE(32, 2); - for(let i=4; i<32; i++) { + for (let i = 4; i < 32; i++) { this.header[i] = 0xff; } - debug('->', this.header); + debug("->", this.header); return this.header; } } set raw(msg) { msg.copy(this.header, 0, 0, 32); - debug('<-', this.header); + debug("<-", this.header); const stamp = this.stamp; - if(stamp > 0) { + if (stamp > 0) { // If the device returned a stamp, store it this._serverStamp = this.stamp; this._serverStampTime = Date.now(); @@ -107,34 +105,32 @@ class Packet { const encrypted = msg.slice(32); - if(this.discovery) { + if (this.discovery) { // This packet is only intended to be used for discovery this.data = encrypted.length > 0; } else { // Normal packet, decrypt data - if(encrypted.length > 0) { - if(! this._token) { - debug('<- No token set, unable to handle packet'); + if (encrypted.length > 0) { + if (!this._token) { + debug("<- No token set, unable to handle packet"); this.data = null; return; } - const digest = crypto.createHash('md5') + const digest = crypto + .createHash("md5") .update(this.header.slice(0, 16)) .update(this._token) .update(encrypted) .digest(); const checksum = this.checksum; - if(! checksum.equals(digest)) { - debug('<- Invalid packet, checksum was', checksum, 'should be', digest); + if (!checksum.equals(digest)) { + debug("<- Invalid packet, checksum was", checksum, "should be", digest); this.data = null; } else { - let decipher = crypto.createDecipheriv('aes-128-cbc', this._tokenKey, this._tokenIV); - this.data = Buffer.concat([ - decipher.update(encrypted), - decipher.final() - ]); + let decipher = crypto.createDecipheriv("aes-128-cbc", this._tokenKey, this._tokenIV); + this.data = Buffer.concat([decipher.update(encrypted), decipher.final()]); } } else { this.data = null; @@ -148,8 +144,15 @@ class Packet { set token(t) { this._token = Buffer.from(t); - this._tokenKey = crypto.createHash('md5').update(t).digest(); - this._tokenIV = crypto.createHash('md5').update(this._tokenKey).update(t).digest(); + this._tokenKey = crypto + .createHash("md5") + .update(t) + .digest(); + this._tokenIV = crypto + .createHash("md5") + .update(this._tokenKey) + .update(t) + .digest(); } get checksum() { diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..d312bbe --- /dev/null +++ b/readme.txt @@ -0,0 +1,334 @@ +# Mi Homey + +Added support for Mi Gateways child devices. + +## Version 1.0.7 - Supported devices: + +### Zigbee subdevices Xiaomi Gateway + +- Double Button Wirelles Remote Switch (DuplexButton86). +- Motion Sensor 1 version (MotionSensor). +- Leak Sensor (WaterDetector). +- Plug zigbee (PlugBase). +- Contact Sensor (MagnetSensor). +- Button Switch (Switch). +- Mi Temperature and Humidity Sensor (Sensor_HT). +- Aqara Smart Light Switch With Neutral (SingleSwitchLN). +- Aqara Wirelles Single Switch (SingleButton86). +- Aqara Wall Outlet (PlugBase86). +- Aqara Smart Light Switch With Neutral (DoubleSwitchLN). +- Aqara Smart Light Switch (DoubleSwitch). +- Aqara Smart Light Switch (SingleSwitch). +- Aqara Temperature and Humidity Sensor (WeatherSensor). +- Aqara Door and Window Sensor (MagnetSensor2). +- Aqara Motion Sensor (MotionSensor2). +- Mi / Aqara Cube (Cube). +- Aqara Curtain Motor / B1 (Curtain). +- Aqara Button Switch (SwitchSensor). +- Mijia Gateway (Gateway). +- MiJia Gas Leak Detector (NatgasDetector). +- MiJia Smoke Detector (SmokeDetector). +- Aqara Wireless Remote Switch Double Button (DuplexButton862) (advanced 2018) +- Aqara Button Switch (SwitchSensor) (advanced 2018). +- Aqara Lock (Lock). +- Aqara Vibration sensor (Vibration). +- Aqara Roller Shade Controller (Curtain). + +### Wifi devices + +- Yeelight Color Bulb V1/V2. +- Yeelight White Bulb. +- Yeelight Ceiling Lamp. +- Yeelight Bedside Lamp. +- Yeelight Light Strip V1/V2. +- Yeelight Jiaoyue 650. +- Yeelight Jiaoyue 450. +- Yeelight Crystal Pendant Lamp +- Mi LED Desk Lamp. +- Philips Eyecare Desk Lapmp 2. +- Philips Eyecare Ceiling Lamp. +- Philips Light Bulb. +- Philips Zhirui Smart LED Bulb E14 Candle Lamp. +- Philips Zhirui Smart LED Bulb E14 Candle Lamp White Crystal. +- Philips Downlight. +- Philips Zhirui Bedside Lamp. +- Mi Smart Plug WiFi. +- Mi Smart Plug With USB WiFi. +- Mi Smart Plug With 2 USB WiFi. +- Mi Smart Power Strip. +- CHINGMI Smart Power Strip. +- Mi Air Purifier S2. +- Mi Air Purifier Pro. +- Mi Air Fresh VA2. +- Mi Humidifier V1. +- Mi Humidifier V2. +- Mi Vacuum Cleaner (gen 1). +- Mi Vacuum Cleaner V2 (gen 2). +- Mi Air Quality Monitor (gen 2). +- Mi Vacuum Cleaner 1S. +- Mi Air Purifier (MJXFJ-300-G1). +- Mi Roborock Vacuum Cleaner T6. + +![](https://raw.githubusercontent.com/Maxmudjon/images/master/DuplexButton86.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiMotionSensor.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/LeakSensor.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiPlugZigbee.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiContactSensor.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiButtonSwtich.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiTempHumSensor.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraWallSwitchWithNeutralSingleRocker.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/SingleButton86.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraWallSmartSocketZiGBee.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraWallSwitchWithNeutralDoubleRocker2.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraWallSwitchDoubleRocker.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraWallSwitchSingleRocker.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraWeather.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraDoorAndWindow.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraMotion.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiCube.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraCurtainMotor.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraButtonSwitch.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MijiaGateway.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/XioamiMiJiaNatGasDetector.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/XiaomiMijiaSmokeDetectorH.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/DuplexButton86-2018.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraButtonSwitchWithShake.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraLock.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/AqaraVibration.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/YeelightColorBulb.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/YeelightWhiteBulb.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/YeelightCeilingLamp.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/PhilipsEyeCareDeskLamp2.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiLEDDeskLamp.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/PhilipsEyeCareCeilingLamp.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/YeelightBedsideLamp.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/YeelightLightStrip.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/PhilipsLightBulb.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiSmartPlugWiFi.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiSmartPlugWithUSBWiFi.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiSmartPlugWith2USBWiFi.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiSmartPowerStrip.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/CHINGMISmartPowerStrip.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/YeelightJiaoyue650.jpg) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiAirPurifierS2.jpg) +![]() +![]() +![](https://raw.githubusercontent.com/Maxmudjon/images/master/PhilipsZhiruiE14Candle.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/PhilipsZhiruiE14CandleCrystal.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/PhilipsDownlight.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/PhilipsZhiruiBedsideLamp.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiAirPurifierPro.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiHumidiferV2.png) +![](https://raw.githubusercontent.com/Maxmudjon/images/master/MiHumidiferV1.png) + +## Version logs + +### 1.0.7 (23.11.2019) + +1. minor fixes. +2. added Mi IR devices (beta). +3. 1.5 version of Homey firmware is no longer supported. + +### 1.0.6 (10.11.2019) + +1. added Mi Roborock Vacuum Cleaner T6. + +### 1.0.5 (31.10.2019) + +1. fix for Vacuum Cleaners. + +### 1.0.4 (28.10.2019) + +1. Mi Air Purifier (MJXFJ-300-G1). + +### 1.0.3 (20.10.2019) + +1. added Mi Vacuum Cleaner 1S. + +### 1.0.2 (15.10.2019) + +1. added Aqara Roller Shade Controller (Curtain). + +### 1.0.1 (13.10.2019) + +1. added Mi Air Quality Monitor (gen 2). +2. added action, conditions for Yeelight Jiaoyue 650. +3. added smal features and fixes. + +### 1.0.0 (29.09.2019) + +1. minor fixes and publish to Athom App Store. +2. added Xiaomi Air Fresh VA2. +3. added Yeelight Crystal Pendant Lamp (cometa). + +### 0.4.0 (07.06.2019) + +1. Added a new feature for android users: the password that is generated in the plugin in capital letters is now saved to the gateway. + +### 0.3.9 (30.05.2019) + +1. Added support Aqara Curtain with battaery (Zigbee version). +2. fixed CHINGMI Smart Power Strip capabilities. + +### 0.3.8 (22.03.2019) + +1. Minor fixes. +2. fixed Settings page. +3. added support for Yeelight Jiaoyue 450. +4. added new functions for Xiaomi Vacuum Cleaners. +5. added new trigger for Aqara Wireless Remote Switch Double Button (DuplexButton862) (advanced 2018). + +### 0.3.7 (23.01.2019) + +1. added support for Mi Air Purifier Pro. +2. added support for Mi Humidifier V1. +3. added support for Mi Humidifier V2. +4. fixed: Yeelight set flows. +5. minor fixes. + +### 0.3.6 (23.12.2018) + +1. fixed: app settings page. +2. fixed: flow triggers. +3. fixed: miio devices pair page. +4. added support for Yeelight Jiaoyue 650. +5. added support for Mi Air Purifier S2. +6. added support for Mi Vacuum Cleaner (gen 1). +7. added support for Mi Vacuum Cleaner V2 (gen 2). +8. added support for Philips Zhirui Smart LED Bulb E14 Candle Lamp. +9. added support for Philips Zhirui Smart LED Bulb E14 Candle Lamp White Crystal. +10. added support for Philips Downlight. +11. added support for Philips Zhirui Bedside Lamp. +12. minor fixes. + +### 0.3.5 (07.12.2018) + +1. added support for Mi LED Desk Lamp. +2. added support for Philips EyeCare Ceiling Lamp. +3. added support for Yeelight Bedside Lamp. +4. added support for Yeelight Light Strip. +5. added support for Philips Light Bulb. +6. added support for Mi Smart Plug WiFi. +7. added support for Mi Smart Plug With USB WiFi. +8. added support for Mi Smart Plug With 2 USB WiFi. +9. added support for Mi Smart Power Strip. +10. added support for CHINGMI Smart Power Strip. +11. minor fixes. + +### 0.3.4 (27.11.2018) + +1. added support for Yeelight Color Bulb. +2. added support for Yeelight White Bulb. +3. added support for Yeelight Ceiling Lamp. +4. added support for Philips Eyecare Desk Lamp 2. +5. fixed: triggers for Aqara Duplex Switch. +6. fixed: Mi/Aqara motion sensor update capability. + +### 0.3.3 (25.11.2018) + +1. added support for Xioami Gateway Security. +2. added support for Aqara Vibration Sensor. +3. minor fixes. +4. added device list soon. + +### 0.3.2 (17.11.2018) + +1. added function for add or remove favorite radio list to gateway. + +### 0.3.1 (16.11.2018) + +1. added gateway radio. +2. minor fixes. +3. added module miio. + +### 0.3.0 (14.11.2018) + +1. rewrited mimorelinks module. +2. added support for Aqara Button Switch (advanced). +3. added support for Aqara Lock. + +### 0.2.9 (8.11.2018) + +1. added support for Aqara Wireless Remote Switch Double Button (advanced 2018) + +### 0.2.8 (23.10.2017) + +1. added support for MiJia Gas Leak Detector. +2. added support for MiJia Smoke Detector. +3. added gateway tones. + +### 0.2.7 (13.10.2018) + +1. added Settings for Gateway devices. +2. fixed: triggers for Aqara Smart Light Switch With Neutral (DoubleSwitchLN) and Aqara Smart Light Switch (DoubleSwitch). + +### 0.2.6 (20.09.2018) + +1. fixed: Flow Action for Aqara Smart Light Switch(DoubleSwitch). +2. fixed: on/off for Gateway. +3. minor fixes and improvements. +4. Added an instruction on how to get mac and password from the Mihome application in the settings. + +### 0.2.5 (19.09.2018) + +1. fixed: Aqara Curtain control. +2. added support for Gateway. + +### 0.2.4 (17.09.2018) + +1. added support for Aqara Curtain Motor. +2. added support for Aqara Button Switch. + +### 0.2.3_beta (17.09.2018) + +1. added filter for Mi and Aqara devices. +2. fixed: flow for some devices. + +### 0.2.2_beta (16.09.2018) + +1. fixed: flow for some devices. +2. added support for Aqara Temperature and Humidity Sensor. +3. added support for Aqara Door and Window Sensor. +4. added support for Aqara Motion Sensor. +5. added support for Mi Cube. + +### 0.2.1_beta (12.09.2018) + +1. added settings for motion sensor and information for all devices +2. added support for Contact Sensor. +3. added support for Button Switch. +4. added support for Mi Temperature and Humidity Sensor. +5. added support for Aqara Wall Switch With Neutral Single Rocker. +6. added support for Aqara Wirelles Single Switch. +7. added support for Aqara Wall Outlet. +8. added support for Aqara Wall Switch With Neutral Double Rocker. +9. added support for Aqara Wall Switch Single Rocker +10. added support for Aqara Wall Switch Double Rocker. + +### 0.2.0_beta (10.09.2018) + +1. fixed: device updates by event. +2. added support for Plug zigbee. +3. minor fixes and improvements. +4. fixed: leakage sensor displays status incorrectly. +5. fixed: sometimes Double Button Wirelles Remote Switch was not updated. + +### 0.1.1_beta (8.09.2018) + +1. added support for Motion Sensor (1 version). +2. added support for Leak Sensor. +3. minor fixes. + +### 0.1.0_beta (8.09.2018) + +1. fixed: crash when the user did not add anything in the settings in the program. +2. fixed: when the user added a SID and a TOKEN but he had to reboot the application in the application section. +3. added mimorelinks node_module and rewrited mihub.js. +4. added event for ManagerSettings. + +### 0.0.1_alpha (5.09.2018) + +1. added support for DuplexButton86. +2. added trigger for left, right and both clicks. +3. added Mobile. +4. added support more gateways.