diff --git a/packages/homebridge-plugin/homebridge-ui/server.js b/packages/homebridge-plugin/homebridge-ui/server.js index e2ac639..e62f869 100644 --- a/packages/homebridge-plugin/homebridge-ui/server.js +++ b/packages/homebridge-plugin/homebridge-ui/server.js @@ -45,8 +45,8 @@ class DibbyWemoUiServer extends HomebridgePluginUiServer { this.onRequest('/devices/discover', async ({ timeout } = {}) => { const ms = typeof timeout === 'number' ? timeout : 10_000; const devices = await wemoClient.discoverDevices(ms); - // Persist updated list - this._store.saveDevices(devices.map((d) => ({ + // Merge into cached list — previously known devices stay even if not found this scan + this._store.mergeDevices(devices.map((d) => ({ host: d.host, port: d.port, udn: d.udn ?? `${d.host}:${d.port}`, @@ -54,7 +54,8 @@ class DibbyWemoUiServer extends HomebridgePluginUiServer { productModel: d.productModel ?? 'Wemo Device', 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 }) => { diff --git a/packages/homebridge-plugin/lib/platform.js b/packages/homebridge-plugin/lib/platform.js index 89bbacd..aa9c37d 100644 --- a/packages/homebridge-plugin/lib/platform.js +++ b/packages/homebridge-plugin/lib/platform.js @@ -114,25 +114,36 @@ class WemoPlatform { this.log.info(`Found ${discovered.length} Wemo device(s)`); - // Save discovered device list for the custom UI - this._store.saveDevices(discovered.map((d) => ({ + // Merge discovered devices into the cached list — keep offline devices too + const freshForStore = discovered.map((d) => ({ host: d.host, port: d.port, udn: d.udn ?? `${d.host}:${d.port}`, friendlyName: d.friendlyName ?? d.host, productModel: d.productModel ?? 'Wemo Device', firmwareVersion: d.firmwareVersion ?? null, - }))); + })); + const allKnown = this._store.mergeDevices(freshForStore); + // Register newly discovered devices in HomeKit for (const device of discovered) { this._registerDevice(device, pollInterval); } - // Remove stale accessories (devices no longer discovered) - const activeUUIDs = new Set(discovered.map((d) => this._uuidForDevice(d))); + // Register previously cached devices that weren't discovered (may be offline) + // 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) { - if (!activeUUIDs.has(uuid)) { - this.log.info('Removing stale accessory: ' + acc.displayName); + if (!acc.context?.device?.host) { + this.log.info('Removing orphaned accessory (no device context): ' + acc.displayName); this._handlers.get(uuid)?.stopPolling(); this._handlers.delete(uuid); this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [acc]); diff --git a/packages/homebridge-plugin/lib/store.js b/packages/homebridge-plugin/lib/store.js index 7e06348..4073116 100644 --- a/packages/homebridge-plugin/lib/store.js +++ b/packages/homebridge-plugin/lib/store.js @@ -56,6 +56,33 @@ class DwmStore { getDeviceGroups() { return this._load().deviceGroups ?? []; } 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 ───────────────────────────────────────────────── getDisabledRules() { return this._load().disabledRules ?? {}; }