@homebridge-plugins/homebridge-ewelink 12.3.3

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 (113) hide show
  1. package/CHANGELOG.md +1363 -0
  2. package/LICENSE +21 -0
  3. package/README.md +68 -0
  4. package/config.schema.json +1757 -0
  5. package/eslint.config.js +15 -0
  6. package/lib/connection/api.js +508 -0
  7. package/lib/connection/http.js +304 -0
  8. package/lib/connection/lan.js +318 -0
  9. package/lib/connection/ws.js +414 -0
  10. package/lib/device/curtain.js +324 -0
  11. package/lib/device/diffuser.js +473 -0
  12. package/lib/device/fan.js +349 -0
  13. package/lib/device/group.js +106 -0
  14. package/lib/device/humidifier.js +199 -0
  15. package/lib/device/index.js +61 -0
  16. package/lib/device/light-cct.js +333 -0
  17. package/lib/device/light-dimmer.js +257 -0
  18. package/lib/device/light-rgb-cct.js +809 -0
  19. package/lib/device/light-rgb.js +272 -0
  20. package/lib/device/motor.js +363 -0
  21. package/lib/device/outlet-multi.js +417 -0
  22. package/lib/device/outlet-single-inched.js +409 -0
  23. package/lib/device/outlet-single.js +384 -0
  24. package/lib/device/panel.js +382 -0
  25. package/lib/device/rf-bridge.js +321 -0
  26. package/lib/device/rf-button.js +125 -0
  27. package/lib/device/rf-sensor.js +173 -0
  28. package/lib/device/sensor-ambient.js +311 -0
  29. package/lib/device/sensor-contact.js +140 -0
  30. package/lib/device/sensor-temp-humi.js +182 -0
  31. package/lib/device/simulation/blind.js +253 -0
  32. package/lib/device/simulation/cooler.js +312 -0
  33. package/lib/device/simulation/door.js +253 -0
  34. package/lib/device/simulation/doorbell.js +190 -0
  35. package/lib/device/simulation/garage-eachen.js +235 -0
  36. package/lib/device/simulation/garage-four.js +176 -0
  37. package/lib/device/simulation/garage-od-switch.js +36 -0
  38. package/lib/device/simulation/garage-one.js +395 -0
  39. package/lib/device/simulation/garage-two.js +394 -0
  40. package/lib/device/simulation/gate-one.js +327 -0
  41. package/lib/device/simulation/heater.js +389 -0
  42. package/lib/device/simulation/index.js +75 -0
  43. package/lib/device/simulation/light-fan.js +258 -0
  44. package/lib/device/simulation/lock-eachen.js +163 -0
  45. package/lib/device/simulation/lock-one.js +366 -0
  46. package/lib/device/simulation/p-button.js +223 -0
  47. package/lib/device/simulation/purifier.js +289 -0
  48. package/lib/device/simulation/rf-blind.js +192 -0
  49. package/lib/device/simulation/rf-door.js +180 -0
  50. package/lib/device/simulation/rf-window.js +180 -0
  51. package/lib/device/simulation/sensor-hidden.js +105 -0
  52. package/lib/device/simulation/sensor-leak.js +123 -0
  53. package/lib/device/simulation/sensor-visible.js +231 -0
  54. package/lib/device/simulation/sensor.js +300 -0
  55. package/lib/device/simulation/switch-valve.js +351 -0
  56. package/lib/device/simulation/tap-one.js +283 -0
  57. package/lib/device/simulation/tap-two.js +266 -0
  58. package/lib/device/simulation/th-cooler.js +443 -0
  59. package/lib/device/simulation/th-dehumidifier.js +424 -0
  60. package/lib/device/simulation/th-heater.js +449 -0
  61. package/lib/device/simulation/th-humidifier.js +424 -0
  62. package/lib/device/simulation/th-thermostat.js +611 -0
  63. package/lib/device/simulation/tv.js +252 -0
  64. package/lib/device/simulation/valve-four.js +232 -0
  65. package/lib/device/simulation/valve-one.js +345 -0
  66. package/lib/device/simulation/valve-two.js +340 -0
  67. package/lib/device/simulation/window.js +253 -0
  68. package/lib/device/switch-man.js +106 -0
  69. package/lib/device/switch-mate.js +82 -0
  70. package/lib/device/switch-multi.js +411 -0
  71. package/lib/device/switch-single-inched.js +332 -0
  72. package/lib/device/switch-single.js +307 -0
  73. package/lib/device/template.js +43 -0
  74. package/lib/device/thermostat.js +286 -0
  75. package/lib/device/zigbee/index.js +27 -0
  76. package/lib/device/zigbee/light-cct.js +263 -0
  77. package/lib/device/zigbee/light-dimmer.js +148 -0
  78. package/lib/device/zigbee/light-rgb-cct.js +400 -0
  79. package/lib/device/zigbee/motor.js +301 -0
  80. package/lib/device/zigbee/sensor-ambient.js +177 -0
  81. package/lib/device/zigbee/sensor-contact.js +137 -0
  82. package/lib/device/zigbee/sensor-leak.js +119 -0
  83. package/lib/device/zigbee/sensor-motion.js +119 -0
  84. package/lib/device/zigbee/sensor-occupancy.js +119 -0
  85. package/lib/device/zigbee/sensor-smoke.js +96 -0
  86. package/lib/device/zigbee/switch-stateless.js +104 -0
  87. package/lib/device/zigbee/thermostat.js +323 -0
  88. package/lib/fakegato/LICENSE +21 -0
  89. package/lib/fakegato/fakegato-history.js +813 -0
  90. package/lib/fakegato/fakegato-storage.js +108 -0
  91. package/lib/fakegato/fakegato-timer.js +125 -0
  92. package/lib/fakegato/uuid.js +27 -0
  93. package/lib/homebridge-ui/public/index.html +365 -0
  94. package/lib/homebridge-ui/server.js +46 -0
  95. package/lib/index.js +7 -0
  96. package/lib/node-dns-sd/LICENSE +21 -0
  97. package/lib/node-dns-sd/README.md +613 -0
  98. package/lib/node-dns-sd/lib/dns-sd-classes.json +6 -0
  99. package/lib/node-dns-sd/lib/dns-sd-composer.js +83 -0
  100. package/lib/node-dns-sd/lib/dns-sd-parser.js +344 -0
  101. package/lib/node-dns-sd/lib/dns-sd-types.json +52 -0
  102. package/lib/node-dns-sd/lib/dns-sd.js +580 -0
  103. package/lib/platform.js +2156 -0
  104. package/lib/utils/colour.js +552 -0
  105. package/lib/utils/constants.js +589 -0
  106. package/lib/utils/custom-chars.js +33 -0
  107. package/lib/utils/eve-chars.js +130 -0
  108. package/lib/utils/functions.js +42 -0
  109. package/lib/utils/lang-en.js +160 -0
  110. package/lib/utils/lang-fr.js +163 -0
  111. package/lib/utils/lang-th.js +160 -0
  112. package/lib/utils/lang.js +5 -0
  113. package/package.json +75 -0
