homebridge-viessmann-vicare 2.0.77 → 2.0.78

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/CHANGELOG.md CHANGED
@@ -3,6 +3,13 @@
3
3
  All notable changes to homebridge-viessmann-vicare.
4
4
 
5
5
 
6
+ ### [2.0.78] - 2026-09-26
7
+ - fix: **report "Device messages" were frozen**: the plugin stopped writing `viessmann-messages-<installation>-<device>.json` in 2.0.50, so the report showed months-old codes. The writer is back, now also includes fault codes (`device.messages.errors.raw`) and keeps a history (the API only returns current messages)
8
+ - fix: report heating-curve analysis uses only heating-season samples (outdoor < 16 °C): no more "weather compensation not active" advice in summer
9
+ - fix: report annual gas estimate requires ~a full year of data (a few summer weeks gave e.g. 52 m³/year)
10
+ - fix: report comfort-vs-efficiency is not evaluated when there is no space-heating gas in the period (it claimed "system optimisation is working" in summer)
11
+ - fix: report burner runtime shows one decimal below 1 %
12
+
6
13
  ### [2.0.77] - 2026-09-26
7
14
  - fix: **30/90/365-day reports failed with "Load failed"**: report generation was O(n²) (90 days took ~3 min on a Raspberry Pi, now a few seconds), the browser request stayed open for minutes, and the report tab was opened after the wait (blocked as a pop-up by Safari). Reports now run as background jobs polled by the page, and the tab is opened at click time
8
15
  - fix: report analysis: no false "short cycling" alarm when burner hours changed by less than 2 h (integer counter), heating-curve check uses only samples with the circuit in heating mode, 30-day gas forecast uses the period average with fewer than 14 days of data
package/README.md CHANGED
@@ -1103,6 +1103,13 @@ For issues and questions:
1103
1103
 
1104
1104
  ## 📈 Changelog
1105
1105
 
1106
+ ### [2.0.78] - 2026-09-26
1107
+ - fix: **report "Device messages" were frozen**: the plugin stopped writing `viessmann-messages-<installation>-<device>.json` in 2.0.50, so the report showed months-old codes. The writer is back, now also includes fault codes (`device.messages.errors.raw`) and keeps a history (the API only returns current messages)
1108
+ - fix: report heating-curve analysis uses only heating-season samples (outdoor < 16 °C): no more "weather compensation not active" advice in summer
1109
+ - fix: report annual gas estimate requires ~a full year of data (a few summer weeks gave e.g. 52 m³/year)
1110
+ - fix: report comfort-vs-efficiency is not evaluated when there is no space-heating gas in the period (it claimed "system optimisation is working" in summer)
1111
+ - fix: report burner runtime shows one decimal below 1 %
1112
+
1106
1113
  ### [2.0.77] - 2026-09-26
1107
1114
  - fix: **30/90/365-day reports failed with "Load failed"**: report generation was O(n²) (90 days took ~3 min on a Raspberry Pi, now a few seconds), the browser request stayed open for minutes, and the report tab was opened after the wait (blocked as a pop-up by Safari). Reports now run as background jobs polled by the page, and the tab is opened at click time
1108
1115
  - fix: report analysis: no false "short cycling" alarm when burner hours changed by less than 2 h (integer counter), heating-curve check uses only samples with the circuit in heating mode, 30-day gas forecast uses the period average with fewer than 14 days of data
package/SETUP-GUIDE.md CHANGED
@@ -1,8 +1,8 @@
1
- # Complete Setup Guide - v2.0.77
1
+ # Complete Setup Guide - v2.0.78
2
2
 
3
3
  ## Overview
4
4
 
5
- This guide will walk you through setting up the Viessmann ViCare plugin v2.0.77 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.78 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
 
@@ -1292,6 +1292,9 @@ sudo systemctl restart homebridge
1292
1292
 
1293
1293
  ## Changelog
1294
1294
 
1295
+ ### v2.0.78 (2026-09-26)
1296
+ - fix: report device messages up to date again (with fault codes and history); seasonal-aware heating-curve, annual gas and comfort analyses
1297
+
1295
1298
  ### v2.0.77 (2026-09-26)
1296
1299
  - fix: long reports (30/90/365 days) — much faster, generated in background, no more "Load failed"
1297
1300
  - fix: OAuth URL always logged (Docker), no empty extra Boiler for energy devices, report analysis false alarms
