homebridge-flume 0.5.0 → 1.1.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 CHANGED
@@ -2,6 +2,38 @@
2
2
 
3
3
  All notable changes to homebridge-flume will be documented in this file.
4
4
 
5
+ ## 1.1.0 (2021-11-30)
6
+
7
+ ### Added
8
+
9
+ - Daily and monthly usage custom characteristics (viewable in HomeKit apps like Eve)
10
+
11
+ ### Removed
12
+
13
+ - `threshold` configuration option as unused
14
+
15
+ ## 1.0.0 (2021-11-29)
16
+
17
+ ### Added
18
+
19
+ - Plugin logo
20
+
21
+ ## 0.7.0 (2021-11-24)
22
+
23
+ ### Added
24
+
25
+ - `StatusFault` and `StatusLowBattery` characteristics to the `LeakSensor` service
26
+
27
+ ## 0.6.0 (2021-11-24)
28
+
29
+ ### Added
30
+
31
+ - Leak sensor service
32
+
33
+ ### Changed
34
+
35
+ - Minimum refresh interval increased to two minutes
36
+
5
37
  ## 0.5.0 (2021-11-23)
6
38
 
7
39
  ### Added
package/README.md CHANGED
@@ -1,17 +1,20 @@
1
+ <p align="center">
2
+ <a href="https://github.com/bwp91/homebridge-flume"><img src="https://user-images.githubusercontent.com/43026681/143831753-ed67cad2-909a-4337-9b18-dd8e65dfdf5e.png" width="600px"></a>
3
+ </p>
1
4
  <span align="center">
