UNPKG

@switchbot/homebridge-switchbot

Version:

The SwitchBot plugin allows you to access your SwitchBot device(s) from HomeKit.

308 lines • 12.7 kB
// Fetch the list of configured devices from the Homebridge UI API import { isValidDeviceType, normalizeDeviceType } from '../../../device-types.js'; import './types.js'; import { uiLog } from './logger.js'; import { toastError } from './toast.js'; export async function fetchDevices() { try { if (typeof homebridge.getPluginConfig !== 'function') { throw new TypeError('Homebridge UI API not available'); } const configArr = await homebridge.getPluginConfig(); const config = Array.isArray(configArr) && configArr.length > 0 ? configArr.find(isSwitchBotPlatformConfig) : null; if (!config || !Array.isArray(config.devices)) { return []; } return config.devices; } catch (e) { const msg = e instanceof Error ? e.message : String(e); uiLog.error('Error fetching devices:', msg); return []; } } /** * Validate and auto-correct device types in the config array before saving. * Returns an array of errors for devices that cannot be fixed. */ export function validateAndFixDeviceTypes(devices) { const errors = []; for (const d of devices) { if (!isValidDeviceType(d.configDeviceType)) { const fixed = normalizeDeviceType(d.configDeviceType); if (fixed) { d.configDeviceType = fixed; } else { errors.push({ deviceId: d.deviceId, name: d.configDeviceName, type: d.configDeviceType, }); } } } return errors; } // API wrapper functions for communicating with the Homebridge UI server function isSwitchBotPlatformConfig(block) { const platformName = String(block?.platform || block?.name || '').toLowerCase(); return (platformName === 'switchbot' || platformName === '@switchbot/homebridge-switchbot' || platformName.includes('switchbot')); } export async function syncParentPluginConfigFromDisk(autoSave = false) { try { if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') { uiLog.warn('Parent config sync API not available'); return false; } // Use Homebridge UI API to fetch and update config const pluginConfigBlocks = await homebridge.getPluginConfig(); if (!Array.isArray(pluginConfigBlocks) || !pluginConfigBlocks.length) { uiLog.warn('No plugin config blocks returned from Homebridge'); return false; } const index = pluginConfigBlocks.findIndex(block => isSwitchBotPlatformConfig(block)); if (index < 0) { uiLog.warn('SwitchBot platform block not found in Homebridge plugin config'); return false; } // Validate and fix device types before saving const errors = validateAndFixDeviceTypes(pluginConfigBlocks[index].devices || []); if (errors.length > 0) { toastError(`Invalid device types found: ${errors.map(e => `${e.name} (${e.type})`).join(', ')}`); return false; } // pluginConfigBlocks[index] is already up to date await homebridge.updatePluginConfig(pluginConfigBlocks); // Auto-save to disk if requested - prevents parent UI from overwriting with stale cache if (autoSave && typeof homebridge.savePluginConfig === 'function') { uiLog.info('Auto-saving config to disk...'); await homebridge.savePluginConfig(); uiLog.info('Config saved successfully'); } return true; } catch (e) { uiLog.warn('Failed to sync parent plugin config cache:', e); return false; } } export async function fetchCredentialStatus() { try { const resp = await homebridge.request('/credentials', {}); uiLog.info('Load credentials response:', resp); if (!resp || resp.success === false) { uiLog.error('Failed to load credentials:', resp); return null; } return resp.data || {}; } catch (e) { uiLog.error('Error loading credentials:', e); return null; } } export async function saveCredentials(token, secret) { uiLog.info('Saving credentials...'); const resp = await homebridge.request('/credentials', { token, secret }); uiLog.info('Save response:', resp); if (!resp || resp.success === false) { throw new Error(resp?.message || 'Save failed'); } return resp.data || resp; } export async function discoverDevices(mode = 'all', options) { const resp = await homebridge.request('/discover', { mode, ...options }); uiLog.info('Discover response:', resp); if (!resp || resp.success === false) { throw new Error(resp?.data?.message || 'Discovery failed'); } return resp.data || []; } export async function fetchBluetoothStatus() { try { const resp = await homebridge.request('/ble-status', {}); if (!resp || resp.success === false) { return { available: false, message: 'Bluetooth status unavailable' }; } return resp.data || { available: false, message: 'Bluetooth status unavailable' }; } catch (_e) { return { available: false, message: 'Bluetooth status unavailable' }; } } export async function testDeviceConnection(payload) { const resp = await homebridge.request('/test-connection', payload); if (!resp || resp.success === false) { throw new Error(resp?.data?.message || 'Connection test failed'); } return resp.data || { success: false, deviceId: payload.deviceId, method: 'Auto', latencyMs: 0, message: 'Connection test failed', }; } export async function addDevice(deviceId, name, type, options) { if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') { throw new TypeError('Homebridge UI API not available'); } const configArr = await homebridge.getPluginConfig(); const idx = Array.isArray(configArr) ? configArr.findIndex(isSwitchBotPlatformConfig) : -1; if (idx === -1) { throw new Error('SwitchBot config not found'); } const config = configArr[idx]; if (!Array.isArray(config.devices)) { config.devices = []; } const normalizedDeviceId = String(deviceId).trim().toLowerCase(); const exists = config.devices.some((d) => String(d.deviceId ?? d.id ?? '').trim().toLowerCase() === normalizedDeviceId); if (exists) { return { alreadyExists: true, message: 'Device already in config' }; } const newDevice = { deviceId, configDeviceName: name, configDeviceType: type }; if (options?.address) { newDevice.address = options.address; } if (options?.model) { newDevice.model = options.model; } if (options?.rssi !== undefined && options?.rssi !== null && options?.rssi !== 0) { newDevice.rssi = options.rssi; } if (options?.encryptionKey) { newDevice.encryptionKey = options.encryptionKey; } if (options?.keyId) { newDevice.keyId = options.keyId; } config.devices.push(newDevice); await homebridge.updatePluginConfig(configArr); if (typeof homebridge.savePluginConfig === 'function') { await homebridge.savePluginConfig(); } return { added: true, message: `Device "${name}" added successfully` }; } export async function addDevicesInBulk(devices) { const resp = await homebridge.request('/add-devices', { devices }); uiLog.info('Bulk add response:', resp); if (!resp || resp.success === false) { throw new Error(resp?.data?.message || 'Bulk add failed'); } return normalizeBulkAddDevicesResponse(resp); } export function normalizeBulkAddDevicesResponse(resp) { const payload = resp?.data && typeof resp.data === 'object' ? resp.data : resp; const addedCount = Number(payload?.addedCount ?? payload?.added ?? 0); const skippedCount = Number(payload?.skippedCount ?? payload?.skipped ?? 0); const updatedCount = Number(payload?.updatedCount ?? payload?.updated ?? 0); return { ...payload, success: resp?.success ?? true, addedCount, skippedCount, updatedCount, }; } export async function updateDevice(deviceId, configDeviceName, configDeviceType, options) { if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') { throw new TypeError('Homebridge UI API not available'); } const configArr = await homebridge.getPluginConfig(); const idx = Array.isArray(configArr) ? configArr.findIndex(isSwitchBotPlatformConfig) : -1; if (idx === -1) { throw new Error('SwitchBot config not found'); } const config = configArr[idx]; if (!Array.isArray(config.devices)) { throw new TypeError('No devices array in config'); } const normalizedDeviceId = String(deviceId).trim().toLowerCase(); const device = config.devices.find((d) => String(d.deviceId ?? d.id ?? '').trim().toLowerCase() === normalizedDeviceId); if (!device) { throw new Error('Device not found in config'); } if (configDeviceName) { device.configDeviceName = configDeviceName; } if (configDeviceType) { device.configDeviceType = configDeviceType; } if (options) { Object.assign(device, options); } await homebridge.updatePluginConfig(configArr); if (typeof homebridge.savePluginConfig === 'function') { await homebridge.savePluginConfig(); } return { updated: true, message: `Device updated successfully` }; } export async function deleteDevice(deviceId) { if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') { throw new TypeError('Homebridge UI API not available'); } const configArr = await homebridge.getPluginConfig(); const idx = Array.isArray(configArr) ? configArr.findIndex(isSwitchBotPlatformConfig) : -1; if (idx === -1) { throw new Error('SwitchBot config not found'); } const config = configArr[idx]; if (!Array.isArray(config.devices)) { throw new TypeError('No devices array in config'); } const normalizedDeviceId = String(deviceId).trim().toLowerCase(); const before = config.devices.length; // Remove the target device config.devices = config.devices.filter(d => String(d.deviceId ?? d.id ?? '').trim().toLowerCase() !== normalizedDeviceId); // Defensive: filter out any invalid device entries (missing required fields) config.devices = config.devices.filter(d => d && typeof d === 'object' && d.deviceId && d.configDeviceType); if (config.devices.length === before) { throw new Error('Device not found in config'); } await homebridge.updatePluginConfig(configArr); if (typeof homebridge.savePluginConfig === 'function') { await homebridge.savePluginConfig(); } return { deleted: true, message: `Device removed from config` }; } export async function deleteAllDevices() { if (typeof homebridge.getPluginConfig !== 'function' || typeof homebridge.updatePluginConfig !== 'function') { throw new TypeError('Homebridge UI API not available'); } const configArr = await homebridge.getPluginConfig(); // Find or create the SwitchBot config block let idx = Array.isArray(configArr) ? configArr.findIndex(isSwitchBotPlatformConfig) : -1; if (idx === -1) { // If not found, create a new config block for SwitchBot const newBlock = { platform: 'SwitchBot', devices: [] }; configArr.push(newBlock); idx = configArr.length - 1; } const config = configArr[idx]; // Always ensure devices is an array if (!Array.isArray(config.devices)) { config.devices = []; } const deletedCount = config.devices.length; config.devices = []; // Defensive: ensure required fields for schema compliance if (!config.platform) { config.platform = 'SwitchBot'; } // Ensure 'name' property is present (required by schema for Homebridge platform blocks) if (!config.name) { config.name = 'SwitchBot'; } // Save updated config await homebridge.updatePluginConfig(configArr); if (typeof homebridge.savePluginConfig === 'function') { await homebridge.savePluginConfig(); } return { deleted: true, deletedCount, message: `Removed ${deletedCount} device(s) from config` }; } //# sourceMappingURL=api.js.map