From ffed9d9d5a6f7fe1f2224d0f50cc1e6fb122c993 Mon Sep 17 00:00:00 2001 From: jghaanstra Date: Mon, 25 Feb 2019 22:56:39 +0100 Subject: [PATCH] Release 2.11.2 --- APPSTORE.md | 6 +- app.json | 121 +------------- drivers/yeelights/assets/bslamp2.svg | 19 +++ drivers/yeelights/device.js | 240 +++++++++++++-------------- drivers/yeelights/driver.js | 31 +++- node_modules/miio/lib/network.js | 3 + package.json | 2 +- 7 files changed, 171 insertions(+), 251 deletions(-) create mode 100644 drivers/yeelights/assets/bslamp2.svg diff --git a/APPSTORE.md b/APPSTORE.md index bb471ce..46f8585 100644 --- a/APPSTORE.md +++ b/APPSTORE.md @@ -27,5 +27,7 @@ For Homey to be able to communicate with devices over the miIO protocol a unique Xiaomi has recently released an update for its v1 vacuum that enabled it for the zoned cleaning and goto function that was previously only available to the v2 vacuum. Using the action cards that utilize these functions are a bit challenging. If you want to use these cards please read the instructions [here](https://github.com/jghaanstra/com.xiaomi-miio/blob/master/docs/mirobot_zonecleanup.md). ## Changelog -### v2.11.1 - 2019-02-11 -* FIX: solve app crash related to philips light bulbs +### v2.11.2 - 2019-02-25 +* NEW: settings Yeelights to 0% dim will turn them off, also within the change dim over time card (except for the JIAOYUE ceilight which will toggle night mode when set to 0% twice in 5 seconds) +* IMPROVEMENT: changed callbacks to promises for Yeelight driver +* IMPROVEMENT: moved Yeelight settings from data to settings object to prevent issues when re-pairing without restarting the app diff --git a/app.json b/app.json index 7a7de37..2d41da2 100644 --- a/app.json +++ b/app.json @@ -10,7 +10,7 @@ "en": [ "Xiaomi", "Mi", "Mi Home", "miio", "vacuumcleaner", "robot", "yeelight", "yeelights", "purifier", "humidifier", "philips", "eyecare", "powerplug", "gateway" ], "nl": [ "Xiaomi", "Mi", "Mi home", "miio", "stofzuiger", "robot", "yeelight", "yeelights", "luchtreiniger", "luchtbevochtiger", "philips", "eyecare", "powerplug", "gateway" ] }, - "version": "2.11.1", + "version": "2.11.2", "compatibility": ">=2.0.0", "author": { "name": "Jelger Haanstra", @@ -189,26 +189,6 @@ "vacuumcleaner_state", "measure_battery" ], - "mobile": { - "components": [ - { - "id": "battery", - "capabilities": [ "measure_battery" ] - }, - { - "id": "icon", - "capabilities": [ "onoff" ] - }, - { - "id": "picker", - "capabilities": [ "vacuumcleaner_state" ] - }, - { - "id": "toggle", - "capabilities": [ "onoff" ] - } - ] - }, "pair": [ { "id": "start" @@ -274,26 +254,6 @@ "measure_humidity", "measure_pm25" ], - "mobile": { - "components": [ - { - "id": "icon", - "capabilities": [ "onoff" ] - }, - { - "id": "sensor", - "capabilities": [ - "measure_temperature", - "measure_humidity", - "measure_pm25" - ] - }, - { - "id": "toggle", - "capabilities": [ "onoff" ] - } - ] - }, "pair": [ { "id": "start" @@ -358,25 +318,6 @@ "measure_temperature", "measure_humidity" ], - "mobile": { - "components": [ - { - "id": "icon", - "capabilities": [ "onoff" ] - }, - { - "id": "sensor", - "capabilities": [ - "measure_temperature", - "measure_humidity" - ] - }, - { - "id": "toggle", - "capabilities": [ "onoff" ] - } - ] - }, "pair": [ { "id": "start" @@ -442,26 +383,6 @@ "measure_humidity", "measure_waterlevel" ], - "mobile": { - "components": [ - { - "id": "icon", - "capabilities": [ "onoff" ] - }, - { - "id": "sensor", - "capabilities": [ - "measure_temperature", - "measure_humidity", - "measure_waterlevel" - ] - }, - { - "id": "toggle", - "capabilities": [ "onoff" ] - } - ] - }, "pair": [ { "id": "start" @@ -526,22 +447,6 @@ "measure_battery", "measure_pm25" ], - "mobile": { - "components": [ - { - "id": "icon", - "capabilities": [ "onoff" ] - }, - { - "id": "sensor", - "capabilities": [ "measure_pm25" ] - }, - { - "id": "toggle", - "capabilities": [ "onoff" ] - } - ] - }, "pair": [ { "id": "start" @@ -735,30 +640,6 @@ "measure_luminance", "homealarm_state" ], - "mobile": { - "components": [ - { - "id": "icon", - "capabilities": [ "onoff" ] - }, - { - "id": "picker", - "capabilities": [ "homealarm_state" ] - }, - { - "id": "sensor", - "capabilities": [ "measure_luminance" ] - }, - { - "id": "slider", - "capabilities": [ "dim" ] - }, - { - "id": "color", - "capabilities": [ "light_hue", "light_saturation" ] - } - ] - }, "pair": [ { "id": "start" diff --git a/drivers/yeelights/assets/bslamp2.svg b/drivers/yeelights/assets/bslamp2.svg new file mode 100644 index 0000000..6b0b634 --- /dev/null +++ b/drivers/yeelights/assets/bslamp2.svg @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/drivers/yeelights/device.js b/drivers/yeelights/device.js index 8fe26c1..2883909 100644 --- a/drivers/yeelights/device.js +++ b/drivers/yeelights/device.js @@ -9,12 +9,6 @@ var yeelights = {}; class YeelightDevice extends Homey.Device { onInit() { - this.registerCapabilityListener('onoff', this.onCapabilityOnoff.bind(this)); - this.registerCapabilityListener('dim', this.onCapabilityDim.bind(this)); - this.registerMultipleCapabilityListener(['light_hue', 'light_saturation'], this.onCapabilityHueSaturation.bind(this), 500); - this.registerCapabilityListener('light_temperature', this.onCapabilityLightTemperature.bind(this)); - this.registerCapabilityListener('night_mode', this.onCapabilityNightMode.bind(this)); - let id = this.getData().id; yeelights[id] = {}; yeelights[id].data = this.getData(); @@ -24,124 +18,122 @@ class YeelightDevice extends Homey.Device { yeelights[id].connecting = false; yeelights[id].connected = false; - this.createDeviceSocket(id); - } + // MOVE SETTINGS TO SETTINGS OBJECT FROM 2.11.2 + if (!this.getSetting('address')) { + var address = this.getData().address; + var port = this.getData().port; + this.setSettings({address: address, port: port}); + } - onDeleted() { - let id = this.getData().id; - delete yeelights[id]; - } + this.createDeviceSocket(id); - // LISTENERS FOR UPDATING CAPABILITIES - onCapabilityOnoff(value, opts, callback) { - if (value) { - this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["on", "smooth", 500]}'); - } else { - this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["off", "smooth", 500]}'); - } - callback(null, value); - } + // LISTENERS FOR UPDATING CAPABILITIES + this.registerCapabilityListener('onoff', (value, opts) => { + if (value) { + return this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["on", "smooth", 500]}'); + } else { + return this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["off", "smooth", 500]}'); + } + }); - async onCapabilityDim(value, opts, callback) { - let brightness = value === 0 ? 1 : value * 100; - let overWriteDimVal; - - // Logic which will toggle between night_mode and normal_mode when brightness is set to 0 or 100 two times within 5 seconds - if (this.hasCapability('night_mode') && opts.duration === undefined) { - if (value === 0) { - if (this.dimMinTime + 5000 > Date.now()) { - const initialNightModeValue = this.getCapabilityValue('night_mode'); - await this.triggerCapabilityListener('night_mode', true); - // If we think we really toggled the night mode we will set the brightness of night mode to 100 - if (initialNightModeValue === false) { - value = 1; - overWriteDimVal = 1; - brightness = 100; + this.registerCapabilityListener('dim', async (value, opts) => { + let brightness = value === 0 ? 1 : value * 100; + // Logic which will toggle between night_mode and normal_mode when brightness is set to 0 or 100 two times within 5 seconds + if (this.hasCapability('night_mode') && opts.duration === undefined) { + if (value === 0) { + if (this.dimMinTime + 5000 > Date.now()) { + await this.triggerCapabilityListener('night_mode', true); + if (this.getCapabilityValue('night_mode') === false) { + brightness = 100; + } + this.dimMinTime = 0; + } else { + this.dimMinTime = Date.now(); } - this.dimMinTime = 0; - } else { - this.dimMinTime = Date.now(); - } - } else if (value === 1) { - if (this.dimMaxTime + 5000 > Date.now()) { - const initialNightModeValue = this.getCapabilityValue('night_mode'); - await this.triggerCapabilityListener('night_mode', false); - // If we think we really toggled the night mode we will set the brightness of normal mode to 1 - if (initialNightModeValue === true) { - value = 0; - overWriteDimVal = 0; - brightness = 1; + } else if (value === 1) { + if (this.dimMaxTime + 5000 > Date.now()) { + await this.triggerCapabilityListener('night_mode', false); + if (this.getCapabilityValue('night_mode') === true) { + brightness = 1; + } + this.dimMaxTime = 0; + } else { + this.dimMaxTime = Date.now(); } + } else { + this.dimMinTime = 0; this.dimMaxTime = 0; + } + } + + if (value === 0 && !this.hasCapability('night_mode')) { + return this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["off", "smooth", 500]}'); + } else if (value === 0 && typeof opts.duration !== 'undefined') { + if (this.getData().model == 'ceiling4') { + var color_temp = yeelight.denormalize(this.getCapabilityValue('light_temperature'), 2700, 6000); + } else if (this.getData().model == 'color') { + var color_temp = yeelight.denormalize(this.getCapabilityValue('light_temperature'), 1700, 6500); } else { - this.dimMaxTime = Date.now(); + var color_temp = yeelight.denormalize(this.getCapabilityValue('light_temperature'), 2700, 6500); } + return this.sendCommand(this.getData().id, '{"id":1,"method":"start_cf","params":[1, 2, "'+ opts.duration +', 2, '+ color_temp +', 0"]}'); + } else if (typeof opts.duration !== 'undefined') { + return this.sendCommand(this.getData().id, '{"id":1,"method":"set_bright","params":['+ brightness +', "smooth", '+ opts.duration +']}'); } else { - this.dimMinTime = 0; - this.dimMaxTime = 0; + return this.sendCommand(this.getData().id, '{"id":1,"method":"set_bright","params":['+ brightness +', "smooth", 500]}'); } - } + }); - if (typeof opts.duration !== 'undefined') { - this.sendCommand(this.getData().id, '{"id":1,"method":"set_bright","params":['+ brightness +', "smooth", '+ opts.duration +']}'); - } else { - this.sendCommand(this.getData().id, '{"id":1,"method":"set_bright","params":['+ brightness +', "smooth", 500]}'); - } - callback(null, value); + this.registerCapabilityListener('night_mode', (value, opts) => { + if (value) { + return this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["on", "smooth", 500, 5]}'); + } else { + return this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["on", "smooth", 500, 1]}'); + } + }); - // "hack" to fix dim bar behaviour in the homey UI - if (overWriteDimVal !== undefined) { - this.setCapabilityValue('dim', overWriteDimVal); - } - } + this.registerMultipleCapabilityListener(['light_hue', 'light_saturation' ], ( valueObj, optsObj ) => { + if (typeof valueObj.light_hue !== 'undefined') { + var hue_value = valueObj.light_hue; + } else { + var hue_value = this.getCapabilityValue('light_hue'); + } - onCapabilityNightMode(value, opts, callback) { - if (value) { - this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["on", "smooth", 500, 5]}'); - } else { - this.sendCommand(this.getData().id, '{"id": 1, "method": "set_power", "params":["on", "smooth", 500, 1]}'); - } - callback(null, value); - } + if (typeof valueObj.light_saturation !== 'undefined') { + var saturation_value = valueObj.light_saturation; + } else { + var saturation_value = this.getCapabilityValue('light_saturation'); + } - onCapabilityHueSaturation(valueObj, optsObj) { - if (typeof valueObj.light_hue !== 'undefined') { - var hue_value = valueObj.light_hue; - } else { - var hue_value = this.getCapabilityValue('light_hue'); - } + var hue = hue_value * 359; + var saturation = saturation_value * 100; - if (typeof valueObj.light_saturation !== 'undefined') { - var saturation_value = valueObj.light_saturation; - } else { - var saturation_value = this.getCapabilityValue('light_saturation'); - } + if (this.getData().model == 'ceiling4') { + return this.sendCommand(this.getData().id, '{"id":1,"method":"bg_set_hsv","params":['+ hue +','+ saturation +', "smooth", 500]}'); + } else { + return this.sendCommand(this.getData().id, '{"id":1,"method":"set_hsv","params":['+ hue +','+ saturation +', "smooth", 500]}'); + } + }, 500); - var hue = hue_value * 359; - var saturation = saturation_value * 100; + this.registerCapabilityListener('light_temperature', (value, opts) => { + if (this.getData().model == 'ceiling4') { + var color_temp = yeelight.denormalize(value, 2700, 6000); + } else if (this.getData().model == 'color') { + var color_temp = yeelight.denormalize(value, 1700, 6500); + } else { + var color_temp = yeelight.denormalize(value, 2700, 6500); + } + if (this.hasCapability('night_mode')) { + this.setCapabilityValue('night_mode', false); + } + return this.sendCommand(this.getData().id, '{"id":1,"method":"set_ct_abx","params":['+ color_temp +', "smooth", 500]}'); + }); - if (this.getData().model == 'ceiling4') { - this.sendCommand(this.getData().id, '{"id":1,"method":"bg_set_hsv","params":['+ hue +','+ saturation +', "smooth", 500]}'); - } else { - this.sendCommand(this.getData().id, '{"id":1,"method":"set_hsv","params":['+ hue +','+ saturation +', "smooth", 500]}'); - } - return Promise.resolve(); } - onCapabilityLightTemperature(value, opts, callback) { - if (this.getData().model == 'ceiling4') { - var color_temp = yeelight.denormalize(value, 2700, 6000); - } else if (this.getData().model == 'color') { - var color_temp = yeelight.denormalize(value, 1700, 6500); - } else { - var color_temp = yeelight.denormalize(value, 2700, 6500); - } - this.sendCommand(this.getData().id, '{"id":1,"method":"set_ct_abx","params":['+ color_temp +', "smooth", 500]}'); - callback(null, value); - - if(this.hasCapability('night_mode')){ - this.setCapabilityValue('night_mode', false); - } + onDeleted() { + let id = this.getData().id; + delete yeelights[id]; } // HELPER FUNCTIONS @@ -155,7 +147,7 @@ class YeelightDevice extends Homey.Device { 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.connect(device.getSetting('port'), device.getSetting('address'), function() { yeelights[id].socket.setKeepAlive(true, 10000); yeelights[id].socket.setTimeout(0); }); @@ -324,22 +316,26 @@ class YeelightDevice extends Homey.Device { /* send commands to devices using their socket connection */ sendCommand(id, command) { - if(yeelights[id].connecting && yeelights[id].connected === false){ - this.log('Unable to send command because socket is still connecting'); - } else if (yeelights[id].connected === false && yeelights[id].socket !== null) { - yeelights[id].socket.emit('error', new Error('Connection to device broken')); - } else if (yeelights[id].socket === null) { - this.log('Unable to send command because socket is not available'); - } else { - yeelights[id].socket.write(command + '\r\n'); - - clearTimeout(yeelights[id].timeout); - yeelights[id].timeout = setTimeout(() => { - if (yeelights[id].connected === true && yeelights[id].socket !== null) { - yeelights[id].socket.emit('error', new Error('Error sending command')); - } - }, 6000); - } + return new Promise(function (resolve, reject) { + if(yeelights[id].connecting && yeelights[id].connected === false){ + return reject('Unable to send command because socket is still connecting'); + } else if (yeelights[id].connected === false && yeelights[id].socket !== null) { + yeelights[id].socket.emit('error', new Error('Connection to device broken')); + return reject('Connection to device broken'); + } else if (yeelights[id].socket === null) { + return reject('Unable to send command because socket is not available'); + } else { + yeelights[id].socket.write(command + '\r\n'); + return resolve(); + + clearTimeout(yeelights[id].timeout); + yeelights[id].timeout = setTimeout(() => { + if (yeelights[id].connected === true && yeelights[id].socket !== null) { + yeelights[id].socket.emit('error', new Error('Error sending command')); + } + }, 6000); + } + }); } /* check if device is connecting or connected */ diff --git a/drivers/yeelights/driver.js b/drivers/yeelights/driver.js index 1dd5c99..7efc5fb 100644 --- a/drivers/yeelights/driver.js +++ b/drivers/yeelights/driver.js @@ -5,21 +5,38 @@ const yeelight = require('/lib/yeelight.js'); const typeCapabilityMap = { 'mono' : [ 'onoff', 'dim' ], + 'mono1' : [ 'onoff', 'dim' ], 'ct' : [ 'onoff', 'dim', 'light_temperature' ], 'color' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode' ], + 'color1' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode' ], + 'color2' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode' ], 'stripe' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode' ], + 'stripe1' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode' ], 'bslamp' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode' ], + 'bslamp1' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode' ], 'ceiling' : [ 'onoff', 'dim', 'light_temperature', 'light_mode', 'night_mode' ], + 'ceiling1' : [ 'onoff', 'dim', 'light_temperature', 'light_mode', 'night_mode' ], + 'ceiling2' : [ 'onoff', 'dim', 'light_temperature', 'light_mode', 'night_mode' ], + 'ceiling3' : [ 'onoff', 'dim', 'light_temperature', 'light_mode', 'night_mode' ], 'ceiling4' : [ 'onoff', 'dim', 'light_hue', 'light_saturation', 'light_temperature', 'light_mode', 'night_mode' ], 'desklamp' : [ 'onoff', 'dim', 'light_temperature', 'light_mode' ] } const typeIconMap = { 'mono' : 'bulb.svg', + 'mono1' : 'bulb.svg', 'color' : 'bulb.svg', + 'color1' : 'bulb.svg', + 'color2' : 'bulb.svg', 'stripe' : 'strip.svg', + 'stripe1' : 'strip.svg', 'bslamp' : 'bslamp.svg', + 'bslamp1' : 'bslamp.svg', + 'bslamp2' : 'bslamp2.svg', 'ceiling' : 'ceiling.svg', + 'ceiling1' : 'ceiling.svg', + 'ceiling2' : 'ceiling.svg', + 'ceiling3' : 'ceiling.svg', 'ceiling4' : 'ceiling4.svg', 'desklamp' : 'desklamp.svg' } @@ -35,15 +52,15 @@ class YeelightDriver extends Homey.Driver { .then(result => { let devices = []; for (let i in result) { - if(result[i].model == 'color') { + if(result[i].model.startsWith('color')) { var name = Homey.__('yeelight_bulb_color')+ ' (' + result[i].address + ')'; - } else if (result[i].model == 'mono') { + } else if (result[i].startsWith('mono')) { var name = Homey.__('yeelight_bulb_white')+ ' (' + result[i].address + ')'; } else if (result[i].model == 'ct') { var name = Homey.__('yeelight_bulb_white_v2')+ ' (' + result[i].address + ')'; - } else if (result[i].model == 'stripe') { + } else if (result[i].model.startsWith('stripe')) { var name = Homey.__('yeelight_led_strip')+ ' (' + result[i].address + ')'; - } else if (result[i].model == 'bslamp') { + } else if (result[i].model.startsWith('bslamp')) { var name = Homey.__('yeelight_bedside_lamp')+ ' (' + result[i].address + ')'; } else if (result[i].model.startsWith('ceiling')) { if(result[i].model !== 'ceiling4') { @@ -57,10 +74,12 @@ class YeelightDriver extends Homey.Driver { name: name, data: { id: result[i].id, - address: result[i].address, - port: result[i].port, model: result[i].model }, + settings: { + address: result[i].address, + port: result[i].port + }, capabilities: typeCapabilityMap[result[i].model], icon: typeIconMap[result[i].model] }); diff --git a/node_modules/miio/lib/network.js b/node_modules/miio/lib/network.js index f4754ca..2eeb2de 100644 --- a/node_modules/miio/lib/network.js +++ b/node_modules/miio/lib/network.js @@ -231,6 +231,9 @@ class Network extends EventEmitter { get socket() { if(! this._socket) { throw new Error('Network communication is unavailable, device might be destroyed'); + + // RECREATING SOCKET IF IT'S KILLED, WE NEED IT AVAILABLE AT ALL TIME + this.createSocket(); } return this._socket; diff --git a/package.json b/package.json index 99fe302..5e8da00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.xiaomi-miio", - "version": "2.11.1", + "version": "2.11.2", "description": "Xiaomi Mi Home", "main": "app.js", "dependencies": {