From 77159668e27eb6fd2cd74be4369ac33eb76afcc5 Mon Sep 17 00:00:00 2001 From: jghaanstra Date: Wed, 17 Jan 2018 21:04:51 +0100 Subject: [PATCH] Release 2.4.2 --- APPSTORE.md | 21 +-- app.js | 12 ++ app.json | 156 +++++++++++++++++++--- drivers/yeelights/device.js | 249 ++++++++++++++++++++---------------- drivers/yeelights/driver.js | 3 - lib/util.js | 5 + lib/yeelight.js | 3 +- package.json | 2 +- 8 files changed, 301 insertions(+), 150 deletions(-) diff --git a/APPSTORE.md b/APPSTORE.md index 7ea4110..34dae26 100644 --- a/APPSTORE.md +++ b/APPSTORE.md @@ -4,7 +4,7 @@ Use Homey to control Xiaomi Mi Home devices (the Mi Home Ecosystem is also brand This app uses an unofficial library called the [miIO Device Library](https://github.com/aholstenson/miio) for communication with those devices which lack official support for controlling externally, credits go out to the author of this library. This Homey app also only adds support for the devices that can be controlled directly through Wi-Fi, there is a whole range of Mi Home sensors that can only be used together with the Xiaomi Smart Home Gateway (which uses ZigBee) but this is currently out the scope of this app. ## Supported devices -Below is a list of supported devices and devices that might be supported in the future if there is demand for this. Post a comment in the app store if you would like to see support for a specific device. +Below is a list of supported devices and devices that might be supported in the future if there is demand for this. Post a comment in the app store if you would like to see support for a specific device, some devices might already be supported but just need to be implemented. For other devices you may need some technical knowledge to discover the device properties yourself as described [here](https://github.com/aholstenson/miio/blob/master/docs/missing-devices.md). * Yeelights: Bulbs Wi-Fi (tested), LED strips (tested), Bedside Lamp II (tested), Ceiling Lights (tested) * Xiaomi Philips: Light Bulbs (untested), Eyecare Lamp 2 (untested) * Xiaomi Air Purifiers 2 and Pro (tested) @@ -45,6 +45,11 @@ For Homey to be able to communicate with devices over the miIO protocol a unique * Default flow cards for on/off, measure power and meter power capabilities class ## Changelog +### 2018-01-17 -- v2.4.2 +* FIX: some improvements for the Yeelights which will hopefully fix devices as appearing offline in Homey +* UPDATE: added favorite mode as option for Air Purifiers +* UPDATE: added card to set the speed of the favorite mode for Air Purifiers + ### 2018-01-07 -- v2.4.1 * FIX: some tweaks for the Yeelight JIAOYUE 650 Ceiling Light as it has seperate white (main lamp) and color (background color ring) capabilities * FIX: probable fix for temperature setting on Philips Light Bulbs @@ -54,17 +59,3 @@ For Homey to be able to communicate with devices over the miIO protocol a unique * NEW: added support for the Philips Xiaomi Light Bulbs (untested) * NEW: added support for the Philips Xiaomi Eyecare Lamp 2 (untested) * NEW: added support for the Xiaomi Single Power Plug (untested) - -### 2017-11-30 -- v2.3.2 -* FIX: added a keep-alive to Yeelight connections to maintain the connection over time -* FIX: fixed pairing wizard for Humidifier and Purifier - -### 2017-11-06 -- v2.3.1 -* UPDATE: code rewrite for SDK2 -* UPDATE: made use of official donation button feature of Homey app store -* REMOVED: driver for the Mi Robot Vacuum Cleaner since it's not working anymore with the latest firmware update of the cleaner (if you still want to use/try it you can do a local install from an archive [beta branch on GitHub](https://github.com/jghaanstra/com.xiaomi-miio/releases/tag/2.3.1-beta) ) -* NEW: changed Yeelight driver to support Yeelight LED strip (require repairing of **ALL** Yeelight devices) -* NEW: added extra action cards for Yeelights: Change brightness over time, Custom command (advanced), Set default on state, Temperature/brightness scene, Color/brightness scene -* NEW: added support for the default dim over time action card for Yeelights -* NEW: added support for Yeelight Bedside Lamp II (WiFi) and Yeelight Ceiling Light -* IMPROVEMENT: switched from custom capability for air quality to the new default pm2.5 capability for the Air Purifier (requires repairing of the air purifier) diff --git a/app.js b/app.js index fa7bda9..cde0f55 100644 --- a/app.js +++ b/app.js @@ -72,6 +72,18 @@ class XiaomiMiioApp extends Homey.App { }) }) + new Homey.FlowCardAction('airpurifierSetFavorite') + .register() + .registerRunListener((args, state) => { + util.sendCommand('setfavorite', args.favorite, args.device.getSetting('address'), args.device.getSetting('token')) + .then(result => { + return Promise.resolve(result); + }) + .catch(error => { + return Promise.reject(error); + }) + }) + new Homey.FlowCardAction('airpurifierOn') .register() .registerRunListener((args, state) => { diff --git a/app.json b/app.json index 8b4eb26..ca57c68 100644 --- a/app.json +++ b/app.json @@ -9,7 +9,7 @@ "en": [ "Xiaomi", "Mi", "Mi Home", "miio", "yeelight", "yeelights", "purifier", "humidifier", "philips", "eyecare", "powerplug" ], "nl": [ "Xiaomi", "Mi", "Mi home", "miio", "yeelight", "yeelights", "luchtreiniger", "luchtbevochtiger", "philips", "eyecare", "powerplug" ] }, - "version": "2.4.1", + "version": "2.4.2", "compatibility": "1.x >=1.5.0", "author": { "name": "Jelger Haanstra", @@ -519,8 +519,8 @@ "nl": "Wijzig helderheid over tijd" }, "hint": { - "en": "Use this card to create a changing effect of temperature and brightness over time (duration in milliseconds, temperature in Kelvin 2700-6500 and brightness in percentage)", - "nl": "Gebruik deze actie voor een veranderd effect van licht temperatuur en helderheid over tijd (doorlooptijd in milliseconden, temperatuur in Kelvin 2700-6500 en helderheid in percentage)" + "en": "Use this card to create a changing effect of temperature and brightness over time (duration in milliseconds, temperature in Kelvin 1700-6500 and brightness in percentage)", + "nl": "Gebruik deze actie voor een veranderd effect van licht temperatuur en helderheid over tijd (doorlooptijd in milliseconden, temperatuur in Kelvin 1700-6500 en helderheid in percentage)" }, "args": [ { @@ -571,7 +571,7 @@ "en": "Temperature", "nl": "Temperatuur" }, - "min": 2700, + "min": 1700, "max": 6500, "step": 1 }, @@ -604,8 +604,8 @@ "nl": "Temperatuur en helderheid" }, "hint": { - "en": "Use this card to set the temperature and brightness directly (temperature in Kelvin 2700-6500 and brightness in percentage)", - "nl": "Gebruik deze actie om de temperatuur en helderheid direct in te stellen (temperatuur in Kelvin 2700-6500 en helderheid in percentage)" + "en": "Use this card to set the temperature and brightness directly (temperature in Kelvin 1700-6500 and brightness in percentage)", + "nl": "Gebruik deze actie om de temperatuur en helderheid direct in te stellen (temperatuur in Kelvin 1700-6500 en helderheid in percentage)" }, "args": [ { @@ -615,7 +615,7 @@ "en": "Temperature", "nl": "Temperatuur" }, - "min": 2700, + "min": 1700, "max": 6500, "step": 1 }, @@ -777,24 +777,146 @@ } }, { - "id": "low", + "id": "favorite", "label": { - "en": "Low", - "nl": "Laag" + "en": "Favorite", + "nl": "Favorite" + } + } + ] + }, + { + "name": "device", + "type": "device", + "placeholder": { + "en": "Select Mi Air Purifier", + "nl": "Selecteer Mi Luchtreiniger" + }, + "filter": "driver_id=mi-airpurifier" + } + ] + }, + { + "id": "airpurifierSetFavorite", + "title": { + "en": "Set Favorite Speed", + "nl": "Favoriete snelheid instellen" + }, + "args": [ + { + "name": "favorite", + "type": "dropdown", + "values": [ + { + "id": "1", + "label": { + "en": "1", + "nl": "1" } }, { - "id": "medium", + "id": "2", "label": { - "en": "Medium", - "nl": "Middel" + "en": "2", + "nl": "2" } }, { - "id": "high", + "id": "3", "label": { - "en": "High", - "nl": "Hoog" + "en": "3", + "nl": "3" + } + }, + { + "id": "4", + "label": { + "en": "4", + "nl": "4" + } + }, + { + "id": "5", + "label": { + "en": "5", + "nl": "5" + } + }, + { + "id": "6", + "label": { + "en": "6", + "nl": "6" + } + }, + { + "id": "7", + "label": { + "en": "7", + "nl": "7" + } + }, + { + "id": "8", + "label": { + "en": "8", + "nl": "8" + } + }, + { + "id": "9", + "label": { + "en": "9", + "nl": "9" + } + }, + { + "id": "10", + "label": { + "en": "10", + "nl": "10" + } + }, + { + "id": "11", + "label": { + "en": "11", + "nl": "11" + } + }, + { + "id": "12", + "label": { + "en": "12", + "nl": "12" + } + }, + { + "id": "13", + "label": { + "en": "13", + "nl": "13" + } + }, + { + "id": "14", + "label": { + "en": "14", + "nl": "14" + } + }, + { + "id": "15", + "label": { + "en": "15", + "nl": "15" + } + }, + { + "id": "16", + "label": { + "en": "16", + "nl": "16" } } ] @@ -807,7 +929,7 @@ "nl": "Selecteer Mi Luchtreiniger" }, "filter": "driver_id=mi-airpurifier" - } + } ] }, { diff --git a/drivers/yeelights/device.js b/drivers/yeelights/device.js index 06975b1..b69d652 100644 --- a/drivers/yeelights/device.js +++ b/drivers/yeelights/device.js @@ -14,12 +14,14 @@ class YeelightDevice extends Homey.Device { this.registerMultipleCapabilityListener(['light_hue', 'light_saturation'], this.onCapabilityHueSaturation.bind(this), 500); this.registerCapabilityListener('light_temperature', this.onCapabilityLightTemperature.bind(this)); - this.setStoreValue('connected', false); let id = this.getData().id; yeelights[id] = {}; yeelights[id].data = this.getData(); yeelights[id].socket = null; yeelights[id].timeout = null; + yeelights[id].reconnect = null; + yeelights[id].connecting = false; + yeelights[id].connected = false; this.createDeviceSocket(id); } @@ -31,6 +33,9 @@ class YeelightDevice extends Homey.Device { yeelights[id].data = this.getData(); yeelights[id].socket = null; yeelights[id].timeout = null; + yeelights[id].reconnect = null; + yeelights[id].connecting = false; + yeelights[id].connected = false; this.createDeviceSocket(id); } @@ -106,17 +111,23 @@ class YeelightDevice extends Homey.Device { let device = Homey.ManagerDrivers.getDriver('yeelights').getDevice(yeelights[id].data); try { - if (yeelights[id].socket === null) { - yeelights[id].socket = new net.Socket(); - yeelights[id].socket.connect(yeelights[id].data.port, yeelights[id].data.address); - yeelights[id].socket.setKeepAlive(true, 60000); + if (yeelights[id].socket === null && yeelights[id].connecting === false && yeelights[id].connected === false) { + yeelights[id].connecting = true; + yeelights[id].socket = new net.Socket(); + yeelights[id].socket.connect(yeelights[id].data.port, yeelights[id].data.address, function() { + yeelights[id].socket.setKeepAlive(true, 10000); + yeelights[id].socket.setTimeout(0); + }); + } else { + this.log("Yeelight - trying to create socket, but connection not cleaned up previously."); } } catch (error) { - console.log("Yeelight: error creating socket " + error); + this.log("Yeelight - error creating socket: " + error); } yeelights[id].socket.on('connect', () => { - device.setStoreValue('connected', true); + yeelights[id].connecting = false; + yeelights[id].connected = true; device.setAvailable(); setTimeout(() => { @@ -127,146 +138,160 @@ class YeelightDevice extends Homey.Device { }); yeelights[id].socket.on('error', (error) => { - yeelights[id].socket.destroy(); - yeelights[id].socket.unref(); - console.log("Yeelight: error on socket "+ error); - }); + this.log("Yeelight - error on socket: "+ error); - yeelights[id].socket.on('timeout', () => { yeelights[id].socket.destroy(); - yeelights[id].socket.unref(); - console.log("Yeelight: timeout on socket"); + + if(error.code === 'ETIMEDOUT' || error.code === 'ECONNRESET' || error == 'Error: Error sending command') { + this.log('Yeelight - trying to reconnect in 6 seconds.'); + + if (yeelights[id].reconnect === null) { + yeelights[id].reconnect = setTimeout(() => { + if (yeelights[id].connected === false && yeelights[id].connected === false) { + this.createDeviceSocket(id); + } + yeelights[id].reconnect = null; + }, 6000); + } + } }); - yeelights[id].socket.on('close', (error) => { - device.setUnavailable(Homey.__('unreachable')); + yeelights[id].socket.on('close', (had_error) => { + yeelights[id].connecting = false; + yeelights[id].connected = false; yeelights[id].socket = null; - device.setStoreValue('connected', false); + device.setUnavailable(Homey.__('unreachable')); }); - process.nextTick(() => { - yeelights[id].socket.on('data', (message, address) => { - clearTimeout(yeelights[id].timeout); - yeelights[id].timeout = null; - - var result = message.toString(); - var result = result.replace(/{"id":1, "result":\["ok"\]}/g, "").replace(/\r\n/g,''); - - if (result.includes('props')) { - try { - var result = JSON.parse(result); - var key = Object.keys(result.params)[0]; - - switch (key) { - case 'power': - if(result.params.power == 'on') { - device.setCapabilityValue('onoff', true); - } else { - device.setCapabilityValue('onoff', false); - } - break; - case 'bright': - var dim = result.params.bright / 100; - device.setCapabilityValue('dim', dim); - break; - case 'ct': - if (device.getData().model == 'ceiling4') { - var color_temp = yeelight.normalize(result.params.ct, 2700, 6000); - } else { - var color_temp = yeelight.normalize(result.params.ct, 1700, 6500); - } - device.setCapabilityValue('light_temperature', color_temp); - break; - case 'rgb': - var color = tinycolor(result.params.rgb.toString(16)); - var hsv = color.toHsv(); - var hue = Math.round(hsv.h) / 359; - var saturation = Math.round(hsv.s); - device.setCapabilityValue('light_hue', hue); - device.setCapabilityValue('light_saturation', saturation); - break; - case 'hue': - var hue = result.params.hue / 359; - device.setCapabilityValue('light_hue', hue); - break; - case 'sat': - var saturation = result.params.sat / 100; - device.setCapabilityValue('light_saturation', saturation); - break; - case 'color_mode': - if (result.params.color_mode == 2) { - device.setCapabilityValue('light_mode', 'temperature'); - } else { - device.setCapabilityValue('light_mode', 'color'); - } - break; - default: - break; - } + yeelights[id].socket.on('data', (message, address) => { + clearTimeout(yeelights[id].timeout); + clearTimeout(yeelights[id].reconnect); + yeelights[id].timeout = null; + yeelights[id].reconnect = null; - } catch (error) { - this.log('Unable to process message because of error: '+ error); - } - } else if (result.includes('result')) { - try { - var result = JSON.parse(result); + if(!device.getAvailable()) { + device.setAvailable(); + } - if (result.result[0] != "ok") { + var result = message.toString(); + var result = result.replace(/{"id":1, "result":\["ok"\]}/g, "").replace(/\r\n/g,''); - var dim = result.result[1] / 100; - var hue = result.result[5] / 359; - var saturation = result.result[6] / 100; - if (device.getData().model == 'ceiling4') { - var color_temp = yeelight.normalize(result.result[3], 2700, 6000); - } else { - var color_temp = yeelight.normalize(result.result[3], 1700, 6500); - } - if(result.result[2] == 2) { - var color_mode = 'temperature'; - } else { - var color_mode = 'color'; - } + if (result.includes('props')) { + try { + var result = JSON.parse(result); + var key = Object.keys(result.params)[0]; - if(result.result[0] == 'on') { + switch (key) { + case 'power': + if(result.params.power == 'on') { device.setCapabilityValue('onoff', true); } else { device.setCapabilityValue('onoff', false); } + break; + case 'bright': + var dim = result.params.bright / 100; device.setCapabilityValue('dim', dim); - device.setCapabilityValue('light_mode', color_mode); + break; + case 'ct': + if (device.getData().model == 'ceiling4') { + var color_temp = yeelight.normalize(result.params.ct, 2700, 6000); + } else { + var color_temp = yeelight.normalize(result.params.ct, 1700, 6500); + } device.setCapabilityValue('light_temperature', color_temp); + break; + case 'rgb': + var color = tinycolor(result.params.rgb.toString(16)); + var hsv = color.toHsv(); + var hue = Math.round(hsv.h) / 359; + var saturation = Math.round(hsv.s); device.setCapabilityValue('light_hue', hue); device.setCapabilityValue('light_saturation', saturation); - } - } catch (error) { - this.log('Unable to process message because of error: '+ error); + break; + case 'hue': + var hue = result.params.hue / 359; + device.setCapabilityValue('light_hue', hue); + break; + case 'sat': + var saturation = result.params.sat / 100; + device.setCapabilityValue('light_saturation', saturation); + break; + case 'color_mode': + if (result.params.color_mode == 2) { + device.setCapabilityValue('light_mode', 'temperature'); + } else { + device.setCapabilityValue('light_mode', 'color'); + } + break; + default: + break; } + + } catch (error) { + this.log('Unable to process message because of error: '+ error); } + } else if (result.includes('result')) { + try { + var result = JSON.parse(result); + + if (result.result[0] != "ok") { + + var dim = result.result[1] / 100; + var hue = result.result[5] / 359; + var saturation = result.result[6] / 100; + if (device.getData().model == 'ceiling4') { + var color_temp = yeelight.normalize(result.result[3], 2700, 6000); + } else { + var color_temp = yeelight.normalize(result.result[3], 1700, 6500); + } + if(result.result[2] == 2) { + var color_mode = 'temperature'; + } else { + var color_mode = 'color'; + } - }); - }); + if(result.result[0] == 'on') { + device.setCapabilityValue('onoff', true); + } else { + device.setCapabilityValue('onoff', false); + } + device.setCapabilityValue('dim', dim); + device.setCapabilityValue('light_mode', color_mode); + device.setCapabilityValue('light_temperature', color_temp); + device.setCapabilityValue('light_hue', hue); + device.setCapabilityValue('light_saturation', saturation); + } + } catch (error) { + this.log('Unable to process message because of error: '+ error); + } + } + }); } /* send commands to devices using their socket connection */ sendCommand(id, command) { - if (yeelights[id].socket === null || this.getStoreValue('connected') === false) { - this.log('Connection to device broken'); - this.createDeviceSocket(id); + if (yeelights[id].connected === false) { + yeelights[id].socket.emit('error', new Error('Connection to device broken')); } else { yeelights[id].socket.write(command + '\r\n'); if (yeelights[id].timeout === null) { yeelights[id].timeout = setTimeout(() => { - if (yeelights[id].socket !== null) { - yeelights[id].socket.destroy(); - yeelights[id].socket.unref(); - } - this.log("Yeelight: error on sending command"); - }, 6000); + yeelights[id].socket.emit('error', new Error('Error sending command')); + }, 3000); } } } + /* check if device is connecting or connected */ + isConnected(id) { + if (yeelights[id].connecting === true || yeelights[id].connected === true) { + return true; + } else { + return false; + } + } } module.exports = YeelightDevice; diff --git a/drivers/yeelights/driver.js b/drivers/yeelights/driver.js index 5116c72..6e1938c 100644 --- a/drivers/yeelights/driver.js +++ b/drivers/yeelights/driver.js @@ -52,9 +52,6 @@ class YeelightDriver extends Homey.Driver { model: result[i].model }, capabilities: typeCapabilityMap[result[i].model], - store: { - connected: false - }, icon: typeIconMap[result[i].model] }); } diff --git a/lib/util.js b/lib/util.js index 3868190..a185650 100644 --- a/lib/util.js +++ b/lib/util.js @@ -224,6 +224,11 @@ exports.sendCommand = function (command, value, address, token) { .then(error => { return reject(error) }); } break; + case 'setfavorite': + device.setFavoriteLevel(value) + .then(result => { return resolve(true) }) + .then(error => { return reject(error) }); + break; /* LIGHTS */ case 'dim': device.setBrightness(value) diff --git a/lib/yeelight.js b/lib/yeelight.js index 01d517b..1386dfc 100644 --- a/lib/yeelight.js +++ b/lib/yeelight.js @@ -36,7 +36,7 @@ exports.listenUpdates = function () { if (result.message_type != 'discover') { var yeelights = Homey.ManagerDrivers.getDriver('yeelights').getDevices(); Object.keys(yeelights).forEach(function(key) { - if(yeelights[key].getData().id == result.device.id) { + if(yeelights[key].getData().id == result.device.id && !yeelights[key].isConnected(result.device.id)) { yeelights[key].createDeviceSocket(result.device.id); } }); @@ -45,7 +45,6 @@ exports.listenUpdates = function () { .catch(error => { console.log(error); }) - }); }); diff --git a/package.json b/package.json index 025f083..2430fab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.xiaomi-miio", - "version": "2.4.1", + "version": "2.4.2", "description": "Xiaomi Mi Home", "main": "app.js", "dependencies": {