From 24e33dfc10e38e294832d6a90de1187162f08ca6 Mon Sep 17 00:00:00 2001
From: rudolfkoenig <>
Date: Wed, 19 Sep 2012 07:29:38 +0000
Subject: [PATCH] Structure changes by Tobias

git-svn-id: https://svn.fhem.de/fhem/trunk@1869 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
 fhem/FHEM/98_structure.pm | 172 +++++++++++++++++++++++++++++++++++++-
 fhem/docs/commandref.html |  59 +++++++++----
 2 files changed, 216 insertions(+), 15 deletions(-)

diff --git a/fhem/FHEM/98_structure.pm b/fhem/FHEM/98_structure.pm
index 12e1ffe45..9a3d8371e 100755
--- a/fhem/FHEM/98_structure.pm
+++ b/fhem/FHEM/98_structure.pm
@@ -4,6 +4,8 @@ package main;
 
 use strict;
 use warnings;
+#use Data::Dumper;
+
 
 #####################################
 sub
@@ -13,9 +15,11 @@ structure_Initialize($)
 
   $hash->{DefFn}     = "structure_Define";
   $hash->{UndefFn}   = "structure_Undef";
-
+  $hash->{NotifyFn}  = "structure_Notify";
   $hash->{SetFn}     = "structure_Set";
   $hash->{AttrFn}    = "structure_Attr";
+  $hash->{AttrList}  = "clientstate_priority clientstate_behavior:relative,absolute";
+
   addToAttrList("structexclude");
 
   my %ahash = ( Fn=>"CommandAddStruct",
@@ -43,6 +47,7 @@ structure_Define($$)
   my $stype   = shift(@a);
 
   addToAttrList($stype);
+  addToAttrList($stype . "_map");
   $hash->{ATTR} = $stype;
 
   my %list;
@@ -70,6 +75,171 @@ structure_Undef($$)
   return undef;
 }
 
