# $Id$ ############################################################################## # # 98_structure.pm # Copyright by # e-mail: # # This file is part of fhem. # # Fhem is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # Fhem is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . # ############################################################################## package main; use strict; use warnings; #use Data::Dumper; sub structure_getChangedDevice($); ##################################### sub structure_Initialize($) { my ($hash) = @_; $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,relativeKnown,absolute,last ". $readingFnAttributes; addToAttrList("structexclude"); my %ahash = ( Fn=>"CommandAddStruct", Hlp=>" ,add to " ); $cmds{addstruct} = \%ahash; my %dhash = ( Fn=>"CommandDelStruct", Hlp=>" ,delete from "); $cmds{delstruct} = \%dhash; } ############################# sub structure_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); my $u = "wrong syntax: define structure [device ...]"; return $u if(int(@a) < 4); my $devname = shift(@a); my $modname = shift(@a); my $stype = shift(@a); addToAttrList($stype); addToAttrList($stype . "_map"); $hash->{ATTR} = $stype; my %list; foreach my $a (@a) { foreach my $d (devspec2array($a)) { $list{$d} = 1; } } $hash->{CONTENT} = \%list; @a = ( "set", $devname, $stype, $devname ); structure_Attr(@a); return undef; } ############################# sub structure_Undef($$) { my ($hash, $def) = @_; my @a = ( "del", $hash->{NAME}, $hash->{ATTR} ); structure_Attr(@a); return undef; } ############################# # returns the really changed Device ############################# sub structure_getChangedDevice($) { my ($dev) = @_; my $lastDevice = ReadingsVal($dev, "LastDevice", undef); $dev = structure_getChangedDevice($lastDevice) if($lastDevice && $defs{$dev}->{TYPE} eq "structure"); return $dev; } ############################# sub structure_Notify($$) { my ($hash, $dev) = @_; my $me = $hash->{NAME}; my $devmap = $hash->{ATTR}."_map"; if( $dev->{NAME} eq "global" ) { my $max = int(@{$dev->{CHANGED}}); for (my $i = 0; $i < $max; $i++) { my $s = $dev->{CHANGED}[$i]; $s = "" if(!defined($s)); if($s =~ m/^RENAMED ([^ ]*) ([^ ]*)$/) { my ($old, $new) = ($1, $2); if( defined($hash->{CONTENT}{$old}) ) { $hash->{DEF} =~ s/(\s+)$old(\s*)/$1$new$2/; delete( $hash->{CONTENT}{$old} ); $hash->{CONTENT}{$new} = 1; } } elsif($s =~ m/^DELETED ([^ ]*)$/) { my ($name) = ($1); if( defined($hash->{CONTENT}{$name}) ) { $hash->{DEF} =~ s/(\s+)$name(\s*)/ /; $hash->{DEF} =~ s/^ //; $hash->{DEF} =~ s/ $//; delete( $hash->{CONTENT}{$name} ); } } } } return "" if(AttrVal($me,"disable", undef)); #pruefen ob Devices welches das notify ausgeloest hat Mitglied dieser # Struktur ist return "" if (!$hash->{CONTENT}->{$dev->{NAME}}); # lade das Verhalten, Standard ist absolute my $behavior = AttrVal($me, "clientstate_behavior", "absolute"); my %clientstate; my @structPrio = attrSplit($attr{$me}{clientstate_priority}) if($attr{$me}{clientstate_priority}); return "" if($hash->{INSET}); # Do not trigger for our own set if($hash->{INNTFY}) { Log3 $me, 1, "ERROR: endless loop detected in structure_Notify $me"; return ""; } $hash->{INNTFY} = 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]; } } my $minprio = 99999; my $devstate; foreach my $d (sort keys %{ $hash->{CONTENT} }) { next if(!$defs{$d}); if($attr{$d} && $attr{$d}{$devmap}) { my @gruppe = attrSplit($attr{$d}{$devmap}); my @value; for (my $i=0; $i<@gruppe; $i++) { @value = split(":", $gruppe[$i]); if(@value == 1) { # value[0]:.* -> .* $devstate = ReadingsVal($d, $value[0], undef); } elsif(@value == 2) { # state:value[0] -> value[1] $devstate = ReadingsVal($d, "state", undef); if(defined($devstate) && $devstate =~ m/^$value[0]/){ $devstate = $value[1]; $i=99999; # RKO: ?? } } elsif(@value == 3) { # value[0]:value[1] -> value[2] $devstate = ReadingsVal($d, $value[0], undef); if(defined($devstate) && $devstate =~ m/^$value[1]/){ $devstate = $value[2]; } } if(defined($devstate)) { if(!$priority{$devstate} && $behavior eq "relativeKnown") { delete($hash->{INNTFY}); return ""; } $minprio = $priority{$devstate} if($priority{$devstate} && $priority{$devstate} < $minprio); $clientstate{$devstate} = 1; } } } else { $devstate = ReadingsVal($d, "state", undef); if(defined($devstate)) { if(!$priority{$devstate} && $behavior eq "relativeKnown") { delete($hash->{INNTFY}); return ""; } $minprio = $priority{$devstate} if($priority{$devstate} && $priority{$devstate} < $minprio); $clientstate{$devstate} = 1; } } } my $newState = "undefined"; if($behavior eq "absolute"){ my @cKeys = keys %clientstate; $newState = (@cKeys == 1 ? $cKeys[0] : "undefined"); } elsif($behavior =~ "^relative" && $minprio < 99999) { $newState = $priority[$minprio]; } elsif($behavior eq "last"){ $newState = ReadingsVal($dev->{NAME}, "state", undef); } Log3 $me, 5, "Update structure '$me' to $newState" . " because device $dev->{NAME} has changed"; readingsBeginUpdate($hash); readingsBulkUpdate($hash, "LastDevice", $dev->{NAME}); readingsBulkUpdate($hash, "LastDevice_Abs", structure_getChangedDevice($dev->{NAME})); readingsBulkUpdate($hash, "state", $newState); readingsEndUpdate($hash, 1); delete($hash->{INNTFY}); undef; } ##################################### sub CommandAddStruct($) { my ($cl, $param) = @_; my @a = split(" ", $param); if(int(@a) != 2) { return "Usage: addstruct "; } my $name = shift(@a); my $hash = $defs{$name}; if(!$hash || $hash->{TYPE} ne "structure") { return "$a is not a structure device"; } foreach my $d (devspec2array($a[0])) { $hash->{CONTENT}{$d} = 1; } @a = ( "set", $hash->{NAME}, $hash->{ATTR}, $hash->{NAME} ); structure_Attr(@a); return undef; } ##################################### sub CommandDelStruct($) { my ($cl, $param) = @_; my @a = split(" ", $param); if(int(@a) != 2) { return "Usage: delstruct "; } my $name = shift(@a); my $hash = $defs{$name}; if(!$hash || $hash->{TYPE} ne "structure") { return "$a is not a structure device"; } foreach my $d (devspec2array($a[0])) { delete($hash->{CONTENT}{$d}); } @a = ( "del", $hash->{NAME}, $hash->{ATTR} ); structure_Attr(@a); return undef; } ################################### sub structure_Set($@) { my ($hash, @list) = @_; my $ret = ""; my %pars; $hash->{INSET} = 1; $hash->{STATE} = join(" ", @list[1..@list-1]) if($list[1] ne "?"); foreach my $d (sort keys %{ $hash->{CONTENT} }) { next if(!$defs{$d}); if($defs{$d}{INSET}) { Log3 $hash, 1, "ERROR: endless loop detected for $d in " . $hash->{NAME}; next; } if($attr{$d} && $attr{$d}{structexclude}) { my $se = $attr{$d}{structexclude}; next if($hash->{NAME} =~ m/$se/); } $list[0] = $d; my $sret .= CommandSet(undef, join(" ", @list)); if($sret) { $ret .= "\n" if($ret); $ret .= $sret; if($list[1] eq "?") { $sret =~ s/.*one of //; map { $pars{$_} = 1 } split(" ", $sret); } } } delete($hash->{INSET}); Log3 $hash, 5, "SET: $ret" if($ret); return $list[1] eq "?" ? "Unknown argument ?, choose one of " . join(" ", sort keys(%pars)) : undef; } ################################### sub structure_Attr($@) { my ($type, @list) = @_; my %ignore = ("alias"=>1, "room"=>1, "group"=>1, "icon"=>1, "devStateIcon"=>1, "webCmd"=>1, "stateFormat"=>1 ); return undef if($ignore{$list[1]} || $list[1] =~ m/clientstate/); my $me = $list[0]; my $hash = $defs{$me}; if($hash->{INATTR}) { Log3 $me, 1, "ERROR: endless loop detected in structure_Attr for $me"; next; } $hash->{INATTR} = 1; my $ret = ""; foreach my $d (sort keys %{ $hash->{CONTENT} }) { next if(!$defs{$d}); if($attr{$d} && $attr{$d}{structexclude}) { my $se = $attr{$d}{structexclude}; next if("$me:$list[1]" =~ m/$se/); } $list[0] = $d; my $sret; if($type eq "del") { $sret .= CommandDeleteAttr(undef, join(" ", @list)); } else { $sret .= CommandAttr(undef, join(" ", @list)); } if($sret) { $ret .= "\n" if($ret); $ret .= $sret; } } delete($hash->{INATTR}); Log3 $me, 4, "Stucture attr $type: $ret" if($ret); return undef; } 1; =pod =begin html

