homebridge-nest-accfactory 0.3.0 → 0.3.2
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 +31 -0
- package/README.md +31 -25
- package/config.schema.json +46 -22
- package/dist/HomeKitDevice.js +523 -281
- package/dist/HomeKitHistory.js +357 -341
- package/dist/config.js +69 -87
- package/dist/consts.js +160 -0
- package/dist/devices.js +40 -48
- package/dist/ffmpeg.js +297 -0
- package/dist/index.js +3 -3
- package/dist/nexustalk.js +182 -149
- package/dist/plugins/camera.js +1164 -933
- package/dist/plugins/doorbell.js +26 -32
- package/dist/plugins/floodlight.js +11 -24
- package/dist/plugins/heatlink.js +411 -5
- package/dist/plugins/lock.js +309 -0
- package/dist/plugins/protect.js +240 -71
- package/dist/plugins/tempsensor.js +159 -35
- package/dist/plugins/thermostat.js +891 -455
- package/dist/plugins/weather.js +128 -33
- package/dist/protobuf/nest/services/apigateway.proto +1 -1
- package/dist/protobuf/nestlabs/gateway/v2.proto +1 -1
- package/dist/protobuf/root.proto +1 -0
- package/dist/rtpmuxer.js +186 -0
- package/dist/streamer.js +490 -248
- package/dist/system.js +1741 -2868
- package/dist/utils.js +327 -0
- package/dist/webrtc.js +358 -229
- package/package.json +19 -16
package/dist/HomeKitHistory.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
//
|
|
11
11
|
// Credit to https://github.com/simont77/fakegato-history for the work on starting the EveHome comms protocol decoding
|
|
12
12
|
//
|
|
13
|
-
// Version 2025
|
|
13
|
+
// Version 2025.08.06
|
|
14
14
|
// Mark Hulskamp
|
|
15
15
|
|
|
16
16
|
// Define nodejs module requirements
|
|
@@ -23,281 +23,265 @@ import fs from 'fs';
|
|
|
23
23
|
const MAX_HISTORY_SIZE = 16384; // 16k entries
|
|
24
24
|
const EPOCH_OFFSET = 978307200; // Seconds since 1/1/1970 to 1/1/2001
|
|
25
25
|
const EVEHOME_MAX_STREAM = 11; // Maximum number of history events we can stream to EveHome
|
|
26
|
-
const
|
|
26
|
+
const DAYS_OF_WEEK = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
|
|
27
|
+
const EMPTY_SCHEDULE = 'ffffffffffffffff';
|
|
28
|
+
const LOG_LEVELS = {
|
|
29
|
+
INFO: 'info',
|
|
30
|
+
SUCCESS: 'success',
|
|
31
|
+
WARN: 'warn',
|
|
32
|
+
ERROR: 'error',
|
|
33
|
+
DEBUG: 'debug',
|
|
34
|
+
};
|
|
27
35
|
|
|
28
36
|
// Create the history object
|
|
29
37
|
export default class HomeKitHistory {
|
|
38
|
+
static GET = 'HomeKitHistory.onEveGet'; // for EveHome read requests
|
|
39
|
+
static SET = 'HomeKitHistory.onEveSet'; // for EveHome write requests
|
|
40
|
+
|
|
41
|
+
// Symbol used to temporarily store EveHome options on a service
|
|
42
|
+
static EVE_OPTIONS = Symbol('eveOptions');
|
|
43
|
+
|
|
44
|
+
historyData = {}; // Tracked history data via persistant storage
|
|
45
|
+
restart = Math.floor(Date.now() / 1000); // time we restarted object or created
|
|
46
|
+
EveHome = undefined;
|
|
47
|
+
|
|
30
48
|
accessory = undefined; // Accessory service for this history
|
|
31
49
|
hap = undefined; // HomeKit Accessory Protocol API stub
|
|
32
50
|
log = undefined; // Logging function object
|
|
33
|
-
maxEntries = MAX_HISTORY_SIZE; // used for rolling history. if 0, means no rollover
|
|
34
|
-
EveHome = undefined;
|
|
35
51
|
|
|
36
|
-
|
|
52
|
+
// Internal data only for this class
|
|
53
|
+
#persistStorage = undefined;
|
|
54
|
+
#persistKey = undefined;
|
|
55
|
+
#maxEntries = MAX_HISTORY_SIZE; // used for rolling history. if 0, means no rollover
|
|
56
|
+
|
|
57
|
+
constructor(accessory = undefined, api = undefined, log = undefined, options = {}) {
|
|
37
58
|
// Validate the passed in logging object. We are expecting certain functions to be present
|
|
38
|
-
if (
|
|
39
|
-
typeof log?.info === 'function' &&
|
|
40
|
-
typeof log?.success === 'function' &&
|
|
41
|
-
typeof log?.warn === 'function' &&
|
|
42
|
-
typeof log?.error === 'function' &&
|
|
43
|
-
typeof log?.debug === 'function'
|
|
44
|
-
) {
|
|
59
|
+
if (Object.values(LOG_LEVELS).every((fn) => typeof log?.[fn] === 'function')) {
|
|
45
60
|
this.log = log;
|
|
46
61
|
}
|
|
47
62
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
// Get the actual HAP entry point from passed in api object, either Homebridge or HAP-NodeJS
|
|
64
|
+
this.hap =
|
|
65
|
+
isNaN(api?.version) === false && typeof api?.hap === 'object' && api?.HAPLibraryVersion === undefined
|
|
66
|
+
? api.hap
|
|
67
|
+
: typeof api?.HAPLibraryVersion === 'function' && api?.version === undefined && api?.hap === undefined
|
|
68
|
+
? api
|
|
69
|
+
: undefined;
|
|
51
70
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
71
|
+
if (this.hap === undefined) {
|
|
72
|
+
this?.log?.error?.('Missing HAP library API, cannot use class');
|
|
73
|
+
return;
|
|
56
74
|
}
|
|
57
75
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// We have the HomeBridge version number and hap API object
|
|
61
|
-
this.hap = api.hap;
|
|
76
|
+
if (typeof accessory !== 'undefined' && typeof accessory === 'object') {
|
|
77
|
+
this.accessory = accessory;
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
this.hap = api;
|
|
80
|
+
if (isNaN(options?.maxEntries) === false) {
|
|
81
|
+
this.#maxEntries = options.maxEntries;
|
|
67
82
|
}
|
|
68
83
|
|
|
69
|
-
//
|
|
70
|
-
this.#createHomeKitServicesAndCharacteristics();
|
|
71
|
-
|
|
72
|
-
// Setup HomeKitHistory using HAP-NodeJS library
|
|
84
|
+
// Determine the peristant storage file name
|
|
73
85
|
if (typeof accessory?.username !== 'undefined') {
|
|
74
86
|
// Since we have a username for the accessory, we'll assume this is not running under Homebridge
|
|
75
87
|
// We'll use it's persist folder for storing history files
|
|
76
|
-
this
|
|
88
|
+
this.#persistKey = util.format('History.%s.json', accessory.username.replace(/:/g, '').toUpperCase());
|
|
77
89
|
}
|
|
78
90
|
|
|
79
91
|
// Setup HomeKitHistory under Homebridge
|
|
80
92
|
if (typeof accessory?.username === 'undefined') {
|
|
81
|
-
this
|
|
93
|
+
this.#persistKey = util.format('History.%s.json', accessory.UUID);
|
|
82
94
|
}
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
this.historyData = this.
|
|
96
|
+
// Setup persistant storage and load any data we have already
|
|
97
|
+
this.#persistStorage = this.hap.HAPStorage.storage();
|
|
98
|
+
this.historyData = this.#persistStorage.getItem(this.#persistKey);
|
|
87
99
|
if (typeof this.historyData !== 'object') {
|
|
88
100
|
// Getting storage key didnt return an object, we'll assume no history present, so start new history for this accessory
|
|
89
101
|
this.resetHistory(); // Start with blank history
|
|
90
102
|
}
|
|
91
103
|
|
|
92
|
-
this.restart = Math.floor(Date.now() / 1000); // time we restarted
|
|
93
|
-
|
|
94
104
|
// perform rollover if needed when starting service
|
|
95
|
-
if (this
|
|
105
|
+
if (this.#maxEntries !== 0 && this.historyData.next >= this.#maxEntries) {
|
|
96
106
|
this.rolloverHistory();
|
|
97
107
|
}
|
|
108
|
+
|
|
109
|
+
// Dynamically create the additional services and characteristics
|
|
110
|
+
this.#createHomeKitServicesAndCharacteristics();
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
// Class functions
|
|
101
|
-
addHistory(
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
this.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
// No logging time was passed in, so set
|
|
113
|
-
entry.time = Math.floor(Date.now() / 1000);
|
|
114
|
+
addHistory(target, entry, timegap) {
|
|
115
|
+
// Validate that target is a Service or Characteristic with a UUID string,
|
|
116
|
+
// entry is an object, and hap.Service exists (class/function)
|
|
117
|
+
if (
|
|
118
|
+
typeof target !== 'object' ||
|
|
119
|
+
typeof target.UUID !== 'string' ||
|
|
120
|
+
typeof entry !== 'object' ||
|
|
121
|
+
typeof this.hap?.Service !== 'function' ||
|
|
122
|
+
typeof this.hap?.Characteristic !== 'function'
|
|
123
|
+
) {
|
|
124
|
+
return;
|
|
114
125
|
}
|
|
115
|
-
|
|
116
|
-
|
|
126
|
+
|
|
127
|
+
// Metadata map keyed by Service or Characteristic UUID
|
|
128
|
+
let SERVICE_HISTORY_META = {
|
|
129
|
+
[this.hap.Service.GarageDoorOpener.UUID]: {
|
|
130
|
+
required: ['status'],
|
|
131
|
+
comment: 'status => 0 = closed, 1 = open',
|
|
132
|
+
},
|
|
133
|
+
[this.hap.Service.LockMechanism.UUID]: {
|
|
134
|
+
required: ['status'],
|
|
135
|
+
comment: 'status => 0 = locked, 1 = unlocked',
|
|
136
|
+
},
|
|
137
|
+
[this.hap.Service.Fan.UUID]: {
|
|
138
|
+
required: ['status'],
|
|
139
|
+
comment: 'status => 0 = off, 1 = on; optional: temperature, humidity',
|
|
140
|
+
},
|
|
141
|
+
[this.hap.Service.Fan.Fanv2.UUID]: {
|
|
142
|
+
required: ['status'],
|
|
143
|
+
comment: 'status => 0 = off, 1 = on; optional: temperature, humidity',
|
|
144
|
+
},
|
|
145
|
+
[this.hap.Service.HumidifierDehumidifier.UUID]: {
|
|
146
|
+
required: ['status'],
|
|
147
|
+
comment: 'status => 0 = off, 1 = on; optional: temperature, humidity',
|
|
148
|
+
},
|
|
149
|
+
[this.hap.Service.MotionSensor.UUID]: {
|
|
150
|
+
required: ['status'],
|
|
151
|
+
comment: 'status => 0 = motion cleared, 1 = motion detected',
|
|
152
|
+
},
|
|
153
|
+
[this.hap.Service.Window.UUID]: {
|
|
154
|
+
required: ['status', 'position'],
|
|
155
|
+
comment: 'status => 0 = closed, 1 = open; position => % open (0–100)',
|
|
156
|
+
},
|
|
157
|
+
[this.hap.Service.WindowCovering.UUID]: {
|
|
158
|
+
required: ['status', 'position'],
|
|
159
|
+
comment: 'status => 0 = closed, 1 = open; position => % open (0–100)',
|
|
160
|
+
},
|
|
161
|
+
[this.hap.Service.HeaterCooler.UUID]: {
|
|
162
|
+
required: ['status', 'temperature', 'target', 'humidity'],
|
|
163
|
+
comment: 'status => 0 = off, 1 = cooling, 2 = heating; includes temperature, target {low/high}, humidity',
|
|
164
|
+
},
|
|
165
|
+
[this.hap.Service.Thermostat.UUID]: {
|
|
166
|
+
required: ['status', 'temperature', 'target', 'humidity'],
|
|
167
|
+
comment: 'status => 0 = off, 1 = cooling, 2 = heating; includes temperature, target {low/high}, humidity',
|
|
168
|
+
},
|
|
169
|
+
[this.hap.Service.TemperatureSensor.UUID]: {
|
|
170
|
+
required: ['temperature'],
|
|
171
|
+
defaults: { humidity: 0, ppm: 0, voc: 0, pressure: 0 },
|
|
172
|
+
comment: 'temperature required; humidity, ppm, voc, pressure default to 0',
|
|
173
|
+
},
|
|
174
|
+
[this.hap.Service.AirQualitySensor.UUID]: {
|
|
175
|
+
required: ['temperature'],
|
|
176
|
+
defaults: { humidity: 0, ppm: 0, voc: 0, pressure: 0 },
|
|
177
|
+
comment: 'temperature required; humidity, ppm, voc, pressure default to 0',
|
|
178
|
+
},
|
|
179
|
+
[this.hap.Service.EveAirPressureSensor.UUID]: {
|
|
180
|
+
required: ['temperature'],
|
|
181
|
+
defaults: { humidity: 0, ppm: 0, voc: 0, pressure: 0 },
|
|
182
|
+
comment: 'temperature required; humidity, ppm, voc, pressure default to 0',
|
|
183
|
+
},
|
|
184
|
+
[this.hap.Service.Valve.UUID]: {
|
|
185
|
+
required: ['status', 'water', 'duration'],
|
|
186
|
+
comment: 'status => 0 = valve closed, 1 = open; includes water (L) and duration (s)',
|
|
187
|
+
},
|
|
188
|
+
[this.hap.Characteristic.WaterLevel.UUID]: {
|
|
189
|
+
required: ['level'],
|
|
190
|
+
comment: 'level => water level percentage (0–100)',
|
|
191
|
+
},
|
|
192
|
+
[this.hap.Service.LeakSensor.UUID]: {
|
|
193
|
+
required: ['status'],
|
|
194
|
+
comment: 'status => 0 = no leak, 1 = leak detected',
|
|
195
|
+
},
|
|
196
|
+
[this.hap.Service.Outlet.UUID]: {
|
|
197
|
+
required: ['status', 'volts', 'watts', 'amps'],
|
|
198
|
+
comment: 'status => 0 = off, 1 = on; includes volts, watts, amps',
|
|
199
|
+
},
|
|
200
|
+
[this.hap.Service.Doorbell.UUID]: {
|
|
201
|
+
required: ['status'],
|
|
202
|
+
comment: 'status => 0 = not pressed, 1 = pressed',
|
|
203
|
+
},
|
|
204
|
+
[this.hap.Service.SmokeSensor.UUID]: {
|
|
205
|
+
required: ['status'],
|
|
206
|
+
comment: 'status => 0 = smoke cleared, 1 = smoke detected',
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Lookup metadata for this target UUID (service or characteristic)
|
|
211
|
+
let meta = SERVICE_HISTORY_META[target.UUID];
|
|
212
|
+
if (typeof meta !== 'object') {
|
|
213
|
+
return;
|
|
117
214
|
}
|
|
118
|
-
|
|
119
|
-
|
|
215
|
+
|
|
216
|
+
// Set restart flag if applicable
|
|
217
|
+
if (isNaN(this.restart) === false && typeof entry?.restart === 'undefined') {
|
|
218
|
+
entry.restart = this.restart;
|
|
219
|
+
this.restart = undefined;
|
|
120
220
|
}
|
|
121
|
-
switch (service.UUID) {
|
|
122
|
-
case this.hap.Service.GarageDoorOpener.UUID: {
|
|
123
|
-
// Garage door history
|
|
124
|
-
// entry.time => unix time in seconds
|
|
125
|
-
// entry.status => 0 = closed, 1 = open
|
|
126
|
-
historyEntry.status = entry.status;
|
|
127
|
-
if (typeof entry.restart !== 'undefined') {
|
|
128
|
-
historyEntry.restart = entry.restart;
|
|
129
|
-
}
|
|
130
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
221
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
historyEntry.status = entry.status;
|
|
139
|
-
if (typeof entry.restart !== 'undefined') {
|
|
140
|
-
historyEntry.restart = entry.restart;
|
|
141
|
-
}
|
|
142
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
222
|
+
// Ensure time is set
|
|
223
|
+
if (isNaN(entry?.time) === true) {
|
|
224
|
+
entry.time = Math.floor(Date.now() / 1000);
|
|
225
|
+
}
|
|
145
226
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// entry.status => 0 = closed, 1 = open
|
|
151
|
-
// entry.position => position in % 0% = closed 100% fully open
|
|
152
|
-
historyEntry.status = entry.status;
|
|
153
|
-
historyEntry.position = entry.position;
|
|
154
|
-
if (typeof entry.restart !== 'undefined') {
|
|
155
|
-
historyEntry.restart = entry.restart;
|
|
156
|
-
}
|
|
157
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
227
|
+
// Provide default subtype if missing
|
|
228
|
+
if (typeof target.subtype === 'undefined') {
|
|
229
|
+
target.subtype = 0;
|
|
230
|
+
}
|
|
160
231
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// entry.status => 0 = off, 1 = fan, 2 = heating, 3 = cooling, 4 = dehumidifying
|
|
166
|
-
// entry.temperature => current temperature in degress C
|
|
167
|
-
// entry.target => {low, high} = cooling limit, heating limit
|
|
168
|
-
// entry.humidity => current humidity
|
|
169
|
-
historyEntry.status = entry.status;
|
|
170
|
-
historyEntry.temperature = entry.temperature;
|
|
171
|
-
historyEntry.target = entry.target;
|
|
172
|
-
historyEntry.humidity = entry.humidity;
|
|
173
|
-
if (typeof entry.restart !== 'undefined') {
|
|
174
|
-
historyEntry.restart = entry.restart;
|
|
175
|
-
}
|
|
176
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
232
|
+
// Default timegap if invalid
|
|
233
|
+
if (isNaN(timegap) === true) {
|
|
234
|
+
timegap = 0;
|
|
235
|
+
}
|
|
179
236
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
// entry.temperature => current temperature in degress C
|
|
186
|
-
// entry.humidity => current humidity
|
|
187
|
-
// optional (entry.ppm)
|
|
188
|
-
// optional (entry.voc => current VOC measurement in ppb)\
|
|
189
|
-
// optional (entry.pressure -> in hpa)
|
|
190
|
-
historyEntry.temperature = entry.temperature;
|
|
191
|
-
if (typeof entry.humidity === 'undefined') {
|
|
192
|
-
// fill out humidity if missing
|
|
193
|
-
entry.humidity = 0;
|
|
194
|
-
}
|
|
195
|
-
if (typeof entry.ppm === 'undefined') {
|
|
196
|
-
// fill out ppm if missing
|
|
197
|
-
entry.ppm = 0;
|
|
198
|
-
}
|
|
199
|
-
if (typeof entry.voc === 'undefined') {
|
|
200
|
-
// fill out voc if missing
|
|
201
|
-
entry.voc = 0;
|
|
202
|
-
}
|
|
203
|
-
if (typeof entry.pressure === 'undefined') {
|
|
204
|
-
// fill out pressure if missing
|
|
205
|
-
entry.pressure = 0;
|
|
206
|
-
}
|
|
207
|
-
historyEntry.temperature = entry.temperature;
|
|
208
|
-
historyEntry.humidity = entry.humidity;
|
|
209
|
-
historyEntry.ppm = entry.ppm;
|
|
210
|
-
historyEntry.voc = entry.voc;
|
|
211
|
-
historyEntry.pressure = entry.pressure;
|
|
212
|
-
if (typeof entry.restart !== 'undefined') {
|
|
213
|
-
historyEntry.restart = entry.restart;
|
|
214
|
-
}
|
|
215
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
216
|
-
break;
|
|
237
|
+
// Validate required keys exist on entry
|
|
238
|
+
let required = [].concat(meta.required || []);
|
|
239
|
+
for (let i = 0; i < required.length; i++) {
|
|
240
|
+
if (typeof entry[required[i]] === 'undefined') {
|
|
241
|
+
return;
|
|
217
242
|
}
|
|
243
|
+
}
|
|
218
244
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
historyEntry.status = entry.status;
|
|
226
|
-
historyEntry.water = entry.water;
|
|
227
|
-
historyEntry.duration = entry.duration;
|
|
228
|
-
if (typeof entry.restart !== 'undefined') {
|
|
229
|
-
historyEntry.restart = entry.restart;
|
|
245
|
+
// Fill in default values if specified
|
|
246
|
+
if (typeof meta.defaults === 'object') {
|
|
247
|
+
let defaults = meta.defaults;
|
|
248
|
+
for (let key in defaults) {
|
|
249
|
+
if (typeof entry[key] === 'undefined') {
|
|
250
|
+
entry[key] = defaults[key];
|
|
230
251
|
}
|
|
231
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
232
|
-
break;
|
|
233
252
|
}
|
|
253
|
+
}
|
|
234
254
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
historyEntry.restart = entry.restart;
|
|
255
|
+
// Compose list of keys to include in history entry (required + defaults)
|
|
256
|
+
let keys = [].concat(required);
|
|
257
|
+
if (typeof meta.defaults === 'object') {
|
|
258
|
+
for (let key in meta.defaults) {
|
|
259
|
+
if (keys.indexOf(key) === -1) {
|
|
260
|
+
keys.push(key);
|
|
242
261
|
}
|
|
243
|
-
this.#addEntry(service.UUID, 0, entry.time, timegap, historyEntry); // Characteristics don't have sub type, so we'll use 0 for it
|
|
244
|
-
break;
|
|
245
262
|
}
|
|
263
|
+
}
|
|
246
264
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
historyEntry.restart = entry.restart;
|
|
254
|
-
}
|
|
255
|
-
this.#addEntry(service.UUID, 0, entry.time, timegap, historyEntry); // Characteristics don't have sub type, so we'll use 0 for it
|
|
256
|
-
break;
|
|
265
|
+
// Build the filtered history entry
|
|
266
|
+
let historyEntry = {};
|
|
267
|
+
for (let i = 0; i < keys.length; i++) {
|
|
268
|
+
let key = keys[i];
|
|
269
|
+
if (typeof entry[key] !== 'undefined') {
|
|
270
|
+
historyEntry[key] = entry[key];
|
|
257
271
|
}
|
|
272
|
+
}
|
|
258
273
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// entry.volts => voltage in Vs
|
|
264
|
-
// entry.watts => watts in W's
|
|
265
|
-
// entry.amps => current in A's
|
|
266
|
-
historyEntry.status = entry.status;
|
|
267
|
-
historyEntry.volts = entry.volts;
|
|
268
|
-
historyEntry.watts = entry.watts;
|
|
269
|
-
historyEntry.amps = entry.amps;
|
|
270
|
-
if (typeof entry.restart !== 'undefined') {
|
|
271
|
-
historyEntry.restart = entry.restart;
|
|
272
|
-
}
|
|
273
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
274
|
-
break;
|
|
275
|
-
}
|
|
274
|
+
// Include restart if set
|
|
275
|
+
if (isNaN(entry?.restart) === false) {
|
|
276
|
+
historyEntry.restart = entry.restart;
|
|
277
|
+
}
|
|
276
278
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
// entry.status => 0 = not pressed, 1 = doorbell pressed
|
|
281
|
-
historyEntry.status = entry.status;
|
|
282
|
-
if (typeof entry.restart !== 'undefined') {
|
|
283
|
-
historyEntry.restart = entry.restart;
|
|
284
|
-
}
|
|
285
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
279
|
+
// Use subtype 0 for characteristics like WaterLevel or LeakSensor, else service subtype
|
|
280
|
+
let subtype =
|
|
281
|
+
target.UUID === this.hap.Characteristic.WaterLevel.UUID || target.UUID === this.hap.Service.LeakSensor.UUID ? 0 : target.subtype;
|
|
288
282
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
// entry.time => unix time in seconds
|
|
292
|
-
// entry.status => 0 = smoke cleared, 1 = smoke detected
|
|
293
|
-
historyEntry.status = entry.status;
|
|
294
|
-
if (typeof historyEntry.restart !== 'undefined') {
|
|
295
|
-
historyEntry.restart = entry.restart;
|
|
296
|
-
}
|
|
297
|
-
this.#addEntry(service.UUID, service.subtype, entry.time, timegap, historyEntry);
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
283
|
+
// Call internal add entry handler
|
|
284
|
+
this.#addEntry(target.UUID, subtype, entry.time, timegap, historyEntry);
|
|
301
285
|
}
|
|
302
286
|
|
|
303
287
|
resetHistory() {
|
|
@@ -308,42 +292,41 @@ export default class HomeKitHistory {
|
|
|
308
292
|
this.historyData.next = 0; // next entry for history is at start
|
|
309
293
|
this.historyData.types = []; // no service types in history
|
|
310
294
|
this.historyData.data = []; // no history data
|
|
311
|
-
this.
|
|
295
|
+
this.#persistStorage.setItem(this.#persistKey, this.historyData);
|
|
312
296
|
}
|
|
313
297
|
|
|
314
298
|
rolloverHistory() {
|
|
315
299
|
// Roll history over and start from zero.
|
|
316
300
|
// We'll include an entry as to when the rollover took place
|
|
317
301
|
// remove all history data after the rollover entry
|
|
318
|
-
this.historyData.data.splice(this
|
|
302
|
+
this.historyData.data.splice(this.#maxEntries, this.historyData.data.length);
|
|
319
303
|
this.historyData.rollover = Math.floor(Date.now() / 1000);
|
|
320
304
|
this.historyData.next = 0;
|
|
321
305
|
this.#updateHistoryTypes();
|
|
322
|
-
this.
|
|
306
|
+
this.#persistStorage.setItem(this.#persistKey, this.historyData);
|
|
323
307
|
}
|
|
324
308
|
|
|
325
309
|
#addEntry(type, sub, time, timegap, entry) {
|
|
326
310
|
let historyEntry = {};
|
|
327
311
|
let recordEntry = true; // always record entry unless we don't need to
|
|
312
|
+
|
|
328
313
|
historyEntry.time = time;
|
|
329
314
|
historyEntry.type = type;
|
|
330
315
|
historyEntry.sub = sub;
|
|
316
|
+
|
|
317
|
+
// Filter out reserved keys
|
|
331
318
|
Object.entries(entry).forEach(([key, value]) => {
|
|
332
|
-
if (key !== 'time'
|
|
333
|
-
// Filer out events we want to control
|
|
319
|
+
if (key !== 'time' && key !== 'type' && key !== 'sub') {
|
|
334
320
|
historyEntry[key] = value;
|
|
335
321
|
}
|
|
336
322
|
});
|
|
337
323
|
|
|
338
324
|
// If we have a minimum time gap specified, find the last time entry for this type and if less than min gap, ignore
|
|
339
325
|
if (timegap !== 0) {
|
|
340
|
-
let typeIndex = this.historyData.types.findIndex((
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
time
|
|
344
|
-
typeof historyEntry.restart === 'undefined'
|
|
345
|
-
) {
|
|
346
|
-
// time between last recorded entry and new entry is less than minimum gap and its not a 'restart' entry
|
|
326
|
+
let typeIndex = this.historyData.types.findIndex((t) => t.type === type && t.sub === sub);
|
|
327
|
+
let entryTime = this.historyData.data?.[this.historyData.types?.[typeIndex]?.lastEntry]?.time;
|
|
328
|
+
if (typeIndex >= 0 && typeof entryTime === 'number' && time - entryTime < timegap && typeof historyEntry.restart === 'undefined') {
|
|
329
|
+
// time between last recorded entry and new entry is less than minimum gap and it's not a 'restart' entry
|
|
347
330
|
// so don't log it
|
|
348
331
|
recordEntry = false;
|
|
349
332
|
}
|
|
@@ -351,34 +334,30 @@ export default class HomeKitHistory {
|
|
|
351
334
|
|
|
352
335
|
if (recordEntry === true) {
|
|
353
336
|
// Work out where this goes in the history data array
|
|
354
|
-
if (this
|
|
337
|
+
if (this.#maxEntries !== 0 && this.historyData.next >= this.#maxEntries) {
|
|
355
338
|
// roll over history data as we've reached the defined max entry size
|
|
356
339
|
this.rolloverHistory();
|
|
357
340
|
}
|
|
358
|
-
|
|
341
|
+
|
|
342
|
+
let entryIndex = this.historyData.next;
|
|
343
|
+
this.historyData.data[entryIndex] = historyEntry;
|
|
359
344
|
this.historyData.next++;
|
|
360
345
|
|
|
361
346
|
// Update types we have in history. This will just be the main type and its latest location in history
|
|
362
|
-
let typeIndex = this.historyData.types.findIndex((
|
|
347
|
+
let typeIndex = this.historyData.types.findIndex((t) => t.type === type && t.sub === sub);
|
|
363
348
|
if (typeIndex === -1) {
|
|
364
|
-
this.historyData.types.push({
|
|
365
|
-
type: historyEntry.type,
|
|
366
|
-
sub: historyEntry.sub,
|
|
367
|
-
lastEntry: this.historyData.next - 1,
|
|
368
|
-
});
|
|
349
|
+
this.historyData.types.push({ type: type, sub: sub, lastEntry: entryIndex });
|
|
369
350
|
} else {
|
|
370
|
-
this.historyData.types[typeIndex].lastEntry =
|
|
351
|
+
this.historyData.types[typeIndex].lastEntry = entryIndex;
|
|
371
352
|
}
|
|
372
353
|
|
|
373
354
|
// Validate types last entries. Helps with rolled over data etc. If we cannot find the type anymore, remove from known types
|
|
374
|
-
this.historyData.types.
|
|
375
|
-
|
|
376
|
-
// not found, so remove from known types
|
|
377
|
-
this.historyData.types.splice(index, 1);
|
|
378
|
-
}
|
|
355
|
+
this.historyData.types = this.historyData.types.filter((typeEntry) => {
|
|
356
|
+
return this.historyData.data[typeEntry?.lastEntry]?.type === typeEntry.type;
|
|
379
357
|
});
|
|
380
358
|
|
|
381
|
-
|
|
359
|
+
// Save to persistent storage
|
|
360
|
+
this.#persistStorage.setItem(this.#persistKey, this.historyData);
|
|
382
361
|
}
|
|
383
362
|
}
|
|
384
363
|
|
|
@@ -497,7 +476,7 @@ export default class HomeKitHistory {
|
|
|
497
476
|
|
|
498
477
|
// Overlay our history into EveHome. Can only have one service history exposed to EveHome (ATM... see if can work around)
|
|
499
478
|
// Returns object created for our EveHome accessory if successfull
|
|
500
|
-
linkToEveHome(service, options) {
|
|
479
|
+
async linkToEveHome(service, options) {
|
|
501
480
|
if (typeof service !== 'object' || typeof this?.EveHome?.service !== 'undefined') {
|
|
502
481
|
return;
|
|
503
482
|
}
|
|
@@ -510,7 +489,8 @@ export default class HomeKitHistory {
|
|
|
510
489
|
case this.hap.Service.ContactSensor.UUID:
|
|
511
490
|
case this.hap.Service.Door.UUID:
|
|
512
491
|
case this.hap.Service.Window.UUID:
|
|
513
|
-
case this.hap.Service.GarageDoorOpener.UUID:
|
|
492
|
+
case this.hap.Service.GarageDoorOpener.UUID:
|
|
493
|
+
case this.hap.Service.LockMechanism.UUID: {
|
|
514
494
|
// treat these as EveHome Door
|
|
515
495
|
// Inverse status used for all UUID types except this.hap.Service.ContactSensor.UUID
|
|
516
496
|
|
|
@@ -539,6 +519,7 @@ export default class HomeKitHistory {
|
|
|
539
519
|
count: tempHistory.length,
|
|
540
520
|
reftime: historyreftime,
|
|
541
521
|
send: 0,
|
|
522
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
542
523
|
};
|
|
543
524
|
|
|
544
525
|
// Setup initial values and callbacks for charateristics we are using
|
|
@@ -587,6 +568,7 @@ export default class HomeKitHistory {
|
|
|
587
568
|
count: tempHistory.length,
|
|
588
569
|
reftime: historyreftime,
|
|
589
570
|
send: 0,
|
|
571
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
590
572
|
};
|
|
591
573
|
|
|
592
574
|
//17 CurrentPosition
|
|
@@ -658,7 +640,7 @@ export default class HomeKitHistory {
|
|
|
658
640
|
}
|
|
659
641
|
|
|
660
642
|
default: {
|
|
661
|
-
this?.log?.debug
|
|
643
|
+
this?.log?.debug?.('Unknown Eve MotionBlinds command "%s" with data "%s"', command, data);
|
|
662
644
|
break;
|
|
663
645
|
}
|
|
664
646
|
}
|
|
@@ -702,6 +684,7 @@ export default class HomeKitHistory {
|
|
|
702
684
|
count: tempHistory.length,
|
|
703
685
|
reftime: historyreftime,
|
|
704
686
|
send: 0,
|
|
687
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
705
688
|
};
|
|
706
689
|
|
|
707
690
|
// Need some internal storage to track Eve Thermo configuration from EveHome app
|
|
@@ -722,12 +705,12 @@ export default class HomeKitHistory {
|
|
|
722
705
|
encodeEveData(util.format('2c %s be', numberToEveHexString(this.EveThermoPersist.firmware, 4))),
|
|
723
706
|
); // firmware version (build xxxx)));
|
|
724
707
|
|
|
725
|
-
service.updateCharacteristic(this.hap.Characteristic.EveProgramData, this.#EveThermoGetDetails(
|
|
726
|
-
service.getCharacteristic(this.hap.Characteristic.EveProgramData).onGet(() => {
|
|
727
|
-
return this.#EveThermoGetDetails(
|
|
708
|
+
service.updateCharacteristic(this.hap.Characteristic.EveProgramData, await this.#EveThermoGetDetails());
|
|
709
|
+
service.getCharacteristic(this.hap.Characteristic.EveProgramData).onGet(async () => {
|
|
710
|
+
return await this.#EveThermoGetDetails();
|
|
728
711
|
});
|
|
729
712
|
|
|
730
|
-
service.getCharacteristic(this.hap.Characteristic.EveProgramCommand).onSet((value) => {
|
|
713
|
+
service.getCharacteristic(this.hap.Characteristic.EveProgramCommand).onSet(async (value) => {
|
|
731
714
|
let programs = [];
|
|
732
715
|
let processedData = {};
|
|
733
716
|
let valHex = decodeEveData(value);
|
|
@@ -873,7 +856,7 @@ export default class HomeKitHistory {
|
|
|
873
856
|
}
|
|
874
857
|
programs.push({
|
|
875
858
|
id: programs.length + 1,
|
|
876
|
-
days:
|
|
859
|
+
days: DAYS_OF_WEEK[index2],
|
|
877
860
|
schedule: times,
|
|
878
861
|
});
|
|
879
862
|
}
|
|
@@ -908,15 +891,15 @@ export default class HomeKitHistory {
|
|
|
908
891
|
}
|
|
909
892
|
|
|
910
893
|
default: {
|
|
911
|
-
this?.log?.debug
|
|
894
|
+
this?.log?.debug?.('Unknown Eve Thermo command "%s"', command);
|
|
912
895
|
break;
|
|
913
896
|
}
|
|
914
897
|
}
|
|
915
898
|
}
|
|
916
899
|
|
|
917
|
-
// Send complete processed command data
|
|
918
|
-
if (typeof
|
|
919
|
-
|
|
900
|
+
// Send complete processed command data via message router if defined
|
|
901
|
+
if (typeof this.EveHome?.messages === 'function' && Object.keys(processedData).length !== 0) {
|
|
902
|
+
await this.EveHome.messages(HomeKitHistory.SET, processedData);
|
|
920
903
|
}
|
|
921
904
|
});
|
|
922
905
|
break;
|
|
@@ -942,6 +925,7 @@ export default class HomeKitHistory {
|
|
|
942
925
|
count: tempHistory.length,
|
|
943
926
|
reftime: historyreftime,
|
|
944
927
|
send: 0,
|
|
928
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
945
929
|
};
|
|
946
930
|
|
|
947
931
|
service.updateCharacteristic(
|
|
@@ -983,6 +967,7 @@ export default class HomeKitHistory {
|
|
|
983
967
|
count: tempHistory.length,
|
|
984
968
|
reftime: historyreftime,
|
|
985
969
|
send: 0,
|
|
970
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
986
971
|
};
|
|
987
972
|
|
|
988
973
|
service.updateCharacteristic(
|
|
@@ -1007,6 +992,7 @@ export default class HomeKitHistory {
|
|
|
1007
992
|
count: tempHistory.length,
|
|
1008
993
|
reftime: historyreftime,
|
|
1009
994
|
send: 0,
|
|
995
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
1010
996
|
};
|
|
1011
997
|
|
|
1012
998
|
service.updateCharacteristic(
|
|
@@ -1053,6 +1039,7 @@ export default class HomeKitHistory {
|
|
|
1053
1039
|
count: tempHistory.length,
|
|
1054
1040
|
reftime: historyreftime,
|
|
1055
1041
|
send: 0,
|
|
1042
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
1056
1043
|
};
|
|
1057
1044
|
|
|
1058
1045
|
// Need some internal storage to track Eve Motion configuration from EveHome app
|
|
@@ -1121,7 +1108,7 @@ export default class HomeKitHistory {
|
|
|
1121
1108
|
}
|
|
1122
1109
|
|
|
1123
1110
|
default : {
|
|
1124
|
-
this?.log?.debug
|
|
1111
|
+
this?.log?.debug?.('Unknown Eve Motion command "%s" with data "%s"', command, data);
|
|
1125
1112
|
break;
|
|
1126
1113
|
}
|
|
1127
1114
|
}
|
|
@@ -1159,6 +1146,7 @@ export default class HomeKitHistory {
|
|
|
1159
1146
|
count: tempHistory.length,
|
|
1160
1147
|
reftime: historyreftime,
|
|
1161
1148
|
send: 0,
|
|
1149
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
1162
1150
|
};
|
|
1163
1151
|
|
|
1164
1152
|
// TODO = work out what the 'signatures' need to be for an Eve Smoke
|
|
@@ -1179,21 +1167,21 @@ export default class HomeKitHistory {
|
|
|
1179
1167
|
// Setup initial values and callbacks for charateristics we are using
|
|
1180
1168
|
service.updateCharacteristic(
|
|
1181
1169
|
this.hap.Characteristic.EveDeviceStatus,
|
|
1182
|
-
this.#EveSmokeGetDetails(
|
|
1170
|
+
await this.#EveSmokeGetDetails(this.hap.Characteristic.EveDeviceStatus),
|
|
1183
1171
|
);
|
|
1184
|
-
service.getCharacteristic(this.hap.Characteristic.EveDeviceStatus).onGet(() => {
|
|
1185
|
-
return this.#EveSmokeGetDetails(
|
|
1172
|
+
service.getCharacteristic(this.hap.Characteristic.EveDeviceStatus).onGet(async () => {
|
|
1173
|
+
return await this.#EveSmokeGetDetails(this.hap.Characteristic.EveDeviceStatus);
|
|
1186
1174
|
});
|
|
1187
1175
|
|
|
1188
1176
|
service.updateCharacteristic(
|
|
1189
1177
|
this.hap.Characteristic.EveGetConfiguration,
|
|
1190
|
-
this.#EveSmokeGetDetails(
|
|
1178
|
+
await this.#EveSmokeGetDetails(this.hap.Characteristic.EveGetConfiguration),
|
|
1191
1179
|
);
|
|
1192
|
-
service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(() => {
|
|
1193
|
-
return this.#EveSmokeGetDetails(
|
|
1180
|
+
service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(async () => {
|
|
1181
|
+
return await this.#EveSmokeGetDetails(this.hap.Characteristic.EveGetConfiguration);
|
|
1194
1182
|
});
|
|
1195
1183
|
|
|
1196
|
-
service.getCharacteristic(this.hap.Characteristic.EveSetConfiguration).onSet((value) => {
|
|
1184
|
+
service.getCharacteristic(this.hap.Characteristic.EveSetConfiguration).onSet(async (value) => {
|
|
1197
1185
|
// Loop through set commands passed to us
|
|
1198
1186
|
let processedData = {};
|
|
1199
1187
|
let valHex = decodeEveData(value);
|
|
@@ -1218,22 +1206,22 @@ export default class HomeKitHistory {
|
|
|
1218
1206
|
processedData.statusled = this.EveSmokePersist.statusled;
|
|
1219
1207
|
}
|
|
1220
1208
|
if (subCommand !== 0x02 && subCommand !== 0x05) {
|
|
1221
|
-
this?.log?.debug
|
|
1209
|
+
this?.log?.debug?.('Unknown Eve Smoke command "%s" with data "%s"', command, data);
|
|
1222
1210
|
}
|
|
1223
1211
|
break;
|
|
1224
1212
|
}
|
|
1225
1213
|
|
|
1226
1214
|
default: {
|
|
1227
|
-
this?.log?.debug
|
|
1215
|
+
this?.log?.debug?.('Unknown Eve Smoke command "%s" with data "%s"', command, data);
|
|
1228
1216
|
break;
|
|
1229
1217
|
}
|
|
1230
1218
|
}
|
|
1231
1219
|
index += 4 + size; // Move to next command accounting for header size of 4 bytes
|
|
1232
1220
|
}
|
|
1233
1221
|
|
|
1234
|
-
// Send complete processed command data
|
|
1235
|
-
if (typeof
|
|
1236
|
-
|
|
1222
|
+
// Send complete processed command data via message router if defined
|
|
1223
|
+
if (typeof this.EveHome?.messages === 'function' && Object.keys(processedData).length !== 0) {
|
|
1224
|
+
await this.EveHome.messages(HomeKitHistory.SET, processedData);
|
|
1237
1225
|
}
|
|
1238
1226
|
});
|
|
1239
1227
|
break;
|
|
@@ -1274,6 +1262,7 @@ export default class HomeKitHistory {
|
|
|
1274
1262
|
count: tempHistory.length,
|
|
1275
1263
|
reftime: historyreftime,
|
|
1276
1264
|
send: 0,
|
|
1265
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
1277
1266
|
};
|
|
1278
1267
|
|
|
1279
1268
|
// Need some internal storage to track Eve Aqua configuration from EveHome app
|
|
@@ -1289,12 +1278,12 @@ export default class HomeKitHistory {
|
|
|
1289
1278
|
};
|
|
1290
1279
|
|
|
1291
1280
|
// Setup initial values and callbacks for charateristics we are using
|
|
1292
|
-
service.updateCharacteristic(this.hap.Characteristic.EveGetConfiguration, this.#EveAquaGetDetails(
|
|
1293
|
-
service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(() => {
|
|
1294
|
-
return this.#EveAquaGetDetails(
|
|
1281
|
+
service.updateCharacteristic(this.hap.Characteristic.EveGetConfiguration, await this.#EveAquaGetDetails());
|
|
1282
|
+
service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(async () => {
|
|
1283
|
+
return this.#EveAquaGetDetails();
|
|
1295
1284
|
});
|
|
1296
1285
|
|
|
1297
|
-
service.getCharacteristic(this.hap.Characteristic.EveSetConfiguration).onSet((value) => {
|
|
1286
|
+
service.getCharacteristic(this.hap.Characteristic.EveSetConfiguration).onSet(async (value) => {
|
|
1298
1287
|
// Loop through set commands passed to us
|
|
1299
1288
|
let programs = [];
|
|
1300
1289
|
let processedData = {};
|
|
@@ -1460,9 +1449,9 @@ export default class HomeKitHistory {
|
|
|
1460
1449
|
//let unknown = EveHexStringToNumber(data.substr(0, 6)); // Unknown data for first 6 bytes
|
|
1461
1450
|
let daysbitmask = EveHexStringToNumber(data.substr(8, 6)) >>> 4;
|
|
1462
1451
|
programs.forEach((program) => {
|
|
1463
|
-
for (let index2 = 0; index2 <
|
|
1452
|
+
for (let index2 = 0; index2 < DAYS_OF_WEEK.length; index2++) {
|
|
1464
1453
|
if (((daysbitmask >>> (index2 * 3)) & 0x7) === program.id) {
|
|
1465
|
-
program.days.push(
|
|
1454
|
+
program.days.push(DAYS_OF_WEEK[index2]);
|
|
1466
1455
|
}
|
|
1467
1456
|
}
|
|
1468
1457
|
});
|
|
@@ -1498,16 +1487,16 @@ export default class HomeKitHistory {
|
|
|
1498
1487
|
}
|
|
1499
1488
|
|
|
1500
1489
|
default: {
|
|
1501
|
-
this?.log?.debug
|
|
1490
|
+
this?.log?.debug?.('Unknown Eve Aqua command "%s" with data "%s"', command, data);
|
|
1502
1491
|
break;
|
|
1503
1492
|
}
|
|
1504
1493
|
}
|
|
1505
1494
|
index += 4 + size; // Move to next command accounting for header size of 4 bytes
|
|
1506
1495
|
}
|
|
1507
1496
|
|
|
1508
|
-
// Send complete processed command data
|
|
1509
|
-
if (typeof
|
|
1510
|
-
|
|
1497
|
+
// Send complete processed command data via message router if defined
|
|
1498
|
+
if (typeof this.EveHome?.messages === 'function' && Object.keys(processedData).length !== 0) {
|
|
1499
|
+
await this.EveHome.messages(HomeKitHistory.SET, processedData);
|
|
1511
1500
|
}
|
|
1512
1501
|
});
|
|
1513
1502
|
break;
|
|
@@ -1544,6 +1533,7 @@ export default class HomeKitHistory {
|
|
|
1544
1533
|
count: tempHistory.length,
|
|
1545
1534
|
reftime: historyreftime,
|
|
1546
1535
|
send: 0,
|
|
1536
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
1547
1537
|
};
|
|
1548
1538
|
|
|
1549
1539
|
// Setup initial values and callbacks for charateristics we are using
|
|
@@ -1554,26 +1544,26 @@ export default class HomeKitHistory {
|
|
|
1554
1544
|
|
|
1555
1545
|
service.updateCharacteristic(
|
|
1556
1546
|
this.hap.Characteristic.EveElectricalCurrent,
|
|
1557
|
-
this.#EveEnergyGetDetails(
|
|
1547
|
+
await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalCurrent),
|
|
1558
1548
|
);
|
|
1559
|
-
service.getCharacteristic(this.hap.Characteristic.EveElectricalCurrent).onGet(() => {
|
|
1560
|
-
return this.#EveEnergyGetDetails(
|
|
1549
|
+
service.getCharacteristic(this.hap.Characteristic.EveElectricalCurrent).onGet(async () => {
|
|
1550
|
+
return await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalCurrent);
|
|
1561
1551
|
});
|
|
1562
1552
|
|
|
1563
1553
|
service.updateCharacteristic(
|
|
1564
1554
|
this.hap.Characteristic.EveElectricalVoltage,
|
|
1565
|
-
this.#EveEnergyGetDetails(
|
|
1555
|
+
await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalVoltage),
|
|
1566
1556
|
);
|
|
1567
|
-
service.getCharacteristic(this.hap.Characteristic.EveElectricalVoltage).onGet(() => {
|
|
1568
|
-
return this.#EveEnergyGetDetails(
|
|
1557
|
+
service.getCharacteristic(this.hap.Characteristic.EveElectricalVoltage).onGet(async () => {
|
|
1558
|
+
return await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalVoltage);
|
|
1569
1559
|
});
|
|
1570
1560
|
|
|
1571
1561
|
service.updateCharacteristic(
|
|
1572
1562
|
this.hap.Characteristic.EveElectricalWattage,
|
|
1573
|
-
this.#EveEnergyGetDetails(
|
|
1563
|
+
await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalWattage),
|
|
1574
1564
|
);
|
|
1575
|
-
service.getCharacteristic(this.hap.Characteristic.EveElectricalWattage).onGet(() => {
|
|
1576
|
-
return this.#EveEnergyGetDetails(
|
|
1565
|
+
service.getCharacteristic(this.hap.Characteristic.EveElectricalWattage).onGet(async () => {
|
|
1566
|
+
return await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalWattage);
|
|
1577
1567
|
});
|
|
1578
1568
|
break;
|
|
1579
1569
|
}
|
|
@@ -1607,6 +1597,7 @@ export default class HomeKitHistory {
|
|
|
1607
1597
|
count: tempHistory.length,
|
|
1608
1598
|
reftime: historyreftime,
|
|
1609
1599
|
send: 0,
|
|
1600
|
+
messages: typeof options?.messages === 'function' ? options.messages : undefined,
|
|
1610
1601
|
};
|
|
1611
1602
|
|
|
1612
1603
|
// Need some internal storage to track Eve Water Guard configuration from EveHome app
|
|
@@ -1617,9 +1608,9 @@ export default class HomeKitHistory {
|
|
|
1617
1608
|
};
|
|
1618
1609
|
|
|
1619
1610
|
// Setup initial values and callbacks for charateristics we are using
|
|
1620
|
-
service.updateCharacteristic(this.hap.Characteristic.EveGetConfiguration, this.#EveWaterGuardGetDetails(
|
|
1621
|
-
service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(() => {
|
|
1622
|
-
return this.#EveWaterGuardGetDetails(
|
|
1611
|
+
service.updateCharacteristic(this.hap.Characteristic.EveGetConfiguration, await this.#EveWaterGuardGetDetails());
|
|
1612
|
+
service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(async () => {
|
|
1613
|
+
return await this.#EveWaterGuardGetDetails();
|
|
1623
1614
|
});
|
|
1624
1615
|
|
|
1625
1616
|
service.getCharacteristic(this.hap.Characteristic.EveSetConfiguration).onSet((value) => {
|
|
@@ -1666,7 +1657,7 @@ export default class HomeKitHistory {
|
|
|
1666
1657
|
}
|
|
1667
1658
|
|
|
1668
1659
|
default: {
|
|
1669
|
-
this?.log?.debug
|
|
1660
|
+
this?.log?.debug?.('Unknown Eve Water Guard command "%s" with data "%s"', command, data);
|
|
1670
1661
|
break;
|
|
1671
1662
|
}
|
|
1672
1663
|
}
|
|
@@ -1700,8 +1691,8 @@ export default class HomeKitHistory {
|
|
|
1700
1691
|
}
|
|
1701
1692
|
}
|
|
1702
1693
|
|
|
1703
|
-
updateEveHome(service
|
|
1704
|
-
if (typeof this?.EveHome?.service !== 'object'
|
|
1694
|
+
async updateEveHome(service) {
|
|
1695
|
+
if (typeof this?.EveHome?.service !== 'object') {
|
|
1705
1696
|
return;
|
|
1706
1697
|
}
|
|
1707
1698
|
|
|
@@ -1709,39 +1700,39 @@ export default class HomeKitHistory {
|
|
|
1709
1700
|
case this.hap.Service.SmokeSensor.UUID: {
|
|
1710
1701
|
service.updateCharacteristic(
|
|
1711
1702
|
this.hap.Characteristic.EveDeviceStatus,
|
|
1712
|
-
this.#EveSmokeGetDetails(
|
|
1703
|
+
await this.#EveSmokeGetDetails(this.hap.Characteristic.EveDeviceStatus),
|
|
1713
1704
|
);
|
|
1714
1705
|
service.updateCharacteristic(
|
|
1715
1706
|
this.hap.Characteristic.EveGetConfiguration,
|
|
1716
|
-
this.#EveSmokeGetDetails(
|
|
1707
|
+
await this.#EveSmokeGetDetails(this.hap.Characteristic.EveGetConfiguration),
|
|
1717
1708
|
);
|
|
1718
1709
|
break;
|
|
1719
1710
|
}
|
|
1720
1711
|
|
|
1721
1712
|
case this.hap.Service.HeaterCooler.UUID:
|
|
1722
1713
|
case this.hap.Service.Thermostat.UUID: {
|
|
1723
|
-
service.updateCharacteristic(this.hap.Characteristic.
|
|
1714
|
+
service.updateCharacteristic(this.hap.Characteristic.EveProgramData, await this.#EveThermoGetDetails());
|
|
1724
1715
|
break;
|
|
1725
1716
|
}
|
|
1726
1717
|
|
|
1727
1718
|
case this.hap.Service.Valve.UUID:
|
|
1728
1719
|
case this.hap.Service.IrrigationSystem.UUID: {
|
|
1729
|
-
service.updateCharacteristic(this.hap.Characteristic.EveGetConfiguration, this.#EveAquaGetDetails(
|
|
1720
|
+
service.updateCharacteristic(this.hap.Characteristic.EveGetConfiguration, await this.#EveAquaGetDetails());
|
|
1730
1721
|
break;
|
|
1731
1722
|
}
|
|
1732
1723
|
|
|
1733
1724
|
case this.hap.Service.Outlet.UUID: {
|
|
1734
1725
|
service.updateCharacteristic(
|
|
1735
1726
|
this.hap.Characteristic.EveElectricalWattage,
|
|
1736
|
-
this.#EveEnergyGetDetails(
|
|
1727
|
+
await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalWattage),
|
|
1737
1728
|
);
|
|
1738
1729
|
service.updateCharacteristic(
|
|
1739
1730
|
this.hap.Characteristic.EveElectricalVoltage,
|
|
1740
|
-
this.#EveEnergyGetDetails(
|
|
1731
|
+
await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalVoltage),
|
|
1741
1732
|
);
|
|
1742
1733
|
service.updateCharacteristic(
|
|
1743
1734
|
this.hap.Characteristic.EveElectricalCurrent,
|
|
1744
|
-
this.#EveEnergyGetDetails(
|
|
1735
|
+
await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalCurrent),
|
|
1745
1736
|
);
|
|
1746
1737
|
break;
|
|
1747
1738
|
}
|
|
@@ -1758,7 +1749,7 @@ export default class HomeKitHistory {
|
|
|
1758
1749
|
return lastTime;
|
|
1759
1750
|
}
|
|
1760
1751
|
|
|
1761
|
-
#EveThermoGetDetails(
|
|
1752
|
+
async #EveThermoGetDetails() {
|
|
1762
1753
|
// returns an encoded value formatted for an Eve Thermo device
|
|
1763
1754
|
//
|
|
1764
1755
|
// TODO - before enabling below need to workout:
|
|
@@ -1786,9 +1777,12 @@ export default class HomeKitHistory {
|
|
|
1786
1777
|
// fc - date/time (mmhhDDMMYY)
|
|
1787
1778
|
// 1a - default day program??
|
|
1788
1779
|
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1780
|
+
// If a message router exists, call it to potentially update our persisted state
|
|
1781
|
+
if (typeof this.EveHome?.messages === 'function') {
|
|
1782
|
+
let updated = await this.EveHome.messages(HomeKitHistory.GET, this.EveThermoPersist);
|
|
1783
|
+
if (typeof updated === 'object') {
|
|
1784
|
+
this.EveThermoPersist = updated;
|
|
1785
|
+
}
|
|
1792
1786
|
}
|
|
1793
1787
|
|
|
1794
1788
|
// Encode current date/time
|
|
@@ -1801,8 +1795,7 @@ export default class HomeKitHistory {
|
|
|
1801
1795
|
// Encode program schedule and temperatures
|
|
1802
1796
|
// f4 = temps
|
|
1803
1797
|
// fa = schedule
|
|
1804
|
-
|
|
1805
|
-
let encodedSchedule = [EMPTYSCHEDULE, EMPTYSCHEDULE, EMPTYSCHEDULE, EMPTYSCHEDULE, EMPTYSCHEDULE, EMPTYSCHEDULE, EMPTYSCHEDULE];
|
|
1798
|
+
let encodedSchedule = [EMPTY_SCHEDULE, EMPTY_SCHEDULE, EMPTY_SCHEDULE, EMPTY_SCHEDULE, EMPTY_SCHEDULE, EMPTY_SCHEDULE, EMPTY_SCHEDULE];
|
|
1806
1799
|
let encodedTemperatures = '0000';
|
|
1807
1800
|
if (typeof this.EveThermoPersist.programs === 'object') {
|
|
1808
1801
|
let tempTemperatures = [];
|
|
@@ -1815,8 +1808,8 @@ export default class HomeKitHistory {
|
|
|
1815
1808
|
numberToEveHexString(Math.round((time.start + time.duration) / 600), 2);
|
|
1816
1809
|
tempTemperatures.push(time.ecotemp, time.comforttemp);
|
|
1817
1810
|
});
|
|
1818
|
-
encodedSchedule[
|
|
1819
|
-
temp.substring(0,
|
|
1811
|
+
encodedSchedule[DAYS_OF_WEEK.indexOf(days.days.toLowerCase())] =
|
|
1812
|
+
temp.substring(0, EMPTY_SCHEDULE.length) + EMPTY_SCHEDULE.substring(temp.length, EMPTY_SCHEDULE.length);
|
|
1820
1813
|
});
|
|
1821
1814
|
let ecoTemp = tempTemperatures.length === 0 ? 0 : Math.min(...tempTemperatures);
|
|
1822
1815
|
let comfortTemp = tempTemperatures.length === 0 ? 0 : Math.max(...tempTemperatures);
|
|
@@ -1842,11 +1835,15 @@ export default class HomeKitHistory {
|
|
|
1842
1835
|
return encodeEveData(value);
|
|
1843
1836
|
}
|
|
1844
1837
|
|
|
1845
|
-
#EveAquaGetDetails(
|
|
1838
|
+
async #EveAquaGetDetails() {
|
|
1846
1839
|
// returns an encoded value formatted for an Eve Aqua device for water usage and last water time
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1840
|
+
|
|
1841
|
+
// If a message router exists, call it to potentially update our persisted state
|
|
1842
|
+
if (typeof this.EveHome?.messages === 'function') {
|
|
1843
|
+
let updated = await this.EveHome.messages(HomeKitHistory.GET, this.EveAquaPersist);
|
|
1844
|
+
if (typeof updated === 'object') {
|
|
1845
|
+
this.EveAquaPersist = updated;
|
|
1846
|
+
}
|
|
1850
1847
|
}
|
|
1851
1848
|
|
|
1852
1849
|
if (Array.isArray(this.EveAquaPersist.programs) === false) {
|
|
@@ -1868,7 +1865,7 @@ export default class HomeKitHistory {
|
|
|
1868
1865
|
// Encode program schedule
|
|
1869
1866
|
// 45 = schedules
|
|
1870
1867
|
// 46 = days of weeks for schedule;
|
|
1871
|
-
const
|
|
1868
|
+
const EMPTY_SCHEDULE = '0800';
|
|
1872
1869
|
let encodedSchedule = '';
|
|
1873
1870
|
let daysbitmask = 0;
|
|
1874
1871
|
let temp45Command = '';
|
|
@@ -1915,12 +1912,12 @@ export default class HomeKitHistory {
|
|
|
1915
1912
|
// Program ID is set in 3bit repeating sections
|
|
1916
1913
|
// sunsatfrithuwedtuemon
|
|
1917
1914
|
program.days.forEach((day) => {
|
|
1918
|
-
daysbitmask = daysbitmask + (program.id << (
|
|
1915
|
+
daysbitmask = daysbitmask + (program.id << (DAYS_OF_WEEK.indexOf(day) * 3));
|
|
1919
1916
|
});
|
|
1920
1917
|
});
|
|
1921
1918
|
|
|
1922
1919
|
// Build the encoded schedules command to send back to Eve
|
|
1923
|
-
temp45Command = '05' + numberToEveHexString(this.EveAquaPersist.programs.length + 1, 2) + '000000' +
|
|
1920
|
+
temp45Command = '05' + numberToEveHexString(this.EveAquaPersist.programs.length + 1, 2) + '000000' + EMPTY_SCHEDULE + encodedSchedule;
|
|
1924
1921
|
temp45Command = '45' + numberToEveHexString(temp45Command.length / 2, 2) + temp45Command;
|
|
1925
1922
|
|
|
1926
1923
|
// Build the encoded days command to send back to Eve
|
|
@@ -1948,13 +1945,16 @@ export default class HomeKitHistory {
|
|
|
1948
1945
|
return encodeEveData(value);
|
|
1949
1946
|
}
|
|
1950
1947
|
|
|
1951
|
-
#EveEnergyGetDetails(
|
|
1948
|
+
async #EveEnergyGetDetails(returnForCharacteristic) {
|
|
1952
1949
|
let energyDetails = {};
|
|
1953
1950
|
let returnValue = null;
|
|
1954
1951
|
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1952
|
+
// If a message router exists, call it to potentially update our persisted state
|
|
1953
|
+
if (typeof this.EveHome?.messages === 'function') {
|
|
1954
|
+
let updated = await this.EveHome.messages(HomeKitHistory.GET, energyDetails);
|
|
1955
|
+
if (typeof updated === 'object') {
|
|
1956
|
+
energyDetails = updated;
|
|
1957
|
+
}
|
|
1958
1958
|
}
|
|
1959
1959
|
|
|
1960
1960
|
if (returnForCharacteristic.UUID === this.hap.Characteristic.EveElectricalWattage.UUID && typeof energyDetails?.watts === 'number') {
|
|
@@ -1970,13 +1970,16 @@ export default class HomeKitHistory {
|
|
|
1970
1970
|
return returnValue;
|
|
1971
1971
|
}
|
|
1972
1972
|
|
|
1973
|
-
#EveSmokeGetDetails(
|
|
1973
|
+
async #EveSmokeGetDetails(returnForCharacteristic) {
|
|
1974
1974
|
// returns an encoded value formatted for an Eve Smoke device
|
|
1975
1975
|
let returnValue = null;
|
|
1976
1976
|
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1977
|
+
// If a message router exists, call it to potentially update our persisted state
|
|
1978
|
+
if (typeof this.EveHome?.messages === 'function') {
|
|
1979
|
+
let updated = await this.EveHome.messages(HomeKitHistory.GET, this.EveSmokePersist);
|
|
1980
|
+
if (typeof updated === 'object') {
|
|
1981
|
+
this.EveSmokePersist = updated;
|
|
1982
|
+
}
|
|
1980
1983
|
}
|
|
1981
1984
|
|
|
1982
1985
|
if (returnForCharacteristic.UUID === this.hap.Characteristic.EveGetConfiguration.UUID) {
|
|
@@ -2036,11 +2039,15 @@ export default class HomeKitHistory {
|
|
|
2036
2039
|
return returnValue;
|
|
2037
2040
|
}
|
|
2038
2041
|
|
|
2039
|
-
#EveWaterGuardGetDetails(
|
|
2042
|
+
async #EveWaterGuardGetDetails() {
|
|
2040
2043
|
// returns an encoded value formatted for an Eve Water Guard
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
+
|
|
2045
|
+
// If a message router exists, call it to potentially update our persisted state
|
|
2046
|
+
if (typeof this.EveHome?.messages === 'function') {
|
|
2047
|
+
let updated = await this.EveHome.messages(HomeKitHistory.GET, this.EveWaterGuardPersist);
|
|
2048
|
+
if (typeof updated === 'object') {
|
|
2049
|
+
this.EveWaterGuardPersist = updated;
|
|
2050
|
+
}
|
|
2044
2051
|
}
|
|
2045
2052
|
|
|
2046
2053
|
let value = util.format(
|
|
@@ -2067,7 +2074,7 @@ export default class HomeKitHistory {
|
|
|
2067
2074
|
numberToEveHexString(this.EveHome.fields.trim().match(/\S*[0-9]\S*/g).length, 2), // Calclate number of fields we have
|
|
2068
2075
|
this.EveHome.fields.trim(), // Fields listed in string. Each field is seperated by spaces
|
|
2069
2076
|
numberToEveHexString(this.EveHome.count, 4), // count of entries
|
|
2070
|
-
numberToEveHexString(this
|
|
2077
|
+
numberToEveHexString(this.#maxEntries === 0 ? MAX_HISTORY_SIZE : this.#maxEntries, 4), // history max size
|
|
2071
2078
|
numberToEveHexString(1, 8),
|
|
2072
2079
|
); // first entry
|
|
2073
2080
|
|
|
@@ -2240,7 +2247,7 @@ export default class HomeKitHistory {
|
|
|
2240
2247
|
numberToEveHexString(historyEntry.temperature * 100, 4), // temperature
|
|
2241
2248
|
numberToEveHexString(historyEntry.humidity * 100, 4), // Humidity
|
|
2242
2249
|
numberToEveHexString(tempTarget * 100, 4), // target temperature for heating
|
|
2243
|
-
numberToEveHexString(historyEntry.status === 2 ? 100 :
|
|
2250
|
+
numberToEveHexString(historyEntry.status === 2 ? 100 : 0, 2), // 0% = off, 100% = heating
|
|
2244
2251
|
numberToEveHexString(0, 2), // Thermo target
|
|
2245
2252
|
numberToEveHexString(0, 2), // Window open status 0 = closed, 1 = open
|
|
2246
2253
|
);
|
|
@@ -2306,29 +2313,38 @@ export default class HomeKitHistory {
|
|
|
2306
2313
|
this.EveHome.entry = 1; // requested to restart from beginning of history for sending to EveHome
|
|
2307
2314
|
}
|
|
2308
2315
|
this.EveHome.send = this.EveHome.count - this.EveHome.entry + 1; // Number of entries we're expected to send
|
|
2309
|
-
this?.log?.debug
|
|
2316
|
+
this?.log?.debug?.('#EveHistoryRequest: requested address', this.EveHome.entry);
|
|
2310
2317
|
}
|
|
2311
2318
|
|
|
2312
2319
|
#EveSetTime(value) {
|
|
2313
2320
|
// Time stamp from EveHome
|
|
2314
2321
|
let timestamp = EPOCH_OFFSET + EveHexStringToNumber(decodeEveData(value));
|
|
2315
2322
|
|
|
2316
|
-
this?.log?.debug
|
|
2323
|
+
this?.log?.debug?.('#EveSetTime: timestamp offset', new Date(timestamp * 1000));
|
|
2317
2324
|
}
|
|
2318
2325
|
|
|
2319
2326
|
#createHistoryService(service, characteristics) {
|
|
2320
|
-
|
|
2327
|
+
if (
|
|
2328
|
+
typeof this?.accessory?.getService !== 'function' ||
|
|
2329
|
+
typeof this?.accessory?.addService !== 'function' ||
|
|
2330
|
+
typeof service?.testCharacteristic !== 'function' ||
|
|
2331
|
+
typeof service?.addCharacteristic !== 'function'
|
|
2332
|
+
) {
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2321
2336
|
let historyService = this.accessory.getService(this.hap.Service.EveHomeHistory);
|
|
2322
2337
|
if (historyService === undefined) {
|
|
2323
2338
|
historyService = this.accessory.addService(this.hap.Service.EveHomeHistory, '', 1);
|
|
2324
2339
|
}
|
|
2325
2340
|
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2341
|
+
if (Array.isArray(characteristics) === true) {
|
|
2342
|
+
characteristics.forEach((char) => {
|
|
2343
|
+
if (service.testCharacteristic(char) === false) {
|
|
2344
|
+
service.addCharacteristic(char);
|
|
2345
|
+
}
|
|
2346
|
+
});
|
|
2347
|
+
}
|
|
2332
2348
|
|
|
2333
2349
|
return historyService;
|
|
2334
2350
|
}
|