homebridge-levoit-humidifiers
Version:
Homebridge plugin for Levoit Humidifiers
235 lines • 10.7 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
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 = __importDefault(require("./deviceTypes"));
const VeSyncFan_1 = __importDefault(require("./VeSyncFan"));
var BypassMethod;
(function (BypassMethod) {
BypassMethod["STATUS"] = "getHumidifierStatus";
BypassMethod["MODE"] = "setHumidityMode";
BypassMethod["NIGHT_LIGHT_BRIGHTNESS"] = "setNightLightBrightness";
BypassMethod["DISPLAY"] = "setDisplay";
BypassMethod["SWITCH"] = "setSwitch";
BypassMethod["HUMIDITY"] = "setTargetHumidity";
BypassMethod["MIST_LEVEL"] = "setVirtualLevel";
BypassMethod["LEVEL"] = "setLevel";
BypassMethod["LIGHT_STATUS"] = "setLightStatus";
BypassMethod["DRYING_MODE"] = "setDryingMode";
})(BypassMethod = exports.BypassMethod || (exports.BypassMethod = {}));
const lock = new async_lock_1.default();
class VeSync {
constructor(email, password, config, debugMode, log) {
var _a;
this.email = email;
this.password = password;
this.config = config;
this.debugMode = debugMode;
this.log = log;
this.VERSION = '4.1.70';
this.FULL_VERSION = `VeSync ${this.VERSION} build15`;
this.AGENT = `VeSync/${this.VERSION} (iPhone; iOS 17.2.1; Humidifier/5.00)`;
this.TIMEZONE = 'America/New_York';
this.OS = 'iOS 17.2.1';
this.BRAND = 'iPhone 15 Pro';
this.LANG = 'en';
this.AXIOS_OPTIONS = {
baseURL: 'https://smartapi.vesync.com',
timeout: ((_a = this.config.options) === null || _a === void 0 ? void 0 : _a.apiTimeout) || 15000,
};
}
generateDetailBody() {
return {
appVersion: this.FULL_VERSION,
phoneBrand: this.BRAND,
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, _b;
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),
});
// Explicitly fail if device is offline
if (response.data.msg == 'device offline') {
this.log.error('VeSync cannot communicate with humidifier! Check the VeSync App.');
if ((_a = this.config.options) === null || _a === void 0 ? void 0 : _a.showOffWhenDisconnected) {
return false;
}
else {
throw new Error('Device was unreachable. Ensure it is plugged in and connected to WiFi.');
}
}
if (!(response === null || response === void 0 ? void 0 : response.data)) {
this.debugMode.debug('[SEND COMMAND]', 'No response data!! JSON:', JSON.stringify(response === null || response === void 0 ? void 0 : response.data));
}
const isSuccess = ((_b = response === null || response === void 0 ? void 0 : response.data) === null || _b === void 0 ? void 0 : _b.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 === null || response === void 0 ? void 0 : response.data)}`);
}
else {
this.debugMode.debug('[SEND COMMAND]', `Successfully sent command ${method} to ${fan.name}`, `with (${JSON.stringify(body)})!`, `Response: ${JSON.stringify(response.data)}`);
}
return isSuccess;
});
}
async getDeviceInfo(fan) {
return lock.acquire('api-call', async () => {
var _a;
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, BypassMethod.STATUS),
...this.generateDetailBody(),
...this.generateBody(true),
});
this.debugMode.debug('[DEVICE INFO]', JSON.stringify(response.data));
// Explicitly fail if device is offline
if (response.data.msg == 'device offline') {
this.log.error('VeSync cannot communicate with humidifier! Check the VeSync App.');
if ((_a = this.config.options) === null || _a === void 0 ? void 0 : _a.showOffWhenDisconnected) {
return false;
}
else {
throw new Error('Device was unreachable. Ensure it is plugged in and connected to WiFi.');
}
}
if (!(response === null || response === void 0 ? void 0 : response.data)) {
this.debugMode.debug('[GET DEVICE INFO]', 'No response data!! JSON:', JSON.stringify(response === null || response === void 0 ? void 0 : response.data));
}
return response.data;
});
}
async startSession() {
this.debugMode.debug('[START SESSION]', 'Starting auth session...');
const firstLoginSuccess = await this.login();
setInterval(this.login.bind(this), 1000 * 60 * 55); // Refresh token every 55 minutes
return firstLoginSuccess;
}
async login() {
return lock.acquire('api-call', async () => {
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 === null || response === void 0 ? void 0 : response.data));
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]', 'Authentication was successful');
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.FULL_VERSION,
tz: this.TIMEZONE,
tk: this.token,
},
});
return true;
});
}
async getDevices() {
return lock.acquire('api-call', async () => {
var _a, _b, _c;
if (!this.api) {
this.log.error('The user is not logged in!');
return [];
}
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 === null || response === void 0 ? void 0 : response.data));
return [];
}
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 [];
}
const { list } = (_c = response.data.result) !== null && _c !== void 0 ? _c : { list: [] };
this.debugMode.debug('[GET DEVICES]', 'Device List -> JSON:', JSON.stringify(list));
const devices = list
.filter(({ deviceType, type }) => !!deviceTypes_1.default.find(({ isValid }) => isValid(deviceType)) &&
type === 'wifi-air')
.map(VeSyncFan_1.default.fromResponse(this));
return devices;
});
}
}
exports.default = VeSync;
//# sourceMappingURL=VeSync.js.map