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 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.49
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.48 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.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
 
@@ -2,8 +2,8 @@
2
2
  "pluginAlias": "ViessmannPlatform",
3
3
  "pluginType": "platform",
4
4
  "singular": true,
5
- "headerDisplay": "πŸ”₯ 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. ⚠️ CRITICAL: Authorization codes expire in 20 seconds during OAuth setup!",
6
- "footerDisplay": "⚠️ Important: Refresh tokens expire after 180 days. For help and support visit: [πŸ“– Setup Guide](https://github.com/diegoweb100/homebridge-viessmann-vicare/blob/main/SETUP-GUIDE.md) | [πŸ”§ Troubleshooting](https://github.com/diegoweb100/homebridge-viessmann-vicare#-troubleshooting) | [πŸ“ž Support](https://github.com/diegoweb100/homebridge-viessmann-vicare/issues)",
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": "πŸ”§ 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."
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": "🎯 Custom Names & Localization",
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": "πŸ—„οΈ Enhanced API Cache Configuration",
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": "πŸ”§ Advanced Settings",
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": "πŸŽ›οΈ Feature Control",
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": "🌐 Network Settings",
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": "πŸ” Basic Configuration",
465
- "description": "Essential settings to connect to your Viessmann system. ⚠️ Remember: Authorization codes expire in 20 seconds!",
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": "πŸ”‘ Authentication Method",
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": "βš™οΈ Manual Authentication (Advanced)",
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": "πŸ”’ Token Management",
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": "🎯 Installation Filtering",
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": "🎯 Custom Names & Localization",
525
- "description": "🌍 Customize accessory names for different languages or personal preferences. Perfect for Italian, German, French, Spanish users or custom naming schemes. Changes require Homebridge restart.",
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": "⚑ Performance & Rate Limiting",
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": "πŸ’Ύ Enhanced API Caching",
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": "πŸŽ›οΈ Feature Control",
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": "πŸ”§ Advanced Settings",
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": "🌐 Network Settings",
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": "πŸ“Š Monitoring & Debug",
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;IAmDjC,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;IAtDzB,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;IAE5D,OAAO,CAAC,MAAM,CAsBZ;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;IA6VzB,oBAAoB,IAAI;QAC7B,WAAW,EAAE,MAAM,CAAC;QACpB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACxC,mBAAmB,EAAE,MAAM,CAAC;QAC5B,sBAAsB,EAAE,MAAM,CAAC;QAC/B,qBAAqB,EAAE,MAAM,CAAC;QAC9B,wBAAwB,EAAE,MAAM,CAAC;QACjC,kBAAkB,EAAE,MAAM,CAAC;QAC3B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB;CAsBF"}
1
+ {"version":3,"file":"boiler-accessory.d.ts","sourceRoot":"","sources":["../../src/accessories/boiler-accessory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;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;IAClB,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,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,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;AAID,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;CAuChC"}
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 CSV_HEADER = 'timestamp,accessory,burner_active,modulation,room_temp,target_temp,outside_temp,outside_humidity,dhw_temp,dhw_target,program,mode,burner_starts,burner_hours,flow_temp,gas_heating_day_m3,gas_dhw_day_m3,pv_production_w,pv_daily_kwh,battery_level,battery_charging_w,battery_discharging_w,grid_feedin_w,grid_draw_w,wallbox_charging,wallbox_power_w\n';
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.timestamp,
131
- row.accessory,
132
- row.burner_active ?? '',
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) {
@@ -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;
@@ -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
@@ -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
- // Require actual burner/boiler operation features β€” not just heating.boiler.serial
519
- // which is also present on VitoCharge and other gen3 devices as a system identifier.
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.debug(`βš™οΈ "${accessory.displayName}" β€” updateHandler not set, skipping`);
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);
@@ -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.49";
12
+ export declare const PLUGIN_VERSION = "2.0.51";
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.49';
15
+ exports.PLUGIN_VERSION = '2.0.51';
16
16
  /**
17
17
  * Default configuration values
18
18
  */
@@ -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":"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"}
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.49",
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 &nbsp;Β·&nbsp; ${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">&#9654;</span>
172
+ Advanced parameters (boiler &amp; 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 &amp; 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">&#176;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">&#8364;/m&#179; β€” 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 &nbsp;Β·&nbsp; report server &nbsp;Β·&nbsp; ${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&hellip;';
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 };