From 0340785260186cdc172eb5b759cb74d3b88b1cfb Mon Sep 17 00:00:00 2001
From: rudolfkoenig <>
Date: Sun, 27 Jan 2013 21:30:41 +0000
Subject: [PATCH] set extensions (on-for-timer, etc)

git-svn-id: https://svn.fhem.de/fhem/trunk@2582 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
 fhem/CHANGED                       |   5 +-
 fhem/FHEM/10_EnOcean.pm            |  23 +++--
 fhem/FHEM/10_FS20.pm               |  18 ++--
 fhem/FHEM/10_ZWave.pm              |  19 +++-
 fhem/FHEM/SetExtensions.pm         | 152 +++++++++++++++++++++++++++++
 fhem/docs/commandref_frame.html    |  38 ++++++++
 fhem/docs/commandref_frame_DE.html |  63 ++++++++++--
 7 files changed, 290 insertions(+), 28 deletions(-)
 create mode 100644 fhem/FHEM/SetExtensions.pm

diff --git a/fhem/CHANGED b/fhem/CHANGED
index 384009f58..79a16856a 100644
--- a/fhem/CHANGED
+++ b/fhem/CHANGED
@@ -51,10 +51,11 @@
   - feature: telnet client mode
   - bugfix:  FHEMWEB longpoll misses initial state change (HM: set_on vs. on)
   - change:  20_OWFS.pm, 21_OWTEMP modules flagged as "deprecated". These
-             modules will be removed in a future release. Use OWServer / OWDevice
-             instead. (M. Fischer)
+             modules will be removed in a future release. Use OWServer /
+             OWDevice instead. (M. Fischer)
   - feature: a lot of new features and known 1-wire slaves to OWServer /
              OWDevice added (M. Fischer)
+  - feature: set-extensions (additional set commands) for FS20, EnOcean, ZWave
 
 - 2012-10-28 (5.3)
   - feature: added functions trim, ltrim, rtrim, UntoggleDirect,
diff --git a/fhem/FHEM/10_EnOcean.pm b/fhem/FHEM/10_EnOcean.pm
index e2cb7acdc..20f162c4f 100755
--- a/fhem/FHEM/10_EnOcean.pm
+++ b/fhem/FHEM/10_EnOcean.pm
@@ -4,6 +4,7 @@ package main;
 
 use strict;
 use warnings;
+use SetExtensions;
 
 sub EnOcean_Define($$);
 sub EnOcean_Initialize($);
