UNPKG

@hoobs/hue

Version:

HOOBS plugin for Philips Hue and deCONZ

1,375 lines (1,357 loc) 97.1 kB
// homebridge-hue/lib/HueSensor.js // Copyright © 2016-2020 Erik Baauw. All rights reserved. // // Homebridge plugin for Philips Hue and/or deCONZ. // // HueSensor provides support for Philips Hue sensors. 'use strict' const moment = require('moment') // Link this module to HuePlatform. module.exports = { setHomebridge: setHomebridge, HueSensor: HueSensor } function toInt (value, minValue, maxValue) { const n = parseInt(value) if (isNaN(n) || n < minValue) { return minValue } if (n > maxValue) { return maxValue } return n } const daylightEvents = { 100: { name: 'Solar Midnight', period: 'Night' }, 110: { name: 'Astronomical Dawn', period: 'Astronomical Twilight' }, 120: { name: 'Nautical Dawn', period: 'Nautical Twilight' }, 130: { name: 'Dawn', period: 'Twilight' }, 140: { name: 'Sunrise', period: 'Sunrise' }, 150: { name: 'End Sunrise', period: 'Golden Hour' }, 160: { name: 'End Golden Hour', period: 'Day' }, 170: { name: 'Solar Noon', period: 'Day' }, 180: { name: 'Start Golden Hour', period: 'Golden Hour' }, 190: { name: 'Start Sunset', period: 'Sunset' }, 200: { name: 'Sunset', period: 'Twilight' }, 210: { name: 'Dusk', period: 'Nautical Twilight' }, 220: { name: 'Nautical Dusk', period: 'Astronomical Twilight' }, 230: { name: 'Astronomical Dusk', period: 'Night' } } const daylightPeriods = { Night: { lightlevel: 0, daylight: false, dark: true }, 'Astronomical Twilight': { lightlevel: 100, daylight: false, dark: true }, 'Nautical Twilight': { lightlevel: 1000, daylight: false, dark: true }, Twilight: { lightlevel: 10000, daylight: false, dark: false }, Sunrise: { lightlevel: 15000, daylight: true, dark: false }, Sunset: { lightlevel: 20000, daylight: true, dark: false }, 'Golden Hour': { lightlevel: 40000, daylight: true, dark: false }, Day: { lightlevel: 65535, daylight: true, dark: false } } // ===== Homebridge ============================================================ // Link this module to homebridge. let Service let Characteristic let my let eve // let HistoryService let SINGLE let SINGLE_DOUBLE let SINGLE_LONG let SINGLE_DOUBLE_LONG // let DOUBLE let DOUBLE_LONG let LONG function setHomebridge (homebridge, _my, _eve) { Service = homebridge.hap.Service Characteristic = homebridge.hap.Characteristic my = _my eve = _eve SINGLE = { minValue: Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, maxValue: Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS } SINGLE_DOUBLE = { minValue: Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, maxValue: Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS } SINGLE_LONG = { minValue: Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, maxValue: Characteristic.ProgrammableSwitchEvent.LONG_PRESS, validValues: [ Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, Characteristic.ProgrammableSwitchEvent.LONG_PRESS ] } SINGLE_DOUBLE_LONG = { minValue: Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS, maxValue: Characteristic.ProgrammableSwitchEvent.LONG_PRESS } // DOUBLE = { // minValue: Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS, // maxValue: Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS // } DOUBLE_LONG = { minValue: Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS, maxValue: Characteristic.ProgrammableSwitchEvent.LONG_PRESS } LONG = { minValue: Characteristic.ProgrammableSwitchEvent.LONG_PRESS, maxValue: Characteristic.ProgrammableSwitchEvent.LONG_PRESS } } function hkLightLevel (v) { let l = v ? Math.pow(10, (v - 1) / 10000) : 0.0001 l = Math.round(l * 10000) / 10000 return l > 100000 ? 100000 : l < 0.0001 ? 0.0001 : l } const PRESS = 0 const HOLD = 1 const SHORT_RELEASE = 2 const LONG_RELEASE = 3 const DOUBLE_PRESS = 4 const TRIPLE_PRESS = 5 const QUADRUPLE_PRESS = 6 const SHAKE = 7 const DROP = 8 // const TILT = 9 // As homebridge-hue polls the Hue bridge, not all dimmer switch buttonevents // are received reliably. Consequently, we only issue one HomeKit change per // Press/Hold/Release event series. function hkZLLSwitchAction (value, oldValue, repeat = false) { const button = Math.floor(value / 1000) const oldButton = Math.floor(oldValue / 1000) const event = value % 1000 const oldEvent = oldValue % 1000 switch (event) { case PRESS: // Wait for Hold or Release after press. return null case SHORT_RELEASE: return Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS case HOLD: case LONG_RELEASE: if (repeat) { return Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS } if (button === oldButton && oldEvent === HOLD) { // Already issued action on previous Hold. return null } // falls through case TRIPLE_PRESS: case QUADRUPLE_PRESS: return Characteristic.ProgrammableSwitchEvent.LONG_PRESS case DOUBLE_PRESS: case SHAKE: case DROP: return Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS default: return null } } // ===== HueSensor ============================================================= function HueSensor (accessory, id, obj) { this.accessory = accessory this.id = id this.obj = obj this.bridge = this.accessory.bridge this.log = this.accessory.log this.serialNumber = this.accessory.serialNumber this.name = this.obj.name this.hk = {} this.resource = '/sensors/' + id this.serviceList = [] if (this.obj.type[0] === 'Z') { // Zigbee sensor. this.manufacturer = this.obj.manufacturername this.model = this.obj.modelid this.endpoint = this.obj.uniqueid.split('-')[1] this.cluster = this.obj.uniqueid.split('-')[2] this.subtype = this.endpoint + '-' + this.cluster this.version = this.obj.swversion } else { // Hue bridge internal sensor. this.manufacturer = this.bridge.manufacturername if (this.accessory.isMulti) { this.model = 'MultiCLIP' this.subtype = this.id } else if ( this.obj.manufacturername === 'homebridge-hue' && this.obj.modelid === this.obj.type && this.obj.uniqueid.split('-')[1] === this.id ) { // Combine multiple CLIP sensors into one accessory. this.model = 'MultiCLIP' this.subtype = this.id } else { this.model = this.obj.type } this.version = this.bridge.version } this.infoService = this.accessory.getInfoService(this) // See: http://www.developers.meethue.com/documentation/supported-sensors let durationKey = 'duration' switch (this.obj.type) { case 'ZGPSwitch': if ( this.obj.manufacturername === this.bridge.philips && this.obj.modelid === 'ZGPSWITCH' ) { // 1.1 - Hue tap this.createLabel(Characteristic.ServiceLabelNamespace.DOTS) this.createButton(1, '1', SINGLE) this.createButton(2, '2', SINGLE) this.createButton(3, '3', SINGLE) this.createButton(4, '4', SINGLE) this.createButton(5, '1 and 2', SINGLE) this.createButton(6, '3 and 4', SINGLE) this.type = { key: 'buttonevent', homekitValue: function (v) { return { 34: 1, 16: 2, 17: 3, 18: 4, 101: 5, 99: 6 }[v] }, homekitAction: function () { return 0 } } } else if ( this.obj.manufacturername === 'PhilipsFoH' && this.obj.modelid === 'FOHSWITCH' ) { // Friends of Hue switch if (this.bridge.model === 'deCONZ') { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Top Left', SINGLE_LONG) this.createButton(2, 'Bottom Left', SINGLE_LONG) this.createButton(3, 'Top Right', SINGLE_LONG) this.createButton(4, 'Bottom Right', SINGLE_LONG) this.createButton(5, 'Top Both', SINGLE_LONG) this.createButton(6, 'Bottom Both', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Top Left', SINGLE) this.createButton(2, 'Bottom Left', SINGLE) this.createButton(3, 'Top Right', SINGLE) this.createButton(4, 'Bottom Right', SINGLE) this.createButton(5, 'Top Both', SINGLE) this.createButton(6, 'Bottom Both', SINGLE) this.type = { key: 'buttonevent', homekitValue: function (v) { return { 20: 1, 21: 2, 23: 3, 22: 4, 101: 5, 99: 6 }[v] }, homekitAction: function () { return 0 } } } } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } break case 'ZLLSwitch': case 'ZHASwitch': if ( this.obj.manufacturername === this.bridge.philips && (this.obj.modelid === 'RWL021' || this.obj.modelid === 'RWL020') ) { // 1.2 - Hue wireless dimmer switch this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'On', SINGLE_LONG) if (this.bridge.platform.config.hueDimmerRepeat) { this.repeat = [2, 3] this.createButton(2, 'Dim Up', SINGLE) this.createButton(3, 'Dim Down', SINGLE) } else { this.createButton(2, 'Dim Up', SINGLE_LONG) this.createButton(3, 'Dim Down', SINGLE_LONG) } this.createButton(4, 'Off', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === this.bridge.philips && this.obj.modelid === 'ROM001' ) { // Hue smart button this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) if (this.bridge.platform.config.hueDimmerRepeat) { this.repeat = [1] this.createButton(1, 'Button', SINGLE) } else { this.createButton(1, 'Button', SINGLE_LONG) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'IKEA of Sweden' && this.obj.modelid === 'TRADFRI remote control' ) { // Ikea Trådfri remote this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'On/Off', SINGLE) this.createButton(2, 'Dim Up', SINGLE_LONG) this.createButton(3, 'Dim Down', SINGLE_LONG) this.createButton(4, 'Previous', SINGLE_LONG) this.createButton(5, 'Next', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'IKEA of Sweden' && this.obj.modelid === 'TRADFRI wireless dimmer' ) { // Ikea Trådfri dimmer this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'On', SINGLE) this.createButton(2, 'Dim Up', SINGLE) this.createButton(3, 'Dim Down', SINGLE) this.createButton(4, 'Off', SINGLE) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'IKEA of Sweden' && this.obj.modelid === 'TRADFRI on/off switch' ) { // Ikea Trådfri on/off switch this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'On', SINGLE_LONG) this.createButton(2, 'Off', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'IKEA of Sweden' && this.obj.modelid === 'TRADFRI open/close remote' ) { // Ikea Trådfri open/close remote this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Open', SINGLE_LONG) this.createButton(2, 'Close', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'IKEA of Sweden' && this.obj.modelid === 'SYMFONISK Sound Controller' ) { // Ikea Symfonisk sound controller this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Button', SINGLE_DOUBLE_LONG) this.createButton(2, 'Turn Right', LONG) this.createButton(3, 'Turn Left', LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'innr' && this.obj.modelid === 'RC 110' ) { // innr RC 110 remote, exposed as multiple ZHASwitch resources this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) if (this.endpoint === '01') { this.createButton(1, 'On/Off', SINGLE) this.createButton(2, 'Dim Up', SINGLE_LONG) this.createButton(3, 'Dim Down', SINGLE_LONG) this.createButton(4, '1', SINGLE) this.createButton(5, '2', SINGLE) this.createButton(6, '3', SINGLE) this.createButton(7, '4', SINGLE) this.createButton(8, '5', SINGLE) this.createButton(9, '6', SINGLE) } else { const light = parseInt(this.endpoint) - 2 const button = 7 + light * 3 this.createButton(button, `On/Off ${light}`, SINGLE) this.createButton(button + 1, `Dim Up ${light}`, SINGLE_LONG) this.createButton(button + 2, `Dim Down ${light}`, SINGLE_LONG) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'ubisys' && this.obj.modelid === 'S1 (5501)' ) { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, '1', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'ubisys' && ( this.obj.modelid === 'S1-R (5601)' || this.obj.modelid === 'S2 (5502)' || this.obj.modelid === 'S2-R (5602)' || this.obj.modelid === 'D1 (5503)' || this.obj.modelid === 'D1-R (5603)' ) ) { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, '1', SINGLE_LONG) this.createButton(2, '2', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'ubisys' && ( this.obj.modelid === 'C4 (5504)' || this.obj.modelid === 'C4-R (5604)' ) ) { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, '1', SINGLE_LONG) this.createButton(2, '2', SINGLE_LONG) this.createButton(3, '3', SINGLE_LONG) this.createButton(4, '4', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'dresden elektronik' && this.obj.modelid === 'Lighting Switch' && this.endpoint === '01' ) { // dresden elektronik lighting switch if (this.obj.mode !== 2) { this.log.warn( '%s: %s: warning: Lighting Switch mode %d instead of 2', this.bridge.name, this.resource, this.obj.mode ) } this.createButton(1, 'Top Left', SINGLE_LONG) this.createButton(2, 'Bottom Left', SINGLE_LONG) this.createButton(3, 'Top Right', SINGLE_LONG) this.createButton(4, 'Bottom Right', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'dresden elektronik' && this.obj.modelid === 'Scene Switch' ) { // dresden elektronik scene switch this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'On', SINGLE_LONG) this.createButton(2, 'Off', SINGLE_LONG) this.createButton(3, 'Scene 1', SINGLE) this.createButton(4, 'Scene 2', SINGLE) this.createButton(5, 'Scene 3', SINGLE) this.createButton(6, 'Scene 4', SINGLE) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'LUMI' && this.obj.modelid === 'lumi.sensor_86sw1' ) { // Xiaomi wall switch (single button) this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Button', SINGLE_DOUBLE) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.sensor_switch' || this.obj.modelid === 'lumi.sensor_switch.aq2' || this.obj.modelid === 'lumi.sensor_switch.aq3' ) ) { // Xiaomi Mi wireless switch // Xiaomi Aqara smart wireless switch // Xiaomi Aqara smart wireless switch with gyro this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Button', SINGLE_DOUBLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.sensor_86sw2' || this.obj.modelid === 'lumi.ctrl_ln2.aq1' ) ) { // Xiaomi wall switch (two buttons) this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Left', SINGLE_DOUBLE) this.createButton(2, 'Right', SINGLE_DOUBLE) this.createButton(3, 'Both', SINGLE_DOUBLE) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.remote.b1acn01' || this.obj.modelid === 'lumi.remote.b186acn01' ) ) { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Left', SINGLE_DOUBLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'LUMI' && this.obj.modelid === 'lumi.remote.b286acn01' ) { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Left', SINGLE_DOUBLE_LONG) this.createButton(2, 'Right', SINGLE_DOUBLE_LONG) this.createButton(3, 'Both', SINGLE_DOUBLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'LUMI' && this.obj.modelid.startsWith('lumi.sensor_cube') ) { // Xiaomi Mi smart cube this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) if (this.endpoint === '02') { this.createButton(1, 'Side 1', SINGLE_DOUBLE_LONG) this.createButton(2, 'Side 2', SINGLE_DOUBLE_LONG) this.createButton(3, 'Side 3', SINGLE_DOUBLE_LONG) this.createButton(4, 'Side 4', SINGLE_DOUBLE_LONG) this.createButton(5, 'Side 5', SINGLE_DOUBLE_LONG) this.createButton(6, 'Side 6', SINGLE_DOUBLE_LONG) this.createButton(7, 'Cube', DOUBLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: function (v) { if (v % 1000 === 0) { return Characteristic.ProgrammableSwitchEvent.LONG_PRESS } else if (v % 1000 === Math.floor(v / 1000) || v % 1000 === 8) { return Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS } else { return Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS } } } } else if (this.endpoint === '03') { this.createButton(8, 'Turn Right', SINGLE_DOUBLE_LONG) this.createButton(9, 'Turn Left', SINGLE_DOUBLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return v > 0 ? 8 : 9 }, homekitAction: function (v) { return Math.abs(v) < 4500 ? Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS : Math.abs(v) < 9000 ? Characteristic.ProgrammableSwitchEvent.DOUBLE_PRESS : Characteristic.ProgrammableSwitchEvent.LONG_PRESS } } } } else if ( this.obj.manufacturername === 'Insta' && ( this.obj.modelid === 'WS_3f_G_1' || this.obj.modelid === 'HS_4f_GJ_1' || this.obj.modelid === 'WS_4f_J_1' ) ) { // Gira Light Link wall transmitter // Gira/Jung Light Link hand transmitter // Jung Light Link wall transmitter this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Off', SINGLE_DOUBLE_LONG) this.createButton(2, 'On', SINGLE_DOUBLE_LONG) this.createButton(3, 'Scene 1', SINGLE) this.createButton(4, 'Scene 2', SINGLE) this.createButton(5, 'Scene 3', SINGLE) this.createButton(6, 'Scene 4', SINGLE) if (this.obj.modelid !== 'WS_3f_G_1') { this.createButton(7, 'Scene 5', SINGLE) this.createButton(8, 'Scene 6', SINGLE) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'Busch-Jaeger' && ( this.obj.modelid === 'RM01' || this.obj.modelid === 'RB01' ) ) { // Busch-Jaeger Light Link control element (mains-powered) // Busch-Jaeger Light Link wall-mounted transmitter // Exposed as multiple ZHASwitch resources? this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) if (this.endpoint === '0a') { this.createButton(1, 'Button 1', SINGLE_LONG) this.createButton(2, 'Button 2', SINGLE_LONG) } else if (this.endpoint === '0b') { this.createButton(3, 'Button 3', SINGLE_LONG) this.createButton(4, 'Button 4', SINGLE_LONG) } else if (this.endpoint === '0c') { this.createButton(5, 'Button 5', SINGLE_LONG) this.createButton(6, 'Button 6', SINGLE_LONG) } else if (this.endpoint === '0d') { this.createButton(7, 'Button 7', SINGLE_LONG) this.createButton(8, 'Button 8', SINGLE_LONG) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'icasa' && ( this.obj.modelid === 'ICZB-KPD12' || this.obj.modelid === 'ICZB-KPD14S' || this.obj.modelid === 'ICZB-KPD18S' ) ) { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Off', SINGLE_LONG) this.createButton(2, 'On', SINGLE_LONG) if (this.obj.modelid !== 'ICZB-KPD12') { this.createButton(3, 'S1', SINGLE) this.createButton(4, 'S2', SINGLE) if (this.obj.modelid === 'ICZB-KPD18S') { this.createButton(5, 'S3', SINGLE) this.createButton(6, 'S4', SINGLE) this.createButton(7, 'S5', SINGLE) this.createButton(8, 'S6', SINGLE) } } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'Lutron' && this.obj.modelid === 'Z3-1BRL' ) { // Lutron Aurora, see #522. this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) if (this.bridge.isHue) { this.createButton(1, 'Button', SINGLE_LONG) } else { this.createButton(1, 'Button', SINGLE) this.createButton(2, 'Turn Right', SINGLE) this.createButton(3, 'Turn Left', SINGLE) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'Samjin' && this.obj.modelid === 'button' ) { // Samsung SmartThings Button this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'Button', SINGLE_DOUBLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'Sunricher' && this.obj.modelid === 'ZG2833K8_EU05' ) { // Sunricher 8-button remote, see #529. this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) if (this.endpoint === '01') { this.createButton(1, 'On 1', SINGLE_LONG) this.createButton(2, 'Off 1', SINGLE_LONG) } else if (this.endpoint === '02') { this.createButton(3, 'On 2', SINGLE_LONG) this.createButton(4, 'Off 2', SINGLE_LONG) } else if (this.endpoint === '03') { this.createButton(5, 'On 3', SINGLE_LONG) this.createButton(6, 'Off 3', SINGLE_LONG) } else if (this.endpoint === '04') { this.createButton(7, 'On 4', SINGLE_LONG) this.createButton(8, 'Off 4', SINGLE_LONG) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'Sunricher' && this.obj.modelid === 'ZGRC-KEY-002' ) { // Sunricher CCT remote, see #529. this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, 'On', SINGLE) this.createButton(2, 'Off', SINGLE) this.createButton(3, 'Dim', LONG) this.createButton(4, 'C/W', SINGLE_LONG) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'Bitron Home' && this.obj.modelid === '902010/23' ) { // Bitron remote, see #639. this.createLabel(Characteristic.ServiceLabelNamespace.DOTS) this.createButton(1, 'DimUp', SINGLE) this.createButton(2, 'On', SINGLE) this.createButton(3, 'Off', SINGLE) this.createButton(4, 'DimDown', SINGLE) this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.remote.b286opcn01' || this.obj.modelid === 'lumi.remote.b486opcn01' || this.obj.modelid === 'lumi.remote.b686opcn01' ) ) { // Xiaomi Aqara Opple, see #637. this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(1, '1', SINGLE_DOUBLE_LONG) this.createButton(2, '2', SINGLE_DOUBLE_LONG) if (this.obj.modelid !== 'lumi.remote.b286opcn01') { this.createButton(3, '3', SINGLE_DOUBLE_LONG) this.createButton(4, '4', SINGLE_DOUBLE_LONG) } if (this.obj.modelid === 'lumi.remote.b686opcn01') { this.createButton(5, '5', SINGLE_DOUBLE_LONG) this.createButton(6, '6', SINGLE_DOUBLE_LONG) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else if ( this.obj.manufacturername === 'icasa' && this.obj.modelid === 'ICZB-RM11S' ) { // icasa remote, see deconz-rest-plugin#1978. this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) if (this.endpoint === '01') { this.createButton(1, '1 Off', SINGLE_LONG) this.createButton(2, '1 On', SINGLE_LONG) this.createButton(9, 'S1', SINGLE) this.createButton(10, 'S2', SINGLE) } else if (this.endpoint === '02') { this.createButton(3, '2 Off', SINGLE_LONG) this.createButton(4, '2 On', SINGLE_LONG) } else if (this.endpoint === '03') { this.createButton(5, '3 Off', SINGLE_LONG) this.createButton(6, '3 On', SINGLE_LONG) } else if (this.endpoint === '04') { this.createButton(7, '4 Off', SINGLE_LONG) this.createButton(8, '4 On', SINGLE_LONG) } this.type = { key: 'buttonevent', homekitValue: function (v) { return Math.floor(v / 1000) }, homekitAction: hkZLLSwitchAction } } else { this.log.warn( '%s: %s: warning: ignoring unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } break case 'ZLLRelativeRotary': // Lutron Aurora, see #522. if ( this.obj.manufacturername === 'Lutron' && this.obj.modelid === 'Z3-1BRL' ) { this.createLabel(Characteristic.ServiceLabelNamespace.ARABIC_NUMERALS) this.createButton(2, 'Turn Right', SINGLE) this.createButton(3, 'Turn Left', SINGLE) this.type = { key: 'expectedrotation', homekitValue: function (v) { return v > 0 ? 2 : 3 }, homekitAction: function () { return Characteristic.ProgrammableSwitchEvent.SINGLE_PRESS } } } else { this.log.warn( '%s: %s: warning: ignoring unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } break case 'CLIPSwitch': // 2.1 // We'd need a way to specify the number of buttons, cf. max value for // a CLIPGenericStatus sensor. this.log.warn( '%s: %s: warning: ignoring unsupported sensor type %s', this.bridge.name, this.resource, this.obj.type ) break case 'ZLLPresence': this.duration = 0 // falls through case 'ZHAPresence': if ( this.obj.manufacturername === this.bridge.philips && (this.obj.modelid === 'SML001' || this.obj.modelid === 'SML002') ) { // 1.3 - Hue motion sensor durationKey = 'delay' } else if ( this.obj.manufacturername === 'IKEA of Sweden' && this.obj.modelid === 'TRADFRI motion sensor' ) { // Ikea Trådfri motion sensor this.obj.state.dark = false } else if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.sensor_motion' || this.obj.modelid === 'lumi.sensor_motion.aq2' ) ) { // Xiaomi motion sensor // Xiaomi Aqara motion sensor } else if ( this.obj.manufacturername === 'Heiman' && this.obj.modelid === 'PIR_TPV11' ) { // Heiman motion sensor } else if ( this.obj.manufacturername === 'SmartThings' && this.obj.modelid === 'tagv4' ) { } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPPresence': // 2.3 case 'Geofence': // Undocumented this.service = new eve.Services.MotionSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.MotionDetected, key: 'presence', name: 'motion', unit: '', history: 'motion', homekitValue: function (v) { return v ? 1 : 0 }, durationKey: durationKey, sensitivitymax: this.obj.config.sensitivitymax } break case 'ZLLTemperature': case 'ZHATemperature': if ( this.obj.manufacturername === this.bridge.philips && (this.obj.modelid === 'SML001' || this.obj.modelid === 'SML002') ) { // 1.4 - Hue motion sensor } else if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.weather' || this.obj.modelid === 'lumi.sensor_ht' ) ) { // Xiaomi temperature/humidity sensor // Xiaomi Aqara weather sensor } else if ( this.obj.manufacturername === 'Heiman' && (this.obj.modelid === 'TH-H_V15' || this.obj.modelid === 'TH-T_V15') ) { // Heiman temperature/humidity sensor } else if ( this.obj.manufacturername === 'Samjin' && this.obj.modelid === 'button' ) { // Samsung SmartThings Button temperature sensor } else if ( this.obj.manufacturername === 'Samjin' && this.obj.modelid === 'multi' ) { // Samsung SmartThings multipurpose sensor } else if ( this.obj.manufacturername === 'Develco Products AS' && ( this.obj.modelid === 'SMSZB-120' || this.obj.modelid === 'HESZB-120' ) ) { // Develco smoke sensor // Develco heat sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPTemperature': // 2.4 this.service = new eve.Services.TemperatureSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.CurrentTemperature, props: { minValue: -40.0, maxValue: 100.0 }, key: 'temperature', name: 'temperature', unit: '°C', history: 'weather', homekitValue: function (v) { return v ? Math.round(v / 10) / 10 : 0 } } break case 'ZLLLightLevel': // 2.7 - Hue Motion Sensor case 'ZHALightLevel': if ( this.obj.manufacturername === this.bridge.philips && (this.obj.modelid === 'SML001' || this.obj.modelid === 'SML002') ) { // 1.4 - Hue motion sensor } else if ( this.obj.manufacturername === 'LUMI' && this.obj.modelid === 'lumi.sensor_motion.aq2' ) { // Xiaomi Aqara motion sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPLightLevel': // 2.7 this.service = new Service.LightSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.CurrentAmbientLightLevel, key: 'lightlevel', name: 'light level', unit: ' lux', homekitValue: hkLightLevel } break case 'ZHAOpenClose': if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.sensor_magnet.aq2' || this.obj.modelid === 'lumi.sensor_magnet' ) ) { // Xiaomi Aqara door/window sensor // Xiaomi Mi door/window sensor } else if ( this.obj.manufacturername === 'Heiman' && this.obj.modelid === 'DOOR_TPV13' ) { // Heiman smart door sensor } else if ( this.obj.manufacturername === 'Samjin' && this.obj.modelid === 'multi' ) { // Samsung SmartThings multipurpose sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPOpenClose': // 2.2 this.service = new eve.Services.ContactSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.ContactSensorState, key: 'open', name: 'contact', unit: '', history: 'door', homekitValue: function (v) { return v ? 1 : 0 } } break case 'ZHAHumidity': if ( this.obj.manufacturername === 'LUMI' && ( this.obj.modelid === 'lumi.weather' || this.obj.modelid === 'lumi.sensor_ht' ) ) { // Xiaomi Aqara weather sensor // Xiaomi Mi temperature/humidity sensor } else if ( this.obj.manufacturername === 'Heiman' && (this.obj.modelid === 'TH-H_V15' || this.obj.modelid === 'TH-T_V15') ) { // Heiman temperature/humidity sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPHumidity': // 2.5 this.service = new Service.HumiditySensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.CurrentRelativeHumidity, key: 'humidity', name: 'humidity', unit: '%', history: 'weather', homekitValue: function (v) { return v ? Math.round(v / 100) : 0 } } break case 'ZHAPressure': if ( this.obj.manufacturername === 'LUMI' && this.obj.modelid === 'lumi.weather' ) { // Xiaomi Aqara weather sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPPressure': this.service = new eve.Services.AirPressureSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: eve.Characteristics.AirPressure, key: 'pressure', name: 'pressure', unit: ' hPa', history: 'weather', homekitValue: function (v) { return v ? Math.round(v) : 0 } } this.service.updateCharacteristic(eve.Characteristics.Elevation, 0) break case 'ZHAAlarm': if ( this.obj.manufacturername.toLowerCase() === 'heiman' && this.obj.modelid.startsWith('WarningDevice') ) { // Heiman Siren } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPAlarm': this.service = new my.Services.Resource(this.name, this.subtype) this.service.addOptionalCharacteristic(my.Characteristics.Alarm) this.serviceList.push(this.service) this.type = { Characteristic: my.Characteristics.Alarm, key: 'alarm', name: 'alarm', homekitValue: function (v) { return v ? 1 : 0 } } break case 'ZHACarbonMonoxide': if ( this.obj.manufacturername === 'Heiman' && this.obj.modelid === 'CO_V16' ) { // Heiman CO sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPCarbonMonoxide': this.service = new Service.CarbonMonoxideSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.CarbonMonoxideDetected, key: 'carbonmonoxide', name: 'CO', unit: '', homekitValue: function (v) { return v ? 1 : 0 } } break case 'ZHAFire': if ( this.obj.manufacturername.toLowerCase() === 'heiman' && ( this.obj.modelid === 'SMOK_V16' || this.obj.modelid === 'GAS_V15' || this.obj.modelid === 'SmokeSensor-N-3.0' || this.obj.modelid === 'GASSensor-EM' ) ) { // Heiman fire sensor // Heiman gas sensor } else if ( this.obj.manufacturername === 'Develco Products AS' && ( this.obj.modelid === 'SMSZB-120' || this.obj.modelid === 'HESZB-120' ) ) { // Develco smoke sensor // Develco heat sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPFire': this.service = new Service.SmokeSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.SmokeDetected, key: 'fire', name: 'smoke', unit: '', homekitValue: function (v) { return v ? 1 : 0 } } break case 'ZHAVibration': if ( this.obj.manufacturername === 'LUMI' && this.obj.modelid === 'lumi.vibration.aq1' ) { // Xiaomi vibration sensor } else if ( this.obj.manufacturername === 'Samjin' && this.obj.modelid === 'multi' ) { // Samsung SmartThings multipurpose sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPVibration': this.service = new eve.Services.MotionSensor(this.name, this.subtype) this.serviceList.push(this.service) this.duration = 0 this.type = { Characteristic: Characteristic.MotionDetected, key: 'vibration', name: 'motion', unit: '', history: 'motion', homekitValue: function (v) { return v ? 1 : 0 }, sensitivitymax: this.obj.config.sensitivitymax } break case 'ZHAWater': if ( ( this.obj.manufacturername === 'LUMI' && this.obj.modelid === 'lumi.sensor_wleak.aq1' ) || ( this.obj.manufacturername === 'Heiman' && this.obj.modelid === 'WATER_TPV11' ) ) { // Xiaomi Aqara flood sensor // Heiman water sensor } else { this.log.warn( '%s: %s: warning: unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } // falls through case 'CLIPWater': this.service = new Service.LeakSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.LeakDetected, key: 'water', name: 'leak', unit: '', homekitValue: function (v) { return v ? 1 : 0 } } break case 'ZHAConsumption': // falls through case 'CLIPConsumption': this.service = this.accessory.lightService == null ? new my.Services.Resource(this.name, this.subtype) : this.accessory.lightService this.serviceList.push(this.service) this.service .addOptionalCharacteristic(eve.Characteristics.TotalConsumption) this.type = { Characteristic: eve.Characteristics.TotalConsumption, key: 'consumption', name: 'total consumption', unit: ' kWh', history: 'energy', homekitValue: function (v) { return v / 1000.0 } } break case 'ZHAPower': // falls through case 'CLIPPower': this.service = this.accessory.lightService == null ? new my.Services.Resource(this.name, this.subtype) : this.accessory.lightService this.serviceList.push(this.service) this.service .addOptionalCharacteristic(eve.Characteristics.CurrentConsumption) this.type = { Characteristic: eve.Characteristics.CurrentConsumption, key: 'power', name: 'current consumption', unit: ' W', history: 'energy', homekitValue: function (v) { return v } } break case 'ZHAThermostat': // falls through case 'CLIPThermostat': this.service = new Service.Thermostat(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.CurrentTemperature, // props: { minValue: -40.0, maxValue: 100.0 }, key: 'temperature', name: 'temperature', unit: '°C', history: 'thermo', homekitValue: function (v) { return v ? Math.round(v / 10) / 10 : 0 } } break case 'Daylight': if ( this.obj.manufacturername === this.bridge.philips && this.obj.modelid === 'PHDL00' ) { // 2.6 - Built-in daylight sensor. if (!this.obj.config.configured) { this.log.warn( '%s: %s: warning: %s sensor not configured', this.bridge.name, this.resource, this.obj.type ) } this.manufacturer = this.obj.manufacturername this.model = this.obj.modelid this.service = new Service.LightSensor(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.CurrentAmbientLightLevel, key: 'lightlevel', name: 'light level', unit: ' lux', homekitValue: hkLightLevel } if (obj.state.status == null) { // Hue bridge obj.state.lightlevel = obj.state.daylight ? 65535 : 0 obj.state.dark = !obj.state.daylight } obj.config.reachable = obj.config.configured } else { this.log.warn( '%s: %s: warning: ignoring unknown %s sensor %j', this.bridge.name, this.resource, this.obj.type, this.obj ) } break case 'ZHABattery': case 'CLIPBattery': this.service = this.accessory.getBatteryService( this.obj.config.battery ) // this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.BatteryLevel, key: 'battery', name: 'battery', unit: '%', homekitValue: function (v) { return toInt(v, 0, 100) } } break case 'CLIPGenericFlag': // 2.8 this.service = new Service.Switch(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: Characteristic.On, key: 'flag', name: 'on', unit: '', homekitValue: function (v) { return v ? 1 : 0 }, bridgeValue: function (v) { return !!v }, setter: true } // Note that Eve handles a read-only switch correctly, but Home doesn't. if ( this.obj.manufacturername === 'homebridge-hue' && this.obj.modelid === 'CLIPGenericFlag' && this.obj.swversion === '0' ) { this.type.props = { perms: [Characteristic.Perms.READ, Characteristic.Perms.NOTIFY] } } break case 'CLIPGenericStatus': // 2.9 this.service = new my.Services.Status(this.name, this.subtype) this.serviceList.push(this.service) this.type = { Characteristic: my.Characteristics.Status, key: 'status', name: 'status',