structure


    Define
      define <name> structure <struct_type> <dev1> <dev2> ...

      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).
      The list of attached devices can be modified through the addstruct / delstruct commands. Each attached device will get the attribute <struct_type>=<name>
      when it is added to the list, and the attribute will be deleted if the device is deleted from the structure.
      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.
      Example:
      • define kitchen structure room lamp1 lamp2
      • addstruct kitchen TYPE=FS20
      • delstruct kitchen lamp1
      • define house structure building kitchen living
      • set house off


    Set
      Every set command is propagated to the attached devices. Exception: if an attached device has an attribute structexclude, and the attribute value matches (as a regexp) the name of the current structure.

    Get
      get is not supported through a structure device.

    Attributes
    • clientstate_behavior
      The backward propagated status change from the devices to this structure works in two different ways.
      • absolute
        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".
      • relative
        See below for clientstate_priority.
      • relativeKnown
        Like relative, but do not trigger on events not described in clientstate_priority. Needed e.g. for HomeMatic devices.
      • last
        The structure state corresponds to the state of the device last changed.
    • clientstate_priority
      If clientstate_behavior is set to relative, then you have to set the attribute "clientstate_priority" with all states of the defined devices to this structure in descending order. Each group is delemited by space or /. Each entry of one group is delimited by "pipe". The status represented by the structure is the first entry of each group. Example:
      • attr kitchen clientstate_behavior relative
      • attr kitchen clientstate_priority An|On|on Aus|Off|off
      • attr house clientstate_priority Any_On|An All_Off|Aus
      In this example the status of kitchen is either on or off. The status of house is either Any_on or All_off.
    • <struct_type>_map
      With this attribute, which has to specified for the structure- member, you can redefine the value reported by a specific structure-member for the structure value. The attribute has three variants:
      • readingName
        take the value from readingName instead of state.
      • oldVal:newVal
        if the state reading matches oldVal, then replace it with newVal
      • readingName:oldVal:newVal
        if readingName matches oldVal, then replace it with newVal
      Example:
      • define door OWSWITCH <ROMID>
      • define lamp1 dummy
      • attr lamp1 cmdlist on off
      • define kitchen structure struct_kitchen lamp1 door
      • attr kitchen clientstate_priority An|on OK|Aus|off
      • attr lamp1 struct_kitchen_map on:An off:Aus
      • attr door struct_kitchen_map A:open:on A:closed:off
      • attr door2 struct_kitchen_map A
    • structexclude
      exclude the device from set/notify or attribute operations. For the set and notify the value of structexclude must match the structure name, for the attr/deleteattr commands ist must match the combination of structure_name:attribute_name. Examples:
        define kitchen structure room lamp1 lamp2
        attr lamp1 structexclude kitchen
        attr lamp1 structexclude kitchen:stateFormat
    • readingFnAttributes

