homebridge-easy-mqtt 1.5.0-beta.0 → 1.5.0-beta.1
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 +1 -1
- package/README.md +2 -0
- package/dist/homebridge-ui/public/index.html +1 -1
- package/lib/fakegato-history/fakegato-history.js +887 -0
- package/lib/fakegato-history/fakegato-storage.js +178 -0
- package/lib/fakegato-history/fakegato-timer.js +133 -0
- package/lib/fakegato-history/lib/uuid.js +25 -0
- package/lib/fakegato-history/package.json +5 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to homebridge-dummy will be documented in this file.
|
|
4
4
|
|
|
5
|
-
## 1.5.0-beta.
|
|
5
|
+
## 1.5.0-beta.1 (2025-10-20)
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
- [Value Transformers](https://github.com/mpatfield/homebridge-easy-mqtt#history) (Thank you [@nehmeroumani](https://github.com/sponsors/nehmeroumani) for the code contribution!)
|
package/README.md
CHANGED
|
@@ -732,4 +732,6 @@ By default, all accessory values are saved and reloaded when the plugin restarts
|
|
|
732
732
|
|
|
733
733
|
[Keryan Belahcene](https://www.instagram.com/keryan.me) for creating the [Flume](https://github.com/homebridge-plugins/homebridge-flume) header logo which I adapted for this plugin
|
|
734
734
|
|
|
735
|
+
[fakegato-history](https://github.com/simont77/fakegato-history) by [@simont77](https://github.com/sponsors/simont77) *Copyright © 2017*
|
|
736
|
+
|
|
735
737
|
And to the amazing creators/contributors of [Homebridge](https://homebridge.io) who made this plugin possible!
|
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
/*jshint esversion: 6,node: true,-W041: false */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const Format = require('util').format;
|
|
5
|
+
const FakeGatoTimer = require('./fakegato-timer').FakeGatoTimer;
|
|
6
|
+
const FakeGatoStorage = require('./fakegato-storage').FakeGatoStorage;
|
|
7
|
+
|
|
8
|
+
const EPOCH_OFFSET = 978307200;
|
|
9
|
+
|
|
10
|
+
const TYPE_ENERGY = 'energy',
|
|
11
|
+
TYPE_ROOM = 'room',
|
|
12
|
+
TYPE_ROOM2 = 'room2',
|
|
13
|
+
TYPE_WEATHER = 'weather',
|
|
14
|
+
TYPE_DOOR = 'door',
|
|
15
|
+
TYPE_MOTION = 'motion',
|
|
16
|
+
TYPE_SWITCH = 'switch',
|
|
17
|
+
TYPE_THERMO = 'thermo',
|
|
18
|
+
TYPE_AQUA = 'aqua',
|
|
19
|
+
TYPE_CUSTOM = 'custom';
|
|
20
|
+
|
|
21
|
+
var homebridge;
|
|
22
|
+
var Characteristic, Service, Formats, Perms;
|
|
23
|
+
|
|
24
|
+
module.exports = function (pHomebridge) {
|
|
25
|
+
if (pHomebridge && !homebridge) {
|
|
26
|
+
homebridge = pHomebridge;
|
|
27
|
+
Characteristic = homebridge.hap.Characteristic;
|
|
28
|
+
Service = homebridge.hap.Service;
|
|
29
|
+
Formats = homebridge.hap.Formats;
|
|
30
|
+
Perms = homebridge.hap.Perms;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
var hexToBase64 = function (val) {
|
|
35
|
+
return Buffer.from(('' + val).replace(/[^0-9A-F]/ig, ''), 'hex').toString('base64');
|
|
36
|
+
},
|
|
37
|
+
base64ToHex = function (val) {
|
|
38
|
+
if (!val)
|
|
39
|
+
return val;
|
|
40
|
+
return Buffer.from(val, 'base64').toString('hex');
|
|
41
|
+
},
|
|
42
|
+
swap16 = function (val) {
|
|
43
|
+
return ((val & 0xFF) << 8)
|
|
44
|
+
| ((val >>> 8) & 0xFF);
|
|
45
|
+
},
|
|
46
|
+
swap32 = function (val) {
|
|
47
|
+
return ((val & 0xFF) << 24)
|
|
48
|
+
| ((val & 0xFF00) << 8)
|
|
49
|
+
| ((val >>> 8) & 0xFF00)
|
|
50
|
+
| ((val >>> 24) & 0xFF);
|
|
51
|
+
},
|
|
52
|
+
hexToHPA = function (val) { //unused
|
|
53
|
+
return parseInt(swap16(val), 10);
|
|
54
|
+
},
|
|
55
|
+
hPAtoHex = function (val) { //unused
|
|
56
|
+
return swap16(Math.round(val)).toString(16);
|
|
57
|
+
},
|
|
58
|
+
numToHex = function (val, len) {
|
|
59
|
+
var s = Number(val >>> 0).toString(16);
|
|
60
|
+
if (s.length % 2 != 0) {
|
|
61
|
+
s = '0' + s;
|
|
62
|
+
}
|
|
63
|
+
if (len) {
|
|
64
|
+
return ('0000000000000' + s).slice(-1 * len);
|
|
65
|
+
}
|
|
66
|
+
return s;
|
|
67
|
+
},
|
|
68
|
+
ucfirst = function (val) {
|
|
69
|
+
return val.charAt(0).toUpperCase() + val.substr(1);
|
|
70
|
+
},
|
|
71
|
+
precisionRound = function (number, precision) {
|
|
72
|
+
var factor = Math.pow(10, precision);
|
|
73
|
+
return Math.round(number * factor) / factor;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
class S2R1Characteristic extends Characteristic {
|
|
77
|
+
constructor() {
|
|
78
|
+
super('S2R1', S2R1Characteristic.UUID);
|
|
79
|
+
this.setProps({
|
|
80
|
+
format: Formats.DATA,
|
|
81
|
+
perms: [
|
|
82
|
+
Perms.PAIRED_READ, Perms.NOTIFY, Perms.HIDDEN
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
S2R1Characteristic.UUID = 'E863F116-079E-48FF-8F27-9C2605A29F52';
|
|
89
|
+
|
|
90
|
+
class S2R2Characteristic extends Characteristic {
|
|
91
|
+
constructor() {
|
|
92
|
+
super('S2R2', S2R2Characteristic.UUID);
|
|
93
|
+
this.setProps({
|
|
94
|
+
format: Formats.DATA,
|
|
95
|
+
perms: [
|
|
96
|
+
Perms.PAIRED_READ, Perms.NOTIFY, Perms.HIDDEN
|
|
97
|
+
]
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
S2R2Characteristic.UUID = 'E863F117-079E-48FF-8F27-9C2605A29F52';
|
|
103
|
+
|
|
104
|
+
class S2W1Characteristic extends Characteristic {
|
|
105
|
+
constructor() {
|
|
106
|
+
super('S2W1', S2W1Characteristic.UUID);
|
|
107
|
+
this.setProps({
|
|
108
|
+
format: Formats.DATA,
|
|
109
|
+
perms: [
|
|
110
|
+
Perms.PAIRED_WRITE, Perms.HIDDEN
|
|
111
|
+
]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
S2W1Characteristic.UUID = 'E863F11C-079E-48FF-8F27-9C2605A29F52';
|
|
117
|
+
|
|
118
|
+
class S2W2Characteristic extends Characteristic {
|
|
119
|
+
constructor() {
|
|
120
|
+
super('S2W2', S2W2Characteristic.UUID);
|
|
121
|
+
this.setProps({
|
|
122
|
+
format: Formats.DATA,
|
|
123
|
+
perms: [
|
|
124
|
+
Perms.PAIRED_WRITE, Perms.HIDDEN
|
|
125
|
+
]
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
S2W2Characteristic.UUID = 'E863F121-079E-48FF-8F27-9C2605A29F52';
|
|
131
|
+
|
|
132
|
+
class FakeGatoHistoryService extends Service {
|
|
133
|
+
constructor(displayName, subtype) {
|
|
134
|
+
super(displayName, FakeGatoHistoryService.UUID, subtype);
|
|
135
|
+
|
|
136
|
+
this.addCharacteristic(S2R1Characteristic);
|
|
137
|
+
this.addCharacteristic(S2R2Characteristic);
|
|
138
|
+
this.addCharacteristic(S2W1Characteristic);
|
|
139
|
+
this.addCharacteristic(S2W2Characteristic);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
FakeGatoHistoryService.UUID = 'E863F007-079E-48FF-8F27-9C2605A29F52';
|
|
144
|
+
var thisAccessory = {};
|
|
145
|
+
class FakeGatoHistory extends Service {
|
|
146
|
+
constructor(accessoryType, accessory, optionalParams) {
|
|
147
|
+
|
|
148
|
+
super(accessory.displayName + " History", FakeGatoHistoryService.UUID);
|
|
149
|
+
|
|
150
|
+
var entry2address = function (val) { // not used ?
|
|
151
|
+
var temp = val % this.memorySize;
|
|
152
|
+
return temp;
|
|
153
|
+
}.bind(this);
|
|
154
|
+
|
|
155
|
+
thisAccessory = accessory;
|
|
156
|
+
this.accessoryName = thisAccessory.displayName;
|
|
157
|
+
this.signatures = [];
|
|
158
|
+
this.uuid = require('./lib/uuid.js');
|
|
159
|
+
|
|
160
|
+
if (typeof (optionalParams) === 'object') {
|
|
161
|
+
this.size = optionalParams.size || 4032;
|
|
162
|
+
this.minutes = optionalParams.minutes || 10; // Optional timer length
|
|
163
|
+
this.storage = optionalParams.storage; // 'fs' or 'googleDrive'
|
|
164
|
+
this.path = optionalParams.path || optionalParams.folder || (this.storage == 'fs' ? homebridge.user.storagePath() : undefined);
|
|
165
|
+
this.filename = optionalParams.filename;
|
|
166
|
+
this.disableTimer = optionalParams.disableTimer || false;
|
|
167
|
+
this.disableRepeatLastData = optionalParams.disableRepeatLastData || false;
|
|
168
|
+
this.log = optionalParams.log || thisAccessory.log || {}; // workaround for typescript blocking of changing of accessory object definition
|
|
169
|
+
} else {
|
|
170
|
+
this.size = 4032;
|
|
171
|
+
this.minutes = 10;
|
|
172
|
+
this.disableTimer = false;
|
|
173
|
+
this.log = thisAccessory.log || {};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!this.log.debug) {
|
|
177
|
+
this.log.debug = function () { };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!this.disableTimer) {
|
|
181
|
+
if (homebridge.globalFakeGatoTimer === undefined)
|
|
182
|
+
homebridge.globalFakeGatoTimer = new FakeGatoTimer({
|
|
183
|
+
minutes: this.minutes,
|
|
184
|
+
log: this.log
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (this.storage !== undefined) {
|
|
189
|
+
this.loaded = false;
|
|
190
|
+
if (homebridge.globalFakeGatoStorage === undefined) {
|
|
191
|
+
homebridge.globalFakeGatoStorage = new FakeGatoStorage({
|
|
192
|
+
log: this.log
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
homebridge.globalFakeGatoStorage.addWriter(this, {
|
|
196
|
+
storage: this.storage,
|
|
197
|
+
path: this.path,
|
|
198
|
+
filename: this.filename,
|
|
199
|
+
keyPath: optionalParams.keyPath || homebridge.user.storagePath() || undefined,
|
|
200
|
+
onReady: function () {
|
|
201
|
+
|
|
202
|
+
this.load(function (err, loaded) {
|
|
203
|
+
//this.log.debug("**Fakegato-history Loaded",loaded);
|
|
204
|
+
//this.registerEvents();
|
|
205
|
+
if (err) this.log.debug('**Fakegato-history Load error :', err);
|
|
206
|
+
else {
|
|
207
|
+
if (loaded) this.log.debug('**Fakegato-history History Loaded from Persistant Storage');
|
|
208
|
+
this.loaded = true;
|
|
209
|
+
}
|
|
210
|
+
}.bind(this));
|
|
211
|
+
}.bind(this)
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
switch (accessoryType) {
|
|
217
|
+
case TYPE_WEATHER:
|
|
218
|
+
this.accessoryType116 = "03 0102 0202 0302";
|
|
219
|
+
this.accessoryType117 = "07";
|
|
220
|
+
if (!this.disableTimer) {
|
|
221
|
+
homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage);
|
|
222
|
+
}
|
|
223
|
+
break;
|
|
224
|
+
case TYPE_ENERGY:
|
|
225
|
+
this.accessoryType116 = "04 0102 0202 0702 0f03";
|
|
226
|
+
this.accessoryType117 = "1f";
|
|
227
|
+
if (!this.disableTimer) {
|
|
228
|
+
homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage);
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
case TYPE_ROOM:
|
|
232
|
+
this.accessoryType116 = "04 0102 0202 0402 0f03";
|
|
233
|
+
this.accessoryType117 = "0f";
|
|
234
|
+
if (!this.disableTimer) {
|
|
235
|
+
homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage);
|
|
236
|
+
}
|
|
237
|
+
break;
|
|
238
|
+
case TYPE_ROOM2:
|
|
239
|
+
this.accessoryType116 = "07 0102 0202 2202 2901 2501 2302 2801";
|
|
240
|
+
this.accessoryType117 = "7f";
|
|
241
|
+
if (!this.disableTimer) {
|
|
242
|
+
homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage);
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
case TYPE_DOOR:
|
|
246
|
+
this.accessoryType116 = "01 0601";
|
|
247
|
+
this.accessoryType117 = "01";
|
|
248
|
+
if (!this.disableTimer) {
|
|
249
|
+
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback
|
|
250
|
+
var backLog = params.backLog || [];
|
|
251
|
+
var immediate = params.immediate;
|
|
252
|
+
|
|
253
|
+
var fakegato = this.service;
|
|
254
|
+
var actualEntry = {};
|
|
255
|
+
|
|
256
|
+
if (backLog.length) {
|
|
257
|
+
if (!immediate) {
|
|
258
|
+
actualEntry.time = Math.round(new Date().valueOf() / 1000);
|
|
259
|
+
actualEntry.status = backLog[0].status;
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
actualEntry.time = backLog[0].time;
|
|
263
|
+
actualEntry.status = backLog[0].status;
|
|
264
|
+
}
|
|
265
|
+
fakegato.log.debug('**Fakegato-timer callbackDoor: ', fakegato.accessoryName, ', immediate: ', immediate, ', entry: ', actualEntry);
|
|
266
|
+
|
|
267
|
+
fakegato._addEntry(actualEntry);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
case TYPE_MOTION:
|
|
273
|
+
this.accessoryType116 = "02 1301 1c01";
|
|
274
|
+
this.accessoryType117 = "02";
|
|
275
|
+
if (!this.disableTimer) {
|
|
276
|
+
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback
|
|
277
|
+
var backLog = params.backLog || [];
|
|
278
|
+
var immediate = params.immediate;
|
|
279
|
+
|
|
280
|
+
var fakegato = this.service;
|
|
281
|
+
var actualEntry = {};
|
|
282
|
+
|
|
283
|
+
if (backLog.length) {
|
|
284
|
+
if (!immediate) {
|
|
285
|
+
actualEntry.time = Math.round(new Date().valueOf() / 1000);
|
|
286
|
+
actualEntry.status = backLog[0].status;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
actualEntry.time = backLog[0].time;
|
|
290
|
+
actualEntry.status = backLog[0].status;
|
|
291
|
+
}
|
|
292
|
+
fakegato.log.debug('**Fakegato-timer callbackMotion: ', fakegato.accessoryName, ', immediate: ', immediate, ', entry: ', actualEntry);
|
|
293
|
+
|
|
294
|
+
fakegato._addEntry(actualEntry);
|
|
295
|
+
}
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
break;
|
|
299
|
+
case TYPE_SWITCH:
|
|
300
|
+
this.accessoryType116 = "01 0e01";
|
|
301
|
+
this.accessoryType117 = "01";
|
|
302
|
+
if (!this.disableTimer) {
|
|
303
|
+
homebridge.globalFakeGatoTimer.subscribe(this, function (params) { // callback
|
|
304
|
+
var backLog = params.backLog || [];
|
|
305
|
+
var immediate = params.immediate;
|
|
306
|
+
|
|
307
|
+
var fakegato = this.service;
|
|
308
|
+
var actualEntry = {};
|
|
309
|
+
|
|
310
|
+
if (backLog.length) {
|
|
311
|
+
if (!immediate) {
|
|
312
|
+
actualEntry.time = Math.round(new Date().valueOf() / 1000);
|
|
313
|
+
actualEntry.status = backLog[0].status;
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
actualEntry.time = backLog[0].time;
|
|
317
|
+
actualEntry.status = backLog[0].status;
|
|
318
|
+
}
|
|
319
|
+
fakegato.log.debug('**Fakegato-timer callbackSwitch: ', fakegato.accessoryName, ', immediate: ', immediate, ', entry: ', actualEntry);
|
|
320
|
+
|
|
321
|
+
fakegato._addEntry(actualEntry);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
break;
|
|
326
|
+
case TYPE_CUSTOM:
|
|
327
|
+
thisAccessory.services.forEach((service, i) => {
|
|
328
|
+
service.characteristics.forEach((characteristic, i) => {
|
|
329
|
+
// console.log('**Fakegato-history characteristics', characteristic.displayName, characteristic.UUID);
|
|
330
|
+
switch(this.uuid.toLongFormUUID(characteristic.UUID)) {
|
|
331
|
+
case Characteristic.CurrentTemperature.UUID: // Temperature
|
|
332
|
+
this.signatures.push({ signature: '0102', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 100, entry: "temp" });
|
|
333
|
+
break;
|
|
334
|
+
case Characteristic.VOCDensity.UUID: // VOC Density
|
|
335
|
+
this.signatures.push({ signature: '2202', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 1, entry: "voc" });
|
|
336
|
+
break;
|
|
337
|
+
case Characteristic.CurrentRelativeHumidity.UUID: // Humidity
|
|
338
|
+
this.signatures.push({ signature: '0202', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 100, entry: "humidity" });
|
|
339
|
+
break;
|
|
340
|
+
case 'E863F10F-079E-48FF-8F27-9C2605A29F52': // CustomCharacteristic.AtmosphericPressureLevel.UUID
|
|
341
|
+
this.signatures.push({ signature: '0302', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 10, entry: "pressure" });
|
|
342
|
+
break;
|
|
343
|
+
case 'E863F10B-079E-48FF-8F27-9C2605A29F52': // PPM
|
|
344
|
+
this.signatures.push({ signature: '0702', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 10, entry: "ppm" });
|
|
345
|
+
break;
|
|
346
|
+
case Characteristic.ContactSensorState.UUID: // Contact Sensor State
|
|
347
|
+
this.signatures.push({ signature: '0601', length: 2, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 1, entry: "contact" });
|
|
348
|
+
break;
|
|
349
|
+
case 'E863F10D-079E-48FF-8F27-9C2605A29F52': // Power
|
|
350
|
+
this.signatures.push({ signature: '0702', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 10, entry: "power" });
|
|
351
|
+
break;
|
|
352
|
+
case Characteristic.On.UUID: // Switch On
|
|
353
|
+
this.signatures.push({ signature: '0e01', length: 2, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 1, entry: "status" });
|
|
354
|
+
break;
|
|
355
|
+
case Characteristic.MotionDetected.UUID: // Motion Detected
|
|
356
|
+
this.signatures.push({ signature: '1c01', length: 2, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 1, entry: "motion" });
|
|
357
|
+
break;
|
|
358
|
+
case Characteristic.TargetTemperature.UUID: // Target Temperature
|
|
359
|
+
this.signatures.push({ signature: '1102', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 100, entry: "setTemp" });
|
|
360
|
+
break;
|
|
361
|
+
case 'E863F12E-079E-48FF-8F27-9C2605A29F52': // Valve Position
|
|
362
|
+
this.signatures.push({ signature: '1001', length: 2, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 1, entry: "valvePosition" });
|
|
363
|
+
break;
|
|
364
|
+
case '0000006B-0000-1000-8000-0026BB765291': // CurrentAmbiantLightLevel
|
|
365
|
+
this.signatures.push({ signature: '3002', length: 4, uuid: this.uuid.toShortFormUUID(characteristic.UUID), factor: 1, entry: "lux" });
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
this.accessoryType116 = (' 0' + this.signatures.length.toString() + ' ' + this.signatures.sort((a, b) => (a.signature > b.signature) ? 1 : -1).map(a => a.signature).join(' ') + ' ');
|
|
371
|
+
if (!this.disableTimer) {
|
|
372
|
+
homebridge.globalFakeGatoTimer.subscribe(this, this.calculateAverage);
|
|
373
|
+
}
|
|
374
|
+
break;
|
|
375
|
+
case TYPE_AQUA:
|
|
376
|
+
this.accessoryType116 = "03 1f01 2a08 2302";
|
|
377
|
+
this.accessoryType117 = "05";
|
|
378
|
+
this.accessoryType117bis = "07";
|
|
379
|
+
break;
|
|
380
|
+
case TYPE_THERMO:
|
|
381
|
+
this.accessoryType116 = "05 0102 1102 1001 1201 1d01";
|
|
382
|
+
this.accessoryType117 = "1f";
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
this.accessoryType = accessoryType;
|
|
387
|
+
this.firstEntry = 0;
|
|
388
|
+
this.lastEntry = 0;
|
|
389
|
+
this.history = ["noValue"];
|
|
390
|
+
this.memorySize = this.size;
|
|
391
|
+
this.usedMemory = 0;
|
|
392
|
+
this.currentEntry = 1;
|
|
393
|
+
this.transfer = false;
|
|
394
|
+
this.setTime = true;
|
|
395
|
+
this.restarted = true;
|
|
396
|
+
this.refTime = 0;
|
|
397
|
+
this.memoryAddress = 0;
|
|
398
|
+
this.dataStream = '';
|
|
399
|
+
|
|
400
|
+
this.saving = false;
|
|
401
|
+
|
|
402
|
+
this.registerEvents();
|
|
403
|
+
if (this.storage === undefined) {
|
|
404
|
+
this.loaded = true;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
calculateAverage(params) { // callback
|
|
409
|
+
var backLog = params.backLog || [];
|
|
410
|
+
var previousAvrg = params.previousAvrg || {};
|
|
411
|
+
var timer = params.timer;
|
|
412
|
+
|
|
413
|
+
var fakegato = this.service;
|
|
414
|
+
var calc = {
|
|
415
|
+
sum: {},
|
|
416
|
+
num: {},
|
|
417
|
+
avrg: {}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
for (var h in backLog) {
|
|
421
|
+
if (backLog.hasOwnProperty(h)) { // only valid keys
|
|
422
|
+
for (let key in backLog[h]) { // each record
|
|
423
|
+
if (backLog[h].hasOwnProperty(key) && key != 'time') { // except time
|
|
424
|
+
if (!calc.sum[key])
|
|
425
|
+
calc.sum[key] = 0;
|
|
426
|
+
if (!calc.num[key])
|
|
427
|
+
calc.num[key] = 0;
|
|
428
|
+
calc.sum[key] += backLog[h][key];
|
|
429
|
+
calc.num[key]++;
|
|
430
|
+
calc.avrg[key] = precisionRound(calc.sum[key] / calc.num[key], 2);
|
|
431
|
+
if (key == 'voc') // VOC expects integers
|
|
432
|
+
calc.avrg[key] = Math.round(calc.avrg[key]);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
calc.avrg.time = Math.round(new Date().valueOf() / 1000); // set the time of the avrg
|
|
438
|
+
|
|
439
|
+
if(!fakegato.disableRepeatLastData) {
|
|
440
|
+
for (let key in previousAvrg) { // each record of previous average
|
|
441
|
+
if (previousAvrg.hasOwnProperty(key) && key != 'time') { // except time
|
|
442
|
+
if (!backLog.length ||//calc.avrg[key] == 0 || // zero value
|
|
443
|
+
calc.avrg[key] === undefined) // no key (meaning no value received for this key yet)
|
|
444
|
+
{
|
|
445
|
+
calc.avrg[key] = previousAvrg[key];
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (Object.keys(calc.avrg).length > 1) {
|
|
452
|
+
fakegato._addEntry(calc.avrg);
|
|
453
|
+
timer.emptyData(fakegato);
|
|
454
|
+
}
|
|
455
|
+
return calc.avrg;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
registerEvents() {
|
|
459
|
+
this.log.debug('**Fakegato-history Registring Events', thisAccessory.displayName);
|
|
460
|
+
if (typeof thisAccessory.getService === "function") {
|
|
461
|
+
// Platform API
|
|
462
|
+
this.log.debug('**Fakegato-history Platform', thisAccessory.displayName);
|
|
463
|
+
|
|
464
|
+
this.service = thisAccessory.getService(FakeGatoHistoryService);
|
|
465
|
+
if (this.service === undefined) {
|
|
466
|
+
this.service = thisAccessory.addService(FakeGatoHistoryService, ucfirst(thisAccessory.displayName) + ' History', this.accessoryType);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
this.service.getCharacteristic(S2R2Characteristic)
|
|
470
|
+
.on('get', this.getCurrentS2R2.bind(this));
|
|
471
|
+
|
|
472
|
+
this.service.getCharacteristic(S2W1Characteristic)
|
|
473
|
+
.on('set', this.setCurrentS2W1.bind(this));
|
|
474
|
+
|
|
475
|
+
this.service.getCharacteristic(S2W2Characteristic)
|
|
476
|
+
.on('set', this.setCurrentS2W2.bind(this));
|
|
477
|
+
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
// Accessory API
|
|
481
|
+
this.log.debug('**Fakegato-history Accessory', thisAccessory.displayName);
|
|
482
|
+
|
|
483
|
+
this.addCharacteristic(S2R1Characteristic);
|
|
484
|
+
|
|
485
|
+
this.addCharacteristic(S2R2Characteristic)
|
|
486
|
+
.on('get', this.getCurrentS2R2.bind(this));
|
|
487
|
+
|
|
488
|
+
this.addCharacteristic(S2W1Characteristic)
|
|
489
|
+
.on('set', this.setCurrentS2W1.bind(this));
|
|
490
|
+
|
|
491
|
+
this.addCharacteristic(S2W2Characteristic)
|
|
492
|
+
.on('set', this.setCurrentS2W2.bind(this));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
sendHistory(address) {
|
|
497
|
+
if (address != 0) {
|
|
498
|
+
this.currentEntry = address;
|
|
499
|
+
} else {
|
|
500
|
+
this.currentEntry = 1;
|
|
501
|
+
}
|
|
502
|
+
this.transfer = true;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
addEntry(entry) {
|
|
506
|
+
switch (this.accessoryType) {
|
|
507
|
+
case TYPE_DOOR:
|
|
508
|
+
case TYPE_MOTION:
|
|
509
|
+
case TYPE_SWITCH:
|
|
510
|
+
if (!this.disableTimer)
|
|
511
|
+
homebridge.globalFakeGatoTimer.addData({ entry: entry, service: this, immediateCallback: true });
|
|
512
|
+
else
|
|
513
|
+
this._addEntry({ time: entry.time, status: entry.status });
|
|
514
|
+
break;
|
|
515
|
+
case TYPE_AQUA:
|
|
516
|
+
this._addEntry({ time: entry.time, status: entry.status, waterAmount: entry.waterAmount });
|
|
517
|
+
break;
|
|
518
|
+
case TYPE_WEATHER:
|
|
519
|
+
if (!this.disableTimer)
|
|
520
|
+
homebridge.globalFakeGatoTimer.addData({ entry: entry, service: this });
|
|
521
|
+
else
|
|
522
|
+
this._addEntry({ time: entry.time, temp: entry.temp, humidity: entry.humidity, pressure: entry.pressure });
|
|
523
|
+
break;
|
|
524
|
+
case TYPE_ROOM:
|
|
525
|
+
if (!this.disableTimer)
|
|
526
|
+
homebridge.globalFakeGatoTimer.addData({ entry: entry, service: this });
|
|
527
|
+
else
|
|
528
|
+
this._addEntry({ time: entry.time, temp: entry.temp, humidity: entry.humidity, ppm: entry.ppm });
|
|
529
|
+
break;
|
|
530
|
+
case TYPE_ROOM2:
|
|
531
|
+
if (!this.disableTimer)
|
|
532
|
+
homebridge.globalFakeGatoTimer.addData({ entry: entry, service: this });
|
|
533
|
+
else
|
|
534
|
+
this._addEntry({ time: entry.time, temp: entry.temp, humidity: entry.humidity, voc: entry.voc });
|
|
535
|
+
break;
|
|
536
|
+
case TYPE_ENERGY:
|
|
537
|
+
if (!this.disableTimer)
|
|
538
|
+
homebridge.globalFakeGatoTimer.addData({ entry: entry, service: this });
|
|
539
|
+
else
|
|
540
|
+
this._addEntry({ time: entry.time, power: entry.power });
|
|
541
|
+
break;
|
|
542
|
+
case TYPE_CUSTOM:
|
|
543
|
+
if (!this.disableTimer)
|
|
544
|
+
if ('power' in entry || 'temp' in entry || 'lux' in entry) { // Only put power, temperature or lux thru averager
|
|
545
|
+
homebridge.globalFakeGatoTimer.addData({ entry: entry, service: this });
|
|
546
|
+
} else {
|
|
547
|
+
this._addEntry(entry);
|
|
548
|
+
}
|
|
549
|
+
else
|
|
550
|
+
this._addEntry(entry);
|
|
551
|
+
break;
|
|
552
|
+
default:
|
|
553
|
+
this._addEntry(entry);
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
//in order to be consistent with Eve, entry address start from 1
|
|
559
|
+
_addEntry(entry) {
|
|
560
|
+
if (this.loaded) {
|
|
561
|
+
var entry2address = function (val) {
|
|
562
|
+
return val % this.memorySize;
|
|
563
|
+
}
|
|
564
|
+
.bind(this);
|
|
565
|
+
|
|
566
|
+
var val;
|
|
567
|
+
|
|
568
|
+
if (this.usedMemory < this.memorySize) {
|
|
569
|
+
this.usedMemory++;
|
|
570
|
+
this.firstEntry = 0;
|
|
571
|
+
this.lastEntry = this.usedMemory;
|
|
572
|
+
} else {
|
|
573
|
+
this.firstEntry++;
|
|
574
|
+
this.lastEntry = this.firstEntry + this.usedMemory;
|
|
575
|
+
if (this.restarted == true) {
|
|
576
|
+
this.history[entry2address(this.lastEntry)] = {
|
|
577
|
+
time: entry.time,
|
|
578
|
+
setRefTime: 1
|
|
579
|
+
};
|
|
580
|
+
this.firstEntry++;
|
|
581
|
+
this.lastEntry = this.firstEntry + this.usedMemory;
|
|
582
|
+
this.restarted = false;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (this.refTime == 0) {
|
|
587
|
+
this.refTime = entry.time - EPOCH_OFFSET;
|
|
588
|
+
this.history[this.lastEntry] = {
|
|
589
|
+
time: entry.time,
|
|
590
|
+
setRefTime: 1
|
|
591
|
+
};
|
|
592
|
+
this.initialTime = entry.time;
|
|
593
|
+
this.lastEntry++;
|
|
594
|
+
this.usedMemory++;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
this.history[entry2address(this.lastEntry)] = (entry);
|
|
598
|
+
|
|
599
|
+
if (this.usedMemory < this.memorySize) {
|
|
600
|
+
val = Format(
|
|
601
|
+
'%s00000000%s%s%s%s%s000000000101',
|
|
602
|
+
numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8),
|
|
603
|
+
numToHex(swap32(this.refTime), 8),
|
|
604
|
+
this.accessoryType116,
|
|
605
|
+
numToHex(swap16(this.usedMemory + 1), 4),
|
|
606
|
+
numToHex(swap16(this.memorySize), 4),
|
|
607
|
+
numToHex(swap32(this.firstEntry), 8));
|
|
608
|
+
} else {
|
|
609
|
+
val = Format(
|
|
610
|
+
'%s00000000%s%s%s%s%s000000000101',
|
|
611
|
+
numToHex(swap32(entry.time - this.refTime - EPOCH_OFFSET), 8),
|
|
612
|
+
numToHex(swap32(this.refTime), 8),
|
|
613
|
+
this.accessoryType116,
|
|
614
|
+
numToHex(swap16(this.usedMemory), 4),
|
|
615
|
+
numToHex(swap16(this.memorySize), 4),
|
|
616
|
+
numToHex(swap32(this.firstEntry + 1), 8));
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (this.service === undefined) { // Accessory API
|
|
620
|
+
this.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val));
|
|
621
|
+
}
|
|
622
|
+
else { // Platform API
|
|
623
|
+
this.service.getCharacteristic(S2R1Characteristic).setValue(hexToBase64(val));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
this.log.debug("**Fakegato-history First entry %s: %s", this.accessoryName, this.firstEntry.toString(16));
|
|
627
|
+
this.log.debug("**Fakegato-history Last entry %s: %s", this.accessoryName, this.lastEntry.toString(16));
|
|
628
|
+
this.log.debug("**Fakegato-history Used memory %s: %s", this.accessoryName, this.usedMemory.toString(16));
|
|
629
|
+
this.log.debug("**Fakegato-history Val 116 %s: %s", this.accessoryName, val);
|
|
630
|
+
|
|
631
|
+
if (this.storage !== undefined) this.save();
|
|
632
|
+
} else {
|
|
633
|
+
setTimeout(function () { // retry in 100ms
|
|
634
|
+
this._addEntry(entry);
|
|
635
|
+
}.bind(this), 100);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
getInitialTime() {
|
|
639
|
+
return this.initialTime;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
setExtraPersistedData(extra) {
|
|
643
|
+
this.extra = extra;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
getExtraPersistedData() {
|
|
647
|
+
return this.extra;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
isHistoryLoaded() {
|
|
651
|
+
return this.loaded;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
save() {
|
|
655
|
+
if (this.loaded) {
|
|
656
|
+
|
|
657
|
+
let data = {
|
|
658
|
+
firstEntry: this.firstEntry,
|
|
659
|
+
lastEntry: this.lastEntry,
|
|
660
|
+
usedMemory: this.usedMemory,
|
|
661
|
+
refTime: this.refTime,
|
|
662
|
+
initialTime: this.initialTime,
|
|
663
|
+
history: this.history,
|
|
664
|
+
extra: this.extra
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
homebridge.globalFakeGatoStorage.write({
|
|
669
|
+
service: this,
|
|
670
|
+
data: typeof (data) === "object" ? JSON.stringify(data) : data
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
} else {
|
|
674
|
+
setTimeout(function () { // retry in 100ms
|
|
675
|
+
this.save();
|
|
676
|
+
}.bind(this), 100);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
load(cb) {
|
|
680
|
+
this.log.debug("**Fakegato-history Loading...");
|
|
681
|
+
homebridge.globalFakeGatoStorage.read({
|
|
682
|
+
service: this,
|
|
683
|
+
callback: function (err, data) {
|
|
684
|
+
if (!err) {
|
|
685
|
+
if (data) {
|
|
686
|
+
try {
|
|
687
|
+
this.log.debug("**Fakegato-history read data from", this.accessoryName, ":", data);
|
|
688
|
+
let jsonFile = typeof (data) === "object" ? data : JSON.parse(data);
|
|
689
|
+
|
|
690
|
+
this.firstEntry = jsonFile.firstEntry;
|
|
691
|
+
this.lastEntry = jsonFile.lastEntry;
|
|
692
|
+
this.usedMemory = jsonFile.usedMemory;
|
|
693
|
+
this.refTime = jsonFile.refTime;
|
|
694
|
+
this.initialTime = jsonFile.initialTime;
|
|
695
|
+
this.history = jsonFile.history;
|
|
696
|
+
this.extra = jsonFile.extra;
|
|
697
|
+
} catch (e) {
|
|
698
|
+
this.log.debug("**Fakegato-history ERROR fetching persisting data restart from zero - invalid JSON**", e);
|
|
699
|
+
cb(e, false);
|
|
700
|
+
}
|
|
701
|
+
cb(null, true);
|
|
702
|
+
}
|
|
703
|
+
} else {
|
|
704
|
+
// file don't exists
|
|
705
|
+
cb(null, false);
|
|
706
|
+
}
|
|
707
|
+
}.bind(this)
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
cleanPersist() {
|
|
711
|
+
this.log.debug("**Fakegato-history Cleaning...");
|
|
712
|
+
homebridge.globalFakeGatoStorage.remove({
|
|
713
|
+
service: this
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
getCurrentS2R2(callback) {
|
|
718
|
+
var entry2address = function (val) {
|
|
719
|
+
return val % this.memorySize;
|
|
720
|
+
}.bind(this);
|
|
721
|
+
|
|
722
|
+
if ((this.currentEntry <= this.lastEntry) && (this.transfer == true)) {
|
|
723
|
+
this.memoryAddress = entry2address(this.currentEntry);
|
|
724
|
+
for (var i = 0; i < 11; i++) {
|
|
725
|
+
if ((this.history[this.memoryAddress].setRefTime == 1) || (this.setTime == true) ||
|
|
726
|
+
(this.currentEntry == this.firstEntry + 1)) {
|
|
727
|
+
this.dataStream += Format(
|
|
728
|
+
",15%s 0100 0000 81%s0000 0000 00 0000",
|
|
729
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
730
|
+
numToHex(swap32(this.refTime), 8));
|
|
731
|
+
this.setTime = false;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
this.log.debug("**Fakegato-history %s Entry: %s, Address: %s", this.accessoryName, this.currentEntry, this.memoryAddress);
|
|
735
|
+
switch (this.accessoryType) {
|
|
736
|
+
case TYPE_WEATHER:
|
|
737
|
+
this.dataStream += Format(
|
|
738
|
+
",10 %s%s-%s:%s %s %s",
|
|
739
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
740
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
741
|
+
this.accessoryType117,
|
|
742
|
+
numToHex(swap16(this.history[this.memoryAddress].temp * 100), 4),
|
|
743
|
+
numToHex(swap16(this.history[this.memoryAddress].humidity * 100), 4),
|
|
744
|
+
numToHex(swap16(this.history[this.memoryAddress].pressure * 10), 4));
|
|
745
|
+
break;
|
|
746
|
+
case TYPE_ENERGY:
|
|
747
|
+
this.dataStream += Format(
|
|
748
|
+
",14 %s%s-%s:0000 0000 %s 0000 0000",
|
|
749
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
750
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
751
|
+
this.accessoryType117,
|
|
752
|
+
numToHex(swap16(this.history[this.memoryAddress].power * 10), 4));
|
|
753
|
+
break;
|
|
754
|
+
case TYPE_ROOM:
|
|
755
|
+
this.dataStream += Format(
|
|
756
|
+
",13 %s%s%s%s%s%s0000 00",
|
|
757
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
758
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
759
|
+
this.accessoryType117,
|
|
760
|
+
numToHex(swap16(this.history[this.memoryAddress].temp * 100), 4),
|
|
761
|
+
numToHex(swap16(this.history[this.memoryAddress].humidity * 100), 4),
|
|
762
|
+
numToHex(swap16(this.history[this.memoryAddress].ppm), 4));
|
|
763
|
+
break;
|
|
764
|
+
case TYPE_ROOM2:
|
|
765
|
+
this.dataStream += Format(
|
|
766
|
+
",15 %s%s%s%s%s%s0054 a80f01",
|
|
767
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
768
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
769
|
+
this.accessoryType117,
|
|
770
|
+
numToHex(swap16(this.history[this.memoryAddress].temp * 100), 4),
|
|
771
|
+
numToHex(swap16(this.history[this.memoryAddress].humidity * 100), 4),
|
|
772
|
+
numToHex(swap16(this.history[this.memoryAddress].voc), 4));
|
|
773
|
+
break;
|
|
774
|
+
case TYPE_DOOR:
|
|
775
|
+
case TYPE_MOTION:
|
|
776
|
+
case TYPE_SWITCH:
|
|
777
|
+
this.dataStream += Format(
|
|
778
|
+
",0b %s%s%s%s",
|
|
779
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
780
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
781
|
+
this.accessoryType117,
|
|
782
|
+
numToHex(this.history[this.memoryAddress].status, 2));
|
|
783
|
+
break;
|
|
784
|
+
case TYPE_AQUA:
|
|
785
|
+
if (this.history[this.memoryAddress].status == true)
|
|
786
|
+
this.dataStream += Format(
|
|
787
|
+
",0d %s%s%s%s 300c",
|
|
788
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
789
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
790
|
+
this.accessoryType117,
|
|
791
|
+
numToHex(this.history[this.memoryAddress].status, 2));
|
|
792
|
+
else
|
|
793
|
+
this.dataStream += Format(
|
|
794
|
+
",15 %s%s%s%s%s 00000000 300c",
|
|
795
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
796
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
797
|
+
this.accessoryType117bis,
|
|
798
|
+
numToHex(this.history[this.memoryAddress].status, 2),
|
|
799
|
+
numToHex(swap32(this.history[this.memoryAddress].waterAmount), 8));
|
|
800
|
+
break;
|
|
801
|
+
case TYPE_THERMO:
|
|
802
|
+
this.dataStream += Format(
|
|
803
|
+
",11 %s%s%s%s%s%s 0000",
|
|
804
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
805
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8),
|
|
806
|
+
this.accessoryType117,
|
|
807
|
+
numToHex(swap16(this.history[this.memoryAddress].currentTemp * 100), 4),
|
|
808
|
+
numToHex(swap16(this.history[this.memoryAddress].setTemp * 100), 4),
|
|
809
|
+
numToHex(this.history[this.memoryAddress].valvePosition, 2));
|
|
810
|
+
break;
|
|
811
|
+
case TYPE_CUSTOM:
|
|
812
|
+
var result = [];
|
|
813
|
+
var bitmask = 0;
|
|
814
|
+
var dataStream = Format("%s%s",
|
|
815
|
+
numToHex(swap32(this.currentEntry), 8),
|
|
816
|
+
numToHex(swap32(this.history[this.memoryAddress].time - this.refTime - EPOCH_OFFSET), 8));
|
|
817
|
+
for (const [key, value] of Object.entries(this.history[this.memoryAddress])) {
|
|
818
|
+
switch (key) {
|
|
819
|
+
case 'time':
|
|
820
|
+
break;
|
|
821
|
+
default:
|
|
822
|
+
for (var x = 0, iLen = this.signatures.length; x < iLen; x++) {
|
|
823
|
+
if (this.signatures[x].entry === key) {
|
|
824
|
+
// console.log('**Fakegato-history key', key, this.signatures[x].uuid, value, this.signatures[x].factor);
|
|
825
|
+
switch(this.signatures[x].length) {
|
|
826
|
+
case 8:
|
|
827
|
+
result[x] = Format('%s', numToHex(swap32(value * this.signatures[x].factor), this.signatures[x].length));
|
|
828
|
+
break;
|
|
829
|
+
case 4:
|
|
830
|
+
result[x] = Format('%s', numToHex(swap16(value * this.signatures[x].factor), this.signatures[x].length));
|
|
831
|
+
break;
|
|
832
|
+
case 2:
|
|
833
|
+
result[x] = Format('%s', numToHex(value * this.signatures[x].factor, this.signatures[x].length));
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
bitmask += Math.pow(2, x);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
var results = dataStream + ' ' + numToHex(bitmask, 2) + ' ' + result.map(a => a).join(' ');
|
|
842
|
+
// console.log('**Fakegato-history results', numToHex((results.replace(/[^0-9A-F]/ig, '').length) / 2 + 1) + ' ' + results);
|
|
843
|
+
this.dataStream += (' ' + numToHex((results.replace(/[^0-9A-F]/ig, '').length) / 2 + 1) + ' ' + results + ',');
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
this.currentEntry++;
|
|
848
|
+
this.memoryAddress = entry2address(this.currentEntry);
|
|
849
|
+
if (this.currentEntry > this.lastEntry)
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
this.log.debug("**Fakegato-history Data %s: %s", this.accessoryName, this.dataStream);
|
|
853
|
+
callback(null, hexToBase64(this.dataStream));
|
|
854
|
+
this.dataStream = '';
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
this.transfer = false;
|
|
858
|
+
callback(null, hexToBase64('00'));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
setCurrentS2W1(val, callback) {
|
|
864
|
+
callback(null);
|
|
865
|
+
this.log.debug("**Fakegato-history Data request %s: %s", this.accessoryName, base64ToHex(val));
|
|
866
|
+
var valHex = base64ToHex(val);
|
|
867
|
+
var substring = valHex.substring(4, 12);
|
|
868
|
+
var valInt = parseInt(substring, 16);
|
|
869
|
+
var address = swap32(valInt);
|
|
870
|
+
var hexAddress = address.toString('16');
|
|
871
|
+
|
|
872
|
+
this.log.debug("**Fakegato-history Address requested %s: %s", this.accessoryName, hexAddress);
|
|
873
|
+
this.sendHistory(address);
|
|
874
|
+
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
setCurrentS2W2(val, callback) {
|
|
878
|
+
this.log.debug("**Fakegato-history Clock adjust %s: %s", this.accessoryName, base64ToHex(val));
|
|
879
|
+
callback(null);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
FakeGatoHistoryService.UUID = 'E863F007-079E-48FF-8F27-9C2605A29F52';
|
|
885
|
+
|
|
886
|
+
return FakeGatoHistory;
|
|
887
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/*jshint esversion: 6,node: true,-W041: false */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const DEBUG = true;
|
|
5
|
+
|
|
6
|
+
var fs = require('fs');
|
|
7
|
+
var os = require('os');
|
|
8
|
+
var path = require('path');
|
|
9
|
+
var hostname = os.hostname().split(".")[0];
|
|
10
|
+
|
|
11
|
+
// Not needed for homebridge-easy-mqtt
|
|
12
|
+
// var googleDrive = require('./lib/googleDrive').drive;
|
|
13
|
+
|
|
14
|
+
var fileSuffix = '_persist.json';
|
|
15
|
+
|
|
16
|
+
var thisStorage;
|
|
17
|
+
|
|
18
|
+
class FakeGatoStorage {
|
|
19
|
+
constructor(params) {
|
|
20
|
+
if (!params)
|
|
21
|
+
params = {};
|
|
22
|
+
|
|
23
|
+
this.writers = [];
|
|
24
|
+
|
|
25
|
+
this.log = params.log || {};
|
|
26
|
+
if (!this.log.debug) {
|
|
27
|
+
this.log.debug = DEBUG ? console.log : function () { };
|
|
28
|
+
}
|
|
29
|
+
thisStorage = this;
|
|
30
|
+
this.addingWriter = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
addWriter(service, params) {
|
|
34
|
+
if (!this.addingWriter) {
|
|
35
|
+
this.addingWriter = true;
|
|
36
|
+
if (!params)
|
|
37
|
+
params = {};
|
|
38
|
+
|
|
39
|
+
this.log.debug("** Fakegato-storage AddWriter :", service.accessoryName);
|
|
40
|
+
|
|
41
|
+
let newWriter = {
|
|
42
|
+
'service': service,
|
|
43
|
+
'callback': params.callback,
|
|
44
|
+
'storage': params.storage || 'fs',
|
|
45
|
+
'fileName': params.filename || hostname + "_" + service.accessoryName + fileSuffix // Unique filename per homebridge server. Allows test environments on other servers not to break prod.
|
|
46
|
+
};
|
|
47
|
+
var onReady = typeof (params.onReady) == 'function' ? params.onReady : function () { }.bind(this);
|
|
48
|
+
|
|
49
|
+
switch (newWriter.storage) {
|
|
50
|
+
case 'fs':
|
|
51
|
+
newWriter.storageHandler = fs;
|
|
52
|
+
newWriter.path = params.path || path.join(os.homedir(), '.homebridge');
|
|
53
|
+
this.writers.push(newWriter);
|
|
54
|
+
this.addingWriter = false;
|
|
55
|
+
onReady();
|
|
56
|
+
break;
|
|
57
|
+
// Not needed for homebridge-easy-mqtt
|
|
58
|
+
// case 'googleDrive':
|
|
59
|
+
// newWriter.path = params.path || 'fakegato';
|
|
60
|
+
// newWriter.keyPath = params.keyPath || path.join(os.homedir(), '.homebridge');
|
|
61
|
+
// newWriter.storageHandler = new googleDrive({
|
|
62
|
+
// keyPath: newWriter.keyPath, callback: function () {
|
|
63
|
+
// this.addingWriter = false;
|
|
64
|
+
// onReady(arguments);
|
|
65
|
+
// }.bind(this), folder: newWriter.path
|
|
66
|
+
// });
|
|
67
|
+
// this.writers.push(newWriter);
|
|
68
|
+
// break;
|
|
69
|
+
/*
|
|
70
|
+
case 'memcached' :
|
|
71
|
+
|
|
72
|
+
break;
|
|
73
|
+
*/
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
setTimeout(function () {
|
|
77
|
+
this.addWriter(service, params);
|
|
78
|
+
}.bind(this), 100);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
getWriter(service) {
|
|
82
|
+
let findServ = function (element) {
|
|
83
|
+
return element.service === service;
|
|
84
|
+
};
|
|
85
|
+
return this.writers.find(findServ);
|
|
86
|
+
}
|
|
87
|
+
_getWriterIndex(service) {
|
|
88
|
+
let findServ = function (element) {
|
|
89
|
+
return element.service === service;
|
|
90
|
+
};
|
|
91
|
+
return this.writers.findIndex(findServ);
|
|
92
|
+
}
|
|
93
|
+
getWriters() {
|
|
94
|
+
return this.writers;
|
|
95
|
+
}
|
|
96
|
+
delWriter(service) {
|
|
97
|
+
let index = this._getWriterIndex(service);
|
|
98
|
+
this.writers.splice(index, 1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
write(params) { // must be asynchronous
|
|
102
|
+
if (!this.writing) {
|
|
103
|
+
this.writing = true;
|
|
104
|
+
let writer = this.getWriter(params.service);
|
|
105
|
+
let callBack = typeof (params.callback) == 'function' ? params.callback : (typeof (writer.callback) == 'function' ? writer.callback : function () { }); // use parameter callback or writer callback or empty function
|
|
106
|
+
switch (writer.storage) {
|
|
107
|
+
case 'fs':
|
|
108
|
+
this.log.debug("** Fakegato-storage write FS file:", path.join(writer.path, writer.fileName), params.data.substr(1, 80));
|
|
109
|
+
writer.storageHandler.writeFile(path.join(writer.path, writer.fileName), params.data, 'utf8', function () {
|
|
110
|
+
this.writing = false;
|
|
111
|
+
callBack(arguments);
|
|
112
|
+
}.bind(this));
|
|
113
|
+
break;
|
|
114
|
+
// Not needed for homebridge-easy-mqtt
|
|
115
|
+
// case 'googleDrive':
|
|
116
|
+
// this.log.debug("** Fakegato-storage write googleDrive file:", writer.path, writer.fileName, params.data.substr(1, 80));
|
|
117
|
+
// writer.storageHandler.writeFile(writer.path, writer.fileName, params.data, function () {
|
|
118
|
+
// this.writing = false;
|
|
119
|
+
// callBack(arguments);
|
|
120
|
+
// }.bind(this));
|
|
121
|
+
// break;
|
|
122
|
+
/*
|
|
123
|
+
case 'memcached' :
|
|
124
|
+
|
|
125
|
+
break;
|
|
126
|
+
*/
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
setTimeout(function () { // retry in 100ms
|
|
130
|
+
this.write(params);
|
|
131
|
+
}.bind(this), 100);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
read(params) {
|
|
135
|
+
let writer = this.getWriter(params.service);
|
|
136
|
+
let callBack = typeof (params.callback) == 'function' ? params.callback : (typeof (writer.callback) == 'function' ? writer.callback : function () { }); // use parameter callback or writer callback or empty function
|
|
137
|
+
switch (writer.storage) {
|
|
138
|
+
case 'fs':
|
|
139
|
+
this.log.debug("** Fakegato-storage read FS file:", path.join(writer.path, writer.fileName));
|
|
140
|
+
writer.storageHandler.readFile(path.join(writer.path, writer.fileName), 'utf8', callBack);
|
|
141
|
+
break;
|
|
142
|
+
// Not needed for homebridge-easy-mqtt
|
|
143
|
+
// case 'googleDrive':
|
|
144
|
+
// this.log.debug("** Fakegato-storage read googleDrive file: %s/%s", writer.path, writer.fileName);
|
|
145
|
+
// writer.storageHandler.readFile(writer.path, writer.fileName, callBack);
|
|
146
|
+
// break;
|
|
147
|
+
/*
|
|
148
|
+
case 'memcached' :
|
|
149
|
+
|
|
150
|
+
break;
|
|
151
|
+
*/
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
remove(params) {
|
|
155
|
+
let writer = this.getWriter(params.service);
|
|
156
|
+
let callBack = typeof (params.callback) == 'function' ? params.callback : (typeof (writer.callback) == 'function' ? writer.callback : function () { }); // use parameter callback or writer callback or empty function
|
|
157
|
+
switch (writer.storage) {
|
|
158
|
+
case 'fs':
|
|
159
|
+
this.log.debug("** Fakegato-storage delete FS file:", path.join(writer.path, writer.fileName));
|
|
160
|
+
writer.storageHandler.unlink(path.join(writer.path, writer.fileName), callBack);
|
|
161
|
+
break;
|
|
162
|
+
// Not needed for homebridge-easy-mqtt
|
|
163
|
+
// case 'googleDrive':
|
|
164
|
+
// this.log.debug("** Fakegato-storage delete googleDrive file:", writer.path, writer.fileName);
|
|
165
|
+
// writer.storageHandler.deleteFile(writer.path, writer.fileName, callBack);
|
|
166
|
+
// break;
|
|
167
|
+
/*
|
|
168
|
+
case 'memcached' :
|
|
169
|
+
|
|
170
|
+
break;
|
|
171
|
+
*/
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
module.exports = {
|
|
177
|
+
FakeGatoStorage: FakeGatoStorage
|
|
178
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/*jshint esversion: 6,node: true,-W041: false */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const DEBUG = true;
|
|
5
|
+
|
|
6
|
+
class FakeGatoTimer {
|
|
7
|
+
constructor(params) {
|
|
8
|
+
if (!params)
|
|
9
|
+
params = {};
|
|
10
|
+
this.subscribedServices = [];
|
|
11
|
+
this.minutes = params.minutes || 10;
|
|
12
|
+
|
|
13
|
+
this.intervalID = null;
|
|
14
|
+
this.running = false;
|
|
15
|
+
this.log = params.log || {};
|
|
16
|
+
if (!this.log.debug) {
|
|
17
|
+
this.log.debug = DEBUG ? console.log : function () { };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Subscription management
|
|
22
|
+
subscribe(service, callback) {
|
|
23
|
+
this.log.debug("** Fakegato-timer Subscription :", service.accessoryName);
|
|
24
|
+
let newService = {
|
|
25
|
+
'service': service,
|
|
26
|
+
'callback': callback,
|
|
27
|
+
'backLog': [],
|
|
28
|
+
'previousBackLog': [],
|
|
29
|
+
'previousAvrg': {}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
this.subscribedServices.push(newService);
|
|
33
|
+
}
|
|
34
|
+
getSubscriber(service) {
|
|
35
|
+
let findServ = function (element) {
|
|
36
|
+
return element.service === service;
|
|
37
|
+
};
|
|
38
|
+
return this.subscribedServices.find(findServ);
|
|
39
|
+
}
|
|
40
|
+
_getSubscriberIndex(service) {
|
|
41
|
+
let findServ = function (element) {
|
|
42
|
+
return element.service === service;
|
|
43
|
+
};
|
|
44
|
+
return this.subscribedServices.findIndex(findServ);
|
|
45
|
+
}
|
|
46
|
+
getSubscribers() {
|
|
47
|
+
return this.subscribedServices;
|
|
48
|
+
}
|
|
49
|
+
unsubscribe(service) {
|
|
50
|
+
let index = this._getSubscriberIndex(service);
|
|
51
|
+
this.subscribedServices.splice(index, 1);
|
|
52
|
+
if (this.subscribedServices.length === 0 && this.running)
|
|
53
|
+
this.stop();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Timer management
|
|
57
|
+
start() {
|
|
58
|
+
this.log.debug("**Start Global Fakegato-Timer - " + this.minutes + "min**");
|
|
59
|
+
if (this.running)
|
|
60
|
+
this.stop();
|
|
61
|
+
this.running = true;
|
|
62
|
+
this.intervalID = setInterval(this.executeCallbacks.bind(this), this.minutes * 60 * 1000);
|
|
63
|
+
}
|
|
64
|
+
stop() {
|
|
65
|
+
this.log.debug("**Stop Global Fakegato-Timer****");
|
|
66
|
+
clearInterval(this.intervalID);
|
|
67
|
+
this.running = false;
|
|
68
|
+
this.intervalID = null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Data management
|
|
72
|
+
executeCallbacks() {
|
|
73
|
+
this.log.debug("**Fakegato-timer: executeCallbacks**");
|
|
74
|
+
if (this.subscribedServices.length !== 0) {
|
|
75
|
+
for (let s in this.subscribedServices) {
|
|
76
|
+
if (this.subscribedServices.hasOwnProperty(s)) {
|
|
77
|
+
|
|
78
|
+
let service = this.subscribedServices[s];
|
|
79
|
+
if (typeof (service.callback) == 'function') {
|
|
80
|
+
service.previousAvrg = service.callback({
|
|
81
|
+
'backLog': service.backLog,
|
|
82
|
+
'previousAvrg': service.previousAvrg,
|
|
83
|
+
'timer': this,
|
|
84
|
+
'immediate': false
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
executeImmediateCallback(service) {
|
|
92
|
+
this.log.debug("**Fakegato-timer: executeImmediateCallback**");
|
|
93
|
+
|
|
94
|
+
if (typeof (service.callback) == 'function' && service.backLog.length)
|
|
95
|
+
service.callback({
|
|
96
|
+
'backLog': service.backLog,
|
|
97
|
+
'timer': this,
|
|
98
|
+
'immediate': true
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
addData(params) {
|
|
102
|
+
let data = params.entry;
|
|
103
|
+
let service = params.service;
|
|
104
|
+
let immediateCallback = params.immediateCallback || false;
|
|
105
|
+
|
|
106
|
+
this.log.debug("**Fakegato-timer: addData ", service.accessoryName, data, " immediate: ", immediateCallback);
|
|
107
|
+
|
|
108
|
+
if (immediateCallback) // door or motion -> replace
|
|
109
|
+
this.getSubscriber(service).backLog[0] = data;
|
|
110
|
+
else
|
|
111
|
+
this.getSubscriber(service).backLog.push(data);
|
|
112
|
+
|
|
113
|
+
if (immediateCallback) {
|
|
114
|
+
//setTimeout(this.executeImmediateCallback.bind(this), 0,service);
|
|
115
|
+
this.executeImmediateCallback(this.getSubscriber(service));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (this.running === false)
|
|
119
|
+
this.start();
|
|
120
|
+
}
|
|
121
|
+
emptyData(service) {
|
|
122
|
+
this.log.debug("**Fakegato-timer: emptyData **", service.accessoryName);
|
|
123
|
+
let source = this.getSubscriber(service);
|
|
124
|
+
|
|
125
|
+
if (source.backLog.length) source.previousBackLog = source.backLog;
|
|
126
|
+
source.backLog = [];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
module.exports = {
|
|
132
|
+
FakeGatoTimer: FakeGatoTimer
|
|
133
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// https://github.com/homebridge/HAP-NodeJS/blob/master/src/lib/util/uuid.ts
|
|
2
|
+
|
|
3
|
+
const VALID_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
|
+
|
|
5
|
+
function isValid(UUID) {
|
|
6
|
+
return VALID_UUID_REGEX.test(UUID);
|
|
7
|
+
}
|
|
8
|
+
const VALID_SHORT_REGEX = /^[0-9a-f]{1,8}$/i;
|
|
9
|
+
|
|
10
|
+
function toLongFormUUID(uuid, base = '-0000-1000-8000-0026BB765291') {
|
|
11
|
+
if (isValid(uuid)) return uuid.toUpperCase();
|
|
12
|
+
if (!VALID_SHORT_REGEX.test(uuid)) throw new TypeError('uuid was not a valid UUID or short form UUID');
|
|
13
|
+
if (!isValid('00000000' + base)) throw new TypeError('base was not a valid base UUID');
|
|
14
|
+
|
|
15
|
+
return (('00000000' + uuid).substr(-8) + base).toUpperCase();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function toShortFormUUID(uuid, base = '-0000-1000-8000-0026BB765291') {
|
|
19
|
+
uuid = toLongFormUUID(uuid, base);
|
|
20
|
+
return (uuid.substr(0, 8));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
exports.isValid = isValid;
|
|
24
|
+
exports.toLongFormUUID = toLongFormUUID;
|
|
25
|
+
exports.toShortFormUUID = toShortFormUUID;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"displayName": "Homebridge Easy MQTT",
|
|
5
5
|
"description": "Homebridge plugin for easy control of MQTT devices",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "1.5.0-beta.
|
|
7
|
+
"version": "1.5.0-beta.1",
|
|
8
8
|
"homepage": "https://github.com/mpatfield/homebridge-easy-mqtt#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@homebridge/plugin-ui-utils": "^2.0.2",
|
|
43
|
-
"fakegato-history": "
|
|
43
|
+
"fakegato-history": "file:./lib/fakegato-history",
|
|
44
44
|
"homebridge-lib": "^7.1.6",
|
|
45
45
|
"lodash.merge": "^4.6.2",
|
|
46
46
|
"mqtt": "^5.7.0",
|