homebridge-winix-purifiers 2.0.1 → 2.0.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.
package/README.md CHANGED
@@ -24,8 +24,9 @@
24
24
  ## Features
25
25
 
26
26
  - **Dynamic Device Discovery**: Automatically discovers and configures Winix purifiers linked to your account.
27
- - **Control**: Power on/off, switch between auto/manual modes, and adjust airflow speed.
28
- - **Customization**: Optionally expose Air Quality, Ambient Light, Plasmawave, and Auto Mode switches to HomeKit.
27
+ - **Control**: Power on/off, switch between sleep/auto/manual modes, and adjust airflow speed.
28
+ - **Customization**: Optionally expose Air Quality, Ambient Light, Plasmawave, Auto Mode, and Sleep Mode switches to
29
+ HomeKit.
29
30
  - **Filter Management**: Exposes the remaining filter life and provides an alert when it's time to change the filter,
30
31
  configurable to trigger at a specified percentage of remaining filter life.
31
32
  - **Efficiency**: Features Winix API response caching to minimize requests and avoid rate limiting.
@@ -75,6 +76,7 @@ While not recommended, if manual setup is required, add the following to the `pl
75
76
  "exposeAmbientLight": true,
76
77
  "exposePlasmawave": false,
77
78
  "exposeAutoSwitch": false,
79
+ "exposeSleepSwitch": false,
78
80
  "filterReplacementIndicatorPercentage": 10,
79
81
  "cacheIntervalSeconds": 300,
80
82
  "deviceRefreshIntervalMinutes": 60,
@@ -111,6 +113,7 @@ While not recommended, if manual setup is required, add the following to the `pl
111
113
  | `exposeAmbientLight` | `false` | Whether to expose the ambient light sensors to HomeKit. |
112
114
  | `exposePlasmawave` | `false` | Whether to expose switches for Plasmawave on/off. |
113
115
  | `exposeAutoSwitch` | `false` | Whether to expose switches for Auto mode on/off. |
116
+ | `exposeSleepSwitch` | `false` | Whether to expose switches for Sleep mode on/off. |
114
117
  | `filterReplacementIndicatorPercentage` | `10` | Percentage of filter life remaining to trigger a filter replacement alert. |
115
118
  | `cacheIntervalSeconds` | `60` | Time, in seconds, for how long to reuse cached responses from Winix. |
116
119
  | `deviceRefreshIntervalMinutes` | `60` | Time, in minutes, for how often to poll Winix to refresh the device list. |