=end html =begin html_DE

structure


    Define
      define <name> structure <struct_type> <dev1> <dev2> ...

      Mit dem Device "Structure" werden Strukturen/Zusammenstellungen von anderen Devices erstellt um sie zu Gruppen zusammenzufassen. (Beispiel: im Haus alles ausschalten)
      Die Liste der Devices die einer Struktur zugeordnet sind kann duch das Kommando addstruct / delstruct im laufenden Betrieb verändert werden. Es können sowohl einzelne Devices als auch Gruppen von Devices (TYPE=FS20) zugefügt werden. Jedes zugefügt Device erhält zwei neue Attribute <struct_type>=<name> sowie <struct_type>_map wenn es zu einer Struktur zugefügt wurde. Diese Attribute werden wieder automatisch entfernt, sobald das Device von der Struktur entfernt wird.
      Eine Struktur kann ebenfalls zu einer anderen Struktur zugefügt werden. Somit können z b. kaskadierende Strukturen erstellt werden. (Z.b. KG,EG,OG, Haus) Beispiel:
      • define Kueche structure room lampe1 lampe2
      • addstruct Kueche TYPE=FS20
      • delstruct Kueche lampe1
      • define house structure building kitchen living
      • set house off


    Set
      Jedes set Kommando wird an alle Devices dieser Struktur weitergegeben.
      Aussnahme: das Attribut structexclude ist in einem Device definiert und dessen Attributwert matched als Regexp zum Namen der aktuellen Struktur.

    Get
      Get wird im Structur-Device nicht unterstützt.

    Attribute
    • clientstate_behavior
      Der Status einer Struktur hängt von den Stati der zugefügten Devices ab. Dabei wird das propagieren der Stati der Devices in zwei Gruppen klassifiziert und mittels diesem Attribut definiert:
      • absolute
        Die Struktur wird erst dann den Status der zugefügten Devices annehmen, wenn alle Devices einen identischen Status vorweisen. Bei unterschiedlichen Devictypen kann dies per Attribut <struct_type>_map pro Device beinflusst werden. Andernfalls hat die Struktur den Status "undefined".
      • relative
        S.u. clientstate_priority.
      • relativeKnown
        wie relative, reagiert aber nicht auf unbekannte, in clientstate_priority nicht beschriebene Ereignisse. Wird für HomeMatic Geräte benötigt.
      • last
        Die Struktur übernimmt den Status des zuletzt geänderten Gerätes.
    • clientstate_priority
      Wird die Struktur auf ein relatives Verhalten eingestellt, so wird die Priorität der Devicestati über das Attribut clientstate_priority beinflusst. Die Prioritäten sind in absteigender Reihenfolge anzugeben. Dabei können Gruppen mit identischer Priorität angegeben werden, um zb. unterschiedliche Devicetypen zusammenfassen zu können. Jede Gruppe wird durch Leerzeichen oder /, jeder Eintrag pro Gruppe durch Pipe getrennt. Der Status der Struktur ist der erste Eintrag in der entsprechenden Gruppe.
      Beispiel:
      • attr kueche clientstate_behavior relative
      • attr kueche clientstate_priority An|On|on Aus|Off|off
      • attr haus clientstate_priority Any_On|An All_Off|Aus
      In diesem Beipiel nimmt die Struktur kuecheentweder den Status An oder Aus an. Die Struktur haus nimmt entweder den Status Any_on oder All_off an. Sobald ein Device der Struktur haus den Status An hat nimmt die Struktur den Status Any_On an. Um dagegen den Status All_off anzunehmen, müssen alle Devices dieser Struktur auf off stehen.
    • <struct_type>_map
      Mit diesem Attribut, das dem Struktur-Mitglied zugewiesen werden muss, koennen die Werte, die die einzelnen Struktur- Mitglieder melden, umdefiniert werden, damit man unterschiedliche Geraeteklassen zusammenfassen kann. Es existieren drei Varianten:
      • readingName
        nehme den Wert von readingName anstatt von state
      • oldVal:newVal
        falls der Wert der state Reading oldVal (als regex) ist, dann ersetze diesen mit newVal.
      • readingName:oldVal:newVal
        falls der Wert der readingName oldVal (als regex) ist, dann ersetze diesen mit newVal.
      Beispiel:
      • define tuer OWSWITCH <ROMID>
      • define lampe1 dummy
      • attr lampe1 cmdlist on off
      • define kueche structure struct_kitchen lamp1 door
      • attr kueche clientstate_priority An|on OK|Aus|off
      • attr lampe1 struct_kitchen_map on:An off:Aus
      • attr tuer struct_kitchen_map A:open:on A:closed:off
      • attr tuer2 struct_kitchen_map A
    • structexclude
      Bei gesetztem Attribut wird set, attr/deleteattr ignoriert. Dies trifft ebenfalls auf die Weitergabe des Devicestatus an die Struktur zu. Fuer set und fuer die Status-Weitergabe muss der Wert den Strukturnamen matchen, bei einem Attribut-Befehl die Kombination Strukturname:Attributname. Beispiel:
        define kitchen structure room lamp1 lamp2
        attr lamp1 structexclude kitchen
        attr lamp1 structexclude kitchen:stateFormat
    • readingFnAttributes

=end html_DE =cut