homebridge-tsvesync
Version:
Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets
267 lines (224 loc) • 7.75 kB
text/typescript
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);
}
}
}