@smarterhomeapp/homebridge-vantage 1.0.4 → 1.0.6
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/dist/platform.d.ts +2 -0
- package/dist/platform.js +36 -2
- package/dist/platformAccessory.js +3 -0
- package/dist/vantageInfusion.d.ts +6 -0
- package/dist/vantageInfusion.js +134 -36
- package/package.json +1 -1
package/dist/platform.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export declare class VantagePlatform implements DynamicPlatformPlugin {
|
|
|
12
12
|
constructor(log: Logger, config: PlatformConfig, api: API);
|
|
13
13
|
configureAccessory(accessory: PlatformAccessory): void;
|
|
14
14
|
private syncAccessories;
|
|
15
|
+
private shouldSkipStaleRemoval;
|
|
16
|
+
private rebuildAccessoryVidMap;
|
|
15
17
|
private wireRealtimeListeners;
|
|
16
18
|
private finalizeLogs;
|
|
17
19
|
getInfusion(): VantageInfusion;
|
package/dist/platform.js
CHANGED
|
@@ -50,10 +50,27 @@ class VantagePlatform {
|
|
|
50
50
|
// Reconcile cached accessories with current device list
|
|
51
51
|
async syncAccessories(devices) {
|
|
52
52
|
const uuidFor = (d) => this.api.hap.uuid.generate(String(d.vid));
|
|
53
|
+
if (devices.length === 0 && this.accessories.length > 0) {
|
|
54
|
+
this.log.error(`Discovery returned 0 devices; preserving ${this.accessories.length} cached accessories ` +
|
|
55
|
+
'and rebuilding realtime VID map.');
|
|
56
|
+
this.rebuildAccessoryVidMap();
|
|
57
|
+
this.wireRealtimeListeners();
|
|
58
|
+
this.syncedOnce = true;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
53
61
|
// Map current devices
|
|
54
62
|
const wanted = new Map();
|
|
55
63
|
for (const d of devices)
|
|
56
64
|
wanted.set(uuidFor(d), d);
|
|
65
|
+
const toUnregister = this.accessories.filter((acc) => !wanted.has(acc.UUID));
|
|
66
|
+
if (toUnregister.length && this.shouldSkipStaleRemoval(devices.length, toUnregister.length)) {
|
|
67
|
+
this.log.error(`Suspicious partial discovery detected; preserving ${this.accessories.length} cached accessories. ` +
|
|
68
|
+
`Discovery returned ${devices.length} devices and would remove ${toUnregister.length}.`);
|
|
69
|
+
this.rebuildAccessoryVidMap();
|
|
70
|
+
this.wireRealtimeListeners();
|
|
71
|
+
this.syncedOnce = true;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
57
74
|
// Update existing / mark seen
|
|
58
75
|
const seen = new Set();
|
|
59
76
|
const toUpdate = [];
|
|
@@ -93,10 +110,9 @@ class VantagePlatform {
|
|
|
93
110
|
this.log.info(`Registered ${toRegister.length} new accessories`);
|
|
94
111
|
}
|
|
95
112
|
// Unregister stale
|
|
96
|
-
const toUnregister = this.accessories.filter((acc) => !wanted.has(acc.UUID));
|
|
97
113
|
if (toUnregister.length) {
|
|
98
114
|
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, toUnregister);
|
|
99
|
-
this.log.info(`Unregistered ${toUnregister.length} stale accessories`);
|
|
115
|
+
this.log.info(`Unregistered ${toUnregister.length} stale accessories after successful discovery`);
|
|
100
116
|
// Remove from local cache & VID map
|
|
101
117
|
for (const acc of toUnregister) {
|
|
102
118
|
const vid = acc?.context?.device?.vid;
|
|
@@ -111,6 +127,24 @@ class VantagePlatform {
|
|
|
111
127
|
this.wireRealtimeListeners();
|
|
112
128
|
this.syncedOnce = true;
|
|
113
129
|
}
|
|
130
|
+
shouldSkipStaleRemoval(discoveredCount, staleCount) {
|
|
131
|
+
const cachedCount = this.accessories.length;
|
|
132
|
+
if (cachedCount === 0 || staleCount === 0)
|
|
133
|
+
return false;
|
|
134
|
+
const removalRatio = staleCount / cachedCount;
|
|
135
|
+
const discoveredRatio = discoveredCount / cachedCount;
|
|
136
|
+
// Allow small cleanup, but refuse sudden large percentage drops that look like partial discovery.
|
|
137
|
+
return staleCount >= 5 && removalRatio > 0.5 && discoveredRatio < 0.5;
|
|
138
|
+
}
|
|
139
|
+
rebuildAccessoryVidMap() {
|
|
140
|
+
this.accessoriesByVid.clear();
|
|
141
|
+
for (const acc of this.accessories) {
|
|
142
|
+
const vid = acc?.context?.device?.vid;
|
|
143
|
+
if (vid !== undefined && vid !== null) {
|
|
144
|
+
this.accessoriesByVid.set(String(vid), acc);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
114
148
|
wireRealtimeListeners() {
|
|
115
149
|
if (this.realtimeWired)
|
|
116
150
|
return;
|
|
@@ -21,6 +21,7 @@ class VantagePlatformAccessory {
|
|
|
21
21
|
this.accessory.addService(this.Service.AccessoryInformation);
|
|
22
22
|
info.setCharacteristic(this.Characteristic.Manufacturer, 'Vantage Controls');
|
|
23
23
|
info.setCharacteristic(this.Characteristic.Model, device.objectType ?? device.type);
|
|
24
|
+
info.setCharacteristic(this.Characteristic.Name, device.name);
|
|
24
25
|
info.setCharacteristic(this.Characteristic.SerialNumber, `VID ${device.vid}`);
|
|
25
26
|
// Build the correct primary service for the type
|
|
26
27
|
let primary;
|
|
@@ -184,6 +185,8 @@ class VantagePlatformAccessory {
|
|
|
184
185
|
this.platform.log.warn(`Unsupported device type ${device.type} for ${this.accessory.displayName}`);
|
|
185
186
|
return;
|
|
186
187
|
}
|
|
188
|
+
primary.displayName = device.name;
|
|
189
|
+
primary.setCharacteristic(this.Characteristic.Name, device.name);
|
|
187
190
|
// Mark primary (helps Home app pick the right tile)
|
|
188
191
|
try {
|
|
189
192
|
primary.setPrimaryService?.(true);
|
|
@@ -18,6 +18,7 @@ export declare class VantageInfusion extends EventEmitter {
|
|
|
18
18
|
private isInsecureCmd;
|
|
19
19
|
private isInsecureCfg;
|
|
20
20
|
private getCacheFilePath;
|
|
21
|
+
private cacheKeyForIp;
|
|
21
22
|
constructor(opts: Options);
|
|
22
23
|
/** Probe ports and open command session */
|
|
23
24
|
start(): Promise<void>;
|
|
@@ -29,11 +30,16 @@ export declare class VantageInfusion extends EventEmitter {
|
|
|
29
30
|
/** XML/config discovery; returns devices (replaces old Discover/DiscoverSSL) */
|
|
30
31
|
discoverDevices(): Promise<VantageDevice[]>;
|
|
31
32
|
private processConfigurationXml;
|
|
33
|
+
private parseAndValidateConfigurationXml;
|
|
34
|
+
private isRelevantConfigObject;
|
|
35
|
+
private errorMessage;
|
|
32
36
|
/** Escape text for XML (same as sanitize in index.js). */
|
|
33
37
|
private static escapeXml;
|
|
34
38
|
/** Build cache XML from readObjects (same structure + sanitization as original index.js). */
|
|
35
39
|
private buildCacheXml;
|
|
36
40
|
private pullConfigurationXml;
|
|
41
|
+
private writeCacheAtomically;
|
|
42
|
+
private validateExistingCacheForReplacement;
|
|
37
43
|
setBrightness(vid: string, level: number): Promise<void>;
|
|
38
44
|
setHue(vid: string, hue: number): Promise<void>;
|
|
39
45
|
setSaturation(vid: string, sat: number): Promise<void>;
|
package/dist/vantageInfusion.js
CHANGED
|
@@ -71,14 +71,19 @@ const OBJECT_TYPES = [
|
|
|
71
71
|
...TYPE_THERMO,
|
|
72
72
|
...TYPE_BLIND,
|
|
73
73
|
];
|
|
74
|
+
const DISCOVERABLE_OBJECT_TYPES = OBJECT_TYPES.filter((type) => type !== 'Area');
|
|
74
75
|
class VantageInfusion extends events_1.EventEmitter {
|
|
75
76
|
getCacheFilePath() {
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
const fileName = `vantage-${this.cacheKeyForIp(this.opts.ipaddress)}.dc`;
|
|
78
|
+
// Try /tmp first (Linux/macOS), then fall back to user's home directory.
|
|
79
|
+
const tmpPath = path.join('/tmp', fileName);
|
|
78
80
|
if (fs.existsSync('/tmp')) {
|
|
79
81
|
return tmpPath;
|
|
80
82
|
}
|
|
81
|
-
return path.join(os.homedir(),
|
|
83
|
+
return path.join(os.homedir(), fileName);
|
|
84
|
+
}
|
|
85
|
+
cacheKeyForIp(ipaddress) {
|
|
86
|
+
return ipaddress.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
82
87
|
}
|
|
83
88
|
constructor(opts) {
|
|
84
89
|
super();
|
|
@@ -268,24 +273,25 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
268
273
|
if (fs.existsSync(cachePath)) {
|
|
269
274
|
try {
|
|
270
275
|
const cachedXml = fs.readFileSync(cachePath, 'utf8');
|
|
271
|
-
this.
|
|
272
|
-
|
|
276
|
+
const devices = await this.processConfigurationXml(cachedXml, `cached configuration from ${cachePath}`);
|
|
277
|
+
this.opts.log.info(`Valid cached configuration loaded from ${cachePath}`);
|
|
278
|
+
return devices;
|
|
273
279
|
}
|
|
274
280
|
catch (error) {
|
|
275
|
-
this.opts.log.warn(`
|
|
281
|
+
this.opts.log.warn(`Cached configuration rejected from ${cachePath}: ${this.errorMessage(error)}`);
|
|
276
282
|
}
|
|
277
283
|
}
|
|
278
284
|
}
|
|
279
285
|
const xml = await this.pullConfigurationXml();
|
|
280
286
|
// Legacy: "end configuration download"
|
|
281
287
|
this.opts.log.debug('VantagePlatform for InFusion Controller (end configuration download)');
|
|
282
|
-
|
|
288
|
+
const devices = await this.processConfigurationXml(xml, 'downloaded configuration');
|
|
289
|
+
this.opts.log.info('Valid configuration loaded from controller download');
|
|
290
|
+
return devices;
|
|
283
291
|
}
|
|
284
|
-
async processConfigurationXml(xml) {
|
|
285
|
-
const
|
|
286
|
-
const parsed = await this.xml.parseStringPromise(sanitized).catch(() => ({}));
|
|
292
|
+
async processConfigurationXml(xml, source = 'configuration') {
|
|
293
|
+
const { objects } = await this.parseAndValidateConfigurationXml(xml, source);
|
|
287
294
|
const devices = [];
|
|
288
|
-
const objects = parsed?.Project?.Objects?.Object || [];
|
|
289
295
|
const Areas = objects.filter((el) => Object.keys(el)[0] === 'Area');
|
|
290
296
|
const Area = {};
|
|
291
297
|
for (const a of Areas)
|
|
@@ -323,7 +329,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
323
329
|
devices.push(dev);
|
|
324
330
|
};
|
|
325
331
|
if (it.DeviceCategory === 'HVAC' || TYPE_THERMO.includes(it.ObjectType)) {
|
|
326
|
-
this.opts.log.info(`New HVAC added (VID=${it.VID}, Name=${
|
|
332
|
+
this.opts.log.info(`New HVAC added (VID=${it.VID}, Name=${name})`);
|
|
327
333
|
pushUnique({
|
|
328
334
|
name,
|
|
329
335
|
address: vid,
|
|
@@ -357,7 +363,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
357
363
|
it.LoadType === 'Motor' ||
|
|
358
364
|
it.LoadType === 'High Voltage Relay';
|
|
359
365
|
if (isRelay) {
|
|
360
|
-
this.opts.log.info(`New relay added (VID=${it.VID}, Name=${
|
|
366
|
+
this.opts.log.info(`New relay added (VID=${it.VID}, Name=${name}, RELAY)`);
|
|
361
367
|
pushUnique({
|
|
362
368
|
name,
|
|
363
369
|
address: vid,
|
|
@@ -370,7 +376,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
370
376
|
});
|
|
371
377
|
}
|
|
372
378
|
else if (it.ObjectType === 'Vantage.DDGColorLoad') {
|
|
373
|
-
this.opts.log.info(`New load added (VID=${it.VID}, Name=${
|
|
379
|
+
this.opts.log.info(`New load added (VID=${it.VID}, Name=${name}, RGB)`);
|
|
374
380
|
pushUnique({
|
|
375
381
|
name,
|
|
376
382
|
address: vid,
|
|
@@ -385,7 +391,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
385
391
|
});
|
|
386
392
|
}
|
|
387
393
|
else {
|
|
388
|
-
this.opts.log.info(`New load added (VID=${it.VID}, Name=${
|
|
394
|
+
this.opts.log.info(`New load added (VID=${it.VID}, Name=${name}, DIMMER)`);
|
|
389
395
|
pushUnique({
|
|
390
396
|
name,
|
|
391
397
|
address: vid,
|
|
@@ -404,7 +410,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
404
410
|
blindOpenClose[it.OpenLoad] = it.OpenLoad;
|
|
405
411
|
blindOpenClose[it.CloseLoad] = it.CloseLoad;
|
|
406
412
|
}
|
|
407
|
-
this.opts.log.info(`New Blind added (VID=${it.VID}, Name=${
|
|
413
|
+
this.opts.log.info(`New Blind added (VID=${it.VID}, Name=${name}, BLIND)`);
|
|
408
414
|
pushUnique({ name, address: vid, type: 'blind', vid, objectType: it.ObjectType, pos: 100, posState: 2 });
|
|
409
415
|
continue;
|
|
410
416
|
}
|
|
@@ -420,6 +426,51 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
420
426
|
}
|
|
421
427
|
return filtered;
|
|
422
428
|
}
|
|
429
|
+
async parseAndValidateConfigurationXml(xml, source) {
|
|
430
|
+
const sanitized = this.sanitizeXml(xml);
|
|
431
|
+
let parsedAny;
|
|
432
|
+
try {
|
|
433
|
+
parsedAny = await this.xml.parseStringPromise(sanitized);
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
const message = `${source} rejected: XML parsing failed: ${this.errorMessage(error)}`;
|
|
437
|
+
this.opts.log.error(message);
|
|
438
|
+
throw new Error(message);
|
|
439
|
+
}
|
|
440
|
+
const parsed = parsedAny?.smarterHome ?? parsedAny;
|
|
441
|
+
const objectNode = parsed?.Project?.Objects?.Object;
|
|
442
|
+
if (!objectNode) {
|
|
443
|
+
const message = `${source} rejected: missing Project.Objects.Object configuration structure`;
|
|
444
|
+
this.opts.log.error(message);
|
|
445
|
+
throw new Error(message);
|
|
446
|
+
}
|
|
447
|
+
const objects = Array.isArray(objectNode) ? objectNode : [objectNode];
|
|
448
|
+
if (objects.length === 0) {
|
|
449
|
+
const message = `${source} rejected: Project.Objects.Object contains no objects`;
|
|
450
|
+
this.opts.log.error(message);
|
|
451
|
+
throw new Error(message);
|
|
452
|
+
}
|
|
453
|
+
const relevantObjectCount = objects.filter((object) => this.isRelevantConfigObject(object)).length;
|
|
454
|
+
if (relevantObjectCount === 0) {
|
|
455
|
+
const message = `${source} rejected: configuration contains no discoverable Vantage objects`;
|
|
456
|
+
this.opts.log.error(message);
|
|
457
|
+
throw new Error(message);
|
|
458
|
+
}
|
|
459
|
+
return { objects, relevantObjectCount };
|
|
460
|
+
}
|
|
461
|
+
isRelevantConfigObject(raw) {
|
|
462
|
+
if (!raw || typeof raw !== 'object')
|
|
463
|
+
return false;
|
|
464
|
+
const key = Object.keys(raw)[0];
|
|
465
|
+
const value = raw[key];
|
|
466
|
+
if (!value || typeof value !== 'object')
|
|
467
|
+
return false;
|
|
468
|
+
const objectType = value?.ObjectType ?? key;
|
|
469
|
+
return DISCOVERABLE_OBJECT_TYPES.includes(key) || DISCOVERABLE_OBJECT_TYPES.includes(objectType);
|
|
470
|
+
}
|
|
471
|
+
errorMessage(error) {
|
|
472
|
+
return error instanceof Error ? error.message : String(error);
|
|
473
|
+
}
|
|
423
474
|
/** Escape text for XML (same as sanitize in index.js). */
|
|
424
475
|
static escapeXml(s) {
|
|
425
476
|
return String(s)
|
|
@@ -440,30 +491,33 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
440
491
|
return `<Project><Objects>${frag}</Objects></Project>`;
|
|
441
492
|
}
|
|
442
493
|
pullConfigurationXml() {
|
|
443
|
-
return new Promise((resolve) => {
|
|
444
|
-
const finishOnce = (socket, xml) => {
|
|
494
|
+
return new Promise((resolve, reject) => {
|
|
495
|
+
const finishOnce = async (socket, xml) => {
|
|
445
496
|
if (finishOnce._done)
|
|
446
497
|
return;
|
|
447
498
|
finishOnce._done = true;
|
|
448
499
|
// Save configuration to cache if usecache is enabled
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
fs.writeFileSync(cachePath, xml);
|
|
453
|
-
this.opts.log.info(`Configuration cached to ${cachePath}`);
|
|
500
|
+
try {
|
|
501
|
+
if (this.opts.usecache) {
|
|
502
|
+
await this.writeCacheAtomically(xml);
|
|
454
503
|
}
|
|
455
|
-
|
|
456
|
-
|
|
504
|
+
// legacy log + event
|
|
505
|
+
this.opts.log.debug('VantagePlatform for InFusion Controller (end configuration download)');
|
|
506
|
+
this.emit('endDownloadConfiguration', xml);
|
|
507
|
+
try {
|
|
508
|
+
socket.destroy();
|
|
457
509
|
}
|
|
510
|
+
catch { /* noop */ }
|
|
511
|
+
resolve(xml);
|
|
458
512
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
513
|
+
catch (error) {
|
|
514
|
+
this.opts.log.error(`Downloaded configuration rejected; preserving existing cache: ${this.errorMessage(error)}`);
|
|
515
|
+
try {
|
|
516
|
+
socket.destroy();
|
|
517
|
+
}
|
|
518
|
+
catch { /* noop */ }
|
|
519
|
+
reject(error);
|
|
464
520
|
}
|
|
465
|
-
catch { /* noop */ }
|
|
466
|
-
resolve(xml);
|
|
467
521
|
};
|
|
468
522
|
const onData = (socket) => {
|
|
469
523
|
this.opts.log.info('load dc file'); // legacy
|
|
@@ -484,7 +538,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
484
538
|
if (readObjects.length && Date.now() - lastProgress > 2000) {
|
|
485
539
|
const xml = this.buildCacheXml(readObjects);
|
|
486
540
|
clearInterval(watchdog);
|
|
487
|
-
finishOnce(socket, xml);
|
|
541
|
+
void finishOnce(socket, xml);
|
|
488
542
|
}
|
|
489
543
|
}, 750);
|
|
490
544
|
const writeOpenFilter = () => {
|
|
@@ -593,7 +647,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
593
647
|
const xml = this.buildCacheXml(readObjects);
|
|
594
648
|
clearLoginFallback();
|
|
595
649
|
clearInterval(watchdog);
|
|
596
|
-
finishOnce(socket, xml);
|
|
650
|
+
void finishOnce(socket, xml);
|
|
597
651
|
}
|
|
598
652
|
});
|
|
599
653
|
});
|
|
@@ -603,7 +657,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
603
657
|
const xml = this.buildCacheXml(readObjects);
|
|
604
658
|
clearLoginFallback();
|
|
605
659
|
clearInterval(watchdog);
|
|
606
|
-
finishOnce(socket, xml);
|
|
660
|
+
void finishOnce(socket, xml);
|
|
607
661
|
}
|
|
608
662
|
});
|
|
609
663
|
socket.on('close', () => {
|
|
@@ -612,7 +666,7 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
612
666
|
const xml = this.buildCacheXml(readObjects);
|
|
613
667
|
clearLoginFallback();
|
|
614
668
|
clearInterval(watchdog);
|
|
615
|
-
finishOnce(socket, xml);
|
|
669
|
+
void finishOnce(socket, xml);
|
|
616
670
|
}
|
|
617
671
|
});
|
|
618
672
|
};
|
|
@@ -624,6 +678,50 @@ class VantageInfusion extends events_1.EventEmitter {
|
|
|
624
678
|
}
|
|
625
679
|
});
|
|
626
680
|
}
|
|
681
|
+
async writeCacheAtomically(xml) {
|
|
682
|
+
const cachePath = this.getCacheFilePath();
|
|
683
|
+
const tmpPath = `${cachePath}.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
684
|
+
const candidateValidation = await this.parseAndValidateConfigurationXml(xml, 'downloaded configuration');
|
|
685
|
+
try {
|
|
686
|
+
fs.writeFileSync(tmpPath, xml, 'utf8');
|
|
687
|
+
const tmpXml = fs.readFileSync(tmpPath, 'utf8');
|
|
688
|
+
const validation = await this.parseAndValidateConfigurationXml(tmpXml, `temporary cache ${tmpPath}`);
|
|
689
|
+
if (fs.existsSync(cachePath)) {
|
|
690
|
+
const existingValidation = await this.validateExistingCacheForReplacement(cachePath);
|
|
691
|
+
if (existingValidation &&
|
|
692
|
+
existingValidation.relevantObjectCount >= 20 &&
|
|
693
|
+
candidateValidation.relevantObjectCount < existingValidation.relevantObjectCount * 0.5) {
|
|
694
|
+
const message = `Downloaded configuration rejected as incomplete: ${candidateValidation.relevantObjectCount} relevant objects ` +
|
|
695
|
+
`vs ${existingValidation.relevantObjectCount} in existing cache.`;
|
|
696
|
+
this.opts.log.error(message);
|
|
697
|
+
throw new Error(message);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
fs.renameSync(tmpPath, cachePath);
|
|
701
|
+
this.opts.log.info(`Validated controller configuration accepted and cache replaced atomically at ${cachePath} ` +
|
|
702
|
+
`(${validation.relevantObjectCount} relevant objects).`);
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
try {
|
|
706
|
+
if (fs.existsSync(tmpPath))
|
|
707
|
+
fs.unlinkSync(tmpPath);
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
/* preserve original error */
|
|
711
|
+
}
|
|
712
|
+
throw error;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
async validateExistingCacheForReplacement(cachePath) {
|
|
716
|
+
try {
|
|
717
|
+
const existingXml = fs.readFileSync(cachePath, 'utf8');
|
|
718
|
+
return await this.parseAndValidateConfigurationXml(existingXml, `existing cache ${cachePath}`);
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
this.opts.log.warn(`Existing cache is invalid; replacing it with validated controller configuration: ${this.errorMessage(error)}`);
|
|
722
|
+
return undefined;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
627
725
|
// ===== Commands used by platformAccessory handlers =====
|
|
628
726
|
async setBrightness(vid, level) {
|
|
629
727
|
const lvl = Math.max(0, Math.min(100, Number(level)));
|