iobroker.sprinklecontrol 0.2.19 → 1.0.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.
@@ -1,978 +1,1414 @@
1
- 'use strict';
2
-
3
- const myConfig = require('./myConfig.js');
4
- const addTime = require('./tools.js').addTime;
5
- const formatTime = require('./tools').formatTime;
6
- const sendMessageText = require('./sendMessageText.js'); // sendMessageText
7
-
8
- /**
9
- * The adapter instance
10
- */
11
- let adapter;
12
-
13
- /**
14
- * Thread-list
15
- * Auflistung aller aktiver Sprenger-Kreise
16
- */
17
- const threadList = [];
18
-
19
- /** bereit für Boost */
20
- let boostReady = true;
21
- /** Boost ist aktive */
22
- let boostOn = false;
23
- /** Timer für die Länge des */
24
- let boostListTimer;
25
- /** maximal zulässige Anzahl der eingeschalteten Ventile */
26
- let maxParallel = 0;
27
- /** Füllstand der Zisterne */
28
- let fillLevelCistern = 0;
29
- let statusCistern = '';
30
- /** Speicherort von setTimeout */
31
- let idValveDelayTime;
32
-
33
- const currentPumpUse = {
34
- /** Pumpen aktive? */
35
- enable: false,
36
- /** Zisterne aktive? */
37
- pumpCistern: false,
38
- /** Pumpen-Bezeichnung; z.B. "hm-rpc.0.MEQ1810129.1.STATE" */
39
- pumpName: '',
40
- /** Pumpenleistung in l/h */
41
- pumpPower: 0
42
- };
43
-
44
- const updateListMarker = {funcActive: false, newStart: false};
45
-
46
- /*==============================================================================================================================================*/
47
- /* interne Funktionen */
48
- /*----------------------------------------------------------------------------------------------------------------------------------------------*/
49
-
50
- /**
51
- * Sprinkle (sprinkleName) delete
52
- * Ventil (sprinkleName) löschen
53
- *
54
- * @param killList - sprinkleName der zu löschenden Objekte
55
- */
56
- function delList (killList) {
57
- for(const sprinkleName of killList) {
58
- let bValveFound = false; // Ventil gefunden
59
- for(let counter = 0, // Loop über das Array
60
- lastArray = (threadList.length - 1); // entsprechend der Anzahl der Eintragungen
61
- counter <= lastArray;
62
- counter++) {
63
- const entry = threadList[counter].sprinkleName;
64
- if ((sprinkleName === entry) || bValveFound) {
65
- if (sprinkleName === entry) bValveFound = true;
66
- if (counter !== lastArray) threadList[counter] = threadList[counter + 1];
67
- }
68
- }
69
- /* If a valve is found, delete the last array (entry). Wenn Ventil gefunden letzten Array (Auftrag) löschen */
70
- if (bValveFound) {
71
- threadList.pop();
72
- adapter.log.debug(`delList=> order deleted ID: ${sprinkleName} ( rest orders: ${threadList.length} )`);
73
- }
74
- }
75
-
76
- } // End delList
77
-
78
- /*----------------------------------------------------------------------------------------------------------------------------------------------*/
79
-
80
- /**
81
- * Activation of the booster (all other active valves are deactivated for the duration of the boost so that the sprinkler can be extended with the maximum possible pressure)
82
- * => aktivierung des Boosters (alle anderen aktiven Ventile werden für die Zeit des Boosts deaktiviert um den maximalen möglichen Druck zum Ausfahren der Sprenger zu ermöglichen)
83
- *
84
- * @param sprinkleID
85
- */
86
- function boostList (sprinkleID) {
87
- boostReady = false;
88
- boostOn = true;
89
- for(const entry of threadList) {
90
- if (entry.enabled) {
91
- if (entry.sprinkleID === sprinkleID) { // Booster
92
- /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, < 3 > break, <<< 4 >>> Boost(on), < 5 > off(Boost) */
93
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
94
- val: 4,
95
- ack: true
96
- });
97
- //valveState(entry, 'Boost(on)');
98
- entry.times.boostTime2 = setTimeout(() => {
99
- /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
100
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
101
- val: 2,
102
- ack: true
103
- });
104
- entry.times.boostTime2 = null;
105
- },31000);
106
- } else { // rest der Ventile
107
- // in die Zwangspause (myBreak = true)
108
- entry.times.boostTime1 = setTimeout(() => {
109
- /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), <<< 5 >>> off(Boost) */
110
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
111
- val: 5,
112
- ack: true
113
- });
114
- entry.myBreak = true;
115
- // valveOnOff(entry, false, '#2.1 Set: off(Boost), ID: ');
116
- entry.times.boostTime1 = null;
117
- },250);
118
- // aus der Zwangspause holen (myBreak = false)
119
- entry.times.boostTime2 = setTimeout(() => {
120
- /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
121
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
122
- val: 2,
123
- ack: true
124
- });
125
- // valveOnOff(entry, true, '#2.2 Set: on, ID: ');
126
- entry.myBreak = false;
127
- entry.times.boostTime2 = null;
128
- },31000);
129
- }
130
- }
131
- }
132
- boostListTimer = setTimeout(() => {
133
- boostOn = false;
134
- updateList();
135
- },32000);
136
- } // End boostList
137
-
138
- /*----------------------------------------------------------------------------------------------------------------------------------------------*/
139
-
140
- /**
141
- * If boostOn is ended by entering "runningTime = 0", normal operation should be restored. (Delete timer)
142
- * → Wenn boostOn über die Eingabe "runningTime = 0" beendet wird, so soll zum Normalen ablauf wieder zurückgekehrt werden. (Löschen der Timer)
143
- *
144
- * @param sprinkleID
145
- */
146
- function boostKill (sprinkleID) {
147
- for(const entry of threadList) {
148
- if (entry.enabled) {
149
- if (entry.sprinkleID === sprinkleID) {
150
- /* booster wird gekillt */
151
- boostOn = false;
152
- if(entry.times.boostTime2) {
153
- clearTimeout(entry.times.boostTime2);
154
- entry.times.boostTime2 = null;
155
- }
156
- } else {
157
- /* normaler weiterbetrieb für den Rest */
158
- if (entry.times.boostTime1) {
159
- clearTimeout(entry.times.boostTime1);
160
- entry.times.boostTime1 = null;
161
- adapter.log.debug(`boostKill => ID: ${entry.sprinkleName} => boostTime2 (Ende) gelöscht)`);
162
- }
163
- if (entry.times.boostTime2) {
164
- clearTimeout(entry.times.boostTime2);
165
- entry.times.boostTime2 = null;
166
- /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
167
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
168
- val: 2,
169
- ack: true
170
- });
171
- // valveOnOff(entry, true, '#2.3 Set: on, ID: ');
172
- }
173
- }
174
- }
175
- }
176
- } // End boostKill
177
-
178
- /*----------------------------------------------------------------------------------------------------------------------------------------------*/
179
-
180
- /**
181
- * Schaltintervall der Ventile, Schaltabstand ist in der Config hinterlegt
182
- *
183
- * @param delay wenn keine Zeit eingegeben ist, wird der Schaltabstand der Config verwendet
184
- */
185
- const valveDelay = (delay = parseInt(adapter.config.switchingDistance)) => {
186
- return new Promise (
187
- resolve => idValveDelayTime = setTimeout (resolve, delay)
188
- );
189
- };
190
-
191
- /**
192
- * Ausschalten der Ventile mit Schaltabstand
193
- *
194
- * @param threadList Auflistung aller aktiven Sprenger-Kreise
195
- * @param parallel aktuelle Anzahl der eingeschalteten Ventile
196
- * @returns
197
- */
198
- const switchTheValvesOffOn = async (threadList, parallel) => {
199
- /**Sammlung von.sprinkleName die am Ende von updateList gelöscht werden
200
- * @type {array} - killList
201
- */
202
- const killList = [];
203
- for (const entry of threadList) { // ausschalten der Ventile
204
- if ((!entry.enabled // Ventile ausgeschaltet z.B. Intervall-Beregnung
205
- || entry.enabled && entry.myBreak // || in Pause z.B. Boost
206
- || entry.killSprinkle) // || Bewässerung erledigt
207
- && entry.enabled !== entry.enabledState // && Ventil nicht aktuell
208
- ) {
209
- try {
210
- adapter.setForeignStateAsync(entry.idState, {
211
- val: false,
212
- ack: false
213
- });
214
- entry.enabledState = entry.enabled;
215
- /* Ventil aus threadList löschen → Aufgabe beendet und sind nicht in der Pause */
216
- if (entry.killSprinkle) {
217
- killList.push(entry.sprinkleName);
218
- }
219
- adapter.log.info(`Set (${myConfig.config[entry.sprinkleID].methodControlSM}) ID: ${entry.sprinkleName}, value: ${entry.enabled}`);
220
- await valveDelay ();
221
- } catch (error) {
222
- adapter.log.error(`Error(ID: ${entry.sprinkleName}): ${error}`);
223
- }
224
- }
225
- }
226
-
227
- if (currentPumpUse.pumpName !== '') {
228
- await setPumpOnOff(parallel > 0);
229
- await valveDelay ();
230
- }
231
- if (adapter.config.triggerControlVoltage) {
232
- setVoltageOnOff(parallel>0);
233
- await valveDelay ();
234
- }
235
-
236
- for (const entry of threadList) { // einschalten der Ventile
237
- if (entry.enabled // intern eingeschaltet
238
- && !entry.myBreak // && keine Pause
239
- && !entry.killSprinkle // Bewässerung noch nicht erledigt
240
- && entry.enabled !== entry.enabledState // && Ventil nicht aktuell
241
- ) {
242
- try {
243
- adapter.setForeignState(entry.idState, {
244
- val: true,
245
- ack: false
246
- });
247
- entry.enabledState = entry.enabled;
248
- if (myConfig.config[entry.sprinkleID].booster) {
249
- boostReady = false;
250
- adapter.log.debug(`ID: ${entry.sprinkleName}UpdateList sprinkle On: boostReady = ${boostReady}`);
251
- setTimeout(() => {
252
- boostList(entry.sprinkleID);
253
- }, 50);
254
- }
255
- /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
256
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
257
- val: 2,
258
- ack: true
259
- });
260
- // valveOnOff(entry, true, '#2.7 Set: on, ID: ');
261
- /* countdown starten */
262
- if (!entry.startTime) {
263
- entry.startTime = new Date();
264
- }
265
- entry.countdown = setInterval(() => {
266
- countSprinkleTime(entry);
267
- }, 1000); // 1000 = 1s
268
- adapter.log.info(`Set Valve (${myConfig.config[entry.sprinkleID].methodControlSM}) ID: ${entry.sprinkleName}, value: ${entry.enabled}, duration: ${addTime(entry.wateringTime)}`);
269
- await valveDelay ();
270
- } catch (error) {
271
- adapter.log.error(`Error(ID: ${entry.sprinkleName}): ${error}`);
272
- }
273
-
274
- }
275
- }
276
-
277
- delList(killList); // erledigte Bewässerungsaufgaben aus der threadList löschen
278
- };
279
-
280
- /**
281
- * Handling von Ventilen, Zeiten, Verbrauchsmengen im 1s Takt
282
- *
283
- * @param entry
284
- */
285
- function countSprinkleTime(entry) {
286
- /* --- function beenden wenn ---*/
287
- if (boostOn && !(myConfig.config[entry.sprinkleID].booster) // boost-On && kein aktuelles Boost-Ventil
288
- ) {
289
- return;
290
- }
291
- entry.count ++;
292
- if ((entry.count < entry.wateringTime) // Zeit noch nicht abgelaufen?
293
- && (!entry.calcOn // alles ausser Berechnung der Verdunstung
294
- || !entry.autoOn // Handbetrieb
295
- || (myConfig.config[entry.sprinkleID].soilMoisture.val < myConfig.config[entry.sprinkleID].soilMoisture.maxIrrigation)) // Bodenfeuchte noch nicht erreicht? (z.B. beim Regen)
296
- ) { /* Zeit läuft */
297
- adapter.setState(`sprinkle.${entry.sprinkleName}.countdown`, {
298
- val: addTime(entry.wateringTime - entry.count),
299
- ack: true
300
- });
301
- /* Alle 15s die Bodenfeuchte anpassen */
302
- if (entry.calcOn // Vergleich nur bei Berechnung der Verdunstung
303
- && !(entry.count % 15) // alle 15s ausführen
304
- ) {
305
- myConfig.addSoilMoistVal(entry.sprinkleID, entry.soilMoisture15s);
306
- }
307
- /* Intervall-Beregnung wenn angegeben (onOffTime > 0) */
308
- if ((entry.onOffTime > 0) && !(entry.count % entry.onOffTime)) {
309
- adapter.log.info(`Intervall-Beregnung, onOffTime: ${entry.onOffTime}, count: ${entry.count}`);
310
- entry.enabled = false;
311
- entry.myBreak = true;
312
- /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, <<< 3 >>> break, < 4 > Boost(on), < 5 > off(Boost) */
313
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
314
- val: 3,
315
- ack: true
316
- });
317
- updateList();
318
- clearInterval(entry.countdown);
319
- entry.onOffTimeoutOff = setTimeout(()=>{
320
- entry.myBreak = false;
321
- /* Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
322
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
323
- val: 1,
324
- ack: true
325
- });
326
- updateList();
327
- },1000 * (entry.onOffTime < 600 ? 600 : entry.onOffTime)); // 600 sek Pause (10 min)
328
- }
329
- } else { /* zeit abgelaufen => Ventil ausschalten */
330
- /* Zustand des Ventils im Thread <<< 0 >>> off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
331
- entry.enabled = false;
332
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
333
- val: 0,
334
- ack: true
335
- });
336
- adapter.setState(`sprinkle.${entry.sprinkleName}.runningTime`, {
337
- val: '0',
338
- ack: true
339
- });
340
- adapter.setState(`sprinkle.${entry.sprinkleName}.countdown`, {
341
- val: '0',
342
- ack: true
343
- });
344
-
345
- /* Wenn in der Konfiguration Bodenfeuchte = 100% gesetzt ist und Auto-Bewässerung aktive, dann Bodenfeuchte = 100% setzen*/
346
- if (entry.autoOn && entry.calcOn && myConfig.config[entry.sprinkleID].endIrrigation) {
347
- myConfig.setSoilMoistPct100(entry.sprinkleID);
348
- }
349
- /* Verbrauchswerte in der Historie aktualisieren */
350
- addConsumedAndTime(entry);
351
- /* Booster zurücksetzen */
352
- if (myConfig.config[entry.sprinkleID].booster) {
353
- if (boostOn) {
354
- boostKill(entry.sprinkleID);
355
- }
356
- boostReady = true;
357
- adapter.log.debug(`ID: ${entry.sprinkleName} UpdateList Sprinkle Off: boostReady = ${boostReady}`);
358
- }
359
- /* Zeiten löschen */
360
- clearInterval(entry.countdown);
361
- /*clearTimeout(entry.onOffTimeoutOn);*/
362
- clearTimeout(entry.onOffTimeoutOff);
363
- /* Ventil aus threadList löschen → Aufgabe beendet */
364
- //delList(entry.sprinkleName);
365
- entry.killSprinkle = true;
366
- updateList();
367
- }
368
- }
369
-
370
- /*----------------------------------------------------------------------------------------------------------------------------------------------*/
371
-
372
- /**
373
- * Control of the active irrigation circuits so that the maximum pump capacity (l / h) is achieved and the maximum number of irrigation circuits is not exceeded.
374
- * => Steuerung der aktiven Bewässerungskreise, damit die maximale Pumpenkapazität (l / h) erreicht wird und die maximale Anzahl der Bewässerungskreise nicht überschritten wird.
375
- */
376
- function updateList () {
377
- /* während des Boost eines Kreises ist ein zuschalten von Sprengern nicht möglich */
378
- if (boostOn) {
379
- return;
380
- }
381
-
382
- if (updateListMarker.funcActive === true) {
383
- updateListMarker.newStart = true;
384
- return;
385
- }
386
- updateListMarker.funcActive = true;
387
- /** aktuelle Rest-Pumpenleistung */
388
- let curFlow = currentPumpUse.pumpPower, /* adapter.config.triggerMainPumpPower; */
389
- /** aktuelle Anzahl der eingeschalteten Ventile */
390
- parallel = 0;
391
-
392
- /**
393
- * Sortierfunktion mySortDescending absteigende Sortierung
394
- *
395
- * @param a
396
- * @param b
397
- * @returns
398
- */
399
- function mySortDescending(a, b) {
400
- return (a.pipeFlow > b.pipeFlow) ? -1 :
401
- (a.pipeFlow < b.pipeFlow) ? 1 :
402
- 0;
403
- }
404
- /**
405
- * Sortierfunktion mySortAscending aufsteigende Sortierung
406
- *
407
- * @param a
408
- * @param b
409
- * @returns
410
- */
411
- function mySortAscending(a, b) {
412
- return (a.pipeFlow < b.pipeFlow) ? -1 :
413
- (a.pipeFlow > b.pipeFlow) ? 1 :
414
- 0;
415
- }
416
-
417
- // ermitteln von curPipe und der anzahl der parallelen Stränge
418
- for(const entry of threadList){
419
- if (entry.enabled && !entry.killSprinkle) {
420
- curFlow -= entry.pipeFlow; // // ermitteln der RestFörderkapazität
421
- parallel ++; // Anzahl der Bewässerungsstellen um 1 erhöhen
422
- }
423
- }
424
-
425
- if (curFlow < 0) {
426
- /* - wenn beim Umschalten der Pumpen die Förderleistung zu gering Ventile deaktivieren - */
427
- // aufsteigend sortieren nach der Verbrauchsmenge
428
- threadList.sort(mySortAscending);
429
-
430
- for(const entry of threadList) {
431
- if (entry.enabled // eingeschaltet
432
- && !entry.killSprinkle // && Aufgabe noch nicht erledigt
433
- && (curFlow < 0) // && Förderleistung der Pumpe zu gering
434
- ) {
435
- entry.enabled = false; // ausgeschaltet merken
436
- clearInterval(entry.countdown); // Zähler für Countdown, Verbrauchsmengen, usw. löschen
437
- curFlow += entry.pipeFlow; // ermitteln der RestFörderkapazität
438
- parallel--; // Anzahl der Bewässerungsstellen um 1 verringern
439
- /* Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
440
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
441
- val: 1,
442
- ack: true
443
- });
444
- // valveOnOff(entry, false, '#2.6 Set: wait, ID: ');
445
- adapter.log.info(`Set Valve ID: ${entry.sprinkleName} Pump delivery rate too low, wait! curFlow ${curFlow} parallel: ${parallel}`);
446
- }
447
- }
448
- }
449
-
450
- // absteigend sortieren nach der Verbrauchsmenge
451
- threadList.sort(mySortDescending);
452
-
453
- // einschalten der Bewässerungsventile nach Verbrauchsmenge und maximaler Anzahl
454
- for(const entry of threadList) {
455
- // adapter.log.info(`Ventile Ein: enabled: ${!entry.enabled} && killSprinkle: ${!entry.killSprinkle} && myBreak: ${!entry.myBreak} && Flow: ${(curFlow >= entry.pipeFlow)} && ||: ${(parallel < maxParallel)} && noBoost: ${((boostReady) || !(myConfig.config[entry.sprinkleID].booster))}`);
456
- if (!entry.enabled // ausgeschaltet
457
- && !entry.killSprinkle // && Aufgabe noch nicht erledigt
458
- && !entry.myBreak // && nicht in der Pause
459
- && (curFlow >= entry.pipeFlow) // && noch genügend Förderleistung der Pumpe
460
- && (parallel < maxParallel) // && maxParallel noch nicht erreicht
461
- && ((boostReady) || !(myConfig.config[entry.sprinkleID].booster)) // nur einer mit boostFunction darf aktive sein
462
- ) {
463
- entry.enabled = true; // einschalten merken
464
-
465
- curFlow -= entry.pipeFlow; // ermitteln der RestFörderkapazität
466
- parallel++; // Anzahl der Bewässerungsstellen um 1 erhöhen
467
-
468
- }
469
- }
470
-
471
- adapter.setState('control.parallelOfMax', {
472
- val: `${parallel} : ${maxParallel}`,
473
- ack: true
474
- });
475
- adapter.setState('control.restFlow', {
476
- val: `${curFlow} (${currentPumpUse.pumpPower} ${currentPumpUse.pumpCistern ? 'Zisterne' : 'Grundwasser'})`,
477
- ack: true
478
- });
479
-
480
- switchTheValvesOffOn(threadList, parallel).then(err => {
481
- if (err) {
482
- adapter.log.error(`Error - Set (false) err: ${err}`);
483
- sendMessageText.sendMessage(`Error - Set (false) err: ${err}`);
484
- }
485
- });
486
-
487
- updateListMarker.funcActive = false;
488
- if (updateListMarker.newStart === true) {
489
- updateListMarker.newStart = false;
490
- setTimeout(updateList(), 50);
491
- }
492
- } // End updateList
493
-
494
- /* --------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
495
-
496
- /**
497
- * +++++ Set the current pump for irrigation +++++
498
- * → Festlegen der aktuellen Pumpe zur Bewässerung
499
- */
500
- function setActualPump () {
501
- let switchOnLevelCistern = (parseFloat(adapter.config.triggerMinCisternLevel) + parseFloat(adapter.config.switchOnLevelCistern)) || 5;
502
- if (switchOnLevelCistern > 100) {
503
- switchOnLevelCistern = 100;
504
- adapter.log.info(`Switch-on point limited to 100%`);
505
- }
506
- if (switchOnLevelCistern < 5) {
507
- switchOnLevelCistern = 5;
508
- adapter.log.info(`Switch-on point increased to 5%`);
509
- }
510
-
511
- /* Zisternen-Bewässerung Einstellung in der config (2. Pumpe) aktiviert */
512
- if (adapter.config.cisternSettings === true) {
513
- if (currentPumpUse.enable === true) {
514
- /* Bewässerungspumpen aktiv */
515
- if ((fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel))
516
- && (currentPumpUse.pumpCistern === true)
517
- ) {
518
- /* (Zisterne unter Minimum) && (ZisternenPumpe läuft) */
519
- adapter.setForeignState(currentPumpUse.pumpName, {
520
- val: false,
521
- ack: false
522
- }); // Pumpe Zisterne Aus
523
- if (currentPumpUse.pumpCistern === true && !sendMessageText.onlySendError()) {
524
- sendMessageText.sendMessage(`Pump change (cistern empty) Cistern pump off => main pump on`);
525
- }
526
- currentPumpUse.pumpCistern = false;
527
- currentPumpUse.pumpName = adapter.config.triggerMainPump || '';
528
- currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
529
- currentPumpUse.leadTime = parseInt(adapter.config.mainPumpLeadTime)*1000 || 1000;
530
- adapter.setForeignState(currentPumpUse.pumpName, {
531
- val: true,
532
- ack: false
533
- }); // Hauptpumpe Ein
534
- adapter.log.info('Pump change (cistern empty) Cistern pump off => main pump on');
535
- updateList(); // Wasserverbrauch an Pumpenleistung anpassen
536
- }
537
- if (fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) {
538
- statusCistern = `Cistern empty: ${fillLevelCistern} % (${switchOnLevelCistern} %)`;
539
- } else {
540
- statusCistern = `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`;
541
- }
542
- } else {
543
- /* Bewässerungspumpen inaktiv */
544
- if ((fillLevelCistern >= switchOnLevelCistern)
545
- && (adapter.config.triggerCisternPump)
546
- && (adapter.config.triggerCisternPumpPower)
547
- ) {
548
- /* Zisterne voll && triggerCisternPump && triggerCisternPumpPower vorhanden*/
549
- statusCistern = `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`;
550
- if (currentPumpUse.pumpCistern === false
551
- && !sendMessageText.onlySendError()
552
- ) {
553
- currentPumpUse.pumpCistern = true;
554
- currentPumpUse.pumpName = adapter.config.triggerCisternPump || '';
555
- currentPumpUse.pumpPower = parseInt(adapter.config.triggerCisternPumpPower);
556
- currentPumpUse.leadTime = parseInt(adapter.config.cisternPumpLeadTime)*1000 || 1000;
557
- adapter.setState('control.restFlow', {
558
- val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} Zisterne)`,
559
- ack: true
560
- });
561
- sendMessageText.sendMessage(`Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`);
562
- }
563
- } else if (fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) {
564
- statusCistern = `Cistern empty: ${fillLevelCistern} % (${switchOnLevelCistern} %)`;
565
- currentPumpUse.pumpCistern = false;
566
- currentPumpUse.pumpName = adapter.config.triggerMainPump || '';
567
- currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
568
- currentPumpUse.leadTime = parseInt(adapter.config.mainPumpLeadTime)*1000 || 1000;
569
- adapter.setState('control.restFlow', {
570
- val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} Grundwasser)`,
571
- ack: true
572
- });
573
- } else {
574
- if (currentPumpUse.pumpCistern === true) {
575
- statusCistern = `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`;
576
- } else {
577
- statusCistern = `Cistern empty: ${fillLevelCistern} % (${switchOnLevelCistern} %)`;
578
- }
579
- }
580
- adapter.setState('info.cisternState', {
581
- val: statusCistern,
582
- ack: true
583
- });
584
- }
585
- } else {
586
- /* Pumpe AUS => Zisternen-Bewässerung nicht aktiviert */
587
- if (adapter.config.triggerCisternPump) {
588
- statusCistern = `Cistern settings are not active!${(fillLevelCistern > 0)?(` level sensor: ${fillLevelCistern} % ${(adapter.config.triggerMinCisternLevel !== '')?(`${adapter.config.triggerMinCisternLevel} %`):('')}`):('')}`;
589
- adapter.setState('info.cisternState', {
590
- val: statusCistern,
591
- ack: true
592
- });
593
- }
594
- }
595
- } // End setActualPump
596
-
597
- /**
598
- * Switching the pump on or off
599
- * => Ein bzw. ausschaltern der Pumpe
600
- *
601
- * @param pumpOnOff ; Pumpe on = true
602
- */
603
- const setPumpOnOff = async (pumpOnOff) => {
604
- clearTimeout(idValveDelayTime);
605
- if (currentPumpUse.pumpName !== '') {
606
- try {
607
- const curPumpState = await adapter.getForeignStateAsync(currentPumpUse.pumpName);
608
- if (curPumpState) {
609
- if (pumpOnOff) {
610
- if (curPumpState.val === false) {
611
- adapter.setForeignState(currentPumpUse.pumpName, {
612
- val: true,
613
- ack: false
614
- });
615
- currentPumpUse.enable = true;
616
- adapter.log.info(`Set (pump) on, wait ${currentPumpUse.leadTime}ms`);
617
- await valveDelay(currentPumpUse.leadTime);
618
- }
619
- } else {
620
- if (curPumpState.val !== false) {
621
- adapter.setForeignState(currentPumpUse.pumpName, {
622
- val: false,
623
- ack: false
624
- });
625
- currentPumpUse.enable = false;
626
- adapter.log.info('Set (pump) off');
627
- }
628
- }
629
- }
630
- } catch (error) {
631
- adapter.log.error(`triggerMainPump ${currentPumpUse.pumpName} is not available (ist nicht erreichbar): ${error}`);
632
- }
633
- }
634
- } // End setPumpOnOff
635
-
636
- /**
637
- * Switching the control voltage on or off
638
- * => Ein bzw. ausschaltern der Steuerspannung
639
- *
640
- * @param voltageOnOff - Voltage on = true
641
- */
642
- function setVoltageOnOff(voltageOnOff) {
643
- if (adapter.config.triggerControlVoltage !== '') {
644
- adapter.getForeignState(adapter.config.triggerControlVoltage, (err, state) => {
645
- if (state) {
646
- if (voltageOnOff) {
647
- if (state.val === false) {
648
- adapter.setForeignState(adapter.config.triggerControlVoltage, {
649
- val: true,
650
- ack: false
651
- });
652
- adapter.log.info('Set (voltage) on');
653
- }
654
- } else {
655
- if (state.val !== false) {
656
- adapter.setForeignState(adapter.config.triggerControlVoltage, {
657
- val: false,
658
- ack: false
659
- });
660
- adapter.log.info('Set (voltage) off');
661
- }
662
- }
663
- } else if (err) {
664
- adapter.log.error(`triggerControlVoltage is not available (ist nicht erreichbar): ${err}`);
665
- }
666
- });
667
- }
668
- }
669
-
670
- /**
671
- * Adding the consumption data to the history
672
- * => Hinzufügen der Verbrauchsdaten zur History
673
- *
674
- * @param entry - array mit den Daten des aktiven Ventils
675
- */
676
- function addConsumedAndTime(entry) {
677
- adapter.setState(`sprinkle.${entry.sprinkleName}.history.lastConsumed`, {
678
- val: Math.round(entry.litersPerSecond * entry.count),
679
- ack: true
680
- });
681
- adapter.setState(`sprinkle.${entry.sprinkleName}.history.lastRunningTime`, {
682
- val: addTime(entry.count),
683
- ack: true
684
- });
685
- adapter.setState(`sprinkle.${entry.sprinkleName}.history.lastOn`, {
686
- val: formatTime(adapter, entry.startTime, 'dd.mm. hh:mm'),
687
- ack: true
688
- });
689
- adapter.getState(`sprinkle.${entry.sprinkleName}.history.curCalWeekConsumed`, (err, state) => {
690
- if (state) {
691
- adapter.setState(`sprinkle.${entry.sprinkleName}.history.curCalWeekConsumed`, {
692
- val: (state.val) + Math.round(entry.litersPerSecond * entry.count),
693
- ack: true
694
- });
695
- }
696
- });
697
- adapter.getState(`sprinkle.${entry.sprinkleName}.history.curCalWeekRunningTime`, (err, state) => {
698
- if (state) {
699
- adapter.setState(`sprinkle.${entry.sprinkleName}.history.curCalWeekRunningTime`, {
700
- val: addTime(state.val, entry.count),
701
- ack: true
702
- });
703
- }
704
- });
705
- } // End addConsumedAndTime
706
-
707
-
708
- /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
709
- /* externe Funktionen */
710
- /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
711
-
712
- /**
713
- * -- externe Funktionen -> initValveControl - addList - clearEntireList - setFillLevelCistern --
714
- *
715
- */
716
- const valveControl = {
717
- /**
718
- * Initialize the start configuration of ventilControl
719
- * => Initialisieren Sie die Startkonfiguration von ventilControl
720
- *
721
- * @param myAdapter
722
- */
723
- initValveControl (myAdapter) {
724
- adapter = myAdapter;
725
- currentPumpUse.pumpCistern = false;
726
- currentPumpUse.pumpName = adapter.config.triggerMainPump || '';
727
- currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower) || 0;
728
- currentPumpUse.leadTime = parseInt(adapter.config.mainPumpLeadTime)*1000 || 1000;
729
- maxParallel = parseInt(adapter.config.maximumParallelValves);
730
- /* Objekt control.restFlow befüllen */
731
- adapter.setState('control.restFlow', {
732
- val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} ${currentPumpUse.pumpCistern ? 'Zisterne' : 'Grundwasser'})`,
733
- ack: true
734
- });
735
- /* Objekt control.parallelOfMax befüllen */
736
- adapter.setState('control.parallelOfMax', {
737
- val: `0 : ${adapter.config.maximumParallelValves}`,
738
- ack: true
739
- });
740
- /* 24V ausschalter, wenn vorhanden */
741
- if (adapter.config.triggerControlVoltage !== '') {
742
- adapter.getForeignState('adapter.config.triggerControlVoltage', (err, state) => {
743
- if (state && state.val === true) {
744
- adapter.setForeignState(adapter.config.triggerControlVoltage, {
745
- val: false,
746
- ack: false
747
- });
748
- }
749
- });
750
- }
751
- /* Pumpe ausschalter, wenn vorhanden */
752
- if (adapter.config.triggerMainPump !== '') {
753
- adapter.getForeignState('adapter.config.triggerMainPump', (err, state) => {
754
- if (state && state.val === true) {
755
- adapter.setForeignState(adapter.config.triggerMainPump, {
756
- val: false,
757
- ack: false
758
- });
759
- }
760
- });
761
- }
762
- /* Pumpe (Zisterne) ausschalter, wenn vorhanden */
763
- if (adapter.config.triggerCisternPump !== '') {
764
- adapter.getForeignState('adapter.config.triggerCisternPump', (err, state) => {
765
- if (state && state.val === true) {
766
- adapter.setForeignState(adapter.config.triggerCisternPump, {
767
- val: false,
768
- ack: false
769
- });
770
- }
771
- });
772
- }
773
- /* alle Ventile (.name = "hm-rpc.0.MEQ1234567.3.STATE") in einem definierten Zustand (false) versetzen*/
774
- const result = adapter.config.events;
775
- if (result) {
776
- for (const res of result) {
777
- adapter.getForeignState(res.name, (err, state) => {
778
- if (state && state.val === true) {
779
- adapter.setForeignState(res.name, {
780
- val: false,
781
- ack: false
782
- });
783
- }
784
- });
785
- }
786
- }
787
- }, // End initValveControl
788
-
789
- /**
790
- * Add Sprinkle→ Sprinkle hinzufügen
791
- *
792
- * @param sprinkleList
793
- * @param sprinkleList[].auto - auto → Automatik == (true), Handbetrieb == (false)
794
- * @param sprinkleList[].sprinkleID - sprinkleID → zugriff auf myConfig.config[sprinkleID]. xyz
795
- * @param sprinkleList[].wateringTime - wateringTime → Bewässerungszeit in min
796
- */
797
- addList (sprinkleList) {
798
- //
799
- for (const res of sprinkleList) {
800
- const sprinkleName = myConfig.config[res.sprinkleID].objectName;
801
- /**
802
- * add done
803
- * → hinzufügen erledigt (Sprenger bereits aktive)
804
- */
805
- let addDone = false;
806
- // schauen ob der Sprenger schon in der threadList ist
807
- if (threadList) {
808
- for (const entry of threadList) {
809
- if (entry.sprinkleID === res.sprinkleID) {
810
- if (entry.wateringTime === res.wateringTime) {
811
- // adapter.setState('sprinkle.' + sprinkleName + '.runningTime', {val: addTime(wateringTime, ''), ack: false});
812
- return;
813
- }
814
- entry.wateringTime = res.wateringTime;
815
- entry.autoOn = res.auto; // auto: = true autostart; = false Handbetrieb
816
- adapter.setState(`sprinkle.${sprinkleName}.runningTime`, {
817
- val: addTime(res.wateringTime),
818
- ack: true
819
- });
820
- addDone = true; // Sprinkle found
821
- adapter.log.debug(`update ID: ${entry.sprinkleName} new time: ${addTime(res.wateringTime)}`);
822
- // Wenn aktueller Spränger im BoostOn ausgeschaltet werden soll (ersten 30s)
823
- if (boostOn
824
- && myConfig.config[res.sprinkleID].booster
825
- && entry.wateringTime <= 0
826
- ) {
827
- boostKill(res.sprinkleID);
828
- boostReady = true;
829
- adapter.log.debug(`ID: ${res.sprinkleName} BoostKill Sprinkle Off: boostReady = ${boostReady}`);
830
- }
831
- break;
832
- }
833
- }
834
- }
835
-
836
- if (!addDone) {
837
- if (res.wateringTime <= 0) {
838
- adapter.setState(`sprinkle.${sprinkleName}.runningTime`, {
839
- val: '0',
840
- ack: true
841
- });
842
- return;
843
- }
844
-
845
- let pipeFlow = myConfig.config[res.sprinkleID].pipeFlow;
846
-
847
- // Kontrolle des Ventilverbrauchs(muss kleiner als Pumpenleistung sein, da Ventil sonst nicht starten kann)
848
- if (adapter.config.cisternSettings === true) {
849
- if ((pipeFlow > adapter.config.triggerMainPumpPower) || (pipeFlow > adapter.config.triggerCisternPumpPower)) {
850
- if (pipeFlow > adapter.config.triggerMainPumpPower) adapter.log.warn(`Emergency irrigation! ${sprinkleName}: Valve consumption > pump performance (main pump)`);
851
- if (pipeFlow > adapter.config.triggerCisternPumpPower) adapter.log.warn(`Emergency irrigation! ${sprinkleName}: Valve consumption > pump performance (cistern pump)`);
852
- pipeFlow = (adapter.config.triggerMainPumpPower < adapter.config.triggerCisternPumpPower) ? adapter.config.triggerMainPumpPower : adapter.config.triggerCisternPumpPower;
853
- }
854
- } else {
855
- if (pipeFlow > adapter.config.triggerMainPumpPower) {
856
- adapter.log.warn(`Emergency irrigation! ${sprinkleName}: Valve consumption > pump performance (main pump)`);
857
- pipeFlow = adapter.config.triggerMainPumpPower;
858
- }
859
- }
860
-
861
- const newThread = {
862
- sprinkleID: res.sprinkleID, // Array[0...]
863
- sprinkleName: sprinkleName, // z.B "Blumenbeet"
864
- idState: myConfig.config[res.sprinkleID].idState, // z.B. "hm-rpc.0.MEQ1810129.1.STATE"
865
- wateringTime: res.wateringTime, // Bewässerungszeit in s (Sekunden)
866
- pipeFlow: pipeFlow, // Wasserverbrauch in l/h
867
- count: 0, // Zähler im Sekundentakt
868
- calcOn: (myConfig.config[res.sprinkleID].methodControlSM === 'calculation'), // Anpassung der Bodenfeuchte true/false
869
- enabled: false, // Ventil softwaremäßig ein
870
- enabledState: false, // Ventil hardwaremäßig ein
871
- myBreak: false, // meine Pause
872
- killSprinkle: false, // Löschauftrag ausführen am Ende in threadList
873
- litersPerSecond: pipeFlow / 3600, // Wasserverbrauchsmenge pro Sekunde
874
- onOffTime: myConfig.config[res.sprinkleID].wateringInterval,
875
- autoOn: res.auto,
876
- soilMoisture15s: 15 * (myConfig.config[res.sprinkleID].soilMoisture.maxIrrigation - myConfig.config[res.sprinkleID].soilMoisture.triggersIrrigation)
877
- / (60 * myConfig.config[res.sprinkleID].wateringTime),
878
- times: {
879
- boostTime1: null, // boost start in die Zwangspause schicken in 0,25s
880
- boostTime2: null, // boost ende aus der Zwangspause holen nach 31s
881
- }, // hinterlegen der verschiedenen Zeiten von timeout für gezieltes späteres löschen
882
- id: threadList.length || 0,
883
- };
884
- threadList.push(newThread);
885
- /* Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
886
- adapter.setState(`sprinkle.${sprinkleName}.sprinklerState`, {
887
- val: 1,
888
- ack: true
889
- });
890
- adapter.setState(`sprinkle.${sprinkleName}.runningTime`, {
891
- val: addTime(res.wateringTime),
892
- ack: true
893
- });
894
- adapter.log.debug(`ID: ${sprinkleName} new order created: ${JSON.stringify(threadList[newThread.id])}`);
895
- }
896
- }
897
- updateList();
898
- }, // End addList
899
-
900
- /**
901
- * switch off all devices, when close the adapter
902
- * => Beim Beenden des adapters alles ausschalten
903
- */
904
- clearEntireList () {
905
- clearTimeout(idValveDelayTime);
906
- setVoltageOnOff(false);
907
- setPumpOnOff(false);
908
- if (boostListTimer) {
909
- boostReady = true;
910
- boostOn = false;
911
- clearTimeout(boostListTimer);
912
- }
913
- // let bValveFound = false; // Ventil gefunden
914
- for (let counter = threadList.length - 1; // Loop über das Array
915
- counter >= 0;
916
- counter--) {
917
- const entry = threadList[counter];
918
- if (entry.enabledState) {
919
- adapter.log.info(`Set Valve (SprinkleControl: off) ID: ${entry.sprinkleName}, value: false`);
920
- adapter.setForeignState(entry.idState, {
921
- val: false,
922
- ack: false
923
- });
924
- }
925
- /* Zustand des Ventils im Thread <<< 0 >>> off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
926
- adapter.setState(`sprinkle.${entry.sprinkleName}.sprinklerState`, {
927
- val: 0,
928
- ack: true
929
- });
930
- adapter.setState(`sprinkle.${entry.sprinkleName}.runningTime`, {
931
- val: '0',
932
- ack: true
933
- });
934
- adapter.setState(`sprinkle.${entry.sprinkleName}.countdown`, {
935
- val: '0',
936
- ack: true
937
- });
938
- // valveOnOff(myEntry, false, '#2.0 Set: off, ID: ');
939
- /* Verbrauchswerte in der Historie aktualisieren */
940
- addConsumedAndTime(entry);
941
- /* del timer countdown */
942
- clearInterval(entry.countdown);
943
- /* del timer onOffTimeoutOff */
944
- clearTimeout(entry.onOffTimeoutOff);
945
- /* del timer newThread.times.boostTime1 */
946
- if (entry.times.boostTime1) {
947
- clearTimeout(entry.times.boostTime1);
948
- entry.times.boostTime1 = null;
949
- }
950
- /* del timer newThread.times.boostTime2 */
951
- if (entry.times.boostTime2) {
952
- clearTimeout(entry.times.boostTime2);
953
- entry.times.boostTime2 = null;
954
- }
955
- adapter.log.debug(`order deleted Stop all ID: ${entry.sprinkleName} ( rest orders: ${threadList.length} )`);
956
- threadList.pop(); // del last array
957
- }
958
- updateList();
959
- }, // End clearEntireList
960
-
961
- /**
962
- * Änderungen des Füllstands setzen + Vorrang der Pumpe setzen
963
- *
964
- * @param levelCistern
965
- */
966
- setFillLevelCistern (levelCistern) {
967
- fillLevelCistern = (typeof levelCistern === 'number') ? levelCistern : 0;
968
- setActualPump();
969
- }, // End setFillLevelCistern
970
-
971
- getStatusCistern () {
972
- return statusCistern;
973
- }
974
- }; // End valveControl
975
-
976
- /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
977
-
978
- module.exports = valveControl;
1
+
2
+ 'use strict';
3
+
4
+ const asyncTime = require('timers/promises');
5
+ // import {setTimeout} from 'timers/promises';
6
+
7
+ const myConfig = require('./myConfig.js');
8
+ //const sleep = require('./tools.js').sleep;
9
+ const addTime = require('./tools.js').addTime;
10
+ const formatTime = require('./tools').formatTime;
11
+ const findAsync = require('./tools.js').findAsync;
12
+ const sendMessageText = require('./sendMessageText.js'); // sendMessageText
13
+
14
+ /**
15
+ * The adapter instance
16
+ */
17
+ let adapter;
18
+
19
+ /**
20
+ * Thread-list
21
+ * Auflistung aller aktiver Sprenger-Kreise
22
+ */
23
+ const threadList = [];
24
+
25
+ /**
26
+ * - bereit zum Boost (true: kein Boostventil aktive; false: BoostVentil aktive)
27
+ */
28
+ let boostReady = true,
29
+ /**
30
+ * - Boost aktive
31
+ */
32
+ boostOn = false,
33
+ /**
34
+ * maximal zulässige Anzahl der eingeschalteten Ventile
35
+ */
36
+ maxParallel = 0,
37
+ /* Control of the cistern pump */
38
+ /**
39
+ * Füllstand der Zisterne
40
+ */
41
+ fillLevelCistern = 0;
42
+ const updateListMarker = {funcActive: false, newStart: false, switchingDistance: 5000, cancelSwitchingDistance: {}};
43
+
44
+ /**
45
+ * aktive Pumpendaten
46
+ * - enable: Pumpe ein/ausgeschaltet
47
+ * - name: Bezeichnung der Pumpe
48
+ * - idState: Objektname des State
49
+ * - id: ID des States unter Objecte im ioBroker info.cisternPump || info.mainPump
50
+ * - intBreak: Pause (Zisterne leer)
51
+ * - pumpCistern: ZisternenPumpe aktiv bei Verwendung zweier Pumpen
52
+ * - leadTime: Vorlaufzeit
53
+ * - cancelLeadTime: Vorlaufzeit abbrechen
54
+ * - pumpPower: Maximalleistung der Pumpe
55
+ * - restFlow: aktuelle LeistungsReserve der Pumpe
56
+ * - controller: Rückmeldung ack
57
+ * - ac: AbortController
58
+ */
59
+
60
+ const currentPumpUse = {enable: false, name: '', idState: '', id: '', pumpCistern: false, intBreak: false, leadTime: 0, cancelLeadTime: {}, pumpPower: 0, restFlow: 0,ac: {},controller: {}};
61
+
62
+ /**
63
+ * Steuerspannung 24V
64
+ * - enable: 24V ein/ausgeschaltet
65
+ * - name: Bezeichnung 24V
66
+ * - idState: Aktorerkennung "hm-rpc.0.MEQ1810129.1.STATE"
67
+ * - controller: controlle von Zeiten und Abbruchsignalen
68
+ */
69
+ const controlVoltage ={enable: false, name: '24V', idState: undefined, ac: {}, controller: {}};
70
+
71
+ /**
72
+ * Schaltabstand in ms
73
+ * - Schaltabstand zwischen den Ventilen
74
+ * - 250 ms - 240 000 ms (4 min)
75
+ */
76
+ let switchingDistanceMS = 250;
77
+ /*==============================================================================================================================================*/
78
+ /* interne Funktionen */
79
+ /*----------------------------------------------------------------------------------------------------------------------------------------------*/
80
+
81
+ /**
82
+ * Schalten der Ventile
83
+ *
84
+ * @param {object} thread -Objekt des Ventils
85
+ * @param {boolean} val - neuer Zustand des Ventils
86
+ * @returns {Promise<string|undefined>}
87
+ */
88
+
89
+ const setValve = async (thread, val) => {
90
+
91
+ const startTime = new Date();
92
+ let result = undefined;
93
+
94
+ thread.ac.acSetValveCancelTimeout = new AbortController;
95
+ /**
96
+ * Sprinkle geschaltet
97
+ *
98
+ * @param {{val:boolean,ack:boolean}} state
99
+ */
100
+ thread.controller.ackTrue = (state) => {
101
+ if (typeof state.val === 'boolean'
102
+ && state.val === val
103
+ && state.ack === true
104
+ ) {
105
+ adapter.log.debug(`setValve ${thread.name} => ackTrue`);
106
+ result = thread.idState;
107
+ thread.enable = val;
108
+ thread.ac.acSetValveCancelTimeout.abort();
109
+ } else {
110
+ adapter.log.warn(`setValve ${thread.name} => ackTrue val = ${val} state: ${JSON.stringify(state)}`);
111
+ }
112
+ };
113
+
114
+ //adapter.log.info(`Set Valve (async () => {...}`);
115
+ try {
116
+ // Ventil ansteuern
117
+ const _setValve = await adapter.setForeignStateAsync(thread.idState, {
118
+ val: val,
119
+ ack: false
120
+ });
121
+ if (_setValve === thread.idState) { // Auftrag ausgeführt
122
+ await asyncTime.setTimeout(10000, undefined, { signal: thread.ac.acSetValveCancelTimeout.signal });
123
+ const _getValve = await adapter.getForeignStateAsync(thread.idState);
124
+ if (_getValve && typeof _getValve.val === 'boolean'
125
+ && _getValve.val === val
126
+ && _getValve.ack === true
127
+ ) {
128
+ thread.enable = val;
129
+ return thread.idState;
130
+ } else {
131
+ adapter.log.debug(`setValve ${thread.name} _getValve: ${_getValve}`);
132
+ throw new Error(`setForeignState not responding (ack === false)`);
133
+ }
134
+ } else {
135
+ throw new Error(`set Valve was not switched`);
136
+ }
137
+ } catch (error) {
138
+ // thread.controller.ackTrue wurde ausgelöst
139
+ if (error.name !== `AbortError`) {
140
+ await adapter.setForeignStateAsync(thread.idState, {
141
+ val: false,
142
+ ack: false
143
+ });
144
+ thread.enable = false;
145
+ if (adapter.config.notificationEnabled) {
146
+ sendMessageText.sendMessage(`setValve ${thread.name} (${thread.idState}) ${error}`);
147
+ }
148
+ throw new Error(`set Valve (${thread.idState}) ${error}`);
149
+ }
150
+ } finally {
151
+ if (thread.ac.acSetValveCancelTimeout.aborted === false) thread.ac.acSetValveCancelTimeout.abort();
152
+ adapter.log.info(`setValve ${thread.name}: ${val}, ${thread.wateringTime > 0 && val === true ? `${ addTime(thread.wateringTime,'') }, ` : ''}processing time: ${(+new Date()) - +startTime}ms`);
153
+ }
154
+ adapter.setStateAsync(`sprinkle.${thread.name}.sprinklerState`, {
155
+ val: thread.state,
156
+ ack: true
157
+ });
158
+ if (thread.name !== 'Cistern pump' && thread.name !== 'Main pump' && thread.name !== '24V' && thread.name !== 'no Pump') {
159
+ adapter.setStateAsync(`sprinkle.${thread.name}.valveOn`, {
160
+ val: thread.enable,
161
+ ack: true
162
+ });
163
+ }
164
+ return result;
165
+ };
166
+
167
+ /**
168
+ * Sprinkle (name) delete
169
+ * → Ventil (name) löschen
170
+ *
171
+ * @param {Array.<{name: string}>} killList
172
+ * @returns {Promise}
173
+ */
174
+ const delList = async (killList) => {
175
+ new Promise((resolve, reject) => {
176
+ const badList = [];
177
+ for (const name of killList) {
178
+ let bValveFound = false; // Ventil gefunden
179
+ for (let counter = 0, // Loop über das Array
180
+ lastArray = (threadList.length - 1); // entsprechend der Anzahl der Eintragungen
181
+ counter <= lastArray;
182
+ counter++) {
183
+ const entry = threadList[counter].name;
184
+ if ((name === entry) || bValveFound) {
185
+ if (name === entry) bValveFound = true;
186
+ if (counter !== lastArray) threadList[counter] = threadList[counter + 1];
187
+ }
188
+ }
189
+ /* If a valve is found, delete the last array (entry). Wenn Ventil gefunden letzten Array (Auftrag) löschen */
190
+ if (bValveFound) {
191
+ threadList.pop();
192
+ adapter.log.debug(`delList => order deleted ID: ${name} ( rest orders: ${threadList.length} )`);
193
+ }else{
194
+ badList.push(name);
195
+ }
196
+ }
197
+ if (badList.length > 0) {
198
+ reject(`delList: could not find ${JSON.stringify(badList)}`);
199
+ } else {
200
+ resolve;
201
+ }
202
+ });
203
+ }; // End delList
204
+
205
+ /**
206
+ * currentConsumption aktueller Verbrauch ermitteln
207
+ *
208
+ * @param {boolean} write
209
+ * @returns {Promise<{curFlow:number, parallel:number, pumpRequired:boolean}>}
210
+ */
211
+ const currentConsumption = async (write) => {
212
+ try {
213
+ let curFlow = currentPumpUse.intBreak ? 0 : currentPumpUse.pumpPower, /* adapter.config.triggerMainPumpPower; */
214
+ /** aktuelle Anzahl der eingeschalteten Ventile */
215
+ parallel = 0,
216
+ pumpRequired = false;
217
+ adapter.log.debug(`currentConsumption curFlow: ${curFlow}, ${currentPumpUse.intBreak} => ${currentPumpUse.intBreak ? 0 : currentPumpUse.pumpPower}`);
218
+ if (!currentPumpUse.intBreak) {
219
+ // ermitteln von curPipe und der Anzahl der parallelen Stränge
220
+ for (const entry of threadList){
221
+ if (entry.state === 'wait') pumpRequired = true; // state => wait
222
+ if (entry.enable === true
223
+ && entry.extBreak === false
224
+ ) {
225
+ curFlow -= entry.pipeFlow; // // ermitteln der RestFörderkapazität
226
+ parallel ++; // Anzahl der Bewässerungsstellen um 1 erhöhen
227
+ pumpRequired = true;
228
+ }
229
+ }
230
+ }
231
+
232
+ if (write || currentPumpUse.intBreak) {
233
+ adapter.setStateAsync('control.parallelOfMax', {
234
+ val: `${parallel} : ${maxParallel}`,
235
+ ack: true
236
+ });
237
+ adapter.setStateAsync('control.restFlow', {
238
+ val: `${curFlow} (${currentPumpUse.pumpPower} | ${currentPumpUse.name})`,
239
+ ack: true
240
+ });
241
+ }
242
+
243
+ return {
244
+ curFlow: curFlow,
245
+ parallel: parallel,
246
+ pumpRequired: pumpRequired
247
+ };
248
+
249
+ } catch (error) {
250
+ adapter.log.error(`currentConsumption Error: ${error}`);
251
+ return {
252
+ curFlow: 0,
253
+ parallel: 99,
254
+ pumpRequired: false
255
+ };
256
+ }
257
+ };
258
+
259
+ /** Interval-Beregnung aus */
260
+
261
+ const onOffTimeoutOff = async (entry) => {
262
+ entry.ac.acOnOffTimeoutOff = new AbortController;
263
+ try {
264
+ const res = await asyncTime.setTimeout(1000 * (entry.onOffTimeOff < 600 ? 600 : entry.onOffTimeOff),`time expired`, { signal: entry.ac.acOnOffTimeoutOff.signal }); // mindestens 600 sek Pause (10 min)
265
+ if (res === `time expired`) { // Zeit abgelaufen
266
+ entry.myBreak = false;
267
+ // Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost)
268
+ entry.state = 'wait';
269
+ adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
270
+ val: entry.state, // <<< 1 >>> wait
271
+ ack: true
272
+ });
273
+ updateList();
274
+ }
275
+ } catch (error) {
276
+ adapter.log.error(`onOffTimeoutOff error: ${error}`);
277
+ } finally {
278
+ if (entry.ac.acOnOffTimeoutOff.aborted === false) entry.ac.acOnOffTimeoutOff.abort();
279
+ }
280
+ };
281
+ /*----------------------------------------------------------------------------------------------------------------------------------------------*/
282
+
283
+ /*----------------------------------------------------------------------------------------------------------------------------------------------*/
284
+
285
+ /**
286
+ * Handling von Ventilen, Zeiten, Verbrauchsmengen im 1s Takt
287
+ *
288
+ * @param {object} entry
289
+ */
290
+ const countSprinkleTime = async (entry) => {
291
+ try {
292
+ /* --- function beenden wenn ---*/
293
+ if ((boostOn && !(myConfig.config[entry.sprinkleID].booster) // boost-On && kein aktuelles Boost-Ventil
294
+ || entry.extBreak) // extBreak aktive
295
+ ) {
296
+ return;
297
+ }
298
+
299
+ entry.count ++;
300
+
301
+ if ((entry.count < entry.wateringTime) // Zeit noch nicht abgelaufen?
302
+ && (!entry.calcOn // Vergleich nur bei Berechnung der Verdunstung
303
+ || !entry.autoOn // Vergleich nur bei Automatik
304
+ || (myConfig.config[entry.sprinkleID].calculation.val < myConfig.config[entry.sprinkleID].calculation.maxIrrigation)) // Bodenfeuchte noch nicht erreicht? (z.B. beim Regen)
305
+ ) { /* Zeit läuft */
306
+ adapter.setStateAsync(`sprinkle.${entry.name}.countdown`, {
307
+ val: addTime(entry.wateringTime - entry.count, ''),
308
+ ack: true
309
+ });
310
+
311
+ /* Alle 15s die Bodenfeuchte anpassen */
312
+ if (entry.calcOn // Vergleich nur bei Berechnung der Verdunstung
313
+ && !(entry.count % 15) // alle 15s ausführen
314
+ ) {
315
+ myConfig.addSoilMoistVal(entry.sprinkleID, entry.soilMoisture15s);
316
+ }
317
+
318
+ /* Intervall-Beregnung wenn angegeben (onOffTimeOff > 0) */
319
+ if ((entry.onOffTimeOff > 0) && !(entry.count % entry.onOffTimeOn)) {
320
+ adapter.log.info(`Intervall-Beregnung, timeOn: ${entry.onOffTimeOn}s, count: ${entry.count}s, timeOff: ${entry.onOffTimeOff}s, count % onOffTime: ${entry.count % entry.onOffTimeOn}`);
321
+ const _setValveOnOff = await setValve(entry, false);
322
+ if (_setValveOnOff === entry.idState) {
323
+ entry.myBreak = true;
324
+ /* Zustand des Ventils im Thread <<< 3 = Pause >>> (0:off; 1:wait; 2:on; 3:break; 4:Boost(on); 5:off(Boost); 6:Cistern empty; 7:extBreak) */
325
+ entry.state = 'break';
326
+ adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
327
+ val: entry.state,
328
+ ack: true
329
+ });
330
+ clearInterval(entry.countdown);
331
+ entry.countdown = null;
332
+ currentConsumption(true);
333
+ onOffTimeoutOff(entry);
334
+ updateList();
335
+ }
336
+ }
337
+ } else { /* zeit abgelaufen => Ventil ausschalten */
338
+ /* Wenn in der Konfiguration Bodenfeuchte = 100% gesetzt ist und Auto-Bewässerung aktive, dann Bodenfeuchte = 100% setzen*/
339
+ if (entry.autoOn && entry.calcOn && myConfig.config[entry.sprinkleID].calculation.endIrrigation) {
340
+ myConfig.setSoilMoistPct100(entry.sprinkleID);
341
+ }
342
+ /* Verbrauchswerte in der Historie aktualisieren */
343
+ addConsumedAndTime(entry);
344
+ /* Booster zurücksetzen */
345
+ if (myConfig.config[entry.sprinkleID].booster) {
346
+ //if (boostOn) {boostKill(entry.sprinkleID);}
347
+ boostReady = true;
348
+ boostOn = false;
349
+ adapter.log.debug(`ID: ${entry.name} UpdateList Sprinkle Off: boostReady = ${boostReady}`);
350
+ }
351
+
352
+ const _setValve = await setValve(entry, false);
353
+
354
+ /* Zustand des Ventils im Thread <<< 0 >>> off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
355
+ entry.state = 'off';
356
+ const _sprinklerState = await adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
357
+ val: entry.state,
358
+ ack: true
359
+ });
360
+ const _runningTime = adapter.setStateAsync(`sprinkle.${entry.name}.runningTime`, {
361
+ val: '00:00',
362
+ ack: true
363
+ });
364
+ const _countdown = adapter.setStateAsync(`sprinkle.${entry.name}.countdown`, {
365
+ val: '0',
366
+ ack: true
367
+ });
368
+ currentConsumption(true);
369
+ // currentConsumption(entry.pipeFlow, false);
370
+ Promise.all([
371
+ _setValve,
372
+ _sprinklerState,
373
+ _runningTime,
374
+ _countdown
375
+ ]).then(async () => {
376
+ entry.killSprinkle = true;
377
+ /* Zeiten löschen */
378
+ clearInterval(entry.countdown);
379
+ entry.countdown = null;
380
+ // entry.ac.acOnOffTimeoutOff.abort(); /// ???
381
+
382
+ }).then(()=>{
383
+ updateList();
384
+ });
385
+
386
+ }
387
+ } catch (error) {
388
+ adapter.log.error(`countSprinkleTime(${entry.name}): ${error}`);
389
+ }
390
+
391
+ };
392
+
393
+ /**
394
+ * Timer zum ausschalten des Boost
395
+ *
396
+ * @param {*} entry
397
+ */
398
+ const boostOnTimer = async (entry) => {
399
+ entry.ac.acBoostOnTimer = new AbortController;
400
+ const combinedSignal = AbortSignal.any([
401
+ entry.ac.acBoostOnTimer.signal
402
+ ]);
403
+ try {
404
+ adapter.log.debug(`ID: ${entry.name} boostOnTimer time start`);
405
+ await asyncTime.setTimeout(30000, undefined, { signal: combinedSignal });
406
+ boostOn = false;
407
+ // Zustand des Ventils im Thread < 0 > off, < 1 > wait, <<< 2 >>> on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost)
408
+ entry.state = 'on';
409
+ adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
410
+ val: entry.state,
411
+ ack: true
412
+ });
413
+ adapter.log.debug(`ID: ${entry.name} boostOnTimer time out: boostOn = ${boostOn}`);
414
+ updateList();
415
+ } catch (error) {
416
+ boostOn = false;
417
+ adapter.log.debug(`ID: ${entry.name} boostOnTimer time out: ERROR: ${error}`);
418
+ updateList();
419
+ }finally{
420
+ if (entry.ac.acBoostOnTimer.aborted === false) entry.ac.acBoostOnTimer.abort();
421
+ }
422
+ };
423
+
424
+ /**
425
+ * Control of the active irrigation circuits so that the maximum pump capacity (l / h) is achieved and the maximum number of irrigation circuits is not exceeded.
426
+ * => Steuerung der aktiven Bewässerungskreise, damit die maximale Pumpenkapazität (l / h) erreicht wird und die maximale Anzahl der Bewässerungskreise nicht überschritten wird.
427
+ */
428
+ const updateList = async () => {
429
+ /* während des Boost eines Kreises ist ein Zuschalten von Sprengern nicht möglich */
430
+ if (boostOn) return;
431
+ if (updateListMarker.funcActive === true) {
432
+ updateListMarker.newStart = true;
433
+ return;
434
+ }
435
+ const killList = [];
436
+ let consumption = {};
437
+ /* (timerOn = true) => Schaltabstand zwischen den Ventilen ein */
438
+ let timerOn = true;
439
+ // let switchingDistanceOn = false;
440
+ updateListMarker.funcActive = true;
441
+
442
+ /**
443
+ * Sortierfunktion mySortDescending absteigende Sortierung
444
+ *
445
+ * @param a
446
+ * @param b
447
+ * @returns {number}
448
+ */
449
+ function mySortDescending(a, b) {
450
+ return a.pipeFlow > b.pipeFlow ? -1 :
451
+ a.pipeFlow < b.pipeFlow ? 1 :
452
+ 0;
453
+ }
454
+ /**
455
+ * Sortierfunktion mySortAscending aufsteigende Sortierung
456
+ *
457
+ * @param a
458
+ * @param b
459
+ * @returns {number}
460
+ */
461
+ function mySortAscending(a, b) {
462
+ return a.pipeFlow < b.pipeFlow ? -1 :
463
+ a.pipeFlow > b.pipeFlow ? 1 :
464
+ 0;
465
+ }
466
+
467
+ // ermitteln von curPipe und der Anzahl der parallelen Stränge
468
+ consumption = await currentConsumption(true);
469
+ if (consumption.pumpRequired === true) { // Pumpe erforderlich
470
+
471
+ // Spannungsversorgung einschalten bei Leistungsanforderung
472
+ try {
473
+ if (adapter.config.triggerControlVoltage
474
+ && controlVoltage.enable === false) {
475
+ const _controlVoltage = await setValve(controlVoltage, true);
476
+ if (_controlVoltage === controlVoltage.idState) {
477
+ adapter.setStateAsync(`info.supplyVoltage`, {
478
+ val: controlVoltage.enable,
479
+ ack: true
480
+ });
481
+ await asyncTime.setTimeout(switchingDistanceMS, undefined, undefined);
482
+ }
483
+ }
484
+ } catch (error) {
485
+ adapter.log.error(`Error trigger Control Voltage [${controlVoltage.idState}]: ${error}`);
486
+ }
487
+
488
+ // Pumpe einschalten bei Leistungsanforderung
489
+ try {
490
+ if (adapter.config.pumpSelection !== 'noPump'
491
+ && currentPumpUse.enable === false) {
492
+ const _currentPumpUse = await setValve(currentPumpUse, true);
493
+ if (_currentPumpUse && _currentPumpUse === currentPumpUse.idState) {
494
+ timerOn = false;
495
+ adapter.setStateAsync(currentPumpUse.id, {
496
+ val: currentPumpUse.enable,
497
+ ack: true
498
+ });
499
+ await asyncTime.setTimeout(currentPumpUse.leadTime, undefined, undefined);
500
+ }
501
+ }
502
+ } catch (error) {
503
+ adapter.log.error(`Error trigger current Pump [${currentPumpUse.idState}]: ${error}`);
504
+ }
505
+ }
506
+
507
+ /* - wenn beim Umschalten der Pumpen die Förderleistung zu gering → Ventile deaktivieren - */
508
+ if (consumption.curFlow < 0) {
509
+ // aufsteigend sortieren nach der Verbrauchsmenge
510
+ threadList.sort(mySortAscending);
511
+
512
+ for await(const entry of threadList) {
513
+ entry.ac.acUpdateListPuOff = new AbortController; // acPumpeOff
514
+ try {
515
+ if (entry.enable // eingeschaltet
516
+ && !entry.killSprinkle // && Aufgabe noch nicht erledigt
517
+ && (consumption.curFlow < 0) // && Förderleistung der Pumpe zu gering
518
+ ) {
519
+ entry.state = 'wait'; //1
520
+ const _setValve2 = await setValve(entry, false);
521
+ if (_setValve2 === entry.idState) {
522
+ consumption = await currentConsumption(true);
523
+ clearInterval(entry.countdown); // Zähler für Countdown, Verbrauchsmengen, usw. löschen
524
+ entry.countdown = null;
525
+ adapter.log.info(`Set Valve ID: ${entry.name} Pump delivery rate too low, wait! curFlow ${consumption.curFlow} parallel: ${consumption.parallel}`);
526
+ await asyncTime.setTimeout(300, undefined, {signal: entry.ac.acUpdateListPuOff.signal});
527
+ }
528
+ }else{
529
+ entry.ac.acUpdateListPuOff.abort();
530
+ }
531
+ } catch (error) { // Fehler beim Reduzieren der Verbrauchsmenge
532
+ adapter.log.error(`Error reducing consumption amount: ${error}`);
533
+ } finally {
534
+ if (entry.ac.acUpdateListPuOff.aborted === false) entry.ac.acUpdateListPuOff.abort();
535
+ }
536
+ }
537
+ }
538
+
539
+ adapter.log.info(`curFlow: ${consumption.curFlow}, parallel: ${consumption.parallel}`);
540
+ // absteigend sortieren nach der Verbrauchsmenge
541
+ threadList.sort(mySortDescending);
542
+
543
+ // einschalten der Bewässerungsventile nach Verbrauchsmenge und maximaler Anzahl
544
+ for await(const entry of threadList) {
545
+ try {
546
+ if (!entry.enable // ausgeschaltet
547
+ && !entry.killSprinkle // && Aufgabe noch nicht erledigt
548
+ && !entry.myBreak // && nicht in der Pause Interval-Beregnung
549
+ && !entry.extBreak // && nicht in der externen Pause (extBreak)
550
+ && (consumption.curFlow >= entry.pipeFlow) // && noch genügend Förderleistung der Pumpe
551
+ && (consumption.parallel < maxParallel) // && maxParallel noch nicht erreicht
552
+ && !boostOn // && Ventile nur einschalten, wenn kein Boost aktive
553
+ && (boostReady || !(myConfig.config[entry.sprinkleID].booster)) // && wenn kein Boostventil aktive ist => alle Ventile ein ansonsten nur Ventile ohne Boostfunktion
554
+ ){
555
+ entry.ac.acUpdateListOn = new AbortController;
556
+
557
+ timerOn === true ? await asyncTime.setTimeout(switchingDistanceMS, undefined, {signal: entry.ac.acUpdateListOn.signal}) : timerOn = true;
558
+ entry.state = myConfig.config[entry.sprinkleID].booster ? 'boost(on)' : 'on'; // state 4 => boost(on) : 2 => on
559
+ const _setValve3 = await setValve(entry, true);
560
+ if (_setValve3 === entry.idState) {
561
+ consumption = await currentConsumption(true);
562
+ if (myConfig.config[entry.sprinkleID].booster) {
563
+ boostReady = false;
564
+ boostOn = true;
565
+ boostOnTimer(entry);
566
+ adapter.log.debug(`ID: ${entry.name} UpdateList sprinkle On: boostReady = ${boostReady}`);
567
+ }
568
+
569
+ /* countdown starten */
570
+ if (!entry.startTime) {
571
+ entry.startTime = new Date();
572
+ }
573
+ adapter.log.debug(`!entry.countdown: ${!entry.countdown}, type: ${typeof(entry.countdown)}`);
574
+ if(!entry.countdown){
575
+ entry.countdown = setInterval(countSprinkleTime, 1000, entry); // 1000 = 1s
576
+ //entry.countdown = asyncTime.setInterval();
577
+ }
578
+ } else {
579
+ entry.ac.acUpdateListOn.abort();
580
+ }
581
+ }
582
+ } catch (error) { //Fehler beim Einschalten der Bewässerungsventile
583
+ adapter.log.error(`Error turning on irrigation valves [${entry.name}]: ${error}`);
584
+ }
585
+ }
586
+
587
+ // Ausschalten der Ventile bei boostReady
588
+ if (boostOn) {
589
+ for await(const entry of threadList){
590
+ entry.ac.acUpdateListBoostOn = new AbortController;
591
+ try {
592
+ if(entry.enable // eingeschaltet
593
+ && !entry.killSprinkle
594
+ && !myConfig.config[entry.sprinkleID].booster
595
+ ){
596
+ entry.state = 'off(Boost)'; //state => 5/off(boost)
597
+ const _setValve4 = await setValve(entry, false);
598
+ if (_setValve4 === entry.idState){
599
+ consumption = await currentConsumption(true);
600
+ clearInterval(entry.countdown);
601
+ entry.countdown = null;
602
+ await asyncTime.setTimeout(300, undefined, {signal: entry.ac.acUpdateListBoostOn.signal});
603
+ }
604
+ }
605
+ } catch (error) {
606
+ entry.ac.acUpdateListBoostOn.abort();
607
+ adapter.log.error(`Error when switching off the valves with boostReady [${entry.name}]: ${error}`);
608
+ } finally {
609
+ if (entry.ac.acUpdateListBoostOn.aborted === false) entry.ac.acUpdateListBoostOn.abort();
610
+ }
611
+ }
612
+ }
613
+
614
+ if (consumption.parallel === 0) { // Pumpe nicht mehr erforderlich | 0 Ventile an
615
+
616
+ // Pumpe ausschalten
617
+ if (currentPumpUse.enable === true) {
618
+ try {
619
+ const _currentPumpUse = await setValve(currentPumpUse, false);
620
+ if (_currentPumpUse === currentPumpUse.idState) {
621
+ adapter.setStateAsync(currentPumpUse.id,{
622
+ val: currentPumpUse.enable,
623
+ ack: true
624
+ });
625
+ await asyncTime.setTimeout(switchingDistanceMS, undefined, undefined);
626
+ }
627
+ } catch (error) {
628
+ adapter.log.error(`Error trigger current Pump [${currentPumpUse.idState}]: ${error}`);
629
+ }
630
+ }
631
+
632
+ // Spannungsversorgung (24V) ausschalten
633
+ if(controlVoltage.enable === true) {
634
+ try {
635
+ const _controlVoltage = await setValve(controlVoltage, false);
636
+ if (_controlVoltage === controlVoltage.idState) {
637
+ adapter.setStateAsync(`info.supplyVoltage`, {
638
+ val: controlVoltage.enable,
639
+ ack: true
640
+ });
641
+ // await asyncTime.setTimeout(switchingDistanceMS, undefined, undefined);
642
+
643
+ }
644
+ } catch (error) {
645
+ adapter.log.error(`Error trigger Control Voltage [${controlVoltage.idState}]: ${error}`);
646
+ }
647
+ }
648
+ }
649
+
650
+ for await(const entry of threadList){
651
+ if(entry.killSprinkle
652
+ && !entry.enable
653
+ ){
654
+ killList.push(entry.name);
655
+ }
656
+ }
657
+
658
+ if (killList.length > 0) {
659
+ await delList(killList); // erledigte Bewässerungsaufgaben aus der threadList löschen
660
+ await asyncTime.setTimeout(50,undefined,undefined);
661
+ }
662
+
663
+ updateListMarker.funcActive = false;
664
+ if (updateListMarker.newStart === true) {
665
+ updateListMarker.newStart = false;
666
+ await asyncTime.setTimeout(50,undefined,undefined);
667
+ updateList();
668
+ }
669
+
670
+ }; // End updateList
671
+
672
+ /* --------------------------------------------------------------------------------------------------------------------------------------------------------------------- */
673
+
674
+ /**
675
+ * +++++ Set the current pump for irrigation +++++
676
+ * → Festlegen der aktuellen Pumpe zur Bewässerung
677
+ */
678
+ const setActualPump = async () => {
679
+ try {
680
+ switch(adapter.config.pumpSelection) {
681
+ case 'noPump':
682
+ case 'mainPump': {
683
+ /* Pumpe AUS => Zisternen-Bewässerung nicht aktiviert */
684
+ if (adapter.config.triggerCisternPump) {
685
+ adapter.setState('info.cisternState', {
686
+ val: `Cistern settings are not active! ${(fillLevelCistern > 0) ? (`level sensor: ${fillLevelCistern}% ${(adapter.config.triggerMinCisternLevel !== '') ? (` ${adapter.config.triggerMinCisternLevel}%`) : ('')}`):('')}`,
687
+ ack: true
688
+ });
689
+ }
690
+ break;
691
+ }
692
+
693
+ case 'cistern': {
694
+ if (fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) { // (Zisterne unter Minimum)
695
+ if (!currentPumpUse.intBreak) {
696
+ currentPumpUse.intBreak = true;
697
+ adapter.log.warn('Cistern empty => irrigation no longer possible');
698
+ if(!sendMessageText.onlySendError()){
699
+ sendMessageText.sendMessage('Cistern empty => irrigation no longer possible'); // Zisterne leer => Bewässerung nicht mehr möglich
700
+ }
701
+ }
702
+ adapter.setState('info.cisternState', {
703
+ val: `Cistern empty: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`,
704
+ ack: true
705
+ });
706
+ // Pumpe ist eingeschaltet => ausschalten
707
+ if (currentPumpUse.enable) {
708
+ await setValve(currentPumpUse, false);
709
+ updateList(); // Wasserverbrauch an Pumpenleistung anpassen
710
+ }
711
+ } else if (fillLevelCistern > parseFloat(adapter.config.triggerOnCisternLevel)) {
712
+ if (currentPumpUse.intBreak) {
713
+ currentPumpUse.intBreak = false;
714
+ adapter.log.warn('Cistern filled => irrigation can begin again'); // Zisterne gefüllt => Bewässerung kann wieder beginnen
715
+ if(!sendMessageText.onlySendError()){
716
+ sendMessageText.sendMessage('Cistern filled => irrigation can begin again');
717
+ }
718
+ }
719
+ currentPumpUse.intBreak = false;
720
+ adapter.setState('info.cisternState', {
721
+ val: `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`,
722
+ ack: true
723
+ });
724
+ updateList(); // Wasserverbrauch an Pumpenleistung anpassen
725
+ } else if (currentPumpUse.intBreak === true) {
726
+ adapter.setState('info.cisternState', {
727
+ val: `Cistern empty: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`,
728
+ ack: true
729
+ });
730
+ } else {
731
+ adapter.setState('info.cisternState', {
732
+ val: `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`,
733
+ ack: true
734
+ });
735
+ }
736
+ break;
737
+ }
738
+
739
+ case 'pumpAndCistern': {
740
+ if (currentPumpUse.enable) {
741
+ /* Bewässerungspumpen aktiv */
742
+ if ((fillLevelCistern < parseFloat(adapter.config.triggerMinCisternLevel)) && (currentPumpUse.pumpCistern === true)) {
743
+ /* (Zisterne unter Minimum) && (ZisternenPumpe läuft) */
744
+ const _setValveCisternPumpOff = await setValve(currentPumpUse, false); // Pumpe Zisterne Aus
745
+ if (_setValveCisternPumpOff === currentPumpUse.idState) {
746
+ await adapter.setStateAsync(currentPumpUse.id, { // set State cisternPump === false
747
+ val: currentPumpUse.enable,
748
+ ack: true
749
+ });
750
+ }
751
+ currentPumpUse.pumpCistern = false;
752
+ currentPumpUse.name = 'Main pump';
753
+ currentPumpUse.idState = adapter.config.triggerMainPump || '';
754
+ currentPumpUse.id = 'info.mainPump';
755
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
756
+ /* Hauptpumpe Ein */
757
+ const _setValveMainPumpOn = await setValve(currentPumpUse, true);
758
+ if (_setValveMainPumpOn === currentPumpUse.idState) {
759
+ await adapter.setStateAsync(currentPumpUse.id, { // set State cisternPump === false
760
+ val: currentPumpUse.enable,
761
+ ack: true
762
+ });
763
+ }
764
+ adapter.log.info('Pump change (cistern empty) Cistern pump off => main pump on');
765
+ if(!sendMessageText.onlySendError()){
766
+ sendMessageText.sendMessage('Pump change (cistern empty) Cistern pump off => main pump on');
767
+ }
768
+ updateList(); // Wasserverbrauch an Pumpenleistung anpassen
769
+ }
770
+ } else {
771
+ /* Bewässerungspumpen inaktiv */
772
+ if ((fillLevelCistern > parseFloat(adapter.config.triggerOnCisternLevel)) && (adapter.config.triggerCisternPump) && (adapter.config.triggerCisternPumpPower)) {
773
+ /* Zisterne voll && triggerCisternPump && triggerCisternPumpPower vorhanden*/
774
+ adapter.setState('info.cisternState', {
775
+ val: `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`,
776
+ ack: true
777
+ });
778
+ currentPumpUse.pumpCistern = true;
779
+ currentPumpUse.name = 'Cistern pump';
780
+ currentPumpUse.id = 'info.cisternPump';
781
+ currentPumpUse.idState = adapter.config.triggerCisternPump || '';
782
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerCisternPumpPower);
783
+
784
+ } else {
785
+ adapter.setState('info.cisternState', {
786
+ val: `Cistern empty: ${fillLevelCistern} % (adapter.config.triggerMinCisternLevel %)`,
787
+ ack: true
788
+ });
789
+ currentPumpUse.pumpCistern = false;
790
+ currentPumpUse.name = 'Main pump';
791
+ currentPumpUse.id = 'info.mainPump';
792
+ currentPumpUse.idState = adapter.config.triggerMainPump || '';
793
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower);
794
+ }
795
+ adapter.setState('control.restFlow', {
796
+ val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} | ${currentPumpUse.name})`,
797
+ ack: true
798
+ });
799
+ }
800
+ if (currentPumpUse.pumpCistern) {
801
+ adapter.setState('info.cisternState', {
802
+ val: `Cistern filled: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`,
803
+ ack: true
804
+ });
805
+ } else {
806
+ adapter.setState('info.cisternState', {
807
+ val: `Cistern empty: ${fillLevelCistern} % (${adapter.config.triggerMinCisternLevel} %)`,
808
+ ack: true
809
+ });
810
+ }
811
+ break;
812
+ }
813
+ }
814
+ } catch (error) {
815
+ adapter.log.error(`setActualPump ${error}`);
816
+ }
817
+ }; // End setActualPump
818
+
819
+
820
+ /**
821
+ * Adding the consumption data to the history
822
+ * => Hinzufügen der Verbrauchsdaten zur History
823
+ *
824
+ * @param entry - array mit den Daten des aktiven Ventils
825
+ */
826
+ function addConsumedAndTime(entry) {
827
+ adapter.setState(`sprinkle.${entry.name}.history.lastConsumed`, {
828
+ val: Math.round(entry.litersPerSecond * entry.count),
829
+ ack: true
830
+ });
831
+ adapter.setState(`sprinkle.${entry.name}.history.lastRunningTime`, {
832
+ val: addTime(entry.count, ''),
833
+ ack: true
834
+ });
835
+ const _formatTime = formatTime(entry.startTime);
836
+ adapter.setState(`sprinkle.${entry.name}.history.lastOn`, {
837
+ val: _formatTime.dayTime,
838
+ ack: true
839
+ });
840
+ adapter.getState(`sprinkle.${entry.name}.history.curCalWeekConsumed`, (err, state) => {
841
+ if (state && state.val) {
842
+ adapter.setState(`sprinkle.${entry.name}.history.curCalWeekConsumed`, {
843
+ val: (+state.val) + Math.round(entry.litersPerSecond * entry.count),
844
+ ack: true
845
+ });
846
+ }
847
+ });
848
+ adapter.getState(`sprinkle.${entry.name}.history.curCalWeekRunningTime`, (err, state) => {
849
+ if (state && state.val) {
850
+ adapter.setState(`sprinkle.${entry.name}.history.curCalWeekRunningTime`, {
851
+ val: addTime(+state.val, entry.count),
852
+ ack: true
853
+ });
854
+ }
855
+ });
856
+ } // End addConsumedAndTime
857
+
858
+
859
+ /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
860
+ /* externe Funktionen */
861
+ /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
862
+
863
+ /**
864
+ * -- externe Funktionen -> initValveControl - addList - extBreak - clearEntireList - setFillLevelCistern --
865
+ */
866
+ const valveControl = {
867
+ /**
868
+ * Initialize the start configuration of ventilControl
869
+ * => Initialisieren Sie die Startkonfiguration von ventilControl
870
+ *
871
+ * @param {ioBroker.Adapter} myAdapter
872
+ */
873
+ initValveControl: async function (myAdapter) {
874
+ adapter = adapter || myAdapter;
875
+
876
+ try {
877
+ switchingDistanceMS = (
878
+ (+adapter.config.switchingDistance || 0) < 200)
879
+ ? 200
880
+ : (
881
+ (+adapter.config.switchingDistance > 240000)
882
+ ? 240000
883
+ : (+adapter.config.switchingDistance)
884
+ );
885
+ adapter.log.info(`switchingDistanceMS: ${switchingDistanceMS}ms`);
886
+ /* Object supplyVoltage (VersorgungsSpannung) anlegen */
887
+ if (adapter.config.triggerControlVoltage.length > 5) {
888
+ controlVoltage.idState = adapter.config.triggerControlVoltage; // Schaltaktor zuweisen
889
+ await adapter.setObjectNotExistsAsync(`info.supplyVoltage`, {
890
+ type: 'state',
891
+ common: {
892
+ role: 'switch',
893
+ name: 'Supply voltage',
894
+ desc: {
895
+ en: 'power supply',
896
+ de: 'Stromversorgung',
897
+ ru: 'источник питания',
898
+ pt: 'fonte de energia',
899
+ nl: 'voeding',
900
+ fr: 'alimentation électrique',
901
+ it: 'Alimentazione elettrica',
902
+ es: 'fuente de alimentación',
903
+ pl: 'zasilacz',
904
+ uk: 'джерело живлення',
905
+ 'zh-cn': '电源'
906
+ },
907
+ type: 'boolean',
908
+ read: true,
909
+ write: false,
910
+ def: false
911
+ },
912
+ native: {},
913
+ });
914
+ } else {
915
+ const _supplyVoltage = await adapter.getObjectAsync(`info.supplyVoltage`);
916
+ if (_supplyVoltage) await adapter.delObjectAsync(`info.supplyVoltage`);
917
+ }
918
+
919
+
920
+ /* Objekt control.parallelOfMax befüllen */
921
+ maxParallel = parseInt(adapter.config.maximumParallelValves);
922
+ adapter.setStateAsync('control.parallelOfMax', {
923
+ val: `0 : ${adapter.config.maximumParallelValves}`,
924
+ ack: true
925
+ });
926
+
927
+ /* Object mainPump und Cistern anlegen, wenn benötigt */
928
+ const objMainPump = {
929
+ type: 'state',
930
+ common: {
931
+ role: 'indicator',
932
+ name: 'Main pump',
933
+ desc: {
934
+ en: 'Main pump',
935
+ de: 'Hauptpumpe',
936
+ ru: 'Главный насос',
937
+ pt: 'Bomba principal',
938
+ nl: 'Hoofdpomp',
939
+ fr: 'Pompe principale',
940
+ it: 'Pompa principale',
941
+ es: 'Bomba principal',
942
+ pl: 'Główna pompa',
943
+ uk: 'Головний насос',
944
+ 'zh-cn': '主泵'
945
+ },
946
+ type: 'boolean',
947
+ read: true,
948
+ write: false,
949
+ def: false
950
+ },
951
+ native: {},
952
+ };
953
+ const objCisternPump = {
954
+ type: 'state',
955
+ common: {
956
+ role: 'indicator',
957
+ name: 'Cistern pump',
958
+ desc: {
959
+ en: 'Cistern pump',
960
+ de: 'Zisternenpumpe',
961
+ ru: 'Насос для цистерны',
962
+ pt: 'Bomba da cisterna',
963
+ nl: 'Cisternpomp',
964
+ fr: 'Pompe de citerne',
965
+ it: 'Pompa cisterna',
966
+ es: 'Bomba de cisterna',
967
+ pl: 'Pompa zbiornika',
968
+ uk: 'Насос цистерни',
969
+ 'zh-cn': '水箱泵'
970
+ },
971
+ type: 'boolean',
972
+ read: true,
973
+ write: false,
974
+ def: false
975
+ },
976
+ native: {},
977
+ };
978
+
979
+ switch (adapter.config.pumpSelection) {
980
+ case 'noPump': {
981
+ currentPumpUse.pumpCistern = false;
982
+ currentPumpUse.name = 'no pump';
983
+ currentPumpUse.intBreak = false;
984
+ currentPumpUse.idState = '';
985
+ currentPumpUse.id = '';
986
+ currentPumpUse.leadTime = 0;
987
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower) || 0;
988
+ currentPumpUse.restFlow = parseInt(adapter.config.triggerMainPumpPower) || 0;
989
+ const _mainPump = await adapter.getObjectAsync(`info.mainPump`);
990
+ if (_mainPump) await adapter.delObjectAsync(`info.mainPump`);
991
+ const _cisternPump = await adapter.getObjectAsync(`info.cisternPump`);
992
+ if (_cisternPump) await adapter.delObjectAsync(`info.cisternPump`);
993
+ break;
994
+ }
995
+ case 'mainPump': {
996
+ currentPumpUse.pumpCistern = false;
997
+ currentPumpUse.name = 'Main pump';
998
+ currentPumpUse.intBreak = false;
999
+ currentPumpUse.idState = adapter.config.triggerMainPump || '';
1000
+ currentPumpUse.id = 'info.mainPump';
1001
+ currentPumpUse.leadTime = (parseInt(adapter.config.mainPumpLeadTime) || 5) * 1000;
1002
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower) || 0;
1003
+ currentPumpUse.restFlow = parseInt(adapter.config.triggerMainPumpPower) || 0;
1004
+ // @ts-ignore
1005
+ await adapter.setObjectNotExistsAsync(`info.mainPump`, objMainPump);
1006
+ const _cisternPump = await adapter.getObjectAsync(`info.cisternPump`);
1007
+ if (_cisternPump) await adapter.delObjectAsync(`info.cisternPump`);
1008
+ break;
1009
+ }
1010
+ case 'cistern': {
1011
+ /* Füllstand der Zisterne in % holen */
1012
+ const _actualValueLevel = await adapter.getForeignStateAsync(adapter.config.actualValueLevel);
1013
+ if (_actualValueLevel && _actualValueLevel.val) {
1014
+ if (typeof _actualValueLevel.val === 'number') {
1015
+ fillLevelCistern = _actualValueLevel.val;
1016
+ (_actualValueLevel.val > parseInt(adapter.config.triggerMinCisternLevel)) ? currentPumpUse.intBreak = false : currentPumpUse.intBreak = true;
1017
+ } else if (typeof _actualValueLevel.val === 'boolean') {
1018
+ if (_actualValueLevel.val) {
1019
+ currentPumpUse.intBreak = false;
1020
+ fillLevelCistern = 100;
1021
+ } else {
1022
+ currentPumpUse.intBreak = true;
1023
+ fillLevelCistern = 0;
1024
+ }
1025
+ } else {
1026
+ adapter.log.warn(`level sensor in the cistern => Wrong value: ${_actualValueLevel.val}, Type: ${typeof _actualValueLevel.val}`);
1027
+ }
1028
+ } else {
1029
+ adapter.log.warn(`level sensor in the cistern => Wrong value: ${JSON.stringify(_actualValueLevel)}`);
1030
+ fillLevelCistern = 0;
1031
+ currentPumpUse.intBreak = true;
1032
+ }
1033
+ currentPumpUse.pumpCistern = false;
1034
+ currentPumpUse.name = 'Cistern pump';
1035
+ currentPumpUse.idState = adapter.config.triggerCisternPump || '';
1036
+ currentPumpUse.id = 'info.cisternPump';
1037
+ currentPumpUse.leadTime = (parseInt(adapter.config.cisternPumpLeadTime) || 5) * 1000;
1038
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerCisternPumpPower) || 0;
1039
+ currentPumpUse.restFlow = parseInt(adapter.config.triggerCisternPumpPower) || 0;
1040
+ // @ts-ignore
1041
+ await adapter.setObjectNotExistsAsync(`info.cisternPump`, objCisternPump);
1042
+ const _mainPump = await adapter.getObjectAsync(`info.mainPump`);
1043
+ if (_mainPump) await adapter.delObjectAsync(`info.mainPump`);
1044
+ break;
1045
+ }
1046
+ case 'pumpAndCistern': {
1047
+ currentPumpUse.enable = false;
1048
+ if (adapter.config.actualValueLevel) {
1049
+ /* Füllstand der Zisterne in % holen */
1050
+ const _actualValueLevel = await adapter.getForeignStateAsync(adapter.config.actualValueLevel);
1051
+ if (_actualValueLevel && _actualValueLevel.val) {
1052
+ if (typeof _actualValueLevel.val === 'number') {
1053
+ fillLevelCistern = _actualValueLevel.val;
1054
+ } else if (typeof _actualValueLevel.val === 'boolean') {
1055
+ _actualValueLevel.val ? fillLevelCistern = 100 : fillLevelCistern = 0;
1056
+ } else {
1057
+ fillLevelCistern = 0;
1058
+ adapter.log.warn(`level sensor in the cistern => Wrong value: ${_actualValueLevel.val}, Type: ${typeof _actualValueLevel.val}`);
1059
+ }
1060
+ }
1061
+
1062
+ if (fillLevelCistern > parseInt(adapter.config.triggerMinCisternLevel)) {
1063
+ currentPumpUse.pumpCistern = true;
1064
+ currentPumpUse.name = 'Cistern pump';
1065
+ currentPumpUse.idState = adapter.config.triggerCisternPump || '';
1066
+ currentPumpUse.id = 'info.cisternPump';
1067
+ currentPumpUse.leadTime = (parseInt(adapter.config.cisternPumpLeadTime) || 5) * 1000;
1068
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerCisternPumpPower) || 0;
1069
+ currentPumpUse.restFlow = parseInt(adapter.config.triggerCisternPumpPower) || 0;
1070
+ } else {
1071
+ currentPumpUse.pumpCistern = false;
1072
+ currentPumpUse.name = 'Main pump';
1073
+ currentPumpUse.idState = adapter.config.triggerMainPump || '';
1074
+ currentPumpUse.id = 'info.mainPump';
1075
+ currentPumpUse.leadTime = (parseInt(adapter.config.mainPumpLeadTime) || 5) * 1000;
1076
+ currentPumpUse.pumpPower = parseInt(adapter.config.triggerMainPumpPower) || 0;
1077
+ currentPumpUse.restFlow = parseInt(adapter.config.triggerMainPumpPower) || 0;
1078
+ }
1079
+
1080
+ currentPumpUse.intBreak = false;
1081
+ }
1082
+ // @ts-ignore
1083
+ await adapter.setObjectNotExistsAsync(`info.mainPump`, objMainPump);
1084
+ // @ts-ignore
1085
+ await adapter.setObjectNotExistsAsync(`info.cisternPump`, objCisternPump);
1086
+ break;
1087
+ }
1088
+ default: {
1089
+ currentPumpUse.intBreak = true;
1090
+ adapter.log.error(`In the configuration, under Pump settings, select the Pump selection field.`);
1091
+ const _mainPump = await adapter.getObjectAsync(`info.mainPump`);
1092
+ if (_mainPump) await adapter.delObjectAsync(`info.mainPump`);
1093
+ const _cisternPump = await adapter.getObjectAsync(`info.cisternPump`);
1094
+ if (_cisternPump) await adapter.delObjectAsync(`info.cisternPump`);
1095
+ break;
1096
+ }
1097
+ }
1098
+ /* Objekt control.restFlow befüllen */
1099
+ adapter.setStateAsync('control.restFlow', {
1100
+ val: `${currentPumpUse.pumpPower} (${currentPumpUse.pumpPower} | ${currentPumpUse.name})`,
1101
+ ack: true
1102
+ });
1103
+
1104
+ /*Pumpe ausschalten*/
1105
+ currentPumpUse.enable = false;
1106
+ /* Pumpe ausschalter, wenn vorhanden */
1107
+ if (adapter.config.triggerMainPump !== '') {
1108
+ const _triggerMainPump = await adapter.getStateAsync('adapter.config.triggerMainPump');
1109
+ if (_triggerMainPump && _triggerMainPump.val && _triggerMainPump.val === true) {
1110
+ adapter.setStateAsync(adapter.config.triggerMainPump, {
1111
+ val: false,
1112
+ ack: false
1113
+ });
1114
+ adapter.setStateAsync('info.mainPump', {
1115
+ val:false,
1116
+ ack:false
1117
+ });
1118
+ }
1119
+ }
1120
+ /* Pumpe (Zisterne) ausschalter, wenn vorhanden */
1121
+ if (adapter.config.triggerCisternPump !== '') {
1122
+ const _triggerCisternPump = await adapter.getStateAsync('adapter.config.triggerCisternPump');
1123
+ if (_triggerCisternPump && _triggerCisternPump.val && _triggerCisternPump.val === true) {
1124
+ adapter.setState(adapter.config.triggerCisternPump, {
1125
+ val: false,
1126
+ ack: false
1127
+ });
1128
+ adapter.setStateAsync('info.cisternPump', {
1129
+ val:false,
1130
+ ack:false
1131
+ });
1132
+ }
1133
+ }
1134
+ /* alle Ventile (.name = "hm-rpc.0.MEQ1234567.3.STATE") in einem definierten Zustand (false) versetzen*/
1135
+ const result = adapter.config.events;
1136
+ let testMaxFlow;
1137
+ if (adapter.config.pumpSelection === 'pumpAndCistern') {
1138
+ testMaxFlow = (parseFloat(adapter.config.triggerCisternPumpPower) > parseFloat(adapter.config.triggerMainPumpPower)) ? parseFloat(adapter.config.triggerCisternPumpPower) : parseFloat(adapter.config.triggerMainPumpPower);
1139
+ } else {
1140
+ testMaxFlow = currentPumpUse.pumpPower;
1141
+ }
1142
+ if (result) {
1143
+ for (const res of result) {
1144
+ const objectName = (res.name !== '') ? res.name.replace(/[.;, ]/g, '_') : res.name.replace(/[.;, ]/g, '_');
1145
+ const _valve = await adapter.getStateAsync(res.name);
1146
+ if (_valve?.val === true) {
1147
+ await adapter.setStateAsync(res.name, {
1148
+ val: false,
1149
+ ack: false
1150
+ });
1151
+ }
1152
+ /*Kontrolle Flow Pumpe > Flow Ventil*/
1153
+ if (testMaxFlow < parseInt(res.pipeFlow)) {
1154
+ adapter.log.warn(`The irrigation circuit ${objectName} cannot start! pipeFlow ${res.pipeFlow} > maxFlow ${testMaxFlow}! logicError in the configuration`);
1155
+ }
1156
+ }
1157
+ }
1158
+ } catch (error) {
1159
+ adapter.log.error(`initValveControl ERROR: ${error}`);
1160
+ }
1161
+
1162
+ }, // End initValveControl
1163
+
1164
+ /**
1165
+ * Add Sprinkle
1166
+ * → Sprinkle hinzufügen
1167
+ * - auto → Automatik == (true), Handbetrieb == (false)
1168
+ * - sprinkleID → zugriff auf myConfig.config[sprinkleID]. xyz
1169
+ * - wateringTime → Bewässerungszeit in min
1170
+ *
1171
+ * @param {Array.<{auto: boolean, sprinkleID: number, wateringTime: number}>} sprinkleList
1172
+ */
1173
+ addList: async function (sprinkleList) {
1174
+ try {
1175
+ if (adapter.config.pumpSelection === 'pumpAndCistern'
1176
+ && currentPumpUse.enable === false
1177
+ && fillLevelCistern > parseFloat(adapter.config.triggerMinCisternLevel)
1178
+ ){
1179
+ await setActualPump();
1180
+ }
1181
+ for await (const res of sprinkleList) {
1182
+ const name = myConfig.config[res.sprinkleID].objectName;
1183
+ /**
1184
+ * add done
1185
+ * → hinzufügen erledigt (Sprenger bereits aktive)
1186
+ *
1187
+ */
1188
+ let addDone = false;
1189
+ // schauen ob der Sprenger schon in der threadList ist
1190
+ if (threadList) {
1191
+ for await (const entry of threadList) {
1192
+ if (entry.sprinkleID === res.sprinkleID) {
1193
+ if (entry.wateringTime === res.wateringTime) {
1194
+ return;
1195
+ }
1196
+ entry.wateringTime = res.wateringTime;
1197
+ entry.autoOn = res.auto; // auto: = true autostart; = false Handbetrieb
1198
+ await adapter.setStateAsync(`sprinkle.${name}.runningTime`, {
1199
+ val: addTime(res.wateringTime, ''),
1200
+ ack: true
1201
+ });
1202
+ addDone = true; // Sprinkle found
1203
+ adapter.log.debug(`update ID: ${entry.name} new time: ${addTime(res.wateringTime, '')}`);
1204
+ break;
1205
+ }
1206
+ }
1207
+ }
1208
+
1209
+ if (!addDone) {
1210
+ if (res.wateringTime <= 0) {
1211
+ adapter.setState(`sprinkle.${name}.runningTime`, {
1212
+ val: '00:00',
1213
+ ack: true
1214
+ });
1215
+ return;
1216
+ }
1217
+ const newThread = {};
1218
+ newThread.sprinkleID = res.sprinkleID; // Array[0...]
1219
+ newThread.name = name; // z.B "Blumenbeet"
1220
+ newThread.idState = myConfig.config[res.sprinkleID].idState; // z.B. "hm-rpc.0.MEQ1810129.1.STATE"
1221
+ newThread.wateringTime = res.wateringTime; // Bewässerungszeit
1222
+ newThread.pipeFlow = myConfig.config[res.sprinkleID].pipeFlow; // Wasserverbrauch
1223
+ newThread.count = 0; // Zähler im Sekundentakt
1224
+ newThread.calcOn = (myConfig.config[res.sprinkleID].methodControlSM === 'calculation'); // Anpassung der Bodenfeuchte true/false
1225
+ if (newThread.calcOn) {
1226
+ newThread.soilMoisture15s = 15 * (myConfig.config[res.sprinkleID].calculation.maxIrrigation - myConfig.config[res.sprinkleID].calculation.triggersIrrigation)
1227
+ / (60 * myConfig.config[res.sprinkleID].wateringTime);
1228
+ }
1229
+ /** 0:"off", 1:"wait", 2:"on", 3:"break", 4:"Boost(on)", 5:"off(Boost)", 6:"Cistern empty", 7:"extBreak" */
1230
+ newThread.state = 'wait'; // zustand des Ventils Softwaremäßig
1231
+ newThread.enable = false; // zustand des Ventil softwaremäßig
1232
+ newThread.myBreak = false; // meine Pause
1233
+ newThread.extBreak = myConfig.config[res.sprinkleID].extBreak; // extBreak wird über .sprinkle.*.extBreak geschaltet
1234
+ newThread.killSprinkle = false; // Löschauftrag ausführen am Ende in threadList
1235
+ newThread.litersPerSecond = myConfig.config[res.sprinkleID].pipeFlow / 3600; // Wasserverbrauchsmenge pro Sekunde
1236
+ newThread.onOffTimeOff = myConfig.config[res.sprinkleID].wateringIntervalOff;
1237
+ newThread.onOffTimeOn = myConfig.config[res.sprinkleID].wateringIntervalOn;
1238
+ newThread.autoOn = res.auto;
1239
+ newThread.controller = {}; // controlle von Zeiten und Abbruchsignalen
1240
+ newThread.ac = {}; // object zum speichern des abortController
1241
+ newThread.id = threadList.length || 0;
1242
+ threadList.push(newThread);
1243
+ /* Zustand des Ventils im Thread < 0 > off, <<< 1 >>> wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost), < 6 > Cistern empty, < 7 > extBreak */
1244
+ newThread.extBreak ? (newThread.state = 'extBreak') : (newThread.state = 'wait');
1245
+ adapter.setState(`sprinkle.${name}.sprinklerState`, {
1246
+ val: newThread.state,
1247
+ ack: true
1248
+ });
1249
+ adapter.setState(`sprinkle.${name}.runningTime`, {
1250
+ val: addTime(res.wateringTime, ''),
1251
+ ack: true
1252
+ });
1253
+ adapter.log.debug(`ID: ${name} new order created: ${JSON.stringify(threadList[newThread.id])}`);
1254
+ }
1255
+ }
1256
+ updateList();
1257
+ } catch (error) {
1258
+ adapter.log.warn(`valveControl.addList: ${JSON.stringify(sprinkleList)} => ${error}`);
1259
+ }
1260
+
1261
+ }, // End addList
1262
+
1263
+ /**
1264
+ * extBreak => Spränger unterbrechen bei extBreak
1265
+ *
1266
+ * @param {number} sprinkleID
1267
+ * @param {boolean} extBreak
1268
+ * @returns {Promise<{val: boolean}>} val: true/false = Ventil gefunden / nicht gefunden
1269
+ */
1270
+ extBreak: async function (sprinkleID, extBreak) {
1271
+ try {
1272
+ const _found = await findAsync(threadList, async (d) => {
1273
+ return Promise.resolve (d.sprinkleID === sprinkleID);
1274
+ });
1275
+
1276
+ if (_found?.sprinkleID === sprinkleID) {
1277
+ _found.extBreak = extBreak;
1278
+ if (extBreak === true) { // extBreak aktiv
1279
+ if (_found.enable === true) { // Ventil ist eingeschaltet
1280
+ const _setValve = await setValve(_found, false);
1281
+ if (_setValve === _found.idState) {
1282
+ _found.state = 'extBreak'; // <<< 7 >>> extBreak
1283
+ clearInterval(_found.countdown); // Timer Countdown löschen
1284
+ _found.countdown = null;
1285
+ /* Booster zurücksetzen */
1286
+ if (myConfig.config[_found.sprinkleID].booster) {
1287
+ _found.ac.acBoostOnTimer.abort(extBreak);
1288
+ boostReady = true;
1289
+ boostOn = false;
1290
+ adapter.log.debug(`ID: ${_found.name} UpdateList Sprinkle Off: boostReady = ${boostReady}`);
1291
+ }
1292
+ }
1293
+ } else { // Ventil ist in bereitschaft, aber extBreak wird aktiviert
1294
+ _found.state = 'extBreak'; // <<< 7 >>> extBreak
1295
+ }
1296
+ } else {
1297
+ // extBreak beendet und Zeit noch nicht abgelaufen?
1298
+ (_found.count < _found.wateringTime) ? (_found.state = 'wait') : (_found.state = 'off'); // (Bewässerung noch nicht erledigt) ? (< 0 > off) : (< 1 > wait)
1299
+ }
1300
+ /* Zustand des Ventils im Thread < 0 > off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost), < 6 > Cistern empty, <<< 7 >>> extBreak */
1301
+ await adapter.setStateAsync(`sprinkle.${_found.name}.sprinklerState`, {
1302
+ val: _found.state,
1303
+ ack: true
1304
+ });
1305
+ adapter.log.debug(`valveControl.extBreak: ${extBreak}, Name: ${_found.name}, ID: ${_found.sprinkleID}`); // löschen
1306
+ updateList();
1307
+ return {val: true};
1308
+ } else {
1309
+ return {val: false};
1310
+ }
1311
+ } catch (error) {
1312
+ adapter.log.warn(`valveControl.extBreak(${sprinkleID}, ${extBreak}) => ${error}`);
1313
+ return {val: false};
1314
+ }
1315
+ },
1316
+
1317
+ /**
1318
+ * switch off all devices, when close the adapter
1319
+ * => Beim Beenden des adapters alles ausschalten
1320
+ */
1321
+ clearEntireList: async function () {
1322
+ try {
1323
+ await setValve(controlVoltage, false);
1324
+ await setValve(currentPumpUse, false);
1325
+ // let bValveFound = false; // Ventil gefunden
1326
+ for (let counter = threadList.length - 1; // Loop über das Array
1327
+ counter >= 0;
1328
+ counter--) {
1329
+ const entry = threadList[counter];
1330
+ /* Zustand des Ventils im Thread <<< 0 >>> off, < 1 > wait, < 2 > on, < 3 > break, < 4 > Boost(on), < 5 > off(Boost) */
1331
+ entry.state = 'off'; //0
1332
+ if (entry.enable) {
1333
+ await setValve(entry, false);
1334
+ adapter.log.info(`Set Valve (SprinkleControl: off) ID: ${entry.name}, value: false`);
1335
+ } else {
1336
+ adapter.setStateAsync(`sprinkle.${entry.name}.sprinklerState`, {
1337
+ val: entry.state,
1338
+ ack: true
1339
+ });
1340
+ }
1341
+ adapter.setStateAsync(`sprinkle.${entry.name}.runningTime`, {
1342
+ val: '00:00',
1343
+ ack: true
1344
+ });
1345
+ adapter.setStateAsync(`sprinkle.${entry.name}.countdown`, {
1346
+ val: '0',
1347
+ ack: true
1348
+ });
1349
+ /* Verbrauchswerte in der Historie aktualisieren */
1350
+ addConsumedAndTime(entry);
1351
+ /* del timer countdown */
1352
+ clearInterval(entry.countdown);
1353
+ entry.countdown = null;
1354
+ /* Timer abbrechen */
1355
+ if (entry.ac.acSetValveCancelTimeout.aborted === false) entry.ac.acSetValveCancelTimeout.abort(); // setValve
1356
+ if (entry.ac.acBoostOnTimer.aborted === false) entry.ac.acBoostOnTimer.abort(); // BoostOnTimer
1357
+ if (entry.ac.acUpdateListPuOff.aborted === false) entry.ac.acUpdateListPuOff.abort(); // UpdateList Pumpe aus (Leistung zu gering beim Pumpenwechsel)
1358
+ if (entry.ac.acUpdateListOn.aborted === false) entry.ac.acUpdateListOn.abort(); // UpdateList Ventile ein
1359
+ if (entry.ac.acUpdateListBoostOn.aborted === false) entry.ac.acUpdateListBoostOn.abort(); // UpdateList Ausschalten der Ventile bei BoostOn
1360
+ if (entry.ac.acOnOffTimeoutOff.aborted === false) entry.ac.acOnOffTimeoutOff.abort(); // OnOffTimeoutOff Ausschaltdauer bei on-off-Betrieb
1361
+
1362
+ adapter.log.debug(`order deleted Stop all ID: ${entry.name} ( rest orders: ${threadList.length} )`);
1363
+ threadList.pop(); // del last array
1364
+ }
1365
+ boostReady = true;
1366
+ boostOn = false;
1367
+ const _currentConsumption = await currentConsumption(true);
1368
+ if (_currentConsumption.parallel > 0) throw new Error(`clearEntireList: Not all valves in the list have been deleted`); // Nicht alle Ventile in der Liste wurden gelöscht
1369
+ return true;
1370
+ } catch (error) {
1371
+ if (error) {
1372
+ adapter.log.warn(`valveControl.clearEntireList() => ${error}`);
1373
+ }
1374
+ return false;
1375
+ }
1376
+
1377
+ }, // End clearEntireList
1378
+
1379
+ /**
1380
+ * Änderungen des Füllstands setzen + Vorrang der Pumpe setzen
1381
+ *
1382
+ * @param {number|string|boolean} levelCistern
1383
+ */
1384
+ setFillLevelCistern: function (levelCistern) {
1385
+ adapter.log.debug(`setFillLevelCistern: ${levelCistern}`);
1386
+ if(typeof levelCistern === 'number') {
1387
+ fillLevelCistern = levelCistern;
1388
+ }else if(typeof levelCistern === 'string') {
1389
+ fillLevelCistern = parseFloat(levelCistern);
1390
+ }else if(typeof levelCistern === 'boolean') {
1391
+ (levelCistern === true) ? fillLevelCistern = 100 : fillLevelCistern = 0;
1392
+ }
1393
+ setActualPump();
1394
+ }, // End setFillLevelCistern
1395
+
1396
+ /**
1397
+ * Abfrage von intBreak der Zisternenpumpe
1398
+ *
1399
+ * @returns {boolean} intBreak
1400
+ */
1401
+ getIntBreakCisternPump: function () {
1402
+ return (adapter.config.pumpSelection === 'cistern') ? currentPumpUse.intBreak : false;
1403
+ }
1404
+
1405
+ }; // End valveControl
1406
+
1407
+ /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
1408
+
1409
+ module.exports = {
1410
+ valveControl,
1411
+ controlVoltage,
1412
+ currentPumpUse,
1413
+ threadList
1414
+ };