UNPKG

homebridge-tsvesync

Version:

Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets

267 lines (224 loc) 7.75 kB
import { CharacteristicValue, PlatformAccessory } from 'homebridge'; import { BaseAccessory } from './base.accessory'; import { TSVESyncPlatform } from '../platform'; import { DeviceCapabilities, VeSyncBulb } from '../types/device.types'; // Constants for color temperature const MIN_COLOR_TEMP = 140; // 7143K (cool) const MAX_COLOR_TEMP = 500; // 2000K (warm) const DEFAULT_COLOR_TEMP = MIN_COLOR_TEMP; export class LightAccessory extends BaseAccessory { protected readonly device: VeSyncBulb; private readonly capabilities: DeviceCapabilities; constructor( platform: TSVESyncPlatform, accessory: PlatformAccessory, device: VeSyncBulb ) { super(platform, accessory, device); this.device = device; this.capabilities = this.getDeviceCapabilities(); } protected setupService(): void { // Get or create the light service this.service = this.accessory.getService(this.platform.Service.Lightbulb) || this.accessory.addService(this.platform.Service.Lightbulb); // Set up required characteristics this.setupCharacteristic( this.platform.Characteristic.On, this.getOn.bind(this), this.setOn.bind(this) ); // Set up optional characteristics based on device capabilities const capabilities = this.getDeviceCapabilities(); if (capabilities.hasBrightness) { this.setupCharacteristic( this.platform.Characteristic.Brightness, this.getBrightness.bind(this), this.setBrightness.bind(this) ); } if (capabilities.hasColorTemp) { this.setupColorTemperature(); } if (capabilities.hasColor) { this.setupColor(); } // Add Name characteristic this.setupCharacteristic( this.platform.Characteristic.Name, async () => this.device.deviceName ); } private setupColorTemperature(): void { this.service.getCharacteristic(this.platform.Characteristic.ColorTemperature) .setProps({ minValue: MIN_COLOR_TEMP, maxValue: MAX_COLOR_TEMP, }); this.setupCharacteristic( this.platform.Characteristic.ColorTemperature, this.getColorTemperature.bind(this), this.setColorTemperature.bind(this) ); } private setupColor(): void { this.setupCharacteristic( this.platform.Characteristic.Hue, this.getHue.bind(this), this.setHue.bind(this) ); this.setupCharacteristic( this.platform.Characteristic.Saturation, this.getSaturation.bind(this), this.setSaturation.bind(this) ); } /** * Update device states based on the latest details */ protected async updateDeviceSpecificStates(details: any): Promise<void> { const lightDetails = details as { deviceStatus: string; brightness?: number; colorTemp?: number; hue?: number; saturation?: number; }; // Update active state const isActive = lightDetails.deviceStatus === 'on'; this.updateCharacteristicValue( this.platform.Characteristic.On, isActive ); // Update brightness if supported if (this.capabilities.hasBrightness && lightDetails.brightness !== undefined) { this.updateCharacteristicValue( this.platform.Characteristic.Brightness, lightDetails.brightness ); } // Update color temperature if supported if (this.capabilities.hasColorTemp && lightDetails.colorTemp !== undefined) { this.updateCharacteristicValue( this.platform.Characteristic.ColorTemperature, lightDetails.colorTemp ); } // Update color if supported if (this.capabilities.hasColor) { if (lightDetails.hue !== undefined) { this.updateCharacteristicValue( this.platform.Characteristic.Hue, lightDetails.hue ); } if (lightDetails.saturation !== undefined) { this.updateCharacteristicValue( this.platform.Characteristic.Saturation, lightDetails.saturation ); } } } protected getDeviceCapabilities(): DeviceCapabilities { // Determine capabilities based on model const model = this.device.deviceType.toUpperCase(); return { hasBrightness: true, // All VeSync bulbs support brightness hasColorTemp: model.includes('CW') || model.includes('MC'), // CW and MC models support color temperature hasColor: model.includes('MC') || model === 'XYD0001', // MC models and XYD0001 support color hasSpeed: false, hasHumidity: false, hasAirQuality: false, hasWaterLevel: false, hasChildLock: false, hasSwingMode: false, }; } private async getOn(): Promise<CharacteristicValue> { return this.device.deviceStatus === 'on'; } private async setOn(value: CharacteristicValue): Promise<void> { try { const isOn = value as boolean; const success = isOn ? await this.device.turnOn() : await this.device.turnOff(); if (!success) { throw new Error(`Failed to turn ${isOn ? 'on' : 'off'} device`); } await this.persistDeviceState('deviceStatus', isOn ? 'on' : 'off'); } catch (error) { this.handleDeviceError('set on state', error); } } private async getBrightness(): Promise<CharacteristicValue> { return this.device.brightness; } private async setBrightness(value: CharacteristicValue): Promise<void> { try { const success = await this.device.setBrightness(value as number); if (!success) { throw new Error(`Failed to set brightness to ${value}`); } await this.persistDeviceState('brightness', value); } catch (error) { this.handleDeviceError('set brightness', error); } } private async getColorTemperature(): Promise<CharacteristicValue> { return this.device.colorTemp || DEFAULT_COLOR_TEMP; } private async setColorTemperature(value: CharacteristicValue): Promise<void> { try { if (!this.device.setColorTemperature) { throw new Error('Device does not support color temperature'); } const success = await this.device.setColorTemperature(value as number); if (!success) { throw new Error(`Failed to set color temperature to ${value}`); } await this.persistDeviceState('colorTemp', value); } catch (error) { this.handleDeviceError('set color temperature', error); } } private async getHue(): Promise<CharacteristicValue> { return this.device.hue || 0; } private async getSaturation(): Promise<CharacteristicValue> { return this.device.saturation || 0; } private async setHue(value: CharacteristicValue): Promise<void> { try { if (!this.device.setColor) { throw new Error('Device does not support color'); } const success = await this.device.setColor( value as number, this.device.saturation || 0 ); if (!success) { throw new Error(`Failed to set hue to ${value}`); } await this.persistDeviceState('hue', value); } catch (error) { this.handleDeviceError('set hue', error); } } private async setSaturation(value: CharacteristicValue): Promise<void> { try { if (!this.device.setColor) { throw new Error('Device does not support color'); } const success = await this.device.setColor( this.device.hue || 0, value as number ); if (!success) { throw new Error(`Failed to set saturation to ${value}`); } await this.persistDeviceState('saturation', value); } catch (error) { this.handleDeviceError('set saturation', error); } } }