homebridge-viessmann-vicare 2.0.40 → 2.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1033,6 +1033,15 @@ For issues and questions:
1033
1033
 
1034
1034
  ## 📈 Changelog
1035
1035
 
1036
+ ### [2.0.41] - 2026-03-15
1037
+ #### Fixed
1038
+ - **Duplicate Boiler accessory on heat pump installations** — `setupBoilerAccessory` was matching `heating.boiler.serial` which is present on VitoCharge and other gen3 devices as a system identifier. Filter now requires actual burner/boiler operation features (`heating.burners.*`, `heating.boiler.temperature.current`, etc.). Fixes "Boiler 2" / "Energy 2" confusion reported on Windows installations with Vitocal 250A.
1039
+
1040
+ #### Added — `viessmann-report.js`
1041
+ - **Gas forecast section** — projects next-30-day and annual gas consumption using linear regression on historical CSV data. Shows cost estimate in € with configurable tariff via `--gasPriceEur` (default: 0.90 €/m³). Includes trend indicator (rising/stable/falling).
1042
+ - **Device messages section** — reads `viessmann-messages-<ID>.json` (written by plugin, future) and displays S./F./I. codes with English translations from Viessmann service documentation (80+ codes covered).
1043
+ - **`--gasPriceEur`** CLI parameter for gas cost calculation.
1044
+
1036
1045
  ### [2.0.40] - 2026-03-15
1037
1046
  #### Fixed
1038
1047
  - **Critical: Accessories not updating after Homebridge restart** — when restoring accessories from cache, `device`, `installation`, and `gateway` were not written to `accessory.context`. The update loop silently skipped all accessories on every subsequent restart, showing `0 device(s) fetched, 0/0 accessories updated`. All four restore-from-cache paths (Boiler, DHW, Heating Circuit, Energy/Heat Pump) are now fixed.
package/SETUP-GUIDE.md CHANGED
@@ -1,8 +1,8 @@
1
- # Complete Setup Guide - v2.0.40
1
+ # Complete Setup Guide - v2.0.41
2
2
 
3
3
  ## Overview
4
4
 
5
- This guide will walk you through setting up the Viessmann ViCare plugin v2.0.40 for Homebridge, including all the advanced features like intelligent caching, rate limiting protection, comprehensive configuration options, **complete localization support with custom names**, **CSV history logging**, **HTML diagnostic reports**, **energy system monitoring** (PV, battery, wallbox), and **heating schedule awareness** with visual bands in the HTML report, and **heat pump (Wärmepumpe) support** with automatic device detection.
5
+ This guide will walk you through setting up the Viessmann ViCare plugin v2.0.41 for Homebridge, including all the advanced features like intelligent caching, rate limiting protection, comprehensive configuration options, **complete localization support with custom names**, **CSV history logging**, **HTML diagnostic reports**, **energy system monitoring** (PV, battery, wallbox), and **heating schedule awareness** with visual bands in the HTML report, and **heat pump (Wärmepumpe) support** with automatic device detection.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -1157,7 +1157,7 @@ When reporting issues, include:
1157
1157
 
