UNPKG

homebridge-vesync-v2

Version:

A Homebridge plugin for controlling VeSync smart devices including outlets, air purifiers, and humidifiers

216 lines (177 loc) 8.19 kB
"use strict"; const EtekcityClient = require('./EtekCityClient'); const DeviceFactory = require('./DeviceFactory'); class VeseyncPlugPlatform { constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.accessories = {}; this.cache_timeout = 10; // seconds this.debug = config['debug'] || false; this.username = config['username']; this.password = config['password']; this.exclude = config['exclude']?.split(',') || []; this.client = new EtekcityClient(log, this.exclude); // Set up HAP references here so they're available immediately if (this.api) { this.Accessory = this.api.platformAccessory; this.Service = this.api.hap.Service; this.Characteristic = this.api.hap.Characteristic; this.UUIDGen = this.api.hap.uuid; this.api.on('didFinishLaunching', () => { this.deviceDiscovery(); setInterval(() => this.deviceDiscovery(), this.cache_timeout * 6000); }); } } configureAccessory(accessory) { const accessoryId = accessory.context?.id; if (!accessoryId) { // If we don't have an ID, this accessory is invalid. Remove it. this.log("Cached accessory has no ID, removing:", accessory.displayName); this.removeAccessory(accessory); return; } if (this.debug) this.log("Configuring accessory: " + accessoryId); // Handle duplicate accessory case if (this.accessories[accessoryId]) { this.log("Duplicate accessory detected, removing existing one"); try { this.removeAccessory(this.accessories[accessoryId]); this.setupDevice(accessory); } catch (error) { this.removeAccessory(accessory); // fallback to old accessory accessory = this.accessories[accessoryId]; } } else { this.setupDevice(accessory); } this.accessories[accessoryId] = accessory; } removeAccessory(accessory, accessoryId = undefined) { if (!accessory) return; const id = accessoryId ?? accessory.context?.id; if (this.debug) this.log("Removing accessory: " + id); try { this.api.unregisterPlatformAccessories("homebridge-vesync-v2", "VesyncPlug", [accessory]); } catch (error) { this.log("Error removing accessory: " + error); } if (id) { delete this.accessories[id]; } } addAccessory(data) { this.log("Starting addAccessory for:", data.name, "ID:", data.id, "Type:", data.type); if (!this.accessories[data.id]) { this.log("Creating new accessory for:", data.name); const uuid = this.UUIDGen.generate(data.id); const newAccessory = new this.Accessory(data.name, uuid); this.log("Created new accessory with UUID:", uuid); this.log("Setting context for new accessory"); newAccessory.context = { name: data.name, id: data.id, type: data.type, cid: data.cid, region: data.region, configModule: data.configModule }; this.log("Context set:", JSON.stringify(newAccessory.context)); this.log("Setting up device with new accessory"); this.setupDevice(newAccessory); this.log("Registering platform accessory"); this.api.registerPlatformAccessories("homebridge-vesync-v2", "VesyncPlug", [newAccessory]); } this.log("Getting accessory from accessories map, ID:", data.id); const accessory = this.accessories[data.id]; this.log("Got accessory:", accessory ? "yes" : "no", "Context:", accessory?.context ? "yes" : "no"); this.log("Initializing accessory info"); this.initAccessoryInfo(accessory, data); this.log("Setting accessory in accessories map"); this.accessories[data.id] = accessory; } initAccessoryInfo(accessory, data) { this.log("Initializing accessory info for:", accessory?.context?.name); this.log("Accessory state:", { hasAccessory: !!accessory, hasContext: !!accessory?.context, hasService: !!this.Service, contextData: accessory?.context }); let info = accessory.getService(this.Service.AccessoryInformation); this.log("Got AccessoryInformation service"); accessory.context.manufacturer = "Etekcity"; info.setCharacteristic(this.Characteristic.Manufacturer, accessory.context.manufacturer); accessory.context.model = "ESW01-USA"; info.setCharacteristic(this.Characteristic.Model, accessory.context.model); info.setCharacteristic(this.Characteristic.SerialNumber, accessory.context.id); this.log("Getting initial service for:", accessory.context?.name); const service = accessory.getService(this.Service.Outlet) || accessory.getService(this.Service.Fan) || accessory.getService(this.Service.AirPurifier) || accessory.getService(this.Service.HumidifierDehumidifier) || accessory.getService(this.Service.Lightbulb); if (service && service.getCharacteristic(this.Characteristic.On)) { this.log("Getting initial On value for:", accessory.context?.name); service.getCharacteristic(this.Characteristic.On).getValue(); } } setupDevice(accessory) { this.log("SETUP: name=" + accessory?.context?.name + " type=" + accessory?.context?.type); this.log("PLATFORM-SERVICES-ALL: " + (this.Service ? Object.keys(this.Service).join(',') : 'none')); const deviceInstance = DeviceFactory.createDevice( accessory, this.client, this.log, this.debug, this.Service, this.Characteristic ); this.log("DEVICE: Created, configuring..."); deviceInstance.configureService(); } deviceDiscovery() { let me = this; if (me.debug) me.log("DeviceDiscovery invoked"); this.client.login(this.username, this.password).then(() => { return this.client.getDevices(); }).then(devices => { if (me.debug) me.log("Adding discovered devices"); for (let i in devices) { let existing = me.accessories[devices[i].id]; if (!existing) { me.log("Adding device:", devices[i].id, devices[i].name); me.addAccessory(devices[i]); } else { if (me.debug) me.log("Skipping existing device", i); } } if (devices) { for (let index in me.accessories) { var acc = me.accessories[index]; var found = devices.find((device) => device.id == index); if (!found) { me.log("Previously configured accessory not found, removing", index); me.removeAccessory(me.accessories[index]); } else if (found.name != acc.context.name) { me.log("Accessory name does not match device name, got " + found.name + " expected " + acc.context.name); me.removeAccessory(me.accessories[index]); me.addAccessory(found); me.log("Accessory removed & readded!"); } } } if (me.debug) me.log("Discovery complete"); }).catch((err) => { me.log("ERROR: " + err); }); } identify(thisPlug, paired, callback) { this.log("Identify requested for " + thisPlug.name); callback(); } } module.exports = VeseyncPlugPlatform;