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.
@@ -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/01/18
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 DAYSOFWEEK = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
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
- constructor(accessory, log, api, options) {
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
- if (typeof accessory !== 'undefined' && typeof accessory === 'object') {
49
- this.accessory = accessory;
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 (typeof options === 'object') {
53
- if (typeof options?.maxEntries === 'number') {
54
- this.maxEntries = options.maxEntries;
55
- }
71
+ if (this.hap === undefined) {
72
+ this?.log?.error?.('Missing HAP library API, cannot use class');
73
+ return;
56
74
  }
57
75
 
58
- // Workout if we're running under HomeBridge or HAP-NodeJS library
59
- if (typeof api?.version === 'number' && typeof api?.hap === 'object' && typeof api?.HAPLibraryVersion === 'undefined') {
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 (typeof api?.HAPLibraryVersion === 'function' && typeof api?.version === 'undefined' && typeof api?.hap === 'undefined') {
65
- // As we're missing the HomeBridge entry points but have the HAP library version
66
- this.hap = api;
80
+ if (isNaN(options?.maxEntries) === false) {
81
+ this.#maxEntries = options.maxEntries;
67
82
  }
68
83
 
69
- // Dynamically create the additional services and characteristics
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.storageKey = util.format('History.%s.json', accessory.username.replace(/:/g, '').toUpperCase());
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.storageKey = util.format('History.%s.json', accessory.UUID);
93
+ this.#persistKey = util.format('History.%s.json', accessory.UUID);
82
94
  }
83
95
 
84
- this.storage = this.hap.HAPStorage.storage();
85
-
86
- this.historyData = this.storage.getItem(this.storageKey);
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.maxEntries !== 0 && this.historyData.next >= this.maxEntries) {
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(service, entry, timegap) {
102
- // we'll use the service or characteristic UUID to determine the history entry time and data we'll add
103
- // reformat the entry object to order the fields consistantly in the output
104
- // Add new history types in the switch statement
105
- let historyEntry = {};
106
- if (this.restart !== null && typeof entry.restart === 'undefined') {
107
- // Object recently created, so log the time restarted our history service
108
- entry.restart = this.restart;
109
- this.restart = null;
110
- }
111
- if (typeof entry.time === 'undefined') {
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
- if (typeof service.subtype === 'undefined') {
116
- service.subtype = 0;
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
- if (typeof timegap === 'undefined') {
119
- timegap = 0; // Zero minimum time gap between entries
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
- case this.hap.Service.MotionSensor.UUID: {
135
- // Motion sensor history
136
- // entry.time => unix time in seconds
137
- // entry.status => 0 = motion cleared, 1 = motion detected
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
- case this.hap.Service.Window.UUID:
147
- case this.hap.Service.WindowCovering.UUID: {
148
- // Window and Window Covering history
149
- // entry.time => unix time in seconds
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
- case this.hap.Service.HeaterCooler.UUID:
162
- case this.hap.Service.Thermostat.UUID: {
163
- // Thermostat and Heater/Cooler history
164
- // entry.time => unix time in seconds
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
- case this.hap.Service.EveAirPressureSensor.UUID:
181
- case this.hap.Service.AirQualitySensor.UUID:
182
- case this.hap.Service.TemperatureSensor.UUID: {
183
- // Temperature sensor history
184
- // entry.time => unix time in seconds
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
- case this.hap.Service.Valve.UUID: {
220
- // Water valve history
221
- // entry.time => unix time in seconds
222
- // entry.status => 0 = valve closed, 1 = valve opened
223
- // entry.water => amount of water in L's
224
- // entry.duration => time for water amount
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
- case this.hap.Characteristic.WaterLevel.UUID: {
236
- // Water level history
237
- // entry.time => unix time in seconds
238
- // entry.level => water level as percentage
239
- historyEntry.level = entry.level;
240
- if (typeof entry.restart !== 'undefined') {
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
- case this.hap.Service.LeakSensor.UUID: {
248
- // Leak sensor history
249
- // entry.time => unix time in seconds
250
- // entry.status => 0 = no leak, 1 = leak
251
- historyEntry.status = entry.status;
252
- if (typeof entry.restart !== 'undefined') {
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
- case this.hap.Service.Outlet.UUID: {
260
- // Power outlet history
261
- // entry.time => unix time in seconds
262
- // entry.status => 0 = off, 1 = on
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
- case this.hap.Service.Doorbell.UUID: {
278
- // Doorbell press history
279
- // entry.time => unix time in seconds
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
- case this.hap.Service.SmokeSensor.UUID: {
290
- // Smoke sensor history
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.storage.setItem(this.storageKey, this.historyData);
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.maxEntries, this.historyData.data.length);
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.storage.setItem(this.storageKey, this.historyData);
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' || key !== 'type' || key !== 'sub') {
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((type) => type.type === historyEntry.type && type.sub === historyEntry.sub);
341
- if (
342
- typeIndex >= 0 &&
343
- time - this.historyData.data[this.historyData.types[typeIndex].lastEntry].time < timegap &&
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.maxEntries !== 0 && this.historyData.next >= this.maxEntries) {
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
- this.historyData.data[this.historyData.next] = historyEntry;
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((type) => type.type === historyEntry.type && type.sub === historyEntry.sub);
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 = this.historyData.next - 1;
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.forEach((typeEntry, index) => {
375
- if (this.historyData.data[typeEntry.lastEntry].type !== typeEntry.type) {
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
- this.storage.setItem(this.storageKey, this.historyData); // Save to persistent storage
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 && this.log.debug('Unknown Eve MotionBlinds command "%s" with data "%s"', command, data);
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(options.getcommand));
726
- service.getCharacteristic(this.hap.Characteristic.EveProgramData).onGet(() => {
727
- return this.#EveThermoGetDetails(options.getcommand);
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: DAYSOFWEEK[index2],
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 && this.log.debug('Unknown Eve Thermo command "%s"', command);
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 if configured to our callback
918
- if (typeof options?.setcommand === 'function' && Object.keys(processedData).length !== 0) {
919
- options.setcommand(processedData);
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 && this.log.debug('Unknown Eve Motion command "%s" with data "%s"', command, data);
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(options.getcommand, this.hap.Characteristic.EveDeviceStatus),
1170
+ await this.#EveSmokeGetDetails(this.hap.Characteristic.EveDeviceStatus),
1183
1171
  );
1184
- service.getCharacteristic(this.hap.Characteristic.EveDeviceStatus).onGet(() => {
1185
- return this.#EveSmokeGetDetails(options.getcommand, this.hap.Characteristic.EveDeviceStatus);
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(options.getcommand, this.hap.Characteristic.EveGetConfiguration),
1178
+ await this.#EveSmokeGetDetails(this.hap.Characteristic.EveGetConfiguration),
1191
1179
  );
1192
- service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(() => {
1193
- return this.#EveSmokeGetDetails(options.getcommand, this.hap.Characteristic.EveGetConfiguration);
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 && this.log.debug('Unknown Eve Smoke command "%s" with data "%s"', command, data);
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 && this.log.debug('Unknown Eve Smoke command "%s" with data "%s"', command, data);
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 if configured to our callback
1235
- if (typeof options?.setcommand === 'function' && Object.keys(processedData).length !== 0) {
1236
- options.setcommand(processedData);
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(options.getcommand));
1293
- service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(() => {
1294
- return this.#EveAquaGetDetails(options.getcommand);
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 < DAYSOFWEEK.length; 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(DAYSOFWEEK[index2]);
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 && this.log.debug('Unknown Eve Aqua command "%s" with data "%s"', command, data);
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 if configured to our callback
1509
- if (typeof options?.setcommand === 'function' && Object.keys(processedData).length !== 0) {
1510
- options.setcommand(processedData);
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(options.getcommand, this.hap.Characteristic.EveElectricalCurrent),
1547
+ await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalCurrent),
1558
1548
  );
1559
- service.getCharacteristic(this.hap.Characteristic.EveElectricalCurrent).onGet(() => {
1560
- return this.#EveEnergyGetDetails(options.getcommand, this.hap.Characteristic.EveElectricalCurrent);
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(options.getcommand, this.hap.Characteristic.EveElectricalVoltage),
1555
+ await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalVoltage),
1566
1556
  );
1567
- service.getCharacteristic(this.hap.Characteristic.EveElectricalVoltage).onGet(() => {
1568
- return this.#EveEnergyGetDetails(options.getcommand, this.hap.Characteristic.EveElectricalVoltage);
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(options.getcommand, this.hap.Characteristic.EveElectricalWattage),
1563
+ await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalWattage),
1574
1564
  );
1575
- service.getCharacteristic(this.hap.Characteristic.EveElectricalWattage).onGet(() => {
1576
- return this.#EveEnergyGetDetails(options.getcommand, this.hap.Characteristic.EveElectricalWattage);
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(options.getcommand));
1621
- service.getCharacteristic(this.hap.Characteristic.EveGetConfiguration).onGet(() => {
1622
- return this.#EveWaterGuardGetDetails(options.getcommand);
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 && this.log.debug('Unknown Eve Water Guard command "%s" with data "%s"', command, data);
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, getcommand) {
1704
- if (typeof this?.EveHome?.service !== 'object' || typeof getcommand !== 'function') {
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(getcommand, this.hap.Characteristic.EveDeviceStatus),
1703
+ await this.#EveSmokeGetDetails(this.hap.Characteristic.EveDeviceStatus),
1713
1704
  );
1714
1705
  service.updateCharacteristic(
1715
1706
  this.hap.Characteristic.EveGetConfiguration,
1716
- this.#EveSmokeGetDetails(getcommand, this.hap.Characteristic.EveGetConfiguration),
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.EveProgramCommand, this.#EveThermoGetDetails(getcommand));
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(getcommand));
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(getcommand, this.hap.Characteristic.EveElectricalWattage),
1727
+ await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalWattage),
1737
1728
  );
1738
1729
  service.updateCharacteristic(
1739
1730
  this.hap.Characteristic.EveElectricalVoltage,
1740
- this.#EveEnergyGetDetails(getcommand, this.hap.Characteristic.EveElectricalVoltage),
1731
+ await this.#EveEnergyGetDetails(this.hap.Characteristic.EveElectricalVoltage),
1741
1732
  );
1742
1733
  service.updateCharacteristic(
1743
1734
  this.hap.Characteristic.EveElectricalCurrent,
1744
- this.#EveEnergyGetDetails(getcommand, this.hap.Characteristic.EveElectricalCurrent),
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(getOptions) {
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
- if (typeof getOptions === 'function') {
1790
- // Fill in details we might want to be dynamic
1791
- this.EveThermoPersist = getOptions(this.EveThermoPersist);
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
- const EMPTYSCHEDULE = 'ffffffffffffffff';
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[DAYSOFWEEK.indexOf(days.days.toLowerCase())] =
1819
- temp.substring(0, EMPTYSCHEDULE.length) + EMPTYSCHEDULE.substring(temp.length, EMPTYSCHEDULE.length);
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(getOptions) {
1838
+ async #EveAquaGetDetails() {
1846
1839
  // returns an encoded value formatted for an Eve Aqua device for water usage and last water time
1847
- if (typeof getOptions === 'function') {
1848
- // Fill in details we might want to be dynamic
1849
- this.EveAquaPersist = getOptions(this.EveAquaPersist);
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 EMPTYSCHEDULE = '0800';
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 << (DAYSOFWEEK.indexOf(day) * 3));
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' + EMPTYSCHEDULE + encodedSchedule;
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(getOptions, returnForCharacteristic) {
1948
+ async #EveEnergyGetDetails(returnForCharacteristic) {
1952
1949
  let energyDetails = {};
1953
1950
  let returnValue = null;
1954
1951
 
1955
- if (typeof getOptions === 'function') {
1956
- // Fill in details we might want to be dynamic
1957
- energyDetails = getOptions(energyDetails);
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(getOptions, returnForCharacteristic) {
1973
+ async #EveSmokeGetDetails(returnForCharacteristic) {
1974
1974
  // returns an encoded value formatted for an Eve Smoke device
1975
1975
  let returnValue = null;
1976
1976
 
1977
- if (typeof getOptions === 'function') {
1978
- // Fill in details we might want to be dynamic
1979
- this.EveSmokePersist = getOptions(this.EveSmokePersist);
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(getOptions) {
2042
+ async #EveWaterGuardGetDetails() {
2040
2043
  // returns an encoded value formatted for an Eve Water Guard
2041
- if (typeof getOptions === 'function') {
2042
- // Fill in details we might want to be dynamic
2043
- this.EveWaterGuardPersist = getOptions(this.EveWaterGuardPersist);
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.maxEntries === 0 ? MAX_HISTORY_SIZE : this.maxEntries, 4), // history max size
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 : historyEntry.status === 3 ? 50 : 0, 2), // 0% = off, 50% = cooling, 100% = heating
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 && this.log.debug('#EveHistoryRequest: requested address', this.EveHome.entry);
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 && this.log.debug('#EveSetTime: timestamp offset', new Date(timestamp * 1000));
2323
+ this?.log?.debug?.('#EveSetTime: timestamp offset', new Date(timestamp * 1000));
2317
2324
  }
2318
2325
 
2319
2326
  #createHistoryService(service, characteristics) {
2320
- // Setup the history service
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
- // Add in any specified characteristics
2327
- characteristics.forEach((characteristic) => {
2328
- if (service.testCharacteristic(characteristic) === false) {
2329
- service.addCharacteristic(characteristic);
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
  }