Rasensprenger Auto-Zeiten

Begonnen von Syrex-o, 21 September 2018, 19:12:38

Vorheriges Thema - Nächstes Thema

Syrex-o

Hey,

Ich hab mir eine Konfiguration für meine Rasensprenger ausgedacht (6 an der Zahl).
Die reagieren auf Zeiteinstellungen und das Wetter. Dabei sammel ich die Wetterevents, die ich brauche, in einem Dummy.
Das grundsätzliche Problem ist, dass die Zeiten sich nicht überschneiden dürfen (Wassermangel). Daher rechne ich die Laufzeit aus und pausiere zwischen den einzelnen Zeiten eine Minute. Das funktioniert auch soweit so gut, bis meine Wetter Notifys ins Spiel kommen (12, da jeder Sprenger 2 Zeiten haben kann).
Jeder Zeiten Dummy reagiert auf seinen Vorgänger.

Hier mal meine aktuelle Funktion zum einstellen von verkürzten und verlängerten Laufzeiten:

sub autoTimes{
    my $device = $_[0];
    my $percentage = ($_[1] > 0) ? 1 + ($_[1] / 100) : 1 - (($_[1] * -1) / 100);
    my $start = timeToMin($_[2]);
    my $end = timeToMin($_[3]);
    my $duration = ($end < $start) ? ((($start - 1440) * -1) + $end) * $percentage : ($end - $start) * $percentage;
    if($duration <= 0){
        my $dur = $duration + 1440;
        $duration = $dur;
    }
    if($_[5]){
        # passing $_[5] parameter (endTime of previous item) + 1 --- starting interval doesn´t need that.
        my $newStart = $start;
        my $endLast = timeToMin($_[5]) + 1;
        if($_[6]){
            # checker for collide of start of second interval with end of first interval
            if($endLast <= $newStart){
                # if collide, the time is changed (important for interval 2)
                $newStart = minToTime($newStart);
            }else{
                $newStart = minToTime($endLast);
            }
        }else{
            # setting start of next time to end of last time + 1 min
            $newStart = minToTime($endLast);
        }
        # new End Time of changed start and changed duration
        my $newEnd = minToTime(timeToMin($newStart) + $duration);
        if($_[4] eq "inter1"){
            fhem("setreading $device smartStart1 $newStart");
            fhem("setreading $device smartEnd1 $newEnd");
        }
        if($_[4] eq "inter2"){
            fhem("setreading $device smartStart2 $newStart");
            fhem("setreading $device smartEnd2 $newEnd");
        }
    }else{
        my $newStart = $_[2];
        my $newEnd = minToTime(timeToMin($newStart) + $duration);
        if($_[4] eq "inter1"){
            fhem("setreading $device smartStart1 $newStart");
            fhem("setreading $device smartEnd1 $newEnd");
        }
        if($_[4] eq "inter2"){
            fhem("setreading $device smartStart2 $newStart");
            fhem("setreading $device smartEnd2 $newEnd");
        }
    }
}


Die Funktion hat 2 Helper Funktionen, die die Zeiten umrechnen (die Namen erklären sich bestimmt selber):
1.sub minToTime($){
    my ($min) = @_;
    my $h = int(($min / 60) >= 24) ? int($min / 60) -24 : int($min / 60);
    $h = ($h < 10) ? "0".$h : $h;
    my $m = (($min % 60) < 10) ? "0".($min % 60) : $min % 60;
    return $h.":".$m;
}

2. sub timeToMin($){
    my ($time) = @_;
    return (substr $time, 0,2) * 60 + (substr $time, 3,2);
}


Der Hauptfunktion übergebe ich immer unterschiedliche Werte.
Im Notify werte ich die Wetterwerte gegenüber von fixen werten und prozentualen Steigerungen oder Minderungen der Laufzeit aus.
Für Interval 1 der Rasensprenger sieht es dann so aus:
1. Sprenger:{autoTimes($device, $percentage, $start1, $end1, "inter1")};
Der Sprenger hat am wenigsten Parameter

2-6. Sprenger:{autoTimes($device, $percentage, $start1, $end1, "inter1", $prevEnd)};
Die Sprenger bekommen zusätzlich noch die Endzeit von ihrem Vorgänger, damit keine Kollision entsteht.

Interval 2:
Interval 2 ist glaube ich mein Knackpunkt.
Sprenger 1 (interval 2) reagiert auf die End-Zeit von Sprenger 6 und prüft zusätzlich ob Interval 2 nun mit Interval 1 kollidiert.
{autoTimes($device, $percentage, $start1, $end1, "inter2", $prevEnd, "check")};

