Autor Thema: RemoveInternalTimer nur auf eine bestimmte Funktion  (Gelesen 4105 mal)

Offline Dietmar63

  • Hero Member
  • *****
  • Beiträge: 2329
RemoveInternalTimer nur auf eine bestimmte Funktion
« am: 23 Oktober 2013, 21:43:38 »
@Rudi
Ich beantrage die folgende Funktion ins fhem.pl aufzunehmen:

#####################################
sub
RemoveInternalTimerFn($$)
{
  my ($arg,$fn) = @_;
  foreach my $a (keys %intAt) {
    delete($intAt{$a}) if($intAt{$a}{ARG} eq $arg && $intAt{$a}{FN} eq $fn );
  }
}

Ich habe hin und wieder das Bedürfnis innerhalb eines Moduls mehrere InternalTimer mit unterschiedlichem Rhythmus zu definieren. Mit dieser Funktion kann ich die Timer selektiv löschen und wieder setzen, ohne die sonst so laufenden Timer zu beeinflussen. Die existierende Funktion löscht immer alles. Das macht dann eine aufwendige Restauration notwendig.

Beispiel:

InternalTimer(gettimeofday()2*3600,       "UpdateReadingsAlleZweiStunden", $hash, 0);
InternalTimer(midnight+5,                 "MitternachtsService",           $hash, 0);

Wenn jetzt UpdateReadingsAlleZehnMinuten neu gesetzt werden soll, weil UpdateReadingsAlleZehnMinuten aus irgendeinem Grund neu gesetzt werden muss, muss ich zunächst mit RemoveInternalTimer den aktuell schon laufenden Timer löschen und verliere auch den MitternachtsService.

Das Problem ließe sich mit der neuen Fuktion einfach lösen.

Die Funktion ist getestet und muss nur eingecheckt werden.
Gruß Dietmar
FB7390, CUL, 2 FHT, FS20
modules: 98_WOL.pm, 98_Heating_Control.pm,   98_WeekdayTimer.pm, 98_RandomTimer.pm, 59_Twilight.pm

Offline rudolfkoenig

  • Administrator
  • Hero Member
  • *****
  • Beiträge: 24243
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #1 am: 23 Oktober 2013, 23:19:00 »
Ich wuerde statt den einen $hash zwei unterschiedliche Argumente uebergeben (neue Strukturen, die $hash enthalten), dann kann man auch diese mit den vorhandenen Argumenten selektiv loeschen. Und womoeglich braucht man dann auch nicht zwei Funktionen zum Aufruf.

Offline Dietmar63

  • Hero Member
  • *****
  • Beiträge: 2329
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #2 am: 24 Oktober 2013, 07:04:10 »
Ich denke nochmals darüber nach!

Gesendet von meinem HTC Desire S mit Tapatalk

Gruß Dietmar
FB7390, CUL, 2 FHT, FS20
modules: 98_WOL.pm, 98_Heating_Control.pm,   98_WeekdayTimer.pm, 98_RandomTimer.pm, 59_Twilight.pm

Offline Dietmar63

  • Hero Member
  • *****
  • Beiträge: 2329
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #3 am: 27 Oktober 2013, 21:28:46 »
helfe mir jetzt mit diesen beiden Funktionen:

################################################################################
sub myInternalTimer($$$$$) {
   my ($modifier, $tim, $callback, $hash, $waitIfInitNotDone) = @_;

   my $mHash;
   if ($modifier eq "") {
      $mHash = $hash;
   } else {
      my $timerName = "$hash->{NAME}_$modifier";
      if (exists  ($hash->{TIMER}{$timerName})) {
          $mHash = $hash->{TIMER}{$timerName};
      } else {
          $mHash = { HASH=>$hash, NAME=>"$hash->{NAME}_$modifier", MODIFIER=>$modifier};
          $hash->{TIMER}{$timerName} = $mHash;
      }
   }
   InternalTimer($tim, $callback, $mHash, $waitIfInitNotDone);
}
################################################################################
sub myRemoveInternalTimer($$) {
   my ($modifier, $hash) = @_;

   my $timerName = "$hash->{NAME}_$modifier";
   if ($modifier eq "") {
      RemoveInternalTimer($hash);
   } else {
      my $myHash = $hash->{TIMER}{$timerName};
      if (defined($myHash)) {
         delete $hash->{TIMER}{$timerName};
         RemoveInternalTimer($myHash);
      }
   }
}
« Letzte Änderung: 03 November 2013, 19:12:33 von Dietmar63 »
Gruß Dietmar
FB7390, CUL, 2 FHT, FS20
modules: 98_WOL.pm, 98_Heating_Control.pm,   98_WeekdayTimer.pm, 98_RandomTimer.pm, 59_Twilight.pm

