homebridge-deconz 0.0.6 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/cli/deconz.js +980 -0
- package/config.schema.json +1 -1
- package/lib/Client/ApiError.js +42 -0
- package/lib/{DeconzClient.js → Deconz/ApiClient.js} +82 -158
- package/lib/Deconz/ApiError.js +42 -0
- package/lib/Deconz/ApiResponse.js +54 -0
- package/lib/Deconz/Device.js +100 -0
- package/lib/{DeconzDiscovery.js → Deconz/Discovery.js} +4 -3
- package/lib/Deconz/Resource.js +1206 -0
- package/lib/{DeconzWsClient.js → Deconz/WsClient.js} +59 -44
- package/lib/Deconz/index.js +21 -0
- package/lib/DeconzAccessory/Contact.js +54 -0
- package/lib/DeconzAccessory/Gateway.js +316 -374
- package/lib/DeconzAccessory/Light.js +72 -0
- package/lib/DeconzAccessory/Motion.js +51 -0
- package/lib/DeconzAccessory/Sensor.js +35 -0
- package/lib/DeconzAccessory/Temperature.js +63 -0
- package/lib/DeconzAccessory/Thermostat.js +50 -0
- package/lib/DeconzAccessory/WarningDevice.js +56 -0
- package/lib/DeconzAccessory/WindowCovering.js +47 -0
- package/lib/DeconzAccessory/index.js +216 -0
- package/lib/DeconzPlatform.js +8 -3
- package/lib/DeconzService/AirPressure.js +43 -0
- package/lib/DeconzService/AirQuality.js +20 -10
- package/lib/DeconzService/Alarm.js +16 -9
- package/lib/DeconzService/Battery.js +43 -0
- package/lib/DeconzService/Button.js +12 -2
- package/lib/DeconzService/CarbonMonoxide.js +38 -0
- package/lib/DeconzService/Consumption.js +65 -0
- package/lib/DeconzService/Contact.js +60 -0
- package/lib/DeconzService/Daylight.js +132 -0
- package/lib/DeconzService/DeviceSettings.js +13 -5
- package/lib/DeconzService/Flag.js +52 -0
- package/lib/DeconzService/GatewaySettings.js +8 -58
- package/lib/DeconzService/Humidity.js +37 -0
- package/lib/DeconzService/Leak.js +38 -0
- package/lib/DeconzService/Light.js +376 -0
- package/lib/DeconzService/LightLevel.js +54 -0
- package/lib/DeconzService/LightsResource.js +112 -0
- package/lib/DeconzService/Motion.js +101 -0
- package/lib/DeconzService/Outlet.js +76 -0
- package/lib/DeconzService/Power.js +83 -0
- package/lib/DeconzService/SensorsResource.js +96 -0
- package/lib/DeconzService/Smoke.js +38 -0
- package/lib/DeconzService/Status.js +53 -0
- package/lib/DeconzService/Switch.js +93 -0
- package/lib/DeconzService/Temperature.js +63 -0
- package/lib/DeconzService/Thermostat.js +175 -0
- package/lib/DeconzService/WarningDevice.js +68 -0
- package/lib/DeconzService/WindowCovering.js +139 -0
- package/lib/DeconzService/index.js +94 -0
- package/package.json +7 -4
- package/lib/DeconzAccessory/Device.js +0 -91
- package/lib/DeconzAccessory.js +0 -16
- package/lib/DeconzDevice.js +0 -245
- package/lib/DeconzService/Sensor.js +0 -58
- package/lib/DeconzService.js +0 -43
@@ -0,0 +1,1206 @@
|
|
1
|
+
// homebridge-deconz/lib/Deconz/Resource.js
|
2
|
+
// Copyright © 2022 Erik Baauw. All rights reserved.
|
3
|
+
//
|
4
|
+
// Homebridge plugin for deCONZ.
|
5
|
+
|
6
|
+
'use strict'
|
7
|
+
|
8
|
+
const homebridgeLib = require('homebridge-lib')
|
9
|
+
const Deconz = require('../Deconz')
|
10
|
+
const DeconzAccessory = require('../DeconzAccessory')
|
11
|
+
const DeconzService = require('../DeconzService')
|
12
|
+
|
13
|
+
const { toInstance, toInt, toObject, toString } = homebridgeLib.OptionParser
|
14
|
+
const { defaultGamut } = homebridgeLib.Colour
|
15
|
+
const { buttonEvent } = Deconz.ApiClient
|
16
|
+
const { SINGLE, DOUBLE, LONG } = DeconzService.Button
|
17
|
+
const rtypes = ['lights', 'sensors', 'groups']
|
18
|
+
|
19
|
+
// From low to high.
|
20
|
+
const sensorsPrios = [
|
21
|
+
'Power',
|
22
|
+
'Consumption',
|
23
|
+
'Temperature',
|
24
|
+
'Presence',
|
25
|
+
'OpenClose',
|
26
|
+
'Thermostat'
|
27
|
+
]
|
28
|
+
|
29
|
+
// =============================================================================
|
30
|
+
|
31
|
+
// See: http://www.developers.meethue.com/documentation/supported-lights
|
32
|
+
|
33
|
+
const hueGamutType = { // Color gamut per light model.
|
34
|
+
A: { // Color Lights
|
35
|
+
r: [0.7040, 0.2960],
|
36
|
+
g: [0.2151, 0.7106],
|
37
|
+
b: [0.1380, 0.0800]
|
38
|
+
},
|
39
|
+
B: { // Extended Color Lights
|
40
|
+
r: [0.6750, 0.3220],
|
41
|
+
g: [0.4090, 0.5180],
|
42
|
+
b: [0.1670, 0.0400]
|
43
|
+
},
|
44
|
+
C: { // next gen Extended Color Lights
|
45
|
+
r: [0.6920, 0.3080],
|
46
|
+
g: [0.1700, 0.7000],
|
47
|
+
b: [0.1530, 0.0480]
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
const hueGamutTypeByModel = {
|
52
|
+
LCT001: 'B', // Hue bulb A19
|
53
|
+
LCT002: 'B', // Hue Spot BR30
|
54
|
+
LCT003: 'B', // Hue Spot GU10
|
55
|
+
LCT007: 'B', // Hue bulb A19
|
56
|
+
LCT010: 'C', // Hue bulb A19
|
57
|
+
LCT011: 'C', // Hue BR30
|
58
|
+
LCT012: 'C', // Hue Color Candle
|
59
|
+
LCT014: 'C', // Hue bulb A19
|
60
|
+
LCT015: 'C', // Hue bulb A19
|
61
|
+
LCT016: 'C', // Hue bulb A19
|
62
|
+
LLC005: 'A', // Living Colors Gen3 Bloom, Aura
|
63
|
+
LLC006: 'A', // Living Colors Gen3 Iris
|
64
|
+
LLC007: 'A', // Living Colors Gen3 Bloom, Aura
|
65
|
+
LLC010: 'A', // Hue Living Colors Iris
|
66
|
+
LLC011: 'A', // Hue Living Colors Bloom
|
67
|
+
LLC012: 'A', // Hue Living Colors Bloom
|
68
|
+
LLC013: 'A', // Disney Living Colors
|
69
|
+
LLC014: 'A', // Living Colors Gen3 Bloom, Aura
|
70
|
+
LLC020: 'C', // Hue Go
|
71
|
+
LLM001: 'B', // Color Light Module
|
72
|
+
LST001: 'A', // Hue LightStrips
|
73
|
+
LST002: 'C' // Hue LightStrips Plus
|
74
|
+
}
|
75
|
+
|
76
|
+
const hueTapMap = {
|
77
|
+
34: 1002, // press 1
|
78
|
+
16: 2002, // press 2
|
79
|
+
17: 3002, // press 3
|
80
|
+
18: 4002, // press 4
|
81
|
+
100: 5002, // press 1 and 2
|
82
|
+
101: 0, // release 1 and 2
|
83
|
+
98: 6002, // press 3 and 4
|
84
|
+
99: 0 // release 3 and 4
|
85
|
+
}
|
86
|
+
|
87
|
+
/** Delegate class for a resource on a deCONZ gateway.
|
88
|
+
*
|
89
|
+
* @memberof Deconz
|
90
|
+
*/
|
91
|
+
class Resource {
|
92
|
+
/** Parse the `uniqueid` in the resource body of a resource for a Zigbee device.
|
93
|
+
* @param {string} uniqueid - The `uniqueid`.
|
94
|
+
* @return {object} The Zigbee `mac`, `endpoint`, and `cluster`.
|
95
|
+
*/
|
96
|
+
static parseUniqueid (uniqueid) {
|
97
|
+
toString('uniqueid', uniqueid, true)
|
98
|
+
const a = uniqueid.replace(/:/g, '').toUpperCase().split('-')
|
99
|
+
return {
|
100
|
+
mac: a.length > 0 ? a[0] : null,
|
101
|
+
endpoint: a.length > 1 ? a[1] : null,
|
102
|
+
cluster: a.length > 2 ? a[2] : null
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
/** Create a new instance of a delegate of a resource.
|
107
|
+
*
|
108
|
+
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
109
|
+
* @param {string} rtype - The resource type of the resource:
|
110
|
+
* `groups`, `lights`, or `sensors`.
|
111
|
+
* @param {integer} rid - The resource ID of the resource.
|
112
|
+
* @param {object} body - The body of the resource.
|
113
|
+
*/
|
114
|
+
constructor (gateway, rtype, rid, body) {
|
115
|
+
toInstance('gateway', gateway, DeconzAccessory.Gateway)
|
116
|
+
|
117
|
+
/** The resource type of the resource: `groups`, `lights`, or `sensors`.
|
118
|
+
* @type {string}
|
119
|
+
*/
|
120
|
+
this.rtype = toString('rtype', rtype, true)
|
121
|
+
if (!(rtypes.includes(rtype))) {
|
122
|
+
throw new RangeError(`rtype: ${rtype}: not a valid resource type`)
|
123
|
+
}
|
124
|
+
|
125
|
+
/** The resource ID of the resource.
|
126
|
+
* @type {integer}
|
127
|
+
*/
|
128
|
+
this.rid = toInt('rid', rid)
|
129
|
+
|
130
|
+
/** The body of the resource.
|
131
|
+
* @type {object}
|
132
|
+
*/
|
133
|
+
this.body = toObject('body', body)
|
134
|
+
toString('body.name', body.name, true)
|
135
|
+
toString('body.type', body.type, true)
|
136
|
+
|
137
|
+
if (
|
138
|
+
this.rtype === 'lights' ||
|
139
|
+
(this.rtype === 'sensors' && this.body.type.startsWith('Z'))
|
140
|
+
) {
|
141
|
+
const { mac, endpoint, cluster } = Resource.parseUniqueid(body.uniqueid)
|
142
|
+
|
143
|
+
/** The device ID.
|
144
|
+
*
|
145
|
+
* For Zigbee devices, the device ID is based on the Zigbee mac address
|
146
|
+
* of the device, from the `uniqueid` in the body of the resource.
|
147
|
+
* For virtual devices, the device ID is based on the Zigbee mac address of
|
148
|
+
* the gateway, and on the resource type and resource ID of the resource.
|
149
|
+
* The UUID of the corresponding HomeKit accessory is based on the device ID.
|
150
|
+
* @type {string}
|
151
|
+
*/
|
152
|
+
this.id = mac
|
153
|
+
|
154
|
+
/** The subtype of the corresponding HomeKit service.
|
155
|
+
*
|
156
|
+
* For Zigbee devices, the subtype is based on the Zigbee endpoint and
|
157
|
+
* cluster, from the `uniqueid` in the body of the resource.
|
158
|
+
* For virtual devices, the subtype is based on the resource type and
|
159
|
+
* resource ID.
|
160
|
+
* @type {string}
|
161
|
+
*/
|
162
|
+
this.subtype = endpoint + (cluster == null ? '' : '-' + cluster)
|
163
|
+
|
164
|
+
/** Zigbee endpoint.
|
165
|
+
* @type {string}
|
166
|
+
*/
|
167
|
+
this.endpoint = endpoint
|
168
|
+
|
169
|
+
/** Zigbee device vs virtual device.
|
170
|
+
*
|
171
|
+
* Derived from the resource type and, for `sensors`, on the `type` in the
|
172
|
+
* resource body.
|
173
|
+
* @type {boolean}
|
174
|
+
*/
|
175
|
+
this.zigbee = true
|
176
|
+
} else {
|
177
|
+
this.subtype = rtype[0].toUpperCase() + rid
|
178
|
+
this.id = gateway.id + '-' + this.subtype
|
179
|
+
this.zigbee = false
|
180
|
+
}
|
181
|
+
|
182
|
+
/** The associated Homekit _Manufacturer_.
|
183
|
+
*
|
184
|
+
* For Zigbee devices, this is the sanitised `manufacturername` in the
|
185
|
+
* resource body.
|
186
|
+
* For virtual devices, this is the _Manufacturer_ for the gateway.
|
187
|
+
* @type {string}
|
188
|
+
*/
|
189
|
+
this.manufacturer = this.zigbee
|
190
|
+
? body.manufacturername
|
191
|
+
: gateway.values.manufacturer
|
192
|
+
|
193
|
+
/** The associated HomeKit _Model_.
|
194
|
+
*
|
195
|
+
* For Zigbee devices, this is the sanitised `modelid` in the
|
196
|
+
* resource body.
|
197
|
+
* For virtual devices, this is the `type` in the resource body.
|
198
|
+
* @type {string}
|
199
|
+
*/
|
200
|
+
this.model = this.zigbee ? body.modelid : body.type
|
201
|
+
|
202
|
+
/** The associated HomeKit _Firmware Version_.
|
203
|
+
*
|
204
|
+
* For Zigbee devices, this is the sanitised `swversion` in the
|
205
|
+
* resource body.
|
206
|
+
* For virtual devices, this is the _Firmware Version_ for the gateway.
|
207
|
+
*/
|
208
|
+
this.firmware = this.zigbee
|
209
|
+
? body.swversion == null ? '0.0.0' : body.swversion
|
210
|
+
: gateway.values.software
|
211
|
+
|
212
|
+
switch (this.serviceName) {
|
213
|
+
case 'Light': {
|
214
|
+
if (body.action != null) {
|
215
|
+
Object.assign(body.state, body.action)
|
216
|
+
delete body.state.on
|
217
|
+
}
|
218
|
+
this.capabilities = {
|
219
|
+
on: body.state.on !== undefined,
|
220
|
+
bri: body.state.bri !== undefined,
|
221
|
+
ct: body.state.ct !== undefined,
|
222
|
+
ctMax: (body.ctmax != null && body.ctmax !== 0 && body.ctmax !== 65535)
|
223
|
+
? body.ctmax
|
224
|
+
: 500,
|
225
|
+
ctMin: (body.ctmin != null && body.ctmin !== 0)
|
226
|
+
? body.ctmin
|
227
|
+
: 153,
|
228
|
+
xy: body.state.xy !== undefined,
|
229
|
+
gamut: defaultGamut,
|
230
|
+
alert: body.state.alert !== undefined,
|
231
|
+
colorLoop: body.state.effect !== undefined
|
232
|
+
}
|
233
|
+
break
|
234
|
+
}
|
235
|
+
case 'WarningDevice':
|
236
|
+
this.capabilities = {}
|
237
|
+
break
|
238
|
+
case 'WindowCovering':
|
239
|
+
this.capabilities = {}
|
240
|
+
break
|
241
|
+
default:
|
242
|
+
this.capabilities = {}
|
243
|
+
break
|
244
|
+
}
|
245
|
+
|
246
|
+
const f = 'patch' + this.serviceName
|
247
|
+
if (typeof this[f] === 'function') {
|
248
|
+
this[f](gateway)
|
249
|
+
}
|
250
|
+
}
|
251
|
+
|
252
|
+
/** The priority of the resource, when determining the primary resource for a
|
253
|
+
* device.
|
254
|
+
* @type {integer}
|
255
|
+
*/
|
256
|
+
get prio () {
|
257
|
+
if (this.rtype === 'groups') return -1
|
258
|
+
if (this.rtype === 'lights') return this.endpoint
|
259
|
+
return sensorsPrios.indexOf(this.type)
|
260
|
+
}
|
261
|
+
|
262
|
+
/** The resource path of the resource, e.g. `/lights/1`.
|
263
|
+
*
|
264
|
+
* This is derived from the resource type and resource ID.
|
265
|
+
* @type {string}
|
266
|
+
*/
|
267
|
+
get rpath () { return '/' + this.rtype + '/' + this.rid }
|
268
|
+
|
269
|
+
/** The name of the {@link DeconzService} subclass of the delegate of the
|
270
|
+
* corresponding HomeKit service, or `null` for unsupported and unknown
|
271
|
+
* resources.
|
272
|
+
*
|
273
|
+
* This is derived from the resource type and`type` in the resource body.
|
274
|
+
* @type {string}
|
275
|
+
*/
|
276
|
+
get serviceName () {
|
277
|
+
if (this.rtype === 'groups') {
|
278
|
+
return 'Light'
|
279
|
+
} else if (this.rtype === 'lights') {
|
280
|
+
switch (this.body.type) {
|
281
|
+
case 'Color dimmable light': return 'Light'
|
282
|
+
case 'Color light': return 'Light'
|
283
|
+
case 'Color temperature light': return 'Light'
|
284
|
+
case 'Dimmable light': return 'Light'
|
285
|
+
case 'Dimmable plug-in unit': return 'Light'
|
286
|
+
case 'Extended color light': return 'Light'
|
287
|
+
// case 'Consumption awareness device': return null
|
288
|
+
case 'Dimmer switch': return 'Light'
|
289
|
+
case 'Level control switch': return 'Light'
|
290
|
+
// case 'Level controllable output': return null
|
291
|
+
// case 'Door Lock': return null
|
292
|
+
// case 'Door Lock Unit': return null
|
293
|
+
case 'Fan': return 'Light'
|
294
|
+
case 'On/Off light switch': return 'Light'
|
295
|
+
case 'On/Off light': return 'Light'
|
296
|
+
case 'On/Off output': return 'Light'
|
297
|
+
case 'On/Off plug-in unit': return 'Light'
|
298
|
+
case 'Smart plug': return 'Light'
|
299
|
+
case 'Configuration tool': return ''
|
300
|
+
case 'Range extender': return ''
|
301
|
+
case 'Warning device': return 'WarningDevice'
|
302
|
+
case 'Window covering controller': return 'WindowCovering'
|
303
|
+
case 'Window covering device': return 'WindowCovering'
|
304
|
+
default: return null
|
305
|
+
}
|
306
|
+
} else { // (this.rtype === 'sensors')
|
307
|
+
switch (this.body.type) {
|
308
|
+
case 'ZHAAirQuality':
|
309
|
+
case 'CLIPAirQuality': return 'AirQuality'
|
310
|
+
case 'ZHAAlarm':
|
311
|
+
case 'CLIPAlarm': return 'Alarm'
|
312
|
+
case 'ZHABattery':
|
313
|
+
case 'CLIPBattery': return 'Battery'
|
314
|
+
case 'ZHACarbonMonoxide':
|
315
|
+
case 'CLIPCarbonMonoxide': return 'CarbonMonoxide'
|
316
|
+
case 'ZHAConsumption':
|
317
|
+
case 'CLIPConsumption': return 'Consumption'
|
318
|
+
// case 'ZHADoorLock':
|
319
|
+
// case 'CLIPDoorLock': return null
|
320
|
+
case 'Daylight': return 'Daylight'
|
321
|
+
case 'ZHAFire':
|
322
|
+
case 'CLIPFire': return 'Smoke'
|
323
|
+
case 'CLIPGenericFlag': return 'Flag'
|
324
|
+
case 'CLIPGenericStatus': return 'Status'
|
325
|
+
case 'ZHAHumidity':
|
326
|
+
case 'CLIPHumidity': return 'Humidity'
|
327
|
+
case 'ZHALightLevel':
|
328
|
+
case 'CLIPLightLevel': return 'LightLevel'
|
329
|
+
// case 'ZHAMoisture':
|
330
|
+
// case 'CLIPMoisture': return null
|
331
|
+
case 'ZHAOpenClose':
|
332
|
+
case 'CLIPOpenClose': return 'Contact'
|
333
|
+
case 'ZHAPower':
|
334
|
+
case 'CLIPPower': return 'Power'
|
335
|
+
case 'ZHAPresence':
|
336
|
+
case 'CLIPPresence': return 'Motion'
|
337
|
+
case 'ZHAPressure':
|
338
|
+
case 'CLIPPressure': return 'AirPressure'
|
339
|
+
case 'ZHASpectral': return ''
|
340
|
+
case 'ZGPSwitch':
|
341
|
+
case 'ZHASwitch':
|
342
|
+
case 'CLIPSwitch': return 'Switch'
|
343
|
+
case 'ZHATemperature':
|
344
|
+
case 'CLIPTemperature': return 'Temperature'
|
345
|
+
case 'ZHAThermostat':
|
346
|
+
case 'CLIPThermostat': return 'Thermostat'
|
347
|
+
case 'ZHATime':
|
348
|
+
case 'CLIPTime': return ''
|
349
|
+
case 'ZHAVibration':
|
350
|
+
case 'CLIPVibration': return 'Motion'
|
351
|
+
case 'ZHAWater':
|
352
|
+
case 'CLIPWater': return 'Leak'
|
353
|
+
default: return null
|
354
|
+
}
|
355
|
+
}
|
356
|
+
}
|
357
|
+
|
358
|
+
/** Patch a resource corresponding to a `Light` service.
|
359
|
+
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
360
|
+
*/
|
361
|
+
patchLight (gateway) {
|
362
|
+
switch (this.manufacturer) {
|
363
|
+
case 'Busch-Jaeger':
|
364
|
+
// See: https://www.busch-jaeger.de/en/products/product-solutions/dimmer/busch-radio-controlled-dimmer-zigbee-light-link/
|
365
|
+
if (
|
366
|
+
this.model === 'RM01' && // 6715 U-500 with 6736-84.
|
367
|
+
this.capabilities.bri && this.body.type === 'On/Off light' // Issue #241
|
368
|
+
) {
|
369
|
+
gateway.vdebug(
|
370
|
+
'%s: ignoring state.bri for %s', this.rpath, this.body.type
|
371
|
+
)
|
372
|
+
this.capabilities.bri = false
|
373
|
+
}
|
374
|
+
break
|
375
|
+
case 'dresden elektronik':
|
376
|
+
// See: https://www.dresden-elektronik.de/funktechnik/solutions/wireless-light-control/wireless-ballasts/?L=1
|
377
|
+
this.capabilities.computesXy = true
|
378
|
+
break
|
379
|
+
case 'FeiBit':
|
380
|
+
if (this.model === 'FNB56-SKT1EHG1.2') { // issue #361
|
381
|
+
this.body.type = 'On/Off plug-in unit'
|
382
|
+
}
|
383
|
+
break
|
384
|
+
case 'GLEDOPTO':
|
385
|
+
// See: https://www.led-trading.de/zigbee-kompatibel-controller-led-lichtsteuerung
|
386
|
+
this.capabilities.gamut = {
|
387
|
+
r: [0.7006, 0.2993],
|
388
|
+
g: [0.1387, 0.8148],
|
389
|
+
b: [0.1510, 0.0227]
|
390
|
+
}
|
391
|
+
if (this.model === 'GLEDOPTO') { // Issue #244
|
392
|
+
if (
|
393
|
+
this.endpoint === '0A' &&
|
394
|
+
this.body.type === 'Dimmable light' &&
|
395
|
+
this.firmware === '1.0.2'
|
396
|
+
) {
|
397
|
+
this.model = 'RGBW'
|
398
|
+
} else if (
|
399
|
+
this.endpoint === '0B' &&
|
400
|
+
this.body.type === 'Color temperature light' &&
|
401
|
+
this.firmware === '1.3.002'
|
402
|
+
) {
|
403
|
+
this.model = 'WW/CW'
|
404
|
+
} else if (
|
405
|
+
this.endpoint === '0B' &&
|
406
|
+
this.body.type === 'Extended color light' &&
|
407
|
+
this.firmware === '1.0.2'
|
408
|
+
) {
|
409
|
+
this.model = 'RGB+CCT'
|
410
|
+
const device = gateway.deviceById[this.id]
|
411
|
+
if (device != null) {
|
412
|
+
this.model = 'RGBW'
|
413
|
+
this.capabilities.ct = false
|
414
|
+
}
|
415
|
+
} else {
|
416
|
+
return
|
417
|
+
}
|
418
|
+
gateway.vdebug('%s: set model to %j', this.rpath, this.model)
|
419
|
+
}
|
420
|
+
break
|
421
|
+
case 'IKEA of Sweden':
|
422
|
+
// See: http://www.ikea.com/us/en/catalog/categories/departments/lighting/smart_lighting/
|
423
|
+
this.capabilities.gamut = defaultGamut // Issue #956
|
424
|
+
this.capabilities.noTransition = true
|
425
|
+
if (this.model === 'TRADFRI bulb E27 CWS 806lm') {
|
426
|
+
this.capabilities.computesXy = true
|
427
|
+
this.capabilities.gamut = {
|
428
|
+
r: [0.68, 0.31],
|
429
|
+
g: [0.11, 0.82],
|
430
|
+
b: [0.13, 0.04]
|
431
|
+
}
|
432
|
+
}
|
433
|
+
break
|
434
|
+
case 'innr':
|
435
|
+
// See: https://shop.innrlighting.com/en/shop
|
436
|
+
this.capabilities.gamut = { // Issue #152
|
437
|
+
r: [0.8817, 0.1033],
|
438
|
+
g: [0.2204, 0.7758],
|
439
|
+
b: [0.0551, 0.1940]
|
440
|
+
}
|
441
|
+
if (this.model === 'SP 120') { // smart plug
|
442
|
+
this.capabilities.bri = false
|
443
|
+
}
|
444
|
+
break
|
445
|
+
case 'LIDL Livarno Lux':
|
446
|
+
this.capabilities.ctMax = 454 // 2200 K
|
447
|
+
this.capabilities.ctMin = 153 // 6500 K
|
448
|
+
if (this.model === 'HG06467') { // Xmas light strip
|
449
|
+
this.capabilities.colorLoop = false
|
450
|
+
this.capabilities.hs = true
|
451
|
+
this.capabilities.effects = [
|
452
|
+
'Steady', 'Snow', 'Rainbow', 'Snake',
|
453
|
+
'Twinkle', 'Fireworks', 'Flag', 'Waves',
|
454
|
+
'Updown', 'Vintage', 'Fading', 'Collide',
|
455
|
+
'Strobe', 'Sparkles', 'Carnival', 'Glow'
|
456
|
+
]
|
457
|
+
}
|
458
|
+
break
|
459
|
+
case 'MLI': // Issue #439
|
460
|
+
this.capabilities.gamut = {
|
461
|
+
r: [0.68, 0.31],
|
462
|
+
g: [0.11, 0.82],
|
463
|
+
b: [0.13, 0.04]
|
464
|
+
}
|
465
|
+
if (this.capabilities.colorloop) {
|
466
|
+
this.capabilities.effects = [
|
467
|
+
'Sunset', 'Party', 'Worklight', 'Campfire', 'Romance', 'Nightlight'
|
468
|
+
]
|
469
|
+
}
|
470
|
+
break
|
471
|
+
case 'OSRAM':
|
472
|
+
this.capabilities.gamut = {
|
473
|
+
r: [0.6877, 0.3161],
|
474
|
+
g: [0.1807, 0.7282],
|
475
|
+
b: [0.1246, 0.0580]
|
476
|
+
}
|
477
|
+
break
|
478
|
+
case 'Philips':
|
479
|
+
case 'Signify Netherlands B.V.': {
|
480
|
+
// See: http://www.developers.meethue.com/documentation/supported-lights
|
481
|
+
this.manufacturer = 'Signify Netherlands B.V.'
|
482
|
+
this.capabilities.breathe = true
|
483
|
+
this.capabilities.computesXy = true
|
484
|
+
const gamut = hueGamutTypeByModel[this.model] || 'C'
|
485
|
+
this.capabilities.gamut = hueGamutType[gamut]
|
486
|
+
}
|
487
|
+
break
|
488
|
+
default:
|
489
|
+
break
|
490
|
+
}
|
491
|
+
}
|
492
|
+
|
493
|
+
/** Patch a resource corresponding to a `Flag` service.
|
494
|
+
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
495
|
+
*/
|
496
|
+
patchFlag (gateway) {
|
497
|
+
if (
|
498
|
+
this.body.manufacturername === 'homebridge-hue' &&
|
499
|
+
this.body.modelid === 'CLIPGenericFlag' &&
|
500
|
+
this.body.swversion === '0'
|
501
|
+
) {
|
502
|
+
this.capabilities.readonly = true
|
503
|
+
}
|
504
|
+
}
|
505
|
+
|
506
|
+
/** Patch a resource corresponding to a `Flag` service.
|
507
|
+
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
508
|
+
*/
|
509
|
+
patchStatus (gateway) {
|
510
|
+
if (
|
511
|
+
this.body.manufacturername === 'homebridge-hue' &&
|
512
|
+
this.body.modelid === 'CLIPGenericStatus'
|
513
|
+
) {
|
514
|
+
const a = this.body.swversion.split(',')
|
515
|
+
const min = parseInt(a[0])
|
516
|
+
const max = parseInt(a[1])
|
517
|
+
const step = parseInt(a[2])
|
518
|
+
// Eve 3.1 displays the following controls, depending on the properties:
|
519
|
+
// 1. {minValue: 0, maxValue: 1, minStep: 1} switch
|
520
|
+
// 2. {minValue: a, maxValue: b, minStep: 1}, 1 < b - a <= 20 down|up
|
521
|
+
// 3. {minValue: a, maxValue: b}, (a, b) != (0, 1) slider
|
522
|
+
// 4. {minValue: a, maxValue: b, minStep: 1}, b - a > 20 slider
|
523
|
+
// Avoid the following bugs:
|
524
|
+
// 5. {minValue: 0, maxValue: 1} nothing
|
525
|
+
// 6. {minValue: a, maxValue: b, minStep: 1}, b - a = 1 switch*
|
526
|
+
// *) switch sends values 0 and 1 instead of a and b;
|
527
|
+
if (min === 0 && max === 0) {
|
528
|
+
this.capabilities.readonly = true
|
529
|
+
} else if (min >= -127 && max <= 127 && min < max) {
|
530
|
+
if (min === 0 && max === 1) {
|
531
|
+
// Workaround Eve bug (case 5 above).
|
532
|
+
this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
|
533
|
+
} else if (max - min === 1) {
|
534
|
+
// Workaround Eve bug (case 6 above).
|
535
|
+
this.capabilities.props = { minValue: min, maxValue: max }
|
536
|
+
} else if (step !== 1) {
|
537
|
+
// Default to slider for backwards compatibility.
|
538
|
+
this.capabilities.props = { minValue: min, maxValue: max }
|
539
|
+
} else {
|
540
|
+
this.capabilities.props = { minValue: min, maxValue: max, minStep: 1 }
|
541
|
+
}
|
542
|
+
}
|
543
|
+
}
|
544
|
+
}
|
545
|
+
|
546
|
+
/** Patch a resource corresponding to a `Switch` service.
|
547
|
+
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
548
|
+
*/
|
549
|
+
patchSwitch (gateway) {
|
550
|
+
const buttons = []
|
551
|
+
let dots = false
|
552
|
+
switch (this.manufacturer) {
|
553
|
+
case 'Bitron Home':
|
554
|
+
switch (this.model) {
|
555
|
+
case '902010/23': // Bitron remote, see #639.
|
556
|
+
dots = true
|
557
|
+
buttons.push([1, 'Dim Up', SINGLE])
|
558
|
+
buttons.push([2, 'On', SINGLE])
|
559
|
+
buttons.push([3, 'Off', SINGLE])
|
560
|
+
buttons.push([4, 'Dim Down', SINGLE])
|
561
|
+
break
|
562
|
+
default:
|
563
|
+
break
|
564
|
+
}
|
565
|
+
break
|
566
|
+
case 'Busch-Jaeger':
|
567
|
+
switch (this.model) {
|
568
|
+
case 'RM01': // Busch-Jaeger Light Link control element (mains-powered)
|
569
|
+
case 'RB01': // Busch-Jaeger Light Link wall-mounted transmitter
|
570
|
+
if (this.endpoint === '0A') {
|
571
|
+
buttons.push([1, 'Button 1', SINGLE | LONG])
|
572
|
+
buttons.push([2, 'Button 2', SINGLE | LONG])
|
573
|
+
} else if (this.endpoint === '0B') {
|
574
|
+
buttons.push([3, 'Button 3', SINGLE | LONG])
|
575
|
+
buttons.push([4, 'Button 4', SINGLE | LONG])
|
576
|
+
} else if (this.endpoint === '0C') {
|
577
|
+
buttons.push([5, 'Button 5', SINGLE | LONG])
|
578
|
+
buttons.push([6, 'Button 6', SINGLE | LONG])
|
579
|
+
} else if (this.endpoint === '0D') {
|
580
|
+
buttons.push([7, 'Button 7', SINGLE | LONG])
|
581
|
+
buttons.push([8, 'Button 8', SINGLE | LONG])
|
582
|
+
}
|
583
|
+
break
|
584
|
+
default:
|
585
|
+
break
|
586
|
+
}
|
587
|
+
break
|
588
|
+
case 'Echostar':
|
589
|
+
switch (this.model) {
|
590
|
+
case 'Bell':
|
591
|
+
buttons.push([1, 'Front Doorbell', SINGLE])
|
592
|
+
buttons.push([2, 'Rear Doorbell', SINGLE])
|
593
|
+
break
|
594
|
+
default:
|
595
|
+
break
|
596
|
+
}
|
597
|
+
break
|
598
|
+
case 'ELKO':
|
599
|
+
switch (this.model) {
|
600
|
+
case 'ElkoDimmerRemoteZHA': // ELKO ESH 316 Endevender RF, see #922.
|
601
|
+
buttons.push([1, 'Press', SINGLE])
|
602
|
+
buttons.push([2, 'Dim Up', SINGLE])
|
603
|
+
buttons.push([3, 'Dim Down', SINGLE])
|
604
|
+
break
|
605
|
+
default:
|
606
|
+
break
|
607
|
+
}
|
608
|
+
break
|
609
|
+
case 'Heiman':
|
610
|
+
switch (this.model) {
|
611
|
+
case 'RC-EF-3.0':
|
612
|
+
dots = true
|
613
|
+
buttons.push([1, 'HomeMode', SINGLE])
|
614
|
+
buttons.push([2, 'Disarm', SINGLE])
|
615
|
+
buttons.push([3, 'SOS', SINGLE])
|
616
|
+
buttons.push([4, 'Arm', SINGLE])
|
617
|
+
break
|
618
|
+
default:
|
619
|
+
break
|
620
|
+
}
|
621
|
+
break
|
622
|
+
case 'IKEA of Sweden':
|
623
|
+
switch (this.model) {
|
624
|
+
case 'Remote Control N2':
|
625
|
+
buttons.push([1, 'Dim Up', SINGLE | LONG])
|
626
|
+
buttons.push([2, 'Dim Down', SINGLE | LONG])
|
627
|
+
buttons.push([3, 'Previous', SINGLE | LONG])
|
628
|
+
buttons.push([4, 'Next', SINGLE | LONG])
|
629
|
+
break
|
630
|
+
case 'SYMFONISK Sound Controller':
|
631
|
+
buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
|
632
|
+
if (this.body.mode === 1) {
|
633
|
+
buttons.push([2, 'Turn Right', LONG])
|
634
|
+
buttons.push([3, 'Turn Left', LONG])
|
635
|
+
} else {
|
636
|
+
buttons.push([2, 'Turn Right', SINGLE])
|
637
|
+
buttons.push([3, 'Turn Left', SINGLE])
|
638
|
+
}
|
639
|
+
break
|
640
|
+
case 'TRADFRI SHORTCUT Button':
|
641
|
+
buttons.push([1, 'Button', SINGLE | LONG])
|
642
|
+
break
|
643
|
+
case 'TRADFRI on/off switch':
|
644
|
+
buttons.push([1, 'On', SINGLE | LONG])
|
645
|
+
buttons.push([2, 'Off', SINGLE | LONG])
|
646
|
+
break
|
647
|
+
case 'TRADFRI open/close remote':
|
648
|
+
buttons.push([1, 'Open', SINGLE | LONG])
|
649
|
+
buttons.push([2, 'Close', SINGLE | LONG])
|
650
|
+
break
|
651
|
+
case 'TRADFRI remote control':
|
652
|
+
buttons.push([1, 'On/Off', SINGLE])
|
653
|
+
buttons.push([2, 'Dim Up', SINGLE | LONG])
|
654
|
+
buttons.push([3, 'Dim Down', SINGLE | LONG])
|
655
|
+
buttons.push([4, 'Previous', SINGLE | LONG])
|
656
|
+
buttons.push([5, 'Next', SINGLE | LONG])
|
657
|
+
break
|
658
|
+
case 'TRADFRI wireless dimmer':
|
659
|
+
if (this.obj.mode === 1) {
|
660
|
+
buttons.push([1, 'Turn Right', SINGLE | LONG])
|
661
|
+
buttons.push([2, 'Turn Left', SINGLE | LONG])
|
662
|
+
} else {
|
663
|
+
buttons.push([1, 'On', SINGLE])
|
664
|
+
buttons.push([2, 'Dim Up', SINGLE])
|
665
|
+
buttons.push([3, 'Dim Down', SINGLE])
|
666
|
+
buttons.push([4, 'Off', SINGLE])
|
667
|
+
}
|
668
|
+
break
|
669
|
+
default:
|
670
|
+
break
|
671
|
+
}
|
672
|
+
break
|
673
|
+
case 'Insta':
|
674
|
+
switch (this.model) {
|
675
|
+
case 'HS_4f_GJ_1': // Gira/Jung Light Link hand transmitter
|
676
|
+
case 'WS_3f_G_1': // Gira Light Link wall transmitter
|
677
|
+
case 'WS_4f_J_1': // Jung Light Link wall transmitter
|
678
|
+
buttons.push([1, 'Off', SINGLE | DOUBLE | LONG])
|
679
|
+
buttons.push([2, 'On', SINGLE | DOUBLE | LONG])
|
680
|
+
buttons.push([3, 'Scene 1', SINGLE])
|
681
|
+
buttons.push([4, 'Scene 2', SINGLE])
|
682
|
+
buttons.push([5, 'Scene 3', SINGLE])
|
683
|
+
buttons.push([6, 'Scene 4', SINGLE])
|
684
|
+
if (this.model !== 'WS_3f_G_1') {
|
685
|
+
buttons.push([7, 'Scene 5', SINGLE])
|
686
|
+
buttons.push([8, 'Scene 6', SINGLE])
|
687
|
+
}
|
688
|
+
break
|
689
|
+
default:
|
690
|
+
break
|
691
|
+
}
|
692
|
+
break
|
693
|
+
case 'LDS':
|
694
|
+
switch (this.model) {
|
695
|
+
case 'ZBT-DIMController-D0800':
|
696
|
+
buttons.push([1, 'On/Off', SINGLE])
|
697
|
+
buttons.push([2, 'Dim Up', SINGLE | LONG])
|
698
|
+
buttons.push([3, 'Dim Down', SINGLE | LONG])
|
699
|
+
buttons.push([4, 'Scene', SINGLE | LONG])
|
700
|
+
break
|
701
|
+
default:
|
702
|
+
break
|
703
|
+
}
|
704
|
+
break
|
705
|
+
case 'LIDL Livarno Lux':
|
706
|
+
switch (this.model) {
|
707
|
+
case 'HG06323':
|
708
|
+
buttons.push([1, 'On', SINGLE | DOUBLE | LONG])
|
709
|
+
buttons.push([2, 'Dim Up', SINGLE | LONG])
|
710
|
+
buttons.push([3, 'Dim Down', SINGLE | LONG])
|
711
|
+
buttons.push([4, 'Off', SINGLE])
|
712
|
+
break
|
713
|
+
default:
|
714
|
+
break
|
715
|
+
}
|
716
|
+
break
|
717
|
+
case 'LUMI':
|
718
|
+
switch (this.model) {
|
719
|
+
case 'lumi.ctrl_neutral1':
|
720
|
+
buttons.push([1, 'Button', SINGLE])
|
721
|
+
break
|
722
|
+
case 'lumi.ctrl_neutral2':
|
723
|
+
buttons.push([1, 'Left', SINGLE])
|
724
|
+
buttons.push([2, 'Right', SINGLE])
|
725
|
+
buttons.push([3, 'Both', SINGLE])
|
726
|
+
break
|
727
|
+
case 'lumi.remote.b1acn01':
|
728
|
+
case 'lumi.remote.b186acn01':
|
729
|
+
case 'lumi.remote.b186acn02':
|
730
|
+
buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
|
731
|
+
break
|
732
|
+
case 'lumi.remote.b28ac1':
|
733
|
+
case 'lumi.remote.b286acn01':
|
734
|
+
case 'lumi.remote.b286acn02':
|
735
|
+
buttons.push([1, 'Left', SINGLE | DOUBLE | LONG])
|
736
|
+
buttons.push([2, 'Right', SINGLE | DOUBLE | LONG])
|
737
|
+
buttons.push([3, 'Both', SINGLE | DOUBLE | LONG])
|
738
|
+
break
|
739
|
+
case 'lumi.remote.b286opcn01': // Xiaomi Aqara Opple, see #637.
|
740
|
+
case 'lumi.remote.b486opcn01': // Xiaomi Aqara Opple, see #637.
|
741
|
+
case 'lumi.remote.b686opcn01': // Xiaomi Aqara Opple, see #637.
|
742
|
+
buttons.push([1, '1', SINGLE | DOUBLE | LONG])
|
743
|
+
buttons.push([2, '2', SINGLE | DOUBLE | LONG])
|
744
|
+
if (this.model !== 'lumi.remote.b286opcn01') {
|
745
|
+
buttons.push([3, '3', SINGLE | DOUBLE | LONG])
|
746
|
+
buttons.push([4, '4', SINGLE | DOUBLE | LONG])
|
747
|
+
if (this.model === 'lumi.remote.b686opcn01') {
|
748
|
+
buttons.push([5, '5', SINGLE | DOUBLE | LONG])
|
749
|
+
buttons.push([6, '6', SINGLE | DOUBLE | LONG])
|
750
|
+
}
|
751
|
+
}
|
752
|
+
break
|
753
|
+
case 'lumi.sensor_86sw1': // Xiaomi wall switch (single button)
|
754
|
+
case 'lumi.ctrl_ln1.aq1':
|
755
|
+
buttons.push([1, 'Button', SINGLE | DOUBLE])
|
756
|
+
break
|
757
|
+
case 'lumi.sensor_86sw2': // Xiaomi wall switch (two buttons)
|
758
|
+
case 'lumi.ctrl_ln2.aq1':
|
759
|
+
buttons.push([1, 'Left', SINGLE | DOUBLE])
|
760
|
+
buttons.push([2, 'Right', SINGLE | DOUBLE])
|
761
|
+
buttons.push([3, 'Both', SINGLE | DOUBLE])
|
762
|
+
break
|
763
|
+
case 'lumi.sensor_cube':
|
764
|
+
case 'lumi.sensor_cube.aqgl01':
|
765
|
+
if (this.endpoint === '02') {
|
766
|
+
buttons.push([1, 'Side 1', SINGLE | DOUBLE | LONG])
|
767
|
+
buttons.push([2, 'Side 2', SINGLE | DOUBLE | LONG])
|
768
|
+
buttons.push([3, 'Side 3', SINGLE | DOUBLE | LONG])
|
769
|
+
buttons.push([4, 'Side 4', SINGLE | DOUBLE | LONG])
|
770
|
+
buttons.push([5, 'Side 5', SINGLE | DOUBLE | LONG])
|
771
|
+
buttons.push([6, 'Side 6', SINGLE | DOUBLE | LONG])
|
772
|
+
buttons.push([7, 'Cube', SINGLE | DOUBLE | LONG])
|
773
|
+
this.capabilities.toButtonEvent = (v) => {
|
774
|
+
const button = Math.floor(v / 1000)
|
775
|
+
let event = v % 1000
|
776
|
+
if (v === 7000) { // Wakeup
|
777
|
+
event = buttonEvent.SHORT_RELEASE
|
778
|
+
} else if (v === 7007 || v === 7008) { // Shake, Drop
|
779
|
+
} else if (event === 0) { // Push
|
780
|
+
event = buttonEvent.LONG_RELEASE
|
781
|
+
} else if (event === button) { // Double tap
|
782
|
+
event = buttonEvent.DOUBLE_PRESS
|
783
|
+
} else { // Flip
|
784
|
+
event = buttonEvent.SHORT_RELEASE
|
785
|
+
}
|
786
|
+
return button * 1000 + event
|
787
|
+
}
|
788
|
+
} else if (this.endpoint === '03') {
|
789
|
+
buttons.push([8, 'Turn Right', SINGLE | DOUBLE | LONG])
|
790
|
+
buttons.push([9, 'Turn Left', SINGLE | DOUBLE | LONG])
|
791
|
+
this.capabilities.toButtonEvent = (v) => {
|
792
|
+
const button = v > 0 ? 8 : 9
|
793
|
+
const event = Math.abs(v) < 4500
|
794
|
+
? buttonEvent.SHORT_RELEASE
|
795
|
+
: Math.abs(v) < 9000
|
796
|
+
? buttonEvent.DOUBLE_PRESS
|
797
|
+
: buttonEvent.LONG_RELEASE
|
798
|
+
return button * 1000 + event
|
799
|
+
}
|
800
|
+
}
|
801
|
+
break
|
802
|
+
case 'lumi.sensor_switch': // Xiaomi Mi wireless switch
|
803
|
+
case 'lumi.sensor_switch.aq2': // Xiaomi Aqara smart wireless switch
|
804
|
+
case 'lumi.sensor_switch.aq3': // Xiaomi Aqara smart wireless switch with gyro
|
805
|
+
buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
|
806
|
+
break
|
807
|
+
default:
|
808
|
+
break
|
809
|
+
}
|
810
|
+
break
|
811
|
+
case 'Lutron':
|
812
|
+
switch (this.model) {
|
813
|
+
case 'LZL4BWHL01 Remote': // Lutron Pico, see 102.
|
814
|
+
buttons.push([1, 'On', SINGLE])
|
815
|
+
buttons.push([2, 'Dim Up', LONG])
|
816
|
+
buttons.push([3, 'Dim Down', LONG])
|
817
|
+
buttons.push([4, 'Off', SINGLE])
|
818
|
+
break
|
819
|
+
case 'Z3-1BRL': // Lutron Aurora, see #522.
|
820
|
+
buttons.push([1, 'Button', SINGLE])
|
821
|
+
buttons.push([2, 'Turn Right', SINGLE])
|
822
|
+
buttons.push([3, 'Turn Left', SINGLE])
|
823
|
+
break
|
824
|
+
default:
|
825
|
+
break
|
826
|
+
}
|
827
|
+
break
|
828
|
+
case 'MLI':
|
829
|
+
switch (this.model) {
|
830
|
+
case 'ZBT-Remote-ALL-RGBW': // Tint remote control by Müller-Licht see deconz-rest-plugin#1209
|
831
|
+
buttons.push([1, 'On/Off', SINGLE])
|
832
|
+
buttons.push([2, 'Dim Up', SINGLE | LONG])
|
833
|
+
buttons.push([3, 'Dim Down', SINGLE | LONG])
|
834
|
+
buttons.push([4, 'Warm', SINGLE])
|
835
|
+
buttons.push([5, 'Cool', SINGLE])
|
836
|
+
buttons.push([6, 'Colour Wheel', SINGLE])
|
837
|
+
buttons.push([7, 'Work Light', SINGLE])
|
838
|
+
buttons.push([8, 'Sunset', SINGLE])
|
839
|
+
buttons.push([9, 'Party', SINGLE])
|
840
|
+
buttons.push([10, 'Night Light', SINGLE])
|
841
|
+
buttons.push([11, 'Campfire', SINGLE])
|
842
|
+
buttons.push([12, 'Romance', SINGLE])
|
843
|
+
break
|
844
|
+
default:
|
845
|
+
break
|
846
|
+
}
|
847
|
+
break
|
848
|
+
case 'OSRAM':
|
849
|
+
switch (this.model) {
|
850
|
+
case 'Lightify Switch Mini':
|
851
|
+
buttons.push([1, 'Up', SINGLE | LONG])
|
852
|
+
buttons.push([2, 'Down', SINGLE | LONG])
|
853
|
+
buttons.push([3, 'Middle', SINGLE | LONG])
|
854
|
+
break
|
855
|
+
default:
|
856
|
+
break
|
857
|
+
}
|
858
|
+
break
|
859
|
+
case 'Philips':
|
860
|
+
case 'Signify Netherlands B.V.':
|
861
|
+
switch (this.model) {
|
862
|
+
case 'RDM001': // Hue wall switch module
|
863
|
+
switch (this.body.config.devicemode) {
|
864
|
+
case 'singlerocker':
|
865
|
+
buttons.push([1, 'Rocker 1', SINGLE])
|
866
|
+
break
|
867
|
+
case 'singlepushbutton':
|
868
|
+
buttons.push([1, 'Push Button 1', SINGLE | LONG, true])
|
869
|
+
break
|
870
|
+
case 'dualrocker':
|
871
|
+
buttons.push([1, 'Rocker 1', SINGLE])
|
872
|
+
buttons.push([2, 'Rocker 2', SINGLE])
|
873
|
+
break
|
874
|
+
case 'dualpushbutton':
|
875
|
+
buttons.push([1, 'Push Button 1', SINGLE | LONG, true])
|
876
|
+
buttons.push([2, 'Push Button 2', SINGLE | LONG, true])
|
877
|
+
break
|
878
|
+
default:
|
879
|
+
break
|
880
|
+
}
|
881
|
+
break
|
882
|
+
case 'ROM001': // Hue smart button
|
883
|
+
buttons.push([1, 'Button', SINGLE | LONG, true])
|
884
|
+
break
|
885
|
+
case 'RWL020':
|
886
|
+
case 'RWL021': // Hue dimmer switch
|
887
|
+
buttons.push([1, 'On', SINGLE | LONG])
|
888
|
+
buttons.push([2, 'Dim Up', SINGLE | LONG, true])
|
889
|
+
buttons.push([3, 'Dim Down', SINGLE | LONG, true])
|
890
|
+
buttons.push([4, 'Off', SINGLE | LONG])
|
891
|
+
break
|
892
|
+
case 'RWL022': // Hue dimmer switch (2021)
|
893
|
+
buttons.push([1, 'On/Off', SINGLE | LONG])
|
894
|
+
buttons.push([2, 'Dim Up', SINGLE | LONG, true])
|
895
|
+
buttons.push([3, 'Dim Down', SINGLE | LONG, true])
|
896
|
+
buttons.push([4, 'Hue', SINGLE | LONG])
|
897
|
+
break
|
898
|
+
case 'ZGPSWITCH': // Hue tap
|
899
|
+
dots = true
|
900
|
+
buttons.push([1, '1', SINGLE])
|
901
|
+
buttons.push([2, '2', SINGLE])
|
902
|
+
buttons.push([3, '3', SINGLE])
|
903
|
+
buttons.push([4, '4', SINGLE])
|
904
|
+
buttons.push([5, '1 and 2', SINGLE])
|
905
|
+
buttons.push([6, '3 and 4', SINGLE])
|
906
|
+
this.capabilities.toButtonEvent = (v) => {
|
907
|
+
return hueTapMap[v]
|
908
|
+
}
|
909
|
+
break
|
910
|
+
default:
|
911
|
+
break
|
912
|
+
}
|
913
|
+
break
|
914
|
+
case 'PhilipsFoH':
|
915
|
+
if (this.model === 'FOHSWITCH') { // Friends-of-Hue switch
|
916
|
+
buttons.push([1, 'Top Left', SINGLE | LONG])
|
917
|
+
buttons.push([2, 'Bottom Left', SINGLE | LONG])
|
918
|
+
buttons.push([3, 'Top Right', SINGLE | LONG])
|
919
|
+
buttons.push([4, 'Bottom Right', SINGLE | LONG])
|
920
|
+
buttons.push([5, 'Top Both', SINGLE | LONG])
|
921
|
+
buttons.push([6, 'Bottom Both', SINGLE | LONG])
|
922
|
+
}
|
923
|
+
break
|
924
|
+
case 'Samjin':
|
925
|
+
switch (this.model) {
|
926
|
+
case 'button':
|
927
|
+
buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
|
928
|
+
break
|
929
|
+
default:
|
930
|
+
break
|
931
|
+
}
|
932
|
+
break
|
933
|
+
case 'Sunricher':
|
934
|
+
switch (this.model) {
|
935
|
+
case 'ZG2833K8_EU05': // Sunricher 8-button remote, see #529.
|
936
|
+
if (this.endpoint === '01') {
|
937
|
+
buttons.push([1, 'On 1', SINGLE | LONG])
|
938
|
+
buttons.push([2, 'Off 1', SINGLE | LONG])
|
939
|
+
} else if (this.endpoint === '02') {
|
940
|
+
buttons.push([3, 'On 2', SINGLE | LONG])
|
941
|
+
buttons.push([4, 'Off 2', SINGLE | LONG])
|
942
|
+
} else if (this.endpoint === '03') {
|
943
|
+
buttons.push([5, 'On 3', SINGLE | LONG])
|
944
|
+
buttons.push([6, 'Off 3', SINGLE | LONG])
|
945
|
+
} else if (this.endpoint === '04') {
|
946
|
+
buttons.push([7, 'On 4', SINGLE | LONG])
|
947
|
+
buttons.push([8, 'Off 4', SINGLE | LONG])
|
948
|
+
}
|
949
|
+
break
|
950
|
+
case 'ZG2833PAC': // Sunricher C4
|
951
|
+
buttons.push([1, 'Rocker 1', SINGLE])
|
952
|
+
buttons.push([2, 'Rocker 2', SINGLE])
|
953
|
+
buttons.push([3, 'Rocker 3', SINGLE])
|
954
|
+
buttons.push([4, 'Rocker 4', SINGLE])
|
955
|
+
break
|
956
|
+
case 'ZGRC-KEY-002': // Sunricher CCT remote, see #529.
|
957
|
+
buttons.push([1, 'On', SINGLE])
|
958
|
+
buttons.push([2, 'Off', SINGLE])
|
959
|
+
buttons.push([3, 'Dim', LONG])
|
960
|
+
buttons.push([4, 'C/W', SINGLE | LONG])
|
961
|
+
break
|
962
|
+
default:
|
963
|
+
break
|
964
|
+
}
|
965
|
+
break
|
966
|
+
case '_TZ3000_arfwfgoa':
|
967
|
+
switch (this.model) {
|
968
|
+
case 'TS0042': // Tuys 2-button switch, single endpoint
|
969
|
+
buttons.push([1, 'Left', SINGLE | DOUBLE | LONG])
|
970
|
+
buttons.push([2, 'Right', SINGLE | DOUBLE | LONG])
|
971
|
+
break
|
972
|
+
default:
|
973
|
+
break
|
974
|
+
}
|
975
|
+
break
|
976
|
+
case '_TZ3000_dfgbtub0':
|
977
|
+
case '_TZ3000_i3rjdrwu':
|
978
|
+
switch (this.model) {
|
979
|
+
case 'TS0042': // Tuya 2-button switch, see #1060.
|
980
|
+
if (this.endpoint === '01') {
|
981
|
+
buttons.push([1, 'Button 1', SINGLE | DOUBLE | LONG])
|
982
|
+
} else if (this.endpoint === '02') {
|
983
|
+
buttons.push([2, 'Button 2', SINGLE | DOUBLE | LONG])
|
984
|
+
}
|
985
|
+
break
|
986
|
+
default:
|
987
|
+
break
|
988
|
+
}
|
989
|
+
break
|
990
|
+
case '_TZ3000_pzui3skt':
|
991
|
+
switch (this.model) {
|
992
|
+
case 'TS0041': // Tuya 1-button switch
|
993
|
+
buttons.push([1, 'Button', SINGLE | DOUBLE | LONG])
|
994
|
+
break
|
995
|
+
default:
|
996
|
+
break
|
997
|
+
}
|
998
|
+
break
|
999
|
+
case '_TZ3000_rrjr1q0u':
|
1000
|
+
switch (this.model) {
|
1001
|
+
case 'TS0043': // Tuya 3-button switch
|
1002
|
+
buttons.push([1, 'Left', SINGLE | DOUBLE | LONG])
|
1003
|
+
buttons.push([2, 'Middle', SINGLE | DOUBLE | LONG])
|
1004
|
+
buttons.push([3, 'Right', SINGLE | DOUBLE | LONG])
|
1005
|
+
break
|
1006
|
+
default:
|
1007
|
+
break
|
1008
|
+
}
|
1009
|
+
break
|
1010
|
+
case '_TZ3000_vp6clf9d':
|
1011
|
+
switch (this.model) {
|
1012
|
+
case 'TS0044':
|
1013
|
+
buttons.push([1, 'Bottom Left', SINGLE | DOUBLE | LONG])
|
1014
|
+
buttons.push([2, 'Bottom Right', SINGLE | DOUBLE | LONG])
|
1015
|
+
buttons.push([3, 'Top Right', SINGLE | DOUBLE | LONG])
|
1016
|
+
buttons.push([4, 'Top Left', SINGLE | DOUBLE | LONG])
|
1017
|
+
break
|
1018
|
+
default:
|
1019
|
+
break
|
1020
|
+
}
|
1021
|
+
break
|
1022
|
+
case '_TZ3000_xabckq1v':
|
1023
|
+
switch (this.model) {
|
1024
|
+
case 'TS004F': // Tuya 4-button switch, single press only
|
1025
|
+
buttons.push([1, 'Top Left', SINGLE])
|
1026
|
+
buttons.push([2, 'Bottom Left', SINGLE])
|
1027
|
+
buttons.push([3, 'Top Right', SINGLE])
|
1028
|
+
buttons.push([4, 'Bottom Right', SINGLE])
|
1029
|
+
break
|
1030
|
+
default:
|
1031
|
+
break
|
1032
|
+
}
|
1033
|
+
break
|
1034
|
+
case 'dresden elektronik':
|
1035
|
+
switch (this.model) {
|
1036
|
+
case 'Kobold':
|
1037
|
+
buttons.push([1, 'Button', SINGLE | LONG])
|
1038
|
+
break
|
1039
|
+
case 'Lighting Switch':
|
1040
|
+
if (this.endpoint === '01') {
|
1041
|
+
if (this.body.mode !== 2) {
|
1042
|
+
gateway.vdebug(
|
1043
|
+
'%s: Lighting Switch mode %d instead of 2',
|
1044
|
+
this.rpath, this.body.mode
|
1045
|
+
)
|
1046
|
+
}
|
1047
|
+
buttons.push([1, 'Top Left', SINGLE | LONG])
|
1048
|
+
buttons.push([2, 'Bottom Left', SINGLE | LONG])
|
1049
|
+
buttons.push([3, 'Top Right', SINGLE | LONG])
|
1050
|
+
buttons.push([4, 'Bottom Right', SINGLE | LONG])
|
1051
|
+
}
|
1052
|
+
break
|
1053
|
+
case 'Scene Switch':
|
1054
|
+
buttons.push([1, 'On', SINGLE | LONG])
|
1055
|
+
buttons.push([2, 'Off', SINGLE | LONG])
|
1056
|
+
buttons.push([3, 'Scene 1', SINGLE])
|
1057
|
+
buttons.push([4, 'Scene 2', SINGLE])
|
1058
|
+
buttons.push([5, 'Scene 3', SINGLE])
|
1059
|
+
buttons.push([6, 'Scene 4', SINGLE])
|
1060
|
+
break
|
1061
|
+
default:
|
1062
|
+
break
|
1063
|
+
}
|
1064
|
+
break
|
1065
|
+
case 'eWeLink':
|
1066
|
+
switch (this.model) {
|
1067
|
+
case 'WB01':
|
1068
|
+
buttons.push([1, 'Press', SINGLE | DOUBLE | LONG])
|
1069
|
+
break
|
1070
|
+
default:
|
1071
|
+
break
|
1072
|
+
}
|
1073
|
+
break
|
1074
|
+
case 'icasa':
|
1075
|
+
switch (this.model) {
|
1076
|
+
case 'ICZB-KPD12':
|
1077
|
+
case 'ICZB-KPD14S':
|
1078
|
+
case 'ICZB-KPD18S':
|
1079
|
+
buttons.push([1, 'Off', SINGLE | LONG])
|
1080
|
+
buttons.push([2, 'On', SINGLE | LONG])
|
1081
|
+
if (this.model !== 'ICZB-KPD12') {
|
1082
|
+
buttons.push([3, 'S1', SINGLE])
|
1083
|
+
buttons.push([4, 'S2', SINGLE])
|
1084
|
+
if (this.model === 'ICZB-KPD18S') {
|
1085
|
+
buttons.push([5, 'S3', SINGLE])
|
1086
|
+
buttons.push([6, 'S4', SINGLE])
|
1087
|
+
buttons.push([7, 'S5', SINGLE])
|
1088
|
+
buttons.push([8, 'S6', SINGLE])
|
1089
|
+
}
|
1090
|
+
}
|
1091
|
+
break
|
1092
|
+
case 'ICZB-RM11S':
|
1093
|
+
buttons.push([1, '1 Off', SINGLE | LONG])
|
1094
|
+
buttons.push([2, '1 On', SINGLE | LONG])
|
1095
|
+
buttons.push([3, '2 Off', SINGLE | LONG])
|
1096
|
+
buttons.push([4, '2 On', SINGLE | LONG])
|
1097
|
+
buttons.push([5, '3 Off', SINGLE | LONG])
|
1098
|
+
buttons.push([6, '3 On', SINGLE | LONG])
|
1099
|
+
buttons.push([7, '4 Off', SINGLE | LONG])
|
1100
|
+
buttons.push([8, '4 On', SINGLE | LONG])
|
1101
|
+
buttons.push([9, 'S1', SINGLE])
|
1102
|
+
buttons.push([10, 'S2', SINGLE])
|
1103
|
+
break
|
1104
|
+
default:
|
1105
|
+
break
|
1106
|
+
}
|
1107
|
+
break
|
1108
|
+
case 'innr':
|
1109
|
+
switch (this.model) {
|
1110
|
+
case 'RC 110':
|
1111
|
+
if (this.endpoint === '01') {
|
1112
|
+
buttons.push([1, 'On/Off', SINGLE])
|
1113
|
+
buttons.push([2, 'Dim Up', SINGLE | LONG])
|
1114
|
+
buttons.push([3, 'Dim Down', SINGLE | LONG])
|
1115
|
+
buttons.push([4, '1', SINGLE])
|
1116
|
+
buttons.push([5, '2', SINGLE])
|
1117
|
+
buttons.push([6, '3', SINGLE])
|
1118
|
+
buttons.push([7, '4', SINGLE])
|
1119
|
+
buttons.push([8, '5', SINGLE])
|
1120
|
+
buttons.push([9, '6', SINGLE])
|
1121
|
+
for (let i = 1; i <= 6; i++) {
|
1122
|
+
const button = 7 + i * 3
|
1123
|
+
buttons.push([button, `On/Off ${i}`, SINGLE])
|
1124
|
+
buttons.push([button + 1, `Dim Up ${i}`, SINGLE | LONG])
|
1125
|
+
buttons.push([button + 2, `Dim Down ${i}`, SINGLE | LONG])
|
1126
|
+
}
|
1127
|
+
}
|
1128
|
+
break
|
1129
|
+
default:
|
1130
|
+
break
|
1131
|
+
}
|
1132
|
+
break
|
1133
|
+
case 'lk':
|
1134
|
+
switch (this.model) {
|
1135
|
+
case 'ZBT-DIMSwitch-D0001': // Linkind 1-Key Remote Control, see #949.
|
1136
|
+
buttons.push([1, 'Button', SINGLE | LONG])
|
1137
|
+
this.capabilities.homekitValue = (v) => { return 1 }
|
1138
|
+
break
|
1139
|
+
default:
|
1140
|
+
break
|
1141
|
+
}
|
1142
|
+
break
|
1143
|
+
case 'ubisys':
|
1144
|
+
switch (this.model) {
|
1145
|
+
case 'C4 (5504)':
|
1146
|
+
case 'C4-R (5604)':
|
1147
|
+
buttons.push([1, '1', SINGLE | LONG])
|
1148
|
+
buttons.push([2, '2', SINGLE | LONG])
|
1149
|
+
buttons.push([3, '3', SINGLE | LONG])
|
1150
|
+
buttons.push([4, '4', SINGLE | LONG])
|
1151
|
+
break
|
1152
|
+
case 'D1 (5503)':
|
1153
|
+
case 'D1-R (5603)':
|
1154
|
+
case 'S1-R (5601)':
|
1155
|
+
case 'S2 (5502)':
|
1156
|
+
case 'S2-R (5602)':
|
1157
|
+
buttons.push([1, '1', SINGLE | LONG])
|
1158
|
+
buttons.push([2, '2', SINGLE | LONG])
|
1159
|
+
break
|
1160
|
+
case 'S1 (5501)':
|
1161
|
+
buttons.push([1, '1', SINGLE | LONG])
|
1162
|
+
break
|
1163
|
+
default:
|
1164
|
+
break
|
1165
|
+
}
|
1166
|
+
break
|
1167
|
+
default:
|
1168
|
+
break
|
1169
|
+
}
|
1170
|
+
if (buttons.length > 0) {
|
1171
|
+
this.capabilities.buttons = {}
|
1172
|
+
for (const button of buttons) {
|
1173
|
+
this.capabilities.buttons[button[0]] = {
|
1174
|
+
label: button[1],
|
1175
|
+
events: button[2],
|
1176
|
+
hasRepeat: button[3]
|
1177
|
+
}
|
1178
|
+
}
|
1179
|
+
this.capabilities.namespace = dots
|
1180
|
+
? gateway.Characteristics.hap.ServiceLabelNamespace.DOTS
|
1181
|
+
: gateway.Characteristics.hap.ServiceLabelNamespace.ARABIC_NUMERALS
|
1182
|
+
}
|
1183
|
+
}
|
1184
|
+
|
1185
|
+
/** Patch a resource corresponding to a `Thermostat` service.
|
1186
|
+
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
1187
|
+
*/
|
1188
|
+
patchThermostat (gateway) {
|
1189
|
+
if (this.manufacturer === 'ELKO' && this.model === 'Super TR') {
|
1190
|
+
this.capbilities.heatValue = 'heat'
|
1191
|
+
} else {
|
1192
|
+
this.capbilities.heatValue = 'auto'
|
1193
|
+
}
|
1194
|
+
}
|
1195
|
+
|
1196
|
+
/** Patch a resource corresponding to a `WindowCovering` service.
|
1197
|
+
* @param {DeconzAccessory.Gateway} gateway - The gateway.
|
1198
|
+
*/
|
1199
|
+
patchWindowCovering (gateway) {
|
1200
|
+
if (this.manufacturer === 'ELKO' && this.model === 'Super TR') {
|
1201
|
+
this.capabilities.positionChange = true
|
1202
|
+
}
|
1203
|
+
}
|
1204
|
+
}
|
1205
|
+
|
1206
|
+
module.exports = Resource
|