Alle folgenden orientieren sich dann wieder an ihrem Vorgänger.

Leider erreiche ich mit dieser Verkettung eine Systemlast von 100% auf dem RPi 2.

Vielleicht hat ja jemand eine Idee, wie man das ganze etwas verbessern kann.
Und vielleicht hat ja auch jemand Spaß an der Zeiten-Rechenaufgabe, so wie ich.  ;)

Liebe Grüße

Syrex-o

Um mir meine Frage mal selbst zu beantworten:

Die Systemauslastung hat sich nach der Komplexität der Notifys gerichtet.
So hat bisher jedes der 12 Notifys das Wetter abgefragt und danach entschieden, ob die Laufzeit verkürzt oder verlängert werden soll.

Ich habe diese Abfragen in die Hauptfunktion umgezogen und die Systemlast von 100 auf 15% gesenkt (RPi2)

Für jemanden, der so etwas in der Art auch bauen will, hier mal die Funktionen und Notifys:
Hauptfunktion:
sub autoTimes{
    my $device = $_[0];
    my $percentage = 0;
    if(ReadingsVal("WetterReact", "maxTemp", "") > ReadingsVal("SmartSprenger", "highTemp", "")){
        $percentage = ReadingsVal("SmartSprenger", "highPercentage", "");
    }
    if(ReadingsVal("WetterReact", "minTemp", "") < ReadingsVal("SmartSprenger", "lowTemp", "")){
        $percentage = ReadingsVal("SmartSprenger", "lowPercentage", "") * -1;
    }
    $percentage = ($percentage > 0) ? 1+($percentage / 100) : 1-(($percentage * -1)/100);
    my $start = timeToMin($_[1]);
    my $end = timeToMin($_[2]);
    my $duration = ($end < $start) ? ((($start - 1440) * -1) + $end) * $percentage : ($end - $start) * $percentage;
    if($duration <= 0){
        my $dur = $duration + 1440;
        $duration = $dur;
    }
    if(ReadingsVal("SmartSprenger", "enabled", "") eq "true"){
        if($_[4]){
            # passing $_[4] parameter (endTime of previous item) + 1 --- starting interval doesn´t need that.
            my $newStart = $start;
            my $endLast = timeToMin($_[4]) + 1;
            if($_[5]){
                # checker for collide of start of second interval with end of first interval
                if($endLast <= $newStart){
                    # if collide, the time is changed (important for interval 2)
                    $newStart = minToTime($newStart);
                }else{
                    $newStart = minToTime($endLast);
                }
            }else{
                # setting start of next time to end of last time + 1 min
                $newStart = minToTime($endLast);
            }
            # new End Time of changed start and changed duration
            my $newEnd = minToTime(timeToMin($newStart) + $duration);
            if($_[3] eq "inter1"){
                fhem("setreading $device smartStart1 $newStart");
                fhem("setreading $device smartEnd1 $newEnd");
            }
            if($_[3] eq "inter2"){
                fhem("setreading $device smartStart2 $newStart");
                fhem("setreading $device smartEnd2 $newEnd");
            }
        }else{
            my $newStart = $_[1];
            my $newEnd = minToTime(timeToMin($newStart) + $duration);
            if($_[3] eq "inter1"){
                fhem("setreading $device smartStart1 $newStart");
                fhem("setreading $device smartEnd1 $newEnd");
            }
            if($_[3] eq "inter2"){
                fhem("setreading $device smartStart2 $newStart");
                fhem("setreading $device smartEnd2 $newEnd");
            }
        }
    }else{
        if($_[3] eq "inter1"){
            fhem("setreading $device smartStart1 $start");
            fhem("setreading $device smartEnd1 $end");
        }
        if($_[3] eq "inter2"){
            fhem("setreading $device smartStart2 $start");
            fhem("setreading $device smartEnd2 $end");
        }
    }
}

Kurzerläuterung:
$_[0] = Zeiten-Device, dass die Zeiten aufbewahrt, damit man sie weiter benutzen kann.
$_[1] = ursprüngliche Startzeit
$_[2] = ursprüngliche Endzeit
$_[3] = Welches interval soll getriggert werden ("inter1" || "inter2")
$_[4] = optional, berücksichtigt die neue Endzeit des Vorgängers, damit keine Kollisionen der Zeiten zustande kommen
$_[5] = optional, wichtig für die erste Startzeit von interval 2. Prüft die Kollision mit der neuen Endzeit von Interval 1

