tsvesync
Version:
A TypeScript library for interacting with VeSync smart home devices
534 lines (533 loc) • 22.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VeSyncAirBypass = void 0;
const vesyncFan_1 = require("../vesyncFan");
const helpers_1 = require("../helpers");
const logger_1 = require("../logger");
/**
* VeSync Air Purifier with Bypass
*/
class VeSyncAirBypass extends vesyncFan_1.VeSyncFan {
get modes() {
return this.getConfigModes(['auto', 'manual', 'sleep']);
}
constructor(details, manager) {
super(details, manager);
this.displayModes = ['on', 'off'];
this.childLockModes = ['on', 'off'];
logger_1.logger.debug(`Initialized VeSyncAirBypass device: ${this.deviceName}`);
}
/**
* Build API dictionary
*/
buildApiDict(method) {
const head = helpers_1.Helpers.reqHeaderBypass();
const body = {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
method,
source: 'APP',
data: {}
}
};
return [head, body];
}
/**
* Get device details
*/
async getDetails() {
var _a;
logger_1.logger.debug(`Getting details for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {},
method: 'getPurifierStatus',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'getDetails');
if (success) {
const result = (_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.result;
if (result) {
this.deviceStatus = result.enabled ? 'on' : 'off';
// Parse filter life with fallback handling
let filterLife = 0;
if (result.filterLifePercent !== undefined) {
filterLife = result.filterLifePercent;
logger_1.logger.debug(`${this.deviceName}: Parsed filter life from filterLifePercent: ${filterLife}%`);
}
else if (result.filter_life !== undefined) {
filterLife = result.filter_life;
logger_1.logger.debug(`${this.deviceName}: Parsed filter life from filter_life: ${filterLife}%`);
}
else {
logger_1.logger.debug(`${this.deviceName}: No filter life data found in API response`);
if (logger_1.logger.getLevel && logger_1.logger.getLevel() !== undefined && logger_1.logger.getLevel() <= 0) {
logger_1.logger.debug(`${this.deviceName}: API response structure:`, JSON.stringify(result, null, 2));
}
}
// Parse air quality with fallback support for multiple API response formats
// Some devices (like Core 400S) may use PascalCase (AQLevel, PM25)
// while others use snake_case (air_quality, air_quality_value)
const airQualityLevel = result.air_quality || result.AQLevel || 0;
const airQualityValue = result.air_quality_value || result.PM25 || 0;
// Debug logging to diagnose air quality field availability
logger_1.logger.debug(`${this.deviceName}: Air quality API response fields:`, {
air_quality: result.air_quality,
AQLevel: result.AQLevel,
air_quality_value: result.air_quality_value,
PM25: result.PM25,
parsed_level: airQualityLevel,
parsed_value: airQualityValue
});
this.details = {
mode: result.mode || '',
speed: result.level || 0,
filter_life: filterLife,
screen_status: result.display ? 'on' : 'off',
child_lock: result.child_lock || false,
air_quality: airQualityLevel,
air_quality_value: airQualityValue
};
const normalizedAirQuality = helpers_1.Helpers.normalizeAirQuality(airQualityLevel);
if (normalizedAirQuality.level >= 1) {
this.details.air_quality_level = normalizedAirQuality.level;
}
this.details.air_quality_label = normalizedAirQuality.label;
// Capture additional air quality properties if available
if ('PM1' in result)
this.details.pm1 = result.PM1;
if ('PM10' in result)
this.details.pm10 = result.PM10;
if ('AQPercent' in result)
this.details.aq_percent = result.AQPercent;
// Debug log additional air quality fields for diagnostics
if (result.PM1 !== undefined || result.PM10 !== undefined || result.AQPercent !== undefined) {
logger_1.logger.debug(`${this.deviceName}: Additional air quality data:`, {
PM1: result.PM1,
PM10: result.PM10,
AQPercent: result.AQPercent
});
}
// Don't overwrite config as it contains features array
if (result.configuration) {
this.details.configuration = result.configuration;
}
logger_1.logger.debug(`Successfully got details for device: ${this.deviceName}`);
}
}
return success;
}
/**
* Turn device on
*/
async turnOn() {
logger_1.logger.info(`Turning on device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
enabled: true,
id: 0
},
method: 'setSwitch',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'turnOn');
if (!success) {
logger_1.logger.error(`Failed to turn on device: ${this.deviceName}`);
}
return success;
}
/**
* Turn device off
*/
async turnOff() {
logger_1.logger.info(`Turning off device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
enabled: false,
id: 0
},
method: 'setSwitch',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'turnOff');
if (!success) {
logger_1.logger.error(`Failed to turn off device: ${this.deviceName}`);
}
return success;
}
/**
* Change fan speed
*/
async changeFanSpeed(speed) {
var _a;
const speeds = (_a = this.config.levels) !== null && _a !== void 0 ? _a : [];
if (!speeds.includes(speed)) {
logger_1.logger.error(`Invalid speed: ${speed}. Must be one of: ${speeds.join(', ')} for device: ${this.deviceName}`);
return false;
}
logger_1.logger.info(`Changing fan speed to ${speed} for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
id: 0,
level: speed,
mode: 'manual',
type: 'wind'
},
method: 'setLevel',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'changeFanSpeed');
if (!success) {
logger_1.logger.error(`Failed to change fan speed to ${speed} for device: ${this.deviceName}`);
}
return success;
}
/**
* Set device mode
*/
async setMode(mode) {
const requested = mode.toLowerCase();
const supported = this.modes.map(m => m.toLowerCase());
const allowsCore200AutoFallback = this.deviceType.includes('Core200S') && requested === 'auto';
if (!supported.includes(requested) && !allowsCore200AutoFallback) {
const error = `Invalid mode: ${mode}. Must be one of: ${this.modes.join(', ')}`;
logger_1.logger.error(`${error} for device: ${this.deviceName}`);
throw new Error(error);
}
logger_1.logger.debug(`Setting mode to ${requested} for device: ${this.deviceName}`);
// Special handling for Core200S auto mode
if (allowsCore200AutoFallback) {
// Core200S doesn't support auto mode, so we'll set it to manual mode
logger_1.logger.warn(`Auto mode not supported for ${this.deviceType}, using manual mode instead`);
return this.setMode('manual');
}
// Standard implementation for other devices
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
mode: requested
},
method: 'setPurifierMode',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'setMode');
if (success) {
this.details.mode = requested;
return true;
}
else {
logger_1.logger.error(`Failed to set mode to ${requested} for device: ${this.deviceName}`);
return false;
}
}
/**
* Set display status
*/
async setDisplay(enabled) {
var _a;
if (!this.hasFeature('display')) {
const error = 'Display control not supported';
logger_1.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_1.logger.warn(`Device ${this.deviceName} is in sleep mode, display control may not work`);
}
logger_1.logger.debug(`Setting display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
state: enabled
},
method: 'setDisplay',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'setDisplay');
// Check for error code 11018000 (operation not supported in current mode)
if (((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.code) === 11018000) {
logger_1.logger.warn(`Display control not supported in current mode for device: ${this.deviceName}`);
return false;
}
if (success) {
this.details.screenStatus = enabled ? 'on' : 'off';
return true;
}
else {
logger_1.logger.error(`Failed to set display to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
return false;
}
}
/**
* Set child lock
*/
async setChildLock(enabled) {
var _a;
if (!this.hasFeature('child_lock')) {
const error = 'Child lock not supported';
logger_1.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_1.logger.warn(`Device ${this.deviceName} is in sleep mode, child lock control may not work`);
}
logger_1.logger.debug(`Setting child lock to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
state: enabled
},
method: 'setChildLock',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'setChildLock');
// Check for error code 11000000 (feature not supported)
if ((response === null || response === void 0 ? void 0 : response.code) === 11000000 || (((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.code) === 11000000)) {
logger_1.logger.warn(`Child lock control not supported via API for device: ${this.deviceName}`);
return false;
}
if (success) {
this.details.childLock = enabled;
return true;
}
else {
logger_1.logger.error(`Failed to set child lock to ${enabled ? 'on' : 'off'} for device: ${this.deviceName}`);
return false;
}
}
/**
* Set timer
*/
async setTimer(hours) {
var _a, _b;
if (!this.hasFeature('timer')) {
const error = 'Timer not supported';
logger_1.logger.error(`${error} for device: ${this.deviceName}`);
throw new Error(error);
}
// Check if device is on - timer can only be set when device is on
if (this.deviceStatus !== 'on') {
logger_1.logger.debug(`Cannot set timer when device is off: ${this.deviceName}`);
return false;
}
logger_1.logger.debug(`Setting timer to ${hours} hours for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
action: 'off',
total: hours * 3600
},
method: 'addTimer',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'setTimer');
// Check for successful response with timer ID
if (success && ((_b = (_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.id)) {
this.timer = { duration: hours * 3600, action: 'off' };
return true;
}
else if (success) {
// API call succeeded but no timer ID returned
logger_1.logger.warn(`Timer API call succeeded but no timer ID returned for device: ${this.deviceName}`);
this.timer = { duration: hours * 3600, action: 'off' };
return true;
}
else {
logger_1.logger.error(`Failed to set timer to ${hours} hours for device: ${this.deviceName}`);
return false;
}
}
/**
* Clear timer
*/
async clearTimer() {
var _a;
if (!this.hasFeature('timer')) {
const error = 'Timer not supported';
logger_1.logger.error(`${error} for device: ${this.deviceName}`);
throw new Error(error);
}
// If no timer is set, return success
if (!this.timer) {
logger_1.logger.debug(`No timer to clear for device: ${this.deviceName}`);
return true;
}
logger_1.logger.debug(`Clearing timer for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {},
method: 'deleteTimer',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'clearTimer');
// Check for error code 11000000 (feature not supported or no timer to clear)
if ((response === null || response === void 0 ? void 0 : response.code) === 11000000 || (((_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.code) === 11000000)) {
logger_1.logger.warn(`No timer to clear or timer control not supported via API for device: ${this.deviceName}`);
this.timer = null;
return true;
}
if (success) {
this.timer = null;
return true;
}
else {
logger_1.logger.error(`Failed to clear timer for device: ${this.deviceName}`);
return false;
}
}
/**
* Turn automatic stop on
*/
async automaticStopOn() {
logger_1.logger.debug(`Setting automatic stop on for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
enabled: true
},
method: 'setAutomaticStop',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'automaticStopOn');
if (!success) {
logger_1.logger.error(`Failed to set automatic stop on for device: ${this.deviceName}`);
}
return success;
}
/**
* Turn automatic stop off
*/
async automaticStopOff() {
logger_1.logger.debug(`Setting automatic stop off for device: ${this.deviceName}`);
const [response, status] = await this.callApi('/cloud/v2/deviceManaged/bypassV2', 'post', {
...helpers_1.Helpers.reqBody(this.manager, 'bypassV2'),
cid: this.cid,
configModule: this.configModule,
payload: {
data: {
enabled: false
},
method: 'setAutomaticStop',
source: 'APP'
}
}, helpers_1.Helpers.reqHeaderBypass());
const success = this.checkResponse([response, status], 'automaticStopOff');
if (!success) {
logger_1.logger.error(`Failed to set automatic stop off for device: ${this.deviceName}`);
}
return success;
}
/**
* Set auto mode
*/
async autoMode() {
// Check if auto mode is supported
if (!this.hasFeature('auto_mode')) {
logger_1.logger.debug(`Auto mode not supported for device: ${this.deviceName}`);
return false;
}
logger_1.logger.debug(`Setting auto mode for device: ${this.deviceName}`);
const success = await this.setMode('auto');
if (!success) {
logger_1.logger.error(`Failed to set auto mode for device: ${this.deviceName}`);
}
return success;
}
/**
* Set manual mode
*/
async manualMode() {
logger_1.logger.debug(`Setting manual mode for device: ${this.deviceName}`);
const success = await this.setMode('manual');
if (!success) {
logger_1.logger.error(`Failed to set manual mode for device: ${this.deviceName}`);
}
return success;
}
/**
* Set sleep mode
*/
async sleepMode() {
logger_1.logger.debug(`Setting sleep mode for device: ${this.deviceName}`);
const success = await this.setMode('sleep');
if (!success) {
logger_1.logger.error(`Failed to set sleep mode for device: ${this.deviceName}`);
}
return success;
}
/**
* Turn off display
*/
async turnOffDisplay() {
logger_1.logger.debug(`Turning off display for device: ${this.deviceName}`);
const success = await this.setDisplay(false);
if (!success) {
logger_1.logger.error(`Failed to turn off display for device: ${this.deviceName}`);
}
return success;
}
/**
* Turn on display
*/
async turnOnDisplay() {
logger_1.logger.debug(`Turning on display for device: ${this.deviceName}`);
const success = await this.setDisplay(true);
if (!success) {
logger_1.logger.error(`Failed to turn on display for device: ${this.deviceName}`);
}
return success;
}
/**
* Get night light state
*/
get nightLight() {
return this.details.night_light || 'off';
}
}
exports.VeSyncAirBypass = VeSyncAirBypass;