homebridge-ac-freedom 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ac-accessory.js +36 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-ac-freedom",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "displayName": "Homebridge AC Freedom",
5
5
  "description": "Homebridge plugin for AUX air conditioners via Broadlink (local UDP) and AUX Cloud API",
6
6
  "main": "index.js",
@@ -180,37 +180,36 @@ class AcFreedomAccessory {
180
180
  });
181
181
  }
182
182
 
183
- // ── Fan Service (linked to HeaterCooler) ────────────────────────
183
+ // ── Fan Speed as SecuritySystem (linked to HeaterCooler) ─────────
184
+ // Off(3)=Auto Night(2)=Low Away(1)=Medium Home(0)=High
184
185
  setupFanService() {
186
+ // Remove legacy Fanv2 service if it exists (migration)
187
+ const oldFan = this.accessory.getServiceById(this.Service.Fanv2, 'fan');
188
+ if (oldFan) this.accessory.removeService(oldFan);
189
+
185
190
  if (this.config.showFan === false) {
186
- const existing = this.accessory.getServiceById(this.Service.Fanv2, 'fan');
191
+ const existing = this.accessory.getServiceById(this.Service.SecuritySystem, 'fan');
187
192
  if (existing) this.accessory.removeService(existing);
188
193
  return;
189
194
  }
190
195
 
191
196
  const C = this.Characteristic;
192
197
 
193
- this.fanService = this.accessory.getServiceById(this.Service.Fanv2, 'fan')
194
- || this.accessory.addService(this.Service.Fanv2, 'Fan', 'fan');
195
-
196
- // Fan Active follows AC power
197
- this.fanService.getCharacteristic(C.Active)
198
- .onGet(() => this.state.power ? C.Active.ACTIVE : C.Active.INACTIVE)
199
- .onSet(async (value) => {
200
- this.state.power = value === C.Active.ACTIVE;
201
- await this.sendPower(this.state.power);
202
- });
198
+ this.fanService = this.accessory.getServiceById(this.Service.SecuritySystem, 'fan')
199
+ || this.accessory.addService(this.Service.SecuritySystem, 'Fan Speed', 'fan');
203
200
 
204
- // Rotation speed: 0=auto, 20=mute, 40=low, 60=med, 80=high, 100=turbo
205
- this.fanService.getCharacteristic(C.RotationSpeed)
206
- .setProps({ minValue: 0, maxValue: 100, minStep: 1 })
207
- .onGet(() => this.fanSpeedToPercent(this.state.fanSpeed))
201
+ // Target state controls fan speed
202
+ this.fanService.getCharacteristic(C.SecuritySystemTargetState)
203
+ .setProps({ validValues: [0, 1, 2, 3] })
204
+ .onGet(() => this.fanSpeedToSecState(this.state.fanSpeed))
208
205
  .onSet(async (value) => {
209
- this.state.fanSpeed = this.percentToFanSpeed(value);
206
+ this.state.fanSpeed = this.secStateToFanSpeed(value);
210
207
  await this.sendFanSpeed(this.state.fanSpeed);
211
208
  });
212
209
 
213
- // Link fan to HeaterCooler so it appears inside the climate card
210
+ this.fanService.getCharacteristic(C.SecuritySystemCurrentState)
211
+ .onGet(() => this.fanSpeedToSecState(this.state.fanSpeed));
212
+
214
213
  this.heaterCooler.addLinkedService(this.fanService);
215
214
  }
216
215
 
@@ -304,19 +303,24 @@ class AcFreedomAccessory {
304
303
  this.heaterCooler.addLinkedService(this.displaySwitch);
305
304
  }
306
305
 
307
- // ── Fan speed mapping ──────────────────────────────────────────
308
- fanSpeedToPercent(speed) {
309
- const map = { 0: 0, 5: 20, 1: 40, 2: 60, 3: 80, 4: 100 };
310
- return map[speed] ?? 0;
306
+ // ── Fan speed ↔ SecuritySystem state mapping ───────────────────
307
+ // Home(0)=High Away(1)=Medium Night(2)=Low Off/Disarmed(3)=Auto
308
+ fanSpeedToSecState(speed) {
309
+ switch (speed) {
310
+ case FAN_SPEED.HIGH: return 0; // Home
311
+ case FAN_SPEED.MEDIUM: return 1; // Away
312
+ case FAN_SPEED.LOW: return 2; // Night
313
+ default: return 3; // Off = Auto
314
+ }
311
315
  }
312
316
 
313
- percentToFanSpeed(pct) {
314
- if (pct <= 0) return FAN_SPEED.AUTO;
315
- if (pct <= 20) return FAN_SPEED.MUTE;
316
- if (pct <= 40) return FAN_SPEED.LOW;
317
- if (pct <= 60) return FAN_SPEED.MEDIUM;
318
- if (pct <= 80) return FAN_SPEED.HIGH;
319
- return FAN_SPEED.TURBO;
317
+ secStateToFanSpeed(state) {
318
+ switch (state) {
319
+ case 0: return FAN_SPEED.HIGH;
320
+ case 1: return FAN_SPEED.MEDIUM;
321
+ case 2: return FAN_SPEED.LOW;
322
+ default: return FAN_SPEED.AUTO;
323
+ }
320
324
  }
321
325
 
322
326
  // ── Poll state ─────────────────────────────────────────────────
@@ -419,10 +423,9 @@ class AcFreedomAccessory {
419
423
  (this.state.swingV || this.state.swingH) ? C.SwingMode.SWING_ENABLED : C.SwingMode.SWING_DISABLED);
420
424
 
421
425
  if (this.fanService) {
422
- this.fanService.updateCharacteristic(C.Active,
423
- this.state.power ? C.Active.ACTIVE : C.Active.INACTIVE);
424
- this.fanService.updateCharacteristic(C.RotationSpeed,
425
- this.fanSpeedToPercent(this.state.fanSpeed));
426
+ const secState = this.fanSpeedToSecState(this.state.fanSpeed);
427
+ this.fanService.updateCharacteristic(C.SecuritySystemCurrentState, secState);
428
+ this.fanService.updateCharacteristic(C.SecuritySystemTargetState, secState);
426
429
  }
427
430
 
428
431
  for (const [key, svc] of Object.entries(this.presetSwitches || {})) {