Skip to content

Commit

Permalink
Feat/v2 (#78)
Browse files Browse the repository at this point in the history
* feat/humidifier-dual-200s
  • Loading branch information
Nicolae-Rares Ailincai authored Oct 20, 2023
1 parent 83e8ec5 commit 1497da9
Show file tree
Hide file tree
Showing 31 changed files with 960 additions and 337 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
matrix:
# the Node.js versions to build on
node-version: [14.x, 16.x, 18.x, 19.x]
node-version: [16.x, 18.x, 19.x, 20.x]

steps:
- uses: actions/checkout@v3
Expand Down
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"files.eol": "\n",
"cSpell.words": ["accountid", "appversion"],
"cSpell.words": [
"accountid",
"appversion",
"Levoit"
],
"editor.defaultFormatter": "vscode.typescript-language-features",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
Expand Down
39 changes: 32 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

# Homebridge Levoit Air Purifier

This is a Homebridge plugin to control Levoit Air Purifiers with via the VeSync Platform.
This is a Homebridge plugin to control Levoit Air Purifiers/Humidifiers with via the VeSync Platform.

| Supported Versions | Tested |
| ------------------ | ------ |
Expand All @@ -24,13 +24,9 @@ This is a Homebridge plugin to control Levoit Air Purifiers with via the VeSync
**If you have a newer version that is not in this table, then open a issue
and i will try to add support for it**

This plugin uses similar API calls as
[homebridge-levoitcore-client](https://github.com/tushardhadiwal/homebridge-levoitcore-client) but with differences on API implementation
and extra features.

Any device from VeSync that is not listed in the supported versions are automatically skipped when discovering devices.

### Features
### Features for Purifiers

1. Displaying the air quality (the same display as the one on the physical device) (Not for 200S)
2. Display the PM2.5 Density value in Home App shown in µg/m^3 (Not for 200S)
Expand All @@ -47,23 +43,52 @@ Any device from VeSync that is not listed in the supported versions are automati
- Auto
- Manual

### Humidifiers (Experimental Feature)

The version 2.x adds support for humidifiers as an experimental feature which by default is disabled, the devices and features are limited for this devices for now

| Supported Versions | Tested |
| ------------------ | ------ |
| Dual 200S ||
| Other versions ||

**If you have a newer version that is not in this table, then open a issue
and i will try to add support for it**

#### Features

1. Displaying the humidity level
2. Target Humidity
3. Speed option:
- 0 -> Off
- 1 -> LOW
- 2 -> HIGH
4. Mode change as Swing in home app
- Auto (Swing enabled)
- Manual (Swing disabled)

### Experimental Features

For the experimental features to be activated you need to add them in the config file of the platform, e.g.

```json
{
"name": "Levoit Air Purifiers",
"experimentalFeatures": ["DeviceDisplay"]
"experimentalFeatures": ["DeviceDisplay", "Humidifiers"]
}
```

---

1. Show the display's switch as a light in the app (`DeviceDisplay`)

The read data is cached for 5 seconds to not trigger the rate limiter for the API.
Each request is delayed by 500ms to not trigger the rate limiter if a huge number of requests are sent.

The timers are not included because you can accomplish similar results by using Home App's Automatization or the Shortcuts app

2. Discovers supported humidifiers (`Humidifiers`)

### Configuration

- Via the Homebridge UI, enter the Homebridge VeSync Client plugin settings.
Expand Down
2 changes: 1 addition & 1 deletion config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"items": {
"title": "Feature",
"type": "string",
"enum": ["DeviceDisplay"]
"enum": ["DeviceDisplay", "Humidifiers"]
}
}
}
Expand Down
27 changes: 15 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"displayName": "Levoit Air Purifier",
"main": "dist/index.js",
"license": "Apache-2.0",
"version": "1.3.1",
"version": "2.0.0",
"private": false,
"bugs": {
"url": "https://github.com/RaresAil/homebridge-levoit-air-purifier/issues"
Expand All @@ -26,27 +26,30 @@
"start": "npm run build && nodemon"
},
"devDependencies": {
"@types/async-lock": "^1.4.0",
"@types/big.js": "^6.1.6",
"@types/node": "^20.2.5",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"eslint": "^8.41.0",
"@types/async-lock": "^1.4.1",
"@types/big.js": "^6.2.1",
"@types/node": "^20.8.7",
"@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.8.0",
"eslint": "^8.51.0",
"homebridge": "^1.6.1",
"nodemon": "^2.0.22",
"rimraf": "^5.0.1",
"nodemon": "^3.0.1",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
"typescript": "^5.2.2"
},
"engines": {
"homebridge": ">=1.3.5",
"node": ">=14.18.1"
"node": ">=16"
},
"dependencies": {
"async-lock": "^1.4.0",
"axios": "^1.4.0",
"axios": "^1.5.1",
"big.js": "^6.2.1"
},
"resolutions": {
"semver": "^7.5.2"
},
"keywords": [
"homebridge-plugin",
"air-purifier",
Expand Down
107 changes: 107 additions & 0 deletions src/VeSyncHumAccessory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { Characteristic, Service } from 'homebridge';

import Platform, { VeSyncContext, VeSyncPlatformAccessory } from './platform';
import VeSyncHumidifier from './api/VeSyncHumidifier';

import RelativeHumidityHumidifierThreshold from './humidifierCharacteristics/RelativeHumidityHumidifierThreshold';
import CurrentHumidifierDehumidifierState from './humidifierCharacteristics/CurrentHumidifierDehumidifierState';
import CurrentRelativeHumidity from './humidifierCharacteristics/CurrentRelativeHumidity';
import RotationSpeed from './humidifierCharacteristics/RotationSpeed';
import AutoMode from './humidifierCharacteristics/AutoMode';
import Active from './humidifierCharacteristics/Active';

export type AccessoryThisType = ThisType<{
currentStateChar?: Characteristic;
humidifierService: Service;
modeChar?: Characteristic;
device: VeSyncHumidifier;
platform: Platform;
}>;

export default class VeSyncHumAccessory {
private currentStateChar?: Characteristic;
private modeChar?: Characteristic;

private humidifierService?: Service;

public get UUID() {
return this.device.uuid.toString();
}

private get device() {
return (this.accessory.context as VeSyncContext).device as VeSyncHumidifier;
}

constructor(
private readonly platform: Platform,
private readonly accessory: VeSyncPlatformAccessory,
) {
try {
const { manufacturer, model, mac } = this.device;

this.accessory
.getService(this.platform.Service.AccessoryInformation)!
.setCharacteristic(
this.platform.Characteristic.Manufacturer,
manufacturer
)
.setCharacteristic(this.platform.Characteristic.Model, model)
.setCharacteristic(this.platform.Characteristic.SerialNumber, mac);

this.humidifierService =
this.accessory.getService(this.platform.Service.HumidifierDehumidifier) ||
this.accessory.addService(this.platform.Service.HumidifierDehumidifier);

this.humidifierService
.getCharacteristic(this.platform.Characteristic.Active)
.onGet(Active.get.bind(this))
.onSet(Active.set.bind(this));

this.currentStateChar = this.humidifierService
.getCharacteristic(this.platform.Characteristic.CurrentHumidifierDehumidifierState)
.onGet(CurrentHumidifierDehumidifierState.get.bind(this));

this.humidifierService
.getCharacteristic(this.platform.Characteristic.TargetHumidifierDehumidifierState)
.setProps({
minValue: 1,
maxValue: 1,
validValueRanges: [1, 1],
validValues: [1]
})
.onGet(() => {
return this.platform.Characteristic.TargetHumidifierDehumidifierState.HUMIDIFIER;
});

this.humidifierService
.getCharacteristic(this.platform.Characteristic.CurrentRelativeHumidity)
.onGet(CurrentRelativeHumidity.get.bind(this));

if (this.device.deviceType.hasAutoMode) {
this.humidifierService
.getCharacteristic(this.platform.Characteristic.RelativeHumidityHumidifierThreshold)
.setProps({
maxValue: 110,
minValue: 30,
})
.onGet(RelativeHumidityHumidifierThreshold.get.bind(this))
.onSet(RelativeHumidityHumidifierThreshold.set.bind(this));

this.modeChar = this.humidifierService.getCharacteristic(this.platform.Characteristic.SwingMode)
.onGet(AutoMode.get.bind(this))
.onSet(AutoMode.set.bind(this));
}

this.humidifierService
.getCharacteristic(this.platform.Characteristic.RotationSpeed)
.setProps({
minStep: this.device.deviceType.speedMinStep,
maxValue: 100
})
.onGet(RotationSpeed.get.bind(this))
.onSet(RotationSpeed.set.bind(this));
} catch (error: any) {
this.platform.log.error(`Error: ${error?.message}`);
}
}
}
6 changes: 3 additions & 3 deletions src/VeSyncAccessory.ts → src/VeSyncPurAccessory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import DisplayLight from './experimentalCharacteristics/Display';

export type AccessoryThisType = ThisType<{
airPurifierCurrentCharacteristic?: Characteristic;
HomeAirQuality: VeSyncAccessory['HomeAirQuality'];
HomeAirQuality: VeSyncPurAccessory['HomeAirQuality'];
airPurifierService: Service;
platform: Platform;
device: VeSyncFan;
}>;

export default class VeSyncAccessory {
export default class VeSyncPurAccessory {
private HomeAirQuality = this.platform.Characteristic.AirQuality;
private airPurifierCurrentCharacteristic?: Characteristic;
private airPurifierService?: Service;
Expand All @@ -32,7 +32,7 @@ export default class VeSyncAccessory {
}

private get device() {
return (this.accessory.context as VeSyncContext).device;
return (this.accessory.context as VeSyncContext).device as VeSyncFan;
}

constructor(
Expand Down
Loading

0 comments on commit 1497da9

Please sign in to comment.