universeller countdown timer (mit on-for-timer unterstützung)

Begonnen von justme1968, 08 Februar 2018, 13:46:16

Vorheriges Thema - Nächstes Thema

justme1968

da homekit für die bewässerungs steuerung inzwischen auch die anzeige der restlaufzeit unterstützt habe ich mir einen mehr oder weniger universellen countdown timer gebaut.

den timer kann man auch für eine restlaufzeit anzeige bei on-for-timer verwenden. zumindest für homematic und bei allen geräten die die SetExtensions verenden.

vielleicht hat ja noch jemand verwendung dafür.


von hand kann das ganze auf perl ebene so verendet werden: startCountdown($name,$dauer);

statt des namens kann auch direkt der device hash übergeben werden. der timer zählt rückwärts bis 0 und aktualisiert alle 30 sekunden das reading timerRemaining im device. nach dem der timer bei 0 angekommen ist wird das remaining reading gelöscht.

um den timer vorzeitig anzuhalten kann man stopCountdown($name); verwenden. auch hier kann statt des namens direkt der device hash übergeben werden.


um ein device das von sich aus das on-for-timer kommando anbietet um ein remaining reading zu ergänzen kann man den timer auch aus einem passenden notify heraus starten. das kann im einfachsten fall so aussehen: define startTimer notify <device>:.* {startCountdown($NAME)}

idealerweise grenzt man die regex für das event so weit ein das nur die relevanten events matchen. dies muss bei homematic mindestens das set_on-for-timer und optional das off event sein. bei allen anderen devices die die SetExtensions verwenden muss es on und optional off sein.

