├── userdata └── empty_folder ├── .homeyignore ├── code.png ├── .gitignore ├── assets ├── images │ ├── large.png │ └── small.png └── icon.svg ├── README.md ├── .editorconfig ├── locales ├── en.json └── nl.json ├── README.txt ├── package.json ├── api.js ├── lib ├── custom-services.js ├── custom-characteristics.js ├── index.js └── devices │ ├── doorbell.js │ ├── switch.js │ ├── button.js │ ├── curtains.js │ ├── fan.js │ ├── speaker.js │ ├── dimblinds.js │ ├── garagedooropener.js │ ├── lock.js │ ├── utils.js │ ├── stateblinds.js │ ├── socket.js │ ├── thermostat.js │ ├── securitysystem.js │ ├── light.js │ └── sensor.js ├── settings ├── mock-setup.js ├── homey-settings-mock.js ├── fuse.min.js ├── index.html └── vue.min.js ├── app.json ├── .homeychangelog.json └── app.js /userdata/empty_folder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.homeyignore: -------------------------------------------------------------------------------- 1 | .git 2 | .editorconfig 3 | userdata/ 4 | debug 5 | -------------------------------------------------------------------------------- /code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swttt/com.swttt.homekit/HEAD/code.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | env.json 2 | node_modules 3 | build 4 | debug 5 | 6 | # Added by Homey CLI 7 | /.homeybuild/ 8 | -------------------------------------------------------------------------------- /assets/images/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swttt/com.swttt.homekit/HEAD/assets/images/large.png -------------------------------------------------------------------------------- /assets/images/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swttt/com.swttt.homekit/HEAD/assets/images/small.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | Development of this app has stopped in favor of the [HomeKitty app](https://homey.app/a/name.klep.homekitty/). 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | charset = utf-8 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 8 | -------------------------------------------------------------------------------- /locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "tabs": { 4 | "devices": "Devices", 5 | "log": "Log" 6 | }, 7 | "device": { 8 | "add": "Add", 9 | "delete": "Delete", 10 | "unknown": "Unknown" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /locales/nl.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "tabs": { 4 | "devices": "Apparaten", 5 | "log": "Log" 6 | }, 7 | "device": { 8 | "add": "Toevoegen", 9 | "delete": "Verwijderen", 10 | "unknown": "Onbekend" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | To add Homey devices to your Apple device, open the Home app on your 2 | device, click the "+", then "Add Accessory". 3 | 4 | Choose "More options...", "My Accessory Isn't Shown Here" and press "Enter 5 | code...". Here you can enter the following HomeKit code: 20020200 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.swttt.homekit", 3 | "version": "5.1.0", 4 | "description": "Homekit app for Homey", 5 | "main": "app.js", 6 | "author": "Robert Klep", 7 | "dependencies": { 8 | "lodash.debounce": "^4.0.8", 9 | "node-persist": "^0.0.11" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /api.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | async getDevices({ homey }) { 3 | return await homey.app.getDevices(); 4 | }, 5 | async addDevice({ homey, params, body }) { 6 | return await homey.app.addDeviceById(params.id); 7 | }, 8 | async deleteDevice({ homey, params, body }) { 9 | return await homey.app.deleteDeviceById(params.id); 10 | }, 11 | async clearStorage({ homey }) { 12 | Homey.app.clearStorage(); 13 | return { value : true }; 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /lib/custom-services.js: -------------------------------------------------------------------------------- 1 | const Service = require('hap-nodejs').Service; 2 | const { AirPressure } = require('./custom-characteristics'); 3 | 4 | module.exports.AirPressureSensor = class AirPressureSensor extends Service { 5 | 6 | constructor(displayName, subtype) { 7 | super(displayName, AirPressureSensor.UUID, subtype); 8 | this.addCharacteristic(AirPressure); 9 | } 10 | 11 | }; 12 | 13 | module.exports.AirPressureSensor.UUID = 'E863F00A-079E-48FF-8F27-9C2605A29F52'; 14 | -------------------------------------------------------------------------------- /lib/custom-characteristics.js: -------------------------------------------------------------------------------- 1 | const Characteristic = require('hap-nodejs').Characteristic; 2 | 3 | module.exports.AirPressure = class AirPressure extends Characteristic { 4 | 5 | constructor() { 6 | super('Air Pressure', AirPressure.UUID); 7 | this.setProps({ 8 | format: Characteristic.Formats.UINT16, 9 | unit: 'mbar', 10 | minValue: 700, 11 | maxValue: 1200, 12 | minStep: 1, 13 | perms: [ Characteristic.Perms.READ, Characteristic.Perms.NOTIFY ] 14 | }); 15 | this.value = this.getDefaultValue(); 16 | } 17 | 18 | }; 19 | 20 | module.exports.AirPressure.UUID = 'E863F10F-079E-48FF-8F27-9C2605A29F52'; 21 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | createLight: require('./devices/light.js'), 5 | createLock: require('./devices/lock.js'), 6 | createStateBlinds: require('./devices/stateblinds.js'), 7 | createDimBlinds: require('./devices/dimblinds.js'), 8 | createCurtains: require('./devices/curtains.js'), 9 | createSocket: require('./devices/socket.js'), 10 | createSwitch: require('./devices/switch.js'), 11 | createButton: require('./devices/button.js'), 12 | createThermostat: require('./devices/thermostat.js'), 13 | createDoorbell: require('./devices/doorbell.js'), 14 | createSecuritySystem: require('./devices/securitysystem.js'), 15 | createSensor: require('./devices/sensor.js'), 16 | createFan: require('./devices/fan.js'), 17 | createSpeaker: require('./devices/speaker.js'), 18 | createGarageDoorOpener: require('./devices/garagedooropener.js'), 19 | } 20 | -------------------------------------------------------------------------------- /settings/mock-setup.js: -------------------------------------------------------------------------------- 1 | /* global Homey */ 2 | void function() { 3 | if (! Homey.isMock) return; 4 | 5 | Homey.setSettings({ 6 | pairedDevices : { 1 : true, 2 : false, 3 : true } 7 | }); 8 | 9 | const ICON = 'data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs='; 10 | 11 | Homey.addRoutes([ 12 | { 13 | method: 'GET', 14 | path: '/devices', 15 | fn: function(args, cb) { 16 | cb(null, { 17 | 1 : { name: 'Lamp Woonkamer', id : 1, class : 'light', iconObj : { url : ICON }, capabilities : [ 'onoff', 'dim' ], zoneName: 'Woonkamer' }, 18 | 2 : { name: 'Lamp Overloop', id : 2, class : 'light', iconObj : { url : ICON }, capabilities : [ 'onoff', 'dim' ], zoneName: 'Overloop' }, 19 | 3 : { name: 'Schakelaar Kast', id : 3, class : 'socket', iconObj : { url : ICON }, capabilities : [ 'onoff' ], zoneName: 'Woonkamer' }, 20 | 4 : { name: 'Bewegingsmelder', id : 4, class : 'sensor', iconObj : { url : ICON }, capabilities : [ 'alarm_motion' ], zoneName: 'Slaapkamer' }, 21 | }); 22 | } 23 | }, 24 | { 25 | method: 'GET', 26 | path: '/clear-storage', 27 | fn: function(args, cb) { 28 | console.log('clear storage', args); 29 | cb(); 30 | } 31 | } 32 | ]); 33 | 34 | Homey.registerOnHandler('log.new', function(ev, cb) { 35 | cb([ 36 | { time : '10:12', type : 'info', string : 'This is an info line' }, 37 | { time : '10:13', type : 'success', string : 'This is a success line' }, 38 | { time : '10:19', type : 'error', string : 'This is an error line' }, 39 | { time : '10:32', type : 'success', string : 'This is another success line' }, 40 | { time : '10:19', string : 'This is an undefined line' }, 41 | ]); 42 | }); 43 | }(); 44 | -------------------------------------------------------------------------------- /lib/devices/doorbell.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | module.exports = function(device, api) { 6 | 7 | // Init device 8 | var homekitAccessory = new Accessory(device.name || 'Doorbell', device.id); 9 | 10 | // Set device info 11 | setupAccessoryInformations(homekitAccessory, device); 12 | 13 | // Device identify when added 14 | homekitAccessory.on('identify', function(paired, callback) { 15 | console.log(device.name + ' identify'); 16 | callback(); 17 | }); 18 | 19 | // Add services and characteristics 20 | // Motion 21 | homekitAccessory 22 | .addService(Service.MotionSensor, device.name) 23 | .getCharacteristic(Characteristic.MotionDetected) 24 | .on('get', returnCapabilityValue(device, 'alarm_generic')); 25 | 26 | // On realtime event update the device 27 | for (let i in device.capabilities) { 28 | if (device.capabilities[i] && ['alarm_generic'].includes(device.capabilities[i].split('.')[0])) { 29 | console.log('created listener for - ' + device.capabilities[i]); 30 | let listener = async (value) => { 31 | onStateChange(device.capabilities[i], value, device); 32 | }; 33 | 34 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 35 | } 36 | } 37 | 38 | async function onStateChange(capability, value, device) { 39 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 40 | homekitAccessory 41 | .getService(Service.MotionSensor) 42 | .getCharacteristic(Characteristic.MotionDetected) 43 | .updateValue(~~value); 44 | } 45 | 46 | // Return device to app.js 47 | return homekitAccessory 48 | } 49 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "com.swttt.homekit", 3 | "version": "5.1.0", 4 | "brandColor": "#FE9C21", 5 | "sdk": 3, 6 | "compatibility": ">=5.0.0", 7 | "homeyCommunityTopicId" : 70469, 8 | "permissions": [ 9 | "homey:manager:api" 10 | ], 11 | "name": { 12 | "en": "HomeyKit", 13 | "nl": "HomeyKit" 14 | }, 15 | "description": { 16 | "en": "Control your Homey devices with Apple's Home app and Siri", 17 | "nl": "Bedien je Homey apparaten met Apple's Woning app en Siri" 18 | }, 19 | "tags": { 20 | "en": ["homekit", "siri", "iOS"], 21 | "nl": ["homekit", "siri", "iOS"] 22 | }, 23 | "category": "appliances", 24 | "author": { 25 | "name": "Robert Klep", 26 | "email": "robert@klep.name" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/swttt/com.swttt.homekit/issues/" 30 | }, 31 | "source": "https://github.com/swttt/com.swttt.homekit/", 32 | "contributors": { 33 | "developers": [{ 34 | "name": "Bas Jansen", 35 | "email": "b@sjansen.email" 36 | }, 37 | { 38 | "name": "Niek Hassink", 39 | "email": "n.hassink@gmail.com" 40 | }, 41 | { 42 | "name": "Robert Klep", 43 | "email": "robert@klep.name" 44 | } 45 | ] 46 | }, 47 | "contributing": { 48 | "donate": { 49 | "paypal": { 50 | "username": "robertklep" 51 | } 52 | } 53 | }, 54 | "images": { 55 | "large": "./assets/images/large.png", 56 | "small": "./assets/images/small.png" 57 | }, 58 | "api" : { 59 | "getDevices": { 60 | "method": "GET", 61 | "path": "/devices" 62 | }, 63 | "addDevice": { 64 | "method": "PUT", 65 | "path": "/devices/:id" 66 | }, 67 | "deleteDevice": { 68 | "method": "DELETE", 69 | "path": "/devices/:id" 70 | }, 71 | "clearStorage": { 72 | "method": "GET", 73 | "path": "/clear-storage" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /lib/devices/switch.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | module.exports = function(device, api) { 6 | 7 | // Init device 8 | var homekitAccessory = new Accessory(device.name || 'Switch', device.id); 9 | 10 | // Set device info 11 | setupAccessoryInformations(homekitAccessory, device); 12 | 13 | // Device identify when added 14 | homekitAccessory.on('identify', function(paired, callback) { 15 | console.log(device.name + ' identify'); 16 | callback(); 17 | }); 18 | 19 | // Add services and characteristics 20 | // Onoff 21 | homekitAccessory 22 | .addService(Service.Switch, device.name) 23 | .getCharacteristic(Characteristic.On) 24 | .on('set', function(value, callback) { 25 | device.setCapabilityValue('onoff', value).catch(() => {}); 26 | callback(); 27 | }) 28 | .on('get', returnCapabilityValue(device, 'onoff')); 29 | 30 | // On realtime event update the device 31 | for (let i in device.capabilities) { 32 | if (device.capabilities[i] && ['onoff'].includes(device.capabilities[i].split('.')[0])) { 33 | console.log('created listener for - ' + device.capabilities[i]); 34 | let listener = async (value) => { 35 | onStateChange(device.capabilities[i], value, device); 36 | }; 37 | 38 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 39 | } 40 | } 41 | 42 | async function onStateChange(capability, value, device) { 43 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 44 | homekitAccessory 45 | .getService(Service.Switch) 46 | .getCharacteristic(Characteristic.On) 47 | .updateValue(value); 48 | } 49 | 50 | // Return device to app.js 51 | return homekitAccessory 52 | } 53 | -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 16 | 18 | image/svg+xml 19 | 21 | 22 | 23 | 24 | 26 | 33 | 34 | -------------------------------------------------------------------------------- /lib/devices/button.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const { setupAccessoryInformations } = require('./utils'); 3 | 4 | module.exports = function(device, api) { 5 | 6 | // Init device 7 | var homekitAccessory = new Accessory(device.name || 'Button', device.id); 8 | 9 | // Set device info 10 | setupAccessoryInformations(homekitAccessory, device); 11 | 12 | // Device identify when added 13 | homekitAccessory.on('identify', function(paired, callback) { 14 | console.log(device.name + ' identify'); 15 | callback(); 16 | }); 17 | 18 | // Add services and characteristics 19 | // Onoff 20 | homekitAccessory 21 | .addService(Service.Switch, device.name) 22 | .getCharacteristic(Characteristic.On) 23 | .on('set', function(value, callback) { 24 | device.setCapabilityValue('button', value).catch(() => {}); 25 | setTimeout(() => { 26 | if (value) { 27 | homekitAccessory 28 | .getService(Service.Switch) 29 | .getCharacteristic(Characteristic.On) 30 | .updateValue(false); 31 | } 32 | }, 1000); 33 | callback(); 34 | }) 35 | .on('get', function(callback) { 36 | callback(null, false); 37 | }); 38 | 39 | // On realtime event update the device 40 | for (let i in device.capabilities) { 41 | if (device.capabilities[i] && ['button'].includes(device.capabilities[i].split('.')[0])) { 42 | console.log('created listener for - ' + device.capabilities[i]); 43 | let listener = async (value) => { 44 | onStateChange(device.capabilities[i], value, device); 45 | }; 46 | 47 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 48 | } 49 | } 50 | 51 | async function onStateChange(capability, value, device) { 52 | 53 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 54 | 55 | homekitAccessory 56 | .getService(Service.Switch) 57 | .getCharacteristic(Characteristic.On) 58 | .updateValue(value); 59 | } 60 | 61 | // Return device to app.js 62 | return homekitAccessory 63 | } 64 | -------------------------------------------------------------------------------- /lib/devices/curtains.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | module.exports = function(device, api) { 6 | 7 | // Init device 8 | var homekitAccessory = new Accessory(device.name || 'Curtains', device.id); 9 | 10 | // Set device info 11 | setupAccessoryInformations(homekitAccessory, device); 12 | 13 | // Device identify when added 14 | homekitAccessory.on('identify', function(paired, callback) { 15 | console.log(device.name + ' identify'); 16 | callback(); 17 | }); 18 | 19 | // Add services and characteristics 20 | 21 | // Currentposition 22 | homekitAccessory 23 | .addService(Service.WindowCovering, device.name) 24 | .getCharacteristic(Characteristic.CurrentPosition) 25 | .on('get', returnCapabilityValue(device, 'windowcoverings_set', v => v * 100)); 26 | 27 | // Targetposition 28 | homekitAccessory 29 | .getService(Service.WindowCovering, device.name) 30 | .getCharacteristic(Characteristic.TargetPosition) 31 | .on('set', debounce(function(value, callback) { 32 | device.setCapabilityValue('windowcoverings_set', value / 100).catch(() => {});; 33 | callback(); 34 | }, 500)) 35 | .on('get', returnCapabilityValue(device, 'windowcoverings_set', v => v * 100)); 36 | 37 | // On realtime event update the device 38 | for (let i in device.capabilities) { 39 | if (device.capabilities[i] && ['windowcoverings_set'].includes(device.capabilities[i].split('.')[0])) { 40 | console.log('created listener for - ' + device.capabilities[i]); 41 | let listener = async (value) => { 42 | onStateChange(device.capabilities[i], value, device); 43 | }; 44 | 45 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 46 | } 47 | } 48 | 49 | async function onStateChange(capability, value, device) { 50 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 51 | const windowcovering = homekitAccessory.getService(Service.WindowCovering); 52 | 53 | windowcovering.getCharacteristic(Characteristic.CurrentPosition) 54 | .updateValue(value * 100); 55 | 56 | windowcovering.getCharacteristic(Characteristic.TargetPosition) 57 | .updateValue(value * 100); 58 | } 59 | 60 | // Return device to app.js 61 | return homekitAccessory 62 | } 63 | -------------------------------------------------------------------------------- /lib/devices/fan.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | module.exports = function(device, api, capabilities) { 6 | 7 | // Init device 8 | var homekitAccessory = new Accessory(device.name || 'Fan', device.id); 9 | 10 | // Set device info 11 | setupAccessoryInformations(homekitAccessory, device); 12 | 13 | // Device identify when added 14 | homekitAccessory.on('identify', function(paired, callback) { 15 | console.log(device.name + ' identify'); 16 | callback(); 17 | }); 18 | 19 | // Add services and characteristics 20 | // Onoff 21 | homekitAccessory 22 | .addService(Service.Fan, device.name) 23 | .getCharacteristic(Characteristic.On) 24 | .on('set', function(value, callback) { 25 | device.setCapabilityValue('onoff', value).catch(() => {}); 26 | callback(); 27 | }) 28 | .on('get', returnCapabilityValue(device, 'onoff')); 29 | 30 | // FanSpeed 31 | if ('dim' in capabilities) { 32 | homekitAccessory 33 | .getService(Service.Fan) 34 | .addCharacteristic(Characteristic.RotationSpeed) 35 | .on('set', debounce(function(value, callback) { 36 | device.setCapabilityValue('dim', value / 100).catch(() => {}); 37 | callback(); 38 | }, 500)) 39 | .on('get', returnCapabilityValue(device, 'dim', v => v * 100)); 40 | } 41 | 42 | // On realtime event update the device 43 | for (let i in device.capabilities) { 44 | if (device.capabilities[i] && ['onoff','dim'].includes(device.capabilities[i].split('.')[0])) { 45 | console.log('created listener for - ' + device.capabilities[i]); 46 | let listener = async (value) => { 47 | onStateChange(device.capabilities[i], value, device); 48 | }; 49 | 50 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 51 | } 52 | } 53 | 54 | async function onStateChange(capability, value, device) { 55 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 56 | const fan = homekitAccessory.getService(Service.Fan); 57 | 58 | if (capability === 'onoff') { 59 | fan.getCharacteristic(Characteristic.On).updateValue(value); 60 | } else if (capability === 'dim') { 61 | fan.getCharacteristic(Characteristic.RotationSpeed).updateValue(value * 100); 62 | } 63 | } 64 | 65 | // Return device to app.js 66 | return homekitAccessory; 67 | } 68 | -------------------------------------------------------------------------------- /lib/devices/speaker.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | module.exports = function(device, api, capabilities) { 6 | // Init device 7 | const homekitAccessory = new Accessory(device.name || 'Speaker', device.id); 8 | 9 | // Set device info 10 | setupAccessoryInformations(homekitAccessory, device); 11 | 12 | // Device identify when added 13 | homekitAccessory.on('identify', function(paired, callback) { 14 | console.log(device.name + ' identify'); 15 | callback(); 16 | }); 17 | 18 | // Add services and characteristics 19 | const speaker = homekitAccessory.addService(Service.Speaker, device.name); 20 | 21 | if ('volume_mute' in capabilities) { 22 | speaker 23 | .getCharacteristic(Characteristic.Mute) 24 | .on('set', function(value, callback) { 25 | device.setCapabilityValue('volume_mute', value).catch(() => {}); 26 | callback(); 27 | }) 28 | .on('get', returnCapabilityValue(device, 'volume_mute')); 29 | } 30 | 31 | if ('volume_set' in capabilities) { 32 | speaker 33 | .addCharacteristic(Characteristic.Volume) 34 | .on('set', debounce(function(value, callback) { 35 | device.setCapabilityValue('volume_set', value / 100).catch(() => {}); 36 | callback(); 37 | }, 500)) 38 | .on('get', returnCapabilityValue(device, 'volume_set', v => v * 100)); 39 | } 40 | 41 | // On realtime event update the device 42 | for (let i in device.capabilities) { 43 | if (device.capabilities[i] && ['volume_mute', 'volume_set'].includes(device.capabilities[i].split('.')[0])) { 44 | console.log('created listener for - ' + device.capabilities[i]); 45 | let listener = async (value) => { 46 | onStateChange(device.capabilities[i], value, device); 47 | }; 48 | 49 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 50 | } 51 | } 52 | 53 | async function onStateChange(capability, value, device) { 54 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 55 | const speaker = homekitAccessory.getService(Service.Speaker); 56 | 57 | if (capability === 'volume_mute') { 58 | speaker.getCharacteristic(Characteristic.Mute).updateValue(value); 59 | } else if (capability === 'volume_set') { 60 | speaker.getCharacteristic(Characteristic.Volume).updateValue(value * 100); 61 | } 62 | } 63 | 64 | // Return device to app.js 65 | return homekitAccessory; 66 | } 67 | -------------------------------------------------------------------------------- /.homeychangelog.json: -------------------------------------------------------------------------------- 1 | { 2 | "3.0.14": { 3 | "en": "Limited compability to Homey firmware v4.1.x" 4 | }, 5 | "3.0.15": { 6 | "en": "Fix for v4.2.0, better discoverability" 7 | }, 8 | "3.0.16": { 9 | "en": "Fixed some issues with window coverings" 10 | }, 11 | "3.0.17": { 12 | "en": "Work around issue with Aqara curtains" 13 | }, 14 | "3.0.18": { 15 | "en": "Added support for double/triple/... lights and sockets" 16 | }, 17 | "3.0.19": { 18 | "en": "Removed external dependencies" 19 | }, 20 | "3.0.20": { 21 | "en": "Removed custom services/characteristics, fixed possible crash" 22 | }, 23 | "3.0.21": { 24 | "en": "Cleanups" 25 | }, 26 | "3.0.22": { 27 | "en": "Handle unknown devices better. Also remove paired devices configuration when doing a reset." 28 | }, 29 | "3.1.0": { 30 | "en": "Show device zone on settings page, fixed search" 31 | }, 32 | "3.2.0": { 33 | "en": "Added support for HomeKit Controller app" 34 | }, 35 | "4.0.0": { 36 | "en": "New stable release" 37 | }, 38 | "4.0.1": { 39 | "en": "Added support for state blinds/curtains/sunshades" 40 | }, 41 | "4.1.0": { 42 | "en": "Changes for Homey v7.4.0-rc.X" 43 | }, 44 | "4.1.1": { 45 | "en": "More fixes to work around issues in SDKv3 on v7.4.X-rc.Y" 46 | }, 47 | "4.1.2": { 48 | "en": "Work around more issues in the Web API" 49 | }, 50 | "4.1.3": { 51 | "en": "Limit the number of bridged accessories" 52 | }, 53 | "4.1.4": { 54 | "en": "Catch errors when starting bridge" 55 | }, 56 | "4.1.5": { 57 | "en": "Work around yet more issues in SDKv3" 58 | }, 59 | "4.1.6": { 60 | "en": "More small fixes" 61 | }, 62 | "4.1.7": { 63 | "en": "Catching more Web API errors" 64 | }, 65 | "4.1.8": { 66 | "en": "Make sure all devices are ready before being added to the bridge" 67 | }, 68 | "4.1.9": { 69 | "en": "Support for \"remote\" devices with onoff capabilities and fixed issues with `locked` subcapabilities." 70 | }, 71 | "4.1.10": { 72 | "en": "Added link to community forum page." 73 | }, 74 | "4.2.0": { 75 | "en": "Updated to latest hap-nodejs (iOS 16.2)" 76 | }, 77 | "4.2.1": { 78 | "en": "Fix storage issues" 79 | }, 80 | "4.3.0": { 81 | "en": "Added migration path from old to new HAP libraries (and back again)." 82 | }, 83 | "4.3.1": { 84 | "en": "Support for garage door" 85 | }, 86 | "4.3.2": { 87 | "en": "Fixed issues with garagedoor implementation" 88 | }, 89 | "4.3.3": { 90 | "en": "Added support for obstruction alarm for garagedoor" 91 | }, 92 | "5.0.0": { 93 | "en": "SDKv3 support" 94 | }, 95 | "5.1.0": { 96 | "en": "HP2023 support" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/devices/dimblinds.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | module.exports = function(device, api) { 6 | 7 | // Init device 8 | var homekitAccessory = new Accessory(device.name || 'Blinds', device.id); 9 | 10 | // Set device info 11 | setupAccessoryInformations(homekitAccessory, device); 12 | 13 | // Device identify when added 14 | homekitAccessory.on('identify', function(paired, callback) { 15 | console.log(device.name + ' identify'); 16 | callback(); 17 | }); 18 | 19 | // Add services and characteristics 20 | 21 | // Currentposition 22 | homekitAccessory 23 | .addService(Service.WindowCovering, device.name) 24 | .getCharacteristic(Characteristic.CurrentPosition) 25 | .on('get', returnCapabilityValue(device, 'dim', v => v * 100)); 26 | 27 | // Targetposition 28 | homekitAccessory 29 | .getService(Service.WindowCovering, device.name) 30 | .getCharacteristic(Characteristic.TargetPosition) 31 | .on('set', debounce(function(value, callback) { 32 | device.setCapabilityValue('dim', value / 100).catch(() => {});; 33 | callback(); 34 | }, 500)) 35 | .on('get', returnCapabilityValue(device, 'dim', v => v * 100)); 36 | 37 | // PositionState 38 | /* 39 | homekitAccessory 40 | .getService(Service.WindowCovering, device.name) 41 | .getCharacteristic(Characteristic.PositionState) 42 | .on('get', function(callback) { 43 | callback(null, 2); 44 | }); 45 | */ 46 | 47 | // On realtime event update the device 48 | for (let i in device.capabilities) { 49 | if (device.capabilities[i] && ['dim'].includes(device.capabilities[i].split('.')[0])) { 50 | console.log('created listener for - ' + device.capabilities[i]); 51 | let listener = async (value) => { 52 | onStateChange(device.capabilities[i], value, device); 53 | }; 54 | 55 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 56 | } 57 | } 58 | 59 | async function onStateChange(capability, value, device) { 60 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 61 | const windowcovering = homekitAccessory.getService(Service.WindowCovering); 62 | 63 | windowcovering.getCharacteristic(Characteristic.CurrentPosition) 64 | .updateValue(value * 100); 65 | 66 | windowcovering.getCharacteristic(Characteristic.TargetPosition) 67 | .updateValue(value * 100); 68 | } 69 | 70 | // Return device to app.js 71 | return homekitAccessory 72 | } 73 | -------------------------------------------------------------------------------- /lib/devices/garagedooropener.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { getCapabilityValue, isCapability, setupAccessoryInformations } = require('./utils'); 4 | 5 | module.exports = function(device, api, capabilities) { 6 | const homekitAccessory = new Accessory(device.name || 'Garage door', device.id); 7 | 8 | // Set device info 9 | setupAccessoryInformations(homekitAccessory, device); 10 | 11 | // Device identify when added 12 | homekitAccessory.on('identify', function(paired, callback) { 13 | console.log(device.name + ' identify'); 14 | callback(); 15 | }); 16 | 17 | // Add services and characteristics 18 | const opener = homekitAccessory.addService(Service.GarageDoorOpener, device.name); 19 | 20 | // Open/Close states 21 | for (const capability of device.capabilities.filter(c => isCapability(c, 'garagedoor_closed'))) { 22 | // Handler to get CurrentDoorState and TargetDoorState 23 | const onGetDoorState = function(callback) { 24 | const closed = getCapabilityValue(device, capability); 25 | callback(null, Characteristic.CurrentDoorState[ closed ? 'CLOSED' : 'OPEN' ]); 26 | }; 27 | 28 | opener.getCharacteristic(Characteristic.CurrentDoorState) 29 | .on('get', onGetDoorState) 30 | 31 | opener.getCharacteristic(Characteristic.TargetDoorState) 32 | .on('get', onGetDoorState) 33 | .on('set', (value, callback) => { 34 | // 0 = OPEN, 1 = CLOSED 35 | device.setCapabilityValue(capability, value === Characteristic.TargetDoorState.CLOSED ? true : false).catch(() => {}); 36 | callback(); 37 | }); 38 | 39 | // Watch for state changes 40 | try { 41 | device.makeCapabilityInstance(capability, isClosed => { 42 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + isClosed); 43 | 44 | const newState = Characteristic.CurrentDoorState[ isClosed ? 'CLOSED' : 'OPEN' ]; 45 | 46 | opener.getCharacteristic(Characteristic.CurrentDoorState) .updateValue(newState); 47 | opener.getCharacteristic(Characteristic.TargetDoorState) .updateValue(newState); 48 | }) 49 | } catch(e) {}; 50 | } 51 | 52 | // Obstruction state 53 | for (const capability of device.capabilities.filter(c => isCapability(c, 'alarm_generic'))) { 54 | opener.getCharacteristic(Characteristic.ObstructionDetected) 55 | .on('get', callback => callback(null, getCapabilityValue(device, capability) ? true : false)); 56 | 57 | try { 58 | device.makeCapabilityInstance(capability, alarmActive => { 59 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + alarmActive); 60 | opener.getCharacteristic(Characteristic.ObstructionDetected).updateValue(alarmActive ? true : false); 61 | }) 62 | } catch(e) {}; 63 | } 64 | 65 | // Return accessory to app.js 66 | return homekitAccessory; 67 | } 68 | -------------------------------------------------------------------------------- /lib/devices/lock.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { 4 | getCapabilityValue, 5 | isCapability, 6 | setupAccessoryInformations 7 | } = require('./utils'); 8 | 9 | module.exports = function(device, api) { 10 | 11 | // Init device 12 | var homekitAccessory = new Accessory(device.name || 'Lock', device.id); 13 | 14 | // Set device info 15 | setupAccessoryInformations(homekitAccessory, device); 16 | 17 | // Device identify when added 18 | homekitAccessory.on('identify', function(paired, callback) { 19 | console.log(device.name + ' identify'); 20 | callback(); 21 | }); 22 | 23 | // Add services and charesteristics for `locked` and any possible subcapabilities. 24 | for (const capability of device.capabilities.filter(c => isCapability(c, 'locked'))) { 25 | // Handler to get LockCurrentState and LockTargetState 26 | const onGetLockState = function(callback) { 27 | const locked = getCapabilityValue(device, capability); 28 | callback(null, Characteristic.LockCurrentState[ locked ? 'SECURED' : 'UNSECURED' ]); 29 | }; 30 | 31 | // CurrentState 32 | homekitAccessory 33 | .addService(Service.LockMechanism, device.name) 34 | .getCharacteristic(Characteristic.LockCurrentState) 35 | .on('get', onGetLockState); 36 | 37 | // TargetState 38 | homekitAccessory 39 | .getService(Service.LockMechanism, device.name) 40 | .getCharacteristic(Characteristic.LockTargetState) 41 | .on('set', function(value, callback) { 42 | 43 | if (value == Characteristic.LockTargetState.SECURED) { 44 | device.setCapabilityValue(capability, true).catch(() => {}); 45 | callback(); 46 | } 47 | else if (value == Characteristic.LockTargetState.UNSECURED) { 48 | device.setCapabilityValue(capability, false).catch(() => {}); 49 | callback(); 50 | } 51 | }) 52 | .on('get', onGetLockState); 53 | 54 | console.log('created listener for - ' + capability); 55 | try { 56 | device.makeCapabilityInstance(capability, value => { 57 | onStateChange(capability, value, device); 58 | }) 59 | } catch(e) {}; 60 | } 61 | 62 | async function onStateChange(capability, value, device) { 63 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 64 | const lock = homekitAccessory.getService(Service.LockMechanism); 65 | 66 | if (value) { 67 | lock.setCharacteristic(Characteristic.LockTargetState, Characteristic.LockTargetState.SECURED); 68 | lock.setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.SECURED); 69 | } else { 70 | lock.setCharacteristic(Characteristic.LockTargetState, Characteristic.LockTargetState.UNSECURED); 71 | lock.setCharacteristic(Characteristic.LockCurrentState, Characteristic.LockCurrentState.UNSECURED); 72 | } 73 | } 74 | 75 | // Return device to app.js 76 | return homekitAccessory 77 | } 78 | -------------------------------------------------------------------------------- /lib/devices/utils.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | 3 | const getCapabilityValue = (device, capability) => { 4 | return (capability in (device.capabilitiesObj || {})) ? device.capabilitiesObj[capability].value : null; 5 | }; 6 | 7 | const isCapability = (capability, type) => capability && (capability.startsWith(type) || capability.startsWith(type + '.')); 8 | 9 | const returnCapabilityValue = (device, capability, xfrm = v => v) => { 10 | return function(callback) { 11 | const value = getCapabilityValue(device, capability); 12 | return callback(null, value === null ? this.value : xfrm(value)); 13 | } 14 | }; 15 | 16 | const DEFAULT_GROUP_NAME = 'default'; 17 | 18 | const getCapabilityNameSegments = (name) => { 19 | const [capabilityBaseName, groupName = DEFAULT_GROUP_NAME] = name.split('.'); 20 | 21 | return { capabilityBaseName, groupName }; 22 | }; 23 | 24 | /** 25 | * in some cases there are capabilities with group suffix, 26 | * e.g. 'onoff' and 'onoff.1', 27 | * we will transform base capabilities list into list of distinct groups 28 | * e.g. 29 | * [ 30 | * { groupName: 'default', capabilities: ['onoff'] }, 31 | * { groupName: '1', capabilities: ['onoff.1'] }, 32 | * ] 33 | */ 34 | const getCapabilityGroups = (capabilities, supportedCapabilityFilter = () => true) => { 35 | return capabilities.reduce((groups, capability) => { 36 | const { groupName, capabilityBaseName } = getCapabilityNameSegments(capability) 37 | 38 | if (!supportedCapabilityFilter(capabilityBaseName)) { 39 | return groups; 40 | } 41 | 42 | const group = groups.find((group) => group.groupName === groupName); 43 | 44 | if (! group) { 45 | groups.push({ 46 | groupName, 47 | capabilities: [capability], 48 | }) 49 | } else { 50 | group.capabilities.push(capability); 51 | } 52 | 53 | return groups; 54 | }, []) 55 | } 56 | 57 | const setupAccessoryInformations = (accessory, device) => { 58 | if (device.driverUri === 'homey:app:de.karpienski.hkcontroller') { 59 | accessory 60 | .getService(Service.AccessoryInformation) 61 | .setCharacteristic(Characteristic.Manufacturer, device.settings.labelManufacturer) 62 | .setCharacteristic(Characteristic.Model, device.settings.labelModel) 63 | .setCharacteristic(Characteristic.SerialNumber, device.settings.labelSerialNumber) 64 | .setCharacteristic(Characteristic.HardwareRevision, device.settings.labelHardwareRevision) 65 | .setCharacteristic(Characteristic.FirmwareRevision, device.settings.labelFirmwareRevision) 66 | .setCharacteristic(Characteristic.Version, device.settings.labelVersion); 67 | } else { 68 | accessory 69 | .getService(Service.AccessoryInformation) 70 | .setCharacteristic(Characteristic.Manufacturer, String(device.driverUri).replace(/^homey:app:/, '')) 71 | .setCharacteristic(Characteristic.Model, `${ device.name } (${ device.zoneName || "onbekende zone" })`) 72 | .setCharacteristic(Characteristic.SerialNumber, device.id); 73 | } 74 | } 75 | 76 | module.exports = { 77 | getCapabilityValue, 78 | isCapability, 79 | returnCapabilityValue, 80 | getCapabilityNameSegments, 81 | getCapabilityGroups, 82 | setupAccessoryInformations, 83 | DEFAULT_GROUP_NAME, 84 | } 85 | -------------------------------------------------------------------------------- /settings/homey-settings-mock.js: -------------------------------------------------------------------------------- 1 | if (! window.Homey) { 2 | window.Homey = new (class Homey { 3 | constructor() { 4 | this.isMock = true; 5 | this.settings = {}; 6 | this.listeners = {}; 7 | this.onHandlers = {}; 8 | this.routes = []; 9 | window.addEventListener('load', function() { 10 | window.onHomeyReady && window.onHomeyReady(this); 11 | }.bind(this)); 12 | } 13 | 14 | // Mock API 15 | setSettings(settings) { 16 | this.settings = Object.assign({}, settings); 17 | } 18 | 19 | addRoutes(routes) { 20 | this.routes = Object.assign([], routes); 21 | for (const route of this.routes) { 22 | route.pathRegex = new RegExp('^' + route.path.replace(/(:)(\w+)/gi, '(?<$2>\\w+)') + '$'); 23 | } 24 | } 25 | 26 | registerOnHandler(event, fn) { 27 | this.onHandlers[event] = fn; 28 | } 29 | 30 | _emit(event, ...args) { 31 | (this.listeners[event] || []).forEach(listener => listener(...args)); 32 | } 33 | 34 | // Regular API. 35 | ready() { 36 | } 37 | 38 | get(name, cb) { 39 | if (typeof name === 'function') cb = name, name = null; 40 | cb && cb(null, name !== null ? this.settings[name] : Object.assign({}, this.settings)); 41 | } 42 | 43 | set(name, value, cb) { 44 | this.settings[name] = value; 45 | this._emit('settings.set', name, value); 46 | cb && cb(); 47 | } 48 | 49 | unset(name, cb) { 50 | delete this.settings[name]; 51 | this._emit('settings.unset', name); 52 | cb && cb(); 53 | } 54 | 55 | on(event, cb) { 56 | if (this.onHandlers[event]) { 57 | return this.onHandlers[event](event, cb); 58 | } 59 | if (! this.listeners[event]) { 60 | this.listeners[event] = []; 61 | } 62 | this.listeners[event].push(cb); 63 | } 64 | 65 | api(method, path, body, cb) { 66 | const url = new URL('https://example.com' + path); 67 | if (typeof body === 'function') cb = body, body = null; 68 | 69 | // Find first matching route. 70 | const route = this.routes.find(route => { 71 | return route.method === method && route.pathRegex.test(url.pathname); 72 | }); 73 | if (! route) { 74 | return cb(new Error('NOT_FOUND')); 75 | } 76 | 77 | // Setup args. 78 | const args = {}; 79 | if (body != null) { 80 | args.body = body; 81 | } 82 | 83 | // Parse query string. 84 | if (url.search) { 85 | args.query = {}; 86 | for (const key of url.searchParams.keys()) { 87 | args.query[key] = url.searchParams.get(key); 88 | } 89 | } 90 | 91 | // Parse params. 92 | args.params = url.pathname.match(route.pathRegex).groups; 93 | 94 | // Call handler. 95 | route.fn(args, cb); 96 | } 97 | 98 | alert(msg, icon, cb) { 99 | if (typeof icon === 'function') cb = icon, icon = null; 100 | alert(msg); 101 | cb && cb(); 102 | } 103 | 104 | confirm(msg, icon, cb) { 105 | if (typeof icon === 'function') cb = icon, icon = null; 106 | let ret = confirm(msg); 107 | cb && cb(null, ret); 108 | } 109 | 110 | popup(url, { width = 400, height = 400 } = {}) { 111 | window.open(url, '', `width=${ width },height=${ height }`); 112 | } 113 | 114 | openURL(url) { 115 | return this.popup(url); 116 | } 117 | 118 | __(key, tokens) { 119 | return key; 120 | } 121 | })(); 122 | } 123 | -------------------------------------------------------------------------------- /lib/devices/stateblinds.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | function state2value(state) { 6 | if (state === 'down') { 7 | return 0; 8 | } else if (state === 'up') { 9 | return 100; 10 | } else { 11 | return 50; 12 | } 13 | } 14 | 15 | function value2state(value) { 16 | if (value === 100) { 17 | return 'up'; 18 | } else if (value === 0) { 19 | return 'down'; 20 | } else { 21 | return 'idle'; 22 | } 23 | } 24 | 25 | function state2num(state) { 26 | if (state === 'up') { 27 | return 1; 28 | } else if (state === 'down') { 29 | return 0; 30 | } else { 31 | return 2; 32 | } 33 | } 34 | 35 | module.exports = function(device, api) { 36 | 37 | // Init device 38 | var homekitAccessory = new Accessory(device.name || 'Blinds', device.id); 39 | 40 | // Set device info 41 | setupAccessoryInformations(homekitAccessory, device); 42 | 43 | // Device identify when added 44 | homekitAccessory.on('identify', function(paired, callback) { 45 | console.log(device.name + ' identify'); 46 | callback(); 47 | }); 48 | 49 | // Add services and characteristics 50 | 51 | // Currentposition 52 | homekitAccessory 53 | .addService(Service.WindowCovering, device.name) 54 | .getCharacteristic(Characteristic.CurrentPosition) 55 | .on('get', returnCapabilityValue(device, 'windowcoverings_state', state2value)); 56 | 57 | // Set steps for position 58 | homekitAccessory 59 | .getService(Service.WindowCovering) 60 | .getCharacteristic(Characteristic.TargetPosition) 61 | .setProps({ 62 | minStep: 50 63 | }); 64 | 65 | // Targetposition 66 | homekitAccessory 67 | .getService(Service.WindowCovering) 68 | .getCharacteristic(Characteristic.TargetPosition) 69 | .on('set', function(value, callback) { 70 | device.setCapabilityValue('windowcoverings_state', value2state(value)).catch(() => {});; 71 | callback(); 72 | }) 73 | .on('get', returnCapabilityValue(device, 'windowcoverings_state', state2value)); 74 | 75 | // PositionState 76 | homekitAccessory 77 | .getService(Service.WindowCovering) 78 | .getCharacteristic(Characteristic.PositionState) 79 | .on('get', returnCapabilityValue(device, 'windowcoverings_state', state2num)); 80 | 81 | // On realtime event update the device 82 | for (let i in device.capabilities) { 83 | if (device.capabilities[i] && ['windowcoverings_state'].includes(device.capabilities[i].split('.')[0])) { 84 | console.log('created listener for - ' + device.capabilities[i]); 85 | let listener = async (value) => { 86 | onStateChange(device.capabilities[i], value, device); 87 | }; 88 | 89 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 90 | } 91 | } 92 | 93 | async function onStateChange(capability, value, device) { 94 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 95 | const windowcovering = homekitAccessory.getService(Service.WindowCovering); 96 | 97 | windowcovering.getCharacteristic(Characteristic.TargetPosition) 98 | .updateValue(state2value(value)); 99 | 100 | windowcovering.getCharacteristic(Characteristic.CurrentPosition) 101 | .updateValue(state2value(value)); 102 | 103 | windowcovering.getCharacteristic(Characteristic.PositionState) 104 | .updateValue(state2num(value)); 105 | } 106 | 107 | // Return device to app.js 108 | return homekitAccessory; 109 | } 110 | -------------------------------------------------------------------------------- /lib/devices/socket.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { 4 | getCapabilityNameSegments, 5 | returnCapabilityValue, 6 | getCapabilityGroups, 7 | setupAccessoryInformations, 8 | } = require('./utils'); 9 | 10 | module.exports = function(device/*, api*/) { 11 | // Init device 12 | const homekitAccessory = new Accessory(device.name || 'Socket', device.id); 13 | 14 | // Set device info 15 | setupAccessoryInformations(homekitAccessory, device); 16 | 17 | // Device identify when added 18 | homekitAccessory.on('identify', function(paired, callback) { 19 | console.log(device.name + ' identify'); 20 | callback(); 21 | }); 22 | 23 | const capabilityGroups = getCapabilityGroups(device.capabilities, isSocketCapability); 24 | 25 | capabilityGroups.forEach((group, groupIndex, groups) => { 26 | setupGroupService({ device, group, groups, homekitAccessory }); 27 | }); 28 | 29 | // Return device to app.js 30 | return homekitAccessory; 31 | } 32 | 33 | const setupGroupService = ({ device, group, groups, homekitAccessory }) => { 34 | const { capabilities, groupName } = group; 35 | 36 | // Register accessory service 37 | const service = homekitAccessory.addService(...getServiceBuildArguments({ groupName, groups, device, group })); 38 | 39 | // For Outlet to work correctly we need to configure this characteristic, see details https://github.com/homebridge/HAP-NodeJS/issues/167 40 | service 41 | .getCharacteristic(Characteristic.OutletInUse) 42 | .on('get', function(callback) { 43 | callback(null, true); 44 | }); 45 | 46 | // Add service characteristics 47 | capabilities.forEach((capability) => registerCharacteristics({ capability, device, service })); 48 | 49 | // On realtime event update the device 50 | capabilities.forEach((capability) => registerCapabilityListener({ capability, device, service })); 51 | } 52 | 53 | const registerCharacteristics = ({ capability, device, service }) => { 54 | const { capabilityBaseName } = getCapabilityNameSegments(capability); 55 | 56 | // Onoff 57 | if (capabilityBaseName === 'onoff') { 58 | service 59 | .getCharacteristic(Characteristic.On) 60 | .on('set', function(value, callback) { 61 | device.setCapabilityValue(capability, value).catch(() => {}); 62 | callback(); 63 | }) 64 | .on('get', returnCapabilityValue(device, capability)); 65 | } 66 | 67 | // Brightness 68 | if (capabilityBaseName === 'dim') { 69 | service 70 | .addCharacteristic(Characteristic.Brightness) 71 | .on('set', debounce(function(value, callback) { 72 | device.setCapabilityValue(capability, value / 100).catch(() => {}); 73 | callback(); 74 | }, 500)) 75 | .on('get', returnCapabilityValue(device, capability, v => v * 100)); 76 | } 77 | }; 78 | 79 | const registerCapabilityListener = ({ device, capability, service }) => { 80 | console.log('created listener for - ' + capability); 81 | 82 | const listener = (value) => { 83 | onStateChange({ service, capability, value, device }); 84 | }; 85 | 86 | // For Homey device we want to use full capability name 87 | try { device.makeCapabilityInstance(capability, listener) } catch(e) {}; 88 | } 89 | 90 | const onStateChange = ({ service, capability, value, device }) => { 91 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 92 | const { capabilityBaseName } = getCapabilityNameSegments(capability) 93 | 94 | if (capabilityBaseName === 'onoff') { 95 | service.getCharacteristic(Characteristic.On).updateValue(value); 96 | } else if (capabilityBaseName === 'dim') { 97 | service.getCharacteristic(Characteristic.Brightness).updateValue(value * 100); 98 | } 99 | } 100 | 101 | const isSocketCapability = (capability) => { 102 | const { capabilityBaseName } = getCapabilityNameSegments(capability) 103 | return ['onoff', 'dim'].includes(capabilityBaseName); 104 | } 105 | 106 | const getServiceBuildArguments = ({ device, groupName, groups, group }) => { 107 | const isSingleService = groups.length === 1; 108 | const serviceName = isSingleService ? device.name : getServiceName({ device, group }) 109 | 110 | return [Service.Outlet, serviceName, groupName]; 111 | }; 112 | 113 | // Name service by first capability title, if its present? NOTE: Maybe there are better ways 114 | const getServiceName = ({ device, group }) => { 115 | const capability = group.capabilities[0]; 116 | const capabilityDetails = device.capabilitiesObj ? device.capabilitiesObj[capability] : {}; 117 | 118 | return capabilityDetails.title || device.name; 119 | }; 120 | 121 | -------------------------------------------------------------------------------- /lib/devices/thermostat.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { returnCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | function state2value(state) { 6 | if (state === 'off') { 7 | return 0; 8 | } else if (state === 'heat') { 9 | return 1; 10 | } else if (state === 'cool') { 11 | return 2; 12 | } else { 13 | return 3; 14 | } 15 | } 16 | 17 | module.exports = function(device, api, capabilities) { 18 | 19 | // Init device 20 | var homekitAccessory = new Accessory(device.name || 'Thermostat', device.id); 21 | 22 | // Set device info 23 | setupAccessoryInformations(homekitAccessory, device); 24 | 25 | // Device identify when added 26 | homekitAccessory.on('identify', function(paired, callback) { 27 | console.log(device.name + ' identify'); 28 | callback(); 29 | }); 30 | 31 | // Add services and charesteristics 32 | // Temp display units 33 | homekitAccessory 34 | .addService(Service.Thermostat, device.name) 35 | .getCharacteristic(Characteristic.TemperatureDisplayUnits) 36 | .on('get', function(callback) { 37 | callback(null, 0); 38 | }); 39 | 40 | // Target temperature 41 | homekitAccessory 42 | .getService(Service.Thermostat) 43 | .getCharacteristic(Characteristic.TargetTemperature) 44 | .on('set', function(value, callback) { 45 | device.setCapabilityValue('target_temperature', value).catch(() => {}); 46 | callback(); 47 | }) 48 | .on('get', returnCapabilityValue(device, 'target_temperature')); 49 | 50 | // Current Temperature 51 | homekitAccessory 52 | .getService(Service.Thermostat) 53 | .getCharacteristic(Characteristic.CurrentTemperature) 54 | .on('get', returnCapabilityValue(device, 'measure_temperature')); 55 | 56 | // Humidity Sensor 57 | if ('measure_humidity' in capabilities) { 58 | homekitAccessory 59 | .getService(Service.Thermostat) 60 | .getCharacteristic(Characteristic.CurrentRelativeHumidity) 61 | .on('get', returnCapabilityValue(device, 'measure_humidity')); 62 | } 63 | 64 | if ('thermostat_mode' in capabilities) { 65 | // Target state 66 | homekitAccessory 67 | .getService(Service.Thermostat) 68 | .getCharacteristic(Characteristic.TargetHeatingCoolingState) 69 | .on('set', function(value, callback) { 70 | if (value == 0) { 71 | device.setCapabilityValue('thermostat_mode', 'off').catch(() => {}); 72 | callback(); 73 | } else if (value == 1) { 74 | device.setCapabilityValue('thermostat_mode', 'heat').catch(() => {}); 75 | callback(); 76 | } else if (value == 2) { 77 | device.setCapabilityValue('thermostat_mode', 'cool').catch(() => {}); 78 | callback(); 79 | } else { 80 | device.setCapabilityValue('thermostat_mode', 'auto').catch(() => {}); 81 | callback(); 82 | } 83 | 84 | }) 85 | .on('get', returnCapabilityValue(device, 'thermostat_mode', state2value)); 86 | 87 | // Current State 88 | homekitAccessory 89 | .getService(Service.Thermostat) 90 | .getCharacteristic(Characteristic.CurrentHeatingCoolingState) 91 | .on('get', returnCapabilityValue(device, 'thermostat_mode', state2value)); 92 | } 93 | 94 | 95 | // On realtime event update the device 96 | for (let i in device.capabilities) { 97 | if (device.capabilities[i] && ['target_temperature','measure_temperature','measure_humidity','thermostat_mode'].includes(device.capabilities[i].split('.')[0])) { 98 | console.log('created listener for - ' + device.capabilities[i]); 99 | let listener = async (value) => { 100 | onStateChange(device.capabilities[i], value, device); 101 | }; 102 | 103 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 104 | } 105 | } 106 | 107 | async function onStateChange(capability, value, device) { 108 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 109 | const thermostat = homekitAccessory.getService(Service.Thermostat); 110 | 111 | if (capability == 'target_temperature') { 112 | thermostat.getCharacteristic(Characteristic.TargetTemperature) 113 | .updateValue(value); 114 | } else if (capability == 'measure_temperature') { 115 | thermostat.getCharacteristic(Characteristic.CurrentTemperature) 116 | .updateValue(value); 117 | } else if (capability == 'measure_humidity') { 118 | thermostat.getCharacteristic(Characteristic.CurrentRelativeHumidity) 119 | .updateValue(value); 120 | } else if (capability == 'thermostat_mode') { 121 | thermostat.getCharacteristic(Characteristic.CurrentHeatingCoolingState) 122 | .updateValue(state2value(value)); 123 | } 124 | } 125 | 126 | // Return device to app.js 127 | return homekitAccessory; 128 | } 129 | -------------------------------------------------------------------------------- /lib/devices/securitysystem.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { getCapabilityValue, setupAccessoryInformations } = require('./utils'); 4 | 5 | function state2value(state, targetStateValue) { 6 | if (state === 'partially_armed') { 7 | if (targetStateValue === 0) { 8 | return 0; 9 | } else if (targetStateValue === 2) { 10 | return 2; 11 | } else { 12 | return 0 13 | } 14 | } else if (state === 'armed') { 15 | return 1; 16 | } else if (state === 'disarmed') { 17 | return 3; 18 | } else { 19 | return 3; 20 | } 21 | } 22 | 23 | function value2state(value) { 24 | if (value === 0) { 25 | return 'partially_armed' 26 | } else if (value === 1) { 27 | return 'armed'; 28 | } else if (value === 2) { 29 | return 'partially_armed'; 30 | } else if (value === 3) { 31 | return 'disarmed'; 32 | } else { 33 | return 'disarmed'; 34 | } 35 | } 36 | 37 | module.exports = function(device, api, capabilities) { 38 | // Init device 39 | let homekitAccessory = new Accessory(device.name || 'Security System', device.id); 40 | 41 | // Var that keeps the target state 42 | let targetStateValue = null; 43 | 44 | // Set device info 45 | setupAccessoryInformations(homekitAccessory, device); 46 | 47 | // Device identify when added 48 | homekitAccessory.on('identify', function(paired, callback) { 49 | console.log(device.name + ' identify'); 50 | callback(); 51 | }); 52 | 53 | // Add services and characteristics 54 | // Current state 55 | homekitAccessory 56 | .addService(Service.SecuritySystem, device.name) 57 | .getCharacteristic(Characteristic.SecuritySystemCurrentState) 58 | .on('get', function(callback) { 59 | // Support for Heimdall app alarm capability 60 | if ('alarm_heimdall' in (device.capabilitiesObj || {}) && device.capabilitiesObj.alarm_heimdall.value === true) { 61 | return callback(null, 4); 62 | } else if ('homealarm_state' in capabilities) { 63 | const value = getCapabilityValue(device, 'homealarm_state'); 64 | return callback(null, value !== null ? state2value(value, targetStateValue) : this.value); 65 | } else if ('onoff' in capabilities) { // Satel Integra 66 | const value = getCapabilityValue(device, 'onoff'); 67 | return callback(null, value !== null ? state2value(value ? 'armed' : 'disarmed', targetStateValue) : this.value); 68 | } 69 | return callback(Error('NO_CAPABILITY')); 70 | }); 71 | 72 | // Target state 73 | homekitAccessory 74 | .getService(Service.SecuritySystem) 75 | .getCharacteristic(Characteristic.SecuritySystemTargetState) 76 | .on('set', function(value, callback) { 77 | targetStateValue = value; 78 | if ('homealarm_state' in capabilities) { 79 | device.setCapabilityValue('homealarm_state', value2state(value) ).catch(() => {}); 80 | } else if ('onoff' in capabilities) { 81 | device.setCapabilityValue('onoff', value === 3 ? false : true).catch(() => {});; 82 | } 83 | return callback(); 84 | }) 85 | .on('get', function(callback) { 86 | if ('homealarm_state' in capabilities) { 87 | const value = getCapabilityValue(device, 'homealarm_state'); 88 | return callback(null, value !== null ? state2value(value, targetStateValue) : this.value); 89 | } else if ('onoff' in capabilities) { // Satel Integra 90 | const value = getCapabilityValue(device, 'onoff'); 91 | return callback(null, value !== null ? state2value(value ? 'armed' : 'disarmed', targetStateValue) : this.value); 92 | } 93 | return callback(Error('NO_CAPABILITY')); 94 | }); 95 | 96 | // Tamper 97 | if ('alarm_tamper' in capabilities) { 98 | homekitAccessory 99 | .getService(Service.SecuritySystem) 100 | .getCharacteristic(Characteristic.StatusTampered) 101 | .on('get', function(callback) { 102 | const value = getCapabilityValue(device, 'alarm_tamper'); 103 | callback(null, value ? ~~value : this.value); 104 | }); 105 | } 106 | 107 | // On realtime event update the device 108 | for (let i in device.capabilities) { 109 | if (device.capabilities[i] && [ 'alarm_heimdall', 'homealarm_state', 'alarm_tamper', 'onoff' ].includes(device.capabilities[i].split('.')[0])) { 110 | console.log('created listener for - ' + device.capabilities[i]); 111 | let listener = async (value) => { 112 | onStateChange(device.capabilities[i], value, device); 113 | }; 114 | 115 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 116 | } 117 | } 118 | 119 | async function onStateChange(capability, value, device) { 120 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 121 | const securitysystem = homekitAccessory.getService(Service.SecuritySystem); 122 | 123 | // Support for Heimdall app alarm capability 124 | if (capability == 'alarm_heimdall' && value == true) { 125 | securitysystem.getCharacteristic(Characteristic.SecuritySystemCurrentState) 126 | .updateValue(4); 127 | } else if (capability == 'homealarm_state') { 128 | securitysystem.getCharacteristic(Characteristic.SecuritySystemTargetState) 129 | .updateValue(state2value(value, targetStateValue)); 130 | securitysystem.getCharacteristic(Characteristic.SecuritySystemCurrentState) 131 | .updateValue(state2value(value, targetStateValue)); 132 | } else if (capability === 'onoff') { 133 | securitysystem.getCharacteristic(Characteristic.SecuritySystemTargetState) 134 | .updateValue(state2value(value ? 'armed' : 'disarmed', targetStateValue)); 135 | securitysystem.getCharacteristic(Characteristic.SecuritySystemCurrentState) 136 | .updateValue(state2value(value ? 'armed' : 'disarmed', targetStateValue)); 137 | } else if (capability === 'alarm_tamper') { 138 | securitysystem.getCharacteristic(Characteristic.StatusTampered) 139 | .updateValue(~~value); 140 | } 141 | } 142 | 143 | // Return device to app.js 144 | return homekitAccessory; 145 | } 146 | -------------------------------------------------------------------------------- /lib/devices/light.js: -------------------------------------------------------------------------------- 1 | const { Accessory, Service, Characteristic } = require('../../modules/hap-nodejs'); 2 | const debounce = require('lodash.debounce'); 3 | const { 4 | getCapabilityNameSegments, 5 | returnCapabilityValue, 6 | getCapabilityGroups, 7 | setupAccessoryInformations 8 | } = require('./utils'); 9 | 10 | function map(inputStart, inputEnd, outputStart, outputEnd, input) { 11 | return outputStart + ((outputEnd - outputStart) / (inputEnd - inputStart)) * (input - inputStart); 12 | } 13 | 14 | module.exports = function(device/*, api*/) { 15 | 16 | // Init device 17 | const homekitAccessory = new Accessory(device.name || 'Light', device.id); 18 | 19 | // Set device info 20 | setupAccessoryInformations(homekitAccessory, device); 21 | 22 | // Device identify when added 23 | homekitAccessory.on('identify', function(paired, callback) { 24 | console.log(device.name + ' identify'); 25 | callback(); 26 | }); 27 | 28 | const capabilityGroups = getCapabilityGroups(device.capabilities, isLightCapability); 29 | 30 | capabilityGroups.forEach((group, groupIndex, groups) => { 31 | setupGroupService({ device, group, groups, homekitAccessory }); 32 | }); 33 | 34 | // Return device to app.js 35 | return homekitAccessory; 36 | } 37 | 38 | const setupGroupService = ({ device, group, groups, homekitAccessory }) => { 39 | const { capabilities, groupName } = group; 40 | 41 | // Register accessory service 42 | const lightService = homekitAccessory.addService(...getServiceBuildArguments({ groupName, groups, device, group })); 43 | 44 | // Add service characteristics 45 | capabilities.forEach((capability) => registerCharacteristics({ capability, device, service: lightService })); 46 | 47 | // On realtime event update the device 48 | capabilities.forEach((capability) => registerCapabilityListener({ capability, device, service: lightService })); 49 | } 50 | 51 | const registerCharacteristics = ({ capability, device, service }) => { 52 | const { capabilityBaseName } = getCapabilityNameSegments(capability); 53 | 54 | // Onoff 55 | if (capabilityBaseName === 'onoff') { 56 | service 57 | .getCharacteristic(Characteristic.On) 58 | .on('set', function(value, callback) { 59 | device.setCapabilityValue(capability, value).catch(() => {}); 60 | callback(); 61 | }) 62 | .on('get', returnCapabilityValue(device, capability)); 63 | } 64 | 65 | // Brightness 66 | if (capabilityBaseName === 'dim') { 67 | service 68 | .addCharacteristic(Characteristic.Brightness) 69 | .on('set', debounce(function(value, callback) { 70 | device.setCapabilityValue(capability, value / 100).catch(() => {}); 71 | callback(); 72 | }, 500)) 73 | .on('get', returnCapabilityValue(device, capability, v => v * 100)); 74 | } 75 | 76 | // Saturation 77 | if (capabilityBaseName === 'light_saturation') { 78 | service 79 | .addCharacteristic(Characteristic.Saturation) 80 | .on('set', function(value, callback) { 81 | device.setCapabilityValue(capability, value / 100).catch(() => {}); 82 | callback(); 83 | }) 84 | .on('get', returnCapabilityValue(device, capability, v => v * 100)); 85 | } 86 | 87 | // Temperature 88 | if (capabilityBaseName === 'light_temperature') { 89 | service 90 | .addCharacteristic(Characteristic.ColorTemperature) 91 | .on('set', function(value, callback) { 92 | 93 | // If device uses light_mode and was previously set on not temperature 94 | // Wait for color mode to be adjusted before setting light_temperature 95 | if ('light_mode' in (device.capabilitiesObj || {}) && device.capabilitiesObj.light_mode.value !== 'temperature') { 96 | device.setCapabilityValue('light_mode', 'temperature').catch(() => {}); 97 | } 98 | 99 | device.setCapabilityValue(capability, map(140, 500, 0, 1, value)).catch(() => {}); 100 | callback(); 101 | }) 102 | .on('get', function(callback) { 103 | const value = 'light_temperature' in (device.capabilitiesObj || {}) ? device.capabilitiesObj.light_temperature.value : 1; 104 | callback(null, map(0, 1, 140, 500, value) || 500); 105 | }); 106 | } 107 | 108 | // Hue 109 | if (capabilityBaseName === 'light_hue') { 110 | service 111 | .addCharacteristic(Characteristic.Hue) 112 | .on('set', function(value, callback) { 113 | device.setCapabilityValue(capability, value / 360).catch(() => {}); 114 | callback(); 115 | }) 116 | .on('get', returnCapabilityValue(device, capability, v => v * 360)); 117 | } 118 | }; 119 | 120 | 121 | const registerCapabilityListener = ({ device, capability, service }) => { 122 | console.log('created listener for - ' + capability); 123 | 124 | const listener = (value) => { 125 | onStateChange({ service, capability, value, device }); 126 | }; 127 | 128 | // For Homey device we want to use full capability name 129 | try { device.makeCapabilityInstance(capability, listener) } catch(e) {}; 130 | } 131 | 132 | const onStateChange = ({ service, capability, value, device }) => { 133 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 134 | const { capabilityBaseName } = getCapabilityNameSegments(capability) 135 | 136 | if (capabilityBaseName === 'onoff') { 137 | service.getCharacteristic(Characteristic.On).updateValue(value); 138 | } else if (capabilityBaseName === 'dim') { 139 | service.getCharacteristic(Characteristic.Brightness).updateValue(value * 100); 140 | } else if (capabilityBaseName === 'light_saturation') { 141 | service.getCharacteristic(Characteristic.Saturation).updateValue(value * 100); 142 | } else if (capabilityBaseName === 'light_temperature') { 143 | service.getCharacteristic(Characteristic.ColorTemperature).updateValue(map(0, 1, 140, 500, value)); 144 | } else if (capabilityBaseName === 'light_hue') { 145 | service.getCharacteristic(Characteristic.Hue).updateValue(value * 360); 146 | } 147 | } 148 | 149 | const isLightCapability = (capability) => { 150 | const { capabilityBaseName } = getCapabilityNameSegments(capability) 151 | return ['onoff', 'dim', 'light_saturation', 'light_temperature', 'light_hue'].includes(capabilityBaseName); 152 | } 153 | 154 | const getServiceBuildArguments = ({ device, groupName, groups, group }) => { 155 | const isSingleService = groups.length === 1; 156 | const serviceName = isSingleService ? device.name : getServiceName({ device, group }) 157 | 158 | return [Service.Lightbulb, serviceName, groupName]; 159 | }; 160 | 161 | // Name service by first capability title, if its present? NOTE: Maybe there are better ways 162 | const getServiceName = ({ device, group }) => { 163 | const capability = group.capabilities[0]; 164 | const capabilityDetails = device.capabilitiesObj ? device.capabilitiesObj[capability] : {}; 165 | 166 | return capabilityDetails.title || device.name; 167 | }; 168 | 169 | -------------------------------------------------------------------------------- /settings/fuse.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Fuse.js v3.0.4 - Lightweight fuzzy-search (http://fusejs.io) 3 | * 4 | * Copyright (c) 2012-2017 Kirollos Risk (http://kiro.me) 5 | * All Rights Reserved. Apache Software License 2.0 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | */ 9 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("Fuse",[],t):"object"==typeof exports?exports.Fuse=t():e.Fuse=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.i=function(e){return e},t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=8)}([function(e,t,n){"use strict";e.exports=function(e){return"[object Array]"===Object.prototype.toString.call(e)}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;nn)return i(e,this.pattern,r);var o=this.options,a=o.location,c=o.distance,h=o.threshold,l=o.findAllMatches,u=o.minMatchCharLength;return s(e,this.pattern,this.patternAlphabet,{location:a,distance:c,threshold:h,findAllMatches:l,minMatchCharLength:u})}}]),e}();e.exports=c},function(e,t,n){"use strict";var r=n(0),o=function e(t,n,o){if(n){var i=n.indexOf("."),s=n,a=null;-1!==i&&(s=n.slice(0,i),a=n.slice(i+1));var c=t[s];if(null!==c&&void 0!==c)if(a||"string"!=typeof c&&"number"!=typeof c)if(r(c))for(var h=0,l=c.length;h0&&void 0!==arguments[0]?arguments[0]:[],t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1,n=[],r=-1,o=-1,i=0,s=e.length;i=t&&n.push([r,o]),r=-1)}return e[i-1]&&i-r>=t&&n.push([r,i-1]),n}},function(e,t,n){"use strict";e.exports=function(e){for(var t={},n=e.length,r=0;r2&&void 0!==arguments[2]?arguments[2]:/ +/g,r=e.match(new RegExp(t.replace(n,"|"))),o=!!r,i=[];if(o)for(var s=0,a=r.length;s=j;T-=1){var E=T-1,K=n[e.charAt(E)];if(K&&(S[E]=1),I[T]=(I[T+1]<<1|1)&K,0!==F&&(I[T]|=(L[T+1]|L[T])<<1|1|L[T+1]),I[T]&A&&(w=r(t,{errors:F,currentLocation:E,expectedLocation:g,distance:h}))<=y){if(y=w,(k=E)<=g)break;j=Math.max(1,2*g-k)}}if(r(t,{errors:F+1,currentLocation:g,expectedLocation:g,distance:h})>y)break;L=I}return{isMatch:k>=0,score:0===w?.001:w,matchedIndices:o(S,p)}}},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:"",t=[];if(this.options.tokenize)for(var n=e.split(this.options.tokenSeparator),r=0,o=n.length;r0&&void 0!==arguments[0]?arguments[0]:[],t=arguments[1],n=this.list,r={},o=[];if("string"==typeof n[0]){for(var i=0,s=n.length;i1)throw new Error("Key weight has to be > 0 and <= 1");v=v.name}else a[v]={weight:1};this._analyze({key:v,value:this.options.getFn(l,v),record:l,index:c},{resultMap:r,results:o,tokenSearchers:e,fullSearcher:t})}return{weights:a,results:o}}},{key:"_analyze",value:function(e,t){var n=e.key,r=e.value,o=e.record,i=e.index,s=t.tokenSearchers,c=void 0===s?[]:s,h=t.fullSearcher,l=void 0===h?[]:h,u=t.resultMap,f=void 0===u?{}:u,v=t.results,d=void 0===v?[]:v;if(void 0!==r&&null!==r){var p=!1,g=-1,m=0;if("string"==typeof r){this._log("\nKey: "+(""===n?"-":n));var y=l.search(r);if(this._log('Full text: "'+r+'", score: '+y.score),this.options.tokenize){for(var k=r.split(this.options.tokenSeparator),x=[],S=0;S-1&&(O=(O+g)/2),this._log("Score average:",O);var P=!this.options.tokenize||!this.options.matchAllTokens||m>=c.length;if(this._log("\nCheck Matches: "+P),(p||y.isMatch)&&P){var j=f[i];j?j.output.push({key:n,score:O,matchedIndices:y.matchedIndices}):(f[i]={item:o,output:[{key:n,score:O,matchedIndices:y.matchedIndices}]},d.push(f[i]))}}else if(a(r))for(var z=0,I=r.length;z device.capabilitiesObj[stateKeys.filter(k => k.startsWith(key))[0]].value; 8 | const dimmable = 'dim' in capabilities; 9 | 10 | // Init device 11 | const homekitAccessory = new Accessory(device.name || 'Sensor', device.id); 12 | 13 | // Set device info 14 | setupAccessoryInformations(homekitAccessory, device); 15 | 16 | // Device identify when added 17 | homekitAccessory.on('identify', function(paired, callback) { 18 | console.log(device.name + ' identify'); 19 | callback(); 20 | }); 21 | 22 | // Add services and characteristics 23 | 24 | // Outlet- or Lightbulb-types. 25 | if ('onoff' in capabilities) { 26 | const service = homekitAccessory.addService(Service[ dimmable ? 'Lightbulb' : 'Outlet' ], device.name); 27 | 28 | service 29 | .getCharacteristic(Characteristic.On) 30 | .on('set', function(value, callback) { 31 | device.setCapabilityValue('onoff', value).catch(() => {}); 32 | callback(); 33 | }) 34 | .on('get', returnCapabilityValue(device, 'onoff')); 35 | 36 | if (dimmable) { 37 | service 38 | .getCharacteristic(Characteristic.Brightness) 39 | .on('set', debounce(function(value, callback) { 40 | device.setCapabilityValue('dim', value / 100).catch(() => {}); 41 | callback(); 42 | }, 500)) 43 | .on('get', returnCapabilityValue(device, 'dim', v => v * 100)); 44 | } 45 | } 46 | 47 | // Light Sensor 48 | if ('measure_luminance' in capabilities) { 49 | homekitAccessory 50 | .addService(Service.LightSensor, device.name) 51 | .getCharacteristic(Characteristic.CurrentAmbientLightLevel) 52 | .on('get', function(callback) { 53 | callback(null, capValue('measure_luminance') ?? this.value); 54 | }); 55 | } 56 | 57 | // Temperature Sensor 58 | if ('measure_temperature' in capabilities) { 59 | homekitAccessory 60 | .addService(Service.TemperatureSensor, device.name) 61 | .getCharacteristic(Characteristic.CurrentTemperature) 62 | .setProps({ 63 | minValue : -100, 64 | maxValue : 100 65 | }) 66 | .on('get', function(callback) { 67 | callback(null, capValue('measure_temperature') ?? this.value); 68 | }); 69 | } 70 | 71 | // Humidity Sensor 72 | if ('measure_humidity' in capabilities) { 73 | homekitAccessory 74 | .addService(Service.HumiditySensor, device.name) 75 | .getCharacteristic(Characteristic.CurrentRelativeHumidity) 76 | .on('get', function(callback) { 77 | callback(null, capValue('measure_humidity') ?? this.value); 78 | }); 79 | } 80 | 81 | // Motion Sensor 82 | if ('alarm_motion' in capabilities) { 83 | homekitAccessory 84 | .addService(Service.MotionSensor, device.name) 85 | .getCharacteristic(Characteristic.MotionDetected) 86 | .on('get', function(callback) { 87 | callback(null, ~~capValue('alarm_motion')); 88 | }); 89 | } 90 | 91 | // Leak Sensor 92 | if ('alarm_water' in capabilities) { 93 | homekitAccessory 94 | .addService(Service.LeakSensor, device.name) 95 | .getCharacteristic(Characteristic.LeakDetected) 96 | .on('get', function(callback) { 97 | callback(null, ~~capValue('alarm_water')); 98 | }); 99 | } 100 | 101 | // Contact Sensor 102 | if ('alarm_contact' in capabilities) { 103 | homekitAccessory 104 | .addService(Service.ContactSensor, device.name) 105 | .getCharacteristic(Characteristic.ContactSensorState) 106 | .on('get', function(callback) { 107 | callback(null, ~~capValue('alarm_contact')); 108 | }); 109 | } 110 | 111 | // Smoke Sensor 112 | if ('alarm_smoke' in capabilities) { 113 | // Smoke Detected 114 | homekitAccessory 115 | .addService(Service.SmokeSensor, device.name) 116 | .getCharacteristic(Characteristic.SmokeDetected) 117 | .on('get', function(callback) { 118 | callback(null, ~~capValue('alarm_smoke')); 119 | }); 120 | } 121 | 122 | // CarbonMonoxide Sensor 123 | if ('measure_co' in capabilities || 'alarm_co' in capabilities) { 124 | var coService = homekitAccessory 125 | .addService(Service.CarbonMonoxideSensor, device.name); 126 | 127 | if ('measure_co' in capabilities) { 128 | coService.getCharacteristic(Characteristic.CarbonMonoxideLevel) 129 | .on('get', function(callback) { 130 | callback(null, capValue('measure_co') || this.value); 131 | }); 132 | } 133 | 134 | if ('alarm_co' in capabilities) { 135 | coService.getCharacteristic(Characteristic.CarbonMonoxideDetected) 136 | .on('get', function(callback) { 137 | callback(null, ~~capValue('alarm_co')); 138 | }); 139 | } 140 | } 141 | 142 | // CarbonDioxide Sensor 143 | if ('measure_co2' in capabilities || 'alarm_co2' in capabilities) { 144 | var co2service = homekitAccessory 145 | .addService(Service.CarbonDioxideSensor, device.name); 146 | 147 | if ('measure_co2' in capabilities) { 148 | co2service.getCharacteristic(Characteristic.CarbonDioxideLevel) 149 | .on('get', function(callback) { 150 | callback(null, capValue('measure_co2') || this.value); 151 | }); 152 | } 153 | 154 | if ('alarm_co2' in capabilities) { 155 | co2service.getCharacteristic(Characteristic.CarbonDioxideDetected) 156 | .on('get', function(callback) { 157 | callback(null, ~~capValue('alarm_co2')); 158 | }); 159 | } 160 | } 161 | 162 | // On realtime event update the device 163 | for (let i in device.capabilities) { 164 | if (device.capabilities[i] && ['measure_luminance','measure_temperature','measure_humidity','measure_pressure','alarm_motion','alarm_water','alarm_contact','alarm_smoke','alarm_co','measure_co','alarm_co2','measure_co2'].includes(device.capabilities[i].split('.')[0])) { 165 | console.log('created listener for - ' + device.capabilities[i]); 166 | let listener = async (value) => { 167 | onStateChange(device.capabilities[i], value, device); 168 | }; 169 | 170 | try { device.makeCapabilityInstance(device.capabilities[i], listener) } catch(e) {}; 171 | } 172 | } 173 | 174 | async function onStateChange(capability, value, device) { 175 | console.log('State Change - ' + device.name + ' - ' + capability + ' - ' + value); 176 | if (! device.capabilitiesObj) return; 177 | 178 | let stateKeys = Object.keys(device.capabilitiesObj); 179 | const capValue = key => device.capabilitiesObj[stateKeys.filter(k => k.startsWith(key))[0]].value; 180 | 181 | switch(capability) { 182 | case 'onoff': 183 | homekitAccessory 184 | .getService(Service[ dimmable ? 'Lightbulb' : 'Outlet']) 185 | .getCharacteristic(Characteristic.On) 186 | .updateValue(value); 187 | break; 188 | case 'dim': 189 | homekitAccessory 190 | .getService(Service.Lightbulb) 191 | .getCharacteristic(Characteristic.Brightness) 192 | .updateValue(value * 100); 193 | break; 194 | case 'measure_luminance': // Light Sensor 195 | homekitAccessory 196 | .getService(Service.LightSensor) 197 | .getCharacteristic(Characteristic.CurrentAmbientLightLevel) 198 | .updateValue(capValue('measure_luminance') ?? this.value); 199 | break; 200 | case 'measure_temperature': // Temperature Sensor 201 | homekitAccessory 202 | .getService(Service.TemperatureSensor) 203 | .getCharacteristic(Characteristic.CurrentTemperature) 204 | .updateValue(capValue('measure_temperature') ?? this.value); 205 | break; 206 | case 'measure_humidity': // Humidity Sensor 207 | homekitAccessory 208 | .getService(Service.HumiditySensor) 209 | .getCharacteristic(Characteristic.CurrentRelativeHumidity) 210 | .updateValue(capValue('measure_humidity') ?? this.value); 211 | break; 212 | case 'alarm_motion': // Motion Sensor 213 | homekitAccessory 214 | .getService(Service.MotionSensor) 215 | .getCharacteristic(Characteristic.MotionDetected) 216 | .updateValue(~~capValue('alarm_motion')); 217 | break; 218 | case 'alarm_water': // Leak Sensor 219 | homekitAccessory 220 | .getService(Service.LeakSensor) 221 | .getCharacteristic(Characteristic.LeakDetected) 222 | .updateValue(~~capValue('alarm_water')); 223 | break; 224 | case 'alarm_contact': // Contact Sensor 225 | homekitAccessory 226 | .getService(Service.ContactSensor) 227 | .getCharacteristic(Characteristic.ContactSensorState) 228 | .updateValue(~~capValue('alarm_contact')); 229 | break; 230 | case 'alarm_smoke': // Smoke Sensor 231 | homekitAccessory 232 | .getService(Service.SmokeSensor) 233 | .getCharacteristic(Characteristic.SmokeDetected) 234 | .updateValue(~~capValue('alarm_smoke')); 235 | break; 236 | case 'alarm_co': // Carbonmonoxide Sensor 237 | homekitAccessory 238 | .getService(Service.CarbonMonoxideSensor) 239 | .getCharacteristic(Characteristic.CarbonMonoxideDetected) 240 | .updateValue(~~capValue('alarm_co')); 241 | break; 242 | case 'measure_co': 243 | homekitAccessory 244 | .getService(Service.CarbonMonoxideSensor) 245 | .getCharacteristic(Characteristic.CarbonMonoxideLevel) 246 | .updateValue(capValue('measure_co')); 247 | break; 248 | case 'alarm_co2': // Carbondioxide Sensor 249 | homekitAccessory 250 | .getService(Service.CarbonDioxideSensor) 251 | .getCharacteristic(Characteristic.CarbonDioxideDetected) 252 | .updateValue(~~capValue('alarm_co2')); 253 | break; 254 | case 'measure_co2': 255 | homekitAccessory 256 | .getService(Service.CarbonDioxideSensor) 257 | .getCharacteristic(Characteristic.CarbonDioxideLevel) 258 | .updateValue(capValue('measure_co2')); 259 | break; 260 | } 261 | } 262 | 263 | // Return device to app.js 264 | return homekitAccessory; 265 | } 266 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const Homey = require('homey') 2 | const { HomeyAPI } = require('./modules/homey-api'); 3 | const fs = require('fs'); 4 | const storage = require('node-persist'); 5 | const path = require('path'); 6 | const debounce = require('lodash.debounce'); 7 | const { 8 | HAPStorage, 9 | uuid, 10 | Bridge, 11 | Service, 12 | Characteristic, 13 | Accessory 14 | } = require('./modules/hap-nodejs'); 15 | const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); 16 | 17 | // Device classes 18 | const homekit = require('./lib/'); 19 | let bridge; 20 | 21 | // Clear storage the hard way. 22 | function clearHAPStorage() { 23 | console.error('Clearing internal storage:'); 24 | for (const f of fs.readdirSync('/userdata')) { 25 | const path = `/userdata/${ f }`; 26 | console.error('- removing', path); 27 | fs.rmSync(path, { recursive : true }); 28 | } 29 | } 30 | 31 | // Try to initialize existing storage. 32 | try { 33 | HAPStorage.setCustomStoragePath('/userdata'); 34 | HAPStorage.storage(); 35 | } catch(e) { 36 | console.error('Error initializing existing storage', e); 37 | clearHAPStorage(); 38 | throw Error('Please restart do to an internal storage issue'); 39 | } 40 | 41 | module.exports = class HomekitApp extends Homey.App { 42 | // Get API control function 43 | async getApi() { 44 | if (! this.api) { 45 | this.api = await HomeyAPI.createAppAPI({ homey: this.homey }); 46 | // have to do this really early to work around a bug in the Web API 47 | await this.api.devices.connect(); 48 | } 49 | return this.api; 50 | } 51 | 52 | // Get all devices function 53 | async getDevices() { 54 | // HomeyAPIV3 doesn't set zoneName property (unlike V2) 55 | // so we'll set it ourselves. 56 | const [ zones, devices ] = await Promise.all([ 57 | this.api.zones.getZones(), 58 | this.api.devices.getDevices() 59 | ]); 60 | 61 | for (const device of Object.values(devices)) { 62 | device.zoneName = zones[device.zone].name; 63 | } 64 | 65 | return devices; 66 | } 67 | 68 | // Start server function 69 | async startingServer() { 70 | this.log('starting server'); 71 | const api = this.api; 72 | 73 | // Start by creating our Bridge which will host all loaded Accessories 74 | let bridgeIdentifier = this.homey.settings.get('bridgeIdentifier') || 'Homey'; 75 | this.log(`Using "${ bridgeIdentifier }" as bridge identifier`); 76 | bridge = new Bridge(bridgeIdentifier, uuid.generate(bridgeIdentifier)); 77 | bridge 78 | .getService(Service.AccessoryInformation) 79 | .setCharacteristic(Characteristic.Manufacturer, 'Athom') 80 | .setCharacteristic(Characteristic.Model, 'Homey') 81 | .setCharacteristic(Characteristic.FirmwareRevision, '2.0'); 82 | 83 | // Listen for bridge identification event 84 | bridge.on('identify', function(paired, callback) { 85 | console.log('Homey identify'); 86 | callback(); // success 87 | }); 88 | 89 | // Retrieve a list of all devices, and a list of devices that should (not) be paired. 90 | let knownDevices = {}; 91 | let allDevices = await this.getDevices(); 92 | let pairedDevicesSetting = this.homey.settings.get('pairedDevices') || {}; 93 | this.pairedDevices = {}; 94 | for (let id in allDevices) { 95 | let device = allDevices[id]; 96 | 97 | // Assume that unknown (new) devices should be paired. 98 | if (! (device.id in pairedDevicesSetting)) { 99 | pairedDevicesSetting[device.id] = true; 100 | } 101 | 102 | if (pairedDevicesSetting[id] === true) { 103 | this.addDevice(device); 104 | } else { 105 | this.pairedDevices[device.id] = false; 106 | this.log(`Not adding '${ device.name }' (shouldn't be paired)`); 107 | } 108 | knownDevices[id] = true; 109 | } 110 | 111 | // Update settings 112 | this.homey.settings.set('pairedDevices', this.pairedDevices); 113 | 114 | // Watch for setting changes 115 | this.homey.settings.on('set', debounce(key => { 116 | if (key === 'pairedDevices') { 117 | this.pairedDevices = this.homey.settings.get('pairedDevices') || {}; 118 | } 119 | }, 100)); 120 | 121 | // Subscribe to realtime events and set all devices global 122 | api.devices.on('device.update', async ({ id, ready }) => { 123 | if (! ready) return; 124 | if (id in knownDevices) return; 125 | knownDevices[id] = true; 126 | this.log('New device found!'); 127 | const device = await api.devices.getDevice({ id }); 128 | this.addDevice(device); 129 | this.homey.settings.set('pairedDevices', this.pairedDevices); 130 | }); 131 | 132 | // Publish bridge 133 | const username = this.homey.settings.get('username') || 'CC:22:3D:E3:CE:F6'; 134 | this.log(`Using ${ username } as username.`); 135 | try { 136 | await bridge.publish({ 137 | username: username, 138 | port: 51833, 139 | pincode: '200-20-200', 140 | category: Accessory.Categories.BRIDGE 141 | }); 142 | this.log('Started bridge'); 143 | } catch(e) { 144 | this.error('Unable to start bridge'); 145 | this.error(e); 146 | } 147 | } 148 | 149 | // On app init 150 | async onInit() { 151 | if (Homey.env.RESET_SETTINGS) { 152 | this.homey.settings.set('pairedDevices', null); 153 | } 154 | try { 155 | this.api = await this.getApi(); 156 | } catch(e) { 157 | this.error('Unable to get API instance'); 158 | this.error(e); 159 | return; 160 | } 161 | this.onInit2(); 162 | } 163 | 164 | async onInit2() { 165 | this.pairedDevices = {}; 166 | 167 | // If the app is started less than 10 minuten after a reboot, wait for 168 | // devices to settle before starting the bridge, otherwise iOS will get 169 | // confused. 170 | const settleTime = this.homey.settings.get('settleTime') || 120; 171 | const uptime = (await this.api.system.getInfo()).uptime; 172 | if (uptime < 600) { 173 | this.log('Homey rebooted, waiting for devices to settle'); 174 | let previousDeviceCount = 0; 175 | while (true) { 176 | let newDeviceCount = Object.keys(await this.getDevices()).length; 177 | if (newDeviceCount && newDeviceCount === previousDeviceCount) { 178 | this.log(`devices have settled (counted ${ newDeviceCount } in total)`); 179 | break; 180 | } 181 | previousDeviceCount = newDeviceCount; 182 | this.log(`devices have not yet settled, waiting for ${ settleTime } seconds...`); 183 | await delay(settleTime * 1000); 184 | } 185 | } 186 | this.startingServer(); 187 | } 188 | 189 | // Add device function 190 | async addDevice(device) { 191 | if (! device || ! device.ready || ! device.capabilitiesObj || bridge.bridgedAccessories.length >= 149) return; 192 | 193 | let api = this.api; 194 | let capabilities = device.capabilities.reduce((acc, val) => { 195 | if (typeof val === 'string') { 196 | acc[val.split('.')[0]] = true; 197 | } 198 | return acc; 199 | }, {}); 200 | 201 | let isPaired = false; 202 | if ([device.class, device.virtualClass].includes('light') && 'onoff' in capabilities) { 203 | this.log('Found light: ' + device.name) 204 | isPaired = true; 205 | bridge.addBridgedAccessory(homekit.createLight(device, api)); 206 | } else if (device.class === 'lock' && 'locked' in capabilities) { 207 | this.log('Found lock: ' + device.name) 208 | isPaired = true; 209 | bridge.addBridgedAccessory(homekit.createLock(device, api)); 210 | } else if ([ 'curtain', 'blinds', 'sunshade', 'windowcoverings' ].includes(device.class) && 'windowcoverings_set' in capabilities && !('dim' in capabilities)) { 211 | this.log(`Found ${ device.class } (windowcovering_set): ${ device.name }`); 212 | isPaired = true; 213 | bridge.addBridgedAccessory(homekit.createCurtains(device, api)); 214 | } else if ([ 'curtain', 'blinds', 'sunshade', 'windowcoverings' ].includes(device.class) && 'windowcoverings_state' in capabilities && !('dim' in capabilities)) { 215 | this.log('Found blinds (state): ' + device.name) 216 | isPaired = true; 217 | bridge.addBridgedAccessory(homekit.createStateBlinds(device, api)); 218 | } else if (device.class === 'windowcoverings' && 'dim' in capabilities) { 219 | this.log('Found blinds (dim): ' + device.name) 220 | isPaired = true; 221 | bridge.addBridgedAccessory(homekit.createDimBlinds(device, api)); 222 | } else if (device.class === 'socket' && 'onoff' in capabilities) { 223 | this.log('Found socket: ' + device.name) 224 | isPaired = true; 225 | bridge.addBridgedAccessory(homekit.createSocket(device, api, capabilities)); 226 | } else if ((device.class === 'fan' || device.class === 'heater') && 'onoff' in capabilities) { 227 | this.log('Found fan/heater: ' + device.name) 228 | isPaired = true; 229 | bridge.addBridgedAccessory(homekit.createFan(device, api, capabilities)); 230 | } else if (['amplifier', 'button', 'coffeemachine', 'kettle', 'tv', 'other', 'remote'].includes(device.class) && 'onoff' in capabilities) { 231 | this.log('Found class with onoff: ' + device.name) 232 | isPaired = true; 233 | bridge.addBridgedAccessory(homekit.createSwitch(device, api)); 234 | } else if (device.class === 'thermostat' && 'measure_temperature' in capabilities && 'target_temperature' in capabilities) { 235 | this.log('Found thermostat: ' + device.name) 236 | isPaired = true; 237 | bridge.addBridgedAccessory(homekit.createThermostat(device, api, capabilities)); 238 | } else if (device.class === 'doorbell' && 'alarm_generic' in capabilities) { 239 | this.log('Found doorbell: ' + device.name) 240 | isPaired = true; 241 | bridge.addBridgedAccessory(homekit.createDoorbell(device, api)); 242 | } else if ('homealarm_state' in capabilities || device.class === 'homealarm' || device.virtualClass === 'homealarm') { 243 | this.log('Found Security system: ' + device.name) 244 | isPaired = true; 245 | bridge.addBridgedAccessory(homekit.createSecuritySystem(device, api, capabilities)); 246 | } else if ([ device.class, device.virtualClass ].includes('speaker')) { 247 | this.log('Found speaker: ' + device.name) 248 | isPaired = true; 249 | bridge.addBridgedAccessory(homekit.createSpeaker(device, api, capabilities)); 250 | } else if ('button' in capabilities) { 251 | this.log('Found button: ' + device.name) 252 | isPaired = true; 253 | bridge.addBridgedAccessory(homekit.createButton(device, api)); 254 | } else if ([ 'sensor', 'other' ].includes(device.class) && ('measure_luminance' in capabilities || 'measure_temperature' in capabilities || 'measure_humidity' in capabilities || 'measure_pressure' in capabilities || 'alarm_motion' in capabilities || 'alarm_water' in capabilities || 'alarm_contact' in capabilities || 'alarm_smoke' in capabilities || 'alarm_co' in capabilities || 'alarm_co2' in capabilities)) { 255 | this.log('Found Sensor: ' + device.name) 256 | isPaired = true; 257 | bridge.addBridgedAccessory(homekit.createSensor(device, api, capabilities)); 258 | } else if (device.class === 'garagedoor' && 'garagedoor_closed' in capabilities) { 259 | this.log('Found garage door:', device.name); 260 | isPaired = true; 261 | bridge.addBridgedAccessory(homekit.createGarageDoorOpener(device, api, capabilities)); 262 | } else { 263 | this.log(`No matching class found for: ${ device.name } of class ${ device.class }`); 264 | } 265 | this.pairedDevices[device.id] = isPaired; 266 | 267 | device.on('$delete', id => { 268 | this.deleteDevice(device); 269 | }); 270 | } 271 | 272 | async addDeviceById(id) { 273 | return this.addDevice(await this.findDeviceById(id)); 274 | } 275 | 276 | deleteDevice(device) { 277 | if (! device || ! bridge) return; 278 | this.log(`Deleting device '${ device.name }' (${ device.id }) from HomeKit`); 279 | delete this.pairedDevices[device.id]; 280 | this.homey.settings.set('pairedDevices', this.pairedDevices); 281 | let acc = bridge.bridgedAccessories.find(r => r.UUID === device.id); 282 | acc && bridge.removeBridgedAccessory(acc); 283 | } 284 | 285 | async deleteDeviceById(id) { 286 | return this.deleteDevice(await this.findDeviceById(id)); 287 | } 288 | 289 | async findDeviceById(id) { 290 | let allDevices = await this.getDevices(); 291 | return Object.values(allDevices).find(device => device.id === id); 292 | } 293 | 294 | clearStorage() { 295 | clearHAPStorage(); 296 | // generate a new bridge 'username' to allow iOS to rediscover the bridge 297 | const username = 'XX:XX:XX:XX:XX:XX'.replace(/X/g, function() { 298 | return '0123456789ABCDEF'.charAt(Math.floor(Math.random() * 16)) 299 | }); 300 | this.log(`Setting new username to ${ username }`); 301 | this.homey.settings.set('username', username); 302 | this.homey.settings.set('pairedDevices', null); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /settings/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 | 29 |
30 | 31 | 32 |
33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |
45 |
{{ device.name }}
46 |
{{ device.zoneName }}
47 |
48 |
49 |
50 | 54 |
55 |
56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 | 64 | 65 | 70 | 73 | 74 |
{{log.time}} 66 | Info 67 | Success 68 | Error 69 | 71 | {{ log.string }} 72 |
75 |
76 |
77 | 78 | 79 |
80 |
81 |
82 |

