├── .npmignore ├── images ├── README.md ├── Button.jpg ├── Gateway.jpg ├── MiCamera.jpg ├── PlugBase.jpg ├── Yeelight.jpg ├── Yeelight2.png ├── Yeelight3.jpg ├── Yeelight3.png ├── AirPurifier.jpg ├── DuplexSwitch.jpg ├── MagicSquare.jpg ├── MotionSensor.jpg ├── PlugBase86.jpg ├── SingleSwitch.jpg ├── Broadlink_MP1.jpg ├── Broadlink_MP2.jpg ├── ContactSensor.jpg ├── DuplexButton86.jpg ├── DuplexSwitchLN.jpg ├── MiRobotVacuum.jpg ├── NatgasDetector.jpg ├── SingleButton86.jpg ├── SingleSwitchLN.jpg ├── SmokeDetector.jpg ├── ElectricCurtain.jpg └── TemperatureAndHumiditySensor.jpg ├── .gitignore ├── kit ├── broadlink │ ├── index.js │ ├── mp.js │ └── broadlink.js ├── index.js ├── mijia │ ├── humidity.js │ ├── motion.js │ ├── humidity.v1.js │ ├── temperature.js │ ├── pressure.v1.js │ ├── magnet.js │ ├── temperature.v1.js │ ├── switch.js │ ├── sw861.js │ ├── magnet.aq2.js │ ├── switch.aq2.js │ ├── ctrlln1.js │ ├── ctrlneutral1.js │ ├── ctrlln2.js │ ├── base.js │ ├── ctrlneutral2.js │ ├── sw862.js │ ├── index.js │ ├── plug86.js │ ├── natgas.js │ ├── smoke.js │ ├── powerstrip.js │ ├── plug.js │ ├── powerplug.js │ ├── motion.aq2.js │ ├── curtain.js │ ├── yeelight.js │ ├── vacuum.js │ ├── gateway.js │ └── airpurifier.js ├── broadlink.js └── mijia.js ├── tests └── test.js ├── web └── index.js ├── index.js ├── package.json ├── LICENSE ├── config-sample.json ├── util ├── color.js └── types.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | tests 2 | -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | NOTHING -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules -------------------------------------------------------------------------------- /kit/broadlink/index.js: -------------------------------------------------------------------------------- 1 | const mp = require('./mp'); 2 | module.exports = { 3 | MP: mp 4 | }; -------------------------------------------------------------------------------- /images/Button.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Button.jpg -------------------------------------------------------------------------------- /images/Gateway.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Gateway.jpg -------------------------------------------------------------------------------- /images/MiCamera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/MiCamera.jpg -------------------------------------------------------------------------------- /images/PlugBase.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/PlugBase.jpg -------------------------------------------------------------------------------- /images/Yeelight.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Yeelight.jpg -------------------------------------------------------------------------------- /images/Yeelight2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Yeelight2.png -------------------------------------------------------------------------------- /images/Yeelight3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Yeelight3.jpg -------------------------------------------------------------------------------- /images/Yeelight3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Yeelight3.png -------------------------------------------------------------------------------- /images/AirPurifier.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/AirPurifier.jpg -------------------------------------------------------------------------------- /images/DuplexSwitch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/DuplexSwitch.jpg -------------------------------------------------------------------------------- /images/MagicSquare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/MagicSquare.jpg -------------------------------------------------------------------------------- /images/MotionSensor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/MotionSensor.jpg -------------------------------------------------------------------------------- /images/PlugBase86.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/PlugBase86.jpg -------------------------------------------------------------------------------- /images/SingleSwitch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/SingleSwitch.jpg -------------------------------------------------------------------------------- /images/Broadlink_MP1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Broadlink_MP1.jpg -------------------------------------------------------------------------------- /images/Broadlink_MP2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/Broadlink_MP2.jpg -------------------------------------------------------------------------------- /images/ContactSensor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/ContactSensor.jpg -------------------------------------------------------------------------------- /images/DuplexButton86.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/DuplexButton86.jpg -------------------------------------------------------------------------------- /images/DuplexSwitchLN.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/DuplexSwitchLN.jpg -------------------------------------------------------------------------------- /images/MiRobotVacuum.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/MiRobotVacuum.jpg -------------------------------------------------------------------------------- /images/NatgasDetector.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/NatgasDetector.jpg -------------------------------------------------------------------------------- /images/SingleButton86.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/SingleButton86.jpg -------------------------------------------------------------------------------- /images/SingleSwitchLN.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/SingleSwitchLN.jpg -------------------------------------------------------------------------------- /images/SmokeDetector.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/SmokeDetector.jpg -------------------------------------------------------------------------------- /images/ElectricCurtain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/ElectricCurtain.jpg -------------------------------------------------------------------------------- /images/TemperatureAndHumiditySensor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rench/homebridge-smarthome/HEAD/images/TemperatureAndHumiditySensor.jpg -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | const color = require('../util/color'); 2 | 3 | console.log(color.rgb2hsv(230, 80, 80)); 4 | console.log(color.hsv2rgb(0, 65.2, 90.2)); 5 | 6 | console.log(color.rgb2hex(230, 80, 80)); 7 | console.log(color.hex2rgb('E65050')); -------------------------------------------------------------------------------- /kit/index.js: -------------------------------------------------------------------------------- 1 | const mijia = require('./mijia'); 2 | const broadlink = require('./broadlink'); 3 | //module exports define 4 | module.exports = { 5 | mijia: (homebridge) => { 6 | return mijia(homebridge); 7 | }, 8 | broadlink: (homebridge) => { 9 | return broadlink(homebridge); 10 | } 11 | } -------------------------------------------------------------------------------- /web/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this app.js is web server to manage your mijia's accessory 3 | */ 4 | const winston = require('winston'); 5 | 6 | class WebApp { 7 | constructor() { 8 | winston.info('webapp start') 9 | } 10 | init() { 11 | this.bind(); 12 | this.start(); 13 | } 14 | bind() { 15 | 16 | } 17 | start() { 18 | 19 | } 20 | } 21 | 22 | 23 | module.exports = () => { 24 | const app = new WebApp(); 25 | app.init(); 26 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * this is smarthome entry point 3 | */ 4 | 5 | const web = require('./web'); 6 | const { mijia, broadlink } = require('./kit'); 7 | const winston = require('winston'); 8 | 9 | module.exports = homebridge => { 10 | //every kit will store their devices on context; 11 | homebridge.context = {}; 12 | //init mijia devices 13 | mijia(homebridge); 14 | //init broadlink devices 15 | broadlink(homebridge); 16 | //init web server 17 | web(homebridge); 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "homebridge-smarthome", 3 | "version": "1.1.3", 4 | "description": "this is a homebridge plugin for mijia eco-chain but also a management app provide web and restapi to manage your mijia eco-chain,you can control and monitor your accessory via apple's homekit or the webapp", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/rench/homebridge-smarthome.git" 12 | }, 13 | "keywords": [ 14 | "homebridge", 15 | "homebridge-plugin", 16 | "xiaomi", 17 | "mijia", 18 | "smarthome" 19 | ], 20 | "author": "Lo.Wang", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/rench/homebridge-smarthome/issues" 24 | }, 25 | "homepage": "https://github.com/rench/homebridge-smarthome#readme", 26 | "dependencies": { 27 | "miio": "^0.13.0", 28 | "winston": "^2.3.1" 29 | }, 30 | "engines": { 31 | "homebridge": ">=0.4.22", 32 | "node": ">=6.10.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 rench 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config-sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "bridge": { 3 | "name": "SmartHome", 4 | "username": "CC:22:3D:E3:CE:30", 5 | "port": 51826, 6 | "pin": "031-45-154" 7 | }, 8 | "platforms": [ 9 | { 10 | "platform": "smarthome-mijia", 11 | "web": { 12 | "port": 8888 13 | }, 14 | "mijia": { 15 | "sids": [ 16 | "34ce0088faed" 17 | ], 18 | "passwords": [ 19 | "75ED5A235C4A44D4" 20 | ], 21 | "devices": [ 22 | { 23 | "sid": "Air Purifier 001", 24 | "name": "Air Purifier", 25 | "type": "wifi", 26 | "model": "air-purifier" 27 | }, 28 | { 29 | "sid": "Power Plug 001", 30 | "name": "Power Plug", 31 | "type": "wifi", 32 | "model": "power-plug" 33 | }, 34 | { 35 | "sid": "Power Strip 001", 36 | "name": "Power Strip", 37 | "type": "wifi", 38 | "model": "power-strip" 39 | }, 40 | { 41 | "sid": "Yeelight 001", 42 | "name": "Yeelight", 43 | "type": "wifi", 44 | "model": "light" 45 | }, 46 | { 47 | "sid": "Vacuum Cleaner 001", 48 | "name": "Vacuum Cleaner", 49 | "ip": "192.168.2.200", 50 | "token": "4ac2cd21f3e9272ab21a5c1fd4053ed9", 51 | "type": "wifi", 52 | "model": "vacuum" 53 | } 54 | ] 55 | } 56 | }, 57 | { 58 | "platform": "smarthome-broadlink", 59 | "broadlink": { 60 | "devices": [ 61 | { 62 | "name": "MP2", 63 | "type": "MP2", 64 | "mac": "34:EA:34:D9:FE:B3" 65 | } 66 | ] 67 | } 68 | } 69 | ] 70 | } -------------------------------------------------------------------------------- /kit/mijia/humidity.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Humidity extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, humidity } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} humidity->${humidity}`); 22 | this.setHumiditySensor(sid, voltage, humidity) 23 | } 24 | /** 25 | * setup humiditysensor 26 | * @param {*} sid 27 | * @param {*} voltage 28 | * @param {*} humidity 29 | */ 30 | setHumiditySensor(sid, voltage, humidity) { 31 | let uuid = UUIDGen.generate('Mijia-HumiditySensor@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia HumiditySensor") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.HumiditySensor(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.HumiditySensor); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'sensor_ht'; 54 | if (humidity != undefined) { 55 | service.getCharacteristic(Characteristic.CurrentRelativeHumidity).updateValue(humidity / 100); 56 | } 57 | this.setBatteryService(sid, voltage, accessory); 58 | if (!this.mijia.accessories[uuid]) { 59 | this.mijia.accessories[uuid] = accessory; 60 | this.registerAccessory([accessory]); 61 | } 62 | return accessory; 63 | } 64 | } 65 | module.exports = Humidity; -------------------------------------------------------------------------------- /kit/mijia/motion.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Motion extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status}`); 22 | this.setMotionSensor(sid, voltage, status) 23 | } 24 | /** 25 | * set up MotionSensor(mijia motion sensor) 26 | * @param {*device id} sid 27 | * @param {*device voltage} voltage 28 | * @param {*device status} status 29 | */ 30 | setMotionSensor(sid, voltage, status) { 31 | let uuid = UUIDGen.generate('Mijia-MotionSensor@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia MotionSensor") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.MotionSensor(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.MotionSensor); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'motion'; 54 | if (status != undefined) { 55 | service.getCharacteristic(Characteristic.MotionDetected).updateValue('motion' == status); 56 | } 57 | this.setBatteryService(sid, voltage, accessory); 58 | if (!this.mijia.accessories[uuid]) { 59 | this.mijia.accessories[uuid] = accessory; 60 | this.registerAccessory([accessory]); 61 | } 62 | return accessory; 63 | } 64 | } 65 | module.exports = Motion; -------------------------------------------------------------------------------- /kit/mijia/humidity.v1.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class HumidityV1 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, humidity } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} humidity->${humidity}`); 22 | if (humidity != undefined) { 23 | this.setHumiditySensor(sid, voltage, humidity); 24 | } 25 | } 26 | /** 27 | * setup humiditysensor 28 | * @param {*} sid 29 | * @param {*} voltage 30 | * @param {*} humidity 31 | */ 32 | setHumiditySensor(sid, voltage, humidity) { 33 | let uuid = UUIDGen.generate('Aqara-HumiditySensor@' + sid); 34 | let accessory = this.mijia.accessories[uuid]; 35 | let service; 36 | if (!accessory) { 37 | //init a new homekit accessory 38 | let name = sid.substring(sid.length - 4); 39 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 40 | accessory.getService(Service.AccessoryInformation) 41 | .setCharacteristic(Characteristic.Manufacturer, "Aqara") 42 | .setCharacteristic(Characteristic.Model, "Aqara HumiditySensor") 43 | .setCharacteristic(Characteristic.SerialNumber, sid); 44 | accessory.on('identify', function (paired, callback) { 45 | callback(); 46 | }); 47 | service = new Service.HumiditySensor(name); 48 | accessory.addService(service, name); 49 | accessory.addService(new Service.BatteryService(name), name); 50 | } else { 51 | service = accessory.getService(Service.HumiditySensor); 52 | } 53 | accessory.reachable = true; 54 | accessory.context.sid = sid; 55 | accessory.context.model = 'weather.v1'; 56 | if (humidity != undefined) { 57 | service.getCharacteristic(Characteristic.CurrentRelativeHumidity).updateValue(humidity / 100); 58 | } 59 | this.setBatteryService(sid, voltage, accessory); 60 | if (!this.mijia.accessories[uuid]) { 61 | this.mijia.accessories[uuid] = accessory; 62 | this.registerAccessory([accessory]); 63 | } 64 | return accessory; 65 | } 66 | } 67 | module.exports = HumidityV1; -------------------------------------------------------------------------------- /kit/mijia/temperature.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Temperature extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, temperature } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} temperature->${temperature}`); 22 | this.setTemperatureSensor(sid, voltage, temperature) 23 | } 24 | /** 25 | * set up TemperatureSensor(mijia temperature and humidity sensor) 26 | * @param {*device id} sid 27 | * @param {*device voltage} voltage 28 | * @param {*device temperature} temperature 29 | */ 30 | setTemperatureSensor(sid, voltage, temperature) { 31 | let uuid = UUIDGen.generate('Mijia-TemperatureSensor@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia TemperatureSensor") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.TemperatureSensor(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.TemperatureSensor); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'sensor_ht'; 54 | if (temperature != undefined) { 55 | service.getCharacteristic(Characteristic.CurrentTemperature).updateValue(temperature / 100); 56 | } 57 | this.setBatteryService(sid, voltage, accessory); 58 | if (!this.mijia.accessories[uuid]) { 59 | this.mijia.accessories[uuid] = accessory; 60 | this.registerAccessory([accessory]); 61 | } 62 | return accessory; 63 | } 64 | } 65 | module.exports = Temperature; -------------------------------------------------------------------------------- /kit/mijia/pressure.v1.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen, CommunityTypes; 3 | class PressureV1 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | CommunityTypes = mijia.CommunityTypes; 12 | } 13 | /** 14 | * parse the gateway json msg 15 | * @param {*json} json 16 | * @param {*remoteinfo} rinfo 17 | */ 18 | parseMsg(json, rinfo) { 19 | let { cmd, model, sid } = json; 20 | let data = JSON.parse(json.data); 21 | let { voltage, pressure } = data; 22 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} pressure->${pressure}`); 23 | if (pressure != undefined) { 24 | this.setPressureSensor(sid, voltage, pressure); 25 | } 26 | } 27 | /** 28 | * set up PressureSensor(aqara pressure and humidity sensor) 29 | * @param {*device id} sid 30 | * @param {*device voltage} voltage 31 | * @param {*device pressure} pressure 32 | */ 33 | setPressureSensor(sid, voltage, pressure) { 34 | let uuid = UUIDGen.generate('Aqara-PressureSensor@' + sid); 35 | let accessory = this.mijia.accessories[uuid]; 36 | let service; 37 | if (!accessory) { 38 | //init a new homekit accessory 39 | let name = sid.substring(sid.length - 4); 40 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 41 | accessory.getService(Service.AccessoryInformation) 42 | .setCharacteristic(Characteristic.Manufacturer, "Aqara") 43 | .setCharacteristic(Characteristic.Model, "Aqara PressureSensor") 44 | .setCharacteristic(Characteristic.SerialNumber, sid); 45 | accessory.on('identify', function (paired, callback) { 46 | callback(); 47 | }); 48 | service = new CommunityTypes.AtmosphericPressureSensor(name); 49 | accessory.addService(service, name); 50 | accessory.addService(new Service.BatteryService(name), name); 51 | } else { 52 | service = accessory.getService(CommunityTypes.AtmosphericPressureSensor); 53 | } 54 | accessory.reachable = true; 55 | accessory.context.sid = sid; 56 | accessory.context.model = 'weather.v1'; 57 | if (pressure != undefined) { 58 | service.getCharacteristic(Characteristic.AtmosphericPressureLevel).updateValue(pressure); 59 | } 60 | this.setBatteryService(sid, voltage, accessory); 61 | if (!this.mijia.accessories[uuid]) { 62 | this.mijia.accessories[uuid] = accessory; 63 | this.registerAccessory([accessory]); 64 | } 65 | return accessory; 66 | } 67 | } 68 | module.exports = PressureV1; -------------------------------------------------------------------------------- /kit/mijia/magnet.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Magnet extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status}`); 22 | this.setContactSensor(sid, voltage, status); 23 | } 24 | /** 25 | * set up ContactSensor(mijia door and window sensors) 26 | * @param {*device id} sid 27 | * @param {*device voltage} voltage 28 | * @param {*device status} status 29 | */ 30 | setContactSensor(sid, voltage, status) { 31 | let uuid = UUIDGen.generate('Mijia-ContactSensor@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia ContactSensor") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.ContactSensor(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.ContactSensor); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'magnet'; 54 | if (status == 'closed') { 55 | service.getCharacteristic(Characteristic.ContactSensorState).updateValue(Characteristic.ContactSensorState.CONTACT_DETECTED); 56 | } else { 57 | service.getCharacteristic(Characteristic.ContactSensorState).updateValue(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED); 58 | } 59 | this.setBatteryService(sid, voltage, accessory); 60 | if (!this.mijia.accessories[uuid]) { 61 | this.mijia.accessories[uuid] = accessory; 62 | this.registerAccessory([accessory]); 63 | } 64 | return accessory; 65 | } 66 | } 67 | module.exports = Magnet; -------------------------------------------------------------------------------- /kit/mijia/temperature.v1.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen, CommunityTypes; 3 | class TemperatureV1 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | CommunityTypes = mijia.CommunityTypes; 12 | } 13 | /** 14 | * parse the gateway json msg 15 | * @param {*json} json 16 | * @param {*remoteinfo} rinfo 17 | */ 18 | parseMsg(json, rinfo) { 19 | let { cmd, model, sid } = json; 20 | let data = JSON.parse(json.data); 21 | let { voltage, temperature } = data; 22 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} temperature->${temperature}`); 23 | if (temperature != undefined) { 24 | this.setTemperatureSensor(sid, voltage, temperature); 25 | } 26 | } 27 | /** 28 | * set up TemperatureSensor(mijia temperature and humidity sensor) 29 | * @param {*device id} sid 30 | * @param {*device voltage} voltage 31 | * @param {*device temperature} temperature 32 | */ 33 | setTemperatureSensor(sid, voltage, temperature) { 34 | let uuid = UUIDGen.generate('Aqara-TemperatureSensor@' + sid); 35 | let accessory = this.mijia.accessories[uuid]; 36 | let service; 37 | if (!accessory) { 38 | //init a new homekit accessory 39 | let name = sid.substring(sid.length - 4); 40 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 41 | accessory.getService(Service.AccessoryInformation) 42 | .setCharacteristic(Characteristic.Manufacturer, "Aqara") 43 | .setCharacteristic(Characteristic.Model, "Aqara TemperatureSensor") 44 | .setCharacteristic(Characteristic.SerialNumber, sid); 45 | accessory.on('identify', function (paired, callback) { 46 | callback(); 47 | }); 48 | service = new Service.TemperatureSensor(name); 49 | accessory.addService(service, name); 50 | accessory.addService(new Service.BatteryService(name), name); 51 | } else { 52 | service = accessory.getService(Service.TemperatureSensor); 53 | } 54 | accessory.reachable = true; 55 | accessory.context.sid = sid; 56 | accessory.context.model = 'weather.v1'; 57 | if (temperature != undefined) { 58 | service.getCharacteristic(Characteristic.CurrentTemperature).updateValue(temperature / 100); 59 | } 60 | this.setBatteryService(sid, voltage, accessory); 61 | if (!this.mijia.accessories[uuid]) { 62 | this.mijia.accessories[uuid] = accessory; 63 | this.registerAccessory([accessory]); 64 | } 65 | return accessory; 66 | } 67 | } 68 | module.exports = TemperatureV1; -------------------------------------------------------------------------------- /kit/mijia/switch.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Switch extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status}`); 22 | this.setSwitch(sid, voltage, status) 23 | } 24 | /** 25 | * set up Switch(mijia Switch) 26 | * @param {*device id} sid 27 | * @param {*device voltage} voltage 28 | * @param {*device status} status 29 | */ 30 | setSwitch(sid, voltage, status) { 31 | let uuid = UUIDGen.generate('Mijia-Switch@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.PROGRAMMABLE_SWITCH); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia Switch") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.StatelessProgrammableSwitch(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.StatelessProgrammableSwitch); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'switch'; 54 | if (status != undefined) { 55 | var event = service.getCharacteristic(Characteristic.ProgrammableSwitchEvent); 56 | if (status == 'click') { 57 | event.updateValue(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); //0 58 | } else if (status == 'double_click') { 59 | event.updateValue(Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS); //1 60 | } 61 | } 62 | this.setBatteryService(sid, voltage, accessory); 63 | if (!this.mijia.accessories[uuid]) { 64 | this.mijia.accessories[uuid] = accessory; 65 | this.registerAccessory([accessory]); 66 | } 67 | return accessory; 68 | } 69 | } 70 | module.exports = Switch; -------------------------------------------------------------------------------- /kit/mijia/sw861.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class SW861 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, channel_0 } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} channel_0->${channel_0}`); 22 | this.setSwitch(sid, voltage, channel_0); 23 | } 24 | /** 25 | * set up Switch(mijia 86sw1) 26 | * @param {*device id} sid 27 | * @param {*device voltage} voltage 28 | * @param {*device channel} channel 29 | */ 30 | setSwitch(sid, voltage, channel) { 31 | let uuid = UUIDGen.generate('Mijia-86SW1@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.PROGRAMMABLE_SWITCH); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia 86SW1") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.StatelessProgrammableSwitch(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.StatelessProgrammableSwitch); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = '86sw1'; 54 | if (channel != undefined) { 55 | var event = service.getCharacteristic(Characteristic.ProgrammableSwitchEvent); 56 | if (status == 'click') { 57 | event.updateValue(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); //0 58 | } else if (status == 'double_click') { 59 | event.updateValue(Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS); //1 60 | } 61 | } 62 | this.setBatteryService(sid, voltage, accessory); 63 | if (!this.mijia.accessories[uuid]) { 64 | this.mijia.accessories[uuid] = accessory; 65 | this.registerAccessory([accessory]); 66 | } 67 | return accessory; 68 | } 69 | } 70 | module.exports = SW861; -------------------------------------------------------------------------------- /kit/mijia/magnet.aq2.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class MagnetAq2 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status}`); 22 | if (status != undefined) { 23 | this.setContactSensor(sid, voltage, status); 24 | } 25 | } 26 | /** 27 | * set up ContactSensor(mijia door and window sensors) 28 | * @param {*device id} sid 29 | * @param {*device voltage} voltage 30 | * @param {*device status} status 31 | */ 32 | setContactSensor(sid, voltage, status) { 33 | let uuid = UUIDGen.generate('Aqara-ContactSensor@' + sid); 34 | let accessory = this.mijia.accessories[uuid]; 35 | let service; 36 | if (!accessory) { 37 | //init a new homekit accessory 38 | let name = sid.substring(sid.length - 4); 39 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 40 | accessory.getService(Service.AccessoryInformation) 41 | .setCharacteristic(Characteristic.Manufacturer, "Aqara") 42 | .setCharacteristic(Characteristic.Model, "Aqara ContactSensor") 43 | .setCharacteristic(Characteristic.SerialNumber, sid); 44 | accessory.on('identify', function (paired, callback) { 45 | callback(); 46 | }); 47 | service = new Service.ContactSensor(name); 48 | accessory.addService(service, name); 49 | accessory.addService(new Service.BatteryService(name), name); 50 | } else { 51 | service = accessory.getService(Service.ContactSensor); 52 | } 53 | accessory.reachable = true; 54 | accessory.context.sid = sid; 55 | accessory.context.model = 'sensor_magnet.aq2'; 56 | if (status == 'closed') { 57 | service.getCharacteristic(Characteristic.ContactSensorState).updateValue(Characteristic.ContactSensorState.CONTACT_DETECTED); 58 | } else { 59 | service.getCharacteristic(Characteristic.ContactSensorState).updateValue(Characteristic.ContactSensorState.CONTACT_NOT_DETECTED); 60 | } 61 | this.setBatteryService(sid, voltage, accessory); 62 | if (!this.mijia.accessories[uuid]) { 63 | this.mijia.accessories[uuid] = accessory; 64 | this.registerAccessory([accessory]); 65 | } 66 | return accessory; 67 | } 68 | } 69 | module.exports = MagnetAq2; -------------------------------------------------------------------------------- /kit/mijia/switch.aq2.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class SwitchAq2 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status}`); 22 | if (status != undefined) { 23 | this.setSwitch(sid, voltage, status); 24 | } 25 | } 26 | /** 27 | * set up Switch(aqara Switch) 28 | * @param {*device id} sid 29 | * @param {*device voltage} voltage 30 | * @param {*device status} status 31 | */ 32 | setSwitch(sid, voltage, status) { 33 | let uuid = UUIDGen.generate('Aqara-Switch@' + sid); 34 | let accessory = this.mijia.accessories[uuid]; 35 | let service; 36 | if (!accessory) { 37 | //init a new homekit accessory 38 | let name = sid.substring(sid.length - 4); 39 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.PROGRAMMABLE_SWITCH); 40 | accessory.getService(Service.AccessoryInformation) 41 | .setCharacteristic(Characteristic.Manufacturer, "Aqara") 42 | .setCharacteristic(Characteristic.Model, "Aqara Switch") 43 | .setCharacteristic(Characteristic.SerialNumber, sid); 44 | accessory.on('identify', function (paired, callback) { 45 | callback(); 46 | }); 47 | service = new Service.StatelessProgrammableSwitch(name); 48 | accessory.addService(service, name); 49 | accessory.addService(new Service.BatteryService(name), name); 50 | } else { 51 | service = accessory.getService(Service.StatelessProgrammableSwitch); 52 | } 53 | accessory.reachable = true; 54 | accessory.context.sid = sid; 55 | accessory.context.model = 'sensor_switch.aq2'; 56 | if (status != undefined) { 57 | var event = service.getCharacteristic(Characteristic.ProgrammableSwitchEvent); 58 | if (status == 'click') { 59 | event.updateValue(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); //0 60 | } else if (status == 'double_click') { 61 | event.updateValue(Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS); //1 62 | } 63 | } 64 | this.setBatteryService(sid, voltage, accessory); 65 | if (!this.mijia.accessories[uuid]) { 66 | this.mijia.accessories[uuid] = accessory; 67 | this.registerAccessory([accessory]); 68 | } 69 | return accessory; 70 | } 71 | } 72 | module.exports = SwitchAq2; -------------------------------------------------------------------------------- /kit/mijia/ctrlln1.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class CtrlLN1 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { channel_0 } = data; 21 | this.mijia.log.debug(`${model} ${cmd} channel_0->${channel_0}`); 22 | this.setSwitch(sid, channel_0); 23 | } 24 | /** 25 | * set up Switch(mijia CtrlNeutral1) 26 | * @param {*device id} sid 27 | * @param {*device channel} channel 28 | */ 29 | setSwitch(sid, channel) { 30 | let uuid = UUIDGen.generate('Mijia-CtrlLN1@' + sid); 31 | let accessory = this.mijia.accessories[uuid]; 32 | let service; 33 | if (!accessory) { 34 | //init a new homekit accessory 35 | let name = sid.substring(sid.length - 4); 36 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SWITCH); 37 | accessory.getService(Service.AccessoryInformation) 38 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 39 | .setCharacteristic(Characteristic.Model, "Mijia CtrlLN1") 40 | .setCharacteristic(Characteristic.SerialNumber, sid); 41 | accessory.on('identify', function (paired, callback) { 42 | callback(); 43 | }); 44 | service = new Service.Switch(name); 45 | accessory.addService(service, name); 46 | } else { 47 | service = accessory.getService(Service.Switch); 48 | } 49 | accessory.reachable = true; 50 | accessory.context.sid = sid; 51 | accessory.context.model = 'ctrl_ln1'; 52 | if (channel != undefined) { 53 | var event = service.getCharacteristic(Characteristic.On); 54 | if (channel == 'on') { 55 | event.updateValue(true); 56 | } else { 57 | event.updateValue(false); 58 | } 59 | } 60 | //bind set event if not set 61 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 62 | if (!setter || setter.length == 0) { 63 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 64 | let data = { channel_0: value ? 'on' : 'off' }; 65 | data.key = this.mijia.generateKey(sid); 66 | let cmd = { cmd: 'write', model: 'ctrl_ln1', sid: sid, data: JSON.stringify(data) }; 67 | this.mijia.sendMsgToSid(cmd, sid); 68 | callback(); 69 | }); 70 | } 71 | if (!this.mijia.accessories[uuid]) { 72 | this.mijia.accessories[uuid] = accessory; 73 | this.registerAccessory([accessory]); 74 | } 75 | return accessory; 76 | } 77 | } 78 | module.exports = CtrlLN1; -------------------------------------------------------------------------------- /kit/mijia/ctrlneutral1.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class CtrlNeutral1 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { channel_0 } = data; 21 | this.mijia.log.debug(`${model} ${cmd} channel_0->${channel_0}`); 22 | this.setSwitch(sid, channel_0); 23 | } 24 | /** 25 | * set up Switch(mijia CtrlNeutral1) 26 | * @param {*device id} sid 27 | * @param {*device channel} channel 28 | */ 29 | setSwitch(sid, channel) { 30 | let uuid = UUIDGen.generate('Mijia-CtrlNeutral1@' + sid); 31 | let accessory = this.mijia.accessories[uuid]; 32 | let service; 33 | if (!accessory) { 34 | //init a new homekit accessory 35 | let name = sid.substring(sid.length - 4); 36 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SWITCH); 37 | accessory.getService(Service.AccessoryInformation) 38 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 39 | .setCharacteristic(Characteristic.Model, "Mijia CtrlNeutral1") 40 | .setCharacteristic(Characteristic.SerialNumber, sid); 41 | accessory.on('identify', function (paired, callback) { 42 | callback(); 43 | }); 44 | service = new Service.Switch(name); 45 | accessory.addService(service, name); 46 | } else { 47 | service = accessory.getService(Service.Switch); 48 | } 49 | accessory.reachable = true; 50 | accessory.context.sid = sid; 51 | accessory.context.model = 'ctrl_neutral1'; 52 | if (channel != undefined) { 53 | var event = service.getCharacteristic(Characteristic.On); 54 | if (channel == 'on') { 55 | event.updateValue(true); 56 | } else { 57 | event.updateValue(false); 58 | } 59 | } 60 | //bind set event if not set 61 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 62 | if (!setter || setter.length == 0) { 63 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 64 | let data = { channel_0: value ? 'on' : 'off' }; 65 | data.key = this.mijia.generateKey(sid); 66 | let cmd = { cmd: 'write', model: 'ctrl_neutral1', sid: sid, data: JSON.stringify(data) }; 67 | this.mijia.sendMsgToSid(cmd, sid); 68 | callback(); 69 | }); 70 | } 71 | if (!this.mijia.accessories[uuid]) { 72 | this.mijia.accessories[uuid] = accessory; 73 | this.registerAccessory([accessory]); 74 | } 75 | return accessory; 76 | } 77 | } 78 | module.exports = CtrlNeutral1; -------------------------------------------------------------------------------- /kit/mijia/ctrlln2.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class CtrlLN2 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { channel_0, channel_1 } = data; 21 | this.mijia.log.debug(`${model} ${cmd} channel_0->${channel_0} channel_1->${channel_1}`); 22 | this.setSwitch(sid, channel_0, 0); 23 | this.setSwitch(sid, channel_1, 1); 24 | } 25 | /** 26 | * set up Switch(mijia CtrlNeutral2) 27 | * @param {*device id} sid 28 | * @param {*device channel} channel 29 | * @param {*device index} index 30 | */ 31 | setSwitch(sid, channel, index) { 32 | let uuid = UUIDGen.generate('Mijia-CtrlLN2_' + index + '@' + sid); 33 | let accessory = this.mijia.accessories[uuid]; 34 | let service; 35 | if (!accessory) { 36 | //init a new homekit accessory 37 | let name = sid.substring(sid.length - 4) + '_' + (index + 1); 38 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SWITCH); 39 | accessory.getService(Service.AccessoryInformation) 40 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 41 | .setCharacteristic(Characteristic.Model, "Mijia CtrlLN2") 42 | .setCharacteristic(Characteristic.SerialNumber, sid); 43 | accessory.on('identify', function (paired, callback) { 44 | callback(); 45 | }); 46 | service = new Service.Switch(name); 47 | accessory.addService(service, name); 48 | } else { 49 | service = accessory.getService(Service.Switch); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'ctrl_ln2'; 54 | if (channel != undefined) { 55 | var event = service.getCharacteristic(Characteristic.On); 56 | if (channel == 'on') { 57 | event.updateValue(true); 58 | } else { 59 | event.updateValue(false); 60 | } 61 | } 62 | //bind set event if not set 63 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 64 | if (!setter || setter.length == 0) { 65 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 66 | let key = 'channel_' + index; 67 | let data = { key: value ? 'on' : 'off' }; 68 | data.key = this.mijia.generateKey(sid); 69 | let cmd = { cmd: 'write', model: 'ctrl_ln2', sid: sid, data: JSON.stringify(data) }; 70 | this.mijia.sendMsgToSid(cmd, sid); 71 | callback(); 72 | }); 73 | } 74 | if (!this.mijia.accessories[uuid]) { 75 | this.mijia.accessories[uuid] = accessory; 76 | this.registerAccessory([accessory]); 77 | } 78 | return accessory; 79 | } 80 | } 81 | module.exports = CtrlLN2; -------------------------------------------------------------------------------- /kit/mijia/base.js: -------------------------------------------------------------------------------- 1 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 2 | class Base { 3 | constructor(mijia) { 4 | this.mijia = mijia; 5 | PlatformAccessory = mijia.PlatformAccessory; 6 | Accessory = mijia.Accessory; 7 | Service = mijia.Service; 8 | Characteristic = mijia.Characteristic; 9 | UUIDGen = mijia.UUIDGen; 10 | } 11 | /** 12 | * return true if the zigbee devices battery level is low 13 | * @param {*voltage} voltage 14 | */ 15 | isBatteryLow(voltage) { 16 | return isNaN(voltage) ? true : (voltage > 2800 ? false : true); 17 | } 18 | /** 19 | * return the devices battery level in homekit 20 | * @param {*} voltage 21 | */ 22 | getBatteryLevel(voltage) { 23 | return isNaN(voltage) ? 0 : ((voltage - 2800) / 400 * 100); 24 | } 25 | /** 26 | * setup 27 | * @param {*device id} sid 28 | * @param {*device voltage} voltage 29 | * @param {*device homekit accessory} accessory 30 | */ 31 | setBatteryService(sid, voltage, accessory) { 32 | let service = accessory.getService(Service.BatteryService); 33 | if (voltage != undefined && service != undefined) { 34 | let isBatteryLow = this.isBatteryLow(voltage); 35 | let batteryLevel = this.getBatteryLevel(voltage); 36 | service.getCharacteristic(Characteristic.StatusLowBattery).updateValue(isBatteryLow); 37 | service.getCharacteristic(Characteristic.BatteryLevel).updateValue(batteryLevel); 38 | service.getCharacteristic(Characteristic.ChargingState).updateValue(false); 39 | } 40 | } 41 | /** 42 | * setup 43 | * @param {*device id} sid 44 | * @param {*device batteryLevel} batteryLevel 45 | * @param {*device isBatteryLow} isBatteryLow 46 | * @param {*device chargingState} chargingState 47 | * @param {*device homekit accessory} accessory 48 | */ 49 | setBatteryServiceV2(sid, batteryLevel, isBatteryLow, chargingState, accessory) { 50 | let service = accessory.getService(Service.BatteryService); 51 | if (batteryLevel != undefined) { 52 | service.getCharacteristic(Characteristic.BatteryLevel).updateValue(batteryLevel); 53 | } 54 | if (isBatteryLow != undefined) { 55 | service.getCharacteristic(Characteristic.StatusLowBattery).updateValue(isBatteryLow); 56 | } 57 | if (chargingState != undefined) { 58 | service.getCharacteristic(Characteristic.ChargingState).updateValue(chargingState); 59 | } 60 | } 61 | /** 62 | * registry accessories to homekit 63 | * @param {*accessories} accessories 64 | */ 65 | registerAccessory(accessories) { 66 | this.mijia.api.registerPlatformAccessories("homebridge-smarthome", "smarthome-mijia", accessories); 67 | } 68 | /** 69 | * unregistry accessories to homekit 70 | * @param {*accessories} accessories 71 | */ 72 | unregisterAccessory(accessories) { 73 | this.mijia.api.unregisterPlatformAccessories("homebridge-smarthome", "smarthome-mijia", accessories); 74 | } 75 | /** 76 | * parse msg receive from gateway 77 | * @param {*} msg 78 | * @param {*} rinfo 79 | */ 80 | parseMsg(msg, rinfo) { 81 | this.mijia.log.warn('base device parseMsg -> %s', JSON.parse(msg)); 82 | } 83 | } 84 | 85 | module.exports = Base; -------------------------------------------------------------------------------- /kit/broadlink.js: -------------------------------------------------------------------------------- 1 | var PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 2 | var _homebridge; 3 | const winston = require('winston'); 4 | const util = require('util'); 5 | const broadlink_lib = require('./broadlink/'); 6 | class Broadlink { 7 | // config may be null 8 | // api may be null if launched from old homebridge version 9 | constructor(log, config, api) { 10 | this.PlatformAccessory = PlatformAccessory; 11 | this.Accessory = Accessory; 12 | this.Service = Service; 13 | this.Characteristic = Characteristic; 14 | this.UUIDGen = UUIDGen; 15 | this.log = log; 16 | this.config = config; 17 | this.devices = {}; 18 | this.accessories = {}; 19 | if (api) { 20 | // Save the API object as plugin needs to register new accessory via this object. 21 | this.api = api; 22 | } else { 23 | this.log.error("Homebridge's version is too old, please upgrade!"); 24 | } 25 | if (this.api) { 26 | this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this)); 27 | } 28 | if (_homebridge.broadlink != undefined) { 29 | _homebridge.broadlink = this; 30 | } 31 | this.log.debug('broadlink constructor done'); 32 | } 33 | static init(homebridge) { 34 | return new Promise((resolve, reject) => { 35 | homebridge.registerPlatform("homebridge-smarthome", "smarthome-broadlink", Broadlink, true); 36 | resolve(); 37 | }); 38 | } 39 | /** 40 | * configure cached accessory 41 | * @param {*} accessory 42 | */ 43 | configureAccessory(accessory) { 44 | accessory.reachable = true; 45 | accessory.on('identify', (paired, callback) => { 46 | this.log.debug(accessory.displayName + " -> Identify!!!"); 47 | callback(); 48 | }); 49 | if (!this.accessories[accessory.UUID]) { 50 | this.accessories[accessory.UUID] = accessory; 51 | } 52 | } 53 | didFinishLaunching() { 54 | let { broadlink } = this.config; 55 | if (broadlink != undefined) { 56 | let { devices } = broadlink; 57 | if (devices && devices.length > 0) { //for wifi devices 58 | devices.map((device) => { 59 | let { name, type, mac, ip } = device; 60 | if (type == 'MP1' || type == 'MP2') { 61 | for (let i = 1; i <= 4; i++) { //mp1 and mp2(two usb devices) have 4 plug 62 | let mp = new broadlink_lib.MP(this); 63 | let deviceCfg = Object.assign({}, device); 64 | deviceCfg.name = name + '@' + i; 65 | mp.init(deviceCfg); 66 | this.log.debug('init broadlink mp device ->' + deviceCfg.name); 67 | } 68 | } else { 69 | this.log.warn('unsupported broadlink device ' + util.inspect(device)); 70 | } 71 | }); 72 | } 73 | } 74 | } 75 | } 76 | 77 | 78 | module.exports = (homebridge) => { 79 | //export some properties from homebridge 80 | PlatformAccessory = homebridge.platformAccessory; 81 | Accessory = homebridge.hap.Accessory; 82 | Service = homebridge.hap.Service; 83 | Characteristic = homebridge.hap.Characteristic; 84 | UUIDGen = homebridge.hap.uuid; 85 | _homebridge = homebridge; 86 | //init mikit 87 | return Broadlink.init(homebridge); 88 | } -------------------------------------------------------------------------------- /kit/mijia/ctrlneutral2.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class CtrlNeutral2 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { channel_0, channel_1 } = data; 21 | this.mijia.log.debug(`${model} ${cmd} channel_0->${channel_0} channel_1->${channel_1}`); 22 | this.setSwitch(sid, channel_0, 0); 23 | this.setSwitch(sid, channel_1, 1); 24 | } 25 | /** 26 | * set up Switch(mijia CtrlNeutral2) 27 | * @param {*device id} sid 28 | * @param {*device channel} channel 29 | * @param {*device index} index 30 | */ 31 | setSwitch(sid, channel, index) { 32 | let uuid = UUIDGen.generate('Mijia-CtrlNeutral2_' + index + '@' + sid); 33 | let accessory = this.mijia.accessories[uuid]; 34 | let service; 35 | if (!accessory) { 36 | //init a new homekit accessory 37 | let name = sid.substring(sid.length - 4) + '_' + (index + 1); 38 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SWITCH); 39 | accessory.getService(Service.AccessoryInformation) 40 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 41 | .setCharacteristic(Characteristic.Model, "Mijia CtrlNeutral2") 42 | .setCharacteristic(Characteristic.SerialNumber, sid); 43 | accessory.on('identify', function (paired, callback) { 44 | callback(); 45 | }); 46 | service = new Service.Switch(name); 47 | accessory.addService(service, name); 48 | } else { 49 | service = accessory.getService(Service.Switch); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'ctrl_neutral2'; 54 | if (channel != undefined) { 55 | var event = service.getCharacteristic(Characteristic.On); 56 | if (channel == 'on') { 57 | event.updateValue(true); 58 | } else { 59 | event.updateValue(false); 60 | } 61 | } 62 | //bind set event if not set 63 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 64 | if (!setter || setter.length == 0) { 65 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 66 | let key = 'channel_' + index; 67 | let data = { key: value ? 'on' : 'off' }; 68 | data.key = this.mijia.generateKey(sid); 69 | let cmd = { cmd: 'write', model: 'ctrl_neutral2', sid: sid, data: JSON.stringify(data) }; 70 | this.mijia.sendMsgToSid(cmd, sid); 71 | callback(); 72 | }); 73 | } 74 | if (!this.mijia.accessories[uuid]) { 75 | this.mijia.accessories[uuid] = accessory; 76 | this.registerAccessory([accessory]); 77 | } 78 | return accessory; 79 | } 80 | } 81 | module.exports = CtrlNeutral2; -------------------------------------------------------------------------------- /kit/mijia/sw862.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class SW862 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, channel_0, channel_1, dual_channel } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} channel_0->${channel_0} channel_1->${channel_1} dual_channel->${dual_channel}`); 22 | this.setSwitch(sid, voltage, channel_0, 0); 23 | this.setSwitch(sid, voltage, channel_1, 1); 24 | this.setSwitch(sid, voltage, dual_channel, 2); 25 | } 26 | /** 27 | * set up Switch(mijia 86sw2) 28 | * @param {*device id} sid 29 | * @param {*device voltage} voltage 30 | * @param {*device channel} channel 31 | * @param {*device index} index 32 | */ 33 | setSwitch(sid, voltage, channel, index) { 34 | let uuid = UUIDGen.generate('Mijia-86SW2_' + index + '@' + sid); 35 | let accessory = this.mijia.accessories[uuid]; 36 | let service; 37 | if (!accessory) { 38 | //init a new homekit accessory 39 | let name = sid.substring(sid.length - 4) + '_' + index; 40 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.PROGRAMMABLE_SWITCH); 41 | accessory.getService(Service.AccessoryInformation) 42 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 43 | .setCharacteristic(Characteristic.Model, "Mijia 86SW2") 44 | .setCharacteristic(Characteristic.SerialNumber, sid); 45 | accessory.on('identify', function (paired, callback) { 46 | callback(); 47 | }); 48 | service = new Service.StatelessProgrammableSwitch(name); 49 | accessory.addService(service, name); 50 | accessory.addService(new Service.BatteryService(name), name); 51 | } else { 52 | service = accessory.getService(Service.StatelessProgrammableSwitch); 53 | } 54 | accessory.reachable = true; 55 | accessory.context.sid = sid; 56 | accessory.context.model = '86sw2'; 57 | if (channel != undefined) { 58 | var event = service.getCharacteristic(Characteristic.ProgrammableSwitchEvent); 59 | if (status == 'click') { 60 | event.updateValue(Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS); //0 61 | } else if (status == 'double_click') { 62 | event.updateValue(Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS); //1 63 | } else if (status == 'both_click') { 64 | event.updateValue(Characteristic.ProgrammableSwitchEvent.LONG_PRESS); //2 65 | } 66 | } 67 | this.setBatteryService(sid, voltage, accessory); 68 | if (!this.mijia.accessories[uuid]) { 69 | this.mijia.accessories[uuid] = accessory; 70 | this.registerAccessory([accessory]); 71 | } 72 | return accessory; 73 | } 74 | } 75 | module.exports = SW862; -------------------------------------------------------------------------------- /kit/mijia/index.js: -------------------------------------------------------------------------------- 1 | const Gateway = require('./gateway'); 2 | const Humidity = require('./humidity'); 3 | const Temperature = require('./temperature'); 4 | const Magnet = require('./magnet'); 5 | const Motion = require('./motion'); 6 | const Switch = require('./switch'); 7 | const Plug = require('./plug'); 8 | const CtrlLN1 = require('./ctrlln1'); 9 | const CtrlLN2 = require('./ctrlln2'); 10 | const CtrlNeutral1 = require('./ctrlneutral1'); 11 | const CtrlNeutral2 = require('./ctrlneutral2'); 12 | const SW861 = require('./sw861'); 13 | const SW862 = require('./sw862'); 14 | const Plug86 = require('./plug86'); 15 | const Natgas = require('./natgas'); 16 | const Smoke = require('./smoke'); 17 | const Curtain = require('./curtain'); 18 | //aqara 19 | const MotionAq2 = require('./motion.aq2'); 20 | const MagnetAq2 = require('./magnet.aq2'); 21 | const SwitchAq2 = require('./switch.aq2'); 22 | const HumidityV1 = require('./humidity.v1'); 23 | const TemperatureV1 = require('./temperature.v1'); 24 | const PressureV1 = require('./pressure.v1'); 25 | //wifi device 26 | const AirPurifier = require('./airpurifier'); 27 | const Vacuum = require('./vacuum'); 28 | const PowerPlug = require('./powerplug'); 29 | const PowerStrip = require('./powerstrip'); 30 | const Yeelight = require('./yeelight'); 31 | module.exports = (mijia) => { 32 | let devices = {}; 33 | devices.gateway = new Gateway(mijia); 34 | let humidity = new Humidity(mijia); 35 | let temperature = new Temperature(mijia); 36 | devices.sensor_ht = { 37 | parseMsg: (json, rinfo) => { 38 | humidity.parseMsg(json, rinfo); 39 | temperature.parseMsg(json, rinfo); 40 | } 41 | }; 42 | devices.magnet = new Magnet(mijia); 43 | devices.motion = new Motion(mijia); 44 | devices.switch = new Switch(mijia); 45 | devices.plug = new Plug(mijia); 46 | devices.ctrl_neutral1 = new CtrlNeutral1(mijia); 47 | devices.ctrl_neutral2 = new CtrlNeutral2(mijia); 48 | devices.ctrl_ln1 = new CtrlLN1(mijia); 49 | devices.ctrl_ln2 = new CtrlLN2(mijia); 50 | devices['86sw1'] = new SW861(mijia); 51 | devices['86sw2'] = new SW862(mijia); 52 | devices['86plug'] = new Plug86(mijia); 53 | devices.natgas = new Natgas(mijia); 54 | devices.smoke = new Smoke(mijia); 55 | devices.curtain = new Curtain(mijia); 56 | // wifi device 57 | devices['air-purifier'] = (mijia, config) => { 58 | new AirPurifier(mijia, config); 59 | }; 60 | devices['vacuum'] = (mijia, config) => { 61 | new Vacuum(mijia, config); 62 | }; 63 | devices['power-plug'] = (mijia, config) => { 64 | new PowerPlug(mijia, config); 65 | }; 66 | devices['power-strip'] = (mijia, config) => { 67 | new PowerStrip(mijia, config); 68 | }; 69 | devices['light'] = (mijia, config) => { 70 | new Yeelight(mijia, config); 71 | } 72 | //aqara 73 | devices['sensor_magnet.aq2'] = new MagnetAq2(mijia); 74 | devices['sensor_motion.aq2'] = new MotionAq2(mijia); 75 | devices['sensor_switch.aq2'] = new SwitchAq2(mijia); 76 | let humidityV1 = new HumidityV1(mijia); 77 | let temperatureV1 = new TemperatureV1(mijia); 78 | devices['weather.v1'] = { 79 | parseMsg: (json, rinfo) => { 80 | humidityV1.parseMsg(json, rinfo); 81 | temperatureV1.parseMsg(json, rinfo); 82 | PressureV1.parseMsg(json, rinfo); 83 | } 84 | } 85 | return devices; 86 | }; -------------------------------------------------------------------------------- /kit/mijia/plug86.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Plug86 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status, inuse, power_consumed, load_power } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status} inuse->${inuse} power_consumed->${power_consumed} load_power->${load_power}`); 22 | this.setOutlet(sid, voltage, status, inuse, power_consumed, load_power); 23 | } 24 | /** 25 | * set up Outlet(mijia zigbee Plug) 26 | * @param {* sid} sid 27 | * @param {* voltage} voltage 28 | * @param {* status} status 29 | * @param {* inuse} inuse 30 | * @param {* power_consumed} power_consumed 31 | * @param {* load_power} load_power 32 | */ 33 | setOutlet(sid, voltage, status, inuse, power_consumed, load_power) { 34 | let uuid = UUIDGen.generate('Mijia-86Plug@' + sid); 35 | let accessory = this.mijia.accessories[uuid]; 36 | let service; 37 | if (!accessory) { 38 | //init a new homekit accessory 39 | let name = sid.substring(sid.length - 4); 40 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.OUTLET); 41 | accessory.getService(Service.AccessoryInformation) 42 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 43 | .setCharacteristic(Characteristic.Model, "Mijia 86Plug") 44 | .setCharacteristic(Characteristic.SerialNumber, sid); 45 | accessory.on('identify', function (paired, callback) { 46 | callback(); 47 | }); 48 | service = new Service.Outlet(name); 49 | accessory.addService(service, name); 50 | } else { 51 | service = accessory.getService(Service.Outlet); 52 | } 53 | accessory.reachable = true; 54 | accessory.context.sid = sid; 55 | accessory.context.model = '86plug'; 56 | let use_state = false, on_state = false; 57 | if (status == 'on') { 58 | on_state = true; 59 | if (inuse) { 60 | use_state = true; 61 | } 62 | } 63 | service.getCharacteristic(Characteristic.On).updateValue(on_state); 64 | service.getCharacteristic(Characteristic.OutletInUse).updateValue(use_state); 65 | //bind set event if not set 66 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 67 | if (!setter || setter.length == 0) { 68 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 69 | let data = { status: value ? 'on' : 'off' }; 70 | data.key = this.mijia.generateKey(sid); 71 | let cmd = { cmd: 'write', model: '86plug', sid: sid, data: JSON.stringify(data) }; 72 | this.mijia.sendMsgToSid(cmd, sid); 73 | callback(); 74 | }); 75 | } 76 | if (!this.mijia.accessories[uuid]) { 77 | this.mijia.accessories[uuid] = accessory; 78 | this.registerAccessory([accessory]); 79 | } 80 | return accessory; 81 | } 82 | } 83 | module.exports = Plug86; -------------------------------------------------------------------------------- /kit/mijia/natgas.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Natgas extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, alarm } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} alarm->${alarm}`); 22 | this.setSmokeSensor(sid, voltage, alarm); 23 | } 24 | /** 25 | * set up NatgasSensor(mijia natgas sensors) 26 | * @param {*device id} sid 27 | * @param {*device voltage} voltage 28 | * @param {*device alarm} alarm 29 | */ 30 | setSmokeSensor(sid, voltage, alarm) { 31 | let uuid = UUIDGen.generate('Mijia-NatgasSensor@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia NatgasSensor") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.SmokeSensor(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.SmokeSensor); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'natgas'; 54 | if (alarm == 0) { //Release alarm 55 | service.getCharacteristic(Characteristic.SmokeDetected).updateValue(false); 56 | service.getCharacteristic(Characteristic.StatusActive).updateValue(true); 57 | service.getCharacteristic(Characteristic.StatusFault).updateValue(Characteristic.StatusFault.NO_FAULT); 58 | } else if (alarm == 1 || alarm == 2) { // 1: Fire alarm 2: Analog alarm 59 | service.getCharacteristic(Characteristic.SmokeDetected).updateValue(true); 60 | service.getCharacteristic(Characteristic.StatusActive).updateValue(true); 61 | service.getCharacteristic(Characteristic.StatusFault).updateValue(Characteristic.StatusFault.NO_FAULT); 62 | } else if (alarm == 64 || alarm == 32768) { //64: Sensitivity fault alarm 32768: IIC communication failure 63 | service.getCharacteristic(Characteristic.SmokeDetected).updateValue(false); 64 | service.getCharacteristic(Characteristic.StatusActive).updateValue(false); 65 | service.getCharacteristic(Characteristic.StatusFault).updateValue(Characteristic.StatusFault.GENERAL_FAULT); 66 | } 67 | this.setBatteryService(sid, voltage, accessory); 68 | if (!this.mijia.accessories[uuid]) { 69 | this.mijia.accessories[uuid] = accessory; 70 | this.registerAccessory([accessory]); 71 | } 72 | return accessory; 73 | } 74 | } 75 | module.exports = Natgas; -------------------------------------------------------------------------------- /kit/mijia/smoke.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Smoke extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, alarm } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} alarm->${alarm}`); 22 | this.setSmokeSensor(sid, voltage, alarm); 23 | } 24 | /** 25 | * set up SmokeSensor(mijia smoke sensors) 26 | * @param {*device id} sid 27 | * @param {*device voltage} voltage 28 | * @param {*device alarm} alarm 29 | */ 30 | setSmokeSensor(sid, voltage, alarm) { 31 | let uuid = UUIDGen.generate('Mijia-SmokeSensor@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia SmokeSensor") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.SmokeSensor(name); 46 | accessory.addService(service, name); 47 | accessory.addService(new Service.BatteryService(name), name); 48 | } else { 49 | service = accessory.getService(Service.SmokeSensor); 50 | } 51 | accessory.reachable = true; 52 | accessory.context.sid = sid; 53 | accessory.context.model = 'smoke'; 54 | if (alarm == 0) { // 0:Release alarm 55 | service.getCharacteristic(Characteristic.SmokeDetected).updateValue(false); 56 | service.getCharacteristic(Characteristic.StatusActive).updateValue(true); 57 | service.getCharacteristic(Characteristic.StatusFault).updateValue(Characteristic.StatusFault.NO_FAULT); 58 | } else if (alarm == 1 || alarm == 2) { // 1: Fire alarm 2: Analog alarm 59 | service.getCharacteristic(Characteristic.SmokeDetected).updateValue(true); 60 | service.getCharacteristic(Characteristic.StatusActive).updateValue(true); 61 | service.getCharacteristic(Characteristic.StatusFault).updateValue(Characteristic.StatusFault.NO_FAULT); 62 | } else if (alarm == 8 || alarm == 64 || alarm == 32768) { // 8: Battery fault alarm 64: Sensitivity fault alarm 32768: IIC communication failure 63 | service.getCharacteristic(Characteristic.SmokeDetected).updateValue(false); 64 | service.getCharacteristic(Characteristic.StatusActive).updateValue(false); 65 | service.getCharacteristic(Characteristic.StatusFault).updateValue(Characteristic.StatusFault.GENERAL_FAULT); 66 | } 67 | this.setBatteryService(sid, voltage, accessory); 68 | if (!this.mijia.accessories[uuid]) { 69 | this.mijia.accessories[uuid] = accessory; 70 | this.registerAccessory([accessory]); 71 | } 72 | return accessory; 73 | } 74 | } 75 | module.exports = Smoke; -------------------------------------------------------------------------------- /kit/mijia/powerstrip.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | const miio = require('miio'); 3 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 4 | class PowerStrip extends Base { 5 | constructor(mijia, config) { 6 | super(mijia); 7 | this.config = config; 8 | this.model = config.model; 9 | this.devices = {}; //all airpurifier devices 10 | PlatformAccessory = mijia.PlatformAccessory; 11 | Accessory = mijia.Accessory; 12 | Service = mijia.Service; 13 | Characteristic = mijia.Characteristic; 14 | UUIDGen = mijia.UUIDGen; 15 | this.discover(); 16 | } 17 | setPowerStrip(config, channel, device) { 18 | let sid = config.id; 19 | let model = device.model; 20 | let uuid = UUIDGen.generate('Mijia-PowerStrip@' + sid) 21 | let accessory = this.mijia.accessories[uuid]; 22 | let service; 23 | if (!accessory) { 24 | let name = sid; 25 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.FAN); 26 | accessory.getService(Service.AccessoryInformation) 27 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 28 | .setCharacteristic(Characteristic.Model, "Mijia PowerStrip") 29 | .setCharacteristic(Characteristic.SerialNumber, sid); 30 | accessory.on('identify', function (paired, callback) { 31 | callback(); 32 | }); 33 | service = new Service.Outlet(name); // outlet 34 | accessory.addService(service, name); 35 | } else { 36 | service = accessory.getService(Service.Outlet); 37 | } 38 | accessory.reachable = true; 39 | accessory.context.sid = sid; 40 | accessory.context.model = this.model; 41 | //bind 42 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 43 | if (!setter || setter.length == 0) { 44 | //service 45 | service.getCharacteristic(Characteristic.On).on('get', (callback) => { 46 | let device = this.devices[sid]; 47 | let status = false; 48 | if (device != undefined) { 49 | status = device.power; 50 | 51 | } 52 | callback(null, status); 53 | }).on('set', (value, callback) => { 54 | let device = this.devices[sid]; 55 | if (device != undefined && value) { 56 | device.setPower(channel, value ? true : false); 57 | } 58 | callback(null, value); 59 | }); 60 | } 61 | if (!this.mijia.accessories[uuid]) { 62 | this.mijia.accessories[uuid] = accessory; 63 | this.registerAccessory([accessory]); 64 | } 65 | } 66 | 67 | discover() { 68 | this.mijia.log.debug('try to discover ' + this.model); 69 | let browser = miio.browse(); //require a new browse 70 | browser.on('available', (reg) => { 71 | if (!reg.token) { //power strip support Auto-token 72 | return; 73 | } 74 | miio.device(reg).then((device) => { 75 | if (device.type != this.model) { 76 | return; 77 | } 78 | this.devices[reg.id] = device; 79 | this.mijia.log.debug('find model->%s with hostname->%s id->%s @ %s:%s.', device.model, reg.hostname, device.id, device.address, device.port); 80 | this.setPowerStrip(reg, 0, device); 81 | }); 82 | }); 83 | 84 | browser.on('unavailable', (reg) => { 85 | if (!reg.token) { // support Auto-token 86 | return; 87 | } 88 | if (this.devices[reg.id] != undefined) { 89 | this.devices[reg.id].destroy(); 90 | delete this.devices[reg.id]; 91 | } 92 | }); 93 | } 94 | 95 | } 96 | 97 | module.exports = PowerStrip; -------------------------------------------------------------------------------- /kit/mijia/plug.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Plug extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status, inuse, power_consumed, load_power } = data; 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status} inuse->${inuse} power_consumed->${power_consumed} load_power->${load_power}`); 22 | this.setOutlet(sid, voltage, status, inuse, power_consumed, load_power); 23 | } 24 | /** 25 | * set up Outlet(mijia zigbee Plug) 26 | * @param {* sid} sid 27 | * @param {* voltage} voltage 28 | * @param {* status} status 29 | * @param {* inuse} inuse 30 | * @param {* power_consumed} power_consumed 31 | * @param {* load_power} load_power 32 | */ 33 | setOutlet(sid, voltage, status, inuse, power_consumed, load_power) { 34 | let uuid = UUIDGen.generate('Mijia-Plug@' + sid); 35 | let accessory = this.mijia.accessories[uuid]; 36 | let service; 37 | if (status == 'unknown' && accessory != undefined) { 38 | this.unregisterAccessory([accessory]); 39 | delete this.mijia.accessories[uuid]; 40 | this.mijia.log.warn('plug status unknown,unregisterAccessory ->%s', sid); 41 | return; 42 | } 43 | if (!accessory) { 44 | //init a new homekit accessory 45 | let name = sid.substring(sid.length - 4); 46 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.OUTLET); 47 | accessory.getService(Service.AccessoryInformation) 48 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 49 | .setCharacteristic(Characteristic.Model, "Mijia Plug") 50 | .setCharacteristic(Characteristic.SerialNumber, sid); 51 | accessory.on('identify', function (paired, callback) { 52 | callback(); 53 | }); 54 | service = new Service.Outlet(name); 55 | accessory.addService(service, name); 56 | } else { 57 | service = accessory.getService(Service.Outlet); 58 | } 59 | accessory.reachable = true; 60 | accessory.context.sid = sid; 61 | accessory.context.model = 'plug'; 62 | let use_state = false, on_state = false; 63 | if (status == 'on') { 64 | on_state = true; 65 | if (inuse == '1') { 66 | use_state = true; 67 | } 68 | } 69 | service.getCharacteristic(Characteristic.On).updateValue(on_state); 70 | service.getCharacteristic(Characteristic.OutletInUse).updateValue(use_state); 71 | //bind set event if not set 72 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 73 | if (!setter || setter.length == 0) { 74 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 75 | let data = { status: value ? 'on' : 'off' }; 76 | data.key = this.mijia.generateKey(sid); 77 | let cmd = { cmd: 'write', model: 'plug', sid: sid, data: JSON.stringify(data) }; 78 | this.mijia.sendMsgToSid(cmd, sid); 79 | callback(); 80 | }); 81 | } 82 | if (!this.mijia.accessories[uuid]) { 83 | this.mijia.accessories[uuid] = accessory; 84 | this.registerAccessory([accessory]); 85 | } 86 | return accessory; 87 | } 88 | } 89 | module.exports = Plug; -------------------------------------------------------------------------------- /util/color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 4 | */ 5 | /** 6 | * convert hex string to rgb array 7 | * @param {* hex} hex 8 | */ 9 | function hex2rgb(hex) { 10 | let r = hex.substring(0, 2); 11 | let g = hex.substring(2, 4); 12 | let b = hex.substring(4, 6); 13 | r = parseInt(r, 16); 14 | g = parseInt(g, 16); 15 | b = parseInt(b, 16); 16 | return [r, g, b, hex]; 17 | } 18 | /** 19 | * convert rgb array to hex string 20 | * @param {* red} r 21 | * @param {* green} g 22 | * @param {* blue} b 23 | */ 24 | function rgb2hex(r, g, b) { 25 | if (r == "") r = 0; 26 | if (g == "") g = 0; 27 | if (b == "") b = 0; 28 | r = parseInt(r); 29 | g = parseInt(g); 30 | b = parseInt(b); 31 | if (r < 0) r = 0; 32 | if (g < 0) g = 0; 33 | if (b < 0) b = 0; 34 | if (r > 255) r = 255; 35 | if (g > 255) g = 255; 36 | if (b > 255) b = 255; 37 | let hex = r * 65536 + g * 256 + b; 38 | hex = hex.toString(16, 6); 39 | let len = hex.length; 40 | if (len < 6) { 41 | for (i = 0; i < 6 - len; i++) { 42 | hex = '0' + hex; 43 | } 44 | } 45 | return hex.toUpperCase(); 46 | } 47 | /** 48 | * convert rgb array to hsv array 49 | * @param {* red} r 50 | * @param {* green} g 51 | * @param {* blue} b 52 | */ 53 | function rgb2hsv(r, g, b) { 54 | if (r == "") r = 0; 55 | if (g == "") g = 0; 56 | if (b == "") b = 0; 57 | r = parseFloat(r); 58 | g = parseFloat(g); 59 | b = parseFloat(b); 60 | if (r < 0) r = 0; 61 | if (g < 0) g = 0; 62 | if (b < 0) b = 0; 63 | if (r > 255) r = 255; 64 | if (g > 255) g = 255; 65 | if (b > 255) b = 255; 66 | let hex = r * 65536 + g * 256 + b; 67 | hex = hex.toString(16, 6); 68 | let len = hex.length; 69 | if (len < 6) { 70 | for (i = 0; i < 6 - len; i++) { 71 | hex = '0' + hex; 72 | } 73 | } 74 | r /= 255; 75 | g /= 255; 76 | b /= 255; 77 | let max = Math.max(r, g, b); 78 | let min = Math.min(r, g, b); 79 | let diff = max - min; 80 | if (diff == 0) h = 0; 81 | else if (max == r) h = ((g - b) / diff) % 6; 82 | else if (max == g) h = (b - r) / diff + 2; 83 | else h = (r - g) / diff + 4; 84 | h *= 60; 85 | if (h < 0) h += 360; 86 | let v = max; 87 | if (v == 0) { 88 | s = 0; 89 | } 90 | else { 91 | s = diff / v; 92 | } 93 | s *= 100; 94 | v *= 100; 95 | return [h.toFixed(0), s.toFixed(1), v.toFixed(1)]; 96 | } 97 | /** 98 | * convert hsv array to rgb array 99 | * @param {* hue} h 100 | * @param {* saturation} s 101 | * @param {* brightness} v 102 | */ 103 | function hsv2rgb(h, s, v) { 104 | if (h == "") h = 0; 105 | if (s == "") s = 0; 106 | if (v == "") v = 0; 107 | h = parseFloat(h); 108 | s = parseFloat(s); 109 | v = parseFloat(v); 110 | if (h < 0) h = 0; 111 | if (s < 0) s = 0; 112 | if (v < 0) v = 0; 113 | if (h >= 360) h = 359; 114 | if (s > 100) s = 100; 115 | if (v > 100) v = 100; 116 | s /= 100; 117 | v /= 100; 118 | C = v * s; 119 | let hh = h / 60; 120 | let X = C * (1 - Math.abs(hh % 2 - 1)); 121 | r = g = b = 0; 122 | if (hh >= 0 && hh < 1) { 123 | r = C; 124 | g = X; 125 | } 126 | else if (hh >= 1 && hh < 2) { 127 | r = X; 128 | g = C; 129 | } 130 | else if (hh >= 2 && hh < 3) { 131 | g = C; 132 | b = X; 133 | } 134 | else if (hh >= 3 && hh < 4) { 135 | g = X; 136 | b = C; 137 | } 138 | else if (hh >= 4 && hh < 5) { 139 | r = X; 140 | b = C; 141 | } 142 | else { 143 | r = C; 144 | b = X; 145 | } 146 | m = v - C; 147 | r += m; 148 | g += m; 149 | b += m; 150 | r *= 255.0; 151 | g *= 255.0; 152 | b *= 255.0; 153 | r = Math.round(r); 154 | g = Math.round(g); 155 | b = Math.round(b); 156 | hex = r * 65536 + g * 256 + b; 157 | hex = hex.toString(16, 6); 158 | len = hex.length; 159 | if (len < 6) { 160 | for (i = 0; i < 6 - len; i++) { 161 | hex = '0' + hex; 162 | } 163 | } 164 | return [r, g, b, hex.toUpperCase()]; 165 | } 166 | 167 | module.exports = { 168 | hex2rgb: hex2rgb, 169 | rgb2hex: rgb2hex, 170 | hsv2rgb: hsv2rgb, 171 | rgb2hsv: rgb2hsv 172 | }; -------------------------------------------------------------------------------- /kit/mijia/powerplug.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | const miio = require('miio'); 3 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 4 | class PowerPlug extends Base { 5 | constructor(mijia, config) { 6 | super(mijia); 7 | this.config = config; 8 | this.model = config.model; 9 | this.devices = {}; //all airpurifier devices 10 | PlatformAccessory = mijia.PlatformAccessory; 11 | Accessory = mijia.Accessory; 12 | Service = mijia.Service; 13 | Characteristic = mijia.Characteristic; 14 | UUIDGen = mijia.UUIDGen; 15 | this.discover(); 16 | } 17 | setPowerPlug(reg, channel, device) { 18 | let sid = reg.id; 19 | let model = device.model; 20 | let uuid = UUIDGen.generate('Mijia-PowerPlug@' + sid) 21 | let accessory = this.mijia.accessories[uuid]; 22 | let service; 23 | if (!accessory) { 24 | let name = sid; 25 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.FAN); 26 | accessory.getService(Service.AccessoryInformation) 27 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 28 | .setCharacteristic(Characteristic.Model, "Mijia PowerPlug") 29 | .setCharacteristic(Characteristic.SerialNumber, sid); 30 | accessory.on('identify', function (paired, callback) { 31 | callback(); 32 | }); 33 | service = new Service.Outlet(name); // outlet 34 | accessory.addService(service, name); 35 | } else { 36 | service = accessory.getService(Service.Outlet); 37 | } 38 | accessory.reachable = true; 39 | accessory.context.sid = sid; 40 | accessory.context.model = this.model; 41 | //bind 42 | let setter = service.getCharacteristic(Characteristic.On).listeners('set'); 43 | if (!setter || setter.length == 0) { 44 | //service 45 | service.getCharacteristic(Characteristic.On).on('get', (callback) => { 46 | let device = this.devices[sid]; 47 | let status = false; 48 | if (device != undefined) { 49 | if (model == 'chuangmi.plug.v1') { 50 | if (channel == 'main') { 51 | status = device.on; 52 | } else if (channel == 'usb') { 53 | status = device.usb_on; 54 | } 55 | } else { 56 | status = device.power; 57 | } 58 | } 59 | callback(null, status); 60 | }).on('set', (value, callback) => { 61 | let device = this.devices[sid]; 62 | if (device != undefined && value) { 63 | device.setPower(channel, value ? true : false); 64 | } 65 | callback(null, value); 66 | }); 67 | } 68 | if (!this.mijia.accessories[uuid]) { 69 | this.mijia.accessories[uuid] = accessory; 70 | this.registerAccessory([accessory]); 71 | } 72 | } 73 | 74 | discover() { 75 | this.mijia.log.debug('try to discover ' + this.model); 76 | let browser = miio.browse(); //require a new browse 77 | browser.on('available', (reg) => { 78 | if (!reg.token) { //power plug support Auto-token 79 | return; 80 | } 81 | miio.device(reg).then((device) => { 82 | if (device.type != this.model) { 83 | return; 84 | } 85 | this.devices[reg.id] = device; 86 | this.mijia.log.debug('find model->%s with hostname->%s id->%s @ %s:%s.', device.model, reg.hostname, device.id, device.address, device.port); 87 | if (device.model == 'chuangmi.plug.v1') { 88 | this.setPowerPlug(reg, 'main', device); 89 | this.setPowerPlug(reg, 'usb', device); 90 | } else { 91 | this.setPowerPlug(reg, 0, device); 92 | } 93 | }); 94 | }); 95 | 96 | browser.on('unavailable', (reg) => { 97 | if (!reg.token) { //airpurifier support Auto-token 98 | return; 99 | } 100 | if (this.devices[reg.id] != undefined) { 101 | this.devices[reg.id].destroy(); 102 | delete this.devices[reg.id]; 103 | } 104 | }); 105 | } 106 | 107 | } 108 | 109 | module.exports = PowerPlug; -------------------------------------------------------------------------------- /kit/mijia/motion.aq2.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class MotionAq2 extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { voltage, status, lux } = data; //lux->0~1200 21 | this.mijia.log.debug(`${model} ${cmd} voltage->${voltage} status->${status} lux->${lux}`); 22 | if (status != undefined) { 23 | this.setMotionSensor(sid, voltage, status); 24 | } 25 | if (lux != undefined) { 26 | this.setLightSensor(sid, lux); 27 | } 28 | } 29 | /** 30 | * set up MotionSensor(aqara motion sensor) 31 | * @param {*device id} sid 32 | * @param {*device voltage} voltage 33 | * @param {*device status} status 34 | */ 35 | setMotionSensor(sid, voltage, status) { 36 | let uuid = UUIDGen.generate('Aqara-MotionSensor@' + sid); 37 | let accessory = this.mijia.accessories[uuid]; 38 | let service; 39 | if (!accessory) { 40 | //init a new homekit accessory 41 | let name = sid.substring(sid.length - 4); 42 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 43 | accessory.getService(Service.AccessoryInformation) 44 | .setCharacteristic(Characteristic.Manufacturer, "Aqara") 45 | .setCharacteristic(Characteristic.Model, "Aqara MotionSensor") 46 | .setCharacteristic(Characteristic.SerialNumber, sid); 47 | accessory.on('identify', function (paired, callback) { 48 | callback(); 49 | }); 50 | service = new Service.MotionSensor(name); 51 | accessory.addService(service, name); 52 | accessory.addService(new Service.BatteryService(name), name); 53 | } else { 54 | service = accessory.getService(Service.MotionSensor); 55 | } 56 | accessory.reachable = true; 57 | accessory.context.sid = sid; 58 | accessory.context.model = 'sensor_motion.aq2'; 59 | if (status != undefined) { 60 | service.getCharacteristic(Characteristic.MotionDetected).updateValue('motion' == status); 61 | } 62 | this.setBatteryService(sid, voltage, accessory); 63 | if (!this.mijia.accessories[uuid]) { 64 | this.mijia.accessories[uuid] = accessory; 65 | this.registerAccessory([accessory]); 66 | } 67 | return accessory; 68 | } 69 | 70 | 71 | /** 72 | * set up LightSensor(aqara lightsensor) 73 | * @param {*device id} sid 74 | * @param {*device lux value} lux 75 | */ 76 | setLightSensor(sid, lux) { 77 | let uuid = UUIDGen.generate('Aqara-LightSensor@' + sid); 78 | let accessory = this.mijia.accessories[uuid]; 79 | let service; 80 | if (!accessory) { 81 | //init a new homekit accessory 82 | let name = sid.substring(sid.length - 4); 83 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 84 | accessory.getService(Service.AccessoryInformation) 85 | .setCharacteristic(Characteristic.Manufacturer, "Aqara") 86 | .setCharacteristic(Characteristic.Model, "Aqara LightSensor") 87 | .setCharacteristic(Characteristic.SerialNumber, sid); 88 | accessory.on('identify', function (paired, callback) { 89 | callback(); 90 | }); 91 | service = new Service.LightSensor(name); 92 | accessory.addService(service, name); 93 | } else { 94 | service = accessory.getService(Service.LightSensor); 95 | } 96 | accessory.reachable = true; 97 | accessory.context.sid = sid; 98 | accessory.context.model = 'sensor_motion.aq2'; 99 | service.getCharacteristic(Characteristic.CurrentAmbientLightLevel).updateValue(lux); 100 | if (!this.mijia.accessories[uuid]) { 101 | this.mijia.accessories[uuid] = accessory; 102 | this.registerAccessory([accessory]); 103 | } 104 | } 105 | } 106 | module.exports = MotionAq2; -------------------------------------------------------------------------------- /kit/mijia/curtain.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class Curtain extends Base { 4 | constructor(mijia) { 5 | super(mijia); 6 | PlatformAccessory = mijia.PlatformAccessory; 7 | Accessory = mijia.Accessory; 8 | Service = mijia.Service; 9 | Characteristic = mijia.Characteristic; 10 | UUIDGen = mijia.UUIDGen; 11 | } 12 | /** 13 | * parse the gateway json msg 14 | * @param {*json} json 15 | * @param {*remoteinfo} rinfo 16 | */ 17 | parseMsg(json, rinfo) { 18 | let { cmd, model, sid } = json; 19 | let data = JSON.parse(json.data); 20 | let { status, curtain_level } = data; 21 | this.mijia.log.debug(`${model} ${cmd} status->${status} curtain_level->${curtain_level}`); 22 | this.setWindowCovering(sid, status, curtain_level); 23 | } 24 | /** 25 | * set up WindowCovering(mijia curtain) 26 | * @param {*device id} sid 27 | * @param {*device status} status 28 | * @param {*device curtain_level} curtain_level 29 | */ 30 | setWindowCovering(sid, status, curtain_level) { 31 | let uuid = UUIDGen.generate('Mijia-Curtain@' + sid); 32 | let accessory = this.mijia.accessories[uuid]; 33 | let service; 34 | if (!accessory) { 35 | //init a new homekit accessory 36 | let name = sid.substring(sid.length - 4); 37 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.WINDOW_COVERING); 38 | accessory.getService(Service.AccessoryInformation) 39 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 40 | .setCharacteristic(Characteristic.Model, "Mijia Curtain") 41 | .setCharacteristic(Characteristic.SerialNumber, sid); 42 | accessory.on('identify', function (paired, callback) { 43 | callback(); 44 | }); 45 | service = new Service.WindowCovering(name); 46 | accessory.addService(service, name); 47 | } else { 48 | service = accessory.getService(Service.WindowCovering); 49 | } 50 | accessory.reachable = true; 51 | accessory.context.sid = sid; 52 | accessory.context.model = 'curtain'; 53 | // 54 | if (curtain_level == undefined) { 55 | curtain_level = 100; 56 | } 57 | accessory.context.curtain_level = curtain_level; 58 | accessory.context.currentPosition = curtain_level; 59 | if (accessory.context.targetPosition == undefined) { 60 | accessory.context.targetPosition = curtain_level; 61 | } 62 | service.getCharacteristic(Characteristic.CurrentPosition).updateValue(accessory.context.currentPosition); 63 | setPositionState(accessory, service); 64 | let setters = service.getCharacteristic(Characteristic.CurrentPosition).listeners('set'); 65 | if (!setters || setters.length == 0) { 66 | service.getCharacteristic(Characteristic.TargetPosition).on('set', function (value, callback) { 67 | accessory.context.targetPosition = value; 68 | if (accessory.context.targetPosition != accessory.context.currentPosition) { 69 | let data = { curtain_level: value }; 70 | data.key = this.mijia.generateKey(sid); 71 | let cmd = { cmd: 'write', model: 'curtain', sid: sid, data: JSON.stringify(data) }; 72 | this.mijia.sendMsgToSid(cmd, sid); 73 | } 74 | callback(); 75 | }); 76 | service.getCharacteristic(Characteristic.TargetPosition).on('get', function (callback) { 77 | callback(null, accessory.context.targetPosition); 78 | }); 79 | service.getCharacteristic(Characteristic.CurrentPosition).on('get', function (callback) { 80 | callback(null, accessory.context.currentPosition); 81 | }); 82 | } 83 | if (!this.mijia.accessories[uuid]) { 84 | this.mijia.accessories[uuid] = accessory; 85 | this.registerAccessory([accessory]); 86 | } 87 | return accessory; 88 | } 89 | /** 90 | * set up window covering state 91 | * @param {* accessory} accessory 92 | * @param {* service} service 93 | */ 94 | setPositionState(accessory, service) { 95 | let positionState = Characteristic.PositionState.STOPPED; 96 | if (accessory.context.targetPosition > accessory.context.currentPosition) { 97 | positionState = Characteristic.PositionState.INCREASING; 98 | } else if (accessory.context.targetPosition < accessory.context.currentPosition) { 99 | positionState = Characteristic.PositionState.DECREASING; 100 | } 101 | service.getCharacteristic(Characteristic.PositionState).updateValue(positionState); 102 | } 103 | } 104 | module.exports = Curtain; -------------------------------------------------------------------------------- /kit/mijia/yeelight.js: -------------------------------------------------------------------------------- 1 | const miio = require('miio'); 2 | const Base = require('./base'); 3 | const color = require('../../util/color'); 4 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 5 | class Yeelight extends Base { 6 | constructor(mijia, config) { 7 | super(mijia); 8 | this.config = config; 9 | this.model = config.model; 10 | this.devices = {}; //all yeelight devices 11 | PlatformAccessory = mijia.PlatformAccessory; 12 | Accessory = mijia.Accessory; 13 | Service = mijia.Service; 14 | Characteristic = mijia.Characteristic; 15 | UUIDGen = mijia.UUIDGen; 16 | this.discover(); 17 | } 18 | /** 19 | * discover yeelight on the localnetwork 20 | */ 21 | discover() { 22 | this.mijia.log.debug('try to discover ' + this.model); 23 | let browser = miio.browse(); //require a new browse 24 | browser.on('available', (reg) => { 25 | if (!reg.token) { //support Auto-token 26 | return; 27 | } 28 | miio.device(reg).then((device) => { 29 | this.devices[reg.id] = device; 30 | this.mijia.log.debug('find model->%s with hostname->%s id->%s @ %s:%s.', device.model, reg.hostname, device.id, device.address, device.port); 31 | this.setLightbulb(reg, device); 32 | }); 33 | }); 34 | 35 | browser.on('unavailable', (reg) => { 36 | if (!reg.token) { //support Auto-token 37 | return; 38 | } 39 | if (this.devices[reg.id] != undefined) { 40 | this.devices[reg.id].destroy(); 41 | delete this.devices[reg.id]; 42 | } 43 | }); 44 | } 45 | /** 46 | * set up Lightbulb Service 47 | * @param {* reg} reg 48 | * @param {* device} device 49 | */ 50 | setLightbulb(reg, device) { 51 | let sid = reg.id; 52 | let model = device.model; 53 | let uuid = UUIDGen.generate('Mijia-PowerPlug@' + sid) 54 | let supportColor = device.model == 'yeelink.light.color1'; 55 | let service; 56 | if (!accessory) { 57 | //init a new homekit accessory 58 | let name = sid; 59 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.LIGHTBULB); 60 | accessory.getService(Service.AccessoryInformation) 61 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 62 | .setCharacteristic(Characteristic.Model, "Mijia Yeelight Lightbulb") 63 | .setCharacteristic(Characteristic.SerialNumber, sid); 64 | accessory.on('identify', function (paired, callback) { 65 | callback(); 66 | }); 67 | service = new Service.Lightbulb(name); 68 | //add optional characteristic intent to display color menu in homekit app 69 | service.addCharacteristic(Characteristic.Brightness); 70 | if (supportColor) { 71 | service.addCharacteristic(Characteristic.Hue); 72 | service.addCharacteristic(Characteristic.Saturation); 73 | } 74 | accessory.addService(service, name); 75 | } else { 76 | service = accessory.getService(Service.Lightbulb); 77 | } 78 | accessory.reachable = true; 79 | accessory.context.sid = sid; 80 | accessory.context.model = 'yeelight'; 81 | 82 | let power = device.power; 83 | //update Characteristics 84 | if (power != undefined) { 85 | service.getCharacteristic(Characteristic.On).updateValue(power); 86 | } else { 87 | service.getCharacteristic(Characteristic.On).updateValue(false); 88 | } 89 | if (supportColor) { 90 | let rgb = device.rgb; 91 | if (rgb == undefined) { 92 | rgb = { red: 255, green: 255, blue: 255 }; 93 | } 94 | let hsv = color.rgb2hsv(red, green, blue); 95 | service.getCharacteristic(Characteristic.Hue).updateValue(hsv[0]); 96 | service.getCharacteristic(Characteristic.Saturation).updateValue(hsv[1]); 97 | accessory.context.lastRgb = rgb; 98 | } 99 | //bind set event if not set 100 | var setters = service.getCharacteristic(Characteristic.On).listeners('set'); 101 | if (!setters || setters.length == 0) { 102 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 103 | let device = this.devices[sid]; 104 | this.mijia.log.debug(`set yeelight on->${value}`); 105 | if (device) { 106 | device.setPower(value ? true : false); 107 | } 108 | callback(); 109 | }); 110 | 111 | service.getCharacteristic(Characteristic.Brightness).on('set', (value, callback) => { 112 | this.mijia.log.debug(`set yeelight brightness->${value}`); 113 | let device = this.devices[sid]; 114 | if (device) { 115 | device.setBrightness(value); 116 | } 117 | accessory.context.lastBrightness = value; 118 | callback(); 119 | }); 120 | if (supportColor) { 121 | service.getCharacteristic(Characteristic.Saturation).on('set', (value, callback) => { 122 | this.mijia.log.debug(`set yeelight Saturation->${value}`); 123 | if (value != undefined) { 124 | accessory.context.lastSaturation = value; 125 | } 126 | callback(); 127 | }); 128 | service.getCharacteristic(Characteristic.Hue).on('set', (value, callback) => { 129 | this.mijia.log.debug(`set yeelight Hue->${value}`); 130 | let device = this.devices[sid]; 131 | let lastSaturation = accessory.context.lastSaturation; 132 | lastSaturation = lastSaturation ? lastSaturation : 100; 133 | let lastBrightness = accessory.context.lastBrightness; 134 | lastBrightness = lastBrightness ? lastBrightness : 100; 135 | let rgb = color.hsv2rgb(value, lastSaturation, lastBrightness); //convert hue and sat to rgb value 136 | accessory.context.lastRgb = rgb; 137 | accessory.context.lastHue = value; 138 | if (device) { 139 | device.setRgb({ red: rgb[0], green: rgb[1], blue: rgb[2] }); 140 | } 141 | callback(); 142 | }); 143 | } 144 | 145 | } 146 | if (!this.mijia.accessories[uuid]) { 147 | this.mijia.accessories[uuid] = accessory; 148 | this.registerAccessory([accessory]); 149 | } 150 | } 151 | } 152 | module.exports = Yeelight; -------------------------------------------------------------------------------- /kit/mijia/vacuum.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | const miio = require('miio'); 3 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 4 | class Vacuum extends Base { 5 | constructor(mijia, config) { 6 | super(mijia); 7 | this.config = config; 8 | this.model = config.model; 9 | this.devices = {}; //all airpurifier devices 10 | PlatformAccessory = mijia.PlatformAccessory; 11 | Accessory = mijia.Accessory; 12 | Service = mijia.Service; 13 | Characteristic = mijia.Characteristic; 14 | UUIDGen = mijia.UUIDGen; 15 | this.discover(); 16 | } 17 | setVacuum(config, device) { 18 | let sid = config.sid; 19 | let uuid = UUIDGen.generate('Mijia-Vacuum@' + sid) 20 | let accessory = this.mijia.accessories[uuid]; 21 | let service_fan, service_battery; 22 | if (!accessory) { 23 | let name = sid; 24 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.FAN); 25 | accessory.getService(Service.AccessoryInformation) 26 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 27 | .setCharacteristic(Characteristic.Model, "Mijia Vacuum") 28 | .setCharacteristic(Characteristic.SerialNumber, sid); 29 | accessory.on('identify', function (paired, callback) { 30 | callback(); 31 | let device = this.devices[sid]; 32 | if (device != undefined) { 33 | device.find(); //find me,will make the device give off a sound. 34 | } 35 | }); 36 | service_fan = new Service.Fan(name); // Fan 37 | accessory.addService(service_fan, name); 38 | service_battery = new Service.BatteryService(name); 39 | accessory.addService(service_battery, name); 40 | } else { 41 | service_fan = accessory.getService(Service.Fan); 42 | service_battery = accessory.getService(Service.BatteryService); 43 | } 44 | accessory.reachable = true; 45 | accessory.context.sid = sid; 46 | accessory.context.model = this.model; 47 | //bind 48 | let setter = service_fan.getCharacteristic(Characteristic.On).listeners('set'); 49 | if (!setter || setter.length == 0) { 50 | //service_fan 51 | service_fan.getCharacteristic(Characteristic.On).on('get', (callback) => { 52 | let device = this.devices[sid]; 53 | let status = false; 54 | if (device != undefined) { 55 | let state = device.state; 56 | switch (state) { 57 | case 'cleaning': 58 | case 'returning': 59 | case 'paused': 60 | case 'spot-cleaning': { 61 | status = true; 62 | break; 63 | } 64 | default: 65 | status = false; 66 | } 67 | } 68 | callback(null, status); 69 | }).on('set', (value, callback) => { 70 | let device = this.devices[sid]; 71 | if (device != undefined && value) { 72 | device.start(); 73 | } else { 74 | device.pause(); 75 | device.charge(); //go to home(app_stop && app_charge) 76 | } 77 | callback(null, value); 78 | }); 79 | service_fan.getCharacteristic(Characteristic.RotationSpeed).on('get', (callback) => { 80 | let device = this.devices[sid]; 81 | if (device != undefined) { 82 | if (device.state == 'paused') { 83 | callback(); 84 | return; 85 | } 86 | callback(null, device.fanPower); 87 | return; 88 | } 89 | callback(); 90 | }).on('set', (value, callback) => { 91 | let device = this.devices[sid]; 92 | if (device != undefined) { 93 | let speeds = [ 94 | 0, 95 | 38, 96 | 60, 97 | 77, 98 | 90 99 | ]; 100 | for (let item in speeds) { 101 | if (value <= item) { 102 | value = item; 103 | break; 104 | } 105 | } 106 | device.setFanPower(value); 107 | if (value == 0) { 108 | device.pause(); 109 | } 110 | callback(null, speed); 111 | } else { 112 | callback(); 113 | } 114 | }); 115 | //battery service 116 | service_battery.getCharacteristic(Characteristic.BatteryLevel).on('get', (callback) => { 117 | let device = this.devices[sid]; 118 | if (device != undefined) { 119 | callback(null, device.battery); 120 | } else { 121 | callback(); 122 | } 123 | }); 124 | service_battery.getCharacteristic(Characteristic.StatusLowBattery).on('get', (callback) => { 125 | let device = this.devices[sid]; 126 | if (device != undefined) { 127 | callback(null, device.battery < 20); //like ios 128 | } else { 129 | callback(); 130 | } 131 | }); 132 | service_battery.getCharacteristic(Characteristic.ChargingState).on('get', (callback) => { 133 | let device = this.devices[sid]; 134 | if (device != undefined) { 135 | callback(null, device.charging); 136 | } else { 137 | callback(); 138 | } 139 | }); 140 | } 141 | if (!this.mijia.accessories[uuid]) { 142 | this.mijia.accessories[uuid] = accessory; 143 | this.registerAccessory([accessory]); 144 | } 145 | } 146 | 147 | discover() { 148 | this.mijia.log.debug('try to discover ' + this.model); 149 | if (this.config.sid == undefined) { 150 | this.config.sid = this.config.ip; //change sid 151 | } 152 | //create device 153 | let device = miio.device({ 154 | address: this.config.ip, 155 | token: this.config.token, 156 | model: 'rockrobo.vacuum.v1' 157 | }).then(() => { 158 | this.mijia.log.debug('init vacuum done->%s', this.config.ip); 159 | this.mijia.log.debug('Battery->%s,State->%s,Fan->%s', device.battery, device.state, device.fanPower); 160 | if (device.state != undefined) { 161 | this.setVacuum(this.config, device); 162 | this.devices[this.config.sid] = device; 163 | } else { 164 | this.mijia.log.warn('vacuum state undefined, discard to setVacuum'); 165 | device.destroy(); 166 | } 167 | }).catch((err) => { 168 | this.mijia.log.debug(err); 169 | this.mijia.log.error('unable to initialize robot vacuum->%s', this.config.ip); 170 | }); 171 | } 172 | 173 | } 174 | 175 | module.exports = Vacuum; -------------------------------------------------------------------------------- /kit/broadlink/mp.js: -------------------------------------------------------------------------------- 1 | const Broadlink_lib = require('./broadlink'); 2 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 3 | class MP { 4 | constructor(broadlink) { 5 | this.broadlink = broadlink; 6 | this.config = {}; 7 | PlatformAccessory = broadlink.PlatformAccessory; 8 | Accessory = broadlink.Accessory; 9 | Service = broadlink.Service; 10 | Characteristic = broadlink.Characteristic; 11 | UUIDGen = broadlink.UUIDGen; 12 | } 13 | init(config) { 14 | this.config = config; 15 | this.index = config.name.split('@')[1]; 16 | this.powered = false; //default off 17 | this.macBuffer = this.macStringToMacBuff(config.mac); 18 | let uuid = UUIDGen.generate('Broadlink-' + config.name); 19 | let accessory = this.broadlink.accessories[uuid]; 20 | let service; 21 | if (!accessory) { 22 | //init a new homekit accessory 23 | let name = config.name; 24 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SWITCH); 25 | accessory.getService(Service.AccessoryInformation) 26 | .setCharacteristic(Characteristic.Manufacturer, "Broadlink") 27 | .setCharacteristic(Characteristic.Model, "Broadlink " + this.config.type) 28 | .setCharacteristic(Characteristic.SerialNumber, config.mac); 29 | accessory.on('identify', function (paired, callback) { 30 | callback(); 31 | }); 32 | service = new Service.Switch(name); 33 | accessory.addService(service, name); 34 | this.broadlink.log.debug('init a new broadlink accessory->' + this.config); 35 | } else { 36 | service = accessory.getService(Service.Switch); 37 | } 38 | accessory.reachable = true; 39 | let setters = service.getCharacteristic(Characteristic.On).listeners('set'); 40 | if (!setters || setters.length == 0) { 41 | service.getCharacteristic(Characteristic.On).on('set', this.setSwitchStatus.bind(this)); 42 | service.getCharacteristic(Characteristic.On).on('get', this.getSwitchStatus.bind(this)); 43 | } 44 | if (!this.broadlink.accessories[uuid]) { 45 | this.broadlink.accessories[uuid] = accessory; 46 | this.registerAccessory([accessory]); 47 | } 48 | return accessory; 49 | 50 | } 51 | /** 52 | * set the mp switch status 53 | * @param {* value} value 54 | * @param {* callback} callback 55 | */ 56 | setSwitchStatus(value, callback) { 57 | this.broadlink.log.debug('set mp device switch status ->' + value); 58 | let lib = new Broadlink_lib(); 59 | let state = false; //power state 60 | let retry = 1; 61 | let callbacked = false; 62 | if (value && !this.powered) { 63 | state = true; 64 | } else if (!value && this.powered) { 65 | state = false; 66 | } else { 67 | callback(null, this.powered); 68 | return; 69 | } 70 | lib.on('deviceReady', (dev) => { 71 | if (this.macBuffer.equals(dev.mac) || dev.host.address == this.config.ip) { 72 | clearInterval(checkAgainSet); 73 | dev.set_power(this.index, state); 74 | dev.exit(); 75 | this.powered = state; 76 | callbacked = true; 77 | callback(null, this.powered); 78 | } else { 79 | dev.exit(); 80 | if (retry > 2) { 81 | clearInterval(checkAgainSet); 82 | callbacked = true; 83 | callback(null, this.powered); 84 | this.broadlink.log.warn('Broadlink setSwitchStatus discover after 3 times,but not found match device'); 85 | } 86 | } 87 | }); 88 | lib.discover(300); 89 | let checkAgainSet = setInterval(() => { 90 | if (retry > 2) { 91 | clearInterval(checkAgainSet); 92 | return; 93 | } 94 | lib.discover(300); 95 | retry++; 96 | }, 500); //re-discover after 0.5s for 2 times 97 | let callbackTimeout = setTimeout(() => { 98 | if (!callbacked) { 99 | callback(null, this.powered); 100 | } 101 | }, 2000); //callback after 2s whatever 102 | } 103 | 104 | getSwitchStatus(callback) { 105 | this.broadlink.log.debug('get mp devices switch status'); 106 | let lib = new Broadlink_lib(); 107 | let retry = 1; 108 | let callbacked = false; 109 | lib.on('deviceReady', (dev) => { 110 | if (this.macBuffer.equals(dev.mac) || dev.host.address == self.ip) { 111 | clearInterval(checkAgainSet); 112 | dev.check_power(); 113 | dev.on("mp_power", (status_array) => { 114 | dev.exit(); 115 | if (!status_array[this.index - 1]) { 116 | this.powered = false; 117 | callbacked = true; 118 | callback(null, false); 119 | } else { 120 | this.powered = true; 121 | callbacked = true; 122 | callback(null, true); 123 | } 124 | }); 125 | } else { 126 | dev.exit(); 127 | if (retry > 2) { 128 | clearInterval(checkAgainSet); 129 | callbacked = true; 130 | callback(null, this.powered); 131 | this.broadlink.log.warn('Broadlink getSwitchStatus discover after 3 times,but not found match device'); 132 | } 133 | } 134 | }); 135 | lib.discover(300); 136 | let checkAgainSet = setInterval(() => { 137 | if (retry > 2) { 138 | clearInterval(checkAgainSet); 139 | return; 140 | } 141 | lib.discover(300); 142 | retry++; 143 | }, 500); //re-discover after 0.5s for 2 times 144 | let callbackTimeout = setTimeout(() => { 145 | if (!callbacked) { 146 | callback(null, this.powered); 147 | } 148 | }, 2000); //callback after 2s whatever 149 | } 150 | 151 | /** 152 | * registry accessories to homekit 153 | * @param {*accessories} accessories 154 | */ 155 | registerAccessory(accessories) { 156 | this.broadlink.api.registerPlatformAccessories("homebridge-smarthome", "smarthome-broadlink", accessories); 157 | } 158 | /** 159 | * macString to mac buffer 160 | * @param {* mac} mac 161 | */ 162 | macStringToMacBuff(mac) { 163 | var mb = new Buffer(6); 164 | if (mac) { 165 | var values = mac.split(':'); 166 | if (!values || values.length !== 6) { 167 | throw new Error('Invalid MAC [' + mac + ']; should follow pattern ##:##:##:##:##:##'); 168 | } 169 | for (var i = 0; i < values.length; ++i) { 170 | var tmpByte = parseInt(values[i], 16); 171 | mb.writeUInt8(tmpByte, i); 172 | } 173 | } 174 | return mb; 175 | } 176 | 177 | 178 | } 179 | module.exports = MP; -------------------------------------------------------------------------------- /kit/mijia/gateway.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | const color = require('../../util/color'); 3 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 4 | class Gateway extends Base { 5 | constructor(mijia) { 6 | super(mijia); 7 | PlatformAccessory = mijia.PlatformAccessory; 8 | Accessory = mijia.Accessory; 9 | Service = mijia.Service; 10 | Characteristic = mijia.Characteristic; 11 | UUIDGen = mijia.UUIDGen; 12 | } 13 | /** 14 | * parse the gateway json msg 15 | * @param {*json} json 16 | * @param {*remoteinfo} rinfo 17 | */ 18 | parseMsg(json, rinfo) { 19 | let { cmd, model, sid } = json; 20 | let data = JSON.parse(json.data); 21 | let { rgb, illumination, proto_version, mid } = data; 22 | this.mijia.log.debug(`${model} ${cmd} rgb->${rgb} illumination->${illumination} proto_version->${proto_version}`); 23 | if (illumination != undefined) { 24 | this.setLightSensor(sid, illumination); 25 | } 26 | if (rgb != undefined) { 27 | this.setLightbulb(sid, rgb); 28 | } 29 | } 30 | /** 31 | * set up gateway LightSensor(mijia gateway lightsensor) 32 | * @param {*device id} sid 33 | * @param {*device illumination value} illumination 34 | */ 35 | setLightSensor(sid, illumination) { 36 | let uuid = UUIDGen.generate('Gateway-LightSensor@' + sid); 37 | let accessory = this.mijia.accessories[uuid]; 38 | let service; 39 | if (!accessory) { 40 | //init a new homekit accessory 41 | let name = sid.substring(sid.length - 4); 42 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.SENSOR); 43 | accessory.getService(Service.AccessoryInformation) 44 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 45 | .setCharacteristic(Characteristic.Model, "Gateway LightSensor") 46 | .setCharacteristic(Characteristic.SerialNumber, sid); 47 | accessory.on('identify', function (paired, callback) { 48 | callback(); 49 | }); 50 | service = new Service.LightSensor(name); 51 | accessory.addService(service, name); 52 | } else { 53 | service = accessory.getService(Service.LightSensor); 54 | } 55 | accessory.reachable = true; 56 | accessory.context.sid = sid; 57 | accessory.context.model = 'gateway'; 58 | service.getCharacteristic(Characteristic.CurrentAmbientLightLevel).updateValue(illumination); 59 | if (!this.mijia.accessories[uuid]) { 60 | this.mijia.accessories[uuid] = accessory; 61 | this.registerAccessory([accessory]); 62 | } 63 | } 64 | /** 65 | * set up Lightbulb Service(mijia gateway Lightbulb) 66 | * @param {*device sid} sid 67 | * @param {*device rgb value} rgb 68 | */ 69 | setLightbulb(sid, rgb) { 70 | let uuid = UUIDGen.generate('Gateway-Lightbulb@' + sid); 71 | let accessory = this.mijia.accessories[uuid]; 72 | let service; 73 | if (!accessory) { 74 | //init a new homekit accessory 75 | let name = sid.substring(sid.length - 4); 76 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.LIGHTBULB); 77 | accessory.getService(Service.AccessoryInformation) 78 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 79 | .setCharacteristic(Characteristic.Model, "Mijia Gateway Lightbulb") 80 | .setCharacteristic(Characteristic.SerialNumber, sid); 81 | accessory.on('identify', function (paired, callback) { 82 | callback(); 83 | }); 84 | service = new Service.Lightbulb(name); 85 | //add optional characteristic intent to display color menu in homekit app 86 | service.addCharacteristic(Characteristic.Hue); 87 | service.addCharacteristic(Characteristic.Saturation); 88 | service.addCharacteristic(Characteristic.Brightness); 89 | accessory.addService(service, name); 90 | } else { 91 | service = accessory.getService(Service.Lightbulb); 92 | } 93 | accessory.reachable = true; 94 | accessory.context.sid = sid; 95 | accessory.context.model = 'gateway'; 96 | //update Characteristics 97 | let brightness = (rgb & 0xFF000000) >>> 24; 98 | let red = (rgb & 0x00FF0000) >>> 16; 99 | let green = (rgb & 0x0000FF00) >>> 8; 100 | let blue = rgb & 0x000000FF; 101 | if (rgb == 0 || brightness == 0) { 102 | service.getCharacteristic(Characteristic.On).updateValue(false); 103 | } else { 104 | service.getCharacteristic(Characteristic.On).updateValue(true); 105 | let hsv = color.rgb2hsv(red, green, blue); 106 | let hue = parseInt(hsv[0]); 107 | let sat = parseInt(hsv[1]); 108 | service.getCharacteristic(Characteristic.Brightness).updateValue(brightness); 109 | service.getCharacteristic(Characteristic.Hue).updateValue(hue); 110 | service.getCharacteristic(Characteristic.Saturation).updateValue(sat); 111 | accessory.context.lastRgb = rgb; 112 | } 113 | //bind set event if not set 114 | var setters = service.getCharacteristic(Characteristic.On).listeners('set'); 115 | if (!setters || setters.length == 0) { 116 | service.getCharacteristic(Characteristic.On).on('set', (value, callback) => { 117 | let data = { 118 | rgb: 0, 119 | key: '' 120 | } 121 | this.mijia.log.debug(`set gateway light on->${value}`); 122 | if (value) { //if value is true or 1 123 | let lastRgb = accessory.context.lastRgb; 124 | if (lastRgb == undefined) { 125 | data.rgb = 0xFFFFFFFF; //default 126 | accessory.context.lastRgb = data.rgb; 127 | } else { 128 | data.rgb = accessory.context.lastRgb; 129 | } 130 | accessory.context.count = 1; 131 | } else { 132 | accessory.context.count = 0; 133 | } 134 | data.key = this.mijia.generateKey(sid); 135 | let cmd = { cmd: 'write', model: 'gateway', sid: sid, data: JSON.stringify(data) } 136 | this.mijia.sendMsgToSid(cmd, sid); 137 | callback(); 138 | }); 139 | 140 | service.getCharacteristic(Characteristic.Brightness).on('set', (value, callback) => { 141 | this.mijia.log.debug(`set gateway light brightness->${value}`); 142 | if (accessory.context.count != undefined && accessory.context.count == 1 && value == 100) { 143 | this.mijia.log.warn(`discard set brightness->${value} when turn on the light`); 144 | } else { 145 | let data = { 146 | rgb: 0, 147 | key: '' 148 | } 149 | let lastRgb = accessory.context.lastRgb; 150 | lastRgb = lastRgb ? lastRgb : 0xFFFFFFFF; 151 | let rgb = value << 24 | (lastRgb & 0x00FFFFFF); 152 | data.rgb = rgb; 153 | data.key = this.mijia.generateKey(sid); 154 | let cmd = { cmd: 'write', model: 'gateway', sid: sid, data: JSON.stringify(data) } 155 | this.mijia.sendMsgToSid(cmd, sid); 156 | accessory.context.lastRgb = rgb; 157 | } 158 | accessory.context.count = 2; 159 | callback(); 160 | }); 161 | 162 | service.getCharacteristic(Characteristic.Saturation).on('set', (value, callback) => { 163 | this.mijia.log.debug(`set gateway light Saturation->${value}`); 164 | if (value != undefined) { 165 | accessory.context.lastSaturation = value; 166 | } 167 | callback(); 168 | }); 169 | 170 | service.getCharacteristic(Characteristic.Hue).on('set', (value, callback) => { 171 | this.mijia.log.debug(`set gateway light Hue->${value}`); 172 | let data = { 173 | rgb: 0, 174 | key: '' 175 | } 176 | let lastRgb = accessory.context.lastRgb; 177 | let lastSaturation = accessory.context.lastSaturation; 178 | lastSaturation = lastSaturation ? lastSaturation : 100; 179 | let lastBrightness = (lastRgb & 0xFF000000) >>> 24; 180 | let rgbArr = color.hsv2rgb(value, lastSaturation, lastBrightness); //convert hue and sat to rgb value 181 | let r = rgbArr[0]; 182 | let g = rgbArr[1]; 183 | let b = rgbArr[2]; 184 | let rgb = r << 16 | g << 8 | b; 185 | rgb = rgb | (lastBrightness << 24); 186 | this.mijia.log.debug(`set gateway light rgb->${rgb}`); 187 | data.rgb = rgb; 188 | data.key = this.mijia.generateKey(sid); 189 | let cmd = { cmd: 'write', model: 'gateway', sid: sid, data: JSON.stringify(data) } 190 | this.mijia.sendMsgToSid(cmd, sid); 191 | accessory.context.lastRgb = rgb; 192 | callback(); 193 | }); 194 | 195 | } 196 | if (!this.mijia.accessories[uuid]) { 197 | this.mijia.accessories[uuid] = accessory; 198 | this.registerAccessory([accessory]); 199 | } 200 | } 201 | } 202 | module.exports = Gateway; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # homebridge plugin and webapp for smarthome 2 | [![npm package](https://nodei.co/npm/homebridge-smarthome.png?downloads=true&downloadRank=true&stars=true)](https://nodei.co/npm/homebridge-smarthome/) 3 | 4 | [![npm](https://img.shields.io/npm/dt/homebridge-smarthome.svg)](https://www.npmjs.com/package/homebridge-smarthome) 5 | [![npm](https://img.shields.io/npm/v/homebridge-smarthome.svg)](https://www.npmjs.com/package/homebridge-smarthome) 6 | [![Dependency Status](https://img.shields.io/versioneye/d/nodejs/homebridge-smarthome.svg)](https://www.versioneye.com/nodejs/homebridge-smarthome/) 7 | 8 | 9 | ## mijia & broadlink & more... 10 | 11 | Thanks for 12 | 1. [snOOrz](https://github.com/snOOrz)(the author of [homebridge-aqara](https://github.com/snOOrz/homebridge-aqara)) 13 | 2. [YinHangCode](https://github.com/YinHangCode/homebridge-mi-aqara)(the author of [homebridge-mi-aqara](https://github.com/YinHangCode/homebridge-mi-aqara)) 14 | 3. [aholstenson](https://github.com/aholstenson/miio)(the author of [miio](https://github.com/aholstenson/miio)) 15 | 4. all other developer and testers. 16 | 17 | **Note: I have only a part of these devices, some devices do not have been tested. If you find bugs, please submit them to [issues](https://github.com/rench/homebridge-smarthome/issues).** 18 | 19 | ## Mijia & Aqara Accessory for homebridge. 20 | 21 | ### Zigbee 22 | ![](http://7fv93h.com1.z0.glb.clouddn.com/Gateway.jpg) 23 | ![](http://7fv93h.com1.z0.glb.clouddn.com/ContactSensor.jpg) 24 | ![](http://7fv93h.com1.z0.glb.clouddn.com/MotionSensor.jpg) 25 | ![](http://7fv93h.com1.z0.glb.clouddn.com/Button.jpg) 26 | ![](http://7fv93h.com1.z0.glb.clouddn.com/TemperatureAndHumiditySensor.jpg) 27 | ![](http://7fv93h.com1.z0.glb.clouddn.com/SingleSwitch.jpg) 28 | ![](http://7fv93h.com1.z0.glb.clouddn.com/DuplexSwitch.jpg) 29 | ![](http://7fv93h.com1.z0.glb.clouddn.com/SingleSwitchLN.jpg) 30 | ![](http://7fv93h.com1.z0.glb.clouddn.com/DuplexSwitchLN.jpg) 31 | ![](http://7fv93h.com1.z0.glb.clouddn.com/SingleButton86.jpg) 32 | ![](http://7fv93h.com1.z0.glb.clouddn.com/DuplexButton86.jpg) 33 | ![](http://7fv93h.com1.z0.glb.clouddn.com/PlugBase.jpg) 34 | ![](http://7fv93h.com1.z0.glb.clouddn.com/PlugBase86.jpg) 35 | ![](http://7fv93h.com1.z0.glb.clouddn.com/MagicSquare.jpg) 36 | ![](http://7fv93h.com1.z0.glb.clouddn.com/SmokeDetector.jpg) 37 | ![](http://7fv93h.com1.z0.glb.clouddn.com/NatgasDetector.jpg) 38 | ![](http://7fv93h.com1.z0.glb.clouddn.com/ElectricCurtain.jpg) 39 | 40 | ### Wifi 41 | ![](http://7fv93h.com1.z0.glb.clouddn.com/AirPurifier.jpg) 42 | ![](http://7fv93h.com1.z0.glb.clouddn.com/MiCamera.jpg) 43 | ![](http://7fv93h.com1.z0.glb.clouddn.com/MiRobotVacuum.jpg) 44 | ![](http://7fv93h.com1.z0.glb.clouddn.com/Yeelight.jpg) 45 | ![](http://7fv93h.com1.z0.glb.clouddn.com/Yeelight2.png) 46 | ![](http://7fv93h.com1.z0.glb.clouddn.com/Yeelight3.png) 47 | 48 | ### Supported Devices 49 | 1. Gateway(LightSensor/Lightbulb[hue]) 50 | 2. Magent(ContactSensor) 51 | 3. TemperatureAndHumiditySensor(HumiditySensor/TemperatureSensor) 52 | 4. Motion(MotionSensor) 53 | 5. Switch(StatelessProgrammableSwitch) 54 | 6. Plug(Outlet) 55 | 7. CtrlNeutral1/CtrlNeutral2(Switch) 56 | 8. CtrlLN1/CtrlLN2(Switch) 57 | 9. 86SW1/86SW2(StatelessProgrammableSwitch) 58 | 10. 86Plug(Outlet) 59 | 11. Smoke(SmokeSensor) 60 | 12. Natgas(SmokeSensor) 61 | 13. Curtain(WindowCovering) 62 | 14. AirPurifier(AirPurifier/AirQualitySensor/TemperatureSensor/HumiditySensor/Lightbulb) 63 | 15. Vacuum(Fan) 64 | 16. PowerPlug(Outlet) 65 | 17. PowerStrip(Outlet) 66 | 18. Yeelight(Lightbulb[hue]) 67 | 19. Aqara Switch(Switch) 68 | 20. Aqara Magent(ContactSensor) 69 | 21. Aqara TemperatureAndHumiditySensorAndPressure(HumiditySensor/TemperatureSensor/`CommunityTypes.AtmosphericPressureSensor`) 70 | 22. Aqara Motion(MotionSensor) 71 | 72 | ## Broadlink Accessory for homebridge. 73 | ![](http://7fv93h.com1.z0.glb.clouddn.com/Broadlink_MP1.jpg) 74 | ![](http://7fv93h.com1.z0.glb.clouddn.com/Broadlink_MP2.jpg) 75 | 76 | ### Supported Devices 77 | 1. MP1(Outlet) 78 | 2. MP2(Outlet) 79 | 80 | 81 | ## Pre-Requirements 82 | 1. Make sure you have V2 of the gateway. V1 has limited space so can't support this feature. 83 | 2. Update gateway firmware to 1.4.1_141.0141 or later. You can contact [@babymoney666](https://github.com/babymoney666) if your firmware is not up to date. 84 | 85 | ## Installation 86 | 1. Install HomeBridge, please follow it's [README](https://github.com/nfarina/homebridge/blob/master/README.md). 87 | If you are using Raspberry Pi, please read [Running-HomeBridge-on-a-Raspberry-Pi](https://github.com/nfarina/homebridge/wiki/Running-HomeBridge-on-a-Raspberry-Pi). 88 | 2. Make sure you can see HomeBridge in your iOS devices, if not, please go back to step 1. 89 | 3. Download homebridge-smarthome to your local folder or `npm i homebridge-smarthome`. 90 | 91 | ## Configuration 92 | 1. Open Aqara gateway's settings, enable [local network protocol](https://github.com/louisZL/lumi-gateway-local-api). 93 | Please follow the steps in this thread: http://bbs.xiaomi.cn/t-13198850. It's in Chinese so you might need a translator to read it. 94 | 2. To control the devices, put gateway's MAC address (lower case without colon) and password to ~/.homebridge/config.json. 95 | 3. How to get device ip and token? see [miio](https://github.com/aholstenson/miio/blob/master/docs/protocol.md). 96 | ``` 97 | { 98 | "bridge": { 99 | "name": "SmartHome", 100 | "username": "CC:22:3D:E3:CE:30", 101 | "port": 51826, 102 | "pin": "031-45-154" 103 | }, 104 | "platforms": [ 105 | { 106 | "platform": "smarthome-mijia", 107 | "web": { 108 | "port": 8888 109 | }, 110 | "mijia": { 111 | "sids": [ 112 | "34ce0088faed" 113 | ], 114 | "passwords": [ 115 | "75ED5A235C4A44D4" 116 | ], 117 | "devices": [ 118 | { 119 | "sid": "Air Purifier 001", 120 | "name": "Air Purifier", 121 | "type": "wifi", 122 | "model": "air-purifier" 123 | }, 124 | { 125 | "sid": "Power Plug 001", 126 | "name": "Power Plug", 127 | "type": "wifi", 128 | "model": "power-plug" 129 | }, 130 | { 131 | "sid": "Power Strip 001", 132 | "name": "Power Strip", 133 | "type": "wifi", 134 | "model": "power-strip" 135 | }, 136 | { 137 | "sid": "Yeelight 001", 138 | "name": "Yeelight", 139 | "type": "wifi", 140 | "model": "light" 141 | }, 142 | { 143 | "sid": "Vacuum Cleaner 001", 144 | "name": "Vacuum Cleaner", 145 | "ip": "192.168.2.200", 146 | "token": "4ac2cd21f3e9272ab21a5c1fd4053ed9", 147 | "type": "wifi", 148 | "model": "vacuum" 149 | } 150 | ] 151 | } 152 | }, 153 | { 154 | "platform": "smarthome-broadlink", 155 | "broadlink": { 156 | "devices": [ 157 | { 158 | "name": "MP2", 159 | "type": "MP2", 160 | "mac": "34:EA:34:D9:FE:B3" 161 | } 162 | ] 163 | } 164 | } 165 | ] 166 | } 167 | ``` 168 | 169 | ## Run it 170 | homebridge -D 171 | 172 | ## Version Logs 173 | 174 | ### 1.1.3 175 | 1. `mijia` fix purifier type error. 176 | 2. `mijia` fix multi getways. 177 | 178 | ### 1.1.2 179 | 1. `mijia` fix yeelight missing miio require. 180 | 181 | ### 1.1.1 182 | 1. `mijia` fix gateway lighthub. 183 | 184 | ### 1.1.0 185 | 186 | 1. `mijia` fix air-purifier accessory(fix multi callback). 187 | 188 | ### 1.0.7 189 | 190 | 1. `mijia` fix air-purifier accessory(remove some duplicated code). 191 | 192 | ### 1.0.6 193 | 1. `aqara` magnet,motion,switch,temperature-humidity-pressure accessory. 194 | 2. `mijia` change monitor.js->motion.js. 195 | 3. `mijia` update miio. 196 | 197 | ### 1.0.5 198 | 1. `mijia` yeelight accessory(bugfix). 199 | 200 | ### 1.0.4 201 | 1. `mijia` yeelight accessory. 202 | 2. `mijia` fix wifi device bug. 203 | 204 | ### 1.0.3 205 | 1. `mijia` vacuum accessory. 206 | 2. `mijia` powerplug accessory. 207 | 3. `mijia` powerstrip accessory. 208 | 209 | ### 1.0.2 210 | 1. `mijia` magnet sensor accessory. 211 | 2. `mijia` ctrln1/ctrln2 switch accessory. 212 | 3. `mijia` ctrlneutral1/ctrlneutral2 switch accessory. 213 | 4. `mijia` motion sensor accessory. 214 | 5. `mijia` plug/86plug plug accessory. 215 | 6. `mijia` 86sw1/86sw2 switch accessory. 216 | 7. `mijia` switch accessory. 217 | 8. `mijia` smoke sensor accessory. 218 | 9. `mijia` natgas sensor accessory. 219 | 10. `mijia` air-purifier accessory. 220 | 11. `broadlink` mp1/mp2 plug accessory. 221 | ### 1.0.1 222 | 1. `mijia` gateway sensor accessory. 223 | 2. `mijia` door and window sensor accessory. 224 | 3. `mijia` temperature and humidity sensor accessory. -------------------------------------------------------------------------------- /kit/mijia/airpurifier.js: -------------------------------------------------------------------------------- 1 | const Base = require('./base'); 2 | const miio = require('miio'); 3 | let PlatformAccessory, Accessory, Service, Characteristic, UUIDGen; 4 | class AirPurifier extends Base { 5 | constructor(mijia, config) { 6 | super(mijia); 7 | this.config = config; 8 | this.model = config.model; 9 | this.devices = {}; //all airpurifier devices 10 | PlatformAccessory = mijia.PlatformAccessory; 11 | Accessory = mijia.Accessory; 12 | Service = mijia.Service; 13 | Characteristic = mijia.Characteristic; 14 | UUIDGen = mijia.UUIDGen; 15 | this.discover(); 16 | } 17 | setAirPurifier(reg, device) { 18 | let sid = reg.id; 19 | let uuid = UUIDGen.generate('Mijia-AirPurifier@' + sid) 20 | let accessory = this.mijia.accessories[uuid]; 21 | let service_air, service_air_sensor, service_temperature, service_humidity, service_led; 22 | if (!accessory) { 23 | let name = sid; 24 | accessory = new PlatformAccessory(name, uuid, Accessory.Categories.AirPurifier); 25 | accessory.getService(Service.AccessoryInformation) 26 | .setCharacteristic(Characteristic.Manufacturer, "Mijia") 27 | .setCharacteristic(Characteristic.Model, "Mijia AirPurifier") 28 | .setCharacteristic(Characteristic.SerialNumber, sid); 29 | accessory.on('identify', function (paired, callback) { 30 | callback(); 31 | }); 32 | service_air = new Service.AirPurifier(name); // airpurifier 33 | accessory.addService(service_air, name); 34 | service_air_sensor = new Service.AirQualitySensor(name); 35 | accessory.addService(service_air_sensor, name); 36 | service_temperature = new Service.TemperatureSensor(name); 37 | accessory.addService(service_temperature, name); 38 | service_humidity = new Service.HumiditySensor(name); 39 | accessory.addService(service_humidity, name); 40 | service_led = new Service.Lightbulb(name); 41 | accessory.addService(service_led, name); 42 | } else { 43 | service_air = accessory.getService(Service.AirPurifier); 44 | service_air_sensor = accessory.getService(Service.AirQualitySensor); 45 | service_temperature = accessory.getService(Service.TemperatureSensor); 46 | service_humidity = accessory.getService(Service.HumiditySensor); 47 | service_led = accessory.getService(Service.Lightbulb); 48 | } 49 | accessory.reachable = true; 50 | accessory.context.sid = sid; 51 | accessory.context.model = this.model; 52 | //bind 53 | let setter = service_air.getCharacteristic(Characteristic.Active).listeners('set'); 54 | if (!setter || setter.length == 0) { 55 | //service_air 56 | service_air.getCharacteristic(Characteristic.Active).on('get', (callback) => { 57 | let device = this.devices[sid]; 58 | if (device != undefined) { 59 | callback(null, device.power ? Characteristic.Active.ACTIVE : Characteristic.Active.INACTIVE); 60 | } else { 61 | callback(null, Characteristic.Active.INACTIVE); 62 | } 63 | }).on('set', (value, callback) => { 64 | let device = this.devices[sid]; 65 | if (device != undefined) { 66 | device.setPower(value == Characteristic.Active.ACTIVE ? true : false); 67 | } 68 | callback(null, value); 69 | }); 70 | 71 | // 72 | service_air.getCharacteristic(Characteristic.TargetAirPurifierState).on('set', (value, callback) => { 73 | let device = this.devices[sid]; 74 | if (device != undefined) { 75 | //'idle','auto','silent','favorite' 76 | if (value == Characteristic.TargetAirPurifierState.AUTO) { 77 | device.setMode('auto'); 78 | } else { 79 | device.setMode('idle'); 80 | } 81 | } 82 | callback(null, value); 83 | }); 84 | 85 | service_air.getCharacteristic(Characteristic.CurrentAirPurifierState).on('get', (callback) => { 86 | let device = this.devices[sid]; 87 | if (device != undefined) { 88 | if (!device.power) { 89 | callback(null, Characteristic.CurrentAirPurifierState.INACTIVE); 90 | return; 91 | } 92 | //'idle','auto','silent','favorite' 93 | if (device.mode == 'auto' || device.mode == 'silent' || device.mode == 'favorite') { 94 | callback(null, Characteristic.CurrentAirPurifierState.PURIFYING_AIR); 95 | } else if (device.mode == 'idle') { 96 | callback(null, Characteristic.CurrentAirPurifierState.IDLE); 97 | } else { 98 | callback(null, Characteristic.CurrentAirPurifierState.INACTIVE); 99 | } 100 | } else { 101 | callback(null, Characteristic.CurrentAirPurifierState.INACTIVE); 102 | } 103 | }); 104 | 105 | service_air.getCharacteristic(Characteristic.RotationSpeed).on('get', (callback) => { 106 | let device = this.devices[sid]; 107 | if (device != undefined) { 108 | if (!device.power) { 109 | callback(); 110 | return; 111 | } 112 | if (device.mode == 'idle') { 113 | callback(null, 25); 114 | } else if (device.mode == 'silent') { 115 | callback(null, 50); 116 | } else if (device.mode == 'auto') { 117 | callback(null, 75); 118 | } else if (device.mode == 'favorite') { 119 | callback(null, 100); 120 | } else { 121 | callback(); 122 | } 123 | } else { 124 | callback(); 125 | } 126 | }).on('set', (value, callback) => { 127 | let device = this.devices[sid]; 128 | if (device != undefined) { 129 | if (!device.power) { 130 | callback(); 131 | return; 132 | } 133 | if (value > 0 && value <= 25) { 134 | device.setMode('idle'); 135 | } else if (value > 25 && value <= 50) { 136 | device.setMode('silent'); 137 | } else if (value > 50 && value <= 75) { 138 | device.setMode('auto'); 139 | } else if (value > 75 && value <= 100) { 140 | device.setMode('favorite'); 141 | } 142 | callback(null, value); 143 | } else { 144 | callback(); 145 | } 146 | }); 147 | //service_air_sensor 148 | service_air_sensor.getCharacteristic(Characteristic.AirQuality).on('get', (callback) => { 149 | let device = this.devices[sid]; 150 | let value = Characteristic.AirQuality.UNKNOWN; 151 | if (device != undefined) { 152 | if (!device.power) { 153 | callback(); 154 | return; 155 | } 156 | let levels = [ 157 | [200, Characteristic.AirQuality.POOR], 158 | [150, Characteristic.AirQuality.INFERIOR], 159 | [100, Characteristic.AirQuality.FAIR], 160 | [50, Characteristic.AirQuality.GOOD], 161 | [0, Characteristic.AirQuality.EXCELLENT], 162 | ]; 163 | for (let level in levels) { 164 | if (device.aqi > level[0]) { 165 | value = level[1]; 166 | } 167 | }; 168 | } 169 | callback(null, value); 170 | }); 171 | //service_temperature 172 | service_temperature.getCharacteristic(Characteristic.CurrentTemperature).on('get', (callback) => { 173 | let device = this.devices[sid]; 174 | let value = 0; 175 | if (device != undefined) { 176 | if (!device.power) { 177 | callback(null, value); 178 | return; 179 | } 180 | value = device.temperature; 181 | } 182 | callback(null, value); 183 | }); 184 | //service_humidity 185 | service_humidity.getCharacteristic(Characteristic.CurrentRelativeHumidity).on('get', (callback) => { 186 | let device = this.devices[sid]; 187 | let value = 0; 188 | if (device != undefined) { 189 | if (!device.power) { 190 | callback(null, value); 191 | return; 192 | } 193 | value = device.humidity; 194 | } 195 | callback(null, value); 196 | }); 197 | //service_led 198 | service_led.getCharacteristic(Characteristic.On).on('get', (callback) => { 199 | let device = this.devices[sid]; 200 | let value = false; 201 | if (device != undefined) { 202 | if (!device.power) { 203 | callback(null, value); 204 | return; 205 | } 206 | value = device.led; 207 | } 208 | callback(null, value); 209 | }).on('set', (value, callback) => { 210 | let device = this.devices[sid]; 211 | if (device != undefined) { 212 | if (!device.power) { 213 | callback(null, false); 214 | return; 215 | } 216 | device.setLed(value); 217 | } 218 | callback(null, value); 219 | }); 220 | 221 | service_led.getCharacteristic(Characteristic.Brightness).on('get', (callback) => { 222 | let device = this.devices[sid]; 223 | let value = false; 224 | if (device != undefined) { 225 | if (!device.power) { 226 | callback(null, value); 227 | return; 228 | } 229 | value = device.ledBrightness; 230 | } 231 | callback(null, value); 232 | }).on('set', (value, callback) => { 233 | let device = this.devices[sid]; 234 | if (device != undefined) { 235 | if (!device.power) { 236 | callback(null, false); 237 | return; 238 | } 239 | if (value > 50) { 240 | device.setLedBrightness('bright'); 241 | } else if (value > 15) { 242 | device.setLedBrightness('dim'); 243 | } else { 244 | device.setLedBrightness('off'); 245 | } 246 | } 247 | callback(null, value); 248 | }); 249 | } 250 | if (!this.mijia.accessories[uuid]) { 251 | this.mijia.accessories[uuid] = accessory; 252 | this.registerAccessory([accessory]); 253 | } 254 | } 255 | 256 | discover() { 257 | this.mijia.log.debug('try to discover ' + this.model); 258 | let browser = miio.browse(); //require a new browse 259 | browser.on('available', (reg) => { 260 | if (!reg.token) { //airpurifier support Auto-token 261 | return; 262 | } 263 | miio.device(reg).then((device) => { 264 | if (device.type != this.model) { 265 | return; 266 | } 267 | this.devices[reg.id] = device; 268 | this.mijia.log.debug('find model->%s with hostname->%s id->%s @ %s:%s.', device.model, reg.hostname, device.id, device.address, device.port); 269 | this.setAirPurifier(reg, device); 270 | }); 271 | }); 272 | 273 | browser.on('unavailable', (reg) => { 274 | if (!reg.token) { //airpurifier support Auto-token 275 | return; 276 | } 277 | if (this.devices[reg.id] != undefined) { 278 | this.devices[reg.id].destroy(); 279 | delete this.devices[reg.id]; 280 | } 281 | }); 282 | } 283 | 284 | } 285 | 286 | module.exports = AirPurifier; -------------------------------------------------------------------------------- /kit/mijia.js: -------------------------------------------------------------------------------- 1 | var PlatformAccessory, Accessory, Service, Characteristic, UUIDGen, CommunityTypes; 2 | var _homebridge; 3 | 4 | const miio = require('miio'); 5 | const dgram = require('dgram'); 6 | const util = require('util'); 7 | const inherits = require('util').inherits; 8 | const crypto = require('crypto'); 9 | const devices = require('./mijia/'); 10 | const types = require('../util/types'); 11 | const iv = Buffer.from([0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58, 0x56, 0x2e]); 12 | const multicastIp = '224.0.0.50'; 13 | const multicastPort = 4321; 14 | const udpPort = 9898; 15 | 16 | 17 | class Mijia { 18 | // config may be null 19 | // api may be null if launched from old homebridge version 20 | constructor(log, config, api) { 21 | this.PlatformAccessory = PlatformAccessory; 22 | this.Accessory = Accessory; 23 | this.Service = Service; 24 | this.Characteristic = Characteristic; 25 | this.UUIDGen = UUIDGen; 26 | this.CommunityTypes = CommunityTypes; 27 | //define 28 | this.udpScoket = null; 29 | this.log = log; 30 | this.config = config; 31 | //init properties 32 | this.gateways = {}; 33 | this.accessories = {}; 34 | //device object 35 | this.devices = {}; 36 | //supported device parser 37 | this._devices = {}; 38 | if (api) { 39 | // Save the API object as plugin needs to register new accessory via this object. 40 | this.api = api; 41 | } else { 42 | this.log.error("Homebridge's version is too old, please upgrade!"); 43 | } 44 | //init upd server 45 | this.initUpdSocket().then(() => { 46 | //init config 47 | this.initConfig(config); 48 | //init device parsers 49 | this.loadDevices(); 50 | //discover wifi device 51 | this.discoverWifiDevice(); 52 | }).catch((err) => { 53 | this.log.error('mijia init upd socket error->%s', err); 54 | }); 55 | if (this.api) { 56 | this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this)); 57 | } 58 | if (_homebridge.mijia != undefined) { 59 | _homebridge.mijia = this; 60 | } 61 | this.log.debug('mijia constructor done'); 62 | } 63 | /** 64 | * static method to export hap properties 65 | * @param {*homebridge} homebridge 66 | */ 67 | static init(homebridge) { 68 | return new Promise((resolve, reject) => { 69 | homebridge.registerPlatform("homebridge-smarthome", "smarthome-mijia", Mijia, true); 70 | resolve(); 71 | }); 72 | } 73 | /** 74 | * init properties from config.json 75 | */ 76 | initConfig(config) { 77 | let { mijia } = config; 78 | let { sids, passwords, devices } = mijia; 79 | if (sids && passwords) { 80 | if (sids.length != passwords.length) { 81 | throw new Error('sids length and passwords length must be equal'); 82 | } 83 | } 84 | sids.map((sid, index) => { 85 | this.gateways[sid] = { password: passwords[index], devices: {} }; 86 | }); 87 | if (devices && devices.length > 0) { //for wifi devices 88 | devices.map((device) => { 89 | if (device.sid != undefined) { 90 | this.devices[device.sid] = device; 91 | } else if (device.name != undefined) { 92 | this.devices[device.name] = device; 93 | } else { 94 | this.log.warn('device do not have sid or name,will discard to register'); 95 | } 96 | }); 97 | } 98 | this.log.debug('initConfig done'); 99 | } 100 | /** 101 | * init udpScoket 102 | */ 103 | initUpdSocket() { 104 | return new Promise((resolve, reject) => { 105 | this.udpScoket = dgram.createSocket({ 106 | type: 'udp4', 107 | reuseAddr: true 108 | }); 109 | this.udpScoket.on('message', (msg, rinfo) => { 110 | this.log.debug('mijia udp socket receive -> %s', new String(msg)); 111 | this.parseMsg(msg, rinfo); 112 | }); 113 | this.udpScoket.on('error', (err) => { 114 | this.log.error('error, msg -> %s, stack -> %s', err.message, err.stack); 115 | reject(error); 116 | }); 117 | this.udpScoket.on('listening', () => { 118 | this.log.debug("mijia upd server is listening on port 9898"); 119 | this.udpScoket.addMembership(multicastIp); 120 | this.log.debug("mijia add multicast to %s", multicastIp); 121 | resolve(); 122 | }); 123 | this.udpScoket.bind(udpPort); 124 | }); 125 | } 126 | 127 | /** 128 | * init parsers 129 | */ 130 | loadDevices() { 131 | this._devices = devices(this); 132 | this.log.debug('loadDevices done'); 133 | } 134 | 135 | didFinishLaunching() { 136 | //1.discover who are mijia gateway 137 | let cmd_whois = { cmd: 'whois' }; 138 | this.sendMsg(cmd_whois, multicastIp, multicastPort); 139 | //2.set interval to update mijia gateway and check the gateway alive 140 | setInterval(() => { 141 | this.sendMsg(cmd_whois, multicastIp, multicastPort); 142 | }, 1800000); //1800s->30m 143 | } 144 | 145 | /** 146 | * configure cached accessory 147 | * @param {*} accessory 148 | */ 149 | configureAccessory(accessory) { 150 | accessory.reachable = true; 151 | accessory.on('identify', (paired, callback) => { 152 | this.log.debug(accessory.displayName + " -> Identify!!!"); 153 | callback(); 154 | }); 155 | if (!this.accessories[accessory.UUID]) { 156 | this.accessories[accessory.UUID] = accessory; 157 | } 158 | } 159 | 160 | /** 161 | * discover wifi deivce 162 | */ 163 | discoverWifiDevice() { 164 | for (let key in this.devices) { 165 | let device = this.devices[key]; 166 | let { type, model } = device; 167 | if (type == 'wifi') { 168 | if (this._devices[model]) { 169 | this._devices[model](this, device); 170 | this.log.debug('construct wifi device->%s', util.inspect(device)); 171 | } else { 172 | this.log.warn('not support device->%s', util.inspect(device)); 173 | } 174 | } 175 | } 176 | this.log.debug('discoverWifiDevice done'); 177 | } 178 | /** 179 | * discover zigbee deivce via gateway 180 | * @param {*gateway ip} ip 181 | * @param {*gateway port} port 182 | */ 183 | discoverZigbeeDevice(ip, port) { 184 | let cmd_get_id_list = { cmd: 'get_id_list' }; 185 | this.sendMsg(cmd_get_id_list, ip, port); 186 | setInterval(() => { 187 | this.sendMsg(cmd_get_id_list, ip, port); 188 | }, 300000); //300s->5m 189 | } 190 | 191 | /** 192 | * send data via upd socket 193 | * @param {*msg} msg 194 | * @param {*ip} ip 195 | * @param {*port} port 196 | */ 197 | sendMsg(msg, ip, port) { 198 | this.log.debug('send msg->%s', util.inspect(msg)); 199 | if (typeof msg == 'string') { 200 | this.udpScoket.send(msg, 0, msg.length, port, ip); 201 | } else { 202 | let str = JSON.stringify(msg); 203 | this.udpScoket.send(str, 0, str.length, port, ip); 204 | } 205 | } 206 | 207 | /** 208 | * send data via upd socket 209 | * @param {*msg} msg 210 | * @param {*sid} sid 211 | */ 212 | sendMsgToSid(msg, sid) { 213 | this.log.debug('send msg->%s', util.inspect(msg)); 214 | let gateway = this.devices[sid]; 215 | if (!gateway) { 216 | gateway = this.gateways[sid]; 217 | } else { 218 | gateway = gateway.gateway; 219 | } 220 | if (!gateway) { 221 | this.log.error(`can't find gateway sid->${sid}`); 222 | return; 223 | } 224 | let { ip, port } = gateway; 225 | if (typeof msg == 'string') { 226 | this.udpScoket.send(msg, 0, msg.length, port, ip); 227 | } else { 228 | let str = JSON.stringify(msg); 229 | this.udpScoket.send(str, 0, str.length, port, ip); 230 | } 231 | } 232 | /** 233 | * parse msg from udp socket 234 | * @param {*msg} msg 235 | * @param {*remoteaddr} rinfo 236 | */ 237 | parseMsg(msg, rinfo) { 238 | let json; 239 | try { 240 | json = JSON.parse(msg); 241 | } catch (ex) { 242 | this.log.error('parse json msg failed -> %s', ex); 243 | return; 244 | } 245 | let cmd = json.cmd; 246 | switch (cmd) { 247 | case 'iam': { 248 | let { ip, port, model } = json; 249 | if (model == 'gateway') { 250 | this.discoverZigbeeDevice(ip, port); 251 | } else { 252 | this.log.warn('receive a iam cmd,but model is %s', model); 253 | } 254 | break; 255 | } 256 | case 'get_id_list_ack': { 257 | let { sid, token } = json; 258 | let data = JSON.parse(json.data); 259 | let gateway = this.gateways[sid] ? this.gateways[sid] : { sid: sid, model: 'gateway', token: token, devices:{} }; 260 | 261 | gateway.ip = rinfo.address; 262 | gateway.port = rinfo.port; 263 | gateway.token = token; 264 | gateway.last_time = new Date(); 265 | 266 | let cmd_read = { cmd: 'read', sid: sid }; 267 | this.sendMsg(cmd_read, gateway.ip, gateway.port); 268 | data.map((did, index) => { 269 | if (!this.devices[did]) { 270 | this.devices[did] = { sid: did, type: 'zigbee' }; 271 | } 272 | this.devices[did].gateway = gateway; 273 | this.devices[did].last_time = new Date(); 274 | gateway.devices[did] = this.devices[did]; 275 | cmd_read.sid = did; 276 | this.sendMsg(cmd_read, gateway.ip, gateway.port); 277 | }); 278 | break; 279 | } 280 | case 'heartbeat': { 281 | let { sid, model, short_id, token } = json; 282 | let data = JSON.parse(json.data); 283 | if (model == 'gateway') { 284 | let gateway = this.gateways[sid] ? this.gateways[sid] : { sid: sid }; 285 | this.gateways[sid] = gateway; 286 | this.gateways[sid].model = 'gateway'; 287 | this.gateways[sid].short_id = short_id; 288 | this.gateways[sid].token = token; 289 | this.gateways[sid].last_time = new Date(); 290 | } else { 291 | let device = this.devices[sid] ? this.devices[sid] : { sid: sid, short_id: short_id, type: 'zigbee' }; 292 | device = Object.assign(device, data); 293 | device.last_time = new Date(); 294 | this.devices[sid] = device; 295 | } 296 | break; 297 | } 298 | case 'write_ack': { 299 | this.log.debug('write_ack ->%s', util.inspect(json)); 300 | break; 301 | } 302 | case 'read_ack': 303 | case 'report': { 304 | this.parseDevice(json, rinfo); 305 | break; 306 | } 307 | default: { 308 | this.log.warn('unkonwn cmd:[%s] from getway[%s]', cmd, (rinfo.address + ':' + rinfo.port)); 309 | } 310 | } 311 | } 312 | /** 313 | * parse zigbee devices msg 314 | * @param {*gateway json msg} json 315 | * @param {*remote info} rinfo 316 | */ 317 | parseDevice(json, rinfo) { 318 | //when the device status changed , will recive data 319 | let { sid, model, short_id, token } = json; 320 | let data = JSON.parse(json.data); 321 | if (model == 'gateway') { 322 | if (short_id) { 323 | this.gateways[sid].short_id = short_id; 324 | } 325 | if (token) { 326 | this.gateways[sid].token = token; 327 | } 328 | this.gateways[sid].last_time = new Date(); 329 | } else { 330 | let device = this.devices[sid] ? this.devices[sid] : { sid: sid, short_id: short_id, model: model }; 331 | device = Object.assign(device, data); 332 | device.last_time = new Date(); 333 | this.devices[sid] = device; 334 | } 335 | if (this._devices[model]) { 336 | this._devices[model].parseMsg(json, rinfo); 337 | } else { 338 | this.log.warn('receive report cmd, but no support device found->%s', model); 339 | } 340 | } 341 | /** 342 | * generate gateway write Key 343 | * @param {*gateway id} sid 344 | */ 345 | generateKey(sid) { 346 | let gateway = this.devices[sid]; 347 | if (!gateway) { 348 | gateway = this.gateways[sid]; 349 | } else { 350 | gateway = gateway.gateway; 351 | } 352 | if (!gateway) { 353 | this.log.error(`can't find gateway sid->${sid}`); 354 | return; 355 | } 356 | let { password, token } = gateway; 357 | let cipher = crypto.createCipheriv('aes-128-cbc', password, iv); 358 | let key = cipher.update(token, "ascii", "hex"); 359 | cipher.final('hex'); 360 | return key; 361 | } 362 | } 363 | //module exports define 364 | module.exports = (homebridge) => { 365 | //export some properties from homebridge 366 | PlatformAccessory = homebridge.platformAccessory; 367 | Accessory = homebridge.hap.Accessory; 368 | Service = homebridge.hap.Service; 369 | Characteristic = homebridge.hap.Characteristic; 370 | UUIDGen = homebridge.hap.uuid; 371 | CommunityTypes = types(homebridge); 372 | _homebridge = homebridge; 373 | //init mikit 374 | return Mijia.init(homebridge); 375 | } -------------------------------------------------------------------------------- /kit/broadlink/broadlink.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lib from broadlinkjs-sm 3 | * @link https://github.com/smka/broadlinkjs-sm 4 | */ 5 | var util = require('util'); 6 | let EventEmitter = require('events'); 7 | let dgram = require('dgram'); 8 | let os = require('os'); 9 | let crypto = require('crypto'); 10 | 11 | var Broadlink = module.exports = function () { 12 | EventEmitter.call(this); 13 | this.devices = {}; 14 | } 15 | util.inherits(Broadlink, EventEmitter); 16 | 17 | 18 | Broadlink.prototype.genDevice = function (devtype, host, mac) { 19 | var dev; 20 | if (devtype == 0) { // SP1 21 | dev = new device(host, mac); 22 | dev.sp1(); 23 | return dev; 24 | } else if (devtype == 0x2711) { // SP2 25 | dev = new device(host, mac); 26 | dev.sp2(); 27 | return dev; 28 | } else if (devtype == 0x2719 || devtype == 0x7919 || devtype == 0x271a || devtype == 0x791a) { // Honeywell SP2 29 | dev = new device(host, mac); 30 | dev.sp2(); 31 | return dev; 32 | } else if (devtype == 0x2720) { // SPMini 33 | dev = new device(host, mac); 34 | dev.sp2(); 35 | return dev; 36 | } else if (devtype == 0x753e) { // SP3 37 | dev = new device(host, mac); 38 | dev.sp2(); 39 | return dev; 40 | } else if (devtype == 0x2728) { // SPMini2 41 | dev = new device(host, mac); 42 | dev.sp2(); 43 | return dev; 44 | } else if (devtype == 0x2733 || devtype == 0x273e) { // OEM branded SPMini Contros 45 | dev = new device(host, mac); 46 | dev.sp2(); 47 | return dev; 48 | } else if (devtype >= 0x7530 && devtype <= 0x7918) { // OEM branded SPMini2 49 | dev = new device(host, mac); 50 | dev.sp2(); 51 | return dev; 52 | } else if (devtype == 0x2736) { // SPMiniPlus 53 | dev = new device(host, mac); 54 | dev.sp2(); 55 | return dev; 56 | } 57 | else if (devtype == 0x2712) { // RM2 58 | dev = new device(host, mac); 59 | dev.rm(); 60 | return dev; 61 | } else if (devtype == 0x2737) { // RM Mini 62 | dev = new device(host, mac); 63 | dev.rm(); 64 | return dev; 65 | } else if (devtype == 0x273d) { // RM Pro Phicomm 66 | dev = new device(host, mac); 67 | dev.rm(); 68 | return dev; 69 | } else if (devtype == 0x2783) { // RM2 Home Plus 70 | dev = new device(host, mac); 71 | dev.rm(); 72 | return dev; 73 | } else if (devtype == 0x277c) { // RM2 Home Plus GDT 74 | dev = new device(host, mac); 75 | dev.rm(); 76 | return dev; 77 | } else if (devtype == 0x272a) { // RM2 Pro Plus 78 | dev = new device(host, mac); 79 | dev.rm(); 80 | return dev; 81 | } else if (devtype == 0x2787) { // RM2 Pro Plus2 82 | dev = new device(host, mac); 83 | dev.rm(); 84 | return dev; 85 | } else if (devtype == 0x278b) { // RM2 Pro Plus BL 86 | dev = new device(host, mac); 87 | dev.rm(); 88 | return dev; 89 | } else if (devtype == 0x278f) { // RM Mini Shate 90 | dev = new device(host, mac); 91 | dev.rm(); 92 | return dev; 93 | } 94 | else if (devtype == 0x2714) { // A1 95 | dev = new device(host, mac); 96 | dev.a1(); 97 | return dev; 98 | } else if (devtype == 0x4EB5) { // MP1 99 | dev = new device(host, mac); 100 | dev.mp1(); 101 | return dev; 102 | } else if (devtype == 0x4F1B) { // MP2 103 | dev = new device(host, mac); 104 | dev.mp2(); 105 | return dev; 106 | } else { 107 | return null; 108 | } 109 | } 110 | 111 | Broadlink.prototype.discover = function (timeout) { 112 | if (!timeout) { 113 | timeout = 1000; 114 | } 115 | self = this; 116 | var interfaces = os.networkInterfaces(); 117 | var addresses = []; 118 | for (var k in interfaces) { 119 | for (var k2 in interfaces[k]) { 120 | var address = interfaces[k][k2]; 121 | if (address.family === 'IPv4' && !address.internal) { 122 | addresses.push(address.address); 123 | } 124 | } 125 | } 126 | var address = addresses[0].split('.'); 127 | var cs = dgram.createSocket({ type: 'udp4', reuseAddr: true }); 128 | cs.on('listening', function () { 129 | cs.setBroadcast(true); 130 | 131 | var port = cs.address().port; 132 | var now = new Date(); 133 | var starttime = now.getTime(); 134 | 135 | var timezone = now.getTimezoneOffset() / -3600; 136 | var packet = Buffer.alloc(0x30, 0); 137 | 138 | var year = now.getYear(); 139 | 140 | if (timezone < 0) { 141 | packet[0x08] = 0xff + timezone - 1; 142 | packet[0x09] = 0xff; 143 | packet[0x0a] = 0xff; 144 | packet[0x0b] = 0xff; 145 | } else { 146 | packet[0x08] = timezone; 147 | packet[0x09] = 0; 148 | packet[0x0a] = 0; 149 | packet[0x0b] = 0; 150 | } 151 | packet[0x0c] = year & 0xff; 152 | packet[0x0d] = year >> 8; 153 | packet[0x0e] = now.getMinutes(); 154 | packet[0x0f] = now.getHours(); 155 | var subyear = year % 100; 156 | packet[0x10] = subyear; 157 | packet[0x11] = now.getDay(); 158 | packet[0x12] = now.getDate(); 159 | packet[0x13] = now.getMonth(); 160 | packet[0x18] = parseInt(address[0]); 161 | packet[0x19] = parseInt(address[1]); 162 | packet[0x1a] = parseInt(address[2]); 163 | packet[0x1b] = parseInt(address[3]); 164 | packet[0x1c] = port & 0xff; 165 | packet[0x1d] = port >> 8; 166 | packet[0x26] = 6; 167 | var checksum = 0xbeaf; 168 | 169 | for (var i = 0; i < packet.length; i++) { 170 | checksum += packet[i]; 171 | } 172 | checksum = checksum & 0xffff; 173 | packet[0x20] = checksum & 0xff; 174 | packet[0x21] = checksum >> 8; 175 | 176 | cs.sendto(packet, 0, packet.length, 80, '255.255.255.255'); 177 | 178 | }); 179 | 180 | cs.on("message", (msg, rinfo) => { 181 | var host = rinfo; 182 | 183 | var mac = Buffer.alloc(6, 0); 184 | msg.copy(mac, 0x00, 0x3F); 185 | msg.copy(mac, 0x01, 0x3E); 186 | msg.copy(mac, 0x02, 0x3D); 187 | msg.copy(mac, 0x03, 0x3C); 188 | msg.copy(mac, 0x04, 0x3B); 189 | msg.copy(mac, 0x05, 0x3A); 190 | 191 | var devtype = msg[0x34] | msg[0x35] << 8; 192 | if (!this.devices) { 193 | this.devices = {}; 194 | } 195 | 196 | if (!this.devices[mac]) { 197 | var dev = this.genDevice(devtype, host, mac); 198 | if (dev) { 199 | this.devices[mac] = dev; 200 | dev.on("deviceReady", () => { this.emit("deviceReady", dev); }); 201 | dev.auth(); 202 | } 203 | } 204 | }); 205 | 206 | cs.on('close', function () { 207 | //console.log('===Server Closed'); 208 | }); 209 | 210 | cs.bind(); 211 | 212 | setTimeout(function () { 213 | cs.close(); 214 | }, timeout); 215 | } 216 | 217 | function device(host, mac, timeout = 10) { 218 | this.host = host; 219 | this.mac = mac; 220 | this.emitter = new EventEmitter(); 221 | 222 | this.on = this.emitter.on; 223 | this.emit = this.emitter.emit; 224 | this.removeListener = this.emitter.removeListener; 225 | 226 | this.timeout = timeout; 227 | this.count = Math.random() & 0xffff; 228 | this.key = new Buffer([0x09, 0x76, 0x28, 0x34, 0x3f, 0xe9, 0x9e, 0x23, 0x76, 0x5c, 0x15, 0x13, 0xac, 0xcf, 0x8b, 0x02]); 229 | this.iv = new Buffer([0x56, 0x2e, 0x17, 0x99, 0x6d, 0x09, 0x3d, 0x28, 0xdd, 0xb3, 0xba, 0x69, 0x5a, 0x2e, 0x6f, 0x58]); 230 | this.id = new Buffer([0, 0, 0, 0]); 231 | this.cs = dgram.createSocket({ type: 'udp4', reuseAddr: true }); 232 | this.cs.on('listening', function () { 233 | //this.cs.setBroadcast(true); 234 | }); 235 | this.cs.on("message", (response, rinfo) => { 236 | var enc_payload = Buffer.alloc(response.length - 0x38, 0); 237 | response.copy(enc_payload, 0, 0x38); 238 | 239 | var decipher = crypto.createDecipheriv('aes-128-cbc', this.key, this.iv); 240 | decipher.setAutoPadding(false); 241 | var payload = decipher.update(enc_payload); 242 | var p2 = decipher.final(); 243 | if (p2) { 244 | payload = Buffer.concat([payload, p2]); 245 | } 246 | 247 | if (!payload) { 248 | return false; 249 | } 250 | 251 | var command = response[0x26]; 252 | var err = response[0x22] | (response[0x23] << 8); 253 | 254 | if (err != 0) return; 255 | 256 | if (command == 0xe9) { 257 | this.key = Buffer.alloc(0x10, 0); 258 | payload.copy(this.key, 0, 0x04, 0x14); 259 | 260 | this.id = Buffer.alloc(0x04, 0); 261 | payload.copy(this.id, 0, 0x00, 0x04); 262 | this.emit("deviceReady"); 263 | } else if (command == 0xee) { 264 | this.emit("payload", err, payload); 265 | } 266 | 267 | }); 268 | this.cs.bind(); 269 | this.type = "Unknown"; 270 | 271 | } 272 | 273 | device.prototype.auth = function () { 274 | var payload = Buffer.alloc(0x50, 0); 275 | payload[0x04] = 0x31; 276 | payload[0x05] = 0x31; 277 | payload[0x06] = 0x31; 278 | payload[0x07] = 0x31; 279 | payload[0x08] = 0x31; 280 | payload[0x09] = 0x31; 281 | payload[0x0a] = 0x31; 282 | payload[0x0b] = 0x31; 283 | payload[0x0c] = 0x31; 284 | payload[0x0d] = 0x31; 285 | payload[0x0e] = 0x31; 286 | payload[0x0f] = 0x31; 287 | payload[0x10] = 0x31; 288 | payload[0x11] = 0x31; 289 | payload[0x12] = 0x31; 290 | payload[0x1e] = 0x01; 291 | payload[0x2d] = 0x01; 292 | payload[0x30] = 'T'.charCodeAt(0); 293 | payload[0x31] = 'e'.charCodeAt(0); 294 | payload[0x32] = 's'.charCodeAt(0); 295 | payload[0x33] = 't'.charCodeAt(0); 296 | payload[0x34] = ' '.charCodeAt(0); 297 | payload[0x35] = ' '.charCodeAt(0); 298 | payload[0x36] = '1'.charCodeAt(0); 299 | 300 | this.sendPacket(0x65, payload); 301 | 302 | } 303 | 304 | device.prototype.exit = function () { 305 | var self = this; 306 | setTimeout(function () { 307 | self.cs.close(); 308 | }, 500); 309 | } 310 | 311 | device.prototype.getType = function () { 312 | return this.type; 313 | } 314 | 315 | device.prototype.sendPacket = function (command, payload) { 316 | this.count = (this.count + 1) & 0xffff; 317 | var packet = Buffer.alloc(0x38, 0); 318 | packet[0x00] = 0x5a; 319 | packet[0x01] = 0xa5; 320 | packet[0x02] = 0xaa; 321 | packet[0x03] = 0x55; 322 | packet[0x04] = 0x5a; 323 | packet[0x05] = 0xa5; 324 | packet[0x06] = 0xaa; 325 | packet[0x07] = 0x55; 326 | packet[0x24] = 0x2a; 327 | packet[0x25] = 0x27; 328 | packet[0x26] = command; 329 | packet[0x28] = this.count & 0xff; 330 | packet[0x29] = this.count >> 8; 331 | packet[0x2a] = this.mac[0]; 332 | packet[0x2b] = this.mac[1]; 333 | packet[0x2c] = this.mac[2]; 334 | packet[0x2d] = this.mac[3]; 335 | packet[0x2e] = this.mac[4]; 336 | packet[0x2f] = this.mac[5]; 337 | packet[0x30] = this.id[0]; 338 | packet[0x31] = this.id[1]; 339 | packet[0x32] = this.id[2]; 340 | packet[0x33] = this.id[3]; 341 | 342 | var checksum = 0xbeaf; 343 | for (var i = 0; i < payload.length; i++) { 344 | checksum += payload[i]; 345 | checksum = checksum & 0xffff; 346 | } 347 | 348 | var cipher = crypto.createCipheriv('aes-128-cbc', this.key, this.iv); 349 | payload = cipher.update(payload); 350 | var p2 = cipher.final(); 351 | 352 | packet[0x34] = checksum & 0xff; 353 | packet[0x35] = checksum >> 8; 354 | 355 | packet = Buffer.concat([packet, payload]); 356 | 357 | checksum = 0xbeaf; 358 | for (var i = 0; i < packet.length; i++) { 359 | checksum += packet[i]; 360 | checksum = checksum & 0xffff; 361 | } 362 | packet[0x20] = checksum & 0xff; 363 | packet[0x21] = checksum >> 8; 364 | //console.log("dev send packet to " + this.host.address + ":" + this.host.port); 365 | this.cs.sendto(packet, 0, packet.length, this.host.port, this.host.address); 366 | } 367 | 368 | device.prototype.mp1 = function () { 369 | this.type = "MP1"; 370 | 371 | this.set_power = function (sid, state) { 372 | //"""Sets the power state of the smart power strip.""" 373 | var sid_mask = 0x01 << (sid - 1); 374 | var packet = Buffer.alloc(16, 0); 375 | packet[0x00] = 0x0d; 376 | packet[0x02] = 0xa5; 377 | packet[0x03] = 0xa5; 378 | packet[0x04] = 0x5a; 379 | packet[0x05] = 0x5a; 380 | packet[0x06] = 0xb2 + (state ? (sid_mask << 1) : sid_mask); 381 | packet[0x07] = 0xc0; 382 | packet[0x08] = 0x02; 383 | packet[0x0a] = 0x03; 384 | packet[0x0d] = sid_mask; 385 | packet[0x0e] = state ? sid_mask : 0; 386 | 387 | this.sendPacket(0x6a, packet); 388 | } 389 | 390 | this.check_power = function () { 391 | //"""Returns the power state of the smart power strip in raw format.""" 392 | var packet = Buffer.alloc(16, 0); 393 | packet[0x00] = 0x0a; 394 | packet[0x02] = 0xa5; 395 | packet[0x03] = 0xa5; 396 | packet[0x04] = 0x5a; 397 | packet[0x05] = 0x5a; 398 | packet[0x06] = 0xae; 399 | packet[0x07] = 0xc0; 400 | packet[0x08] = 0x01; 401 | 402 | this.sendPacket(0x6a, packet); 403 | } 404 | 405 | this.on("payload", (err, payload) => { 406 | var param = payload[0]; 407 | switch (param) { 408 | case 14: 409 | var s1 = Boolean(payload[0x0e] & 0x01); 410 | var s2 = Boolean(payload[0x0e] & 0x02); 411 | var s3 = Boolean(payload[0x0e] & 0x04); 412 | var s4 = Boolean(payload[0x0e] & 0x08); 413 | this.emit("mp_power", [s1, s2, s3, s4]); 414 | break; 415 | default: 416 | break; 417 | } 418 | }); 419 | } 420 | 421 | device.prototype.mp2 = function () { 422 | this.type = "MP2"; 423 | 424 | this.set_power = function (sid, state) { 425 | //"""Sets the power state of the smart power strip.""" 426 | var sid_mask = 0x01 << (sid - 1); 427 | var packet = Buffer.alloc(16, 0); 428 | packet[0x00] = 0x0d; 429 | packet[0x02] = 0xa5; 430 | packet[0x03] = 0xa5; 431 | packet[0x04] = 0x5a; 432 | packet[0x05] = 0x5a; 433 | packet[0x06] = 0xb2 + (state ? (sid_mask << 1) : sid_mask); 434 | packet[0x07] = 0xc0; 435 | packet[0x08] = 0x02; 436 | packet[0x0a] = 0x03; 437 | packet[0x0d] = sid_mask; 438 | packet[0x0e] = state ? sid_mask : 0; 439 | 440 | this.sendPacket(0x6a, packet); 441 | } 442 | 443 | this.check_power = function () { 444 | //"""Returns the power state of the smart power strip in raw format.""" 445 | var packet = Buffer.alloc(16, 0); 446 | packet[0x00] = 0x0a; 447 | packet[0x02] = 0xa5; 448 | packet[0x03] = 0xa5; 449 | packet[0x04] = 0x5a; 450 | packet[0x05] = 0x5a; 451 | packet[0x06] = 0xae; 452 | packet[0x07] = 0xc0; 453 | packet[0x08] = 0x01; 454 | 455 | this.sendPacket(0x6a, packet); 456 | } 457 | 458 | this.on("payload", (err, payload) => { 459 | var param = payload[0]; 460 | switch (param) { 461 | case 0x1b: 462 | var s1 = Boolean(payload[0x0e] & 0x01); 463 | var s2 = Boolean(payload[0x0e] & 0x02); 464 | var s3 = Boolean(payload[0x0e] & 0x04); 465 | var s4 = Boolean(payload[0x0e] & 0x08); 466 | this.emit("mp_power", [s1, s2, s3, s4]); 467 | break; 468 | default: 469 | break; 470 | } 471 | }); 472 | } 473 | 474 | device.prototype.sp1 = function () { 475 | this.type = "SP1"; 476 | this.set_power = function (state) { 477 | var packet = Buffer.alloc(4, 4); 478 | packet[0] = state; 479 | this.sendPacket(0x66, packet); 480 | } 481 | } 482 | 483 | 484 | 485 | device.prototype.sp2 = function () { 486 | var self = this; 487 | this.type = "SP2"; 488 | this.set_power = function (state) { 489 | //"""Sets the power state of the smart plug.""" 490 | var packet = Buffer.alloc(16, 0); 491 | packet[0] = 2; 492 | packet[4] = state ? 1 : 0; 493 | this.sendPacket(0x6a, packet); 494 | 495 | } 496 | 497 | this.check_power = function () { 498 | //"""Returns the power state of the smart plug.""" 499 | var packet = Buffer.alloc(16, 0); 500 | packet[0] = 1; 501 | this.sendPacket(0x6a, packet); 502 | 503 | } 504 | 505 | this.on("payload", (err, payload) => { 506 | var param = payload[0]; 507 | switch (param) { 508 | case 1: //get from check_power 509 | var pwr = Boolean(payload[0x4]); 510 | this.emit("power", pwr); 511 | break; 512 | } 513 | 514 | }); 515 | 516 | 517 | } 518 | 519 | device.prototype.a1 = function () { 520 | this.type = "A1"; 521 | this.check_sensors = function () { 522 | var packet = Buffer.alloc(16, 0); 523 | packet[0] = 1; 524 | this.sendPacket(0x6a, packet); 525 | } 526 | 527 | this.check_sensors_raw = function () { 528 | var packet = Buffer.alloc(16, 0); 529 | packet[0] = 1; 530 | this.sendPacket(0x6a, packet); 531 | } 532 | } 533 | 534 | 535 | device.prototype.rm = function () { 536 | this.type = "RM2"; 537 | this.checkData = function () { 538 | var packet = Buffer.alloc(16, 0); 539 | packet[0] = 4; 540 | this.sendPacket(0x6a, packet); 541 | } 542 | 543 | this.sendData = function (data) { 544 | packet = new Buffer([0x02, 0x00, 0x00, 0x00]); 545 | packet = Buffer.concat([packet, data]); 546 | this.sendPacket(0x6a, packet); 547 | } 548 | 549 | this.enterLearning = function () { 550 | var packet = Buffer.alloc(16, 0); 551 | packet[0] = 3; 552 | this.sendPacket(0x6a, packet); 553 | } 554 | 555 | this.checkTemperature = function () { 556 | var packet = Buffer.alloc(16, 0); 557 | packet[0] = 1; 558 | this.sendPacket(0x6a, packet); 559 | } 560 | 561 | this.on("payload", (err, payload) => { 562 | var param = payload[0]; 563 | switch (param) { 564 | case 1: 565 | var temp = (payload[0x4] * 10 + payload[0x5]) / 10.0; 566 | this.emit("temperature", temp); 567 | break; 568 | case 4: //get from check_data 569 | var data = Buffer.alloc(payload.length - 4, 0); 570 | payload.copy(data, 0, 4); 571 | this.emit("rawData", data); 572 | break; 573 | } 574 | }); 575 | } -------------------------------------------------------------------------------- /util/types.js: -------------------------------------------------------------------------------- 1 | var inherits = require('util').inherits; 2 | var Service, Characteristic; 3 | 4 | module.exports = function(homebridge) { 5 | Service = homebridge.hap.Service; 6 | Characteristic = homebridge.hap.Characteristic; 7 | 8 | var CommunityTypes = {}; 9 | 10 | 11 | // Characteristics 12 | 13 | CommunityTypes.Timestamp = function() { 14 | Characteristic.call(this, "Timestamp", CommunityTypes.Timestamp.UUID); 15 | this.setProps({ 16 | format: Characteristic.Formats.STRING, 17 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 18 | }); 19 | this.value = this.getDefaultValue(); 20 | }; 21 | CommunityTypes.Timestamp.UUID = 'FF000001-0000-1000-8000-135D67EC4377'; 22 | inherits(CommunityTypes.Timestamp, Characteristic); 23 | 24 | CommunityTypes.AudioDataURL = function() { 25 | Characteristic.call(this, "Audio URL", CommunityTypes.AudioDataURL.UUID); 26 | this.setProps({ 27 | format: Characteristic.Formats.STRING, 28 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 29 | }); 30 | }; 31 | CommunityTypes.AudioDataURL.UUID = 'FF000002-0000-1000-8000-135D67EC4377'; 32 | inherits(CommunityTypes.AudioDataURL, Characteristic); 33 | 34 | CommunityTypes.VideoDataURL = function() { 35 | Characteristic.call(this, "Video URL", CommunityTypes.VideoDataURL.UUID); 36 | this.setProps({ 37 | format: Characteristic.Formats.STRING, 38 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 39 | }); 40 | }; 41 | CommunityTypes.VideoDataURL.UUID = 'FF000003-0000-1000-8000-135D67EC4377'; 42 | inherits(CommunityTypes.VideoDataURL, Characteristic); 43 | 44 | CommunityTypes.AudioVolume = function() { 45 | Characteristic.call(this, 'Audio Volume', CommunityTypes.AudioVolume.UUID); 46 | this.setProps({ 47 | format: Characteristic.Formats.UINT8, 48 | unit: Characteristic.Units.PERCENTAGE, 49 | maxValue: 100, 50 | minValue: 0, 51 | minStep: 1, 52 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 53 | }); 54 | this.value = this.getDefaultValue(); 55 | }; 56 | CommunityTypes.AudioVolume.UUID = '00001001-0000-1000-8000-135D67EC4377'; 57 | inherits(CommunityTypes.AudioVolume, Characteristic); 58 | 59 | CommunityTypes.Muting = function() { 60 | Characteristic.call(this, 'Muting', CommunityTypes.Muting.UUID); 61 | this.setProps({ 62 | format: Characteristic.Formats.UINT8, 63 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 64 | }); 65 | this.value = this.getDefaultValue(); 66 | }; 67 | CommunityTypes.Muting.UUID = '00001002-0000-1000-8000-135D67EC4377'; 68 | inherits(CommunityTypes.Muting, Characteristic); 69 | 70 | CommunityTypes.PlaybackState = function() { 71 | Characteristic.call(this, 'Playback State', CommunityTypes.PlaybackState.UUID); 72 | this.setProps({ 73 | format: Characteristic.Formats.UINT8, 74 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 75 | }); 76 | this.value = this.getDefaultValue(); 77 | }; 78 | CommunityTypes.PlaybackState.UUID = '00002001-0000-1000-8000-135D67EC4377'; 79 | inherits(CommunityTypes.PlaybackState, Characteristic); 80 | CommunityTypes.PlaybackState.PLAYING = 0; 81 | CommunityTypes.PlaybackState.PAUSED = 1; 82 | CommunityTypes.PlaybackState.STOPPED = 2; 83 | 84 | CommunityTypes.SkipForward = function() { 85 | Characteristic.call(this, 'Skip Forward', CommunityTypes.SkipForward.UUID); 86 | this.setProps({ 87 | format: Characteristic.Formats.BOOL, 88 | perms: [ Characteristic.Perms.WRITE ] 89 | }); 90 | this.value = this.getDefaultValue(); 91 | }; 92 | CommunityTypes.SkipForward.UUID = '00002002-0000-1000-8000-135D67EC4377'; 93 | inherits(CommunityTypes.SkipForward, Characteristic); 94 | 95 | CommunityTypes.SkipBackward = function() { 96 | Characteristic.call(this, 'Skip Backward', CommunityTypes.SkipBackward.UUID); 97 | this.setProps({ 98 | format: Characteristic.Formats.BOOL, 99 | perms: [ Characteristic.Perms.WRITE ] 100 | }); 101 | this.value = this.getDefaultValue(); 102 | }; 103 | CommunityTypes.SkipBackward.UUID = '00002003-0000-1000-8000-135D67EC4377'; 104 | inherits(CommunityTypes.SkipBackward, Characteristic); 105 | 106 | CommunityTypes.ShuffleMode = function() { 107 | Characteristic.call(this, 'Shuffle Mode', CommunityTypes.ShuffleMode.UUID); 108 | this.setProps({ 109 | format: Characteristic.Formats.UINT8, 110 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 111 | }); 112 | this.value = this.getDefaultValue(); 113 | }; 114 | CommunityTypes.ShuffleMode.UUID = '00002004-0000-1000-8000-135D67EC4377'; 115 | inherits(CommunityTypes.ShuffleMode, Characteristic); 116 | //NOTE: If GROUP or SET is not supported, accessories should coerce to ALBUM. 117 | // If ALBUM is not supported, coerce to ITEM. 118 | // In general, it is recommended for apps to only assume OFF, ITEM, and ALBUM 119 | // are supported unless it is known that the accessory supports other settings. 120 | CommunityTypes.ShuffleMode.OFF = 0; 121 | //NOTE: INDIVIDUAL is deprecated. 122 | CommunityTypes.ShuffleMode.ITEM = CommunityTypes.ShuffleMode.INDIVIDUAL = 1; 123 | CommunityTypes.ShuffleMode.GROUP = 2; // e.g. iTunes "Groupings" 124 | CommunityTypes.ShuffleMode.ALBUM = 3; // e.g. album or season 125 | CommunityTypes.ShuffleMode.SET = 4; // e.g. T.V. Series or album box set 126 | 127 | CommunityTypes.RepeatMode = function() { 128 | Characteristic.call(this, 'Repeat Mode', CommunityTypes.RepeatMode.UUID); 129 | this.setProps({ 130 | format: Characteristic.Formats.UINT8, 131 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 132 | }); 133 | this.value = this.getDefaultValue(); 134 | }; 135 | CommunityTypes.RepeatMode.UUID = '00002005-0000-1000-8000-135D67EC4377'; 136 | inherits(CommunityTypes.RepeatMode, Characteristic); 137 | CommunityTypes.RepeatMode.OFF = 0; 138 | CommunityTypes.RepeatMode.ONE = 1; 139 | CommunityTypes.RepeatMode.ALL = 2; 140 | 141 | CommunityTypes.PlaybackSpeed = function() { 142 | Characteristic.call(this, 'Playback Speed', CommunityTypes.PlaybackSpeed.UUID); 143 | this.setProps({ 144 | format: Characteristic.Formats.FLOAT, 145 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 146 | }); 147 | this.value = this.getDefaultValue(); 148 | }; 149 | CommunityTypes.PlaybackSpeed.UUID = '00002006-0000-1000-8000-135D67EC4377'; 150 | inherits(CommunityTypes.PlaybackSpeed, Characteristic); 151 | 152 | CommunityTypes.MediaCurrentPosition = function() { 153 | Characteristic.call(this, 'Media Current Position', CommunityTypes.MediaCurrentPosition.UUID); 154 | this.setProps({ 155 | format: Characteristic.Formats.FLOAT, // In seconds 156 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 157 | }); 158 | this.value = this.getDefaultValue(); 159 | }; 160 | CommunityTypes.MediaCurrentPosition.UUID = '00002007-0000-1000-8000-135D67EC4377'; 161 | inherits(CommunityTypes.MediaCurrentPosition, Characteristic); 162 | 163 | CommunityTypes.MediaItemName = function() { 164 | Characteristic.call(this, 'Media Name', CommunityTypes.MediaItemName.UUID); 165 | this.setProps({ 166 | format: Characteristic.Formats.STRING, 167 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 168 | }); 169 | this.value = this.getDefaultValue(); 170 | }; 171 | CommunityTypes.MediaItemName.UUID = '00003001-0000-1000-8000-135D67EC4377'; 172 | inherits(CommunityTypes.MediaItemName, Characteristic); 173 | 174 | CommunityTypes.MediaItemAlbumName = function() { 175 | Characteristic.call(this, 'Media Album Name', CommunityTypes.MediaItemAlbumName.UUID); 176 | this.setProps({ 177 | format: Characteristic.Formats.STRING, 178 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 179 | }); 180 | this.value = this.getDefaultValue(); 181 | }; 182 | CommunityTypes.MediaItemAlbumName.UUID = '00003002-0000-1000-8000-135D67EC4377'; 183 | inherits(CommunityTypes.MediaItemAlbumName, Characteristic); 184 | 185 | CommunityTypes.MediaItemArtist = function() { 186 | Characteristic.call(this, 'Media Artist', CommunityTypes.MediaItemArtist.UUID); 187 | this.setProps({ 188 | format: Characteristic.Formats.STRING, 189 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 190 | }); 191 | this.value = this.getDefaultValue(); 192 | }; 193 | CommunityTypes.MediaItemArtist.UUID = '00003003-0000-1000-8000-135D67EC4377'; 194 | inherits(CommunityTypes.MediaItemArtist, Characteristic); 195 | 196 | CommunityTypes.MediaItemDuration = function() { 197 | Characteristic.call(this, 'Media Duration', CommunityTypes.MediaItemDuration.UUID); 198 | this.setProps({ 199 | format: Characteristic.Formats.FLOAT, // In seconds 200 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 201 | }); 202 | this.value = this.getDefaultValue(); 203 | }; 204 | CommunityTypes.MediaItemDuration.UUID = '00003005-0000-1000-8000-135D67EC4377'; 205 | inherits(CommunityTypes.MediaItemDuration, Characteristic); 206 | 207 | CommunityTypes.StillImage = function() { 208 | Characteristic.call(this, 'Still Image', CommunityTypes.StillImage.UUID); 209 | this.setProps({ 210 | format: Characteristic.Formats.DATA, 211 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 212 | }); 213 | this.value = this.getDefaultValue(); 214 | }; 215 | CommunityTypes.StillImage.UUID = '00004001-0000-1000-8000-135D67EC4377'; 216 | inherits(CommunityTypes.StillImage, Characteristic); 217 | 218 | // Also known as MIME type... 219 | CommunityTypes.MediaTypeIdentifier = function() { 220 | Characteristic.call(this, 'Media Type Identifier', CommunityTypes.MediaTypeIdentifier.UUID); 221 | this.setProps({ 222 | format: Characteristic.Formats.STRING, 223 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 224 | }); 225 | this.value = null; 226 | }; 227 | CommunityTypes.MediaTypeIdentifier.UUID = '00004002-0000-1000-8000-135D67EC4377'; 228 | inherits(CommunityTypes.MediaTypeIdentifier, Characteristic); 229 | 230 | CommunityTypes.MediaWidth = function() { 231 | Characteristic.call(this, 'Media Width', CommunityTypes.MediaWidth.UUID); 232 | this.setProps({ 233 | format: Characteristic.Formats.UINT32, 234 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 235 | }); 236 | this.value = this.getDefaultValue(); 237 | }; 238 | CommunityTypes.MediaWidth.UUID = '00004003-0000-1000-8000-135D67EC4377'; 239 | inherits(CommunityTypes.MediaWidth, Characteristic); 240 | 241 | CommunityTypes.MediaHeight = function() { 242 | Characteristic.call(this, 'Media Width', CommunityTypes.MediaHeight.UUID); 243 | this.setProps({ 244 | format: Characteristic.Formats.UINT32, 245 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 246 | }); 247 | this.value = this.getDefaultValue(); 248 | }; 249 | CommunityTypes.MediaHeight.UUID = '00004004-0000-1000-8000-135D67EC4377'; 250 | inherits(CommunityTypes.MediaHeight, Characteristic); 251 | 252 | // courtesy of https://gist.github.com/gomfunkel/b1a046d729757120907c 253 | CommunityTypes.Volts = function() { 254 | Characteristic.call(this, 'Volts', CommunityTypes.Volts.UUID); 255 | this.setProps({ 256 | format: Characteristic.Formats.UINT16, 257 | unit: "V", 258 | minValue: 0, 259 | maxValue: 65535, 260 | minStep: 1, 261 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 262 | }); 263 | this.value = this.getDefaultValue(); 264 | }; 265 | CommunityTypes.Volts.UUID = 'E863F10A-079E-48FF-8F27-9C2605A29F52'; 266 | inherits(CommunityTypes.Volts, Characteristic); 267 | 268 | CommunityTypes.Amperes = function() { 269 | Characteristic.call(this, 'Amps', CommunityTypes.Amperes.UUID); 270 | this.setProps({ 271 | format: Characteristic.Formats.UINT16, 272 | unit: "A", 273 | minValue: 0, 274 | maxValue: 65535, 275 | minStep: 1, 276 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 277 | }); 278 | this.value = this.getDefaultValue(); 279 | }; 280 | CommunityTypes.Amperes.UUID = 'E863F126-079E-48FF-8F27-9C2605A29F52'; 281 | inherits(CommunityTypes.Amperes, Characteristic); 282 | 283 | CommunityTypes.Watts = function() { 284 | Characteristic.call(this, 'Consumption', CommunityTypes.Watts.UUID); 285 | this.setProps({ 286 | format: Characteristic.Formats.UINT16, 287 | unit: "W", 288 | minValue: 0, 289 | maxValue: 65535, 290 | minStep: 1, 291 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 292 | }); 293 | this.value = this.getDefaultValue(); 294 | }; 295 | CommunityTypes.Watts.UUID = 'E863F10D-079E-48FF-8F27-9C2605A29F52'; 296 | inherits(CommunityTypes.Watts, Characteristic); 297 | 298 | CommunityTypes.VoltAmperes = function() { 299 | Characteristic.call(this, 'Apparent Power', CommunityTypes.VoltAmperes.UUID); 300 | this.setProps({ 301 | format: Characteristic.Formats.UINT16, 302 | unit: "VA", 303 | minValue: 0, 304 | maxValue: 65535, 305 | minStep: 1, 306 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 307 | }); 308 | this.value = this.getDefaultValue(); 309 | }; 310 | CommunityTypes.VoltAmperes.UUID = 'E863F110-079E-48FF-8F27-9C2605A29F52'; 311 | inherits(CommunityTypes.VoltAmperes, Characteristic); 312 | 313 | CommunityTypes.KilowattHours = function() { 314 | Characteristic.call(this, 'Total Consumption', CommunityTypes.KilowattHours.UUID); 315 | this.setProps({ 316 | format: Characteristic.Formats.UINT32, 317 | unit: "kWh", 318 | minValue: 0, 319 | maxValue: 65535, 320 | minStep: 1, 321 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 322 | }); 323 | this.value = this.getDefaultValue(); 324 | }; 325 | CommunityTypes.KilowattHours.UUID = 'E863F10C-079E-48FF-8F27-9C2605A29F52'; 326 | inherits(CommunityTypes.KilowattHours, Characteristic); 327 | 328 | CommunityTypes.KilowattVoltAmpereHour = function() { 329 | Characteristic.call(this, 'Apparent Energy', CommunityTypes.KilowattVoltAmpereHour.UUID); 330 | this.setProps({ 331 | format: Characteristic.Formats.UINT32, 332 | unit: "kVAh", 333 | minValue: 0, 334 | maxValue: 65535, 335 | minStep: 1, 336 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 337 | }); 338 | this.value = this.getDefaultValue(); 339 | }; 340 | CommunityTypes.KilowattVoltAmpereHour.UUID = 'E863F127-079E-48FF-8F27-9C2605A29F52'; 341 | inherits(CommunityTypes.KilowattVoltAmpereHour, Characteristic); 342 | 343 | CommunityTypes.BatteryLevel = function() { 344 | Characteristic.call(this, 'Battery Level', CommunityTypes.BatteryLevel.UUID); 345 | this.setProps({ 346 | format: Characteristic.Formats.UINT16, 347 | unit: Characteristic.Units.PERCENTAGE, 348 | maxValue: 100, 349 | minValue: 0, 350 | minStep: 1, 351 | perms: [ Characteristic.Perms.READ ] 352 | }); 353 | this.value = this.getDefaultValue(); 354 | }; 355 | CommunityTypes.BatteryLevel.UUID = 'E863F11B-079E-48FF-8F27-9C2605A29F52'; 356 | inherits(CommunityTypes.BatteryLevel, Characteristic); 357 | 358 | // courtesy of https://github.com/robi-van-kinobi/homebridge-cubesensors 359 | 360 | CommunityTypes.AtmosphericPressureLevel = function () { 361 | Characteristic.call(this, 'Barometric Pressure', CommunityTypes.AtmosphericPressureLevel.UUID); 362 | this.setProps({ 363 | format: Characteristic.Formats.UINT8, 364 | unit: "mbar", 365 | minValue: 800, 366 | maxValue: 1200, 367 | minStep: 1, 368 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 369 | }); 370 | this.value = this.getDefaultValue(); 371 | }; 372 | CommunityTypes.AtmosphericPressureLevel.UUID = '28FDA6BC-9C2A-4DEA-AAFD-B49DB6D155AB'; 373 | inherits(CommunityTypes.AtmosphericPressureLevel, Characteristic); 374 | 375 | CommunityTypes.NoiseLevel = function () { 376 | Characteristic.call(this, 'Noise Level', CommunityTypes.NoiseLevel.UUID); 377 | this.setProps({ 378 | format: Characteristic.Formats.UINT8, 379 | unit: "dB", 380 | minValue: 0, 381 | maxValue: 200, 382 | minStep: 1, 383 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 384 | }); 385 | this.value = this.getDefaultValue(); 386 | }; 387 | CommunityTypes.NoiseLevel.UUID = '2CD7B6FD-419A-4740-8995-E3BFE43735AB'; 388 | inherits(CommunityTypes.NoiseLevel, Characteristic); 389 | 390 | // courtesy of https://github.com/homespun/homebridge-platform-snmp 391 | 392 | CommunityTypes.AirFlow = function () { 393 | Characteristic.call(this, 'Air Flow', CommunityTypes.AirFlow.UUID); 394 | this.setProps({ 395 | format: Characteristic.Formats.UINT8, 396 | unit: "m/s", 397 | minValue: 0, 398 | maxValue: 135, 399 | minStep: 1, 400 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 401 | }); 402 | this.value = this.getDefaultValue(); 403 | }; 404 | CommunityTypes.AirFlow.UUID = '49C8AE5A-A3A5-41AB-BF1F-12D5654F9F41'; 405 | inherits(CommunityTypes.AirFlow, Characteristic); 406 | 407 | CommunityTypes.NitrogenDioxideDetected = function () { 408 | Characteristic.call(this, 'Nitrogen Dioxide Detected', CommunityTypes.NitrogenDioxideDetected.UUID); 409 | this.setProps({ 410 | format: Characteristic.Formats.UINT8, 411 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 412 | }); 413 | this.value = this.getDefaultValue(); 414 | }; 415 | CommunityTypes.NitrogenDioxideDetected.UUID = 'D737B40A-3AF0-4316-950F-76090B98C5CF'; 416 | inherits(CommunityTypes.NitrogenDioxideDetected, Characteristic); 417 | 418 | CommunityTypes.NitrogenDioxideDetected.NO2_LEVELS_NORMAL = 0; 419 | CommunityTypes.NitrogenDioxideDetected.NO2_LEVELS_ABNORMAL = 1; 420 | 421 | CommunityTypes.NitrogenDioxideLevel = function () { 422 | Characteristic.call(this, 'Nitrogen Dioxide Level', CommunityTypes.NitrogenDioxideLevel.UUID); 423 | this.setProps({ 424 | format: Characteristic.Formats.FLOAT, 425 | unit: "ppm", 426 | minValue: 0, 427 | maxValue: 1500, 428 | minStep: 1, 429 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 430 | }); 431 | this.value = this.getDefaultValue(); 432 | }; 433 | CommunityTypes.NitrogenDioxideLevel.UUID = 'B762A2AF-D9D0-4A79-814A-E9EBAB0ED290'; 434 | inherits(CommunityTypes.NitrogenDioxideLevel, Characteristic); 435 | 436 | CommunityTypes.NitrogenDioxidePeakLevel = function () { 437 | Characteristic.call(this, 'Nitrogen Dioxide Peak Level', CommunityTypes.NitrogenDioxidePeakLevel.UUID); 438 | this.setProps({ 439 | format: Characteristic.Formats.FLOAT, 440 | unit: "ppm", 441 | minValue: 0, 442 | maxValue: 1500, 443 | minStep: 1, 444 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 445 | }); 446 | this.value = this.getDefaultValue(); 447 | }; 448 | CommunityTypes.NitrogenDioxidePeakLevel.UUID = 'B6594847-7B88-496C-A1A0-B7860F3D7601'; 449 | inherits(CommunityTypes.NitrogenDioxidePeakLevel, Characteristic); 450 | 451 | // courtesy of https://github.com/homespun/homebridge-platform-aqe 452 | CommunityTypes.OzoneDetected = function () { 453 | Characteristic.call(this, 'Ozone Detected', CommunityTypes.OzoneDetected.UUID); 454 | this.setProps({ 455 | format: Characteristic.Formats.UINT8, 456 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 457 | }); 458 | this.value = this.getDefaultValue(); 459 | }; 460 | CommunityTypes.OzoneDetected.UUID = '0168FA60-5CF4-4314-AA45-0F84E389A093'; 461 | inherits(CommunityTypes.OzoneDetected, Characteristic); 462 | 463 | CommunityTypes.OzoneDetected.O3_LEVELS_NORMAL = 0; 464 | CommunityTypes.OzoneDetected.O3_LEVELS_ABNORMAL = 1; 465 | 466 | CommunityTypes.OzoneLevel = function () { 467 | Characteristic.call(this, 'Ozone Level', CommunityTypes.OzoneLevel.UUID); 468 | this.setProps({ 469 | format: Characteristic.Formats.FLOAT, 470 | unit: "ppb", 471 | minValue: 0, 472 | maxValue: 1500, 473 | minStep: 0.01, 474 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 475 | }); 476 | this.value = this.getDefaultValue(); 477 | }; 478 | CommunityTypes.OzoneLevel.UUID = '03C17FD9-672E-42F5-8DD4-30C6822C739A'; 479 | inherits(CommunityTypes.OzoneLevel, Characteristic); 480 | 481 | CommunityTypes.OzonePeakLevel = function () { 482 | Characteristic.call(this, 'Ozone Peak Level', CommunityTypes.OzonePeakLevel.UUID); 483 | this.setProps({ 484 | format: Characteristic.Formats.FLOAT, 485 | unit: "ppb", 486 | minValue: 0, 487 | maxValue: 1500, 488 | minStep: 0.01, 489 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 490 | }); 491 | this.value = this.getDefaultValue(); 492 | }; 493 | CommunityTypes.OzonePeakLevel.UUID = '550EE1FF-FC66-4BB6-A1C1-4B0A07109AE3'; 494 | inherits(CommunityTypes.OzonePeakLevel, Characteristic); 495 | 496 | CommunityTypes.SodiumDioxideDetected = function () { 497 | Characteristic.call(this, 'Sodium Dioxide Detected', CommunityTypes.SodiumDioxideDetected.UUID); 498 | this.setProps({ 499 | format: Characteristic.Formats.UINT8, 500 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 501 | }); 502 | this.value = this.getDefaultValue(); 503 | }; 504 | CommunityTypes.SodiumDioxideDetected.UUID = '4D237DAB-1CB6-4D52-B446-4667F58F7D28'; 505 | inherits(CommunityTypes.SodiumDioxideDetected, Characteristic); 506 | 507 | CommunityTypes.SodiumDioxideDetected.SO2_LEVELS_NORMAL = 0; 508 | CommunityTypes.SodiumDioxideDetected.SO2_LEVELS_ABNORMAL = 1; 509 | 510 | CommunityTypes.SodiumDioxideLevel = function () { 511 | Characteristic.call(this, 'Sodium Dioxide Level', CommunityTypes.SodiumDioxideLevel.UUID); 512 | this.setProps({ 513 | format: Characteristic.Formats.FLOAT, 514 | unit: "ppb", 515 | minValue: 0, 516 | maxValue: 1500, 517 | minStep: 0.01, 518 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 519 | }); 520 | this.value = this.getDefaultValue(); 521 | }; 522 | CommunityTypes.SodiumDioxideLevel.UUID = '66C4D315-FBEF-470E-9434-B047679F1141'; 523 | inherits(CommunityTypes.SodiumDioxideLevel, Characteristic); 524 | 525 | CommunityTypes.SodiumDioxidePeakLevel = function () { 526 | Characteristic.call(this, 'Sodium Dioxide Peak Level', CommunityTypes.SodiumDioxidePeakLevel.UUID); 527 | this.setProps({ 528 | format: Characteristic.Formats.FLOAT, 529 | unit: "ppb", 530 | minValue: 0, 531 | maxValue: 1500, 532 | minStep: 0.01, 533 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 534 | }); 535 | this.value = this.getDefaultValue(); 536 | }; 537 | CommunityTypes.SodiumDioxidePeakLevel.UUID = '4CD6F648-2F92-43D8-86DF-0E8DE75E033B'; 538 | inherits(CommunityTypes.SodiumDioxidePeakLevel, Characteristic); 539 | 540 | CommunityTypes.VolatileOrganicCompoundDetected = function () { 541 | Characteristic.call(this, 'Volatile Organic Compound Detected', CommunityTypes.VolatileOrganicCompoundDetected.UUID); 542 | this.setProps({ 543 | format: Characteristic.Formats.UINT8, 544 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 545 | }); 546 | this.value = this.getDefaultValue(); 547 | }; 548 | CommunityTypes.VolatileOrganicCompoundDetected.UUID = '65DBC0F5-C40B-4E04-ADED-DC70031B0B82'; 549 | inherits(CommunityTypes.VolatileOrganicCompoundDetected, Characteristic); 550 | 551 | CommunityTypes.VolatileOrganicCompoundDetected.VOC_LEVELS_NORMAL = 0; 552 | CommunityTypes.VolatileOrganicCompoundDetected.VOC_LEVELS_ABNORMAL = 1; 553 | 554 | CommunityTypes.VolatileOrganicCompoundLevel = function () { 555 | Characteristic.call(this, 'Volatile Organic Compound Level', CommunityTypes.VolatileOrganicCompoundLevel.UUID); 556 | this.setProps({ 557 | format: Characteristic.Formats.FLOAT, 558 | unit: "ppb", 559 | minValue: 0, 560 | maxValue: 1500, 561 | minStep: 0.01, 562 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 563 | }); 564 | this.value = this.getDefaultValue(); 565 | }; 566 | CommunityTypes.VolatileOrganicCompoundLevel.UUID = '35C4C797-193D-4998-879F-A08514E87897'; 567 | inherits(CommunityTypes.VolatileOrganicCompoundLevel, Characteristic); 568 | 569 | CommunityTypes.VolatileOrganicCompoundPeakLevel = function () { 570 | Characteristic.call(this, 'Volatile Organic Compound Peak Level', CommunityTypes.VolatileOrganicCompoundPeakLevel.UUID); 571 | this.setProps({ 572 | format: Characteristic.Formats.FLOAT, 573 | unit: "ppb", 574 | minValue: 0, 575 | maxValue: 1500, 576 | minStep: 0.01, 577 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 578 | }); 579 | this.value = this.getDefaultValue(); 580 | }; 581 | CommunityTypes.VolatileOrganicCompoundPeakLevel.UUID = 'A62CB784-1916-4BDF-B840-BDB9F8A264E9'; 582 | inherits(CommunityTypes.VolatileOrganicCompoundPeakLevel, Characteristic); 583 | 584 | CommunityTypes.NotificationCode = function() { 585 | Characteristic.call(this, 'Notification Code', CommunityTypes.NotificationCode.UUID); 586 | this.setProps({ 587 | format: Characteristic.Formats.UINT8, 588 | maxValue: 255, 589 | minValue: 0, 590 | minStep: 1, 591 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.WRITE, Characteristic.Perms.NOTIFY ] 592 | }); 593 | this.value = 255; 594 | }; 595 | CommunityTypes.NotificationCode.UUID = '381C47A3-CB06-4177-8E3D-A1B4C22EB031'; 596 | inherits(CommunityTypes.NotificationCode, Characteristic); 597 | 598 | CommunityTypes.NotificationText = function() { 599 | Characteristic.call(this, 'Notification Text', CommunityTypes.NotificationText.UUID); 600 | this.setProps({ 601 | format: Characteristic.Formats.STRING, 602 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 603 | }); 604 | this.value = ''; 605 | }; 606 | CommunityTypes.NotificationText.UUID = 'e244ca80-813e-423a-86bd-02f293b857a0'; 607 | inherits(CommunityTypes.NotificationText, Characteristic); 608 | 609 | // Services 610 | 611 | CommunityTypes.AudioDeviceService = function(displayName, subtype) { 612 | Service.call(this, displayName, CommunityTypes.AudioDeviceService.UUID, subtype); 613 | 614 | // Required Characteristics 615 | this.addCharacteristic(CommunityTypes.AudioVolume); 616 | 617 | // Optional Characteristics 618 | this.addOptionalCharacteristic(CommunityTypes.Muting); 619 | this.addOptionalCharacteristic(Characteristic.Name); 620 | }; 621 | CommunityTypes.AudioDeviceService.UUID = '00000001-0000-1000-8000-135D67EC4377'; 622 | inherits(CommunityTypes.AudioDeviceService, Service); 623 | 624 | CommunityTypes.PlaybackDeviceService = function(displayName, subtype) { 625 | Service.call(this, displayName, CommunityTypes.PlaybackDeviceService.UUID, subtype); 626 | 627 | // Required Characteristics 628 | this.addCharacteristic(CommunityTypes.PlaybackState); 629 | 630 | // Optional Characteristics 631 | this.addOptionalCharacteristic(CommunityTypes.SkipForward); 632 | this.addOptionalCharacteristic(CommunityTypes.SkipBackward); 633 | this.addOptionalCharacteristic(CommunityTypes.ShuffleMode); 634 | this.addOptionalCharacteristic(CommunityTypes.RepeatMode); 635 | this.addOptionalCharacteristic(CommunityTypes.PlaybackSpeed); 636 | this.addOptionalCharacteristic(CommunityTypes.MediaCurrentPosition); 637 | this.addOptionalCharacteristic(CommunityTypes.MediaItemName); 638 | this.addOptionalCharacteristic(CommunityTypes.MediaItemAlbumName); 639 | this.addOptionalCharacteristic(CommunityTypes.MediaItemArtist); 640 | this.addOptionalCharacteristic(CommunityTypes.MediaItemDuration); 641 | this.addOptionalCharacteristic(Characteristic.Name); 642 | // Artwork characteristics...would be better reported in a separate service? 643 | this.addOptionalCharacteristic(CommunityTypes.StillImage); 644 | this.addOptionalCharacteristic(CommunityTypes.MediaTypeIdentifier); 645 | this.addOptionalCharacteristic(CommunityTypes.MediaWidth); 646 | this.addOptionalCharacteristic(CommunityTypes.MediaHeight); 647 | }; 648 | CommunityTypes.PlaybackDeviceService.UUID = '00000002-0000-1000-8000-135D67EC4377'; 649 | inherits(CommunityTypes.PlaybackDeviceService, Service); 650 | 651 | // A media information service that has no playback controls, for e.g. DAB radio... 652 | CommunityTypes.MediaInformationService = function(displayName, subtype) { 653 | Service.call(this, displayName, CommunityTypes.MediaInformationService.UUID, subtype); 654 | 655 | // Required Characteristics 656 | this.addCharacteristic(CommunityTypes.MediaItemName); 657 | 658 | // Optional Characteristics 659 | this.addOptionalCharacteristic(CommunityTypes.MediaItemAlbumName); 660 | this.addOptionalCharacteristic(CommunityTypes.MediaItemArtist); 661 | this.addOptionalCharacteristic(CommunityTypes.MediaItemDuration); 662 | this.addOptionalCharacteristic(CommunityTypes.MediaCurrentPosition); 663 | this.addOptionalCharacteristic(Characteristic.Name); 664 | // Artwork characteristics...would be better reported in a separate service? 665 | this.addOptionalCharacteristic(CommunityTypes.StillImage); 666 | this.addOptionalCharacteristic(CommunityTypes.MediaTypeIdentifier); 667 | this.addOptionalCharacteristic(CommunityTypes.MediaWidth); 668 | this.addOptionalCharacteristic(CommunityTypes.MediaHeight); 669 | }; 670 | CommunityTypes.MediaInformationService.UUID = '00000003-0000-1000-8000-135D67EC4377'; 671 | inherits(CommunityTypes.MediaInformationService, Service); 672 | 673 | CommunityTypes.StillImageService = function(displayName, subtype) { 674 | Service.call(this, displayName, CommunityTypes.StillImageService.UUID, subtype); 675 | 676 | // Required Characteristics 677 | this.addCharacteristic(CommunityTypes.StillImage); 678 | this.addCharacteristic(CommunityTypes.MediaTypeIdentifier); 679 | 680 | // Optional Characteristics 681 | this.addOptionalCharacteristic(CommunityTypes.MediaWidth); 682 | this.addOptionalCharacteristic(CommunityTypes.MediaHeight); 683 | this.addOptionalCharacteristic(Characteristic.Name); 684 | }; 685 | CommunityTypes.StillImageService.UUID = '00000004-0000-1000-8000-135D67EC4377'; 686 | inherits(CommunityTypes.StillImageService, Service); 687 | 688 | CommunityTypes.SecurityCameraService = function(displayName, subtype) { 689 | Service.call(this, displayName, CommunityTypes.SecurityCameraService.UUID, subtype); 690 | 691 | // Required Characteristics 692 | this.addCharacteristic(CommunityTypes.StillImageService); 693 | this.addCharacteristic(CommunityTypes.MediaTypeIdentifier); 694 | 695 | // Optional Characteristics 696 | this.addOptionalCharacteristic(CommunityTypes.Timestamp); 697 | this.addOptionalCharacteristic(CommunityTypes.MediaWidth); 698 | this.addOptionalCharacteristic(CommunityTypes.MediaHeight); 699 | this.addOptionalCharacteristic(CommunityTypes.VideoDataURL); 700 | this.addOptionalCharacteristic(CommunityTypes.AudioDataURL); 701 | this.addOptionalCharacteristic(Characteristic.MotionDetected); 702 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 703 | this.addOptionalCharacteristic(Characteristic.Name); 704 | }; 705 | CommunityTypes.SecurityCameraService.UUID = '00000005-0000-1000-8000-135D67EC4377'; 706 | 707 | // courtesy of https://github.com/robi-van-kinobi/homebridge-cubesensors 708 | CommunityTypes.AtmosphericPressureSensor = function (displayName, subtype) { 709 | Service.call(this, displayName, CommunityTypes.AtmosphericPressureSensor.UUID, subtype); 710 | 711 | // Required Characteristics 712 | this.addCharacteristic(CommunityTypes.AtmosphericPressureLevel); 713 | 714 | // Optional Characteristics 715 | this.addOptionalCharacteristic(Characteristic.StatusActive); 716 | this.addOptionalCharacteristic(Characteristic.StatusFault); 717 | this.addOptionalCharacteristic(Characteristic.StatusLowBattery); 718 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 719 | this.addOptionalCharacteristic(Characteristic.Name); 720 | }; 721 | CommunityTypes.AtmosphericPressureSensor.UUID = 'B77831FD-D66A-46A4-B66D-FD7EE8DFE3CE'; 722 | inherits(CommunityTypes.AtmosphericPressureSensor, Service); 723 | 724 | CommunityTypes.NoiseLevelSensor = function (displayName, subtype) { 725 | Service.call(this, displayName, CommunityTypes.NoiseLevelSensor.UUID, subtype); 726 | 727 | // Required Characteristics 728 | this.addCharacteristic(CommunityTypes.NoiseLevel); 729 | 730 | // Optional Characteristics 731 | this.addOptionalCharacteristic(Characteristic.StatusActive); 732 | this.addOptionalCharacteristic(Characteristic.StatusFault); 733 | this.addOptionalCharacteristic(Characteristic.StatusLowBattery); 734 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 735 | this.addOptionalCharacteristic(Characteristic.Name); 736 | }; 737 | CommunityTypes.NoiseLevelSensor.UUID = '28FDA6BC-9C2A-4DEA-AAFD-B49DB6D155AB'; 738 | inherits(CommunityTypes.NoiseLevelSensor, Service); 739 | 740 | // courtesy of https://github.com/homespun/homebridge-platform-snmp 741 | 742 | CommunityTypes.AirFlowSensor = function (displayName, subtype) { 743 | Service.call(this, displayName, CommunityTypes.AirFlowSensor.UUID, subtype); 744 | 745 | // Required Characteristics 746 | this.addCharacteristic(CommunityTypes.AirFlow); 747 | 748 | // Optional Characteristics 749 | this.addOptionalCharacteristic(Characteristic.StatusActive); 750 | this.addOptionalCharacteristic(Characteristic.StatusFault); 751 | this.addOptionalCharacteristic(Characteristic.StatusLowBattery); 752 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 753 | this.addOptionalCharacteristic(Characteristic.Name); 754 | }; 755 | CommunityTypes.AirFlowSensor.UUID = 'AF5C192E-420F-4A13-AB67-B8F3968A4935'; 756 | inherits(CommunityTypes.AirFlowSensor, Service); 757 | 758 | CommunityTypes.NitrogenDioxideSensor = function (displayName, subtype) { 759 | Service.call(this, displayName, CommunityTypes.NitrogenDioxideSensor.UUID, subtype); 760 | 761 | // Required Characteristics 762 | this.addCharacteristic(CommunityTypes.NitrogenDioxideDetected); 763 | 764 | // Optional Characteristics 765 | this.addOptionalCharacteristic(Characteristic.StatusActive); 766 | this.addOptionalCharacteristic(Characteristic.StatusFault); 767 | this.addOptionalCharacteristic(Characteristic.StatusLowBattery); 768 | this.addOptionalCharacteristic(CommunityTypes.NitrogenDioxideLevel); 769 | this.addOptionalCharacteristic(CommunityTypes.NitrogenDioxidePeakLevel); 770 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 771 | this.addOptionalCharacteristic(Characteristic.Name); 772 | }; 773 | CommunityTypes.NitrogenDioxideSensor.UUID = '9F6B797D-D43B-4C88-9AA0-57018AB8A91E'; 774 | inherits(CommunityTypes.NitrogenDioxideSensor, Service); 775 | 776 | // courtesy of https://github.com/homespun/homebridge-platform-aqe 777 | CommunityTypes.OzoneSensor = function (displayName, subtype) { 778 | Service.call(this, displayName, CommunityTypes.OzoneSensor.UUID, subtype); 779 | 780 | // Required Characteristics 781 | this.addCharacteristic(CommunityTypes.OzoneDetected); 782 | 783 | // Optional Characteristics 784 | this.addOptionalCharacteristic(Characteristic.StatusActive); 785 | this.addOptionalCharacteristic(Characteristic.StatusFault); 786 | this.addOptionalCharacteristic(Characteristic.StatusLowBattery); 787 | this.addOptionalCharacteristic(CommunityTypes.OzoneLevel); 788 | this.addOptionalCharacteristic(CommunityTypes.OzonePeakLevel); 789 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 790 | this.addOptionalCharacteristic(Characteristic.Name); 791 | }; 792 | CommunityTypes.OzoneSensor.UUID = 'B91C2BD6-D071-4F49-A23B-20721AC6CCEB'; 793 | inherits(CommunityTypes.OzoneSensor, Service); 794 | 795 | CommunityTypes.SodiumDioxideSensor = function (displayName, subtype) { 796 | Service.call(this, displayName, CommunityTypes.SodiumDioxideSensor.UUID, subtype); 797 | 798 | // Required Characteristics 799 | this.addCharacteristic(CommunityTypes.SodiumDioxideDetected); 800 | 801 | // Optional Characteristics 802 | this.addOptionalCharacteristic(Characteristic.StatusActive); 803 | this.addOptionalCharacteristic(Characteristic.StatusFault); 804 | this.addOptionalCharacteristic(Characteristic.StatusLowBattery); 805 | this.addOptionalCharacteristic(CommunityTypes.SodiumDioxideLevel); 806 | this.addOptionalCharacteristic(CommunityTypes.SodiumDioxidePeakLevel); 807 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 808 | this.addOptionalCharacteristic(Characteristic.Name); 809 | }; 810 | CommunityTypes.SodiumDioxideSensor.UUID = 'FE7CFB1F-12D0-405D-86FD-7E268D65C453'; 811 | inherits(CommunityTypes.SodiumDioxideSensor, Service); 812 | 813 | CommunityTypes.VolatileOrganicCompoundSensor = function (displayName, subtype) { 814 | Service.call(this, displayName, CommunityTypes.VolatileOrganicCompoundSensor.UUID, subtype); 815 | 816 | // Required Characteristics 817 | this.addCharacteristic(CommunityTypes.VolatileOrganicCompoundDetected); 818 | 819 | // Optional Characteristics 820 | this.addOptionalCharacteristic(Characteristic.StatusActive); 821 | this.addOptionalCharacteristic(Characteristic.StatusFault); 822 | this.addOptionalCharacteristic(Characteristic.StatusLowBattery); 823 | this.addOptionalCharacteristic(CommunityTypes.VolatileOrganicCompoundLevel); 824 | this.addOptionalCharacteristic(CommunityTypes.VolatileOrganicCompoundPeakLevel); 825 | this.addOptionalCharacteristic(Characteristic.StatusTampered); 826 | this.addOptionalCharacteristic(Characteristic.Name); 827 | }; 828 | CommunityTypes.VolatileOrganicCompoundSensor.UUID = '776E34BC-1660-46EC-A33D-2DFE5B958699'; 829 | inherits(CommunityTypes.VolatileOrganicCompoundSensor, Service); 830 | 831 | CommunityTypes.NotificationService = function (displayName, subtype) { 832 | Service.call(this, displayName, CommunityTypes.NotificationService.UUID, subtype); 833 | 834 | // Required Characteristics 835 | this.addCharacteristic(CommunityTypes.NotificationCode); 836 | this.addCharacteristic(CommunityTypes.NotificationText); 837 | 838 | // Optional Characteristics 839 | this.addOptionalCharacteristic(Characteristic.Name); 840 | }; 841 | CommunityTypes.NotificationService.UUID = '074D8CE9-5B4B-48D5-9990-D98850C2F3FE'; 842 | inherits(CommunityTypes.NotificationService, Service); 843 | 844 | 845 | return CommunityTypes; 846 | }; 847 | 848 | --------------------------------------------------------------------------------