UNPKG

tsvesync

Version:

A TypeScript library for interacting with VeSync smart home devices

534 lines (482 loc) 18 kB
import { VeSyncFan } from '../vesyncFan'; import { VeSync } from '../vesync'; import { Helpers } from '../helpers'; import { logger } from '../logger'; /** * VeSync Air Purifier 131 Series (LV-PUR131S, LV-RH131S) * This class implements the specific API for LV series devices */ export class VeSyncAir131 extends VeSyncFan { protected readonly modes = ['auto', 'manual', 'sleep'] as const; protected readonly displayModes = ['on', 'off'] as const; protected readonly childLockModes = ['on', 'off'] as const; constructor(details: Record<string, any>, manager: VeSync) { super(details, manager); logger.debug(`Initialized VeSyncAir131 device: ${this.deviceName}`); } /** * Get device details */ async getDetails(): Promise<Boolean> { logger.debug(`Getting details for device: ${this.deviceName}`); const [response, status] = await this.callApi( '/131airPurifier/v1/device/devicedetail', 'post', { acceptLanguage: 'en', accountID: this.manager.accountId!, appVersion: '2.8.6', method: 'devicedetail', mobileId: '1234567890123456', phoneBrand: 'SM N9005', phoneOS: 'Android', timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'getDetails'); if (success && response) { this.deviceStatus = response.result?.result?.deviceStatus === 'on' ? 'on' : 'off'; this.details = { mode: response.result?.result?.mode || '', speed: response.result?.result?.level || 0, filterLife: response.result?.result?.filterLife || 0, screenStatus: response.result?.result?.display ? 'on' : 'off', childLock: response.result?.result?.childLock || false, airQuality: response.result?.result?.airQuality || 0, active_time: response.result?.result?.activeTime || 0 }; return true; } return false; } /** * Turn device on */ async turnOn(): Promise<boolean> { logger.info(`Turning on device: ${this.deviceName}`); const [response, status] = await this.callApi( '/131airPurifier/v1/device/deviceStatus', 'put', { acceptLanguage: 'en', accountID: this.manager.accountId!, status: 'on', timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'turnOn'); if (!success) { logger.error(`Failed to turn on device: ${this.deviceName}`); } return success; } /** * Turn device off */ async turnOff(): Promise<boolean> { logger.info(`Turning off device: ${this.deviceName}`); const [response, status] = await this.callApi( '/131airPurifier/v1/device/deviceStatus', 'put', { acceptLanguage: 'en', accountID: this.manager.accountId!, status: 'off', timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'turnOff'); if (!success) { logger.error(`Failed to turn off device: ${this.deviceName}`); } return success; } /** * Change fan speed */ async changeFanSpeed(speed: number): Promise<boolean> { logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`); // Check if device is in manual mode if (this.details.mode !== 'manual') { logger.debug(`${this.deviceName} not in manual mode, cannot change speed`); return false; } // Validate speed for LV series (1-3) if (speed < 1 || speed > 3) { logger.error(`Invalid fan speed: ${speed}. Must be between 1 and 3 for device: ${this.deviceName}`); return false; } const [response, status] = await this.callApi( '/131airPurifier/v1/device/updateSpeed', 'put', { acceptLanguage: 'en', accountID: this.manager.accountId!, level: speed, timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'changeFanSpeed'); if (success) { this.details.speed = speed; return true; } else { logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`); return false; } } /** * Set device mode */ async setMode(mode: string): Promise<boolean> { if (!this.modes.includes(mode as any)) { const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`; logger.error(`${error} for device: ${this.deviceName}`); throw new Error(error); } logger.debug(`Setting mode to ${mode} for device: ${this.deviceName}`); const payload: Record<string, any> = { acceptLanguage: 'en', accountID: this.manager.accountId!, mode: mode, timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }; // For manual mode, we need to set the level if (mode === 'manual') { payload.level = this.details.speed || 1; } const [response, status] = await this.callApi( '/131airPurifier/v1/device/updateMode', 'put', payload, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'setMode'); if (success) { this.details.mode = mode; return true; } else { logger.error(`Failed to set mode to ${mode} for device: ${this.deviceName}`); return false; } } /** * Set display status */ async setDisplay(enabled: boolean): Promise<boolean> { if (!this.hasFeature('display')) { const error = 'Display control not supported'; logger.error(`${error} for device: ${this.deviceName}`); throw new Error(error); } // Check if device is in sleep mode - display control may not work in sleep mode if (this.details.mode === 'sleep') { logger.warn(`Device ${this.deviceName} is in sleep mode, display control may not work`); } logger.debug(`Setting display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`); const [response, status] = await this.callApi( '/131airPurifier/v1/device/updateScreen', 'put', { acceptLanguage: 'en', accountID: this.manager.accountId!, status: enabled ? 'on' : 'off', timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'setDisplay'); if (success) { this.details.screenStatus = enabled ? 'on' : 'off'; return true; } else { logger.error(`Failed to set display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`); return false; } } /** * Set child lock */ async setChildLock(enabled: boolean): Promise<boolean> { if (!this.hasFeature('child_lock')) { const error = 'Child lock not supported'; logger.error(`${error} for device: ${this.deviceName}`); throw new Error(error); } // Check if device is in sleep mode - child lock may not work in sleep mode if (this.details.mode === 'sleep') { logger.warn(`Device ${this.deviceName} is in sleep mode, child lock control may not work`); } logger.debug(`Setting child lock to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`); const [response, status] = await this.callApi( '/131airPurifier/v1/device/updateChildLock', 'put', { acceptLanguage: 'en', accountID: this.manager.accountId!, status: enabled ? 'on' : 'off', timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'setChildLock'); if (success) { this.details.childLock = enabled; return true; } else { // Check for error code 11000000 (feature not supported) if (response?.code === 11000000) { logger.warn(`Child lock control not supported via API for device: ${this.deviceName}`); return false; } logger.error(`Failed to set child lock to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`); return false; } } /** * Set timer */ async setTimer(hours: number): Promise<boolean> { if (!this.hasFeature('timer')) { const error = 'Timer not supported'; logger.error(`${error} for device: ${this.deviceName}`); throw new Error(error); } logger.debug(`Setting timer to ${hours} hours for device: ${this.deviceName}`); const [response, status] = await this.callApi( '/131airPurifier/v1/device/updateTimer', 'put', { acceptLanguage: 'en', accountID: this.manager.accountId!, action: 'off', duration: hours, timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'setTimer'); if (success) { this.timer = { duration: hours * 3600, action: 'off' }; return true; } else { logger.error(`Failed to set timer to ${hours} hours for device: ${this.deviceName}`); return false; } } /** * Clear timer */ async clearTimer(): Promise<boolean> { if (!this.hasFeature('timer')) { const error = 'Timer not supported'; logger.error(`${error} for device: ${this.deviceName}`); throw new Error(error); } // If no timer is set, return success if (!this.timer) { logger.debug(`No timer to clear for device: ${this.deviceName}`); return true; } logger.debug(`Clearing timer for device: ${this.deviceName}`); const [response, status] = await this.callApi( '/131airPurifier/v1/device/cancelTimer', 'put', { acceptLanguage: 'en', accountID: this.manager.accountId!, timeZone: this.manager.timeZone!, token: this.manager.token!, uuid: this.uuid }, { 'accept-language': 'en', 'accountId': this.manager.accountId!, 'appVersion': '2.8.6', 'content-type': 'application/json', 'tk': this.manager.token!, 'tz': this.manager.timeZone! } ); const success = this.checkResponse([response, status], 'clearTimer'); if (success) { this.timer = null; return true; } else { logger.error(`Failed to clear timer for device: ${this.deviceName}`); return false; } } /** * Set auto mode */ async autoMode(): Promise<boolean> { logger.debug(`Setting auto mode for device: ${this.deviceName}`); const success = await this.setMode('auto'); if (!success) { logger.error(`Failed to set auto mode for device: ${this.deviceName}`); } return success; } /** * Set manual mode */ async manualMode(): Promise<boolean> { logger.debug(`Setting manual mode for device: ${this.deviceName}`); const success = await this.setMode('manual'); if (!success) { logger.error(`Failed to set manual mode for device: ${this.deviceName}`); } return success; } /** * Set sleep mode */ async sleepMode(): Promise<boolean> { logger.debug(`Setting sleep mode for device: ${this.deviceName}`); const success = await this.setMode('sleep'); if (!success) { logger.error(`Failed to set sleep mode for device: ${this.deviceName}`); } return success; } /** * Turn off display */ async turnOffDisplay(): Promise<boolean> { logger.debug(`Turning off display for device: ${this.deviceName}`); const success = await this.setDisplay(false); if (!success) { logger.error(`Failed to turn off display for device: ${this.deviceName}`); } return success; } /** * Turn on display */ async turnOnDisplay(): Promise<boolean> { logger.debug(`Turning on display for device: ${this.deviceName}`); const success = await this.setDisplay(true); if (!success) { logger.error(`Failed to turn on display for device: ${this.deviceName}`); } return success; } /** * Get active time in minutes */ get activeTime(): number { return this.details.active_time || 0; } /** * Display device info */ override display(): void { super.display(); const info = [ ['Mode: ', this.mode], ['Speed: ', this.speed], ['Filter Life: ', this.filterLife, '%'], ['Screen Status: ', this.screenStatus], ['Child Lock: ', this.childLock ? 'Enabled' : 'Disabled'], ['Air Quality: ', this.airQuality], ['Active Time: ', this.activeTime, 'minutes'] ]; for (const [key, value, unit = ''] of info) { logger.info(`${key.toString().padEnd(30, '.')} ${value}${unit}`); } } /** * Return JSON details for device */ override displayJSON(): string { const baseInfo = JSON.parse(super.displayJSON()); const details: Record<string, string> = { ...baseInfo, 'Mode': this.mode, 'Speed': this.speed.toString(), 'Filter Life': this.filterLife.toString(), 'Screen Status': this.screenStatus, 'Child Lock': this.childLock ? 'Enabled' : 'Disabled', 'Air Quality': this.airQuality, 'Active Time': this.activeTime.toString() }; return JSON.stringify(details, null, 4); } }