homebridge-viessmann-vicare 2.0.7 → 2.0.8

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
@@ -142,7 +142,7 @@ src/
142
142
  **API Errors** → Debug `viessmann-api-endpoints.ts` parsing
143
143
  **Network Issues** → Examine `network-utils.ts` detection
144
144
 
145
- For detailed architecture documentation, see [ARCHITECTURE.md](ARCHITECTURE.md).
145
+ For detailed architecture documentation, see the [Plugin Architecture](#%EF%B8%8F-plugin-architecture-v20) section above.
146
146
 
147
147
  ## 🆕 What's New in v2.0
148
148
 
@@ -263,7 +263,7 @@ npm install -g homebridge-viessmann-vicare
263
263
  "maxDelay": 300000,
264
264
  "maxConsecutiveErrors": 5,
265
265
  "deviceUpdateDelay": 1000,
266
- "userAgent": "homebridge-viessmann-vicare/2.0.4"
266
+ "userAgent": "homebridge-viessmann-vicare/2.0.8"
267
267
  },
268
268
  "debug": false
269
269
  }
@@ -810,7 +810,7 @@ Contributions are welcome! Please:
810
810
  ## 📋 Compatibility
811
811
 
812
812
  - **Homebridge**: >= 1.8.0 or >= 2.0.0-beta.0
813
- - **Node.js**: >= 18.15.0
813
+ - **Node.js**: >= 20.0.0
814
814
  - **Viessmann API**: v1 and v2
815
815
  - **iOS**: All HomeKit-supported devices
816
816
  - **Languages**: All languages supported via custom names
@@ -841,6 +841,27 @@ For issues and questions:
841
841
 
842
842
  ## 📈 Changelog
843
843
 
844
+ ### [2.0.8] - 2026-02-26
845
+ **Fixed**
846
+ - 🐛 Token storage now uses `api.user.storagePath()` (Homebridge storage directory) instead of `process.cwd()`, as required by Homebridge plugin verification guidelines.
847
+ - 🐛 Early config validation in `ViessmannPlatform` constructor: plugin exits cleanly if `clientId` or `username` are missing, instead of crashing on first API call.
848
+ **Changed**
849
+ - 🔧 `PLUGIN_VERSION` updated to `2.0.8` in `settings.ts` (User-Agent and logs now reflect the correct version).
850
+ - 🔧 `ViessmannAPI` constructor now accepts optional `storagePath` parameter passed from Homebridge.
851
+ - 🔧 `AuthManager` constructor accepts optional `storagePath` parameter (fallback to `.homebridge` in cwd for backward compatibility).
852
+
853
+ ### [2.0.7] - 2025-10-07
854
+ **Fixed**
855
+ - 🐛 Minor stability improvements and internal code cleanup.
856
+
857
+ ### [2.0.6] - 2025-10-07
858
+ **Fixed**
859
+ - 🐛 Improved error handling for edge cases in token refresh flow.
860
+
861
+ ### [2.0.5] - 2025-10-07
862
+ **Fixed**
863
+ - 🐛 Additional resilience improvements for authentication on headless systems.
864
+
844
865
  ### [2.0.4] - 2025-10-06
845
866
  **Added**
846
867
  - ✨ `logEnvDiagnostics()` for better detection of graphical environment (X11, Wayland, systemd, headless).
@@ -1 +1 @@
1
- {"version":3,"file":"boiler-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/boiler-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAEnI,qBAAa,wBAAwB;IA2CjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IA9CzB,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,iBAAiB,CAAC,CAAU;IACpC,OAAO,CAAC,aAAa,CAAC,CAAU;IAGhC,OAAO,CAAC,yBAAyB,CAAC,CAAU;IAC5C,OAAO,CAAC,qBAAqB,CAAC,CAAU;IACxC,OAAO,CAAC,uBAAuB,CAAC,CAAU;IAC1C,OAAO,CAAC,uBAAuB,CAAC,CAAU;IAC1C,OAAO,CAAC,qBAAqB,CAAC,CAAU;IACxC,OAAO,CAAC,uBAAuB,CAAC,CAAU;IAC1C,OAAO,CAAC,oBAAoB,CAAC,CAAU;IAEvC,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,iBAAiB,CAAK;IAE9B,OAAO,CAAC,MAAM,CAoBZ;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe;YAuB5B,sBAAsB;IAmBpC,OAAO,CAAC,mBAAmB;IA2E3B,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,wBAAwB;IAoEhC,OAAO,CAAC,kBAAkB;IAwD1B,OAAO,CAAC,sBAAsB;IAyE9B,OAAO,CAAC,uBAAuB;IAmO/B,OAAO,CAAC,gCAAgC;IAqCxC,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,gBAAgB;IAIlB,qBAAqB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIrD,8BAA8B,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI9D,8BAA8B,CAAC,KAAK,EAAE,mBAAmB;YAoCjD,YAAY;YAQZ,kBAAkB;IA+SzB,oBAAoB,IAAI;QAC7B,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACxC,mBAAmB,EAAE,MAAM,CAAC;QAC5B,sBAAsB,EAAE,MAAM,CAAC;QAC/B,qBAAqB,EAAE,MAAM,CAAC;QAC9B,wBAAwB,EAAE,MAAM,CAAC;QACjC,kBAAkB,EAAE,MAAM,CAAC;QAC3B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB;CAsBF"}
1
+ {"version":3,"file":"boiler-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/boiler-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAEnI,qBAAa,wBAAwB;IA2CjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IA9CzB,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,iBAAiB,CAAC,CAAU;IACpC,OAAO,CAAC,aAAa,CAAC,CAAU;IAGhC,OAAO,CAAC,yBAAyB,CAAC,CAAU;IAC5C,OAAO,CAAC,qBAAqB,CAAC,CAAU;IACxC,OAAO,CAAC,uBAAuB,CAAC,CAAU;IAC1C,OAAO,CAAC,uBAAuB,CAAC,CAAU;IAC1C,OAAO,CAAC,qBAAqB,CAAC,CAAU;IACxC,OAAO,CAAC,uBAAuB,CAAC,CAAU;IAC1C,OAAO,CAAC,oBAAoB,CAAC,CAAU;IAEvC,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,iBAAiB,CAAK;IAE9B,OAAO,CAAC,MAAM,CAoBZ;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe;YAuB5B,sBAAsB;IAmBpC,OAAO,CAAC,mBAAmB;IA2E3B,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,yBAAyB;IAoBjC,OAAO,CAAC,wBAAwB;IAoEhC,OAAO,CAAC,kBAAkB;IAwD1B,OAAO,CAAC,sBAAsB;IAyE9B,OAAO,CAAC,uBAAuB;IAmO/B,OAAO,CAAC,gCAAgC;IAqCxC,OAAO,CAAC,2BAA2B;IAInC,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,gBAAgB;IAIlB,qBAAqB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIrD,8BAA8B,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI9D,8BAA8B,CAAC,KAAK,EAAE,mBAAmB;YAoCjD,YAAY;YAYZ,kBAAkB;IAoTzB,oBAAoB,IAAI;QAC7B,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACxC,mBAAmB,EAAE,MAAM,CAAC;QAC5B,sBAAsB,EAAE,MAAM,CAAC;QAC/B,qBAAqB,EAAE,MAAM,CAAC;QAC9B,wBAAwB,EAAE,MAAM,CAAC;QACjC,kBAAkB,EAAE,MAAM,CAAC;QAC3B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB;CAsBF"}
@@ -562,11 +562,15 @@ class ViessmannBoilerAccessory {
562
562
  }
563
563
  }