@@ -63,6 +63,13 @@ export declare class ViessmannPlatform implements DynamicPlatformPlugin {
63
63
  private setupRoomSensorDiscovery;
64
64
  private setupRoomAccessories;
65
65
  updateAllDevices(): Promise<void>;
66
+ /**
67
+ * Writes device.messages.* entries (status S.xx, info I.xx, service, errors F.xx) to
68
+ * viessmann-messages-<installationId>-<deviceId>.json, used by viessmann-report.js.
69
+ * The API only returns the CURRENT messages, so entries are merged with the existing
70
+ * file (deduplicated by code+timestamp, newest first, max 200) to keep a history.
71
+ */
72
+ private writeDeviceMessages;
66
73
  private updateCycleComplete;
67
74
  private writeApiStatusFile;
68
75
  private adjustRefreshInterval;
@@ -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
@@ -614,6 +614,9 @@ class ViessmannPlatform {
614
614
  await this.setupHeatingCircuitAccessories(installation, gateway, device, features);
615
615
  // Setup Energy / Heat Pump accessory (PV, battery, wallbox, electric DHW, Wärmepumpe)
616
616
  await this.setupEnergyAccessory(installation, gateway, device, features);
617
+ // Device messages (S./F./I. codes) for the HTML report — restored in 2.0.78
618
+ // (the writer was accidentally removed in 2.0.50, so the report showed stale messages)
619
+ this.writeDeviceMessages(features, installation.id, device.id);
617
620
  // ViCare Smart Climate rooms (one accessory per room) — issue #4
618
621
  if (this.config.features?.enableRoomSensors) {
619
622
  await this.setupRoomAccessories(installation, gateway, device, features);
@@ -895,6 +898,7 @@ class ViessmannPlatform {
895
898
  clearTimeout(timeoutId);
896
899
  deviceFeatureCache.set(deviceKey, features);
897
900
  successfulDevices++;
901
+ this.writeDeviceMessages(features, accessory.context.installation.id, accessory.context.device.id);
898
902
  this.log.debug(`✅ Fetched ${features.length} features for device ${accessory.context.device.id}`);
899
903
  // Small delay between distinct device API calls only
900
904
  if (this.config.enableRateLimitProtection !== false) {
@@ -952,6 +956,61 @@ class ViessmannPlatform {
952
956
  this.isUpdating = false;
953
957
  }
954
958
  }
959
+ /**
960
+ * Writes device.messages.* entries (status S.xx, info I.xx, service, errors F.xx) to
961
+ * viessmann-messages-<installationId>-<deviceId>.json, used by viessmann-report.js.
962
+ * The API only returns the CURRENT messages, so entries are merged with the existing
963
+ * file (deduplicated by code+timestamp, newest first, max 200) to keep a history.
964
+ */
965
+ writeDeviceMessages(features, installationId, deviceId) {
966
+ try {
967
+ const fsm = require('fs');
968
+ const pathm = require('path');
969
+ const sources = [
970
+ ['device.messages.errors.raw', 'error'],
971
+ ['device.messages.status.raw', 'status'],
972
+ ['device.messages.info.raw', 'info'],
973
+ ['device.messages.service.raw', 'service'],
974
+ ];
975
+ const fresh = [];
976
+ let found = false;
977
+ for (const [featureName, type] of sources) {
978
+ const entries = features.find(f => f.feature === featureName)?.properties?.entries?.value;
979
+ if (!Array.isArray(entries))
980
+ continue;
981
+ found = true;
982
+ for (const e of entries) {
983
+ if (e?.errorCode) {
984
+ fresh.push({ errorCode: e.errorCode, timestamp: e.timestamp || new Date().toISOString(), type,
985
+ busAddress: e.busAddress, busType: e.busType });
986
+ }
987
+ }
988
+ }
989
+ if (!found)
990
+ return; // device without message features: leave any existing file untouched
991
+ const file = pathm.join(this.api.user.storagePath(), `viessmann-messages-${installationId}-${deviceId}.json`);
992
+ let old = [];
993
+ try {
994
+ old = JSON.parse(fsm.readFileSync(file, 'utf8'));
995
+ if (!Array.isArray(old))
996
+ old = [];
997
+ }
998
+ catch { /* first run */ }
999
+ const seen = new Set();
1000
+ const merged = [...fresh, ...old].filter(m => {
1001
+ const k = `${m.errorCode}|${m.timestamp}`;
1002
+ if (seen.has(k))
1003
+ return false;
1004
+ seen.add(k);
1005
+ return true;
1006
+ }).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).slice(0, 200);
1007
+ fsm.writeFileSync(file, JSON.stringify(merged, null, 2), 'utf8');
1008
+ this.log.debug(`📋 Device messages: ${fresh.length} current, ${merged.length} kept → ${file}`);
1009
+ }
1010
+ catch (e) {
1011
+ this.log.debug(`📋 writeDeviceMessages failed: ${e}`);
1012
+ }
1013
+ }
955
1014
  updateCycleComplete(successful, rateLimited, errors, skipped, handlersCalled, handlersSkipped, elapsedMs) {
956
1015
  const totalAccessories = this.accessories.length;
957
1016
  const elapsedSec = (elapsedMs / 1000).toFixed(1);
@@ -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.77";
12
+ export declare const PLUGIN_VERSION = "2.0.78";
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.77';
15
+ exports.PLUGIN_VERSION = '2.0.78';
16
16
  /**
17
17
  * Default configuration values
18
18
  */
@@ -83,7 +83,7 @@
83
83
  "name": "kwh_m3",
84
84
  "label": "kWh/m³",
85
85
  "type": "constant",
86
- "query": "10.5",
86
+ "query": "11.0",
87
87
  "hide": 2
88
88
  },
89
89
  {
@@ -1217,11 +1217,11 @@
1217
1217
  },
1218
1218
  "hide": 2,
1219
1219
  "refresh": 1,
1220
- "query": "SELECT IF('${lang}'='en','Heat produced / gas energy (current year). Calorific value from variable kwh_m3 (default 10.5 kWh/m³). Above 100% = inaccurate boiler estimate. Since v2.0.75.','Calore prodotto / energia del gas (anno corrente). Potere calorifico da variabile kwh_m3 (default 10,5 kWh/m³). Oltre 100% = stima della caldaia imprecisa. Dalla v2.0.75.')",
1221
- "definition": "SELECT IF('${lang}'='en','Heat produced / gas energy (current year). Calorific value from variable kwh_m3 (default 10.5 kWh/m³). Above 100% = inaccurate boiler estimate. Since v2.0.75.','Calore prodotto / energia del gas (anno corrente). Potere calorifico da variabile kwh_m3 (default 10,5 kWh/m³). Oltre 100% = stima della caldaia imprecisa. Dalla v2.0.75.')",
1220
+ "query": "SELECT IF('${lang}'='en','Heat produced / gas energy (current year). Gross calorific value from variable kwh_m3 (default 11.0 kWh/Smc = 0.0396 GJ/Smc, as printed on Italian gas bills). Based on GCV: a well-condensing boiler is around 90–97%. Since v2.0.75.','Calore prodotto / energia del gas (anno corrente). Potere calorifico superiore da variabile kwh_m3 (default 11,0 kWh/Smc = 0,0396 GJ/Smc, il valore riportato in bolletta). Calcolato sul PCS: una condensazione ottima sta intorno al 90–97%. Dalla v2.0.75.')",
1221
+ "definition": "SELECT IF('${lang}'='en','Heat produced / gas energy (current year). Gross calorific value from variable kwh_m3 (default 11.0 kWh/Smc = 0.0396 GJ/Smc, as printed on Italian gas bills). Based on GCV: a well-condensing boiler is around 90–97%. Since v2.0.75.','Calore prodotto / energia del gas (anno corrente). Potere calorifico superiore da variabile kwh_m3 (default 11,0 kWh/Smc = 0,0396 GJ/Smc, il valore riportato in bolletta). Calcolato sul PCS: una condensazione ottima sta intorno al 90–97%. Dalla v2.0.75.')",
1222
1222
  "current": {
1223
- "text": "Calore prodotto / energia del gas (anno corrente). Potere calorifico da variabile kwh_m3 (default 10,5 kWh/m³). Oltre 100% = stima della caldaia imprecisa. Dalla v2.0.75.",
1224
- "value": "Calore prodotto / energia del gas (anno corrente). Potere calorifico da variabile kwh_m3 (default 10,5 kWh/m³). Oltre 100% = stima della caldaia imprecisa. Dalla v2.0.75."
1223
+ "text": "Calore prodotto / energia del gas (anno corrente). Potere calorifico superiore da variabile kwh_m3 (default 11,0 kWh/Smc = 0,0396 GJ/Smc, il valore riportato in bolletta). Calcolato sul PCS: una condensazione ottima sta intorno al 90–97%. Dalla v2.0.75.",
1224
+ "value": "Calore prodotto / energia del gas (anno corrente). Potere calorifico superiore da variabile kwh_m3 (default 11,0 kWh/Smc = 0,0396 GJ/Smc, il valore riportato in bolletta). Calcolato sul PCS: una condensazione ottima sta intorno al 90–97%. Dalla v2.0.75."
1225
1225
  },
1226
1226
  "options": [],
1227
1227
  "multi": false,
@@ -1415,11 +1415,121 @@
1415
1415
  },
1416
1416
  "hide": 2,
1417
1417
  "refresh": 1,
1418
- "query": "SELECT IF('${lang}'='en','A slow steady drop indicates a leak or an expansion vessel that needs recharging.','Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare.')",
1419
- "definition": "SELECT IF('${lang}'='en','A slow steady drop indicates a leak or an expansion vessel that needs recharging.','Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare.')",
1418
+ "query": "SELECT IF('${lang}'='en','Bands: red below 0.8 bar (refill), green 1.0–2.5 bar (optimal), orange above 2.5 bar. A slow steady drop indicates a leak or an expansion vessel that needs recharging.','Fasce: rosso sotto 0,8 bar (ricaricare), verde 1,0–2,5 bar (ottimale), arancio sopra 2,5 bar. Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare.')",
1419
+ "definition": "SELECT IF('${lang}'='en','Bands: red below 0.8 bar (refill), green 1.0–2.5 bar (optimal), orange above 2.5 bar. A slow steady drop indicates a leak or an expansion vessel that needs recharging.','Fasce: rosso sotto 0,8 bar (ricaricare), verde 1,0–2,5 bar (ottimale), arancio sopra 2,5 bar. Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare.')",
1420
1420
  "current": {
1421
- "text": "Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare.",
1422
- "value": "Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare."
1421
+ "text": "Fasce: rosso sotto 0,8 bar (ricaricare), verde 1,0–2,5 bar (ottimale), arancio sopra 2,5 bar. Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare.",
1422
+ "value": "Fasce: rosso sotto 0,8 bar (ricaricare), verde 1,0–2,5 bar (ottimale), arancio sopra 2,5 bar. Un calo lento e costante indica una perdita o il vaso di espansione da ricaricare."
1423
+ },
1424
+ "options": [],
1425
+ "multi": false,
1426
+ "includeAll": false,
1427
+ "regex": "",
1428
+ "sort": 0,
1429
+ "skipUrlSync": true
1430
+ },
1431
+ {
1432
+ "name": "t_wifi_ts",
1433
+ "type": "query",
1434
+ "datasource": {
1435
+ "type": "mysql",
1436
+ "uid": "${ds}"
1437
+ },
1438
+ "hide": 2,
1439
+ "refresh": 1,
1440
+ "query": "SELECT IF('${lang}'='en','Boiler Wi-Fi signal','Segnale Wi-Fi caldaia')",
1441
+ "definition": "SELECT IF('${lang}'='en','Boiler Wi-Fi signal','Segnale Wi-Fi caldaia')",
1442
+ "current": {
1443
+ "text": "Segnale Wi-Fi caldaia",
1444
+ "value": "Segnale Wi-Fi caldaia"
1445
+ },
1446
+ "options": [],
1447
+ "multi": false,
1448
+ "includeAll": false,
1449
+ "regex": "",
1450
+ "sort": 0,
1451
+ "skipUrlSync": true
1452
+ },
1453
+ {
1454
+ "name": "t_wifi_ts_d",
1455
+ "type": "query",
1456
+ "datasource": {
1457
+ "type": "mysql",
1458
+ "uid": "${ds}"
1459
+ },
1460
+ "hide": 2,
1461
+ "refresh": 1,
1462
+ "query": "SELECT IF('${lang}'='en','Bands: green above -67 dBm (good), orange -67…-80 dBm (fair), red below -80 dBm (dropouts likely).','Fasce: verde oltre -67 dBm (ottimo), arancio -67…-80 dBm (sufficiente), rosso sotto -80 dBm (disconnessioni probabili).')",
1463
+ "definition": "SELECT IF('${lang}'='en','Bands: green above -67 dBm (good), orange -67…-80 dBm (fair), red below -80 dBm (dropouts likely).','Fasce: verde oltre -67 dBm (ottimo), arancio -67…-80 dBm (sufficiente), rosso sotto -80 dBm (disconnessioni probabili).')",
1464
+ "current": {
1465
+ "text": "Fasce: verde oltre -67 dBm (ottimo), arancio -67…-80 dBm (sufficiente), rosso sotto -80 dBm (disconnessioni probabili).",
1466
+ "value": "Fasce: verde oltre -67 dBm (ottimo), arancio -67…-80 dBm (sufficiente), rosso sotto -80 dBm (disconnessioni probabili)."
1467
+ },
1468
+ "options": [],
1469
+ "multi": false,
1470
+ "includeAll": false,
1471
+ "regex": "",
1472
+ "sort": 0,
1473
+ "skipUrlSync": true
1474
+ },
1475
+ {
1476
+ "name": "t_curve_calc",
1477
+ "type": "query",
1478
+ "datasource": {
1479
+ "type": "mysql",
1480
+ "uid": "${ds}"
1481
+ },
1482
+ "hide": 2,
1483
+ "refresh": 1,
1484
+ "query": "SELECT IF('${lang}'='en','Curve flow','Mandata da curva')",
1485
+ "definition": "SELECT IF('${lang}'='en','Curve flow','Mandata da curva')",
1486
+ "current": {
1487
+ "text": "Mandata da curva",
1488
+ "value": "Mandata da curva"
1489
+ },
1490
+ "options": [],
1491
+ "multi": false,
1492
+ "includeAll": false,
1493
+ "regex": "",
1494
+ "sort": 0,
1495
+ "skipUrlSync": true
1496
+ },
1497
+ {
1498
+ "name": "t_curve_meas",
1499
+ "type": "query",
1500
+ "datasource": {
1501
+ "type": "mysql",
1502
+ "uid": "${ds}"
1503
+ },
1504
+ "hide": 2,
1505
+ "refresh": 1,
1506
+ "query": "SELECT IF('${lang}'='en','Measured flow (burner on)','Mandata misurata (bruciatore acceso)')",
1507
+ "definition": "SELECT IF('${lang}'='en','Measured flow (burner on)','Mandata misurata (bruciatore acceso)')",
1508
+ "current": {
1509
+ "text": "Mandata misurata (bruciatore acceso)",
1510
+ "value": "Mandata misurata (bruciatore acceso)"
1511
+ },
1512
+ "options": [],
1513
+ "multi": false,
1514
+ "includeAll": false,
1515
+ "regex": "",
1516
+ "sort": 0,
1517
+ "skipUrlSync": true
1518
+ },
1519
+ {
1520
+ "name": "t_outdoor",
1521
+ "type": "query",
1522
+ "datasource": {
1523
+ "type": "mysql",
1524
+ "uid": "${ds}"
1525
+ },
1526
+ "hide": 2,
1527
+ "refresh": 1,
1528
+ "query": "SELECT IF('${lang}'='en','Outdoor (boiler sensor)','Temperatura esterna (sonda caldaia)')",
1529
+ "definition": "SELECT IF('${lang}'='en','Outdoor (boiler sensor)','Temperatura esterna (sonda caldaia)')",
1530
+ "current": {
1531
+ "text": "Temperatura esterna (sonda caldaia)",
1532
+ "value": "Temperatura esterna (sonda caldaia)"
1423
1533
  },
1424
1534
  "options": [],
1425
1535
  "multi": false,
@@ -1437,11 +1547,11 @@
1437
1547
  },
1438
1548
  "hide": 2,
1439
1549
  "refresh": 1,
1440
- "query": "SELECT IF('${lang}'='en','Heating curve','Curva di riscaldamento')",
1441
- "definition": "SELECT IF('${lang}'='en','Heating curve','Curva di riscaldamento')",
1550
+ "query": "SELECT IF('${lang}'='en','Heating curve','Curva climatica')",
1551
+ "definition": "SELECT IF('${lang}'='en','Heating curve','Curva climatica')",
1442
1552
  "current": {
1443
- "text": "Curva di riscaldamento",
1444
- "value": "Curva di riscaldamento"
1553
+ "text": "Curva climatica",
1554
+ "value": "Curva climatica"
1445
1555
  },
1446
1556
  "options": [],
1447
1557
  "multi": false,
@@ -1459,11 +1569,11 @@
1459
1569
  },