@@ -221,8 +222,10 @@ EnOcean_Set($@)
         $dimVal=0;
 
       } else {
-        return "Unknown argument $cmd, choose one of dim:slider,0,1,100 ".
-                "dimup:slider,0,1,100 dimdown:slider,0,1,100 on off teach"
+        my $list = "dim:slider,0,1,100 dimup:slider,0,1,100 ".
+                   "dimdown:slider,0,1,100 on off teach";
+        return SetExtensions($hash, $list, $name, @a);
+
       }
 	  
       if($sendDimCmd) {
@@ -288,11 +291,12 @@ EnOcean_Set($@)
     ###########################
     } else {                                          # Simulate a PTM
       my ($c1,$c2) = split(",", $cmd, 2);
-      return "Unknown argument $cmd, choose one of " .
-      join(" ", sort keys %EnO_ptm200btn)
-        if(!defined($EnO_ptm200btn{$c1}) ||
-           ($c2 && !defined($EnO_ptm200btn{$c2})));
-      Log $ll2, "EnOcean: set $name $cmd";
+
+      if(!defined($EnO_ptm200btn{$c1}) ||
+          ($c2 && !defined($EnO_ptm200btn{$c2}))) {
+        my $list = join(" ", sort keys %EnO_ptm200btn);
+        return SetExtensions($hash, $list, $name, @a);
+      }
 
       my ($db_3, $status) = split(":", $EnO_ptm200btn{$c1}, 2);
       $db_3 <<= 5;
@@ -636,6 +640,7 @@ EnOcean_A5Cmd($$$)
   <a name="EnOceanset"></a>
   <b>Set</b>
   <ul>
+    <br>
     <li>MD15 commands. Note: The command is not sent until the MD15
     wakes up and sends a mesage, usually every 10 minutes.
     <ul>
@@ -705,6 +710,10 @@ EnOcean_A5Cmd($$$)
       attr eventMap BI:on B0:off<br>
       set switch1 on<br>
     </code></ul>
+    <b>Note</b>: <a href="#setExtensions">set extensions</a> are supported,
+        if the corresponding <a href="#eventMap">eventMap</a> specifies 
+        the on and off mappings.
+    <br>
     </li>
 
     </ul>
diff --git a/fhem/FHEM/10_FS20.pm b/fhem/FHEM/10_FS20.pm
index 76ada4b38..0770515b6 100755
--- a/fhem/FHEM/10_FS20.pm
+++ b/fhem/FHEM/10_FS20.pm
@@ -4,6 +4,7 @@ package main;
 
 use strict;
 use warnings;
+use SetExtensions;
 
 my %codes = (
   "00" => "off",
@@ -167,7 +168,7 @@ FS20_Set($@)
   my $ret = undef;
   my $na = int(@a);
 
-  return "no set value specified" if($na < 2 || $na > 3);
+  return "no set value specified" if($na < 2);
   return "Readonly value $a[1]" if(defined($readonly{$a[1]}));
 
   if($na > 2 && $a[1] eq "dim") {
@@ -181,16 +182,14 @@ FS20_Set($@)
   if(!defined($c)) {
 
     # Model specific set arguments
+    my $list;
     if(defined($attr{$name}) && defined($attr{$name}{"model"})) {
       my $mt = $models{$attr{$name}{"model"}};
-      return "Unknown argument $a[1], choose one of "
-                                               if($mt && $mt eq "sender");
-      return "Unknown argument $a[1], choose one of $fs20_simple"
-                                               if($mt && $mt eq "simple");
+      $list = "" if($mt && $mt eq "sender");
+      $list = $fs20_simple if($mt && $mt eq "simple");
     }
-    return "Unknown argument $a[1], choose one of " .
-                                join(" ", sort keys %fs20_c2b) .
-                                " dim:slider,0,6.25,100";
+    $list = join(" ", sort keys %fs20_c2b) if(!defined($list));
+    return SetExtensions($hash, $list, @a);
 
   }
 
@@ -538,6 +537,8 @@ four2hex($$)
     toggle            # between off and previous dim val
     on-till           # Special, see the note
 </pre>
+    The <a href="#setExtensions"> set extensions</a> are also supported.<br>
+    <br>
     Examples:
     <ul>
       <code>set lamp on</code><br>
@@ -546,6 +547,7 @@ four2hex($$)
       <code>set lamp on-for-timer 12</code><br>
     </ul>
     <br>
+
     Notes:
     <ul>
       <li>Use reset with care: the device forgets even the housecode.
diff --git a/fhem/FHEM/10_ZWave.pm b/fhem/FHEM/10_ZWave.pm
index 3d64e8092..bc05c687c 100755
--- a/fhem/FHEM/10_ZWave.pm
+++ b/fhem/FHEM/10_ZWave.pm
@@ -14,6 +14,7 @@ package main;
 
 use strict;
 use warnings;
+use SetExtensions;
 
 sub ZWave_Parse($$@);
 sub ZWave_Set($@);
@@ -236,6 +237,7 @@ ZWave_Cmd($$@)
   my $name = shift(@a);
   my $cmd  = shift(@a);
 
+
   # Collect the commands from the distinct classes
   my %cmdList;
   my $classes = AttrVal($name, "classes", "");
@@ -249,14 +251,23 @@ ZWave_Cmd($$@)
       }
     }
   }
+
   if(!$cmdList{$cmd}) {
     my $list = join(" ",sort keys %cmdList);
     foreach my $cmd (keys %zwave_cmdArgs) {      # add slider & co
       $list =~ s/\b$cmd\b/$cmd:$zwave_cmdArgs{$cmd}/;
     }
-    return "Unknown $type argument $cmd, choose one of $list";
+
+    if($type eq "set") {
+      unshift @a, $name, $cmd;
+      return SetExtensions($hash, $list, @a);
+    } else {
+      return "Unknown argument $cmd, choose one of $list";
+    }
+
   }
 
+  Log GetLogLevel($name,2), "ZWave $type $name $cmd";
 
   ################################
   # ZW_SEND_DATA,nodeId,CMD,ACK|AUTO_ROUTE
@@ -525,8 +536,11 @@ ZWave_Undef($$)
   <a name="ZWaveset"></a>
   <b>Set</b>
   <ul>
+  <br>
+  <b>Note</b>: devices with on/off functionality support the <a
+      href="#setExtensions"> set extensions</a>.
 