Offline 3des

  • Developer
  • New Member
  • ****
  • Beiträge: 13
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #4 am: 01 November 2014, 15:09:21 »
Hallo Zusammen,

aus gegebenem Anlaß hab ich mal diesen doch schon etwas älteren Beitrag wieder hoch geholt.

Habe dasselbe Problem mit RemoveInternalTimer und außerdem ist mir noch (störend) aufgefallen, daß man zyklische Timer immer selbst neu aufsetzen darf...

Ich hab beides so gelöst, daß bestehender Code keine Probleme haben dürfte und außerdem, wie von Rudi gewünscht und was auch mir sehr sinnvoll erscheint, keine zusätzliche Funktion notwendig ist.

Changes (InternalTimer):
- ein optional zu übergebenden Timer-Namen wird nun im Timer abgelegt
- ein optional zu übergebender Wert für einen zyklischen Timer wird nun im Timer abgelegt
- falls das globale attribut "stacktrace" gesetzt ist, wird der Erzeuger des Timers im Timer mit abgelegt (erleichtert das Debuggen mitunter)
#####################################
# $arg can be:
#   - hash ref:                  behavior as usual
#   - array ref with 2 elements: first element it the usual hash ref
#                                second element is a timer name that can be used to remove only a certain timer not all belonging to the same device (can be set to "undef" if not needed!)
#   - array ref with 3 elements: first and second element see "array ref with 2 elements"
#                                third element gives the delta for cyclic timers
sub
InternalTimer($$$$)
{
  my ($tim, $fn, $arg, $waitIfInitNotDone) = @_;

  my $timerName;
  my $cycle = 0;
  if (ref($arg) eq "ARRAY") {
    if (scalar(@{$arg}) == 3) {
      $cycle = $arg->[2];
    }
    $timerName = $arg->[1];
    $arg = $arg->[0];
  }

  if(!$init_done && $waitIfInitNotDone) {
    select(undef, undef, undef, $tim-gettimeofday());
    no strict "refs";
    &{$fn}($arg);
    use strict "refs";
    return;
  }
  $intAt{$intAtCnt}{TRIGGERTIME} = $tim;
  $intAt{$intAtCnt}{FN} = $fn;
  $intAt{$intAtCnt}{ARG} = $arg;
  $intAt{$intAtCnt}{TIMERNAME} = $timerName  if ($timerName);
  $intAt{$intAtCnt}{CYCLE}     = $cycle      if ($cycle);

  if ($attr{global}{stacktrace}) {
    my @caller = (caller(1));
    $intAt{$intAtCnt}{SETUP} = "$caller[1]:$caller[2] ($caller[3])";
  }

  $intAtCnt++;
  $nextat = $tim if(!$nextat || $nextat > $tim);
}


Changes (RemoveInternalTimer):
- ein optional zu übergebenden Timer-Name wird verwendet um nur bestimmte Timer zu löschen (per regexp, dadurch auch für "Timer-Gruppen" möglich)
- die Anzahl der gelöschten Timer wird von der Funktion zurück geliefert (kann das irgend wo Probleme bereiten???)
#####################################
# $arg can be:
#   - hash ref:                  behavior as usual
#   - array ref with 2 elements: first element it the usual hash ref
#                                second element is a timer name and only timer(s) of given cache matching the name will be removed
# return value gives amout of deleted timers
sub
RemoveInternalTimer($)
{
  my ($arg) = @_;

  my $timerName;
  if (ref($arg) eq "ARRAY") {
    $timerName = $arg->[1];
    $arg = $arg->[0];
  }

  my $removed = 0;
  foreach my $a (keys %intAt) {
    if(($intAt{$a}{ARG} eq $arg) && (!defined($timerName) || $intAt{$a}{TIMERNAME} =~ /$timerName/)) {
      delete($intAt{$a});
      $removed++;
    }
  }

  return $removed;
}


