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