-  <br><b>Class BASIC</b>
+  <br><br><b>Class BASIC</b>
   <li>basicValue value<br>
     Send value (0-255) to this device. The interpretation is device dependent,
     e.g. for a SWITCH_BINARY device 0 is off and anything else is on.</li>
@@ -571,7 +585,6 @@ ZWave_Undef($$)
 
   <li>associationDel groupId nodeId ...<br>
   Remove the specified list of nodeIds from the assotion group groupId.</li>
-
   </ul>
   <br>
 
diff --git a/fhem/FHEM/SetExtensions.pm b/fhem/FHEM/SetExtensions.pm
new file mode 100644
index 000000000..789139dfb
--- /dev/null
+++ b/fhem/FHEM/SetExtensions.pm
@@ -0,0 +1,152 @@
+##############################################
+# $Id: $
+
+package main;
+use strict;
+use warnings;
+
+sub SetExtensions($$@);
+sub SetExtensionsFn($);
+
+sub
+SetExtensions($$@)
+{
+  my ($hash, $list, $name, $cmd, @a) = @_;
+
+  my %se_list = (
+    "on-for-timer"  => 1,
+    "off-for-timer" => 1,
+    "on-till"       => 1,
+    "off-till"      => 1,
+    "blink"         => 2,
+    "intervals"     => 0,
+  );
+
+  my $hasOn  = ($list =~ m/\bon\b/);
+  my $hasOff = ($list =~ m/\bon\b/);
+  if(!$hasOn || !$hasOff) {
+    my $em = AttrVal($name, "eventMap", undef);
+    if($em) {
+      $hasOn  = ($em =~ m/:on\b/)  if(!$hasOn);
+      $hasOff = ($em =~ m/:off\b/) if(!$hasOff);
+    }
+    $cmd = ReplaceEventMap($name, $cmd, 1) if($cmd ne "?"); # Fix B0-for-timer
+  }
+  if(!$hasOn || !$hasOff) { # No extension
+    return "Unknown argument $cmd, choose one of $list";
+  }
+
+  if(!defined($se_list{$cmd})) {
+    # Add only "new" commands
+    my @mylist = grep { $list !~ m/\b$_\b/ } keys %se_list;
+    return "Unknown argument $cmd, choose one of $list " .
+        join(" ", @mylist);
+  }
+  if($se_list{$cmd} && $se_list{$cmd} != int(@a)) {
+    return "$cmd requires $se_list{$cmd} parameter";
+  }
+
+  my $cmd1 = ($cmd =~ m/on.*/ ? "on" : "off");
+  my $cmd2 = ($cmd =~ m/on.*/ ? "off" : "on");
+  my $param = $a[0];
+
+  if($cmd eq "on-for-timer" || $cmd eq "off-for-timer") {
+    RemoveInternalTimer("SE $name $cmd");
+    return "$cmd requires a number as argument" if($param !~ m/^\d*\.?\d*$/);
+
+    if($param) {
+      DoSet($name, $cmd1);
+      InternalTimer(gettimeofday()+$param,"SetExtensionsFn","SE $name $cmd",0);
+    }
+
+  } elsif($cmd eq "on-till" || $cmd eq "off-till") {
+    my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($param);
+    return "$cmd: $err" if($err);
+
+    my $at = $name . "_till";
+    CommandDelete(undef, $at) if($defs{$at});
+
+    my @lt = localtime;
+    my $hms_till = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
+    my $hms_now  = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
+    if($hms_now ge $hms_till) {
+      Log 4, "$cmd: won't switch as now ($hms_now) is later than $hms_till";
+      return "";
+    }
+    DoSet($name, $cmd1);
+    CommandDefine(undef, "$at at $hms_till set $name $cmd2");
+
+  } elsif($cmd eq "blink") {
+    my $p2 = $a[1];
+    delete($hash->{SE_BLINKPARAM});
+    return "$cmd requires 2 numbers as argument"
+        if($param !~ m/^\d+$/ || $p2 !~ m/^\d*\d?\d*$/);
+
+    if($param) {
+      DoSet($name, "on-for-timer", $p2);
+      $param--;
+      if($param) {
+        $hash->{SE_BLINKPARAM} = "$param $p2";
+        InternalTimer(gettimeofday()+2*$p2,"SetExtensionsFn","SE $name $cmd",0);
+      }
+    }
+
+  } elsif($cmd eq "intervals") {
+    my $at0 = "${name}_till";
+    my $at1 = "${name}_intervalFrom",
+    my $at2 = "${name}_intervalNext";
+    CommandDelete(undef, $at0) if($defs{$at0});
+    CommandDelete(undef, $at1) if($defs{$at1});
+    CommandDelete(undef, $at2) if($defs{$at2});
+
+    my $intSpec = shift(@a);
+    if($intSpec) {
+      my ($from, $till) = split("-", $intSpec);
+
+      my ($err, $hr, $min, $sec, $fn) = GetTimeSpec($from);
+      return "$cmd: $err" if($err);
+      my @lt = localtime;
+      my $hms_from = sprintf("%02d:%02d:%02d", $hr, $min, $sec);
+      my $hms_now  = sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0]);
+
+      if($hms_from le $hms_now) { # By slight delays at will schedule tomorrow.
+        SetExtensions($hash, $list, $name, "on-till", $till);
+
+      } else {
+        CommandDefine(undef, "$at1 at $from set $name on-till $till");
+
+      }
+
+      if(@a) {
+        my $rest = join(" ", @a);
+        my ($from, $till) = split("-", shift @a);
+        CommandDefine(undef, "$at2 at $from set $name intervals $rest");
+      }
+    }
+    
+  }
+
+  return undef;
+}
+
+sub
+SetExtensionsFn($)
+{
+  my (undef, $name, $cmd) = split(" ", shift, 3);
+  return if(!defined($defs{$name}));
+
+
+  if($cmd eq "on-for-timer") {
+    DoSet($name, "off");
+
+  } elsif($cmd eq "off-for-timer") {
+    DoSet($name, "on");
+
+  } elsif($cmd eq "blink") {
+    DoSet($name, "blink", split(" ", $defs{$name}{SE_BLINKPARAM}, 2));
+
+  }
+
+}
+
+1;
diff --git a/fhem/docs/commandref_frame.html b/fhem/docs/commandref_frame.html
index 81bac499a..47b91c5b0 100644
--- a/fhem/docs/commandref_frame.html
+++ b/fhem/docs/commandref_frame.html
@@ -782,7 +782,45 @@ A line ending with \ will be concatenated with the next one, so long lines
   Each device has different set parameters, see the corresponding device
   section for details.<br>
   <br>