Changes (HandleTimeout):
- falls ein Timer einen Eintrag "CYCLE" enthält wird der in "CYCLE" abgelegten Wert aufaddiert und der Timer verbleibt im Timer stack
#####################################
# Return the time to the next event (or undef if there is none)
# and call each function which was scheduled for this time
sub
HandleTimeout()
{
  return undef if(!$nextat);

  my $now = gettimeofday();
  return ($nextat-$now) if($now < $nextat);

  $now += 0.01;# need to cover min delay at least
  $nextat = 0;
  #############
  # Check the internal list.
  foreach my $i (sort { $intAt{$a}{TRIGGERTIME} <=>
                        $intAt{$b}{TRIGGERTIME} } keys %intAt) {
    my $tim = $intAt{$i}{TRIGGERTIME};
    my $fn = $intAt{$i}{FN};

    if(!defined($tim) || !defined($fn)) {   # remove invalid timers from timer hash (is this really possible???)
      delete($intAt{$i});
      next;
    } elsif($tim <= $now) {     # handle timer if expired
      no strict "refs";
      &{$fn}($intAt{$i}{ARG});
      use strict "refs";

      # if timer is a cyclic timer re-setup it otherwise remove it from timer hash
      if ($intAt{$i}{CYCLE}) {
        $tim = $intAt{$i}{TRIGGERTIME} + $intAt{$i}{CYCLE};
        $intAt{$i}{TRIGGERTIME} = $tim;
        $nextat = $tim if(!$nextat || $nextat > $tim);  # re-setup timer could be next one!
      }
      else {
        delete($intAt{$i});
      }
    } else {
      $nextat = $tim if(!$nextat || $nextat > $tim);
    }
  }

  return undef if(!$nextat);
  $now = gettimeofday(); # possibly some tasks did timeout in the meantime
                         # we will cover them
  return ($now+ 0.01 < $nextat) ? ($nextat-$now) : 0.01;
}

Über produktive Kommentare würde ich mich freuen.

@Rudi: Wie stehen die Chancen, daß der Code in die offizielle Version der fhem.pl einfließt?

Vielen Dank schon mal vorab!

Offline 3des

  • Developer
  • New Member
  • ****
  • Beiträge: 13
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #5 am: 01 November 2014, 15:37:51 »
trotz entsprchender Tests ist mir jetzt im Nachhinein (war eh klar!) noch eine Kleinigkeit aufgefallen und zwar wenn man tatsächlich für ein Device "benamte" und "unbenamte" Timer anlegt und dann "benamte" löscht, kommt es zu einem "Use of uninitialized value"... so sollte es nun aber passen:

#####################################
# $arg can be:
#   - hash ref:                  behavior as usual
#   - array ref with 2 elements: first element it the usual hash ref
#                                second element is a timer name and only timer(s) of given cache matching the name will be removed
# return value gives amout of deleted timers
sub
RemoveInternalTimer($)
{
  my ($arg) = @_;

  my $timerName;
  if (ref($arg) eq "ARRAY") {
    $timerName = $arg->[1];
    $arg = $arg->[0];
  }

  my $removed = 0;
  foreach my $a (keys %intAt) {
    if(($intAt{$a}{ARG} eq $arg) && (!defined($timerName) || (defined($intAt{$a}{TIMERNAME}) && $intAt{$a}{TIMERNAME} =~ /$timerName/))) {
      delete($intAt{$a});
      $removed++;
    }
  }

  return $removed;
}

