homebridge-tsvesync 1.1.2 → 1.3.4

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +312 -0
  2. package/dist/accessories/air-purifier.accessory.d.ts +1 -0
  3. package/dist/accessories/air-purifier.accessory.d.ts.map +1 -1
  4. package/dist/accessories/air-purifier.accessory.js +38 -26
  5. package/dist/accessories/air-purifier.accessory.js.map +1 -1
  6. package/dist/accessories/light.accessory.d.ts +25 -3
  7. package/dist/accessories/light.accessory.d.ts.map +1 -1
  8. package/dist/accessories/light.accessory.js +290 -14
  9. package/dist/accessories/light.accessory.js.map +1 -1
  10. package/dist/platform.d.ts +18 -0
  11. package/dist/platform.d.ts.map +1 -1
  12. package/dist/platform.js +224 -11
  13. package/dist/platform.js.map +1 -1
  14. package/dist/types/device.types.d.ts +16 -0
  15. package/dist/types/device.types.d.ts.map +1 -1
  16. package/dist/utils/api-proxy.d.ts +4 -1
  17. package/dist/utils/api-proxy.d.ts.map +1 -1
  18. package/dist/utils/api-proxy.js +5 -3
  19. package/dist/utils/api-proxy.js.map +1 -1
  20. package/dist/utils/device-factory.d.ts +1 -0
  21. package/dist/utils/device-factory.d.ts.map +1 -1
  22. package/dist/utils/device-factory.js +10 -2
  23. package/dist/utils/device-factory.js.map +1 -1
  24. package/dist/utils/session-store.d.ts +28 -0
  25. package/dist/utils/session-store.d.ts.map +1 -0
  26. package/dist/utils/session-store.js +92 -0
  27. package/dist/utils/session-store.js.map +1 -0
  28. package/package.json +2 -2
  29. package/src/accessories/air-purifier.accessory.ts +38 -22
  30. package/src/accessories/light.accessory.ts +415 -34
  31. package/src/platform.ts +223 -9
  32. package/src/types/device.types.ts +18 -0
  33. package/src/utils/api-proxy.ts +8 -2
  34. package/src/utils/device-factory.ts +16 -4
  35. package/src/utils/session-store.ts +92 -0
@@ -24,7 +24,8 @@ const OUTLET_MODELS = [
24
24
  'ESO15-TB', 'ESW15-USA', 'ESW03-USA', 'ESW01-EU',
25
25
  'ESW10-USA', 'wifi-switch-1.3'
26
26
  ];