564
564
  async handleUpdate(features) {
565
+ const t0 = Date.now();
565
566
  try {
566
567
  await this.updateFromFeatures(features);
568
+ this.platform.log.debug(`🔥 Caldaia handleUpdate OK in ${Date.now() - t0}ms`);
567
569
  }
568
570
  catch (error) {
569
- this.platform.log.error('Error handling boiler update:', error);
571
+ const msg = error instanceof Error ? error.message : String(error);
572
+ this.platform.log.error(`❌ Caldaia handleUpdate failed after ${Date.now() - t0}ms: ${msg}`);
573
+ this.platform.log.error(` State at failure: burner=${this.states.BurnerActive}, temp=${this.states.CurrentTemperature}°C, mod=${this.states.Modulation}%`);
570
574
  }
571
575
  }
572
576
  async updateFromFeatures(features) {
@@ -798,8 +802,15 @@ class ViessmannBoilerAccessory {
798
802
  const efficiency = parseFloat(startsPerHour) < 2 ? 'Good' : 'Poor';
799
803
  diagnosticInfo.push(`Efficiency: ${startsPerHour}starts/h(${efficiency})`);
800
804
  }
801
- const diagnosticStr = diagnosticInfo.length > 0 ? `, ${diagnosticInfo.join(', ')}` : '';
802
- this.platform.log.debug(`Boiler Status - Temp: ${this.states.CurrentTemperature}°C ${this.states.HeatingThresholdTemperature}°C, Burner: ${this.states.BurnerActive ? 'ON' : 'OFF'}, Modulation: ${this.states.Modulation}%${diagnosticStr}`);
805
+ const diagnosticStr = diagnosticInfo.length > 0 ? ` | ${diagnosticInfo.join(' | ')}` : '';
806
+ // Log sempre a debug; se il bruciatore è attivo logga anche a info per visibilità
807
+ const statusLine = `🔥 Caldaia — burner=${this.states.BurnerActive ? 'ON 🔥' : 'OFF'} | mod=${this.states.Modulation}% | temp=${this.states.CurrentTemperature}°C→${this.states.HeatingThresholdTemperature}°C${diagnosticStr}`;
808
+ if (this.states.BurnerActive) {
809
+ this.platform.log.info(statusLine);
810
+ }
811
+ else {
812
+ this.platform.log.debug(statusLine);
813
+ }
803
814
  }
804
815
  // 🆕 NEW: Public method to get diagnostic summary for platform health reports
805
816
  getDiagnosticSummary() {
@@ -1 +1 @@
1
- {"version":3,"file":"dhw-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/dhw-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAGnI,qBAAa,qBAAqB;IAsB9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAzBzB,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,UAAU,CAAC,CAAU;IAC7B,OAAO,CAAC,UAAU,CAAC,CAAU;IAE7B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,MAAM,CAOZ;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe;YAuB5B,sBAAsB;IAmBpC,OAAO,CAAC,mBAAmB;IAmE7B,OAAO,CAAC,oBAAoB;IAW1B,OAAO,CAAC,yBAAyB;IAqBjC,OAAO,CAAC,wBAAwB;IAsFhC,OAAO,CAAC,iBAAiB;IAgGzB,OAAO,CAAC,qBAAqB;IAqBzB,SAAS,CAAC,KAAK,EAAE,mBAAmB;IAoBlC,cAAc,CAAC,KAAK,EAAE,mBAAmB;IAsBzC,UAAU,CAAC,KAAK,EAAE,mBAAmB;IAsBrC,UAAU,CAAC,KAAK,EAAE,mBAAmB;YAsB7B,OAAO;IA0CrB,OAAO,CAAC,wBAAwB;YA8BlB,iBAAiB;IAqBzB,qBAAqB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIrD,8BAA8B,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAK9D,8BAA8B,CAAC,KAAK,EAAE,mBAAmB;YA8CjD,YAAY;YAQZ,kBAAkB;IAqCzB,2BAA2B,CAAC,YAAY,EAAE,GAAG,GAAG,IAAI;IA2DpD,kBAAkB,IAAI;QAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,0BAA0B,EAAE,OAAO,CAAC;QACpC,gBAAgB,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/C,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,OAAO,CAAC;QACxB,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;KACrB;IAaM,YAAY,IAAI;QACrB,QAAQ,EAAE,OAAO,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;IAgBY,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IA+B9C,OAAO,CAAC,8BAA8B;IAwBzB,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IA4BxC,OAAO,IAAI,IAAI;CASvB"}
1
+ {"version":3,"file":"dhw-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/dhw-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAGnI,qBAAa,qBAAqB;IAsB9B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAzBzB,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,kBAAkB,CAAU;IACpC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,UAAU,CAAC,CAAU;IAC7B,OAAO,CAAC,UAAU,CAAC,CAAU;IAE7B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,MAAM,CAOZ;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe;YAuB5B,sBAAsB;IAmBpC,OAAO,CAAC,mBAAmB;IAmE7B,OAAO,CAAC,oBAAoB;IAW1B,OAAO,CAAC,yBAAyB;IAqBjC,OAAO,CAAC,wBAAwB;IAsFhC,OAAO,CAAC,iBAAiB;IAgGzB,OAAO,CAAC,qBAAqB;IAqBzB,SAAS,CAAC,KAAK,EAAE,mBAAmB;IAoBlC,cAAc,CAAC,KAAK,EAAE,mBAAmB;IAsBzC,UAAU,CAAC,KAAK,EAAE,mBAAmB;IAsBrC,UAAU,CAAC,KAAK,EAAE,mBAAmB;YAsB7B,OAAO;IA0CrB,OAAO,CAAC,wBAAwB;YA8BlB,iBAAiB;IAqBzB,qBAAqB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIrD,8BAA8B,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAK9D,8BAA8B,CAAC,KAAK,EAAE,mBAAmB;YA8CjD,YAAY;YAcZ,kBAAkB;IA+DzB,2BAA2B,CAAC,YAAY,EAAE,GAAG,GAAG,IAAI;IA2DpD,kBAAkB,IAAI;QAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;QACzB,0BAA0B,EAAE,OAAO,CAAC;QACpC,gBAAgB,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAA;SAAE,CAAC;QAC/C,WAAW,EAAE,MAAM,CAAC;QACpB,cAAc,EAAE,OAAO,CAAC;QACxB,UAAU,EAAE,OAAO,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;KACrB;IAaM,YAAY,IAAI;QACrB,QAAQ,EAAE,OAAO,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;QACpB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,iBAAiB,EAAE,MAAM,CAAC;QAC1B,kBAAkB,EAAE,OAAO,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,CAAC,EAAE,MAAM,CAAC;KACzB;IAgBY,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IA+B9C,OAAO,CAAC,8BAA8B;IAwBzB,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IA4BxC,OAAO,IAAI,IAAI;CASvB"}
@@ -495,42 +495,78 @@ class ViessmannDHWAccessory {
495
495
  }
496
496
  }
497
497
  async handleUpdate(features) {
498
+ // FIX#6: log entry/exit with timing so stale states are immediately visible in logs
499
+ const t0 = Date.now();
498
500
  try {
499
501
  await this.updateFromFeatures(features);
502
+ this.platform.log.debug(`🚿 ACS handleUpdate OK in ${Date.now() - t0}ms`);
500
503
  }
501
504
  catch (error) {
502
- this.platform.log.error('Error handling DHW update:', error);
505
+ // FIX#6: was silently swallowing errors — now logs full context so stale state is diagnosable
506
+ const msg = error instanceof Error ? error.message : String(error);
507
+ this.platform.log.error(`❌ ACS handleUpdate failed after ${Date.now() - t0}ms: ${msg}`);
508
+ this.platform.log.error(` State at failure: mode=${this.currentMode}, temp=${this.states.CurrentTemperature}°C, target=${this.states.HeatingThresholdTemperature}°C`);
503
509
  }
504
510
  }