Die Hilfsfunktionen findet ihr in Post 1.

Die Notifys könnten dann folgendermaßen aussehen:
Erste Startzeit von interval 1:
Reagiert auf die Wetterauswertung, um nicht auf alle Änderungen zu reagieren
SmartSprenger ist meine Sammlung von Werten (Prozentwerten, Temperatur Schwellenwerten, Bedingungen, ob es zu stark regnet oder zu windig ist, um zu bewässern)
(WetterReact|SmartSprenger:enabled|SprengerTime1:endTime1):.* {
  my $device = "SmartTime1";
  my $start1 = ReadingsVal("SprengerTime1", "startTime1", "");
  my $end1 = ReadingsVal("SprengerTime1", "endTime1", "");
  {autoTimes($device, $start1, $end1, "inter1")};
}


Folgelaufzeiten von Interval 1:
Die Folgelaufzeiten orientieren sich an den Events ihres Vorgängers, um nacheinander berechnet zu werden.
Zusätzlich wird noch das Ende des Vorgängers als Parameter übergeben.
(SmartTime1:smartEnd1|SprengerTime2:endTime1):.* {
  my $device = "SmartTime2";
  my $start1 = ReadingsVal("SprengerTime2", "startTime1", "");
  my $end1 = ReadingsVal("SprengerTime2", "endTime1", "");
  my $prevEnd = ReadingsVal("SmartTime1", "smartEnd1", "");
  {autoTimes($device, $start1, $end1, "inter1", $prevEnd)};
}


Start von Interval 2:
Ein zusätzlicher Parameter um die Kollision mit interval 1 zu checken.
(SmartTime6:smartEnd1|SprengerTime1:endTime2):.* {
  my $device = "SmartTime1";
  my $start1 = ReadingsVal("SprengerTime1", "startTime2", "");
  my $end1 = ReadingsVal("SprengerTime1", "endTime2", "");
  my $prevEnd = ReadingsVal("SmartTime6", "smartEnd1", "");
  {autoTimes($device,  $start1, $end1, "inter2", $prevEnd, "check")};
}


Ich hoffe jemand kann damit etwas anfangen.
Falls jemand so etwas umsetzen möchte und noch Fragen sind, immer gern.

Grüße

networker

Gefällt mir.

Gibt es für SmartSprenger und WetterReact auch definitionen.

Wie werden die erzeugt?

Gruß Networker

Syrex-o

@networker

Klar gibt es die  ;D
SmartSprenger und WetterReact sind auch nur dummys, die von Notifys befüllt werden.
SmartSprenger sieht im Endeffekt so aus:

Readings
enabled
true
2018-09-25 09:25:58
highPercentage
30
2018-09-25 09:25:58
highTemp
25
2018-09-25 09:25:58
lowPercentage
30
2018-09-25 09:25:58
lowTemp
10
2018-09-25 09:25:58
mail
mailadresse\@gmail.com
2018-09-25 09:25:58
rain
true
2018-09-25 09:25:58
state
|true<25#10-30>30!true?true%true+mailadresse\@gmail.com
2018-09-25 09:25:58
wind
true
2018-09-25 09:25:58
winterProtect
true
2018-09-25 09:25:58


Dabei würde ich nicht so viel auf state achten, da der wert nur so übergeben wird und anschließend "aufgesplittet" wird.
Daher musst du dir nur überlegen, wie diese werte bei dir festgelegt werden.
Grundsätzlich aber über setreading

WetterReact ist ausschließlich dazu da, die für mich relevanten Wetterdaten von Proplanta zu sortieren und für die Verarbeitung einfacher zu machen.
So sehen die Readings aus:

maxTemp
14
2018-09-25 16:53:35
minTemp
4
2018-09-25 16:53:35
rain
0
2018-09-25 16:53:35
tooRainy
false
2018-09-25 16:53:35
tooWindy
false
2018-09-25 16:53:35
wind
16.2
2018-09-25 16:53:35


Und das notify ist folgendermaßen definiert:

