homebridge-viessmann-vicare 2.0.39 → 2.0.41

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
@@ -1033,6 +1033,27 @@ For issues and questions:
1033
1033
 
1034
1034
  ## 📈 Changelog
1035
1035
 
1036
+ ### [2.0.41] - 2026-03-15
1037
+ #### Fixed
1038
+ - **Duplicate Boiler accessory on heat pump installations** — `setupBoilerAccessory` was matching `heating.boiler.serial` which is present on VitoCharge and other gen3 devices as a system identifier. Filter now requires actual burner/boiler operation features (`heating.burners.*`, `heating.boiler.temperature.current`, etc.). Fixes "Boiler 2" / "Energy 2" confusion reported on Windows installations with Vitocal 250A.
1039
+
1040
+ #### Added — `viessmann-report.js`
1041
+ - **Gas forecast section** — projects next-30-day and annual gas consumption using linear regression on historical CSV data. Shows cost estimate in € with configurable tariff via `--gasPriceEur` (default: 0.90 €/m³). Includes trend indicator (rising/stable/falling).
1042
+ - **Device messages section** — reads `viessmann-messages-<ID>.json` (written by plugin, future) and displays S./F./I. codes with English translations from Viessmann service documentation (80+ codes covered).
1043
+ - **`--gasPriceEur`** CLI parameter for gas cost calculation.
1044
+
1045
+ ### [2.0.40] - 2026-03-15
1046
+ #### Fixed
1047
+ - **Critical: Accessories not updating after Homebridge restart** — when restoring accessories from cache, `device`, `installation`, and `gateway` were not written to `accessory.context`. The update loop silently skipped all accessories on every subsequent restart, showing `0 device(s) fetched, 0/0 accessories updated`. All four restore-from-cache paths (Boiler, DHW, Heating Circuit, Energy/Heat Pump) are now fixed.
1048
+
1049
+ #### Changed
1050
+ - **Full feature dump** — moved from `INFO` to `DEBUG` level; only visible when `debug: true` is set in plugin config.
1051
+ - **Capability detail log** — resolved HP paths and capability breakdown moved to `DEBUG`; single compact `INFO` line now summarises detected capabilities (e.g. `Capabilities detected: HeatPump`).
1052
+ - **`updateHandler not set` warning** — downgraded from `WARN` to `DEBUG`. Per-device spam eliminated; update cycle summary still shows the count when non-zero.
1053
+
1054
+ #### Notes
1055
+ - Users upgrading from ≤ v2.0.38 with a heat pump may see ghost "Heat Pump" accessories in Homebridge cache. Remove via Homebridge UI → Settings → Remove Single Accessory.
1056
+
1036
1057
  ### [2.0.39] - 2026-03-11
1037
1058
  #### Fixed
1038
1059
  - **Critical: Heat pump device detection** — `isHeatPumpDevice()` was incorrectly matching ALL Viessmann gen3 devices because `type:E3` is a gen3 architecture marker present on every device (TCU gateway, TRVs, room sensors, repeaters, VitoCharge, HEMS, wallbox, etc.). Detection now requires `type:heatpump` (exact role) or modelId containing `vitocal`. This was causing spurious "Adding new energy accessory: … Heat Pump" log entries for every device.
package/SETUP-GUIDE.md CHANGED
@@ -1,8 +1,8 @@
1
- # Complete Setup Guide - v2.0.39
1
+ # Complete Setup Guide - v2.0.41
2
2
 
3
3
  ## Overview
4
4
 
5
- This guide will walk you through setting up the Viessmann ViCare plugin v2.0.39 for Homebridge, including all the advanced features like intelligent caching, rate limiting protection, comprehensive configuration options, **complete localization support with custom names**, **CSV history logging**, **HTML diagnostic reports**, **energy system monitoring** (PV, battery, wallbox), and **heating schedule awareness** with visual bands in the HTML report, and **heat pump (Wärmepumpe) support** with automatic device detection.
5
+ This guide will walk you through setting up the Viessmann ViCare plugin v2.0.41 for Homebridge, including all the advanced features like intelligent caching, rate limiting protection, comprehensive configuration options, **complete localization support with custom names**, **CSV history logging**, **HTML diagnostic reports**, **energy system monitoring** (PV, battery, wallbox), and **heating schedule awareness** with visual bands in the HTML report, and **heat pump (Wärmepumpe) support** with automatic device detection.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -1157,7 +1157,7 @@ When reporting issues, include:
1157
1157
 