+#############################
+# returns the unique keys of the given array
+# @my_array = ("one","two","three","two","three");
+# print join(" ", @my_array), "\n";
+# print join(" ", uniq(@my_array)), "\n";
+
+sub uniq {
+    return keys %{{ map { $_ => 1 } @_ }};
+}
+
+
+#############################
+sub structure_Notify($$)
+{
+  my ($hash, $dev) = @_;
+  #Log 1, Dumper($hash);
+  my $me = $hash->{NAME};
+  my $devmap = $hash->{ATTR}."_map";
+
+  # lade das Verhalten, Standard ist absolute 
+  my $behavior = AttrVal($me,"clientstate_behavior", "absolute");
+  my @clientstate;
+
+  return "" if($attr{$me} && $attr{$me}{disable});
+
+  #pruefen ob Devices welches das notify ausgeloest hat Mitglied dieser
+  # Struktur ist
+  return "" if (!$hash->{CONTENT}->{$dev->{NAME}});
+
+  # hier nur den Struktur-Status anpassen wenn 
+  # a) behavior=absolute oder 
+  # b) behavior=relative UND das Attr clientstate_priority gefaellt ist
+  my @structPrio = split(" ", $attr{$me}{clientstate_priority})
+        if($attr{$me}{clientstate_priority});
+  return "" if (!@structPrio && $behavior eq "relative");
+
+  # assoziatives Array aus Prioritaetsliste aufbauen
+  # Bsp: Original: "On|An Off|Aus"
+  # wobei der erste Wert der "Oder"-Liste als Status der Struktur uebernommen
+  # wird hier also On oder Off
+  # priority[On]=0
+  # priority[An]=0
+  # priority[Off]=1
+  # priority[Aus]=1 
+  my %priority;
+  my (@priority, @foo); 
+  for (my $i=0; $i<@structPrio; $i++) {
+      @foo = split(/\|/, $structPrio[$i]);
+      for (my $j=0; $j<@foo;$j++) {
+        $priority{$foo[$j]} = $i+1;
+        $priority[$i+1]=$foo[0];
+      }
+  }
+  undef @foo;
+  undef @structPrio;
+  #Log 1, Dumper(%priority) . "\n";
+  
+  $hash->{INSET} = 1;
+  
+  my $minprio = 99999;
+  my $devstate;
+
+  #ueber jedes Device das zu dieser Struktur gehoert
+  foreach my $d (sort keys %{ $hash->{CONTENT} }) {
+    next if(!$defs{$d});
+    if($defs{$d}{INSET}) {
+      Log 1, "ERROR: endless loop detected for $d in " . $hash->{NAME};
+      next;
+    }
+
+    # wenn zum Device das "structexclude" gesetzt ist, wird dieses nicht
+    # beruecksichtigt
+    if($attr{$d} && $attr{$d}{structexclude}) {
+      my $se = $attr{$d}{structexclude};
+      next if($hash->{NAME} =~ m/$se/);
+    }
+
+
+    # Status des Devices gemaess den Regeln des gesetztes StrukturAttr
+    # umformatieren
+    if ($attr{$d}{$devmap}) {
+      my @gruppe = split(" ", $attr{$d}{$devmap});
+      my @value;
+      for (my $i=0; $i<@gruppe; $i++) {
+        @value = split(":", $gruppe[$i]);
+        if(@value == 1) {
+          # nur das zu lesende Reading ist angegeben, zb. bei 1wire Modul
+          # OWSWITCH
+          #Bsp: A --> nur Reading A gehuert zur Struktur
+          #Bsp: A B --> Reading A und B gehuert zur Struktur
+          $devstate = ReadingsVal($d, $value[0], undef);
+          push(@clientstate, $devstate);
+        } elsif(@value == 2) {
+          # zustand wenn der Status auf dem in der Struktur definierten
+          # umdefiniert werden muss
+          # bsp: on:An
+          if($devstate eq $value[0]){
+            $devstate = $value[1];
+            push(@clientstate, $devstate);
+            $i=99999;
+          }
+        } elsif(@value == 3) {
+          # Das zu lesende Reading wurde mit angegeben:
+          # Reading:OriginalStatus:NeuerStatus wenn zb. ein Device mehrere
+          # Readings abbildet, zb. 1wire DS2406, DS2450 Bsp: A:Zu.:Geschlossen
+          $devstate = ReadingsVal($d, $value[0], undef);
+          if($devstate eq $value[1]){
+            $devstate = $value[2];
+            push(@clientstate, $devstate);
+            # $i=99999; entfernt, wenn Device mehrere Ports/Readings abbildet
+            # wird beim ersten Auftreten sonst nicht weiter geprueft
+          }
+        }
+        # Log 1, "Dev: ".$d." Anzahl: ".@value." Value:".$value[0]." devstate:
+        # ".$devstate;
+        $minprio = $priority{$devstate}
+                if($devstate &&
+                   $priority{$devstate} &&
+                   $priority{$devstate} < $minprio);
+      }
+    } else {
+      # falls kein mapping im Device angegeben wurde
+      $devstate = ReadingsVal($d, "state", undef);
+      $minprio = $priority{$devstate}
+               if($devstate &&
+                  $priority{$devstate} &&
+                  $priority{$devstate} < $minprio);
+      push(@clientstate, $devstate);
+    }
+
+    #besser als 1 kann minprio nicht werden
+    last if($minprio == 1);
+  } #foreach
+
+  @clientstate = uniq(@clientstate);# eleminiere alle Dubletten
+
+  #ermittle Endstatus
+  my $newState;
+  if($behavior eq "absolute"){
+    # wenn absolute, dann gebe undefinierten Status aus falls die Clients
+    # unterschiedliche Status' haben  
+    if(@clientstate > 1) { $newState = "undefined";}
+    else { $newState = $clientstate[0];}
+  } elsif($behavior eq "relative" && $minprio < 99999) {
+    $newState = $priority[$minprio];
+  } else {
+    $newState = "undefined";
+  }
+
+
+  #eigenen Status jetzt setzen, nur wenn abweichend
+  if($hash->{STATE} ne $newState) {
+    Log 3, "Update structure '" .$me . "' to " . $newState .
+                " because device '" .$dev->{NAME}. "' has changed";
+    $hash->{STATE} = $newState;
+    readingsBeginUpdate($hash);
+    readingsUpdate($hash, "state", $newState);
+    readingsEndUpdate($hash, 1);
+  }
+  #Log 1, "devstate: ".$devstate." - minprio final: " . $minprio . "\n";
+  #Log 1, Dumper(%priority);
+  delete($hash->{INSET});
+
+  undef;
+}
 
 #####################################
 sub
diff --git a/fhem/docs/commandref.html b/fhem/docs/commandref.html
index 72baf31fe..e026eebd1 100644
--- a/fhem/docs/commandref.html
+++ b/fhem/docs/commandref.html
@@ -5385,6 +5385,7 @@ To send the data, both send or write could be used.<br>
   Notice: All links are relative to <code>http://hostname:8083/fhem</code>.
   </ul>
   <br><br>
