@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.
Files changed (41) hide show
  1. package/CHANGELOG.md +809 -0
  2. package/LICENSE +21 -0
  3. package/README.md +66 -0
  4. package/config.schema.json +814 -0
  5. package/eslint.config.js +50 -0
  6. package/lib/connection/http.js +174 -0
  7. package/lib/connection/upnp.js +155 -0
  8. package/lib/device/coffee.js +167 -0
  9. package/lib/device/crockpot.js +380 -0
  10. package/lib/device/dimmer.js +280 -0
  11. package/lib/device/heater.js +416 -0
  12. package/lib/device/humidifier.js +379 -0
  13. package/lib/device/index.js +39 -0
  14. package/lib/device/insight.js +353 -0
  15. package/lib/device/lightswitch.js +154 -0
  16. package/lib/device/link-bulb.js +467 -0
  17. package/lib/device/link-hub.js +63 -0
  18. package/lib/device/maker-garage.js +426 -0
  19. package/lib/device/maker-switch.js +202 -0
  20. package/lib/device/motion.js +148 -0
  21. package/lib/device/outlet.js +148 -0
  22. package/lib/device/purifier.js +468 -0
  23. package/lib/device/simulation/purifier-insight.js +357 -0
  24. package/lib/device/simulation/purifier.js +159 -0
  25. package/lib/device/simulation/switch-insight.js +315 -0
  26. package/lib/device/simulation/switch.js +154 -0
  27. package/lib/fakegato/LICENSE +21 -0
  28. package/lib/fakegato/fakegato-history.js +814 -0
  29. package/lib/fakegato/fakegato-storage.js +108 -0
  30. package/lib/fakegato/fakegato-timer.js +125 -0
  31. package/lib/fakegato/uuid.js +27 -0
  32. package/lib/homebridge-ui/public/index.html +315 -0
  33. package/lib/homebridge-ui/server.js +10 -0
  34. package/lib/index.js +8 -0
  35. package/lib/platform.js +1423 -0
  36. package/lib/utils/colour.js +135 -0
  37. package/lib/utils/constants.js +129 -0
  38. package/lib/utils/eve-chars.js +130 -0
  39. package/lib/utils/functions.js +52 -0
  40. package/lib/utils/lang-en.js +125 -0
  41. package/package.json +70 -0