1158
1158
  ```json
1159
1159
  {
1160
- "plugin_version": "2.0.40",
1160
+ "plugin_version": "2.0.41",
1161
1161
  "homebridge_version": "1.8.x",
1162
1162
  "node_version": "18.x.x",
1163
1163
  "heating_system": "Viessmann Model",
@@ -1228,6 +1228,13 @@ sudo systemctl restart homebridge
1228
1228
 
1229
1229
  ## Changelog
1230
1230
 
1231
+ ### v2.0.41 (2026-03-15)
1232
+ **Fix duplicate Boiler accessory + gas forecast + error code translations:**
1233
+ - **Fixed: duplicate "Boiler 2" on heat pump installations** — `setupBoilerAccessory` was matching `heating.boiler.serial` present on VitoCharge and other gen3 devices. Filter now requires actual burner operation features. Fixes accessory naming confusion on Windows installs with Vitocal 250A.
1234
+ - **Report: Gas forecast** — new section projecting 30-day and annual gas consumption via linear regression on CSV history. Cost estimate in € with `--gasPriceEur` parameter (default 0.90 €/m³). Trend indicator (rising/stable/falling).
1235
+ - **Report: Device messages** — new section with 80+ Viessmann S./F./I. code translations (English). Reads `viessmann-messages-<ID>.json` when available.
1236
+ - **`--gasPriceEur <price>`** new CLI parameter for `viessmann-report.js`.
1237
+
1231
1238
  ### v2.0.40 (2026-03-15)
1232
1239
  **Critical bug fix + log noise reduction:**
1233
1240
  - **Fixed: accessories not updating after restart** — all four restore-from-cache paths (Boiler, DHW, HC, Energy) were missing `context.device/installation/gateway` assignment. This caused `updateAllDevices` to skip every accessory silently on every restart after the first, showing `0 device(s) fetched`. Affected all users.
@@ -1 +1 @@
1
- {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
package/dist/platform.js CHANGED
@@ -511,8 +511,12 @@ class ViessmannPlatform {
511
511
  }
512
512
  }
513
513
  async setupBoilerAccessory(installation, gateway, device, features) {
514
- const boilerFeatures = features.filter(f => f.feature.includes('heating.boiler') ||
515
- f.feature.includes('heating.burners'));
514
+ // Require actual burner/boiler operation features not just heating.boiler.serial
515
+ // which is also present on VitoCharge and other gen3 devices as a system identifier.
516
+ const boilerFeatures = features.filter(f => f.feature.startsWith('heating.burners') ||
517
+ f.feature === 'heating.boiler.temperature.current' ||
518
+ f.feature === 'heating.boiler.sensors.temperature.commonSupply' ||
519
+ f.feature === 'heating.boiler.pumps.internal');
516
520
  if (boilerFeatures.length === 0) {
517
521
  return;
518
522
  }
@@ -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.40";
12
+ export declare const PLUGIN_VERSION = "2.0.41";
13
13
  /**
14
14
  * Default configuration values
15
15
  */
package/dist/settings.js CHANGED
@@ -12,7 +12,7 @@ exports.PLUGIN_NAME = 'homebridge-viessmann-vicare';
12
12
  /**
13
13
  * Plugin version for User-Agent and logging
14
14
  */
15
- exports.PLUGIN_VERSION = '2.0.40';
15
+ exports.PLUGIN_VERSION = '2.0.41';
16
16
  /**
17
17
  * Default configuration values
18
18
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "homebridge-viessmann-vicare",
3
- "version": "2.0.40",
3
+ "version": "2.0.41",
4
4
  "description": "Complete Homebridge plugin for Viessmann ViCare heating systems with full control capabilities, advanced rate limiting protection, and intelligent cache management",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -581,6 +581,165 @@ const ovOutsideHum = overviewTimes.map(ts => interpolate(boilerRows, ts, 'outsid
581
581
  const ovMod = overviewTimes.map(ts => interpolate(boilerRows,ts, 'modulation'));
582
582
  const ovBurner = overviewTimes.map(ts => lookupBurner(boilerRows, ts));
583
583
 
584
+
585
+ // ─────────────────────────────────────────────────────────────────────────────
586
+ // GAS FORECAST
587
+ // Uses gasPerDay (already computed above) to project monthly/annual consumption.
588
+ // Strategy: linear regression on last N days → extrapolate to 30/365 days.
589
+ // ─────────────────────────────────────────────────────────────────────────────
590
+ let gasForecast = null;
591
+ if (gasDays.length >= 3) {
592
+ // Use all available days; total = heating + dhw
593
+ const totalPerDay = gasDays.map(d => gasPerDay[d].heating + gasPerDay[d].dhw);
594
+ const n = totalPerDay.length;
595
+ // Simple linear regression: y = a + b*x (x = day index)
596
+ const xMean = (n - 1) / 2;
597
+ const yMean = totalPerDay.reduce((s, v) => s + v, 0) / n;
598
+ let num = 0, den = 0;
599
+ totalPerDay.forEach((y, i) => { num += (i - xMean) * (y - yMean); den += (i - xMean) ** 2; });
600
+ const slope = den !== 0 ? num / den : 0;
601
+ const intercept = yMean - slope * xMean;
602
+ // Project from today (index = n-1) forward
603
+ const projectDay = (offset) => Math.max(0, intercept + slope * (n - 1 + offset));
604
+ // Monthly: sum of next 30 days
605
+ let monthSum = 0;
606
+ for (let i = 1; i <= 30; i++) monthSum += projectDay(i);
607
+ // Annual: sum of next 365 days (approximated as 30-day avg × 12 with seasonal note)
608
+ // For simplicity: annualise the period avg × 365 (more stable than long regression)
609
+ const periodAvgPerDay = yMean;
610
+ const annualEst = periodAvgPerDay * 365;
611
+ // Cost estimate (€): use --gasPriceEur param or env (default 0.90 €/m³ — Italian average)
612
+ const GAS_PRICE = parseFloat(getArg('--gasPriceEur', process.env.GAS_PRICE_EUR || '0.90'));
613
+ gasForecast = {
614
+ avgPerDay: yMean.toFixed(2),
615
+ trend: slope > 0.05 ? 'rising' : slope < -0.05 ? 'falling' : 'stable',
616
+ trendSlope: slope.toFixed(3),
617
+ month30: monthSum.toFixed(1),
618
+ annualEst: annualEst.toFixed(0),
619
+ costMonth: (monthSum * GAS_PRICE).toFixed(2),
620
+ costAnnual: (annualEst * GAS_PRICE).toFixed(2),
621
+ gasPrice: GAS_PRICE.toFixed(2),
622
+ daysUsed: n,
623
+ };
624
+ }
625
+
626
+ // ─────────────────────────────────────────────────────────────────────────────
627
+ // VIESSMANN STATUS / ERROR CODE TRANSLATIONS
628
+ // Source: Viessmann service documentation + vieventlog error_codes.go
629
+ // ─────────────────────────────────────────────────────────────────────────────
630
+ const VIESSMANN_CODES = {
631
+ // ── Status codes (S.) ──
632
+ 'S.0': { en: 'Standby', de: 'Bereitschaft' },
633
+ 'S.1': { en: 'DHW heating', de: 'Warmwasserbereitung' },
634
+ 'S.2': { en: 'Central heating', de: 'Heizbetrieb' },
635
+ 'S.3': { en: 'Burner on', de: 'Brenner ein' },
636
+ 'S.4': { en: 'Burner off', de: 'Brenner aus' },
637
+ 'S.5': { en: 'Fan pre-purge', de: 'Lüfter Vorspülung' },
638
+ 'S.6': { en: 'Ignition', de: 'Zündung' },
639
+ 'S.7': { en: 'Flame detected', de: 'Flamme erkannt' },
640
+ 'S.8': { en: 'Burner post-purge', de: 'Lüfter Nachspülung' },
641
+ 'S.9': { en: 'Cooling mode', de: 'Kühlbetrieb' },
642
+ 'S.10': { en: 'Frost protection active', de: 'Frostschutz aktiv' },
643
+ 'S.12': { en: 'Circulation pump active', de: 'Umwälzpumpe aktiv' },
644
+ 'S.17': { en: 'Flue gas test', de: 'Abgastest' },
645
+ 'S.19': { en: 'Chimney sweep mode', de: 'Schornsteinfegerbetrieb' },
646
+ 'S.20': { en: 'Boiler protection (overtemp)', de: 'Kesselschutz (Übertemperatur)' },
647
+ 'S.22': { en: 'External demand active', de: 'Externe Anforderung aktiv' },
648
+ 'S.24': { en: 'Pump overrun', de: 'Pumpennachlauf' },
649
+ 'S.31': { en: 'Summer eco mode', de: 'Sommer-Eco-Betrieb' },
650
+ 'S.32': { en: 'Heating circuit standby', de: 'Heizkreis Standby' },
651
+ 'S.40': { en: 'Heat pump heating', de: 'Wärmepumpe Heizbetrieb' },
652
+ 'S.41': { en: 'Heat pump DHW', de: 'Wärmepumpe Warmwasser' },
653
+ 'S.42': { en: 'Heat pump defrost', de: 'Wärmepumpe Abtauung' },
654
+ 'S.43': { en: 'Heat pump cooling', de: 'Wärmepumpe Kühlung' },
655
+ 'S.44': { en: 'Compressor starting', de: 'Verdichter startet' },
656
+ 'S.45': { en: 'Compressor running', de: 'Verdichter läuft' },
657
+ 'S.46': { en: 'Compressor stopping', de: 'Verdichter stoppt' },
658
+ 'S.100': { en: 'Heating mode', de: 'Heizbetrieb' },
659
+ 'S.109': { en: 'Heating circuit active', de: 'Heizkreis aktiv' },
660
+ 'S.111': { en: 'Normal heating program', de: 'Normalbetrieb Heizung' },
661
+ 'S.112': { en: 'Reduced heating program', de: 'Absenkbetrieb Heizung' },
662
+ 'S.113': { en: 'Comfort heating program', de: 'Komfortbetrieb Heizung' },
663
+ 'S.114': { en: 'DHW demand', de: 'Warmwasseranforderung' },
664
+ 'S.118': { en: 'Primary circuit active', de: 'Primärkreis aktiv' },
665
+ 'S.119': { en: 'Secondary circuit active', de: 'Sekundärkreis aktiv' },
666
+ 'S.120': { en: 'Circulation pump running', de: 'Umwälzpumpe läuft' },
667
+ 'S.123': { en: 'Heat pump standby', de: 'Wärmepumpe Bereitschaft' },
668
+ 'S.124': { en: 'Heat pump grid lock', de: 'Wärmepumpe Netzsperrzeit' },
669
+ 'S.125': { en: 'Heat pump demand pending', de: 'Wärmepumpe Anforderung ausstehend' },
670
+ 'S.126': { en: 'Heat pump frost protection', de: 'Wärmepumpe Frostschutz' },
671
+ 'S.130': { en: 'Defrost active', de: 'Abtauung aktiv' },
672
+ 'S.131': { en: 'Defrost completed', de: 'Abtauung abgeschlossen' },
673
+ 'S.134': { en: 'Heat pump heating active', de: 'Wärmepumpe Heizbetrieb aktiv' },
674
+ 'S.140': { en: 'Smart grid active', de: 'Smart Grid aktiv' },
675
+ 'S.200': { en: 'Legionella protection', de: 'Legionellenschutz' },
676
+ 'S.201': { en: 'DHW efficient mode', de: 'Warmwasser Effizienzbetrieb' },
677
+ 'S.202': { en: 'DHW comfort mode', de: 'Warmwasser Komfortbetrieb' },
678
+ // ── Info codes (I.) ──
679
+ 'I.0': { en: 'System OK', de: 'System OK' },
680
+ 'I.1': { en: 'Maintenance due', de: 'Wartung fällig' },
681
+ 'I.2': { en: 'Filter replacement due', de: 'Filterwechsel fällig' },
682
+ 'I.10': { en: 'External temperature sensor fault', de: 'Außentemperaturfühler Fehler' },
683
+ 'I.11': { en: 'Return temperature sensor fault', de: 'Rücklauftemperaturfühler Fehler' },
684
+ 'I.12': { en: 'DHW sensor fault', de: 'Warmwasserfühler Fehler' },
685
+ 'I.20': { en: 'Low water pressure warning', de: 'Niederdruck Warnung' },
686
+ 'I.100': { en: 'System info', de: 'Systeminformation' },
687
+ 'I.113': { en: 'Heating curve optimisation info', de: 'Heizkurvenoptimierung Info' },
688
+ 'I.114': { en: 'Energy balance info', de: 'Energiebilanz Info' },
689
+ 'I.115': { en: 'Runtime statistics info', de: 'Laufzeitstatistik Info' },
690
+ // ── Fault codes (F.) ──
691
+ 'F.0': { en: 'No fault', de: 'Kein Fehler' },
692
+ 'F.1': { en: 'Burner fault — no ignition', de: 'Brennerstörung — keine Zündung' },
693
+ 'F.2': { en: 'Flame signal lost', de: 'Flammensignal verloren' },
694
+ 'F.3': { en: 'Ignition fault', de: 'Zündfehler' },
695
+ 'F.4': { en: 'Safety chain open', de: 'Sicherheitskette offen' },
696
+ 'F.5': { en: 'Gas valve fault', de: 'Gasventil Fehler' },
697
+ 'F.7': { en: 'Flue gas sensor fault', de: 'Abgastemperaturfühler Fehler' },
698
+ 'F.9': { en: 'Supply sensor fault', de: 'Vorlauftemperaturfühler Fehler' },
699
+ 'F.10': { en: 'External sensor fault', de: 'Außenfühler Fehler' },
700
+ 'F.11': { en: 'Return sensor fault', de: 'Rücklauftemperaturfühler Fehler' },
701
+ 'F.12': { en: 'DHW sensor fault', de: 'Warmwasserfühler Fehler' },
702
+ 'F.20': { en: 'Safety temperature limiter', de: 'Sicherheitstemperaturbegrenzer' },
703
+ 'F.22': { en: 'Low water pressure fault', de: 'Wassermangel' },
704
+ 'F.23': { en: 'Pump fault — overtemp', de: 'Pumpe Fehler — Übertemperatur' },
705
+ 'F.24': { en: 'Pump circulation fault', de: 'Pumpe Zirkulationsfehler' },
706
+ 'F.28': { en: 'Ignition fault (gas)', de: 'Zündstörung (Gas)' },
707
+ 'F.29': { en: 'Flame fault after ignition', de: 'Flammenfehler nach Zündung' },
708
+ 'F.30': { en: 'STB safety shutdown', de: 'STB Sicherheitsabschaltung' },
709
+ 'F.31': { en: 'Boiler overtemperature', de: 'Kesselübertemperatur' },
710
+ 'F.32': { en: 'Flue overtemperature', de: 'Abgasübertemperatur' },
711
+ 'F.33': { en: 'Draft fault', de: 'Zugfehler' },
712
+ 'F.36': { en: 'Fan fault', de: 'Lüfter Fehler' },
713
+ 'F.40': { en: 'Compressor fault', de: 'Verdichter Fehler' },
714
+ 'F.41': { en: 'Refrigerant circuit fault', de: 'Kältemittelkreis Fehler' },
715
+ 'F.42': { en: 'Defrost fault', de: 'Abtaufehler' },
716
+ 'F.50': { en: 'Communication fault gateway', de: 'Kommunikationsfehler Gateway' },
717
+ 'F.51': { en: 'Communication fault controller', de: 'Kommunikationsfehler Regler' },
718
+ 'F.52': { en: 'Bus fault', de: 'Busfehler' },
719
+ 'F.60': { en: 'Expansion vessel fault', de: 'Ausdehnungsgefäß Fehler' },
720
+ 'F.73': { en: 'Water pressure too high', de: 'Wasserdruck zu hoch' },
721
+ 'F.74': { en: 'Water pressure too low', de: 'Wasserdruck zu niedrig' },
722
+ 'F.75': { en: 'Pump speed sensor fault', de: 'Pumpendrehzahlsensor Fehler' },
723
+ };
724
+
725
+ function translateCode(code) {
726
+ if (!code) return null;
727
+ const entry = VIESSMANN_CODES[code];
728
+ if (entry) return entry.en;
729
+ // Partial match fallback (e.g. S.134 → try S.13 → S.1)
730
+ const parts = code.split('.');
731
+ if (parts.length === 2) {
732
+ const shorter = parts[0] + '.' + parts[1].slice(0, -1);
733
+ if (VIESSMANN_CODES[shorter]) return VIESSMANN_CODES[shorter].en + ' (variant)';
734
+ }
735
+ return null;
736
+ }
737
+
738
+ // Extract device messages from the latest API data snapshot (if available via CSV extension)
739
+ // For now we show the codes from the last row that has error_code-like fields
740
+ // (future: read from a separate messages JSON file written by the plugin)
741
+ const lastBoiler = boilerRows.length ? boilerRows[boilerRows.length - 1] : null;
742
+
584
743
  const sc = (l,v,u='',badge='') => `<div class="sc"><div class="sl">${l}</div><div class="sv">${v!==null?v+u:'<span class="na">N/A</span>'} ${badge}</div></div>`;
585
744
  const badge = (cls,txt) => `<span class="badge badge-${cls}">${txt}</span>`;
586
745
  const genAt = new Date().toLocaleString('en-GB');
@@ -721,6 +880,18 @@ footer{text-align:center;font-size:10px;color:#bbb;padding:16px}
721
880
  ${dhwRows.length >= 2 ? `<div class="ch-tall"><canvas id="cDhw"></canvas></div>` : ''}
722
881
  </div>
723
882
 
883
+ ${gasForecast ? `
884
+ <div class="box">
885
+ <h2>⛽ Gas Forecast</h2>
886
+ <p class="note" style="margin-bottom:14px">Projection based on last ${gasForecast.daysUsed} day(s) of data · gas price: €${gasForecast.gasPrice}/m³ · trend: <strong>${gasForecast.trend === 'rising' ? '↑ Rising' : gasForecast.trend === 'falling' ? '↓ Falling' : '→ Stable'}</strong></p>
887
+ <div class="grid">
888
+ ${sc('Avg consumption/day', gasForecast.avgPerDay, ' m³')}
889
+ ${sc('Projected next 30 days', gasForecast.month30, ' m³', badge(gasForecast.trend === 'rising' ? 'warn' : 'good', '≈ €' + gasForecast.costMonth))}
890
+ ${sc('Annual estimate', gasForecast.annualEst, ' m³', badge('neutral', '≈ €' + gasForecast.costAnnual))}
891
+ </div>
892
+ <p class="note" style="margin-top:10px">⚠️ Annual estimate uses period average × 365 — add more days for seasonal accuracy. Use <code>--gasPriceEur</code> to set your tariff.</p>
893
+ </div>` : ''}
894
+
724
895
  ${energyRows.length >= 1 ? `