505
511
  async updateFromFeatures(features) {
512
+ let changed = false;
506
513
  // Update DHW current temperature
507
514
  const dhwTempFeature = features.find(f => f.feature === 'heating.dhw.sensors.temperature.dhwCylinder' ||
508
515
  f.feature === 'heating.dhw.sensors.temperature.hotWaterStorage' ||
509
516
  f.feature === 'heating.dhw.sensors.temperature.outlet');
510
517
  if (dhwTempFeature?.properties?.value?.value !== undefined) {
511
- this.states.CurrentTemperature = dhwTempFeature.properties.value.value;
518
+ const newTemp = dhwTempFeature.properties.value.value;
519
+ if (newTemp !== this.states.CurrentTemperature) {
520
+ this.platform.log.debug(`🌡️ ACS current temp: ${this.states.CurrentTemperature}°C → ${newTemp}°C`);
521
+ this.states.CurrentTemperature = newTemp;
522
+ changed = true;
523
+ }
512
524
  this.heaterCoolerService.updateCharacteristic(this.platform.Characteristic.CurrentTemperature, this.states.CurrentTemperature);
513
525
  }
526
+ else {
527
+ this.platform.log.debug(`🌡️ ACS current temp: no sensor feature found (checked dhwCylinder/hotWaterStorage/outlet)`);
528
+ }
514
529
  // Update DHW target temperature
515
530
  const dhwTargetTempFeature = features.find(f => f.feature === 'heating.dhw.temperature.main');
516
531
  if (dhwTargetTempFeature?.properties?.value?.value !== undefined && this.supportsTemperatureControl) {
517
532
  const targetTemp = dhwTargetTempFeature.properties.value.value;
518
533
  if (targetTemp >= this.temperatureConstraints.min && targetTemp <= this.temperatureConstraints.max) {
519
- this.states.HeatingThresholdTemperature = targetTemp;
534
+ if (targetTemp !== this.states.HeatingThresholdTemperature) {
535
+ this.platform.log.debug(`🎯 ACS target temp: ${this.states.HeatingThresholdTemperature}°C → ${targetTemp}°C`);
536
+ this.states.HeatingThresholdTemperature = targetTemp;
537
+ changed = true;
538
+ }
520
539
  this.heaterCoolerService.updateCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature, targetTemp);
521
540
  }
541
+ else {
542
+ this.platform.log.warn(`🎯 ACS target temp ${targetTemp}°C out of range ${this.temperatureConstraints.min}-${this.temperatureConstraints.max}°C — ignored`);
543
+ }
522
544
  }
523
- // Update DHW operating mode
545
+ // Update DHW operating mode — THE MOST CRITICAL UPDATE
524
546
  const dhwOperatingModeFeature = features.find(f => f.feature === 'heating.dhw.operating.modes.active');
525
547
  if (dhwOperatingModeFeature?.properties?.value?.value !== undefined) {
526
548
  const newMode = dhwOperatingModeFeature.properties.value.value;
527
549
  if (newMode !== this.currentMode) {
528
- this.platform.log.debug(`DHW mode updated: ${this.currentMode.toUpperCase()} → ${newMode.toUpperCase()}`);
550
+ this.platform.log.info(`🚿 ACS mode changed: ${this.currentMode.toUpperCase()} → ${newMode.toUpperCase()}`);
529
551
  this.currentMode = newMode;
530
552
  this.updateAllCharacteristics();
553
+ changed = true;
531
554
  }
555
+ else {
556
+ this.platform.log.debug(`🚿 ACS mode unchanged: ${this.currentMode.toUpperCase()}`);
557
+ }
558
+ }
559
+ else {
560
+ this.platform.log.warn(`🚿 ACS mode: feature 'heating.dhw.operating.modes.active' not found in ${features.length} features — state NOT updated`);
561
+ }
562
+ // Summary log — always visible at debug, visible at info only when something changed
563
+ const summary = `mode=${this.currentMode.toUpperCase()} | temp=${this.states.CurrentTemperature}°C | target=${this.states.HeatingThresholdTemperature}°C`;
564
+ if (changed) {
565
+ this.platform.log.info(`🚿 ACS state updated: ${summary}`);
566
+ }
567
+ else {
568
+ this.platform.log.debug(`🚿 ACS state unchanged: ${summary}`);
532
569
  }
533
- this.platform.log.debug(`DHW Status - Mode: ${this.currentMode.toUpperCase()}, Temp: ${this.states.CurrentTemperature}°C → ${this.states.HeatingThresholdTemperature}°C`);
534
570
  }
535
571
  // 🆕 NEW: Handle immediate burner status updates from platform
