AT kleine Helferlein

Aus FHEMWiki

Ein AT kann statt einer Uhrzeit bzw. eines Datums mit einer Uhrzeit auch eine Perl-Funktion nutzen. Beispiel solcher Funktionen sind at_ultimo oder das Modul SUNRISE_EL. Hier sollen weitere Funktionen vorgestellt werden.

Der Code kann in der 99 myUtils untergebracht werden.

AT am x-ten Wochentag im Monat ausführen

Aufgabe ist es, das at z.B. an jedem 2. Montag im Monat auszuführen. Zusätzlich gibt es auch die Möglichkeit, das at immer am z.B. letzten Montag im Monat auszuführen.

########################################
#
# Berechnung des Datums des 
# x.ten Wochentags im Monat
#
# x=1-4 x.ter Wochentag im Monat
# x=9   letzter Wochentag im Monat
# w=1-7 Wochentag, 1=Montag
# h,m,s Uhrzeit Stunde, Minute, Sekunde
#
sub at_xwday($$$$$) {
    my ($x,$w,$h,$m,$s) = @_;

    my $ziel_tag = 0;

    # akt. Datum und Uhrzeit in Variablen aufteilen
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = 
        localtime(time);

    # bei Neuberechnung einen Monat addieren
    my $add = $data{AT_RECOMPUTE} ? 1 : 0;
    $mon = $mon + $add;

    my ($nm, $ny) = ($mon == 12) ? (0,$year+1) : ($mon,$year);

    # Letztes Vorkommen des Wochentags berechnen?
    if ($x == 9) {
        # Berechnen des letzten Tags des Monats
        # ersten Tag des Folgemonats als timestamp
        my ($xm, $xy) = ($mon == 11) ? (0,$year+1) : ($mon+1,$year);
        my $nt = mktime(0, 0, 0, 1, $xm, $xy);

        # letzter Tag des aktuellen Monats = erster Tag des Folgemonats minus ein Tag
        my ($letzter_tag_des_monats, $letzter_wochentag) = (localtime($nt - DAYSECONDS))[3,6];

        # Prüfen, ob der letzte Wochentag im Monat mit dem gewünschten Wochentag übereinstimmt
        $ziel_tag = $letzter_tag_des_monats;
        if ($letzter_wochentag > $w) {
           # Falls der letzte Wochentag später im Monat ist, subtrahiere die Differenz
           my $differenz = $letzter_wochentag - $w;
           $ziel_tag -= $differenz;
        } elsif ($letzter_wochentag < $w) {
           # Falls der letzte Wochentag früher im Monat ist, subtrahiere 7 plus die Differenz
           my $differenz = $w - $letzter_wochentag;
           $ziel_tag -= (7 - $differenz);
        }
    } else {
        # Berechne den Tag des ersten Vorkommens des Wochentags im Monat
        my $erster_tag = 1;
        my $erster_tag_des_monats = (localtime(mktime(0,0,0,$erster_tag,$nm,$ny)))[6];
        my $erstes_vorkommen = ($w - $erster_tag_des_monats + 7) % 7 + 1;

        # Berechne den Tag des x.ten Vorkommens des Wochentags im Monat
        $ziel_tag = $erstes_vorkommen + 7 * ($x - 1);

        # Überprüfe, ob das Datum im gültigen Bereich liegt
        if ($ziel_tag > 31 || $ziel_tag < 1) {
          return "Ungültiges Datum";
        }
    }
    return mktime($s,$m,$h,$ziel_tag,$nm,$ny);
}

Als Parameter benötigt die Funktion, welches Vorkommen des Wochentags berechnet werden soll (1-4). Soll das letzte Vorkommen berechnet werden, übergibt man 9. Als Wochentag wird 1-7 angegeben, wobei Montag der Wochentag 1 ist. Danach kommen als getrennte Parameter Stunde, Minute und Sekunde. Die Definition sieht dann so aus

defmod at_heat1_Treffen_FG_Radverkehr at *{at_xwday(1,2,17,45,0)} {\
fhem("set TRV_Laden temp 21.0");;\
fhem("defmod at_heat_off_FG_Radverkehr at +02:00:00 set TRV_Laden temp 15.0");;\
}

Die Heizung wird für das Treffen der Fachgruppe Radverkehr am ersten Dienstag im Monat auf 21 Grad gestellt. Gleichzeitig wird ein AT gestartet, das die Heizung zwei Stunden später wieder auf 15 Grad reduziert. Kleiner Tipp: Man kann im AT auch zwei FHEM-Befehle hintereinander senden. Klappte in diesem Fall aber nur über den kleinen Umweg über Perl.

AT alle x Tage ausführen

Soll ein Termin z.B. alle sechs Wochen stattfinden, so kann folgende Funktion genutzt werden.

########################################
#
# Berechnung des Datums des 
# nächsten Treffens in x Tagen

sub at_xdays($$) {
	my ($tm,$days) = @_;
	my $delta = $days * DAYSECONDS;		# Nächster Termin in sec

	return "Wrong timespec, use \"yyyy-mm-ddThh:mm:ss\"" if($tm !~ m/^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$/);
	my ($y,$m,$d,$h,$m2,$s) = ($1,$2,$3,$4,$5,$6);
	my $abstime = mktime($s,$m2,$h,$d,$m-1,$y-1900, 0,0,-1);
	my $now = int(time);

	my $diff = $now-$abstime;
	if ($diff>0){
    	# bei Neuberechnung sechs Wochen zur aktuellen Zeit addieren
		# wird auch bei Erstberechnung aufgerufen, wenn Timestamp in der Vergangenheit
		my $temp = ((int($diff/$delta)+1)*$delta)+$abstime;
		my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($temp);
		return mktime($s,$m2,$h,$mday,$mon,$year);
	} else {return $abstime;}
}

Dabei wird der erste Ausführungstermin im FHEM-üblichen Format ("yyyy-mm-ddThh:mm:ss") und das Intervall in Tagen übergeben. Die Variable $delta enthält dann das Intervall in Sekunden. Es wird zwar mit dem Timestamp gerechnet. Zurückgegeben wird aber das berechnete Datum und die der Funktion übergebene Uhrzeit. Das verhindert Probleme mit der Zeitumstellung. Hier die Definition für einen Termin alle sechs Wochen, beginnend am 29.05.2024 um 18:45 Uhr. Auch hier wird die Heizung zwei Stunden später wieder herunter gestellt.

defmod at_heat2_Lasse at *{at_xdays("2024-05-29T18:45:00",42)} {\
fhem("set TRV_Laden temp 21.0");;\
fhem("defmod at_heat_off_Lasse at +02:00:00 set TRV_Laden temp 15.0");;\
}