UNPKG

homebridge-pura

Version:

Homebridge plugin for Pura smart fragrance diffusers

206 lines 8.29 kB
import { PuraPlatformAccessory } from './platformAccessory.js'; import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js'; import { PuraApi } from './puraApi.js'; /** * PuraPlatform * This class is the main constructor for the Pura plugin, this is where we * authenticate with Pura and discover/register accessories with Homebridge. */ export class PuraPlatform { log; config; api; Service; Characteristic; // this is used to track restored cached accessories accessories = new Map(); discoveredCacheUUIDs = []; puraApi; puraConfig; refreshInterval = null; constructor(log, config, api) { this.log = log; this.config = config; this.api = api; this.Service = api.hap.Service; this.Characteristic = api.hap.Characteristic; // Validate configuration if (!config.username || !config.password) { this.log.error('Username and password are required in the config'); throw new Error('Username and password are required in the config'); } this.puraConfig = config; this.puraApi = new PuraApi(this.log); this.log.debug('Finished initializing platform:', this.config.name); // When this event is fired it means Homebridge has restored all cached accessories from disk. // Dynamic Platform plugins should only register new accessories after this event was fired, // in order to ensure they weren't added to homebridge already. This event can also be used // to start discovery of new accessories. this.api.on('didFinishLaunching', () => { log.debug('Executed didFinishLaunching callback'); // run the method to discover / register your devices as accessories this.discoverDevices(); }); // Clean up on shutdown this.api.on('shutdown', () => { if (this.refreshInterval) { clearInterval(this.refreshInterval); } }); } /** * This function is invoked when homebridge restores cached accessories from disk at startup. * It should be used to set up event handlers for characteristics and update respective values. */ configureAccessory(accessory) { this.log.info('Loading accessory from cache:', accessory.displayName); // add the restored accessory to the accessories cache, so we can track if it has already been registered this.accessories.set(accessory.UUID, accessory); } /** * Discover and register Pura devices */ async discoverDevices() { try { // Authenticate with Pura this.log.info('Authenticating with Pura...'); await this.puraApi.authenticate(this.puraConfig.username, this.puraConfig.password); this.log.info('Pura authentication successful'); // Get devices this.log.info('Discovering Pura devices...'); const devices = await this.puraApi.getDevices(); this.log.info(`Found ${devices.length} Pura device(s)`); // Register each device for (const device of devices) { await this.registerDevice(device); } // Remove accessories that are no longer present this.removeStaleAccessories(); // Set up refresh interval this.setupRefreshInterval(); } catch (error) { this.log.error('Failed to discover Pura devices:', error); } } /** * Register a Pura device as a HomeKit accessory */ async registerDevice(device) { this.log.debug('Registering device:', device.name, device.id); // Create accessory for each bay that exists if (device.bay1) { await this.registerBayAccessory(device, 1); } if (device.bay2) { await this.registerBayAccessory(device, 2); } } /** * Register a bay as a separate accessory */ async registerBayAccessory(device, bayNumber) { const bay = bayNumber === 1 ? device.bay1 : device.bay2; if (!bay) { return; } const accessoryName = `${device.name} Bay ${bayNumber}`; const uniqueId = `${device.id}-bay${bayNumber}`; const uuid = this.api.hap.uuid.generate(uniqueId); // Check if accessory already exists const existingAccessory = this.accessories.get(uuid); if (existingAccessory) { // Update existing accessory this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName); existingAccessory.context.device = device; existingAccessory.context.bayNumber = bayNumber; this.api.updatePlatformAccessories([existingAccessory]); // Create the accessory handler new PuraPlatformAccessory(this, existingAccessory, this.puraApi); } else { // Create new accessory this.log.info('Adding new accessory:', accessoryName); const accessory = new this.api.platformAccessory(accessoryName, uuid); // Store device info in context accessory.context.device = device; accessory.context.bayNumber = bayNumber; // Create the accessory handler new PuraPlatformAccessory(this, accessory, this.puraApi); // Register the accessory this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]); } // Track this UUID as discovered this.discoveredCacheUUIDs.push(uuid); } /** * Remove accessories that are no longer present */ removeStaleAccessories() { 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]); } } } /** * Set up periodic refresh of device status */ setupRefreshInterval() { const interval = (this.puraConfig.refreshInterval || 300) * 1000; // Convert to milliseconds this.log.debug(`Setting up refresh interval: ${interval / 1000} seconds`); this.refreshInterval = setInterval(async () => { try { this.log.debug('Refreshing device status...'); await this.refreshDeviceStatus(); } catch (error) { this.log.error('Failed to refresh device status:', error); } }, interval); } /** * Refresh status of all devices */ async refreshDeviceStatus() { try { const devices = await this.puraApi.getDevices(); for (const device of devices) { // Update accessories for each bay if (device.bay1) { await this.updateBayAccessory(device, 1); } if (device.bay2) { await this.updateBayAccessory(device, 2); } } } catch (error) { this.log.debug('Device status refresh failed:', error); // Try to refresh tokens if authentication failed if (error instanceof Error && error.message.includes('authentication')) { try { await this.puraApi.refreshToken(); this.log.debug('Token refresh successful'); } catch (refreshError) { this.log.error('Token refresh failed:', refreshError); } } } } /** * Update a bay accessory with fresh device data */ async updateBayAccessory(device, bayNumber) { const uniqueId = `${device.id}-bay${bayNumber}`; const uuid = this.api.hap.uuid.generate(uniqueId); const accessory = this.accessories.get(uuid); if (accessory) { accessory.context.device = device; this.api.updatePlatformAccessories([accessory]); } } } //# sourceMappingURL=platform.js.map