iobroker.utility-monitor 1.4.5 → 1.4.6
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 +72 -0
- package/admin/jsonConfig.json +130 -5
- package/io-package.json +14 -1
- package/lib/billingManager.js +43 -49
- package/lib/consumptionManager.js +63 -103
- package/lib/messagingHandler.js +108 -47
- package/lib/multiMeterManager.js +14 -8
- package/lib/stateManager.js +6 -5
- package/main.js +30 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,10 +61,55 @@ Gefällt dir dieser Adapter? Du kannst mich gerne mit einem Kaffee unterstützen
|
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
64
|
+
## ⚠️ Breaking Changes in Version 1.4.6
|
|
65
|
+
|
|
66
|
+
**WICHTIG:** Version 1.4.6 ändert die State-Struktur grundlegend!
|
|
67
|
+
|
|
68
|
+
### Was hat sich geändert?
|
|
69
|
+
|
|
70
|
+
**Vorher (bis 1.4.5):**
|
|
71
|
+
```
|
|
72
|
+
gas.consumption.daily
|
|
73
|
+
gas.costs.monthly
|
|
74
|
+
wasser.consumption.daily
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Jetzt (ab 1.4.6):**
|
|
78
|
+
```
|
|
79
|
+
gas.main.consumption.daily ← Hauptzähler mit Namen "main"
|
|
80
|
+
gas.main.costs.monthly
|
|
81
|
+
wasser.main.consumption.daily
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 🔧 Migration erforderlich
|
|
85
|
+
|
|
86
|
+
1. **Config öffnen**: Neue Felder "Name des Hauptzählers" für Gas/Wasser/Strom/PV
|
|
87
|
+
2. **Namen eingeben**: Standard ist "main" (empfohlen), oder eigener Name wie "wohnung", "haus"
|
|
88
|
+
3. **Skripte anpassen**: Alle Verweise auf States müssen angepasst werden
|
|
89
|
+
```javascript
|
|
90
|
+
// Alt:
|
|
91
|
+
getState('utility-monitor.0.gas.consumption.daily')
|
|
92
|
+
|
|
93
|
+
// Neu:
|
|
94
|
+
getState('utility-monitor.0.gas.main.consumption.daily')
|
|
95
|
+
```
|
|
96
|
+
4. **Visualisierungen updaten**: VIS, Grafana, etc. auf neue Pfade anpassen
|
|
97
|
+
|
|
98
|
+
### 💡 Warum diese Änderung?
|
|
99
|
+
|
|
100
|
+
- **Konsistenz**: Alle Zähler (Haupt + Zusätzlich) verwenden jetzt die gleiche Struktur
|
|
101
|
+
- **Flexibilität**: Hauptzähler kann jetzt frei benannt werden (z.B. "erdgeschoss", "gesamt")
|
|
102
|
+
- **Klarheit**: Keine Special-Case Logik mehr im Code
|
|
103
|
+
- **Multi-Meter**: Bessere Unterstützung für mehrere Zähler pro Typ
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
64
107
|
## 📊 Datenpunkte erklärt
|
|
65
108
|
|
|
66
109
|
Für jede aktivierte Verbrauchsart (Gas/Wasser/Strom/PV) werden folgende Ordner angelegt:
|
|
67
110
|
|
|
111
|
+
**Wichtig:** Seit Version 1.4.6 beinhalten alle Pfade den Zähler-Namen (z.B. `gas.main.*` statt `gas.*`)
|
|
112
|
+
|
|
68
113
|
### 🗂️ **consumption** (Verbrauch)
|
|
69
114
|
|
|
70
115
|
| Datenpunkt | Beschreibung | Beispiel |
|
|
@@ -229,6 +274,33 @@ Der Adapter setzt Zähler automatisch zurück:
|
|
|
229
274
|
|
|
230
275
|
## Changelog
|
|
231
276
|
|
|
277
|
+
### 1.4.6 (2026-01-20)
|
|
278
|
+
|
|
279
|
+
- **⚠️ BREAKING CHANGE:** 🔄 **Main Meter Naming** - Hauptzähler benötigt jetzt einen Namen:
|
|
280
|
+
- **State-Pfade geändert**: `gas.*` → `gas.METER_NAME.*` (z.B. `gas.main.*`)
|
|
281
|
+
- **Neue Config-Felder**: "Name des Hauptzählers" für Gas/Wasser/Strom/PV
|
|
282
|
+
- **Default-Name**: "main" (wird automatisch verwendet wenn leer gelassen)
|
|
283
|
+
- **Konsistente Struktur**: Alle Zähler (Haupt + Zusätzlich) verwenden jetzt `type.meterName.*`
|
|
284
|
+
- **Flexibilität**: Hauptzähler kann jetzt frei benannt werden (z.B. "wohnung", "erdgeschoss", "gesamt")
|
|
285
|
+
- **Keine Special-Cases**: Vereinfachter Code ohne `meterName === 'main'` Bedingungen
|
|
286
|
+
- **NEW:** 🔔 **Smart Notifications** - Zählerauswahl für Benachrichtigungen:
|
|
287
|
+
- Wähle pro Utility-Typ aus, welche Zähler benachrichtigt werden sollen
|
|
288
|
+
- Multi-Select Dropdown zeigt alle konfigurierten Zähler
|
|
289
|
+
- Wenn leer: Alle Zähler werden benachrichtigt (Standard)
|
|
290
|
+
- Wenn ausgewählt: Nur gewählte Zähler erhalten Benachrichtigungen
|
|
291
|
+
- Gilt für Abrechnungsende, Vertragswechsel und monatliche Berichte
|
|
292
|
+
- **IMPROVED:** 🏗️ **Code-Architektur** - Entfernung von 19 Special-Case Checks in 7 Dateien:
|
|
293
|
+
- Vereinfachte basePath-Berechnungen in multiMeterManager, billingManager, stateManager
|
|
294
|
+
- Vereinheitlichter Config-Zugriff (alle Meter nutzen `meter.config.contractStart`)
|
|
295
|
+
- HT/NT-Logik basiert jetzt auf `config.htNtEnabled` statt Meter-Name
|
|
296
|
+
- Button-Trigger erkennt nur noch einheitliche Pfadstruktur
|
|
297
|
+
- Legacy-Code entfernt: updateBillingCountdown, updateCurrentPrice jetzt per-meter
|
|
298
|
+
- **MIGRATION:** 📋 **Upgrade-Hinweise**:
|
|
299
|
+
- Bei Neuinstallation: Namen für Hauptzähler eingeben (oder "main" akzeptieren)
|
|
300
|
+
- Bei Upgrade: Adapter neu konfigurieren + Skripte/Visualisierungen anpassen
|
|
301
|
+
- History: Alte States bleiben erhalten, neue States werden parallel erstellt
|
|
302
|
+
- Empfehlung: "main" als Namen verwenden für einfachere Migration
|
|
303
|
+
|
|
232
304
|
### 1.4.5 (2026-01-20)
|
|
233
305
|
|
|
234
306
|
- **FIX:** 🐛 **Critical Multi-Meter Cost Calculation Bugs** - Comprehensive fixes for multi-meter functionality:
|
package/admin/jsonConfig.json
CHANGED
|
@@ -21,6 +21,19 @@
|
|
|
21
21
|
"lg": 6,
|
|
22
22
|
"xl": 6
|
|
23
23
|
},
|
|
24
|
+
"gasMainMeterName": {
|
|
25
|
+
"type": "text",
|
|
26
|
+
"label": "📝 Name des Hauptzählers",
|
|
27
|
+
"placeholder": "z.B. main, wohnung, haus, gesamt",
|
|
28
|
+
"help": "Technischer Name für den Hauptzähler. Wird Teil des State-Pfads (gas.DIESER_NAME.*). Nur Kleinbuchstaben und Zahlen, max 20 Zeichen.",
|
|
29
|
+
"default": "main",
|
|
30
|
+
"hidden": "!data.gasAktiv",
|
|
31
|
+
"sm": 12,
|
|
32
|
+
"md": 6,
|
|
33
|
+
"xs": 12,
|
|
34
|
+
"lg": 6,
|
|
35
|
+
"xl": 4
|
|
36
|
+
},
|
|
24
37
|
"_gasSensorHeader": {
|
|
25
38
|
"type": "header",
|
|
26
39
|
"text": "Sensor-Konfiguration",
|
|
@@ -451,6 +464,19 @@
|
|
|
451
464
|
"lg": 6,
|
|
452
465
|
"xl": 6
|
|
453
466
|
},
|
|
467
|
+
"wasserMainMeterName": {
|
|
468
|
+
"type": "text",
|
|
469
|
+
"label": "📝 Name des Hauptzählers",
|
|
470
|
+
"placeholder": "z.B. main, wohnung, haus, gesamt",
|
|
471
|
+
"help": "Technischer Name für den Hauptzähler. Wird Teil des State-Pfads (wasser.DIESER_NAME.*). Nur Kleinbuchstaben und Zahlen, max 20 Zeichen.",
|
|
472
|
+
"default": "main",
|
|
473
|
+
"hidden": "!data.wasserAktiv",
|
|
474
|
+
"sm": 12,
|
|
475
|
+
"md": 6,
|
|
476
|
+
"xs": 12,
|
|
477
|
+
"lg": 6,
|
|
478
|
+
"xl": 4
|
|
479
|
+
},
|
|
454
480
|
"_wasserSensorHeader": {
|
|
455
481
|
"type": "header",
|
|
456
482
|
"text": "Sensor-Konfiguration",
|
|
@@ -745,6 +771,19 @@
|
|
|
745
771
|
"lg": 6,
|
|
746
772
|
"xl": 6
|
|
747
773
|
},
|
|
774
|
+
"stromMainMeterName": {
|
|
775
|
+
"type": "text",
|
|
776
|
+
"label": "📝 Name des Hauptzählers",
|
|
777
|
+
"placeholder": "z.B. main, wohnung, haus, gesamt",
|
|
778
|
+
"help": "Technischer Name für den Hauptzähler. Wird Teil des State-Pfads (strom.DIESER_NAME.*). Nur Kleinbuchstaben und Zahlen, max 20 Zeichen.",
|
|
779
|
+
"default": "main",
|
|
780
|
+
"hidden": "!data.stromAktiv",
|
|
781
|
+
"sm": 12,
|
|
782
|
+
"md": 6,
|
|
783
|
+
"xs": 12,
|
|
784
|
+
"lg": 6,
|
|
785
|
+
"xl": 4
|
|
786
|
+
},
|
|
748
787
|
"_stromSensorHeader": {
|
|
749
788
|
"type": "header",
|
|
750
789
|
"text": "Sensor-Konfiguration",
|
|
@@ -1132,6 +1171,19 @@
|
|
|
1132
1171
|
"lg": 6,
|
|
1133
1172
|
"xl": 6
|
|
1134
1173
|
},
|
|
1174
|
+
"pvMainMeterName": {
|
|
1175
|
+
"type": "text",
|
|
1176
|
+
"label": "📝 Name des Hauptzählers",
|
|
1177
|
+
"placeholder": "z.B. main, einspeisung, pv",
|
|
1178
|
+
"help": "Technischer Name für den Hauptzähler. Wird Teil des State-Pfads (pv.DIESER_NAME.*). Nur Kleinbuchstaben und Zahlen, max 20 Zeichen.",
|
|
1179
|
+
"default": "main",
|
|
1180
|
+
"hidden": "!data.pvAktiv",
|
|
1181
|
+
"sm": 12,
|
|
1182
|
+
"md": 6,
|
|
1183
|
+
"xs": 12,
|
|
1184
|
+
"lg": 6,
|
|
1185
|
+
"xl": 4
|
|
1186
|
+
},
|
|
1135
1187
|
"_pvSensorHeader": {
|
|
1136
1188
|
"type": "header",
|
|
1137
1189
|
"text": "Sensor-Konfiguration",
|
|
@@ -1423,6 +1475,20 @@
|
|
|
1423
1475
|
"lg": 4,
|
|
1424
1476
|
"xl": 4
|
|
1425
1477
|
},
|
|
1478
|
+
"notificationGasMeters": {
|
|
1479
|
+
"type": "selectSendTo",
|
|
1480
|
+
"label": "Welche Gas-Zähler benachrichtigen?",
|
|
1481
|
+
"command": "getMeters",
|
|
1482
|
+
"jsonData": "{\"type\": \"gas\"}",
|
|
1483
|
+
"multiple": true,
|
|
1484
|
+
"help": "Wähle die Zähler aus, für die du Benachrichtigungen erhalten möchtest. Wenn leer, werden ALLE Zähler benachrichtigt.",
|
|
1485
|
+
"hidden": "!data.notificationEnabled || !data.notificationGasEnabled || !data.gasAktiv",
|
|
1486
|
+
"sm": 12,
|
|
1487
|
+
"md": 8,
|
|
1488
|
+
"xs": 12,
|
|
1489
|
+
"lg": 8,
|
|
1490
|
+
"xl": 8
|
|
1491
|
+
},
|
|
1426
1492
|
"notificationWasserEnabled": {
|
|
1427
1493
|
"type": "checkbox",
|
|
1428
1494
|
"label": "Wasser-Erinnerung",
|
|
@@ -1433,6 +1499,20 @@
|
|
|
1433
1499
|
"lg": 4,
|
|
1434
1500
|
"xl": 4
|
|
1435
1501
|
},
|
|
1502
|
+
"notificationWasserMeters": {
|
|
1503
|
+
"type": "selectSendTo",
|
|
1504
|
+
"label": "Welche Wasser-Zähler benachrichtigen?",
|
|
1505
|
+
"command": "getMeters",
|
|
1506
|
+
"jsonData": "{\"type\": \"water\"}",
|
|
1507
|
+
"multiple": true,
|
|
1508
|
+
"help": "Wähle die Zähler aus, für die du Benachrichtigungen erhalten möchtest. Wenn leer, werden ALLE Zähler benachrichtigt.",
|
|
1509
|
+
"hidden": "!data.notificationEnabled || !data.notificationWasserEnabled || !data.wasserAktiv",
|
|
1510
|
+
"sm": 12,
|
|
1511
|
+
"md": 8,
|
|
1512
|
+
"xs": 12,
|
|
1513
|
+
"lg": 8,
|
|
1514
|
+
"xl": 8
|
|
1515
|
+
},
|
|
1436
1516
|
"notificationStromEnabled": {
|
|
1437
1517
|
"type": "checkbox",
|
|
1438
1518
|
"label": "Strom-Erinnerung",
|
|
@@ -1443,6 +1523,20 @@
|
|
|
1443
1523
|
"lg": 4,
|
|
1444
1524
|
"xl": 4
|
|
1445
1525
|
},
|
|
1526
|
+
"notificationStromMeters": {
|
|
1527
|
+
"type": "selectSendTo",
|
|
1528
|
+
"label": "Welche Strom-Zähler benachrichtigen?",
|
|
1529
|
+
"command": "getMeters",
|
|
1530
|
+
"jsonData": "{\"type\": \"electricity\"}",
|
|
1531
|
+
"multiple": true,
|
|
1532
|
+
"help": "Wähle die Zähler aus, für die du Benachrichtigungen erhalten möchtest. Wenn leer, werden ALLE Zähler benachrichtigt.",
|
|
1533
|
+
"hidden": "!data.notificationEnabled || !data.notificationStromEnabled || !data.stromAktiv",
|
|
1534
|
+
"sm": 12,
|
|
1535
|
+
"md": 8,
|
|
1536
|
+
"xs": 12,
|
|
1537
|
+
"lg": 8,
|
|
1538
|
+
"xl": 8
|
|
1539
|
+
},
|
|
1446
1540
|
"notificationPVEnabled": {
|
|
1447
1541
|
"type": "checkbox",
|
|
1448
1542
|
"label": "PV-Erinnerung",
|
|
@@ -1452,6 +1546,20 @@
|
|
|
1452
1546
|
"xs": 12,
|
|
1453
1547
|
"lg": 4,
|
|
1454
1548
|
"xl": 4
|
|
1549
|
+
},
|
|
1550
|
+
"notificationPVMeters": {
|
|
1551
|
+
"type": "selectSendTo",
|
|
1552
|
+
"label": "Welche PV-Zähler benachrichtigen?",
|
|
1553
|
+
"command": "getMeters",
|
|
1554
|
+
"jsonData": "{\"type\": \"pv\"}",
|
|
1555
|
+
"multiple": true,
|
|
1556
|
+
"help": "Wähle die Zähler aus, für die du Benachrichtigungen erhalten möchtest. Wenn leer, werden ALLE Zähler benachrichtigt.",
|
|
1557
|
+
"hidden": "!data.notificationEnabled || !data.notificationPVEnabled || !data.pvAktiv",
|
|
1558
|
+
"sm": 12,
|
|
1559
|
+
"md": 8,
|
|
1560
|
+
"xs": 12,
|
|
1561
|
+
"lg": 8,
|
|
1562
|
+
"xl": 8
|
|
1455
1563
|
}
|
|
1456
1564
|
}
|
|
1457
1565
|
},
|
|
@@ -1466,7 +1574,7 @@
|
|
|
1466
1574
|
},
|
|
1467
1575
|
"_infoVersion": {
|
|
1468
1576
|
"type": "staticText",
|
|
1469
|
-
"text": "**Version:** 1.4.
|
|
1577
|
+
"text": "**Version:** 1.4.6\n\n**Autor:** fischi87\n\n⚠️ **Breaking Change in v1.4.6:** State-Pfade haben sich geändert! Siehe unten.",
|
|
1470
1578
|
"sm": 12,
|
|
1471
1579
|
"xs": 12,
|
|
1472
1580
|
"md": 12,
|
|
@@ -1492,7 +1600,7 @@
|
|
|
1492
1600
|
},
|
|
1493
1601
|
"_features": {
|
|
1494
1602
|
"type": "staticText",
|
|
1495
|
-
"text": "📊 **Verbrauchsüberwachung** - Gas, Wasser, Strom & PV/Einspeisung\n\n💰 **Automatische Kostenberechnung** - Mit Arbeitspreis & Grundgebühren\n\n☀️ **PV-Integration** - Überwachung von Einspeisung & Vergütung\n\n💳 **Abschlagsüberwachung** - Direkter Vergleich: Bezahlt vs. Verbraucht\n\n🔄 **Gas-Spezial** - Automatische m³ zu kWh Umrechnung\n\n⚡ **HT/NT-Unterstützung** - Volle Unterstützung für Doppeltarife\n\n📅 **Jahres-Historie** - Automatische Archivierung zum Vertragsbeginn",
|
|
1603
|
+
"text": "📊 **Verbrauchsüberwachung** - Gas, Wasser, Strom & PV/Einspeisung\n\n🔢 **Multi-Meter Support** *(NEU in 1.4.6)* - Mehrere Zähler pro Typ (z.B. Haus + Werkstatt)\n\n📛 **Konfigurierbare Namen** *(NEU in 1.4.6)* - Gib jedem Zähler einen individuellen Namen\n\n💰 **Automatische Kostenberechnung** - Mit Arbeitspreis & Grundgebühren\n\n☀️ **PV-Integration** - Überwachung von Einspeisung & Vergütung\n\n💳 **Abschlagsüberwachung** - Direkter Vergleich: Bezahlt vs. Verbraucht\n\n🔄 **Gas-Spezial** - Automatische m³ zu kWh Umrechnung\n\n⚡ **HT/NT-Unterstützung** - Volle Unterstützung für Doppeltarife\n\n📅 **Jahres-Historie** - Automatische Archivierung zum Vertragsbeginn\n\n🔔 **Smart Notifications** - Wähle für welche Zähler du benachrichtigt werden möchtest",
|
|
1496
1604
|
"sm": 12,
|
|
1497
1605
|
"xs": 12,
|
|
1498
1606
|
"md": 12,
|
|
@@ -1502,14 +1610,31 @@
|
|
|
1502
1610
|
"_divider2": {
|
|
1503
1611
|
"type": "divider"
|
|
1504
1612
|
},
|
|
1613
|
+
"_breakingHeader": {
|
|
1614
|
+
"type": "header",
|
|
1615
|
+
"text": "⚠️ Breaking Changes v1.4.6",
|
|
1616
|
+
"size": 4
|
|
1617
|
+
},
|
|
1618
|
+
"_breakingChanges": {
|
|
1619
|
+
"type": "staticText",
|
|
1620
|
+
"text": "**State-Pfade haben sich geändert:**\n\n**Vorher:** `gas.consumption.daily`, `gas.costs.yearly`\n\n**Neu:** `gas.METER_NAME.consumption.daily`, `gas.METER_NAME.costs.yearly`\n\n**Standardname:** Wenn du keinen Namen vergibst, wird automatisch \"main\" verwendet.\n\n**Was du tun musst:**\n• Skripte anpassen die States verwenden\n• Visualisierungen (VIS, Grafana) aktualisieren\n• History-Adapter neu konfigurieren\n\n**Mehrere Zähler:** Bei mehreren Zählern wird automatisch eine `totals/` Struktur mit Summen erstellt.",
|
|
1621
|
+
"sm": 12,
|
|
1622
|
+
"xs": 12,
|
|
1623
|
+
"md": 12,
|
|
1624
|
+
"lg": 12,
|
|
1625
|
+
"xl": 12
|
|
1626
|
+
},
|
|
1627
|
+
"_divider2a": {
|
|
1628
|
+
"type": "divider"
|
|
1629
|
+
},
|
|
1505
1630
|
"_usageHeader": {
|
|
1506
1631
|
"type": "header",
|
|
1507
|
-
"text": "Benachrichtigungen
|
|
1632
|
+
"text": "📬 Benachrichtigungen",
|
|
1508
1633
|
"size": 4
|
|
1509
1634
|
},
|
|
1510
1635
|
"_usage": {
|
|
1511
1636
|
"type": "staticText",
|
|
1512
|
-
"text": "**1. Abrechnungsende** 📅\n• Erinnert dich (z.B. 7 Tage vorher) daran, den Zählerstand abzulesen und den Zeitraum abzuschließen.\n\n**2. Vertragswechsel** 📋\n• Erinnert dich frühzeitig (z.B. 60 Tage vorher) daran, Tarife zu vergleichen oder die Kündigungsfrist zu prüfen.\n\n**3. Monatlicher Bericht** 📈\n• Optionaler Status-Bericht am 1. jedes Monats via Benachrichtigung.\n\n**💡 Tipp:** Nutze den **Test-Button** zum Prüfen deiner Einstellungen. Du bekommst sofort ein Popup mit dem Ergebnis!",
|
|
1637
|
+
"text": "**1. Abrechnungsende** 📅\n• Erinnert dich (z.B. 7 Tage vorher) daran, den Zählerstand abzulesen und den Zeitraum abzuschließen.\n\n**2. Vertragswechsel** 📋\n• Erinnert dich frühzeitig (z.B. 60 Tage vorher) daran, Tarife zu vergleichen oder die Kündigungsfrist zu prüfen.\n\n**3. Monatlicher Bericht** 📈\n• Optionaler Status-Bericht am 1. jedes Monats via Benachrichtigung.\n\n**4. Zählerauswahl** 🎯 *(NEU in 1.4.6)*\n• Wähle aus, für welche Zähler du Benachrichtigungen erhalten möchtest\n• Wenn leer: Alle Zähler werden benachrichtigt\n\n**💡 Tipp:** Nutze den **Test-Button** zum Prüfen deiner Einstellungen. Du bekommst sofort ein Popup mit dem Ergebnis!",
|
|
1513
1638
|
"sm": 12,
|
|
1514
1639
|
"xs": 12,
|
|
1515
1640
|
"md": 12,
|
|
@@ -1526,7 +1651,7 @@
|
|
|
1526
1651
|
},
|
|
1527
1652
|
"_statesInfo": {
|
|
1528
1653
|
"type": "staticText",
|
|
1529
|
-
"text": "**📅 billing/** (Abrechnung)\n• **daysRemaining** - Tage bis zum
|
|
1654
|
+
"text": "**⚠️ Neue Struktur ab v1.4.6:**\n\nAlle Datenpunkte sind jetzt unter `TYPE.METER_NAME.*` zu finden!\n\n**Beispiel Single-Meter:**\n• `gas.main.consumption.daily`\n• `gas.main.costs.totalYearly`\n• `gas.main.billing.closePeriod`\n\n**Beispiel Multi-Meter:**\n• `gas.haus.consumption.yearly` ← Zähler 1\n• `gas.werkstatt.consumption.yearly` ← Zähler 2\n• `gas.totals.consumption.yearly` ← Summe\n\n**📅 billing/** (Abrechnung)\n• **daysRemaining** - Tage bis zum Vertragsende\n• **closePeriod** - Button zum Zeitraum-Abschluss\n• **endReading** - Hier Endzählerstand eintragen\n\n**💰 costs/** (Kosten)\n• **balance** - Aktuelle Bilanz (Nachzahlung/Guthaben)\n• **totalYearly** - Gesamtkosten inkl. Gebühren\n• **yearly** - Nur Verbrauchskosten\n\n**📈 statistics/** (Statistiken)\n• **lastDay** - Gestern-Verbrauch\n• **averageDaily** - Durchschnitt pro Tag\n• **averageMonthly** - Durchschnitt pro Monat",
|
|
1530
1655
|
"sm": 12,
|
|
1531
1656
|
"xs": 12,
|
|
1532
1657
|
"md": 12,
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "utility-monitor",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.6",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.4.6": {
|
|
7
|
+
"en": "Breaking Change: Main meter now requires a name (default: 'main'). State paths changed from 'gas.*' to 'gas.METER_NAME.*'. All meters now use consistent naming structure.",
|
|
8
|
+
"de": "Breaking Change: Hauptzähler benötigt jetzt einen Namen (Standard: 'main'). State-Pfade geändert von 'gas.*' zu 'gas.METER_NAME.*'. Alle Zähler nutzen jetzt konsistente Namensstruktur."
|
|
9
|
+
},
|
|
6
10
|
"1.4.5": {
|
|
7
11
|
"en": "Fix: Critical multi-meter cost calculation bugs (main meter sync, basicCharge/paidTotal accumulation, annualFee as fixed yearly value). Fix: Balance formula corrected. Fix: Removed duplicate initialization causing sync issues.",
|
|
8
12
|
"de": "Fix: Kritische Multi-Meter Kostenberechnungsfehler (Hauptzähler-Sync, basicCharge/paidTotal Akkumulation, Jahresgebühr als fester Jahreswert). Fix: Balance-Formel korrigiert. Fix: Doppelte Initialisierung entfernt.",
|
|
@@ -91,6 +95,7 @@
|
|
|
91
95
|
},
|
|
92
96
|
"native": {
|
|
93
97
|
"gasAktiv": false,
|
|
98
|
+
"gasMainMeterName": "main",
|
|
94
99
|
"gasSensorDP": "",
|
|
95
100
|
"gasOffset": 0,
|
|
96
101
|
"gasInitialReading": 0,
|
|
@@ -100,6 +105,7 @@
|
|
|
100
105
|
"gasGrundgebuehr": 0,
|
|
101
106
|
"gasAbschlag": 0,
|
|
102
107
|
"wasserAktiv": false,
|
|
108
|
+
"wasserMainMeterName": "main",
|
|
103
109
|
"wasserSensorDP": "",
|
|
104
110
|
"wasserOffset": 0,
|
|
105
111
|
"wasserInitialReading": 0,
|
|
@@ -107,6 +113,7 @@
|
|
|
107
113
|
"wasserGrundgebuehr": 0,
|
|
108
114
|
"wasserAbschlag": 0,
|
|
109
115
|
"stromAktiv": false,
|
|
116
|
+
"stromMainMeterName": "main",
|
|
110
117
|
"stromSensorDP": "",
|
|
111
118
|
"stromOffset": 0,
|
|
112
119
|
"stromInitialReading": 0,
|
|
@@ -114,6 +121,7 @@
|
|
|
114
121
|
"stromGrundgebuehr": 0,
|
|
115
122
|
"stromAbschlag": 0,
|
|
116
123
|
"pvAktiv": false,
|
|
124
|
+
"pvMainMeterName": "main",
|
|
117
125
|
"pvSensorDP": "",
|
|
118
126
|
"pvOffset": 0,
|
|
119
127
|
"pvInitialReading": 0,
|
|
@@ -125,8 +133,13 @@
|
|
|
125
133
|
"notificationInstance": "",
|
|
126
134
|
"notificationDaysBefore": 30,
|
|
127
135
|
"notificationGasEnabled": false,
|
|
136
|
+
"notificationGasMeters": [],
|
|
128
137
|
"notificationWasserEnabled": false,
|
|
138
|
+
"notificationWasserMeters": [],
|
|
129
139
|
"notificationStromEnabled": false,
|
|
140
|
+
"notificationStromMeters": [],
|
|
141
|
+
"notificationPVEnabled": false,
|
|
142
|
+
"notificationPVMeters": [],
|
|
130
143
|
"notificationBillingEnabled": true,
|
|
131
144
|
"notificationBillingDays": 7,
|
|
132
145
|
"notificationChangeDays": 60
|
package/lib/billingManager.js
CHANGED
|
@@ -422,7 +422,7 @@ class BillingManager {
|
|
|
422
422
|
* @param {object} meter - Meter object from multiMeterManager
|
|
423
423
|
*/
|
|
424
424
|
async closeBillingPeriodForMeter(type, meter) {
|
|
425
|
-
const basePath =
|
|
425
|
+
const basePath = `${type}.${meter.name}`;
|
|
426
426
|
const label = meter.displayName || meter.name;
|
|
427
427
|
|
|
428
428
|
this.adapter.log.info(`🔔 Schließe Abrechnungszeitraum für ${basePath} (${label})...`);
|
|
@@ -438,14 +438,8 @@ class BillingManager {
|
|
|
438
438
|
return;
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
// Get contract date for THIS meter
|
|
442
|
-
|
|
443
|
-
if (meter.name === 'main') {
|
|
444
|
-
const configType = this.adapter.consumptionManager.getConfigType(type);
|
|
445
|
-
contractStartDate = this.adapter.config[`${configType}ContractStart`];
|
|
446
|
-
} else {
|
|
447
|
-
contractStartDate = meter.config?.contractStart;
|
|
448
|
-
}
|
|
441
|
+
// Get contract date for THIS meter (all meters have config.contractStart)
|
|
442
|
+
const contractStartDate = meter.config?.contractStart;
|
|
449
443
|
|
|
450
444
|
if (!contractStartDate) {
|
|
451
445
|
this.adapter.log.error(`❌ Kein Vertragsbeginn für ${basePath} konfiguriert.`);
|
|
@@ -497,42 +491,49 @@ class BillingManager {
|
|
|
497
491
|
}
|
|
498
492
|
|
|
499
493
|
/**
|
|
500
|
-
* Updates billing countdown
|
|
494
|
+
* Updates billing countdown for all meters of a type
|
|
495
|
+
* NOTE: Since v1.4.6, this updates ALL meters (main + additional)
|
|
501
496
|
*
|
|
502
497
|
* @param {string} type - Utility type
|
|
503
498
|
*/
|
|
504
499
|
async updateBillingCountdown(type) {
|
|
505
|
-
|
|
506
|
-
const
|
|
500
|
+
// Get all meters for this type
|
|
501
|
+
const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
507
502
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
503
|
+
// Update countdown for each meter based on its contract date
|
|
504
|
+
for (const meter of meters) {
|
|
505
|
+
const contractStart = meter.config?.contractStart;
|
|
511
506
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
507
|
+
if (!contractStart) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
516
510
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
511
|
+
const startDate = calculator.parseGermanDate(contractStart);
|
|
512
|
+
if (!startDate) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
520
515
|
|
|
521
|
-
|
|
522
|
-
nextAnniversary
|
|
523
|
-
|
|
516
|
+
const today = new Date();
|
|
517
|
+
const nextAnniversary = new Date(startDate);
|
|
518
|
+
nextAnniversary.setFullYear(today.getFullYear());
|
|
524
519
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
displayPeriodEnd.setDate(displayPeriodEnd.getDate() - 1);
|
|
520
|
+
if (nextAnniversary < today) {
|
|
521
|
+
nextAnniversary.setFullYear(today.getFullYear() + 1);
|
|
522
|
+
}
|
|
529
523
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
displayPeriodEnd.
|
|
534
|
-
|
|
535
|
-
|
|
524
|
+
const msPerDay = 1000 * 60 * 60 * 24;
|
|
525
|
+
const daysRemaining = Math.ceil((nextAnniversary.getTime() - today.getTime()) / msPerDay);
|
|
526
|
+
const displayPeriodEnd = new Date(nextAnniversary);
|
|
527
|
+
displayPeriodEnd.setDate(displayPeriodEnd.getDate() - 1);
|
|
528
|
+
|
|
529
|
+
const basePath = `${type}.${meter.name}`;
|
|
530
|
+
await this.adapter.setStateAsync(`${basePath}.billing.daysRemaining`, daysRemaining, true);
|
|
531
|
+
await this.adapter.setStateAsync(
|
|
532
|
+
`${basePath}.billing.periodEnd`,
|
|
533
|
+
displayPeriodEnd.toLocaleDateString('de-DE'),
|
|
534
|
+
true,
|
|
535
|
+
);
|
|
536
|
+
}
|
|
536
537
|
}
|
|
537
538
|
|
|
538
539
|
/**
|
|
@@ -580,21 +581,14 @@ class BillingManager {
|
|
|
580
581
|
// YEARLY RESET: Each meter resets individually based on ITS contract date
|
|
581
582
|
const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
582
583
|
for (const meter of meters) {
|
|
583
|
-
const basePath =
|
|
584
|
+
const basePath = `${type}.${meter.name}`;
|
|
584
585
|
const lastYearStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastYearStart`);
|
|
585
586
|
|
|
586
587
|
if (lastYearStartState?.val) {
|
|
587
588
|
const lastYearStartDate = new Date(lastYearStartState.val);
|
|
588
589
|
|
|
589
|
-
// Get contract date for THIS specific meter
|
|
590
|
-
|
|
591
|
-
if (meter.name === 'main') {
|
|
592
|
-
// Main meter: use adapter config
|
|
593
|
-
contractStartDate = this.adapter.config[`${configType}ContractStart`];
|
|
594
|
-
} else {
|
|
595
|
-
// Additional meter: use meter's individual config
|
|
596
|
-
contractStartDate = meter.config?.contractStart;
|
|
597
|
-
}
|
|
590
|
+
// Get contract date for THIS specific meter (all meters have config.contractStart)
|
|
591
|
+
const contractStartDate = meter.config?.contractStart;
|
|
598
592
|
|
|
599
593
|
if (contractStartDate) {
|
|
600
594
|
const contractStart = calculator.parseGermanDate(contractStartDate);
|
|
@@ -650,7 +644,7 @@ class BillingManager {
|
|
|
650
644
|
|
|
651
645
|
// Reset each meter
|
|
652
646
|
for (const meter of meters) {
|
|
653
|
-
const basePath =
|
|
647
|
+
const basePath = `${type}.${meter.name}`;
|
|
654
648
|
const label = meter.displayName || meter.name;
|
|
655
649
|
|
|
656
650
|
this.adapter.log.debug(`Resetting daily counter for ${basePath} (${label})`);
|
|
@@ -719,7 +713,7 @@ class BillingManager {
|
|
|
719
713
|
|
|
720
714
|
// Reset each meter
|
|
721
715
|
for (const meter of meters) {
|
|
722
|
-
const basePath =
|
|
716
|
+
const basePath = `${type}.${meter.name}`;
|
|
723
717
|
const label = meter.displayName || meter.name;
|
|
724
718
|
|
|
725
719
|
this.adapter.log.debug(`Resetting monthly counter for ${basePath} (${label})`);
|
|
@@ -777,7 +771,7 @@ class BillingManager {
|
|
|
777
771
|
|
|
778
772
|
// Reset each meter
|
|
779
773
|
for (const meter of meters) {
|
|
780
|
-
const basePath =
|
|
774
|
+
const basePath = `${type}.${meter.name}`;
|
|
781
775
|
const label = meter.displayName || meter.name;
|
|
782
776
|
|
|
783
777
|
this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
|
|
@@ -809,7 +803,7 @@ class BillingManager {
|
|
|
809
803
|
* @param {object} meter - Meter object from multiMeterManager
|
|
810
804
|
*/
|
|
811
805
|
async resetYearlyCountersForMeter(type, meter) {
|
|
812
|
-
const basePath =
|
|
806
|
+
const basePath = `${type}.${meter.name}`;
|
|
813
807
|
const label = meter.displayName || meter.name;
|
|
814
808
|
|
|
815
809
|
this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
|
|
@@ -47,8 +47,8 @@ class ConsumptionManager {
|
|
|
47
47
|
|
|
48
48
|
this.adapter.log.info(`Initializing ${type} monitoring...`);
|
|
49
49
|
|
|
50
|
-
//
|
|
51
|
-
|
|
50
|
+
// State structure is now created by MultiMeterManager per meter (v1.4.6)
|
|
51
|
+
// Old createUtilityStateStructure removed - states are created under type.meterName.*
|
|
52
52
|
|
|
53
53
|
const configType = this.getConfigType(type);
|
|
54
54
|
const sensorDPKey = `${configType}SensorDP`;
|
|
@@ -56,7 +56,7 @@ class ConsumptionManager {
|
|
|
56
56
|
|
|
57
57
|
if (!sensorDP) {
|
|
58
58
|
this.adapter.log.warn(`${type} is active but no sensor datapoint configured!`);
|
|
59
|
-
|
|
59
|
+
// Note: sensorActive state is now created per meter by MultiMeterManager
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -69,91 +69,34 @@ class ConsumptionManager {
|
|
|
69
69
|
this.adapter.log.info(`${type}: Managed with contract start: ${contractStartDateStr}`);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
//
|
|
73
|
-
this.adapter.
|
|
74
|
-
await this.adapter.setStateAsync(`${type}.info.sensorActive`, true, true);
|
|
75
|
-
this.adapter.log.debug(`Subscribed to ${type} sensor: ${sensorDP}`);
|
|
72
|
+
// Sensor subscription is now handled by MultiMeterManager per meter
|
|
73
|
+
this.adapter.log.debug(`${type} sensor will be subscribed by MultiMeterManager: ${sensorDP}`);
|
|
76
74
|
|
|
77
75
|
// Initialize all meters (main + additional) via MultiMeterManager
|
|
76
|
+
// This handles everything now: state creation, sensor subscription, costs calculation
|
|
78
77
|
if (this.adapter.multiMeterManager) {
|
|
79
78
|
await this.adapter.multiMeterManager.initializeType(type);
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// Initialize with current sensor value
|
|
90
|
-
try {
|
|
91
|
-
const sensorState = await this.adapter.getForeignStateAsync(sensorDP);
|
|
92
|
-
if (sensorState && sensorState.val !== null && typeof sensorState.val === 'number') {
|
|
93
|
-
await this.handleSensorUpdate(type, sensorDP, sensorState.val);
|
|
94
|
-
}
|
|
95
|
-
} catch (error) {
|
|
96
|
-
this.adapter.log.warn(`Could not read initial value from ${sensorDP}: ${error.message}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Initialize period start timestamps if not set
|
|
100
|
-
const now = Date.now();
|
|
101
|
-
const dayStart = await this.adapter.getStateAsync(`${type}.statistics.lastDayStart`);
|
|
102
|
-
if (!dayStart || !dayStart.val) {
|
|
103
|
-
await this.adapter.setStateAsync(`${type}.statistics.lastDayStart`, now, true);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const monthStart = await this.adapter.getStateAsync(`${type}.statistics.lastMonthStart`);
|
|
107
|
-
if (!monthStart || !monthStart.val) {
|
|
108
|
-
await this.adapter.setStateAsync(`${type}.statistics.lastMonthStart`, now, true);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const yearStart = await this.adapter.getStateAsync(`${type}.statistics.lastYearStart`);
|
|
112
|
-
if (!yearStart || !yearStart.val) {
|
|
113
|
-
// Determine year start based on contract date or January 1st
|
|
114
|
-
const contractStartKey = `${configType}ContractStart`;
|
|
115
|
-
const contractStartDateStr = this.adapter.config[contractStartKey];
|
|
116
|
-
|
|
117
|
-
let yearStartDate;
|
|
118
|
-
if (contractStartDateStr) {
|
|
119
|
-
const contractStart = calculator.parseGermanDate(contractStartDateStr);
|
|
120
|
-
if (contractStart && !isNaN(contractStart.getTime())) {
|
|
121
|
-
// Calculate last anniversary
|
|
122
|
-
const nowDate = new Date(now);
|
|
123
|
-
const currentYear = nowDate.getFullYear();
|
|
124
|
-
yearStartDate = new Date(currentYear, contractStart.getMonth(), contractStart.getDate(), 12, 0, 0);
|
|
125
|
-
|
|
126
|
-
// If anniversary is in the future this year, take last year
|
|
127
|
-
if (yearStartDate > nowDate) {
|
|
128
|
-
yearStartDate.setFullYear(currentYear - 1);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!yearStartDate) {
|
|
134
|
-
// Fallback: January 1st of current year
|
|
135
|
-
const nowDate = new Date(now);
|
|
136
|
-
yearStartDate = new Date(nowDate.getFullYear(), 0, 1, 12, 0, 0);
|
|
137
|
-
this.adapter.log.info(
|
|
138
|
-
`${type}: No contract start found. Setting initial year start to January 1st: ${yearStartDate.toLocaleDateString('de-DE')}`,
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
await this.adapter.setStateAsync(`${type}.statistics.lastYearStart`, yearStartDate.getTime(), true);
|
|
143
|
-
}
|
|
144
|
-
// Update current price
|
|
145
|
-
await this.updateCurrentPrice(type);
|
|
146
|
-
|
|
147
|
-
// Initial cost calculation (wichtig! Sonst bleiben Kosten bei 0)
|
|
148
|
-
if (typeof this.adapter.updateCosts === 'function') {
|
|
149
|
-
await this.adapter.updateCosts(type);
|
|
150
|
-
}
|
|
81
|
+
// Note: All initialization moved to MultiMeterManager in v1.4.6:
|
|
82
|
+
// - Sensor value restoration (per meter)
|
|
83
|
+
// - Period start timestamps (per meter)
|
|
84
|
+
// - Current price updates (per meter)
|
|
85
|
+
// - Cost calculations (per meter)
|
|
86
|
+
// Old type-level states (gas.info.*, gas.statistics.*) are no longer used
|
|
151
87
|
|
|
152
88
|
// Initialize yearly consumption from initial reading if set
|
|
89
|
+
// NOTE: This is now handled per meter by MultiMeterManager in v1.4.6
|
|
90
|
+
// This legacy code path should not execute for new setups, but is kept for safety
|
|
153
91
|
const initialReadingKey = `${configType}InitialReading`;
|
|
154
92
|
const initialReading = this.adapter.config[initialReadingKey] || 0;
|
|
155
93
|
|
|
156
|
-
if (initialReading > 0) {
|
|
94
|
+
if (initialReading > 0 && sensorDP) {
|
|
95
|
+
// Get the main meter name to use the correct path
|
|
96
|
+
const mainMeterNameKey = `${configType}MainMeterName`;
|
|
97
|
+
const mainMeterName = this.adapter.config[mainMeterNameKey] || 'main';
|
|
98
|
+
const basePath = `${type}.${mainMeterName}`;
|
|
99
|
+
|
|
157
100
|
const sensorState = await this.adapter.getForeignStateAsync(sensorDP);
|
|
158
101
|
if (sensorState && typeof sensorState.val === 'number') {
|
|
159
102
|
let currentRaw = sensorState.val;
|
|
@@ -173,7 +116,7 @@ class ConsumptionManager {
|
|
|
173
116
|
const zZahl = this.adapter.config.gasZahl || 0.95;
|
|
174
117
|
const yearlyVolume = yearlyConsumption;
|
|
175
118
|
yearlyConsumption = calculator.convertGasM3ToKWh(yearlyConsumption, brennwert, zZahl);
|
|
176
|
-
await this.adapter.setStateAsync(`${
|
|
119
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyVolume`, yearlyVolume, true);
|
|
177
120
|
this.adapter.log.info(
|
|
178
121
|
`Init yearly ${type}: ${yearlyConsumption.toFixed(2)} kWh = ${(currentRaw - initialReading).toFixed(2)} m³ (current: ${currentRaw.toFixed(2)} m³, initial: ${initialReading} m³)`,
|
|
179
122
|
);
|
|
@@ -183,17 +126,15 @@ class ConsumptionManager {
|
|
|
183
126
|
);
|
|
184
127
|
}
|
|
185
128
|
|
|
186
|
-
await this.adapter.setStateAsync(`${
|
|
129
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, yearlyConsumption, true);
|
|
187
130
|
if (typeof this.adapter.updateCosts === 'function') {
|
|
188
131
|
await this.adapter.updateCosts(type);
|
|
189
132
|
}
|
|
190
133
|
}
|
|
191
134
|
}
|
|
192
135
|
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
await this.adapter.updateBillingCountdown(type);
|
|
196
|
-
}
|
|
136
|
+
// Note: Billing countdown is now handled per meter by billingManager.updateBillingCountdown()
|
|
137
|
+
// which is called during checkPeriodResets()
|
|
197
138
|
|
|
198
139
|
this.adapter.log.debug(`Initial cost calculation completed for ${type}`);
|
|
199
140
|
}
|
|
@@ -201,17 +142,25 @@ class ConsumptionManager {
|
|
|
201
142
|
/**
|
|
202
143
|
* Handles sensor value updates
|
|
203
144
|
*
|
|
145
|
+
* NOTE: This method is DEPRECATED since v1.4.6!
|
|
146
|
+
* All sensor updates are now handled by MultiMeterManager.handleSensorUpdate()
|
|
147
|
+
* This method remains only as fallback but should NEVER be called in normal operation.
|
|
148
|
+
*
|
|
204
149
|
* @param {string} type - Utility type
|
|
205
150
|
* @param {string} sensorDP - Sensor datapoint ID
|
|
206
151
|
* @param {number} value - New sensor value
|
|
207
152
|
*/
|
|
208
153
|
async handleSensorUpdate(type, sensorDP, value) {
|
|
154
|
+
this.adapter.log.warn(
|
|
155
|
+
`consumptionManager.handleSensorUpdate() called - this is deprecated! All sensors should be handled by MultiMeterManager.`,
|
|
156
|
+
);
|
|
157
|
+
|
|
209
158
|
if (typeof value !== 'number' || value < 0) {
|
|
210
159
|
this.adapter.log.warn(`Invalid sensor value for ${type}: ${value}`);
|
|
211
160
|
return;
|
|
212
161
|
}
|
|
213
162
|
|
|
214
|
-
this.adapter.log.debug(`Sensor update for ${type}: ${value}`);
|
|
163
|
+
this.adapter.log.debug(`[DEPRECATED] Sensor update for ${type}: ${value}`);
|
|
215
164
|
|
|
216
165
|
const now = Date.now();
|
|
217
166
|
let consumption = value;
|
|
@@ -371,36 +320,47 @@ class ConsumptionManager {
|
|
|
371
320
|
}
|
|
372
321
|
|
|
373
322
|
/**
|
|
374
|
-
* Updates the current price display
|
|
323
|
+
* Updates the current price display for all meters of a type
|
|
324
|
+
* NOTE: Since v1.4.6, this updates ALL meters (main + additional)
|
|
325
|
+
* Only main meters support HT/NT, additional meters have fixed price
|
|
375
326
|
*
|
|
376
327
|
* @param {string} type - Utility type
|
|
377
328
|
*/
|
|
378
329
|
async updateCurrentPrice(type) {
|
|
379
330
|
const configType = this.getConfigType(type);
|
|
380
331
|
|
|
381
|
-
//
|
|
382
|
-
const
|
|
383
|
-
const htNtEnabled = this.adapter.config[htNtEnabledKey] || false;
|
|
332
|
+
// Get all meters for this type
|
|
333
|
+
const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
384
334
|
|
|
385
|
-
|
|
386
|
-
|
|
335
|
+
for (const meter of meters) {
|
|
336
|
+
let tariffName = 'Standard';
|
|
337
|
+
let activePrice = 0;
|
|
387
338
|
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
339
|
+
// Check if this meter has HT/NT enabled
|
|
340
|
+
const htNtEnabled = meter.config?.htNtEnabled || false;
|
|
341
|
+
|
|
342
|
+
if (htNtEnabled) {
|
|
343
|
+
const isHT = calculator.isHTTime(this.adapter.config, configType);
|
|
344
|
+
if (isHT) {
|
|
345
|
+
activePrice = this.adapter.config[`${configType}HtPrice`] || 0;
|
|
346
|
+
tariffName = 'Haupttarif (HT)';
|
|
347
|
+
} else {
|
|
348
|
+
activePrice = this.adapter.config[`${configType}NtPrice`] || 0;
|
|
349
|
+
tariffName = 'Nebentarif (NT)';
|
|
350
|
+
}
|
|
393
351
|
} else {
|
|
394
|
-
|
|
395
|
-
|
|
352
|
+
// Use meter's configured price
|
|
353
|
+
activePrice = meter.config?.preis || 0;
|
|
396
354
|
}
|
|
397
|
-
} else {
|
|
398
|
-
const priceKey = `${configType}Preis`;
|
|
399
|
-
activePrice = this.adapter.config[priceKey] || 0;
|
|
400
|
-
}
|
|
401
355
|
|
|
402
|
-
|
|
403
|
-
|
|
356
|
+
const basePath = `${type}.${meter.name}`;
|
|
357
|
+
await this.adapter.setStateAsync(
|
|
358
|
+
`${basePath}.info.currentPrice`,
|
|
359
|
+
calculator.roundToDecimals(activePrice, 4),
|
|
360
|
+
true,
|
|
361
|
+
);
|
|
362
|
+
await this.adapter.setStateAsync(`${basePath}.info.currentTariff`, tariffName, true);
|
|
363
|
+
}
|
|
404
364
|
}
|
|
405
365
|
}
|
|
406
366
|
|
package/lib/messagingHandler.js
CHANGED
|
@@ -24,7 +24,36 @@ class MessagingHandler {
|
|
|
24
24
|
|
|
25
25
|
this.adapter.log.debug(`[onMessage] Received command: ${obj.command} from ${obj.from}`);
|
|
26
26
|
|
|
27
|
-
if (obj.command === '
|
|
27
|
+
if (obj.command === 'getMeters') {
|
|
28
|
+
try {
|
|
29
|
+
// Get the utility type from the message
|
|
30
|
+
const type = obj.message?.type;
|
|
31
|
+
|
|
32
|
+
if (!type) {
|
|
33
|
+
this.adapter.sendTo(obj.from, obj.command, [], obj.callback);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Get all meters for this type from multiMeterManager
|
|
38
|
+
const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
39
|
+
|
|
40
|
+
if (meters.length === 0) {
|
|
41
|
+
this.adapter.sendTo(obj.from, obj.command, [], obj.callback);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Build options array: [{ value: "main", label: "Hauptzähler (main)" }, ...]
|
|
46
|
+
const result = meters.map(meter => ({
|
|
47
|
+
value: meter.name,
|
|
48
|
+
label: meter.displayName ? `${meter.displayName} (${meter.name})` : meter.name,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
this.adapter.sendTo(obj.from, obj.command, result, obj.callback);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.adapter.log.error(`Error in getMeters callback: ${error.message}`);
|
|
54
|
+
this.adapter.sendTo(obj.from, obj.command, [], obj.callback);
|
|
55
|
+
}
|
|
56
|
+
} else if (obj.command === 'getInstances') {
|
|
28
57
|
try {
|
|
29
58
|
const instances = await this.adapter.getForeignObjectsAsync('system.adapter.*', 'instance');
|
|
30
59
|
const messengerTypes = [
|
|
@@ -163,43 +192,66 @@ class MessagingHandler {
|
|
|
163
192
|
continue;
|
|
164
193
|
}
|
|
165
194
|
|
|
166
|
-
// Get
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
//
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
`📅 Datum: ${periodEnd}\n\n` +
|
|
182
|
-
`Bitte trage den Zählerstand rechtzeitig ein:\n` +
|
|
183
|
-
`1️⃣ Datenpunkt: ${type}.billing.endReading\n` +
|
|
184
|
-
`2️⃣ Zeitraum abschließen: ${type}.billing.closePeriod = true`;
|
|
185
|
-
|
|
186
|
-
await this.sendNotification(type, message, 'billing');
|
|
187
|
-
}
|
|
195
|
+
// Get all meters for this type (main + additional)
|
|
196
|
+
const allMeters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
197
|
+
if (allMeters.length === 0) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Filter meters based on configuration
|
|
202
|
+
// If notificationXXXMeters is empty or undefined, notify ALL meters
|
|
203
|
+
const configKey = `notification${configType.charAt(0).toUpperCase() + configType.slice(1)}Meters`;
|
|
204
|
+
const selectedMeters = this.adapter.config[configKey];
|
|
205
|
+
|
|
206
|
+
let metersToNotify = allMeters;
|
|
207
|
+
if (selectedMeters && Array.isArray(selectedMeters) && selectedMeters.length > 0) {
|
|
208
|
+
// Filter: only notify selected meters
|
|
209
|
+
metersToNotify = allMeters.filter(meter => selectedMeters.includes(meter.name));
|
|
188
210
|
}
|
|
189
211
|
|
|
190
|
-
//
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
const
|
|
212
|
+
// Check notifications for each meter individually
|
|
213
|
+
for (const meter of metersToNotify) {
|
|
214
|
+
const basePath = `${type}.${meter.name}`;
|
|
215
|
+
const meterLabel = meter.displayName || meter.name;
|
|
216
|
+
|
|
217
|
+
// Get current days remaining for THIS meter
|
|
218
|
+
const daysRemainingState = await this.adapter.getStateAsync(`${basePath}.billing.daysRemaining`);
|
|
219
|
+
const daysRemaining = typeof daysRemainingState?.val === 'number' ? daysRemainingState.val : 999;
|
|
220
|
+
const periodEndState = await this.adapter.getStateAsync(`${basePath}.billing.periodEnd`);
|
|
221
|
+
const periodEnd = periodEndState?.val || '--.--.----';
|
|
222
|
+
|
|
223
|
+
// 1. BILLING END REMINDER (Zählerstand ablesen)
|
|
224
|
+
if (this.adapter.config.notificationBillingEnabled) {
|
|
225
|
+
const billingSent = await this.adapter.getStateAsync(`${basePath}.billing.notificationSent`);
|
|
226
|
+
const billingDaysThreshold = this.adapter.config.notificationBillingDays || 7;
|
|
227
|
+
|
|
228
|
+
if (billingSent?.val !== true && daysRemaining <= billingDaysThreshold) {
|
|
229
|
+
const message =
|
|
230
|
+
`🔔 *Nebenkosten-Monitor: Zählerstand ablesen*\n\n` +
|
|
231
|
+
`Dein Abrechnungszeitraum für *${typesDe[type]} (${meterLabel})* endet in ${daysRemaining} Tagen!\n\n` +
|
|
232
|
+
`📅 Datum: ${periodEnd}\n\n` +
|
|
233
|
+
`Bitte trage den Zählerstand rechtzeitig ein:\n` +
|
|
234
|
+
`1️⃣ Datenpunkt: ${basePath}.billing.endReading\n` +
|
|
235
|
+
`2️⃣ Zeitraum abschließen: ${basePath}.billing.closePeriod = true`;
|
|
236
|
+
|
|
237
|
+
await this.sendNotification(basePath, message, 'billing');
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 2. CONTRACT CHANGE REMINDER (Tarif wechseln / Kündigungsfrist)
|
|
242
|
+
if (this.adapter.config.notificationChangeEnabled) {
|
|
243
|
+
const changeSent = await this.adapter.getStateAsync(`${basePath}.billing.notificationChangeSent`);
|
|
244
|
+
const changeDaysThreshold = this.adapter.config.notificationChangeDays || 60;
|
|
194
245
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
246
|
+
if (changeSent?.val !== true && daysRemaining <= changeDaysThreshold) {
|
|
247
|
+
const message =
|
|
248
|
+
`💡 *Nebenkosten-Monitor: Tarif-Check*\n\n` +
|
|
249
|
+
`Dein Vertrag für *${typesDe[type]} (${meterLabel})* endet am ${periodEnd}.\n\n` +
|
|
250
|
+
`⏰ Noch ${daysRemaining} Tage bis zum Ende des Zeitraums.\n\n` +
|
|
251
|
+
`Jetzt ist ein guter Zeitpunkt, um Preise zu vergleichen oder die Kündigungsfrist zu prüfen! 💸`;
|
|
201
252
|
|
|
202
|
-
|
|
253
|
+
await this.sendNotification(basePath, message, 'change');
|
|
254
|
+
}
|
|
203
255
|
}
|
|
204
256
|
}
|
|
205
257
|
}
|
|
@@ -258,16 +310,24 @@ class MessagingHandler {
|
|
|
258
310
|
// Multi-meter: use totals
|
|
259
311
|
yearlyState = await this.adapter.getStateAsync(`${type}.totals.consumption.yearly`);
|
|
260
312
|
totalYearlyState = await this.adapter.getStateAsync(`${type}.totals.costs.totalYearly`);
|
|
261
|
-
// Balance/paidTotal not available in totals, use
|
|
262
|
-
|
|
263
|
-
|
|
313
|
+
// Balance/paidTotal not available in totals, use first meter as representative
|
|
314
|
+
const firstMeter = meters[0];
|
|
315
|
+
if (firstMeter) {
|
|
316
|
+
paidTotalState = await this.adapter.getStateAsync(`${type}.${firstMeter.name}.costs.paidTotal`);
|
|
317
|
+
balanceState = await this.adapter.getStateAsync(`${type}.${firstMeter.name}.costs.balance`);
|
|
318
|
+
}
|
|
264
319
|
message += `(${meters.length} Zähler gesamt)\\n`;
|
|
320
|
+
} else if (meters.length === 1) {
|
|
321
|
+
// Single meter: use first meter values (new path structure)
|
|
322
|
+
const meter = meters[0];
|
|
323
|
+
const basePath = `${type}.${meter.name}`;
|
|
324
|
+
yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
|
|
325
|
+
totalYearlyState = await this.adapter.getStateAsync(`${basePath}.costs.totalYearly`);
|
|
326
|
+
paidTotalState = await this.adapter.getStateAsync(`${basePath}.costs.paidTotal`);
|
|
327
|
+
balanceState = await this.adapter.getStateAsync(`${basePath}.costs.balance`);
|
|
265
328
|
} else {
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
totalYearlyState = await this.adapter.getStateAsync(`${type}.costs.totalYearly`);
|
|
269
|
-
paidTotalState = await this.adapter.getStateAsync(`${type}.costs.paidTotal`);
|
|
270
|
-
balanceState = await this.adapter.getStateAsync(`${type}.costs.balance`);
|
|
329
|
+
// No meters configured - skip this type
|
|
330
|
+
continue;
|
|
271
331
|
}
|
|
272
332
|
|
|
273
333
|
let val = yearlyState?.val || 0;
|
|
@@ -310,14 +370,14 @@ class MessagingHandler {
|
|
|
310
370
|
/**
|
|
311
371
|
* Helper to send notification and mark as sent
|
|
312
372
|
*
|
|
313
|
-
* @param {string}
|
|
373
|
+
* @param {string} pathOrType - Full path like "gas.main" or just "system" for reports
|
|
314
374
|
* @param {string} message - Message text
|
|
315
375
|
* @param {string} reminderType - billing, change, or report
|
|
316
376
|
*/
|
|
317
|
-
async sendNotification(
|
|
377
|
+
async sendNotification(pathOrType, message, reminderType) {
|
|
318
378
|
try {
|
|
319
379
|
const instance = this.adapter.config.notificationInstance;
|
|
320
|
-
this.adapter.log.info(`Sending ${reminderType} notification for ${
|
|
380
|
+
this.adapter.log.info(`Sending ${reminderType} notification for ${pathOrType} via ${instance}`);
|
|
321
381
|
|
|
322
382
|
await this.adapter.sendToAsync(instance, 'send', {
|
|
323
383
|
text: message,
|
|
@@ -326,12 +386,13 @@ class MessagingHandler {
|
|
|
326
386
|
});
|
|
327
387
|
|
|
328
388
|
// Mark as sent (only for billing/change)
|
|
389
|
+
// pathOrType is now the full path like "gas.main" or "gas.werkstatt"
|
|
329
390
|
if (reminderType !== 'report') {
|
|
330
391
|
const stateKey = reminderType === 'change' ? 'notificationChangeSent' : 'notificationSent';
|
|
331
|
-
await this.adapter.setStateAsync(`${
|
|
392
|
+
await this.adapter.setStateAsync(`${pathOrType}.billing.${stateKey}`, true, true);
|
|
332
393
|
}
|
|
333
394
|
} catch (error) {
|
|
334
|
-
this.adapter.log.error(`Failed to send ${reminderType} notification for ${
|
|
395
|
+
this.adapter.log.error(`Failed to send ${reminderType} notification for ${pathOrType}: ${error.message}`);
|
|
335
396
|
}
|
|
336
397
|
}
|
|
337
398
|
}
|
package/lib/multiMeterManager.js
CHANGED
|
@@ -70,8 +70,14 @@ class MultiMeterManager {
|
|
|
70
70
|
// Main meter (always present if type is active)
|
|
71
71
|
const mainActive = this.adapter.config[`${configType}Aktiv`];
|
|
72
72
|
if (mainActive) {
|
|
73
|
+
// Get main meter name from config and normalize
|
|
74
|
+
const mainMeterName = this.adapter.config[`${configType}MainMeterName`] || 'main';
|
|
75
|
+
const normalizedName = this.normalizeMeterName(mainMeterName);
|
|
76
|
+
const displayName = mainMeterName; // Original name for display
|
|
77
|
+
|
|
73
78
|
meters.push({
|
|
74
|
-
name:
|
|
79
|
+
name: normalizedName,
|
|
80
|
+
displayName: displayName,
|
|
75
81
|
config: {
|
|
76
82
|
sensorDP: this.adapter.config[`${configType}SensorDP`],
|
|
77
83
|
preis: parseConfigNumber(this.adapter.config[`${configType}Preis`], 0),
|
|
@@ -173,7 +179,7 @@ class MultiMeterManager {
|
|
|
173
179
|
* @returns {Promise<void>}
|
|
174
180
|
*/
|
|
175
181
|
async initializeMeter(type, meterName, config, displayName) {
|
|
176
|
-
const basePath =
|
|
182
|
+
const basePath = `${type}.${meterName}`;
|
|
177
183
|
const label = displayName || meterName;
|
|
178
184
|
|
|
179
185
|
this.adapter.log.info(`Initializing ${type} meter: ${label}`);
|
|
@@ -349,7 +355,7 @@ class MultiMeterManager {
|
|
|
349
355
|
return;
|
|
350
356
|
}
|
|
351
357
|
|
|
352
|
-
const basePath =
|
|
358
|
+
const basePath = `${type}.${meterName}`;
|
|
353
359
|
this.adapter.log.debug(`Sensor update for ${basePath}: ${value}`);
|
|
354
360
|
|
|
355
361
|
// Get meter config
|
|
@@ -491,14 +497,14 @@ class MultiMeterManager {
|
|
|
491
497
|
* @param {object} config - Meter configuration
|
|
492
498
|
*/
|
|
493
499
|
async updateCurrentPrice(type, meterName, config) {
|
|
494
|
-
const basePath =
|
|
500
|
+
const basePath = `${type}.${meterName}`;
|
|
495
501
|
const configType = this.getConfigType(type);
|
|
496
502
|
|
|
497
503
|
let tariffName = 'Standard';
|
|
498
504
|
let activePrice = config.preis || 0;
|
|
499
505
|
|
|
500
|
-
// Only
|
|
501
|
-
if (
|
|
506
|
+
// Only meters with HT/NT enabled support it
|
|
507
|
+
if (config.htNtEnabled) {
|
|
502
508
|
const isHT = calculator.isHTTime(this.adapter.config, configType);
|
|
503
509
|
if (isHT) {
|
|
504
510
|
activePrice = this.adapter.config[`${configType}HtPrice`] || 0;
|
|
@@ -526,7 +532,7 @@ class MultiMeterManager {
|
|
|
526
532
|
* @param {object} config - Meter configuration
|
|
527
533
|
*/
|
|
528
534
|
async updateCosts(type, meterName, config) {
|
|
529
|
-
const basePath =
|
|
535
|
+
const basePath = `${type}.${meterName}`;
|
|
530
536
|
|
|
531
537
|
// Get consumption values
|
|
532
538
|
const daily = (await this.adapter.getStateAsync(`${basePath}.consumption.daily`))?.val || 0;
|
|
@@ -626,7 +632,7 @@ class MultiMeterManager {
|
|
|
626
632
|
let totalCostsYearly = 0;
|
|
627
633
|
|
|
628
634
|
for (const meter of meters) {
|
|
629
|
-
const basePath =
|
|
635
|
+
const basePath = `${type}.${meter.name}`;
|
|
630
636
|
|
|
631
637
|
totalDaily += (await this.adapter.getStateAsync(`${basePath}.consumption.daily`))?.val || 0;
|
|
632
638
|
totalMonthly += (await this.adapter.getStateAsync(`${basePath}.consumption.monthly`))?.val || 0;
|
package/lib/stateManager.js
CHANGED
|
@@ -817,11 +817,12 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
817
817
|
async function createMeterStructure(adapter, type, meterName, _config = {}) {
|
|
818
818
|
const isGas = type === 'gas';
|
|
819
819
|
|
|
820
|
+
// All meters now include the meter name in labels for consistency
|
|
820
821
|
const labels = {
|
|
821
|
-
gas: { name:
|
|
822
|
-
water: { name:
|
|
823
|
-
electricity: { name:
|
|
824
|
-
pv: { name:
|
|
822
|
+
gas: { name: `Gas (${meterName})`, unit: 'kWh', volumeUnit: 'm³' },
|
|
823
|
+
water: { name: `Wasser (${meterName})`, unit: 'm³', volumeUnit: 'm³' },
|
|
824
|
+
electricity: { name: `Strom (${meterName})`, unit: 'kWh', volumeUnit: 'kWh' },
|
|
825
|
+
pv: { name: `PV (${meterName})`, unit: 'kWh', volumeUnit: 'kWh' },
|
|
825
826
|
};
|
|
826
827
|
|
|
827
828
|
const label = labels[type];
|
|
@@ -832,7 +833,7 @@ async function createMeterStructure(adapter, type, meterName, _config = {}) {
|
|
|
832
833
|
// Fallback to prevent crash
|
|
833
834
|
return;
|
|
834
835
|
}
|
|
835
|
-
const basePath =
|
|
836
|
+
const basePath = `${type}.${meterName}`;
|
|
836
837
|
|
|
837
838
|
// Create main channel
|
|
838
839
|
await adapter.setObjectNotExistsAsync(basePath, {
|
package/main.js
CHANGED
|
@@ -195,20 +195,15 @@ class UtilityMonitor extends utils.Adapter {
|
|
|
195
195
|
if (id.includes('.billing.closePeriod') && state.val === true && !state.ack) {
|
|
196
196
|
const parts = id.split('.');
|
|
197
197
|
|
|
198
|
-
// Parse state ID: nebenkosten-monitor.0.gas.
|
|
199
|
-
// Remove adapter prefix: gas.
|
|
198
|
+
// Parse state ID: nebenkosten-monitor.0.gas.meterName.billing.closePeriod
|
|
199
|
+
// Remove adapter prefix: gas.meterName.billing.closePeriod
|
|
200
200
|
const statePathParts = parts.slice(2); // Remove "nebenkosten-monitor" and "0"
|
|
201
201
|
|
|
202
|
-
//
|
|
203
|
-
if (statePathParts.length ===
|
|
204
|
-
// Main meter: gas.billing.closePeriod
|
|
205
|
-
const type = statePathParts[0];
|
|
206
|
-
this.log.info(`User triggered billing period closure for ${type} (main meter)`);
|
|
207
|
-
await this.closeBillingPeriod(type);
|
|
208
|
-
} else if (statePathParts.length === 4) {
|
|
209
|
-
// Additional meter: gas.erdgeschoss.billing.closePeriod
|
|
202
|
+
// All meters now use: gas.meterName.billing.closePeriod (length === 4)
|
|
203
|
+
if (statePathParts.length === 4) {
|
|
210
204
|
const type = statePathParts[0];
|
|
211
205
|
const meterName = statePathParts[1];
|
|
206
|
+
|
|
212
207
|
this.log.info(`User triggered billing period closure for ${type}.${meterName}`);
|
|
213
208
|
|
|
214
209
|
// Find the meter object from multiMeterManager
|
|
@@ -221,24 +216,42 @@ class UtilityMonitor extends utils.Adapter {
|
|
|
221
216
|
this.log.error(`Meter "${meterName}" not found for type ${type}!`);
|
|
222
217
|
await this.setStateAsync(`${type}.${meterName}.billing.closePeriod`, false, true);
|
|
223
218
|
}
|
|
219
|
+
} else {
|
|
220
|
+
// Invalid path format
|
|
221
|
+
this.log.warn(`Invalid billing period closure trigger: ${id}`);
|
|
224
222
|
}
|
|
225
223
|
return;
|
|
226
224
|
}
|
|
227
225
|
|
|
228
226
|
// Check if this is an adjustment value change
|
|
227
|
+
// New structure: gas.meterName.adjustment.value (4 parts after namespace)
|
|
229
228
|
if (id.includes('.adjustment.value') && !state.ack) {
|
|
230
229
|
const parts = id.split('.');
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
await this.setStateAsync(`${type}.adjustment.applied`, Date.now(), true);
|
|
230
|
+
// Parse: nebenkosten-monitor.0.gas.meterName.adjustment.value
|
|
231
|
+
const statePathParts = parts.slice(2); // Remove "nebenkosten-monitor" and "0"
|
|
234
232
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
233
|
+
if (statePathParts.length === 3) {
|
|
234
|
+
// New structure: gas.meterName.adjustment.value
|
|
235
|
+
const type = statePathParts[0];
|
|
236
|
+
const meterName = statePathParts[1];
|
|
237
|
+
|
|
238
|
+
this.log.info(`Adjustment value changed for ${type}.${meterName}: ${state.val}`);
|
|
239
|
+
await this.setStateAsync(`${type}.${meterName}.adjustment.applied`, Date.now(), true);
|
|
240
|
+
|
|
241
|
+
// Update costs for THIS specific meter
|
|
242
|
+
if (this.multiMeterManager) {
|
|
243
|
+
const meters = this.multiMeterManager.getMetersForType(type);
|
|
244
|
+
const meter = meters.find(m => m.name === meterName);
|
|
245
|
+
if (meter) {
|
|
246
|
+
await this.multiMeterManager.updateCosts(type, meterName, meter.config);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
238
251
|
}
|
|
239
252
|
|
|
240
253
|
// Determine which utility this sensor belongs to
|
|
241
|
-
//
|
|
254
|
+
// All meters (including main) are now handled by multiMeterManager
|
|
242
255
|
if (this.multiMeterManager) {
|
|
243
256
|
const meterInfo = this.multiMeterManager.findMeterBySensor(id);
|
|
244
257
|
if (meterInfo && typeof state.val === 'number') {
|
|
@@ -246,19 +259,6 @@ class UtilityMonitor extends utils.Adapter {
|
|
|
246
259
|
return;
|
|
247
260
|
}
|
|
248
261
|
}
|
|
249
|
-
|
|
250
|
-
// Check main meter sensors
|
|
251
|
-
const types = ['gas', 'water', 'electricity', 'pv'];
|
|
252
|
-
for (const type of types) {
|
|
253
|
-
const configType = this.consumptionManager.getConfigType(type);
|
|
254
|
-
|
|
255
|
-
if (this.config[`${configType}Aktiv`] && this.config[`${configType}SensorDP`] === id) {
|
|
256
|
-
if (typeof state.val === 'number') {
|
|
257
|
-
await this.handleSensorUpdate(type, id, state.val);
|
|
258
|
-
}
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
/**
|