iobroker.utility-monitor 1.5.1 → 1.6.1
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 +78 -5
- package/io-package.json +31 -1
- package/lib/billingManager.js +173 -22
- package/lib/multiMeterManager.js +15 -9
- package/lib/state/history.js +95 -0
- package/lib/state/meter.js +605 -0
- package/lib/state/roles.js +16 -0
- package/lib/state/totals.js +136 -0
- package/lib/state/utility.js +650 -0
- package/lib/stateManager.js +8 -2046
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -108,6 +108,36 @@ wasser.main.consumption.daily
|
|
|
108
108
|
- **Klarheit**: Keine Special-Case Logik mehr im Code
|
|
109
109
|
- **Multi-Meter**: Bessere Unterstützung für mehrere Zähler pro Typ
|
|
110
110
|
- **CSV Import**: Einfaches Nachpflegen von historischen Daten via Drag-and-Drop im Admin-Interface
|
|
111
|
+
- **Strukturierte Statistiken (v1.6.0)**: Klare Trennung von Verbrauch, Kosten und Zeitstempeln
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## ⚠️ Breaking Changes in Version 1.6.0
|
|
116
|
+
|
|
117
|
+
**WICHTIG:** Version 1.6.0 strukturiert das Statistik-Objekt um!
|
|
118
|
+
|
|
119
|
+
### Was hat sich geändert?
|
|
120
|
+
|
|
121
|
+
**Vorher (bis 1.5.1):**
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
gas.main.statistics.lastDay
|
|
125
|
+
gas.main.statistics.lastMonth
|
|
126
|
+
gas.main.statistics.lastDayStart
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Jetzt (ab 1.6.0):**
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
gas.main.statistics.consumption.lastDay ← Verbrauchswerte
|
|
133
|
+
gas.main.statistics.cost.lastDay ← Kostenwerte (NEU!)
|
|
134
|
+
gas.main.statistics.timestamps.lastDayStart ← Zeitstempel der Resets
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 🔧 Migration erforderlich
|
|
138
|
+
|
|
139
|
+
1. **Skripte/VIS anpassen**: Falls du direkt auf Statistik-Datenpunkte zugreifst, musst du die Pfade anpassen.
|
|
140
|
+
2. **Kostenstatistiken**: Du profitierst nun von historischen Kostenübersichten (Tag/Woche/Monat).
|
|
111
141
|
|
|
112
142
|
---
|
|
113
143
|
|
|
@@ -212,13 +242,34 @@ Balance: +62,64 € → Nachzahlung
|
|
|
212
242
|
|
|
213
243
|
### 📈 **statistics** (Statistiken)
|
|
214
244
|
|
|
245
|
+
Seit Version 1.6.1 sind Statistiken in drei Unterkanäle unterteilt:
|
|
246
|
+
|
|
247
|
+
#### 📊 **consumption** (Verbrauchshistorie)
|
|
248
|
+
|
|
249
|
+
| Datenpunkt | Beschreibung |
|
|
250
|
+
| ---------------- | ------------------------------------ |
|
|
251
|
+
| `lastDay` | Verbrauch **gestern** (Vortag) |
|
|
252
|
+
| `lastWeek` | Verbrauch **letzte Woche** |
|
|
253
|
+
| `lastMonth` | Verbrauch **letzter Monat** |
|
|
254
|
+
| `lastYear` | Verbrauch **letztes Jahr** (Vorjahr) |
|
|
255
|
+
| `averageDaily` | Durchschnittlicher Tagesverbrauch |
|
|
256
|
+
| `averageMonthly` | Durchschnittlicher Monatsverbrauch |
|
|
257
|
+
|
|
258
|
+
#### 💰 **cost** (Kostenhistorie - NEU in 1.6.0)
|
|
259
|
+
|
|
260
|
+
| Datenpunkt | Beschreibung |
|
|
261
|
+
| ---------------- | --------------------------------- |
|
|
262
|
+
| `lastDay` | Kosten **gestern** (Vortag) |
|
|
263
|
+
| `lastWeek` | Kosten **letzte Woche** |
|
|
264
|
+
| `lastMonth` | Kosten **letzter Monat** |
|
|
265
|
+
| `lastYear` | Kosten **letztes Jahr** (Vorjahr) |
|
|
266
|
+
| `averageDaily` | Durchschnittliche Tageskosten |
|
|
267
|
+
| `averageMonthly` | Durchschnittliche Monatskosten |
|
|
268
|
+
|
|
269
|
+
#### 📅 **timestamps** (Reset-Zeitstempel)
|
|
270
|
+
|
|
215
271
|
| Datenpunkt | Beschreibung |
|
|
216
272
|
| ---------------- | ---------------------------------------- |
|
|
217
|
-
| `averageDaily` | Durchschnittlicher Tagesverbrauch |
|
|
218
|
-
| `averageMonthly` | Durchschnittlicher Monatsverbrauch |
|
|
219
|
-
| `lastDay` | Verbrauch **gestern** (Vortag) |
|
|
220
|
-
| `lastWeek` | Verbrauch **letzte Woche** |
|
|
221
|
-
| `lastMonth` | Verbrauch **letzter Monat** |
|
|
222
273
|
| `lastDayStart` | Letzter Tages-Reset (23:59 Uhr) |
|
|
223
274
|
| `lastWeekStart` | Letzter Wochen-Reset (Sonntag 23:59) |
|
|
224
275
|
| `lastMonthStart` | Letzter Monats-Reset (letzter Tag 23:59) |
|
|
@@ -308,6 +359,28 @@ Der Adapter setzt Zähler automatisch zurück:
|
|
|
308
359
|
|
|
309
360
|
## Changelog
|
|
310
361
|
|
|
362
|
+
### 1.6.1 (2026-01-28)
|
|
363
|
+
|
|
364
|
+
- **NEU:** 📊 **Erweiterte Jahresstatistiken** - Einführung von `lastYear` Datenpunkten in den Statistiken:
|
|
365
|
+
- `statistics.consumption.lastYear`: Gesamtverbrauch des Vorjahres
|
|
366
|
+
- `statistics.cost.lastYear`: Gesamtkosten des Vorjahres
|
|
367
|
+
- Unterstützung für HT/NT und Gas-Volumen in der Vorjahresansicht
|
|
368
|
+
- **NEU:** 🔄 **Automatisches Sichern** - Vorjahreswerte werden beim jährlichen Reset automatisch in die Statistik archiviert
|
|
369
|
+
- **FIX:** 🛠️ **Syntax & Einheiten** - Korrektur von Einheiten-Inkonsistenzen (speziell Wasser/m³) und Linter-Fehlern
|
|
370
|
+
- **DOCS:** 🌐 **Übersetzungen** - News-Einträge in alle unterstützten Sprachen übersetzt
|
|
371
|
+
|
|
372
|
+
### 1.6.0 (2026-01-28)
|
|
373
|
+
|
|
374
|
+
- **NEU:** 📊 **Strukturierte Statistiken** - Einführung von Unterkanälen für bessere Übersicht:
|
|
375
|
+
- `statistics.consumption`: Alle historischen Verbrauchswerte
|
|
376
|
+
- `statistics.cost`: Alle historischen Kostenwerte (Tag/Woche/Monat)
|
|
377
|
+
- `statistics.timestamps`: Alle Reset-Zeitstempel an einem Ort
|
|
378
|
+
- **NEU:** 💰 **Kostenstatistiken** - Verfolge deine Kosten nun auch für gestern, letzte Woche und letzten Monat
|
|
379
|
+
- **REFACTORING:** 🏗️ **Modulare State-Verwaltung**:
|
|
380
|
+
- `stateManager.js` wurde in spezialisierte Module aufgeteilt (`lib/state/`)
|
|
381
|
+
- Verbesserte Wartbarkeit und Testbarkeit
|
|
382
|
+
- **CLEANUP:** 🧹 **Bereinigung** - Automatische Entfernung veralteter Statistik-Datenpunkte beim ersten Start
|
|
383
|
+
|
|
311
384
|
### 1.5.1 (2026-01-26)
|
|
312
385
|
|
|
313
386
|
- **FIX:** 🕛 **Reset-Timing** - Automatische Resets werden nun um 23:59 Uhr ausgeführt (statt 00:00 Uhr)
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "utility-monitor",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.1",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.6.1": {
|
|
7
|
+
"en": "New statistics data points: lastDay, lastWeek, lastMonth, and lastYear for costs and consumption.",
|
|
8
|
+
"de": "Neue Statistik-Datenpunkte: lastDay, lastWeek, lastMonth, lastYear für Kosten und Verbrauch.",
|
|
9
|
+
"ru": "Новые точки статистических данных: lastDay, lastWeek, lastMonth и lastYear для затрат и потребления.",
|
|
10
|
+
"pt": "Novos pontos de dados estatísticos: lastDay, lastWeek, lastMonth e lastYear para custos e consumo.",
|
|
11
|
+
"nl": "Nieuwe statistiekgegevenspunten: lastDay, lastWeek, lastMonth en lastYear voor kosten en verbruik.",
|
|
12
|
+
"fr": "Nouveaux points de données statistiques : lastDay, lastWeek, lastMonth et lastYear pour les coûts et la consommation.",
|
|
13
|
+
"it": "Nuovi punti dati statistici: lastDay, lastWeek, lastMonth e lastYear per costi e consumi.",
|
|
14
|
+
"es": "Nuevos puntos de datos estadísticos: lastDay, lastWeek, lastMonth y lastYear para costes y consumo.",
|
|
15
|
+
"pl": "Nowe punkty danych statystycznych: lastDay, lastWeek, lastMonth i lastYear dla kosztów i zużycia.",
|
|
16
|
+
"uk": "Нові статистичні дані: lastDay, lastWeek, lastMonth і lastYear для витрат і споживання.",
|
|
17
|
+
"zh-cn": "新的统计数据点:成本和消耗的 lastDay、lastWeek、lastMonth 和 lastYear。"
|
|
18
|
+
},
|
|
19
|
+
"1.6.0": {
|
|
20
|
+
"en": "New: Structured statistics with consumption and cost sub-channels. New: Weekly and monthly cost tracking. Refactoring: Split stateManager.js into modular files. Cleanup: Removed redundant old statistics states.",
|
|
21
|
+
"de": "Neu: Strukturierte Statistiken mit Unterkanälen für Verbrauch und Kosten. Neu: Wöchentliche und monatliche Kostenverfolgung. Refactoring: stateManager.js in modulare Dateien aufgeteilt. Bereinigung: Veraltete Statistik-Datenpunkte entfernt."
|
|
22
|
+
},
|
|
23
|
+
"1.5.1": {
|
|
24
|
+
"en": "Fix: Reset timing and monthly calculation logic. Fix: JSDoc type definitions.",
|
|
25
|
+
"de": "Fix: Reset-Timing und monatliche Berechnungslogik. Fix: JSDoc Typ-Definitionen.",
|
|
26
|
+
"ru": "Исправление: время сброса и логика ежемесячного расчета. Исправление: определения типов JSDoc.",
|
|
27
|
+
"pt": "Correção: tempo de redefinição e lógica de cálculo mensal. Correção: definições de tipo JSDoc.",
|
|
28
|
+
"nl": "Fix: Reset timing en maandelijkse berekeningslogica. Fix: JSDoc type definities.",
|
|
29
|
+
"fr": "Correctif : synchronisation de la réinitialisation et logique de calcul mensuel. Correctif : définitions de type JSDoc.",
|
|
30
|
+
"it": "Correzione: tempi di ripristino e logica di calcolo mensile. Correzione: definizioni dei tipi JSDoc.",
|
|
31
|
+
"es": "Solución: sincronización de reinicio y lógica de cálculo mensual. Solución: definiciones de tipos de JSDoc.",
|
|
32
|
+
"pl": "Naprawa: czas resetowania i logika obliczeń miesięcznych. Naprawa: definicje typów JSDoc.",
|
|
33
|
+
"uk": "Виправлення: час скидання та логіка щомісячного розрахунку. Виправлення: визначення типів JSDoc.",
|
|
34
|
+
"zh-cn": "修复:重置时间和每月计算逻辑。修复:JSDoc 类型定义。"
|
|
35
|
+
},
|
|
6
36
|
"1.5.0": {
|
|
7
37
|
"en": "New: CSV Import feature with drag and drop support. New: Weekly tracking points. Fix: Resets now performed at 23:59. Refactoring: Modular backend architecture.",
|
|
8
38
|
"de": "Neu: CSV-Import mit Drag-and-Drop. Neu: Wöchentliche Datenpunkte. Fix: Resets werden jetzt um 23:59 Uhr ausgeführt. Refactoring: Modulare Backend-Architektur.",
|
package/lib/billingManager.js
CHANGED
|
@@ -171,7 +171,7 @@ class BillingManager {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
if (!startDate || isNaN(startDate.getTime())) {
|
|
174
|
-
const lastYearStart = await this.adapter.getStateAsync(`${type}.statistics.lastYearStart`);
|
|
174
|
+
const lastYearStart = await this.adapter.getStateAsync(`${type}.statistics.timestamps.lastYearStart`);
|
|
175
175
|
startDate = new Date(lastYearStart?.val || Date.now());
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -372,7 +372,11 @@ class BillingManager {
|
|
|
372
372
|
// even if the user closes the period early (e.g. 2 days before)
|
|
373
373
|
const thisYearAnniversary = new Date(startDate);
|
|
374
374
|
thisYearAnniversary.setFullYear(new Date().getFullYear());
|
|
375
|
-
await this.adapter.setStateAsync(
|
|
375
|
+
await this.adapter.setStateAsync(
|
|
376
|
+
`${type}.statistics.timestamps.lastYearStart`,
|
|
377
|
+
thisYearAnniversary.getTime(),
|
|
378
|
+
true,
|
|
379
|
+
);
|
|
376
380
|
|
|
377
381
|
this.adapter.log.info(`✅ Abrechnungszeitraum ${year} für ${type} erfolgreich abgeschlossen!`);
|
|
378
382
|
this.adapter.log.info(
|
|
@@ -526,7 +530,11 @@ class BillingManager {
|
|
|
526
530
|
// Update lastYearStart to contract anniversary
|
|
527
531
|
const thisYearAnniversary = new Date(startDate);
|
|
528
532
|
thisYearAnniversary.setFullYear(new Date().getFullYear());
|
|
529
|
-
await this.adapter.setStateAsync(
|
|
533
|
+
await this.adapter.setStateAsync(
|
|
534
|
+
`${basePath}.statistics.timestamps.lastYearStart`,
|
|
535
|
+
thisYearAnniversary.getTime(),
|
|
536
|
+
true,
|
|
537
|
+
);
|
|
530
538
|
|
|
531
539
|
// Update totals if multiple meters exist
|
|
532
540
|
const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
@@ -644,7 +652,7 @@ class BillingManager {
|
|
|
644
652
|
};
|
|
645
653
|
|
|
646
654
|
// DAILY RESET: Trigger at 23:59 if today's reset hasn't happened yet
|
|
647
|
-
const lastDayStart = await this.adapter.getStateAsync(`${basePath}.statistics.lastDayStart`);
|
|
655
|
+
const lastDayStart = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastDayStart`);
|
|
648
656
|
if (lastDayStart?.val) {
|
|
649
657
|
const lastResetTime = lastDayStart.val;
|
|
650
658
|
const alreadyResetToday = isValidResetToday(lastResetTime);
|
|
@@ -665,7 +673,7 @@ class BillingManager {
|
|
|
665
673
|
}
|
|
666
674
|
|
|
667
675
|
// WEEKLY RESET: Trigger at 23:59 on Sunday if this week's reset hasn't happened yet
|
|
668
|
-
const lastWeekStart = await this.adapter.getStateAsync(`${basePath}.statistics.lastWeekStart`);
|
|
676
|
+
const lastWeekStart = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`);
|
|
669
677
|
if (lastWeekStart?.val) {
|
|
670
678
|
const lastWeekTime = lastWeekStart.val;
|
|
671
679
|
const isSunday = nowDate.getDay() === 0; // 0 = Sunday
|
|
@@ -694,7 +702,9 @@ class BillingManager {
|
|
|
694
702
|
|
|
695
703
|
// MONTHLY RESET: Trigger at 23:59 on last day of month
|
|
696
704
|
if (meters.length > 0) {
|
|
697
|
-
const lastMonthStartState = await this.adapter.getStateAsync(
|
|
705
|
+
const lastMonthStartState = await this.adapter.getStateAsync(
|
|
706
|
+
`${basePath}.statistics.timestamps.lastMonthStart`,
|
|
707
|
+
);
|
|
698
708
|
if (lastMonthStartState?.val) {
|
|
699
709
|
const lastMonthTime = lastMonthStartState.val;
|
|
700
710
|
const lastMonthDate = new Date(lastMonthTime);
|
|
@@ -725,7 +735,7 @@ class BillingManager {
|
|
|
725
735
|
for (const meter of meters) {
|
|
726
736
|
const meterBasePath = `${type}.${meter.name}`;
|
|
727
737
|
const lastYearStartState = await this.adapter.getStateAsync(
|
|
728
|
-
`${meterBasePath}.statistics.lastYearStart`,
|
|
738
|
+
`${meterBasePath}.statistics.timestamps.lastYearStart`,
|
|
729
739
|
);
|
|
730
740
|
|
|
731
741
|
if (lastYearStartState?.val) {
|
|
@@ -835,7 +845,12 @@ class BillingManager {
|
|
|
835
845
|
const dailyValue = dailyState?.val || 0;
|
|
836
846
|
|
|
837
847
|
// Save last day consumption
|
|
838
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastDay`, dailyValue, true);
|
|
848
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastDay`, dailyValue, true);
|
|
849
|
+
|
|
850
|
+
// Save last day costs
|
|
851
|
+
const dailyCostState = await this.adapter.getStateAsync(`${basePath}.costs.daily`);
|
|
852
|
+
const dailyCostValue = dailyCostState?.val || 0;
|
|
853
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDay`, dailyCostValue, true);
|
|
839
854
|
|
|
840
855
|
await this.adapter.setStateAsync(`${basePath}.consumption.daily`, 0, true);
|
|
841
856
|
|
|
@@ -843,7 +858,11 @@ class BillingManager {
|
|
|
843
858
|
const dailyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.dailyVolume`);
|
|
844
859
|
const dailyVolumeValue = dailyVolume?.val || 0;
|
|
845
860
|
// Save last day volume for gas
|
|
846
|
-
await this.adapter.setStateAsync(
|
|
861
|
+
await this.adapter.setStateAsync(
|
|
862
|
+
`${basePath}.statistics.consumption.lastDayVolume`,
|
|
863
|
+
dailyVolumeValue,
|
|
864
|
+
true,
|
|
865
|
+
);
|
|
847
866
|
await this.adapter.setStateAsync(`${basePath}.consumption.dailyVolume`, 0, true);
|
|
848
867
|
}
|
|
849
868
|
|
|
@@ -853,23 +872,44 @@ class BillingManager {
|
|
|
853
872
|
if (htNtEnabled) {
|
|
854
873
|
const dailyHT = await this.adapter.getStateAsync(`${basePath}.consumption.dailyHT`);
|
|
855
874
|
const dailyNT = await this.adapter.getStateAsync(`${basePath}.consumption.dailyNT`);
|
|
856
|
-
await this.adapter.setStateAsync(
|
|
857
|
-
|
|
875
|
+
await this.adapter.setStateAsync(
|
|
876
|
+
`${basePath}.statistics.consumption.lastDayHT`,
|
|
877
|
+
dailyHT?.val || 0,
|
|
878
|
+
true,
|
|
879
|
+
);
|
|
880
|
+
await this.adapter.setStateAsync(
|
|
881
|
+
`${basePath}.statistics.consumption.lastDayNT`,
|
|
882
|
+
dailyNT?.val || 0,
|
|
883
|
+
true,
|
|
884
|
+
);
|
|
858
885
|
await this.adapter.setStateAsync(`${basePath}.consumption.dailyHT`, 0, true);
|
|
859
886
|
await this.adapter.setStateAsync(`${basePath}.consumption.dailyNT`, 0, true);
|
|
887
|
+
|
|
888
|
+
// Costs for HT/NT
|
|
889
|
+
const dailyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.dailyHT`))?.val || 0;
|
|
890
|
+
const dailyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.dailyNT`))?.val || 0;
|
|
891
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDayHT`, dailyCostHT, true);
|
|
892
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDayNT`, dailyCostNT, true);
|
|
893
|
+
await this.adapter.setStateAsync(`${basePath}.costs.dailyHT`, 0, true);
|
|
894
|
+
await this.adapter.setStateAsync(`${basePath}.costs.dailyNT`, 0, true);
|
|
860
895
|
}
|
|
861
896
|
|
|
862
897
|
await this.adapter.setStateAsync(`${basePath}.costs.daily`, 0, true);
|
|
863
898
|
|
|
864
899
|
// Update lastDayStart timestamp (use normalized timestamp if provided)
|
|
865
900
|
const timestamp = resetTimestamp || Date.now();
|
|
866
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastDayStart`, timestamp, true);
|
|
901
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastDayStart`, timestamp, true);
|
|
867
902
|
|
|
868
903
|
await this.adapter.setStateAsync(
|
|
869
|
-
`${basePath}.statistics.averageDaily`,
|
|
904
|
+
`${basePath}.statistics.consumption.averageDaily`,
|
|
870
905
|
calculator.roundToDecimals(dailyValue, 2),
|
|
871
906
|
true,
|
|
872
907
|
);
|
|
908
|
+
await this.adapter.setStateAsync(
|
|
909
|
+
`${basePath}.statistics.cost.averageDaily`,
|
|
910
|
+
calculator.roundToDecimals(dailyCostValue, 2),
|
|
911
|
+
true,
|
|
912
|
+
);
|
|
873
913
|
}
|
|
874
914
|
|
|
875
915
|
// Update totals if multiple meters exist
|
|
@@ -907,13 +947,22 @@ class BillingManager {
|
|
|
907
947
|
const monthlyValue = monthlyState?.val || 0;
|
|
908
948
|
|
|
909
949
|
// Save last month consumption
|
|
910
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastMonth`, monthlyValue, true);
|
|
950
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonth`, monthlyValue, true);
|
|
951
|
+
|
|
952
|
+
// Save last month costs
|
|
953
|
+
const monthlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.monthly`);
|
|
954
|
+
const monthlyCostValue = monthlyCostState?.val || 0;
|
|
955
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonth`, monthlyCostValue, true);
|
|
911
956
|
|
|
912
957
|
// For gas: also save volume
|
|
913
958
|
if (type === 'gas') {
|
|
914
959
|
const monthlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.monthlyVolume`);
|
|
915
960
|
const monthlyVolumeValue = monthlyVolume?.val || 0;
|
|
916
|
-
await this.adapter.setStateAsync(
|
|
961
|
+
await this.adapter.setStateAsync(
|
|
962
|
+
`${basePath}.statistics.consumption.lastMonthVolume`,
|
|
963
|
+
monthlyVolumeValue,
|
|
964
|
+
true,
|
|
965
|
+
);
|
|
917
966
|
await this.adapter.setStateAsync(`${basePath}.consumption.monthlyVolume`, 0, true);
|
|
918
967
|
}
|
|
919
968
|
|
|
@@ -924,21 +973,38 @@ class BillingManager {
|
|
|
924
973
|
const configType = getConfigType(type);
|
|
925
974
|
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
926
975
|
if (htNtEnabled) {
|
|
976
|
+
const monthlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.monthlyHT`))?.val || 0;
|
|
977
|
+
const monthlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.monthlyNT`))?.val || 0;
|
|
978
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonthHT`, monthlyHT, true);
|
|
979
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonthNT`, monthlyNT, true);
|
|
927
980
|
await this.adapter.setStateAsync(`${basePath}.consumption.monthlyHT`, 0, true);
|
|
928
981
|
await this.adapter.setStateAsync(`${basePath}.consumption.monthlyNT`, 0, true);
|
|
982
|
+
|
|
983
|
+
// Costs for HT/NT
|
|
984
|
+
const monthlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.monthlyHT`))?.val || 0;
|
|
985
|
+
const monthlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.monthlyNT`))?.val || 0;
|
|
986
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonthHT`, monthlyCostHT, true);
|
|
987
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonthNT`, monthlyCostNT, true);
|
|
988
|
+
await this.adapter.setStateAsync(`${basePath}.costs.monthlyHT`, 0, true);
|
|
989
|
+
await this.adapter.setStateAsync(`${basePath}.costs.monthlyNT`, 0, true);
|
|
929
990
|
}
|
|
930
991
|
|
|
931
992
|
await this.adapter.setStateAsync(`${basePath}.costs.monthly`, 0, true);
|
|
932
993
|
|
|
933
994
|
// Update lastMonthStart timestamp (use normalized timestamp if provided)
|
|
934
995
|
const timestamp = resetTimestamp || Date.now();
|
|
935
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastMonthStart`, timestamp, true);
|
|
996
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastMonthStart`, timestamp, true);
|
|
936
997
|
|
|
937
998
|
await this.adapter.setStateAsync(
|
|
938
|
-
`${basePath}.statistics.averageMonthly`,
|
|
999
|
+
`${basePath}.statistics.consumption.averageMonthly`,
|
|
939
1000
|
calculator.roundToDecimals(monthlyValue, 2),
|
|
940
1001
|
true,
|
|
941
1002
|
);
|
|
1003
|
+
await this.adapter.setStateAsync(
|
|
1004
|
+
`${basePath}.statistics.cost.averageMonthly`,
|
|
1005
|
+
calculator.roundToDecimals(monthlyCostValue, 2),
|
|
1006
|
+
true,
|
|
1007
|
+
);
|
|
942
1008
|
}
|
|
943
1009
|
|
|
944
1010
|
// Update totals if multiple meters exist
|
|
@@ -970,18 +1036,51 @@ class BillingManager {
|
|
|
970
1036
|
|
|
971
1037
|
this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
|
|
972
1038
|
|
|
1039
|
+
const yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
|
|
1040
|
+
const yearlyValue = yearlyState?.val || 0;
|
|
1041
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYear`, yearlyValue, true);
|
|
1042
|
+
|
|
1043
|
+
const yearlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.yearly`);
|
|
1044
|
+
const yearlyCostValue = yearlyCostState?.val || 0;
|
|
1045
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYear`, yearlyCostValue, true);
|
|
1046
|
+
|
|
973
1047
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, 0, true);
|
|
974
1048
|
|
|
975
1049
|
if (type === 'gas') {
|
|
1050
|
+
const yearlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.yearlyVolume`);
|
|
1051
|
+
const yearlyVolumeValue = yearlyVolume?.val || 0;
|
|
1052
|
+
await this.adapter.setStateAsync(
|
|
1053
|
+
`${basePath}.statistics.consumption.lastYearVolume`,
|
|
1054
|
+
yearlyVolumeValue,
|
|
1055
|
+
true,
|
|
1056
|
+
);
|
|
976
1057
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyVolume`, 0, true);
|
|
977
1058
|
}
|
|
978
1059
|
|
|
1060
|
+
const configType = getConfigType(type);
|
|
1061
|
+
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
1062
|
+
if (htNtEnabled) {
|
|
1063
|
+
const yearlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyHT`))?.val || 0;
|
|
1064
|
+
const yearlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyNT`))?.val || 0;
|
|
1065
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearHT`, yearlyHT, true);
|
|
1066
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearNT`, yearlyNT, true);
|
|
1067
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyHT`, 0, true);
|
|
1068
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyNT`, 0, true);
|
|
1069
|
+
|
|
1070
|
+
const yearlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyHT`))?.val || 0;
|
|
1071
|
+
const yearlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyNT`))?.val || 0;
|
|
1072
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearHT`, yearlyCostHT, true);
|
|
1073
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearNT`, yearlyCostNT, true);
|
|
1074
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyHT`, 0, true);
|
|
1075
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyNT`, 0, true);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
979
1078
|
await this.adapter.setStateAsync(`${basePath}.costs.yearly`, 0, true);
|
|
980
1079
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationSent`, false, true);
|
|
981
1080
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationChangeSent`, false, true);
|
|
982
1081
|
|
|
983
1082
|
// Update lastYearStart timestamp
|
|
984
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastYearStart`, Date.now(), true);
|
|
1083
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastYearStart`, Date.now(), true);
|
|
985
1084
|
}
|
|
986
1085
|
|
|
987
1086
|
// Update totals if multiple meters exist
|
|
@@ -1003,19 +1102,52 @@ class BillingManager {
|
|
|
1003
1102
|
|
|
1004
1103
|
this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
|
|
1005
1104
|
|
|
1105
|
+
const yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
|
|
1106
|
+
const yearlyValue = yearlyState?.val || 0;
|
|
1107
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYear`, yearlyValue, true);
|
|
1108
|
+
|
|
1109
|
+
const yearlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.yearly`);
|
|
1110
|
+
const yearlyCostValue = yearlyCostState?.val || 0;
|
|
1111
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYear`, yearlyCostValue, true);
|
|
1112
|
+
|
|
1006
1113
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, 0, true);
|
|
1007
1114
|
|
|
1008
1115
|
if (type === 'gas') {
|
|
1116
|
+
const yearlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.yearlyVolume`);
|
|
1117
|
+
const yearlyVolumeValue = yearlyVolume?.val || 0;
|
|
1118
|
+
await this.adapter.setStateAsync(
|
|
1119
|
+
`${basePath}.statistics.consumption.lastYearVolume`,
|
|
1120
|
+
yearlyVolumeValue,
|
|
1121
|
+
true,
|
|
1122
|
+
);
|
|
1009
1123
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyVolume`, 0, true);
|
|
1010
1124
|
}
|
|
1011
1125
|
|
|
1126
|
+
const configType = getConfigType(type);
|
|
1127
|
+
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
1128
|
+
if (htNtEnabled) {
|
|
1129
|
+
const yearlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyHT`))?.val || 0;
|
|
1130
|
+
const yearlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyNT`))?.val || 0;
|
|
1131
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearHT`, yearlyHT, true);
|
|
1132
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearNT`, yearlyNT, true);
|
|
1133
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyHT`, 0, true);
|
|
1134
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyNT`, 0, true);
|
|
1135
|
+
|
|
1136
|
+
const yearlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyHT`))?.val || 0;
|
|
1137
|
+
const yearlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyNT`))?.val || 0;
|
|
1138
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearHT`, yearlyCostHT, true);
|
|
1139
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearNT`, yearlyCostNT, true);
|
|
1140
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyHT`, 0, true);
|
|
1141
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyNT`, 0, true);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1012
1144
|
await this.adapter.setStateAsync(`${basePath}.costs.yearly`, 0, true);
|
|
1013
1145
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationSent`, false, true);
|
|
1014
1146
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationChangeSent`, false, true);
|
|
1015
1147
|
|
|
1016
1148
|
// Update lastYearStart timestamp (use normalized timestamp if provided)
|
|
1017
1149
|
const timestamp = resetTimestamp || Date.now();
|
|
1018
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastYearStart`, timestamp, true);
|
|
1150
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastYearStart`, timestamp, true);
|
|
1019
1151
|
}
|
|
1020
1152
|
|
|
1021
1153
|
/**
|
|
@@ -1034,13 +1166,22 @@ class BillingManager {
|
|
|
1034
1166
|
// Save last week consumption before reset
|
|
1035
1167
|
const weeklyState = await this.adapter.getStateAsync(`${basePath}.consumption.weekly`);
|
|
1036
1168
|
const weeklyValue = weeklyState?.val || 0;
|
|
1037
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastWeek`, weeklyValue, true);
|
|
1169
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeek`, weeklyValue, true);
|
|
1170
|
+
|
|
1171
|
+
// Save last week costs before reset
|
|
1172
|
+
const weeklyCostState = await this.adapter.getStateAsync(`${basePath}.costs.weekly`);
|
|
1173
|
+
const weeklyCostValue = weeklyCostState?.val || 0;
|
|
1174
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeek`, weeklyCostValue, true);
|
|
1038
1175
|
|
|
1039
1176
|
// For gas: also save volume
|
|
1040
1177
|
if (type === 'gas') {
|
|
1041
1178
|
const weeklyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.weeklyVolume`);
|
|
1042
1179
|
const weeklyVolumeValue = weeklyVolume?.val || 0;
|
|
1043
|
-
await this.adapter.setStateAsync(
|
|
1180
|
+
await this.adapter.setStateAsync(
|
|
1181
|
+
`${basePath}.statistics.consumption.lastWeekVolume`,
|
|
1182
|
+
weeklyVolumeValue,
|
|
1183
|
+
true,
|
|
1184
|
+
);
|
|
1044
1185
|
await this.adapter.setStateAsync(`${basePath}.consumption.weeklyVolume`, 0, true);
|
|
1045
1186
|
}
|
|
1046
1187
|
|
|
@@ -1051,15 +1192,25 @@ class BillingManager {
|
|
|
1051
1192
|
const configType = getConfigType(type);
|
|
1052
1193
|
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
1053
1194
|
if (htNtEnabled) {
|
|
1195
|
+
const weeklyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.weeklyHT`))?.val || 0;
|
|
1196
|
+
const weeklyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.weeklyNT`))?.val || 0;
|
|
1197
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeekHT`, weeklyHT, true);
|
|
1198
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeekNT`, weeklyNT, true);
|
|
1054
1199
|
await this.adapter.setStateAsync(`${basePath}.consumption.weeklyHT`, 0, true);
|
|
1055
1200
|
await this.adapter.setStateAsync(`${basePath}.consumption.weeklyNT`, 0, true);
|
|
1201
|
+
|
|
1202
|
+
// Costs for HT/NT
|
|
1203
|
+
const weeklyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.weeklyHT`))?.val || 0;
|
|
1204
|
+
const weeklyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.weeklyNT`))?.val || 0;
|
|
1205
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeekHT`, weeklyCostHT, true);
|
|
1206
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeekNT`, weeklyCostNT, true);
|
|
1056
1207
|
await this.adapter.setStateAsync(`${basePath}.costs.weeklyHT`, 0, true);
|
|
1057
1208
|
await this.adapter.setStateAsync(`${basePath}.costs.weeklyNT`, 0, true);
|
|
1058
1209
|
}
|
|
1059
1210
|
|
|
1060
1211
|
// Update lastWeekStart timestamp (use normalized timestamp if provided)
|
|
1061
1212
|
const timestamp = resetTimestamp || Date.now();
|
|
1062
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastWeekStart`, timestamp, true);
|
|
1213
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`, timestamp, true);
|
|
1063
1214
|
}
|
|
1064
1215
|
|
|
1065
1216
|
if (meters.length > 1) {
|
package/lib/multiMeterManager.js
CHANGED
|
@@ -227,7 +227,7 @@ class MultiMeterManager {
|
|
|
227
227
|
const timestampRoles = ['lastDayStart', 'lastWeekStart', 'lastMonthStart', 'lastYearStart'];
|
|
228
228
|
|
|
229
229
|
for (const role of timestampRoles) {
|
|
230
|
-
const statePath = `${basePath}.statistics.${role}`;
|
|
230
|
+
const statePath = `${basePath}.statistics.timestamps.${role}`;
|
|
231
231
|
const state = await this.adapter.getStateAsync(statePath);
|
|
232
232
|
|
|
233
233
|
if (role === 'lastYearStart') {
|
|
@@ -325,8 +325,8 @@ class MultiMeterManager {
|
|
|
325
325
|
const now = Date.now();
|
|
326
326
|
|
|
327
327
|
// Get period start timestamps
|
|
328
|
-
const lastWeekStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastWeekStart`);
|
|
329
|
-
const lastDayStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastDayStart`);
|
|
328
|
+
const lastWeekStartState = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`);
|
|
329
|
+
const lastDayStartState = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastDayStart`);
|
|
330
330
|
|
|
331
331
|
if (!lastWeekStartState?.val || !lastDayStartState?.val) {
|
|
332
332
|
this.adapter.log.debug(`[${basePath}] No period timestamps found, skipping reconstruction`);
|
|
@@ -350,7 +350,7 @@ class MultiMeterManager {
|
|
|
350
350
|
// Get current consumption values
|
|
351
351
|
const weeklyState = await this.adapter.getStateAsync(`${basePath}.consumption.weekly`);
|
|
352
352
|
const dailyState = await this.adapter.getStateAsync(`${basePath}.consumption.daily`);
|
|
353
|
-
const lastDayState = await this.adapter.getStateAsync(`${basePath}.statistics.lastDay`);
|
|
353
|
+
const lastDayState = await this.adapter.getStateAsync(`${basePath}.statistics.consumption.lastDay`);
|
|
354
354
|
|
|
355
355
|
const currentWeekly = weeklyState?.val || 0;
|
|
356
356
|
const currentDaily = dailyState?.val || 0;
|
|
@@ -376,7 +376,9 @@ class MultiMeterManager {
|
|
|
376
376
|
// Also reconstruct gas volume if applicable
|
|
377
377
|
if (type === 'gas') {
|
|
378
378
|
const weeklyVolumeState = await this.adapter.getStateAsync(`${basePath}.consumption.weeklyVolume`);
|
|
379
|
-
const lastDayVolumeState = await this.adapter.getStateAsync(
|
|
379
|
+
const lastDayVolumeState = await this.adapter.getStateAsync(
|
|
380
|
+
`${basePath}.statistics.consumption.lastDayVolume`,
|
|
381
|
+
);
|
|
380
382
|
const currentWeeklyVolume = weeklyVolumeState?.val || 0;
|
|
381
383
|
const lastDayVolume = lastDayVolumeState?.val || 0;
|
|
382
384
|
|
|
@@ -396,7 +398,9 @@ class MultiMeterManager {
|
|
|
396
398
|
}
|
|
397
399
|
|
|
398
400
|
// Similar logic for monthly reconstruction
|
|
399
|
-
const lastMonthStartState = await this.adapter.getStateAsync(
|
|
401
|
+
const lastMonthStartState = await this.adapter.getStateAsync(
|
|
402
|
+
`${basePath}.statistics.timestamps.lastMonthStart`,
|
|
403
|
+
);
|
|
400
404
|
if (lastMonthStartState?.val) {
|
|
401
405
|
const lastMonthStart = lastMonthStartState.val;
|
|
402
406
|
const daysSinceMonthStart = (now - lastMonthStart) / (24 * 60 * 60 * 1000);
|
|
@@ -422,7 +426,7 @@ class MultiMeterManager {
|
|
|
422
426
|
`${basePath}.consumption.monthlyVolume`,
|
|
423
427
|
);
|
|
424
428
|
const lastDayVolumeState = await this.adapter.getStateAsync(
|
|
425
|
-
`${basePath}.statistics.lastDayVolume`,
|
|
429
|
+
`${basePath}.statistics.consumption.lastDayVolume`,
|
|
426
430
|
);
|
|
427
431
|
const currentMonthlyVolume = monthlyVolumeState?.val || 0;
|
|
428
432
|
const lastDayVolume = lastDayVolumeState?.val || 0;
|
|
@@ -649,8 +653,10 @@ class MultiMeterManager {
|
|
|
649
653
|
const value = state?.val || 0;
|
|
650
654
|
|
|
651
655
|
// Get period start timestamp to calculate expected max consumption
|
|
656
|
+
const periodKey =
|
|
657
|
+
period === 'daily' ? 'Day' : period === 'weekly' ? 'Week' : period === 'monthly' ? 'Month' : 'Year';
|
|
652
658
|
const periodStartState = await this.adapter.getStateAsync(
|
|
653
|
-
`${basePath}.statistics.last${
|
|
659
|
+
`${basePath}.statistics.timestamps.last${periodKey}Start`,
|
|
654
660
|
);
|
|
655
661
|
const periodStart = periodStartState?.val || now;
|
|
656
662
|
const daysSincePeriodStart = (now - periodStart) / (24 * 60 * 60 * 1000);
|
|
@@ -982,7 +988,7 @@ class MultiMeterManager {
|
|
|
982
988
|
* @returns {Promise<number>} Months since start (at least 1)
|
|
983
989
|
*/
|
|
984
990
|
async _calculateMonthsSinceYearStart(basePath) {
|
|
985
|
-
const yearStartState = await this.adapter.getStateAsync(`${basePath}.statistics.lastYearStart`);
|
|
991
|
+
const yearStartState = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastYearStart`);
|
|
986
992
|
let monthsSinceYearStart = 1;
|
|
987
993
|
|
|
988
994
|
if (yearStartState && yearStartState.val) {
|