achtung: der timer wird zwar bei manuellem off automatisch abgebrochen (wenn das off event im notify berücksichtigt wird. die meisten anderen schaltzusände (wir z.b. ein normales on wärend das on-for-timer noch läuft) werden (noch) nicht korrekt berücksichtig. das kann man mit einem passenden stopCountdown aufruf von hand beheben.

der code für 99_myUtils.pm:sub                               
startCountdown($;$$) {           
  my($name, $duration, $interval) = @_;
                                 
  my $hash = $name;               
  $hash = $defs{$name} if( ref($hash) ne 'HASH' );
  $name = $hash->{NAME};         
                                 
  if( !$hash ) {                 
    Log3 $name, 2, "startCountdown error: no such device: $name";
    return;                       
  }                               
                                 
  Log3 $name, 4, "startCountdown for: $name";
                                 
   my $remaining;                 
  if( !defined($duration) ) {     
    my $state = ReadingsVal($name, 'state', undef);
    if( $state eq 'off' ) {       
      stopCountdown($name);       
      return;                     
    } elsif( defined(ReadingsVal($name, 'timerDuration', undef)) ) {
      return;                     
    };                           
                                 
    $duration = "<unknown>";     
    if( my $TIMED_OnOff = $hash->{TIMED_OnOff} ) {
      $duration = $TIMED_OnOff->{DURATION};
      $remaining = $TIMED_OnOff->{START} + $TIMED_OnOff->{DURATION} - time();
    } elsif( $state =~ m/set_on-for-timer (\d*)/ ) {
      $duration = $1;             
      $remaining = $1;           
    } elsif( $state =~ m/set_off-for-timer (\d*)/ ) {
      $duration = $1;             
      $remaining = $1;           
    }                             
                                 
  }                               
                                 
  if( $duration ne "<unknown>" ) {
    if( $duration <= 0 ) {       
      stopCountdown($hash);       
      return;                     
    }                             
                                 
    readingsSingleUpdate($hash, 'timerDuration', $duration, 1);
    updateCountdown($hash, $remaining);
                                 
  } else {                       
    readingsSingleUpdate($hash, 'timerDuration', $duration, 1);
                                 
  }                               
                                 
  return;                         
}                                 
                                 
sub                               
updateCountdown($;$) {           
  my($name, $remaining) = @_;     
                                 
  my $hash = $name;               
  $hash = $defs{$name} if( ref($hash) ne 'HASH' );
  $name = $hash->{NAME};         
                                 
  if( !$hash ) {                 
    Log3 $name, 2, "updateCountdown error: no such device: $name";
    return;                       
  }                               
                                 
  if( !defined($remaining) ) {   
    $remaining = "<unknown>";     
                                 
    if( my $TIMED_OnOff = $hash->{TIMED_OnOff} ) {
      $remaining = $TIMED_OnOff->{START} + $TIMED_OnOff->{DURATION} - time();
    } else {                     
      $remaining = ReadingsVal($name, 'timerDuration', undef);
      if( $remaining ne '<unknown>' ) {
        my $age = ReadingsAge($name, 'timerDuration', undef);
        $remaining = $remaining - $age;
      }                           
    }                             
  }                               
                                 
  Log3 $name, 4, "updateCountdown: remaining $remaining";
  if( $remaining ne "<unknown>" ) {
    if( $remaining <= 0 ) {       
      stopCountdown($name);       
      return;                     
    }                             
                                 
    readingsSingleUpdate($hash, 'timerRemaining', int($remaining), 1);
    InternalTimer( gettimeofday() + 30, 'updateCountdown', $hash);
  }                               
}                                 
                                 
sub                               
stopCountdown($) {               
  my($name) = @_;                 
                                 
  my $hash = $name;               
  $hash = $defs{$name} if( ref($hash) ne 'HASH' );
  $name = $hash->{NAME};         
                                 
  if( !$hash ) {                 
    Log3 $name, 2, "stopCountdown error: no such device: $name";
    return;                       
  }                               
                                 
  Log3 $name, 4, "stopCountdown for: $name";
                                 
  readingsSingleUpdate($hash, 'timerRemaining', 0, 1);
                                 
  RemoveInternalTimer( $hash, 'updateCountdown' );
  CommandDeleteReading( undef, "$name timerDuration" );
  CommandDeleteReading( undef, "$name timerRemaining" );
                                 
  return;                         
}


testen kann man das z.b. mit einem dummy:define timer dummy
attr timer setList on off set_on-for-timer
attr timer useSetExtensions 1
define timerNotify notify timer:.* {startCountdown($NAME)}

inform timer timer
set timer on-for-timer 60


um schneller etwas zu sehen empfiehlt es sich bei den tests das update intervall von 30 auf 1 oder 2 zu verkürzen.

edit 2018-02-09:code etwas überarbeitet. reading in timerRemaining umbenannt, zusätzliches reading timerDuration eingebaut.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

justme1968

edit 2018-02-09:code etwas überarbeitet. reading in timerRemaining umbenannt, zusätzliches reading timerDuration eingebaut.
hue, tradfri, alexa-fhem, homebridge-fhem, LightScene, readingsGroup, ...

https://github.com/sponsors/justme-1968

FunkOdyssey

Gerade mal wieder über diesen Thread gestolpert und ich bin erneut begeistert.
Danke für die Funktion und die Anleitung.
Eigentlich schade, dass so etwas nicht bereits in den SetExtensions enthalten ist.

kumue

#3
Über Ostern diesen Countdown gefunden und wollte ihn für Homematic-Devices verwenden.
Aber irgendwie funktioniert das Update der Readings nicht.
Zu Beginn werden die Readings timerDuration und timerRemaining im Device angelegt, aber dann wird timerRemaining nicht runtergezählt.
timerDuration und timerRemaining bleiben halt auf dem Ausgangswert stehen... bei timerDuration ist das ja klar...

Zum Schluss wird nur ein timerRemaining: 0 ausgegeben.

Habe es mit einer Gosund_SP1 (TYPE MQTT_DEVICE) probiert.. da funktioniert es tadellos...

Muss bei HM irgendwo noch ein Attr gesetzt werde o.ä.?


UPDATE
Habe es gerade mit einem HM-Dimmer probiert., da war alles ok.

Mit dem Model geht es nicht:     
HM-LC-SW1PBU-FM   ==> muss hier ein spez. Register gesetzt werden ?

megadodopublications

Hallo justme1968,

ich versuche, das Updateinterval als Parameter zu übergeben,
es wird aber immer statisch 30 genommen.

Hier wird das Updateintervall noch als Parameter übergeben


sub                               
startCountdown($;$$) {           
  my($name, $duration, $interval) = @_;my($name, $duration, $interval) = @_;
 


In der updateCountdown wird dann aber
InternalTimer( gettimeofday() + 30, 'updateCountdown', $hash);
ausgeführt.

Sehe ich das falsch/mache ich etwas falsch?

Ich finde die Funktion klasse und würde sie gerne an diversen Stellen (Waschmaschine etc) einsetzen, da sind aber eher längere Updateintervalle im Minutenbereich sinnvoll (Systemlast/die Events gering halten).

Ich habe mit meinen begrenzten Kenntnissen versucht, das "reinzupfuschen" laufe aber in eine unerwünschte Endlosschleife.

Könntest du helfen / hast du eine "richtige" Lösung?

Danke und Gruß
Ralph

Beta-User

Versuch's mal mit diesen Fassungen:

sub startCountdown($;$$) {
  my $name = shift // return
  my $duration = shift;
  my $interval = shift // 30;
                                 
  my $hash = $name;               
  $hash = $defs{$name} if ref $hash ne 'HASH';
  $name = $hash->{NAME};         

  if( !$hash ) {                 
    Log3( $name, 2, "startCountdown error: no such device: $name" );
    return;                       
  }                               

  Log3( $name, 4, "startCountdown for: $name" );

  my $remaining;                 
  if( !defined $duration ) {     
    my $state = ReadingsVal($name, 'state', undef);
    if( $state eq 'off' ) {       
      stopCountdown($name);       
      return;                     
    }
    if( defined ReadingsVal($name, 'timerDuration', undef) ) {
      return;                     
    };                           

    $duration = '<unknown>';     
    if( my $TIMED_OnOff = $hash->{TIMED_OnOff} ) {
      $duration = $TIMED_OnOff->{DURATION};
      $remaining = $TIMED_OnOff->{START} + $TIMED_OnOff->{DURATION} - time();
    } elsif( $state =~ m/set_o[nf]+-for-timer (\d+)/ ) {
      $duration = $1;
      $remaining = $1;
    }                             
  }                               

  if( $duration ne '<unknown>' ) {
    if( $duration <= 0 ) {
      stopCountdown($hash);
      return;
    }

    readingsSingleUpdate($hash, 'timerDuration', $duration, 1);
    updateCountdown($hash, $remaining, $interval);
  } else {
    readingsSingleUpdate($hash, 'timerDuration', $duration, 1);
  }                               
  return;                         
}                                 
                                 
sub updateCountdown($;$$) {           
  my $name = shift //return;
  my $remaining = shift;
  my $interval = shift // 30;

  my $hash = $name;               
  $hash = $defs{$name} if( ref($hash) ne 'HASH' );
  $name = $hash->{NAME};         

  if( !$hash ) {                 
    Log3 $name, 2, "updateCountdown error: no such device: $name";
    return;                       
  }                               

  if( !defined($remaining) ) {
    $remaining = '<unknown>';

    if( my $TIMED_OnOff = $hash->{TIMED_OnOff} ) {
      $remaining = $TIMED_OnOff->{START} + $TIMED_OnOff->{DURATION} - time();
    } else {
      $remaining = ReadingsVal($name, 'timerDuration', undef);
      if( $remaining ne '<unknown>' ) {
        my $age = ReadingsAge($name, 'timerDuration', undef);
        $remaining = $remaining - $age;
      }
    }
  }
                                 
  Log3( $name, 4, "updateCountdown: remaining $remaining");
  if ( $remaining ne '<unknown>' ) {
    if( $remaining <= 0 ) {
      stopCountdown($name);
      return;
    }

    readingsSingleUpdate($hash, 'timerRemaining', int($remaining), 1);
    InternalTimer( gettimeofday() + $interval, 'updateCountdown', $hash);
  }
}

sub stopCountdown($) {               
  my $name = shift // return;                 
     
  my $hash = $name;
  $hash = $defs{$name} if ref $hash ne 'HASH';
  $name = $hash->{NAME};
         
  if( !$hash ) {
    Log3( $name, 2, "stopCountdown error: no such device: $name" );
    return;
  }

  Log3( $name, 4, "stopCountdown for: $name" );

  readingsSingleUpdate($hash, 'timerRemaining', 0, 1);

  RemoveInternalTimer( $hash, 'updateCountdown' );
  CommandDeleteReading( undef, "$name timerDuration" );
  CommandDeleteReading( undef, "$name timerRemaining" );

  return;                         
}
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

megadodopublications

Hallo Beta-User,

Danke, klappt noch nicht ganz.

Die erste Intervall-Aktualisierung ist ok.

Jede folgende scheitert - vermutlich, weil InternalTimer( gettimeofday() + $interval, 'updateCountdown', $hash); nur $name aber nicht $remaining und $interval übergibt:

2022-03-18 16:51:42 dummy timertest on
2022-03-18 16:51:42 dummy timertest timerDuration: 300
2022-03-18 16:51:42 dummy timertest timerRemaining: 300
2022-03-18 16:51:47 dummy timertest timerRemaining: 295
2022-03-18 16:52:17 dummy timertest timerRemaining: 265
2022-03-18 16:52:47 dummy timertest timerRemaining: 235


In der Doku zu InternalTimer steht, dass _ein_ Parameter übergeben werden kann, alternativ "jeder weitere Datentyp sein, der mit einem Skalar übergeben werden kann" - wenn du/ihr noch eine Idee habt; ansonsten hört es hier erneut für meine Frickes-und-Pfusch Kenntnisse auf.

Viele Grüße
Ralph.

Beta-User

Hmm, ...da war doch was...

Ungetestet:
sub updateCountdown($;$$) {           
  my $name = shift //return;
  my $remaining = shift;
  my $interval = shift // 30;
 
  if ($name =~ m{:} ) {
     ($name, $interval) = split m{:}x, $name;
  }

  my $hash = $name;               
  $hash = $defs{$name} if ref $hash ne 'HASH';
  $name = $hash->{NAME};         

  if( !$hash ) {                 
    Log3 $name, 2, "updateCountdown error: no such device: $name";
    return;                       
  }                               

  if( !defined($remaining) ) {
    $remaining = '<unknown>';

    if( my $TIMED_OnOff = $hash->{TIMED_OnOff} ) {
      $remaining = $TIMED_OnOff->{START} + $TIMED_OnOff->{DURATION} - time();
    } else {
      $remaining = ReadingsVal($name, 'timerDuration', undef);
      if( $remaining ne '<unknown>' ) {
        my $age = ReadingsAge($name, 'timerDuration', undef);
        $remaining = $remaining - $age;
      }
    }
  }
                                 
  Log3( $name, 4, "updateCountdown: remaining $remaining" );
  if ( $remaining ne '<unknown>' ) {
    if( $remaining <= 0 ) {
      stopCountdown($name);
      return;
    }

    readingsSingleUpdate($hash, 'timerRemaining', int($remaining), 1);
    InternalTimer( gettimeofday() + $interval, 'updateCountdown', "${name}:$interval" );
  }
}
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files