homebridge-tsvesync 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintignore ADDED
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ coverage/
4
+ *.js
package/.eslintrc.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "parser": "@typescript-eslint/parser",
3
+ "extends": [
4
+ "eslint:recommended",
5
+ "plugin:@typescript-eslint/recommended"
6
+ ],
7
+ "plugins": ["@typescript-eslint"],
8
+ "parserOptions": {
9
+ "ecmaVersion": 2018,
10
+ "sourceType": "module"
11
+ },
12
+ "env": {
13
+ "node": true,
14
+ "es6": true
15
+ },
16
+ "rules": {
17
+ "@typescript-eslint/explicit-function-return-type": "off",
18
+ "@typescript-eslint/no-non-null-assertion": "off",
19
+ "@typescript-eslint/explicit-module-boundary-types": "off",
20
+ "@typescript-eslint/no-explicit-any": "off",
21
+ "no-unused-vars": "off",
22
+ "@typescript-eslint/no-unused-vars": ["warn", {
23
+ "argsIgnorePattern": "^_",
24
+ "varsIgnorePattern": "^_"
25
+ }]
26
+ },
27
+ "ignorePatterns": [
28
+ "dist/*",
29
+ "node_modules/*"
30
+ ]
31
+ }
package/README.md ADDED
@@ -0,0 +1,170 @@
1
+ # homebridge-tsvesync
2
+
3
+ This is a Homebridge plugin that allows you to control your VeSync/Levoit/Etekcity devices through HomeKit. It provides native HomeKit integration for a wide range of VeSync-enabled devices.
4
+
5
+ ## Supported Devices
6
+
7
+ ### Air Purifiers
8
+ All air purifiers are exposed as HomeKit air purifiers with the following features:
9
+ - Core200S, Core300S, Core400S, Core600S
10
+ * On/Off control
11
+ * Fan speed control
12
+ * Filter life monitoring
13
+ * Auto/Manual/Sleep mode selection
14
+ * Air quality monitoring (300S/400S/600S only)
15
+
16
+ - LV-PUR131S
17
+ * On/Off control
18
+ * Air quality monitoring
19
+ * Filter life monitoring
20
+
21
+ - Vital100S, Vital200S
22
+ * On/Off control
23
+ * Fan speed control (1-4)
24
+ * Mode selection (Manual, Auto, Sleep, Pet)
25
+ * Air quality monitoring
26
+ * Filter life monitoring
27
+
28
+ - EverestAir
29
+ * On/Off control
30
+ * Fan speed control (1-3)
31
+ * Mode selection (Manual, Auto, Sleep, Turbo)
32
+ * Air quality monitoring
33
+ * Filter life monitoring
34
+
35
+ ### Humidifiers
36
+ All humidifiers are exposed as HomeKit humidifiers with the following features:
37
+ - Classic300S, Classic200S
38
+ * On/Off control
39
+ * Target humidity setting
40
+ * Current humidity reading
41
+ * Auto/Manual mode selection
42
+ * Mist level control (1-9)
43
+ * Night light control (300S only)
44
+
45
+ - Dual200S
46
+ * On/Off control
47
+ * Target humidity setting
48
+ * Current humidity reading
49
+ * Auto/Manual mode selection
50
+ * Mist level control (1-2)
51
+
52
+ - LV600S, OasisMist Series, Superior6000S
53
+ * On/Off control
54
+ * Target humidity setting
55
+ * Current humidity reading
56
+ * Mode selection (Auto/Manual/Sleep)
57
+ * Mist level control
58
+ * Warm mist control (where supported)
59
+ * Night light control (where supported)
60
+
61
+ ### Smart Bulbs
62
+ All bulbs are exposed as HomeKit lights with their respective capabilities:
63
+ - ESL100
64
+ * On/Off control
65
+ * Brightness control
66
+
67
+ - ESL100CW
68
+ * On/Off control
69
+ * Brightness control
70
+ * Color temperature control
71
+
72
+ - ESL100MC, XYD0001
73
+ * On/Off control
74
+ * Brightness control
75
+ * Full RGB color control
76
+ * Color temperature control
77
+
78
+ ### Smart Outlets
79
+ All outlets are exposed as HomeKit outlets with the following features:
80
+ - 15A Outlets (ESO15-TB, ESW15-USA)
81
+ * On/Off control
82
+ * Power/Energy monitoring
83
+ * Voltage monitoring
84
+
85
+ - 10A Outlets (ESW03-USA, ESW01-EU)
86
+ * On/Off control
87
+ * Power/Energy monitoring
88
+ * Voltage monitoring
89
+
90
+ - 7A Outlet (wifi-switch-1.3)
91
+ * On/Off control
92
+ * Basic energy usage tracking
93
+
94
+ ### Tower Fans
95
+ - LTF-F422S Series
96
+ * On/Off control
97
+ * Fan speed control (1-12)
98
+ * Mode selection (Normal, Auto, Sleep, Turbo)
99
+ * Oscillation control
100
+
101
+ ## Installation
102
+
103
+ 1. Install Homebridge if you haven't already (see [homebridge.io](https://homebridge.io))
104
+ 2. Install this plugin:
105
+ ```bash
106
+ npm install -g homebridge-tsvesync
107
+ ```
108
+ 3. Configure the plugin in your Homebridge `config.json`
109
+
110
+ ## Configuration
111
+
112
+ Add the following to your Homebridge `config.json`:
113
+
114
+ ```json
115
+ {
116
+ "platforms": [
117
+ {
118
+ "platform": "TSVESyncPlatform",
119
+ "name": "TSVESync",
120
+ "username": "YOUR_VESYNC_EMAIL",
121
+ "password": "YOUR_VESYNC_PASSWORD"
122
+ }
123
+ ]
124
+ }
125
+ ```
126
+
127
+ ### Configuration Options
128
+
129
+ * `platform` (required): Must be set to "TSVESyncPlatform"
130
+ * `name` (required): Can be anything, this is the name that will appear in your Homebridge log
131
+ * `username` (required): Your VeSync account email
132
+ * `password` (required): Your VeSync account password
133
+
134
+ ## Features
135
+
136
+ * Automatic device discovery
137
+ * Real-time status updates
138
+ * Native HomeKit integration
139
+ * Power consumption monitoring (supported outlets)
140
+ * Air quality monitoring (supported purifiers)
141
+ * Humidity control (supported humidifiers)
142
+ * RGB and white color control (supported bulbs)
143
+ * Fan speed and mode control (purifiers and fans)
144
+
145
+ ## Troubleshooting
146
+
147
+ 1. Make sure your VeSync credentials are correct
148
+ 2. Check the Homebridge logs for any error messages
149
+ 3. Ensure your devices are properly set up in the VeSync app
150
+ 4. Verify your devices are running the latest firmware
151
+ 5. Check that your devices are online in the VeSync app
152
+
153
+ ## Development
154
+
155
+ * Clone this repository
156
+ * Install dependencies: `npm install`
157
+ * Build the plugin: `npm run build`
158
+ * Link it to your local Homebridge installation: `npm link`
159
+
160
+ ## Contributing
161
+
162
+ Contributions are welcome! Please feel free to submit a Pull Request.
163
+
164
+ ## License
165
+
166
+ MIT
167
+
168
+ ## Credits
169
+
170
+ This plugin uses the [tsvesync](https://github.com/mickgiles/tsvesync) library for device communication.
@@ -0,0 +1,4 @@
1
+ import { API } from 'homebridge';
2
+ declare const _default: (api: API) => void;
3
+ export = _default;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;8BAIlB,GAAG;AAAlB,kBAEE"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ const settings_1 = require("./settings");
3
+ const platform_1 = require("./platform");
4
+ module.exports = (api) => {
5
+ api.registerPlatform(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, platform_1.TSVESyncPlatform);
6
+ };
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,yCAAwD;AACxD,yCAA8C;AAE9C,iBAAS,CAAC,GAAQ,EAAE,EAAE;IACpB,GAAG,CAAC,gBAAgB,CAAC,sBAAW,EAAE,wBAAa,EAAE,2BAAgB,CAAC,CAAC;AACrE,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
2
+ export declare class TSVESyncPlatform implements DynamicPlatformPlugin {
3
+ readonly log: Logger;
4
+ readonly config: PlatformConfig;
5
+ readonly api: API;
6
+ readonly Service: typeof Service;
7
+ readonly Characteristic: typeof Characteristic;
8
+ readonly accessories: PlatformAccessory[];
9
+ private client;
10
+ private deviceUpdateInterval?;
11
+ constructor(log: Logger, config: PlatformConfig, api: API);
12
+ /**
13
+ * This function is invoked when homebridge restores cached accessories from disk at startup.
14
+ */
15
+ configureAccessory(accessory: PlatformAccessory): void;
16
+ /**
17
+ * Discover TSVESync devices and register them as accessories
18
+ */
19
+ discoverDevices(): Promise<void>;
20
+ /**
21
+ * Update all device states
22
+ */
23
+ private updateDeviceStates;
24
+ }
25
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE,cAAc,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAK5H,qBAAa,gBAAiB,YAAW,qBAAqB;aAW1C,GAAG,EAAE,MAAM;aACX,MAAM,EAAE,cAAc;aACtB,GAAG,EAAE,GAAG;IAZ1B,SAAgB,OAAO,EAAE,OAAO,OAAO,CAAwB;IAC/D,SAAgB,cAAc,EAAE,OAAO,cAAc,CAA+B;IAGpF,SAAgB,WAAW,EAAE,iBAAiB,EAAE,CAAM;IAEtD,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,oBAAoB,CAAC,CAAiB;gBAG5B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,GAAG;IAoC1B;;OAEG;IACH,kBAAkB,CAAC,SAAS,EAAE,iBAAiB;IAK/C;;OAEG;IACG,eAAe;IAsCrB;;OAEG;YACW,kBAAkB;CAejC"}
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TSVESyncPlatform = void 0;
4
+ const settings_1 = require("./settings");
5
+ const platformAccessory_1 = require("./platformAccessory");
6
+ const tsvesync_1 = require("tsvesync");
7
+ class TSVESyncPlatform {
8
+ constructor(log, config, api) {
9
+ this.log = log;
10
+ this.config = config;
11
+ this.api = api;
12
+ this.Service = this.api.hap.Service;
13
+ this.Characteristic = this.api.hap.Characteristic;
14
+ // this is used to track restored cached accessories
15
+ this.accessories = [];
16
+ // Validate configuration
17
+ if (!config.username || !config.password) {
18
+ log.error('Missing required configuration. Please check your config.json');
19
+ return;
20
+ }
21
+ // Initialize TSVESync client
22
+ this.client = new tsvesync_1.TSVESync({
23
+ username: config.username,
24
+ password: config.password,
25
+ });
26
+ this.log.debug('Finished initializing platform:', this.config.name);
27
+ // When this event is fired it means Homebridge has restored all cached accessories from disk.
28
+ this.api.on('didFinishLaunching', () => {
29
+ log.debug('Executed didFinishLaunching callback');
30
+ // run the method to discover / register your devices as accessories
31
+ this.discoverDevices();
32
+ // Set up periodic device updates (every 30 seconds)
33
+ this.deviceUpdateInterval = setInterval(() => {
34
+ this.updateDeviceStates();
35
+ }, 30000);
36
+ });
37
+ // Clean up when shutting down
38
+ this.api.on('shutdown', () => {
39
+ if (this.deviceUpdateInterval) {
40
+ clearInterval(this.deviceUpdateInterval);
41
+ }
42
+ });
43
+ }
44
+ /**
45
+ * This function is invoked when homebridge restores cached accessories from disk at startup.
46
+ */
47
+ configureAccessory(accessory) {
48
+ this.log.info('Loading accessory from cache:', accessory.displayName);
49
+ this.accessories.push(accessory);
50
+ }
51
+ /**
52
+ * Discover TSVESync devices and register them as accessories
53
+ */
54
+ async discoverDevices() {
55
+ try {
56
+ // Login and get devices
57
+ await this.client.login();
58
+ const devices = await this.client.getDevices();
59
+ // Loop over the discovered devices and register each one
60
+ for (const device of devices) {
61
+ const uuid = this.api.hap.uuid.generate(device.id);
62
+ const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
63
+ if (existingAccessory) {
64
+ this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
65
+ existingAccessory.context.device = device;
66
+ new platformAccessory_1.TSVESyncAccessory(this, existingAccessory, this.client);
67
+ }
68
+ else {
69
+ this.log.info('Adding new accessory:', device.name);
70
+ const accessory = new this.api.platformAccessory(device.name, uuid);
71
+ accessory.context.device = device;
72
+ new platformAccessory_1.TSVESyncAccessory(this, accessory, this.client);
73
+ this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
74
+ }
75
+ }
76
+ // Remove accessories that are no longer present
77
+ this.accessories.forEach(accessory => {
78
+ const device = devices.find(device => this.api.hap.uuid.generate(device.id) === accessory.UUID);
79
+ if (!device) {
80
+ this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
81
+ }
82
+ });
83
+ }
84
+ catch (error) {
85
+ this.log.error('Failed to discover devices:', error);
86
+ }
87
+ }
88
+ /**
89
+ * Update all device states
90
+ */
91
+ async updateDeviceStates() {
92
+ try {
93
+ await this.client.login();
94
+ const devices = await this.client.getDevices();
95
+ this.accessories.forEach(accessory => {
96
+ const device = devices.find(d => this.api.hap.uuid.generate(d.id) === accessory.UUID);
97
+ if (device) {
98
+ accessory.context.device = device;
99
+ }
100
+ });
101
+ }
102
+ catch (error) {
103
+ this.log.error('Failed to update device states:', error);
104
+ }
105
+ }
106
+ }
107
+ exports.TSVESyncPlatform = TSVESyncPlatform;
108
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { PlatformAccessory, CharacteristicValue } from 'homebridge';
2
+ import { TSVESyncPlatform } from './platform';
3
+ import { TSVESync } from 'tsvesync';
4
+ export declare class TSVESyncAccessory {
5
+ private readonly platform;
6
+ private readonly accessory;
7
+ private readonly client;
8
+ private service;
9
+ constructor(platform: TSVESyncPlatform, accessory: PlatformAccessory, client: TSVESync);
10
+ private setupService;
11
+ /**
12
+ * Handle "SET" requests from HomeKit for power state
13
+ */
14
+ setOn(value: CharacteristicValue): Promise<void>;
15
+ /**
16
+ * Handle "GET" requests from HomeKit for power state
17
+ */
18
+ getOn(): Promise<CharacteristicValue>;
19
+ /**
20
+ * Handle "SET" requests from HomeKit for brightness
21
+ */
22
+ setBrightness(value: CharacteristicValue): Promise<void>;
23
+ /**
24
+ * Handle "GET" requests from HomeKit for brightness
25
+ */
26
+ getBrightness(): Promise<CharacteristicValue>;
27
+ }
28
+ //# sourceMappingURL=platformAccessory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platformAccessory.d.ts","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAU,MAAM,UAAU,CAAC;AAE5C,qBAAa,iBAAiB;IAI1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,OAAO,CAAW;gBAGP,QAAQ,EAAE,gBAAgB,EAC1B,SAAS,EAAE,iBAAiB,EAC5B,MAAM,EAAE,QAAQ;IAenC,OAAO,CAAC,YAAY;IAuBpB;;OAEG;IACG,KAAK,CAAC,KAAK,EAAE,mBAAmB;IAWtC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAY3C;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,mBAAmB;IAa9C;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,mBAAmB,CAAC;CAcpD"}
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TSVESyncAccessory = void 0;
4
+ class TSVESyncAccessory {
5
+ constructor(platform, accessory, client) {
6
+ this.platform = platform;
7
+ this.accessory = accessory;
8
+ this.client = client;
9
+ // Get the device from context
10
+ const device = accessory.context.device;
11
+ // set accessory information
12
+ this.accessory.getService(this.platform.Service.AccessoryInformation)
13
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, 'TSVESync')
14
+ .setCharacteristic(this.platform.Characteristic.Model, device.model || 'Unknown')
15
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.id);
16
+ // Set up the appropriate service based on device type
17
+ this.setupService(device);
18
+ }
19
+ setupService(device) {
20
+ // Determine the appropriate service type based on device capabilities
21
+ if (device.supportsBrightness) {
22
+ // Set up as a lightbulb if it supports brightness
23
+ this.service = this.accessory.getService(this.platform.Service.Lightbulb)
24
+ || this.accessory.addService(this.platform.Service.Lightbulb);
25
+ // Add brightness characteristic
26
+ this.service.getCharacteristic(this.platform.Characteristic.Brightness)
27
+ .onSet(this.setBrightness.bind(this))
28
+ .onGet(this.getBrightness.bind(this));
29
+ }
30
+ else {
31
+ // Set up as a switch for basic on/off functionality
32
+ this.service = this.accessory.getService(this.platform.Service.Switch)
33
+ || this.accessory.addService(this.platform.Service.Switch);
34
+ }
35
+ // Add on/off characteristic (common to both switch and lightbulb)
36
+ this.service.getCharacteristic(this.platform.Characteristic.On)
37
+ .onSet(this.setOn.bind(this))
38
+ .onGet(this.getOn.bind(this));
39
+ }
40
+ /**
41
+ * Handle "SET" requests from HomeKit for power state
42
+ */
43
+ async setOn(value) {
44
+ try {
45
+ const device = this.accessory.context.device;
46
+ await this.client.setPowerState(device.id, value);
47
+ this.platform.log.debug('Set power state ->', value);
48
+ }
49
+ catch (error) {
50
+ this.platform.log.error('Failed to set power state:', error);
51
+ throw error;
52
+ }
53
+ }
54
+ /**
55
+ * Handle "GET" requests from HomeKit for power state
56
+ */
57
+ async getOn() {
58
+ try {
59
+ const device = this.accessory.context.device;
60
+ const state = await this.client.getPowerState(device.id);
61
+ this.platform.log.debug('Get power state ->', state);
62
+ return state;
63
+ }
64
+ catch (error) {
65
+ this.platform.log.error('Failed to get power state:', error);
66
+ throw error;
67
+ }
68
+ }
69
+ /**
70
+ * Handle "SET" requests from HomeKit for brightness
71
+ */
72
+ async setBrightness(value) {
73
+ try {
74
+ const device = this.accessory.context.device;
75
+ if (device.supportsBrightness) {
76
+ await this.client.setBrightness(device.id, value);
77
+ this.platform.log.debug('Set brightness ->', value);
78
+ }
79
+ }
80
+ catch (error) {
81
+ this.platform.log.error('Failed to set brightness:', error);
82
+ throw error;
83
+ }
84
+ }
85
+ /**
86
+ * Handle "GET" requests from HomeKit for brightness
87
+ */
88
+ async getBrightness() {
89
+ try {
90
+ const device = this.accessory.context.device;
91
+ if (device.supportsBrightness) {
92
+ const brightness = await this.client.getBrightness(device.id);
93
+ this.platform.log.debug('Get brightness ->', brightness);
94
+ return brightness;
95
+ }
96
+ return 100;
97
+ }
98
+ catch (error) {
99
+ this.platform.log.error('Failed to get brightness:', error);
100
+ throw error;
101
+ }
102
+ }
103
+ }
104
+ exports.TSVESyncAccessory = TSVESyncAccessory;
105
+ //# sourceMappingURL=platformAccessory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platformAccessory.js","sourceRoot":"","sources":["../src/platformAccessory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
3
+ */
4
+ export declare const PLATFORM_NAME = "TSVESyncPlatform";
5
+ /**
6
+ * This must match the name of your plugin as defined the package.json
7
+ */
8
+ export declare const PLUGIN_NAME = "homebridge-tsvesync";
9
+ //# sourceMappingURL=settings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,aAAa,qBAAqB,CAAC;AAEhD;;GAEG;AACH,eAAO,MAAM,WAAW,wBAAwB,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PLUGIN_NAME = exports.PLATFORM_NAME = void 0;
4
+ /**
5
+ * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
6
+ */
7
+ exports.PLATFORM_NAME = 'TSVESyncPlatform';
8
+ /**
9
+ * This must match the name of your plugin as defined the package.json
10
+ */
11
+ exports.PLUGIN_NAME = 'homebridge-tsvesync';
12
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":";;;AAAA;;GAEG;AACU,QAAA,aAAa,GAAG,kBAAkB,CAAC;AAEhD;;GAEG;AACU,QAAA,WAAW,GAAG,qBAAqB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "homebridge-tsvesync",
3
+ "version": "1.0.0",
4
+ "description": "Homebridge plugin for TSVESync devices",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "lint": "eslint src/**.ts",
8
+ "watch": "npm run build && npm link && nodemon",
9
+ "build": "rimraf ./dist && tsc",
10
+ "prepublishOnly": "npm run lint && npm run build"
11
+ },
12
+ "keywords": [
13
+ "homebridge-plugin",
14
+ "tsvesync",
15
+ "etekcity",
16
+ "vesync",
17
+ "levoit",
18
+ "air purifier",
19
+ "humidifier",
20
+ "smart bulb",
21
+ "smart outlet",
22
+ "tower fan",
23
+ "Core200S",
24
+ "Core300S",
25
+ "Core400S",
26
+ "Core600S",
27
+ "LV-PUR131S",
28
+ "Vital100S",
29
+ "Vital200S",
30
+ "EverestAir",
31
+ "Classic300S",
32
+ "Classic200S",
33
+ "Dual200S",
34
+ "LV600S",
35
+ "OasisMist",
36
+ "OasisMist1000S",
37
+ "Superior6000S",
38
+ "ESL100",
39
+ "ESL100CW",
40
+ "ESL100MC",
41
+ "XYD0001",
42
+ "ESO15-TB",
43
+ "ESW15-USA",
44
+ "ESW03-USA",
45
+ "ESW01-EU",
46
+ "LTF-F422S",
47
+ "rgb-light",
48
+ "tunable-white",
49
+ "energy-monitoring",
50
+ "power-monitoring",
51
+ "voltage-monitoring",
52
+ "air-quality",
53
+ "humidity-control",
54
+ "fan-control",
55
+ "filter-life",
56
+ "night-light",
57
+ "warm-mist",
58
+ "auto-mode",
59
+ "sleep-mode",
60
+ "manual-mode",
61
+ "pet-mode",
62
+ "turbo-mode"
63
+ ],
64
+ "engines": {
65
+ "node": ">=14.18.1",
66
+ "homebridge": ">=1.3.5"
67
+ },
68
+ "dependencies": {
69
+ "tsvesync": "^1.0.11"
70
+ },
71
+ "devDependencies": {
72
+ "@types/node": "^16.10.9",
73
+ "@typescript-eslint/eslint-plugin": "^5.0.0",
74
+ "@typescript-eslint/parser": "^5.0.0",
75
+ "eslint": "^8.0.1",
76
+ "homebridge": "^1.3.5",
77
+ "nodemon": "^2.0.13",
78
+ "rimraf": "^3.0.2",
79
+ "ts-node": "^10.3.0",
80
+ "typescript": "^4.4.4"
81
+ }
82
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { API } from 'homebridge';
2
+ import { PLATFORM_NAME, PLUGIN_NAME } from './settings';
3
+ import { TSVESyncPlatform } from './platform';
4
+
5
+ export = (api: API) => {
6
+ api.registerPlatform(PLUGIN_NAME, PLATFORM_NAME, TSVESyncPlatform);
7
+ };
@@ -0,0 +1,122 @@
1
+ import { API, DynamicPlatformPlugin, Logger, PlatformAccessory, PlatformConfig, Service, Characteristic } from 'homebridge';
2
+ import { PLATFORM_NAME, PLUGIN_NAME } from './settings';
3
+ import { TSVESyncAccessory } from './platformAccessory';
4
+ import { TSVESync } from 'tsvesync';
5
+
6
+ export class TSVESyncPlatform implements DynamicPlatformPlugin {
7
+ public readonly Service: typeof Service = this.api.hap.Service;
8
+ public readonly Characteristic: typeof Characteristic = this.api.hap.Characteristic;
9
+
10
+ // this is used to track restored cached accessories
11
+ public readonly accessories: PlatformAccessory[] = [];
12
+
13
+ private client!: TSVESync;
14
+ private deviceUpdateInterval?: NodeJS.Timeout;
15
+
16
+ constructor(
17
+ public readonly log: Logger,
18
+ public readonly config: PlatformConfig,
19
+ public readonly api: API,
20
+ ) {
21
+ // Validate configuration
22
+ if (!config.username || !config.password) {
23
+ log.error('Missing required configuration. Please check your config.json');
24
+ return;
25
+ }
26
+
27
+ // Initialize TSVESync client
28
+ this.client = new TSVESync({
29
+ username: config.username,
30
+ password: config.password,
31
+ });
32
+
33
+ this.log.debug('Finished initializing platform:', this.config.name);
34
+
35
+ // When this event is fired it means Homebridge has restored all cached accessories from disk.
36
+ this.api.on('didFinishLaunching', () => {
37
+ log.debug('Executed didFinishLaunching callback');
38
+ // run the method to discover / register your devices as accessories
39
+ this.discoverDevices();
40
+
41
+ // Set up periodic device updates (every 30 seconds)
42
+ this.deviceUpdateInterval = setInterval(() => {
43
+ this.updateDeviceStates();
44
+ }, 30000);
45
+ });
46
+
47
+ // Clean up when shutting down
48
+ this.api.on('shutdown', () => {
49
+ if (this.deviceUpdateInterval) {
50
+ clearInterval(this.deviceUpdateInterval);
51
+ }
52
+ });
53
+ }
54
+
55
+ /**
56
+ * This function is invoked when homebridge restores cached accessories from disk at startup.
57
+ */
58
+ configureAccessory(accessory: PlatformAccessory) {
59
+ this.log.info('Loading accessory from cache:', accessory.displayName);
60
+ this.accessories.push(accessory);
61
+ }
62
+
63
+ /**
64
+ * Discover TSVESync devices and register them as accessories
65
+ */
66
+ async discoverDevices() {
67
+ try {
68
+ // Login and get devices
69
+ await this.client.login();
70
+ const devices = await this.client.getDevices();
71
+
72
+ // Loop over the discovered devices and register each one
73
+ for (const device of devices) {
74
+ const uuid = this.api.hap.uuid.generate(device.id);
75
+ const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
76
+
77
+ if (existingAccessory) {
78
+ this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
79
+ existingAccessory.context.device = device;
80
+ new TSVESyncAccessory(this, existingAccessory, this.client);
81
+
82
+ } else {
83
+ this.log.info('Adding new accessory:', device.name);
84
+ const accessory = new this.api.platformAccessory(device.name, uuid);
85
+ accessory.context.device = device;
86
+ new TSVESyncAccessory(this, accessory, this.client);
87
+ this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
88
+ }
89
+ }
90
+
91
+ // Remove accessories that are no longer present
92
+ this.accessories.forEach(accessory => {
93
+ const device = devices.find(device => this.api.hap.uuid.generate(device.id) === accessory.UUID);
94
+ if (!device) {
95
+ this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
96
+ }
97
+ });
98
+
99
+ } catch (error) {
100
+ this.log.error('Failed to discover devices:', error);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Update all device states
106
+ */
107
+ private async updateDeviceStates() {
108
+ try {
109
+ await this.client.login();
110
+ const devices = await this.client.getDevices();
111
+
112
+ this.accessories.forEach(accessory => {
113
+ const device = devices.find(d => this.api.hap.uuid.generate(d.id) === accessory.UUID);
114
+ if (device) {
115
+ accessory.context.device = device;
116
+ }
117
+ });
118
+ } catch (error) {
119
+ this.log.error('Failed to update device states:', error);
120
+ }
121
+ }
122
+ }
@@ -0,0 +1,111 @@
1
+ import { Service, PlatformAccessory, CharacteristicValue } from 'homebridge';
2
+ import { TSVESyncPlatform } from './platform';
3
+ import { TSVESync, Device } from 'tsvesync';
4
+
5
+ export class TSVESyncAccessory {
6
+ private service!: Service;
7
+
8
+ constructor(
9
+ private readonly platform: TSVESyncPlatform,
10
+ private readonly accessory: PlatformAccessory,
11
+ private readonly client: TSVESync,
12
+ ) {
13
+ // Get the device from context
14
+ const device = accessory.context.device as Device;
15
+
16
+ // set accessory information
17
+ this.accessory.getService(this.platform.Service.AccessoryInformation)!
18
+ .setCharacteristic(this.platform.Characteristic.Manufacturer, 'TSVESync')
19
+ .setCharacteristic(this.platform.Characteristic.Model, device.model || 'Unknown')
20
+ .setCharacteristic(this.platform.Characteristic.SerialNumber, device.id);
21
+
22
+ // Set up the appropriate service based on device type
23
+ this.setupService(device);
24
+ }
25
+
26
+ private setupService(device: Device) {
27
+ // Determine the appropriate service type based on device capabilities
28
+ if (device.supportsBrightness) {
29
+ // Set up as a lightbulb if it supports brightness
30
+ this.service = this.accessory.getService(this.platform.Service.Lightbulb)
31
+ || this.accessory.addService(this.platform.Service.Lightbulb);
32
+
33
+ // Add brightness characteristic
34
+ this.service.getCharacteristic(this.platform.Characteristic.Brightness)
35
+ .onSet(this.setBrightness.bind(this))
36
+ .onGet(this.getBrightness.bind(this));
37
+ } else {
38
+ // Set up as a switch for basic on/off functionality
39
+ this.service = this.accessory.getService(this.platform.Service.Switch)
40
+ || this.accessory.addService(this.platform.Service.Switch);
41
+ }
42
+
43
+ // Add on/off characteristic (common to both switch and lightbulb)
44
+ this.service.getCharacteristic(this.platform.Characteristic.On)
45
+ .onSet(this.setOn.bind(this))
46
+ .onGet(this.getOn.bind(this));
47
+ }
48
+
49
+ /**
50
+ * Handle "SET" requests from HomeKit for power state
51
+ */
52
+ async setOn(value: CharacteristicValue) {
53
+ try {
54
+ const device = this.accessory.context.device as Device;
55
+ await this.client.setPowerState(device.id, value as boolean);
56
+ this.platform.log.debug('Set power state ->', value);
57
+ } catch (error) {
58
+ this.platform.log.error('Failed to set power state:', error);
59
+ throw error;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Handle "GET" requests from HomeKit for power state
65
+ */
66
+ async getOn(): Promise<CharacteristicValue> {
67
+ try {
68
+ const device = this.accessory.context.device as Device;
69
+ const state = await this.client.getPowerState(device.id);
70
+ this.platform.log.debug('Get power state ->', state);
71
+ return state;
72
+ } catch (error) {
73
+ this.platform.log.error('Failed to get power state:', error);
74
+ throw error;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Handle "SET" requests from HomeKit for brightness
80
+ */
81
+ async setBrightness(value: CharacteristicValue) {
82
+ try {
83
+ const device = this.accessory.context.device as Device;
84
+ if (device.supportsBrightness) {
85
+ await this.client.setBrightness(device.id, value as number);
86
+ this.platform.log.debug('Set brightness ->', value);
87
+ }
88
+ } catch (error) {
89
+ this.platform.log.error('Failed to set brightness:', error);
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Handle "GET" requests from HomeKit for brightness
96
+ */
97
+ async getBrightness(): Promise<CharacteristicValue> {
98
+ try {
99
+ const device = this.accessory.context.device as Device;
100
+ if (device.supportsBrightness) {
101
+ const brightness = await this.client.getBrightness(device.id);
102
+ this.platform.log.debug('Get brightness ->', brightness);
103
+ return brightness;
104
+ }
105
+ return 100;
106
+ } catch (error) {
107
+ this.platform.log.error('Failed to get brightness:', error);
108
+ throw error;
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * This is the name of the platform that users will use to register the plugin in the Homebridge config.json
3
+ */
4
+ export const PLATFORM_NAME = 'TSVESyncPlatform';
5
+
6
+ /**
7
+ * This must match the name of your plugin as defined the package.json
8
+ */
9
+ export const PLUGIN_NAME = 'homebridge-tsvesync';
@@ -0,0 +1,26 @@
1
+ declare module 'tsvesync' {
2
+ export interface Device {
3
+ id: string;
4
+ name: string;
5
+ model?: string;
6
+ supportsBrightness?: boolean;
7
+ deviceType?: string;
8
+ deviceStatus?: string;
9
+ }
10
+
11
+ export interface TSVESyncConfig {
12
+ username: string;
13
+ password: string;
14
+ }
15
+
16
+ export class TSVESync {
17
+ constructor(config: TSVESyncConfig);
18
+
19
+ login(): Promise<void>;
20
+ getDevices(): Promise<Device[]>;
21
+ setPowerState(deviceId: string, state: boolean): Promise<void>;
22
+ getPowerState(deviceId: string): Promise<boolean>;
23
+ setBrightness(deviceId: string, brightness: number): Promise<void>;
24
+ getBrightness(deviceId: string): Promise<number>;
25
+ }
26
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2018",
4
+ "module": "commonjs",
5
+ "lib": [
6
+ "ES2015",
7
+ "ES2016",
8
+ "ES2017",
9
+ "ES2018"
10
+ ],
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "./src",
16
+ "strict": true,
17
+ "esModuleInterop": true,
18
+ "noImplicitAny": false,
19
+ "typeRoots": [
20
+ "./node_modules/@types",
21
+ "./src"
22
+ ]
23
+ },
24
+ "include": [
25
+ "src"
26
+ ],
27
+ "exclude": [
28
+ "**/*.spec.ts"
29
+ ]
30
+ }