+  <br>
+  <a name="setExtensions"></a>
+  Some modules support a common list of <b>set extensions</b>, and point in
+  their documentation to this section. If the module itself implements one of
+  the following commands, then the module-implementation takes precedence.
 
+  <ul>
+    <li>on-for-timer &lt;seconds&gt<br>
+        Issue the on command for the device, and after &lt;seconds&gt; the off
+        command. For issuing the off command an internal timer will be
+        scheduled, which is deleted upon a restart. To delete this internal
+        timer without restart specify 0 as argument.</li>
+    <li>off-for-timer &lt;seconds&gt<br>
+        see on-for-timer above.</li>
+    <li>on-till &lt;timedet&gt<br>
+        Issue the on command for the device, and create an at definition with
+        &lt;timedet&gt; (in the form HH:MM[:SS]) to set it off. This definition
+        is visible, and its name is deviceName+"_till". To cancel the scheduled
+        off, delete the at definition.</li>
+    <li>off-till &lt;timedet&gt<br>
+        see on-till above.</li>
+    <li>blink &lt;number&gt; &lt;blink-period&gt;<br>
+        set the device on for &lt;blink-period&gt; then off for
+        &lt;blink-period&gt; and repeat this &lt;number&gt; times.
+        To stop blinking specify "0 0" as argument.</li>
+    <li>intervals &lt;from1&gt;-&lt;till1&gt; &lt;from2&gt;-&lt;till2&gt;...
+        </br>
+        set the device on for the specified intervals, which are all timespecs
+        in the form HH:MM[:SS]. The intervals are space separated.</li>
+  </ul>
+  Examples:
+  <ul>
+    <code>
+    set switch on-for-timer 12.5<br>
+    set switch on-till {sunset()}<br>
+    set switch blink 3 1<br>
+    set switch intervals 08:00-12:00 13:00-18:00<br>
+    </code>
+  </ul>
 </ul>
 
 
diff --git a/fhem/docs/commandref_frame_DE.html b/fhem/docs/commandref_frame_DE.html
index 3031e4f62..b5fc4d5ad 100644
--- a/fhem/docs/commandref_frame_DE.html
+++ b/fhem/docs/commandref_frame_DE.html
@@ -800,19 +800,66 @@ Zeilen erstreckende Befehle, indem man keine \ am Zeilenende eingeben muss.</p>
 <ul>
   <code>set &lt;devspec&gt; &lt;type-specific&gt;</code>
   <br><br>
