@homebridge-plugins/homebridge-wemo 7.0.0
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/CHANGELOG.md +809 -0
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/config.schema.json +814 -0
- package/eslint.config.js +50 -0
- package/lib/connection/http.js +174 -0
- package/lib/connection/upnp.js +155 -0
- package/lib/device/coffee.js +167 -0
- package/lib/device/crockpot.js +380 -0
- package/lib/device/dimmer.js +280 -0
- package/lib/device/heater.js +416 -0
- package/lib/device/humidifier.js +379 -0
- package/lib/device/index.js +39 -0
- package/lib/device/insight.js +353 -0
- package/lib/device/lightswitch.js +154 -0
- package/lib/device/link-bulb.js +467 -0
- package/lib/device/link-hub.js +63 -0
- package/lib/device/maker-garage.js +426 -0
- package/lib/device/maker-switch.js +202 -0
- package/lib/device/motion.js +148 -0
- package/lib/device/outlet.js +148 -0
- package/lib/device/purifier.js +468 -0
- package/lib/device/simulation/purifier-insight.js +357 -0
- package/lib/device/simulation/purifier.js +159 -0
- package/lib/device/simulation/switch-insight.js +315 -0
- package/lib/device/simulation/switch.js +154 -0
- package/lib/fakegato/LICENSE +21 -0
- package/lib/fakegato/fakegato-history.js +814 -0
- package/lib/fakegato/fakegato-storage.js +108 -0
- package/lib/fakegato/fakegato-timer.js +125 -0
- package/lib/fakegato/uuid.js +27 -0
- package/lib/homebridge-ui/public/index.html +315 -0
- package/lib/homebridge-ui/server.js +10 -0
- package/lib/index.js +8 -0
- package/lib/platform.js +1423 -0
- package/lib/utils/colour.js +135 -0
- package/lib/utils/constants.js +129 -0
- package/lib/utils/eve-chars.js +130 -0
- package/lib/utils/functions.js +52 -0
- package/lib/utils/lang-en.js +125 -0
- package/package.json +70 -0
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateRandomString,
|
|
3
|
+
hasProperty,
|
|
4
|
+
parseError,
|
|
5
|
+
sleep,
|
|
6
|
+
} from '../utils/functions.js'
|
|
7
|
+
import platformLang from '../utils/lang-en.js'
|
|
8
|
+
|
|
9
|
+
export default class {
|
|
10
|
+
constructor(platform, accessory) {
|
|
11
|
+
// Set up variables from the platform
|
|
12
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
13
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
14
|
+
this.hapServ = platform.api.hap.Service
|
|
15
|
+
this.platform = platform
|
|
16
|
+
|
|
17
|
+
// Set up variables from the accessory
|
|
18
|
+
this.accessory = accessory
|
|
19
|
+
|
|
20
|
+
// Add the heater service if it doesn't already exist
|
|
21
|
+
this.service = this.accessory.getService(this.hapServ.HeaterCooler)
|
|
22
|
+
|| this.accessory.addService(this.hapServ.HeaterCooler)
|
|
23
|
+
|
|
24
|
+
// Add the set handler to the heater active characteristic
|
|
25
|
+
this.service
|
|
26
|
+
.getCharacteristic(this.hapChar.Active)
|
|
27
|
+
.removeOnSet()
|
|
28
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
29
|
+
|
|
30
|
+
// Add options to the heater target state characteristic
|
|
31
|
+
this.service.getCharacteristic(this.hapChar.TargetHeaterCoolerState).setProps({
|
|
32
|
+
minValue: 0,
|
|
33
|
+
maxValue: 0,
|
|
34
|
+
validValues: [0],
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
// Add the set handler and a range to the heater target temperature characteristic
|
|
38
|
+
this.service
|
|
39
|
+
.getCharacteristic(this.hapChar.HeatingThresholdTemperature)
|
|
40
|
+
.setProps({
|
|
41
|
+
minValue: 0,
|
|
42
|
+
maxValue: 24,
|
|
43
|
+
minStep: 0.5,
|
|
44
|
+
})
|
|
45
|
+
.onSet(async (value) => {
|
|
46
|
+
await this.internalCookingTimeUpdate(value)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Add the set handler to the heater rotation speed characteristic
|
|
50
|
+
this.service
|
|
51
|
+
.getCharacteristic(this.hapChar.RotationSpeed)
|
|
52
|
+
.setProps({ minStep: 33 })
|
|
53
|
+
.onSet(async (value) => {
|
|
54
|
+
await this.internalModeUpdate(value)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// Add a range to the heater current temperature characteristic
|
|
58
|
+
this.service.getCharacteristic(this.hapChar.CurrentTemperature).setProps({
|
|
59
|
+
minValue: 0,
|
|
60
|
+
maxValue: 24,
|
|
61
|
+
minStep: 0.5,
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Some conversion objects
|
|
65
|
+
this.modeLabels = {
|
|
66
|
+
0: platformLang.labelOff,
|
|
67
|
+
50: platformLang.labelWarm,
|
|
68
|
+
51: platformLang.labelLow,
|
|
69
|
+
52: platformLang.labelHigh,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Output the customised options to the log
|
|
73
|
+
const opts = JSON.stringify({
|
|
74
|
+
})
|
|
75
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
76
|
+
|
|
77
|
+
// Request a device update immediately
|
|
78
|
+
this.requestDeviceUpdate()
|
|
79
|
+
|
|
80
|
+
// Start a polling interval if the user has disabled upnp
|
|
81
|
+
if (this.accessory.context.connection === 'http') {
|
|
82
|
+
this.pollingInterval = setInterval(
|
|
83
|
+
() => this.requestDeviceUpdate(),
|
|
84
|
+
platform.config.pollingInterval * 1000,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async requestDeviceUpdate() {
|
|
90
|
+
try {
|
|
91
|
+
// Request the update
|
|
92
|
+
const data = await this.platform.httpClient.sendDeviceUpdate(
|
|
93
|
+
this.accessory,
|
|
94
|
+
'urn:Belkin:service:basicevent:1',
|
|
95
|
+
'GetCrockpotState',
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
// Check for existence since data.mode can be 0
|
|
99
|
+
if (hasProperty(data, 'mode')) {
|
|
100
|
+
// Log the receiving update if debug is enabled
|
|
101
|
+
this.accessory.logDebug(`${platformLang.recUpd} [mode: ${data.mode}]`)
|
|
102
|
+
|
|
103
|
+
// Send the data to the receiver function
|
|
104
|
+
this.externalModeUpdate(Number.parseInt(data.mode, 10))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// data.time can be 0 so check for existence
|
|
108
|
+
if (hasProperty(data, 'time')) {
|
|
109
|
+
// Log the receiving update if debug is enabled
|
|
110
|
+
this.accessory.logDebug(`${platformLang.recUpd} [time: ${data.time}]`)
|
|
111
|
+
|
|
112
|
+
// Send the data to the receiver function
|
|
113
|
+
this.externalTimeLeftUpdate(Number.parseInt(data.time, 10))
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
const eText = parseError(err, [
|
|
117
|
+
platformLang.timeout,
|
|
118
|
+
platformLang.timeoutUnreach,
|
|
119
|
+
platformLang.noService,
|
|
120
|
+
])
|
|
121
|
+
this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
receiveDeviceUpdate(attribute) {
|
|
126
|
+
// Log the receiving update if debug is enabled
|
|
127
|
+
this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
|
|
128
|
+
|
|
129
|
+
// Send a HomeKit needed true/false argument
|
|
130
|
+
// attribute.value is 0 if and only if the outlet is off
|
|
131
|
+
// this.externalStateUpdate(attribute.value !== 0)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async sendDeviceUpdate(mode, time) {
|
|
135
|
+
// Log the sending update if debug is enabled
|
|
136
|
+
this.accessory.logDebug(`${platformLang.senUpd} {"mode": ${mode}, "time": ${time}`)
|
|
137
|
+
|
|
138
|
+
// Send the update
|
|
139
|
+
await this.platform.httpClient.sendDeviceUpdate(
|
|
140
|
+
this.accessory,
|
|
141
|
+
'urn:Belkin:service:basicevent:1',
|
|
142
|
+
'SetCrockpotState',
|
|
143
|
+
{
|
|
144
|
+
mode: { '#text': mode },
|
|
145
|
+
time: { '#text': time },
|
|
146
|
+
},
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async internalStateUpdate(value) {
|
|
151
|
+
const prevState = this.service.getCharacteristic(this.hapChar.Active).value
|
|
152
|
+
try {
|
|
153
|
+
// Don't continue if the new value is the same as before
|
|
154
|
+
if (value === prevState) {
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// A slight pause seems to make Home app more responsive for characteristic updates later
|
|
159
|
+
await sleep(500)
|
|
160
|
+
|
|
161
|
+
// Note value === 0 is OFF, value === 1 is ON
|
|
162
|
+
if (value === 0) {
|
|
163
|
+
// Turn everything off
|
|
164
|
+
this.service.setCharacteristic(this.hapChar.RotationSpeed, 0)
|
|
165
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, 0)
|
|
166
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, 0)
|
|
167
|
+
this.accessory.context.cacheTime = 0
|
|
168
|
+
} else {
|
|
169
|
+
// Set rotation speed to the lowest ON value
|
|
170
|
+
this.service.setCharacteristic(this.hapChar.RotationSpeed, 33)
|
|
171
|
+
}
|
|
172
|
+
} catch (err) {
|
|
173
|
+
const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
|
|
174
|
+
this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
|
|
175
|
+
|
|
176
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
this.service.updateCharacteristic(this.hapChar.Active, prevState)
|
|
179
|
+
}, 2000)
|
|
180
|
+
throw new this.hapErr(-70402)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async internalModeUpdate(value) {
|
|
185
|
+
const prevSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
|
|
186
|
+
try {
|
|
187
|
+
// Avoid multiple updates in quick succession
|
|
188
|
+
const updateKeyMode = generateRandomString(5)
|
|
189
|
+
this.updateKeyMode = updateKeyMode
|
|
190
|
+
await sleep(500)
|
|
191
|
+
if (updateKeyMode !== this.updateKeyMode) {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Generate newValue for the needed mode and newSpeed in 33% multiples
|
|
196
|
+
let newValue = 0
|
|
197
|
+
let newSpeed = 0
|
|
198
|
+
if (value > 25 && value <= 50) {
|
|
199
|
+
newValue = 50
|
|
200
|
+
newSpeed = 33
|
|
201
|
+
} else if (value > 50 && value <= 75) {
|
|
202
|
+
newValue = 51
|
|
203
|
+
newSpeed = 66
|
|
204
|
+
} else if (value > 75) {
|
|
205
|
+
newValue = 52
|
|
206
|
+
newSpeed = 99
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Don't continue if the speed is the same as before
|
|
210
|
+
if (prevSpeed === newSpeed) {
|
|
211
|
+
return
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// A slight pause seems to make Home app more responsive for characteristic updates later
|
|
215
|
+
await sleep(500)
|
|
216
|
+
if ([0, 33].includes(newSpeed)) {
|
|
217
|
+
// Reset the cooking times to 0 if turned off or set to warm
|
|
218
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, 0)
|
|
219
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, 0)
|
|
220
|
+
this.accessory.context.cacheTime = 0
|
|
221
|
+
|
|
222
|
+
// Log the change if appropriate
|
|
223
|
+
this.accessory.log(`${platformLang.curTimer} [0:00]`)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Send the update
|
|
227
|
+
await this.sendDeviceUpdate(newValue, this.accessory.context.cacheTime)
|
|
228
|
+
|
|
229
|
+
// Update the cache and log if appropriate
|
|
230
|
+
this.cacheMode = newValue
|
|
231
|
+
this.accessory.log(`${platformLang.curMode} [${this.modeLabels[newValue]}]`)
|
|
232
|
+
} catch (err) {
|
|
233
|
+
const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
|
|
234
|
+
this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
|
|
235
|
+
|
|
236
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
237
|
+
setTimeout(() => {
|
|
238
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, prevSpeed)
|
|
239
|
+
}, 2000)
|
|
240
|
+
throw new this.hapErr(-70402)
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async internalCookingTimeUpdate(value) {
|
|
245
|
+
const prevTemp = this.service.getCharacteristic(this.hapChar.HeatingThresholdTemperature).value
|
|
246
|
+
try {
|
|
247
|
+
// Avoid multiple updates in quick succession
|
|
248
|
+
const updateKeyTemp = generateRandomString(5)
|
|
249
|
+
this.updateKeyTemp = updateKeyTemp
|
|
250
|
+
await sleep(500)
|
|
251
|
+
if (updateKeyTemp !== this.updateKeyTemp) {
|
|
252
|
+
return
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// The value is cooking hours, I don't think device can be set to cook for 24 hours as max
|
|
256
|
+
if (value === 24) {
|
|
257
|
+
value = 23.5
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Don't continue if the value is the same as before
|
|
261
|
+
if (value === prevTemp) {
|
|
262
|
+
return
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Find the needed mode based on the new value
|
|
266
|
+
const prevSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
|
|
267
|
+
let modeChange = this.cacheMode
|
|
268
|
+
// If cooking time is changed to above zero and mode is OFF or WARM, then set to LOW
|
|
269
|
+
if (value !== 0 && [0, 33].includes(prevSpeed)) {
|
|
270
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, 66)
|
|
271
|
+
modeChange = 51
|
|
272
|
+
this.cacheMode = 51
|
|
273
|
+
|
|
274
|
+
// Log the mode change if appropriate
|
|
275
|
+
this.accessory.log(`${platformLang.curMode} [${this.modeLabels[51]}]`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Send the update
|
|
279
|
+
const minutes = value * 60
|
|
280
|
+
await this.sendDeviceUpdate(modeChange, minutes)
|
|
281
|
+
|
|
282
|
+
// Log the change of cooking minutes if appropriate
|
|
283
|
+
const modMinutes = minutes % 60
|
|
284
|
+
this.accessory.log(`${platformLang.curTimer} [${Math.floor(value)}:${modMinutes >= 10 ? modMinutes : `0${modMinutes}`}]`)
|
|
285
|
+
} catch (err) {
|
|
286
|
+
const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
|
|
287
|
+
this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
|
|
288
|
+
|
|
289
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
290
|
+
setTimeout(() => {
|
|
291
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, prevTemp)
|
|
292
|
+
}, 2000)
|
|
293
|
+
throw new this.hapErr(-70402)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
externalModeUpdate(value) {
|
|
298
|
+
try {
|
|
299
|
+
// Don't continue if the given mode is the same as before
|
|
300
|
+
if (value === this.cacheMode) {
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Find the needed rotation speed based on the given mode
|
|
305
|
+
let rotSpeed = 0
|
|
306
|
+
switch (value) {
|
|
307
|
+
case 0:
|
|
308
|
+
break
|
|
309
|
+
case 50: {
|
|
310
|
+
rotSpeed = 33
|
|
311
|
+
break
|
|
312
|
+
}
|
|
313
|
+
case 51: {
|
|
314
|
+
rotSpeed = 66
|
|
315
|
+
break
|
|
316
|
+
}
|
|
317
|
+
case 52: {
|
|
318
|
+
rotSpeed = 99
|
|
319
|
+
break
|
|
320
|
+
}
|
|
321
|
+
default:
|
|
322
|
+
throw new Error(`Unknown value passed [${value} ${typeof value}]`)
|
|
323
|
+
}
|
|
324
|
+
// Update the HomeKit characteristics
|
|
325
|
+
this.service.updateCharacteristic(this.hapChar.Active, value !== 0 ? 1 : 0)
|
|
326
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, rotSpeed)
|
|
327
|
+
|
|
328
|
+
// Update the cache and log if appropriate
|
|
329
|
+
this.cacheMode = value
|
|
330
|
+
this.accessory.log(`${platformLang.curMode} [${this.modeLabels[value]}]`)
|
|
331
|
+
|
|
332
|
+
// If turned off then set the cooking time characteristics to 0
|
|
333
|
+
if (value === 0) {
|
|
334
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, 0)
|
|
335
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, 0)
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
externalTimeLeftUpdate(value) {
|
|
343
|
+
try {
|
|
344
|
+
// Don't continue if the rounded cooking time is the same as before
|
|
345
|
+
if (value === this.accessory.context.cacheTime) {
|
|
346
|
+
return
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// The value is passed in minutes (cooking time remaining)
|
|
350
|
+
let hkValue = 0
|
|
351
|
+
if (value > 0) {
|
|
352
|
+
/*
|
|
353
|
+
(1) convert to half-hour units (e.g. 159 -> 5.3)
|
|
354
|
+
(2) round to nearest 0.5 hour unit (e.g. 5.3 -> 5)
|
|
355
|
+
(3) if 0 then raise to 0.5 (as technically still cooking even if 1 minute)
|
|
356
|
+
*/
|
|
357
|
+
hkValue = Math.max(Math.round(value / 30) / 2, 0.5)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const rotSpeed = this.service.getCharacteristic(this.hapChar.RotationSpeed).value
|
|
361
|
+
|
|
362
|
+
// Change to LOW mode if cooking but cache is OFF
|
|
363
|
+
if (hkValue > 0 && rotSpeed === 0) {
|
|
364
|
+
this.service.updateCharacteristic(this.hapChar.RotationSpeed, 33)
|
|
365
|
+
this.cacheMode = 50
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Update the cooking time HomeKit characteristics
|
|
369
|
+
this.service.updateCharacteristic(this.hapChar.CurrentTemperature, hkValue)
|
|
370
|
+
this.service.updateCharacteristic(this.hapChar.HeatingThresholdTemperature, hkValue)
|
|
371
|
+
|
|
372
|
+
// Update the cache and log if appropriate
|
|
373
|
+
this.accessory.context.cacheTime = value
|
|
374
|
+
const modMinutes = value % 60
|
|
375
|
+
this.accessory.log(`${platformLang.curTimer} [${Math.floor(value / 60)}:${modMinutes >= 10 ? modMinutes : `0${modMinutes}`}]`)
|
|
376
|
+
} catch (err) {
|
|
377
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import platformConsts from '../utils/constants.js'
|
|
2
|
+
import {
|
|
3
|
+
generateRandomString,
|
|
4
|
+
hasProperty,
|
|
5
|
+
parseError,
|
|
6
|
+
sleep,
|
|
7
|
+
} from '../utils/functions.js'
|
|
8
|
+
import platformLang from '../utils/lang-en.js'
|
|
9
|
+
|
|
10
|
+
export default class {
|
|
11
|
+
constructor(platform, accessory) {
|
|
12
|
+
// Set up variables from the platform
|
|
13
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
14
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
15
|
+
this.hapServ = platform.api.hap.Service
|
|
16
|
+
this.platform = platform
|
|
17
|
+
|
|
18
|
+
// Set up variables from the accessory
|
|
19
|
+
this.accessory = accessory
|
|
20
|
+
|
|
21
|
+
// Set up custom variables for this device type
|
|
22
|
+
const deviceConf = platform.deviceConf[accessory.context.serialNumber] || {}
|
|
23
|
+
this.brightStep = deviceConf.brightnessStep
|
|
24
|
+
? Math.min(deviceConf.brightnessStep, 100)
|
|
25
|
+
: platformConsts.defaultValues.brightnessStep
|
|
26
|
+
|
|
27
|
+
// Add the lightbulb service if it doesn't already exist
|
|
28
|
+
this.service = accessory.getService(this.hapServ.Lightbulb) || accessory.addService(this.hapServ.Lightbulb)
|
|
29
|
+
|
|
30
|
+
// Add the set handler to the lightbulb on/off characteristic
|
|
31
|
+
this.service
|
|
32
|
+
.getCharacteristic(this.hapChar.On)
|
|
33
|
+
.removeOnSet()
|
|
34
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
35
|
+
|
|
36
|
+
// Add the set handler to the lightbulb brightness characteristic
|
|
37
|
+
this.service
|
|
38
|
+
.getCharacteristic(this.hapChar.Brightness)
|
|
39
|
+
.setProps({ minStep: this.brightStep })
|
|
40
|
+
.onSet(async (value) => {
|
|
41
|
+
await this.internalBrightnessUpdate(value)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// Output the customised options to the log
|
|
45
|
+
const opts = JSON.stringify({
|
|
46
|
+
brightnessStep: this.brightStep,
|
|
47
|
+
})
|
|
48
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
49
|
+
|
|
50
|
+
// Request a device update immediately
|
|
51
|
+
this.requestDeviceUpdate()
|
|
52
|
+
|
|
53
|
+
// Start a polling interval if the user has disabled upnp
|
|
54
|
+
if (this.accessory.context.connection === 'http') {
|
|
55
|
+
this.pollingInterval = setInterval(
|
|
56
|
+
() => this.requestDeviceUpdate(),
|
|
57
|
+
platform.config.pollingInterval * 1000,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
receiveDeviceUpdate(attribute) {
|
|
63
|
+
// Log the receiving update if debug is enabled
|
|
64
|
+
this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
|
|
65
|
+
|
|
66
|
+
// Check which attribute we are getting
|
|
67
|
+
switch (attribute.name) {
|
|
68
|
+
case 'BinaryState': {
|
|
69
|
+
// Send a HomeKit needed true/false argument
|
|
70
|
+
// attribute.value is 0 if and only if the device is off
|
|
71
|
+
const hkValue = attribute.value !== 0
|
|
72
|
+
this.externalSwitchUpdate(hkValue)
|
|
73
|
+
break
|
|
74
|
+
}
|
|
75
|
+
case 'Brightness':
|
|
76
|
+
// Send a HomeKit needed INT argument
|
|
77
|
+
this.externalBrightnessUpdate(attribute.value)
|
|
78
|
+
break
|
|
79
|
+
default:
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async sendDeviceUpdate(value) {
|
|
84
|
+
// Log the sending update if debug is enabled
|
|
85
|
+
this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
|
|
86
|
+
|
|
87
|
+
// Send the update
|
|
88
|
+
await this.platform.httpClient.sendDeviceUpdate(
|
|
89
|
+
this.accessory,
|
|
90
|
+
'urn:Belkin:service:basicevent:1',
|
|
91
|
+
'SetBinaryState',
|
|
92
|
+
value,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async requestDeviceUpdate() {
|
|
97
|
+
try {
|
|
98
|
+
// Request the update
|
|
99
|
+
const data = await this.platform.httpClient.sendDeviceUpdate(
|
|
100
|
+
this.accessory,
|
|
101
|
+
'urn:Belkin:service:basicevent:1',
|
|
102
|
+
'GetBinaryState',
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
// Check for existence since BinaryState can be int 0
|
|
106
|
+
if (hasProperty(data, 'BinaryState')) {
|
|
107
|
+
this.receiveDeviceUpdate({
|
|
108
|
+
name: 'BinaryState',
|
|
109
|
+
value: Number.parseInt(data.BinaryState, 10),
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for existence since brightness can be int 0
|
|
114
|
+
if (hasProperty(data, 'brightness')) {
|
|
115
|
+
this.receiveDeviceUpdate({
|
|
116
|
+
name: 'Brightness',
|
|
117
|
+
value: Number.parseInt(data.brightness, 10),
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const eText = parseError(err, [
|
|
122
|
+
platformLang.timeout,
|
|
123
|
+
platformLang.timeoutUnreach,
|
|
124
|
+
platformLang.noService,
|
|
125
|
+
])
|
|
126
|
+
this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async getCurrentBrightness() {
|
|
131
|
+
// A quick function to get the current brightness of the device
|
|
132
|
+
const data = await this.platform.httpClient.sendDeviceUpdate(
|
|
133
|
+
this.accessory,
|
|
134
|
+
'urn:Belkin:service:basicevent:1',
|
|
135
|
+
'GetBinaryState',
|
|
136
|
+
)
|
|
137
|
+
return Number.parseInt(data.brightness, 10)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async internalStateUpdate(value) {
|
|
141
|
+
try {
|
|
142
|
+
// Wait a longer time than the brightness so in scenes brightness is sent first
|
|
143
|
+
await sleep(500)
|
|
144
|
+
|
|
145
|
+
// Send the update
|
|
146
|
+
await this.sendDeviceUpdate({
|
|
147
|
+
BinaryState: value ? 1 : 0,
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
// Update the cache and log if appropriate
|
|
151
|
+
this.cacheState = value
|
|
152
|
+
this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
|
|
153
|
+
|
|
154
|
+
// Don't continue if turning the device off
|
|
155
|
+
if (!value) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Wrap the extra brightness request in another try, so it doesn't affect the on/off change
|
|
160
|
+
try {
|
|
161
|
+
// When turning the device on we want to update the HomeKit brightness
|
|
162
|
+
const updatedBrightness = await this.getCurrentBrightness()
|
|
163
|
+
|
|
164
|
+
// Don't continue if the brightness is the same
|
|
165
|
+
if (updatedBrightness === this.cacheBright) {
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Update the brightness characteristic
|
|
170
|
+
this.service.updateCharacteristic(this.hapChar.Brightness, updatedBrightness)
|
|
171
|
+
|
|
172
|
+
// Update the cache and log if appropriate
|
|
173
|
+
this.cacheBright = updatedBrightness
|
|
174
|
+
this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
|
|
175
|
+
} catch (err) {
|
|
176
|
+
this.accessory.logWarn(`${platformLang.brightnessFail} ${parseError(err)}`)
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
|
|
180
|
+
this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
|
|
181
|
+
|
|
182
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
183
|
+
setTimeout(() => {
|
|
184
|
+
this.service.updateCharacteristic(this.hapChar.On, this.cacheState)
|
|
185
|
+
}, 2000)
|
|
186
|
+
throw new this.hapErr(-70402)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async internalBrightnessUpdate(value) {
|
|
191
|
+
try {
|
|
192
|
+
// Avoid multiple updates in quick succession
|
|
193
|
+
const updateKey = generateRandomString(5)
|
|
194
|
+
this.updateKey = updateKey
|
|
195
|
+
await sleep(300)
|
|
196
|
+
if (updateKey !== this.updateKey) {
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Send the update
|
|
201
|
+
await this.sendDeviceUpdate({
|
|
202
|
+
BinaryState: value === 0 ? 0 : 1,
|
|
203
|
+
brightness: value,
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Update the cache and log if appropriate
|
|
207
|
+
this.cacheBright = value
|
|
208
|
+
this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
|
|
209
|
+
} catch (err) {
|
|
210
|
+
const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
|
|
211
|
+
this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
|
|
212
|
+
|
|
213
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
this.service.updateCharacteristic(this.hapChar.Brightness, this.cacheBright)
|
|
216
|
+
}, 2000)
|
|
217
|
+
throw new this.hapErr(-70402)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async externalSwitchUpdate(value) {
|
|
222
|
+
try {
|
|
223
|
+
// Don't continue if the value is the same as the cache
|
|
224
|
+
if (value === this.cacheState) {
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Update the ON/OFF characteristic
|
|
229
|
+
this.service.updateCharacteristic(this.hapChar.On, value)
|
|
230
|
+
|
|
231
|
+
// Update the cache and log if appropriate
|
|
232
|
+
this.cacheState = value
|
|
233
|
+
this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
|
|
234
|
+
|
|
235
|
+
// Don't continue if the new state is OFF
|
|
236
|
+
if (!value) {
|
|
237
|
+
return
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Wrap the extra brightness request in another try, so it doesn't affect the on/off change
|
|
241
|
+
try {
|
|
242
|
+
// If the new state is ON then we want to update the HomeKit brightness
|
|
243
|
+
const updatedBrightness = await this.getCurrentBrightness()
|
|
244
|
+
|
|
245
|
+
// Don't continue if the brightness is the same
|
|
246
|
+
if (updatedBrightness === this.cacheBright) {
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Update the HomeKit brightness characteristic
|
|
251
|
+
this.service.updateCharacteristic(this.hapChar.Brightness, updatedBrightness)
|
|
252
|
+
|
|
253
|
+
// Update the cache and log if appropriate
|
|
254
|
+
this.cacheBright = updatedBrightness
|
|
255
|
+
this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
|
|
256
|
+
} catch (err) {
|
|
257
|
+
this.accessory.logWarn(`${platformLang.brightnessFail} ${parseError(err)}`)
|
|
258
|
+
}
|
|
259
|
+
} catch (err) {
|
|
260
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
externalBrightnessUpdate(value) {
|
|
265
|
+
try {
|
|
266
|
+
// Don't continue if the brightness is the same as the cache value
|
|
267
|
+
if (value === this.cacheBright) {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
// Update the HomeKit brightness characteristic
|
|
271
|
+
this.service.updateCharacteristic(this.hapChar.Brightness, value)
|
|
272
|
+
|
|
273
|
+
// Update the cache and log if appropriate
|
|
274
|
+
this.cacheBright = value
|
|
275
|
+
this.accessory.log(`${platformLang.curBright} [${this.cacheBright}%]`)
|
|
276
|
+
} catch (err) {
|
|
277
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|