feat: persist and cache all known devices; discovery only adds/updates
- Store.mergeDevices(): updates existing by UDN, adds new, keeps offline devices - platform.js: merges discovered into cache; registers cached-offline devices in HomeKit so they remain visible; only removes truly orphaned accessories - server.js: discover endpoint merges and returns full known device list Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,8 +45,8 @@ class DibbyWemoUiServer extends HomebridgePluginUiServer {
|
|||||||
this.onRequest('/devices/discover', async ({ timeout } = {}) => {
|
this.onRequest('/devices/discover', async ({ timeout } = {}) => {
|
||||||
const ms = typeof timeout === 'number' ? timeout : 10_000;
|
const ms = typeof timeout === 'number' ? timeout : 10_000;
|
||||||
const devices = await wemoClient.discoverDevices(ms);
|
const devices = await wemoClient.discoverDevices(ms);
|
||||||
// Persist updated list
|
// Merge into cached list — previously known devices stay even if not found this scan
|
||||||
this._store.saveDevices(devices.map((d) => ({
|
this._store.mergeDevices(devices.map((d) => ({
|
||||||
host: d.host,
|
host: d.host,
|
||||||
port: d.port,
|
port: d.port,
|
||||||
udn: d.udn ?? `${d.host}:${d.port}`,
|
udn: d.udn ?? `${d.host}:${d.port}`,
|
||||||
@@ -54,7 +54,8 @@ class DibbyWemoUiServer extends HomebridgePluginUiServer {
|
|||||||
productModel: d.productModel ?? 'Wemo Device',
|
productModel: d.productModel ?? 'Wemo Device',
|
||||||
firmwareVersion: d.firmwareVersion ?? null,
|
firmwareVersion: d.firmwareVersion ?? null,
|
||||||
})));
|
})));
|
||||||
return devices;
|
// Return the full merged list so the UI shows all known devices
|
||||||
|
return this._store.getDevices();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.onRequest('/devices/state', async ({ host, port }) => {
|
this.onRequest('/devices/state', async ({ host, port }) => {
|
||||||
|
|||||||
@@ -114,25 +114,36 @@ class WemoPlatform {
|
|||||||
|
|
||||||
this.log.info(`Found ${discovered.length} Wemo device(s)`);
|
this.log.info(`Found ${discovered.length} Wemo device(s)`);
|
||||||
|
|
||||||
// Save discovered device list for the custom UI
|
// Merge discovered devices into the cached list — keep offline devices too
|
||||||
this._store.saveDevices(discovered.map((d) => ({
|
const freshForStore = discovered.map((d) => ({
|
||||||
host: d.host,
|
host: d.host,
|
||||||
port: d.port,
|
port: d.port,
|
||||||
udn: d.udn ?? `${d.host}:${d.port}`,
|
udn: d.udn ?? `${d.host}:${d.port}`,
|
||||||
friendlyName: d.friendlyName ?? d.host,
|
friendlyName: d.friendlyName ?? d.host,
|
||||||
productModel: d.productModel ?? 'Wemo Device',
|
productModel: d.productModel ?? 'Wemo Device',
|
||||||
firmwareVersion: d.firmwareVersion ?? null,
|
firmwareVersion: d.firmwareVersion ?? null,
|
||||||
})));
|
}));
|
||||||
|
const allKnown = this._store.mergeDevices(freshForStore);
|
||||||
|
|
||||||
|
// Register newly discovered devices in HomeKit
|
||||||
for (const device of discovered) {
|
for (const device of discovered) {
|
||||||
this._registerDevice(device, pollInterval);
|
this._registerDevice(device, pollInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove stale accessories (devices no longer discovered)
|
// Register previously cached devices that weren't discovered (may be offline)
|
||||||
const activeUUIDs = new Set(discovered.map((d) => this._uuidForDevice(d)));
|
// so HomeKit still knows about them
|
||||||
|
const discoveredUDNs = new Set(discovered.map((d) => d.udn ?? `${d.host}:${d.port}`));
|
||||||
|
for (const cached of allKnown) {
|
||||||
|
if (!discoveredUDNs.has(cached.udn)) {
|
||||||
|
this.log.info(`Device offline/not found during discovery, keeping cached: ${cached.friendlyName}`);
|
||||||
|
this._registerDevice(cached, pollInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove orphaned accessories — those with no device context at all
|
||||||
for (const [uuid, acc] of this._accessories) {
|
for (const [uuid, acc] of this._accessories) {
|
||||||
if (!activeUUIDs.has(uuid)) {
|
if (!acc.context?.device?.host) {
|
||||||
this.log.info('Removing stale accessory: ' + acc.displayName);
|
this.log.info('Removing orphaned accessory (no device context): ' + acc.displayName);
|
||||||
this._handlers.get(uuid)?.stopPolling();
|
this._handlers.get(uuid)?.stopPolling();
|
||||||
this._handlers.delete(uuid);
|
this._handlers.delete(uuid);
|
||||||
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [acc]);
|
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [acc]);
|
||||||
|
|||||||
@@ -56,6 +56,33 @@ class DwmStore {
|
|||||||
getDeviceGroups() { return this._load().deviceGroups ?? []; }
|
getDeviceGroups() { return this._load().deviceGroups ?? []; }
|
||||||
saveDeviceGroups(groups) { const d = this._load(); d.deviceGroups = groups; this._save(d); }
|
saveDeviceGroups(groups) { const d = this._load(); d.deviceGroups = groups; this._save(d); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge freshly discovered devices into the cached list.
|
||||||
|
* - Existing devices are updated with fresh data (host/port/name/firmware).
|
||||||
|
* - Previously cached devices NOT in the new scan are kept as-is (offline ≠ removed).
|
||||||
|
* - Newly found devices are appended.
|
||||||
|
* Returns the merged list.
|
||||||
|
*/
|
||||||
|
mergeDevices(fresh) {
|
||||||
|
const d = this._load();
|
||||||
|
const cached = d.devices ?? [];
|
||||||
|
const byUdn = new Map(cached.map((dev) => [dev.udn, dev]));
|
||||||
|
|
||||||
|
for (const f of fresh) {
|
||||||
|
const udn = f.udn ?? `${f.host}:${f.port}`;
|
||||||
|
if (byUdn.has(udn)) {
|
||||||
|
// Update existing entry with latest network data
|
||||||
|
byUdn.set(udn, { ...byUdn.get(udn), ...f, udn });
|
||||||
|
} else {
|
||||||
|
byUdn.set(udn, { ...f, udn });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
d.devices = Array.from(byUdn.values());
|
||||||
|
this._save(d);
|
||||||
|
return d.devices;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Disabled-rule backups ─────────────────────────────────────────────────
|
// ── Disabled-rule backups ─────────────────────────────────────────────────
|
||||||
|
|
||||||
getDisabledRules() { return this._load().disabledRules ?? {}; }
|
getDisabledRules() { return this._load().disabledRules ?? {}; }
|
||||||
|
|||||||
Reference in New Issue
Block a user