iobroker.nebenkosten-monitor 1.3.1 → 1.3.3
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 +40 -3
- package/admin/jsonConfig.json +305 -44
- package/io-package.json +17 -9
- package/lib/importManager.js +166 -0
- package/lib/importers/abstractImporter.js +35 -0
- package/lib/importers/ehb.js +91 -0
- package/lib/messagingHandler.js +2 -2
- package/lib/stateManager.js +11 -9
- package/main.js +12 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# ioBroker.nebenkosten-monitor
|
|
4
4
|
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.nebenkosten-monitor)
|
|
5
6
|
[](https://github.com/fischi87/ioBroker.nebenkosten-monitor/releases)
|
|
6
7
|
[](https://github.com/fischi87/ioBroker.nebenkosten-monitor/blob/main/LICENSE)
|
|
7
8
|
[](https://github.com/fischi87/ioBroker.nebenkosten-monitor/actions)
|
|
@@ -13,12 +14,14 @@
|
|
|
13
14
|
|
|
14
15
|
### ✨ Hauptfunktionen
|
|
15
16
|
|
|
16
|
-
- 📊 **Verbrauchsüberwachung** für Gas, Wasser und
|
|
17
|
+
- 📊 **Verbrauchsüberwachung** für Gas, Wasser, Strom und **PV/Einspeisung**
|
|
17
18
|
- 💰 **Automatische Kostenberechnung** mit Arbeitspreis und Grundgebühr
|
|
19
|
+
- ☀️ **PV & Einspeisung** - Überwache deine Einspeisung und Vergütung
|
|
18
20
|
- 💳 **Abschlagsüberwachung** - Sehe sofort ob Nachzahlung oder Guthaben droht
|
|
19
21
|
- 🔄 **Flexible Sensoren** - Nutzt vorhandene Sensoren (Shelly, Tasmota, Homematic, etc.)
|
|
20
22
|
- ⚡ **HT/NT-Tarife** - Volle Unterstützung für Hoch- und Nebentarife (Tag/Nacht)
|
|
21
|
-
-
|
|
23
|
+
- � **CSV-Import** - Importiere historische Daten (z.B. aus der EhB+ App)
|
|
24
|
+
- �🔄 **Gas-Spezial** - Automatische Umrechnung von m³ in kWh
|
|
22
25
|
- 🕛 **Automatische Resets** - Täglich, monatlich und jährlich (Vertragsjubiläum)
|
|
23
26
|
- 🔔 **Intelligente Benachrichtigungen** - Getrennte Erinnerungen für Abrechnungsende (Zählerstand) und Vertragswechsel (Tarif-Check) mit einstellbaren Vorlaufzeiten.
|
|
24
27
|
|
|
@@ -60,7 +63,7 @@ Gefällt dir dieser Adapter? Du kannst mich gerne mit einem Kaffee unterstützen
|
|
|
60
63
|
|
|
61
64
|
## 📊 Datenpunkte erklärt
|
|
62
65
|
|
|
63
|
-
Für jede aktivierte Verbrauchsart (Gas/Wasser/Strom) werden folgende Ordner angelegt:
|
|
66
|
+
Für jede aktivierte Verbrauchsart (Gas/Wasser/Strom/PV) werden folgende Ordner angelegt:
|
|
64
67
|
|
|
65
68
|
### 🗂️ **consumption** (Verbrauch)
|
|
66
69
|
|
|
@@ -213,6 +216,25 @@ Gasverbrauch wird in **m³ gemessen**, aber in **kWh abgerechnet**.
|
|
|
213
216
|
|
|
214
217
|
---
|
|
215
218
|
|
|
219
|
+
### 📥 CSV Import & Historische Daten
|
|
220
|
+
|
|
221
|
+
Du kannst historische Daten importieren, um deine Jahresstatistik zu vervollständigen.
|
|
222
|
+
|
|
223
|
+
1. Gehe in den Tab **Import**.
|
|
224
|
+
2. Wähle das **Ziel-Medium** (Strom, Gas, Wasser, PV) oder **Benutzerdefiniert**.
|
|
225
|
+
3. Wähle das **Format** (derzeit "EhB+ App (CSV)").
|
|
226
|
+
4. Füge den **CSV-Inhalt** ein.
|
|
227
|
+
- Format: `Datum;Zählerstand;Kommentar` (z.B. `01.01.2023 00:00;12345,6;Start`)
|
|
228
|
+
5. Klicke auf **Importieren**.
|
|
229
|
+
|
|
230
|
+
**Funktionen:**
|
|
231
|
+
|
|
232
|
+
- Berechnet automatisch den Jahresverbrauch für vergangene Jahre.
|
|
233
|
+
- Erstellt die Historie unter `history.JJJJ`.
|
|
234
|
+
- Benutzerdefinierte Zähler ("Einliegerwohnung") werden automatisch angelegt.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
216
238
|
### 🔄 Automatische Resets
|
|
217
239
|
|
|
218
240
|
Der Adapter setzt Zähler automatisch zurück:
|
|
@@ -227,6 +249,21 @@ Der Adapter setzt Zähler automatisch zurück:
|
|
|
227
249
|
|
|
228
250
|
## Changelog
|
|
229
251
|
|
|
252
|
+
### 1.3.3 (2026-01-09)
|
|
253
|
+
|
|
254
|
+
- **NEW:** **CSV-Import** - Importiere historische Daten (z.B. aus der EhB+ App) für Strom, Gas, Wasser und PV.
|
|
255
|
+
- **NEW:** **Benutzerdefinierte Zähler** - Unterstützung für Zwischenzähler (z.B. Gartenhaus, Einliegerwohnung).
|
|
256
|
+
- **IMPROVED:** Import-UI optimiert (Icons, Button-Layout).
|
|
257
|
+
|
|
258
|
+
### 1.3.2 (2026-01-09)
|
|
259
|
+
|
|
260
|
+
- **NEW:** **PV / Einspeise-Unterstützung** ☀️ - Neuer Tab für Photovoltaik:
|
|
261
|
+
- Überwache deine Netzeinspeisung (kWh).
|
|
262
|
+
- Berechne deine Vergütung (Earnings) automatisch.
|
|
263
|
+
- Volle Unterstützung für Zählerstände, Abrechnungszeiträume und Historie.
|
|
264
|
+
- **NEW:** **PV-Benachrichtigungen** - Erhalte Erinnerungen auch für deine PV-Anlage (Abrechnung/Vertrag).
|
|
265
|
+
- **IMPROVED:** Konfigurations-Reihenfolge optimiert (Gebühren logisch gruppiert).
|
|
266
|
+
|
|
230
267
|
### 1.3.1 (2026-01-09)
|
|
231
268
|
|
|
232
269
|
- **FIX:** Kritischer Fehler behoben: HT/NT-Datenpunkte wurden aufgrund eines internen Namensfehlers (electricity vs. strom) nicht angelegt.
|
package/admin/jsonConfig.json
CHANGED
|
@@ -172,6 +172,30 @@
|
|
|
172
172
|
"lg": 4,
|
|
173
173
|
"xl": 3
|
|
174
174
|
},
|
|
175
|
+
"gasGrundgebuehr": {
|
|
176
|
+
"type": "number",
|
|
177
|
+
"hidden": "!data.gasAktiv",
|
|
178
|
+
"label": "Grundgebühr (€/Monat)",
|
|
179
|
+
"step": 0.01,
|
|
180
|
+
"default": 0,
|
|
181
|
+
"xs": 12,
|
|
182
|
+
"sm": 12,
|
|
183
|
+
"md": 6,
|
|
184
|
+
"lg": 4,
|
|
185
|
+
"xl": 3
|
|
186
|
+
},
|
|
187
|
+
"gasJahresgebuehr": {
|
|
188
|
+
"type": "number",
|
|
189
|
+
"hidden": "!data.gasAktiv",
|
|
190
|
+
"label": "Jahresgebühr (z.B. Zählermiete) (€/Jahr)",
|
|
191
|
+
"step": 0.01,
|
|
192
|
+
"default": 0,
|
|
193
|
+
"xs": 12,
|
|
194
|
+
"sm": 12,
|
|
195
|
+
"md": 6,
|
|
196
|
+
"lg": 4,
|
|
197
|
+
"xl": 3
|
|
198
|
+
},
|
|
175
199
|
"_gasHtNtDivider": {
|
|
176
200
|
"type": "divider",
|
|
177
201
|
"hidden": "!data.gasAktiv"
|
|
@@ -272,31 +296,6 @@
|
|
|
272
296
|
"lg": 4,
|
|
273
297
|
"xl": 3
|
|
274
298
|
},
|
|
275
|
-
"gasGrundgebuehr": {
|
|
276
|
-
"type": "number",
|
|
277
|
-
"hidden": "!data.gasAktiv",
|
|
278
|
-
"label": "Grundgebühr (€/Monat)",
|
|
279
|
-
"step": 0.01,
|
|
280
|
-
"default": 0,
|
|
281
|
-
"xs": 12,
|
|
282
|
-
"sm": 12,
|
|
283
|
-
"md": 6,
|
|
284
|
-
"lg": 4,
|
|
285
|
-
"xl": 3
|
|
286
|
-
},
|
|
287
|
-
"gasJahresgebuehr": {
|
|
288
|
-
"type": "number",
|
|
289
|
-
"hidden": "!data.gasAktiv",
|
|
290
|
-
"label": "Jahresgebühr (z.B. Zählermiete) (€/Jahr)",
|
|
291
|
-
"step": 0.01,
|
|
292
|
-
"default": 0,
|
|
293
|
-
"xs": 12,
|
|
294
|
-
"sm": 12,
|
|
295
|
-
"md": 6,
|
|
296
|
-
"lg": 4,
|
|
297
|
-
"xl": 3
|
|
298
|
-
},
|
|
299
|
-
|
|
300
299
|
"_gasAbschlagHeader": {
|
|
301
300
|
"type": "header",
|
|
302
301
|
"text": "💳 Abschlag (Monatliche Vorauszahlung)",
|
|
@@ -646,6 +645,30 @@
|
|
|
646
645
|
"lg": 4,
|
|
647
646
|
"xl": 3
|
|
648
647
|
},
|
|
648
|
+
"stromGrundgebuehr": {
|
|
649
|
+
"type": "number",
|
|
650
|
+
"hidden": "!data.stromAktiv",
|
|
651
|
+
"label": "Grundgebühr (€/Monat)",
|
|
652
|
+
"step": 0.01,
|
|
653
|
+
"default": 0,
|
|
654
|
+
"xs": 12,
|
|
655
|
+
"sm": 12,
|
|
656
|
+
"md": 6,
|
|
657
|
+
"lg": 4,
|
|
658
|
+
"xl": 3
|
|
659
|
+
},
|
|
660
|
+
"stromJahresgebuehr": {
|
|
661
|
+
"type": "number",
|
|
662
|
+
"hidden": "!data.stromAktiv",
|
|
663
|
+
"label": "Jahresgebühr (z.B. Zählermiete) (€/Jahr)",
|
|
664
|
+
"step": 0.01,
|
|
665
|
+
"default": 0,
|
|
666
|
+
"xs": 12,
|
|
667
|
+
"sm": 12,
|
|
668
|
+
"md": 6,
|
|
669
|
+
"lg": 4,
|
|
670
|
+
"xl": 3
|
|
671
|
+
},
|
|
649
672
|
"_stromHtNtDivider": {
|
|
650
673
|
"type": "divider",
|
|
651
674
|
"hidden": "!data.stromAktiv"
|
|
@@ -741,41 +764,145 @@
|
|
|
741
764
|
"lg": 4,
|
|
742
765
|
"xl": 3
|
|
743
766
|
},
|
|
744
|
-
"
|
|
767
|
+
"_stromAbschlagHeader": {
|
|
768
|
+
"type": "header",
|
|
769
|
+
"text": "💳 Abschlag (Monatliche Vorauszahlung)",
|
|
770
|
+
"size": 4,
|
|
771
|
+
"hidden": "!data.stromAktiv"
|
|
772
|
+
},
|
|
773
|
+
"_stromAbschlagHelp": {
|
|
774
|
+
"type": "staticText",
|
|
775
|
+
"text": "Trage deinen monatlichen Abschlag ein. Der Adapter berechnet dann, ob du über oder unter deinem Verbrauch liegst.",
|
|
776
|
+
"hidden": "!data.stromAktiv",
|
|
777
|
+
"sm": 12,
|
|
778
|
+
"style": {
|
|
779
|
+
"fontSize": "0.9em",
|
|
780
|
+
"color": "#666",
|
|
781
|
+
"marginBottom": "10px"
|
|
782
|
+
},
|
|
783
|
+
"xs": 12,
|
|
784
|
+
"md": 12,
|
|
785
|
+
"lg": 12,
|
|
786
|
+
"xl": 12
|
|
787
|
+
},
|
|
788
|
+
"stromAbschlag": {
|
|
745
789
|
"type": "number",
|
|
790
|
+
"label": "Monatlicher Abschlag (€)",
|
|
791
|
+
"placeholder": "z.B. 65.00",
|
|
746
792
|
"hidden": "!data.stromAktiv",
|
|
747
|
-
"
|
|
793
|
+
"sm": 12,
|
|
794
|
+
"md": 6,
|
|
748
795
|
"step": 0.01,
|
|
749
796
|
"default": 0,
|
|
750
797
|
"xs": 12,
|
|
798
|
+
"lg": 4,
|
|
799
|
+
"xl": 3
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
"tabPv": {
|
|
804
|
+
"type": "panel",
|
|
805
|
+
"label": "PV / Einspeisung",
|
|
806
|
+
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjI0IiBmaWxsPSJjdXJyZW50Q29sb3IiPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJMNi43NiA0Ljg0bC0xLjgtMS43OS0xLjQxIDEuNDEgMS43OSAxLjc5IDEuNDItMS40MXpNNCAxMC41SDF2M2gzdi0zem05LTkuOTVoLTJWMy41aDJWLjU1em03LjQ1IDMuOTFsLTEuNDEtMS40MS0xLjc5IDEuNzkgMS40MSAxLjQxIDEuNzktMS43OXptLTMuMjEgMTMuN2wxLjc5IDEuOCAxLjQxLTEuNDEtMS44LTEuNzktMS40IDEuNHoyMCAxMC41djNoM3diLTNoLTN6bS04LTVjLTMuMzEgMC02IDIuNjktNiA2czIuNjkgNiA2IDYgNi0yLjY5IDYtNi0yLjY5LTYtNi02em0tMSAxNi45NWgyVjE5LjVoLTJ2Mi45NXptLTcuNDUtMy45MWwxLjQxIDEuNDEgMS43OS0xLjgtMS40MS0xLjQxLTEuNzkgMS44eiIvPjwvc3ZnPg==",
|
|
807
|
+
"items": {
|
|
808
|
+
"_pvActivationHeader": {
|
|
809
|
+
"type": "header",
|
|
810
|
+
"text": "Grundeinstellungen",
|
|
811
|
+
"size": 4
|
|
812
|
+
},
|
|
813
|
+
"pvAktiv": {
|
|
814
|
+
"type": "checkbox",
|
|
815
|
+
"label": "PV-Einspeisung überwachen (Lieferung)",
|
|
816
|
+
"sm": 12,
|
|
817
|
+
"md": 6,
|
|
818
|
+
"newLine": true,
|
|
819
|
+
"xs": 12,
|
|
820
|
+
"lg": 6,
|
|
821
|
+
"xl": 6
|
|
822
|
+
},
|
|
823
|
+
"_pvSensorHeader": {
|
|
824
|
+
"type": "header",
|
|
825
|
+
"text": "Sensor-Konfiguration",
|
|
826
|
+
"size": 5,
|
|
827
|
+
"hidden": "!data.pvAktiv"
|
|
828
|
+
},
|
|
829
|
+
"pvSensorDP": {
|
|
830
|
+
"type": "objectId",
|
|
831
|
+
"label": "🔍 Sensor für Einspeisung auswählen (kWh)",
|
|
832
|
+
"hidden": "!data.pvAktiv",
|
|
833
|
+
"sm": 12,
|
|
834
|
+
"xs": 12,
|
|
835
|
+
"md": 8,
|
|
836
|
+
"lg": 6,
|
|
837
|
+
"xl": 4
|
|
838
|
+
},
|
|
839
|
+
"_pvMeterHeader": {
|
|
840
|
+
"type": "header",
|
|
841
|
+
"text": "Offset (Optional)",
|
|
842
|
+
"size": 5,
|
|
843
|
+
"hidden": "!data.pvAktiv"
|
|
844
|
+
},
|
|
845
|
+
"_pvOffsetHelp": {
|
|
846
|
+
"type": "staticText",
|
|
847
|
+
"text": "Nur nötig wenn Sensor nicht mit physischem Zähler übereinstimmt. Offset = Physischer Wert - Sensor Wert",
|
|
848
|
+
"hidden": "!data.pvAktiv",
|
|
849
|
+
"sm": 12,
|
|
850
|
+
"style": {
|
|
851
|
+
"fontSize": "0.9em",
|
|
852
|
+
"color": "#666",
|
|
853
|
+
"marginBottom": "10px"
|
|
854
|
+
},
|
|
855
|
+
"xs": 12,
|
|
856
|
+
"md": 12,
|
|
857
|
+
"lg": 12,
|
|
858
|
+
"xl": 12
|
|
859
|
+
},
|
|
860
|
+
"pvOffset": {
|
|
861
|
+
"type": "number",
|
|
862
|
+
"label": "Offset (kWh)",
|
|
863
|
+
"placeholder": "z.B. 0 (optional)",
|
|
864
|
+
"hidden": "!data.pvAktiv",
|
|
751
865
|
"sm": 12,
|
|
752
866
|
"md": 6,
|
|
867
|
+
"step": 0.001,
|
|
868
|
+
"xs": 12,
|
|
753
869
|
"lg": 4,
|
|
754
870
|
"xl": 3
|
|
755
871
|
},
|
|
756
|
-
"
|
|
872
|
+
"pvInitialReading": {
|
|
757
873
|
"type": "number",
|
|
758
|
-
"
|
|
759
|
-
"
|
|
760
|
-
"
|
|
761
|
-
"
|
|
874
|
+
"label": "Einspeise-Zählerstand bei Start (kWh)",
|
|
875
|
+
"placeholder": "z.B. 0 (Zählerstand am Stichtag)",
|
|
876
|
+
"help": "Wird für Jahres-Einspeisung verwendet: Aktuell - Beginn = Jahreseinspeisung. 💡 Jahresabschluss: Nutze pv.billing.closePeriod zum automatischen Archivieren & Zurücksetzen",
|
|
877
|
+
"hidden": "!data.pvAktiv",
|
|
878
|
+
"sm": 12,
|
|
879
|
+
"md": 6,
|
|
880
|
+
"step": 0.001,
|
|
762
881
|
"xs": 12,
|
|
882
|
+
"lg": 4,
|
|
883
|
+
"xl": 3
|
|
884
|
+
},
|
|
885
|
+
"pvContractStart": {
|
|
886
|
+
"type": "text",
|
|
887
|
+
"label": "📅 Stichtag / Vertragsbeginn",
|
|
888
|
+
"placeholder": "z.B. 01.01.2025",
|
|
889
|
+
"hidden": "!data.pvAktiv",
|
|
763
890
|
"sm": 12,
|
|
764
891
|
"md": 6,
|
|
892
|
+
"xs": 12,
|
|
765
893
|
"lg": 4,
|
|
766
894
|
"xl": 3
|
|
767
895
|
},
|
|
768
|
-
|
|
769
|
-
"_stromAbschlagHeader": {
|
|
896
|
+
"_pvPreisHeader": {
|
|
770
897
|
"type": "header",
|
|
771
|
-
"text": "
|
|
898
|
+
"text": "💰 Vergütungsinformationen",
|
|
772
899
|
"size": 4,
|
|
773
|
-
"hidden": "!data.
|
|
900
|
+
"hidden": "!data.pvAktiv"
|
|
774
901
|
},
|
|
775
|
-
"
|
|
902
|
+
"_pvPriceHelp": {
|
|
776
903
|
"type": "staticText",
|
|
777
|
-
"text": "Trage
|
|
778
|
-
"hidden": "!data.
|
|
904
|
+
"text": "Trage hier deine Einspeisevergütung ein.",
|
|
905
|
+
"hidden": "!data.pvAktiv",
|
|
779
906
|
"sm": 12,
|
|
780
907
|
"style": {
|
|
781
908
|
"fontSize": "0.9em",
|
|
@@ -787,16 +914,39 @@
|
|
|
787
914
|
"lg": 12,
|
|
788
915
|
"xl": 12
|
|
789
916
|
},
|
|
790
|
-
"
|
|
917
|
+
"pvPreis": {
|
|
791
918
|
"type": "number",
|
|
792
|
-
"
|
|
793
|
-
"
|
|
794
|
-
"
|
|
919
|
+
"hidden": "!data.pvAktiv",
|
|
920
|
+
"label": "Einspeisevergütung (€/kWh)",
|
|
921
|
+
"min": 0,
|
|
922
|
+
"step": 0.0001,
|
|
923
|
+
"xs": 12,
|
|
795
924
|
"sm": 12,
|
|
796
925
|
"md": 6,
|
|
926
|
+
"lg": 4,
|
|
927
|
+
"xl": 3
|
|
928
|
+
},
|
|
929
|
+
"pvGrundgebuehr": {
|
|
930
|
+
"type": "number",
|
|
931
|
+
"hidden": "!data.pvAktiv",
|
|
932
|
+
"label": "Grundgebühr (Messstellenbetrieb) (€/Monat)",
|
|
797
933
|
"step": 0.01,
|
|
798
934
|
"default": 0,
|
|
799
935
|
"xs": 12,
|
|
936
|
+
"sm": 12,
|
|
937
|
+
"md": 6,
|
|
938
|
+
"lg": 4,
|
|
939
|
+
"xl": 3
|
|
940
|
+
},
|
|
941
|
+
"pvJahresgebuehr": {
|
|
942
|
+
"type": "number",
|
|
943
|
+
"hidden": "!data.pvAktiv",
|
|
944
|
+
"label": "Jahresgebühr (€/Jahr)",
|
|
945
|
+
"step": 0.01,
|
|
946
|
+
"default": 0,
|
|
947
|
+
"xs": 12,
|
|
948
|
+
"sm": 12,
|
|
949
|
+
"md": 6,
|
|
800
950
|
"lg": 4,
|
|
801
951
|
"xl": 3
|
|
802
952
|
}
|
|
@@ -950,13 +1100,124 @@
|
|
|
950
1100
|
},
|
|
951
1101
|
"notificationStromEnabled": {
|
|
952
1102
|
"type": "checkbox",
|
|
953
|
-
"label": "
|
|
1103
|
+
"label": "Strom-Erinnerung",
|
|
954
1104
|
"hidden": "!data.notificationEnabled || !data.stromAktiv",
|
|
955
1105
|
"sm": 12,
|
|
956
1106
|
"md": 4,
|
|
957
1107
|
"xs": 12,
|
|
958
1108
|
"lg": 4,
|
|
959
1109
|
"xl": 4
|
|
1110
|
+
},
|
|
1111
|
+
"notificationPVEnabled": {
|
|
1112
|
+
"type": "checkbox",
|
|
1113
|
+
"label": "PV-Erinnerung",
|
|
1114
|
+
"hidden": "!data.notificationEnabled || !data.pvAktiv",
|
|
1115
|
+
"sm": 12,
|
|
1116
|
+
"md": 4,
|
|
1117
|
+
"xs": 12,
|
|
1118
|
+
"lg": 4,
|
|
1119
|
+
"xl": 4
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1123
|
+
"tabImport": {
|
|
1124
|
+
"type": "panel",
|
|
1125
|
+
"label": "Import",
|
|
1126
|
+
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iY3VycmVudENvbG9yIj48cGF0aCBkPSJNMCAwaDI0djI0SDBWMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTkgOWgtNFYzSDl2Nkg1bDcgNyA3LTd6TTUgMTh2MmgxNHdiLTJINXoiLz48L3N2Zz4=",
|
|
1127
|
+
"items": {
|
|
1128
|
+
"_importHeader": {
|
|
1129
|
+
"type": "header",
|
|
1130
|
+
"text": "Historische Daten importieren",
|
|
1131
|
+
"size": 2
|
|
1132
|
+
},
|
|
1133
|
+
"_importDesc": {
|
|
1134
|
+
"type": "staticText",
|
|
1135
|
+
"text": "Hier kannst du alte Zählerstände aus CSV-Dateien (z.B. EhB+ App) importieren. Die Daten werden in die Historie (Jahresverbrauch) geschrieben. Bitte kopiere den Inhalt der CSV-Datei in das Textfeld.",
|
|
1136
|
+
"xs": 12
|
|
1137
|
+
},
|
|
1138
|
+
"importTarget": {
|
|
1139
|
+
"type": "select",
|
|
1140
|
+
"label": "Ziel-Medium",
|
|
1141
|
+
"options": [
|
|
1142
|
+
{ "label": "Bitte wählen...", "value": "" },
|
|
1143
|
+
{ "label": "Strom", "value": "electricity" },
|
|
1144
|
+
{ "label": "Gas", "value": "gas" },
|
|
1145
|
+
{ "label": "Wasser", "value": "water" },
|
|
1146
|
+
{ "label": "PV / Einspeisung", "value": "pv" },
|
|
1147
|
+
{ "label": "Benutzerdefiniert / Zwischenzähler", "value": "custom" }
|
|
1148
|
+
],
|
|
1149
|
+
"default": "",
|
|
1150
|
+
"xs": 12,
|
|
1151
|
+
"sm": 12,
|
|
1152
|
+
"md": 6,
|
|
1153
|
+
"lg": 4,
|
|
1154
|
+
"xl": 4
|
|
1155
|
+
},
|
|
1156
|
+
"importCustomName": {
|
|
1157
|
+
"type": "text",
|
|
1158
|
+
"label": "Name des Zählers",
|
|
1159
|
+
"placeholder": "z.B. Einliegerwohnung",
|
|
1160
|
+
"hidden": "data.importTarget !== 'custom'",
|
|
1161
|
+
"xs": 12,
|
|
1162
|
+
"sm": 12,
|
|
1163
|
+
"md": 6,
|
|
1164
|
+
"lg": 4,
|
|
1165
|
+
"xl": 4
|
|
1166
|
+
},
|
|
1167
|
+
"importUnit": {
|
|
1168
|
+
"type": "select",
|
|
1169
|
+
"label": "Einheit",
|
|
1170
|
+
"options": [
|
|
1171
|
+
{ "label": "kWh", "value": "kWh" },
|
|
1172
|
+
{ "label": "m³", "value": "m3" }
|
|
1173
|
+
],
|
|
1174
|
+
"default": "kWh",
|
|
1175
|
+
"hidden": "data.importTarget !== 'custom'",
|
|
1176
|
+
"xs": 12,
|
|
1177
|
+
"sm": 12,
|
|
1178
|
+
"md": 6,
|
|
1179
|
+
"lg": 4,
|
|
1180
|
+
"xl": 4
|
|
1181
|
+
},
|
|
1182
|
+
"importFormat": {
|
|
1183
|
+
"type": "select",
|
|
1184
|
+
"label": "Quell-Format",
|
|
1185
|
+
"options": [{ "label": "EhB+ App (CSV)", "value": "ehb" }],
|
|
1186
|
+
"default": "ehb",
|
|
1187
|
+
"xs": 12,
|
|
1188
|
+
"sm": 12,
|
|
1189
|
+
"md": 6,
|
|
1190
|
+
"lg": 4,
|
|
1191
|
+
"xl": 4
|
|
1192
|
+
},
|
|
1193
|
+
"importContent": {
|
|
1194
|
+
"type": "text",
|
|
1195
|
+
"label": "CSV Inhalt (hier einfügen)",
|
|
1196
|
+
"rows": 10,
|
|
1197
|
+
"xs": 12,
|
|
1198
|
+
"sm": 12,
|
|
1199
|
+
"md": 12,
|
|
1200
|
+
"lg": 12,
|
|
1201
|
+
"xl": 12,
|
|
1202
|
+
"noCreate": true
|
|
1203
|
+
},
|
|
1204
|
+
"btnImport": {
|
|
1205
|
+
"type": "sendTo",
|
|
1206
|
+
"label": "Daten importieren",
|
|
1207
|
+
"command": "importData",
|
|
1208
|
+
"jsonData": "{\"utility\": \"${data.importTarget}\", \"type\": \"${data.importFormat}\", \"content\": \"${data.importContent}\", \"customName\": \"${data.importCustomName}\", \"unit\": \"${data.importUnit}\"}",
|
|
1209
|
+
"disabled": "!data.importTarget || !data.importContent",
|
|
1210
|
+
"variant": "outlined",
|
|
1211
|
+
"style": {
|
|
1212
|
+
"marginTop": "10px",
|
|
1213
|
+
"width": "100%"
|
|
1214
|
+
},
|
|
1215
|
+
"xs": 12,
|
|
1216
|
+
"sm": 12,
|
|
1217
|
+
"md": 4,
|
|
1218
|
+
"lg": 4,
|
|
1219
|
+
"xl": 4,
|
|
1220
|
+
"icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMjRweCIgdmlld0JveD0iMCAwIDI0IDI0IiB3aWR0aD0iMjRweCIgZmlsbD0iY3VycmVudENvbG9yIj48cGF0aCBkPSJNMCAwaDI0djI0SDBWMHoiIGZpbGw9Im5vbmUiLz48cGF0aCBkPSJNMTkgOWgtNFYzSDl2Nkg1bDcgNyA3LTd6TTUgMTh2MmgxNHdiLTJINXoiLz48L3N2Zz4="
|
|
960
1221
|
}
|
|
961
1222
|
}
|
|
962
1223
|
},
|
package/io-package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"common": {
|
|
3
3
|
"name": "nebenkosten-monitor",
|
|
4
|
-
"version": "1.3.
|
|
4
|
+
"version": "1.3.3",
|
|
5
5
|
"news": {
|
|
6
|
+
"1.3.3": {
|
|
7
|
+
"en": "New: CSV Import feature for historical data (e.g. EhB+ App). New: Support for Custom Meters (intermediate meters).",
|
|
8
|
+
"de": "Neu: CSV-Import-Funktion für historische Daten (z.B. EhB+ App). Neu: Unterstützung für benutzerdefinierte Zähler (Zwischenzähler)."
|
|
9
|
+
},
|
|
10
|
+
"1.3.2": {
|
|
11
|
+
"en": "New: PV/Feed-in integration! Monitor your solar feed-in and earnings. New: Notifications for PV. Improved: Reorganized config UI.",
|
|
12
|
+
"de": "Neu: PV/Einspeise-Integration! Überwache deine Solareinspeisung und Vergütung. Neu: Benachrichtigungen für PV. Verbessert: Aufgeräumte Konfigurations-Oberfläche."
|
|
13
|
+
},
|
|
6
14
|
"1.3.1": {
|
|
7
15
|
"en": "Fix: Critical bug where HT/NT objects were not created for non-gas utilities (missing config mapping)",
|
|
8
16
|
"de": "Fix: Kritischer Fehler, bei dem HT/NT-Objekte für Strom/Wasser nicht erstellt wurden (fehlendes Config-Mapping)"
|
|
@@ -14,14 +22,6 @@
|
|
|
14
22
|
"1.2.7": {
|
|
15
23
|
"en": "New: Universal notification system for reminders. New: PayPal donation support. Fix: Improved precision for daily consumption.",
|
|
16
24
|
"de": "Neu: Universelles Benachrichtigungssystem für Erinnerungen. Neu: PayPal-Spendenunterstützung. Fix: Verbesserte Präzision für den Tagesverbrauch."
|
|
17
|
-
},
|
|
18
|
-
"1.2.6": {
|
|
19
|
-
"en": "Fix: Allow empty basic charge fields in configuration (default to 0)",
|
|
20
|
-
"de": "Fix: Grundgebühr-Felder dürfen in der Konfiguration leer sein (Standard 0)"
|
|
21
|
-
},
|
|
22
|
-
"1.2.5": {
|
|
23
|
-
"en": "Stability update: Fixed critical consumption delta calculation bug",
|
|
24
|
-
"de": "Stabilitäts-Update: Kritischen Fehler in der Verbrauchs-Delta-Berechnung behoben"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"titleLang": {
|
|
@@ -108,6 +108,14 @@
|
|
|
108
108
|
"stromPreis": 0,
|
|
109
109
|
"stromGrundgebuehr": 0,
|
|
110
110
|
"stromAbschlag": 0,
|
|
111
|
+
"pvAktiv": false,
|
|
112
|
+
"pvSensorDP": "",
|
|
113
|
+
"pvOffset": 0,
|
|
114
|
+
"pvInitialReading": 0,
|
|
115
|
+
"pvContractStart": "",
|
|
116
|
+
"pvPreis": 0,
|
|
117
|
+
"pvGrundgebuehr": 0,
|
|
118
|
+
"pvJahresgebuehr": 0,
|
|
111
119
|
"notificationEnabled": false,
|
|
112
120
|
"notificationInstance": "",
|
|
113
121
|
"notificationDaysBefore": 30,
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const EhbImporter = require('./importers/ehb');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ImportManager handles data import from different sources
|
|
7
|
+
*/
|
|
8
|
+
class ImportManager {
|
|
9
|
+
/**
|
|
10
|
+
* @param {object} adapter - ioBroker adapter instance
|
|
11
|
+
*/
|
|
12
|
+
constructor(adapter) {
|
|
13
|
+
this.adapter = adapter;
|
|
14
|
+
this.importers = {
|
|
15
|
+
ehb: new EhbImporter(adapter),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* handle import message
|
|
21
|
+
*
|
|
22
|
+
* @param {object} msg - The message object
|
|
23
|
+
*/
|
|
24
|
+
async handleImportMessage(msg) {
|
|
25
|
+
try {
|
|
26
|
+
let { utility, type, content, customName, unit } = msg.message; // utility='gas', type='ehb', content='...'
|
|
27
|
+
|
|
28
|
+
if (utility === 'custom') {
|
|
29
|
+
if (!customName) {
|
|
30
|
+
throw new Error('Name für benutzerdefinierten Zähler fehlt.');
|
|
31
|
+
}
|
|
32
|
+
// Sanitize name: "Garten Haus" -> "garten_haus"
|
|
33
|
+
utility = customName
|
|
34
|
+
.toLowerCase()
|
|
35
|
+
.replace(/\s+/g, '_')
|
|
36
|
+
.replace(/[^a-z0-9_]/g, '');
|
|
37
|
+
|
|
38
|
+
if (utility.length === 0) {
|
|
39
|
+
throw new Error('Ungültiger Name für Zähler.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Ensure unit is valid (default to kWh if something weird comes in)
|
|
43
|
+
if (unit === 'm3') {
|
|
44
|
+
unit = 'm³'; // Fix mapping from UI value
|
|
45
|
+
}
|
|
46
|
+
if (!unit) {
|
|
47
|
+
unit = 'kWh';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!this.importers[type]) {
|
|
52
|
+
throw new Error(`Unknown importer type: ${type}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.adapter.log.info(`[Import] Starting import for ${utility} using ${type}...`);
|
|
56
|
+
const records = await this.importers[type].parse(content);
|
|
57
|
+
|
|
58
|
+
if (!records || records.length === 0) {
|
|
59
|
+
return { error: 'No valid data found in CSV.' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result = await this.processRecords(utility, records, unit);
|
|
63
|
+
|
|
64
|
+
const details = result.details.map(d => `${d.year}: ${d.consumption.toFixed(2)} ${d.unit}`).join(', ');
|
|
65
|
+
this.adapter.log.info(`[Import] Details: ${details}`);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
result: `Erfolgreich importiert: ${records.length} Datensätze verarbeitet.`,
|
|
69
|
+
native: {
|
|
70
|
+
importContent: '',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
} catch (error) {
|
|
74
|
+
this.adapter.log.error(`[Import] Error: ${error.message}`);
|
|
75
|
+
return { error: error.message };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Process records and write to history
|
|
81
|
+
*
|
|
82
|
+
* @param {string} utility - The utility type (gas, water, electricity, pv, or custom name)
|
|
83
|
+
* @param {Array<{timestamp: number, value: number, dateObj: Date}>} records - Parsed records
|
|
84
|
+
* @param {string} [customUnit] - Unit for custom meters
|
|
85
|
+
*/
|
|
86
|
+
async processRecords(utility, records, customUnit) {
|
|
87
|
+
// Group by year
|
|
88
|
+
const years = {};
|
|
89
|
+
|
|
90
|
+
for (const r of records) {
|
|
91
|
+
const year = r.dateObj.getFullYear();
|
|
92
|
+
if (!years[year]) {
|
|
93
|
+
years[year] = [];
|
|
94
|
+
}
|
|
95
|
+
years[year].push(r);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// eslint-disable-next-line jsdoc/check-tag-names
|
|
99
|
+
/** @type {{ yearsUpdated: string[], details: Array<{year: number, consumption: number, unit: string}> }} */
|
|
100
|
+
const stats = { yearsUpdated: [], details: [] };
|
|
101
|
+
|
|
102
|
+
for (const year of Object.keys(years)) {
|
|
103
|
+
const readings = years[year];
|
|
104
|
+
// Sort just in case
|
|
105
|
+
readings.sort((a, b) => a.timestamp - b.timestamp);
|
|
106
|
+
|
|
107
|
+
const startVal = readings[0].value;
|
|
108
|
+
const endVal = readings[readings.length - 1].value;
|
|
109
|
+
let consumption = endVal - startVal;
|
|
110
|
+
|
|
111
|
+
if (consumption < 0) {
|
|
112
|
+
consumption = 0; // Reset handling? simpler for now.
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let unit = customUnit || 'kWh'; // Default or custom
|
|
116
|
+
|
|
117
|
+
// Gas: Convert m3 to kWh? (Only for standard 'gas' utility)
|
|
118
|
+
if (utility === 'gas') {
|
|
119
|
+
unit = 'kWh'; // Gas is always kWh in history despite input
|
|
120
|
+
|
|
121
|
+
// Read current conversion factors (Not perfect for history, but better than nothing)
|
|
122
|
+
const brennwert = this.adapter.config.gasBrennwert || 1;
|
|
123
|
+
const zustandszahl = this.adapter.config.gasZahl || 1;
|
|
124
|
+
|
|
125
|
+
// Save Volume
|
|
126
|
+
await this.adapter.setObjectNotExistsAsync(`${utility}.history.${year}.yearlyVolume`, {
|
|
127
|
+
type: 'state',
|
|
128
|
+
common: { name: `Verbrauch ${year} in m³`, type: 'number', unit: 'm³', role: 'value' },
|
|
129
|
+
native: {},
|
|
130
|
+
});
|
|
131
|
+
await this.adapter.setStateAsync(`${utility}.history.${year}.yearlyVolume`, consumption, true);
|
|
132
|
+
|
|
133
|
+
// Convert to kWh
|
|
134
|
+
consumption = consumption * brennwert * zustandszahl;
|
|
135
|
+
} else if (utility === 'water') {
|
|
136
|
+
unit = 'm³';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Write Yearly Consumption
|
|
140
|
+
await this.adapter.setObjectNotExistsAsync(`${utility}.history.${year}`, {
|
|
141
|
+
type: 'channel',
|
|
142
|
+
common: { name: `Jahr ${year}` },
|
|
143
|
+
native: {},
|
|
144
|
+
});
|
|
145
|
+
await this.adapter.setObjectNotExistsAsync(`${utility}.history.${year}.yearly`, {
|
|
146
|
+
type: 'state',
|
|
147
|
+
common: { name: `Jahresverbrauch ${year}`, type: 'number', unit: unit, role: 'value' },
|
|
148
|
+
native: {},
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const stateId = `${utility}.history.${year}.yearly`;
|
|
152
|
+
await this.adapter.setStateAsync(stateId, consumption, true);
|
|
153
|
+
|
|
154
|
+
stats.yearsUpdated.push(year);
|
|
155
|
+
stats.details.push({ year: parseInt(year), consumption, unit });
|
|
156
|
+
|
|
157
|
+
this.adapter.log.info(
|
|
158
|
+
`[Import] ${utility} ${year}: ${consumption.toFixed(2)} ${unit} (from ${startVal} to ${endVal})`,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return stats;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = ImportManager;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for importers
|
|
5
|
+
*/
|
|
6
|
+
class AbstractImporter {
|
|
7
|
+
/**
|
|
8
|
+
* @param {object} adapter - Adapter instance
|
|
9
|
+
*/
|
|
10
|
+
constructor(adapter) {
|
|
11
|
+
this.adapter = adapter;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parses the CSV content
|
|
16
|
+
*
|
|
17
|
+
* @param {string} _content - Raw CSV string
|
|
18
|
+
* @returns {Promise<Array<{timestamp: number, value: number}>>} - Array of objects with timestamp and value
|
|
19
|
+
*/
|
|
20
|
+
async parse(_content) {
|
|
21
|
+
throw new Error('Method "parse" must be implemented');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validates if the content matches this importer
|
|
26
|
+
*
|
|
27
|
+
* @param {string} _content - Raw content to validate
|
|
28
|
+
* @returns {boolean} - True if valid, false otherwise
|
|
29
|
+
*/
|
|
30
|
+
validate(_content) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = AbstractImporter;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const AbstractImporter = require('./abstractImporter');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Importer for "Energie Haushalts Buch" (EhB) App
|
|
7
|
+
* Format assumption: German CSV (semicolon separator)
|
|
8
|
+
* Date;Value;Comment
|
|
9
|
+
* 01.01.2023 12:00;1234.5;Ablesung
|
|
10
|
+
*/
|
|
11
|
+
class EhbImporter extends AbstractImporter {
|
|
12
|
+
/**
|
|
13
|
+
* Parse the CSV content
|
|
14
|
+
*
|
|
15
|
+
* @param {string} content - Raw content
|
|
16
|
+
* @returns {Promise<Array<any>>} - Parsed results
|
|
17
|
+
*/
|
|
18
|
+
async parse(content) {
|
|
19
|
+
const results = [];
|
|
20
|
+
// Regex to match: Date;Value;Comment
|
|
21
|
+
// Supports:
|
|
22
|
+
// DD.MM.YYYY or DD.MM.YYYY HH:mm
|
|
23
|
+
// Semicolon separator
|
|
24
|
+
// Value with dot or comma
|
|
25
|
+
// Optional comment
|
|
26
|
+
const regex = /(\d{2}\.\d{2}\.\d{4}(?:\s+\d{2}:\d{2}(?::\d{2})?)?)\s*;\s*(\d+(?:[.,]\d+)?)/g;
|
|
27
|
+
|
|
28
|
+
let match;
|
|
29
|
+
while ((match = regex.exec(content)) !== null) {
|
|
30
|
+
const dateStr = match[1].trim();
|
|
31
|
+
const valueStr = match[2].trim().replace(',', '.');
|
|
32
|
+
|
|
33
|
+
const date = this.parseGermanDate(dateStr);
|
|
34
|
+
const value = parseFloat(valueStr);
|
|
35
|
+
|
|
36
|
+
if (date && !isNaN(value)) {
|
|
37
|
+
results.push({
|
|
38
|
+
timestamp: date.getTime(),
|
|
39
|
+
value: value,
|
|
40
|
+
dateObj: date,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Sort by date ascending
|
|
46
|
+
if (results.length > 0) {
|
|
47
|
+
results.sort((a, b) => a.timestamp - b.timestamp);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Parse german date string
|
|
55
|
+
*
|
|
56
|
+
* @param {string} dateStr - Date string
|
|
57
|
+
* @returns {Date|null} - Date object or null
|
|
58
|
+
*/
|
|
59
|
+
parseGermanDate(dateStr) {
|
|
60
|
+
try {
|
|
61
|
+
// Check for time component
|
|
62
|
+
let timeStr = '00:00:00';
|
|
63
|
+
let dStr = dateStr;
|
|
64
|
+
|
|
65
|
+
if (dateStr.includes(' ')) {
|
|
66
|
+
const parts = dateStr.split(/\s+/); // Handle multiple spaces
|
|
67
|
+
dStr = parts[0];
|
|
68
|
+
if (parts[1]) {
|
|
69
|
+
timeStr = parts[1];
|
|
70
|
+
// Add seconds if missing
|
|
71
|
+
if (timeStr.split(':').length === 2) {
|
|
72
|
+
timeStr += ':00';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const [day, month, year] = dStr.split('.');
|
|
78
|
+
// Simple check
|
|
79
|
+
if (!day || !month || !year) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create ISO string YYYY-MM-DDTHH:mm:ss
|
|
84
|
+
return new Date(`${year}-${month}-${day}T${timeStr}`);
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = EhbImporter;
|
package/lib/messagingHandler.js
CHANGED
|
@@ -152,8 +152,8 @@ class MessagingHandler {
|
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
const types = ['gas', 'water', 'electricity'];
|
|
156
|
-
const typesDe = { gas: 'Gas', water: 'Wasser', electricity: 'Strom' };
|
|
155
|
+
const types = ['gas', 'water', 'electricity', 'pv'];
|
|
156
|
+
const typesDe = { gas: 'Gas', water: 'Wasser', electricity: 'Strom', pv: 'PV' };
|
|
157
157
|
|
|
158
158
|
for (const type of types) {
|
|
159
159
|
const configType = this.adapter.consumptionManager.getConfigType(type);
|
package/lib/stateManager.js
CHANGED
|
@@ -30,6 +30,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
30
30
|
gas: { name: 'Gas', unit: 'kWh', volumeUnit: 'm³' },
|
|
31
31
|
water: { name: 'Wasser', unit: 'm³', volumeUnit: 'm³' },
|
|
32
32
|
electricity: { name: 'Strom', unit: 'kWh', volumeUnit: 'kWh' },
|
|
33
|
+
pv: { name: 'PV', unit: 'kWh', volumeUnit: 'kWh', consumption: 'Einspeisung', cost: 'Vergütung' },
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
const label = labels[type];
|
|
@@ -47,7 +48,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
47
48
|
await adapter.setObjectNotExistsAsync(`${type}.consumption`, {
|
|
48
49
|
type: 'channel',
|
|
49
50
|
common: {
|
|
50
|
-
name: 'Verbrauch',
|
|
51
|
+
name: label.consumption || 'Verbrauch',
|
|
51
52
|
},
|
|
52
53
|
native: {},
|
|
53
54
|
});
|
|
@@ -101,7 +102,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
101
102
|
await adapter.setObjectNotExistsAsync(`${type}.consumption.daily`, {
|
|
102
103
|
type: 'state',
|
|
103
104
|
common: {
|
|
104
|
-
name: `
|
|
105
|
+
name: `Tages-${(label.consumption || 'Verbrauch').toLowerCase()} (${label.unit})`,
|
|
105
106
|
type: 'number',
|
|
106
107
|
role: STATE_ROLES.consumption,
|
|
107
108
|
read: true,
|
|
@@ -115,7 +116,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
115
116
|
await adapter.setObjectNotExistsAsync(`${type}.consumption.monthly`, {
|
|
116
117
|
type: 'state',
|
|
117
118
|
common: {
|
|
118
|
-
name: `
|
|
119
|
+
name: `Monats-${(label.consumption || 'Verbrauch').toLowerCase()} (${label.unit})`,
|
|
119
120
|
type: 'number',
|
|
120
121
|
role: STATE_ROLES.consumption,
|
|
121
122
|
read: true,
|
|
@@ -129,7 +130,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
129
130
|
await adapter.setObjectNotExistsAsync(`${type}.consumption.yearly`, {
|
|
130
131
|
type: 'state',
|
|
131
132
|
common: {
|
|
132
|
-
name: `
|
|
133
|
+
name: `Jahres-${(label.consumption || 'Verbrauch').toLowerCase()} (${label.unit})`,
|
|
133
134
|
type: 'number',
|
|
134
135
|
role: STATE_ROLES.consumption,
|
|
135
136
|
read: true,
|
|
@@ -145,6 +146,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
145
146
|
electricity: 'strom',
|
|
146
147
|
water: 'wasser',
|
|
147
148
|
gas: 'gas',
|
|
149
|
+
pv: 'pv',
|
|
148
150
|
};
|
|
149
151
|
const configType = configTypeMap[type] || type;
|
|
150
152
|
|
|
@@ -195,7 +197,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
195
197
|
await adapter.setObjectNotExistsAsync(`${type}.costs`, {
|
|
196
198
|
type: 'channel',
|
|
197
199
|
common: {
|
|
198
|
-
name: 'Kosten',
|
|
200
|
+
name: label.cost || 'Kosten',
|
|
199
201
|
},
|
|
200
202
|
native: {},
|
|
201
203
|
});
|
|
@@ -203,7 +205,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
203
205
|
await adapter.setObjectNotExistsAsync(`${type}.costs.daily`, {
|
|
204
206
|
type: 'state',
|
|
205
207
|
common: {
|
|
206
|
-
name: '
|
|
208
|
+
name: `Tages-${(label.cost || 'Kosten').toLowerCase()} (€)`,
|
|
207
209
|
type: 'number',
|
|
208
210
|
role: STATE_ROLES.cost,
|
|
209
211
|
read: true,
|
|
@@ -217,7 +219,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
217
219
|
await adapter.setObjectNotExistsAsync(`${type}.costs.monthly`, {
|
|
218
220
|
type: 'state',
|
|
219
221
|
common: {
|
|
220
|
-
name: '
|
|
222
|
+
name: `Monats-${(label.cost || 'Kosten').toLowerCase()} (€)`,
|
|
221
223
|
type: 'number',
|
|
222
224
|
role: STATE_ROLES.cost,
|
|
223
225
|
read: true,
|
|
@@ -231,7 +233,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
231
233
|
await adapter.setObjectNotExistsAsync(`${type}.costs.yearly`, {
|
|
232
234
|
type: 'state',
|
|
233
235
|
common: {
|
|
234
|
-
name: '
|
|
236
|
+
name: `Jahres-${(label.cost || 'Kosten').toLowerCase()} (€)`,
|
|
235
237
|
type: 'number',
|
|
236
238
|
role: STATE_ROLES.cost,
|
|
237
239
|
read: true,
|
|
@@ -333,7 +335,7 @@ async function createUtilityStateStructure(adapter, type, _config = {}) {
|
|
|
333
335
|
await adapter.setObjectNotExistsAsync(`${type}.costs.totalYearly`, {
|
|
334
336
|
type: 'state',
|
|
335
337
|
common: {
|
|
336
|
-
name: '
|
|
338
|
+
name: `Gesamt-${(label.cost || 'Kosten').toLowerCase()} Jahr (Verbrauch + Grundgebühr) (€)`,
|
|
337
339
|
type: 'number',
|
|
338
340
|
role: STATE_ROLES.cost,
|
|
339
341
|
read: true,
|
package/main.js
CHANGED
|
@@ -9,6 +9,7 @@ const utils = require('@iobroker/adapter-core');
|
|
|
9
9
|
const ConsumptionManager = require('./lib/consumptionManager');
|
|
10
10
|
const BillingManager = require('./lib/billingManager');
|
|
11
11
|
const MessagingHandler = require('./lib/messagingHandler');
|
|
12
|
+
const ImportManager = require('./lib/importManager');
|
|
12
13
|
|
|
13
14
|
class NebenkostenMonitor extends utils.Adapter {
|
|
14
15
|
/**
|
|
@@ -28,6 +29,7 @@ class NebenkostenMonitor extends utils.Adapter {
|
|
|
28
29
|
this.consumptionManager = new ConsumptionManager(this);
|
|
29
30
|
this.billingManager = new BillingManager(this);
|
|
30
31
|
this.messagingHandler = new MessagingHandler(this);
|
|
32
|
+
this.importManager = new ImportManager(this);
|
|
31
33
|
|
|
32
34
|
this.periodicTimers = {};
|
|
33
35
|
}
|
|
@@ -42,6 +44,7 @@ class NebenkostenMonitor extends utils.Adapter {
|
|
|
42
44
|
await this.initializeUtility('gas', this.config.gasAktiv);
|
|
43
45
|
await this.initializeUtility('water', this.config.wasserAktiv);
|
|
44
46
|
await this.initializeUtility('electricity', this.config.stromAktiv);
|
|
47
|
+
await this.initializeUtility('pv', this.config.pvAktiv);
|
|
45
48
|
|
|
46
49
|
// Subscribe to billing period closure triggers
|
|
47
50
|
this.subscribeStates('*.billing.closePeriod');
|
|
@@ -169,7 +172,7 @@ class NebenkostenMonitor extends utils.Adapter {
|
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
// Determine which utility this sensor belongs to
|
|
172
|
-
const types = ['gas', 'water', 'electricity'];
|
|
175
|
+
const types = ['gas', 'water', 'electricity', 'pv'];
|
|
173
176
|
for (const type of types) {
|
|
174
177
|
const configType = this.consumptionManager.getConfigType(type);
|
|
175
178
|
if (this.config[`${configType}Aktiv`] && this.config[`${configType}SensorDP`] === id) {
|
|
@@ -187,7 +190,14 @@ class NebenkostenMonitor extends utils.Adapter {
|
|
|
187
190
|
* @param {Record<string, any>} obj - Message object from config
|
|
188
191
|
*/
|
|
189
192
|
async onMessage(obj) {
|
|
190
|
-
|
|
193
|
+
if (obj.command === 'importData') {
|
|
194
|
+
const result = await this.importManager.handleImportMessage(obj);
|
|
195
|
+
if (obj.callback) {
|
|
196
|
+
this.sendTo(obj.from, obj.command, result, obj.callback);
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
await this.messagingHandler.handleMessage(obj);
|
|
200
|
+
}
|
|
191
201
|
}
|
|
192
202
|
}
|
|
193
203
|
|