1460
1570
  "hide": 2,
1461
1571
  "refresh": 1,
1462
- "query": "SELECT IF('${lang}'='en','Slope and shift of the weather-compensated heating curve. A slope that is too high raises flow temperature and burner starts.','Pendenza e spostamento (parallelo) della curva climatica. Una pendenza troppo alta aumenta la temperatura di mandata e le accensioni.')",
1463
- "definition": "SELECT IF('${lang}'='en','Slope and shift of the weather-compensated heating curve. A slope that is too high raises flow temperature and burner starts.','Pendenza e spostamento (parallelo) della curva climatica. Una pendenza troppo alta aumenta la temperatura di mandata e le accensioni.')",
1572
+ "query": "SELECT IF('${lang}'='en','Line: flow temperature computed from the curve (slope, shift and Normal temperature) versus the outdoor sensor. Dots: average measured flow with the circuit heating and the burner on, in the selected period (no dots in summer). Note: the curve uses the boiler outdoor sensor, not the real outdoor temperature.','Linea: temperatura di mandata calcolata dalla curva (pendenza, spostamento e temperatura Normale) in funzione della sonda esterna. Punti: mandata media misurata con circuito in riscaldamento e bruciatore acceso, nel periodo selezionato (in estate non ci sono punti). Nota: la curva usa la sonda esterna della caldaia, non la temperatura reale.')",
1573
+ "definition": "SELECT IF('${lang}'='en','Line: flow temperature computed from the curve (slope, shift and Normal temperature) versus the outdoor sensor. Dots: average measured flow with the circuit heating and the burner on, in the selected period (no dots in summer). Note: the curve uses the boiler outdoor sensor, not the real outdoor temperature.','Linea: temperatura di mandata calcolata dalla curva (pendenza, spostamento e temperatura Normale) in funzione della sonda esterna. Punti: mandata media misurata con circuito in riscaldamento e bruciatore acceso, nel periodo selezionato (in estate non ci sono punti). Nota: la curva usa la sonda esterna della caldaia, non la temperatura reale.')",
1464
1574
  "current": {
1465
- "text": "Pendenza e spostamento (parallelo) della curva climatica. Una pendenza troppo alta aumenta la temperatura di mandata e le accensioni.",
1466
- "value": "Pendenza e spostamento (parallelo) della curva climatica. Una pendenza troppo alta aumenta la temperatura di mandata e le accensioni."
1575
+ "text": "Linea: temperatura di mandata calcolata dalla curva (pendenza, spostamento e temperatura Normale) in funzione della sonda esterna. Punti: mandata media misurata con circuito in riscaldamento e bruciatore acceso, nel periodo selezionato (in estate non ci sono punti). Nota: la curva usa la sonda esterna della caldaia, non la temperatura reale.",
1576
+ "value": "Linea: temperatura di mandata calcolata dalla curva (pendenza, spostamento e temperatura Normale) in funzione della sonda esterna. Punti: mandata media misurata con circuito in riscaldamento e bruciatore acceso, nel periodo selezionato (in estate non ci sono punti). Nota: la curva usa la sonda esterna della caldaia, non la temperatura reale."
1467
1577
  },