-  Der Befehl setzt Ger&auml;teparameter/sendet Signale an ein Ger&auml;t. Sie erhalten 
-  eine Liste verf&uuml;gbarer Parameter wenn Sie folgendes eingeben:
+  Der Befehl setzt Ger&auml;teparameter/sendet Signale an ein Ger&auml;t. Sie
+  erhalten eine Liste verf&uuml;gbarer Parameter wenn Sie folgendes eingeben:
   <ul>
     <code>set &lt;name&gt; ?</code>
   </ul>
-  Lesen Sie bitte den Abschnitt <a href="#devspec">Device specification</a> f&uuml;r 
-  Details zu
-  &lt;devspec&gt;. Der &quot;set&quot;-Befehl gibt nur bei Fehler einen Wert zur&uuml;ck.<br>
-  <br>
-  Jedes Ger&auml;t hat verschiedene Parameter die mit &quot;set&quot; gesetzt werden k&ouml;nnen. 
-  Lesen Sie bitte den entsprechenden Abschnitt f&uuml;r das Ger&auml;t f&uuml;r Details durch.<br>
+  Lesen Sie bitte den Abschnitt <a href="#devspec">Device specification</a>
+  f&uuml;r Details zu &lt;devspec&gt;. Der &quot;set&quot;-Befehl gibt nur bei
+  Fehler einen Wert zur&uuml;ck.
+  <br><br>
+  Jedes Ger&auml;t hat verschiedene Parameter die mit &quot;set&quot; gesetzt
+  werden k&ouml;nnen.  Lesen Sie bitte den entsprechenden Abschnitt f&uuml;r
+  das Ger&auml;t f&uuml;r Details durch.
   <br>
 
+  <br>
+  <a name="setExtensions"></a>
+  Manche Module unterst&uuml;tzen die sog. <b>set extensions</b>, und in der
+  entsprechenden Dokumentation ist ein Link auf diesem Text zu finden. Falls im
+  Modul selber einer der unten aufgef&uuml;hrten Befehle implementiert ist, dann
+  wird die Modul-Implementation verwendet.
+  <ul>
+    <li>on-for-timer &lt;sekunden&gt<br>
+        Das Ger&auml;t wird per "on" eingeschaltet, und ein interner Zeitgeber
+        wird erstellt, um nach &lt;sekunden&gt; ein "off" Kommando
+        auszuf&uuml;hren.  Um diesen Zeitgeber zu entfernen sollte man das
+        Kommando mit dem Argument 0 erneut aufrufen. Achtung: dieser Zeitgeber
+        wird bei einem restart nicht gespeichert.</li>
+    <li>off-for-timer &lt;sekunden&gt<br>
+        siehe on-for-timer.</li>
+    <li>on-till &lt;timedet&gt<br>
+        Das Ger&auml;t wird per "on" eingeschaltet, und ein at Instanz wird
+        definiert, um es um &lt;timedet&gt; (Format: HH:MM[:SS]) per off
+        auszuschalten.  Diese at Instanz ist sichtbar unter dem Namen
+        ger&auml;teName+"_till".  Um das Ausschalten zu deaktivieren
+        l&ouml;scht man diese at Definition.  </li>
+    <li>off-till &lt;timedet&gt<br>
+        siehe on-till.</li>
+    <li>blink &lt;anzahl&gt; &lt;blink-periode&gt;<br>
+        Das Ger&auml;t wird mit "on" f&uuml;r die &lt;blink-periode&gt;
+        eingeschaltet, und das wird nach &lt;blink-periode&gt; wiederholt. Um
+        das Blinken vorzeitig zu stoppen spezifiziert man "0 0" als
+        Argument.</li>
+    <li>intervals &lt;from1&gt;-&lt;till1&gt; &lt;from2&gt;-&lt;till2&gt;...
+        </br>
+        Das Ger&auml;t wird f&uuml;r die spezifizierten Intervalle
+        eingeschaltet. Die einzelnen Intervalle sind Leerzeichen getrennt, und
+        ein Intervall besteht aus zwei Zeitspezifikationen, die mit einem "-"
+        getrennt sind.
+        </li>
+    </ul>
+    Beispiele:
+    <ul>
+      <code>
+        set switch on-for-timer 12.5<br>
+        set switch on-till {sunset()}<br>
+        set switch blink 3 1<br>
+        set switch intervals 08:00-12:00 13:00-18:00<br>
+      </code>
+    </ul>
+  </ul>
 </ul>