homebridge-tsvesync 1.0.13 → 1.0.14
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/dist/accessories/air-purifier.accessory.d.ts +32 -2
- package/dist/accessories/air-purifier.accessory.d.ts.map +1 -1
- package/dist/accessories/air-purifier.accessory.js +110 -51
- package/dist/accessories/air-purifier.accessory.js.map +1 -1
- package/dist/accessories/base.accessory.d.ts +12 -3
- package/dist/accessories/base.accessory.d.ts.map +1 -1
- package/dist/accessories/base.accessory.js +53 -24
- package/dist/accessories/base.accessory.js.map +1 -1
- package/dist/accessories/fan.accessory.d.ts +6 -2
- package/dist/accessories/fan.accessory.d.ts.map +1 -1
- package/dist/accessories/fan.accessory.js +27 -33
- package/dist/accessories/fan.accessory.js.map +1 -1
- package/dist/accessories/humidifier.accessory.d.ts +6 -2
- package/dist/accessories/humidifier.accessory.d.ts.map +1 -1
- package/dist/accessories/humidifier.accessory.js +38 -37
- package/dist/accessories/humidifier.accessory.js.map +1 -1
- package/dist/accessories/light.accessory.d.ts +5 -1
- package/dist/accessories/light.accessory.d.ts.map +1 -1
- package/dist/accessories/light.accessory.js +25 -19
- package/dist/accessories/light.accessory.js.map +1 -1
- package/dist/accessories/outlet.accessory.d.ts +7 -1
- package/dist/accessories/outlet.accessory.d.ts.map +1 -1
- package/dist/accessories/outlet.accessory.js +25 -14
- package/dist/accessories/outlet.accessory.js.map +1 -1
- package/dist/types/device.types.d.ts +1 -1
- package/dist/types/device.types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/accessories/air-purifier.accessory.ts +117 -84
- package/src/accessories/base.accessory.ts +62 -35
- package/src/accessories/fan.accessory.ts +40 -44
- package/src/accessories/humidifier.accessory.ts +61 -48
- package/src/accessories/light.accessory.ts +32 -18
- package/src/accessories/outlet.accessory.ts +33 -14
- package/src/types/device.types.ts +1 -1
|
@@ -4,17 +4,22 @@ import { TSVESyncPlatform } from '../platform';
|
|
|
4
4
|
import { DeviceCapabilities, VeSyncAirPurifier } from '../types/device.types';
|
|
5
5
|
|
|
6
6
|
export class AirPurifierAccessory extends BaseAccessory {
|
|
7
|
+
private readonly capabilities: DeviceCapabilities;
|
|
7
8
|
protected readonly device: VeSyncAirPurifier;
|
|
8
9
|
|
|
9
10
|
constructor(
|
|
10
11
|
platform: TSVESyncPlatform,
|
|
11
12
|
accessory: PlatformAccessory,
|
|
12
|
-
device: VeSyncAirPurifier
|
|
13
|
+
device: VeSyncAirPurifier
|
|
13
14
|
) {
|
|
14
15
|
super(platform, accessory, device);
|
|
15
16
|
this.device = device;
|
|
17
|
+
this.capabilities = this.getDeviceCapabilities();
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Set up the air purifier service
|
|
22
|
+
*/
|
|
18
23
|
protected setupService(): void {
|
|
19
24
|
// Get or create the air purifier service
|
|
20
25
|
this.service = this.accessory.getService(this.platform.Service.AirPurifier) ||
|
|
@@ -68,7 +73,7 @@ export class AirPurifierAccessory extends BaseAccessory {
|
|
|
68
73
|
this.setupCharacteristic(
|
|
69
74
|
this.platform.Characteristic.RotationSpeed,
|
|
70
75
|
this.getRotationSpeed.bind(this),
|
|
71
|
-
this.
|
|
76
|
+
this.handleSetRotationSpeed.bind(this)
|
|
72
77
|
);
|
|
73
78
|
}
|
|
74
79
|
|
|
@@ -98,88 +103,114 @@ export class AirPurifierAccessory extends BaseAccessory {
|
|
|
98
103
|
);
|
|
99
104
|
}
|
|
100
105
|
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Get device capabilities
|
|
108
|
+
*/
|
|
109
|
+
protected getDeviceCapabilities(): DeviceCapabilities {
|
|
110
|
+
return {
|
|
111
|
+
hasSpeed: true,
|
|
112
|
+
hasAirQuality: true,
|
|
113
|
+
hasChildLock: true,
|
|
114
|
+
hasBrightness: false,
|
|
115
|
+
hasColorTemp: false,
|
|
116
|
+
hasColor: false,
|
|
117
|
+
hasHumidity: false,
|
|
118
|
+
hasWaterLevel: false,
|
|
119
|
+
hasSwingMode: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
103
122
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
airQualityService.updateCharacteristic(
|
|
125
|
-
this.platform.Characteristic.PM2_5Density,
|
|
126
|
-
pm25Value
|
|
127
|
-
);
|
|
128
|
-
}
|
|
123
|
+
/**
|
|
124
|
+
* Update device states based on the latest details
|
|
125
|
+
*/
|
|
126
|
+
protected async updateDeviceSpecificStates(details: any): Promise<void> {
|
|
127
|
+
// Update power state
|
|
128
|
+
const isOn = details.enabled;
|
|
129
|
+
this.service.updateCharacteristic(this.platform.Characteristic.Active, isOn ? 1 : 0);
|
|
130
|
+
this.service.updateCharacteristic(this.platform.Characteristic.CurrentAirPurifierState, isOn ? 2 : 0);
|
|
131
|
+
|
|
132
|
+
// Update rotation speed
|
|
133
|
+
const speed = details.speed;
|
|
134
|
+
const rotationSpeed = Math.round((speed / this.device.maxSpeed) * 100);
|
|
135
|
+
this.service.updateCharacteristic(this.platform.Characteristic.RotationSpeed, rotationSpeed);
|
|
136
|
+
|
|
137
|
+
// Update air quality if available
|
|
138
|
+
if (this.capabilities.hasAirQuality) {
|
|
139
|
+
const pm25 = details.airQuality?.pm25 ?? 0;
|
|
140
|
+
const airQuality = this.convertAirQualityToHomeKit(pm25);
|
|
141
|
+
this.service.updateCharacteristic(this.platform.Characteristic.AirQuality, airQuality);
|
|
142
|
+
this.service.updateCharacteristic(this.platform.Characteristic.PM2_5Density, pm25);
|
|
129
143
|
}
|
|
130
144
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
145
|
+
// Update filter life if available
|
|
146
|
+
if (details.filterLife !== undefined) {
|
|
147
|
+
const filterLife = details.filterLife;
|
|
148
|
+
this.service.updateCharacteristic(this.platform.Characteristic.FilterLifeLevel, filterLife);
|
|
149
|
+
this.service.updateCharacteristic(
|
|
150
|
+
this.platform.Characteristic.FilterChangeIndication,
|
|
151
|
+
filterLife <= 20 ? 1 : 0
|
|
136
152
|
);
|
|
137
153
|
}
|
|
138
154
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
details.childLock ? 1 : 0
|
|
143
|
-
);
|
|
144
|
-
}
|
|
155
|
+
// Update device state based on activity
|
|
156
|
+
this.updatePollingState(isOn);
|
|
157
|
+
}
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
this.
|
|
154
|
-
|
|
155
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Handle set active state
|
|
161
|
+
*/
|
|
162
|
+
private async handleSetActive(value: CharacteristicValue): Promise<void> {
|
|
163
|
+
try {
|
|
164
|
+
const newState = value as number;
|
|
165
|
+
if (newState === 1) {
|
|
166
|
+
await this.device.turnOn();
|
|
167
|
+
} else {
|
|
168
|
+
await this.device.turnOff();
|
|
169
|
+
}
|
|
170
|
+
} catch (err) {
|
|
171
|
+
await this.handleDeviceError('set active state', err);
|
|
172
|
+
throw err;
|
|
156
173
|
}
|
|
174
|
+
}
|
|
157
175
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
176
|
+
/**
|
|
177
|
+
* Handle set target state
|
|
178
|
+
*/
|
|
179
|
+
private async handleSetTargetState(value: CharacteristicValue): Promise<void> {
|
|
180
|
+
try {
|
|
181
|
+
const newState = value as number;
|
|
182
|
+
if (this.device.setMode) {
|
|
183
|
+
await this.device.setMode(newState === 1 ? 'auto' : 'manual');
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
await this.handleDeviceError('set target state', err);
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
164
190
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
191
|
+
/**
|
|
192
|
+
* Handle set rotation speed
|
|
193
|
+
*/
|
|
194
|
+
private async handleSetRotationSpeed(value: CharacteristicValue): Promise<void> {
|
|
195
|
+
const speed = Math.round((value as number / 100) * this.device.maxSpeed);
|
|
196
|
+
await this.withRetry(async () => {
|
|
197
|
+
await this.device.changeFanSpeed(speed);
|
|
198
|
+
}, 'set rotation speed');
|
|
169
199
|
}
|
|
170
200
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Handle set child lock
|
|
203
|
+
*/
|
|
204
|
+
private async handleSetChildLock(value: CharacteristicValue): Promise<void> {
|
|
205
|
+
try {
|
|
206
|
+
const enabled = value as number === 1;
|
|
207
|
+
if (this.device.setChildLock) {
|
|
208
|
+
await this.device.setChildLock(enabled);
|
|
209
|
+
}
|
|
210
|
+
} catch (err) {
|
|
211
|
+
await this.handleDeviceError('set child lock', err);
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
183
214
|
}
|
|
184
215
|
|
|
185
216
|
private async getActive(): Promise<CharacteristicValue> {
|
|
@@ -252,19 +283,15 @@ export class AirPurifierAccessory extends BaseAccessory {
|
|
|
252
283
|
return Math.min(Math.max((this.device.speed / this.device.maxSpeed) * 100, 0), 100);
|
|
253
284
|
}
|
|
254
285
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
throw new Error(`Failed to set speed to ${speed}`);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
await this.persistDeviceState('speed', speed);
|
|
265
|
-
} catch (error) {
|
|
266
|
-
this.handleDeviceError('set rotation speed', error);
|
|
286
|
+
/**
|
|
287
|
+
* Convert HomeKit rotation speed (0-100) to fan speed (1-maxSpeed)
|
|
288
|
+
*/
|
|
289
|
+
private convertRotationSpeedToFanSpeed(value: number): number {
|
|
290
|
+
if (value === 0) {
|
|
291
|
+
return 0; // Off
|
|
267
292
|
}
|
|
293
|
+
// Convert from 0-100 to 1-maxSpeed
|
|
294
|
+
return Math.max(1, Math.round((value / 100) * this.device.maxSpeed));
|
|
268
295
|
}
|
|
269
296
|
|
|
270
297
|
private async getLockPhysicalControls(): Promise<CharacteristicValue> {
|
|
@@ -297,4 +324,10 @@ export class AirPurifierAccessory extends BaseAccessory {
|
|
|
297
324
|
private async getFilterLifeLevel(): Promise<CharacteristicValue> {
|
|
298
325
|
return this.device.filterLife;
|
|
299
326
|
}
|
|
327
|
+
|
|
328
|
+
protected async updateRotationSpeed(speed: number): Promise<void> {
|
|
329
|
+
await this.withRetry(async () => {
|
|
330
|
+
await this.device.changeFanSpeed(speed);
|
|
331
|
+
}, 'update rotation speed');
|
|
332
|
+
}
|
|
300
333
|
}
|
|
@@ -13,6 +13,7 @@ export abstract class BaseAccessory {
|
|
|
13
13
|
private readonly pollingManager: PollingManager;
|
|
14
14
|
private readonly retryManager: RetryManager;
|
|
15
15
|
private readonly logger: PluginLogger;
|
|
16
|
+
private needsRetry = false;
|
|
16
17
|
|
|
17
18
|
constructor(
|
|
18
19
|
platform: TSVESyncPlatform,
|
|
@@ -83,6 +84,10 @@ export abstract class BaseAccessory {
|
|
|
83
84
|
return result;
|
|
84
85
|
} catch (error) {
|
|
85
86
|
this.logger.operationEnd(context, error as Error);
|
|
87
|
+
await this.handleDeviceError(
|
|
88
|
+
`Failed to ${operationName}`,
|
|
89
|
+
error instanceof Error ? error : new Error(String(error))
|
|
90
|
+
);
|
|
86
91
|
throw error;
|
|
87
92
|
}
|
|
88
93
|
}
|
|
@@ -95,7 +100,7 @@ export abstract class BaseAccessory {
|
|
|
95
100
|
/**
|
|
96
101
|
* Update device-specific states
|
|
97
102
|
*/
|
|
98
|
-
protected abstract updateDeviceSpecificStates(details: any): void
|
|
103
|
+
protected abstract updateDeviceSpecificStates(details: any): Promise<void>;
|
|
99
104
|
|
|
100
105
|
/**
|
|
101
106
|
* Get device capabilities
|
|
@@ -168,36 +173,21 @@ export abstract class BaseAccessory {
|
|
|
168
173
|
* Sync the device state with VeSync
|
|
169
174
|
*/
|
|
170
175
|
protected async syncDeviceState(): Promise<void> {
|
|
171
|
-
const context = this.getLogContext('sync device state');
|
|
172
|
-
|
|
173
176
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
this.updatePollingState(isActive);
|
|
184
|
-
|
|
185
|
-
this.logger.pollingEvent({
|
|
186
|
-
...context,
|
|
187
|
-
interval: this.pollingManager.getCurrentInterval(),
|
|
188
|
-
active: isActive,
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// Update device-specific states
|
|
192
|
-
this.updateDeviceSpecificStates(details);
|
|
193
|
-
}, 'sync device state');
|
|
177
|
+
if (this.needsRetry) {
|
|
178
|
+
this.platform.log.debug(`[${this.accessory.displayName}] Retrying previous failed operation`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const details = await this.device.getDetails();
|
|
182
|
+
this.needsRetry = false; // Clear retry flag on success
|
|
183
|
+
|
|
184
|
+
// Update the device state
|
|
185
|
+
await this.updateDeviceSpecificStates(details);
|
|
194
186
|
} catch (error) {
|
|
195
|
-
this.
|
|
187
|
+
await this.handleDeviceError(
|
|
196
188
|
'Failed to sync device state',
|
|
197
|
-
|
|
198
|
-
error as Error
|
|
189
|
+
error instanceof Error ? error : new Error(String(error))
|
|
199
190
|
);
|
|
200
|
-
throw error; // Let polling manager handle the error
|
|
201
191
|
}
|
|
202
192
|
}
|
|
203
193
|
|
|
@@ -318,15 +308,52 @@ export abstract class BaseAccessory {
|
|
|
318
308
|
}
|
|
319
309
|
|
|
320
310
|
/**
|
|
321
|
-
* Handle device errors
|
|
311
|
+
* Handle device errors with appropriate recovery actions
|
|
322
312
|
*/
|
|
323
|
-
protected handleDeviceError(operation: string, error: unknown): void {
|
|
313
|
+
protected async handleDeviceError(operation: string, error: unknown): Promise<void> {
|
|
324
314
|
const errorObj = error instanceof Error ? error : new Error(String(error));
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
315
|
+
const errorDetails = errorObj as any;
|
|
316
|
+
const errorCode = errorDetails?.error?.code || errorDetails?.code;
|
|
317
|
+
const errorMsg = errorDetails?.error?.msg || errorDetails?.msg;
|
|
318
|
+
|
|
319
|
+
// Log the error with context
|
|
320
|
+
this.platform.log.error(`[${this.accessory.displayName}] ${operation}: ${JSON.stringify(errorDetails)}`);
|
|
321
|
+
|
|
322
|
+
// Handle specific error codes
|
|
323
|
+
switch (errorCode) {
|
|
324
|
+
case 4041008: // Device not found
|
|
325
|
+
this.platform.log.warn(`[${this.accessory.displayName}] Device not found, will attempt to rediscover`);
|
|
326
|
+
await this.attemptReconnection();
|
|
327
|
+
break;
|
|
328
|
+
case -11102086: // Internal error
|
|
329
|
+
this.platform.log.warn(`[${this.accessory.displayName}] VeSync API internal error, will retry later`);
|
|
330
|
+
// Mark device for retry on next update cycle
|
|
331
|
+
this.markForRetry();
|
|
332
|
+
break;
|
|
333
|
+
default:
|
|
334
|
+
if (errorMsg?.includes('not found')) {
|
|
335
|
+
await this.attemptReconnection();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Attempt to reconnect to the device by triggering device discovery
|
|
342
|
+
*/
|
|
343
|
+
private async attemptReconnection(): Promise<void> {
|
|
344
|
+
try {
|
|
345
|
+
// Attempt to refresh the device list
|
|
346
|
+
await this.platform.discoverDevices();
|
|
347
|
+
this.platform.log.info(`[${this.accessory.displayName}] Successfully refreshed device list`);
|
|
348
|
+
} catch (error) {
|
|
349
|
+
this.platform.log.error(`[${this.accessory.displayName}] Failed to reconnect: ${error}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Mark the device for retry on next update cycle
|
|
355
|
+
*/
|
|
356
|
+
private markForRetry(): void {
|
|
357
|
+
this.needsRetry = true;
|
|
331
358
|
}
|
|
332
359
|
}
|
|
@@ -9,14 +9,16 @@ const COUNTER_CLOCKWISE = 1;
|
|
|
9
9
|
|
|
10
10
|
export class FanAccessory extends BaseAccessory {
|
|
11
11
|
protected readonly device: VeSyncFan;
|
|
12
|
+
private readonly capabilities: DeviceCapabilities;
|
|
12
13
|
|
|
13
14
|
constructor(
|
|
14
15
|
platform: TSVESyncPlatform,
|
|
15
16
|
accessory: PlatformAccessory,
|
|
16
|
-
device: VeSyncFan
|
|
17
|
+
device: VeSyncFan
|
|
17
18
|
) {
|
|
18
19
|
super(platform, accessory, device);
|
|
19
20
|
this.device = device;
|
|
21
|
+
this.capabilities = this.getDeviceCapabilities();
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
protected setupService(): void {
|
|
@@ -38,7 +40,7 @@ export class FanAccessory extends BaseAccessory {
|
|
|
38
40
|
this.setupCharacteristic(
|
|
39
41
|
this.platform.Characteristic.RotationSpeed,
|
|
40
42
|
this.getRotationSpeed.bind(this),
|
|
41
|
-
this.
|
|
43
|
+
this.handleSetRotationSpeed.bind(this)
|
|
42
44
|
);
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -74,48 +76,50 @@ export class FanAccessory extends BaseAccessory {
|
|
|
74
76
|
);
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Update device states based on the latest details
|
|
81
|
+
*/
|
|
82
|
+
protected async updateDeviceSpecificStates(details: any): Promise<void> {
|
|
83
|
+
const fanDetails = details as {
|
|
84
|
+
deviceStatus: string;
|
|
85
|
+
speed: number;
|
|
86
|
+
childLock: boolean;
|
|
87
|
+
swingMode: boolean;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Update active state
|
|
91
|
+
const isActive = fanDetails.deviceStatus === 'on';
|
|
92
|
+
this.updateCharacteristicValue(
|
|
93
|
+
this.platform.Characteristic.Active,
|
|
94
|
+
isActive ? 1 : 0
|
|
95
|
+
);
|
|
79
96
|
|
|
80
|
-
// Update speed if
|
|
81
|
-
if (capabilities.hasSpeed
|
|
82
|
-
const normalizedSpeed = (details.speed / this.device.maxSpeed) * 100;
|
|
97
|
+
// Update rotation speed if supported
|
|
98
|
+
if (this.capabilities.hasSpeed) {
|
|
83
99
|
this.updateCharacteristicValue(
|
|
84
100
|
this.platform.Characteristic.RotationSpeed,
|
|
85
|
-
|
|
101
|
+
fanDetails.speed
|
|
86
102
|
);
|
|
87
103
|
}
|
|
88
104
|
|
|
89
|
-
// Update
|
|
90
|
-
if (
|
|
105
|
+
// Update child lock if supported
|
|
106
|
+
if (this.capabilities.hasChildLock) {
|
|
91
107
|
this.updateCharacteristicValue(
|
|
92
|
-
this.platform.Characteristic.
|
|
93
|
-
|
|
108
|
+
this.platform.Characteristic.LockPhysicalControls,
|
|
109
|
+
fanDetails.childLock ? 1 : 0
|
|
94
110
|
);
|
|
95
111
|
}
|
|
96
112
|
|
|
97
|
-
// Update swing mode if
|
|
98
|
-
if (capabilities.hasSwingMode
|
|
113
|
+
// Update swing mode if supported
|
|
114
|
+
if (this.capabilities.hasSwingMode) {
|
|
99
115
|
this.updateCharacteristicValue(
|
|
100
116
|
this.platform.Characteristic.SwingMode,
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Update child lock if available
|
|
106
|
-
if (capabilities.hasChildLock && details.childLock !== undefined) {
|
|
107
|
-
this.updateCharacteristicValue(
|
|
108
|
-
this.platform.Characteristic.LockPhysicalControls,
|
|
109
|
-
details.childLock ? 1 : 0
|
|
117
|
+
fanDetails.swingMode ? 1 : 0
|
|
110
118
|
);
|
|
111
119
|
}
|
|
112
120
|
|
|
113
|
-
// Update active
|
|
114
|
-
|
|
115
|
-
this.updateCharacteristicValue(
|
|
116
|
-
this.platform.Characteristic.Active,
|
|
117
|
-
isActive ? 1 : 0
|
|
118
|
-
);
|
|
121
|
+
// Update polling state based on active status
|
|
122
|
+
this.updatePollingState(isActive);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
protected getDeviceCapabilities(): DeviceCapabilities {
|
|
@@ -151,23 +155,15 @@ export class FanAccessory extends BaseAccessory {
|
|
|
151
155
|
}
|
|
152
156
|
}
|
|
153
157
|
|
|
154
|
-
private async
|
|
155
|
-
|
|
158
|
+
private async handleSetRotationSpeed(value: CharacteristicValue): Promise<void> {
|
|
159
|
+
const speed = Math.round((value as number / 100) * this.device.maxSpeed);
|
|
160
|
+
await this.withRetry(async () => {
|
|
161
|
+
await this.device.changeFanSpeed(speed);
|
|
162
|
+
}, 'set rotation speed');
|
|
156
163
|
}
|
|
157
164
|
|
|
158
|
-
private async
|
|
159
|
-
|
|
160
|
-
const speed = Math.round((value as number / 100) * this.device.maxSpeed);
|
|
161
|
-
const success = await this.device.setSpeed(speed);
|
|
162
|
-
|
|
163
|
-
if (!success) {
|
|
164
|
-
throw new Error(`Failed to set speed to ${speed}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
await this.persistDeviceState('speed', speed);
|
|
168
|
-
} catch (error) {
|
|
169
|
-
this.handleDeviceError('set rotation speed', error);
|
|
170
|
-
}
|
|
165
|
+
private async getRotationSpeed(): Promise<CharacteristicValue> {
|
|
166
|
+
return (this.device.speed / this.device.maxSpeed) * 100;
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
private async getRotationDirection(): Promise<CharacteristicValue> {
|