diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..3db01b6 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,23 @@ +``` +MIT License + +Copyright (c) 2017 Sei Kan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2c619e6 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# homebridge-mi-air-purifier + +This is Xiaomi Mi Air Purifier plugin for [Homebridge](https://github.com/nfarina/homebridge). + +![mi-air-purifier](https://cloud.githubusercontent.com/assets/73107/26249685/1d0ae78c-3cda-11e7-8b64-71e8d4323a3e.jpg) + +### Features + +* Switch on/off. +* Control modes: `idle`, `silent`, `low`, `medium`. +* Get air quality. + + + +### Installation + +1. Install required packages. + + ``` + npm install -g homebridge-mi-air-purifier miio + ``` + + ​ + +2. Add your Accessory to the `config.json`. + + ``` + "accessories": [ + { + "accessory": "MiAirPurifier" + } + ] + ``` + + ​ + +3. Restart Homebridge, and your Mi air purifier will be discovered automatically. + + + +### License + +See the [LICENSE](https://github.com/seikan/homebridge-mi-air-purifier/blob/master/LICENSE.md) file for license rights and limitations (MIT). + + + diff --git a/example.config.json b/example.config.json new file mode 100644 index 0000000..6c2e980 --- /dev/null +++ b/example.config.json @@ -0,0 +1,14 @@ +{ + "bridge": { + "name": "Homebridge", + "username": "CC:22:3D:E3:CE:30", + "port": 51826, + "pin": "123-45-568" + }, + + "accessories": [ + { + "accessory": "MiAirPurifier", + } + ] +} diff --git a/index.js b/index.js new file mode 100644 index 0000000..3155255 --- /dev/null +++ b/index.js @@ -0,0 +1,160 @@ +var miio = require('miio'); +var Accessory, Service, Characteristic, UUIDGen; +var devices = []; + +module.exports = function(homebridge) { + Accessory = homebridge.platformAccessory; + Service = homebridge.hap.Service; + Characteristic = homebridge.hap.Characteristic; + UUIDGen = homebridge.hap.uuid; + + homebridge.registerAccessory('homebridge-mi-air-purifier', 'MiAirPurifier', MiAirPurifier); +} + +function MiAirPurifier(log, config) { + this.log = log; + this.modes = [ + [0, 'idle'], [40, 'silent'], [60, 'auto'], [80, 'low'], [100, 'medium'] + ]; + + // Air purifier is not available in Homekit yet, use as fan for now + this.fanService = new Service.Fan('Air Purifier'); + + // Register another service as air quality sensor + this.airQualitySensorService = new Service.AirQualitySensor('Air Quality'); + + this.serviceInfo = new Service.AccessoryInformation(); + + this.fanService + .getCharacteristic(Characteristic.On) + .on('get', this.getOn.bind(this)) + .on('set', this.setOn.bind(this)); + + this.fanService + .getCharacteristic(Characteristic.RotationSpeed) + .on('get', this.getRotationSpeed.bind(this)) + .on('set', this.setRotationSpeed.bind(this)); + + this.airQualitySensorService + .getCharacteristic(Characteristic.AirQuality) + .on('get', this.getAirQuality.bind(this)); + + this.discover(); +} + +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; + + accessory.serviceInfo + .setCharacteristic(Characteristic.Manufacturer, 'Xiao Mi') + .setCharacteristic(Characteristic.Model, device.model) + .setCharacteristic(Characteristic.SerialNumber, device.id); + + log.debug('Discovered "%s" (ID: %s) on %s:%s.', reg.hostname, device.id, device.address, device.port); + }); + }); + + browser.on('unavailable', function(reg){ + // Skip device without token + if(!reg.token) + return; + + var device = devices[reg.id]; + + if(!device) + return; + + device.destroy(); + delete devices[reg.id]; + }); + }, + + getOn: function(callback) { + callback(null, this.device.power); + }, + + setOn: function(powerOn, callback) { + if(!this.device){ + callback(new Error('No air purifier is discovered.')); + return; + } + + var accessory = this; + + this.device.setPower(powerOn) + .then(function(state){ + accessory.getRotationSpeed(accessory); + callback(null, state); + }); + + callback(); + }, + + getRotationSpeed: function(callback) { + for(var item of this.modes){ + if(this.device.mode == item[1]){ + callback(null, item[0]); + } + } + }, + + setRotationSpeed: function(speed, callback) { + for(var item of this.modes){ + if(speed <= item[0]){ + this.log.debug('Set mode: ' + item[1]); + this.device.setMode(item[1]); + break; + } + } + + callback(); + }, + + getAirQuality: function(callback) { + var airQuality = Characteristic.AirQuality.UNKNOWN; + + if(this.device.aqi > 200) + airQuality = Characteristic.AirQuality.POOR; + + else if(this.device.aqi > 150) + airQuality = Characteristic.AirQuality.INFERIOR; + + else if(this.device.aqi > 100) + airQuality = Characteristic.AirQuality.FAIR; + + else if(this.device.aqi > 50) + airQuality = Characteristic.AirQuality.GOOD; + + else + airQuality = Characteristic.AirQuality.EXCELLENT; + + callback(null, airQuality); + }, + + identify: function(callback) { + callback(); + }, + + getServices: function() { + return [this.serviceInfo, this.fanService, this.airQualitySensorService]; + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..60aa721 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "homebridge-mi-air-purifier", + "version": "1.0.0", + "description": "Xiaomi Mi air purifier plugin for Homebridge.", + "license": "MIT", + "keywords": [ + "homebridge-plugin", + "xiaomi", + "mi air purifier" + ], + "repository": { + "type": "git", + "url": "https://github.com/seikan/homebridge-mi-air-purifier.git" + }, + "engines": { + "node": ">=0.12.0", + "homebridge": ">=0.2.0", + "miio": ">=0.12.0" + } +}