From 842db87676fe5d225929fe98d385a3dbca772d95 Mon Sep 17 00:00:00 2001 From: Brandon McFarlin <6525520+Brandawg93@users.noreply.github.com> Date: Mon, 8 Apr 2024 02:48:24 -0400 Subject: [PATCH] add support for vital purifiers (#91) --- src/api/VeSync.ts | 13 ++++++++- src/api/VeSyncFan.ts | 64 ++++++++++++++++++++++++++++-------------- src/api/deviceTypes.ts | 16 ++++++++++- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/src/api/VeSync.ts b/src/api/VeSync.ts index 76f9986..72a2e2e 100644 --- a/src/api/VeSync.ts +++ b/src/api/VeSync.ts @@ -334,7 +334,7 @@ export default class VeSync { ); - const purifiers = list + let purifiers = list .filter( ({ deviceType, type, extension }) => !!deviceTypes.find(({ isValid }) => isValid(deviceType)) && @@ -343,6 +343,17 @@ export default class VeSync { ) .map(VeSyncFan.fromResponse(this)); + // Newer Vital purifiers + purifiers = purifiers.concat(list + .filter( + ({ deviceType, type, deviceProp }) => + !!deviceTypes.find(({ isValid }) => isValid(deviceType)) && + type === 'wifi-air' && + !!deviceProp + ) + .map((fan: any) => ({ ...fan, extension: { ...fan.deviceProp, airQualityLevel: fan.deviceProp.AQLevel, mode: fan.deviceProp.workMode } })) + .map(VeSyncFan.fromResponse(this))); + const humidifiers = list .filter( ({ deviceType, type, extension }) => diff --git a/src/api/VeSyncFan.ts b/src/api/VeSyncFan.ts index b9e5e55..462144a 100644 --- a/src/api/VeSyncFan.ts +++ b/src/api/VeSyncFan.ts @@ -1,5 +1,5 @@ import AsyncLock from 'async-lock'; -import deviceTypes, { DeviceType } from './deviceTypes'; +import deviceTypes, { DeviceType, DeviceCategory } from './deviceTypes'; import VeSync, { BypassMethod } from './VeSync'; import { VeSyncGeneric } from './VeSyncGeneric'; @@ -20,6 +20,7 @@ export enum Mode { export default class VeSyncFan implements VeSyncGeneric { private lock: AsyncLock = new AsyncLock(); public readonly deviceType: DeviceType; + public readonly deviceCategory: DeviceCategory; private lastCheck = 0; private _screenVisible = true; @@ -85,12 +86,16 @@ export default class VeSyncFan implements VeSyncGeneric { public readonly mac: string ) { this.deviceType = deviceTypes.find(({ isValid }) => isValid(this.model))!; + this.deviceCategory = this.model.includes('V') ? 'Vital' : 'Core'; } public async setChildLock(lock: boolean): Promise { - const success = await this.client.sendCommand(this, BypassMethod.LOCK, { - child_lock: lock - }); + const data = this.deviceCategory === 'Vital' ? { + childLockSwitch: lock ? 1 : 0 + } : { + child_lock: lock, + }; + const success = await this.client.sendCommand(this, BypassMethod.LOCK, data); if (success) { this._childLock = lock; @@ -100,10 +105,14 @@ export default class VeSyncFan implements VeSyncGeneric { } public async setPower(power: boolean): Promise { - const success = await this.client.sendCommand(this, BypassMethod.SWITCH, { - enabled: power, + const data = this.deviceCategory === 'Vital' ? { + powerSwitch: power ? 1 : 0, + switchIdx: 0 + } : { + switch: power, id: 0 - }); + }; + const success = await this.client.sendCommand(this, BypassMethod.SWITCH, data); if (success) { this._isOn = power; @@ -120,9 +129,12 @@ export default class VeSyncFan implements VeSyncGeneric { return false; } - const success = await this.client.sendCommand(this, BypassMethod.MODE, { + const data = this.deviceCategory === 'Vital' ? { + workMode: mode.toString() + } : { mode: mode.toString() - }); + }; + const success = await this.client.sendCommand(this, BypassMethod.MODE, data); if (success) { this._mode = mode; @@ -136,11 +148,17 @@ export default class VeSyncFan implements VeSyncGeneric { return false; } - const success = await this.client.sendCommand(this, BypassMethod.SPEED, { + const data = this.deviceCategory === 'Vital' ? { + manualSpeedLevel: speed, + switchIdx: 0, + type: 'wind' + } : { level: speed, type: 'wind', id: 0 - }); + }; + + const success = await this.client.sendCommand(this, BypassMethod.SPEED, data); if (success) { this._speed = speed; @@ -150,10 +168,14 @@ export default class VeSyncFan implements VeSyncGeneric { } public async setDisplay(display: boolean): Promise { - const success = await this.client.sendCommand(this, BypassMethod.DISPLAY, { + const data = this.deviceCategory === 'Vital' ? { + screenSwitch: display ? 1 : 0 + } : { state: display, id: 0 - }); + }; + + const success = await this.client.sendCommand(this, BypassMethod.DISPLAY, data); if (success) { this._screenVisible = display; @@ -178,16 +200,16 @@ export default class VeSyncFan implements VeSyncGeneric { const result = data?.result?.result; - this._pm25 = this.deviceType.hasPM25 ? result.air_quality_value : 0; + this._pm25 = this.deviceType.hasPM25 ? result.air_quality_value || result.PM25 : 0; this._airQualityLevel = this.deviceType.hasAirQuality - ? result.air_quality + ? result.air_quality || result.AQLevel : AirQuality.UNKNOWN; - this._filterLife = result.filter_life; - this._screenVisible = result.display; - this._childLock = result.child_lock; - this._isOn = result.enabled; - this._speed = result.level; - this._mode = result.mode; + this._filterLife = result.filter_life || result.filterLifePercent; + this._screenVisible = result.display || result.screenSwitch; + this._childLock = result.child_lock || result.childLockSwitch; + this._isOn = result.enabled || result.powerSwitch; + this._speed = result.level || result.fanSpeedLevel; + this._mode = result.mode || result.workMode; } catch (err: any) { this.client.log.error(err?.message); } diff --git a/src/api/deviceTypes.ts b/src/api/deviceTypes.ts index e4a944f..4007437 100644 --- a/src/api/deviceTypes.ts +++ b/src/api/deviceTypes.ts @@ -8,7 +8,9 @@ export enum DeviceName { Core301S = '301S', Core300S = '300S', Core201S = '201S', - Core200S = '200S' + Core200S = '200S', + Vital100S = 'V102S', + Vital200S = 'V201S', } export enum HumidifierDeviceName { @@ -26,6 +28,8 @@ export interface DeviceType { hasPM25: boolean; } +export type DeviceCategory = 'Core' | 'Vital'; + export type HumidifierDeviceType = Omit & { isHumidifier: true }; const deviceTypes: DeviceType[] = [ @@ -63,6 +67,16 @@ const deviceTypes: DeviceType[] = [ speedLevels: 4, hasPM25: false }, + { + isValid: (input: string) => + input.includes(DeviceName.Vital100S) || + input.includes(DeviceName.Vital200S), + hasAirQuality: true, + hasAutoMode: true, + speedMinStep: 25, + speedLevels: 4, + hasPM25: true + }, ]; export const humidifierDeviceTypes: HumidifierDeviceType[] = [