homebridge-tsvesync
Version:
Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets
253 lines • 10.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FanAccessory = void 0;
const base_accessory_1 = require("./base.accessory");
// Constants for fan direction
const CLOCKWISE = 1;
const COUNTER_CLOCKWISE = 0;
// Constants for fan modes
const MODE_NORMAL = 0;
const MODE_AUTO = 1;
const MODE_SLEEP = 2;
const MODE_TURBO = 3;
// Fan speed levels for different device types
const FAN_SPEED_LEVELS = {
'LTF-F422S-KEU': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
'LTF-F422S-WUSR': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
'LTF-F422_WJP': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
'LTF-F422S-WUS': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
'Core200S': [1, 2, 3],
'Core300S': [1, 2, 3, 4],
'Core400S': [1, 2, 3, 4],
'Core600S': [1, 2, 3, 4],
'LAP-C201S-AUSR': [1, 2, 3],
'LAP-C202S-WUSR': [1, 2, 3],
'LAP-C301S-WJP': [1, 2, 3, 4],
'LAP-C302S-WUSB': [1, 2, 3, 4],
'LAP-C301S-WAAA': [1, 2, 3, 4],
'LAP-C401S-WJP': [1, 2, 3, 4],
'LAP-C401S-WUSR': [1, 2, 3, 4],
'LAP-C401S-WAAA': [1, 2, 3, 4],
'LAP-C601S-WUS': [1, 2, 3, 4],
'LAP-C601S-WUSR': [1, 2, 3, 4],
'LAP-C601S-WEU': [1, 2, 3, 4],
'LV-PUR131S': [1, 2, 3],
'LV-RH131S': [1, 2, 3]
};
class FanAccessory extends base_accessory_1.BaseAccessory {
constructor(platform, accessory, device) {
super(platform, accessory, device);
this.device = device;
this.capabilities = this.getDeviceCapabilities();
this.speedLevels = FAN_SPEED_LEVELS[this.device.deviceType] || [1, 2, 3, 4];
}
setupService() {
// Get or create the fan service
this.service = this.accessory.getService(this.platform.Service.Fanv2) ||
this.accessory.addService(this.platform.Service.Fanv2);
// Set up required characteristics
this.setupCharacteristic(this.platform.Characteristic.Active, this.getActive.bind(this), this.setActive.bind(this));
// Set up speed control
this.setupCharacteristic(this.platform.Characteristic.RotationSpeed, this.getRotationSpeed.bind(this), this.handleSetRotationSpeed.bind(this));
// Set up mode control
this.setupCharacteristic(this.platform.Characteristic.SwingMode, this.getSwingMode.bind(this), this.setSwingMode.bind(this));
// Set up rotation direction
this.setupCharacteristic(this.platform.Characteristic.RotationDirection, this.getRotationDirection.bind(this), this.setRotationDirection.bind(this));
// Set up child lock
this.setupCharacteristic(this.platform.Characteristic.LockPhysicalControls, this.getLockPhysicalControls.bind(this), this.setLockPhysicalControls.bind(this));
// Add mode control service
const modeService = this.accessory.getService('Fan Mode') ||
this.accessory.addService(this.platform.Service.Switch, 'Fan Mode', 'fan-mode');
// Set up mode characteristics
modeService.getCharacteristic(this.platform.Characteristic.On)
.onGet(() => this.getMode())
.onSet((value) => this.setMode(value));
// Add Name characteristic
this.setupCharacteristic(this.platform.Characteristic.Name, async () => this.device.deviceName);
}
/**
* Update device states based on the latest details
*/
async updateDeviceSpecificStates(details) {
const fanDetails = details;
// Update active state
const isActive = fanDetails.deviceStatus === 'on';
this.updateCharacteristicValue(this.platform.Characteristic.Active, isActive ? 1 : 0);
// Update current state (INACTIVE = 0, IDLE = 1, BLOWING_AIR = 2)
this.updateCharacteristicValue(this.platform.Characteristic.CurrentFanState, isActive ? 2 : 0);
// Update rotation speed
let rotationSpeed = 0;
if (isActive && fanDetails.speed !== undefined) {
const speedIndex = this.speedLevels.indexOf(fanDetails.speed);
if (speedIndex !== -1) {
const maxLevel = this.speedLevels.length;
const levelSize = 100 / maxLevel;
rotationSpeed = Math.round((speedIndex + 1) * levelSize);
}
}
this.updateCharacteristicValue(this.platform.Characteristic.RotationSpeed, rotationSpeed);
// Update oscillation status
if ('oscillationState' in fanDetails) {
this.updateCharacteristicValue(this.platform.Characteristic.SwingMode, fanDetails.oscillationState ? 1 : 0);
}
// Update mode
const modeService = this.accessory.getService('Fan Mode');
if (modeService && fanDetails.mode) {
modeService.setCharacteristic(this.platform.Characteristic.On, this.getModeValue(fanDetails.mode));
}
}
getDeviceCapabilities() {
return {
hasBrightness: false,
hasColorTemp: false,
hasColor: false,
hasSpeed: true,
hasHumidity: false,
hasAirQuality: false,
hasWaterLevel: false,
hasChildLock: true,
hasSwingMode: true,
};
}
async getActive() {
return this.device.deviceStatus === 'on' ? 1 : 0;
}
async setActive(value) {
try {
const isOn = value === 1;
const success = isOn ? await this.device.turnOn() : await this.device.turnOff();
if (!success) {
throw new Error(`Failed to turn ${isOn ? 'on' : 'off'} device`);
}
// Update device state and characteristics
await this.updateDeviceSpecificStates(this.device);
await this.persistDeviceState('deviceStatus', isOn ? 'on' : 'off');
}
catch (error) {
this.handleDeviceError('set active state', error);
}
}
async handleSetRotationSpeed(value) {
const percentage = value;
if (percentage === 0) {
// Turn off the device instead of setting speed to 0
await this.device.turnOff();
return;
}
// Convert HomeKit percentage to device speed level
const maxLevel = this.speedLevels.length;
const levelSize = 100 / maxLevel;
const levelIndex = Math.min(Math.floor(percentage / levelSize), maxLevel - 1);
const speed = this.speedLevels[levelIndex];
await this.device.changeFanSpeed(speed);
}
async getRotationSpeed() {
if (this.device.speed === undefined || this.device.speed === null) {
return 0;
}
// Convert device speed to HomeKit percentage
const currentSpeed = this.device.speed;
const speedIndex = this.speedLevels.indexOf(currentSpeed);
if (speedIndex === -1) {
return 0;
}
const maxLevel = this.speedLevels.length;
const levelSize = 100 / maxLevel;
return Math.round((speedIndex + 1) * levelSize);
}
async getRotationDirection() {
return 'oscillation' in this.device && this.device.oscillation
? CLOCKWISE
: COUNTER_CLOCKWISE;
}
async setRotationDirection(value) {
try {
if (!('setOscillation' in this.device)) {
throw new Error('Device does not support oscillation');
}
// Map CLOCKWISE to oscillation ON, COUNTER_CLOCKWISE to oscillation OFF
const shouldOscillate = value === CLOCKWISE;
const success = await this.device.setOscillation(shouldOscillate);
if (!success) {
throw new Error(`Failed to ${shouldOscillate ? 'enable' : 'disable'} oscillation`);
}
}
catch (error) {
this.handleDeviceError('set rotation direction', error);
throw error;
}
}
async getSwingMode() {
return this.device.oscillationState ? 1 : 0;
}
async setSwingMode(value) {
try {
if (!this.device.setOscillation) {
throw new Error('Device does not support oscillation');
}
const enabled = value === 1;
const success = await this.device.setOscillation(enabled);
if (!success) {
throw new Error(`Failed to ${enabled ? 'enable' : 'disable'} oscillation`);
}
await this.persistDeviceState('oscillationState', enabled);
}
catch (error) {
this.handleDeviceError('set oscillation', error);
}
}
async getLockPhysicalControls() {
return this.device.childLock ? 1 : 0;
}
async setLockPhysicalControls(value) {
try {
if (!this.device.setChildLock) {
throw new Error('Device does not support child lock');
}
const enabled = value === 1;
const success = await this.device.setChildLock(enabled);
if (!success) {
throw new Error(`Failed to ${enabled ? 'enable' : 'disable'} child lock`);
}
await this.persistDeviceState('childLock', enabled);
}
catch (error) {
this.handleDeviceError('set child lock', error);
}
}
getModeValue(mode) {
switch (mode) {
case 'normal': return MODE_NORMAL;
case 'auto': return MODE_AUTO;
case 'sleep': return MODE_SLEEP;
case 'turbo': return MODE_TURBO;
default: return MODE_NORMAL;
}
}
getModeString(value) {
switch (value) {
case MODE_AUTO: return 'auto';
case MODE_SLEEP: return 'sleep';
case MODE_TURBO: return 'turbo';
default: return 'normal';
}
}
async getMode() {
return this.getModeValue(this.device.mode || 'normal');
}
async setMode(value) {
try {
const mode = this.getModeString(value);
const success = await this.device.setMode(mode);
if (!success) {
throw new Error(`Failed to set mode to ${mode}`);
}
await this.persistDeviceState('mode', mode);
}
catch (error) {
this.handleDeviceError('set mode', error);
}
}
}
exports.FanAccessory = FanAccessory;
//# sourceMappingURL=fan.accessory.js.map