@@ -124,6 +127,7 @@ While not recommended, if manual setup is required, add the following to the `pl
124
127
  | `deviceOverrides[].nameAmbientLightSensor` | `"Ambient Light"` | The display name of the ambient light sensor. Optional. |
125
128
  | `deviceOverrides[].namePlasmawaveSwitch` | `"Plasmawave"` | The display name of the Plasmawave switch. Optional. |
126
129
  | `deviceOverrides[].nameAutoSwitch` | `"Auto Mode"` | The display name of the Auto switch. Optional. |
130
+ | `deviceOverrides[].nameSleepSwitch` | `"Sleep"` | The display name of the Sleep switch. Optional. |
127
131
  | `platform` | `"WinixPurifiers"` | Must always be `"WinixPurifiers"` in order for the plugin to load this config. |
128
132
 
129
133
  ## FAQ
@@ -27,7 +27,13 @@
27
27
  },
28
28
  "exposeAutoSwitch": {
29
29
  "title": "Auto Switch",
30
- "description": "Expose the auto on/off switch",
30
+ "description": "Expose the Auto on/off switch",
31
+ "type": "boolean",
32
+ "default": false
33
+ },
34
+ "exposeSleepSwitch": {
35
+ "title": "Sleep Switch",
36
+ "description": "Expose the Sleep on/off switch",
31
37
  "type": "boolean",
32
38
  "default": false
33
39
  },
@@ -158,6 +164,7 @@
158
164
  "exposeAmbientLight",
159
165
  "exposePlasmawave",
160
166
  "exposeAutoSwitch",
167
+ "exposeSleepSwitch",
161
168
  "filterReplacementIndicatorPercentage",
162
169
  "cacheIntervalSeconds",
163
170
  "deviceRefreshIntervalMinutes"
package/dist/accessory.js CHANGED
@@ -48,7 +48,7 @@ class WinixPurifierAccessory {
48
48
  .onSet(this.setTargetState.bind(this));
49
49
  characteristics.get(this.purifier, this.Characteristic.RotationSpeed)
50
50
  .onGet(this.getRotationSpeed.bind(this))
51
- .onSet(this.setRotationSpeed.bind(this));
51
+ .onSet(this.debounce(this.setRotationSpeed.bind(this), 500));
52
52
  characteristics.get(this.purifier, this.Characteristic.FilterLifeLevel)
53
53
  .onGet(this.getFilterLifeLevel.bind(this));
54
54
  characteristics.get(this.purifier, this.Characteristic.FilterChangeIndication)
@@ -102,6 +102,16 @@ class WinixPurifierAccessory {
102
102
  .onGet(this.getAutoSwitchState.bind(this))
103
103
  .onSet(this.setAutoSwitchState.bind(this));
104
104
  }
105
+ if (config.exposeSleepSwitch) {
106
+ this.sleepSwitch = accessory.getServiceById(this.ServiceType.Switch, 'switch-sleep') ||
107
+ accessory.addService(this.ServiceType.Switch, 'Sleep', 'switch-sleep');
108
+ this.servicesInUse.add(this.sleepSwitch);
109
+ characteristics.set(this.sleepSwitch, this.Characteristic.Name, 'Sleep');
110
+ characteristics.set(this.sleepSwitch, this.Characteristic.ConfiguredName, 'Sleep');
111
+ characteristics.get(this.sleepSwitch, this.Characteristic.On)
112
+ .onGet(this.getSleepSwitchState.bind(this))
113
+ .onSet(this.setSleepSwitchState.bind(this));
114
+ }
105
115
  this.pruneUnusedServices();
106
116
  }
107
117
  /**
@@ -124,7 +134,7 @@ class WinixPurifierAccessory {
124
134
  */
125
135
  async getActiveState() {
126
136
  const power = await this.device.getPower();
127
- this.log.debug('getActiveState()', power);
137
+ this.log.debug('accessory:getActiveState()', power);
128
138
  return this.toActiveState(power);
129
139
  }
130
140
  /**
@@ -133,7 +143,7 @@ class WinixPurifierAccessory {
133
143
  */
134
144
  async setActiveState(state) {
135
145
  const power = state === this.Characteristic.Active.ACTIVE ? winix_api_1.Power.On : winix_api_1.Power.Off;
136
- this.log.debug(`setActiveState(${state})`, power);
146
+ this.log.debug(`accessory:setActiveState(${state})`, power);
137
147
  await this.device.setPower(power);
138
148
  await this.sendHomekitUpdate();
139
149
  }
@@ -143,7 +153,7 @@ class WinixPurifierAccessory {
143
153
  */
144
154
  async getCurrentState() {
145
155
  const power = await this.device.getPower();
146
- this.log.debug('getCurrentState()', power);
156
+ this.log.debug('accessory:getCurrentState()', power);
147
157
  return this.toCurrentState(power);
148
158
  }
149
159
  /**
@@ -151,7 +161,7 @@ class WinixPurifierAccessory {
151
161
  */
152
162
  async getTargetState() {
153
163
  const mode = await this.device.getMode();
154
- this.log.debug('getTargetState()', mode);
164
+ this.log.debug('accessory:getTargetState()', mode);
155
165
  return this.toTargetState(mode);
156
166
  }
157
167
  /**
@@ -159,25 +169,21 @@ class WinixPurifierAccessory {
159
169
  */
160
170
  async setTargetState(state) {
161
171
  const newMode = state === this.Characteristic.TargetAirPurifierState.AUTO ? winix_api_1.Mode.Auto : winix_api_1.Mode.Manual;
162
- this.log.debug(`setTargetState(${state})`, newMode);
172
+ this.log.debug(`accessory:setTargetState(${state})`, newMode);
163
173
  await this.device.setMode(newMode);
164
174
  if (newMode === winix_api_1.Mode.Manual) {
165
175
  return;
166
176
  }
167
177
  // If we're switching back to auto, the airflow speed will most likely change on the Winix device itself.
168
178
  // Pause, get the latest airflow speed, then send the update to Homekit
169
- this.log.debug('scheduling homekit update to rotation speed');
170
- setTimeout(async () => {
171
- await this.device.update();
172
- await this.sendHomekitUpdate();
173
- }, 2000);
179
+ this.scheduleHomekitUpdate();
174
180
  }
175
181
  /**
176
182
  * Get the rotation speed of the purifier.
177
183
  */
178
184
  async getRotationSpeed() {
179
185
  const airflow = await this.device.getAirflow();
180
- this.log.debug('getRotationSpeed()', airflow);
186
+ this.log.debug('accessory:getRotationSpeed()', airflow);
181
187
  return this.toRotationSpeed(airflow);
182
188
  }
183
189
  /**
@@ -185,7 +191,11 @@ class WinixPurifierAccessory {
185
191
  */
186
192
  async setRotationSpeed(state) {
187
193
  const airflow = this.toAirflow(state);
188
- this.log.debug(`setRotationSpeed(${state}):`, airflow);
194
+ this.log.debug(`accessory:setRotationSpeed(${state}):`, airflow);
195
+ // Don't set the airflow if it's null. this means state was 0 - this is a power-off signal
196
+ if (!airflow) {
197
+ return;
198
+ }
189
199
  await this.device.setAirflow(airflow);
190
200
  await this.sendHomekitUpdate();
191
201
  }
@@ -194,7 +204,7 @@ class WinixPurifierAccessory {
194
204
  */
195
205
  async getAirQuality() {
196
206
  const airQuality = await this.device.getAirQuality();
197
- this.log.debug('getAirQuality():', airQuality);
207
+ this.log.debug('accessory:getAirQuality():', airQuality);
198
208
  return this.toAirQuality(airQuality);
199
209
  }
200
210
  /**
@@ -202,7 +212,7 @@ class WinixPurifierAccessory {
202
212
  */
203
213
  async getPlasmawave() {
204
214
  const plasmawave = await this.device.getPlasmawave();
205
- this.log.debug('getPlasmawave():', plasmawave);
215
+ this.log.debug('accessory:getPlasmawave():', plasmawave);
206
216
  return this.toSwitch(plasmawave);
207
217
  }
208
218
  /**
@@ -210,7 +220,7 @@ class WinixPurifierAccessory {
210
220
  */
211
221
  async setPlasmawave(state) {
212
222
  const plasmawave = this.toPlasmawave(state);
213
- this.log.debug(`setPlasmawave(${state}):`, plasmawave);
223
+ this.log.debug(`accessory:setPlasmawave(${state}):`, plasmawave);
214
224
  await this.device.setPlasmawave(plasmawave);
215
225
  await this.sendHomekitUpdate();
216
226
  }
@@ -220,8 +230,8 @@ class WinixPurifierAccessory {
220
230
  async getAmbientLight() {
221
231
  const ambientLight = await this.device.getAmbientLight();
222
232
  // Fix ambient light value under 0.0001 warning
223
- const fixedAmbientLight = Math.max(ambientLight, MIN_AMBIENT_LIGHT);
224
- this.log.debug('getAmbientLight():', 'measured:', ambientLight, 'fixed:', fixedAmbientLight);
233
+ const fixedAmbientLight = this.toAmbientLight(ambientLight);
234
+ this.log.debug('accessory:getAmbientLight():', 'measured:', ambientLight, 'fixed:', fixedAmbientLight);
225
235
  return fixedAmbientLight;
226
236
  }
227
237
  /**
@@ -230,9 +240,8 @@ class WinixPurifierAccessory {
230
240
  async getAutoSwitchState() {
231
241
  const targetState = await this.getTargetState();
232
242
  // Translate target state (auto/manual mode) to auto switch state
233
- const result = targetState === this.Characteristic.TargetAirPurifierState.AUTO ?
234
- this.Characteristic.Active.ACTIVE : this.Characteristic.Active.INACTIVE;
235
- this.log.debug('getAutoSwitchState()', 'target', targetState, 'result', result);
243
+ const result = targetState === this.Characteristic.TargetAirPurifierState.AUTO;
244
+ this.log.debug('accessory:getAutoSwitchState()', 'target', targetState, 'result', result);
236
245
  return result;
237
246
  }
238
247
  /**
@@ -243,8 +252,26 @@ class WinixPurifierAccessory {
243
252
  const proxyState = state ?
244
253
  this.Characteristic.TargetAirPurifierState.AUTO :
245
254
  this.Characteristic.TargetAirPurifierState.MANUAL;
246
- this.log.debug(`setAutoSwitchState(${state})`, proxyState);
247
- return this.setTargetState(proxyState);
255
+ this.log.debug(`accessory:setAutoSwitchState(${state})`, proxyState);
256
+ return await this.setTargetState(proxyState);
257
+ }
258
+ /**
259
+ * Get the sleep switch state of the purifier.
260
+ */
261
+ async getSleepSwitchState() {
262
+ const airflow = await this.device.getAirflow();
263
+ const isInSleep = airflow === winix_api_1.Airflow.Sleep;
264
+ this.log.debug('accessory:getSleepSwitchState()', isInSleep);
265
+ return isInSleep;
266
+ }
267
+ /**
268
+ * Set the sleep switch state of the purifier.
269
+ */
270
+ async setSleepSwitchState(state) {
271
+ const airflow = state ? winix_api_1.Airflow.Sleep : winix_api_1.Airflow.Low;
272
+ this.log.debug(`accessory:setSleepSwitchState(${state})`, airflow);
273
+ await this.device.setAirflow(airflow);
274
+ this.scheduleHomekitUpdate();
248
275
  }
249
276
  /**
250
277
  * Get the filter life level of the purifier.
@@ -252,12 +279,12 @@ class WinixPurifierAccessory {
252
279
  async getFilterLifeLevel() {
253
280
  const currentFilterHours = await this.device.getFilterHours();
254
281
  if (currentFilterHours <= 0) {
255
- this.log.debug('getFilterLifeLevel(): currentFilterHours is not a positive number:', currentFilterHours);
282
+ this.log.debug('accessory:getFilterLifeLevel(): currentFilterHours is not a positive number:', currentFilterHours);
256
283
  return 100;
257
284
  }
258
285
  const remainingLife = Math.max(MAX_FILTER_HOURS - currentFilterHours, 0);
259
286
  const remainingPercentage = Math.round((remainingLife / MAX_FILTER_HOURS) * 100);
260
- this.log.debug('getFilterLifeLevel()', remainingPercentage);
287
+ this.log.debug('accessory:getFilterLifeLevel()', remainingPercentage);
261
288
  return remainingPercentage;
262
289
  }
263
290
  /**
@@ -269,16 +296,23 @@ class WinixPurifierAccessory {
269
296
  const shouldReplaceFilter = filterLife <= replacementPercentage ?
270
297
  this.Characteristic.FilterChangeIndication.CHANGE_FILTER :
271
298
  this.Characteristic.FilterChangeIndication.FILTER_OK;
272
- this.log.debug('getFilterChangeIndication() filterLife:', filterLife, 'replacementPercentage:', replacementPercentage, 'shouldReplaceFilter:', shouldReplaceFilter);
299
+ this.log.debug('accessory:getFilterChangeIndication() filterLife:', filterLife, 'replacementPercentage:', replacementPercentage, 'shouldReplaceFilter:', shouldReplaceFilter);
273
300
  return shouldReplaceFilter;
274
301
  }
302
+ scheduleHomekitUpdate() {
303
+ this.log.debug('scheduling homekit update');
304
+ setTimeout(async () => {
305
+ await this.device.update();
306
+ await this.sendHomekitUpdate();
307
+ }, 1000);
308
+ }
275
309
  /**
276
310
  * Send an update to Homekit with the latest device status.
277
311
  */
278
312
  async sendHomekitUpdate() {
279
- this.log.debug('sendHomekitUpdate()');
313
+ this.log.debug('accessory:sendHomekitUpdate()');
280
314
  if (!this.device.hasData()) {
281
- this.log.debug('sendHomekitUpdate(): skipping update, no status');
315
+ this.log.debug('accessory:sendHomekitUpdate(): skipping update, no status');
282
316
  return;
283
317
  }
284
318
  const { power, mode, airflow, airQuality, plasmawave, ambientLight, } = await this.device.getState();
@@ -293,11 +327,14 @@ class WinixPurifierAccessory {
293
327
  this.plasmawave?.updateCharacteristic(this.Characteristic.On, this.toSwitch(plasmawave));
294
328
  }
295
329
  if (this.ambientLight !== undefined) {
296
- this.ambientLight?.updateCharacteristic(this.Characteristic.CurrentAmbientLightLevel, ambientLight);
330
+ this.ambientLight?.updateCharacteristic(this.Characteristic.CurrentAmbientLightLevel, this.toAmbientLight(ambientLight));
297
331
  }
298
332
  if (this.autoSwitch !== undefined) {
299
333
  this.autoSwitch?.updateCharacteristic(this.Characteristic.On, this.toTargetState(mode) === this.Characteristic.TargetAirPurifierState.AUTO);
300
334
  }
335
+ if (this.sleepSwitch !== undefined) {
336
+ this.sleepSwitch?.updateCharacteristic(this.Characteristic.On, airflow === winix_api_1.Airflow.Sleep);
337
+ }
301
338
  }
302
339
  toActiveState(power) {
303
340
  switch (power) {
@@ -326,7 +363,7 @@ class WinixPurifierAccessory {
326
363
  toRotationSpeed(airflow) {
327
364
  switch (airflow) {
328
365
  case winix_api_1.Airflow.Sleep:
329
- return 0;
366
+ return 1;
330
367
  case winix_api_1.Airflow.Low:
331
368
  return 25;
332
369
  case winix_api_1.Airflow.Medium:
@@ -338,9 +375,13 @@ class WinixPurifierAccessory {
338
375
  }
339
376
  }
340
377
  toAirflow(state) {
378
+ // Don't return any airflow if the state is explicitly 0, this is a power-off signal
379
+ if (state === 0) {
380
+ return null;
381
+ }
341
382
  // Round to nearest 25
342
383
  const nearestState = Math.round(state / 25) * 25;
343
- this.log.debug(`toAirflow(${state}): ${nearestState}`);
384
+ this.log.debug(`accessory:toAirflow(${state}): ${nearestState}`);
344
385
  switch (nearestState) {
345
386
  case 0:
346
387
  return winix_api_1.Airflow.Sleep;
@@ -373,5 +414,17 @@ class WinixPurifierAccessory {
373
414
  toPlasmawave(state) {
374
415
  return state ? winix_api_1.Plasmawave.On : winix_api_1.Plasmawave.Off;
375
416
  }
417
+ toAmbientLight(ambientLight) {
418
+ return Math.max(ambientLight, MIN_AMBIENT_LIGHT);
419
+ }
420
+ debounce(func, delay) {
421
+ let timeoutId = null;
422
+ return async (arg) => {
423
+ if (timeoutId) {
424
+ clearTimeout(timeoutId);
425
+ }
426
+ timeoutId = setTimeout(async () => await func(arg), delay);
427
+ };
428
+ }
376
429
  }
377
430
  exports.WinixPurifierAccessory = WinixPurifierAccessory;
package/dist/device.js CHANGED
@@ -57,10 +57,10 @@ class Device {
57
57
  async setPower(value) {
58
58
  const initialPower = await this.getPower();
59
59
  if (initialPower === value) {
60
- this.log.debug('setPower(%s)', value, '(no change)');
60
+ this.log.debug('device:setPower(%s)', value, '(no change)');
61
61
  return;
62
62
  }
63
- this.log.debug('setPower()', initialPower, value);
63
+ this.log.debug('device:setPower()', initialPower, value);
64
64
  await winix_api_1.WinixAPI.setPower(this.deviceId, value);
65
65
  this.state.power = value;
66
66
  // default to auto mode when turning on from off
@@ -73,17 +73,17 @@ class Device {
73
73
  // Don't try to set the mode if it's already set to the same value
74
74
  // Fixes issues with this being set right around the time of power on
75
75
  if (!turnedOn && value === await this.getMode()) {
76
- this.log.debug('setMode(%s)', value, '(no change)');
76
+ this.log.debug('device:setMode(%s)', value, '(no change)');
77
77
  return;
78
78
  }
79
- this.log.debug('setMode(%s)', value);
79
+ this.log.debug('device:setMode(%s)', value);
80
80
  await winix_api_1.WinixAPI.setMode(this.deviceId, value);
81
81
  this.state.mode = value;
82
82
  // default to low airflow when switching modes
83
83
  this.state.airflow = winix_api_1.Airflow.Low;
84
84
  }
85
85
  async setAirflow(value) {
86
- this.log.debug('setAirflow(%s)', value);
86
+ this.log.debug('device:setAirflow(%s)', value);
87
87
  // Device must be on and in manual mode to set airflow
88
88
  await this.ensureOn();
89
89
  await this.setMode(winix_api_1.Mode.Manual);
@@ -91,7 +91,7 @@ class Device {
91
91
  this.state.airflow = value;
92
92
  }
93
93
  async setPlasmawave(value) {
94
- this.log.debug('setPlasmawave()', value);
94
+ this.log.debug('device:setPlasmawave()', value);
95
95
  await this.ensureOn();
96
96
  await winix_api_1.WinixAPI.setPlasmawave(this.deviceId, value);
97
97
  this.state.plasmawave = value;
@@ -109,9 +109,9 @@ class Device {
109
109
  };
110
110
  }
111
111
  async update() {
112
- this.log.debug('update()');
112
+ this.log.debug('device:update()');
113
113
  this.state = await winix_api_1.WinixAPI.getDeviceStatus(this.deviceId);
114
- this.log.debug('update()', JSON.stringify(this.state));
114
+ this.log.debug('device:update()', JSON.stringify(this.state));
115
115
  this.lastWinixPoll = Date.now();
116
116
  }
117
117
  async ensureUpdated() {
@@ -127,10 +127,10 @@ class Device {
127
127
  }
128
128
  async ensureOn() {
129
129
  if (await this.getPower() === winix_api_1.Power.On) {
130
- this.log.debug('ensureOn()', 'already on');
130
+ this.log.debug('device:ensureOn()', 'already on');
131
131
  return false;
132
132
  }
133
- this.log.debug('ensureOn()');
133
+ this.log.debug('device:ensureOn()');
134
134
  await this.setPower(winix_api_1.Power.On);
135
135
  return true;
136
136
  }
@@ -0,0 +1 @@
1
+ (function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))c(t);new MutationObserver(t=>{for(const i of t)if(i.type==="childList")for(const a of i.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&c(a)}).observe(document,{childList:!0,subtree:!0});function o(t){const i={};return t.integrity&&(i.integrity=t.integrity),t.referrerPolicy&&(i.referrerPolicy=t.referrerPolicy),t.crossOrigin==="use-credentials"?i.credentials="include":t.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function c(t){if(t.ep)return;t.ep=!0;const i=o(t);fetch(t.href,i)}})();const h={schema:{type:"object",properties:{email:{title:"Email",type:"string","x-schema-form":{type:"email"},required:!0},password:{title:"Password",type:"string","x-schema-form":{type:"password"},required:!0}}}},v=r=>({schema:{deviceOverrides:{type:"array",items:{type:"object",properties:{deviceId:{type:"string",title:"Device ID",required:!0},deviceAlias:{type:"string",title:"Device Alias",required:!0},modelName:{type:"string",title:"Model Name",required:!0},serialNumber:{type:"string",title:"Serial Number",description:"The serial number of the device",placeholder:"WNXAI00000000",required:!1},nameDevice:{type:"string",title:"Device Name",description:"The display name of the device",required:!1},nameAirQualitySensor:{type:"string",title:"Air Quality Sensor Name",description:"The display name of the air quality sensor",placeholder:"Air Quality",required:!1},nameAmbientLightSensor:{type:"string",title:"Ambient Light Sensor Name",description:"The display name of the ambient light sensor",placeholder:"Ambient Light",required:!1},namePlasmawaveSwitch:{type:"string",title:"Plasmawave Switch Name",description:"The display name of the Plasmawave switch",placeholder:"Plasmawave",required:!1},nameAutoSwitch:{type:"string",title:"Auto Switch Name",description:"The display name of the Auto switch",placeholder:"Auto Mode",required:!1},nameSleepSwitch:{type:"string",title:"Sleep Switch Name",description:"The display name of the Sleep switch",placeholder:"Sleep",required:!1}}}}},form:[{type:"tabarray",key:"deviceOverrides",legend:"{{value.deviceAlias}} ({{value.modelName}})",maxItems:r,removable:!1,items:["deviceOverrides[].serialNumber","deviceOverrides[].nameDevice","deviceOverrides[].nameAirQualitySensor","deviceOverrides[].nameAmbientLightSensor","deviceOverrides[].namePlasmawaveSwitch","deviceOverrides[].nameAutoSwitch","deviceOverrides[].nameSleepSwitch"]}]}),{homebridge:e}=window,d=document.getElementById("header-winix-buttons"),w=document.getElementById("btn-winix-new-token"),S=document.getElementById("btn-winix-device-overrides"),m=document.getElementById("header-winix-link-account"),u=document.getElementById("header-winix-device-overrides"),p=document.getElementById("txt-auth-issues");w.addEventListener("click",()=>y());S.addEventListener("click",()=>b());e.showSpinner();P();async function P(){var o;const[r]=await e.getPluginConfig();((o=r==null?void 0:r.auth)==null?void 0:o.refreshToken)?g():await y()}async function y(){e.hideSchemaForm(),d==null||d.style.setProperty("display","none"),m==null||m.style.setProperty("display","block");const r=await O();r&&(p==null||p.style.setProperty("display","block")),e.hideSpinner();const n=e.createForm(h,{},"Log In",r?"Back":void 0);n.onSubmit(async({email:o,password:c})=>{e.showSpinner();try{const t=await e.request("/login",{email:o,password:c});await N(t),e.toast.success("Linked with Winix account","Winix Purifiers"),g()}catch(t){const i=t;console.error("error logging in",i.message),e.toast.error("Login Failed: "+i.message,"Winix Purifiers")}finally{e.hideSpinner()}}),n.onCancel(()=>g())}async function b(){e.showSpinner();const r=await x();let n;try{n=await e.request("/discover",r)}catch(s){const l=s;console.error("error discovering devices",l.message),e.toast.error("Device Discovery Failed: "+l.message,"Winix Purifiers"),e.hideSpinner();return}const o=n.devices.map(s=>({deviceId:s.deviceId,deviceAlias:s.deviceAlias,modelName:s.modelName})).reduce((s,l)=>s.set(l.deviceId,l),new Map),[c]=await e.getPluginConfig();((c==null?void 0:c.deviceOverrides)??[]).forEach(s=>{const l=o.get(s.deviceId);l&&o.set(s.deviceId,{...l,...s})});const i=Array.from(o.values());d==null||d.style.setProperty("display","none"),u==null||u.style.setProperty("display","block"),e.hideSchemaForm(),e.hideSpinner();const a=e.createForm(v(i.length),{deviceOverrides:i},"Save All","Back");a.onSubmit(async({deviceOverrides:s})=>{s=A(s);const[l,...f]=await e.getPluginConfig();await e.updatePluginConfig([{...l,deviceOverrides:s},...f]),await e.savePluginConfig(),e.toast.success("Device configurations updated successfully","Winix Purifiers"),g()}),a.onCancel(()=>{e.toast.warning("Device configurations not updated","Winix Purifiers"),g()})}function A(r){return r.filter(n=>{const{deviceId:o,deviceAlias:c,modelName:t,...i}=n;return Object.values(i).some(a=>a!==void 0&&a!=="")}).map(n=>{const{deviceId:o,deviceAlias:c,modelName:t,...i}=n;return Object.keys(i).forEach(a=>{if(!(a in i))return;const s=i[a];(s===void 0||s==="")&&(i[a]=void 0)}),{deviceId:o,...i}})}function g(){d==null||d.style.setProperty("display","block"),m==null||m.style.setProperty("display","none"),p==null||p.style.setProperty("display","none"),u==null||u.style.setProperty("display","none"),e.showSchemaForm(),e.hideSpinner()}async function N(r){const[n,...o]=await e.getPluginConfig();await e.updatePluginConfig([{...n,auth:r},...o]),await e.savePluginConfig()}async function O(){var n;const[r]=await e.getPluginConfig();return!!((n=r==null?void 0:r.auth)!=null&&n.refreshToken)}async function x(){const[r]=await e.getPluginConfig();return r.auth}
@@ -1,4 +1,4 @@
1
- <script type="module" crossorigin src="./index-S5C9rjtx.js"></script>
1
+ <script type="module" crossorigin src="./index-ta8s453r.js"></script>
2
2
  <div id="header-winix-buttons" class="container-fluid" style="display: none;">
3
3
  <div class="row">
4
4
  <!-- Left-aligned button -->
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "displayName": "Winix Air Purifiers",
4
4
  "name": "homebridge-winix-purifiers",
5
- "version": "2.0.1",
5
+ "version": "2.0.4",
6
6
  "description": "Homebridge plugin for Winix air purifiers",
7
7
  "license": "Apache-2.0",
8
8
  "repository": {
@@ -1 +0,0 @@
1
- (function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))c(t);new MutationObserver(t=>{for(const i of t)if(i.type==="childList")for(const a of i.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&c(a)}).observe(document,{childList:!0,subtree:!0});function o(t){const i={};return t.integrity&&(i.integrity=t.integrity),t.referrerPolicy&&(i.referrerPolicy=t.referrerPolicy),t.crossOrigin==="use-credentials"?i.credentials="include":t.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function c(t){if(t.ep)return;t.ep=!0;const i=o(t);fetch(t.href,i)}})();const h={schema:{type:"object",properties:{email:{title:"Email",type:"string","x-schema-form":{type:"email"},required:!0},password:{title:"Password",type:"string","x-schema-form":{type:"password"},required:!0}}}},v=r=>({schema:{deviceOverrides:{type:"array",items:{type:"object",properties:{deviceId:{type:"string",title:"Device ID",required:!0},deviceAlias:{type:"string",title:"Device Alias",required:!0},modelName:{type:"string",title:"Model Name",required:!0},serialNumber:{type:"string",title:"Serial Number",description:"The serial number of the device",placeholder:"WNXAI00000000",required:!1},nameDevice:{type:"string",title:"Device Name",description:"The display name of the device",required:!1},nameAirQualitySensor:{type:"string",title:"Air Quality Sensor Name",description:"The display name of the air quality sensor",placeholder:"Air Quality",required:!1},nameAmbientLightSensor:{type:"string",title:"Ambient Light Sensor Name",description:"The display name of the ambient light sensor",placeholder:"Ambient Light",required:!1},namePlasmawaveSwitch:{type:"string",title:"Plasmawave Switch Name",description:"The display name of the Plasmawave switch",placeholder:"Plasmawave",required:!1},nameAutoSwitch:{type:"string",title:"Auto Switch Name",description:"The display name of the Auto switch",placeholder:"Auto Mode",required:!1}}}}},form:[{type:"tabarray",key:"deviceOverrides",legend:"{{value.deviceAlias}} ({{value.modelName}})",maxItems:r,removable:!1,items:["deviceOverrides[].serialNumber","deviceOverrides[].nameDevice","deviceOverrides[].nameAirQualitySensor","deviceOverrides[].nameAmbientLightSensor","deviceOverrides[].namePlasmawaveSwitch","deviceOverrides[].nameAutoSwitch"]}]}),{homebridge:e}=window,d=document.getElementById("header-winix-buttons"),w=document.getElementById("btn-winix-new-token"),P=document.getElementById("btn-winix-device-overrides"),u=document.getElementById("header-winix-link-account"),m=document.getElementById("header-winix-device-overrides"),p=document.getElementById("txt-auth-issues");w.addEventListener("click",()=>y());P.addEventListener("click",()=>S());e.showSpinner();b();async function b(){var o;const[r]=await e.getPluginConfig();((o=r==null?void 0:r.auth)==null?void 0:o.refreshToken)?g():await y()}async function y(){e.hideSchemaForm(),d==null||d.style.setProperty("display","none"),u==null||u.style.setProperty("display","block");const r=await x();r&&(p==null||p.style.setProperty("display","block")),e.hideSpinner();const n=e.createForm(h,{},"Log In",r?"Back":void 0);n.onSubmit(async({email:o,password:c})=>{e.showSpinner();try{const t=await e.request("/login",{email:o,password:c});await N(t),e.toast.success("Linked with Winix account","Winix Purifiers"),g()}catch(t){const i=t;console.error("error logging in",i.message),e.toast.error("Login Failed: "+i.message,"Winix Purifiers")}finally{e.hideSpinner()}}),n.onCancel(()=>g())}async function S(){e.showSpinner();const r=await O();let n;try{n=await e.request("/discover",r)}catch(s){const l=s;console.error("error discovering devices",l.message),e.toast.error("Device Discovery Failed: "+l.message,"Winix Purifiers"),e.hideSpinner();return}const o=n.devices.map(s=>({deviceId:s.deviceId,deviceAlias:s.deviceAlias,modelName:s.modelName})).reduce((s,l)=>s.set(l.deviceId,l),new Map),[c]=await e.getPluginConfig();((c==null?void 0:c.deviceOverrides)??[]).forEach(s=>{const l=o.get(s.deviceId);l&&o.set(s.deviceId,{...l,...s})});const i=Array.from(o.values());d==null||d.style.setProperty("display","none"),m==null||m.style.setProperty("display","block"),e.hideSchemaForm(),e.hideSpinner();const a=e.createForm(v(i.length),{deviceOverrides:i},"Save All","Back");a.onSubmit(async({deviceOverrides:s})=>{s=A(s);const[l,...f]=await e.getPluginConfig();await e.updatePluginConfig([{...l,deviceOverrides:s},...f]),await e.savePluginConfig(),e.toast.success("Device configurations updated successfully","Winix Purifiers"),g()}),a.onCancel(()=>{e.toast.warning("Device configurations not updated","Winix Purifiers"),g()})}function A(r){return r.filter(n=>{const{deviceId:o,deviceAlias:c,modelName:t,...i}=n;return Object.values(i).some(a=>a!==void 0&&a!=="")}).map(n=>{const{deviceId:o,deviceAlias:c,modelName:t,...i}=n;return Object.keys(i).forEach(a=>{if(!(a in i))return;const s=i[a];(s===void 0||s==="")&&(i[a]=void 0)}),{deviceId:o,...i}})}function g(){d==null||d.style.setProperty("display","block"),u==null||u.style.setProperty("display","none"),p==null||p.style.setProperty("display","none"),m==null||m.style.setProperty("display","none"),e.showSchemaForm(),e.hideSpinner()}async function N(r){const[n,...o]=await e.getPluginConfig();await e.updatePluginConfig([{...n,auth:r},...o]),await e.savePluginConfig()}async function x(){var n;const[r]=await e.getPluginConfig();return!!((n=r==null?void 0:r.auth)!=null&&n.refreshToken)}async function O(){const[r]=await e.getPluginConfig();return r.auth}