+</ul>
 
 <a name="USF1000"></a>
 <h3>USF1000</h3>
@@ -7921,6 +7922,7 @@ This module supports Samsung TV devices with few commands. It's developed and te
   
  <b>Get</b><br>
    <ul>N/A</ul><br>
+</ul>
 
    
 <a name="NetIO230B"></a>
@@ -10498,29 +10500,27 @@ KlikAanKlikUit, NEXA, CHACON, HomeEasy UK. <br> You need to define an RFXtrx433
 
 </ul>
 
-
 <a name="structure"></a>
 <h3>structure</h3>
 <ul>
   <br>
-
   <a name="structuredefine"></a>
   <b>Define</b>
   <ul>
     <code>define &lt;name&gt; structure &lt;struct_type&gt; &lt;dev1&gt; &lt;dev2&gt; ...</code>
     <br><br>
-
-    The structure device is used to organize/structure a devices in order to
+    The structure device is used to organize/structure devices in order to
     set groups of them at once (e.g. switching everything off in a house).<br>
 
     The list of attached devices can be modified through the addstruct /
     delstruct commands. Each attached device will get the attribute
     &lt;struct_type&gt;=&lt;name&gt;<br> when it is added to the list, and the
     attribute will be deleted if the device is deleted from the structure.
-    Exception to this are the attributes room and alias.  The structure devices
-    can also be added to a structure, e.g. you can have a building consisting
-    of levels which consists of rooms of devices.
     <br>
+    The structure devices can also be added to a structure, e.g. you can have
+    a building consisting of levels which consists of rooms of devices.
+    <br>
+
     Example:<br>
     <ul>
       <li>define kitchen structure room lamp1 lamp2</li>
@@ -10529,9 +10529,45 @@ KlikAanKlikUit, NEXA, CHACON, HomeEasy UK. <br> You need to define an RFXtrx433
       <li>define house structure building kitchen living</li>
       <li>set house off</li>
     </ul>
-  </ul>
-  <br>
+    <br>
 
+    The backward propagated status change from the devices to this structure
+    works in two different ways.
+    <br>Attribute clientstate_behavior<br>
+    <li>absolute</li>
+    <ul>
+      The structure status will changed to the common device status of all defined devices 
+      to this structure if all devices are identical. Otherwise the structure status is "undefined".
+    </ul>
+    <li>relative</li>
+    <ul>
+      You have to set the attribute "clientstate_priority" with all states of
+      the defined devices to this structure in descending order. Each group are
+      delemited by space. Each entry of one group are delimited by "pipe"
+    </ul>
+    <br>Example:<br>
+    <ul>
+      <li>attr kittchen clientstate_behavior relative</li>
+      <li>attr kittchen clientstate_priority An|On|on Aus|Off|off</li>
+      <li>attr house clientstate_priority Any_On|An All_Off|Aus</li>
+    </ul>
+    <br>
+    To group more devices from different types of devices you can define 
+    a clientstate redefining on each device. For example the Reading "A" of device door
+    is "open" or "closed" and the state of device lamp1 should redefine from 
+    "on" to "An" and "off" to "Aus"
+    <br>Example:<br> 
+    <ul>
+      <li>define door OWSWITCH &lt;ROMID&gt</li>
+      <li>define lamp1 dummy</li>
+      <li>attr lamp1 cmdlist on off</li>
+      <li>define kitchen structure struct_kitchen lamp1 door</li>
+      <li>attr kittchen clientstate_priority An|on OK|Aus|off</li>
+      <li>attr lamp1 struct_kitchen on:An off:Aus</li>
+      <li>attr door struct_kitchen A:open:on A:closed:off</li>
+    </ul>
+
+  <br>
   <a name="structureset"></a>
   <b>Set</b>
   <ul>
@@ -10540,15 +10576,12 @@ KlikAanKlikUit, NEXA, CHACON, HomeEasy UK. <br> You need to define an RFXtrx433
     matches (as a regexp) the name of the current structure.
   </ul>
   <br>
-
   <a name="structureget"></a>
   <b>Get</b>
   <ul>
     get is not supported through a structure device.
   </ul>
   <br>
-
-
   <a name="structureattr"></a>
   <b>Attributes</b>
   <ul>
@@ -10556,10 +10589,8 @@ KlikAanKlikUit, NEXA, CHACON, HomeEasy UK. <br> You need to define an RFXtrx433
         exclude the device from set operations, see the set command above.</li>
   </ul>
   <br>
-
 </ul>
 
-
 <a name="watchdog"></a>
 <h3>watchdog</h3>
 <ul>