homebridge-viessmann-vicare 2.0.49 β 2.0.51
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 +10 -0
- package/SETUP-GUIDE.md +11 -2
- package/config.schema.json +36 -22
- package/dist/accessories/boiler-accessory.d.ts +1 -0
- package/dist/accessories/boiler-accessory.d.ts.map +1 -1
- package/dist/accessories/boiler-accessory.js +82 -9
- package/dist/accessories/history-logger.d.ts +9 -0
- package/dist/accessories/history-logger.d.ts.map +1 -1
- package/dist/accessories/history-logger.js +21 -29
- package/dist/platform.d.ts +0 -7
- package/dist/platform.d.ts.map +1 -1
- package/dist/platform.js +27 -73
- package/dist/settings.d.ts +1 -1
- package/dist/settings.js +1 -1
- package/dist/viessmann-api.d.ts +2 -2
- package/dist/viessmann-api.d.ts.map +1 -1
- package/package.json +3 -2
- package/viessmann-report-server.js +313 -0
package/README.md
CHANGED
|
@@ -1033,6 +1033,16 @@ For issues and questions:
|
|
|
1033
1033
|
|
|
1034
1034
|
## π Changelog
|
|
1035
1035
|
|
|
1036
|
+
### [2.0.51] - 2026-03-16
|
|
1037
|
+
- fix: viessmann-report-server.js missing from npm package (added to files field)
|
|
1038
|
+
|
|
1039
|
+
### [2.0.50] - 2026-03-16
|
|
1040
|
+
- feat: Report web server (viessmann-report-server.js) β configurable port, auto-detect installations, all params from UI
|
|
1041
|
+
- feat: reportServerPort + reportServerPath in plugin config and Homebridge UI
|
|
1042
|
+
- feat: CSV β 9 new columns: event_type, burner_starts/hours_today (delta), gas/heat monthly, heat production day/month
|
|
1043
|
+
- fix: Burner on/off events written to CSV immediately (not only at 15-min snapshot)
|
|
1044
|
+
- fix: Statistics read before burner state change detection β event row has accurate starts/hours
|
|
1045
|
+
|
|
1036
1046
|
### [2.0.49] - 2026-03-16
|
|
1037
1047
|
- fix: battery standby state now correctly shows 0W (not discharge)
|
|
1038
1048
|
- fix: PV daily yield unit-aware conversion (wattHour vs kilowattHour)
|
package/SETUP-GUIDE.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# Complete Setup Guide - v2.0.
|
|
1
|
+
# Complete Setup Guide - v2.0.51
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
This guide will walk you through setting up the Viessmann ViCare plugin v2.0.
|
|
5
|
+
This guide will walk you through setting up the Viessmann ViCare plugin v2.0.50 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
|
|
|
@@ -1228,6 +1228,15 @@ sudo systemctl restart homebridge
|
|
|
1228
1228
|
|
|
1229
1229
|
## Changelog
|
|
1230
1230
|
|
|
1231
|
+
### v2.0.51 (2026-03-16)
|
|
1232
|
+
- fix: viessmann-report-server.js missing from npm package
|
|
1233
|
+
|
|
1234
|
+
### v2.0.50 (2026-03-16)
|
|
1235
|
+
- feat: Report web server on configurable port; all params from browser UI
|
|
1236
|
+
- feat: CSV 9 new columns β burner event rows, heat production, monthly gas, daily deltas
|
|
1237
|
+
- fix: Burner state changes logged immediately to CSV with event_type=burner_on/off
|
|
1238
|
+
- fix: Statistics read order corrected β event rows have accurate burner_starts/hours
|
|
1239
|
+
|
|
1231
1240
|
### v2.0.49 (2026-03-16)
|
|
1232
1241
|
- fix: battery standby=0W; PV daily unit-aware; COP comment
|
|
1233
1242
|
|
package/config.schema.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"pluginAlias": "ViessmannPlatform",
|
|
3
3
|
"pluginType": "platform",
|
|
4
4
|
"singular": true,
|
|
5
|
-
"headerDisplay": "
|
|
6
|
-
"footerDisplay": "
|
|
5
|
+
"headerDisplay": "\ud83d\udd25 Homebridge plugin for Viessmann heating systems with full control capabilities including boiler, DHW, and heating circuits. Features advanced rate limiting protection, intelligent cache management, and automatic retry logic with persistent token storage. \u26a0\ufe0f CRITICAL: Authorization codes expire in 20 seconds during OAuth setup!",
|
|
6
|
+
"footerDisplay": "\u26a0\ufe0f Important: Refresh tokens expire after 180 days. For help and support visit: [\ud83d\udcd6 Setup Guide](https://github.com/diegoweb100/homebridge-viessmann-vicare/blob/main/SETUP-GUIDE.md) | [\ud83d\udd27 Troubleshooting](https://github.com/diegoweb100/homebridge-viessmann-vicare#-troubleshooting) | [\ud83d\udcde Support](https://github.com/diegoweb100/homebridge-viessmann-vicare/issues)",
|
|
7
7
|
"schema": {
|
|
8
8
|
"type": "object",
|
|
9
9
|
"properties": {
|
|
@@ -163,14 +163,28 @@
|
|
|
163
163
|
"default": false,
|
|
164
164
|
"description": "Enable verbose debug logging for troubleshooting. Warning: May expose sensitive information in logs."
|
|
165
165
|
},
|
|
166
|
+
"reportServerPort": {
|
|
167
|
+
"title": "Report Server Port",
|
|
168
|
+
"type": "integer",
|
|
169
|
+
"default": 0,
|
|
170
|
+
"minimum": 0,
|
|
171
|
+
"maximum": 65535,
|
|
172
|
+
"description": "Port for the Viessmann history report web server (0 = disabled). E.g. 3001"
|
|
173
|
+
},
|
|
174
|
+
"reportServerPath": {
|
|
175
|
+
"title": "Report Server Data Path",
|
|
176
|
+
"type": "string",
|
|
177
|
+
"default": "/var/lib/homebridge",
|
|
178
|
+
"description": "Path to Homebridge data directory containing viessmann-history-*.csv files"
|
|
179
|
+
},
|
|
166
180
|
"forceServiceRecreation": {
|
|
167
181
|
"title": "Force Service Recreation",
|
|
168
182
|
"type": "boolean",
|
|
169
183
|
"default": false,
|
|
170
|
-
"description": "
|
|
184
|
+
"description": "\ud83d\udd27 DEVELOPMENT: Force recreation of HomeKit services with new subtypes to refresh names immediately. Enable temporarily if custom names don't appear, then disable after restart."
|
|
171
185
|
},
|
|
172
186
|
"customNames": {
|
|
173
|
-
"title": "
|
|
187
|
+
"title": "\ud83c\udfaf Custom Names & Localization",
|
|
174
188
|
"type": "object",
|
|
175
189
|
"description": "Customize accessory names for localization or personal preference. Perfect for non-English users or custom naming schemes.",
|
|
176
190
|
"properties": {
|
|
@@ -261,7 +275,7 @@
|
|
|
261
275
|
}
|
|
262
276
|
},
|
|
263
277
|
"cache": {
|
|
264
|
-
"title": "
|
|
278
|
+
"title": "\ud83d\uddc4\ufe0f Enhanced API Cache Configuration",
|
|
265
279
|
"type": "object",
|
|
266
280
|
"properties": {
|
|
267
281
|
"enabled": {
|
|
@@ -337,7 +351,7 @@
|
|
|
337
351
|
}
|
|
338
352
|
},
|
|
339
353
|
"advanced": {
|
|
340
|
-
"title": "
|
|
354
|
+
"title": "\ud83d\udd27 Advanced Settings",
|
|
341
355
|
"type": "object",
|
|
342
356
|
"properties": {
|
|
343
357
|
"baseDelay": {
|
|
@@ -381,7 +395,7 @@
|
|
|
381
395
|
}
|
|
382
396
|
},
|
|
383
397
|
"features": {
|
|
384
|
-
"title": "
|
|
398
|
+
"title": "\ud83c\udf9b\ufe0f Feature Control",
|
|
385
399
|
"type": "object",
|
|
386
400
|
"properties": {
|
|
387
401
|
"enableBoilerAccessories": {
|
|
@@ -423,7 +437,7 @@
|
|
|
423
437
|
}
|
|
424
438
|
},
|
|
425
439
|
"network": {
|
|
426
|
-
"title": "
|
|
440
|
+
"title": "\ud83c\udf10 Network Settings",
|
|
427
441
|
"type": "object",
|
|
428
442
|
"properties": {
|
|
429
443
|
"enableIPv6": {
|
|
@@ -461,8 +475,8 @@
|
|
|
461
475
|
"layout": [
|
|
462
476
|
{
|
|
463
477
|
"type": "fieldset",
|
|
464
|
-
"title": "
|
|
465
|
-
"description": "Essential settings to connect to your Viessmann system.
|
|
478
|
+
"title": "\ud83d\udd10 Basic Configuration",
|
|
479
|
+
"description": "Essential settings to connect to your Viessmann system. \u26a0\ufe0f Remember: Authorization codes expire in 20 seconds!",
|
|
466
480
|
"expandable": true,
|
|
467
481
|
"expanded": true,
|
|
468
482
|
"items": [
|
|
@@ -475,7 +489,7 @@
|
|
|
475
489
|
},
|
|
476
490
|
{
|
|
477
491
|
"type": "fieldset",
|
|
478
|
-
"title": "
|
|
492
|
+
"title": "\ud83d\udd11 Authentication Method",
|
|
479
493
|
"description": "Choose how to authenticate with Viessmann API. Automatic OAuth is recommended for desktop environments, manual for headless/Docker setups.",
|
|
480
494
|
"expandable": true,
|
|
481
495
|
"expanded": true,
|
|
@@ -488,7 +502,7 @@
|
|
|
488
502
|
},
|
|
489
503
|
{
|
|
490
504
|
"type": "fieldset",
|
|
491
|
-
"title": "
|
|
505
|
+
"title": "\u2699\ufe0f Manual Authentication (Advanced)",
|
|
492
506
|
"description": "Only required if authMethod is 'manual'. For headless/server environments where browser OAuth isn't possible.",
|
|
493
507
|
"expandable": true,
|
|
494
508
|
"expanded": false,
|
|
@@ -499,7 +513,7 @@
|
|
|
499
513
|
},
|
|
500
514
|
{
|
|
501
515
|
"type": "fieldset",
|
|
502
|
-
"title": "
|
|
516
|
+
"title": "\ud83d\udd12 Token Management",
|
|
503
517
|
"description": "Configure token refresh and persistence. Refresh tokens are valid for 180 days.",
|
|
504
518
|
"expandable": true,
|
|
505
519
|
"expanded": false,
|
|
@@ -510,7 +524,7 @@
|
|
|
510
524
|
},
|
|
511
525
|
{
|
|
512
526
|
"type": "fieldset",
|
|
513
|
-
"title": "
|
|
527
|
+
"title": "\ud83c\udfaf Installation Filtering",
|
|
514
528
|
"description": "Filter which installations to show in HomeKit to reduce API calls and improve performance. Can reduce API usage by up to 50%.",
|
|
515
529
|
"expandable": true,
|
|
516
530
|
"expanded": false,
|
|
@@ -521,8 +535,8 @@
|
|
|
521
535
|
},
|
|
522
536
|
{
|
|
523
537
|
"type": "fieldset",
|
|
524
|
-
"title": "
|
|
525
|
-
"description": "
|
|
538
|
+
"title": "\ud83c\udfaf Custom Names & Localization",
|
|
539
|
+
"description": "\ud83c\udf0d Customize accessory names for different languages or personal preferences. Perfect for Italian, German, French, Spanish users or custom naming schemes. Changes require Homebridge restart.",
|
|
526
540
|
"expandable": true,
|
|
527
541
|
"expanded": false,
|
|
528
542
|
"items": [
|
|
@@ -545,7 +559,7 @@
|
|
|
545
559
|
},
|
|
546
560
|
{
|
|
547
561
|
"type": "fieldset",
|
|
548
|
-
"title": "
|
|
562
|
+
"title": "\u26a1 Performance & Rate Limiting",
|
|
549
563
|
"description": "Configure timing and rate limiting protection to prevent API blocking. Essential for maintaining reliable operation.",
|
|
550
564
|
"expandable": true,
|
|
551
565
|
"expanded": false,
|
|
@@ -560,7 +574,7 @@
|
|
|
560
574
|
},
|
|
561
575
|
{
|
|
562
576
|
"type": "fieldset",
|
|
563
|
-
"title": "
|
|
577
|
+
"title": "\ud83d\udcbe Enhanced API Caching",
|
|
564
578
|
"description": "Configure intelligent API response caching to reduce API calls by 70-90% and improve responsiveness. Includes compression and smart prefetch.",
|
|
565
579
|
"expandable": true,
|
|
566
580
|
"expanded": false,
|
|
@@ -579,7 +593,7 @@
|
|
|
579
593
|
},
|
|
580
594
|
{
|
|
581
595
|
"type": "fieldset",
|
|
582
|
-
"title": "
|
|
596
|
+
"title": "\ud83c\udf9b\ufe0f Feature Control",
|
|
583
597
|
"description": "Enable or disable specific accessory types to customize your HomeKit experience and reduce API overhead.",
|
|
584
598
|
"expandable": true,
|
|
585
599
|
"expanded": false,
|
|
@@ -594,7 +608,7 @@
|
|
|
594
608
|
},
|
|
595
609
|
{
|
|
596
610
|
"type": "fieldset",
|
|
597
|
-
"title": "
|
|
611
|
+
"title": "\ud83d\udd27 Advanced Settings",
|
|
598
612
|
"description": "Advanced configuration options for fine-tuning performance, behavior, and compatibility.",
|
|
599
613
|
"expandable": true,
|
|
600
614
|
"expanded": false,
|
|
@@ -608,7 +622,7 @@
|
|
|
608
622
|
},
|
|
609
623
|
{
|
|
610
624
|
"type": "fieldset",
|
|
611
|
-
"title": "
|
|
625
|
+
"title": "\ud83c\udf10 Network Settings",
|
|
612
626
|
"description": "Configure network-specific options for special environments or connectivity requirements.",
|
|
613
627
|
"expandable": true,
|
|
614
628
|
"expanded": false,
|
|
@@ -621,7 +635,7 @@
|
|
|
621
635
|
},
|
|
622
636
|
{
|
|
623
637
|
"type": "fieldset",
|
|
624
|
-
"title": "
|
|
638
|
+
"title": "\ud83d\udcca Monitoring & Debug",
|
|
625
639
|
"description": "Enable monitoring and debugging features. Disable debug logging for production use to improve performance.",
|
|
626
640
|
"expandable": true,
|
|
627
641
|
"expanded": false,
|
|
@@ -25,6 +25,7 @@ export declare class ViessmannBoilerAccessory {
|
|
|
25
25
|
private pendingTempUntil;
|
|
26
26
|
private pendingExpectedTemp;
|
|
27
27
|
private pendingPreviousTemp;
|
|
28
|
+
private dailyRef;
|
|
28
29
|
private states;
|
|
29
30
|
constructor(platform: ViessmannPlatform, accessory: PlatformAccessory, installation: ViessmannInstallation, gateway: ViessmannGateway, device: ViessmannDevice);
|
|
30
31
|
private initializeCapabilities;
|
|
@@ -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;AAE7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAEnI,qBAAa,wBAAwB;
|
|
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;AAE7E,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAA2B,MAAM,aAAa,CAAC;AAEnI,qBAAa,wBAAwB;IA+DjC,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;IAlEzB,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,aAAa,CAAC,CAAyB;IAC/C,OAAO,CAAC,sBAAsB,CAAwB;IACtD,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,iBAAiB,CAAK;IAG9B,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,mBAAmB,CAAiC;IAC5D,OAAO,CAAC,mBAAmB,CAAiC;IAG5D,OAAO,CAAC,QAAQ,CAId;IAEF,OAAO,CAAC,MAAM,CA2BZ;gBAGiB,QAAQ,EAAE,iBAAiB,EAC3B,SAAS,EAAE,iBAAiB,EAC5B,YAAY,EAAE,qBAAqB,EACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,eAAe;YA0B5B,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;IA8C/D,OAAO,CAAC,2BAA2B;YAiDrB,YAAY;YAYZ,kBAAkB;IAgazB,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"}
|
|
@@ -29,6 +29,12 @@ class ViessmannBoilerAccessory {
|
|
|
29
29
|
pendingTempUntil = 0;
|
|
30
30
|
pendingExpectedTemp = undefined;
|
|
31
31
|
pendingPreviousTemp = undefined;
|
|
32
|
+
// Daily delta tracking for burner starts/hours β reference values reset at midnight
|
|
33
|
+
dailyRef = {
|
|
34
|
+
date: '', // 'YYYY-MM-DD' of the reference β reset when date changes
|
|
35
|
+
startsRef: 0, // BurnerStarts value at midnight
|
|
36
|
+
hoursRef: 0, // BurnerHours value at midnight
|
|
37
|
+
};
|
|
32
38
|
states = {
|
|
33
39
|
CurrentTemperature: 20,
|
|
34
40
|
HeatingThresholdTemperature: 20,
|
|
@@ -44,6 +50,11 @@ class ViessmannBoilerAccessory {
|
|
|
44
50
|
GasConsumptionThisMonth: 0,
|
|
45
51
|
GasConsumptionThisYear: 0,
|
|
46
52
|
GasConsumptionDhwToday: 0, // heating.gas.consumption.summary.dhw.currentDay
|
|
53
|
+
GasConsumptionDhwThisMonth: 0,
|
|
54
|
+
HeatProductionHeatingToday: 0, // heating.heat.production.summary.heating.currentDay (kWh)
|
|
55
|
+
HeatProductionHeatingThisMonth: 0,
|
|
56
|
+
HeatProductionDhwToday: 0, // heating.heat.production.summary.dhw.currentDay (kWh)
|
|
57
|
+
HeatProductionDhwThisMonth: 0,
|
|
47
58
|
PowerConsumptionToday: 0,
|
|
48
59
|
PowerConsumptionThisMonth: 0,
|
|
49
60
|
PowerConsumptionThisYear: 0,
|
|
@@ -665,6 +676,14 @@ class ViessmannBoilerAccessory {
|
|
|
665
676
|
this.heaterCoolerService.updateCharacteristic(this.platform.Characteristic.HeatingThresholdTemperature, targetTemp);
|
|
666
677
|
}
|
|
667
678
|
}
|
|
679
|
+
// Update burner statistics FIRST β so the event row written on state change has current values
|
|
680
|
+
const statisticsFeature = features.find(f => f.feature === 'heating.burners.0.statistics');
|
|
681
|
+
if (statisticsFeature?.properties?.hours?.value !== undefined) {
|
|
682
|
+
this.states.BurnerHours = statisticsFeature.properties.hours.value;
|
|
683
|
+
}
|
|
684
|
+
if (statisticsFeature?.properties?.starts?.value !== undefined) {
|
|
685
|
+
this.states.BurnerStarts = statisticsFeature.properties.starts.value;
|
|
686
|
+
}
|
|
668
687
|
// Update burner status
|
|
669
688
|
const burnerFeature = features.find(f => f.feature === 'heating.burners.0');
|
|
670
689
|
if (burnerFeature?.properties?.active?.value !== undefined) {
|
|
@@ -688,6 +707,24 @@ class ViessmannBoilerAccessory {
|
|
|
688
707
|
this.platform.log.debug(`Burner activity: ${newBurnerState ? 'OPEN (Active)' : 'CLOSED (Inactive)'}`);
|
|
689
708
|
}
|
|
690
709
|
this.platform.log.debug(`Boiler burner ${newBurnerState ? 'activated' : 'deactivated'}`);
|
|
710
|
+
// π Write immediate event row to CSV β captures on/off transitions
|
|
711
|
+
// that would otherwise be invisible at 15-min snapshot granularity
|
|
712
|
+
if (this.historyLogger) {
|
|
713
|
+
const startsToday = this.states.BurnerStarts - this.dailyRef.startsRef;
|
|
714
|
+
const hoursToday = this.states.BurnerHours - this.dailyRef.hoursRef;
|
|
715
|
+
this.historyLogger.appendCsvRow({
|
|
716
|
+
timestamp: new Date().toISOString(),
|
|
717
|
+
accessory: 'boiler',
|
|
718
|
+
event_type: newBurnerState ? 'burner_on' : 'burner_off',
|
|
719
|
+
burner_active: newBurnerState,
|
|
720
|
+
modulation: this.states.Modulation,
|
|
721
|
+
outside_temp: this.states.OutsideTemperature || undefined,
|
|
722
|
+
burner_starts: this.states.BurnerStarts,
|
|
723
|
+
burner_hours: this.states.BurnerHours,
|
|
724
|
+
burner_starts_today: startsToday >= 0 ? startsToday : undefined,
|
|
725
|
+
burner_hours_today: hoursToday >= 0 ? hoursToday : undefined,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
691
728
|
}
|
|
692
729
|
}
|
|
693
730
|
// Update modulation
|
|
@@ -704,14 +741,6 @@ class ViessmannBoilerAccessory {
|
|
|
704
741
|
this.platform.log.debug(`Boiler modulation: ${newModulation}%`);
|
|
705
742
|
}
|
|
706
743
|
}
|
|
707
|
-
// Update burner statistics
|
|
708
|
-
const statisticsFeature = features.find(f => f.feature === 'heating.burners.0.statistics');
|
|
709
|
-
if (statisticsFeature?.properties?.hours?.value !== undefined) {
|
|
710
|
-
this.states.BurnerHours = statisticsFeature.properties.hours.value;
|
|
711
|
-
}
|
|
712
|
-
if (statisticsFeature?.properties?.starts?.value !== undefined) {
|
|
713
|
-
this.states.BurnerStarts = statisticsFeature.properties.starts.value;
|
|
714
|
-
}
|
|
715
744
|
// π NEW: Update diagnostic information
|
|
716
745
|
// Update outside temperature
|
|
717
746
|
const outsideTempFeature = features.find(f => f.feature === 'heating.sensors.temperature.outside');
|
|
@@ -786,6 +815,39 @@ class ViessmannBoilerAccessory {
|
|
|
786
815
|
this.powerConsumptionService.updateCharacteristic(this.platform.Characteristic.MotionDetected, hasActiveConsumption);
|
|
787
816
|
this.platform.log.debug(`Power consumption ${hasActiveConsumption ? 'DETECTED' : 'IDLE'}: ${this.states.PowerConsumptionToday} kWh`);
|
|
788
817
|
}
|
|
818
|
+
// Update heat production (heating) β available on Vitodens gen3 and heat pumps
|
|
819
|
+
const heatHeatingFeature = features.find(f => f.feature === 'heating.heat.production.summary.heating');
|
|
820
|
+
if (heatHeatingFeature?.properties) {
|
|
821
|
+
if (heatHeatingFeature.properties.currentDay?.value !== undefined) {
|
|
822
|
+
this.states.HeatProductionHeatingToday = heatHeatingFeature.properties.currentDay.value;
|
|
823
|
+
}
|
|
824
|
+
if (heatHeatingFeature.properties.currentMonth?.value !== undefined) {
|
|
825
|
+
this.states.HeatProductionHeatingThisMonth = heatHeatingFeature.properties.currentMonth.value;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Update heat production (DHW)
|
|
829
|
+
const heatDhwFeature = features.find(f => f.feature === 'heating.heat.production.summary.dhw');
|
|
830
|
+
if (heatDhwFeature?.properties) {
|
|
831
|
+
if (heatDhwFeature.properties.currentDay?.value !== undefined) {
|
|
832
|
+
this.states.HeatProductionDhwToday = heatDhwFeature.properties.currentDay.value;
|
|
833
|
+
}
|
|
834
|
+
if (heatDhwFeature.properties.currentMonth?.value !== undefined) {
|
|
835
|
+
this.states.HeatProductionDhwThisMonth = heatDhwFeature.properties.currentMonth.value;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
// Update DHW gas consumption monthly figure
|
|
839
|
+
if (gasDhwFeature?.properties?.currentMonth?.value !== undefined) {
|
|
840
|
+
this.states.GasConsumptionDhwThisMonth = gasDhwFeature.properties.currentMonth.value;
|
|
841
|
+
}
|
|
842
|
+
// Update daily delta for burner starts/hours (reset reference at midnight)
|
|
843
|
+
const todayStr = new Date().toISOString().slice(0, 10);
|
|
844
|
+
if (this.dailyRef.date !== todayStr) {
|
|
845
|
+
// New day β set reference to current cumulative values
|
|
846
|
+
this.dailyRef.date = todayStr;
|
|
847
|
+
this.dailyRef.startsRef = this.states.BurnerStarts;
|
|
848
|
+
this.dailyRef.hoursRef = this.states.BurnerHours;
|
|
849
|
+
this.platform.log.debug(`Boiler daily reference reset: starts=${this.dailyRef.startsRef} hours=${this.dailyRef.hoursRef}`);
|
|
850
|
+
}
|
|
789
851
|
// Update water pressure
|
|
790
852
|
const waterPressureFeature = features.find(f => f.feature === 'heating.sensors.pressure.supply' ||
|
|
791
853
|
f.feature === 'heating.boiler.sensors.pressure.supply' ||
|
|
@@ -898,20 +960,31 @@ class ViessmannBoilerAccessory {
|
|
|
898
960
|
else {
|
|
899
961
|
this.platform.log.debug(statusLine);
|
|
900
962
|
}
|
|
901
|
-
// π History logging β FakeGato energy + CSV
|
|
963
|
+
// π History logging β FakeGato energy + CSV snapshot
|
|
902
964
|
if (this.historyLogger) {
|
|
903
965
|
this.historyLogger.addEnergyEntry({ power: this.states.Modulation });
|
|
966
|
+
const startsToday = this.states.BurnerStarts - this.dailyRef.startsRef;
|
|
967
|
+
const hoursToday = this.states.BurnerHours - this.dailyRef.hoursRef;
|
|
904
968
|
this.historyLogger.appendCsvRow({
|
|
905
969
|
timestamp: new Date().toISOString(),
|
|
906
970
|
accessory: 'boiler',
|
|
971
|
+
event_type: 'snapshot',
|
|
907
972
|
burner_active: this.states.BurnerActive,
|
|
908
973
|
modulation: this.states.Modulation,
|
|
909
974
|
outside_temp: this.states.OutsideTemperature || undefined,
|
|
910
975
|
outside_humidity: this.states.OutsideHumidity,
|
|
911
976
|
burner_starts: this.states.BurnerStarts,
|
|
912
977
|
burner_hours: this.states.BurnerHours,
|
|
978
|
+
burner_starts_today: startsToday >= 0 ? startsToday : undefined,
|
|
979
|
+
burner_hours_today: hoursToday >= 0 ? hoursToday : undefined,
|
|
913
980
|
gas_heating_day_m3: this.states.GasConsumptionToday || undefined,
|
|
914
981
|
gas_dhw_day_m3: this.states.GasConsumptionDhwToday || undefined,
|
|
982
|
+
gas_heating_month_m3: this.states.GasConsumptionThisMonth || undefined,
|
|
983
|
+
gas_dhw_month_m3: this.states.GasConsumptionDhwThisMonth || undefined,
|
|
984
|
+
heat_heating_day_kwh: this.states.HeatProductionHeatingToday || undefined,
|
|
985
|
+
heat_dhw_day_kwh: this.states.HeatProductionDhwToday || undefined,
|
|
986
|
+
heat_heating_month_kwh: this.states.HeatProductionHeatingThisMonth || undefined,
|
|
987
|
+
heat_dhw_month_kwh: this.states.HeatProductionDhwThisMonth || undefined,
|
|
915
988
|
});
|
|
916
989
|
}
|
|
917
990
|
}
|
|
@@ -27,6 +27,7 @@ export interface EnergyEntry {
|
|
|
27
27
|
export interface CsvRow {
|
|
28
28
|
timestamp: string;
|
|
29
29
|
accessory: string;
|
|
30
|
+
event_type?: string;
|
|
30
31
|
burner_active?: boolean;
|
|
31
32
|
modulation?: number;
|
|
32
33
|
room_temp?: number;
|
|
@@ -39,9 +40,17 @@ export interface CsvRow {
|
|
|
39
40
|
mode?: string;
|
|
40
41
|
burner_starts?: number;
|
|
41
42
|
burner_hours?: number;
|
|
43
|
+
burner_starts_today?: number;
|
|
44
|
+
burner_hours_today?: number;
|
|
42
45
|
flow_temp?: number;
|
|
43
46
|
gas_heating_day_m3?: number;
|
|
44
47
|
gas_dhw_day_m3?: number;
|
|
48
|
+
gas_heating_month_m3?: number;
|
|
49
|
+
gas_dhw_month_m3?: number;
|
|
50
|
+
heat_heating_day_kwh?: number;
|
|
51
|
+
heat_dhw_day_kwh?: number;
|
|
52
|
+
heat_heating_month_kwh?: number;
|
|
53
|
+
heat_dhw_month_kwh?: number;
|
|
45
54
|
pv_production_w?: number;
|
|
46
55
|
pv_daily_kwh?: number;
|
|
47
56
|
battery_level?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"history-logger.d.ts","sourceRoot":"","sources":["../../src/accessories/history-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"history-logger.d.ts","sourceRoot":"","sources":["../../src/accessories/history-logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9C,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAqBD,qBAAa,sBAAsB;IAO/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAR9B,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;gBAGL,QAAQ,EAAE,GAAG,EACb,SAAS,EAAE,GAAG,EACd,WAAW,EAAE,WAAW,EACzC,OAAO,EAAE,MAAM,EACf,cAAc,CAAC,EAAE,MAAM;IASzB,OAAO,CAAC,YAAY;IAgBpB;;OAEG;IACI,cAAc,CAAC,KAAK,EAAE,WAAW;IAgBxC;;OAEG;IACI,cAAc,CAAC,KAAK,EAAE,WAAW;IAWxC;;OAEG;IACI,YAAY,CAAC,GAAG,EAAE,MAAM;CAehC"}
|
|
@@ -53,7 +53,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
53
53
|
exports.ViessmannHistoryLogger = void 0;
|
|
54
54
|
const fs = __importStar(require("fs"));
|
|
55
55
|
const path = __importStar(require("path"));
|
|
56
|
-
const
|
|
56
|
+
const CSV_COLUMNS = [
|
|
57
|
+
'timestamp', 'accessory', 'event_type',
|
|
58
|
+
'burner_active', 'modulation',
|
|
59
|
+
'room_temp', 'target_temp', 'outside_temp', 'outside_humidity',
|
|
60
|
+
'dhw_temp', 'dhw_target', 'program', 'mode',
|
|
61
|
+
'burner_starts', 'burner_hours', 'burner_starts_today', 'burner_hours_today',
|
|
62
|
+
'flow_temp',
|
|
63
|
+
'gas_heating_day_m3', 'gas_dhw_day_m3',
|
|
64
|
+
'gas_heating_month_m3', 'gas_dhw_month_m3',
|
|
65
|
+
'heat_heating_day_kwh', 'heat_dhw_day_kwh',
|
|
66
|
+
'heat_heating_month_kwh', 'heat_dhw_month_kwh',
|
|
67
|
+
'pv_production_w', 'pv_daily_kwh',
|
|
68
|
+
'battery_level', 'battery_charging_w', 'battery_discharging_w',
|
|
69
|
+
'grid_feedin_w', 'grid_draw_w',
|
|
70
|
+
'wallbox_charging', 'wallbox_power_w',
|
|
71
|
+
];
|
|
72
|
+
const CSV_HEADER = CSV_COLUMNS.join(',') + '\n';
|
|
57
73
|
class ViessmannHistoryLogger {
|
|
58
74
|
platform;
|
|
59
75
|
accessory;
|
|
@@ -126,34 +142,10 @@ class ViessmannHistoryLogger {
|
|
|
126
142
|
if (!exists) {
|
|
127
143
|
fs.writeFileSync(this.csvPath, CSV_HEADER, 'utf8');
|
|
128
144
|
}
|
|
129
|
-
const line =
|
|
130
|
-
row
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
row.modulation ?? '',
|
|
134
|
-
row.room_temp ?? '',
|
|
135
|
-
row.target_temp ?? '',
|
|
136
|
-
row.outside_temp ?? '',
|
|
137
|
-
row.outside_humidity ?? '',
|
|
138
|
-
row.dhw_temp ?? '',
|
|
139
|
-
row.dhw_target ?? '',
|
|
140
|
-
row.program ?? '',
|
|
141
|
-
row.mode ?? '',
|
|
142
|
-
row.burner_starts ?? '',
|
|
143
|
-
row.burner_hours ?? '',
|
|
144
|
-
row.flow_temp ?? '',
|
|
145
|
-
row.gas_heating_day_m3 ?? '',
|
|
146
|
-
row.gas_dhw_day_m3 ?? '',
|
|
147
|
-
row.pv_production_w ?? '',
|
|
148
|
-
row.pv_daily_kwh ?? '',
|
|
149
|
-
row.battery_level ?? '',
|
|
150
|
-
row.battery_charging_w ?? '',
|
|
151
|
-
row.battery_discharging_w ?? '',
|
|
152
|
-
row.grid_feedin_w ?? '',
|
|
153
|
-
row.grid_draw_w ?? '',
|
|
154
|
-
row.wallbox_charging ?? '',
|
|
155
|
-
row.wallbox_power_w ?? '',
|
|
156
|
-
].join(',') + '\n';
|
|
145
|
+
const line = CSV_COLUMNS.map(col => {
|
|
146
|
+
const val = row[col];
|
|
147
|
+
return val === undefined || val === null ? '' : val;
|
|
148
|
+
}).join(',') + '\n';
|
|
157
149
|
fs.appendFileSync(this.csvPath, line, 'utf8');
|
|
158
150
|
}
|
|
159
151
|
catch (e) {
|
package/dist/platform.d.ts
CHANGED
|
@@ -55,13 +55,6 @@ export declare class ViessmannPlatform implements DynamicPlatformPlugin {
|
|
|
55
55
|
setupHeatingCircuitAccessories(installation: ViessmannInstallation, gateway: ViessmannGateway, device: ViessmannDevice, features: ViessmannFeature[]): Promise<void>;
|
|
56
56
|
setupEnergyAccessory(installation: ViessmannInstallation, gateway: ViessmannGateway, device: ViessmannDevice, features: ViessmannFeature[]): Promise<void>;
|
|
57
57
|
updateAllDevices(): Promise<void>;
|
|
58
|
-
/**
|
|
59
|
-
* Extracts device.messages.status/info/service entries from features and
|
|
60
|
-
* writes them to viessmann-messages-<installationId>-<deviceId>.json for viessmann-report.js.
|
|
61
|
-
* One file per device β multiple devices per installation each get their own file.
|
|
62
|
-
* Called once per unique device per update cycle (deduplicated via deviceFeatureCache).
|
|
63
|
-
*/
|
|
64
|
-
private writeDeviceMessages;
|
|
65
58
|
private updateCycleComplete;
|
|
66
59
|
private adjustRefreshInterval;
|
|
67
60
|
private sleep;
|
package/dist/platform.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,GAAG,EAGH,qBAAqB,EACrB,MAAM,EACN,iBAAiB,EACjB,cAAc,EACd,OAAO,EACP,cAAc,EACf,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
package/dist/platform.js
CHANGED
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ViessmannPlatform = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
38
|
const viessmann_api_1 = require("./viessmann-api");
|
|
40
39
|
const boiler_accessory_1 = require("./accessories/boiler-accessory");
|
|
41
40
|
const dhw_accessory_1 = require("./accessories/dhw-accessory");
|
|
@@ -98,6 +97,30 @@ class ViessmannPlatform {
|
|
|
98
97
|
this.discoverDevices();
|
|
99
98
|
// Initialize health monitoring
|
|
100
99
|
this.startHealthMonitoring();
|
|
100
|
+
// Start report web server if configured
|
|
101
|
+
const reportPort = this.config.reportServerPort ?? 0;
|
|
102
|
+
if (reportPort > 0) {
|
|
103
|
+
try {
|
|
104
|
+
const reportServerPath = this.config.reportServerPath
|
|
105
|
+
?? this.api.user.storagePath();
|
|
106
|
+
const reportScript = require('path').join(__dirname, '..', 'viessmann-report-server.js');
|
|
107
|
+
const args = ['--port', String(reportPort), '--path', reportServerPath];
|
|
108
|
+
const { execFile } = require('child_process');
|
|
109
|
+
// Run as detached child so it survives plugin reload
|
|
110
|
+
const child = execFile(process.execPath, [reportScript, ...args], {
|
|
111
|
+
detached: false,
|
|
112
|
+
stdio: 'inherit',
|
|
113
|
+
});
|
|
114
|
+
child.on('error', (err) => {
|
|
115
|
+
this.log.warn(`[Viessmann] Report server failed to start: ${err.message}`);
|
|
116
|
+
});
|
|
117
|
+
this.log.info(`[Viessmann] Report server started on http://0.0.0.0:${reportPort}`);
|
|
118
|
+
this.log.info(`[Viessmann] Open in browser: http://localhost:${reportPort}`);
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
this.log.warn(`[Viessmann] Could not start report server: ${e.message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
101
124
|
});
|
|
102
125
|
this.api.on("shutdown" /* APIEvent.SHUTDOWN */, () => {
|
|
103
126
|
if (this.refreshTimer) {
|
|
@@ -506,21 +529,14 @@ class ViessmannPlatform {
|
|
|
506
529
|
await this.setupHeatingCircuitAccessories(installation, gateway, device, features);
|
|
507
530
|
// Setup Energy / Heat Pump accessory (PV, battery, wallbox, electric DHW, WΓ€rmepumpe)
|
|
508
531
|
await this.setupEnergyAccessory(installation, gateway, device, features);
|
|
509
|
-
// Write initial device messages on setup β so viessmann-report.js has data
|
|
510
|
-
// even before the first updateAllDevices cycle runs.
|
|
511
|
-
this.writeDeviceMessages(features, installation.id, device.id);
|
|
512
532
|
}
|
|
513
533
|
catch (error) {
|
|
514
534
|
this.log.error(`Failed to setup accessories for device ${device.id}:`, error);
|
|
515
535
|
}
|
|
516
536
|
}
|
|
517
537
|
async setupBoilerAccessory(installation, gateway, device, features) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const boilerFeatures = features.filter(f => f.feature.startsWith('heating.burners') ||
|
|
521
|
-
f.feature === 'heating.boiler.temperature.current' ||
|
|
522
|
-
f.feature === 'heating.boiler.sensors.temperature.commonSupply' ||
|
|
523
|
-
f.feature === 'heating.boiler.pumps.internal');
|
|
538
|
+
const boilerFeatures = features.filter(f => f.feature.includes('heating.boiler') ||
|
|
539
|
+
f.feature.includes('heating.burners'));
|
|
524
540
|
if (boilerFeatures.length === 0) {
|
|
525
541
|
return;
|
|
526
542
|
}
|
|
@@ -531,9 +547,6 @@ class ViessmannPlatform {
|
|
|
531
547
|
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
|
|
532
548
|
if (existingAccessory) {
|
|
533
549
|
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
534
|
-
existingAccessory.context.device = device;
|
|
535
|
-
existingAccessory.context.installation = installation;
|
|
536
|
-
existingAccessory.context.gateway = gateway;
|
|
537
550
|
new boiler_accessory_1.ViessmannBoilerAccessory(this, existingAccessory, installation, gateway, device);
|
|
538
551
|
}
|
|
539
552
|
else {
|
|
@@ -559,9 +572,6 @@ class ViessmannPlatform {
|
|
|
559
572
|
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
|
|
560
573
|
if (existingAccessory) {
|
|
561
574
|
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
562
|
-
existingAccessory.context.device = device;
|
|
563
|
-
existingAccessory.context.installation = installation;
|
|
564
|
-
existingAccessory.context.gateway = gateway;
|
|
565
575
|
new dhw_accessory_1.ViessmannDHWAccessory(this, existingAccessory, installation, gateway, device);
|
|
566
576
|
}
|
|
567
577
|
else {
|
|
@@ -591,9 +601,6 @@ class ViessmannPlatform {
|
|
|
591
601
|
const existingAccessory = this.accessories.find(accessory => accessory.UUID === uuid);
|
|
592
602
|
if (existingAccessory) {
|
|
593
603
|
this.log.info('Restoring existing accessory from cache:', existingAccessory.displayName);
|
|
594
|
-
existingAccessory.context.device = device;
|
|
595
|
-
existingAccessory.context.installation = installation;
|
|
596
|
-
existingAccessory.context.gateway = gateway;
|
|
597
604
|
new heating_circuit_accessory_1.ViessmannHeatingCircuitAccessory(this, existingAccessory, installation, gateway, device, circuitNumber);
|
|
598
605
|
}
|
|
599
606
|
else {
|
|
@@ -645,9 +652,6 @@ class ViessmannPlatform {
|
|
|
645
652
|
const existingAccessory = this.accessories.find(a => a.UUID === uuid);
|
|
646
653
|
if (existingAccessory) {
|
|
647
654
|
this.log.info('Restoring existing energy accessory from cache:', existingAccessory.displayName);
|
|
648
|
-
existingAccessory.context.device = device;
|
|
649
|
-
existingAccessory.context.installation = installation;
|
|
650
|
-
existingAccessory.context.gateway = gateway;
|
|
651
655
|
new energy_accessory_1.ViessmannEnergyAccessory(this, existingAccessory, installation, gateway, device);
|
|
652
656
|
}
|
|
653
657
|
else {
|
|
@@ -718,10 +722,6 @@ class ViessmannPlatform {
|
|
|
718
722
|
deviceFeatureCache.set(deviceKey, features);
|
|
719
723
|
successfulDevices++;
|
|
720
724
|
this.log.debug(`β
Fetched ${features.length} features for device ${accessory.context.device.id}`);
|
|
721
|
-
// Write device messages JSON (S./F./I. codes) for viessmann-report.js
|
|
722
|
-
// Pass both installationId and deviceId β multiple devices per installation
|
|
723
|
-
// (e.g. Vitocal + VitoCharge) each get their own messages file.
|
|
724
|
-
this.writeDeviceMessages(features, accessory.context.installation.id, accessory.context.device.id);
|
|
725
725
|
// Small delay between distinct device API calls only
|
|
726
726
|
if (this.config.enableRateLimitProtection !== false) {
|
|
727
727
|
await this.sleep(500);
|
|
@@ -738,7 +738,7 @@ class ViessmannPlatform {
|
|
|
738
738
|
}
|
|
739
739
|
else {
|
|
740
740
|
handlersSkipped++;
|
|
741
|
-
this.log.
|
|
741
|
+
this.log.warn(`β οΈ "${accessory.displayName}" β updateHandler not set (accessory still initializing?)`);
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
744
|
catch (error) {
|
|
@@ -773,52 +773,6 @@ class ViessmannPlatform {
|
|
|
773
773
|
this.isUpdating = false;
|
|
774
774
|
}
|
|
775
775
|
}
|
|
776
|
-
/**
|
|
777
|
-
* Extracts device.messages.status/info/service entries from features and
|
|
778
|
-
* writes them to viessmann-messages-<installationId>-<deviceId>.json for viessmann-report.js.
|
|
779
|
-
* One file per device β multiple devices per installation each get their own file.
|
|
780
|
-
* Called once per unique device per update cycle (deduplicated via deviceFeatureCache).
|
|
781
|
-
*/
|
|
782
|
-
writeDeviceMessages(features, installationId, deviceId) {
|
|
783
|
-
try {
|
|
784
|
-
const messageFeatures = [
|
|
785
|
-
'device.messages.status.raw',
|
|
786
|
-
'device.messages.info.raw',
|
|
787
|
-
'device.messages.service.raw',
|
|
788
|
-
];
|
|
789
|
-
const messages = [];
|
|
790
|
-
for (const featureName of messageFeatures) {
|
|
791
|
-
const feature = features.find(f => f.feature === featureName);
|
|
792
|
-
const entries = feature?.properties?.entries?.value;
|
|
793
|
-
if (!Array.isArray(entries))
|
|
794
|
-
continue;
|
|
795
|
-
const type = featureName.includes('status') ? 'status'
|
|
796
|
-
: featureName.includes('info') ? 'info'
|
|
797
|
-
: 'service';
|
|
798
|
-
for (const entry of entries) {
|
|
799
|
-
if (entry?.errorCode) {
|
|
800
|
-
messages.push({
|
|
801
|
-
errorCode: entry.errorCode,
|
|
802
|
-
timestamp: entry.timestamp || new Date().toISOString(),
|
|
803
|
-
type,
|
|
804
|
-
busAddress: entry.busAddress,
|
|
805
|
-
busType: entry.busType,
|
|
806
|
-
});
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
// Sort by timestamp descending (newest first)
|
|
811
|
-
messages.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
812
|
-
const storagePath = this.api.user.storagePath();
|
|
813
|
-
// One file per device β avoids overwrite when multiple devices share an installation
|
|
814
|
-
const msgFile = path.join(storagePath, `viessmann-messages-${installationId}-${deviceId}.json`);
|
|
815
|
-
fs.writeFileSync(msgFile, JSON.stringify(messages, null, 2), 'utf8');
|
|
816
|
-
this.log.debug(`π Device messages written: ${messages.length} entries β ${msgFile}`);
|
|
817
|
-
}
|
|
818
|
-
catch (e) {
|
|
819
|
-
this.log.debug(`π writeDeviceMessages failed: ${e}`);
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
776
|
updateCycleComplete(successful, rateLimited, errors, skipped, handlersCalled, handlersSkipped, elapsedMs) {
|
|
823
777
|
const totalAccessories = this.accessories.length;
|
|
824
778
|
const elapsedSec = (elapsedMs / 1000).toFixed(1);
|
package/dist/settings.d.ts
CHANGED
|
@@ -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.
|
|
12
|
+
export declare const PLUGIN_VERSION = "2.0.51";
|
|
13
13
|
/**
|
|
14
14
|
* Default configuration values
|
|
15
15
|
*/
|
package/dist/settings.js
CHANGED
package/dist/viessmann-api.d.ts
CHANGED
|
@@ -16,8 +16,6 @@ export interface ViessmannPlatformConfig extends AuthConfig {
|
|
|
16
16
|
enableImmediateBurnerUpdates?: boolean;
|
|
17
17
|
burnerUpdateDelay?: number;
|
|
18
18
|
burnerUpdateDebounce?: number;
|
|
19
|
-
nominalPowerKw?: number;
|
|
20
|
-
maxCompressorRps?: number;
|
|
21
19
|
postCommandRetry?: {
|
|
22
20
|
delays?: number[];
|
|
23
21
|
guardDuration?: number;
|
|
@@ -48,6 +46,8 @@ export interface ViessmannPlatformConfig extends AuthConfig {
|
|
|
48
46
|
holidayAtHome?: string;
|
|
49
47
|
extendedHeating?: string;
|
|
50
48
|
};
|
|
49
|
+
reportServerPort?: number;
|
|
50
|
+
reportServerPath?: string;
|
|
51
51
|
cache?: {
|
|
52
52
|
enabled?: boolean;
|
|
53
53
|
installationsTTL?: number;
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"viessmann-api.d.ts","sourceRoot":"","sources":["../src/viessmann-api.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-viessmann-vicare",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.51",
|
|
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": {
|
|
@@ -50,7 +50,8 @@
|
|
|
50
50
|
"config.schema.json",
|
|
51
51
|
"README.md",
|
|
52
52
|
"SETUP-GUIDE.md",
|
|
53
|
-
"viessmann-report.js"
|
|
53
|
+
"viessmann-report.js",
|
|
54
|
+
"viessmann-report-server.js"
|
|
54
55
|
],
|
|
55
56
|
"funding": {
|
|
56
57
|
"type": "individual",
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Viessmann Report Server
|
|
4
|
+
* Serves a web UI to generate the Viessmann history report.
|
|
5
|
+
*
|
|
6
|
+
* Standalone: node viessmann-report-server.js [--port 3001] [--path /var/lib/homebridge]
|
|
7
|
+
* Via plugin: started automatically if reportServerPort > 0 in plugin config.
|
|
8
|
+
*/
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const http = require('http');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const url = require('url');
|
|
15
|
+
const { execFile } = require('child_process');
|
|
16
|
+
|
|
17
|
+
// ββ CLI args βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const getArg = (flag, def) => { const i = args.indexOf(flag); return i !== -1 && args[i+1] ? args[i+1] : def; };
|
|
20
|
+
const PORT = parseInt(getArg('--port', process.env.REPORT_SERVER_PORT || '3001'), 10);
|
|
21
|
+
const HB_PATH = getArg('--path', process.env.HB_PATH || '/var/lib/homebridge');
|
|
22
|
+
const SCRIPT = getArg('--script', path.join(__dirname, 'viessmann-report.js'));
|
|
23
|
+
|
|
24
|
+
// ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
25
|
+
|
|
26
|
+
function detectInstallations() {
|
|
27
|
+
try {
|
|
28
|
+
const files = fs.readdirSync(HB_PATH);
|
|
29
|
+
const ids = [];
|
|
30
|
+
for (const f of files) {
|
|
31
|
+
const m = f.match(/^viessmann-history-(\d+)\.csv$/);
|
|
32
|
+
if (m) ids.push(m[1]);
|
|
33
|
+
}
|
|
34
|
+
if (files.includes('viessmann-history.csv')) ids.push('');
|
|
35
|
+
return ids;
|
|
36
|
+
} catch { return []; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function numParam(val, def, min, max) {
|
|
40
|
+
const n = parseFloat(val);
|
|
41
|
+
if (isNaN(n)) return def;
|
|
42
|
+
return Math.min(max, Math.max(min, n));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function safeNum(val) {
|
|
46
|
+
if (!val) return '';
|
|
47
|
+
return String(val).replace(/[^0-9.\-]/g, '');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function generateReport(params) {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const tmpOut = path.join(HB_PATH, 'viessmann-report-web-' + Date.now() + '.html');
|
|
53
|
+
const cliArgs = [SCRIPT, '--path', HB_PATH, '--days', String(params.days), '--out', tmpOut];
|
|
54
|
+
if (params.installation) cliArgs.push('--installation', params.installation);
|
|
55
|
+
if (params.boilerKW) cliArgs.push('--boilerKW', params.boilerKW);
|
|
56
|
+
if (params.designTemp) cliArgs.push('--designTemp', params.designTemp);
|
|
57
|
+
if (params.gasPrice) cliArgs.push('--gasPriceEur', params.gasPrice);
|
|
58
|
+
|
|
59
|
+
execFile(process.execPath, cliArgs, { timeout: 60000 }, (err, stdout, stderr) => {
|
|
60
|
+
if (err) { reject(new Error(stderr || err.message)); return; }
|
|
61
|
+
try {
|
|
62
|
+
const html = fs.readFileSync(tmpOut, 'utf8');
|
|
63
|
+
try { fs.unlinkSync(tmpOut); } catch {}
|
|
64
|
+
resolve(html);
|
|
65
|
+
} catch (e) { reject(e); }
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ββ UI HTML ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
71
|
+
|
|
72
|
+
function buildUI(installations) {
|
|
73
|
+
const today = new Date().toLocaleDateString('en-GB', { day:'2-digit', month:'short', year:'numeric' });
|
|
74
|
+
const instOptions = installations.length === 0
|
|
75
|
+
? '<option value="">No CSV found β check path</option>'
|
|
76
|
+
: installations.map(id =>
|
|
77
|
+
'<option value="' + id + '">' + (id ? 'Installation ' + id : 'Default (no ID)') + '</option>'
|
|
78
|
+
).join('');
|
|
79
|
+
|
|
80
|
+
return `<!DOCTYPE html>
|
|
81
|
+
<html lang="en">
|
|
82
|
+
<head>
|
|
83
|
+
<meta charset="UTF-8">
|
|
84
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
85
|
+
<title>Viessmann Report</title>
|
|
86
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Syne:wght@400;600;800&display=swap" rel="stylesheet">
|
|
87
|
+
<style>
|
|
88
|
+
:root{--bg:#0d1117;--surface:#161b22;--border:#21262d;--accent:#f97316;--accent2:#fb923c;--text:#e6edf3;--muted:#7d8590;--good:#3fb950;--r:10px}
|
|
89
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
90
|
+
body{font-family:'Syne',sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:40px 16px 60px}
|
|
91
|
+
header{text-align:center;margin-bottom:40px}
|
|
92
|
+
.logo{display:inline-flex;align-items:center;gap:10px;margin-bottom:10px}
|
|
93
|
+
.logo svg{width:36px;height:36px}
|
|
94
|
+
header h1{font-size:clamp(22px,5vw,34px);font-weight:800;letter-spacing:-.5px}
|
|
95
|
+
header h1 span{color:var(--accent)}
|
|
96
|
+
header p{color:var(--muted);font-size:13px;margin-top:6px;font-family:'Space Mono',monospace}
|
|
97
|
+
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:26px 28px;width:100%;max-width:560px}
|
|
98
|
+
.card+.card{margin-top:14px}
|
|
99
|
+
.section-label{font-family:'Space Mono',monospace;font-size:10px;text-transform:uppercase;letter-spacing:2px;color:var(--accent);margin-bottom:14px}
|
|
100
|
+
.presets{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-bottom:14px}
|
|
101
|
+
.preset{background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:10px 6px;font-family:'Space Mono',monospace;font-size:11px;color:var(--muted);cursor:pointer;text-align:center;transition:all .15s;user-select:none}
|
|
102
|
+
.preset:hover,.preset.active{border-color:var(--accent);color:var(--accent);background:rgba(249,115,22,.08)}
|
|
103
|
+
.preset .n{font-size:20px;font-weight:700;color:var(--text);display:block;margin-bottom:2px}
|
|
104
|
+
.preset.active .n{color:var(--accent)}
|
|
105
|
+
.custom-row{display:flex;align-items:center;gap:10px}
|
|
106
|
+
.custom-row label{font-size:12px;color:var(--muted);white-space:nowrap;font-family:'Space Mono',monospace}
|
|
107
|
+
input,select{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-family:'Space Mono',monospace;font-size:13px;padding:9px 12px;width:100%;transition:border-color .15s;appearance:none;-webkit-appearance:none}
|
|
108
|
+
input:focus,select:focus{outline:none;border-color:var(--accent)}
|
|
109
|
+
.custom-row input{max-width:80px}
|
|
110
|
+
.field-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
|
111
|
+
@media(max-width:420px){.field-grid{grid-template-columns:1fr}}
|
|
112
|
+
.field label{display:block;font-size:11px;font-family:'Space Mono',monospace;color:var(--muted);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px}
|
|
113
|
+
.hint{font-size:10px;color:var(--muted);margin-top:4px;opacity:.7}
|
|
114
|
+
.adv-toggle{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:12px;font-family:'Space Mono',monospace;color:var(--muted);user-select:none;transition:color .15s}
|
|
115
|
+
.adv-toggle:hover{color:var(--text)}
|
|
116
|
+
.arr{transition:transform .2s;display:inline-block}
|
|
117
|
+
.arr.open{transform:rotate(90deg)}
|
|
118
|
+
#adv-fields{display:none;margin-top:16px}
|
|
119
|
+
#adv-fields.visible{display:block}
|
|
120
|
+
.btn{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;max-width:560px;padding:16px;background:var(--accent);border:none;border-radius:var(--r);color:#fff;font-family:'Syne',sans-serif;font-size:16px;font-weight:700;cursor:pointer;transition:background .15s,transform .1s;margin-top:16px}
|
|
121
|
+
.btn:hover{background:var(--accent2)}
|
|
122
|
+
.btn:active{transform:scale(.98)}
|
|
123
|
+
.btn:disabled{background:var(--border);color:var(--muted);cursor:not-allowed;transform:none}
|
|
124
|
+
#status{width:100%;max-width:560px;margin-top:12px;font-family:'Space Mono',monospace;font-size:12px;text-align:center;min-height:18px}
|
|
125
|
+
#status.loading{color:var(--accent)}
|
|
126
|
+
#status.ok{color:var(--good)}
|
|
127
|
+
#status.err{color:#f85149}
|
|
128
|
+
@keyframes spin{to{transform:rotate(360deg)}}
|
|
129
|
+
.spinner{display:inline-block;width:13px;height:13px;border:2px solid rgba(249,115,22,.3);border-top-color:var(--accent);border-radius:50%;animation:spin .7s linear infinite;vertical-align:middle;margin-right:6px}
|
|
130
|
+
footer{margin-top:36px;font-family:'Space Mono',monospace;font-size:10px;color:var(--muted);text-align:center;opacity:.4}
|
|
131
|
+
</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
<header>
|
|
135
|
+
<div class="logo">
|
|
136
|
+
<svg viewBox="0 0 36 36" fill="none">
|
|
137
|
+
<circle cx="18" cy="18" r="17" stroke="#f97316" stroke-width="2"/>
|
|
138
|
+
<path d="M11 24L18 10L25 24" stroke="#f97316" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
139
|
+
<path d="M13.5 19.5H22.5" stroke="#f97316" stroke-width="2" stroke-linecap="round"/>
|
|
140
|
+
</svg>
|
|
141
|
+
<h1>Viessmann <span>ViCare</span></h1>
|
|
142
|
+
</div>
|
|
143
|
+
<p>History Report Generator Β· ${today}</p>
|
|
144
|
+
</header>
|
|
145
|
+
|
|
146
|
+
<div class="card">
|
|
147
|
+
<div class="section-label">Period</div>
|
|
148
|
+
<div class="presets">
|
|
149
|
+
<div class="preset active" data-days="7" onclick="setDays(7,this)"><span class="n">7</span>days</div>
|
|
150
|
+
<div class="preset" data-days="14" onclick="setDays(14,this)"><span class="n">14</span>days</div>
|
|
151
|
+
<div class="preset" data-days="30" onclick="setDays(30,this)"><span class="n">30</span>days</div>
|
|
152
|
+
<div class="preset" data-days="90" onclick="setDays(90,this)"><span class="n">90</span>days</div>
|
|
153
|
+
</div>
|
|
154
|
+
<div class="custom-row">
|
|
155
|
+
<label>Custom:</label>
|
|
156
|
+
<input type="number" id="days-input" value="7" min="1" max="365" oninput="onCustom(this)">
|
|
157
|
+
<label>days</label>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div class="card">
|
|
162
|
+
<div class="section-label">Installation</div>
|
|
163
|
+
<div class="field">
|
|
164
|
+
<label>Installation ID</label>
|
|
165
|
+
<select id="installation">${instOptions}</select>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<div class="card">
|
|
170
|
+
<div class="adv-toggle" onclick="toggleAdv()">
|
|
171
|
+
<span class="arr" id="arr">▶</span>
|
|
172
|
+
Advanced parameters (boiler & gas)
|
|
173
|
+
</div>
|
|
174
|
+
<div id="adv-fields">
|
|
175
|
+
<div class="field-grid">
|
|
176
|
+
<div class="field">
|
|
177
|
+
<label>Boiler nominal power</label>
|
|
178
|
+
<input type="number" id="boilerKW" placeholder="e.g. 19" min="0" max="200" step="0.5">
|
|
179
|
+
<div class="hint">kW β enables heat demand & sizing</div>
|
|
180
|
+
</div>
|
|
181
|
+
<div class="field">
|
|
182
|
+
<label>Design outdoor temp</label>
|
|
183
|
+
<input type="number" id="designTemp" placeholder="-7" min="-30" max="10" step="1">
|
|
184
|
+
<div class="hint">°C β for peak load calculation</div>
|
|
185
|
+
</div>
|
|
186
|
+
<div class="field">
|
|
187
|
+
<label>Gas price</label>
|
|
188
|
+
<input type="number" id="gasPrice" placeholder="0.90" min="0" max="10" step="0.01">
|
|
189
|
+
<div class="hint">€/m³ β for cost forecast</div>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<button class="btn" id="gen-btn" onclick="generate()">Generate Report</button>
|
|
196
|
+
<div id="status"></div>
|
|
197
|
+
<footer>homebridge-viessmann-vicare Β· report server Β· ${HB_PATH}</footer>
|
|
198
|
+
|
|
199
|
+
<script>
|
|
200
|
+
let currentDays = 7;
|
|
201
|
+
function setDays(d,el){currentDays=d;document.getElementById('days-input').value=d;document.querySelectorAll('.preset').forEach(p=>p.classList.remove('active'));if(el)el.classList.add('active');}
|
|
202
|
+
function onCustom(el){currentDays=parseInt(el.value)||7;document.querySelectorAll('.preset').forEach(p=>{p.classList.toggle('active',parseInt(p.dataset.days)===currentDays);});}
|
|
203
|
+
function toggleAdv(){document.getElementById('adv-fields').classList.toggle('visible');document.getElementById('arr').classList.toggle('open');}
|
|
204
|
+
async function generate(){
|
|
205
|
+
const btn=document.getElementById('gen-btn'),status=document.getElementById('status');
|
|
206
|
+
const days=parseInt(document.getElementById('days-input').value)||7;
|
|
207
|
+
const installation=document.getElementById('installation').value;
|
|
208
|
+
const boilerKW=document.getElementById('boilerKW').value.trim();
|
|
209
|
+
const designTemp=document.getElementById('designTemp').value.trim();
|
|
210
|
+
const gasPrice=document.getElementById('gasPrice').value.trim();
|
|
211
|
+
btn.disabled=true;
|
|
212
|
+
status.className='loading';
|
|
213
|
+
status.innerHTML='<span class="spinner"></span>Generating report…';
|
|
214
|
+
const p=new URLSearchParams({days});
|
|
215
|
+
if(installation)p.set('installation',installation);
|
|
216
|
+
if(boilerKW)p.set('boilerKW',boilerKW);
|
|
217
|
+
if(designTemp)p.set('designTemp',designTemp);
|
|
218
|
+
if(gasPrice)p.set('gasPrice',gasPrice);
|
|
219
|
+
try{
|
|
220
|
+
const res=await fetch('/report?'+p.toString());
|
|
221
|
+
if(!res.ok){throw new Error(await res.text()||res.statusText);}
|
|
222
|
+
const html=await res.text();
|
|
223
|
+
const blob=new Blob([html],{type:'text/html'});
|
|
224
|
+
window.open(URL.createObjectURL(blob),'_blank');
|
|
225
|
+
status.className='ok';
|
|
226
|
+
status.textContent='\u2713 Report opened in new tab';
|
|
227
|
+
}catch(e){
|
|
228
|
+
status.className='err';
|
|
229
|
+
status.textContent='\u2717 '+e.message;
|
|
230
|
+
}finally{
|
|
231
|
+
btn.disabled=false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
</script>
|
|
235
|
+
</body>
|
|
236
|
+
</html>`;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ββ HTTP Server ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
240
|
+
|
|
241
|
+
const server = http.createServer(async (req, res) => {
|
|
242
|
+
const parsed = url.parse(req.url, true);
|
|
243
|
+
const pathname = parsed.pathname;
|
|
244
|
+
|
|
245
|
+
if (pathname === '/health') {
|
|
246
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
247
|
+
res.end(JSON.stringify({ status: 'ok', path: HB_PATH, port: PORT }));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (pathname === '/' || pathname === '') {
|
|
252
|
+
const html = buildUI(detectInstallations());
|
|
253
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
254
|
+
res.end(html);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (pathname === '/report') {
|
|
259
|
+
const q = parsed.query;
|
|
260
|
+
const days = Math.round(numParam(q.days, 7, 1, 365));
|
|
261
|
+
const installation = (q.installation || '').replace(/[^0-9]/g, '');
|
|
262
|
+
const boilerKW = safeNum(q.boilerKW);
|
|
263
|
+
const designTemp = safeNum(q.designTemp);
|
|
264
|
+
const gasPrice = safeNum(q.gasPrice);
|
|
265
|
+
|
|
266
|
+
const csvName = installation
|
|
267
|
+
? 'viessmann-history-' + installation + '.csv'
|
|
268
|
+
: 'viessmann-history.csv';
|
|
269
|
+
|
|
270
|
+
if (!fs.existsSync(path.join(HB_PATH, csvName))) {
|
|
271
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
272
|
+
res.end('CSV not found: ' + path.join(HB_PATH, csvName));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
if (!fs.existsSync(SCRIPT)) {
|
|
276
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
277
|
+
res.end('Report script not found: ' + SCRIPT);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const html = await generateReport({ days, installation, boilerKW, designTemp, gasPrice });
|
|
283
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
284
|
+
res.end(html);
|
|
285
|
+
} catch (e) {
|
|
286
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
287
|
+
res.end('Error generating report:\n' + e.message);
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
293
|
+
res.end('Not found');
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// ββ Start ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
297
|
+
|
|
298
|
+
if (require.main === module) {
|
|
299
|
+
server.listen(PORT, '0.0.0.0', () => {
|
|
300
|
+
console.log('[Viessmann] Report server listening on http://0.0.0.0:' + PORT);
|
|
301
|
+
console.log('[Viessmann] Data path: ' + HB_PATH);
|
|
302
|
+
console.log('[Viessmann] Script: ' + SCRIPT);
|
|
303
|
+
});
|
|
304
|
+
server.on('error', (err) => {
|
|
305
|
+
if (err.code === 'EADDRINUSE') {
|
|
306
|
+
console.error('[Viessmann] Port ' + PORT + ' already in use');
|
|
307
|
+
} else {
|
|
308
|
+
console.error('[Viessmann] Report server error:', err.message);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
module.exports = { server, PORT };
|