83 | If you want to run multiple instances of Homeykit in your 84 | network, for instance if you have two (or more) Homey's, each 85 | instance should have a unique bridge identifier. 86 |

87 |

88 | 89 | After changing this setting, restart the Homeykit app for the 90 | change to take effect. 91 | 92 |

93 |

94 | Change this only if you have a reason to do 95 | so, otherwise you may lose your current HomeKit 96 | configuration. 97 |

98 |
99 |
100 |
101 | 102 |
103 |
104 |
105 |

106 | 107 |

108 |
109 |
110 |
111 |
112 |
113 |
114 |

115 | If you run into the problem that your Homekit configuration (rooms, automations) gets lost after a reboot, 116 | you can configure a "settle time" here. By default, this is 2 minutes, which may not be enough for Homey's 117 | with lots of devices and/or apps installed. 118 |

119 |
120 |
121 |
122 | 123 |
124 |
125 |
126 |

127 | 128 |

129 |
130 |
131 |
132 |
133 |
134 |
135 |

136 | If you have problems trying to add Homey to HomeKit, for instance, if HomeKit can't find the "Homey" accessory, 137 | you can try and clear the HomeKit storage. Make sure to restart the app afterwards! 138 |

139 |

140 | Clear HomeKit storage and start over. 141 |