27
- const SWITCH_MODELS = ['ESWD16', 'ESWL01', 'ESWL03'];
27
+ const SWITCH_MODELS = ['ESWL01', 'ESWL03'];
28
+ const DIMMER_MODELS = ['ESWD16'];
28
29
  class DeviceFactory {
29
30
  static modelMatches(deviceType, models) {
30
31
  const upperDeviceType = deviceType.toUpperCase();
@@ -46,6 +47,9 @@ class DeviceFactory {
46
47
  static isBulb(deviceType) {
47
48
  return deviceType.startsWith('ESL') || deviceType === 'XYD0001';
48
49
  }
50
+ static isDimmer(deviceType) {
51
+ return this.modelMatches(deviceType, DIMMER_MODELS);
52
+ }
49
53
  static isOutlet(deviceType) {
50
54
  return this.modelMatches(deviceType, OUTLET_MODELS);
51
55
  }
@@ -66,6 +70,10 @@ class DeviceFactory {
66
70
  if (this.isFan(deviceType)) {
67
71
  return new fan_accessory_1.FanAccessory(platform, accessory, device);
68
72
  }
73
+ // Dimmers
74
+ if (this.isDimmer(deviceType)) {
75
+ return new light_accessory_1.LightAccessory(platform, accessory, device);
76
+ }
69
77
  // Bulbs
70
78
  if (this.isBulb(deviceType)) {
71
79
  return new light_accessory_1.LightAccessory(platform, accessory, device);
@@ -93,7 +101,7 @@ class DeviceFactory {
93
101
  if (this.isFan(type)) {
94
102
  return 3 /* Categories.FAN */;
95
103
  }
96
- if (this.isBulb(type)) {
104
+ if (this.isDimmer(type) || this.isBulb(type)) {
97
105
  return 5 /* Categories.LIGHTBULB */;
98
106
  }
99
107
  if (this.isSwitch(type)) {
@@ -1 +1 @@
1
- {"version":3,"file":"device-factory.js","sourceRoot":"","sources":["../../src/utils/device-factory.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"device-factory.js","sourceRoot":"","sources":["../../src/utils/device-factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { PluginLogger } from './logger';
2
+ export interface PluginSession {
3
+ token: string;
4
+ accountId: string;
5
+ countryCode?: string | null;
6
+ region: string;
7
+ apiBaseUrl: string;
8
+ authFlowUsed?: 'legacy' | 'new';
9
+ issuedAt?: number | null;
10
+ expiresAt?: number | null;
11
+ lastValidatedAt?: number | null;
12
+ libraryVersion?: string;
13
+ username?: string;
14
+ }
15
+ export declare class FileSessionStore {
16
+ private readonly logger;
17
+ private readonly dir;
18
+ private readonly file;
19
+ constructor(basePath: string, logger: PluginLogger);
20
+ load(): Promise<PluginSession | null>;
21
+ save(session: PluginSession): Promise<void>;
22
+ clear(): Promise<void>;
23
+ }
24
+ export declare function decodeJwtTimestampsLocal(token: string): {
25
+ iat?: number;
26
+ exp?: number;
27
+ } | null;
28
+ //# sourceMappingURL=session-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/utils/session-store.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,gBAAgB;IAIG,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHrD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAElB,QAAQ,EAAE,MAAM,EAAmB,MAAM,EAAE,YAAY;IAK7D,IAAI,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAsBrC,IAAI,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAiB3C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAS7B;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAa7F"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.decodeJwtTimestampsLocal = exports.FileSessionStore = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ class FileSessionStore {
10
+ constructor(basePath, logger) {
11
+ this.logger = logger;
12
+ this.dir = path_1.default.join(basePath, 'tsvesync');
13
+ this.file = path_1.default.join(this.dir, 'session.json');
14
+ }
15
+ async load() {
16
+ try {
17
+ await fs_1.default.promises.mkdir(this.dir, { recursive: true });
18
+ const data = await fs_1.default.promises.readFile(this.file, 'utf8');
19
+ const session = JSON.parse(data);
20
+ if (!session || !session.token || !session.accountId)
21
+ return null;
22
+ try {
23
+ const expSec = session.expiresAt && session.expiresAt > 1e11 ? Math.floor(session.expiresAt / 1000) : session.expiresAt;
24
+ const exp = expSec ? new Date(expSec * 1000).toISOString() : 'unknown';
25
+ this.logger.debug(`Loaded persisted session from ${this.file} (exp: ${exp})`);
26
+ }
27
+ catch (_a) { }
28
+ return session;
29
+ }
30
+ catch (e) {
31
+ if ((e === null || e === void 0 ? void 0 : e.code) === 'ENOENT') {
32
+ this.logger.debug(`No persisted session found at ${this.file}`);
33
+ }
34
+ else {
35
+ this.logger.debug(`Session load error: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
36
+ }
37
+ return null;
38
+ }
39
+ }
40
+ async save(session) {
41
+ try {
42
+ await fs_1.default.promises.mkdir(this.dir, { recursive: true });
43
+ const tmp = this.file + '.tmp';
44
+ await fs_1.default.promises.writeFile(tmp, JSON.stringify(session), { encoding: 'utf8', mode: 0o600 });
45
+ await fs_1.default.promises.rename(tmp, this.file);
46
+ try {
47
+ await fs_1.default.promises.chmod(this.file, 0o600);
48
+ }
49
+ catch ( /* best effort */_a) { /* best effort */ }
50
+ try {
51
+ const expSec = session.expiresAt && session.expiresAt > 1e11 ? Math.floor(session.expiresAt / 1000) : session.expiresAt;
52
+ const exp = expSec ? new Date(expSec * 1000).toISOString() : 'unknown';
53
+ this.logger.debug(`Persisted session to ${this.file} (exp: ${exp})`);
54
+ }
55
+ catch (_b) { }
56
+ }
57
+ catch (e) {
58
+ this.logger.debug(`Session save error: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
59
+ }
60
+ }
61
+ async clear() {
62
+ try {
63
+ await fs_1.default.promises.unlink(this.file);
64
+ }
65
+ catch (e) {
66
+ if ((e === null || e === void 0 ? void 0 : e.code) !== 'ENOENT') {
67
+ this.logger.debug(`Session clear error: ${(e === null || e === void 0 ? void 0 : e.message) || e}`);
68
+ }
69
+ }
70
+ }
71
+ }
72
+ exports.FileSessionStore = FileSessionStore;
73
+ function decodeJwtTimestampsLocal(token) {
74
+ try {
75
+ const parts = token.split('.');
76
+ if (parts.length !== 3)
77
+ return null;
78
+ const payload = JSON.parse(Buffer.from(parts[1].replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));
79
+ let iat = typeof payload.iat === 'number' ? payload.iat : undefined;
80
+ let exp = typeof payload.exp === 'number' ? payload.exp : undefined;
81
+ if (iat && iat > 1e11)
82
+ iat = Math.floor(iat / 1000);
83
+ if (exp && exp > 1e11)
84
+ exp = Math.floor(exp / 1000);
85
+ return { iat, exp };
86
+ }
87
+ catch (_a) {
88
+ return null;
89
+ }
90
+ }
91
+ exports.decodeJwtTimestampsLocal = decodeJwtTimestampsLocal;
92
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/utils/session-store.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-tsvesync",
3
- "version": "1.1.2",
3
+ "version": "1.3.4",
4
4
  "description": "Homebridge plugin for VeSync devices including Levoit air purifiers, humidifiers, and Etekcity smart outlets",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -98,7 +98,7 @@
98
98
  "axios": "^1.6.5",
99
99
  "dotenv": "^16.3.1",
100
100
  "js-yaml": "^4.1.0",
101
- "tsvesync": "1.1.2"
101
+ "tsvesync": "1.3.4"
102
102
  },
103
103
  "devDependencies": {
104
104
  "@types/jest": "^29.5.11",
@@ -291,13 +291,20 @@ export class AirPurifierAccessory extends BaseAccessory {
291
291
  return 3;
292
292
  }
293
293
 
294
+ private isCore200S(): boolean {
295
+ return (this.device.deviceType || '').toUpperCase().includes('CORE200S');
296
+ }
297
+
294
298
  /**
295
299
  * Calculate the appropriate step size for discrete speed levels
296
300
  */
297
301
  private calculateRotationSpeedStep(): number {
298
- // Expose Sleep as the first notch when supported: 0,25,50,75,100
302
+ // Expose Sleep as the first notch when supported.
303
+ // Core 200S uses 3 manual speeds (25% step). 4-speed models use 20% step.
299
304
  if (this.hasFeature('sleep_mode')) {
300
- return 25;
305
+ if (this.isCore200S()) return 25;
306
+ const max = this.getMaxFanSpeed();
307
+ return max >= 4 ? 20 : 25;
301
308
  }
302
309
  const maxSpeed = this.getMaxFanSpeed();
303
310
  if (maxSpeed === 3) return 33.33;
@@ -309,12 +316,14 @@ export class AirPurifierAccessory extends BaseAccessory {
309
316
  * Convert device speed to percentage
310
317
  */
311
318
  private speedToPercentage(speed: number): number {
312
- // Sleep as first notch: show manual 1→50, 2→75, 3+→100
319
+ // Sleep as first notch: map manual speeds to notches based on model
313
320
  if (this.hasFeature('sleep_mode')) {
314
321
  if (typeof speed !== 'number' || speed <= 0) return 0;
315
- if (speed === 1) return 50;
316
- if (speed === 2) return 75;
317
- return 100;
322
+ const step = this.calculateRotationSpeedStep();
323
+ const max = this.isCore200S() ? 3 : this.getMaxFanSpeed();
324
+ // Manual speeds occupy notches 2..(max+1)
325
+ const notch = Math.max(2, Math.min(max + 1, speed + 1));
326
+ return Math.min(100, notch * step);
318
327
  }
319
328
 
320
329
  const maxSpeed = this.getMaxFanSpeed();
@@ -377,13 +386,15 @@ export class AirPurifierAccessory extends BaseAccessory {
377
386
  * Convert percentage to device speed
378
387
  */
379
388
  private percentageToSpeed(percentage: number): number {
380
- // Sleep as first notch: round to nearest 25% and map 50/75/100 → 1/2/3
389
+ // Sleep as first notch: round to nearest step and map notches → speeds
381
390
  if (this.hasFeature('sleep_mode')) {
382
391
  if (typeof percentage !== 'number') return 1;
383
- const notch = Math.max(0, Math.min(4, Math.round(Math.min(100, Math.max(0, percentage)) / 25)));
384
- if (notch <= 2) return 1; // 50%
385
- if (notch === 3) return 2; // 75%
386
- return 3; // 100%
392
+ const step = this.calculateRotationSpeedStep();
393
+ const max = this.isCore200S() ? 3 : this.getMaxFanSpeed();
394
+ const maxNotch = max + 1; // include Sleep notch
395
+ const notch = Math.max(0, Math.min(maxNotch, Math.round(Math.min(100, Math.max(0, percentage)) / step)));
396
+ const manualNotch = Math.max(2, notch);
397
+ return Math.min(max, manualNotch - 1);
387
398
  }
388
399
 
389
400
  const maxSpeed = this.getMaxFanSpeed();
@@ -642,9 +653,10 @@ export class AirPurifierAccessory extends BaseAccessory {
642
653
 
643
654
  // Update rotation speed
644
655
  if (isOn && this.hasFeature('sleep_mode') && isSleep) {
645
- this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 25);
656
+ const step = this.calculateRotationSpeedStep();
657
+ this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, step);
646
658
  this.lastSetSpeed = 0;
647
- this.lastSetPercentage = 25;
659
+ this.lastSetPercentage = step;
648
660
  } else if (isOn && details.speed !== undefined && details.speed !== null) {
649
661
  // Check if we should skip this update
650
662
  if (this.skipNextUpdate) {
@@ -756,7 +768,7 @@ export class AirPurifierAccessory extends BaseAccessory {
756
768
 
757
769
  // For all other devices, including Core300S, normal mode handling
758
770
  const mode = targetState === 1 ? 'auto' : 'manual';
759
- this.platform.log.debug(`Setting mode to ${mode} for device: ${this.device.deviceName}`);
771
+ this.platform.log.info(`Changing mode to ${mode} for device: ${this.device.deviceName}`);
760
772
 
761
773
  let success = false;
762
774
 
@@ -795,7 +807,7 @@ export class AirPurifierAccessory extends BaseAccessory {
795
807
  // If in sleep mode, surface the first notch
796
808
  const extended = this.device as ExtendedVeSyncAirPurifier;
797
809
  if (this.hasFeature('sleep_mode') && (extended.mode || '').toLowerCase() === 'sleep') {
798
- return 25;
810
+ return this.calculateRotationSpeedStep();
799
811
  }
800
812
  return 0;
801
813
  }
@@ -866,10 +878,13 @@ export class AirPurifierAccessory extends BaseAccessory {
866
878
 
867
879
  // When using sleep-as-first-notch, map slider notches robustly by rounding
868
880
  if (this.hasFeature('sleep_mode')) {
869
- const notch = Math.max(0, Math.min(4, Math.round(percentage / 25)));
881
+ const max = this.isCore200S() ? 3 : this.getMaxFanSpeed();
882
+ const step = this.calculateRotationSpeedStep();
883
+ const maxNotch = max + 1; // include Sleep notch
884
+ const notch = Math.max(0, Math.min(maxNotch, Math.round(percentage / step)));
870
885
  if (notch <= 1) {
871
886
  // Sleep
872
- this.platform.log.debug(`Setting sleep mode via slider on ${this.device.deviceName} (notch=${notch})`);
887
+ this.platform.log.info(`Changing mode to sleep for device: ${this.device.deviceName}`);
873
888
  let ok = false;
874
889
  if (typeof extendedDevice.sleepMode === 'function') {
875
890
  ok = await extendedDevice.sleepMode();
@@ -880,19 +895,20 @@ export class AirPurifierAccessory extends BaseAccessory {
880
895
  throw new Error('Failed to set sleep mode');
881
896
  }
882
897
  // Immediate UI feedback
883
- this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, 25);
898
+ this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, step);
884
899
  this.service.updateCharacteristic(this.platform.Characteristic.Active, 1);
885
900
  this.service.updateCharacteristic(this.platform.Characteristic.CurrentAirPurifierState, 2);
886
901
  this.lastSetSpeed = 0;
887
- this.lastSetPercentage = 25;
902
+ this.lastSetPercentage = step;
888
903
  this.skipNextUpdate = true;
889
904
  return;
890
905
  } else {
891
- // Map notches 2/3/4 to speeds 1/2/3
892
- const desiredSpeed = Math.min(3, notch - 1);
906
+ // Map manual notches (2..maxNotch) to speeds 1..max
907
+ const desiredSpeed = Math.min(max, notch - 1);
893
908
  // Ensure manual mode before speed change if necessary
894
909
  const modeNow = (extendedDevice.mode || '').toLowerCase();
895
910
  if (modeNow === 'auto' || modeNow === 'sleep') {
911
+ this.platform.log.info(`Changing mode to manual for device: ${this.device.deviceName}`);
896
912
  if (typeof extendedDevice.manualMode === 'function') {
897
913
  await extendedDevice.manualMode();
898
914
  } else if (typeof extendedDevice.setMode === 'function') {
@@ -900,7 +916,7 @@ export class AirPurifierAccessory extends BaseAccessory {
900
916
  }
901
917
  }
902
918
  // Update UI early
903
- const uiPct = desiredSpeed === 1 ? 50 : desiredSpeed === 2 ? 75 : 100;
919
+ const uiPct = Math.min(100, (desiredSpeed + 1) * step);
904
920
  this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, uiPct);
905
921
  this.lastSetSpeed = desiredSpeed;
906
922
  this.lastSetPercentage = uiPct;