Offline rudolfkoenig

  • Administrator
  • Hero Member
  • *****
  • Beiträge: 24243
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #6 am: 01 November 2014, 23:28:49 »
Zitat
@Rudi: Wie stehen die Chancen, daß der Code in die offizielle Version der fhem.pl einfließt?

Schlecht.
Fuer den Programmierer ist die Verwendung von mehreren hashes nicht komplizierter, und ein wiederholter Timer-Aufruf kostet durch richtige Programmierung keine Zeile mehr. Oder ich habe das Problem nicht verstanden.

Das naechste mal bitte patches per diff -u schicken, und fuer meine Module auf 80-Zeichen breite achten.

Gruss,
  Rudi

Offline 3des

  • Developer
  • New Member
  • ****
  • Beiträge: 13
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #7 am: 02 November 2014, 13:16:24 »
Hi Rudi,

Fuer den Programmierer ist die Verwendung von mehreren hashes nicht komplizierter
Mit Angabe eines Timer-Names hat man halt die Möglichkeit einen ganz bestimmten Timer oder (wg. der regexp) Timer-Gruppen zu löschen aber man verliert nicht die Möglichkeit alle Timer eines devices abzuräumen. Mittels mehrerer Hashes kann man ja dann nur noch den einen Timer abräumen, oder verstehe ich Dich da falsch?

und ein wiederholter Timer-Aufruf kostet durch richtige Programmierung keine Zeile mehr.
Durch "richtige" Programmierung benötigt man keine extra Zeile, das stimmt schon, aber ein zyklischer Timer, der nicht erst lange aus der Timerliste entfernt und wieder neu eingefügt wird, sondern der sich nur durch Addieren der angegebenen Zykluszeit selbständig wieder aktiviert und um den man sich nicht weiter kümmern muß, wäre mMn schon von Vorteil.

Üblicherweise werden Timer ja immer mittels "gettimeofday()+$irgendwas" aufgesetzt. Da es durch mehrere Timer, die zur selben Zeit ablaufen, aber zu Verzögerungen kommen kann, müßte man sich die Anfangszeit auch noch merken (obwohl die ja im Timer sowieso schon steht) um wirklich periodisch alle n Sekunden/Minuten/... aufgerufen zu werden. Andernfalls bekommt man zum ohnehin unvermeidbaren Jitter auch noch einen sich immer weiter aufsummierenden "Fehler" in der Aufrufzeit.

Das naechste mal bitte patches per diff -u schicken, und fuer meine Module auf 80-Zeichen breite achten.
Sorry, werde in Zukunft darauf achten...

So nebenbei hätte mich noch interessiert ob folgender Code aus "HandleTimeout" nicht komplett überflüssig ist? Wodurch könnten solche "korrupten" Timer überhaupt entstehen?
    if(!defined($tim) || !defined($fn)) {
      delete($intAt{$i});
      next;
    }

Offline rudolfkoenig

  • Administrator
  • Hero Member
  • *****
  • Beiträge: 24243
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #8 am: 03 November 2014, 09:16:09 »
Zitat
hätte mich noch interessiert ob folgender Code aus "HandleTimeout" nicht komplett überflüssig ist?

Weiss leider nicht mehr, und es aergert mich, dass ich den Grund nicht in einem Kommentar hinterlassen habe.
Kommentarre sind mAn nur fuer solche nicht offensichtlichen Faelle relevant.

Offline 3des

  • Developer
  • New Member
  • ****
  • Beiträge: 13
Antw:RemoveInternalTimer nur auf eine bestimmte Funktion
« Antwort #9 am: 03 November 2014, 11:33:45 »
Kommentarre sind mAn nur fuer solche nicht offensichtlichen Faelle relevant.
Aus eigener Erfahrung weiß ich, wie dumm ich mitunter nach Jahren vor meinem eigenen Code hocke, da etwas was beim stricken mal total offensichtlich war, es heute nicht mehr ist. Deshalb kommentiere ich sehr viel und trotzdem passierts mir immer noch, daß mir die Intention erst nach längerem Überlegen wieder klar wird, warum der Code so ist wie er eben ist. Aber da hat wohl jeder so seine eigenen Ansichten ;)

Schöne Grüße,
Manfred