1468
1578
  "options": [],
1469
1579
  "multi": false,
@@ -1473,7 +1583,7 @@
1473
1583
  "skipUrlSync": true
1474
1584
  },
1475
1585
  {
1476
- "name": "t_wifi_ts",
1586
+ "name": "t_curve_params",
1477
1587
  "type": "query",
1478
1588
  "datasource": {
1479
1589
  "type": "mysql",
@@ -1481,11 +1591,121 @@
1481
1591
  },
1482
1592
  "hide": 2,
1483
1593
  "refresh": 1,
1484
- "query": "SELECT IF('${lang}'='en','Boiler Wi-Fi signal','Segnale Wi-Fi caldaia')",
1485
- "definition": "SELECT IF('${lang}'='en','Boiler Wi-Fi signal','Segnale Wi-Fi caldaia')",
1594
+ "query": "SELECT IF('${lang}'='en','Curve settings','Impostazioni curva')",
1595
+ "definition": "SELECT IF('${lang}'='en','Curve settings','Impostazioni curva')",
1486
1596
  "current": {
1487
- "text": "Segnale Wi-Fi caldaia",
1488
- "value": "Segnale Wi-Fi caldaia"
1597
+ "text": "Impostazioni curva",
1598
+ "value": "Impostazioni curva"
1599
+ },
1600
+ "options": [],
1601
+ "multi": false,
1602
+ "includeAll": false,
1603
+ "regex": "",
1604
+ "sort": 0,
1605
+ "skipUrlSync": true
1606
+ },
1607
+ {
1608
+ "name": "t_curve_params_d",
1609
+ "type": "query",
1610
+ "datasource": {
1611
+ "type": "mysql",
1612
+ "uid": "${ds}"
1613
+ },
1614
+ "hide": 2,
1615
+ "refresh": 1,
1616
+ "query": "SELECT IF('${lang}'='en','Current values set on the boiler. Typical slope for radiators 1.0–1.6; typical shift 0–5. The Comfort temperature is used only with the Comfort program / extended heating.','Valori attuali impostati in caldaia. Pendenza tipica radiatori 1,0–1,6; spostamento tipico 0–5. La temperatura Comfort viene usata solo con programma Comfort / riscaldamento prolungato.')",
1617
+ "definition": "SELECT IF('${lang}'='en','Current values set on the boiler. Typical slope for radiators 1.0–1.6; typical shift 0–5. The Comfort temperature is used only with the Comfort program / extended heating.','Valori attuali impostati in caldaia. Pendenza tipica radiatori 1,0–1,6; spostamento tipico 0–5. La temperatura Comfort viene usata solo con programma Comfort / riscaldamento prolungato.')",
1618
+ "current": {
1619
+ "text": "Valori attuali impostati in caldaia. Pendenza tipica radiatori 1,0–1,6; spostamento tipico 0–5. La temperatura Comfort viene usata solo con programma Comfort / riscaldamento prolungato.",
1620
+ "value": "Valori attuali impostati in caldaia. Pendenza tipica radiatori 1,0–1,6; spostamento tipico 0–5. La temperatura Comfort viene usata solo con programma Comfort / riscaldamento prolungato."
1621
+ },
1622
+ "options": [],
1623
+ "multi": false,
1624
+ "includeAll": false,
1625
+ "regex": "",
1626
+ "sort": 0,
1627
+ "skipUrlSync": true
1628
+ },
1629
+ {
1630
+ "name": "t_slope",
1631
+ "type": "query",
1632
+ "datasource": {
1633
+ "type": "mysql",
1634
+ "uid": "${ds}"
1635
+ },
1636
+ "hide": 2,
1637
+ "refresh": 1,
1638
+ "query": "SELECT IF('${lang}'='en','Slope','Pendenza')",
1639
+ "definition": "SELECT IF('${lang}'='en','Slope','Pendenza')",
1640
+ "current": {
1641
+ "text": "Pendenza",
1642
+ "value": "Pendenza"
1643
+ },
1644
+ "options": [],
1645
+ "multi": false,
1646
+ "includeAll": false,
1647
+ "regex": "",
1648
+ "sort": 0,
1649
+ "skipUrlSync": true
1650
+ },
1651
+ {
1652
+ "name": "t_shift",
1653
+ "type": "query",
1654
+ "datasource": {
1655
+ "type": "mysql",
1656
+ "uid": "${ds}"
1657
+ },
1658
+ "hide": 2,
1659
+ "refresh": 1,
1660
+ "query": "SELECT IF('${lang}'='en','Shift','Spostamento')",
1661
+ "definition": "SELECT IF('${lang}'='en','Shift','Spostamento')",
1662
+ "current": {
1663
+ "text": "Spostamento",
1664
+ "value": "Spostamento"
1665
+ },
1666
+ "options": [],
1667
+ "multi": false,
1668
+ "includeAll": false,
1669
+ "regex": "",
1670
+ "sort": 0,
1671
+ "skipUrlSync": true
1672
+ },
1673
+ {
1674
+ "name": "t_normal",
1675
+ "type": "query",
1676
+ "datasource": {
1677
+ "type": "mysql",
1678
+ "uid": "${ds}"
1679
+ },
1680
+ "hide": 2,
1681
+ "refresh": 1,
1682
+ "query": "SELECT IF('${lang}'='en','Normal','Normale')",
1683
+ "definition": "SELECT IF('${lang}'='en','Normal','Normale')",
1684
+ "current": {
1685
+ "text": "Normale",
1686
+ "value": "Normale"
1687
+ },
1688
+ "options": [],
1689
+ "multi": false,
1690
+ "includeAll": false,
1691
+ "regex": "",
1692
+ "sort": 0,
1693
+ "skipUrlSync": true
1694
+ },
1695
+ {
1696
+ "name": "t_reduced",
1697
+ "type": "query",
1698
+ "datasource": {
1699
+ "type": "mysql",
1700
+ "uid": "${ds}"
1701
+ },
1702
+ "hide": 2,
1703
+ "refresh": 1,
1704
+ "query": "SELECT IF('${lang}'='en','Reduced','Ridotta')",
1705
+ "definition": "SELECT IF('${lang}'='en','Reduced','Ridotta')",
1706
+ "current": {
1707
+ "text": "Ridotta",
1708
+ "value": "Ridotta"
1489
1709
  },
1490
1710
  "options": [],
1491
1711
  "multi": false,
@@ -2045,7 +2265,7 @@
2045
2265
  },
2046
2266
  {
2047
2267
  "color": "orange",
2048
- "value": 1500
2268
+ "value": 1800
2049
2269
  },