2
-
3
- # homebridge-flume
4
-
5
- Homebridge plugin to integrate Flume devices into HomeKit
6
-
7
- [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
8
- [![npm](https://img.shields.io/npm/v/homebridge-flume/latest?label=latest)](https://www.npmjs.com/package/homebridge-flume)
9
- [![npm](https://img.shields.io/npm/v/homebridge-flume/beta?label=beta)](https://github.com/bwp91/homebridge-flume/wiki/Beta-Version)
10
- [![npm](https://img.shields.io/npm/dt/homebridge-flume)](https://www.npmjs.com/package/homebridge-flume)
11
- [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
12
- [![Discord](https://img.shields.io/discord/784827113378676736?color=728ED5&logo=discord&label=bwp91-discord)](https://discord.com/channels/784827113378676736/784827113378676739)
13
- [![Discord](https://img.shields.io/discord/432663330281226270?color=728ED5&logo=discord&label=hb-discord)](https://discord.com/channels/432663330281226270/742733745743855627)
14
-
5
+
6
+ # homebridge-flume
7
+
8
+ Homebridge plugin to integrate Flume devices into HomeKit
9
+
10
+ [![verified-by-homebridge](https://badgen.net/badge/homebridge/verified/purple)](https://github.com/homebridge/homebridge/wiki/Verified-Plugins)
11
+ [![npm](https://img.shields.io/npm/v/homebridge-flume/latest?label=latest)](https://www.npmjs.com/package/homebridge-flume)
12
+ [![npm](https://img.shields.io/npm/v/homebridge-flume/beta?label=beta)](https://github.com/bwp91/homebridge-flume/wiki/Beta-Version)
13
+ [![npm](https://img.shields.io/npm/dt/homebridge-flume)](https://www.npmjs.com/package/homebridge-flume)
14
+ [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
15
+ [![Discord](https://img.shields.io/discord/784827113378676736?color=728ED5&logo=discord&label=bwp91-discord)](https://discord.com/channels/784827113378676736/784827113378676739)
16
+ [![Discord](https://img.shields.io/discord/432663330281226270?color=728ED5&logo=discord&label=hb-discord)](https://discord.com/channels/432663330281226270/742733745743855627)
17
+
15
18
  </span>
16
19
 
17
20
  ### Plugin Information
@@ -41,6 +44,7 @@
41
44
  ### Credits
42
45
 
43
46
  - This is a forked rewrite of the [homebridge-flume-water-sensor](https://www.npmjs.com/package/homebridge-flume-water-sensor) plugin by @weallknowwhoisatfaulthere.
47
+ - To the creator of the awesome plugin header logo: [Keryan Belahcene](https://www.instagram.com/keryan.me).
44
48
  - To the creators/contributors of [Homebridge](https://homebridge.io) who make this plugin possible.
45
49
 
46
50
  ### Disclaimer
@@ -4,7 +4,7 @@
4
4
  "singular": true,
5
5
  "customUi": true,
6
6
  "customUiPath": "./lib/homebridge-ui",
7
- "headerDisplay": "<p align=\"center\">For help and support please visit our <a href=\"https://github.com/bwp91/homebridge-flume/wiki\">GitHub Wiki</a>. We hope you find this plugin useful!</p>",
7
+ "headerDisplay": "<p align=\"center\"><img width=\"60%\" src=\"https://user-images.githubusercontent.com/43026681/143831753-ed67cad2-909a-4337-9b18-dd8e65dfdf5e.png\"></p><p align=\"center\">For help and support please visit our <a href=\"https://github.com/bwp91/homebridge-thermobit/wiki\">GitHub Wiki</a>. We hope you find this plugin useful!</p>",
8
8
  "schema": {
9
9
  "type": "object",
10
10
  "properties": {
@@ -42,14 +42,8 @@
42
42
  "refreshInterval": {
43
43
  "title": "Refresh Interval",
44
44
  "type": "integer",
45
- "placeholder": 1,
46
- "description": "Number of minutes between updates. Must be 1 or more."
47
- },
48
- "threshold": {
49
- "title": "Threshold",
50
- "type": "number",
51
- "placeholder": 0,
52
- "description": "Ignore a steady water draw below this value in gallons per refresh interval. Must be 0 or more."
45
+ "placeholder": 2,
46
+ "description": "Number of minutes between updates. Must be 2 or more."
53
47
  },
54
48
  "disableDeviceLogging": {
55
49
  "type": "boolean",
@@ -79,7 +73,7 @@
79
73
  "title": "Optional Settings",
80
74
  "description": "Optional settings for the plugin, including global logging settings.",
81
75
  "expandable": true,
82
- "items": ["refreshInterval", "threshold", "disableDeviceLogging", "debug", "disablePlugin"]
76
+ "items": ["refreshInterval", "disableDeviceLogging", "debug", "disablePlugin"]
83
77
  }
84
78
  ]
85
79
  }
@@ -202,19 +202,58 @@ module.exports = class connectionHTTP {
202
202
  }
203
203
  }
204
204
 
205
- async getDeviceInfo (deviceId, fromWhen) {
205
+ async getDeviceInfo (deviceId) {
206
206
  // Refresh the access token if it has expired already
207
207
  if (Date.now() > this.expiresIn) {
208
208
  await this.renewToken()
209
209
  }
210
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) {
236
+ // Refresh the access token if it has expired already
237
+ if (Date.now() > this.expiresIn) {
238
+ await this.renewToken()
239
+ }
240
+ const today = new Date().toISOString().substring(0, 10) + ' 00:00:00'
241
+ const month = today.substring(0, 8) + '01' + today.substring(10)
242
+
211
243
  // Generate the JSON data to send
212
244
  const body = {
213
245
  queries: [
214
246
  {
215
- request_id: 'currentusage',
216
- bucket: 'MIN',
217
- since_datetime: fromWhen,
247
+ request_id: 'today',
248
+ bucket: 'DAY',
249
+ since_datetime: today,
250
+ operation: 'SUM',
251
+ units: 'GALLONS'
252
+ },
253
+ {
254
+ request_id: 'month',
255
+ bucket: 'MON',
256
+ since_datetime: month,
218
257
  operation: 'SUM',
219
258
  units: 'GALLONS'
220
259
  }
@@ -239,7 +278,37 @@ module.exports = class connectionHTTP {
239
278
 
240
279
  // Log the response if in debug mode
241
280
  if (this.debug) {
242
- this.log('[HTTP getDevice()] %s.', JSON.stringify(res.data))
281
+ this.log('[HTTP getWaterInfo()] %s.', JSON.stringify(res.data))
282
+ }
283
+
284
+ // Parse the response
285
+ return res.data.data[0]
286
+ }
287
+
288
+ async getLeakInfo (deviceId) {
289
+ // Refresh the access token if it has expired already
290
+ if (Date.now() > this.expiresIn) {
291
+ await this.renewToken()
292
+ }
293
+
294
+ // Send the request
295
+ const res = await axios.get(
296
+ 'https://api.flumetech.com/users/' + this.userId + '/devices/' + deviceId + '/leaks/active',
297
+ {
298
+ headers: {
299
+ Authorization: 'Bearer ' + this.accessToken
300
+ }
301
+ }
302
+ )
303
+
304
+ // Check to see we got a response
305
+ if (!res.data) {
306
+ throw new Error(this.lang.noDataReceived)
307
+ }
308
+
309
+ // Log the response if in debug mode
310
+ if (this.debug) {
311
+ this.log('[HTTP getLeakInfo()] %s.', JSON.stringify(res.data))
243
312
  }
244
313
 
245
314
  // Parse the response
@@ -0,0 +1,93 @@
1
+ /* jshint node: true, esversion: 10, -W014, -W033 */
2
+ /* eslint-disable new-cap */
3
+ 'use strict'
4
+
5
+ module.exports = class deviceLeakSensor {
6
+ constructor (platform, accessory) {
7
+ // Set up variables from the platform
8
+ this.accessory = accessory
9
+ this.cusChar = platform.cusChar
10
+ this.funcs = platform.funcs
11
+ this.hapChar = platform.api.hap.Characteristic
12
+ this.hapErr = platform.api.hap.HapStatusError
13
+ this.hapServ = platform.api.hap.Service
14
+ this.lang = platform.lang
15
+ this.log = platform.config.disableDeviceLogging ? () => {} : platform.log
16
+ this.name = accessory.displayName
17
+ this.platform = platform
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
28
+
29
+ // Add the custom characteristics if they haven't been already
30
+ if (!this.leakService.testCharacteristic(this.cusChar.TodayUsage)) {
31
+ this.leakService.addCharacteristic(this.cusChar.TodayUsage)
32
+ }
33
+ if (!this.leakService.testCharacteristic(this.cusChar.MonthUsage)) {
34
+ this.leakService.addCharacteristic(this.cusChar.MonthUsage)
35
+ }
36
+ }
37
+
38
+ externalUpdate (params) {
39
+ // Check the data for leak detection
40
+ if (
41
+ this.funcs.hasProperty(params.leakInfo, 'active') &&
42
+ params.leakInfo.active !== this.cacheLeak
43
+ ) {
44
+ this.cacheLeak = params.leakInfo.active
45
+ this.leakService.updateCharacteristic(this.hapChar.LeakDetected, this.cacheLeak ? 1 : 0)
46
+ this.log('[%s] current leak status [%sdetected].', this.name, this.cacheLeak ? '' : 'not ')
47
+ }
48
+
49
+ // Check the data for battery level, cacheBatt is true for OK and false for LOW
50
+ if (
51
+ this.funcs.hasProperty(params.devInfo, 'battery_level') &&
52
+ (params.devInfo.battery_level !== 'low') !== this.cacheBatt
53
+ ) {
54
+ this.cacheBatt = params.devInfo.battery_level !== 'low'
55
+ this.leakService.updateCharacteristic(this.hapChar.StatusLowBattery, this.cacheBatt ? 0 : 1)
56
+ this.log('[%s] current battery [%s].', this.name, this.cacheBatt ? 'ok' : 'low')
57
+ }
58
+
59
+ // Check the data for connectivity, cacheStatus is true for OK and false for NOT CONNECTED
60
+ if (
61
+ this.funcs.hasProperty(params.devInfo, 'connected') &&
62
+ params.devInfo.connected !== this.cacheStatus
63
+ ) {
64
+ this.cacheStatus = params.devInfo.connected
65
+ this.leakService.updateCharacteristic(this.hapChar.StatusFault, this.cacheStatus ? 0 : 1)
66
+ this.log('[%s] current status [%sconnected].', this.name, this.cacheStatus ? '' : 'not ')
67
+ }
68
+
69
+ // Water info
70
+ if (params.waterInfo) {
71
+ if (
72
+ params.waterInfo.today &&
73
+ params.waterInfo.today[0] &&
74
+ this.funcs.hasProperty(params.waterInfo.today[0], 'value')
75
+ ) {
76
+ this.leakService.updateCharacteristic(
77
+ this.cusChar.TodayUsage,
78
+ params.waterInfo.today[0].value
79
+ )
80
+ }
81
+ if (
82
+ params.waterInfo.month &&
83
+ params.waterInfo.month[0] &&
84
+ this.funcs.hasProperty(params.waterInfo.month[0], 'value')
85
+ ) {
86
+ this.leakService.updateCharacteristic(
87
+ this.cusChar.MonthUsage,
88
+ params.waterInfo.month[0].value
89
+ )
90
+ }
91
+ }
92
+ }
93
+ }
@@ -1,3 +1,10 @@
1
+ <p class="text-center">
2
+ <img
3
+ src="https://user-images.githubusercontent.com/43026681/143831753-ed67cad2-909a-4337-9b18-dd8e65dfdf5e.png"
4
+ alt="homebridge-flume logo"
5
+ style="width: 60%;"
6
+ />
7
+ </p>
1
8
  <div id="pageIntro" class="text-center" style="display: none;">
2
9
  <p class="lead">Thank you for installing <strong>homebridge-flume</strong></p>
3
10
  <p>
@@ -83,6 +90,10 @@
83
90
  >
84
91
  plugin by @weallknowwhoisatfaulthere
85
92
  </li>
93
+ <li>
94
+ To the creator of the awesome plugin header logo:
95
+ <a href="https://www.instagram.com/keryan.me" target="_blank">Keryan Belahcene</a>.
96
+ </li>
86
97
  <li>
87
98
  To the creators/contributors of
88
99
  <a href="https://homebridge.io" target="_blank">Homebridge</a> who make this plugin possible.
package/lib/index.js CHANGED
@@ -106,18 +106,6 @@ class FlumePlatform {
106
106
  }
107
107
  this.config[key] = val === 'false' ? false : !!val
108
108
  break
109
- case 'threshold': {
110
- if (typeof v === 'string') {
111
- logQuotes(key)
112
- }
113
- const numVal = Number(val)
114
- if (isNaN(numVal)) {
115
- logIgnore(key)
116
- } else {
117
- this.config[key] = numVal
118
- }
119
- break
120
- }
121
109
  case 'name':
122
110
  case 'platform':
123
111
  case 'plugin_map':
@@ -167,6 +155,9 @@ class FlumePlatform {
167
155
  throw new Error(this.lang.noCreds)
168
156
  }
169
157
 
158
+ // Require any libraries that the accessory instances use
159
+ this.cusChar = new (require('./utils/custom-chars'))(this.api)
160
+
170
161
  // Setup the HTTP client if Thermobit username and password have been provided
171
162
  this.httpClient = new (require('./connection/http'))(this)
172
163
  await this.httpClient.obtainToken()
@@ -193,10 +184,8 @@ class FlumePlatform {
193
184
  }
194
185
  })
195
186
 
196
- // Set up an initial last sync time
197
- this.lastSync = new Date(Date.now() - this.config.refreshInterval * 60000)
198
-
199
187
  // Perform a first sync and setup the refresh interval
188
+ this.counter = 0
200
189
  this.flumeSync()
201
190
  this.refreshInterval = setInterval(
202
191
  () => this.flumeSync(),
@@ -233,22 +222,28 @@ class FlumePlatform {
233
222
 
234
223
  async flumeSync () {
235
224
  try {
236
- const fromWhen = this.lastSync
237
- .toISOString()
238
- .substring(0, 19)
239
- .replace('T', ' ')
225
+ // Reset the counter for water info once we reach 10
226
+ if (this.counter === 10) {
227
+ this.counter = 0
228
+ }
240
229
  this.devicesInHB.forEach(async accessory => {
241
230
  try {
242
- const res = await this.httpClient.getDeviceInfo(accessory.context.deviceId, fromWhen)
243
- res.fromWhen = fromWhen
244
- accessory.control.externalUpdate(res)
231
+ const toReturn = {}
232
+ const devInfo = await this.httpClient.getDeviceInfo(accessory.context.deviceId)
233
+ toReturn.devInfo = devInfo
234
+ if (this.counter === 0) {
235
+ const waterInfo = await this.httpClient.getWaterInfo(accessory.context.deviceId)
236
+ toReturn.waterInfo = waterInfo
237
+ }
238
+ const leakInfo = await this.httpClient.getLeakInfo(accessory.context.deviceId)
239
+ toReturn.leakInfo = leakInfo
240
+ accessory.control.externalUpdate(toReturn)
245
241
  } catch (err) {
246
242
  const eText = this.funcs.parseError(err)
247
243
  this.log.warn('[%s] %s %s.', accessory.displayName, this.lang.devNotRef, eText)
248
244
  }
245
+ this.counter++
249
246
  })
250
-
251
- this.lastSync = new Date()
252
247
  } catch (err) {
253
248
  // Catch any errors performing the sync
254
249
  const eText = this.funcs.parseError(err, [])
@@ -284,7 +279,7 @@ class FlumePlatform {
284
279
  }
285
280
 
286
281
  // Create the instance for this device type
287
- accessory.control = new (require('./device/valve'))(this, accessory)
282
+ accessory.control = new (require('./device/leak-sensor'))(this, accessory)
288
283
 
289
284
  // Log the device initialisation
290
285
  this.log('[%s] %s [%s].', accessory.displayName, this.lang.devInit, device.id)
@@ -10,7 +10,6 @@ module.exports = {
10
10
  clientId: '',
11
11
  clientSecret: '',
12
12
  refreshInterval: 1,
13
- threshold: 0,
14
13
  disableDeviceLogging: false,
15
14
  debug: false,
16
15
  disablePlugin: false,
@@ -18,13 +17,11 @@ module.exports = {
18
17
  },
19
18
 
20
19
  defaultValues: {
21
- refreshInterval: 1,
22
- threshold: 0
20
+ refreshInterval: 2
23
21
  },
24
22
 
25
23
  minValues: {
26
- refreshInterval: 1,
27
- threshold: 0
24
+ refreshInterval: 2
28
25
  },
29
26
 
30
27
  httpRetryCodes: ['ENOTFOUND', 'ETIMEDOUT', 'EAI_AGAIN', 'ECONNABORTED']
@@ -0,0 +1,38 @@
1
+ /* jshint node: true, esversion: 10, -W014, -W033 */
2
+ /* eslint-disable new-cap */
3
+ 'use strict'
4
+
5
+ module.exports = class customCharacteristics {
6
+ constructor (api) {
7
+ this.hapServ = api.hap.Service
8
+ this.hapChar = api.hap.Characteristic
9
+ this.uuids = {
10
+ todayUsage: 'E966F001-079E-48FF-8F27-9C2605A29F52',
11
+ monthUsage: 'E966F002-079E-48FF-8F27-9C2605A29F52'
12
+ }
13
+ const self = this
14
+ this.TodayUsage = function () {
15
+ self.hapChar.call(this, 'Today Usage', self.uuids.todayUsage)
16
+ this.setProps({
17
+ format: self.hapChar.Formats.UINT32,
18
+ perms: [self.hapChar.Perms.READ, self.hapChar.Perms.NOTIFY],
19
+ unit: 'Gallons'
20
+ })
21
+ this.value = this.getDefaultValue()
22
+ }
23
+ this.MonthUsage = function () {
24
+ self.hapChar.call(this, 'Month Usage', self.uuids.monthUsage)
25
+ this.setProps({
26
+ format: self.hapChar.Formats.UINT32,
27
+ perms: [self.hapChar.Perms.READ, self.hapChar.Perms.NOTIFY],
28
+ unit: 'Gallons'
29
+ })
30
+ this.value = this.getDefaultValue()
31
+ }
32
+ const inherits = require('util').inherits
33
+ inherits(this.TodayUsage, this.hapChar)
34
+ inherits(this.MonthUsage, this.hapChar)
35
+ this.TodayUsage.UUID = this.uuids.todayUsage
36
+ this.MonthUsage.UUID = this.uuids.monthUsage
37
+ }
38
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-flume",
3
3
  "alias": "Flume",
4
- "version": "0.5.0",
4
+ "version": "1.1.0",
5
5
  "author": {
6
6
  "name": "Ben Potter",
7
7
  "email": "bwp91@icloud.com"
@@ -1,36 +0,0 @@
1
- /* jshint node: true, esversion: 10, -W014, -W033 */
2
- /* eslint-disable new-cap */
3
- 'use strict'
4
-
5
- module.exports = class deviceValve {
6
- constructor (platform, accessory) {
7
- // Set up variables from the platform
8
- this.accessory = accessory
9
- this.funcs = platform.funcs
10
- this.threshold = platform.config.threshold
11
- this.hapChar = platform.api.hap.Characteristic
12
- this.hapErr = platform.api.hap.HapStatusError
13
- this.hapServ = platform.api.hap.Service
14
- this.lang = platform.lang
15
- this.log = platform.config.disableDeviceLogging ? () => {} : platform.log
16
- this.name = accessory.displayName
17
- this.platform = platform
18
- this.refreshInterval = platform.config.refreshInterval
19
- }
20
-
21
- externalUpdate (params) {
22
- // Here we deal with the incoming data
23
- const usage =
24
- params.currentusage && params.currentusage[0] && params.currentusage[0].value
25
- ? params.currentusage[0].value
26
- : 0
27
- if (usage > this.threshold) {
28
- this.log(
29
- '[%s] usage detected - [%s] gallons within the last [%s] minutes.',
30
- this.name,
31
- usage,
32
- this.refreshInterval
33
- )
34
- }
35
- }
36
- }