@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,426 @@
|
|
|
1
|
+
import { parseStringPromise } from 'xml2js'
|
|
2
|
+
|
|
3
|
+
import platformConsts from '../utils/constants.js'
|
|
4
|
+
import { decodeXML, parseError, sleep } from '../utils/functions.js'
|
|
5
|
+
import platformLang from '../utils/lang-en.js'
|
|
6
|
+
|
|
7
|
+
export default class {
|
|
8
|
+
constructor(platform, accessory) {
|
|
9
|
+
// Set up variables from the platform
|
|
10
|
+
this.eveChar = platform.eveChar
|
|
11
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
12
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
13
|
+
this.hapServ = platform.api.hap.Service
|
|
14
|
+
this.platform = platform
|
|
15
|
+
|
|
16
|
+
// Set up variables from the accessory
|
|
17
|
+
this.accessory = accessory
|
|
18
|
+
|
|
19
|
+
// Set up custom variables for this device type
|
|
20
|
+
const deviceConf = platform.deviceConf[accessory.context.serialNumber] || {}
|
|
21
|
+
this.doorOpenTimer = deviceConf.makerTimer || platformConsts.defaultValues.makerTimer
|
|
22
|
+
|
|
23
|
+
// Some conversion objects
|
|
24
|
+
this.gStates = {
|
|
25
|
+
Open: 0,
|
|
26
|
+
Closed: 1,
|
|
27
|
+
Opening: 2,
|
|
28
|
+
Closing: 3,
|
|
29
|
+
Stopped: 4,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If the accessory has a switch service then remove it
|
|
33
|
+
if (this.accessory.getService(this.hapServ.Switch)) {
|
|
34
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// If the accessory has a contact sensor service then remove it
|
|
38
|
+
if (this.accessory.getService(this.hapServ.ContactSensor)) {
|
|
39
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.ContactSensor))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add the garage door service if it doesn't already exist
|
|
43
|
+
this.service = this.accessory.getService(this.hapServ.GarageDoorOpener)
|
|
44
|
+
if (!this.service) {
|
|
45
|
+
this.service = this.accessory.addService(this.hapServ.GarageDoorOpener)
|
|
46
|
+
this.service.addCharacteristic(this.eveChar.LastActivation)
|
|
47
|
+
this.service.addCharacteristic(this.eveChar.ResetTotal)
|
|
48
|
+
this.service.addCharacteristic(this.eveChar.TimesOpened)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Remove unused characteristics
|
|
52
|
+
if (this.service.testCharacteristic(this.hapChar.ContactSensorState)) {
|
|
53
|
+
this.service.removeCharacteristic(
|
|
54
|
+
this.service.getCharacteristic(this.hapChar.ContactSensorState),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
if (this.service.testCharacteristic(this.eveChar.OpenDuration)) {
|
|
58
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(this.eveChar.OpenDuration))
|
|
59
|
+
}
|
|
60
|
+
if (this.service.testCharacteristic(this.eveChar.ClosedDuration)) {
|
|
61
|
+
this.service.removeCharacteristic(this.service.getCharacteristic(this.eveChar.ClosedDuration))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Add the set handler to the garage door reset total characteristic
|
|
65
|
+
this.service.getCharacteristic(this.eveChar.ResetTotal).onSet(() => {
|
|
66
|
+
this.service.updateCharacteristic(this.eveChar.TimesOpened, 0)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// Add the set handler to the target door state characteristic
|
|
70
|
+
this.service
|
|
71
|
+
.getCharacteristic(this.hapChar.TargetDoorState)
|
|
72
|
+
.removeOnSet()
|
|
73
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
74
|
+
|
|
75
|
+
// Pass the accessory to Fakegato to set up with Eve
|
|
76
|
+
this.accessory.eveService = new platform.eveService('door', this.accessory, {
|
|
77
|
+
log: () => {},
|
|
78
|
+
})
|
|
79
|
+
this.accessory.eveService.addEntry({
|
|
80
|
+
status: this.service.getCharacteristic(this.hapChar.CurrentDoorState).value === 0 ? 0 : 1,
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Output the customised options to the log
|
|
84
|
+
const opts = JSON.stringify({
|
|
85
|
+
makerTimer: this.doorOpenTimer,
|
|
86
|
+
})
|
|
87
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
88
|
+
|
|
89
|
+
// This is to remove the 'No Response' message that is there before the plugin finds this device
|
|
90
|
+
this.service.updateCharacteristic(
|
|
91
|
+
this.hapChar.TargetDoorState,
|
|
92
|
+
this.accessory.context.cacheLastTargetState,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
// Request a device update immediately
|
|
96
|
+
this.requestDeviceUpdate()
|
|
97
|
+
|
|
98
|
+
// Start a polling interval if the user has disabled upnp
|
|
99
|
+
if (this.accessory.context.connection === 'http') {
|
|
100
|
+
this.pollingInterval = setInterval(
|
|
101
|
+
() => this.requestDeviceUpdate(),
|
|
102
|
+
platform.config.pollingInterval * 1000,
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
receiveDeviceUpdate(attribute) {
|
|
108
|
+
// Log the receiving update if debug is enabled
|
|
109
|
+
this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
|
|
110
|
+
|
|
111
|
+
// Check which attribute we are getting
|
|
112
|
+
switch (attribute.name) {
|
|
113
|
+
case 'Switch': {
|
|
114
|
+
if (attribute.value !== 0) {
|
|
115
|
+
this.externalStateUpdate()
|
|
116
|
+
}
|
|
117
|
+
break
|
|
118
|
+
}
|
|
119
|
+
case 'Sensor': {
|
|
120
|
+
this.externalSensorUpdate(attribute.value, true)
|
|
121
|
+
break
|
|
122
|
+
}
|
|
123
|
+
default:
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async sendDeviceUpdate(value) {
|
|
128
|
+
// Log the sending update if debug is enabled
|
|
129
|
+
this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
|
|
130
|
+
|
|
131
|
+
// Send the update
|
|
132
|
+
await this.platform.httpClient.sendDeviceUpdate(
|
|
133
|
+
this.accessory,
|
|
134
|
+
'urn:Belkin:service:basicevent:1',
|
|
135
|
+
'SetBinaryState',
|
|
136
|
+
value,
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async requestDeviceUpdate() {
|
|
141
|
+
try {
|
|
142
|
+
// Request the update
|
|
143
|
+
const data = await this.platform.httpClient.sendDeviceUpdate(
|
|
144
|
+
this.accessory,
|
|
145
|
+
'urn:Belkin:service:deviceevent:1',
|
|
146
|
+
'GetAttributes',
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
// Parse the response
|
|
150
|
+
const decoded = decodeXML(data.attributeList)
|
|
151
|
+
const xml = `<attributeList>${decoded}</attributeList>`
|
|
152
|
+
const result = await parseStringPromise(xml, { explicitArray: false })
|
|
153
|
+
const attributes = {}
|
|
154
|
+
Object.keys(result.attributeList.attribute).forEach((key) => {
|
|
155
|
+
const attribute = result.attributeList.attribute[key]
|
|
156
|
+
attributes[attribute.name] = Number.parseInt(attribute.value, 10)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// Only send the required attributes to the receiveDeviceUpdate function
|
|
160
|
+
if (attributes.SwitchMode === 0) {
|
|
161
|
+
this.accessory.logWarn(platformLang.makerNeedMMode)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
if (attributes.SensorPresent === 1) {
|
|
165
|
+
this.sensorPresent = true
|
|
166
|
+
this.externalSensorUpdate(attributes.Sensor)
|
|
167
|
+
} else {
|
|
168
|
+
this.sensorPresent = false
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
const eText = parseError(err, [
|
|
172
|
+
platformLang.timeout,
|
|
173
|
+
platformLang.timeoutUnreach,
|
|
174
|
+
platformLang.noService,
|
|
175
|
+
])
|
|
176
|
+
this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async internalStateUpdate(value) {
|
|
181
|
+
const prevTarg = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
|
|
182
|
+
const prevCurr = this.service.getCharacteristic(this.hapChar.CurrentDoorState).value
|
|
183
|
+
try {
|
|
184
|
+
// Checks to see if the new required movement is already happening
|
|
185
|
+
if (this.isMoving) {
|
|
186
|
+
if (value === this.gStates.Closed && prevCurr === this.gStates.Closing) {
|
|
187
|
+
this.accessory.log(platformLang.makerClosing)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
if (value === this.gStates.Open && prevCurr === this.gStates.Opening) {
|
|
191
|
+
this.accessory.log(platformLang.makerOpening)
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
} else if (value === this.gStates.Closed && prevCurr === this.gStates.Closed) {
|
|
195
|
+
this.accessory.log(platformLang.makerClosed)
|
|
196
|
+
return
|
|
197
|
+
} else if (value === this.gStates.Open && prevCurr === this.gStates.Open) {
|
|
198
|
+
this.accessory.log(platformLang.makerOpen)
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Required movement isn't already in progress so make the new movement happen
|
|
203
|
+
this.homekitTriggered = true
|
|
204
|
+
|
|
205
|
+
// Send the update
|
|
206
|
+
await this.sendDeviceUpdate({
|
|
207
|
+
BinaryState: 1,
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// Log the change if appropriate
|
|
211
|
+
this.accessory.log(`${platformLang.tarState} [${value ? platformLang.labelClosed : platformLang.labelOpen}]`)
|
|
212
|
+
|
|
213
|
+
// Call the function to set the door moving
|
|
214
|
+
this.accessory.context.cacheLastTargetState = value
|
|
215
|
+
this.setDoorMoving(value, true)
|
|
216
|
+
} catch (err) {
|
|
217
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
218
|
+
|
|
219
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, prevTarg)
|
|
222
|
+
this.accessory.context.cacheLastTargetState = prevTarg
|
|
223
|
+
}, 2000)
|
|
224
|
+
throw new this.hapErr(-70402)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
externalStateUpdate() {
|
|
229
|
+
try {
|
|
230
|
+
// We want to ignore update notifications from when controlled through HomeKit
|
|
231
|
+
if (this.homekitTriggered) {
|
|
232
|
+
this.homekitTriggered = false
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// The change of state must have been triggered externally
|
|
237
|
+
const target = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
|
|
238
|
+
const state = 1 - target
|
|
239
|
+
this.accessory.log(
|
|
240
|
+
`${platformLang.tarState} [${state === 1 ? platformLang.labelClosed : platformLang.labelOpen}] [${platformLang.makerTrigExt}]`,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
// Update the new target state HomeKit characteristic
|
|
244
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, state)
|
|
245
|
+
this.accessory.context.cacheLastTargetState = state
|
|
246
|
+
|
|
247
|
+
// If the door has been opened externally then update the Eve-only characteristics
|
|
248
|
+
if (state === 0) {
|
|
249
|
+
this.accessory.eveService.addEntry({ status: 0 })
|
|
250
|
+
this.service.updateCharacteristic(
|
|
251
|
+
this.eveChar.LastActivation,
|
|
252
|
+
Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
|
|
253
|
+
)
|
|
254
|
+
this.service.updateCharacteristic(
|
|
255
|
+
this.eveChar.TimesOpened,
|
|
256
|
+
this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
this.setDoorMoving(state)
|
|
260
|
+
} catch (err) {
|
|
261
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
externalSensorUpdate(state, wasTriggered) {
|
|
266
|
+
try {
|
|
267
|
+
// 0->1 and 1->0 reverse values to match HomeKit needs
|
|
268
|
+
const value = 1 - state
|
|
269
|
+
const target = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
|
|
270
|
+
if (target === 0) {
|
|
271
|
+
// CASE target is to OPEN
|
|
272
|
+
if (value === 0) {
|
|
273
|
+
// Garage door HK target state is OPEN and the sensor has reported OPEN
|
|
274
|
+
if (this.isMoving) {
|
|
275
|
+
// Garage door is in the process of opening
|
|
276
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Opening)
|
|
277
|
+
this.accessory.eveService.addEntry({ status: 0 })
|
|
278
|
+
this.service.updateCharacteristic(
|
|
279
|
+
this.eveChar.LastActivation,
|
|
280
|
+
Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
|
|
281
|
+
)
|
|
282
|
+
this.service.updateCharacteristic(
|
|
283
|
+
this.eveChar.TimesOpened,
|
|
284
|
+
this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
// Log the change if appropriate
|
|
288
|
+
this.accessory.log(`${platformLang.curState} [${platformLang.labelOpening}]`)
|
|
289
|
+
} else {
|
|
290
|
+
// Garage door is open and not moving
|
|
291
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Open)
|
|
292
|
+
|
|
293
|
+
// Log the change if appropriate
|
|
294
|
+
this.accessory.log(`${platformLang.curState} [${platformLang.labelOpen}]`)
|
|
295
|
+
}
|
|
296
|
+
} else {
|
|
297
|
+
// Garage door HK target state is OPEN and the sensor has reported CLOSED
|
|
298
|
+
// Must have been triggered externally
|
|
299
|
+
this.isMoving = false
|
|
300
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, this.gStates.Closed)
|
|
301
|
+
this.accessory.context.cacheLastTargetState = this.gStates.Closed
|
|
302
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Closed)
|
|
303
|
+
this.accessory.eveService.addEntry({ status: 1 })
|
|
304
|
+
|
|
305
|
+
// Log the change if appropriate
|
|
306
|
+
this.accessory.log(`${platformLang.curState} [${platformLang.labelClosed}] [${platformLang.makerTrigExt}]`)
|
|
307
|
+
}
|
|
308
|
+
} else if (value === 1) {
|
|
309
|
+
// Garage door HK target state is CLOSED and the sensor has reported CLOSED
|
|
310
|
+
this.isMoving = false
|
|
311
|
+
if (this.movingTimer) {
|
|
312
|
+
clearTimeout(this.movingTimer)
|
|
313
|
+
this.movingTimer = false
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Update the HomeKit characteristics
|
|
317
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Closed)
|
|
318
|
+
this.accessory.eveService.addEntry({ status: 1 })
|
|
319
|
+
|
|
320
|
+
// Log the change if appropriate
|
|
321
|
+
this.accessory.log(`${platformLang.curState} [${platformLang.labelClosed}]`)
|
|
322
|
+
} else {
|
|
323
|
+
// Garage door HK target state is CLOSED but the sensor has reported OPEN
|
|
324
|
+
// Must have been triggered externally
|
|
325
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, this.gStates.Open)
|
|
326
|
+
this.accessory.context.cacheLastTargetState = this.gStates.Open
|
|
327
|
+
this.accessory.eveService.addEntry({ status: 0 })
|
|
328
|
+
this.service.updateCharacteristic(
|
|
329
|
+
this.eveChar.LastActivation,
|
|
330
|
+
Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
|
|
331
|
+
)
|
|
332
|
+
this.service.updateCharacteristic(
|
|
333
|
+
this.eveChar.TimesOpened,
|
|
334
|
+
this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
// Log the change if appropriate
|
|
338
|
+
this.accessory.log(`${platformLang.tarState} [${platformLang.labelOpen}] [${platformLang.makerTrigExt}]`)
|
|
339
|
+
if (wasTriggered) {
|
|
340
|
+
this.setDoorMoving(0)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async setDoorMoving(targetDoorState, homekitTriggered) {
|
|
349
|
+
// If a moving timer already exists then stop it
|
|
350
|
+
if (this.movingTimer) {
|
|
351
|
+
clearTimeout(this.movingTimer)
|
|
352
|
+
this.movingTimer = false
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// The door must have stopped
|
|
356
|
+
if (this.isMoving) {
|
|
357
|
+
this.isMoving = false
|
|
358
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, 4)
|
|
359
|
+
this.accessory.log(`${platformLang.curState} [${platformLang.labelStopped}]`)
|
|
360
|
+
|
|
361
|
+
// Toggle TargetDoorState after receiving a stop
|
|
362
|
+
await sleep(500)
|
|
363
|
+
const target = targetDoorState === this.gStates.Open ? this.gStates.Closed : this.gStates.Open
|
|
364
|
+
this.service.updateCharacteristic(this.hapChar.TargetDoorState, target)
|
|
365
|
+
this.accessory.context.cacheLastTargetState = target
|
|
366
|
+
return
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Set the moving flag to true
|
|
370
|
+
this.isMoving = true
|
|
371
|
+
if (homekitTriggered) {
|
|
372
|
+
// CASE: triggered through HomeKit
|
|
373
|
+
const curState = this.service.getCharacteristic(this.hapChar.CurrentDoorState).value
|
|
374
|
+
if (targetDoorState === this.gStates.Closed) {
|
|
375
|
+
// CASE: triggered through HomeKit and requested to CLOSE
|
|
376
|
+
if (curState !== this.gStates.Closed) {
|
|
377
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Closing)
|
|
378
|
+
|
|
379
|
+
// Log the change if appropriate
|
|
380
|
+
this.accessory.log(`${platformLang.curState} [${platformLang.labelClosing}]`)
|
|
381
|
+
}
|
|
382
|
+
} else if (
|
|
383
|
+
curState === this.gStates.Stopped
|
|
384
|
+
|| (curState !== this.gStates.Open && !this.sensorPresent)
|
|
385
|
+
) {
|
|
386
|
+
// CASE: triggered through HomeKit and requested to OPEN
|
|
387
|
+
this.service.updateCharacteristic(this.hapChar.CurrentDoorState, this.gStates.Opening)
|
|
388
|
+
this.accessory.eveService.addEntry({ status: 0 })
|
|
389
|
+
this.service.updateCharacteristic(
|
|
390
|
+
this.eveChar.LastActivation,
|
|
391
|
+
Math.round(new Date().valueOf() / 1000) - this.accessory.eveService.getInitialTime(),
|
|
392
|
+
)
|
|
393
|
+
this.service.updateCharacteristic(
|
|
394
|
+
this.eveChar.TimesOpened,
|
|
395
|
+
this.service.getCharacteristic(this.eveChar.TimesOpened).value + 1,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
// Log the change if appropriate
|
|
399
|
+
this.accessory.log(`${platformLang.curState} [${platformLang.labelOpening}]`)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Set up the moving timer
|
|
404
|
+
this.movingTimer = setTimeout(() => {
|
|
405
|
+
this.movingTimer = false
|
|
406
|
+
this.isMoving = false
|
|
407
|
+
const target = this.service.getCharacteristic(this.hapChar.TargetDoorState).value
|
|
408
|
+
if (!this.sensorPresent) {
|
|
409
|
+
this.service.updateCharacteristic(
|
|
410
|
+
this.hapChar.CurrentDoorState,
|
|
411
|
+
target === 1 ? this.gStates.Closed : this.gStates.Open,
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
// Log the change if appropriate
|
|
415
|
+
this.accessory.log(`${platformLang.curState} [${target === 1 ? platformLang.labelClosed : platformLang.labelOpen}]`)
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
if (target === 1) {
|
|
419
|
+
this.accessory.eveService.addEntry({ status: 1 })
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Request a device update at the end of the timer
|
|
423
|
+
this.requestDeviceUpdate()
|
|
424
|
+
}, this.doorOpenTimer * 1000)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { parseStringPromise } from 'xml2js'
|
|
2
|
+
|
|
3
|
+
import { decodeXML, parseError } from '../utils/functions.js'
|
|
4
|
+
import platformLang from '../utils/lang-en.js'
|
|
5
|
+
|
|
6
|
+
export default class {
|
|
7
|
+
constructor(platform, accessory) {
|
|
8
|
+
// Set up variables from the platform
|
|
9
|
+
this.hapChar = platform.api.hap.Characteristic
|
|
10
|
+
this.hapErr = platform.api.hap.HapStatusError
|
|
11
|
+
this.hapServ = platform.api.hap.Service
|
|
12
|
+
this.platform = platform
|
|
13
|
+
|
|
14
|
+
// Set up variables from the accessory
|
|
15
|
+
this.accessory = accessory
|
|
16
|
+
|
|
17
|
+
// Set up custom variables for this device type
|
|
18
|
+
const deviceConf = platform.deviceConf[accessory.context.serialNumber] || {}
|
|
19
|
+
this.reversePolarity = deviceConf.reversePolarity
|
|
20
|
+
|
|
21
|
+
// If the accessory has a garage door service then remove it
|
|
22
|
+
if (this.accessory.getService(this.hapServ.GarageDoorOpener)) {
|
|
23
|
+
this.accessory.removeService(this.accessory.getService(this.hapServ.GarageDoorOpener))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Add the switch service if it doesn't already exist
|
|
27
|
+
this.service = this.accessory.getService(this.hapServ.Switch)
|
|
28
|
+
|| this.accessory.addService(this.hapServ.Switch)
|
|
29
|
+
|
|
30
|
+
// This is used to remove any no response status on startup
|
|
31
|
+
this.service.updateCharacteristic(
|
|
32
|
+
this.hapChar.On,
|
|
33
|
+
this.service.getCharacteristic(this.hapChar.On).value || false,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
// Add the set handler to the switch on/off characteristic
|
|
37
|
+
this.service
|
|
38
|
+
.getCharacteristic(this.hapChar.On)
|
|
39
|
+
.removeOnSet()
|
|
40
|
+
.onSet(async value => this.internalStateUpdate(value))
|
|
41
|
+
|
|
42
|
+
// Output the customised options to the log
|
|
43
|
+
const opts = JSON.stringify({
|
|
44
|
+
})
|
|
45
|
+
platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
|
|
46
|
+
|
|
47
|
+
// Request a device update immediately
|
|
48
|
+
this.requestDeviceUpdate()
|
|
49
|
+
|
|
50
|
+
// Start a polling interval if the user has disabled upnp
|
|
51
|
+
if (this.accessory.context.connection === 'http') {
|
|
52
|
+
this.pollingInterval = setInterval(
|
|
53
|
+
() => this.requestDeviceUpdate(),
|
|
54
|
+
platform.config.pollingInterval * 1000,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
receiveDeviceUpdate(attribute) {
|
|
60
|
+
// Log the receiving update if debug is enabled
|
|
61
|
+
this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
|
|
62
|
+
|
|
63
|
+
// Check which attribute we are getting
|
|
64
|
+
switch (attribute.name) {
|
|
65
|
+
case 'Switch': {
|
|
66
|
+
const hkValue = attribute.value === 1
|
|
67
|
+
this.externalStateUpdate(hkValue)
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
case 'Sensor':
|
|
71
|
+
this.externalSensorUpdate(attribute.value)
|
|
72
|
+
break
|
|
73
|
+
default:
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async sendDeviceUpdate(value) {
|
|
78
|
+
// Log the sending update if debug is enabled
|
|
79
|
+
this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
|
|
80
|
+
|
|
81
|
+
// Send the update
|
|
82
|
+
await this.platform.httpClient.sendDeviceUpdate(
|
|
83
|
+
this.accessory,
|
|
84
|
+
'urn:Belkin:service:basicevent:1',
|
|
85
|
+
'SetBinaryState',
|
|
86
|
+
value,
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async requestDeviceUpdate() {
|
|
91
|
+
try {
|
|
92
|
+
// Request the update
|
|
93
|
+
const data = await this.platform.httpClient.sendDeviceUpdate(
|
|
94
|
+
this.accessory,
|
|
95
|
+
'urn:Belkin:service:deviceevent:1',
|
|
96
|
+
'GetAttributes',
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
// Parse the response
|
|
100
|
+
const decoded = decodeXML(data.attributeList)
|
|
101
|
+
const xml = `<attributeList>${decoded}</attributeList>`
|
|
102
|
+
const result = await parseStringPromise(xml, { explicitArray: false })
|
|
103
|
+
const attributes = {}
|
|
104
|
+
Object.keys(result.attributeList.attribute).forEach((key) => {
|
|
105
|
+
const attribute = result.attributeList.attribute[key]
|
|
106
|
+
attributes[attribute.name] = Number.parseInt(attribute.value, 10)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
// Only send the required attributes to the receiveDeviceUpdate function
|
|
110
|
+
if (attributes.Switch) {
|
|
111
|
+
this.externalStateUpdate(attributes.Switch === 1)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check to see if the accessory has a contact sensor
|
|
115
|
+
const contactSensor = this.accessory.getService(this.hapServ.ContactSensor)
|
|
116
|
+
if (attributes.SensorPresent === 1) {
|
|
117
|
+
// Add a contact sensor service if the physical device has one
|
|
118
|
+
if (!contactSensor) {
|
|
119
|
+
this.accessory.addService(this.hapServ.ContactSensor)
|
|
120
|
+
}
|
|
121
|
+
if (attributes.Sensor) {
|
|
122
|
+
this.externalSensorUpdate(attributes.Sensor)
|
|
123
|
+
}
|
|
124
|
+
} else if (contactSensor) {
|
|
125
|
+
// Remove the contact sensor service if the physical device doesn't have one
|
|
126
|
+
this.accessory.removeService(contactSensor)
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const eText = parseError(err, [
|
|
130
|
+
platformLang.timeout,
|
|
131
|
+
platformLang.timeoutUnreach,
|
|
132
|
+
platformLang.noService,
|
|
133
|
+
])
|
|
134
|
+
this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async internalStateUpdate(value) {
|
|
139
|
+
try {
|
|
140
|
+
// Send the update
|
|
141
|
+
await this.sendDeviceUpdate({
|
|
142
|
+
BinaryState: value ? 1 : 0,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Update the cache and log if appropriate
|
|
146
|
+
this.cacheState = value
|
|
147
|
+
this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
|
|
150
|
+
this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
|
|
151
|
+
|
|
152
|
+
// Throw a 'no response' error and set a timeout to revert this after 2 seconds
|
|
153
|
+
setTimeout(() => {
|
|
154
|
+
this.service.updateCharacteristic(this.hapChar.On, this.cacheState)
|
|
155
|
+
}, 2000)
|
|
156
|
+
throw new this.hapErr(-70402)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
externalStateUpdate(value) {
|
|
161
|
+
try {
|
|
162
|
+
// Don't continue if the value is the same as before
|
|
163
|
+
if (value === this.cacheState) {
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Update the HomeKit characteristic
|
|
168
|
+
this.service.updateCharacteristic(this.hapChar.On, value)
|
|
169
|
+
|
|
170
|
+
// Update the cache and log if appropriate
|
|
171
|
+
this.cacheState = value
|
|
172
|
+
this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
|
|
173
|
+
} catch (err) {
|
|
174
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
externalSensorUpdate(value) {
|
|
179
|
+
try {
|
|
180
|
+
// Reverse the polarity if enabled by user
|
|
181
|
+
if (this.reversePolarity) {
|
|
182
|
+
value = 1 - value
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Don't continue if the sensor value is the same as before
|
|
186
|
+
if (value === this.cacheContact) {
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Update the HomeKit characteristic
|
|
191
|
+
this.accessory
|
|
192
|
+
.getService(this.hapServ.ContactSensor)
|
|
193
|
+
.updateCharacteristic(this.hapChar.ContactSensorState, value)
|
|
194
|
+
|
|
195
|
+
// Update the cache and log the change if appropriate
|
|
196
|
+
this.cacheContact = value
|
|
197
|
+
this.accessory.log(`${platformLang.curCont} [${value === 1 ? platformLang.detectedNo : platformLang.detectedYes}]`)
|
|
198
|
+
} catch (err) {
|
|
199
|
+
this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|