UNPKG

@viguza/homebridge-ezviz

Version:

A short description about what your plugin does.

222 lines 9.09 kB
import { SmartPlug } from './accessories/smart-plug.js'; import { IPCamera } from './accessories/ip-camera.js'; import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; import { EZVIZAPI } from './api/ezviz-api.js'; import { DeviceTypes } from './utils/enums.js'; /** * EZVIZ Platform for Homebridge * Handles device discovery, authentication, and accessory management */ export class EZVIZPlatform { log; config; api; Service; Characteristic; // this is used to track restored cached accessories accessories = new Map(); discoveredCacheUUIDs = []; constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.Service = api.hap.Service; this.Characteristic = api.hap.Characteristic; this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this)); this.log.debug('Finished initializing platform:', this.config.name); } /** * Called when Homebridge finishes launching * Handles authentication and device discovery */ async didFinishLaunching() { try { const ezvizAPI = new EZVIZAPI(this.config, this.log); const credentials = await this.authenticate(ezvizAPI); if (credentials) { // Set up re-authentication every 12 hours setInterval(async () => { this.log.debug('Reauthenticating to EZVIZ API'); try { await this.authenticate(ezvizAPI); } catch (error) { this.log.error('Re-authentication failed:', error); } }, 3600000 * 12); await this.discoverDevices(ezvizAPI); } else { this.log.error('Could not authenticate with EZVIZ API. Please check your credentials.'); } } catch (error) { this.log.error('Error during platform initialization:', error); } this.log.debug('Executed didFinishLaunching callback'); } /** * Authenticates with the EZVIZ API * @param ezvizAPI - The EZVIZ API instance * @returns Promise resolving to credentials or undefined if authentication fails */ async authenticate(ezvizAPI) { const region = this.config.region; const email = this.config.email; const password = this.config.password; if (!email || !password) { this.log.error('You must provide your email and password in config.json.'); return; } if (!region) { this.log.error('You must provide your region in config.json.'); return; } try { this.config.domain = await ezvizAPI.getDomain(region); const credentials = await ezvizAPI.authenticate(); if (credentials) { this.log.info('Successfully authenticated with EZVIZ API'); } return credentials; } catch (error) { this.log.error('Authentication failed:', error); return; } } /** * Configures an accessory from cache * @param accessory - The accessory to configure */ configureAccessory(accessory) { this.log.info('Loading accessory from cache:', accessory.displayName); this.accessories.set(accessory.UUID, accessory); } /** * Discovers and manages EZVIZ devices * @param ezvizAPI - The EZVIZ API instance */ async discoverDevices(ezvizAPI) { try { const devicesResponse = await ezvizAPI.listDevices(); if (!devicesResponse) { this.log.error('No devices found or failed to retrieve device list'); return; } const devices = this.extractDevicesData(devicesResponse); this.log.info(`Found ${devices.length} devices`); for (const device of devices) { const existingAccessory = this.accessories.get(device.UUID); if (existingAccessory) { this.log.debug(`Restoring existing ${device.Type} from cache: ${existingAccessory.displayName}`); existingAccessory.context.device = device; this.createAccessory(ezvizAPI, existingAccessory, device.Type); } else { this.log.info(`Adding new ${device.Type}: ${device.Name}`); const accessory = new this.api.platformAccessory(device.Name, device.UUID); accessory.context.device = device; this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); this.createAccessory(ezvizAPI, accessory, device.Type); } this.discoveredCacheUUIDs.push(device.UUID); } // Remove accessories that are no longer available for (const [uuid, accessory] of this.accessories) { if (!this.discoveredCacheUUIDs.includes(uuid)) { this.log.info('Removing existing accessory from cache:', accessory.displayName); this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); } } } catch (error) { this.log.error('Error discovering devices:', error); } } /** * Creates the appropriate accessory based on device type * @param ezvizAPI - The EZVIZ API instance * @param accessory - The platform accessory * @param deviceType - The type of device */ createAccessory(ezvizAPI, accessory, deviceType) { try { if (deviceType === DeviceTypes.Socket) { new SmartPlug(ezvizAPI, this, accessory); } else if (deviceType === DeviceTypes.IPC || deviceType === DeviceTypes.CatEye) { new IPCamera(ezvizAPI, this, accessory); } else { this.log.warn(`Unsupported device type: ${deviceType}`); } } catch (error) { this.log.error(`Error creating accessory for ${accessory.displayName}:`, error); } } /** * Extracts device data from the API response * @param devicesResponse - The API response containing device information * @returns Array of processed device data */ extractDevicesData(devicesResponse) { const devices = []; for (const device of devicesResponse.deviceInfos) { const uuid = this.api.hap.uuid.generate(device.deviceSerial); const deviceType = DeviceTypes[device.deviceCategory]; if (!deviceType) { this.log.error(`Device ${device.name} has an unsupported type ${device.deviceCategory} and will be skipped`); continue; } let deviceConfig; if (deviceType === DeviceTypes.Socket) { deviceConfig = this.config.plugs?.find((plug) => plug.serial === device.deviceSerial); } else if (deviceType === DeviceTypes.IPC || deviceType === DeviceTypes.CatEye) { deviceConfig = this.config.cameras?.find((camera) => camera.serial === device.deviceSerial); if (!deviceConfig) { this.log.info(`Camera ${device.name} (${device.deviceSerial}) is not configured and will be skipped`); continue; } const error = this.cameraConfigErrors(deviceConfig); if (error) { this.log.info(`Device ${device.name} (${device.deviceSerial}) is not configured correctly and will be skipped: ${error}`); continue; } } const data = { UUID: uuid, Serial: device.deviceSerial, Name: device.name, Type: deviceType, Connection: devicesResponse.CONNECTION[device.deviceSerial], Status: devicesResponse.STATUS[device.deviceSerial], Switches: devicesResponse.SWITCH[device.deviceSerial], P2P: devicesResponse.P2P[device.deviceSerial], ResourceInfo: devicesResponse.resourceInfos.find((resource) => resource.deviceSerial === device.deviceSerial), DeviceInfo: device, HBConfig: deviceConfig, }; devices.push(data); } ; return devices; } /** * Validates camera configuration * @param camera - The camera configuration to validate * @returns Error message if validation fails, empty string if valid */ cameraConfigErrors(camera) { if (!camera.username) { return 'No Username'; } if (!camera.code) { return 'No Verification Code'; } return ''; } } //# sourceMappingURL=platform.js.map