1158
1158
  ```json
1159
1159
  {
1160
- "plugin_version": "2.0.39",
1160
+ "plugin_version": "2.0.41",
1161
1161
  "homebridge_version": "1.8.x",
1162
1162
  "node_version": "18.x.x",
1163
1163
  "heating_system": "Viessmann Model",
@@ -1228,6 +1228,21 @@ sudo systemctl restart homebridge
1228
1228
 
1229
1229
  ## Changelog
1230
1230
 
1231
+ ### v2.0.41 (2026-03-15)
1232
+ **Fix duplicate Boiler accessory + gas forecast + error code translations:**
1233
+ - **Fixed: duplicate "Boiler 2" on heat pump installations** — `setupBoilerAccessory` was matching `heating.boiler.serial` present on VitoCharge and other gen3 devices. Filter now requires actual burner operation features. Fixes accessory naming confusion on Windows installs with Vitocal 250A.
1234
+ - **Report: Gas forecast** — new section projecting 30-day and annual gas consumption via linear regression on CSV history. Cost estimate in € with `--gasPriceEur` parameter (default 0.90 €/m³). Trend indicator (rising/stable/falling).
1235
+ - **Report: Device messages** — new section with 80+ Viessmann S./F./I. code translations (English). Reads `viessmann-messages-<ID>.json` when available.
1236
+ - **`--gasPriceEur <price>`** new CLI parameter for `viessmann-report.js`.
1237
+
1238
+ ### v2.0.40 (2026-03-15)
1239
+ **Critical bug fix + log noise reduction:**
1240
+ - **Fixed: accessories not updating after restart** — all four restore-from-cache paths (Boiler, DHW, HC, Energy) were missing `context.device/installation/gateway` assignment. This caused `updateAllDevices` to skip every accessory silently on every restart after the first, showing `0 device(s) fetched`. Affected all users.
1241
+ - Full feature dump moved to `DEBUG` level (only shown when `debug: true`).
1242
+ - Capability detail and resolved HP paths moved to `DEBUG`; single compact `INFO` line shows detected capabilities.
1243
+ - `updateHandler not set` per-device warning downgraded to `DEBUG`; update cycle summary count retained.
1244
+ - **Note:** Users upgrading from ≤v2.0.38 with a heat pump may have ghost "Heat Pump" accessories in cache — remove via Homebridge UI → Settings → Remove Single Accessory.
1245
+
1231
1246
  ### v2.0.39 (2026-03-11)
1232
1247
  **Critical fix** — Heat pump device detection overhaul:
1233
1248
  - Fixed `isHeatPumpDevice()`: removed `type:E3` which matched ALL gen3 devices. Now requires `type:heatpump` (exact) or Vitocal modelId.
@@ -1 +1 @@
1
- {"version":3,"file":"energy-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/energy-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAuB,MAAM,YAAY,CAAC;AAC7E,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EAChB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,wBAAwB;IAwFjC,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;IAxFzB,OAAO,CAAC,kBAAkB,CAAU;IAGpC,OAAO,CAAC,eAAe,CAAC,CAAU;IAClC,OAAO,CAAC,kBAAkB,CAAC,CAAU;IAGrC,OAAO,CAAC,mBAAmB,CAAC,CAAU;IACtC,OAAO,CAAC,eAAe,CAAC,CAAU;IAGlC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,mBAAmB,CAAC,CAAU;IAGtC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,oBAAoB,CAAC,CAAU;IAGvC,OAAO,CAAC,kBAAkB,CAAC,CAAU;IAIrC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,cAAc,CAAU;IAIhC,OAAO,CAAC,OAAO,CAOb;IAGF,OAAO,CAAC,MAAM,CAwCZ;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe;IAoB1C,OAAO,CAAC,gBAAgB;YAUV,sBAAsB;IAuBpC,OAAO,CAAC,cAAc;IAmCtB,OAAO,CAAC,kBAAkB;IAkD1B;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,qBAAqB;IAwF7B,OAAO,CAAC,eAAe;IAkCvB,OAAO,CAAC,oBAAoB;IAgE5B,OAAO,CAAC,oBAAoB;IAwC5B,OAAO,CAAC,uBAAuB;IAwDlB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAa5B,kBAAkB;YAalB,cAAc;YA2Hd,mBAAmB;YAuInB,kBAAkB;YAmBlB,oBAAoB;YAmBpB,yBAAyB;CAgBxC"}
1
+ {"version":3,"file":"energy-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/energy-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAuB,MAAM,YAAY,CAAC;AAC7E,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,gBAAgB,EAChB,eAAe,EAChB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,wBAAwB;IAwFjC,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;IAxFzB,OAAO,CAAC,kBAAkB,CAAU;IAGpC,OAAO,CAAC,eAAe,CAAC,CAAU;IAClC,OAAO,CAAC,kBAAkB,CAAC,CAAU;IAGrC,OAAO,CAAC,mBAAmB,CAAC,CAAU;IACtC,OAAO,CAAC,eAAe,CAAC,CAAU;IAGlC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,mBAAmB,CAAC,CAAU;IAGtC,OAAO,CAAC,cAAc,CAAC,CAAU;IACjC,OAAO,CAAC,oBAAoB,CAAC,CAAU;IAGvC,OAAO,CAAC,kBAAkB,CAAC,CAAU;IAIrC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,cAAc,CAAU;IAIhC,OAAO,CAAC,OAAO,CAOb;IAGF,OAAO,CAAC,MAAM,CAwCZ;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe;IAoB1C,OAAO,CAAC,gBAAgB;YAUV,sBAAsB;IAuBpC,OAAO,CAAC,cAAc;IAoCtB,OAAO,CAAC,kBAAkB;IAyD1B;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAqD5B,OAAO,CAAC,aAAa;IAoBrB,OAAO,CAAC,qBAAqB;IAwF7B,OAAO,CAAC,eAAe;IAkCvB,OAAO,CAAC,oBAAoB;IAgE5B,OAAO,CAAC,oBAAoB;IAwC5B,OAAO,CAAC,uBAAuB;IAwDlB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;YAa5B,kBAAkB;YAalB,cAAc;YA2Hd,mBAAmB;YAuInB,kBAAkB;YAmBlB,oBAAoB;YAmBpB,yBAAyB;CAgBxC"}
@@ -134,14 +134,16 @@ class ViessmannEnergyAccessory {
134
134
  this.setupServices();
135
135
  }
136
136
  }
137
- // ── Full feature dump (always logged at INFO) ─────────────────────────────
137
+ // ── Full feature dump (debug only) ────────────────────────────────────────
138
138
  logAllFeatures(features) {
139
+ if (!this.platform.config.debug)
140
+ return;
139
141
  const tag = '[EnergyAccessory]';
140
142
  const deviceLabel = `device=${this.device.id} model=${this.device.modelId ?? '?'} roles=${JSON.stringify(this.device.roles ?? [])}`;
141
- this.platform.log.info(`${tag} ════════════════════════════════════════════════`);
142
- this.platform.log.info(`${tag} FULL FEATURE DUMP — ${deviceLabel}`);
143
- this.platform.log.info(`${tag} Total features: ${features.length}`);
144
- this.platform.log.info(`${tag} ────────────────────────────────────────────────`);
143
+ this.platform.log.debug(`${tag} ════════════════════════════════════════════════`);
144
+ this.platform.log.debug(`${tag} FULL FEATURE DUMP — ${deviceLabel}`);
145
+ this.platform.log.debug(`${tag} Total features: ${features.length}`);
146
+ this.platform.log.debug(`${tag} ────────────────────────────────────────────────`);
145
147
  // Sort features alphabetically for readability
146
148
  const sorted = [...features].sort((a, b) => a.feature.localeCompare(b.feature));
147
149
  for (const f of sorted) {
@@ -150,13 +152,13 @@ class ViessmannEnergyAccessory {
150
152
  const enabled = f.isEnabled ? '✓' : '✗';
151
153
  const ready = f.isReady ? '✓' : '✗';
152
154
  const propVals = JSON.stringify(f.properties ?? {});
153
- this.platform.log.info(`${tag} [${enabled}${ready}] ${f.feature}`);
154
- this.platform.log.info(`${tag} props=(${propKeys}) values=${propVals.substring(0, 120)}`);
155
+ this.platform.log.debug(`${tag} [${enabled}${ready}] ${f.feature}`);
156
+ this.platform.log.debug(`${tag} props=(${propKeys}) values=${propVals.substring(0, 120)}`);
155
157
  if (cmdKeys !== '—') {
156
- this.platform.log.info(`${tag} commands=(${cmdKeys})`);
158
+ this.platform.log.debug(`${tag} commands=(${cmdKeys})`);
157
159
  }
158
160
  }
159
- this.platform.log.info(`${tag} ════════════════════════════════════════════════`);
161
+ this.platform.log.debug(`${tag} ════════════════════════════════════════════════`);
160
162
  }
161
163
  // ── Capability detection ──────────────────────────────────────────────────
162
164
  detectCapabilities(features) {
@@ -196,12 +198,19 @@ class ViessmannEnergyAccessory {
196
198
  names.some(n => n.startsWith('heating.dhw.pumps.primary')) ||
197
199
  names.some(n => n === 'heating.dhw.operating.modes.electricBoost');
198
200
  }
199
- this.platform.log.info(`${tag} ── Capability result:`);
200
- this.platform.log.info(`${tag} isHeatPump : ${this.isHeatPump}`);
201
- this.platform.log.info(`${tag} PV/Solar : ${this.hasPV}`);
202
- this.platform.log.info(`${tag} Battery : ${this.hasBattery}`);
203
- this.platform.log.info(`${tag} Wallbox/EV : ${this.hasWallbox}`);
204
- this.platform.log.info(`${tag} Electric DHW : ${this.hasElectricDHW}`);
201
+ const caps = [
202
+ this.isHeatPump ? 'HeatPump' : null,
203
+ this.hasPV ? 'PV' : null,
204
+ this.hasBattery ? 'Battery' : null,
205
+ this.hasWallbox ? 'Wallbox' : null,
206
+ this.hasElectricDHW ? 'ElecDHW' : null,
207
+ ].filter(Boolean).join(', ') || 'none';
208
+ this.platform.log.info(`${tag} Capabilities detected: ${caps}`);
209
+ this.platform.log.debug(`${tag} isHeatPump : ${this.isHeatPump}`);
210
+ this.platform.log.debug(`${tag} PV/Solar : ${this.hasPV}`);
211
+ this.platform.log.debug(`${tag} Battery : ${this.hasBattery}`);
212
+ this.platform.log.debug(`${tag} Wallbox/EV : ${this.hasWallbox}`);
213
+ this.platform.log.debug(`${tag} Electric DHW : ${this.hasElectricDHW}`);
205
214
  }
206
215
  /**
207
216
  * Tries multiple known path variants for heat pump features.
@@ -217,10 +226,10 @@ class ViessmannEnergyAccessory {
217
226
  this.hpPaths.supplyTemp = pick('heating.circuits.0.sensors.temperature.supply', 'heating.primaryCircuit.sensors.temperature.supply', 'heating.heatpump.sensors.temperature.flow');
218
227
  this.hpPaths.returnTemp = pick('heating.sensors.temperature.return', 'heating.secondaryCircuit.sensors.temperature.return', 'heating.primaryCircuit.sensors.temperature.return', 'heating.heatpump.sensors.temperature.return', 'heating.circuits.0.sensors.temperature.return');
219
228
  this.hpPaths.cop = pick('heating.scop.heating', 'heating.spf.heating', 'heating.scop.total', 'heating.heatpump.cop', 'heating.compressor.0.cop');
220
- this.platform.log.info(`${tag} Resolved paths:`);
229
+ this.platform.log.debug(`${tag} Resolved paths:`);
221
230
  for (const [key, val] of Object.entries(this.hpPaths)) {
222
- const status = val ? `✓ ${val}` : '✗ not found (will log raw features above)';
223
- this.platform.log.info(`${tag} ${key.padEnd(18)}: ${status}`);
231
+ const status = val ? `✓ ${val}` : '✗ not found';
232
+ this.platform.log.debug(`${tag} ${key.padEnd(18)}: ${status}`);
224
233
  }
225
234
  }
226
235
  // ── Service creation ───────────────────────────────────────────────────────
@@ -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
@@ -511,8 +511,12 @@ class ViessmannPlatform {
511
511
  }
512
512
  }
513
513
  async setupBoilerAccessory(installation, gateway, device, features) {
514
- const boilerFeatures = features.filter(f => f.feature.includes('heating.boiler') ||
515
- f.feature.includes('heating.burners'));
514
+ // Require actual burner/boiler operation features not just heating.boiler.serial
515
+ // which is also present on VitoCharge and other gen3 devices as a system identifier.
516
+ const boilerFeatures = features.filter(f => f.feature.startsWith('heating.burners') ||
517
+ f.feature === 'heating.boiler.temperature.current' ||
518
+ f.feature === 'heating.boiler.sensors.temperature.commonSupply' ||
519
+ f.feature === 'heating.boiler.pumps.internal');
516
520
  if (boilerFeatures.length === 0) {
517
521
  return;
518
522
  }
@@ -523,6 +527,9 @@ class ViessmannPlatform {
523
527
  const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
524
528
  if (existingAccessory) {
525
529
  this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
530
+ existingAccessory.context.device = device;
531
+ existingAccessory.context.installation = installation;
532
+ existingAccessory.context.gateway = gateway;
526
533
  new boiler_accessory_1.ViessmannBoilerAccessory(this, existingAccessory, installation, gateway, device);
527
534
  }
528
535
  else {
@@ -548,6 +555,9 @@ class ViessmannPlatform {
548
555
  const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
549
556
  if (existingAccessory) {
550
557
  this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
558
+ existingAccessory.context.device = device;
559
+ existingAccessory.context.installation = installation;
560
+ existingAccessory.context.gateway = gateway;
551
561
  new dhw_accessory_1.ViessmannDHWAccessory(this, existingAccessory, installation, gateway, device);
552
562
  }
553
563
  else {
@@ -577,6 +587,9 @@ class ViessmannPlatform {
577
587
  const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
578
588
  if (existingAccessory) {
579
589
  this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
590
+ existingAccessory.context.device = device;
591
+ existingAccessory.context.installation = installation;
592
+ existingAccessory.context.gateway = gateway;
580
593
  new heating_circuit_accessory_1.ViessmannHeatingCircuitAccessory(this, existingAccessory, installation, gateway, device, circuitNumber);
581
594
  }
582
595
  else {
@@ -628,6 +641,9 @@ class ViessmannPlatform {
628
641
  const existingAccessory = this.accessories.find(a => a.UUID === uuid);
629
642
  if (existingAccessory) {
630
643
  this.log.info('Restoring existing energy accessory from cache:', existingAccessory.displayName);
644
+ existingAccessory.context.device = device;
645
+ existingAccessory.context.installation = installation;
646
+ existingAccessory.context.gateway = gateway;
631
647
  new energy_accessory_1.ViessmannEnergyAccessory(this, existingAccessory, installation, gateway, device);
632
648
  }
633
649
  else {
@@ -714,7 +730,7 @@ class ViessmannPlatform {
714
730
  }
715
731
  else {
716
732
  handlersSkipped++;
717
- this.log.warn(`⚠️ "${accessory.displayName}" — updateHandler not set (accessory still initializing?)`);
733
+ this.log.debug(`⚙️ "${accessory.displayName}" — updateHandler not set, skipping`);
718
734
  }
719
735
  }
720
736
  catch (error) {
@@ -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.39";
12
+ export declare const PLUGIN_VERSION = "2.0.41";
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.39';
15
+ exports.PLUGIN_VERSION = '2.0.41';
16
16
  /**
17
17
  * Default configuration values
18
18
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-viessmann-vicare",
3
- "version": "2.0.39",
3
+ "version": "2.0.41",
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": {
@@ -59,4 +59,4 @@
59
59
  "optionalDependencies": {
60
60
  "fakegato-history": "^0.6.7"
61
61
  }
62
- }
62
+ }
@@ -581,6 +581,165 @@ const ovOutsideHum = overviewTimes.map(ts => interpolate(boilerRows, ts, 'outsid
581
581
  const ovMod = overviewTimes.map(ts => interpolate(boilerRows,ts, 'modulation'));
582
582
  const ovBurner = overviewTimes.map(ts => lookupBurner(boilerRows, ts));
583
583
 
584
+
585
+ // ─────────────────────────────────────────────────────────────────────────────
586
+ // GAS FORECAST
587
+ // Uses gasPerDay (already computed above) to project monthly/annual consumption.
588
+ // Strategy: linear regression on last N days → extrapolate to 30/365 days.
589
+ // ─────────────────────────────────────────────────────────────────────────────
590
+ let gasForecast = null;
591
+ if (gasDays.length >= 3) {
592
+ // Use all available days; total = heating + dhw
593
+ const totalPerDay = gasDays.map(d => gasPerDay[d].heating + gasPerDay[d].dhw);
594
+ const n = totalPerDay.length;
595
+ // Simple linear regression: y = a + b*x (x = day index)
596
+ const xMean = (n - 1) / 2;
597
+ const yMean = totalPerDay.reduce((s, v) => s + v, 0) / n;
598
+ let num = 0, den = 0;
599
+ totalPerDay.forEach((y, i) => { num += (i - xMean) * (y - yMean); den += (i - xMean) ** 2; });
600
+ const slope = den !== 0 ? num / den : 0;
601
+ const intercept = yMean - slope * xMean;
602
+ // Project from today (index = n-1) forward
603
+ const projectDay = (offset) => Math.max(0, intercept + slope * (n - 1 + offset));
604
+ // Monthly: sum of next 30 days
605
+ let monthSum = 0;
606
+ for (let i = 1; i <= 30; i++) monthSum += projectDay(i);
607
+ // Annual: sum of next 365 days (approximated as 30-day avg × 12 with seasonal note)
608
+ // For simplicity: annualise the period avg × 365 (more stable than long regression)
609
+ const periodAvgPerDay = yMean;
610
+ const annualEst = periodAvgPerDay * 365;
611
+ // Cost estimate (€): use --gasPriceEur param or env (default 0.90 €/m³ — Italian average)
612
+ const GAS_PRICE = parseFloat(getArg('--gasPriceEur', process.env.GAS_PRICE_EUR || '0.90'));
613
+ gasForecast = {
614
+ avgPerDay: yMean.toFixed(2),
615
+ trend: slope > 0.05 ? 'rising' : slope < -0.05 ? 'falling' : 'stable',
616
+ trendSlope: slope.toFixed(3),
617
+ month30: monthSum.toFixed(1),
618
+ annualEst: annualEst.toFixed(0),
619
+ costMonth: (monthSum * GAS_PRICE).toFixed(2),
620
+ costAnnual: (annualEst * GAS_PRICE).toFixed(2),
621
+ gasPrice: GAS_PRICE.toFixed(2),
622
+ daysUsed: n,
623
+ };
624
+ }
625
+
626
+ // ─────────────────────────────────────────────────────────────────────────────
627
+ // VIESSMANN STATUS / ERROR CODE TRANSLATIONS
628
+ // Source: Viessmann service documentation + vieventlog error_codes.go
629
+ // ─────────────────────────────────────────────────────────────────────────────
630
+ const VIESSMANN_CODES = {
631
+ // ── Status codes (S.) ──
632
+ 'S.0': { en: 'Standby', de: 'Bereitschaft' },
633
+ 'S.1': { en: 'DHW heating', de: 'Warmwasserbereitung' },
634
+ 'S.2': { en: 'Central heating', de: 'Heizbetrieb' },
635
+ 'S.3': { en: 'Burner on', de: 'Brenner ein' },
636
+ 'S.4': { en: 'Burner off', de: 'Brenner aus' },
637
+ 'S.5': { en: 'Fan pre-purge', de: 'Lüfter Vorspülung' },
638
+ 'S.6': { en: 'Ignition', de: 'Zündung' },
639
+ 'S.7': { en: 'Flame detected', de: 'Flamme erkannt' },
640
+ 'S.8': { en: 'Burner post-purge', de: 'Lüfter Nachspülung' },
641
+ 'S.9': { en: 'Cooling mode', de: 'Kühlbetrieb' },
642
+ 'S.10': { en: 'Frost protection active', de: 'Frostschutz aktiv' },
643
+ 'S.12': { en: 'Circulation pump active', de: 'Umwälzpumpe aktiv' },
644
+ 'S.17': { en: 'Flue gas test', de: 'Abgastest' },
645
+ 'S.19': { en: 'Chimney sweep mode', de: 'Schornsteinfegerbetrieb' },
646
+ 'S.20': { en: 'Boiler protection (overtemp)', de: 'Kesselschutz (Übertemperatur)' },
647
+ 'S.22': { en: 'External demand active', de: 'Externe Anforderung aktiv' },
648
+ 'S.24': { en: 'Pump overrun', de: 'Pumpennachlauf' },
649
+ 'S.31': { en: 'Summer eco mode', de: 'Sommer-Eco-Betrieb' },
650
+ 'S.32': { en: 'Heating circuit standby', de: 'Heizkreis Standby' },
651
+ 'S.40': { en: 'Heat pump heating', de: 'Wärmepumpe Heizbetrieb' },
652
+ 'S.41': { en: 'Heat pump DHW', de: 'Wärmepumpe Warmwasser' },
653
+ 'S.42': { en: 'Heat pump defrost', de: 'Wärmepumpe Abtauung' },
654
+ 'S.43': { en: 'Heat pump cooling', de: 'Wärmepumpe Kühlung' },
655
+ 'S.44': { en: 'Compressor starting', de: 'Verdichter startet' },
656
+ 'S.45': { en: 'Compressor running', de: 'Verdichter läuft' },
657
+ 'S.46': { en: 'Compressor stopping', de: 'Verdichter stoppt' },
658
+ 'S.100': { en: 'Heating mode', de: 'Heizbetrieb' },
659
+ 'S.109': { en: 'Heating circuit active', de: 'Heizkreis aktiv' },
660
+ 'S.111': { en: 'Normal heating program', de: 'Normalbetrieb Heizung' },
661
+ 'S.112': { en: 'Reduced heating program', de: 'Absenkbetrieb Heizung' },
662
+ 'S.113': { en: 'Comfort heating program', de: 'Komfortbetrieb Heizung' },
663
+ 'S.114': { en: 'DHW demand', de: 'Warmwasseranforderung' },
664
+ 'S.118': { en: 'Primary circuit active', de: 'Primärkreis aktiv' },
665
+ 'S.119': { en: 'Secondary circuit active', de: 'Sekundärkreis aktiv' },
666
+ 'S.120': { en: 'Circulation pump running', de: 'Umwälzpumpe läuft' },
667
+ 'S.123': { en: 'Heat pump standby', de: 'Wärmepumpe Bereitschaft' },
668
+ 'S.124': { en: 'Heat pump grid lock', de: 'Wärmepumpe Netzsperrzeit' },
669
+ 'S.125': { en: 'Heat pump demand pending', de: 'Wärmepumpe Anforderung ausstehend' },
670
+ 'S.126': { en: 'Heat pump frost protection', de: 'Wärmepumpe Frostschutz' },
671
+ 'S.130': { en: 'Defrost active', de: 'Abtauung aktiv' },
672
+ 'S.131': { en: 'Defrost completed', de: 'Abtauung abgeschlossen' },
673
+ 'S.134': { en: 'Heat pump heating active', de: 'Wärmepumpe Heizbetrieb aktiv' },
674
+ 'S.140': { en: 'Smart grid active', de: 'Smart Grid aktiv' },
675
+ 'S.200': { en: 'Legionella protection', de: 'Legionellenschutz' },
676
+ 'S.201': { en: 'DHW efficient mode', de: 'Warmwasser Effizienzbetrieb' },
677
+ 'S.202': { en: 'DHW comfort mode', de: 'Warmwasser Komfortbetrieb' },
678
+ // ── Info codes (I.) ──
679
+ 'I.0': { en: 'System OK', de: 'System OK' },
680
+ 'I.1': { en: 'Maintenance due', de: 'Wartung fällig' },
681
+ 'I.2': { en: 'Filter replacement due', de: 'Filterwechsel fällig' },
682
+ 'I.10': { en: 'External temperature sensor fault', de: 'Außentemperaturfühler Fehler' },
683
+ 'I.11': { en: 'Return temperature sensor fault', de: 'Rücklauftemperaturfühler Fehler' },
684
+ 'I.12': { en: 'DHW sensor fault', de: 'Warmwasserfühler Fehler' },
685
+ 'I.20': { en: 'Low water pressure warning', de: 'Niederdruck Warnung' },
686
+ 'I.100': { en: 'System info', de: 'Systeminformation' },
687
+ 'I.113': { en: 'Heating curve optimisation info', de: 'Heizkurvenoptimierung Info' },
688
+ 'I.114': { en: 'Energy balance info', de: 'Energiebilanz Info' },
689
+ 'I.115': { en: 'Runtime statistics info', de: 'Laufzeitstatistik Info' },
690
+ // ── Fault codes (F.) ──
691
+ 'F.0': { en: 'No fault', de: 'Kein Fehler' },
692
+ 'F.1': { en: 'Burner fault — no ignition', de: 'Brennerstörung — keine Zündung' },
693
+ 'F.2': { en: 'Flame signal lost', de: 'Flammensignal verloren' },
694
+ 'F.3': { en: 'Ignition fault', de: 'Zündfehler' },
695
+ 'F.4': { en: 'Safety chain open', de: 'Sicherheitskette offen' },
696
+ 'F.5': { en: 'Gas valve fault', de: 'Gasventil Fehler' },
697
+ 'F.7': { en: 'Flue gas sensor fault', de: 'Abgastemperaturfühler Fehler' },
698
+ 'F.9': { en: 'Supply sensor fault', de: 'Vorlauftemperaturfühler Fehler' },
699
+ 'F.10': { en: 'External sensor fault', de: 'Außenfühler Fehler' },
700
+ 'F.11': { en: 'Return sensor fault', de: 'Rücklauftemperaturfühler Fehler' },
701
+ 'F.12': { en: 'DHW sensor fault', de: 'Warmwasserfühler Fehler' },
702
+ 'F.20': { en: 'Safety temperature limiter', de: 'Sicherheitstemperaturbegrenzer' },
703
+ 'F.22': { en: 'Low water pressure fault', de: 'Wassermangel' },
704
+ 'F.23': { en: 'Pump fault — overtemp', de: 'Pumpe Fehler — Übertemperatur' },
705
+ 'F.24': { en: 'Pump circulation fault', de: 'Pumpe Zirkulationsfehler' },
706
+ 'F.28': { en: 'Ignition fault (gas)', de: 'Zündstörung (Gas)' },
707
+ 'F.29': { en: 'Flame fault after ignition', de: 'Flammenfehler nach Zündung' },
708
+ 'F.30': { en: 'STB safety shutdown', de: 'STB Sicherheitsabschaltung' },
709
+ 'F.31': { en: 'Boiler overtemperature', de: 'Kesselübertemperatur' },
710
+ 'F.32': { en: 'Flue overtemperature', de: 'Abgasübertemperatur' },
711
+ 'F.33': { en: 'Draft fault', de: 'Zugfehler' },
712
+ 'F.36': { en: 'Fan fault', de: 'Lüfter Fehler' },
713
+ 'F.40': { en: 'Compressor fault', de: 'Verdichter Fehler' },
714
+ 'F.41': { en: 'Refrigerant circuit fault', de: 'Kältemittelkreis Fehler' },
715
+ 'F.42': { en: 'Defrost fault', de: 'Abtaufehler' },
716
+ 'F.50': { en: 'Communication fault gateway', de: 'Kommunikationsfehler Gateway' },
717
+ 'F.51': { en: 'Communication fault controller', de: 'Kommunikationsfehler Regler' },
718
+ 'F.52': { en: 'Bus fault', de: 'Busfehler' },
719
+ 'F.60': { en: 'Expansion vessel fault', de: 'Ausdehnungsgefäß Fehler' },
720
+ 'F.73': { en: 'Water pressure too high', de: 'Wasserdruck zu hoch' },
721
+ 'F.74': { en: 'Water pressure too low', de: 'Wasserdruck zu niedrig' },
722
+ 'F.75': { en: 'Pump speed sensor fault', de: 'Pumpendrehzahlsensor Fehler' },
723
+ };
724
+
725
+ function translateCode(code) {
726
+ if (!code) return null;
727
+ const entry = VIESSMANN_CODES[code];
728
+ if (entry) return entry.en;
729
+ // Partial match fallback (e.g. S.134 → try S.13 → S.1)
730
+ const parts = code.split('.');
731
+ if (parts.length === 2) {
732
+ const shorter = parts[0] + '.' + parts[1].slice(0, -1);
733
+ if (VIESSMANN_CODES[shorter]) return VIESSMANN_CODES[shorter].en + ' (variant)';
734
+ }
735
+ return null;
736
+ }
737
+
738
+ // Extract device messages from the latest API data snapshot (if available via CSV extension)
739
+ // For now we show the codes from the last row that has error_code-like fields
740
+ // (future: read from a separate messages JSON file written by the plugin)
741
+ const lastBoiler = boilerRows.length ? boilerRows[boilerRows.length - 1] : null;
742
+
584
743
  const sc = (l,v,u='',badge='') => `<div class="sc"><div class="sl">${l}</div><div class="sv">${v!==null?v+u:'<span class="na">N/A</span>'} ${badge}</div></div>`;
585
744
  const badge = (cls,txt) => `<span class="badge badge-${cls}">${txt}</span>`;
586
745
  const genAt = new Date().toLocaleString('en-GB');
@@ -721,6 +880,18 @@ footer{text-align:center;font-size:10px;color:#bbb;padding:16px}
721
880
  ${dhwRows.length >= 2 ? `<div class="ch-tall"><canvas id="cDhw"></canvas></div>` : ''}
722
881
  </div>
723
882
 
883
+ ${gasForecast ? `
884
+ <div class="box">
885
+ <h2>⛽ Gas Forecast</h2>
886
+ <p class="note" style="margin-bottom:14px">Projection based on last ${gasForecast.daysUsed} day(s) of data · gas price: €${gasForecast.gasPrice}/m³ · trend: <strong>${gasForecast.trend === 'rising' ? '↑ Rising' : gasForecast.trend === 'falling' ? '↓ Falling' : '→ Stable'}</strong></p>
887
+ <div class="grid">
888
+ ${sc('Avg consumption/day', gasForecast.avgPerDay, ' m³')}
889
+ ${sc('Projected next 30 days', gasForecast.month30, ' m³', badge(gasForecast.trend === 'rising' ? 'warn' : 'good', '≈ €' + gasForecast.costMonth))}
890
+ ${sc('Annual estimate', gasForecast.annualEst, ' m³', badge('neutral', '≈ €' + gasForecast.costAnnual))}
891
+ </div>
892
+ <p class="note" style="margin-top:10px">⚠️ Annual estimate uses period average × 365 — add more days for seasonal accuracy. Use <code>--gasPriceEur</code> to set your tariff.</p>
893
+ </div>` : ''}
894
+
724
895
  ${energyRows.length >= 1 ? `