725
896
  <div class="box">
726
897
  <h2>Energy System</h2>
@@ -737,6 +908,49 @@ ${energyRows.length >= 1 ? `
737
908
  ${hasWallbox && energyRows.length >= 2 ? `<div class="ch" style="margin-top:14px"><canvas id="cWallbox"></canvas></div>` : ''}
738
909
  </div>` : ''}
739
910
 
911
+ </div>
912
+
913
+ <div class="box" id="device-messages">
914
+ <h2>🔔 Device Messages</h2>
915
+ <p class="note" style="margin-bottom:12px">Status, info and fault codes reported by the device. Translated from Viessmann service documentation.</p>
916
+ <div id="msg-list">
917
+ ${(() => {
918
+ // Read messages from viessmann-messages-<ID>.json if available (written by plugin)
919
+ const msgFile = INSTALLATION_ID
920
+ ? require('path').join(HB_PATH, 'viessmann-messages-' + INSTALLATION_ID + '.json')
921
+ : require('path').join(HB_PATH, 'viessmann-messages.json');
922
+ let messages = [];
923
+ try {
924
+ if (require('fs').existsSync(msgFile)) {
925
+ messages = JSON.parse(require('fs').readFileSync(msgFile, 'utf8'));
926
+ }
927
+ } catch(_) {}
928
+
929
+ if (!messages.length) {
930
+ return `<p class="note">No messages file found yet. Messages will appear here once the plugin writes <code>viessmann-messages-${INSTALLATION_ID || ''}.json</code>.</p>`;
931
+ }
932
+
933
+ return messages.slice(0, 20).map(m => {
934
+ const code = m.errorCode || m.code || '';
935
+ const desc = translateCode(code);
936
+ const ts = m.timestamp ? new Date(m.timestamp).toLocaleString('en-GB') : '';
937
+ const type = code.startsWith('F.') ? 'fault' : code.startsWith('I.') ? 'info' : 'status';
938
+ const bg = type === 'fault' ? '#fff3e0' : type === 'info' ? '#e8f4fd' : '#f1f8f1';
939
+ const br = type === 'fault' ? '#ffb74d' : type === 'info' ? '#90caf9' : '#a5d6a7';
940
+ const icon = type === 'fault' ? '⚠️' : type === 'info' ? 'ℹ️' : '✅';
941
+ return `<div style="display:flex;gap:12px;align-items:flex-start;padding:10px 12px;background:${bg};border-left:3px solid ${br};border-radius:4px;margin-bottom:6px;font-size:13px">
942
+ <span style="flex-shrink:0">${icon}</span>
943
+ <div style="flex:1">
944
+ <strong>${code}</strong>${desc ? ` — ${desc}` : ' — (unknown code)'}
945
+ ${ts ? `<span style="color:#888;font-size:11px;margin-left:8px">${ts}</span>` : ''}
946
+ ${m.busAddress ? `<span style="color:#aaa;font-size:11px;margin-left:8px">bus: ${m.busAddress}</span>` : ''}
947
+ </div>
948
+ </div>`;
949
+ }).join('');
950
+ })()}
951
+ </div>
952
+ </div>
953
+
740
954
  </div>
741
955
  <footer>homebridge-viessmann-vicare &nbsp;|&nbsp; ${CSV_FILE}</footer>
742
956