homebridge-levoit-air-purifier
Version:
Made for Core 200S/300S/400S/400S Pro/600S
291 lines • 13.3 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HumidifierBypassMethod = exports.BypassMethod = void 0;
const axios_1 = __importDefault(require("axios"));
const async_lock_1 = __importDefault(require("async-lock"));
const crypto_1 = __importDefault(require("crypto"));
const deviceTypes_1 = __importStar(require("./deviceTypes"));
const VeSyncHumidifier_1 = __importDefault(require("./VeSyncHumidifier"));
const VeSyncFan_1 = __importDefault(require("./VeSyncFan"));
var BypassMethod;
(function (BypassMethod) {
BypassMethod["STATUS"] = "getPurifierStatus";
BypassMethod["MODE"] = "setPurifierMode";
BypassMethod["NIGHT"] = "setNightLight";
BypassMethod["DISPLAY"] = "setDisplay";
BypassMethod["LOCK"] = "setChildLock";
BypassMethod["SWITCH"] = "setSwitch";
BypassMethod["SPEED"] = "setLevel";
})(BypassMethod || (exports.BypassMethod = BypassMethod = {}));
var HumidifierBypassMethod;
(function (HumidifierBypassMethod) {
HumidifierBypassMethod["HUMIDITY"] = "setTargetHumidity";
HumidifierBypassMethod["STATUS"] = "getHumidifierStatus";
HumidifierBypassMethod["MIST_LEVEL"] = "setVirtualLevel";
HumidifierBypassMethod["MODE"] = "setHumidityMode";
HumidifierBypassMethod["DISPLAY"] = "setDisplay";
HumidifierBypassMethod["SWITCH"] = "setSwitch";
HumidifierBypassMethod["LEVEL"] = "setLevel";
})(HumidifierBypassMethod || (exports.HumidifierBypassMethod = HumidifierBypassMethod = {}));
const lock = new async_lock_1.default();
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
class VeSync {
constructor(email, password, debugMode, log) {
this.email = email;
this.password = password;
this.debugMode = debugMode;
this.log = log;
this.VERSION = '1.3.1';
this.AGENT = `VeSync/VeSync 3.0.51(F5321;HomeBridge-VeSync ${this.VERSION})`;
this.TIMEZONE = 'America/New_York';
this.OS = 'HomeBridge-VeSync';
this.LANG = 'en';
this.AXIOS_OPTIONS = {
baseURL: 'https://smartapi.vesync.com',
timeout: 30000
};
}
generateDetailBody() {
return {
appVersion: this.VERSION,
phoneBrand: this.OS,
traceId: Date.now(),
phoneOS: this.OS
};
}
generateBody(includeAuth = false) {
return {
acceptLanguage: this.LANG,
timeZone: this.TIMEZONE,
...(includeAuth
? {
accountID: this.accountId,
token: this.token
}
: {})
};
}
generateV2Body(fan, method, data = {}) {
return {
method: 'bypassV2',
debugMode: false,
deviceRegion: fan.region,
cid: fan.cid,
configModule: fan.configModule,
payload: {
data: {
...data
},
method,
source: 'APP'
}
};
}
async sendCommand(fan, method, body = {}) {
return lock.acquire('api-call', async () => {
var _a;
try {
if (!this.api) {
throw new Error('The user is not logged in!');
}
this.debugMode.debug('[SEND COMMAND]', `Sending command ${method} to ${fan.name}`, `with (${JSON.stringify(body)})...`);
const response = await this.api.put('cloud/v2/deviceManaged/bypassV2', {
...this.generateV2Body(fan, method, body),
...this.generateDetailBody(),
...this.generateBody(true)
});
if (!(response === null || response === void 0 ? void 0 : response.data)) {
this.debugMode.debug('[SEND COMMAND]', 'No response data!! JSON:', JSON.stringify(response));
}
const isSuccess = ((_a = response === null || response === void 0 ? void 0 : response.data) === null || _a === void 0 ? void 0 : _a.code) === 0;
if (!isSuccess) {
this.debugMode.debug('[SEND COMMAND]', `Failed to send command ${method} to ${fan.name}`, `with (${JSON.stringify(body)})!`, `Response: ${JSON.stringify(response)}`);
}
await delay(500);
return isSuccess;
}
catch (error) {
this.log.error(`Failed to send command ${method} to ${fan === null || fan === void 0 ? void 0 : fan.name}`, `Error: ${error === null || error === void 0 ? void 0 : error.message}`);
return false;
}
});
}
async getDeviceInfo(fan, humidifier = false) {
return lock.acquire('api-call', async () => {
try {
if (!this.api) {
throw new Error('The user is not logged in!');
}
this.debugMode.debug('[GET DEVICE INFO]', 'Getting device info...');
const response = await this.api.post('cloud/v2/deviceManaged/bypassV2', {
...this.generateV2Body(fan, humidifier ? HumidifierBypassMethod.STATUS : BypassMethod.STATUS),
...this.generateDetailBody(),
...this.generateBody(true)
});
if (!(response === null || response === void 0 ? void 0 : response.data)) {
this.debugMode.debug('[GET DEVICE INFO]', 'No response data!! JSON:', JSON.stringify(response));
}
await delay(500);
this.debugMode.debug('[GET DEVICE INFO]', 'JSON:', JSON.stringify(response.data));
return response.data;
}
catch (error) {
this.log.error(`Failed to get device info for ${fan === null || fan === void 0 ? void 0 : fan.name}`, `Error: ${error === null || error === void 0 ? void 0 : error.message}`);
return null;
}
});
}
async startSession() {
this.debugMode.debug('[START SESSION]', 'Starting auth session...');
const firstLoginSuccess = await this.login();
setInterval(this.login.bind(this), 1000 * 60 * 55);
return firstLoginSuccess;
}
async login() {
return lock.acquire('api-call', async () => {
try {
if (!this.email || !this.password) {
throw new Error('Email and password are required');
}
this.debugMode.debug('[LOGIN]', 'Logging in...');
const pwdHashed = crypto_1.default
.createHash('md5')
.update(this.password)
.digest('hex');
const response = await axios_1.default.post('cloud/v1/user/login', {
email: this.email,
password: pwdHashed,
devToken: '',
userType: 1,
method: 'login',
token: '',
...this.generateDetailBody(),
...this.generateBody()
}, {
...this.AXIOS_OPTIONS
});
if (!(response === null || response === void 0 ? void 0 : response.data)) {
this.debugMode.debug('[LOGIN]', 'No response data!! JSON:', JSON.stringify(response));
return false;
}
const { result } = response.data;
const { token, accountID } = result !== null && result !== void 0 ? result : {};
if (!token || !accountID) {
this.debugMode.debug('[LOGIN]', 'The authentication failed!! JSON:', JSON.stringify(response.data));
return false;
}
this.debugMode.debug('[LOGIN]', 'The authentication success');
this.accountId = accountID;
this.token = token;
this.api = axios_1.default.create({
...this.AXIOS_OPTIONS,
headers: {
'content-type': 'application/json',
'accept-language': this.LANG,
accountid: this.accountId,
'user-agent': this.AGENT,
appversion: this.VERSION,
tz: this.TIMEZONE,
tk: this.token
}
});
await delay(500);
return true;
}
catch (error) {
this.log.error('Failed to login', `Error: ${error === null || error === void 0 ? void 0 : error.message}`);
return false;
}
});
}
async getDevices() {
return lock.acquire('api-call', async () => {
var _a, _b, _c;
try {
if (!this.api) {
throw new Error('The user is not logged in!');
}
const response = await this.api.post('cloud/v2/deviceManaged/devices', {
method: 'devices',
pageNo: 1,
pageSize: 1000,
...this.generateDetailBody(),
...this.generateBody(true)
});
if (!(response === null || response === void 0 ? void 0 : response.data)) {
this.debugMode.debug('[GET DEVICES]', 'No response data!! JSON:', JSON.stringify(response));
return {
purifiers: [],
humidifiers: []
};
}
if (!Array.isArray((_b = (_a = response.data) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.list)) {
this.debugMode.debug('[GET DEVICES]', 'No list found!! JSON:', JSON.stringify(response.data));
return {
purifiers: [],
humidifiers: []
};
}
const { list } = (_c = response.data.result) !== null && _c !== void 0 ? _c : { list: [] };
this.debugMode.debug('[GET DEVICES]', 'Device List -> JSON:', JSON.stringify(list));
let purifiers = list
.filter(({ deviceType, type, extension }) => !!deviceTypes_1.default.find(({ isValid }) => isValid(deviceType)) &&
type === 'wifi-air' &&
!!(extension === null || extension === void 0 ? void 0 : extension.fanSpeedLevel))
.map(VeSyncFan_1.default.fromResponse(this));
// Newer Vital purifiers
purifiers = purifiers.concat(list
.filter(({ deviceType, type, deviceProp }) => !!deviceTypes_1.default.find(({ isValid }) => isValid(deviceType)) &&
type === 'wifi-air' &&
!!deviceProp)
.map((fan) => ({ ...fan, extension: { ...fan.deviceProp, airQualityLevel: fan.deviceProp.AQLevel, mode: fan.deviceProp.workMode } }))
.map(VeSyncFan_1.default.fromResponse(this)));
const humidifiers = list
.filter(({ deviceType, type, extension }) => !!deviceTypes_1.humidifierDeviceTypes.find(({ isValid }) => isValid(deviceType)) &&
type === 'wifi-air' &&
!extension)
.map(VeSyncHumidifier_1.default.fromResponse(this));
await delay(1500);
return {
purifiers,
humidifiers
};
}
catch (error) {
this.log.error('Failed to get devices', `Error: ${error === null || error === void 0 ? void 0 : error.message}`);
return {
purifiers: [],
humidifiers: []
};
}
});
}
}
exports.default = VeSync;
//# sourceMappingURL=VeSync.js.map