@@ -0,0 +1,813 @@
1
+ import { Buffer } from 'node:buffer'
2
+ import { format as Format } from 'node:util'
3
+ import FakeGatoStorage from './fakegato-storage.js'
4
+ import FakeGatoTimer from './fakegato-timer.js'
5
+ import { toLongFormUUID, toShortFormUUID } from './uuid.js'
6
+
7
+ const _EPOCH = 978307200
8
+ let Characteristic
9
+ let Service
10
+
11
+ export default function (homebridge) {
12
+ Characteristic = homebridge.hap.Characteristic
13
+ Service = homebridge.hap.Service
14
+
15
+ const hexToBase64 = val => Buffer.from((`${val}`).replace(/[^\dA-F]/gi, ''), 'hex').toString('base64')
16
+
17
+ const base64ToHex = (val) => {
18
+ if (!val) {
19
+ return val
20
+ }
21
+ return Buffer.from(val, 'base64').toString('hex')
22
+ }
23
+
24
+ const swap16 = val => ((val & 0xFF) << 8) | ((val >>> 8) & 0xFF)
25
+
26
+ const swap32 = val => (
27
+ ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) | ((val >>> 8) & 0xFF00) | ((val >>> 24) & 0xFF)
28
+ )
29
+
30
+ const numToHex = (val, len) => {
31
+ let s = Number(val >>> 0).toString(16)
32
+ if (s.length % 2 !== 0) {
33
+ s = `0${s}`
34
+ }
35
+ if (len) {
36
+ return (`0000000000000${s}`).slice(-1 * len)
37
+ }
38
+ return s
39
+ }
40
+
41
+ const ucfirst = val => val.charAt(0).toUpperCase() + val.substr(1)
42
+
43
+ const precisionRound = (number, precision) => {
44
+ const factor = 10 ** precision
45
+ return Math.round(number * factor) / factor
46
+ }
47
+
48
+ class S2R1Characteristic extends Characteristic {
49
+ constructor() {
50
+ super('S2R1', S2R1Characteristic.UUID)
51
+ this.setProps({
52
+ format: homebridge.hap.Formats.DATA,
53
+ perms: [homebridge.hap.Perms.READ, homebridge.hap.Perms.NOTIFY, homebridge.hap.Perms.HIDDEN],
54
+ })
55
+ }
56
+ }
57
+
58
+ S2R1Characteristic.UUID = 'E863F116-079E-48FF-8F27-9C2605A29F52'
59
+
60
+ class S2R2Characteristic extends Characteristic {
61
+ constructor() {
62
+ super('S2R2', S2R2Characteristic.UUID)
63
+ this.setProps({
64
+ format: homebridge.hap.Formats.DATA,
65
+ perms: [homebridge.hap.Perms.READ, homebridge.hap.Perms.NOTIFY, homebridge.hap.Perms.HIDDEN],
66
+ })
67
+ }
68
+ }
69
+
70
+ S2R2Characteristic.UUID = 'E863F117-079E-48FF-8F27-9C2605A29F52'
71
+
72
+ class S2W1Characteristic extends Characteristic {
73
+ constructor() {
74
+ super('S2W1', S2W1Characteristic.UUID)
75
+ this.setProps({
76
+ format: homebridge.hap.Formats.DATA,
77
+ perms: [homebridge.hap.Perms.WRITE, homebridge.hap.Perms.HIDDEN],
78
+ })
79
+ }
80
+ }
81
+
82
+ S2W1Characteristic.UUID = 'E863F11C-079E-48FF-8F27-9C2605A29F52'
83
+
84
+ class S2W2Characteristic extends Characteristic {
85
+ constructor() {
86
+ super('S2W2', S2W2Characteristic.UUID)
87
+ this.setProps({
88
+ format: homebridge.hap.Formats.DATA,
89
+ perms: [homebridge.hap.Perms.WRITE, homebridge.hap.Perms.HIDDEN],
90
+ })
91
+ }
92
+ }
93
+
94
+ S2W2Characteristic.UUID = 'E863F121-079E-48FF-8F27-9C2605A29F52'
95
+
96
+ class FakeGatoHistoryService extends Service {
97
+ constructor(displayName, subtype) {
98
+ super(displayName, FakeGatoHistoryService.UUID, subtype)
99
+ this.addCharacteristic(S2R1Characteristic)
100
+ this.addCharacteristic(S2R2Characteristic)
101
+ this.addCharacteristic(S2W1Characteristic)
102
+ this.addCharacteristic(S2W2Characteristic)
103
+ }
104
+ }
105
+
106
+ FakeGatoHistoryService.UUID = 'E863F007-079E-48FF-8F27-9C2605A29F52'
107
+
108
+ let thisAccessory = {}
109
+
110
+ class FakeGatoHistory extends Service {
111
+ constructor(accessoryType, accessory, optionalParams) {
112
+ super(`${accessory.displayName} History`, FakeGatoHistoryService.UUID)
113
+ thisAccessory = accessory
114
+ this.accessoryName = thisAccessory.displayName
115
+ this.signatures = []
116
+ this.path = `${homebridge.user.storagePath()}/persist/`
117
+ this.minutes = optionalParams.minutes || 10
118
+ this.log = optionalParams.log
119
+ if (!this.log) {
120
+ this.log = () => {}
121
+ }
122
+ if (homebridge.globalFakeGatoTimer === undefined) {
123
+ homebridge.globalFakeGatoTimer = new FakeGatoTimer({
124
+ minutes: this.minutes,
125
+ log: this.log,
126
+ })
127
+ }
128
+ this.loaded = false
129
+ if (homebridge.globalFakeGatoStorage === undefined) {
130
+ homebridge.globalFakeGatoStorage = new FakeGatoStorage({
131
+ log: this.log,
132
+ })
133
+ }
134
+ homebridge.globalFakeGatoStorage.addWriter(this, {
135
+ path: this.path,
136
+ onReady: function () {
137
+ this.load(
138
+ (err, loaded) => {
139
+ if (err) {
140
+ this.log('FGH load error: %s.', err)
141
+ } else {
142
+ if (loaded) {
143
+ this.log('FGH loaded from storage.')
144
+ }
145
+ this.loaded = true
146
+ }
147
+ },
148
+ )
149
+ }.bind(this),
150
+ })
151
+ switch (accessoryType) {
152
+ case 'weather':
153
+ this.accessoryType116 = '03 0102 0202 0302'
154
+ this.accessoryType117 = '07'
155
+ homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage)
156
+ break
157
+ case 'energy':
158
+ this.accessoryType116 = '04 0102 0202 0702 0f03'
159
+ this.accessoryType117 = '1f'
160
+ homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage)
161
+ break
162
+ case 'room':
163
+ this.accessoryType116 = '04 0102 0202 0402 0f03'
164
+ this.accessoryType117 = '0f'
165
+ homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage)
166
+ break
167
+ case 'door':
168
+ this.accessoryType116 = '01 0601'
169
+ this.accessoryType117 = '01'
170
+ homebridge.globalFakeGatoTimer.subscribe(this, function (params) {
171
+ const backLog = params.backLog || []
172
+ const { immediate } = params
173
+ const fakegato = this.service
174
+ const actualEntry = {}
175
+ if (backLog.length) {
176
+ if (immediate) {
177
+ actualEntry.time = backLog[0].time
178
+ actualEntry.status = backLog[0].status
179
+ } else {
180
+ actualEntry.time = Math.round(new Date().valueOf() / 1000)
181
+ actualEntry.status = backLog[0].status
182
+ }
183
+ fakegato.log(
184
+ '[%s] FG timer: callbackDoor [%s] immediate [%s].',
185
+ fakegato.accessoryName,
186
+ actualEntry,
187
+ immediate,
188
+ )
189
+ fakegato._addEntry(actualEntry)
190
+ }
191
+ })
192
+ break
193
+ case 'motion':
194
+ this.accessoryType116 = '02 1301 1c01'
195
+ this.accessoryType117 = '02'
196
+ homebridge.globalFakeGatoTimer.subscribe(this, function (params) {
197
+ const backLog = params.backLog || []
198
+ const { immediate } = params
199
+ const fakegato = this.service
200
+ const actualEntry = {}
201
+ if (backLog.length) {
202
+ if (immediate) {
203
+ actualEntry.time = backLog[0].time
204
+ actualEntry.status = backLog[0].status
205
+ } else {
206
+ actualEntry.time = Math.round(new Date().valueOf() / 1000)
207
+ actualEntry.status = backLog[0].status
208
+ }
209
+ fakegato.log(
210
+ '[%s] FG timer: callbackMotion [%s] immediate [%s].',
211
+ fakegato.accessoryName,
212
+ actualEntry,
213
+ immediate,
214
+ )
215
+ fakegato._addEntry(actualEntry)
216
+ }
217
+ })
218
+ break
219
+ case 'switch':
220
+ this.accessoryType116 = '01 0e01'
221
+ this.accessoryType117 = '01'
222
+ homebridge.globalFakeGatoTimer.subscribe(this, function (params) {
223
+ const backLog = params.backLog || []
224
+ const { immediate } = params
225
+ const fakegato = this.service
226
+ const actualEntry = {}
227
+ if (backLog.length) {
228
+ if (immediate) {
229
+ actualEntry.time = backLog[0].time
230
+ actualEntry.status = backLog[0].status
231
+ } else {
232
+ actualEntry.time = Math.round(new Date().valueOf() / 1000)
233
+ actualEntry.status = backLog[0].status
234
+ }
235
+ fakegato.log(
236
+ '[%s] FG timer: callbackSwitch [%s] immediate [%s].',
237
+ fakegato.accessoryName,
238
+ actualEntry,
239
+ immediate,
240
+ )
241
+ fakegato._addEntry(actualEntry)
242
+ }
243
+ })
244
+ break
245
+ case 'custom':
246
+ thisAccessory.services.forEach((service) => {
247
+ service.characteristics.forEach((characteristic) => {
248
+ switch (toLongFormUUID(characteristic.UUID)) {
249
+ case Characteristic.CurrentTemperature.UUID:
250
+ this.signatures.push({
251
+ signature: '0102',
252
+ length: 4,
253
+ uuid: toShortFormUUID(characteristic.UUID),
254
+ factor: 100,
255
+ entry: 'temp',
256
+ })
257
+ break
258
+ case Characteristic.CurrentRelativeHumidity.UUID:
259
+ this.signatures.push({
260
+ signature: '0202',
261
+ length: 4,
262
+ uuid: toShortFormUUID(characteristic.UUID),
263
+ factor: 100,
264
+ entry: 'humidity',
265
+ })
266
+ break
267
+ case 'E863F10F-079E-48FF-8F27-9C2605A29F52':
268
+ this.signatures.push({
269
+ signature: '0302',
270
+ length: 4,
271
+ uuid: toShortFormUUID(characteristic.UUID),
272
+ factor: 10,
273
+ entry: 'pressure',
274
+ })
275
+ break
276
+ case 'E863F10B-079E-48FF-8F27-9C2605A29F52':
277
+ this.signatures.push({
278
+ signature: '0702',
279
+ length: 4,
280
+ uuid: toShortFormUUID(characteristic.UUID),
281
+ factor: 10,
282
+ entry: 'ppm',
283
+ })
284
+ break
285
+ case Characteristic.ContactSensorState.UUID:
286
+ this.signatures.push({
287
+ signature: '0601',
288
+ length: 2,
289
+ uuid: toShortFormUUID(characteristic.UUID),
290
+ factor: 1,
291
+ entry: 'contact',
292
+ })
293
+ break
294
+ case 'E863F10D-079E-48FF-8F27-9C2605A29F52':
295
+ this.signatures.push({
296
+ signature: '0702',
297
+ length: 4,
298
+ uuid: toShortFormUUID(characteristic.UUID),
299
+ factor: 10,
300
+ entry: 'power',
301
+ })
302
+ break
303
+ case Characteristic.On.UUID:
304
+ this.signatures.push({
305
+ signature: '0e01',
306
+ length: 2,
307
+ uuid: toShortFormUUID(characteristic.UUID),
308
+ factor: 1,
309
+ entry: 'status',
310
+ })
311
+ break
312
+ case Characteristic.MotionDetected.UUID:
313
+ this.signatures.push({
314
+ signature: '1c01',
315
+ length: 2,
316
+ uuid: toShortFormUUID(characteristic.UUID),
317
+ factor: 1,
318
+ entry: 'motion',
319
+ })
320
+ break
321
+ }
322
+ })
323
+ })
324
+ this.accessoryType116 = ` 0${
325
+ this.signatures.length.toString()
326
+ } ${
327
+ this.signatures
328
+ .sort((a, b) => (a.signature > b.signature ? 1 : -1))
329
+ .map(a => a.signature)
330
+ .join(' ')
331
+ } `
332
+ homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage)
333
+ break
334
+ case 'aqua':
335
+ this.accessoryType116 = '03 1f01 2a08 2302'
336
+ this.accessoryType117 = '05'
337
+ this.accessoryType117bis = '07'
338
+ break
339
+ case 'thermo':
340
+ this.accessoryType116 = '05 0102 1102 1001 1201 1d01'
341
+ this.accessoryType117 = '1f'
342
+ break
343
+ }
344
+ this.accessoryType = accessoryType
345
+ this.firstEntry = 0
346
+ this.lastEntry = 0
347
+ this.history = ['noValue']
348
+ this.memorySize = 4032
349
+ this.usedMemory = 0
350
+ this.currentEntry = 1
351
+ this.transfer = false
352
+ this.setTime = true
353
+ this.restarted = true
354
+ this.refTime = 0
355
+ this.memAddress = 0
356
+ this.dataStream = ''
357
+ this.saving = false
358
+ this.registerEvents()
359
+ }
360
+
361
+ calculateAverage(params) {
362
+ const backLog = params.backLog || []
363
+ const previousAvrg = params.previousAvrg || {}
364
+ const { timer } = params
365
+ const fakegato = this.service
366
+ const calc = {
367
+ sum: {},
368
+ num: {},
369
+ avrg: {},
370
+ }
371
+ for (const h in backLog) {
372
+ if (Object.prototype.hasOwnProperty.call(backLog, h)) {
373
+ for (const key in backLog[h]) {
374
+ if (Object.prototype.hasOwnProperty.call(backLog[h], key) && key !== 'time') {
375
+ if (!calc.sum[key]) {
376
+ calc.sum[key] = 0
377
+ }
378
+ if (!calc.num[key]) {
379
+ calc.num[key] = 0
380
+ }
381
+ calc.sum[key] += backLog[h][key]
382
+ calc.num[key]++
383
+ calc.avrg[key] = precisionRound(calc.sum[key] / calc.num[key], 2)
384
+ }
385
+ }
386
+ }
387
+ }
388
+ calc.avrg.time = Math.round(new Date().valueOf() / 1000)
389
+
390
+ for (const key in previousAvrg) {
391
+ if (Object.prototype.hasOwnProperty.call(previousAvrg, key) && key !== 'time') {
392
+ if (!backLog.length || calc.avrg[key] === undefined) {
393
+ calc.avrg[key] = previousAvrg[key]
394
+ }
395
+ }
396
+ }
397
+
398
+ if (Object.keys(calc.avrg).length > 1) {
399
+ fakegato._addEntry(calc.avrg)
400
+ timer.emptyData(fakegato)
401
+ }
402
+ return calc.avrg
403
+ }
404
+
405
+ registerEvents() {
406
+ this.log('[%s] FGH registering events.', thisAccessory.displayName)
407
+ this.service = thisAccessory.getService(FakeGatoHistoryService)
408
+ if (this.service === undefined) {
409
+ this.service = thisAccessory.addService(
410
+ FakeGatoHistoryService,
411
+ `${ucfirst(thisAccessory.displayName)} History`,
412
+ this.accessoryType,
413
+ )
414
+ }
415
+ this.service.getCharacteristic(S2R2Characteristic).onGet(() => this.getCurrentS2R2())
416
+ this.service.getCharacteristic(S2W1Characteristic).onSet((value) => {
417
+ this.setCurrentS2W1(value)
418
+ })
419
+ this.service.getCharacteristic(S2W2Characteristic).onSet((value) => {
420
+ this.setCurrentS2W2(value)
421
+ })
422
+ }
423
+
424
+ sendHistory(address) {
425
+ if (address !== 0) {
426
+ this.currentEntry = address
427
+ } else {
428
+ this.currentEntry = 1
429
+ }
430
+ this.transfer = true
431
+ }
432
+
433
+ addEntry(entry) {
434
+ entry.time = Math.round(new Date().valueOf() / 1000)
435
+ switch (this.accessoryType) {
436
+ case 'door':
437
+ case 'motion':
438
+ case 'switch':
439
+ homebridge.globalFakeGatoTimer.addData({
440
+ entry,
441
+ service: this,
442
+ immediateCallback: true,
443
+ })
444
+ break
445
+ case 'aqua':
446
+ this._addEntry({
447
+ time: entry.time,
448
+ status: entry.status,
449
+ waterAmount: entry.waterAmount,
450
+ })
451
+ break
452
+ case 'weather':
453
+ homebridge.globalFakeGatoTimer.addData({
454
+ entry,
455
+ service: this,
456
+ })
457
+ break
458
+ case 'room':
459
+ homebridge.globalFakeGatoTimer.addData({
460
+ entry,
461
+ service: this,
462
+ })
463
+ break
464
+ case 'energy':
465
+ homebridge.globalFakeGatoTimer.addData({
466
+ entry,
467
+ service: this,
468
+ })
469
+ break
470
+ case 'custom':
471
+ if ('power' in entry || 'temp' in entry || 'humidity' in entry) {
472
+ homebridge.globalFakeGatoTimer.addData({
473
+ entry,
474
+ service: this,
475
+ })
476
+ } else {
477
+ this._addEntry(entry)
478
+ }
479
+ break
480
+ default:
481
+ this._addEntry(entry)
482
+ break
483
+ }
484
+ }
485
+
486
+ _addEntry(entry) {
487
+ if (this.loaded) {
488
+ const entry2address = val => val % this.memorySize
489
+ let val
490
+ if (this.usedMemory < this.memorySize) {
491
+ this.usedMemory++
492
+ this.firstEntry = 0
493
+ this.lastEntry = this.usedMemory
494
+ } else {
495
+ this.firstEntry++
496
+ this.lastEntry = this.firstEntry + this.usedMemory
497
+ if (this.restarted) {
498
+ this.history[entry2address(this.lastEntry)] = {
499
+ time: entry.time,
500
+ setRefTime: 1,
501
+ }
502
+ this.firstEntry++
503
+ this.lastEntry = this.firstEntry + this.usedMemory
504
+ this.restarted = false
505
+ }
506
+ }
507
+ if (this.refTime === 0) {
508
+ this.refTime = entry.time - _EPOCH
509
+ this.history[this.lastEntry] = {
510
+ time: entry.time,
511
+ setRefTime: 1,
512
+ }
513
+ this.initialTime = entry.time
514
+ this.lastEntry++
515
+ this.usedMemory++
516
+ }
517
+ this.history[entry2address(this.lastEntry)] = entry
518
+ if (this.usedMemory < this.memorySize) {
519
+ val = Format(
520
+ '%s00000000%s%s%s%s%s000000000101',
521
+ numToHex(swap32(entry.time - this.refTime - _EPOCH), 8),
522
+ numToHex(swap32(this.refTime), 8),
523
+ this.accessoryType116,
524
+ numToHex(swap16(this.usedMemory + 1), 4),
525
+ numToHex(swap16(this.memorySize), 4),
526
+ numToHex(swap32(this.firstEntry), 8),
527
+ )
528
+ } else {
529
+ val = Format(
530
+ '%s00000000%s%s%s%s%s000000000101',
531
+ numToHex(swap32(entry.time - this.refTime - _EPOCH), 8),
532
+ numToHex(swap32(this.refTime), 8),
533
+ this.accessoryType116,
534
+ numToHex(swap16(this.usedMemory), 4),
535
+ numToHex(swap16(this.memorySize), 4),
536
+ numToHex(swap32(this.firstEntry + 1), 8),
537
+ )
538
+ }
539
+ this.service.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val))
540
+ this.log(
541
+ '[%s] FGH first entry [%s] last entry [%s] used memory [%s] 116 [%s].',
542
+ this.accessoryName,
543
+ this.firstEntry.toString(16),
544
+ this.lastEntry.toString(16),
545
+ this.usedMemory.toString(16),
546
+ val,
547
+ )
548
+ this.save()
549
+ } else {
550
+ setTimeout(() => this._addEntry(entry), 100)
551
+ }
552
+ }
553
+
554
+ getInitialTime() {
555
+ return this.initialTime || 0
556
+ }
557
+
558
+ setExtraPersistedData(extra) {
559
+ this.extra = extra
560
+ }
561
+
562
+ getExtraPersistedData() {
563
+ return this.extra
564
+ }
565
+
566
+ isHistoryLoaded() {
567
+ return this.loaded
568
+ }
569
+
570
+ save() {
571
+ if (this.loaded) {
572
+ const data = {
573
+ firstEntry: this.firstEntry,
574
+ lastEntry: this.lastEntry,
575
+ usedMemory: this.usedMemory,
576
+ refTime: this.refTime,
577
+ initialTime: this.initialTime,
578
+ history: this.history,
579
+ extra: this.extra,
580
+ }
581
+ homebridge.globalFakeGatoStorage.write({
582
+ service: this,
583
+ data: typeof data === 'object' ? JSON.stringify(data) : data,
584
+ })
585
+ } else {
586
+ setTimeout(() => this.save(), 100)
587
+ }
588
+ }
589
+
590
+ load(cb) {
591
+ this.log('[%s] FGH loading.', this.accessoryName)
592
+ homebridge.globalFakeGatoStorage.read({
593
+ service: this,
594
+ callback: function (err, data) {
595
+ if (!err) {
596
+ if (data) {
597
+ try {
598
+ // this.log('[%s] read data [%s].', this.accessoryName, data)
599
+ const jsonFile = typeof data === 'object' ? data : JSON.parse(data)
600
+ this.firstEntry = jsonFile.firstEntry
601
+ this.lastEntry = jsonFile.lastEntry
602
+ this.usedMemory = jsonFile.usedMemory
603
+ this.refTime = jsonFile.refTime
604
+ this.initialTime = jsonFile.initialTime
605
+ this.history = jsonFile.history
606
+ this.extra = jsonFile.extra
607
+ } catch (e) {
608
+ this.log('FGH error fetching data - invalid JSON [%s].', e)
609
+ cb(e, false)
610
+ }
611
+ cb(null, true)
612
+ }
613
+ } else {
614
+ cb(null, false)
615
+ }
616
+ }.bind(this),
617
+ })
618
+ }
619
+
620
+ cleanPersist() {
621
+ this.log('FGH cleaning.')
622
+ homebridge.globalFakeGatoStorage.remove({ service: this })
623
+ }
624
+
625
+ getCurrentS2R2() {
626
+ const entry2address = val => val % this.memorySize
627
+ if (this.currentEntry <= this.lastEntry && this.transfer) {
628
+ this.memAddress = entry2address(this.currentEntry)
629
+ for (let i = 0; i < 11; i += 1) {
630
+ if (
631
+ this.history[this.memAddress].setRefTime === 1
632
+ || this.setTime
633
+ || this.currentEntry === this.firstEntry + 1
634
+ ) {
635
+ this.dataStream += Format(
636
+ ',15%s 0100 0000 81%s0000 0000 00 0000',
637
+ numToHex(swap32(this.currentEntry), 8),
638
+ numToHex(swap32(this.refTime), 8),
639
+ )
640
+ this.setTime = false
641
+ } else {
642
+ this.log(
643
+ '[%s] FGH entry [%s] address [%s].',
644
+ this.accessoryName,
645
+ this.currentEntry,
646
+ this.memAddress,
647
+ )
648
+ switch (this.accessoryType) {
649
+ case 'weather':
650
+ this.dataStream += Format(
651
+ ',10 %s%s-%s:%s %s %s',
652
+ numToHex(swap32(this.currentEntry), 8),
653
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
654
+ this.accessoryType117,
655
+ numToHex(swap16(this.history[this.memAddress].temp * 100), 4),
656
+ numToHex(swap16(this.history[this.memAddress].humidity * 100), 4),
657
+ numToHex(swap16(this.history[this.memAddress].pressure * 10), 4),
658
+ )
659
+ break
660
+ case 'energy':
661
+ this.dataStream += Format(
662
+ ',14 %s%s-%s:0000 0000 %s 0000 0000',
663
+ numToHex(swap32(this.currentEntry), 8),
664
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
665
+ this.accessoryType117,
666
+ numToHex(swap16(this.history[this.memAddress].power * 10), 4),
667
+ )
668
+ break
669
+ case 'room':
670
+ this.dataStream += Format(
671
+ ',13 %s%s%s%s%s%s0000 00',
672
+ numToHex(swap32(this.currentEntry), 8),
673
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
674
+ this.accessoryType117,
675
+ numToHex(swap16(this.history[this.memAddress].temp * 100), 4),
676
+ numToHex(swap16(this.history[this.memAddress].humidity * 100), 4),
677
+ numToHex(swap16(this.history[this.memAddress].ppm), 4),
678
+ )
679
+ break
680
+ case 'door':
681
+ case 'motion':
682
+ case 'switch':
683
+ this.dataStream += Format(
684
+ ',0b %s%s%s%s',
685
+ numToHex(swap32(this.currentEntry), 8),
686
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
687
+ this.accessoryType117,
688
+ numToHex(this.history[this.memAddress].status, 2),
689
+ )
690
+ break
691
+ case 'aqua':
692
+ if (this.history[this.memAddress].status) {
693
+ this.dataStream += Format(
694
+ ',0d %s%s%s%s 300c',
695
+ numToHex(swap32(this.currentEntry), 8),
696
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
697
+ this.accessoryType117,
698
+ numToHex(this.history[this.memAddress].status, 2),
699
+ )
700
+ } else {
701
+ this.dataStream += Format(
702
+ ',15 %s%s%s%s%s 00000000 300c',
703
+ numToHex(swap32(this.currentEntry), 8),
704
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
705
+ this.accessoryType117bis,
706
+ numToHex(this.history[this.memAddress].status, 2),
707
+ numToHex(swap32(this.history[this.memAddress].waterAmount), 8),
708
+ )
709
+ }
710
+ break
711
+ case 'thermo':
712
+ this.dataStream += Format(
713
+ ',11 %s%s%s%s%s%s 0000',
714
+ numToHex(swap32(this.currentEntry), 8),
715
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
716
+ this.accessoryType117,
717
+ numToHex(swap16(this.history[this.memAddress].currentTemp * 100), 4),
718
+ numToHex(swap16(this.history[this.memAddress].setTemp * 100), 4),
719
+ numToHex(this.history[this.memAddress].valvePosition, 2),
720
+ )
721
+ break
722
+ case 'custom': {
723
+ const result = []
724
+ let bitmask = 0
725
+ const dataStream = Format(
726
+ '%s%s',
727
+ numToHex(swap32(this.currentEntry), 8),
728
+ numToHex(swap32(this.history[this.memAddress].time - this.refTime - _EPOCH), 8),
729
+ )
730
+ for (const [k, v] of Object.entries(this.history[this.memAddress])) {
731
+ switch (k) {
732
+ case 'time':
733
+ break
734
+ default:
735
+ for (let x = 0, iLen = this.signatures.length; x < iLen; x++) {
736
+ if (this.signatures[x].entry === k) {
737
+ switch (this.signatures[x].length) {
738
+ case 8:
739
+ result[x] = Format(
740
+ '%s',
741
+ numToHex(
742
+ swap32(v * this.signatures[x].factor),
743
+ this.signatures[x].length,
744
+ ),
745
+ )
746
+ break
747
+ case 4:
748
+ result[x] = Format(
749
+ '%s',
750
+ numToHex(
751
+ swap16(v * this.signatures[x].factor),
752
+ this.signatures[x].length,
753
+ ),
754
+ )
755
+ break
756
+ case 2:
757
+ result[x] = Format(
758
+ '%s',
759
+ numToHex(v * this.signatures[x].factor, this.signatures[x].length),
760
+ )
761
+ break
762
+ }
763
+ bitmask += 2 ** x
764
+ }
765
+ }
766
+ }
767
+ }
768
+ const results = `${dataStream} ${numToHex(bitmask, 2)} ${result.map(a => a).join(' ')}`
769
+ this.dataStream
770
+ += ` ${
771
+ numToHex(results.replace(/[^0-9A-F]/gi, '').length / 2 + 1)
772
+ } ${
773
+ results
774
+ },`
775
+ break
776
+ }
777
+ }
778
+ }
779
+ this.currentEntry++
780
+ this.memAddress = entry2address(this.currentEntry)
781
+ if (this.currentEntry > this.lastEntry) {
782
+ break
783
+ }
784
+ }
785
+ this.log('[%s] FGH data [%s].', this.accessoryName, this.dataStream)
786
+ const toReturn = hexToBase64(this.dataStream)
787
+ this.dataStream = ''
788
+ return toReturn
789
+ }
790
+ this.transfer = false
791
+ return hexToBase64('00')
792
+ }
793
+
794
+ setCurrentS2W1(value) {
795
+ this.log('[%s] FGH data req [%s].', this.accessoryName, base64ToHex(value))
796
+ const valHex = base64ToHex(value)
797
+ const substring = valHex.substring(4, 12)
798
+ const valInt = Number.parseInt(substring, 16)
799
+ const address = swap32(valInt)
800
+ const hexAddress = address.toString('16')
801
+ this.log('[%s] FGH address req [%s].', this.accessoryName, hexAddress)
802
+ this.sendHistory(address)
803
+ }
804
+
805
+ setCurrentS2W2(value) {
806
+ this.log('[%s] FGH clock adj [%s].', this.accessoryName, base64ToHex(value))
807
+ }
808
+ }
809
+
810
+ FakeGatoHistoryService.UUID = 'E863F007-079E-48FF-8F27-9C2605A29F52'
811
+
812
+ return FakeGatoHistory
813
+ }