@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,315 @@
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 air purifier service then remove it
38
+ if (this.accessory.getService(this.hapServ.AirPurifier)) {
39
+ this.accessory.removeService(this.accessory.getService(this.hapServ.AirPurifier))
40
+ }
41
+
42
+ // If the accessory has an outlet service then remove it
43
+ if (this.accessory.getService(this.hapServ.Outlet)) {
44
+ this.accessory.removeService(this.accessory.getService(this.hapServ.Outlet))
45
+ }
46
+
47
+ // Add the switch service if it doesn't already exist
48
+ this.service = this.accessory.getService(this.hapServ.Switch)
49
+ if (!this.service) {
50
+ this.service = this.accessory.addService(this.hapServ.Switch)
51
+ this.service.addCharacteristic(this.eveChar.CurrentConsumption)
52
+ this.service.addCharacteristic(this.eveChar.TotalConsumption)
53
+ this.service.addCharacteristic(this.eveChar.ResetTotal)
54
+ }
55
+
56
+ // Add the set handler to the switch on/off characteristic
57
+ this.service
58
+ .getCharacteristic(this.hapChar.On)
59
+ .removeOnSet()
60
+ .onSet(async value => this.internalStateUpdate(value))
61
+
62
+ // Add the set handler to the switch reset (eve) characteristic
63
+ this.service.getCharacteristic(this.eveChar.ResetTotal).onSet(() => {
64
+ this.accessory.context.cacheLastWM = 0
65
+ this.accessory.context.cacheLastTC = 0
66
+ this.accessory.context.cacheTotalTC = 0
67
+ this.service.updateCharacteristic(this.eveChar.TotalConsumption, 0)
68
+ })
69
+
70
+ // Pass the accessory to fakegato to set up the Eve info service
71
+ this.accessory.historyService = new platform.eveService('switch', this.accessory, {
72
+ log: () => {},
73
+ })
74
+
75
+ // Output the customised options to the log
76
+ const opts = JSON.stringify({
77
+ showAs: 'switch',
78
+ showTodayTC: this.showTodayTC,
79
+ timeDiff: this.timeDiff,
80
+ wattDiff: this.wattDiff,
81
+ })
82
+ platform.log('[%s] %s %s.', accessory.displayName, platformLang.devInitOpts, opts)
83
+
84
+ // Request a device update immediately
85
+ this.requestDeviceUpdate()
86
+
87
+ // Start a polling interval if the user has disabled upnp
88
+ if (this.accessory.context.connection === 'http') {
89
+ this.pollingInterval = setInterval(
90
+ () => this.requestDeviceUpdate(),
91
+ platform.config.pollingInterval * 1000,
92
+ )
93
+ }
94
+ }
95
+
96
+ receiveDeviceUpdate(attribute) {
97
+ // Log the receiving update if debug is enabled
98
+ this.accessory.logDebug(`${platformLang.recUpd} [${attribute.name}: ${JSON.stringify(attribute.value)}]`)
99
+
100
+ // Let's see which attribute has been provided
101
+ switch (attribute.name) {
102
+ case 'BinaryState': {
103
+ // BinaryState is reported as 0=off, 1=on, 8=standby
104
+ // Send a HomeKit needed true/false argument (0=false, 1,8=true)
105
+ this.externalStateUpdate(attribute.value !== 0)
106
+ break
107
+ }
108
+ case 'InsightParams':
109
+ // Send the insight data straight to the function
110
+ this.externalInsightUpdate(
111
+ attribute.value.state,
112
+ attribute.value.power,
113
+ attribute.value.todayWm,
114
+ attribute.value.todayOnSeconds,
115
+ )
116
+ break
117
+ default:
118
+ }
119
+ }
120
+
121
+ async sendDeviceUpdate(value) {
122
+ // Log the sending update if debug is enabled
123
+ this.accessory.logDebug(`${platformLang.senUpd} ${JSON.stringify(value)}`)
124
+
125
+ // Send the update
126
+ await this.platform.httpClient.sendDeviceUpdate(
127
+ this.accessory,
128
+ 'urn:Belkin:service:basicevent:1',
129
+ 'SetBinaryState',
130
+ value,
131
+ )
132
+ }
133
+
134
+ async requestDeviceUpdate() {
135
+ try {
136
+ // Request the update
137
+ const data = await this.platform.httpClient.sendDeviceUpdate(
138
+ this.accessory,
139
+ 'urn:Belkin:service:basicevent:1',
140
+ 'GetBinaryState',
141
+ )
142
+
143
+ // Check for existence since BinaryState can be int 0
144
+ if (hasProperty(data, 'BinaryState')) {
145
+ this.receiveDeviceUpdate({
146
+ name: 'BinaryState',
147
+ value: Number.parseInt(data.BinaryState, 10),
148
+ })
149
+ }
150
+ } catch (err) {
151
+ const eText = parseError(err, [
152
+ platformLang.timeout,
153
+ platformLang.timeoutUnreach,
154
+ platformLang.noService,
155
+ ])
156
+ this.accessory.logDebugWarn(`${platformLang.rduErr} ${eText}`)
157
+ }
158
+ }
159
+
160
+ async internalStateUpdate(value) {
161
+ try {
162
+ // Send the update
163
+ await this.sendDeviceUpdate({
164
+ BinaryState: value ? 1 : 0,
165
+ })
166
+
167
+ // Update the cache value
168
+ this.cacheState = value
169
+
170
+ // Log the change if appropriate
171
+ this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
172
+
173
+ // If turning the switch off then update the current consumption
174
+ if (!value) {
175
+ // Update the HomeKit characteristics
176
+ this.service.updateCharacteristic(this.eveChar.CurrentConsumption, 0)
177
+
178
+ // Add an Eve entry for no power
179
+ this.accessory.historyService.addEntry({ power: 0 })
180
+
181
+ // Log the change if appropriate
182
+ this.accessory.log(`${platformLang.curCons} [0W]`)
183
+ }
184
+ } catch (err) {
185
+ const eText = parseError(err, [platformLang.timeout, platformLang.timeoutUnreach])
186
+ this.accessory.logWarn(`${platformLang.cantCtl} ${eText}`)
187
+
188
+ // Throw a 'no response' error and set a timeout to revert this after 2 seconds
189
+ setTimeout(() => {
190
+ this.service.updateCharacteristic(this.hapChar.On, this.cacheState)
191
+ }, 2000)
192
+ throw new this.hapErr(-70402)
193
+ }
194
+ }
195
+
196
+ externalInsightUpdate(value, power, todayWm, todayOnSeconds) {
197
+ // Update whether the switch is ON (value=1) or OFF (value=0)
198
+ this.externalStateUpdate(value !== 0)
199
+
200
+ // Update the total consumption
201
+ this.externalTotalConsumptionUpdate(todayWm, todayOnSeconds)
202
+
203
+ // Update the current consumption
204
+ this.externalConsumptionUpdate(power)
205
+ }
206
+
207
+ externalStateUpdate(value) {
208
+ try {
209
+ // Check to see if the cache value is different
210
+ if (value === this.cacheState) {
211
+ return
212
+ }
213
+
214
+ // Update the HomeKit characteristics
215
+ this.service.updateCharacteristic(this.hapChar.On, value)
216
+
217
+ // Update the cache value
218
+ this.cacheState = value
219
+
220
+ // Log the change if appropriate
221
+ this.accessory.log(`${platformLang.curState} [${value ? 'on' : 'off'}]`)
222
+
223
+ // If the device has turned off then update the consumption
224
+ if (!value) {
225
+ this.externalConsumptionUpdate(0)
226
+ }
227
+ } catch (err) {
228
+ // Catch any errors
229
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
230
+ }
231
+ }
232
+
233
+ externalConsumptionUpdate(power) {
234
+ try {
235
+ // Divide by 1000 to get the power value in W
236
+ const powerInWatts = Math.round(power / 1000)
237
+
238
+ // Check to see if the cache value is different
239
+ if (powerInWatts === this.cachePowerInWatts) {
240
+ return
241
+ }
242
+
243
+ // Update the power in watts cache
244
+ this.cachePowerInWatts = powerInWatts
245
+
246
+ // Update the HomeKit characteristic
247
+ this.service.updateCharacteristic(this.eveChar.CurrentConsumption, this.cachePowerInWatts)
248
+
249
+ // Add the Eve wattage entry
250
+ this.accessory.historyService.addEntry({ power: this.cachePowerInWatts })
251
+
252
+ // Calculate a difference from the last reading
253
+ const diff = Math.abs(powerInWatts - this.cachePowerInWatts)
254
+
255
+ // Don't continue with logging if the user has set a timeout between entries or a min difference between entries
256
+ if (!this.skipTimeDiff && diff >= this.wattDiff) {
257
+ // Log the change if appropriate
258
+ this.accessory.log(`${platformLang.curCons} [${this.cachePowerInWatts}W]`)
259
+
260
+ // Set the time difference timeout if needed
261
+ if (this.timeDiff) {
262
+ this.skipTimeDiff = true
263
+ setTimeout(() => {
264
+ this.skipTimeDiff = false
265
+ }, this.timeDiff * 1000)
266
+ }
267
+ }
268
+ } catch (err) {
269
+ // Catch any errors
270
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
271
+ }
272
+ }
273
+
274
+ externalTotalConsumptionUpdate(todayWm, todayOnSeconds) {
275
+ try {
276
+ if (todayWm === this.accessory.context.cacheLastWM) {
277
+ return
278
+ }
279
+
280
+ // Update the cache last value
281
+ this.accessory.context.cacheLastWM = todayWm
282
+
283
+ // Convert to Wh (hours) from raw data of Wm (minutes)
284
+ const todayWh = Math.round(todayWm / 60000)
285
+
286
+ // Convert to kWh
287
+ const todaykWh = todayWh / 1000
288
+
289
+ // Convert to hours, minutes and seconds (HH:MM:SS)
290
+ const todayOnHours = new Date(todayOnSeconds * 1000).toISOString().substr(11, 8)
291
+
292
+ // Calculate the difference (ie extra usage from the last reading)
293
+ const difference = Math.max(todaykWh - this.accessory.context.cacheLastTC, 0)
294
+
295
+ // Update the caches
296
+ this.accessory.context.cacheTotalTC += difference
297
+ this.accessory.context.cacheLastTC = todaykWh
298
+
299
+ // Update the total consumption characteristic
300
+ this.service.updateCharacteristic(
301
+ this.eveChar.TotalConsumption,
302
+ this.showTodayTC ? todaykWh : this.accessory.context.cacheTotalTC,
303
+ )
304
+
305
+ if (!this.skipTimeDiff) {
306
+ this.accessory.log(
307
+ `${platformLang.insOnTime} [${todayOnHours}] ${platformLang.insCons} [${todaykWh.toFixed(3)} kWh] ${platformLang.insTC} [${this.accessory.context.cacheTotalTC.toFixed(3)} kWh]`,
308
+ )
309
+ }
310
+ } catch (err) {
311
+ // Catch any errors
312
+ this.accessory.logWarn(`${platformLang.cantUpd} ${parseError(err)}`)
313
+ }
314
+ }
315
+ }
@@ -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
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 simont77
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.