725
896
  <div class="box">
726
897
  <h2>Energy System</h2>
@@ -737,6 +908,49 @@ ${energyRows.length >= 1 ? `
737
908
  ${hasWallbox && energyRows.length >= 2 ? `<div class="ch" style="margin-top:14px"><canvas id="cWallbox"></canvas></div>` : ''}
738
909
  </div>` : ''}
739
910
 
911
+ </div>
912
+
913
+ <div class="box" id="device-messages">
914
+ <h2>🔔 Device Messages</h2>
915
+ <p class="note" style="margin-bottom:12px">Status, info and fault codes reported by the device. Translated from Viessmann service documentation.</p>
916
+ <div id="msg-list">
917
+ ${(() => {
918
+ // Read messages from viessmann-messages-<ID>.json if available (written by plugin)
919
+ const msgFile = INSTALLATION_ID
920
+ ? require('path').join(HB_PATH, 'viessmann-messages-' + INSTALLATION_ID + '.json')
921
+ : require('path').join(HB_PATH, 'viessmann-messages.json');
922
+ let messages = [];
923
+ try {
924
+ if (require('fs').existsSync(msgFile)) {
925
+ messages = JSON.parse(require('fs').readFileSync(msgFile, 'utf8'));
926
+ }
927
+ } catch(_) {}
928
+
929
+ if (!messages.length) {
930
+ return `<p class="note">No messages file found yet. Messages will appear here once the plugin writes <code>viessmann-messages-${INSTALLATION_ID || ''}.json</code>.</p>`;
931
+ }
932
+
933
+ return messages.slice(0, 20).map(m => {
934
+ const code = m.errorCode || m.code || '';
935
+ const desc = translateCode(code);
936
+ const ts = m.timestamp ? new Date(m.timestamp).toLocaleString('en-GB') : '';
937
+ const type = code.startsWith('F.') ? 'fault' : code.startsWith('I.') ? 'info' : 'status';
938
+ const bg = type === 'fault' ? '#fff3e0' : type === 'info' ? '#e8f4fd' : '#f1f8f1';
939
+ const br = type === 'fault' ? '#ffb74d' : type === 'info' ? '#90caf9' : '#a5d6a7';
940
+ const icon = type === 'fault' ? '⚠️' : type === 'info' ? 'ℹ️' : '✅';
941
+ return `<div style="display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:${bg};border-left:3px solid ${br};border-radius:4px;margin-bottom:6px;font-size:13px">
942
+ <span style="flex-shrink:0">${icon}</span>
943
+ <div style="flex:1">
944
+ <strong>${code}</strong>${desc ? ` — ${desc}` : ' — (unknown code)'}
945
+ ${ts ? `<span style="color:#888;font-size:11px;margin-left:8px">${ts}</span>` : ''}
946
+ ${m.busAddress ? `<span style="color:#aaa;font-size:11px;margin-left:8px">bus: ${m.busAddress}</span>` : ''}
947
+ </div>
948
+ </div>`;
949
+ }).join('');
950
+ })()}
951
+ </div>
952
+ </div>
953
+
740
954
  </div>
741
955
  <footer>homebridge-viessmann-vicare &nbsp;|&nbsp; ${CSV_FILE}</footer>
742
956