iobroker.poolcontrol 0.2.1 → 0.3.0
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 +56 -63
- package/io-package.json +27 -55
- package/lib/helpers/controlHelper.js +6 -0
- package/lib/helpers/controlHelper2.js +195 -0
- package/lib/helpers/frostHelper.js +2 -2
- package/lib/helpers/migrationHelper.js +117 -0
- package/lib/helpers/pumpHelper2.js +160 -0
- package/lib/helpers/pumpHelper3.js +259 -0
- package/lib/helpers/runtimeHelper.js +12 -2
- package/lib/helpers/solarHelper.js +2 -2
- package/lib/helpers/statusHelper.js +35 -10
- package/lib/stateDefinitions/controlStates.js +118 -14
- package/lib/stateDefinitions/pumpStates.js +52 -7
- package/lib/stateDefinitions/pumpStates2.js +125 -0
- package/lib/stateDefinitions/pumpStates3.js +222 -0
- package/lib/stateDefinitions/solarStates.js +57 -26
- package/lib/stateDefinitions/speechStates.js +34 -8
- package/lib/stateDefinitions/statusStates.js +8 -2
- package/main.js +37 -0
- package/package.json +3 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* migrationHelper
|
|
5
|
+
* --------------------------------------------------
|
|
6
|
+
* Führt nachträgliche Struktur- oder State-Anpassungen
|
|
7
|
+
* für bestehende Installationen durch.
|
|
8
|
+
*
|
|
9
|
+
* - Wird beim Adapterstart einmalig ausgeführt.
|
|
10
|
+
* - Korrigiert veraltete Definitionen (z. B. Schreibrechte, persist-Flags, etc.)
|
|
11
|
+
*
|
|
12
|
+
* Version: 1.0.1
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const migrationHelper = {
|
|
16
|
+
adapter: null,
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Initialisiert den Migration-Helper.
|
|
20
|
+
* Wird einmalig beim Adapterstart aufgerufen.
|
|
21
|
+
*
|
|
22
|
+
* @param {ioBroker.Adapter} adapter - Die aktive Adapterinstanz
|
|
23
|
+
*/
|
|
24
|
+
async init(adapter) {
|
|
25
|
+
this.adapter = adapter;
|
|
26
|
+
this.adapter.log.info('[migrationHelper] Starte Migration-Check ...');
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// ------------------------------------------------------
|
|
30
|
+
// Hier alle Migrationsroutinen nacheinander aufrufen
|
|
31
|
+
// ------------------------------------------------------
|
|
32
|
+
await this._fixSpeechQueue();
|
|
33
|
+
await this._fixSolarWarnActivePersist();
|
|
34
|
+
|
|
35
|
+
// Weitere Routinen folgen hier später:
|
|
36
|
+
// await this._ensurePumpReason();
|
|
37
|
+
// await this._cleanupOldStates();
|
|
38
|
+
|
|
39
|
+
this.adapter.log.debug('[migrationHelper] Migration-Checks abgeschlossen.');
|
|
40
|
+
} catch (err) {
|
|
41
|
+
this.adapter.log.warn(`[migrationHelper] Fehler beim Migration-Check: ${err.message}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.adapter.log.info('[migrationHelper] Migration-Helper beendet.');
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// ------------------------------------------------------
|
|
48
|
+
// Migration: Schreibrecht für speech.queue korrigieren
|
|
49
|
+
// ------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Prüft und korrigiert den State "speech.queue", falls er noch write:false gesetzt hat.
|
|
53
|
+
* Dadurch verschwinden Warnungen beim Schreiben (Read-only state ... written without ack).
|
|
54
|
+
*/
|
|
55
|
+
async _fixSpeechQueue() {
|
|
56
|
+
const id = 'speech.queue';
|
|
57
|
+
try {
|
|
58
|
+
const obj = await this.adapter.getObjectAsync(id);
|
|
59
|
+
if (!obj) {
|
|
60
|
+
this.adapter.log.debug(`[migrationHelper] ${id} existiert nicht – keine Anpassung nötig.`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const isReadOnly = obj.common?.write === false;
|
|
65
|
+
if (isReadOnly) {
|
|
66
|
+
this.adapter.log.info(`[migrationHelper] Aktualisiere Schreibrecht für ${id} → write:true`);
|
|
67
|
+
await this.adapter.extendObjectAsync(id, {
|
|
68
|
+
common: {
|
|
69
|
+
write: true,
|
|
70
|
+
desc: 'Nur intern durch den Adapter beschreibbar (nicht manuell ändern!)',
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
this.adapter.log.debug(`[migrationHelper] ${id} ist bereits korrekt konfiguriert.`);
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
this.adapter.log.warn(`[migrationHelper] Fehler bei Prüfung von ${id}: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
// ------------------------------------------------------
|
|
82
|
+
// Migration: persist-Flag für solar.warn_active ergänzen
|
|
83
|
+
// ------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Ergänzt persist:true bei solar.warn_active,
|
|
87
|
+
* damit die Einstellung (Warnfunktion aktivieren/deaktivieren)
|
|
88
|
+
* nach einem Neustart erhalten bleibt.
|
|
89
|
+
*/
|
|
90
|
+
async _fixSolarWarnActivePersist() {
|
|
91
|
+
const id = 'solar.warn_active';
|
|
92
|
+
try {
|
|
93
|
+
const obj = await this.adapter.getObjectAsync(id);
|
|
94
|
+
if (!obj) {
|
|
95
|
+
this.adapter.log.debug(`[migrationHelper] ${id} existiert nicht – keine Anpassung nötig.`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const hasPersist = obj.common?.persist === true;
|
|
100
|
+
if (!hasPersist) {
|
|
101
|
+
this.adapter.log.info(`[migrationHelper] Ergänze persist:true für ${id}`);
|
|
102
|
+
await this.adapter.extendObjectAsync(id, {
|
|
103
|
+
common: {
|
|
104
|
+
persist: true,
|
|
105
|
+
desc: `${obj.common?.desc || ''} (automatisch per Migration persistiert)`,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
} else {
|
|
109
|
+
this.adapter.log.debug(`[migrationHelper] ${id} ist bereits mit persist:true versehen.`);
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
this.adapter.log.warn(`[migrationHelper] Fehler bei Prüfung von ${id}: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
module.exports = migrationHelper;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pumpHelper2.js
|
|
5
|
+
* ----------------------------------------------------------
|
|
6
|
+
* Ergänzende Berechnungslogik für Pumpen-Livewerte
|
|
7
|
+
* (reeller Durchfluss, Prozentleistung, letzter Durchflusswert).
|
|
8
|
+
*
|
|
9
|
+
* Verwendet vorhandene States:
|
|
10
|
+
* - pump.current_power
|
|
11
|
+
* - pump.pump_max_watt
|
|
12
|
+
* - pump.pump_power_lph
|
|
13
|
+
* - pump.pump_switch
|
|
14
|
+
*
|
|
15
|
+
* Schreibt neue Werte in:
|
|
16
|
+
* - pump.live.current_power_w
|
|
17
|
+
* - pump.live.flow_current_lh
|
|
18
|
+
* - pump.live.flow_percent
|
|
19
|
+
* - pump.live.last_flow_lh
|
|
20
|
+
*
|
|
21
|
+
* Alle Zielstates sind persistent (siehe pumpStates2.js).
|
|
22
|
+
* ----------------------------------------------------------
|
|
23
|
+
* Version: 1.0.0
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const pumpHelper2 = {
|
|
27
|
+
adapter: null,
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Initialisiert den PumpHelper2
|
|
31
|
+
*
|
|
32
|
+
* @param {ioBroker.Adapter} adapter - Aktive ioBroker Adapterinstanz
|
|
33
|
+
*/
|
|
34
|
+
async init(adapter) {
|
|
35
|
+
this.adapter = adapter;
|
|
36
|
+
this.adapter.log.info('[pumpHelper2] Initialisierung gestartet');
|
|
37
|
+
|
|
38
|
+
// Relevante States überwachen
|
|
39
|
+
this.adapter.subscribeStates('pump.current_power');
|
|
40
|
+
this.adapter.subscribeStates('pump.pump_switch');
|
|
41
|
+
|
|
42
|
+
// Initialwerte berechnen
|
|
43
|
+
await this._updateLiveValues();
|
|
44
|
+
|
|
45
|
+
this.adapter.log.info('[pumpHelper2] Erfolgreich initialisiert');
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* StateChange-Verarbeitung
|
|
50
|
+
*
|
|
51
|
+
* @param {string} id - State-ID
|
|
52
|
+
* @param {ioBroker.State | null | undefined} state - Neuer Statewert
|
|
53
|
+
*/
|
|
54
|
+
async handleStateChange(id, state) {
|
|
55
|
+
if (!state || state.ack === false) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Leistungsänderung → reelle Durchflusswerte aktualisieren
|
|
60
|
+
if (id.endsWith('pump.current_power')) {
|
|
61
|
+
await this._updateLiveValues();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Pumpenstatus-Änderung → ggf. letzten Durchflusswert speichern
|
|
65
|
+
if (id.endsWith('pump.pump_switch')) {
|
|
66
|
+
const pumpOn = state.val === true;
|
|
67
|
+
if (!pumpOn) {
|
|
68
|
+
await this._storeLastFlowValue();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Führt die Berechnung der Livewerte durch.
|
|
75
|
+
* (Nur wenn gültige Basiswerte vorhanden sind.)
|
|
76
|
+
*/
|
|
77
|
+
async _updateLiveValues() {
|
|
78
|
+
try {
|
|
79
|
+
const [currentPower, maxPower, nominalFlow] = await Promise.all([
|
|
80
|
+
this._getNumber('pump.current_power'),
|
|
81
|
+
this._getNumber('pump.pump_max_watt'),
|
|
82
|
+
this._getNumber('pump.pump_power_lph'),
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
// Schutz gegen ungültige Werte
|
|
86
|
+
if (maxPower <= 0 || nominalFlow <= 0) {
|
|
87
|
+
this.adapter.log.debug('[pumpHelper2] Ungültige Basiswerte, Berechnung übersprungen');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Prozentuale Auslastung
|
|
92
|
+
const flowPercent = Math.min(Math.max((currentPower / maxPower) * 100, 0), 100);
|
|
93
|
+
|
|
94
|
+
// Reeller Durchfluss
|
|
95
|
+
const flowCurrentLh = Math.round(nominalFlow * (currentPower / maxPower) * 10) / 10; // 1 Nachkommastelle
|
|
96
|
+
|
|
97
|
+
// In States schreiben
|
|
98
|
+
await this._setIfChanged('pump.live.current_power_w', currentPower);
|
|
99
|
+
await this._setIfChanged('pump.live.flow_current_lh', flowCurrentLh);
|
|
100
|
+
await this._setIfChanged('pump.live.flow_percent', flowPercent);
|
|
101
|
+
|
|
102
|
+
this.adapter.log.debug(
|
|
103
|
+
`[pumpHelper2] Reeller Durchfluss aktualisiert: ${flowCurrentLh} l/h (${flowPercent.toFixed(1)}%)`,
|
|
104
|
+
);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
this.adapter.log.warn(`[pumpHelper2] Fehler bei _updateLiveValues: ${err.message}`);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Speichert den letzten bekannten Durchflusswert beim Pumpen-Stopp.
|
|
112
|
+
*/
|
|
113
|
+
async _storeLastFlowValue() {
|
|
114
|
+
try {
|
|
115
|
+
const currentFlow = await this._getNumber('pump.live.flow_current_lh');
|
|
116
|
+
await this._setIfChanged('pump.live.last_flow_lh', currentFlow);
|
|
117
|
+
this.adapter.log.debug(`[pumpHelper2] Letzter Durchflusswert gespeichert: ${currentFlow} l/h`);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
this.adapter.log.warn(`[pumpHelper2] Fehler bei _storeLastFlowValue: ${err.message}`);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Liest einen numerischen Statewert (oder 0 bei Fehler).
|
|
125
|
+
*
|
|
126
|
+
* @param {string} id - Objekt-ID des zu lesenden States
|
|
127
|
+
* @returns {Promise<number>} - Aktueller numerischer Wert oder 0
|
|
128
|
+
*/
|
|
129
|
+
async _getNumber(id) {
|
|
130
|
+
const state = await this.adapter.getStateAsync(id);
|
|
131
|
+
const val = Number(state?.val);
|
|
132
|
+
return isNaN(val) ? 0 : val;
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Schreibt neuen Wert nur, wenn er sich geändert hat.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} id - Objekt-ID des zu schreibenden States
|
|
139
|
+
* @param {number} newVal - Neuer Wert, des gesetzt werden soll
|
|
140
|
+
*/
|
|
141
|
+
async _setIfChanged(id, newVal) {
|
|
142
|
+
const current = await this.adapter.getStateAsync(id);
|
|
143
|
+
if (current && Number(current.val) === newVal) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
await this.adapter.setStateAsync(id, { val: newVal, ack: true });
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Cleanup bei Adapter-Unload
|
|
151
|
+
* Wird aktuell nur als Platzhalter verwendet.
|
|
152
|
+
*/
|
|
153
|
+
cleanup() {
|
|
154
|
+
// Derzeit keine Timer oder Intervalle vorhanden
|
|
155
|
+
// Platzhalter für zukünftige Erweiterungen
|
|
156
|
+
this.adapter?.log.debug('[pumpHelper2] Cleanup ausgeführt.');
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
module.exports = pumpHelper2;
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pumpHelper3.js
|
|
5
|
+
* ----------------------------------------------------------
|
|
6
|
+
* Lern- und Analysemodul für die Pumpe
|
|
7
|
+
* - Ermittelt Durchschnitts- und Normalwerte aus realen Laufzyklen
|
|
8
|
+
* - Berechnet Abweichungen (Leistung & Durchfluss)
|
|
9
|
+
* - Liefert textbasierte Statusbewertung ("im Normalbereich" etc.)
|
|
10
|
+
* - Keine Sprach- oder Queue-Ausgabe
|
|
11
|
+
*
|
|
12
|
+
* Verwaltet:
|
|
13
|
+
* pump.learning.*
|
|
14
|
+
*
|
|
15
|
+
* Abhängigkeiten:
|
|
16
|
+
* - pump.live.current_power_w
|
|
17
|
+
* - pump.live.flow_current_lh
|
|
18
|
+
* - pump.pump_switch
|
|
19
|
+
*
|
|
20
|
+
* Version: 1.0.0
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const pumpHelper3 = {
|
|
24
|
+
adapter: null,
|
|
25
|
+
|
|
26
|
+
/** interner Speicher */
|
|
27
|
+
currentSessionValues: {
|
|
28
|
+
power: [],
|
|
29
|
+
flow: [],
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialisiert den Helper
|
|
34
|
+
*
|
|
35
|
+
* @param {ioBroker.Adapter} adapter - aktive Adapterinstanz
|
|
36
|
+
*/
|
|
37
|
+
async init(adapter) {
|
|
38
|
+
this.adapter = adapter;
|
|
39
|
+
this.adapter.log.info('[pumpHelper3] Initialisierung gestartet');
|
|
40
|
+
|
|
41
|
+
// Relevante States überwachen
|
|
42
|
+
this.adapter.subscribeStates('pump.pump_switch');
|
|
43
|
+
this.adapter.subscribeStates('pump.live.current_power_w');
|
|
44
|
+
this.adapter.subscribeStates('pump.live.flow_current_lh');
|
|
45
|
+
|
|
46
|
+
this.adapter.log.info('[pumpHelper3] Erfolgreich initialisiert');
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Verarbeitet State-Änderungen
|
|
51
|
+
*
|
|
52
|
+
* @param {string} id - State-ID
|
|
53
|
+
* @param {ioBroker.State} state - neuer Statewert
|
|
54
|
+
*/
|
|
55
|
+
async handleStateChange(id, state) {
|
|
56
|
+
if (!state || state.ack === false) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
if (id.endsWith('pump.pump_switch')) {
|
|
62
|
+
const isOn = state.val === true;
|
|
63
|
+
if (isOn) {
|
|
64
|
+
// Pumpe startet → neue Sitzung
|
|
65
|
+
this.currentSessionValues.power = [];
|
|
66
|
+
this.currentSessionValues.flow = [];
|
|
67
|
+
this.adapter.log.debug('[pumpHelper3] Neue Lern-Session gestartet');
|
|
68
|
+
} else {
|
|
69
|
+
// Pumpe stoppt → Lernwerte aktualisieren
|
|
70
|
+
await this._finalizeLearningCycle();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Wenn Pumpe läuft, aktuelle Werte sammeln
|
|
75
|
+
const pumpRunning = !!(await this.adapter.getStateAsync('pump.pump_switch'))?.val;
|
|
76
|
+
if (pumpRunning) {
|
|
77
|
+
if (id.endsWith('pump.live.current_power_w')) {
|
|
78
|
+
this._pushValue('power', state.val);
|
|
79
|
+
}
|
|
80
|
+
if (id.endsWith('pump.live.flow_current_lh')) {
|
|
81
|
+
this._pushValue('flow', state.val);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Bei jeder Änderung der Livewerte aktuelle Abweichung bewerten
|
|
86
|
+
await this._updateDeviationAndStatus();
|
|
87
|
+
} catch (err) {
|
|
88
|
+
this.adapter.log.warn(`[pumpHelper3] Fehler bei handleStateChange: ${err.message}`);
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Fügt einen neuen Wert in den temporären Speicher ein.
|
|
94
|
+
*
|
|
95
|
+
* @param {"power"|"flow"} type - Art des Wertes
|
|
96
|
+
* @param {number} value - neuer Messwert
|
|
97
|
+
*/
|
|
98
|
+
_pushValue(type, value) {
|
|
99
|
+
if (!this.currentSessionValues[type]) {
|
|
100
|
+
this.currentSessionValues[type] = [];
|
|
101
|
+
}
|
|
102
|
+
if (typeof value === 'number' && value > 0) {
|
|
103
|
+
this.currentSessionValues[type].push(value);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Wird aufgerufen, wenn ein Pumpenlauf endet.
|
|
109
|
+
* Berechnet Durchschnittswerte des Laufs und aktualisiert Lernfelder.
|
|
110
|
+
*/
|
|
111
|
+
async _finalizeLearningCycle() {
|
|
112
|
+
try {
|
|
113
|
+
const { power, flow } = this.currentSessionValues;
|
|
114
|
+
if (power.length === 0 || flow.length === 0) {
|
|
115
|
+
this.adapter.log.debug('[pumpHelper3] Keine Werte zum Lernen vorhanden, Zyklus übersprungen');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const avgPower = this._average(power);
|
|
120
|
+
const avgFlow = this._average(flow);
|
|
121
|
+
|
|
122
|
+
// Vorhandene Lernwerte lesen
|
|
123
|
+
const learnedPower = (await this._getNumber('pump.learning.learned_avg_power_w')) || 0;
|
|
124
|
+
const learnedFlow = (await this._getNumber('pump.learning.learned_avg_flow_lh')) || 0;
|
|
125
|
+
const cycles = (await this._getNumber('pump.learning.learning_cycles_total')) || 0;
|
|
126
|
+
|
|
127
|
+
// Gleitenden Durchschnitt berechnen
|
|
128
|
+
const newCycles = cycles + 1;
|
|
129
|
+
const newAvgPower = (learnedPower * cycles + avgPower) / newCycles;
|
|
130
|
+
const newAvgFlow = (learnedFlow * cycles + avgFlow) / newCycles;
|
|
131
|
+
|
|
132
|
+
// Normalbereiche ±15 %
|
|
133
|
+
const rangePowerLow = Math.round(newAvgPower * 0.85);
|
|
134
|
+
const rangePowerHigh = Math.round(newAvgPower * 1.15);
|
|
135
|
+
const rangeFlowLow = Math.round(newAvgFlow * 0.85);
|
|
136
|
+
const rangeFlowHigh = Math.round(newAvgFlow * 1.15);
|
|
137
|
+
|
|
138
|
+
// States schreiben
|
|
139
|
+
await this.adapter.setStateAsync('pump.learning.learned_avg_power_w', {
|
|
140
|
+
val: Math.round(newAvgPower),
|
|
141
|
+
ack: true,
|
|
142
|
+
});
|
|
143
|
+
await this.adapter.setStateAsync('pump.learning.learned_avg_flow_lh', {
|
|
144
|
+
val: Math.round(newAvgFlow),
|
|
145
|
+
ack: true,
|
|
146
|
+
});
|
|
147
|
+
await this.adapter.setStateAsync('pump.learning.normal_range_power_low', { val: rangePowerLow, ack: true });
|
|
148
|
+
await this.adapter.setStateAsync('pump.learning.normal_range_power_high', {
|
|
149
|
+
val: rangePowerHigh,
|
|
150
|
+
ack: true,
|
|
151
|
+
});
|
|
152
|
+
await this.adapter.setStateAsync('pump.learning.normal_range_flow_low', { val: rangeFlowLow, ack: true });
|
|
153
|
+
await this.adapter.setStateAsync('pump.learning.normal_range_flow_high', { val: rangeFlowHigh, ack: true });
|
|
154
|
+
await this.adapter.setStateAsync('pump.learning.learning_cycles_total', { val: newCycles, ack: true });
|
|
155
|
+
|
|
156
|
+
this.adapter.log.debug(
|
|
157
|
+
`[pumpHelper3] Lernzyklus #${newCycles} abgeschlossen (Power Ø${avgPower}W, Flow Ø${avgFlow}l/h)`,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// Speicher leeren
|
|
161
|
+
this.currentSessionValues.power = [];
|
|
162
|
+
this.currentSessionValues.flow = [];
|
|
163
|
+
} catch (err) {
|
|
164
|
+
this.adapter.log.warn(`[pumpHelper3] Fehler bei _finalizeLearningCycle: ${err.message}`);
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Bewertet aktuelle Abweichungen und schreibt Status.
|
|
170
|
+
*/
|
|
171
|
+
async _updateDeviationAndStatus() {
|
|
172
|
+
try {
|
|
173
|
+
const currentPower = await this._getNumber('pump.live.current_power_w');
|
|
174
|
+
const currentFlow = await this._getNumber('pump.live.flow_current_lh');
|
|
175
|
+
const avgPower = await this._getNumber('pump.learning.learned_avg_power_w');
|
|
176
|
+
const avgFlow = await this._getNumber('pump.learning.learned_avg_flow_lh');
|
|
177
|
+
|
|
178
|
+
if (avgPower <= 0 || avgFlow <= 0) {
|
|
179
|
+
return;
|
|
180
|
+
} // Noch kein Lernwert vorhanden
|
|
181
|
+
|
|
182
|
+
// Prozentuale Abweichungen
|
|
183
|
+
const deviationPower = ((currentPower - avgPower) / avgPower) * 100;
|
|
184
|
+
const deviationFlow = ((currentFlow - avgFlow) / avgFlow) * 100;
|
|
185
|
+
|
|
186
|
+
await this.adapter.setStateAsync('pump.learning.deviation_power_percent', {
|
|
187
|
+
val: Math.round(deviationPower * 10) / 10,
|
|
188
|
+
ack: true,
|
|
189
|
+
});
|
|
190
|
+
await this.adapter.setStateAsync('pump.learning.deviation_flow_percent', {
|
|
191
|
+
val: Math.round(deviationFlow * 10) / 10,
|
|
192
|
+
ack: true,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Bewertung
|
|
196
|
+
const statusText = this._getStatusText(deviationPower, deviationFlow);
|
|
197
|
+
await this.adapter.setStateAsync('pump.learning.status_text', { val: statusText, ack: true });
|
|
198
|
+
} catch (err) {
|
|
199
|
+
this.adapter.log.warn(`[pumpHelper3] Fehler bei _updateDeviationAndStatus: ${err.message}`);
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Liefert textbasierte Bewertung anhand der Abweichung.
|
|
205
|
+
*
|
|
206
|
+
* @param devPower - Aktuelle Abweichung der Leistung (W)
|
|
207
|
+
* @param devFlow - Aktuelle Abweichung des Durchflusses (L/H)
|
|
208
|
+
*/
|
|
209
|
+
_getStatusText(devPower, devFlow) {
|
|
210
|
+
const absPower = Math.abs(devPower);
|
|
211
|
+
const absFlow = Math.abs(devFlow);
|
|
212
|
+
|
|
213
|
+
if (absPower <= 15 && absFlow <= 15) {
|
|
214
|
+
return 'Pumpe läuft im Normalbereich';
|
|
215
|
+
}
|
|
216
|
+
if (devPower < -15 || devFlow < -15) {
|
|
217
|
+
return 'Pumpe läuft unterhalb des Normalbereichs (möglicher Filterdruck)';
|
|
218
|
+
}
|
|
219
|
+
if (devPower > 15 || devFlow > 15) {
|
|
220
|
+
return 'Pumpe läuft oberhalb des Normalbereichs (möglicher Luftstau)';
|
|
221
|
+
}
|
|
222
|
+
return 'Pumpe außerhalb des bekannten Bereichs';
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Erzeugt Durchschnittswerte aus einem Array numerischer Werte.
|
|
227
|
+
*
|
|
228
|
+
* @param {number[]} arr - Wertearray
|
|
229
|
+
* @returns {number} - Durchschnittswert
|
|
230
|
+
*/
|
|
231
|
+
_average(arr) {
|
|
232
|
+
if (!arr || arr.length === 0) {
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
return arr.reduce((a, b) => a + b, 0) / arr.length;
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Liest einen numerischen Statewert oder 0 bei Fehler.
|
|
240
|
+
*
|
|
241
|
+
* @param {string} id - Objekt-ID
|
|
242
|
+
* @returns {Promise<number>} - Gelesener Wert oder 0
|
|
243
|
+
*/
|
|
244
|
+
async _getNumber(id) {
|
|
245
|
+
const state = await this.adapter.getStateAsync(id);
|
|
246
|
+
const val = Number(state?.val);
|
|
247
|
+
return isNaN(val) ? 0 : val;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Cleanup bei Adapter-Unload
|
|
252
|
+
*/
|
|
253
|
+
cleanup() {
|
|
254
|
+
this.currentSessionValues = { power: [], flow: [] };
|
|
255
|
+
this.adapter?.log.debug('[pumpHelper3] Cleanup ausgeführt.');
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
module.exports = pumpHelper3;
|
|
@@ -135,14 +135,24 @@ const runtimeHelper = {
|
|
|
135
135
|
await this.adapter.setStateAsync('runtime.start_count_today', { val: this.startCountToday, ack: true });
|
|
136
136
|
|
|
137
137
|
// Umwälzmenge berechnen
|
|
138
|
-
|
|
138
|
+
// Reeller Durchflusswert aus pump.live.flow_current_lh
|
|
139
|
+
const liveFlowLh = (await this.adapter.getStateAsync('pump.live.flow_current_lh'))?.val || 0;
|
|
140
|
+
|
|
141
|
+
if (liveFlowLh <= 0) {
|
|
142
|
+
this.adapter.log.debug('[runtimeHelper] Kein Live-Durchflusswert vorhanden, Berechnung übersprungen');
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Poolparameter laden
|
|
139
147
|
const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
|
|
140
148
|
const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
|
|
141
149
|
|
|
142
|
-
|
|
150
|
+
// Berechnung der realen Tagesumwälzung (Liter)
|
|
151
|
+
const dailyTotal = Math.round((effectiveToday / 3600) * liveFlowLh);
|
|
143
152
|
const dailyRequired = Math.round(poolSize * minCirc);
|
|
144
153
|
const dailyRemaining = Math.max(dailyRequired - dailyTotal, 0);
|
|
145
154
|
|
|
155
|
+
// Werte schreiben
|
|
146
156
|
await this.adapter.setStateAsync('circulation.daily_total', { val: dailyTotal, ack: true });
|
|
147
157
|
await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
|
|
148
158
|
await this.adapter.setStateAsync('circulation.daily_remaining', { val: dailyRemaining, ack: true });
|
|
@@ -33,8 +33,8 @@ const solarHelper = {
|
|
|
33
33
|
async _checkSolar() {
|
|
34
34
|
try {
|
|
35
35
|
// --- NEU: Vorrangprüfung durch ControlHelper ---
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
36
|
+
const activeHelper = (await this.adapter.getStateAsync('pump.active_helper'))?.val || '';
|
|
37
|
+
if (activeHelper === 'controlHelper') {
|
|
38
38
|
this.adapter.log.debug('[solarHelper] Vorrang durch ControlHelper aktiv – Solarregelung pausiert.');
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
@@ -12,6 +12,7 @@ const statusHelper = {
|
|
|
12
12
|
adapter: null,
|
|
13
13
|
midnightTimer: null,
|
|
14
14
|
pumpOn: null, // interner Merker für Pumpenstatus
|
|
15
|
+
_lastSummaryUpdate: 0, // FIX: Zeitstempel für Throttle-Schutz
|
|
15
16
|
|
|
16
17
|
init(adapter) {
|
|
17
18
|
this.adapter = adapter;
|
|
@@ -95,8 +96,23 @@ const statusHelper = {
|
|
|
95
96
|
await this.updateSummary();
|
|
96
97
|
},
|
|
97
98
|
|
|
99
|
+
// FIX: Hilfsfunktion zur sicheren Formatierung
|
|
100
|
+
safeValue(v, digits = 1) {
|
|
101
|
+
if (v == null || isNaN(v)) {
|
|
102
|
+
return '–';
|
|
103
|
+
}
|
|
104
|
+
return Number(v).toFixed(digits);
|
|
105
|
+
},
|
|
106
|
+
|
|
98
107
|
async updateSummary() {
|
|
99
108
|
try {
|
|
109
|
+
// FIX: Throttle - Mehrfachupdates innerhalb 1 Sekunde vermeiden
|
|
110
|
+
if (Date.now() - this._lastSummaryUpdate < 1000) {
|
|
111
|
+
this.adapter.log.debug('[statusHelper] UpdateSummary übersprungen (Throttle)');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
this._lastSummaryUpdate = Date.now();
|
|
115
|
+
|
|
100
116
|
// Werte laden
|
|
101
117
|
const pumpStatus = (await this.adapter.getStateAsync('pump.status'))?.val || 'unbekannt';
|
|
102
118
|
const pumpMode = (await this.adapter.getStateAsync('pump.mode'))?.val || 'unknown';
|
|
@@ -112,32 +128,41 @@ const statusHelper = {
|
|
|
112
128
|
// Laufzeit formatieren
|
|
113
129
|
const h = Math.floor(runtimeToday / 3600);
|
|
114
130
|
const m = Math.floor((runtimeToday % 3600) / 60);
|
|
115
|
-
const runtimeFormatted = `${h}h ${m}m`;
|
|
131
|
+
const runtimeFormatted = isNaN(h) || isNaN(m) ? '0h 00m' : `${h}h ${m}m`; // FIX: Schutz gegen NaN
|
|
116
132
|
|
|
117
133
|
// Umwälzungsquote
|
|
118
134
|
let circulationPct = 0;
|
|
119
135
|
if (dailyRequired > 0) {
|
|
120
136
|
circulationPct = Math.round((dailyTotal / dailyRequired) * 100);
|
|
121
137
|
}
|
|
138
|
+
if (isNaN(circulationPct)) {
|
|
139
|
+
circulationPct = 0;
|
|
140
|
+
} // FIX: NaN-Absicherung
|
|
122
141
|
|
|
123
142
|
// Text bauen
|
|
124
143
|
let text = `Pumpe: ${pumpStatus}`;
|
|
125
144
|
if (pumpMode && pumpMode !== 'unknown') {
|
|
126
145
|
text += ` (Modus: ${pumpMode})`;
|
|
127
146
|
}
|
|
147
|
+
|
|
148
|
+
const safe = this.safeValue.bind(this); // FIX: Kurzreferenz
|
|
128
149
|
if (poolTemp != null) {
|
|
129
|
-
text += `. Pool: ${poolTemp
|
|
150
|
+
text += `. Pool: ${safe(poolTemp)} °C`;
|
|
130
151
|
}
|
|
131
152
|
if (collectorTemp != null) {
|
|
132
|
-
text += `, Kollektor: ${collectorTemp
|
|
153
|
+
text += `, Kollektor: ${safe(collectorTemp)} °C`;
|
|
133
154
|
}
|
|
134
155
|
if (outsideTemp != null) {
|
|
135
|
-
text += `, Außentemperatur: ${outsideTemp
|
|
156
|
+
text += `, Außentemperatur: ${safe(outsideTemp)} °C`;
|
|
136
157
|
}
|
|
137
158
|
text += `. Tageslaufzeit: ${runtimeFormatted} (${circulationPct}% der Soll-Umwälzung).`;
|
|
138
159
|
|
|
139
|
-
// In States schreiben
|
|
140
|
-
await this.adapter.
|
|
160
|
+
// In States schreiben (nur bei Änderung)
|
|
161
|
+
const current = (await this.adapter.getStateAsync('status.summary'))?.val;
|
|
162
|
+
if (current !== text) {
|
|
163
|
+
await this.adapter.setStateAsync('status.summary', { val: text, ack: true });
|
|
164
|
+
}
|
|
165
|
+
|
|
141
166
|
await this.adapter.setStateAsync('status.last_summary_update', {
|
|
142
167
|
val: new Date().toISOString(),
|
|
143
168
|
ack: true,
|
|
@@ -147,10 +172,10 @@ const statusHelper = {
|
|
|
147
172
|
const json = {
|
|
148
173
|
pump: pumpStatus,
|
|
149
174
|
mode: pumpMode,
|
|
150
|
-
pool: poolTemp,
|
|
151
|
-
collector: collectorTemp,
|
|
152
|
-
outside: outsideTemp,
|
|
153
|
-
runtime_today: runtimeToday,
|
|
175
|
+
pool: poolTemp ?? null,
|
|
176
|
+
collector: collectorTemp ?? null,
|
|
177
|
+
outside: outsideTemp ?? null,
|
|
178
|
+
runtime_today: runtimeToday ?? 0,
|
|
154
179
|
runtime_formatted: runtimeFormatted,
|
|
155
180
|
circulation_pct: circulationPct,
|
|
156
181
|
};
|