142 |
143 |
144 |
145 | 146 |
147 | 148 | 149 | 150 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /settings/vue.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Vue.js v2.6.12 3 | * (c) 2014-2020 Evan You 4 | * Released under the MIT License. 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).Vue=t()}(this,function(){"use strict";var e=Object.freeze({});function t(e){return null==e}function n(e){return null!=e}function r(e){return!0===e}function i(e){return"string"==typeof e||"number"==typeof e||"symbol"==typeof e||"boolean"==typeof e}function o(e){return null!==e&&"object"==typeof e}var a=Object.prototype.toString;function s(e){return"[object Object]"===a.call(e)}function c(e){var t=parseFloat(String(e));return t>=0&&Math.floor(t)===t&&isFinite(e)}function u(e){return n(e)&&"function"==typeof e.then&&"function"==typeof e.catch}function l(e){return null==e?"":Array.isArray(e)||s(e)&&e.toString===a?JSON.stringify(e,null,2):String(e)}function f(e){var t=parseFloat(e);return isNaN(t)?e:t}function p(e,t){for(var n=Object.create(null),r=e.split(","),i=0;i-1)return e.splice(n,1)}}var m=Object.prototype.hasOwnProperty;function y(e,t){return m.call(e,t)}function g(e){var t=Object.create(null);return function(n){return t[n]||(t[n]=e(n))}}var _=/-(\w)/g,b=g(function(e){return e.replace(_,function(e,t){return t?t.toUpperCase():""})}),$=g(function(e){return e.charAt(0).toUpperCase()+e.slice(1)}),w=/\B([A-Z])/g,C=g(function(e){return e.replace(w,"-$1").toLowerCase()});var x=Function.prototype.bind?function(e,t){return e.bind(t)}:function(e,t){function n(n){var r=arguments.length;return r?r>1?e.apply(t,arguments):e.call(t,n):e.call(t)}return n._length=e.length,n};function k(e,t){t=t||0;for(var n=e.length-t,r=new Array(n);n--;)r[n]=e[n+t];return r}function A(e,t){for(var n in t)e[n]=t[n];return e}function O(e){for(var t={},n=0;n0,Z=J&&J.indexOf("edge/")>0,G=(J&&J.indexOf("android"),J&&/iphone|ipad|ipod|ios/.test(J)||"ios"===K),X=(J&&/chrome\/\d+/.test(J),J&&/phantomjs/.test(J),J&&J.match(/firefox\/(\d+)/)),Y={}.watch,Q=!1;if(z)try{var ee={};Object.defineProperty(ee,"passive",{get:function(){Q=!0}}),window.addEventListener("test-passive",null,ee)}catch(e){}var te=function(){return void 0===B&&(B=!z&&!V&&"undefined"!=typeof global&&(global.process&&"server"===global.process.env.VUE_ENV)),B},ne=z&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function re(e){return"function"==typeof e&&/native code/.test(e.toString())}var ie,oe="undefined"!=typeof Symbol&&re(Symbol)&&"undefined"!=typeof Reflect&&re(Reflect.ownKeys);ie="undefined"!=typeof Set&&re(Set)?Set:function(){function e(){this.set=Object.create(null)}return e.prototype.has=function(e){return!0===this.set[e]},e.prototype.add=function(e){this.set[e]=!0},e.prototype.clear=function(){this.set=Object.create(null)},e}();var ae=S,se=0,ce=function(){this.id=se++,this.subs=[]};ce.prototype.addSub=function(e){this.subs.push(e)},ce.prototype.removeSub=function(e){h(this.subs,e)},ce.prototype.depend=function(){ce.target&&ce.target.addDep(this)},ce.prototype.notify=function(){for(var e=this.subs.slice(),t=0,n=e.length;t-1)if(o&&!y(i,"default"))a=!1;else if(""===a||a===C(e)){var c=Pe(String,i.type);(c<0||s0&&(st((u=e(u,(a||"")+"_"+c))[0])&&st(f)&&(s[l]=he(f.text+u[0].text),u.shift()),s.push.apply(s,u)):i(u)?st(f)?s[l]=he(f.text+u):""!==u&&s.push(he(u)):st(u)&&st(f)?s[l]=he(f.text+u.text):(r(o._isVList)&&n(u.tag)&&t(u.key)&&n(a)&&(u.key="__vlist"+a+"_"+c+"__"),s.push(u)));return s}(e):void 0}function st(e){return n(e)&&n(e.text)&&!1===e.isComment}function ct(e,t){if(e){for(var n=Object.create(null),r=oe?Reflect.ownKeys(e):Object.keys(e),i=0;i0,a=t?!!t.$stable:!o,s=t&&t.$key;if(t){if(t._normalized)return t._normalized;if(a&&r&&r!==e&&s===r.$key&&!o&&!r.$hasNormal)return r;for(var c in i={},t)t[c]&&"$"!==c[0]&&(i[c]=pt(n,c,t[c]))}else i={};for(var u in n)u in i||(i[u]=dt(n,u));return t&&Object.isExtensible(t)&&(t._normalized=i),R(i,"$stable",a),R(i,"$key",s),R(i,"$hasNormal",o),i}function pt(e,t,n){var r=function(){var e=arguments.length?n.apply(null,arguments):n({});return(e=e&&"object"==typeof e&&!Array.isArray(e)?[e]:at(e))&&(0===e.length||1===e.length&&e[0].isComment)?void 0:e};return n.proxy&&Object.defineProperty(e,t,{get:r,enumerable:!0,configurable:!0}),r}function dt(e,t){return function(){return e[t]}}function vt(e,t){var r,i,a,s,c;if(Array.isArray(e)||"string"==typeof e)for(r=new Array(e.length),i=0,a=e.length;idocument.createEvent("Event").timeStamp&&(sn=function(){return cn.now()})}function un(){var e,t;for(an=sn(),rn=!0,Qt.sort(function(e,t){return e.id-t.id}),on=0;onon&&Qt[n].id>e.id;)n--;Qt.splice(n+1,0,e)}else Qt.push(e);nn||(nn=!0,Ye(un))}}(this)},fn.prototype.run=function(){if(this.active){var e=this.get();if(e!==this.value||o(e)||this.deep){var t=this.value;if(this.value=e,this.user)try{this.cb.call(this.vm,e,t)}catch(e){Re(e,this.vm,'callback for watcher "'+this.expression+'"')}else this.cb.call(this.vm,e,t)}}},fn.prototype.evaluate=function(){this.value=this.get(),this.dirty=!1},fn.prototype.depend=function(){for(var e=this.deps.length;e--;)this.deps[e].depend()},fn.prototype.teardown=function(){if(this.active){this.vm._isBeingDestroyed||h(this.vm._watchers,this);for(var e=this.deps.length;e--;)this.deps[e].removeSub(this);this.active=!1}};var pn={enumerable:!0,configurable:!0,get:S,set:S};function dn(e,t,n){pn.get=function(){return this[t][n]},pn.set=function(e){this[t][n]=e},Object.defineProperty(e,n,pn)}function vn(e){e._watchers=[];var t=e.$options;t.props&&function(e,t){var n=e.$options.propsData||{},r=e._props={},i=e.$options._propKeys=[];e.$parent&&$e(!1);var o=function(o){i.push(o);var a=Me(o,t,n,e);xe(r,o,a),o in e||dn(e,"_props",o)};for(var a in t)o(a);$e(!0)}(e,t.props),t.methods&&function(e,t){e.$options.props;for(var n in t)e[n]="function"!=typeof t[n]?S:x(t[n],e)}(e,t.methods),t.data?function(e){var t=e.$options.data;s(t=e._data="function"==typeof t?function(e,t){le();try{return e.call(t,t)}catch(e){return Re(e,t,"data()"),{}}finally{fe()}}(t,e):t||{})||(t={});var n=Object.keys(t),r=e.$options.props,i=(e.$options.methods,n.length);for(;i--;){var o=n[i];r&&y(r,o)||(a=void 0,36!==(a=(o+"").charCodeAt(0))&&95!==a&&dn(e,"_data",o))}var a;Ce(t,!0)}(e):Ce(e._data={},!0),t.computed&&function(e,t){var n=e._computedWatchers=Object.create(null),r=te();for(var i in t){var o=t[i],a="function"==typeof o?o:o.get;r||(n[i]=new fn(e,a||S,S,hn)),i in e||mn(e,i,o)}}(e,t.computed),t.watch&&t.watch!==Y&&function(e,t){for(var n in t){var r=t[n];if(Array.isArray(r))for(var i=0;i-1:"string"==typeof e?e.split(",").indexOf(t)>-1:(n=e,"[object RegExp]"===a.call(n)&&e.test(t));var n}function An(e,t){var n=e.cache,r=e.keys,i=e._vnode;for(var o in n){var a=n[o];if(a){var s=xn(a.componentOptions);s&&!t(s)&&On(n,o,r,i)}}}function On(e,t,n,r){var i=e[t];!i||r&&i.tag===r.tag||i.componentInstance.$destroy(),e[t]=null,h(n,t)}!function(t){t.prototype._init=function(t){var n=this;n._uid=bn++,n._isVue=!0,t&&t._isComponent?function(e,t){var n=e.$options=Object.create(e.constructor.options),r=t._parentVnode;n.parent=t.parent,n._parentVnode=r;var i=r.componentOptions;n.propsData=i.propsData,n._parentListeners=i.listeners,n._renderChildren=i.children,n._componentTag=i.tag,t.render&&(n.render=t.render,n.staticRenderFns=t.staticRenderFns)}(n,t):n.$options=De($n(n.constructor),t||{},n),n._renderProxy=n,n._self=n,function(e){var t=e.$options,n=t.parent;if(n&&!t.abstract){for(;n.$options.abstract&&n.$parent;)n=n.$parent;n.$children.push(e)}e.$parent=n,e.$root=n?n.$root:e,e.$children=[],e.$refs={},e._watcher=null,e._inactive=null,e._directInactive=!1,e._isMounted=!1,e._isDestroyed=!1,e._isBeingDestroyed=!1}(n),function(e){e._events=Object.create(null),e._hasHookEvent=!1;var t=e.$options._parentListeners;t&&qt(e,t)}(n),function(t){t._vnode=null,t._staticTrees=null;var n=t.$options,r=t.$vnode=n._parentVnode,i=r&&r.context;t.$slots=ut(n._renderChildren,i),t.$scopedSlots=e,t._c=function(e,n,r,i){return Pt(t,e,n,r,i,!1)},t.$createElement=function(e,n,r,i){return Pt(t,e,n,r,i,!0)};var o=r&&r.data;xe(t,"$attrs",o&&o.attrs||e,null,!0),xe(t,"$listeners",n._parentListeners||e,null,!0)}(n),Yt(n,"beforeCreate"),function(e){var t=ct(e.$options.inject,e);t&&($e(!1),Object.keys(t).forEach(function(n){xe(e,n,t[n])}),$e(!0))}(n),vn(n),function(e){var t=e.$options.provide;t&&(e._provided="function"==typeof t?t.call(e):t)}(n),Yt(n,"created"),n.$options.el&&n.$mount(n.$options.el)}}(wn),function(e){var t={get:function(){return this._data}},n={get:function(){return this._props}};Object.defineProperty(e.prototype,"$data",t),Object.defineProperty(e.prototype,"$props",n),e.prototype.$set=ke,e.prototype.$delete=Ae,e.prototype.$watch=function(e,t,n){if(s(t))return _n(this,e,t,n);(n=n||{}).user=!0;var r=new fn(this,e,t,n);if(n.immediate)try{t.call(this,r.value)}catch(e){Re(e,this,'callback for immediate watcher "'+r.expression+'"')}return function(){r.teardown()}}}(wn),function(e){var t=/^hook:/;e.prototype.$on=function(e,n){var r=this;if(Array.isArray(e))for(var i=0,o=e.length;i1?k(t):t;for(var n=k(arguments,1),r='event handler for "'+e+'"',i=0,o=t.length;iparseInt(this.max)&&On(a,s[0],s,this._vnode)),t.data.keepAlive=!0}return t||e&&e[0]}}};!function(e){var t={get:function(){return F}};Object.defineProperty(e,"config",t),e.util={warn:ae,extend:A,mergeOptions:De,defineReactive:xe},e.set=ke,e.delete=Ae,e.nextTick=Ye,e.observable=function(e){return Ce(e),e},e.options=Object.create(null),M.forEach(function(t){e.options[t+"s"]=Object.create(null)}),e.options._base=e,A(e.options.components,Tn),function(e){e.use=function(e){var t=this._installedPlugins||(this._installedPlugins=[]);if(t.indexOf(e)>-1)return this;var n=k(arguments,1);return n.unshift(this),"function"==typeof e.install?e.install.apply(e,n):"function"==typeof e&&e.apply(null,n),t.push(e),this}}(e),function(e){e.mixin=function(e){return this.options=De(this.options,e),this}}(e),Cn(e),function(e){M.forEach(function(t){e[t]=function(e,n){return n?("component"===t&&s(n)&&(n.name=n.name||e,n=this.options._base.extend(n)),"directive"===t&&"function"==typeof n&&(n={bind:n,update:n}),this.options[t+"s"][e]=n,n):this.options[t+"s"][e]}})}(e)}(wn),Object.defineProperty(wn.prototype,"$isServer",{get:te}),Object.defineProperty(wn.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(wn,"FunctionalRenderContext",{value:Tt}),wn.version="2.6.12";var En=p("style,class"),Nn=p("input,textarea,option,select,progress"),jn=function(e,t,n){return"value"===n&&Nn(e)&&"button"!==t||"selected"===n&&"option"===e||"checked"===n&&"input"===e||"muted"===n&&"video"===e},Dn=p("contenteditable,draggable,spellcheck"),Ln=p("events,caret,typing,plaintext-only"),Mn=function(e,t){return Hn(t)||"false"===t?"false":"contenteditable"===e&&Ln(t)?t:"true"},In=p("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,translate,truespeed,typemustmatch,visible"),Fn="http://www.w3.org/1999/xlink",Pn=function(e){return":"===e.charAt(5)&&"xlink"===e.slice(0,5)},Rn=function(e){return Pn(e)?e.slice(6,e.length):""},Hn=function(e){return null==e||!1===e};function Bn(e){for(var t=e.data,r=e,i=e;n(i.componentInstance);)(i=i.componentInstance._vnode)&&i.data&&(t=Un(i.data,t));for(;n(r=r.parent);)r&&r.data&&(t=Un(t,r.data));return function(e,t){if(n(e)||n(t))return zn(e,Vn(t));return""}(t.staticClass,t.class)}function Un(e,t){return{staticClass:zn(e.staticClass,t.staticClass),class:n(e.class)?[e.class,t.class]:t.class}}function zn(e,t){return e?t?e+" "+t:e:t||""}function Vn(e){return Array.isArray(e)?function(e){for(var t,r="",i=0,o=e.length;i-1?hr(e,t,n):In(t)?Hn(n)?e.removeAttribute(t):(n="allowfullscreen"===t&&"EMBED"===e.tagName?"true":t,e.setAttribute(t,n)):Dn(t)?e.setAttribute(t,Mn(t,n)):Pn(t)?Hn(n)?e.removeAttributeNS(Fn,Rn(t)):e.setAttributeNS(Fn,t,n):hr(e,t,n)}function hr(e,t,n){if(Hn(n))e.removeAttribute(t);else{if(q&&!W&&"TEXTAREA"===e.tagName&&"placeholder"===t&&""!==n&&!e.__ieph){var r=function(t){t.stopImmediatePropagation(),e.removeEventListener("input",r)};e.addEventListener("input",r),e.__ieph=!0}e.setAttribute(t,n)}}var mr={create:dr,update:dr};function yr(e,r){var i=r.elm,o=r.data,a=e.data;if(!(t(o.staticClass)&&t(o.class)&&(t(a)||t(a.staticClass)&&t(a.class)))){var s=Bn(r),c=i._transitionClasses;n(c)&&(s=zn(s,Vn(c))),s!==i._prevClass&&(i.setAttribute("class",s),i._prevClass=s)}}var gr,_r,br,$r,wr,Cr,xr={create:yr,update:yr},kr=/[\w).+\-_$\]]/;function Ar(e){var t,n,r,i,o,a=!1,s=!1,c=!1,u=!1,l=0,f=0,p=0,d=0;for(r=0;r=0&&" "===(h=e.charAt(v));v--);h&&kr.test(h)||(u=!0)}}else void 0===i?(d=r+1,i=e.slice(0,r).trim()):m();function m(){(o||(o=[])).push(e.slice(d,r).trim()),d=r+1}if(void 0===i?i=e.slice(0,r).trim():0!==d&&m(),o)for(r=0;r-1?{exp:e.slice(0,$r),key:'"'+e.slice($r+1)+'"'}:{exp:e,key:null};_r=e,$r=wr=Cr=0;for(;!zr();)Vr(br=Ur())?Jr(br):91===br&&Kr(br);return{exp:e.slice(0,wr),key:e.slice(wr+1,Cr)}}(e);return null===n.key?e+"="+t:"$set("+n.exp+", "+n.key+", "+t+")"}function Ur(){return _r.charCodeAt(++$r)}function zr(){return $r>=gr}function Vr(e){return 34===e||39===e}function Kr(e){var t=1;for(wr=$r;!zr();)if(Vr(e=Ur()))Jr(e);else if(91===e&&t++,93===e&&t--,0===t){Cr=$r;break}}function Jr(e){for(var t=e;!zr()&&(e=Ur())!==t;);}var qr,Wr="__r",Zr="__c";function Gr(e,t,n){var r=qr;return function i(){null!==t.apply(null,arguments)&&Qr(e,i,n,r)}}var Xr=Ve&&!(X&&Number(X[1])<=53);function Yr(e,t,n,r){if(Xr){var i=an,o=t;t=o._wrapper=function(e){if(e.target===e.currentTarget||e.timeStamp>=i||e.timeStamp<=0||e.target.ownerDocument!==document)return o.apply(this,arguments)}}qr.addEventListener(e,t,Q?{capture:n,passive:r}:n)}function Qr(e,t,n,r){(r||qr).removeEventListener(e,t._wrapper||t,n)}function ei(e,r){if(!t(e.data.on)||!t(r.data.on)){var i=r.data.on||{},o=e.data.on||{};qr=r.elm,function(e){if(n(e[Wr])){var t=q?"change":"input";e[t]=[].concat(e[Wr],e[t]||[]),delete e[Wr]}n(e[Zr])&&(e.change=[].concat(e[Zr],e.change||[]),delete e[Zr])}(i),rt(i,o,Yr,Qr,Gr,r.context),qr=void 0}}var ti,ni={create:ei,update:ei};function ri(e,r){if(!t(e.data.domProps)||!t(r.data.domProps)){var i,o,a=r.elm,s=e.data.domProps||{},c=r.data.domProps||{};for(i in n(c.__ob__)&&(c=r.data.domProps=A({},c)),s)i in c||(a[i]="");for(i in c){if(o=c[i],"textContent"===i||"innerHTML"===i){if(r.children&&(r.children.length=0),o===s[i])continue;1===a.childNodes.length&&a.removeChild(a.childNodes[0])}if("value"===i&&"PROGRESS"!==a.tagName){a._value=o;var u=t(o)?"":String(o);ii(a,u)&&(a.value=u)}else if("innerHTML"===i&&qn(a.tagName)&&t(a.innerHTML)){(ti=ti||document.createElement("div")).innerHTML=""+o+"";for(var l=ti.firstChild;a.firstChild;)a.removeChild(a.firstChild);for(;l.firstChild;)a.appendChild(l.firstChild)}else if(o!==s[i])try{a[i]=o}catch(e){}}}}function ii(e,t){return!e.composing&&("OPTION"===e.tagName||function(e,t){var n=!0;try{n=document.activeElement!==e}catch(e){}return n&&e.value!==t}(e,t)||function(e,t){var r=e.value,i=e._vModifiers;if(n(i)){if(i.number)return f(r)!==f(t);if(i.trim)return r.trim()!==t.trim()}return r!==t}(e,t))}var oi={create:ri,update:ri},ai=g(function(e){var t={},n=/:(.+)/;return e.split(/;(?![^(]*\))/g).forEach(function(e){if(e){var r=e.split(n);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t});function si(e){var t=ci(e.style);return e.staticStyle?A(e.staticStyle,t):t}function ci(e){return Array.isArray(e)?O(e):"string"==typeof e?ai(e):e}var ui,li=/^--/,fi=/\s*!important$/,pi=function(e,t,n){if(li.test(t))e.style.setProperty(t,n);else if(fi.test(n))e.style.setProperty(C(t),n.replace(fi,""),"important");else{var r=vi(t);if(Array.isArray(n))for(var i=0,o=n.length;i-1?t.split(yi).forEach(function(t){return e.classList.add(t)}):e.classList.add(t);else{var n=" "+(e.getAttribute("class")||"")+" ";n.indexOf(" "+t+" ")<0&&e.setAttribute("class",(n+t).trim())}}function _i(e,t){if(t&&(t=t.trim()))if(e.classList)t.indexOf(" ")>-1?t.split(yi).forEach(function(t){return e.classList.remove(t)}):e.classList.remove(t),e.classList.length||e.removeAttribute("class");else{for(var n=" "+(e.getAttribute("class")||"")+" ",r=" "+t+" ";n.indexOf(r)>=0;)n=n.replace(r," ");(n=n.trim())?e.setAttribute("class",n):e.removeAttribute("class")}}function bi(e){if(e){if("object"==typeof e){var t={};return!1!==e.css&&A(t,$i(e.name||"v")),A(t,e),t}return"string"==typeof e?$i(e):void 0}}var $i=g(function(e){return{enterClass:e+"-enter",enterToClass:e+"-enter-to",enterActiveClass:e+"-enter-active",leaveClass:e+"-leave",leaveToClass:e+"-leave-to",leaveActiveClass:e+"-leave-active"}}),wi=z&&!W,Ci="transition",xi="animation",ki="transition",Ai="transitionend",Oi="animation",Si="animationend";wi&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(ki="WebkitTransition",Ai="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(Oi="WebkitAnimation",Si="webkitAnimationEnd"));var Ti=z?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(e){return e()};function Ei(e){Ti(function(){Ti(e)})}function Ni(e,t){var n=e._transitionClasses||(e._transitionClasses=[]);n.indexOf(t)<0&&(n.push(t),gi(e,t))}function ji(e,t){e._transitionClasses&&h(e._transitionClasses,t),_i(e,t)}function Di(e,t,n){var r=Mi(e,t),i=r.type,o=r.timeout,a=r.propCount;if(!i)return n();var s=i===Ci?Ai:Si,c=0,u=function(){e.removeEventListener(s,l),n()},l=function(t){t.target===e&&++c>=a&&u()};setTimeout(function(){c0&&(n=Ci,l=a,f=o.length):t===xi?u>0&&(n=xi,l=u,f=c.length):f=(n=(l=Math.max(a,u))>0?a>u?Ci:xi:null)?n===Ci?o.length:c.length:0,{type:n,timeout:l,propCount:f,hasTransform:n===Ci&&Li.test(r[ki+"Property"])}}function Ii(e,t){for(;e.length1}function Ui(e,t){!0!==t.data.show&&Pi(t)}var zi=function(e){var o,a,s={},c=e.modules,u=e.nodeOps;for(o=0;ov?_(e,t(i[y+1])?null:i[y+1].elm,i,d,y,o):d>y&&$(r,p,v)}(p,h,y,o,l):n(y)?(n(e.text)&&u.setTextContent(p,""),_(p,null,y,0,y.length-1,o)):n(h)?$(h,0,h.length-1):n(e.text)&&u.setTextContent(p,""):e.text!==i.text&&u.setTextContent(p,i.text),n(v)&&n(d=v.hook)&&n(d=d.postpatch)&&d(e,i)}}}function k(e,t,i){if(r(i)&&n(e.parent))e.parent.data.pendingInsert=t;else for(var o=0;o-1,a.selected!==o&&(a.selected=o);else if(N(Wi(a),r))return void(e.selectedIndex!==s&&(e.selectedIndex=s));i||(e.selectedIndex=-1)}}function qi(e,t){return t.every(function(t){return!N(t,e)})}function Wi(e){return"_value"in e?e._value:e.value}function Zi(e){e.target.composing=!0}function Gi(e){e.target.composing&&(e.target.composing=!1,Xi(e.target,"input"))}function Xi(e,t){var n=document.createEvent("HTMLEvents");n.initEvent(t,!0,!0),e.dispatchEvent(n)}function Yi(e){return!e.componentInstance||e.data&&e.data.transition?e:Yi(e.componentInstance._vnode)}var Qi={model:Vi,show:{bind:function(e,t,n){var r=t.value,i=(n=Yi(n)).data&&n.data.transition,o=e.__vOriginalDisplay="none"===e.style.display?"":e.style.display;r&&i?(n.data.show=!0,Pi(n,function(){e.style.display=o})):e.style.display=r?o:"none"},update:function(e,t,n){var r=t.value;!r!=!t.oldValue&&((n=Yi(n)).data&&n.data.transition?(n.data.show=!0,r?Pi(n,function(){e.style.display=e.__vOriginalDisplay}):Ri(n,function(){e.style.display="none"})):e.style.display=r?e.__vOriginalDisplay:"none")},unbind:function(e,t,n,r,i){i||(e.style.display=e.__vOriginalDisplay)}}},eo={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function to(e){var t=e&&e.componentOptions;return t&&t.Ctor.options.abstract?to(zt(t.children)):e}function no(e){var t={},n=e.$options;for(var r in n.propsData)t[r]=e[r];var i=n._parentListeners;for(var o in i)t[b(o)]=i[o];return t}function ro(e,t){if(/\d-keep-alive$/.test(t.tag))return e("keep-alive",{props:t.componentOptions.propsData})}var io=function(e){return e.tag||Ut(e)},oo=function(e){return"show"===e.name},ao={name:"transition",props:eo,abstract:!0,render:function(e){var t=this,n=this.$slots.default;if(n&&(n=n.filter(io)).length){var r=this.mode,o=n[0];if(function(e){for(;e=e.parent;)if(e.data.transition)return!0}(this.$vnode))return o;var a=to(o);if(!a)return o;if(this._leaving)return ro(e,o);var s="__transition-"+this._uid+"-";a.key=null==a.key?a.isComment?s+"comment":s+a.tag:i(a.key)?0===String(a.key).indexOf(s)?a.key:s+a.key:a.key;var c=(a.data||(a.data={})).transition=no(this),u=this._vnode,l=to(u);if(a.data.directives&&a.data.directives.some(oo)&&(a.data.show=!0),l&&l.data&&!function(e,t){return t.key===e.key&&t.tag===e.tag}(a,l)&&!Ut(l)&&(!l.componentInstance||!l.componentInstance._vnode.isComment)){var f=l.data.transition=A({},c);if("out-in"===r)return this._leaving=!0,it(f,"afterLeave",function(){t._leaving=!1,t.$forceUpdate()}),ro(e,o);if("in-out"===r){if(Ut(a))return u;var p,d=function(){p()};it(c,"afterEnter",d),it(c,"enterCancelled",d),it(f,"delayLeave",function(e){p=e})}}return o}}},so=A({tag:String,moveClass:String},eo);function co(e){e.elm._moveCb&&e.elm._moveCb(),e.elm._enterCb&&e.elm._enterCb()}function uo(e){e.data.newPos=e.elm.getBoundingClientRect()}function lo(e){var t=e.data.pos,n=e.data.newPos,r=t.left-n.left,i=t.top-n.top;if(r||i){e.data.moved=!0;var o=e.elm.style;o.transform=o.WebkitTransform="translate("+r+"px,"+i+"px)",o.transitionDuration="0s"}}delete so.mode;var fo={Transition:ao,TransitionGroup:{props:so,beforeMount:function(){var e=this,t=this._update;this._update=function(n,r){var i=Zt(e);e.__patch__(e._vnode,e.kept,!1,!0),e._vnode=e.kept,i(),t.call(e,n,r)}},render:function(e){for(var t=this.tag||this.$vnode.data.tag||"span",n=Object.create(null),r=this.prevChildren=this.children,i=this.$slots.default||[],o=this.children=[],a=no(this),s=0;s-1?Gn[e]=t.constructor===window.HTMLUnknownElement||t.constructor===window.HTMLElement:Gn[e]=/HTMLUnknownElement/.test(t.toString())},A(wn.options.directives,Qi),A(wn.options.components,fo),wn.prototype.__patch__=z?zi:S,wn.prototype.$mount=function(e,t){return function(e,t,n){var r;return e.$el=t,e.$options.render||(e.$options.render=ve),Yt(e,"beforeMount"),r=function(){e._update(e._render(),n)},new fn(e,r,S,{before:function(){e._isMounted&&!e._isDestroyed&&Yt(e,"beforeUpdate")}},!0),n=!1,null==e.$vnode&&(e._isMounted=!0,Yt(e,"mounted")),e}(this,e=e&&z?Yn(e):void 0,t)},z&&setTimeout(function(){F.devtools&&ne&&ne.emit("init",wn)},0);var po=/\{\{((?:.|\r?\n)+?)\}\}/g,vo=/[-.*+?^${}()|[\]\/\\]/g,ho=g(function(e){var t=e[0].replace(vo,"\\$&"),n=e[1].replace(vo,"\\$&");return new RegExp(t+"((?:.|\\n)+?)"+n,"g")});var mo={staticKeys:["staticClass"],transformNode:function(e,t){t.warn;var n=Fr(e,"class");n&&(e.staticClass=JSON.stringify(n));var r=Ir(e,"class",!1);r&&(e.classBinding=r)},genData:function(e){var t="";return e.staticClass&&(t+="staticClass:"+e.staticClass+","),e.classBinding&&(t+="class:"+e.classBinding+","),t}};var yo,go={staticKeys:["staticStyle"],transformNode:function(e,t){t.warn;var n=Fr(e,"style");n&&(e.staticStyle=JSON.stringify(ai(n)));var r=Ir(e,"style",!1);r&&(e.styleBinding=r)},genData:function(e){var t="";return e.staticStyle&&(t+="staticStyle:"+e.staticStyle+","),e.styleBinding&&(t+="style:("+e.styleBinding+"),"),t}},_o=function(e){return(yo=yo||document.createElement("div")).innerHTML=e,yo.textContent},bo=p("area,base,br,col,embed,frame,hr,img,input,isindex,keygen,link,meta,param,source,track,wbr"),$o=p("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr,source"),wo=p("address,article,aside,base,blockquote,body,caption,col,colgroup,dd,details,dialog,div,dl,dt,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,head,header,hgroup,hr,html,legend,li,menuitem,meta,optgroup,option,param,rp,rt,source,style,summary,tbody,td,tfoot,th,thead,title,tr,track"),Co=/^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,xo=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,ko="[a-zA-Z_][\\-\\.0-9_a-zA-Z"+P.source+"]*",Ao="((?:"+ko+"\\:)?"+ko+")",Oo=new RegExp("^<"+Ao),So=/^\s*(\/?)>/,To=new RegExp("^<\\/"+Ao+"[^>]*>"),Eo=/^]+>/i,No=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},Io=/&(?:lt|gt|quot|amp|#39);/g,Fo=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,Po=p("pre,textarea",!0),Ro=function(e,t){return e&&Po(e)&&"\n"===t[0]};function Ho(e,t){var n=t?Fo:Io;return e.replace(n,function(e){return Mo[e]})}var Bo,Uo,zo,Vo,Ko,Jo,qo,Wo,Zo=/^@|^v-on:/,Go=/^v-|^@|^:|^#/,Xo=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,Yo=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,Qo=/^\(|\)$/g,ea=/^\[.*\]$/,ta=/:(.*)$/,na=/^:|^\.|^v-bind:/,ra=/\.[^.\]]+(?=[^\]]*$)/g,ia=/^v-slot(:|$)|^#/,oa=/[\r\n]/,aa=/\s+/g,sa=g(_o),ca="_empty_";function ua(e,t,n){return{type:1,tag:e,attrsList:t,attrsMap:ma(t),rawAttrsMap:{},parent:n,children:[]}}function la(e,t){Bo=t.warn||Sr,Jo=t.isPreTag||T,qo=t.mustUseProp||T,Wo=t.getTagNamespace||T;t.isReservedTag;zo=Tr(t.modules,"transformNode"),Vo=Tr(t.modules,"preTransformNode"),Ko=Tr(t.modules,"postTransformNode"),Uo=t.delimiters;var n,r,i=[],o=!1!==t.preserveWhitespace,a=t.whitespace,s=!1,c=!1;function u(e){if(l(e),s||e.processed||(e=fa(e,t)),i.length||e===n||n.if&&(e.elseif||e.else)&&da(n,{exp:e.elseif,block:e}),r&&!e.forbidden)if(e.elseif||e.else)a=e,(u=function(e){var t=e.length;for(;t--;){if(1===e[t].type)return e[t];e.pop()}}(r.children))&&u.if&&da(u,{exp:a.elseif,block:a});else{if(e.slotScope){var o=e.slotTarget||'"default"';(r.scopedSlots||(r.scopedSlots={}))[o]=e}r.children.push(e),e.parent=r}var a,u;e.children=e.children.filter(function(e){return!e.slotScope}),l(e),e.pre&&(s=!1),Jo(e.tag)&&(c=!1);for(var f=0;f]*>)","i")),p=e.replace(f,function(e,n,r){return u=r.length,Do(l)||"noscript"===l||(n=n.replace(//g,"$1").replace(//g,"$1")),Ro(l,n)&&(n=n.slice(1)),t.chars&&t.chars(n),""});c+=e.length-p.length,e=p,A(l,c-u,c)}else{var d=e.indexOf("<");if(0===d){if(No.test(e)){var v=e.indexOf("--\x3e");if(v>=0){t.shouldKeepComment&&t.comment(e.substring(4,v),c,c+v+3),C(v+3);continue}}if(jo.test(e)){var h=e.indexOf("]>");if(h>=0){C(h+2);continue}}var m=e.match(Eo);if(m){C(m[0].length);continue}var y=e.match(To);if(y){var g=c;C(y[0].length),A(y[1],g,c);continue}var _=x();if(_){k(_),Ro(_.tagName,e)&&C(1);continue}}var b=void 0,$=void 0,w=void 0;if(d>=0){for($=e.slice(d);!(To.test($)||Oo.test($)||No.test($)||jo.test($)||(w=$.indexOf("<",1))<0);)d+=w,$=e.slice(d);b=e.substring(0,d)}d<0&&(b=e),b&&C(b.length),t.chars&&b&&t.chars(b,c-b.length,c)}if(e===n){t.chars&&t.chars(e);break}}function C(t){c+=t,e=e.substring(t)}function x(){var t=e.match(Oo);if(t){var n,r,i={tagName:t[1],attrs:[],start:c};for(C(t[0].length);!(n=e.match(So))&&(r=e.match(xo)||e.match(Co));)r.start=c,C(r[0].length),r.end=c,i.attrs.push(r);if(n)return i.unarySlash=n[1],C(n[0].length),i.end=c,i}}function k(e){var n=e.tagName,c=e.unarySlash;o&&("p"===r&&wo(n)&&A(r),s(n)&&r===n&&A(n));for(var u=a(n)||!!c,l=e.attrs.length,f=new Array(l),p=0;p=0&&i[a].lowerCasedTag!==s;a--);else a=0;if(a>=0){for(var u=i.length-1;u>=a;u--)t.end&&t.end(i[u].tag,n,o);i.length=a,r=a&&i[a-1].tag}else"br"===s?t.start&&t.start(e,[],!0,n,o):"p"===s&&(t.start&&t.start(e,[],!1,n,o),t.end&&t.end(e,n,o))}A()}(e,{warn:Bo,expectHTML:t.expectHTML,isUnaryTag:t.isUnaryTag,canBeLeftOpenTag:t.canBeLeftOpenTag,shouldDecodeNewlines:t.shouldDecodeNewlines,shouldDecodeNewlinesForHref:t.shouldDecodeNewlinesForHref,shouldKeepComment:t.comments,outputSourceRange:t.outputSourceRange,start:function(e,o,a,l,f){var p=r&&r.ns||Wo(e);q&&"svg"===p&&(o=function(e){for(var t=[],n=0;nc&&(s.push(o=e.slice(c,i)),a.push(JSON.stringify(o)));var u=Ar(r[1].trim());a.push("_s("+u+")"),s.push({"@binding":u}),c=i+r[0].length}return c-1"+("true"===o?":("+t+")":":_q("+t+","+o+")")),Mr(e,"change","var $$a="+t+",$$el=$event.target,$$c=$$el.checked?("+o+"):("+a+");if(Array.isArray($$a)){var $$v="+(r?"_n("+i+")":i)+",$$i=_i($$a,$$v);if($$el.checked){$$i<0&&("+Br(t,"$$a.concat([$$v])")+")}else{$$i>-1&&("+Br(t,"$$a.slice(0,$$i).concat($$a.slice($$i+1))")+")}}else{"+Br(t,"$$c")+"}",null,!0)}(e,r,i);else if("input"===o&&"radio"===a)!function(e,t,n){var r=n&&n.number,i=Ir(e,"value")||"null";Er(e,"checked","_q("+t+","+(i=r?"_n("+i+")":i)+")"),Mr(e,"change",Br(t,i),null,!0)}(e,r,i);else if("input"===o||"textarea"===o)!function(e,t,n){var r=e.attrsMap.type,i=n||{},o=i.lazy,a=i.number,s=i.trim,c=!o&&"range"!==r,u=o?"change":"range"===r?Wr:"input",l="$event.target.value";s&&(l="$event.target.value.trim()"),a&&(l="_n("+l+")");var f=Br(t,l);c&&(f="if($event.target.composing)return;"+f),Er(e,"value","("+t+")"),Mr(e,u,f,null,!0),(s||a)&&Mr(e,"blur","$forceUpdate()")}(e,r,i);else if(!F.isReservedTag(o))return Hr(e,r,i),!1;return!0},text:function(e,t){t.value&&Er(e,"textContent","_s("+t.value+")",t)},html:function(e,t){t.value&&Er(e,"innerHTML","_s("+t.value+")",t)}},isPreTag:function(e){return"pre"===e},isUnaryTag:bo,mustUseProp:jn,canBeLeftOpenTag:$o,isReservedTag:Wn,getTagNamespace:Zn,staticKeys:function(e){return e.reduce(function(e,t){return e.concat(t.staticKeys||[])},[]).join(",")}(ba)},xa=g(function(e){return p("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(e?","+e:""))});function ka(e,t){e&&($a=xa(t.staticKeys||""),wa=t.isReservedTag||T,function e(t){t.static=function(e){if(2===e.type)return!1;if(3===e.type)return!0;return!(!e.pre&&(e.hasBindings||e.if||e.for||d(e.tag)||!wa(e.tag)||function(e){for(;e.parent;){if("template"!==(e=e.parent).tag)return!1;if(e.for)return!0}return!1}(e)||!Object.keys(e).every($a)))}(t);if(1===t.type){if(!wa(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var n=0,r=t.children.length;n|^function(?:\s+[\w$]+)?\s*\(/,Oa=/\([^)]*?\);*$/,Sa=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,Ta={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},Ea={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},Na=function(e){return"if("+e+")return null;"},ja={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:Na("$event.target !== $event.currentTarget"),ctrl:Na("!$event.ctrlKey"),shift:Na("!$event.shiftKey"),alt:Na("!$event.altKey"),meta:Na("!$event.metaKey"),left:Na("'button' in $event && $event.button !== 0"),middle:Na("'button' in $event && $event.button !== 1"),right:Na("'button' in $event && $event.button !== 2")};function Da(e,t){var n=t?"nativeOn:":"on:",r="",i="";for(var o in e){var a=La(e[o]);e[o]&&e[o].dynamic?i+=o+","+a+",":r+='"'+o+'":'+a+","}return r="{"+r.slice(0,-1)+"}",i?n+"_d("+r+",["+i.slice(0,-1)+"])":n+r}function La(e){if(!e)return"function(){}";if(Array.isArray(e))return"["+e.map(function(e){return La(e)}).join(",")+"]";var t=Sa.test(e.value),n=Aa.test(e.value),r=Sa.test(e.value.replace(Oa,""));if(e.modifiers){var i="",o="",a=[];for(var s in e.modifiers)if(ja[s])o+=ja[s],Ta[s]&&a.push(s);else if("exact"===s){var c=e.modifiers;o+=Na(["ctrl","shift","alt","meta"].filter(function(e){return!c[e]}).map(function(e){return"$event."+e+"Key"}).join("||"))}else a.push(s);return a.length&&(i+=function(e){return"if(!$event.type.indexOf('key')&&"+e.map(Ma).join("&&")+")return null;"}(a)),o&&(i+=o),"function($event){"+i+(t?"return "+e.value+"($event)":n?"return ("+e.value+")($event)":r?"return "+e.value:e.value)+"}"}return t||n?e.value:"function($event){"+(r?"return "+e.value:e.value)+"}"}function Ma(e){var t=parseInt(e,10);if(t)return"$event.keyCode!=="+t;var n=Ta[e],r=Ea[e];return"_k($event.keyCode,"+JSON.stringify(e)+","+JSON.stringify(n)+",$event.key,"+JSON.stringify(r)+")"}var Ia={on:function(e,t){e.wrapListeners=function(e){return"_g("+e+","+t.value+")"}},bind:function(e,t){e.wrapData=function(n){return"_b("+n+",'"+e.tag+"',"+t.value+","+(t.modifiers&&t.modifiers.prop?"true":"false")+(t.modifiers&&t.modifiers.sync?",true":"")+")"}},cloak:S},Fa=function(e){this.options=e,this.warn=e.warn||Sr,this.transforms=Tr(e.modules,"transformCode"),this.dataGenFns=Tr(e.modules,"genData"),this.directives=A(A({},Ia),e.directives);var t=e.isReservedTag||T;this.maybeComponent=function(e){return!!e.component||!t(e.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function Pa(e,t){var n=new Fa(t);return{render:"with(this){return "+(e?Ra(e,n):'_c("div")')+"}",staticRenderFns:n.staticRenderFns}}function Ra(e,t){if(e.parent&&(e.pre=e.pre||e.parent.pre),e.staticRoot&&!e.staticProcessed)return Ha(e,t);if(e.once&&!e.onceProcessed)return Ba(e,t);if(e.for&&!e.forProcessed)return za(e,t);if(e.if&&!e.ifProcessed)return Ua(e,t);if("template"!==e.tag||e.slotTarget||t.pre){if("slot"===e.tag)return function(e,t){var n=e.slotName||'"default"',r=qa(e,t),i="_t("+n+(r?","+r:""),o=e.attrs||e.dynamicAttrs?Ga((e.attrs||[]).concat(e.dynamicAttrs||[]).map(function(e){return{name:b(e.name),value:e.value,dynamic:e.dynamic}})):null,a=e.attrsMap["v-bind"];!o&&!a||r||(i+=",null");o&&(i+=","+o);a&&(i+=(o?"":",null")+","+a);return i+")"}(e,t);var n;if(e.component)n=function(e,t,n){var r=t.inlineTemplate?null:qa(t,n,!0);return"_c("+e+","+Va(t,n)+(r?","+r:"")+")"}(e.component,e,t);else{var r;(!e.plain||e.pre&&t.maybeComponent(e))&&(r=Va(e,t));var i=e.inlineTemplate?null:qa(e,t,!0);n="_c('"+e.tag+"'"+(r?","+r:"")+(i?","+i:"")+")"}for(var o=0;o>>0}(a):"")+")"}(e,e.scopedSlots,t)+","),e.model&&(n+="model:{value:"+e.model.value+",callback:"+e.model.callback+",expression:"+e.model.expression+"},"),e.inlineTemplate){var o=function(e,t){var n=e.children[0];if(n&&1===n.type){var r=Pa(n,t.options);return"inlineTemplate:{render:function(){"+r.render+"},staticRenderFns:["+r.staticRenderFns.map(function(e){return"function(){"+e+"}"}).join(",")+"]}"}}(e,t);o&&(n+=o+",")}return n=n.replace(/,$/,"")+"}",e.dynamicAttrs&&(n="_b("+n+',"'+e.tag+'",'+Ga(e.dynamicAttrs)+")"),e.wrapData&&(n=e.wrapData(n)),e.wrapListeners&&(n=e.wrapListeners(n)),n}function Ka(e){return 1===e.type&&("slot"===e.tag||e.children.some(Ka))}function Ja(e,t){var n=e.attrsMap["slot-scope"];if(e.if&&!e.ifProcessed&&!n)return Ua(e,t,Ja,"null");if(e.for&&!e.forProcessed)return za(e,t,Ja);var r=e.slotScope===ca?"":String(e.slotScope),i="function("+r+"){return "+("template"===e.tag?e.if&&n?"("+e.if+")?"+(qa(e,t)||"undefined")+":undefined":qa(e,t)||"undefined":Ra(e,t))+"}",o=r?"":",proxy:true";return"{key:"+(e.slotTarget||'"default"')+",fn:"+i+o+"}"}function qa(e,t,n,r,i){var o=e.children;if(o.length){var a=o[0];if(1===o.length&&a.for&&"template"!==a.tag&&"slot"!==a.tag){var s=n?t.maybeComponent(a)?",1":",0":"";return""+(r||Ra)(a,t)+s}var c=n?function(e,t){for(var n=0,r=0;r':'
',ts.innerHTML.indexOf(" ")>0}var os=!!z&&is(!1),as=!!z&&is(!0),ss=g(function(e){var t=Yn(e);return t&&t.innerHTML}),cs=wn.prototype.$mount;return wn.prototype.$mount=function(e,t){if((e=e&&Yn(e))===document.body||e===document.documentElement)return this;var n=this.$options;if(!n.render){var r=n.template;if(r)if("string"==typeof r)"#"===r.charAt(0)&&(r=ss(r));else{if(!r.nodeType)return this;r=r.innerHTML}else e&&(r=function(e){if(e.outerHTML)return e.outerHTML;var t=document.createElement("div");return t.appendChild(e.cloneNode(!0)),t.innerHTML}(e));if(r){var i=rs(r,{outputSourceRange:!1,shouldDecodeNewlines:os,shouldDecodeNewlinesForHref:as,delimiters:n.delimiters,comments:n.comments},this),o=i.render,a=i.staticRenderFns;n.render=o,n.staticRenderFns=a}}return cs.call(this,e,t)},wn.compile=rs,wn}); --------------------------------------------------------------------------------