@@ -0,0 +1,357 @@
1
+ import platformConsts from '../../utils/constants.js'
2
+ import { hasProperty, parseError } from '../../utils/functions.js'
3
+ import platformLang from '../../utils/lang-en.js'
4
+
5
+ export default class {
6
+ constructor(platform, accessory) {
7
+ // Set up variables from the platform
8
+ this.eveChar = platform.eveChar
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.showTodayTC = deviceConf.showTodayTC
20
+ this.wattDiff = deviceConf.wattDiff || platformConsts.defaultValues.wattDiff
21
+ this.timeDiff = deviceConf.timeDiff || platformConsts.defaultValues.timeDiff
22
+ if (this.timeDiff === 1) {
23
+ this.timeDiff = false
24
+ }
25
+ this.skipTimeDiff = false
26
+
27
+ if (!hasProperty(this.accessory.context, 'cacheLastWM')) {
28
+ this.accessory.context.cacheLastWM = 0
29
+ }
30
+ if (!hasProperty(this.accessory.context, 'cacheLastTC')) {
31
+ this.accessory.context.cacheLastTC = 0
32
+ }
33
+ if (!hasProperty(this.accessory.context, 'cacheTotalTC')) {
34
+ this.accessory.context.cacheTotalTC = 0
35
+ }
36
+
37
+ // If the accessory has an outlet service then remove it
38
+ if (this.accessory.getService(this.hapServ.Outlet)) {
39
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Outlet))
40
+ }
41
+
42
+ // If the accessory has a switch service then remove it
43
+ if (this.accessory.getService(this.hapServ.Switch)) {
44
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
45
+ }
46
+
47
+ // Add the purifier service if it doesn't already exist
48
+ this.service = this.accessory.getService(this.hapServ.AirPurifier)
49
+ || this.accessory.addService(this.hapServ.AirPurifier)
50
+
51
+ // Add the Eve power characteristics
52
+ if (!this.service.testCharacteristic(this.eveChar.CurrentConsumption)) {
53
+ this.service.addCharacteristic(this.eveChar.CurrentConsumption)
54
+ }
55
+ if (!this.service.testCharacteristic(this.eveChar.TotalConsumption)) {
56
+ this.service.addCharacteristic(this.eveChar.TotalConsumption)
57
+ }
58
+ if (!this.service.testCharacteristic(this.eveChar.ResetTotal)) {
59
+ this.service.addCharacteristic(this.eveChar.ResetTotal)
60
+ }
61
+
62
+ // Add the set handler to the purifier active characteristic
63
+ this.service
64
+ .getCharacteristic(this.hapChar.Active)
65
+ .removeOnSet()
66
+ .onSet(async value => this.internalStateUpdate(value))
67
+
68
+ // Add options to the purifier target state characteristic
69
+ this.service.getCharacteristic(this.hapChar.TargetAirPurifierState).setProps({
70
+ minValue: 1,
71
+ maxValue: 1,
72
+ validValues: [1],
73
+ })
74
+ this.service.updateCharacteristic(this.hapChar.TargetAirPurifierState, 1)
75
+
76
+ // Add the set handler to the switch reset (eve) characteristic
77
+ this.service.getCharacteristic(this.eveChar.ResetTotal).onSet(() => {
78
+ this.accessory.context.cacheLastWM = 0
79
+ this.accessory.context.cacheLastTC = 0
80
+ this.accessory.context.cacheTotalTC = 0
81
+ this.service.updateCharacteristic(this.eveChar.TotalConsumption, 0)
82
+ })
83
+
84
+ // Pass the accessory to fakegato to set up the Eve info service
85
+ this.accessory.historyService = new platform.eveService('energy', this.accessory, {
86
+ log: () => {},
87
+ })
88
+
89
+ // Output the customised options to the log
90
+ const opts = JSON.stringify({
91
+ showAs: 'purifier',
92
+ showTodayTC: this.showTodayTC,
93
+ timeDiff: this.timeDiff,
94
+ wattDiff: this.wattDiff,
95
+ })
96
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
97
+
98
+ // Request a device update immediately
99
+ this.requestDeviceUpdate()
100
+
101
+ // Start a polling interval if the user has disabled upnp
102
+ if (this.accessory.context.connection === 'http') {
103
+ this.pollingInterval = setInterval(
104
+ () => this.requestDeviceUpdate(),
105
+ platform.config.pollingInterval * 1000,
106
+ )
107
+ }
108
+ }
109
+
110
+ receiveDeviceUpdate(attribute) {
111
+ // Log the receiving update if debug is enabled
112
+ this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
113
+
114
+ // Let's see which attribute has been provided
115
+ switch (attribute.name) {
116
+ case 'BinaryState': {
117
+ // BinaryState is reported as 0=off, 1=on, 8=standby
118
+ // Send a HomeKit needed 1/0 argument (0=0, 1,8=1)
119
+ this.externalStateUpdate(attribute.value === 0 ? 0 : 1)
120
+ break
121
+ }
122
+ case 'InsightParams':
123
+ // Send the insight data straight to the function
124
+ this.externalInsightUpdate(
125
+ attribute.value.state,
126
+ attribute.value.power,
127
+ attribute.value.todayWm,
128
+ attribute.value.todayOnSeconds,
129
+ )
130
+ break
131
+ default:
132
+ }
133
+ }
134
+
135
+ async sendDeviceUpdate(value) {
136
+ // Log the sending update if debug is enabled
137
+ this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
138
+
139
+ // Send the update
140
+ await this.platform.httpClient.sendDeviceUpdate(
141
+ this.accessory,
142
+ 'urn:Belkin:service:basicevent:1',
143
+ 'SetBinaryState',
144
+ value,
145
+ )
146
+ }
147
+
148
+ async requestDeviceUpdate() {
149
+ try {
150
+ // Request the update
151
+ const data = await this.platform.httpClient.sendDeviceUpdate(
152
+ this.accessory,
153
+ 'urn:Belkin:service:basicevent:1',
154
+ 'GetBinaryState',
155
+ )
156
+
157
+ // Check for existence since BinaryState can be int 0
158
+ if (hasProperty(data, 'BinaryState')) {
159
+ this.receiveDeviceUpdate({
160
+ name: 'BinaryState',
161
+ value: Number.parseInt(data.BinaryState, 10),
162
+ })
163
+ }
164
+ } catch (err) {
165
+ const eText = parseError(err, [
166
+ platformLang.timeout,
167
+ platformLang.timeoutUnreach,
168
+ platformLang.noService,
169
+ ])
170
+ this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
171
+ }
172
+ }
173
+
174
+ async internalStateUpdate(value) {
175
+ try {
176
+ // Send the update
177
+ await this.sendDeviceUpdate({
178
+ BinaryState: value ? 1 : 0,
179
+ })
180
+
181
+ // Update the cache value
182
+ this.cacheState = value
183
+
184
+ // Log the change if appropriate
185
+ this.accessory.log(`${platformLang.curState} [${value ? platformLang.purifyYes : platformLang.purifyNo}]`)
186
+
187
+ // If turning the switch off then update the purifying state and current consumption
188
+ if (!value) {
189
+ // Update the HomeKit characteristics
190
+ this.service.updateCharacteristic(this.eveChar.CurrentConsumption, 0)
191
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, 0)
192
+
193
+ // Add an Eve entry for no power
194
+ this.accessory.historyService.addEntry({ power: 0 })
195
+
196
+ // Log the change if appropriate
197
+ this.accessory.log(`${platformLang.curCons} [0W]`)
198
+ } else {
199
+ // Set the current state to purifying
200
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, 2)
201
+ }
202
+ } catch (err) {
203
+ const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
204
+ this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
205
+
206
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
207
+ setTimeout(() => {
208
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState)
209
+ }, 2000)
210
+ throw new this.hapErr(-70402)
211
+ }
212
+ }
213
+
214
+ externalInsightUpdate(value, power, todayWm, todayOnSeconds) {
215
+ // Update whether the switch is ON (value=1) or OFF (value=0)
216
+ this.externalStateUpdate(value === 0 ? 0 : 1)
217
+
218
+ // Update whether the outlet-in-use is YES (value=1) or NO (value=0,8)
219
+ this.externalInUseUpdate(value === 1)
220
+
221
+ // Update the total consumption
222
+ this.externalTotalConsumptionUpdate(todayWm, todayOnSeconds)
223
+
224
+ // Update the current consumption
225
+ this.externalConsumptionUpdate(power)
226
+ }
227
+
228
+ externalStateUpdate(value) {
229
+ try {
230
+ // Check to see if the cache value is different
231
+ if (value === this.cacheState) {
232
+ return
233
+ }
234
+
235
+ // Update the HomeKit characteristics
236
+ this.service.updateCharacteristic(this.hapChar.Active, value)
237
+
238
+ // Update the cache value
239
+ this.cacheState = value
240
+
241
+ // Log the change if appropriate
242
+ this.accessory.log(`${platformLang.curState} [${value ? platformLang.purifyYes : platformLang.purifyNo}]`)
243
+
244
+ // If the device has turned off then update the current consumption
245
+ if (!value) {
246
+ this.externalConsumptionUpdate(0)
247
+ }
248
+ } catch (err) {
249
+ // Catch any errors
250
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
251
+ }
252
+ }
253
+
254
+ externalInUseUpdate(value) {
255
+ try {
256
+ // Check to see if the cache value is different
257
+ if (value === this.cacheInUse) {
258
+ return
259
+ }
260
+
261
+ // Update the HomeKit characteristic
262
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value ? 2 : 1)
263
+
264
+ // Update the cache value
265
+ this.cacheInUse = value
266
+
267
+ // Log the change if appropriate
268
+ this.accessory.log(`${platformLang.curState} [${value ? platformLang.purifyYes : platformLang.purifyNo}]`)
269
+ } catch (err) {
270
+ // Catch any errors
271
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
272
+ }
273
+ }
274
+
275
+ externalConsumptionUpdate(power) {
276
+ try {
277
+ // Divide by 1000 to get the power value in W
278
+ const powerInWatts = Math.round(power / 1000)
279
+
280
+ // Check to see if the cache value is different
281
+ if (powerInWatts === this.cachePowerInWatts) {
282
+ return
283
+ }
284
+
285
+ // Update the power in watts cache
286
+ this.cachePowerInWatts = powerInWatts
287
+
288
+ // Update the HomeKit characteristic
289
+ this.service.updateCharacteristic(this.eveChar.CurrentConsumption, this.cachePowerInWatts)
290
+
291
+ // Add the Eve wattage entry
292
+ this.accessory.historyService.addEntry({ power: this.cachePowerInWatts })
293
+
294
+ // Calculate a difference from the last reading
295
+ const diff = Math.abs(powerInWatts - this.cachePowerInWatts)
296
+
297
+ // Don't continue with logging if the user has set a timeout between entries or a min difference between entries
298
+ if (!this.skipTimeDiff && diff >= this.wattDiff) {
299
+ // Log the change if appropriate
300
+ this.accessory.log(`${platformLang.curCons} [${this.cachePowerInWatts}W]`)
301
+
302
+ // Set the time difference timeout if needed
303
+ if (this.timeDiff) {
304
+ this.skipTimeDiff = true
305
+ setTimeout(() => {
306
+ this.skipTimeDiff = false
307
+ }, this.timeDiff * 1000)
308
+ }
309
+ }
310
+ } catch (err) {
311
+ // Catch any errors
312
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
313
+ }
314
+ }
315
+
316
+ externalTotalConsumptionUpdate(todayWm, todayOnSeconds) {
317
+ try {
318
+ if (todayWm === this.accessory.context.cacheLastWM) {
319
+ return
320
+ }
321
+
322
+ // Update the cache last value
323
+ this.accessory.context.cacheLastWM = todayWm
324
+
325
+ // Convert to Wh (hours) from raw data of Wm (minutes)
326
+ const todayWh = Math.round(todayWm / 60000)
327
+
328
+ // Convert to kWh
329
+ const todaykWh = todayWh / 1000
330
+
331
+ // Convert to hours, minutes and seconds (HH:MM:SS)
332
+ const todayOnHours = new Date(todayOnSeconds * 1000).toISOString().substr(11, 8)
333
+
334
+ // Calculate the difference (ie extra usage from the last reading)
335
+ const difference = Math.max(todaykWh - this.accessory.context.cacheLastTC, 0)
336
+
337
+ // Update the caches
338
+ this.accessory.context.cacheTotalTC += difference
339
+ this.accessory.context.cacheLastTC = todaykWh
340
+
341
+ // Update the total consumption characteristic
342
+ this.service.updateCharacteristic(
343
+ this.eveChar.TotalConsumption,
344
+ this.showTodayTC ? todaykWh : this.accessory.context.cacheTotalTC,
345
+ )
346
+
347
+ if (!this.skipTimeDiff) {
348
+ this.accessory.log(
349
+ `${platformLang.insOnTime} [${todayOnHours}] ${platformLang.insCons} [${todaykWh.toFixed(3)} kWh] ${platformLang.insTC} [${this.accessory.context.cacheTotalTC.toFixed(3)} kWh]`,
350
+ )
351
+ }
352
+ } catch (err) {
353
+ // Catch any errors
354
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
355
+ }
356
+ }
357
+ }
@@ -0,0 +1,159 @@
1
+ import { hasProperty, parseError } from '../../utils/functions.js'
2
+ import platformLang from '../../utils/lang-en.js'
3
+
4
+ export default class {
5
+ constructor(platform, accessory) {
6
+ // Set up variables from the platform
7
+ this.hapChar = platform.api.hap.Characteristic
8
+ this.hapErr = platform.api.hap.HapStatusError
9
+ this.hapServ = platform.api.hap.Service
10
+ this.platform = platform
11
+
12
+ // Set up variables from the accessory
13
+ this.accessory = accessory
14
+
15
+ // If the accessory has an outlet service then remove it
16
+ if (this.accessory.getService(this.hapServ.Outlet)) {
17
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Outlet))
18
+ }
19
+
20
+ // If the accessory has a switch service then remove it
21
+ if (this.accessory.getService(this.hapServ.Switch)) {
22
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Switch))
23
+ }
24
+
25
+ // Add the air purifier service if it doesn't already exist
26
+ this.service = this.accessory.getService(this.hapServ.AirPurifier)
27
+ || this.accessory.addService(this.hapServ.AirPurifier)
28
+
29
+ // Add the set handler to the purifier active characteristic
30
+ this.service
31
+ .getCharacteristic(this.hapChar.Active)
32
+ .removeOnSet()
33
+ .onSet(async value => this.internalStateUpdate(value))
34
+
35
+ // Add options to the purifier target state characteristic
36
+ this.service.getCharacteristic(this.hapChar.TargetAirPurifierState).setProps({
37
+ minValue: 1,
38
+ maxValue: 1,
39
+ validValues: [1],
40
+ })
41
+ this.service.updateCharacteristic(this.hapChar.TargetAirPurifierState, 1)
42
+
43
+ // Output the customised options to the log
44
+ const opts = JSON.stringify({
45
+ showAs: 'purifier',
46
+ })
47
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
48
+
49
+ // Request a device update immediately
50
+ this.requestDeviceUpdate()
51
+
52
+ // Start a polling interval if the user has disabled upnp
53
+ if (this.accessory.context.connection === 'http') {
54
+ this.pollingInterval = setInterval(
55
+ () => this.requestDeviceUpdate(),
56
+ platform.config.pollingInterval * 1000,
57
+ )
58
+ }
59
+ }
60
+
61
+ receiveDeviceUpdate(attribute) {
62
+ // Log the receiving update if debug is enabled
63
+ this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
64
+
65
+ // Send a HomeKit needed 1/0 argument
66
+ // attribute.value is 0 if and only if the switch is off
67
+ this.externalStateUpdate(attribute.value)
68
+ }
69
+
70
+ async sendDeviceUpdate(value) {
71
+ // Log the sending update if debug is enabled
72
+ this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
73
+
74
+ // Send the update
75
+ await this.platform.httpClient.sendDeviceUpdate(
76
+ this.accessory,
77
+ 'urn:Belkin:service:basicevent:1',
78
+ 'SetBinaryState',
79
+ value,
80
+ )
81
+ }
82
+
83
+ async requestDeviceUpdate() {
84
+ try {
85
+ // Request the update
86
+ const data = await this.platform.httpClient.sendDeviceUpdate(
87
+ this.accessory,
88
+ 'urn:Belkin:service:basicevent:1',
89
+ 'GetBinaryState',
90
+ )
91
+
92
+ // Check for existence since BinaryState can be int 0
93
+ if (hasProperty(data, 'BinaryState')) {
94
+ // Send the data to the receiver function
95
+ this.receiveDeviceUpdate({
96
+ name: 'BinaryState',
97
+ value: Number.parseInt(data.BinaryState, 10),
98
+ })
99
+ }
100
+ } catch (err) {
101
+ const eText = parseError(err, [
102
+ platformLang.timeout,
103
+ platformLang.timeoutUnreach,
104
+ platformLang.noService,
105
+ ])
106
+ this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
107
+ }
108
+ }
109
+
110
+ async internalStateUpdate(value) {
111
+ try {
112
+ // Send the update
113
+ await this.sendDeviceUpdate({
114
+ BinaryState: value,
115
+ })
116
+
117
+ // Update the HomeKit characteristic
118
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
119
+
120
+ // Update the cache value
121
+ this.cacheState = value
122
+
123
+ // Log the change if appropriate
124
+ this.accessory.log(`${platformLang.curState} [${value === 1 ? platformLang.purifyYes : platformLang.purifyNo}]`)
125
+ } catch (err) {
126
+ // Catch any errors
127
+ const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
128
+ this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
129
+
130
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
131
+ setTimeout(() => {
132
+ this.service.updateCharacteristic(this.hapChar.Active, this.cacheState)
133
+ }, 2000)
134
+ throw new this.hapErr(-70402)
135
+ }
136
+ }
137
+
138
+ externalStateUpdate(value) {
139
+ try {
140
+ // Check to see if the cache value is different
141
+ if (value === this.cacheState) {
142
+ return
143
+ }
144
+
145
+ // Update the HomeKit characteristic
146
+ this.service.updateCharacteristic(this.hapChar.Active, value)
147
+ this.service.updateCharacteristic(this.hapChar.CurrentAirPurifierState, value === 1 ? 2 : 0)
148
+
149
+ // Update the cache value
150
+ this.cacheState = value
151
+
152
+ // Log the change if appropriate
153
+ this.accessory.log(`${platformLang.curState} [${value === 1 ? platformLang.purifyYes : platformLang.purifyNo}]`)
154
+ } catch (err) {
155
+ // Catch any errors
156
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
157
+ }
158
+ }
159
+ }