tsvesync 1.3.10 โ†’ 1.3.12

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/README.md CHANGED
@@ -4,6 +4,9 @@ A TypeScript library for interacting with VeSync smart home devices. This librar
4
4
 
5
5
  ## ๐Ÿ†• Recent Updates
6
6
 
7
+ - **ESWD16 State Parity (v1.3.12)**: Bypass calls now reuse pyvesync-style trace IDs and mark the dimmer on after backlight tweaks, eliminating the lingering โ€œoffโ€ state in HomeKit.
8
+ - **ESWD16 Bypass Compatibility (v1.3.11)**: Migrated the dimmer switch to the modern bypass-v1 API so power, brightness, and indicator updates stay in sync with pyvesync.
9
+ - **ESL Bulb API Parity (v1.3.10)**: ESL100/ESL100CW/ESL100MC now mirror pyvesync `/SmartBulb/v1` and `/cloud/v2` payloads for brightness and colour control.
7
10
  - **Enhanced Authentication**: Now supports the new VeSync authentication flow (pyvesync PR #340) with automatic fallback to legacy authentication
8
11
  - **Regional API Support**: Automatic detection and routing for US (`smartapi.vesync.com`) and EU (`smartapi.vesync.eu`) endpoints
9
12
  - **International Account Support**: Full support for accounts worldwide including Australia, New Zealand, Japan, and all European countries
@@ -52,6 +52,7 @@ export declare function detectUserRegion(email: string): string;
52
52
  export declare function getCountryCodeFromRegion(region: string, email?: string): string;
53
53
  export declare function generateAppId(): string;
54
54
  export declare function generateTerminalId(): string;
55
+ export declare function generateTraceId(): string;
55
56
  export interface RequestBody {
56
57
  acceptLanguage?: string;
57
58
  accountID?: string;
@@ -104,6 +105,7 @@ export interface LoginResponse {
104
105
  }
105
106
  export declare class Helpers {
106
107
  static shouldRedact: boolean;
108
+ static generateTraceId(): string;
107
109
  /**
108
110
  * Calculate MD5 hash
109
111
  */
@@ -18,6 +18,7 @@ exports.detectUserRegion = detectUserRegion;
18
18
  exports.getCountryCodeFromRegion = getCountryCodeFromRegion;
19
19
  exports.generateAppId = generateAppId;
20
20
  exports.generateTerminalId = generateTerminalId;
21
+ exports.generateTraceId = generateTraceId;
21
22
  const axios_1 = __importDefault(require("axios"));
22
23
  const crypto_1 = __importDefault(require("crypto"));
23
24
  const logger_1 = require("./logger");
@@ -172,7 +173,15 @@ function generateTerminalId() {
172
173
  }
173
174
  return result;
174
175
  }
176
+ function generateTraceId() {
177
+ const timestamp = Math.floor(Date.now() / 1000);
178
+ const randomPart = Math.floor(Math.random() * 100000).toString().padStart(5, '0');
179
+ return `APP${exports.MOBILE_ID.slice(-5, -1)}${timestamp}-${randomPart}`;
180
+ }
175
181
  class Helpers {
182
+ static generateTraceId() {
183
+ return generateTraceId();
184
+ }
176
185
  /**
177
186
  * Calculate MD5 hash
178
187
  */
@@ -29,10 +29,7 @@ export declare class VeSyncWallSwitch extends VeSyncSwitch {
29
29
  * Dimmer Switch Implementation (ESWD16)
30
30
  */
31
31
  export declare class VeSyncDimmerSwitch extends VeSyncSwitch {
32
- private _brightness;
33
- private _rgbValue;
34
- private _rgbStatus;
35
- private _indicatorLight;
32
+ private stateSnapshot;
36
33
  constructor(details: Record<string, any>, manager: VeSync);
37
34
  /**
38
35
  * Get dimmer switch details
@@ -78,5 +75,9 @@ export declare class VeSyncDimmerSwitch extends VeSyncSwitch {
78
75
  get indicatorLightStatus(): string;
79
76
  get rgbLightStatus(): string;
80
77
  get rgbLightValue(): Record<string, number>;
78
+ private toggleSwitch;
79
+ private toggleIndicatorLight;
80
+ private buildDimmerRequest;
81
+ private callDimmerCommand;
81
82
  }
82
83
  export declare const switchModules: Record<string, new (details: Record<string, any>, manager: VeSync) => VeSyncSwitch>;
@@ -101,39 +101,44 @@ exports.VeSyncWallSwitch = VeSyncWallSwitch;
101
101
  class VeSyncDimmerSwitch extends vesyncSwitch_1.VeSyncSwitch {
102
102
  constructor(details, manager) {
103
103
  super(details, manager);
104
- this._brightness = 0;
105
- this._rgbValue = { red: 0, blue: 0, green: 0 };
106
- this._rgbStatus = 'unknown';
107
- this._indicatorLight = 'unknown';
104
+ this.stateSnapshot = {
105
+ activeTime: 0,
106
+ connectionStatus: 'offline',
107
+ brightness: 0,
108
+ rgbStatus: 'off',
109
+ indicatorStatus: 'off',
110
+ rgbValue: { red: 0, green: 0, blue: 0 },
111
+ deviceStatus: 'off'
112
+ };
108
113
  logger_1.logger.debug(`Initialized VeSyncDimmerSwitch device: ${this.deviceName}`);
109
114
  }
110
115
  /**
111
116
  * Get dimmer switch details
112
117
  */
113
118
  async getDetails() {
119
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
114
120
  logger_1.logger.debug(`Getting details for device: ${this.deviceName}`);
115
- const body = {
116
- ...helpers_1.Helpers.reqBody(this.manager, 'devicedetail'),
117
- uuid: this.uuid,
118
- method: 'devicedetail'
119
- };
120
- const [response, statusCode] = await this.callApi('/dimmer/v1/device/devicedetail', 'post', body, helpers_1.Helpers.reqHeaders(this.manager));
121
+ const [response, statusCode] = await this.callDimmerCommand('deviceDetail', {});
121
122
  const success = this.checkResponse([response, statusCode], 'getDetails');
122
- if (success && (response === null || response === void 0 ? void 0 : response.result)) {
123
- const result = response.result;
124
- this.deviceStatus = result.deviceStatus || this.deviceStatus;
125
- this.details.active_time = result.activeTime || 0;
126
- this.connectionStatus = result.connectionStatus || this.connectionStatus;
127
- this._brightness = result.brightness || this._brightness;
128
- this._rgbStatus = result.rgbStatus || this._rgbStatus;
129
- this._indicatorLight = result.indicatorlightStatus || this._indicatorLight;
130
- if (result.rgbValue) {
131
- this._rgbValue = {
132
- red: result.rgbValue.red || 0,
133
- green: result.rgbValue.green || 0,
134
- blue: result.rgbValue.blue || 0
135
- };
136
- }
123
+ const resultWrapper = (_b = (_a = response === null || response === void 0 ? void 0 : response.result) === null || _a === void 0 ? void 0 : _a.result) !== null && _b !== void 0 ? _b : response === null || response === void 0 ? void 0 : response.result;
124
+ if (success && resultWrapper) {
125
+ const result = resultWrapper;
126
+ this.stateSnapshot = {
127
+ activeTime: (_c = result.activeTime) !== null && _c !== void 0 ? _c : this.stateSnapshot.activeTime,
128
+ connectionStatus: (_d = result.connectionStatus) !== null && _d !== void 0 ? _d : this.stateSnapshot.connectionStatus,
129
+ brightness: Number((_e = result.brightness) !== null && _e !== void 0 ? _e : this.stateSnapshot.brightness),
130
+ rgbStatus: (_f = result.rgbStatus) !== null && _f !== void 0 ? _f : this.stateSnapshot.rgbStatus,
131
+ indicatorStatus: (_g = result.indicatorlightStatus) !== null && _g !== void 0 ? _g : this.stateSnapshot.indicatorStatus,
132
+ rgbValue: {
133
+ red: Number((_j = (_h = result.rgbValue) === null || _h === void 0 ? void 0 : _h.red) !== null && _j !== void 0 ? _j : this.stateSnapshot.rgbValue.red),
134
+ green: Number((_l = (_k = result.rgbValue) === null || _k === void 0 ? void 0 : _k.green) !== null && _l !== void 0 ? _l : this.stateSnapshot.rgbValue.green),
135
+ blue: Number((_o = (_m = result.rgbValue) === null || _m === void 0 ? void 0 : _m.blue) !== null && _o !== void 0 ? _o : this.stateSnapshot.rgbValue.blue)
136
+ },
137
+ deviceStatus: (_p = result.deviceStatus) !== null && _p !== void 0 ? _p : this.stateSnapshot.deviceStatus
138
+ };
139
+ this.details.active_time = this.stateSnapshot.activeTime;
140
+ this.connectionStatus = this.stateSnapshot.connectionStatus;
141
+ this.deviceStatus = this.stateSnapshot.deviceStatus;
137
142
  logger_1.logger.debug(`Successfully got details for device: ${this.deviceName}`);
138
143
  }
139
144
  return success;
@@ -161,39 +166,13 @@ class VeSyncDimmerSwitch extends vesyncSwitch_1.VeSyncSwitch {
161
166
  * Turn off dimmer switch
162
167
  */
163
168
  async turnOff() {
164
- logger_1.logger.info(`Turning off device: ${this.deviceName}`);
165
- const body = {
166
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
167
- status: 'off',
168
- uuid: this.uuid
169
- };
170
- const [response] = await this.callApi('/dimmer/v1/device/devicestatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
171
- if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
172
- this.deviceStatus = 'off';
173
- logger_1.logger.debug(`Successfully turned off device: ${this.deviceName}`);
174
- return true;
175
- }
176
- logger_1.logger.error(`Failed to turn off device: ${this.deviceName}`);
177
- return false;
169
+ return await this.toggleSwitch(false);
178
170
  }
179
171
  /**
180
172
  * Turn on dimmer switch
181
173
  */
182
174
  async turnOn() {
183
- logger_1.logger.info(`Turning on device: ${this.deviceName}`);
184
- const body = {
185
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
186
- status: 'on',
187
- uuid: this.uuid
188
- };
189
- const [response] = await this.callApi('/dimmer/v1/device/devicestatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
190
- if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
191
- this.deviceStatus = 'on';
192
- logger_1.logger.debug(`Successfully turned on device: ${this.deviceName}`);
193
- return true;
194
- }
195
- logger_1.logger.error(`Failed to turn on device: ${this.deviceName}`);
196
- return false;
175
+ return await this.toggleSwitch(true);
197
176
  }
198
177
  /**
199
178
  * Set brightness level
@@ -204,18 +183,20 @@ class VeSyncDimmerSwitch extends vesyncSwitch_1.VeSyncSwitch {
204
183
  return false;
205
184
  }
206
185
  logger_1.logger.debug(`Setting brightness to ${brightness} for device: ${this.deviceName}`);
207
- const body = {
208
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
209
- brightness,
210
- uuid: this.uuid
211
- };
212
- const [response] = await this.callApi('/dimmer/v1/device/updatebrightness', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
186
+ const value = Math.max(0, Math.min(100, Math.round(brightness)));
187
+ const [response] = await this.callDimmerCommand('dimmerBrightnessCtl', {
188
+ brightness: value
189
+ });
213
190
  if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
214
- this._brightness = brightness;
215
- logger_1.logger.debug(`Successfully set brightness to ${brightness} for device: ${this.deviceName}`);
191
+ this.stateSnapshot.brightness = value;
192
+ this.stateSnapshot.deviceStatus = 'on';
193
+ this.deviceStatus = 'on';
194
+ this.stateSnapshot.connectionStatus = 'online';
195
+ this.connectionStatus = 'online';
196
+ logger_1.logger.debug(`Successfully set brightness to ${value} for device: ${this.deviceName}`);
216
197
  return true;
217
198
  }
218
- logger_1.logger.error(`Failed to set brightness to ${brightness} for device: ${this.deviceName}`);
199
+ logger_1.logger.error(`Failed to set brightness to ${value} for device: ${this.deviceName}`);
219
200
  return false;
220
201
  }
221
202
  /**
@@ -223,20 +204,21 @@ class VeSyncDimmerSwitch extends vesyncSwitch_1.VeSyncSwitch {
223
204
  */
224
205
  async rgbColorSet(red, green, blue) {
225
206
  logger_1.logger.debug(`Setting RGB color to (${red}, ${green}, ${blue}) for device: ${this.deviceName}`);
226
- const body = {
227
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
207
+ const [response] = await this.callDimmerCommand('dimmerRgbValueCtl', {
208
+ status: 'on',
228
209
  rgbValue: {
229
210
  red: Math.round(red),
230
211
  green: Math.round(green),
231
212
  blue: Math.round(blue)
232
- },
233
- status: 'on',
234
- uuid: this.uuid
235
- };
236
- const [response] = await this.callApi('/dimmer/v1/device/devicergbstatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
213
+ }
214
+ });
237
215
  if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
238
- this._rgbValue = { red, green, blue };
239
- this._rgbStatus = 'on';
216
+ this.stateSnapshot.rgbValue = { red: Math.round(red), green: Math.round(green), blue: Math.round(blue) };
217
+ this.stateSnapshot.rgbStatus = 'on';
218
+ this.stateSnapshot.deviceStatus = 'on';
219
+ this.deviceStatus = 'on';
220
+ this.stateSnapshot.connectionStatus = 'online';
221
+ this.connectionStatus = 'online';
240
222
  logger_1.logger.debug(`Successfully set RGB color for device: ${this.deviceName}`);
241
223
  return true;
242
224
  }
@@ -248,14 +230,15 @@ class VeSyncDimmerSwitch extends vesyncSwitch_1.VeSyncSwitch {
248
230
  */
249
231
  async rgbColorOff() {
250
232
  logger_1.logger.debug(`Turning off RGB color for device: ${this.deviceName}`);
251
- const body = {
252
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
253
- status: 'off',
254
- uuid: this.uuid
255
- };
256
- const [response] = await this.callApi('/dimmer/v1/device/devicergbstatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
233
+ const [response] = await this.callDimmerCommand('dimmerRgbValueCtl', {
234
+ status: 'off'
235
+ });
257
236
  if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
258
- this._rgbStatus = 'off';
237
+ this.stateSnapshot.rgbStatus = 'off';
238
+ this.stateSnapshot.deviceStatus = 'on';
239
+ this.deviceStatus = 'on';
240
+ this.stateSnapshot.connectionStatus = 'online';
241
+ this.connectionStatus = 'online';
259
242
  logger_1.logger.debug(`Successfully turned off RGB color for device: ${this.deviceName}`);
260
243
  return true;
261
244
  }
@@ -267,14 +250,15 @@ class VeSyncDimmerSwitch extends vesyncSwitch_1.VeSyncSwitch {
267
250
  */
268
251
  async rgbColorOn() {
269
252
  logger_1.logger.debug(`Turning on RGB color for device: ${this.deviceName}`);
270
- const body = {
271
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
272
- uuid: this.uuid,
253
+ const [response] = await this.callDimmerCommand('dimmerRgbValueCtl', {
273
254
  status: 'on'
274
- };
275
- const [response] = await this.callApi('/dimmer/v1/device/devicergbstatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
255
+ });
276
256
  if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
277
- this._rgbStatus = 'on';
257
+ this.stateSnapshot.rgbStatus = 'on';
258
+ this.stateSnapshot.deviceStatus = 'on';
259
+ this.deviceStatus = 'on';
260
+ this.stateSnapshot.connectionStatus = 'online';
261
+ this.connectionStatus = 'online';
278
262
  logger_1.logger.debug(`Successfully turned on RGB color for device: ${this.deviceName}`);
279
263
  return true;
280
264
  }
@@ -286,51 +270,84 @@ class VeSyncDimmerSwitch extends vesyncSwitch_1.VeSyncSwitch {
286
270
  */
287
271
  async indicatorLightOn() {
288
272
  logger_1.logger.debug(`Turning on indicator light for device: ${this.deviceName}`);
289
- const body = {
290
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
291
- uuid: this.uuid,
292
- status: 'on'
293
- };
294
- const [response] = await this.callApi('/dimmer/v1/device/indicatorlightstatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
295
- if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
296
- this._indicatorLight = 'on';
297
- logger_1.logger.debug(`Successfully turned on indicator light for device: ${this.deviceName}`);
298
- return true;
299
- }
300
- logger_1.logger.error(`Failed to turn on indicator light for device: ${this.deviceName}`);
301
- return false;
273
+ return this.toggleIndicatorLight(true);
302
274
  }
303
275
  /**
304
276
  * Turn indicator light off
305
277
  */
306
278
  async indicatorLightOff() {
307
279
  logger_1.logger.debug(`Turning off indicator light for device: ${this.deviceName}`);
308
- const body = {
309
- ...helpers_1.Helpers.reqBody(this.manager, 'devicestatus'),
310
- uuid: this.uuid,
311
- status: 'off'
312
- };
313
- const [response] = await this.callApi('/dimmer/v1/device/indicatorlightstatus', 'put', body, helpers_1.Helpers.reqHeaders(this.manager));
314
- if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
315
- this._indicatorLight = 'off';
316
- logger_1.logger.debug(`Successfully turned off indicator light for device: ${this.deviceName}`);
317
- return true;
318
- }
319
- logger_1.logger.error(`Failed to turn off indicator light for device: ${this.deviceName}`);
320
- return false;
280
+ return this.toggleIndicatorLight(false);
321
281
  }
322
282
  // Getters
323
283
  get brightness() {
324
- return this._brightness;
284
+ return this.stateSnapshot.brightness;
325
285
  }
326
286
  get indicatorLightStatus() {
327
- return this._indicatorLight;
287
+ return this.stateSnapshot.indicatorStatus;
328
288
  }
329
289
  get rgbLightStatus() {
330
- return this._rgbStatus;
290
+ return this.stateSnapshot.rgbStatus;
331
291
  }
332
292
  get rgbLightValue() {
333
- return { ...this._rgbValue };
293
+ return { ...this.stateSnapshot.rgbValue };
294
+ }
295
+ async toggleSwitch(turnOn) {
296
+ const [response] = await this.callDimmerCommand('dimmerPowerSwitchCtl', {
297
+ status: turnOn ? 'on' : 'off'
298
+ });
299
+ if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
300
+ this.stateSnapshot.deviceStatus = turnOn ? 'on' : 'off';
301
+ this.deviceStatus = this.stateSnapshot.deviceStatus;
302
+ this.stateSnapshot.connectionStatus = 'online';
303
+ this.connectionStatus = 'online';
304
+ logger_1.logger.debug(`Successfully toggled device: ${this.deviceName} to ${this.deviceStatus}`);
305
+ return true;
306
+ }
307
+ logger_1.logger.error(`Failed to toggle device: ${this.deviceName}`);
308
+ return false;
309
+ }
310
+ async toggleIndicatorLight(turnOn) {
311
+ const [response] = await this.callDimmerCommand('dimmerIndicatorLightCtl', {
312
+ status: turnOn ? 'on' : 'off'
313
+ });
314
+ if ((response === null || response === void 0 ? void 0 : response.code) === 0) {
315
+ this.stateSnapshot.indicatorStatus = turnOn ? 'on' : 'off';
316
+ this.stateSnapshot.connectionStatus = 'online';
317
+ this.connectionStatus = 'online';
318
+ logger_1.logger.debug(`Successfully toggled indicator light for device: ${this.deviceName}`);
319
+ return true;
320
+ }
321
+ logger_1.logger.error(`Failed to toggle indicator light for device: ${this.deviceName}`);
322
+ return false;
323
+ }
324
+ buildDimmerRequest(command, data = {}) {
325
+ var _a, _b, _c, _d;
326
+ const manager = this.manager;
327
+ const traceId = helpers_1.Helpers.generateTraceId();
328
+ return {
329
+ acceptLanguage: 'en',
330
+ accountID: (_a = manager.accountId) !== null && _a !== void 0 ? _a : '',
331
+ appVersion: helpers_1.APP_VERSION,
332
+ cid: this.cid,
333
+ configModule: this.configModule,
334
+ configModel: this.configModule,
335
+ debugMode: false,
336
+ deviceId: this.cid,
337
+ method: command,
338
+ phoneBrand: helpers_1.PHONE_BRAND,
339
+ phoneOS: helpers_1.PHONE_OS,
340
+ traceId,
341
+ timeZone: (_b = manager.timeZone) !== null && _b !== void 0 ? _b : helpers_1.DEFAULT_TZ,
342
+ token: (_c = manager.token) !== null && _c !== void 0 ? _c : '',
343
+ userCountryCode: (_d = manager.countryCode) !== null && _d !== void 0 ? _d : helpers_1.DEFAULT_REGION,
344
+ uuid: this.uuid,
345
+ ...data
346
+ };
347
+ }
348
+ async callDimmerCommand(command, data) {
349
+ const request = this.buildDimmerRequest(command, data);
350
+ return await this.callApi(`/cloud/v1/deviceManaged/${command}`, 'post', request, helpers_1.Helpers.reqHeaderBypass());
334
351
  }
335
352
  }
336
353
  exports.VeSyncDimmerSwitch = VeSyncDimmerSwitch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsvesync",
3
- "version": "1.3.10",
3
+ "version": "1.3.12",
4
4
  "description": "A TypeScript library for interacting with VeSync smart home devices",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -177,6 +177,12 @@ export function generateTerminalId(): string {
177
177
  return result;
178
178
  }
179
179
 
180
+ export function generateTraceId(): string {
181
+ const timestamp = Math.floor(Date.now() / 1000);
182
+ const randomPart = Math.floor(Math.random() * 100000).toString().padStart(5, '0');
183
+ return `APP${MOBILE_ID.slice(-5, -1)}${timestamp}-${randomPart}`;
184
+ }
185
+
180
186
  export interface RequestBody {
181
187
  acceptLanguage?: string;
182
188
  accountID?: string;
@@ -235,6 +241,10 @@ export interface LoginResponse {
235
241
  export class Helpers {
236
242
  static shouldRedact = true;
237
243
 
244
+ static generateTraceId(): string {
245
+ return generateTraceId();
246
+ }
247
+
238
248
  /**
239
249
  * Calculate MD5 hash
240
250
  */
@@ -4,9 +4,19 @@
4
4
 
5
5
  import { VeSyncSwitch } from './vesyncSwitch';
6
6
  import { VeSync } from './vesync';
7
- import { Helpers } from './helpers';
7
+ import { Helpers, APP_VERSION, PHONE_BRAND, PHONE_OS, DEFAULT_TZ, DEFAULT_REGION } from './helpers';
8
8
  import { logger } from './logger';
9
9
 
10
+ interface DimmerState {
11
+ activeTime: number;
12
+ connectionStatus: string;
13
+ brightness: number;
14
+ rgbStatus: string;
15
+ indicatorStatus: string;
16
+ rgbValue: { red: number; green: number; blue: number };
17
+ deviceStatus: string;
18
+ }
19
+
10
20
  /**
11
21
  * Basic Wall Switch Implementation (ESWL01, ESWL03)
12
22
  */
@@ -130,10 +140,15 @@ export class VeSyncWallSwitch extends VeSyncSwitch {
130
140
  * Dimmer Switch Implementation (ESWD16)
131
141
  */
132
142
  export class VeSyncDimmerSwitch extends VeSyncSwitch {
133
- private _brightness: number = 0;
134
- private _rgbValue: Record<string, number> = { red: 0, blue: 0, green: 0 };
135
- private _rgbStatus: string = 'unknown';
136
- private _indicatorLight: string = 'unknown';
143
+ private stateSnapshot: DimmerState = {
144
+ activeTime: 0,
145
+ connectionStatus: 'offline',
146
+ brightness: 0,
147
+ rgbStatus: 'off',
148
+ indicatorStatus: 'off',
149
+ rgbValue: { red: 0, green: 0, blue: 0 },
150
+ deviceStatus: 'off'
151
+ };
137
152
 
138
153
  constructor(details: Record<string, any>, manager: VeSync) {
139
154
  super(details, manager);
@@ -145,35 +160,28 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
145
160
  */
146
161
  async getDetails(): Promise<Boolean> {
147
162
  logger.debug(`Getting details for device: ${this.deviceName}`);
148
- const body = {
149
- ...Helpers.reqBody(this.manager, 'devicedetail'),
150
- uuid: this.uuid,
151
- method: 'devicedetail'
152
- };
153
-
154
- const [response, statusCode] = await this.callApi(
155
- '/dimmer/v1/device/devicedetail',
156
- 'post',
157
- body,
158
- Helpers.reqHeaders(this.manager)
159
- );
163
+ const [response, statusCode] = await this.callDimmerCommand('deviceDetail', {});
160
164
 
161
165
  const success = this.checkResponse([response, statusCode], 'getDetails');
162
- if (success && response?.result) {
163
- const result = response.result;
164
- this.deviceStatus = result.deviceStatus || this.deviceStatus;
165
- this.details.active_time = result.activeTime || 0;
166
- this.connectionStatus = result.connectionStatus || this.connectionStatus;
167
- this._brightness = result.brightness || this._brightness;
168
- this._rgbStatus = result.rgbStatus || this._rgbStatus;
169
- this._indicatorLight = result.indicatorlightStatus || this._indicatorLight;
170
- if (result.rgbValue) {
171
- this._rgbValue = {
172
- red: result.rgbValue.red || 0,
173
- green: result.rgbValue.green || 0,
174
- blue: result.rgbValue.blue || 0
175
- };
176
- }
166
+ const resultWrapper = response?.result?.result ?? response?.result;
167
+ if (success && resultWrapper) {
168
+ const result = resultWrapper;
169
+ this.stateSnapshot = {
170
+ activeTime: result.activeTime ?? this.stateSnapshot.activeTime,
171
+ connectionStatus: result.connectionStatus ?? this.stateSnapshot.connectionStatus,
172
+ brightness: Number(result.brightness ?? this.stateSnapshot.brightness),
173
+ rgbStatus: result.rgbStatus ?? this.stateSnapshot.rgbStatus,
174
+ indicatorStatus: result.indicatorlightStatus ?? this.stateSnapshot.indicatorStatus,
175
+ rgbValue: {
176
+ red: Number(result.rgbValue?.red ?? this.stateSnapshot.rgbValue.red),
177
+ green: Number(result.rgbValue?.green ?? this.stateSnapshot.rgbValue.green),
178
+ blue: Number(result.rgbValue?.blue ?? this.stateSnapshot.rgbValue.blue)
179
+ },
180
+ deviceStatus: result.deviceStatus ?? this.stateSnapshot.deviceStatus
181
+ };
182
+ this.details.active_time = this.stateSnapshot.activeTime;
183
+ this.connectionStatus = this.stateSnapshot.connectionStatus;
184
+ this.deviceStatus = this.stateSnapshot.deviceStatus;
177
185
  logger.debug(`Successfully got details for device: ${this.deviceName}`);
178
186
  }
179
187
  return success;
@@ -209,54 +217,14 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
209
217
  * Turn off dimmer switch
210
218
  */
211
219
  async turnOff(): Promise<boolean> {
212
- logger.info(`Turning off device: ${this.deviceName}`);
213
- const body = {
214
- ...Helpers.reqBody(this.manager, 'devicestatus'),
215
- status: 'off',
216
- uuid: this.uuid
217
- };
218
-
219
- const [response] = await this.callApi(
220
- '/dimmer/v1/device/devicestatus',
221
- 'put',
222
- body,
223
- Helpers.reqHeaders(this.manager)
224
- );
225
-
226
- if (response?.code === 0) {
227
- this.deviceStatus = 'off';
228
- logger.debug(`Successfully turned off device: ${this.deviceName}`);
229
- return true;
230
- }
231
- logger.error(`Failed to turn off device: ${this.deviceName}`);
232
- return false;
220
+ return await this.toggleSwitch(false);
233
221
  }
234
222
 
235
223
  /**
236
224
  * Turn on dimmer switch
237
225
  */
238
226
  async turnOn(): Promise<boolean> {
239
- logger.info(`Turning on device: ${this.deviceName}`);
240
- const body = {
241
- ...Helpers.reqBody(this.manager, 'devicestatus'),
242
- status: 'on',
243
- uuid: this.uuid
244
- };
245
-
246
- const [response] = await this.callApi(
247
- '/dimmer/v1/device/devicestatus',
248
- 'put',
249
- body,
250
- Helpers.reqHeaders(this.manager)
251
- );
252
-
253
- if (response?.code === 0) {
254
- this.deviceStatus = 'on';
255
- logger.debug(`Successfully turned on device: ${this.deviceName}`);
256
- return true;
257
- }
258
- logger.error(`Failed to turn on device: ${this.deviceName}`);
259
- return false;
227
+ return await this.toggleSwitch(true);
260
228
  }
261
229
 
262
230
  /**
@@ -269,25 +237,21 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
269
237
  }
270
238
 
271
239
  logger.debug(`Setting brightness to ${brightness} for device: ${this.deviceName}`);
272
- const body = {
273
- ...Helpers.reqBody(this.manager, 'devicestatus'),
274
- brightness,
275
- uuid: this.uuid
276
- };
277
-
278
- const [response] = await this.callApi(
279
- '/dimmer/v1/device/updatebrightness',
280
- 'put',
281
- body,
282
- Helpers.reqHeaders(this.manager)
283
- );
240
+ const value = Math.max(0, Math.min(100, Math.round(brightness)));
241
+ const [response] = await this.callDimmerCommand('dimmerBrightnessCtl', {
242
+ brightness: value
243
+ });
284
244
 
285
245
  if (response?.code === 0) {
286
- this._brightness = brightness;
287
- logger.debug(`Successfully set brightness to ${brightness} for device: ${this.deviceName}`);
246
+ this.stateSnapshot.brightness = value;
247
+ this.stateSnapshot.deviceStatus = 'on';
248
+ this.deviceStatus = 'on';
249
+ this.stateSnapshot.connectionStatus = 'online';
250
+ this.connectionStatus = 'online';
251
+ logger.debug(`Successfully set brightness to ${value} for device: ${this.deviceName}`);
288
252
  return true;
289
253
  }
290
- logger.error(`Failed to set brightness to ${brightness} for device: ${this.deviceName}`);
254
+ logger.error(`Failed to set brightness to ${value} for device: ${this.deviceName}`);
291
255
  return false;
292
256
  }
293
257
 
@@ -296,27 +260,22 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
296
260
  */
297
261
  async rgbColorSet(red: number, green: number, blue: number): Promise<boolean> {
298
262
  logger.debug(`Setting RGB color to (${red}, ${green}, ${blue}) for device: ${this.deviceName}`);
299
- const body = {
300
- ...Helpers.reqBody(this.manager, 'devicestatus'),
263
+ const [response] = await this.callDimmerCommand('dimmerRgbValueCtl', {
264
+ status: 'on',
301
265
  rgbValue: {
302
266
  red: Math.round(red),
303
267
  green: Math.round(green),
304
268
  blue: Math.round(blue)
305
- },
306
- status: 'on',
307
- uuid: this.uuid
308
- };
309
-
310
- const [response] = await this.callApi(
311
- '/dimmer/v1/device/devicergbstatus',
312
- 'put',
313
- body,
314
- Helpers.reqHeaders(this.manager)
315
- );
269
+ }
270
+ });
316
271
 
317
272
  if (response?.code === 0) {
318
- this._rgbValue = { red, green, blue };
319
- this._rgbStatus = 'on';
273
+ this.stateSnapshot.rgbValue = { red: Math.round(red), green: Math.round(green), blue: Math.round(blue) };
274
+ this.stateSnapshot.rgbStatus = 'on';
275
+ this.stateSnapshot.deviceStatus = 'on';
276
+ this.deviceStatus = 'on';
277
+ this.stateSnapshot.connectionStatus = 'online';
278
+ this.connectionStatus = 'online';
320
279
  logger.debug(`Successfully set RGB color for device: ${this.deviceName}`);
321
280
  return true;
322
281
  }
@@ -329,21 +288,16 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
329
288
  */
330
289
  async rgbColorOff(): Promise<boolean> {
331
290
  logger.debug(`Turning off RGB color for device: ${this.deviceName}`);
332
- const body = {
333
- ...Helpers.reqBody(this.manager, 'devicestatus'),
334
- status: 'off',
335
- uuid: this.uuid
336
- };
337
-
338
- const [response] = await this.callApi(
339
- '/dimmer/v1/device/devicergbstatus',
340
- 'put',
341
- body,
342
- Helpers.reqHeaders(this.manager)
343
- );
291
+ const [response] = await this.callDimmerCommand('dimmerRgbValueCtl', {
292
+ status: 'off'
293
+ });
344
294
 
345
295
  if (response?.code === 0) {
346
- this._rgbStatus = 'off';
296
+ this.stateSnapshot.rgbStatus = 'off';
297
+ this.stateSnapshot.deviceStatus = 'on';
298
+ this.deviceStatus = 'on';
299
+ this.stateSnapshot.connectionStatus = 'online';
300
+ this.connectionStatus = 'online';
347
301
  logger.debug(`Successfully turned off RGB color for device: ${this.deviceName}`);
348
302
  return true;
349
303
  }
@@ -356,21 +310,16 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
356
310
  */
357
311
  async rgbColorOn(): Promise<boolean> {
358
312
  logger.debug(`Turning on RGB color for device: ${this.deviceName}`);
359
- const body = {
360
- ...Helpers.reqBody(this.manager, 'devicestatus'),
361
- uuid: this.uuid,
313
+ const [response] = await this.callDimmerCommand('dimmerRgbValueCtl', {
362
314
  status: 'on'
363
- };
364
-
365
- const [response] = await this.callApi(
366
- '/dimmer/v1/device/devicergbstatus',
367
- 'put',
368
- body,
369
- Helpers.reqHeaders(this.manager)
370
- );
315
+ });
371
316
 
372
317
  if (response?.code === 0) {
373
- this._rgbStatus = 'on';
318
+ this.stateSnapshot.rgbStatus = 'on';
319
+ this.stateSnapshot.deviceStatus = 'on';
320
+ this.deviceStatus = 'on';
321
+ this.stateSnapshot.connectionStatus = 'online';
322
+ this.connectionStatus = 'online';
374
323
  logger.debug(`Successfully turned on RGB color for device: ${this.deviceName}`);
375
324
  return true;
376
325
  }
@@ -383,26 +332,7 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
383
332
  */
384
333
  async indicatorLightOn(): Promise<boolean> {
385
334
  logger.debug(`Turning on indicator light for device: ${this.deviceName}`);
386
- const body = {
387
- ...Helpers.reqBody(this.manager, 'devicestatus'),
388
- uuid: this.uuid,
389
- status: 'on'
390
- };
391
-
392
- const [response] = await this.callApi(
393
- '/dimmer/v1/device/indicatorlightstatus',
394
- 'put',
395
- body,
396
- Helpers.reqHeaders(this.manager)
397
- );
398
-
399
- if (response?.code === 0) {
400
- this._indicatorLight = 'on';
401
- logger.debug(`Successfully turned on indicator light for device: ${this.deviceName}`);
402
- return true;
403
- }
404
- logger.error(`Failed to turn on indicator light for device: ${this.deviceName}`);
405
- return false;
335
+ return this.toggleIndicatorLight(true);
406
336
  }
407
337
 
408
338
  /**
@@ -410,43 +340,98 @@ export class VeSyncDimmerSwitch extends VeSyncSwitch {
410
340
  */
411
341
  async indicatorLightOff(): Promise<boolean> {
412
342
  logger.debug(`Turning off indicator light for device: ${this.deviceName}`);
413
- const body = {
414
- ...Helpers.reqBody(this.manager, 'devicestatus'),
415
- uuid: this.uuid,
416
- status: 'off'
417
- };
418
-
419
- const [response] = await this.callApi(
420
- '/dimmer/v1/device/indicatorlightstatus',
421
- 'put',
422
- body,
423
- Helpers.reqHeaders(this.manager)
424
- );
425
-
426
- if (response?.code === 0) {
427
- this._indicatorLight = 'off';
428
- logger.debug(`Successfully turned off indicator light for device: ${this.deviceName}`);
429
- return true;
430
- }
431
- logger.error(`Failed to turn off indicator light for device: ${this.deviceName}`);
432
- return false;
343
+ return this.toggleIndicatorLight(false);
433
344
  }
434
345
 
435
346
  // Getters
436
347
  get brightness(): number {
437
- return this._brightness;
348
+ return this.stateSnapshot.brightness;
438
349
  }
439
350
 
440
351
  get indicatorLightStatus(): string {
441
- return this._indicatorLight;
352
+ return this.stateSnapshot.indicatorStatus;
442
353
  }
443
354
 
444
355
  get rgbLightStatus(): string {
445
- return this._rgbStatus;
356
+ return this.stateSnapshot.rgbStatus;
446
357
  }
447
358
 
448
359
  get rgbLightValue(): Record<string, number> {
449
- return { ...this._rgbValue };
360
+ return { ...this.stateSnapshot.rgbValue };
361
+ }
362
+
363
+ private async toggleSwitch(turnOn: boolean): Promise<boolean> {
364
+ const [response] = await this.callDimmerCommand('dimmerPowerSwitchCtl', {
365
+ status: turnOn ? 'on' : 'off'
366
+ });
367
+
368
+ if (response?.code === 0) {
369
+ this.stateSnapshot.deviceStatus = turnOn ? 'on' : 'off';
370
+ this.deviceStatus = this.stateSnapshot.deviceStatus;
371
+ this.stateSnapshot.connectionStatus = 'online';
372
+ this.connectionStatus = 'online';
373
+ logger.debug(`Successfully toggled device: ${this.deviceName} to ${this.deviceStatus}`);
374
+ return true;
375
+ }
376
+ logger.error(`Failed to toggle device: ${this.deviceName}`);
377
+ return false;
378
+ }
379
+
380
+ private async toggleIndicatorLight(turnOn: boolean): Promise<boolean> {
381
+ const [response] = await this.callDimmerCommand('dimmerIndicatorLightCtl', {
382
+ status: turnOn ? 'on' : 'off'
383
+ });
384
+
385
+ if (response?.code === 0) {
386
+ this.stateSnapshot.indicatorStatus = turnOn ? 'on' : 'off';
387
+ this.stateSnapshot.connectionStatus = 'online';
388
+ this.connectionStatus = 'online';
389
+ logger.debug(`Successfully toggled indicator light for device: ${this.deviceName}`);
390
+ return true;
391
+ }
392
+ logger.error(`Failed to toggle indicator light for device: ${this.deviceName}`);
393
+ return false;
394
+ }
395
+
396
+ private buildDimmerRequest(command: string, data: Record<string, any> = {}): Record<string, any> {
397
+ const manager = this.manager as unknown as {
398
+ accountId?: string | null;
399
+ token?: string | null;
400
+ timeZone?: string | null;
401
+ countryCode?: string | null;
402
+ };
403
+
404
+ const traceId = Helpers.generateTraceId();
405
+
406
+ return {
407
+ acceptLanguage: 'en',
408
+ accountID: manager.accountId ?? '',
409
+ appVersion: APP_VERSION,
410
+ cid: this.cid,
411
+ configModule: this.configModule,
412
+ configModel: this.configModule,
413
+ debugMode: false,
414
+ deviceId: this.cid,
415
+ method: command,
416
+ phoneBrand: PHONE_BRAND,
417
+ phoneOS: PHONE_OS,
418
+ traceId,
419
+ timeZone: manager.timeZone ?? DEFAULT_TZ,
420
+ token: manager.token ?? '',
421
+ userCountryCode: manager.countryCode ?? DEFAULT_REGION,
422
+ uuid: this.uuid,
423
+ ...data
424
+ };
425
+ }
426
+
427
+ private async callDimmerCommand(command: string, data: Record<string, any>): Promise<[any, number]> {
428
+ const request = this.buildDimmerRequest(command, data);
429
+ return await this.callApi(
430
+ `/cloud/v1/deviceManaged/${command}`,
431
+ 'post',
432
+ request,
433
+ Helpers.reqHeaderBypass()
434
+ );
450
435
  }
451
436
  }
452
437