2050
2270
  {
2051
2271
  "color": "red",
@@ -2139,7 +2359,7 @@
2139
2359
  {
2140
2360
  "type": "value",
2141
2361
  "options": {
2142
- "Ridotto": {
2362
+ "Reduced": {
2143
2363
  "color": "blue",
2144
2364
  "index": 3
2145
2365
  }
@@ -2148,7 +2368,7 @@
2148
2368
  {
2149
2369
  "type": "value",
2150
2370
  "options": {
2151
- "Reduced": {
2371
+ "Ridotto": {
2152
2372
  "color": "blue",
2153
2373
  "index": 4
2154
2374
  }
@@ -2175,7 +2395,7 @@
2175
2395
  {
2176
2396
  "type": "value",
2177
2397
  "options": {
2178
- "Vacanza": {
2398
+ "Holiday": {
2179
2399
  "color": "yellow",
2180
2400
  "index": 7
2181
2401
  }
@@ -2184,7 +2404,7 @@
2184
2404
  {
2185
2405
  "type": "value",
2186
2406
  "options": {
2187
- "Holiday": {
2407
+ "Vacanza": {
2188
2408
  "color": "yellow",
2189
2409
  "index": 8
2190
2410
  }
@@ -2284,7 +2504,7 @@
2284
2504
  {
2285
2505
  "type": "value",
2286
2506
  "options": {
2287
- "DHW only": {
2507
+ "Solo ACS": {
2288
2508
  "color": "blue",
2289
2509
  "index": 3
2290
2510
  }
@@ -2293,7 +2513,7 @@
2293
2513
  {
2294
2514
  "type": "value",
2295
2515
  "options": {
2296
- "Solo ACS": {
2516
+ "DHW only": {
2297
2517
  "color": "blue",
2298
2518
  "index": 4
2299
2519
  }
@@ -2402,7 +2622,7 @@
2402
2622
  {
2403
2623
  "type": "value",
2404
2624
  "options": {
2405
- "Off": {
2625
+ "Spenta": {
2406
2626
  "color": "text",
2407
2627
  "index": 2
2408
2628
  }
@@ -2411,7 +2631,7 @@
2411
2631
  {
2412
2632
  "type": "value",
2413
2633
  "options": {
2414
- "Spenta": {
2634
+ "Off": {
2415
2635
  "color": "text",
2416
2636
  "index": 3
2417
2637
  }
@@ -2420,7 +2640,7 @@
2420
2640
  {
2421
2641
  "type": "value",
2422
2642
  "options": {
2423
- "Efficiente": {
2643
+ "Efficient": {
2424
2644
  "color": "blue",
2425
2645
  "index": 4
2426
2646
  }
@@ -2429,7 +2649,7 @@
2429
2649
  {
2430
2650
  "type": "value",
2431
2651
  "options": {
2432
- "Efficient": {
2652
+ "Efficiente": {
2433
2653
  "color": "blue",
2434
2654
  "index": 5
2435
2655
  }
@@ -2438,7 +2658,7 @@
2438
2658
  {
2439
2659
  "type": "value",
2440
2660
  "options": {
2441
- "Efficient+": {
2661
+ "Efficiente+": {
2442
2662
  "color": "blue",
2443
2663
  "index": 6
2444
2664
  }
@@ -2447,7 +2667,7 @@
2447
2667
  {
2448
2668
  "type": "value",
2449
2669
  "options": {
2450
- "Efficiente+": {
2670
+ "Efficient+": {
2451
2671
  "color": "blue",
2452
2672
  "index": 7
2453
2673
  }
@@ -2939,7 +3159,7 @@
2939
3159
  {
2940
3160
  "matcher": {
2941
3161
  "id": "byName",
2942
- "options": "Room target"
3162
+ "options": "Obiettivo ambiente"
2943
3163
  },
2944
3164
  "properties": [
2945
3165
  {
@@ -2954,7 +3174,7 @@
2954
3174
  {
2955
3175
  "matcher": {
2956
3176
  "id": "byName",
2957
- "options": "Obiettivo ambiente"
3177
+ "options": "Room target"
2958
3178
  },
2959
3179
  "properties": [
2960
3180
  {
@@ -3107,7 +3327,7 @@
3107
3327
  {
3108
3328
  "matcher": {
3109
3329
  "id": "byName",
3110
- "options": "Circuit flow"
3330
+ "options": "Mandata circuito"
3111
3331
  },
3112
3332
  "properties": [
3113
3333
  {
@@ -3122,7 +3342,7 @@
3122
3342
  {
3123
3343
  "matcher": {
3124
3344
  "id": "byName",
3125
- "options": "Mandata circuito"
3345
+ "options": "Circuit flow"
3126
3346
  },
3127
3347
  "properties": [
3128
3348
  {
@@ -3262,7 +3482,7 @@
3262
3482
  {
3263
3483
  "matcher": {
3264
3484
  "id": "byName",
3265
- "options": "DHW target"
3485
+ "options": "Obiettivo ACS"
3266
3486
  },
3267
3487
  "properties": [
3268
3488
  {
@@ -3277,7 +3497,7 @@
3277
3497
  {
3278
3498
  "matcher": {
3279
3499
  "id": "byName",
3280
- "options": "Obiettivo ACS"
3500
+ "options": "DHW target"
3281
3501
  },
3282
3502
  "properties": [
3283
3503
  {
@@ -3453,7 +3673,7 @@
3453
3673
  {
3454
3674
  "matcher": {
3455
3675
  "id": "byName",
3456
- "options": "Reduced"
3676
+ "options": "Ridotta"
3457
3677
  },
3458
3678
  "properties": [
3459
3679
  {
@@ -3468,7 +3688,7 @@
3468
3688
  {
3469
3689
  "matcher": {
3470
3690
  "id": "byName",
3471
- "options": "Ridotta"
3691
+ "options": "Reduced"
3472
3692
  },
3473
3693
  "properties": [
3474
3694
  {
@@ -3642,7 +3862,7 @@
3642
3862
  {
3643
3863
  "matcher": {
3644
3864
  "id": "byName",
3645
- "options": "Modulazione"
3865
+ "options": "Modulation"
3646
3866
  },
3647
3867
  "properties": [
3648
3868
  {
@@ -3657,7 +3877,7 @@
3657
3877
  {
3658
3878
  "matcher": {
3659
3879
  "id": "byName",
3660
- "options": "Modulation"
3880
+ "options": "Modulazione"
3661
3881
  },
3662
3882
  "properties": [
3663
3883
  {
@@ -4238,11 +4458,11 @@
4238
4458
  },
4239
4459
  {
4240
4460
  "color": "green",
4241
- "value": 88
4461
+ "value": 86
4242
4462
  },
4243
4463
  {
4244
4464
  "color": "purple",
4245
- "value": 110
4465
+ "value": 105
4246
4466
  }
4247
4467
  ]
4248
4468
  },
@@ -4409,7 +4629,7 @@
4409
4629
  {
4410
4630
  "type": "value",
4411
4631
  "options": {
4412
- "Ridotto": {
4632
+ "Reduced": {
4413
4633
  "color": "blue",
4414
4634
  "index": 3
4415
4635
  }
@@ -4418,7 +4638,7 @@
4418
4638
  {
4419
4639
  "type": "value",
4420
4640
  "options": {
4421
- "Reduced": {
4641
+ "Ridotto": {
4422
4642
  "color": "blue",
4423
4643
  "index": 4
4424
4644
  }
@@ -4445,7 +4665,7 @@
4445
4665
  {
4446
4666
  "type": "value",
4447
4667
  "options": {
4448
- "Vacanza": {
4668
+ "Holiday": {
4449
4669
  "color": "yellow",
4450
4670
  "index": 7
4451
4671
  }
@@ -4454,7 +4674,7 @@
4454
4674
  {
4455
4675
  "type": "value",
4456
4676
  "options": {
4457
- "Holiday": {
4677
+ "Vacanza": {
4458
4678
  "color": "yellow",
4459
4679
  "index": 8
4460
4680
  }
@@ -4490,7 +4710,7 @@
4490
4710
  {
4491
4711
  "type": "value",
4492
4712
  "options": {
4493
- "DHW only": {
4713
+ "Solo ACS": {
4494
4714
  "color": "blue",
4495
4715
  "index": 3
4496
4716
  }
@@ -4499,7 +4719,7 @@
4499
4719
  {
4500
4720
  "type": "value",
4501
4721
  "options": {
4502
- "Solo ACS": {
4722
+ "DHW only": {
4503
4723
  "color": "blue",
4504
4724
  "index": 4
4505
4725
  }
@@ -4613,7 +4833,7 @@
4613
4833
  {
4614
4834
  "type": "value",
4615
4835
  "options": {
4616
- "Off": {
4836
+ "Spenta": {
4617
4837
  "color": "text",
4618
4838
  "index": 2
4619
4839
  }
@@ -4622,7 +4842,7 @@
4622
4842
  {
4623
4843
  "type": "value",
4624
4844
  "options": {
4625
- "Spenta": {
4845
+ "Off": {
4626
4846
  "color": "text",
4627
4847
  "index": 3
4628
4848
  }
@@ -4631,7 +4851,7 @@
4631
4851
  {
4632
4852
  "type": "value",
4633
4853
  "options": {
4634
- "Efficiente": {
4854
+ "Efficient": {
4635
4855
  "color": "blue",
4636
4856
  "index": 4
4637
4857
  }
@@ -4640,7 +4860,7 @@
4640
4860
  {
4641
4861
  "type": "value",
4642
4862
  "options": {
4643
- "Efficient": {
4863
+ "Efficiente": {
4644
4864
  "color": "blue",
4645
4865
  "index": 5
4646
4866
  }
@@ -4649,7 +4869,7 @@
4649
4869
  {
4650
4870
  "type": "value",
4651
4871
  "options": {
4652
- "Efficient+": {
4872
+ "Efficiente+": {
4653
4873
  "color": "blue",
4654
4874
  "index": 6
4655
4875
  }
@@ -4658,7 +4878,7 @@
4658
4878
  {
4659
4879
  "type": "value",
4660
4880
  "options": {
4661
- "Efficiente+": {
4881
+ "Efficient+": {
4662
4882
  "color": "blue",
4663
4883
  "index": 7
4664
4884
  }
@@ -4707,8 +4927,8 @@
4707
4927
  "uid": "${ds}"
4708
4928
  },
4709
4929
  "gridPos": {
4710
- "h": 8,
4711
- "w": 8,
4930
+ "h": 7,
4931
+ "w": 12,
4712
4932
  "x": 0,
4713
4933
  "y": 63
4714
4934
  },
@@ -4751,19 +4971,46 @@
4751
4971
  "mode": "none",
4752
4972
  "group": "A"
4753
4973
  },
4754
- "axisBorderShow": false
4974
+ "axisBorderShow": false,
4975
+ "thresholdsStyle": {
4976
+ "mode": "area"
4977
+ },
4978
+ "pointSize": 6
4755
4979
  },
4756
4980
  "color": {
4757
4981
  "mode": "palette-classic"
4758
4982
  },
4759
4983
  "decimals": 2,
4760
- "noValue": "—"
4984
+ "min": 0,
4985
+ "max": 3,
4986
+ "noValue": "—",
4987
+ "thresholds": {
4988
+ "mode": "absolute",
4989
+ "steps": [
4990
+ {
4991
+ "color": "red",
4992
+ "value": null
4993
+ },
4994
+ {
4995
+ "color": "orange",
4996
+ "value": 0.8
4997
+ },
4998
+ {
4999
+ "color": "green",
5000
+ "value": 1.0
5001
+ },
5002
+ {
5003
+ "color": "orange",
5004
+ "value": 2.5
5005
+ }
5006
+ ]
5007
+ }
4761
5008
  },
4762
5009
  "overrides": [
4763
5010
  {
4764
5011
  "matcher": {
4765
5012
  "id": "byName",
4766
- "options": "Pressure"
5013
+ "options": "Pressione"
4767
5014
  },
4768
5015
  "properties": [
4769
5016
  {
@@ -4778,7 +5025,7 @@
4778
5025
  {
4779
5026
  "matcher": {
4780
5027
  "id": "byName",
4781
- "options": "Pressione"
5028
+ "options": "Pressure"
4782
5029
  },
4783
5030
  "properties": [
4784
5031
  {
@@ -4797,15 +5044,15 @@
4797
5044
  {
4798
5045
  "type": "timeseries",
4799
5046
  "id": 37,
4800
- "title": "${t_curve}",
5047
+ "title": "${t_wifi_ts}",
4801
5048
  "datasource": {
4802
5049
  "type": "mysql",
4803
5050
  "uid": "${ds}"
4804
5051
  },
4805
5052
  "gridPos": {
4806
- "h": 8,
4807
- "w": 8,
4808
- "x": 8,
5053
+ "h": 7,
5054
+ "w": 12,
5055
+ "x": 12,
4809
5056
  "y": 63
4810
5057
  },
4811
5058
  "targets": [
@@ -4818,7 +5065,7 @@
4818
5065
  "rawQuery": true,
4819
5066
  "editorMode": "code",
4820
5067
  "format": "time_series",
4821
- "rawSql": "SELECT TIMESTAMPDIFF(SECOND,'1970-01-01 00:00:00',ts) AS time, IF('${lang}'='en','Slope','Pendenza') AS metric, hc_slope AS value FROM viessmann_history WHERE accessory='hc0' AND event_type='snapshot' AND ts BETWEEN '1970-01-01' + INTERVAL $__unixEpochFrom() SECOND AND '1970-01-01' + INTERVAL $__unixEpochTo() SECOND AND hc_slope IS NOT NULL UNION ALL SELECT TIMESTAMPDIFF(SECOND,'1970-01-01 00:00:00',ts) AS time, IF('${lang}'='en','Shift','Spostamento') AS metric, hc_shift AS value FROM viessmann_history WHERE accessory='hc0' AND event_type='snapshot' AND ts BETWEEN '1970-01-01' + INTERVAL $__unixEpochFrom() SECOND AND '1970-01-01' + INTERVAL $__unixEpochTo() SECOND AND hc_shift IS NOT NULL ORDER BY time"
5068
+ "rawSql": "SELECT TIMESTAMPDIFF(SECOND,'1970-01-01 00:00:00',ts) AS time, IF('${lang}'='en','RSSI','RSSI') AS metric, wifi_rssi AS value FROM viessmann_history WHERE accessory='boiler' AND event_type='snapshot' AND ts BETWEEN '1970-01-01' + INTERVAL $__unixEpochFrom() SECOND AND '1970-01-01' + INTERVAL $__unixEpochTo() SECOND AND wifi_rssi IS NOT NULL ORDER BY time"
4822
5069
  }
4823
5070
  ],
4824
5071
  "options": {
@@ -4834,12 +5081,12 @@
4834
5081
  },
4835
5082
  "fieldConfig": {
4836
5083
  "defaults": {
4837
- "unit": "none",
5084
+ "unit": "dBm",
4838
5085
  "custom": {
4839
5086
  "drawStyle": "line",
4840
5087
  "lineInterpolation": "stepAfter",
4841
5088
  "lineWidth": 2,
4842
- "fillOpacity": 0,
5089
+ "fillOpacity": 10,
4843
5090
  "showPoints": "auto",
4844
5091
  "spanNulls": 3600000,
4845
5092
  "barAlignment": 0,
@@ -4847,30 +5094,70 @@
4847
5094
  "mode": "none",
4848
5095
  "group": "A"
4849
5096
  },
4850
- "axisBorderShow": false
5097
+ "axisBorderShow": false,
5098
+ "thresholdsStyle": {
5099
+ "mode": "area"
5100
+ },
5101
+ "pointSize": 6
4851
5102
  },
4852
5103
  "color": {
4853
5104
  "mode": "palette-classic"
4854
5105
  },
4855
- "decimals": 1
5106
+ "decimals": 0,
5107
+ "min": -95,
5108
+ "max": -30,
5109
+ "noValue": "—",
5110
+ "thresholds": {
5111
+ "mode": "absolute",
5112
+ "steps": [
5113
+ {
5114
+ "color": "red",
5115
+ "value": null
5116
+ },
5117
+ {
5118
+ "color": "orange",
5119
+ "value": -80
5120
+ },
5121
+ {
5122
+ "color": "green",
5123
+ "value": -67
5124
+ }
5125
+ ]
5126
+ }
4856
5127
  },
4857
- "overrides": []
5128
+ "overrides": [
5129
+ {
5130
+ "matcher": {
5131
+ "id": "byName",
5132
+ "options": "RSSI"
5133
+ },
5134
+ "properties": [
5135
+ {
5136
+ "id": "color",
5137
+ "value": {
5138
+ "mode": "fixed",
5139
+ "fixedColor": "blue"
5140
+ }
5141
+ }
5142
+ ]
5143
+ }
5144
+ ]
4858
5145
  },
4859
- "description": "${t_curve_d}"
5146
+ "description": "${t_wifi_ts_d}"
4860
5147
  },
4861
5148
  {
4862
- "type": "timeseries",
5149
+ "type": "trend",
4863
5150
  "id": 38,
4864
- "title": "${t_wifi_ts}",
5151
+ "title": "${t_curve}",
4865
5152
  "datasource": {
4866
5153
  "type": "mysql",
4867
5154
  "uid": "${ds}"
4868
5155
  },
4869
5156
  "gridPos": {
4870
- "h": 8,
4871
- "w": 8,
4872
- "x": 16,
4873
- "y": 63
5157
+ "h": 9,
5158
+ "w": 16,
5159
+ "x": 0,
5160
+ "y": 70
4874
5161
  },
4875
5162
  "targets": [
4876
5163
  {
@@ -4881,11 +5168,13 @@
4881
5168
  },
4882
5169
  "rawQuery": true,
4883
5170
  "editorMode": "code",
4884
- "format": "time_series",
4885
- "rawSql": "SELECT TIMESTAMPDIFF(SECOND,'1970-01-01 00:00:00',ts) AS time, IF('${lang}'='en','RSSI','RSSI') AS metric, wifi_rssi AS value FROM viessmann_history WHERE accessory='boiler' AND event_type='snapshot' AND ts BETWEEN '1970-01-01' + INTERVAL $__unixEpochFrom() SECOND AND '1970-01-01' + INTERVAL $__unixEpochTo() SECOND AND wifi_rssi IS NOT NULL ORDER BY time"
5171
+ "format": "table",
5172
+ "rawSql": "WITH RECURSIVE s(x) AS (SELECT -15 UNION ALL SELECT x+1 FROM s WHERE x<25), p AS (SELECT hc_slope sl, hc_shift sh, COALESCE(hc_normal_temp, 20) rt FROM viessmann_history WHERE accessory='hc0' AND hc_slope IS NOT NULL ORDER BY ts DESC LIMIT 1), m AS (SELECT ROUND(b.outside_temp) o, ROUND(AVG(h.flow_temp),1) f, COUNT(*) n FROM viessmann_history h JOIN viessmann_history b ON b.accessory='boiler' AND b.event_type='snapshot' AND b.ts BETWEEN h.ts - INTERVAL 10 SECOND AND h.ts + INTERVAL 10 SECOND WHERE h.accessory='hc0' AND h.event_type='snapshot' AND h.mode='heating' AND b.burner_active=1 AND h.flow_temp > 0 AND h.ts BETWEEN '1970-01-01' + INTERVAL $__unixEpochFrom() SECOND AND '1970-01-01' + INTERVAL $__unixEpochTo() SECOND GROUP BY ROUND(b.outside_temp)) SELECT s.x AS outdoor, LEAST(GREATEST(ROUND(p.rt + p.sh - p.sl*(s.x-p.rt)*(1.4347 + 0.021*(s.x-p.rt) + 0.0002479*POW(s.x-p.rt,2)),1), p.rt), 80) AS `${t_curve_calc}`, m.f AS `${t_curve_meas}` FROM s JOIN p LEFT JOIN m ON m.o = s.x ORDER BY s.x"
4886
5173
  }
4887
5174
  ],
5175
+ "description": "${t_curve_d}",
4888
5176
  "options": {
5177
+ "xField": "outdoor",
4889
5178
  "legend": {
4890
5179
  "displayMode": "list",
4891
5180
  "placement": "bottom",
@@ -4898,29 +5187,137 @@
4898
5187
  },
4899
5188
  "fieldConfig": {
4900
5189
  "defaults": {
4901
- "unit": "dBm",
5190
+ "unit": "celsius",
5191
+ "decimals": 1,
5192
+ "color": {
5193
+ "mode": "palette-classic"
5194
+ },
4902
5195
  "custom": {
4903
5196
  "drawStyle": "line",
4904
- "lineInterpolation": "stepAfter",
4905
5197
  "lineWidth": 2,
4906
- "fillOpacity": 10,
4907
- "showPoints": "auto",
4908
- "spanNulls": 3600000,
4909
- "barAlignment": 0,
4910
- "stacking": {
4911
- "mode": "none",
4912
- "group": "A"
5198
+ "showPoints": "never",
5199
+ "spanNulls": true,
5200
+ "fillOpacity": 0
5201
+ }
5202
+ },
5203
+ "overrides": [
5204
+ {
5205
+ "matcher": {
5206
+ "id": "byName",
5207
+ "options": "outdoor"
4913
5208
  },
4914
- "axisBorderShow": false
5209
+ "properties": [
5210
+ {
5211
+ "id": "displayName",
5212
+ "value": "${t_outdoor}"
5213
+ }
5214
+ ]
5215
+ },
5216
+ {
5217
+ "matcher": {
5218
+ "id": "byName",
5219
+ "options": "${t_curve_calc}"
5220
+ },
5221
+ "properties": [
5222
+ {
5223
+ "id": "color",
5224
+ "value": {
5225
+ "mode": "fixed",
5226
+ "fixedColor": "orange"
5227
+ }
5228
+ }
5229
+ ]
5230
+ },
5231
+ {
5232
+ "matcher": {
5233
+ "id": "byName",
5234
+ "options": "${t_curve_meas}"
5235
+ },
5236
+ "properties": [
5237
+ {
5238
+ "id": "custom.drawStyle",
5239
+ "value": "points"
5240
+ },
5241
+ {
5242
+ "id": "custom.pointSize",
5243
+ "value": 8
5244
+ },
5245
+ {
5246
+ "id": "color",
5247
+ "value": {
5248
+ "mode": "fixed",
5249
+ "fixedColor": "blue"
5250
+ }
5251
+ }
5252
+ ]
5253
+ }
5254
+ ]
5255
+ }
5256
+ },
5257
+ {
5258
+ "type": "stat",
5259
+ "id": 39,
5260
+ "title": "${t_curve_params}",
5261
+ "datasource": {
5262
+ "type": "mysql",
5263
+ "uid": "${ds}"
5264
+ },
5265
+ "gridPos": {
5266
+ "h": 9,
5267
+ "w": 8,
5268
+ "x": 16,
5269
+ "y": 70
5270
+ },
5271
+ "targets": [
5272
+ {
5273
+ "refId": "A",
5274
+ "datasource": {
5275
+ "type": "mysql",
5276
+ "uid": "${ds}"
5277
+ },
5278
+ "rawQuery": true,
5279
+ "editorMode": "code",
5280
+ "format": "table",
5281
+ "rawSql": "SELECT UNIX_TIMESTAMP() AS time, hc_slope AS `${t_slope}`, hc_shift AS `${t_shift}`, hc_normal_temp AS `${t_normal} (°C)`, hc_reduced_temp AS `${t_reduced} (°C)`, hc_comfort_temp AS `Comfort (°C)` FROM viessmann_history WHERE accessory='hc0' AND hc_slope IS NOT NULL ORDER BY ts DESC LIMIT 1"
5282
+ }
5283
+ ],
5284
+ "options": {
5285
+ "reduceOptions": {
5286
+ "calcs": [
5287
+ "lastNotNull"
5288
+ ],
5289
+ "fields": "",
5290
+ "values": false
5291
+ },
5292
+ "colorMode": "none",
5293
+ "graphMode": "none",
5294
+ "textMode": "value_and_name",
5295
+ "justifyMode": "center",
5296
+ "orientation": "auto",
5297
+ "wideLayout": true,
5298
+ "showPercentChange": false
5299
+ },
5300
+ "fieldConfig": {
5301
+ "defaults": {
5302
+ "unit": "none",
5303
+ "mappings": [],
5304
+ "thresholds": {
5305
+ "mode": "absolute",
5306
+ "steps": [
5307
+ {
5308
+ "color": "green",
5309
+ "value": null
5310
+ }
5311
+ ]
4915
5312
  },
4916
5313
  "color": {
4917
- "mode": "palette-classic"
5314
+ "mode": "thresholds"
4918
5315
  },
4919
- "decimals": 0,
4920
- "noValue": "—"
5316
+ "decimals": 1
4921
5317
  },
4922
5318
  "overrides": []
4923
- }
5319
+ },
5320
+ "description": "${t_curve_params_d}"
4924
5321
  }
4925
5322
  ]
4926
5323
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-viessmann-vicare",
3
- "version": "2.0.77",
3
+ "version": "2.0.78",
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": {
@@ -151,6 +151,7 @@ const STRINGS = {
151
151
  // ── Comfort efficiency strings ────────────────────────────────────────
152
152
  ceTitle: '⚖️ Comfort vs Efficiency',
153
153
  ceNotEnoughData: 'Accumulating data — this analysis will be available after {need} days of monitoring ({have} of {need} collected so far).',
154
+ ceNoHeating: 'No space heating in the selected period (summer / hot water only): comfort vs efficiency cannot be evaluated.',
154
155
  ceStabilityLabel: 'Temperature stability',
155
156
  ceGasNormLabel: 'Normalised gas consumption',
156
157
  ceTrendStability: 'Comfort trend',
@@ -492,6 +493,7 @@ const STRINGS = {
492
493
  // ── Comfort efficiency strings ────────────────────────────────────────
493
494
  ceTitle: '⚖️ Comfort vs Efficienza',
494
495
  ceNotEnoughData: "Dati in accumulo — questa analisi sarà disponibile dopo {need} giorni di monitoraggio ({have} di {need} raccolti finora).",
496
+ ceNoHeating: "Nessun riscaldamento ambiente nel periodo selezionato (estate / solo acqua calda): comfort ed efficienza non sono valutabili.",
495
497
  ceStabilityLabel: 'Stabilità temperatura',
496
498
  ceGasNormLabel: 'Consumo gas normalizzato',
497
499
  ceTrendStability: 'Trend comfort',
@@ -850,7 +852,7 @@ const avgCycleDurReal = (!hoursLowRes && deltaStarts !== null && deltaHours !==
850
852
  ? (deltaHours * 60 / deltaStarts).toFixed(1)
851
853
  : null;
852
854
  const burnerRuntimePct = (!hoursLowRes && deltaHours !== null && periodHours !== null && periodHours > 0)
853
- ? (deltaHours / periodHours * 100).toFixed(0)
855
+ ? (deltaHours / periodHours * 100).toFixed(deltaHours / periodHours * 100 < 1 ? 1 : 0)
854
856
  : null;
855
857
  const realCycleCount = deltaStarts;
856
858
  const realAvgDur = avgCycleDurReal;
@@ -1344,7 +1346,7 @@ const corrPairs = hcHeatingRows
1344
1346
  const out = parseFloat(r.outside_temp) || parseFloat(
1345
1347
  (nearestRow(boilerRows, r.timestamp).diff === 0 ? nearestRow(boilerRows, r.timestamp).row.outside_temp : '') || ''
1346
1348
  );
1347
- return (isNaN(flow) || isNaN(out) || flow <= 0 || out === 0) ? null : [out, flow];
1349
+ return (isNaN(flow) || isNaN(out) || flow <= 0 || out === 0 || out >= 16) ? null : [out, flow];
1348
1350
  })
1349
1351
  .filter(Boolean);
1350
1352
 
@@ -1355,7 +1357,9 @@ const corrPairs2 = (() => {
1355
1357
  if (isNaN(flow) || flow <= 0) return null;
1356
1358
  const { row: best, diff: bd } = nearestRow(boilerRows, r.timestamp);
1357
1359
  const out = best && bd < 30*60*1000 ? parseFloat(best.outside_temp) : NaN;
1358
- return (!isNaN(out) && out !== 0) ? [out, flow] : null;
1360
+ // Heating season only: above ~16 °C outdoor the circuit is in summer/ECO
1361
+ // standby and the flow temperature follows DHW, not the heating curve.
1362
+ return (!isNaN(out) && out !== 0 && out < 16) ? [out, flow] : null;
1359
1363
  }).filter(Boolean);
1360
1364
  })();
1361
1365
 
@@ -1632,6 +1636,8 @@ const comfortEfficiency = (() => {
1632
1636
  }
1633
1637
 
1634
1638
  if (windows.length < 10) return { available: false, daysHave: dataDays, daysNeed: MIN_DAYS_COMFORT };
1639
+ // No space-heating gas in the period (summer): the comparison is meaningless
1640
+ if (!windows.some(w => w.gasNorm > 0)) return { available: false, daysHave: dataDays, daysNeed: MIN_DAYS_COMFORT, noHeating: true };
1635
1641
 
1636
1642
  // Split into first/second half for trend
1637
1643
  const half = Math.floor(windows.length / 2);
@@ -1698,7 +1704,9 @@ if (gasDays.length >= 3) {
1698
1704
  const GAS_PRICE = parseFloat(getArg('--gasPriceEur', process.env.GAS_PRICE_EUR || '0.90'));
1699
1705
  // Annual estimate requires at least 14 days to avoid misleading projections
1700
1706
  // from short atypical periods (e.g. unusually cold/warm week).
1701
- const ANNUAL_MIN_DAYS = 14;
1707
+ // A yearly figure extrapolated from a few summer weeks (DHW only) is meaningless:
1708
+ // require data covering most of a year.
1709
+ const ANNUAL_MIN_DAYS = 300;
1702
1710
  const hasEnoughForAnnual = n >= ANNUAL_MIN_DAYS;
1703
1711
  gasForecast = {
1704
1712
  avgPerDay: yMean.toFixed(2),
@@ -2162,7 +2170,7 @@ footer{text-align:center;font-size:10px;color:#bbb;padding:16px}
2162
2170
  })()}` : `
2163
2171
  <div style="padding:18px 16px;background:#f8f9fa;border-radius:8px;border:1px dashed #ddd;text-align:center;color:#888;font-size:13px">
2164
2172
  <div style="font-size:24px;margin-bottom:8px">📊</div>
2165
- <div>${T('ceNotEnoughData', {need: comfortEfficiency.daysNeed, have: comfortEfficiency.daysHave})}</div>
2173
+ <div>${comfortEfficiency.noHeating ? T('ceNoHeating') : T('ceNotEnoughData', {need: comfortEfficiency.daysNeed, have: comfortEfficiency.daysHave})}</div>
2166
2174
  </div>`}
2167
2175
  </div>
2168
2176