Wetter:wind:.* {
  my $rain = ReadingsVal("Wetter","fc0_rain", "");
  my $wind = ( ReadingsVal("Wetter","fc0_wind00", "") + ReadingsVal("Wetter","fc0_wind03", "") + ReadingsVal("Wetter","fc0_wind06", "") + ReadingsVal("Wetter","fc0_wind06", "") + ReadingsVal("Wetter","fc0_wind09", "") + ReadingsVal("Wetter","fc0_wind12", "") + ReadingsVal("Wetter","fc0_wind15", "") + ReadingsVal("Wetter","fc0_wind18", "") + ReadingsVal("Wetter","fc0_wind21", "") ) / 8;
  my $maxTemp = ReadingsVal("Wetter", "fc0_tempMax", "");
  my $minTemp = ReadingsVal("Wetter", "fc0_tempMin", "");
  my $mail = ReadingsVal("SmartSprenger", "winterProtect", "");
  my $offWind = ReadingsVal("SmartSprenger", "wind", "");
  my $offRain = ReadingsVal("SmartSprenger", "rain", "");
  my $tooWindy = ($wind >= 50) ? "true" : "false";
  my $tooRainy = ($rain > 10) ? "true" : "false";
  { fhem("setreading WetterReact rain $rain") };
  { fhem("setreading WetterReact wind $wind") };
  { fhem("setreading WetterReact maxTemp $maxTemp") };
  { fhem("setreading WetterReact minTemp $minTemp") };
  { fhem("set WetterCB $maxTemp|$minTemp#$rain?$wind") };
  if($offWind eq "true"){
    { fhem("setreading WetterReact tooWindy $tooWindy") };
  }
  if($offWind eq "false"){
    { fhem("setreading WetterReact tooWindy false") };
  }
  if($offRain eq "true"){
    { fhem("setreading WetterReact tooRainy $tooRainy") };
  }
  if($offRain eq "false"){
    { fhem("setreading WetterReact tooRainy false") };
  }
  if($minTemp < 5 and ReadingsVal("SmartSprenger", "winterProtect", "") eq "true" and ReadingsVal("sendMail", "state", "") eq "false"){
    my $address = ReadingsVal("SmartSprenger", "mail", "");
    {WinterWarn($address, "Winter-Warnung", "Message")};
    {fhem("set sendMail true")};
  }
}


Wetter ist bei mir mein Proplanta device.

So werden die Werte erzeugt und in die Dummys gepackt.

Anschließend übernehmen 6 DOIFs die Schaltung beider Intervalle
Beispielhaft mal eins:
([[$SELF:anfang1]-[$SELF:ende1]|[$SELF:woche1]] and [SprengerWinter:state] ne "on" and [Sprenger_allNow:state] ne "true" and [manual1:state] ne "on" and [manual2:state] ne "on" and [manual3:state] ne "on" and [manual4:state] ne "on" and [manual5:state] ne "on" and [manual6:state] ne "on" and [WetterReact:tooRainy] ne "true" and [WetterReact:tooWindy] ne "true") (set Kreis1 on)
DOELSEIF ([[$SELF:anfang2]-[$SELF:ende2]|[$SELF:woche2]] and [SprengerWinter:state] ne "on" and [enableInterval2:state] ne "off" and [Sprenger_allNow:state] ne "true" and [manual1:state] ne "on" and [manual2:state] ne "on" and [manual3:state] ne "on" and [manual4:state] ne "on" and [manual5:state] ne "on" and [manual6:state] ne "on" and [WetterReact:tooRainy] ne "true" and [WetterReact:tooWindy] ne "true") (set Kreis1 on)
DOELSEIF ([[$SELF:ende1]|[$SELF:wocheBG]] and [SprengerWinter:state] ne "on" and [Sprenger_allNow:state] ne "true" and [manual1:state] ne "on" and [manual2:state] ne "on" and [manual3:state] ne "on" and [manual4:state] ne "on" and [manual5:state] ne "on" and [manual6:state] ne "on" and [WetterReact:tooRainy] ne "true" and [WetterReact:tooWindy] ne "true") (set Kreis1 off)
DOELSEIF ([[$SELF:ende2]|[$SELF:wocheBG]] and [SprengerWinter:state] ne "on" and [Sprenger_allNow:state] ne "true" and [manual1:state] ne "on" and [manual2:state] ne "on" and [manual3:state] ne "on" and [manual4:state] ne "on" and [manual5:state] ne "on" and [manual6:state] ne "on" and [WetterReact:tooRainy] ne "true" and [WetterReact:tooWindy] ne "true") (set Kreis1 off)
DOELSE (set Kreis1 off)


Die Abfrage der Manual states dient nur dazu, dass kein Sprenger angeht, wenn einer bereits durch ein manuelles Programm gestartet wurde.
Und "wocheBG" ist einfach: 1234560, damit an allen Tagen abgeschaltet wird. So gibt es kein nach Mitternacht Schalt-Problem.

Bei weiteren Fragen, immer gern  ;)

Grüße