homebridge-flume 0.3.1 → 0.7.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 +33 -1
- package/README.md +1 -1
- package/config.schema.json +29 -35
- package/lib/connection/http.js +106 -16
- package/lib/device/valve.js +45 -7
- package/lib/homebridge-ui/public/index.html +5 -1
- package/lib/index.js +23 -9
- package/lib/utils/constants.js +4 -4
- package/lib/utils/lang-en.js +7 -20
- package/package.json +19 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to homebridge-flume will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.7.0 (2021-11-24)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `StatusFault` and `StatusLowBattery` characteristics to the `LeakSensor` service
|
|
10
|
+
|
|
11
|
+
## 0.6.0 (2021-11-24)
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Leak sensor service
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Minimum refresh interval increased to two minutes
|
|
20
|
+
|
|
21
|
+
## 0.5.0 (2021-11-23)
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Make use of the debug logging option for HTTP responses
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- `client_id` and `client_secret` config options changed to `clientId` and `clientSecret` for consistency
|
|
30
|
+
|
|
31
|
+
## 0.4.0 (2021-11-23)
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- The plugin will remove 'stale' accessories that don't appear in the obtained device list
|
|
36
|
+
|
|
5
37
|
## 0.3.1 (2021-11-23)
|
|
6
38
|
|
|
7
39
|
### Fixed
|
|
@@ -18,4 +50,4 @@ Converted from accessory plugin to platform plugin
|
|
|
18
50
|
|
|
19
51
|
## 0.1.0 (2021-11-22)
|
|
20
52
|
|
|
21
|
-
Initial release
|
|
53
|
+
Initial release
|
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
### Credits
|
|
42
42
|
|
|
43
|
-
- This is a forked rewrite of the [homebridge-flume-water-sensor](https://www.npmjs.com/package/homebridge-flume-water-sensor) plugin by @weallknowwhoisatfaulthere
|
|
43
|
+
- This is a forked rewrite of the [homebridge-flume-water-sensor](https://www.npmjs.com/package/homebridge-flume-water-sensor) plugin by @weallknowwhoisatfaulthere.
|
|
44
44
|
- To the creators/contributors of [Homebridge](https://homebridge.io) who make this plugin possible.
|
|
45
45
|
|
|
46
46
|
### Disclaimer
|
package/config.schema.json
CHANGED
|
@@ -17,75 +17,69 @@
|
|
|
17
17
|
"title": "Username",
|
|
18
18
|
"type": "string",
|
|
19
19
|
"required": true,
|
|
20
|
-
"description": "Your
|
|
20
|
+
"description": "Your Flume username."
|
|
21
21
|
},
|
|
22
22
|
"password": {
|
|
23
23
|
"title": "Password",
|
|
24
24
|
"type": "string",
|
|
25
25
|
"required": true,
|
|
26
|
-
"description": "Your
|
|
26
|
+
"description": "Your Flume password."
|
|
27
27
|
},
|
|
28
|
-
"
|
|
28
|
+
"clientId": {
|
|
29
29
|
"title": "Client ID",
|
|
30
30
|
"type": "string",
|
|
31
|
-
"placeholder": "
|
|
31
|
+
"placeholder": "1234567890ABCD",
|
|
32
32
|
"required": true,
|
|
33
|
-
"description": "Your Client ID
|
|
33
|
+
"description": "Your Flume Client ID, found at https://portal.flumetech.com."
|
|
34
34
|
},
|
|
35
|
-
"
|
|
35
|
+
"clientSecret": {
|
|
36
36
|
"title": "Client Secret",
|
|
37
37
|
"type": "string",
|
|
38
|
-
"placeholder": "
|
|
38
|
+
"placeholder": "1234567890ABCDEFGHIJ",
|
|
39
39
|
"required": true,
|
|
40
|
-
"description": "Your Client Secret
|
|
40
|
+
"description": "Your Flume Client Secret, found at https://portal.flumetech.com."
|
|
41
41
|
},
|
|
42
42
|
"refreshInterval": {
|
|
43
|
-
"title": "Refresh
|
|
43
|
+
"title": "Refresh Interval",
|
|
44
44
|
"type": "integer",
|
|
45
|
-
"placeholder":
|
|
46
|
-
"description": "Number of minutes between updates.
|
|
45
|
+
"placeholder": 2,
|
|
46
|
+
"description": "Number of minutes between updates. Must be 2 or more."
|
|
47
47
|
},
|
|
48
48
|
"threshold": {
|
|
49
|
-
"title": "
|
|
49
|
+
"title": "Threshold",
|
|
50
50
|
"type": "number",
|
|
51
51
|
"placeholder": 0,
|
|
52
|
-
"description": "
|
|
52
|
+
"description": "Ignore a steady water draw below this value in gallons per refresh interval. Must be 0 or more."
|
|
53
53
|
},
|
|
54
54
|
"disableDeviceLogging": {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
55
|
+
"type": "boolean",
|
|
56
|
+
"title": "Disable Device Logging",
|
|
57
|
+
"description": "Global logging setting for accessory status changes. If true then accessory status changes will not be logged."
|
|
58
|
+
},
|
|
59
|
+
"debug": {
|
|
60
|
+
"title": "Debug Logging",
|
|
61
|
+
"type": "boolean",
|
|
62
|
+
"description": "Global logging setting for the plugin. If true then debug information will be added to the log."
|
|
63
|
+
},
|
|
64
|
+
"disablePlugin": {
|
|
65
|
+
"title": "Disable Plugin",
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"description": "If true, the plugin will remove all accessories and not load the plugin on restart."
|
|
68
|
+
}
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
71
|
"layout": [
|
|
72
72
|
{
|
|
73
73
|
"type": "fieldset",
|
|
74
74
|
"title": "Required Settings",
|
|
75
|
-
"items": ["username", "password", "
|
|
75
|
+
"items": ["username", "password", "clientId", "clientSecret"]
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"type": "fieldset",
|
|
79
79
|
"title": "Optional Settings",
|
|
80
80
|
"description": "Optional settings for the plugin, including global logging settings.",
|
|
81
81
|
"expandable": true,
|
|
82
|
-
"items": [
|
|
83
|
-
"refreshInterval",
|
|
84
|
-
"threshold",
|
|
85
|
-
"disableDeviceLogging",
|
|
86
|
-
"debug",
|
|
87
|
-
"disablePlugin"
|
|
88
|
-
]
|
|
82
|
+
"items": ["refreshInterval", "threshold", "disableDeviceLogging", "debug", "disablePlugin"]
|
|
89
83
|
}
|
|
90
84
|
]
|
|
91
85
|
}
|
package/lib/connection/http.js
CHANGED
|
@@ -15,8 +15,8 @@ module.exports = class connectionHTTP {
|
|
|
15
15
|
this.log = platform.log
|
|
16
16
|
this.username = platform.config.username
|
|
17
17
|
this.password = platform.config.password
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
18
|
+
this.clientId = platform.config.clientId
|
|
19
|
+
this.clientSecret = platform.config.clientSecret
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async obtainToken () {
|
|
@@ -24,8 +24,8 @@ module.exports = class connectionHTTP {
|
|
|
24
24
|
// Generate the JSON data to send
|
|
25
25
|
const body = {
|
|
26
26
|
grant_type: 'password',
|
|
27
|
-
client_id: this.
|
|
28
|
-
client_secret: this.
|
|
27
|
+
client_id: this.clientId,
|
|
28
|
+
client_secret: this.clientSecret,
|
|
29
29
|
username: this.username,
|
|
30
30
|
password: this.password
|
|
31
31
|
}
|
|
@@ -38,7 +38,7 @@ module.exports = class connectionHTTP {
|
|
|
38
38
|
|
|
39
39
|
// Check to see we got a response
|
|
40
40
|
if (!res.data) {
|
|
41
|
-
throw new Error(this.lang.
|
|
41
|
+
throw new Error(this.lang.noDataReceived)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/*
|
|
@@ -62,6 +62,11 @@ module.exports = class connectionHTTP {
|
|
|
62
62
|
}
|
|
63
63
|
*/
|
|
64
64
|
|
|
65
|
+
// Log the response if in debug mode
|
|
66
|
+
if (this.debug) {
|
|
67
|
+
this.log('[HTTP obtainToken()] %s.', JSON.stringify(res.data))
|
|
68
|
+
}
|
|
69
|
+
|
|
65
70
|
// Make the token available in other functions
|
|
66
71
|
this.accessToken = res.data.data[0].access_token
|
|
67
72
|
this.refreshToken = res.data.data[0].refresh_token
|
|
@@ -84,7 +89,7 @@ module.exports = class connectionHTTP {
|
|
|
84
89
|
} catch (err) {
|
|
85
90
|
if (err.code && this.consts.httpRetryCodes.includes(err.code)) {
|
|
86
91
|
// Retry if another attempt could be successful
|
|
87
|
-
this.log.warn('[HTTP
|
|
92
|
+
this.log.warn('[HTTP obtainToken()] %s [%s].', this.lang.httpRetry, err.code)
|
|
88
93
|
await this.funcs.sleep(30000)
|
|
89
94
|
return await this.login()
|
|
90
95
|
} else {
|
|
@@ -95,11 +100,16 @@ module.exports = class connectionHTTP {
|
|
|
95
100
|
|
|
96
101
|
async renewToken () {
|
|
97
102
|
try {
|
|
103
|
+
// Check we have a refresh token
|
|
104
|
+
if (!this.refreshToken) {
|
|
105
|
+
throw new Error(this.lang.noRefreshToken)
|
|
106
|
+
}
|
|
107
|
+
|
|
98
108
|
// Generate the JSON data to send
|
|
99
109
|
const body = {
|
|
100
110
|
grant_type: 'refresh_token',
|
|
101
|
-
client_id: this.
|
|
102
|
-
client_secret: this.
|
|
111
|
+
client_id: this.clientId,
|
|
112
|
+
client_secret: this.clientSecret,
|
|
103
113
|
refresh_token: this.refreshToken
|
|
104
114
|
}
|
|
105
115
|
const now = Date.now()
|
|
@@ -111,7 +121,12 @@ module.exports = class connectionHTTP {
|
|
|
111
121
|
|
|
112
122
|
// Check to see we got a response
|
|
113
123
|
if (!res.data) {
|
|
114
|
-
throw new Error(this.lang.
|
|
124
|
+
throw new Error(this.lang.noDataReceived)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Log the response if in debug mode
|
|
128
|
+
if (this.debug) {
|
|
129
|
+
this.log('[HTTP renewToken()] %s.', JSON.stringify(res.data))
|
|
115
130
|
}
|
|
116
131
|
|
|
117
132
|
/*
|
|
@@ -142,11 +157,11 @@ module.exports = class connectionHTTP {
|
|
|
142
157
|
} catch (err) {
|
|
143
158
|
if (err.code && this.consts.httpRetryCodes.includes(err.code)) {
|
|
144
159
|
// Retry if another attempt could be successful
|
|
145
|
-
this.log.warn('[HTTP
|
|
160
|
+
this.log.warn('[HTTP renewToken()] %s [%s].', this.lang.httpRetry, err.code)
|
|
146
161
|
await this.funcs.sleep(30000)
|
|
147
162
|
return await this.login()
|
|
148
163
|
} else {
|
|
149
|
-
throw new Error('[HTTP
|
|
164
|
+
throw new Error('[HTTP renewToken()] ' + err.message)
|
|
150
165
|
}
|
|
151
166
|
}
|
|
152
167
|
}
|
|
@@ -154,8 +169,8 @@ module.exports = class connectionHTTP {
|
|
|
154
169
|
async getDevices () {
|
|
155
170
|
try {
|
|
156
171
|
// Check we have a user id
|
|
157
|
-
if (!this.userId) {
|
|
158
|
-
throw new Error(
|
|
172
|
+
if (!this.userId || !this.accessToken) {
|
|
173
|
+
throw new Error(this.lang.noUserId)
|
|
159
174
|
}
|
|
160
175
|
|
|
161
176
|
// Perform the HTTP request
|
|
@@ -166,14 +181,19 @@ module.exports = class connectionHTTP {
|
|
|
166
181
|
|
|
167
182
|
// Check to see we got a response
|
|
168
183
|
if (!res.data) {
|
|
169
|
-
throw new Error(
|
|
184
|
+
throw new Error(this.lang.noDataReceived)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Log the response if in debug mode
|
|
188
|
+
if (this.debug) {
|
|
189
|
+
this.log('[HTTP getDevices()] %s.', JSON.stringify(res.data))
|
|
170
190
|
}
|
|
171
191
|
|
|
172
192
|
return res.data.data
|
|
173
193
|
} catch (err) {
|
|
174
194
|
if (err.code && this.consts.httpRetryCodes.includes(err.code)) {
|
|
175
195
|
// Retry if another attempt could be successful
|
|
176
|
-
this.log.warn('[HTTP
|
|
196
|
+
this.log.warn('[HTTP getDevices()] %s [%s].', this.lang.httpRetry, err.code)
|
|
177
197
|
await this.funcs.sleep(30000)
|
|
178
198
|
return await this.login()
|
|
179
199
|
} else {
|
|
@@ -182,7 +202,37 @@ module.exports = class connectionHTTP {
|
|
|
182
202
|
}
|
|
183
203
|
}
|
|
184
204
|
|
|
185
|
-
async getDeviceInfo (deviceId
|
|
205
|
+
async getDeviceInfo (deviceId) {
|
|
206
|
+
// Refresh the access token if it has expired already
|
|
207
|
+
if (Date.now() > this.expiresIn) {
|
|
208
|
+
await this.renewToken()
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Send the request
|
|
212
|
+
const res = await axios.get(
|
|
213
|
+
'https://api.flumetech.com/users/' + this.userId + '/devices/' + deviceId,
|
|
214
|
+
{
|
|
215
|
+
headers: {
|
|
216
|
+
Authorization: 'Bearer ' + this.accessToken
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
// Check to see we got a response
|
|
222
|
+
if (!res.data) {
|
|
223
|
+
throw new Error(this.lang.noDataReceived)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Log the response if in debug mode
|
|
227
|
+
if (this.debug) {
|
|
228
|
+
this.log('[HTTP getDeviceInfo()] %s.', JSON.stringify(res.data))
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Parse the response
|
|
232
|
+
return res.data.data[0]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async getWaterInfo (deviceId, fromWhen) {
|
|
186
236
|
// Refresh the access token if it has expired already
|
|
187
237
|
if (Date.now() > this.expiresIn) {
|
|
188
238
|
await this.renewToken()
|
|
@@ -212,6 +262,46 @@ module.exports = class connectionHTTP {
|
|
|
212
262
|
}
|
|
213
263
|
)
|
|
214
264
|
|
|
265
|
+
// Check to see we got a response
|
|
266
|
+
if (!res.data) {
|
|
267
|
+
throw new Error(this.lang.noDataReceived)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Log the response if in debug mode
|
|
271
|
+
if (this.debug) {
|
|
272
|
+
this.log('[HTTP getWaterInfo()] %s.', JSON.stringify(res.data))
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Parse the response
|
|
276
|
+
return res.data.data[0]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async getLeakInfo (deviceId) {
|
|
280
|
+
// Refresh the access token if it has expired already
|
|
281
|
+
if (Date.now() > this.expiresIn) {
|
|
282
|
+
await this.renewToken()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Send the request
|
|
286
|
+
const res = await axios.get(
|
|
287
|
+
'https://api.flumetech.com/users/' + this.userId + '/devices/' + deviceId + '/leaks/active',
|
|
288
|
+
{
|
|
289
|
+
headers: {
|
|
290
|
+
Authorization: 'Bearer ' + this.accessToken
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
// Check to see we got a response
|
|
296
|
+
if (!res.data) {
|
|
297
|
+
throw new Error(this.lang.noDataReceived)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Log the response if in debug mode
|
|
301
|
+
if (this.debug) {
|
|
302
|
+
this.log('[HTTP getLeakInfo()] %s.', JSON.stringify(res.data))
|
|
303
|
+
}
|
|
304
|
+
|
|
215
305
|
// Parse the response
|
|
216
306
|
return res.data.data[0]
|
|
217
307
|
}
|
package/lib/device/valve.js
CHANGED
|
@@ -6,30 +6,68 @@ module.exports = class deviceValve {
|
|
|
6
6
|
constructor (platform, accessory) {
|
|
7
7
|
// Set up variables from the platform
|
|
8
8
|
this.accessory = accessory
|
|
9
|
-
this.disableDeviceLogging = platform.config.disableDeviceLogging
|
|
10
9
|
this.funcs = platform.funcs
|
|
11
10
|
this.threshold = platform.config.threshold
|
|
12
11
|
this.hapChar = platform.api.hap.Characteristic
|
|
13
12
|
this.hapErr = platform.api.hap.HapStatusError
|
|
14
13
|
this.hapServ = platform.api.hap.Service
|
|
15
14
|
this.lang = platform.lang
|
|
16
|
-
this.log = platform.log
|
|
15
|
+
this.log = platform.config.disableDeviceLogging ? () => {} : platform.log
|
|
17
16
|
this.name = accessory.displayName
|
|
18
17
|
this.platform = platform
|
|
19
18
|
this.refreshInterval = platform.config.refreshInterval
|
|
19
|
+
|
|
20
|
+
// Add the leak sensor service if it doesn't exist already
|
|
21
|
+
this.leakService =
|
|
22
|
+
this.accessory.getService(this.hapServ.LeakSensor) ||
|
|
23
|
+
this.accessory.addService(this.hapServ.LeakSensor)
|
|
24
|
+
|
|
25
|
+
this.cacheLeak = !!this.leakService.getCharacteristic(this.hapChar.LeakDetected).value
|
|
26
|
+
this.cacheBatt = !this.leakService.getCharacteristic(this.hapChar.StatusLowBattery).value
|
|
27
|
+
this.cacheStatus = !this.leakService.getCharacteristic(this.hapChar.StatusFault).value
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
externalUpdate (params) {
|
|
23
|
-
//
|
|
31
|
+
// Check the data for leak detection
|
|
32
|
+
if (
|
|
33
|
+
this.funcs.hasProperty(params.leakInfo, 'active') &&
|
|
34
|
+
params.leakInfo.active !== this.cacheLeak
|
|
35
|
+
) {
|
|
36
|
+
this.cacheLeak = params.leakInfo.active
|
|
37
|
+
this.leakService.updateCharacteristic(this.hapChar.LeakDetected, this.cacheLeak ? 1 : 0)
|
|
38
|
+
this.log('[%s] current leak status [%sdetected].', this.name, this.cacheLeak ? '' : 'not ')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check the data for battery level, cacheBatt is true for OK and false for LOW
|
|
42
|
+
if (
|
|
43
|
+
this.funcs.hasProperty(params.devInfo, 'battery_level') &&
|
|
44
|
+
(params.devInfo.battery_level !== 'low') !== this.cacheBatt
|
|
45
|
+
) {
|
|
46
|
+
this.cacheBatt = params.devInfo.battery_level !== 'low'
|
|
47
|
+
this.leakService.updateCharacteristic(this.hapChar.StatusLowBattery, this.cacheBatt ? 0 : 1)
|
|
48
|
+
this.log('[%s] current battery [%s].', this.name, this.cacheBatt ? 'ok' : 'low')
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check the data for connectivity, cacheStatus is true for OK and false for NOT CONNECTED
|
|
52
|
+
if (
|
|
53
|
+
this.funcs.hasProperty(params.devInfo, 'connected') &&
|
|
54
|
+
params.devInfo.connected !== this.cacheStatus
|
|
55
|
+
) {
|
|
56
|
+
this.cacheStatus = params.devInfo.connected
|
|
57
|
+
this.leakService.updateCharacteristic(this.hapChar.StatusFault, this.cacheStatus ? 0 : 1)
|
|
58
|
+
this.log('[%s] current status [%sconnected].', this.name, this.cacheStatus ? '' : 'not ')
|
|
59
|
+
}
|
|
60
|
+
|
|
24
61
|
const usage =
|
|
25
|
-
params.
|
|
26
|
-
|
|
62
|
+
params.waterInfo.currentusage &&
|
|
63
|
+
params.waterInfo.currentusage[0] &&
|
|
64
|
+
params.waterInfo.currentusage[0].value
|
|
65
|
+
? params.waterInfo.currentusage[0].value
|
|
27
66
|
: 0
|
|
28
67
|
if (usage > this.threshold) {
|
|
29
68
|
this.log(
|
|
30
|
-
'[%s]
|
|
69
|
+
'[%s] usage detected - [%s] gallons within the last [%s] minutes.',
|
|
31
70
|
this.name,
|
|
32
|
-
'usage detected',
|
|
33
71
|
usage,
|
|
34
72
|
this.refreshInterval
|
|
35
73
|
)
|
|
@@ -77,7 +77,11 @@
|
|
|
77
77
|
<h4>Credits</h4>
|
|
78
78
|
<ul>
|
|
79
79
|
<li>
|
|
80
|
-
This is a forked rewrite of the
|
|
80
|
+
This is a forked rewrite of the
|
|
81
|
+
<a href="https://www.npmjs.com/package/homebridge-flume-water-sensor" target="_blank"
|
|
82
|
+
>homebridge-flume-water-sensor</a
|
|
83
|
+
>
|
|
84
|
+
plugin by @weallknowwhoisatfaulthere
|
|
81
85
|
</li>
|
|
82
86
|
<li>
|
|
83
87
|
To the creators/contributors of
|
package/lib/index.js
CHANGED
|
@@ -88,8 +88,8 @@ class FlumePlatform {
|
|
|
88
88
|
// Begin applying the user's config
|
|
89
89
|
for (const [key, val] of Object.entries(config)) {
|
|
90
90
|
switch (key) {
|
|
91
|
-
case '
|
|
92
|
-
case '
|
|
91
|
+
case 'clientId':
|
|
92
|
+
case 'clientSecret':
|
|
93
93
|
case 'password':
|
|
94
94
|
case 'username':
|
|
95
95
|
if (typeof val !== 'string' || val === '') {
|
|
@@ -161,8 +161,8 @@ class FlumePlatform {
|
|
|
161
161
|
if (
|
|
162
162
|
!this.config.username ||
|
|
163
163
|
!this.config.password ||
|
|
164
|
-
!this.config.
|
|
165
|
-
!this.config.
|
|
164
|
+
!this.config.clientId ||
|
|
165
|
+
!this.config.clientSecret
|
|
166
166
|
) {
|
|
167
167
|
throw new Error(this.lang.noCreds)
|
|
168
168
|
}
|
|
@@ -174,9 +174,11 @@ class FlumePlatform {
|
|
|
174
174
|
|
|
175
175
|
// Check we have devices we can work with
|
|
176
176
|
if (!Array.isArray(deviceList) || deviceList.length === 0) {
|
|
177
|
+
this.devicesInHB.forEach(accessory => this.removeAccessory(accessory))
|
|
177
178
|
throw new Error(this.lang.noDevices)
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
// Initialise each device into Homebridge
|
|
180
182
|
deviceList.forEach(device => {
|
|
181
183
|
if (!device.bridge_id) {
|
|
182
184
|
return
|
|
@@ -184,6 +186,13 @@ class FlumePlatform {
|
|
|
184
186
|
this.initialiseDevice(device)
|
|
185
187
|
})
|
|
186
188
|
|
|
189
|
+
// Remove any stale accessories that don't appear in the device list
|
|
190
|
+
this.devicesInHB.forEach(accessory => {
|
|
191
|
+
if (!deviceList.find(el => accessory.context.deviceId === el.id)) {
|
|
192
|
+
this.removeAccessory(accessory)
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
187
196
|
// Set up an initial last sync time
|
|
188
197
|
this.lastSync = new Date(Date.now() - this.config.refreshInterval * 60000)
|
|
189
198
|
|
|
@@ -224,18 +233,23 @@ class FlumePlatform {
|
|
|
224
233
|
|
|
225
234
|
async flumeSync () {
|
|
226
235
|
try {
|
|
227
|
-
const
|
|
236
|
+
const since = this.lastSync
|
|
228
237
|
.toISOString()
|
|
229
238
|
.substring(0, 19)
|
|
230
239
|
.replace('T', ' ')
|
|
231
240
|
this.devicesInHB.forEach(async accessory => {
|
|
232
241
|
try {
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
const toReturn = { since }
|
|
243
|
+
const devInfo = await this.httpClient.getDeviceInfo(accessory.context.deviceId)
|
|
244
|
+
toReturn.devInfo = devInfo
|
|
245
|
+
const waterInfo = await this.httpClient.getWaterInfo(accessory.context.deviceId, since)
|
|
246
|
+
toReturn.waterInfo = waterInfo
|
|
247
|
+
const leakInfo = await this.httpClient.getLeakInfo(accessory.context.deviceId)
|
|
248
|
+
toReturn.leakInfo = leakInfo
|
|
249
|
+
accessory.control.externalUpdate(toReturn)
|
|
236
250
|
} catch (err) {
|
|
237
251
|
const eText = this.funcs.parseError(err)
|
|
238
|
-
this.log.warn(eText)
|
|
252
|
+
this.log.warn('[%s] %s %s.', accessory.displayName, this.lang.devNotRef, eText)
|
|
239
253
|
}
|
|
240
254
|
})
|
|
241
255
|
|
package/lib/utils/constants.js
CHANGED
|
@@ -7,8 +7,8 @@ module.exports = {
|
|
|
7
7
|
name: 'Flume',
|
|
8
8
|
username: '',
|
|
9
9
|
password: '',
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
clientId: '',
|
|
11
|
+
clientSecret: '',
|
|
12
12
|
refreshInterval: 1,
|
|
13
13
|
threshold: 0,
|
|
14
14
|
disableDeviceLogging: false,
|
|
@@ -18,12 +18,12 @@ module.exports = {
|
|
|
18
18
|
},
|
|
19
19
|
|
|
20
20
|
defaultValues: {
|
|
21
|
-
refreshInterval:
|
|
21
|
+
refreshInterval: 2,
|
|
22
22
|
threshold: 0
|
|
23
23
|
},
|
|
24
24
|
|
|
25
25
|
minValues: {
|
|
26
|
-
refreshInterval:
|
|
26
|
+
refreshInterval: 2,
|
|
27
27
|
threshold: 0
|
|
28
28
|
},
|
|
29
29
|
|
package/lib/utils/lang-en.js
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
module.exports = {
|
|
6
6
|
accNotFound: 'accessory not found',
|
|
7
|
-
accNotReady: 'cannot currently be controlled, see plugin startup logs for any error',
|
|
8
7
|
brand: 'Flume',
|
|
9
8
|
cfgDef: 'is not a valid number so using default of',
|
|
10
9
|
cfgDup: 'will be ignored since another entry with this ID already exists',
|
|
@@ -15,10 +14,6 @@ module.exports = {
|
|
|
15
14
|
cfgRmv: 'is unused and can be removed',
|
|
16
15
|
cfgQts: 'should not have quotes around its entry',
|
|
17
16
|
complete: '✓ Setup complete',
|
|
18
|
-
curHeat: 'current heating',
|
|
19
|
-
curState: 'current state',
|
|
20
|
-
curTarg: 'current target',
|
|
21
|
-
curTemp: 'current temperature',
|
|
22
17
|
devAdd: 'has been added to Homebridge',
|
|
23
18
|
devInit: 'initialised with id',
|
|
24
19
|
devNotAdd: 'could not be added to Homebridge as',
|
|
@@ -29,36 +24,28 @@ module.exports = {
|
|
|
29
24
|
devNotUpdated: 'could not be updated as',
|
|
30
25
|
devRemove: 'has been removed from Homebridge',
|
|
31
26
|
disabled: 'To change this, set disablePlugin to false',
|
|
32
|
-
disableHTTP: 'Disabling HTTP client as',
|
|
33
27
|
disabling: 'Disabling plugin',
|
|
34
28
|
hbVersionFail: 'Your version of Homebridge is too low - please update to v1.3',
|
|
35
29
|
httpRetry: 'Unable to reach Flume, retrying in 30 seconds',
|
|
36
30
|
initialised: 'Plugin initialised. Setting up accessories...',
|
|
37
31
|
initialising: 'Initialising plugin',
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
noCreds: 'Flume username and/or password not configured',
|
|
43
|
-
noDevices: 'no data received from Flume server whilst obtaining device list',
|
|
44
|
-
noTokenExists: 'no Flume account token exists',
|
|
32
|
+
noCreds: 'Flume username and/or password and/or client id/secret not configured',
|
|
33
|
+
noDataReceived: 'No data received from request',
|
|
34
|
+
noRefreshToken: 'No refresh token has been retrieved',
|
|
35
|
+
noUserId: 'No user id has been retrieved',
|
|
45
36
|
pluginNotConf: 'Plugin has not been configured',
|
|
46
|
-
receivingUpdate: 'receiving update',
|
|
47
|
-
scheduleFailed: 'Program schedule could not be updated as',
|
|
48
|
-
scheduleInvalid: 'Not updating program schedule as configuration is invalid or incomplete',
|
|
49
|
-
scheduleUpdated: 'Program schedule successfully updated',
|
|
50
37
|
syncFailed: 'Sync process failed as',
|
|
51
38
|
updateFail: 'could not be updated as',
|
|
52
39
|
zWelcome: [
|
|
53
40
|
"Don't forget to ☆ this plugin on GitHub if you're finding it useful!",
|
|
54
|
-
|
|
41
|
+
'Have a feature request? Visit https://bit.ly/hb-flume-issues to ask!',
|
|
55
42
|
'Interested in sponsoring this plugin? https://github.com/sponsors/bwp91',
|
|
56
43
|
"Join the plugin's Discord community! https://discord.gg/cMGhNtZ3tW",
|
|
57
44
|
'Thanks for using this plugin, I hope you find it helpful!',
|
|
58
45
|
'This plugin has been made with ♥ by bwp91 from the UK!',
|
|
59
46
|
'Check out my other Homebridge plugins! https://github.com/bwp91',
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
'Have time to give this plugin a review? https://bit.ly/hb-flume-review',
|
|
48
|
+
"This plugin needs it's first ☆ rating on HOOBS! https://bit.ly/hb-flume-review",
|
|
62
49
|
'Want to see this plugin in your own language? Let me know!'
|
|
63
50
|
]
|
|
64
51
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-flume",
|
|
3
3
|
"alias": "Flume",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ben Potter",
|
|
7
7
|
"email": "bwp91@icloud.com"
|
|
@@ -33,6 +33,24 @@
|
|
|
33
33
|
"bugs": {
|
|
34
34
|
"url": "https://github.com/bwp91/homebridge-flume/issues"
|
|
35
35
|
},
|
|
36
|
+
"funding": [
|
|
37
|
+
{
|
|
38
|
+
"type": "github",
|
|
39
|
+
"url": "https://github.com/sponsors/bwp91"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "kofi",
|
|
43
|
+
"url": "https://ko-fi.com/bwp91"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"type": "patreon",
|
|
47
|
+
"url": "https://www.patreon.com/bwp91"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "paypal",
|
|
51
|
+
"url": "https://www.paypal.me/BenPotter"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
36
54
|
"dependencies": {
|
|
37
55
|
"@homebridge/plugin-ui-utils": "^0.0.19",
|
|
38
56
|
"axios": "^0.24.0",
|