536
572
  handleImmediateBurnerUpdate(burnerStatus) {
@@ -1 +1 @@
1
- {"version":3,"file":"heating-circuit-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/heating-circuit-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAInI,qBAAa,gCAAgC;IA8CzC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAlDhC,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,kBAAkB,CAAU;IAGpC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,oBAAoB,CAAC,CAAU;IACvC,OAAO,CAAC,sBAAsB,CAAC,CAAU;IAGzC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,cAAc,CAAC,CAAU;IAEjC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,wBAAwB,CAAgB;IAChD,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,cAAc,CAAY;IAElC,OAAO,CAAC,MAAM,CAaZ;IAGF,OAAO,CAAC,mBAAmB,CAIzB;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe,EACvB,aAAa,EAAE,MAAM;YAuB1B,sBAAsB;IAyBpC,OAAO,CAAC,mBAAmB;IAuJ3B,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,yBAAyB;IAqBjC,OAAO,CAAC,wBAAwB;IA4DlC,OAAO,CAAC,+BAA+B;IA8FrC,OAAO,CAAC,2BAA2B;IAqFnC,OAAO,CAAC,mCAAmC;IA0B3C,OAAO,CAAC,+BAA+B;YA2BzB,iBAAiB;YAejB,gBAAgB;YAehB,iBAAiB;YAejB,wBAAwB;IA2CtC,OAAO,CAAC,gCAAgC;IAqBlC,SAAS,CAAC,KAAK,EAAE,mBAAmB;IAgBpC,0BAA0B,CAAC,KAAK,EAAE,mBAAmB;YAa7C,cAAc;YA+Dd,oBAAoB;YA4DpB,sBAAsB;YAqGtB,6BAA6B;YAsH7B,qBAAqB;IAsBnC,OAAO,CAAC,4BAA4B;IAyCpC,OAAO,CAAC,2BAA2B;IAMnC;;OAEG;YACW,oCAAoC;IAiDlD;;OAEG;YACW,6BAA6B;IAyB3C;;OAEG;YACW,+BAA+B;IAsB7C;;OAEG;IACH,OAAO,CAAC,+BAA+B;YAyBzB,OAAO;IAqCrB,OAAO,CAAC,wBAAwB;IAa1B,qBAAqB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIrD,8BAA8B,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI9D,8BAA8B,CAAC,KAAK,EAAE,mBAAmB;IAgD/D,OAAO,CAAC,kBAAkB;IA4BpB,0BAA0B,IAAI,OAAO,CAAC,mBAAmB,CAAC;YAIlD,YAAY;YAQZ,kBAAkB;CA6IjC"}
1
+ {"version":3,"file":"heating-circuit-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/heating-circuit-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAInI,qBAAa,gCAAgC;IA8CzC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAlDhC,OAAO,CAAC,mBAAmB,CAAU;IACrC,OAAO,CAAC,kBAAkB,CAAU;IAGpC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,oBAAoB,CAAC,CAAU;IACvC,OAAO,CAAC,sBAAsB,CAAC,CAAU;IAGzC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,cAAc,CAAC,CAAU;IAEjC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,iBAAiB,CAAgB;IACzC,OAAO,CAAC,wBAAwB,CAAgB;IAChD,OAAO,CAAC,0BAA0B,CAAS;IAC3C,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAa;IAChC,OAAO,CAAC,cAAc,CAAY;IAElC,OAAO,CAAC,MAAM,CAaZ;IAGF,OAAO,CAAC,mBAAmB,CAIzB;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe,EACvB,aAAa,EAAE,MAAM;YAuB1B,sBAAsB;IAyBpC,OAAO,CAAC,mBAAmB;IAuJ3B,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,yBAAyB;IAqBjC,OAAO,CAAC,wBAAwB;IA4DlC,OAAO,CAAC,+BAA+B;IA8FrC,OAAO,CAAC,2BAA2B;IAqFnC,OAAO,CAAC,mCAAmC;IA0B3C,OAAO,CAAC,+BAA+B;YA2BzB,iBAAiB;YAejB,gBAAgB;YAehB,iBAAiB;YAejB,wBAAwB;IA2CtC,OAAO,CAAC,gCAAgC;IAqBlC,SAAS,CAAC,KAAK,EAAE,mBAAmB;IAgBpC,0BAA0B,CAAC,KAAK,EAAE,mBAAmB;YAa7C,cAAc;YA+Dd,oBAAoB;YA4DpB,sBAAsB;YAqGtB,6BAA6B;YAsH7B,qBAAqB;IAsBnC,OAAO,CAAC,4BAA4B;IAyCpC,OAAO,CAAC,2BAA2B;IAMnC;;OAEG;YACW,oCAAoC;IAiDlD;;OAEG;YACW,6BAA6B;IAyB3C;;OAEG;YACW,+BAA+B;IAsB7C;;OAEG;IACH,OAAO,CAAC,+BAA+B;YAyBzB,OAAO;IAqCrB,OAAO,CAAC,wBAAwB;IAa1B,qBAAqB,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAIrD,8BAA8B,IAAI,OAAO,CAAC,mBAAmB,CAAC;IAI9D,8BAA8B,CAAC,KAAK,EAAE,mBAAmB;IAgD/D,OAAO,CAAC,kBAAkB;IA4BpB,0BAA0B,IAAI,OAAO,CAAC,mBAAmB,CAAC;YAIlD,YAAY;YAYZ,kBAAkB;CAwJjC"}
@@ -1072,11 +1072,15 @@ class ViessmannHeatingCircuitAccessory {
1072
1072
  return this.states.CurrentRelativeHumidity;
1073
1073
  }
1074
1074
  async handleUpdate(features) {
1075
+ const t0 = Date.now();
1075
1076
  try {
1076
1077
  await this.updateFromFeatures(features);
1078
+ this.platform.log.debug(`🌡️ Riscaldamento ${this.circuitNumber} handleUpdate OK in ${Date.now() - t0}ms`);
1077
1079
  }
1078
1080
  catch (error) {
1079
- this.platform.log.error(`Error handling heating circuit ${this.circuitNumber} update:`, error);
1081
+ const msg = error instanceof Error ? error.message : String(error);
1082
+ this.platform.log.error(`❌ Riscaldamento ${this.circuitNumber} handleUpdate failed after ${Date.now() - t0}ms: ${msg}`);
1083
+ this.platform.log.error(` State at failure: mode=${this.currentMode}, program=${this.currentProgram}, temp=${this.states.CurrentTemperature}°C`);
1080
1084
  }
1081
1085
  }
1082
1086
  async updateFromFeatures(features) {
@@ -1190,17 +1194,31 @@ class ViessmannHeatingCircuitAccessory {
1190
1194
  if (operatingModeFeature?.properties?.value?.value !== undefined) {
1191
1195
  const newMode = operatingModeFeature.properties.value.value;
1192
1196
  if (newMode !== this.currentMode) {
1193
- this.platform.log.debug(`Heating circuit ${this.circuitNumber} mode updated: ${this.currentMode.toUpperCase()} → ${newMode.toUpperCase()}`);
1197
+ this.platform.log.info(`🌡️ Riscaldamento ${this.circuitNumber} mode changed: ${this.currentMode.toUpperCase()} → ${newMode.toUpperCase()}`);
1194
1198
  this.currentMode = newMode;
1195
1199
  this.updateAllCharacteristics();
1200
+ anyProgramStateChanged = true;
1201
+ }
1202
+ else {
1203
+ this.platform.log.debug(`🌡️ Riscaldamento ${this.circuitNumber} mode unchanged: ${this.currentMode.toUpperCase()}`);
1196
1204
  }
1197
1205
  }
1206
+ else {
1207
+ this.platform.log.warn(`🌡️ Riscaldamento ${this.circuitNumber}: feature '${circuitPrefix}.operating.modes.active' not found — mode NOT updated`);
1208
+ }
1198
1209
  // Update humidity if available
1199
1210
  const humidityFeature = features.find(f => f.feature.includes('sensors.humidity'));
1200
1211
  if (humidityFeature?.properties?.value?.value !== undefined) {
1201
1212
  this.states.CurrentRelativeHumidity = humidityFeature.properties.value.value;
1202
1213
  }
1203
- this.platform.log.debug(`Heating Circuit ${this.circuitNumber} Status - Mode: ${this.currentMode.toUpperCase()}, Room Temp: ${this.states.CurrentTemperature}°C, Target: ${this.states.HeatingThresholdTemperature}°C, Program: ${this.currentProgram.toUpperCase()}`);
1214
+ // Summary: log at info when mode or program changed, debug otherwise
1215
+ const summaryLine = `🌡️ Riscaldamento ${this.circuitNumber} — mode=${this.currentMode.toUpperCase()} | prog=${this.currentProgram.toUpperCase()} | room=${this.states.CurrentTemperature}°C | target=${this.states.HeatingThresholdTemperature}°C`;
1216
+ if (anyProgramStateChanged || anyTemperatureChanged) {
1217
+ this.platform.log.info(summaryLine);
1218
+ }
1219
+ else {
1220
+ this.platform.log.debug(summaryLine);
1221
+ }
1204
1222
  }
1205
1223
  }
1206
1224
  exports.ViessmannHeatingCircuitAccessory = ViessmannHeatingCircuitAccessory;
@@ -36,7 +36,8 @@ export declare class AuthManager {
36
36
  private authServer?;
37
37
  private authTimeout?;
38
38
  private tokenRefreshTimer?;
39
- constructor(log: Logger, config: AuthConfig, httpClient: AxiosInstance, hostIp: string);
39
+ private envDiagnosticsLogged;
40
+ constructor(log: Logger, config: AuthConfig, httpClient: AxiosInstance, hostIp: string, storagePath?: string);
40
41
  private validateAuthConfiguration;
41
42
  private generatePKCECodes;
42
43
  private initializeTokens;
@@ -1 +1 @@
1
- {"version":3,"file":"auth-manager.d.ts","sourceRoot":"","sources":["../src/auth-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-manager.d.ts","sourceRoot":"","sources":["../src/auth-manager.ts"],"names":[],"mappings":"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"}
@@ -62,13 +62,14 @@ class AuthManager {
62
62
  authServer;
63
63
  authTimeout;
64
64
  tokenRefreshTimer;
65
- constructor(log, config, httpClient, hostIp) {
65
+ envDiagnosticsLogged = false; // FIX#4: log once at startup only
66
+ constructor(log, config, httpClient, hostIp, storagePath) {
66
67
  this.log = log;
67
68
  this.config = config;
68
69
  this.httpClient = httpClient;
69
70
  this.hostIp = hostIp;
70
71
  this.redirectUri = `http://${this.hostIp}:${this.config.redirectPort || 4200}/`;
71
- this.tokenStoragePath = path.join(process.cwd(), '.homebridge', 'viessmann-tokens.json');
72
+ this.tokenStoragePath = storagePath || path.join(process.cwd(), '.homebridge', 'viessmann-tokens.json');
72
73
  this.log.debug(`Using redirect URI: ${this.redirectUri}`);
73
74
  this.log.debug(`Token storage path: ${this.tokenStoragePath}`);
74
75
  this.validateAuthConfiguration();
@@ -253,7 +254,8 @@ class AuthManager {
253
254
  if (this.tokenRefreshTimer) {
254
255
  clearTimeout(this.tokenRefreshTimer);
255
256
  }
256
- const refreshTime = this.tokenExpiresAt - (this.config.tokenRefreshBuffer || 300000) - Date.now();
257
+ const buffer = this.config.tokenRefreshBuffer || 300000;
258
+ const refreshTime = this.tokenExpiresAt - buffer - Date.now();
257
259
  if (refreshTime > 0) {
258
260
  this.tokenRefreshTimer = setTimeout(async () => {
259
261
  try {
@@ -266,20 +268,27 @@ class AuthManager {
266
268
  // Don't clear tokens yet, let normal auth flow handle it
267
269
  }
268
270
  }, refreshTime);
269
- const refreshInMinutes = Math.ceil(refreshTime / 60000);
270
- this.log.debug(`⏰ Token refresh scheduled in ${refreshInMinutes} minutes`);
271
+ const refreshInMin = Math.round(refreshTime / 60000);
272
+ const atTime = new Date(Date.now() + refreshTime).toLocaleTimeString('it-IT');
273
+ this.log.info(`⏰ Next token refresh scheduled in ${refreshInMin} min (at ${atTime}, buffer: ${buffer / 1000}s)`);
271
274
  }
272
275
  else {
273
- this.log.warn('⚠️ Token expires soon, immediate refresh needed');
276
+ this.log.warn(`⚠️ Token expires in ${Math.round((this.tokenExpiresAt - Date.now()) / 1000)}s — immediate refresh needed`);
274
277
  }
275
278
  }
276
279
  async authenticate() {
277
280
  try {
278
- this.logEnvDiagnostics();
281
+ // FIX#4: log env diagnostics only once at startup, not on every authenticate() call
282
+ if (!this.envDiagnosticsLogged) {
283
+ this.logEnvDiagnostics();
284
+ this.envDiagnosticsLogged = true;
285
+ }
279
286
  if (this.isTokenValid()) {
280
- this.log.debug('✅ Using existing valid token');
287
+ const remaining = Math.round((this.tokenExpiresAt - Date.now()) / 1000);
288
+ this.log.debug(`🔑 Token valid — ${remaining}s remaining, skipping authenticate`);
281
289
  return;
282
290
  }
291
+ this.log.debug(`🔑 Token invalid or missing — attempting authentication...`);
283
292
  if (this.refreshToken) {
284
293
  this.log.debug('🔄 Attempting to refresh token');
285
294
  try {
@@ -695,6 +704,10 @@ class AuthManager {
695
704
  if (this.refreshTokenExpiresAt && this.refreshTokenExpiresAt < Date.now()) {
696
705
  throw new Error('Refresh token has expired (180 days TTL exceeded)');
697
706
  }
707
+ if (this.refreshTokenExpiresAt) {
708
+ const daysLeft = Math.round((this.refreshTokenExpiresAt - Date.now()) / (24 * 60 * 60 * 1000));
709
+ this.log.debug(`🔑 Using refresh token (${daysLeft} days until expiry)`);
710
+ }
698
711
  const tokenData = new url_1.URLSearchParams({
699
712
  grant_type: 'refresh_token',
700
713
  client_id: this.config.clientId,
@@ -720,7 +733,10 @@ class AuthManager {
720
733
  const tokenRefreshBuffer = this.config.tokenRefreshBuffer || 300000;
721
734
  this.accessToken = authData.access_token;
722
735
  this.refreshToken = authData.refresh_token || this.refreshToken;
723
- this.tokenExpiresAt = now + (authData.expires_in * 1000) - tokenRefreshBuffer;
736
+ // FIX#2: store the REAL expiry time do NOT subtract buffer here.
737
+ // The buffer is applied exclusively in isTokenValid() and scheduleTokenRefresh().
738
+ // Previously the buffer was subtracted twice, causing refresh ~2x too early.
739
+ this.tokenExpiresAt = now + (authData.expires_in * 1000);
724
740
  this.tokenIssuedAt = now;
725
741
  this.tokenScope = 'IoT User offline_access';
726
742
  // Set refresh token expiry if we got a new refresh token
@@ -731,9 +747,10 @@ class AuthManager {
731
747
  this.saveTokens();
732
748
  // Schedule proactive refresh
733
749
  this.scheduleTokenRefresh();
734
- const expiresIn = Math.round((this.tokenExpiresAt - now) / 1000);
750
+ const realExpiryInMin = Math.round(authData.expires_in / 60);
751
+ const effectiveWindowSec = Math.round((authData.expires_in * 1000 - tokenRefreshBuffer) / 1000);
735
752
  const refreshTokenDays = this.refreshTokenExpiresAt ? Math.round((this.refreshTokenExpiresAt - now) / (24 * 60 * 60 * 1000)) : 'unknown';
736
- this.log.debug(`🔑 Tokens updated successfully (expires in ${expiresIn} seconds, refresh token valid for ${refreshTokenDays} days)`);
753
+ this.log.info(`🔑 Tokens acquired real expiry: ${realExpiryInMin}min | effective window: ${effectiveWindowSec}s (buffer: ${tokenRefreshBuffer / 1000}s) | refresh token: ${refreshTokenDays} days`);
737
754
  }
738
755
  getAccessToken() {
739
756
  return this.accessToken;
@@ -753,8 +770,9 @@ class AuthManager {
753
770
  }
754
771
  logEnvDiagnostics() {
755
772
  const env = process.env;
756
- this.log.warn([
757
- '🧪 ENV DIAGNOSTICS',
773
+ // FIX#4: changed from warn to debug — called once at startup only
774
+ this.log.debug([
775
+ '🧪 ENV DIAGNOSTICS (startup)',
758
776
  `platform=${process.platform}`,
759
777
  `DISPLAY=${env.DISPLAY ?? '(unset)'}`,
760
778
  `WAYLAND_DISPLAY=${env.WAYLAND_DISPLAY ?? '(unset)'}`,
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
package/dist/platform.js CHANGED
@@ -359,20 +359,14 @@ class ViessmannPlatform {
359
359
  }, delay);
360
360
  }
361
361
  startRefreshTimer() {
362
- // Calculate refresh interval with rate limit protection
363
- let refreshInterval = this.config.refreshInterval || 60000; // Default 1 minute
364
- if (this.config.enableRateLimitProtection !== false) { // Enabled by default
365
- // Apply backoff multiplier based on previous errors
366
- refreshInterval *= this.backoffMultiplier;
367
- // Ensure minimum interval to prevent rate limiting
368
- const minInterval = 60000; // 1 minute minimum
369
- refreshInterval = Math.max(refreshInterval, minInterval);
370
- // Warn if interval was adjusted
371
- if (refreshInterval !== (this.config.refreshInterval || 60000)) {
372
- this.log.warn(`Refresh interval adjusted to ${refreshInterval / 1000} seconds for rate limit protection`);
373
- }
374
- }
375
- this.log.info(`Starting refresh timer with ${refreshInterval / 1000} second interval`);
362
+ const baseInterval = this.config.refreshInterval || 60000; // Default 1 minute
363
+ let refreshInterval = baseInterval;
364
+ if (this.config.enableRateLimitProtection !== false) {
365
+ refreshInterval = Math.max(baseInterval * this.backoffMultiplier, 60000); // min 1 minute
366
+ }
367
+ const intervalSec = Math.round(refreshInterval / 1000);
368
+ const nextAtTime = new Date(Date.now() + refreshInterval).toLocaleTimeString('it-IT');
369
+ this.log.info(`⏱️ Refresh timer started — interval: ${intervalSec}s (backoff: ×${this.backoffMultiplier.toFixed(2)}) | next update at ${nextAtTime}`);
376
370
  this.refreshTimer = setInterval(() => {
377
371
  this.updateAllDevices();
378
372
  }, refreshInterval);
@@ -512,121 +506,133 @@ class ViessmannPlatform {
512
506
  async updateAllDevices() {
513
507
  // Prevent overlapping updates
514
508
  if (this.isUpdating) {
515
- this.log.debug('Skipping update cycle - previous update still in progress');
509
+ this.log.debug('⏭️ Skipping update cycle previous update still in progress');
516
510
  return;
517
511
  }
518
512
  // Check rate limit status before updating
519
513
  const rateLimitStatus = this.viessmannAPI.getRateLimitStatus();
520
514
  if (rateLimitStatus.isLimited) {
521
- this.log.warn(`Skipping update cycle: API is rate limited for ${rateLimitStatus.waitSeconds} seconds`);
515
+ this.log.warn(`⛔ Skipping update cycle API rate limited for ${rateLimitStatus.waitSeconds}s`);
522
516
  this.adjustRefreshInterval(true);
523
517
  return;
524
518
  }
525
519
  this.isUpdating = true;
526
- this.log.debug('Starting device update cycle...');
527
- let successfulUpdates = 0;
528
- let rateLimitedUpdates = 0;
529
- let errorUpdates = 0;
520
+ const cycleStart = Date.now();
521
+ this.log.debug('🔄 Starting device update cycle...');
522
+ // FIX#5: Deduplicate API calls — build a map of unique deviceKey → features.
523
+ // Previously we called getDeviceFeatures once per accessory, but Caldaia/ACS/Risc all
524
+ // share the same installation+gateway+device, causing 3 redundant API calls per cycle.
525
+ // Now we fetch features ONCE per device and dispatch to all matching accessories.
526
+ const deviceFeatureCache = new Map();
527
+ let successfulDevices = 0;
528
+ let rateLimitedDevices = 0;
529
+ let errorDevices = 0;
530
530
  let skippedForBurnerUpdates = 0;
531
+ let handlersCalled = 0;
532
+ let handlersSkipped = 0;
531
533
  try {
532
534
  for (const accessory of this.accessories) {
535
+ if (!accessory.context.device) {
536
+ this.log.debug(`⚠️ Accessory "${accessory.displayName}" has no device context — skipping`);
537
+ continue;
538
+ }
539
+ const deviceKey = `${accessory.context.installation.id}-${accessory.context.gateway.serial}-${accessory.context.device.id}`;
540
+ // Skip if a recent immediate burner update is pending for this device
541
+ const hasRecentBurnerUpdate = this.pendingBurnerUpdates.has(deviceKey);
542
+ if (hasRecentBurnerUpdate && this.config.enableImmediateBurnerUpdates !== false) {
543
+ this.log.debug(`⚡ "${accessory.displayName}" — skipping full update, immediate burner update in progress`);
544
+ skippedForBurnerUpdates++;
545
+ continue;
546
+ }
533
547
  try {
534
- if (accessory.context.device) {
535
- const deviceKey = `${accessory.context.installation.id}-${accessory.context.gateway.serial}-${accessory.context.device.id}`;
536
- // 🆕 NEW: Skip full update if recent immediate burner update occurred
537
- const hasRecentBurnerUpdate = this.pendingBurnerUpdates.has(deviceKey);
538
- if (hasRecentBurnerUpdate && this.config.enableImmediateBurnerUpdates !== false) {
539
- this.log.debug(`⚡ Skipping full update for device ${accessory.context.device.id} - recent immediate burner update in progress`);
540
- skippedForBurnerUpdates++;
541
- successfulUpdates++; // Count as successful to avoid error handling
542
- continue;
543
- }
544
- // Add timeout handling per device
545
- const controller = new AbortController();
546
- const timeoutId = setTimeout(() => controller.abort(), 25000); // 25s timeout per device
547
- try {
548
- const features = await this.viessmannAPI.getDeviceFeatures(accessory.context.installation.id, accessory.context.gateway.serial, accessory.context.device.id);
549
- clearTimeout(timeoutId);
550
- successfulUpdates++;
551
- // Update accessory based on its type
552
- const service = accessory.getService(this.Service.Thermostat) ||
553
- accessory.getService(this.Service.Switch) ||
554
- accessory.getService(this.Service.TemperatureSensor);
555
- if (service && accessory.context.updateHandler) {
556
- // Call the update handler if available
557
- accessory.context.updateHandler(features);
558
- }
559
- }
560
- catch (error) {
561
- clearTimeout(timeoutId);
562
- throw error;
548
+ // FIX#5: fetch features only once per unique device, reuse for other accessories
549
+ let features;
550
+ if (deviceFeatureCache.has(deviceKey)) {
551
+ features = deviceFeatureCache.get(deviceKey);
552
+ this.log.debug(`📋 "${accessory.displayName}" using already-fetched features for device ${accessory.context.device.id}`);
553
+ }
554
+ else {
555
+ this.log.debug(`🌐 "${accessory.displayName}" fetching features for device ${accessory.context.device.id}...`);
556
+ const timeoutId = setTimeout(() => {
557
+ this.log.warn(`⏱️ API timeout after 25s for device ${accessory.context.device.id}`);
558
+ }, 25000);
559
+ features = await this.viessmannAPI.getDeviceFeatures(accessory.context.installation.id, accessory.context.gateway.serial, accessory.context.device.id);
560
+ clearTimeout(timeoutId);
561
+ deviceFeatureCache.set(deviceKey, features);
562
+ successfulDevices++;
563
+ this.log.debug(`✅ Fetched ${features.length} features for device ${accessory.context.device.id}`);
564
+ // Small delay between distinct device API calls only
565
+ if (this.config.enableRateLimitProtection !== false) {
566
+ await this.sleep(500);
563
567
  }
564
568
  }
569
+ // FIX#1: Call updateHandler directly — no longer gating on service type.
570
+ // Previously the code checked for Thermostat/Switch/TemperatureSensor, but
571
+ // DHW/Boiler/HC all use HeaterCooler as their primary service, so the condition
572
+ // could silently skip the handler if services were mid-recreation during async init.
573
+ if (accessory.context.updateHandler) {
574
+ accessory.context.updateHandler(features);
575
+ handlersCalled++;
576
+ this.log.debug(`📡 "${accessory.displayName}" — updateHandler dispatched`);
577
+ }
578
+ else {
579
+ handlersSkipped++;
580
+ this.log.warn(`⚠️ "${accessory.displayName}" — updateHandler not set (accessory still initializing?)`);
581
+ }
565
582
  }
566
583
  catch (error) {
567
- // Enhanced error handling with rate limit detection
568
- if (error && typeof error === 'object' && 'message' in error) {
569
- const errorMessage = error.message;
570
- if (errorMessage.includes('Rate limited') || errorMessage.includes('429')) {
571
- rateLimitedUpdates++;
572
- this.log.warn(`Rate limited updating accessory ${accessory.displayName}`);
573
- // Break the loop if we hit rate limits to avoid further API calls
574
- break;
575
- }
576
- else if (errorMessage.includes('ECONNABORTED') || errorMessage.includes('ABORT_ERR')) {
577
- this.log.warn(`Timeout updating accessory ${accessory.displayName} - API may be slow, will retry next cycle`);
578
- errorUpdates++;
579
- }
580
- else {
581
- this.log.error(`Failed to update accessory ${accessory.displayName}: ${errorMessage}`);
582
- errorUpdates++;
583
- }
584
+ const msg = error instanceof Error ? error.message : String(error);
585
+ if (msg.includes('Rate limited') || msg.includes('429')) {
586
+ rateLimitedDevices++;
587
+ this.log.warn(`🚫 "${accessory.displayName}" — rate limited during update, stopping cycle`);
588
+ break;
589
+ }
590
+ else if (msg.includes('ECONNABORTED') || msg.includes('ABORT_ERR') || msg.includes('timeout')) {
591
+ this.log.warn(`⏱️ "${accessory.displayName}" — API timeout, will retry next cycle`);
592
+ errorDevices++;
593
+ }
594
+ else if (msg.includes('401') || msg.includes('403') || msg.includes('Unauthorized')) {
595
+ this.log.error(`🔑 "${accessory.displayName}" — auth error during update (${msg}). Token refresh may be needed.`);
596
+ errorDevices++;
584
597
  }
585
598
  else {
586
- this.log.error(`Failed to update accessory ${accessory.displayName}:`, error);
587
- errorUpdates++;
599
+ this.log.error(`❌ "${accessory.displayName}" update failed: ${msg}`);
600
+ errorDevices++;
588
601
  }
589
602
  }
590
- // Add small delay between accessory updates to be gentle on the API
591
- if (this.config.enableRateLimitProtection !== false) {
592
- await this.sleep(1000); // 1 second delay between accessories
593
- }
594
603
  }
595
- // Update statistics and adjust refresh interval if needed
596
- this.updateCycleComplete(successfulUpdates, rateLimitedUpdates, errorUpdates, skippedForBurnerUpdates);
604
+ const elapsed = Date.now() - cycleStart;
605
+ this.updateCycleComplete(successfulDevices, rateLimitedDevices, errorDevices, skippedForBurnerUpdates, handlersCalled, handlersSkipped, elapsed);
597
606
  }
598
607
  catch (error) {
599
- this.log.error('Error during device update cycle:', error);
608
+ this.log.error(' Fatal error during device update cycle:', error);
600
609
  this.adjustRefreshInterval(true);
601
610
  }
602
611
  finally {
603
612
  this.isUpdating = false;
604
613
  }
605
614
  }
606
- updateCycleComplete(successful, rateLimited, errors, skipped) {
607
- const total = this.accessories.length;
615
+ updateCycleComplete(successful, rateLimited, errors, skipped, handlersCalled, handlersSkipped, elapsedMs) {
616
+ const totalAccessories = this.accessories.length;
617
+ const elapsedSec = (elapsedMs / 1000).toFixed(1);
608
618
  if (rateLimited > 0) {
609
- this.log.warn(`Update cycle completed: ${successful}/${total} successful, ${rateLimited} rate limited, ${errors} errors, ${skipped} skipped for burner updates`);
619
+ this.log.warn(`🔄 Update cycle done in ${elapsedSec}s — ${successful} device(s) fetched, ${rateLimited} rate-limited, ${errors} errors | handlers: ${handlersCalled} called, ${handlersSkipped} skipped | accessories: ${totalAccessories}`);
610
620
  this.adjustRefreshInterval(true);
611
621
  this.consecutiveErrors++;
612
622
  }
613
- else if (errors > total / 2) {
614
- this.log.warn(`Update cycle completed with many errors: ${successful}/${total} successful, ${errors} errors, ${skipped} skipped for burner updates`);
623
+ else if (errors > totalAccessories / 2) {
624
+ this.log.warn(`🔄 Update cycle done in ${elapsedSec}s ${successful} device(s) fetched, ${errors} errors (>50%) | handlers: ${handlersCalled} called | accessories: ${totalAccessories}`);
615
625
  this.adjustRefreshInterval(true);
616
626
  this.consecutiveErrors++;
617
627
  }
618
628
  else {
619
- if (skipped > 0) {
620
- this.log.debug(`Update cycle completed: ${successful}/${total} successful, ${errors} errors, ${skipped} skipped for immediate burner updates`);
621
- }
622
- else {
623
- this.log.debug(`Update cycle completed: ${successful}/${total} successful, ${errors} errors`);
624
- }
625
- // Reset error tracking on successful cycle
629
+ // Success path log at info level so it's visible without debug mode
630
+ const skippedMsg = skipped > 0 ? `, ${skipped} skipped (burner update)` : '';
631
+ const handlersMsg = handlersSkipped > 0 ? ` ⚠️ ${handlersSkipped} handler(s) not set yet` : '';
632
+ this.log.info(`🔄 Update cycle done in ${elapsedSec}s — ${successful} device(s) fetched, ${handlersCalled}/${totalAccessories} accessories updated${skippedMsg}${handlersMsg}`);
626
633
  if (successful > 0 && errors === 0) {
627
634
  this.consecutiveErrors = 0;
628
635
  this.adjustRefreshInterval(false);
629
- // Log health score periodically
630
636
  const healthScore = this.viessmannAPI.getAPIHealthScore();
631
637
  const healthStatus = this.viessmannAPI.getAPIHealthStatus();
632
638
  this.log.debug(`📊 API Health: ${healthScore}/100 (${healthStatus})`);
@@ -635,20 +641,25 @@ class ViessmannPlatform {
635
641
  }
636
642
  adjustRefreshInterval(increase) {
637
643
  if (this.config.enableRateLimitProtection === false) {
638
- return; // Don't adjust if protection is disabled
644
+ return;
639
645
  }
646
+ const prevMultiplier = this.backoffMultiplier;
640
647
  if (increase) {
641
- this.backoffMultiplier = Math.min(this.backoffMultiplier * 1.5, 10); // Max 10x increase
648
+ this.backoffMultiplier = Math.min(this.backoffMultiplier * 1.5, 10);
642
649
  }
643
650
  else {
644
- this.backoffMultiplier = Math.max(this.backoffMultiplier * 0.9, 1); // Gradually return to normal
645
- }
646
- // Restart timer with new interval if multiplier changed significantly
647
- if ((increase && this.backoffMultiplier > 2) || (!increase && this.backoffMultiplier < 2)) {
648
- if (this.refreshTimer) {
649
- clearInterval(this.refreshTimer);
650
- this.startRefreshTimer();
651
- }
651
+ // FIX#3: clamp to 1 — multiplier will always be 1 after the first successful cycle,
652
+ // so the old condition (!increase && multiplier < 2) was always true and restarted
653
+ // the timer on every single successful cycle. Now we only restart when the multiplier
654
+ // actually changes by more than 10% from its previous value.
655
+ this.backoffMultiplier = Math.max(this.backoffMultiplier * 0.9, 1);
656
+ }
657
+ const changeRatio = Math.abs(this.backoffMultiplier - prevMultiplier) / Math.max(prevMultiplier, 1);
658
+ const significantChange = changeRatio > 0.1; // >10% change
659
+ if (significantChange && this.refreshTimer) {
660
+ this.log.info(`⏱️ Refresh interval adjusted: backoff ×${prevMultiplier.toFixed(2)} → ×${this.backoffMultiplier.toFixed(2)} (${increase ? 'backing off' : 'recovering'})`);
661
+ clearInterval(this.refreshTimer);
662
+ this.startRefreshTimer();
652
663
  }
653
664
  }
654
665
  sleep(ms) {
@@ -9,7 +9,7 @@ export declare const PLUGIN_NAME = "homebridge-viessmann-vicare";
9
9
  /**
10
10
  * Plugin version for User-Agent and logging
11
11
  */
12
- export declare const PLUGIN_VERSION = "2.0.0";
12
+ export declare const PLUGIN_VERSION = "2.0.9";
13
13
  /**
14
14
  * Default configuration values
15
15
  */
package/dist/settings.js CHANGED
@@ -12,7 +12,7 @@ exports.PLUGIN_NAME = 'homebridge-viessmann-vicare';
12
12
  /**
13
13
  * Plugin version for User-Agent and logging
14
14
  */
15
- exports.PLUGIN_VERSION = '2.0.0';
15
+ exports.PLUGIN_VERSION = '2.0.9';
16
16
  /**
17
17
  * Default configuration values
18
18
  */
@@ -59,10 +59,11 @@ export { ViessmannInstallation, ViessmannFeature, ViessmannGateway, ViessmannDev
59
59
  export declare class ViessmannAPI {
60
60
  private readonly log;
61
61
  private readonly config;
62
+ private readonly storagePath?;
62
63
  private readonly authManager;
63
64
  private readonly apiClient;
64
65
  private readonly endpoints;
65
- constructor(log: Logger, config: ViessmannPlatformConfig);
66
+ constructor(log: Logger, config: ViessmannPlatformConfig, storagePath?: string | undefined);
66
67
  private createHttpClient;
67
68
  private buildCacheConfig;
68
69
  setBurnerUpdateCallback(callback: (installationId: number, gatewaySerial: string, deviceId: string, status: BurnerStatus, reason: string) => void): void;
@@ -1 +1 @@
1
- {"version":3,"file":"viessmann-api.d.ts","sourceRoot":"","sources":["../src/viessmann-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"viessmann-api.d.ts","sourceRoot":"","sources":["../src/viessmann-api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACpC,OAAO,EAAe,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEzD,OAAO,EAAyB,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC5J,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAuBlD,MAAM,WAAW,uBAAwB,SAAQ,UAAU;IAEzD,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAG9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAG3B,4BAA4B,CAAC,EAAE,OAAO,CAAC;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAG9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAG3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAGjC,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAGF,WAAW,CAAC,EAAE;QACZ,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,cAAc,CAAC,EAAE,MAAM,CAAC;QAGxB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;QAGjB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,GAAG,CAAC,EAAE,MAAM,CAAC;QAGb,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QAGpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAGF,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;QAC7B,yBAAyB,CAAC,EAAE,OAAO,CAAC;QACpC,yBAAyB,CAAC,EAAE,OAAO,CAAC;QACpC,kBAAkB,CAAC,EAAE,OAAO,CAAC;KAC9B,CAAC;CACH;AAGD,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAEpG,qBAAa,YAAY;IAMrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAP/B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;gBAG/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,uBAAuB,EAC/B,WAAW,CAAC,EAAE,MAAM,YAAA;IAmCvC,OAAO,CAAC,gBAAgB;IAMxB,OAAO,CAAC,gBAAgB;IAqBjB,uBAAuB,CAC5B,QAAQ,EAAE,CACR,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,KACX,IAAI,GACR,IAAI;IAKM,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAK7B,gBAAgB,IAAI,OAAO,CAAC,qBAAqB,EAAE,CAAC;IAKpD,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;IAK/D,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAK/G,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAMlI,eAAe,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAKvG,mBAAmB,CAC9B,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,GAAE,MAAyB,GAChC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAKlB,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACxG,aAAa,EAAE,OAAO,CAAC;QACvB,aAAa,EAAE,OAAO,CAAC;QACvB,aAAa,EAAE,OAAO,CAAC;QACvB,qBAAqB,EAAE,OAAO,CAAC;QAC/B,iBAAiB,EAAE,MAAM,EAAE,CAAC;KAC7B,CAAC;IAMW,cAAc,CACzB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,MAAM,GAAE,GAAQ,GACf,OAAO,CAAC,OAAO,CAAC;IAMN,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKzH,4BAA4B,CACvC,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,OAAO,CAAC;IAKN,gBAAgB,CAC3B,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC;IAKN,qBAAqB,CAChC,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC;IAKN,mBAAmB,CAC9B,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,IAAI,EACf,OAAO,EAAE,IAAI,GACZ,OAAO,CAAC,OAAO,CAAC;IAKN,qBAAqB,CAChC,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,OAAO,CAAC;IAKN,UAAU,CACrB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC;IAMN,sBAAsB,CACjC,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,OAAO,EACjB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC;IAMZ,kBAAkB;;;;;;;;;IAIlB,cAAc;;;;;;;;;;IAId,aAAa,IAAI,UAAU;IAK3B,iBAAiB,IAAI,MAAM;IAI3B,kBAAkB,IAAI,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU;IAIzE,uBAAuB;;;;;;;IAIvB,qBAAqB;IAIrB,qBAAqB;;;;;;;;IAIrB,eAAe,IAAI,IAAI;IAKvB,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoCf,aAAa,IAAI,UAAU,GAAG,IAAI;IAIlC,UAAU,CAAC,OAAO,CAAC,EAAE,MAAM;IAI3B,iBAAiB,CAAC,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC;IAK9C,OAAO,IAAI,IAAI;CAKvB"}
@@ -28,16 +28,18 @@ function detectLocalIP() {
28
28
  class ViessmannAPI {
29
29
  log;
30
30
  config;
31
+ storagePath;
31
32
  authManager;
32
33
  apiClient;
33
34
  endpoints;
34
- constructor(log, config) {
35
+ constructor(log, config, storagePath) {
35
36
  this.log = log;
36
37
  this.config = config;
38
+ this.storagePath = storagePath;
37
39
  // Detect host IP if not provided
38
40
  const hostIp = this.config.hostIp || detectLocalIP();
39
- // Initialize auth manager
40
- this.authManager = new auth_manager_1.AuthManager(this.log, this.config, this.createHttpClient(), hostIp);
41
+ // Initialize auth manager — pass storagePath for Homebridge-compliant file storage
42
+ this.authManager = new auth_manager_1.AuthManager(this.log, this.config, this.createHttpClient(), hostIp, this.storagePath);
41
43
  // Initialize API client
42
44
  const apiClientConfig = {
43
45
  requestTimeout: this.config.requestTimeout || 30000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-viessmann-vicare",
3
- "version": "2.0.7",
3
+ "version": "2.0.8",
4
4
  "description": "Complete Homebridge plugin for Viessmann ViCare heating systems with full control capabilities, advanced rate limiting protection, and intelligent cache management",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "homepage": "https://github.com/diegoweb100/homebridge-viessmann-vicare#readme",
37
37
  "engines": {
38
- "node": ">=18.15.0",
38
+ "node": ">=20.0.0",
39
39
  "homebridge": ">=1.8.0 || >=2.0.0-beta.0"
40
40
  },
41
41
  "dependencies": {
@@ -58,4 +58,4 @@
58
58
  "type": "individual",
59
59
  "url": "https://github.com/sponsors/diegoweb100"
60
60
  }
61
- }
61
+ }