From fd3ab9130daf4157a339b32c20ca245f3158ce1e Mon Sep 17 00:00:00 2001 From: Sei Kan Date: Fri, 22 Sep 2017 09:02:55 +0800 Subject: [PATCH] The device will added as Air Purifier in Home app as the release of iOS 11. --- README.md | 44 ++++++++- index.js | 245 +++++++++++++++++++++++++++++---------------------- package.json | 2 +- 3 files changed, 180 insertions(+), 111 deletions(-) diff --git a/README.md b/README.md index 84a0fcc..c1044eb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # homebridge-mi-air-purifier -This is Xiaomi Mi Air Purifier plugin for [Homebridge](https://github.com/nfarina/homebridge). Since Apple Homekit is not supporting air purifier device yet, this plugin will add the air purifier as **Fan** and **Air Quality Sensor** to your Home app. +This is Xiaomi Mi Air Purifier plugin for [Homebridge](https://github.com/nfarina/homebridge). This plugin will add the air purifier and **Air Quality Sensor** to your Home app. This version is working with iOS 11 to add the device as air purifier in Home app. ![mi-air-purifier](https://cloud.githubusercontent.com/assets/73107/26249685/1d0ae78c-3cda-11e7-8b64-71e8d4323a3e.jpg) @@ -8,6 +8,10 @@ This is Xiaomi Mi Air Purifier plugin for [Homebridge](https://github.com/nfarin * Switch on / off. +* Switch auto / manual mode. + +* Turn parental mode on / off. + * Control modes: - `Idle / Off`: Lift fan speed to 0% from Home app. @@ -48,13 +52,47 @@ This is Xiaomi Mi Air Purifier plugin for [Homebridge](https://github.com/nfarin ​ -2. Add following accessory to the `config.json`. +2. Make sure your Homebridge server is same network with your air purifier, then run following command to discover the token. + + ``` + miio --discover --sync + ``` + + + +3. You may need to wait few minutes until you get the response similar to below: + + ``` + Device ID: 49466088 + Model info: Unknown + Address: 192.168.1.8 + Token: 6f7a65786550386c700a6b526666744d via auto-token + Support: Unknown + ``` + + ​ + +4. Record down the `Address` and `Token` values as we need it in our configuration file later. + +5. If you are getting `??????????????` for your token value, please reset your device and connect your Homebridge server directly to the access point advertised by the device. + +6. Then run the command again. + + ``` + miio --discover --sync + ``` + + ​ + +7. Add following accessory to the `config.json`. ``` "accessories": [ { "accessory": "MiAirPurifier", "name": "Air Purifier", + "ip": "ADDRESS_OF_THE_AIR_PURIFIER", + "token": "TOKEN_FROM_STEP_3". "showTemperature": true, "showHumidity": true, "showAirQuality": true @@ -66,7 +104,7 @@ This is Xiaomi Mi Air Purifier plugin for [Homebridge](https://github.com/nfarin ​ -3. Restart Homebridge, and your Mi air purifier will be discovered automatically. +8. Restart Homebridge, and your Mi air purifier will be added to Home app. diff --git a/index.js b/index.js index ca4998c..509aad7 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,8 @@ var miio = require('miio'); -var Accessory, Service, Characteristic; +var Service, Characteristic; var devices = []; module.exports = function(homebridge) { - Accessory = homebridge.platformAccessory; Service = homebridge.hap.Service; Characteristic = homebridge.hap.Characteristic; @@ -12,6 +11,8 @@ module.exports = function(homebridge) { function MiAirPurifier(log, config) { this.log = log; + this.ip = config.ip; + this.token = config.token; this.name = config.name || 'Air Purifier'; this.showAirQuality = config.showAirQuality || false; this.showTemperature = config.showTemperature || false; @@ -19,36 +20,57 @@ function MiAirPurifier(log, config) { this.services = []; + if(!this.ip) + throw new Error('Your must provide IP address of the Air Purifier.'); + + if(!this.token) + throw new Error('Your must provide token of the Air Purifier.'); + // Modes supported this.modes = [ [0, 'idle'], [60, 'auto'], [80, 'silent'], [100, 'favorite'] ]; - // Air purifier is not available in Homekit yet, register as Fan - this.fanService = new Service.Fan(this.name); + // Register the service + this.service = new Service.AirPurifier(this.name); + + this.service + .getCharacteristic(Characteristic.Active) + .on('get', this.getActive.bind(this)) + .on('set', this.setActive.bind(this)); + + this.service + .getCharacteristic(Characteristic.CurrentAirPurifierState) + .on('get', this.getCurrentAirPurifierState.bind(this)); - this.fanService - .getCharacteristic(Characteristic.On) - .on('get', this.getPowerState.bind(this)) - .on('set', this.setPowerState.bind(this)); + this.service + .getCharacteristic(Characteristic.TargetAirPurifierState) + .on('get', this.getTargetAirPurifierState.bind(this)) + .on('set', this.setTargetAirPurifierState.bind(this)); - this.fanService + this.service + .getCharacteristic(Characteristic.LockPhysicalControls) + .on('get', this.getLockPhysicalControls.bind(this)) + .on('set', this.setLockPhysicalControls.bind(this)); + + this.service .getCharacteristic(Characteristic.RotationSpeed) .on('get', this.getRotationSpeed.bind(this)) .on('set', this.setRotationSpeed.bind(this)); - this.services.push(this.fanService); - + // Service information this.serviceInfo = new Service.AccessoryInformation(); this.serviceInfo .setCharacteristic(Characteristic.Manufacturer, 'Xiaomi') - .setCharacteristic(Characteristic.Model, 'Air Purifier'); - + .setCharacteristic(Characteristic.Model, 'Air Purifier') + .setCharacteristic(Characteristic.SerialNumber, '0799-E5C0-57A641308C0D'); + + this.services.push(this.service); this.services.push(this.serviceInfo); if(this.showAirQuality){ - this.airQualitySensorService = new Service.AirQualitySensor('Air Quality Sensor'); + this.airQualitySensorService = new Service.AirQualitySensor('Air Quality'); this.airQualitySensorService .getCharacteristic(Characteristic.AirQuality) @@ -82,110 +104,117 @@ function MiAirPurifier(log, config) { MiAirPurifier.prototype = { discover: function(){ - var accessory = this; - var log = this.log; - - log.debug('Discovering Mi air purifier devices...'); - - // Discover device in the network - var browser = miio.browse(); - - browser.on('available', function(reg){ - // Skip device without token - if(!reg.token) - return; - - miio.device(reg).then(function(device){ - if(device.type != 'air-purifier') - return; - - devices[reg.id] = device; - accessory.device = device; - - log.debug('Discovered "%s" (ID: %s) on %s:%s.', reg.hostname, device.id, device.address, device.port); - }); + this.device = new miio.Device({ + address : this.ip, + token : this.token }); + }, - browser.on('unavailable', function(reg){ - // Skip device without token - if(!reg.token) - return; + getActive: function(callback) { + this.device.call('get_prop', ['mode']) + .then(result => { + callback(null, (result[0] === 'idle') ? Characteristic.Active.INACTIVE: Characteristic.Active.ACTIVE); + }).catch(err => { + callback(err); + }); + }, - var device = devices[reg.id]; - - if(!device) - return; + setActive: function(state, callback) { + this.device.call('set_power', [(state) ? 'on' : 'off']) + .then(result => { + (result[0] === 'ok') ? callback() : callback(new Error(result[0])); + }) + .catch(err => { + callback(err); + }); + }, - device.destroy(); - delete devices[reg.id]; - }); + getCurrentAirPurifierState: function(callback) { + this.device.call('get_prop', ['mode']) + .then(result => { + callback(null, (result[0] === 'idle') ? Characteristic.CurrentAirPurifierState.INACTIVE : Characteristic.CurrentAirPurifierState.PURIFYING_AIR); + }).catch(err => { + callback(err); + }); }, - getPowerState: function(callback) { - if(!this.device){ - callback(null, false); - return; - } + getTargetAirPurifierState: function(callback) { + this.device.call('get_prop', ['mode']) + .then(result => { + callback(null, (result[0] === 'favorite') ? Characteristic.TargetAirPurifierState.MANUAL : Characteristic.TargetAirPurifierState.AUTO); + }).catch(err => { + callback(err); + }); + }, - callback(null, this.device.power); + setTargetAirPurifierState: function(state, callback) { + this.device.call('set_mode', [(state) ? 'auto' : 'favorite']) + .then(result => { + (result[0] === 'ok') ? callback() : callback(new Error(result[0])); + }) + .catch(err => { + callback(err); + }); }, - setPowerState: function(state, callback) { - if(!this.device){ - callback(new Error('No air purifier is discovered.')); - return; - } + getLockPhysicalControls: function(callback) { + this.device.call('get_prop', ['child_lock']) + .then(result => { + callback(null, result[0] === 'on' ? Characteristic.LockPhysicalControls.CONTROL_LOCK_ENABLED : Characteristic.LockPhysicalControls.CONTROL_LOCK_DISABLED); + }) + .catch(err => { + callback(err); + }); + }, - this.device.setPower(state); - callback(); + setLockPhysicalControls: function(state, callback) { + this.device.call('set_child_lock', [(state) ? 'on' : 'off']) + .then(result => { + (result[0] === 'ok') ? callback() : callback(new Error(result[0])); + }).catch(err => { + callback(err); + }); }, getCurrentRelativeHumidity: function(callback) { - if(!this.device){ - callback(null, 0); - return; - } - - callback(null, this.device.humidity); + this.device.call('get_prop', ['humidity']) + .then(result => { + callback(null, result[0]); + }).catch(err => { + callback(err); + }); }, getRotationSpeed: function(callback) { - if(!this.device){ - callback(null, 0); - return; - } - - for(var item of this.modes){ - if(this.device.mode == item[1]){ - callback(null, item[0]); - return; - } - } + this.device.call('get_prop', ['mode']) + .then(result => { + for(var item of this.modes){ + if(result[0] == item[1]){ + callback(null, item[0]); + return; + } + } + }).catch(err => { + callback(err); + }); }, setRotationSpeed: function(speed, callback) { - if(!this.device){ - callback(new Error('No air purifier is discovered.')); - return; - } - for(var item of this.modes){ if(speed <= item[0]){ - this.log.debug('Set mode: ' + item[1]); - this.device.setMode(item[1]); + this.device.call('set_mode', [item[1]]) + .then(result => { + (result[0] === 'ok') ? callback() : callback(new Error(result[0])); + }) + .catch(err => { + callback(err); + }); break; } } - - callback(); }, getAirQuality: function(callback) { - if(!this.device){ - callback(null, Characteristic.AirQuality.UNKNOWN); - return; - } - var levels = [ [200, Characteristic.AirQuality.POOR], [150, Characteristic.AirQuality.INFERIOR], @@ -194,25 +223,27 @@ MiAirPurifier.prototype = { [0, Characteristic.AirQuality.EXCELLENT], ]; - var quality = Characteristic.AirQuality.UNKNOWN; - - for(var item of levels){ - if(this.device.aqi >= item[0]){ - quality = item[1]; - break; - } - } - - callback(null, quality); + this.device.call('get_prop', ['aqi']) + .then(result => { + for(var item of levels){ + if(result[0] >= item[0]){ + callback(null, item[1]); + return; + } + } + }).catch(err => { + callback(err); + }); }, getCurrentTemperature: function(callback) { - if(!this.device){ - callback(null, 0); - return; - } - - callback(null, this.device.temperature); + this.device.call('get_prop', ['temp_dec']) + .then(result => { + callback(null, result[0] / 10.0); + }) + .catch(err => { + callback(err); + }); }, identify: function(callback) { diff --git a/package.json b/package.json index 03ce5b3..8605040 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homebridge-mi-air-purifier", - "version": "1.1.1", + "version": "1.2.0", "description": "Xiaomi Mi air purifier plugin for Homebridge.", "license": "MIT", "keywords": [