# $Id$ ############################################################################## # # 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 SetExtensions; use vars qw(%defs); use vars qw(%attr); use vars qw($readingFnAttributes); use vars qw($init_done); sub Log($$); sub Log3($$$); sub readingsProxy_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "readingsProxy_Define"; $hash->{NotifyFn} = "readingsProxy_Notify"; $hash->{UndefFn} = "readingsProxy_Undefine"; $hash->{SetFn} = "readingsProxy_Set"; $hash->{GetFn} = "readingsProxy_Get"; $hash->{AttrFn} = "readingsProxy_Attr"; $hash->{AttrList} = "disable:1 " ."getList " ."setList " ."getFn:textField-long setFn:textField-long valueFn:textField-long " .$readingFnAttributes; } sub readingsProxy_setNotfiyDev($) { my ($hash) = @_; if( $hash->{DEVICE} ) { notifyRegexpChanged($hash,"(global|".$hash->{DEVICE}.")"); } else { notifyRegexpChanged($hash,''); } } sub readingsProxy_updateDevices($) { my ($hash) = @_; my %list; delete $hash->{DEVICE}; delete $hash->{READING}; my @params = split(" ", $hash->{DEF}); while (@params) { my $param = shift(@params); my @device = split(":", $param); if( defined($defs{$device[0]}) ) { $list{$device[0]} = 1; $hash->{DEVICE} = $device[0]; $hash->{READING} = $device[1]; $hash->{READING} = "state" if( !$hash->{READING} ); } } InternalTimer(gettimeofday(), "readingsProxy_setNotfiyDev", $hash); $hash->{CONTENT} = \%list; readingsProxy_update($hash, undef); } sub readingsProxy_Define($$) { my ($hash, $def) = @_; my @args = split("[ \t]+", $def); return "Usage: define readingsProxy :" if(@args != 3); my $name = shift(@args); my $type = shift(@args); $hash->{STATE} = 'Initialized'; if( $init_done ) { readingsProxy_updateDevices($hash); } return undef; } sub readingsProxy_Undefine($$) { my ($hash,$arg) = @_; return undef; } sub readingsProxy_update($$) { my ($hash,$value) = @_; my $name = $hash->{NAME}; my $DEVICE = $hash->{DEVICE}; return if( !$DEVICE ); my $READING = $hash->{READING}; $value = ReadingsVal($DEVICE,$READING,undef) if( $DEVICE && !defined($value) ); #return if( !defined($value) ); my $value_fn = AttrVal( $name, "valueFn", "" ); if( $value_fn =~ m/^{.*}$/s ) { my $VALUE = $value; my $LASTCMD = ReadingsVal($name,"lastCmd",undef); my $value_fn = eval $value_fn; Log3 $name, 3, $name .": valueFn: ". $@ if($@); return undef if( !defined($value_fn) ); $value = $value_fn if( $value_fn ne '' ); } if( AttrVal($name, 'event-on-change-reading', undef ) || AttrVal($name, 'event-on-update-reading', undef ) ) { readingsSingleUpdate($hash, 'state', $value, 1) } else { readingsSingleUpdate($hash, 'state', $value, 0); DoTrigger( $name, $value ); } } sub readingsProxy_Notify($$) { my ($hash,$dev) = @_; my $name = $hash->{NAME}; my $events = deviceEvents($dev,1); return if( !$events ); if( grep(m/^INITIALIZED$/, @{$events}) ) { readingsProxy_updateDevices($hash); return undef; } elsif( grep(m/^REREADCFG$/, @{$events}) ) { readingsProxy_updateDevices($hash); return undef; } return if( !$init_done ); return if( AttrVal($name,"disable", 0) > 0 ); return if($dev->{NAME} eq $name); my $max = int(@{$events}); for (my $i = 0; $i < $max; $i++) { my $s = $events->[$i]; $s = "" if(!defined($s)); if( $dev->{NAME} eq "global" && $s =~ m/^RENAMED ([^ ]*) ([^ ]*)$/) { my ($old, $new) = ($1, $2); if( defined($hash->{CONTENT}{$old}) ) { $hash->{DEF} =~ s/(^|\s+)$old((:\S+)?\s*)/$1$new$2/g; readingsProxy_updateDevices($hash); } } elsif( $dev->{NAME} eq "global" && $s =~ m/^DELETED ([^ ]*)$/) { my ($name) = ($1); if( defined($hash->{CONTENT}{$name}) ) { #$hash->{DEF} =~ s/(^|\s+)$name((:\S+)?\s*)/ /g; #$hash->{DEF} =~ s/^ //; #$hash->{DEF} =~ s/ $//; readingsProxy_updateDevices($hash); } } elsif( $dev->{NAME} eq "global" && $s =~ m/^DEFINED ([^ ]*)$/) { my ($name) = ($1); readingsProxy_updateDevices($hash) if( !$hash->{DEVICE} ); } else { next if( !$hash->{DEVICE} ); next if( $dev->{NAME} ne $hash->{DEVICE} ); my @parts = split(/: /,$s); my $reading = shift @parts; my $value = join(": ", @parts); $reading = "" if( !defined($reading) ); $value = "" if( !defined($value) ); if( $value eq "" ) { $reading = "state"; $value = $s; } next if( $reading ne $hash->{READING} ); readingsProxy_update($hash, $value); } } return undef; } sub readingsProxy_Set($@) { my ($hash, $name, @a) = @_; return "no set value specified" if(int(@a) < 1); my $setList = AttrVal($name, "setList", ""); $setList = getAllSets($hash->{DEVICE}) if( $setList eq "%PARENT%" ); return SetExtensions($hash,$setList,$name,@a) if(!$setList || $a[0] eq "?"); my $found = 0; foreach my $set (split(" ", $setList)) { if( "$set " =~ m/^${a[0]}[ :]/ ) { $found = 1; last; } elsif( "$set " =~ m/^state[ :]/ ) { $found = 1; last; } } return SetExtensions($hash,$setList,$name,@a) if( !$found ); SetExtensionsCancel($hash); my $v = join(" ", @a); my $set_fn = AttrVal( $hash->{NAME}, "setFn", "" ); if( $set_fn =~ m/^{.*}$/s ) { my $CMD = $a[0]; my $DEVICE = $hash->{DEVICE}; my $READING = $hash->{READING}; my $ARGS = join(" ", @a[1..$#a]); my $set_fn = eval $set_fn; Log3 $name, 3, $name .": setFn: ". $@ if($@); readingsSingleUpdate($hash, "lastCmd", $a[0], 0); return undef if( !defined($set_fn) ); $v = $set_fn if( $set_fn ne '' ); } else { readingsSingleUpdate($hash, "lastCmd", $a[0], 0); } if( $hash->{INSET} ) { Log3 $name, 2, "$name: ERROR: endless loop detected"; return "ERROR: endless loop detected for $hash->{NAME}"; } Log3 $name, 4, "$name: set hash->{DEVICE} $v"; $hash->{INSET} = 1; my $ret = CommandSet(undef,"$hash->{DEVICE} ".$v); delete($hash->{INSET}); return $ret; } sub readingsProxy_Get($@) { my ($hash, $name, @a) = @_; return "no get value specified" if(int(@a) < 1); my $getList = AttrVal($name, "getList", ""); $getList = getAllGets($hash->{DEVICE}) if( $getList eq "%PARENT%" ); return "Unknown argument ?, choose one of $getList" if(!$getList || $a[0] eq "?"); my $found = 0; foreach my $get (split(" ", $getList)) { if( "$get " =~ m/^${a[0]}[ :]/ ) { $found = 1; last; } } return "Unknown argument $a[0], choose one of $getList" if(!$found); my $v = join(" ", @a); my $get_fn = AttrVal( $hash->{NAME}, "getFn", "" ); if( $get_fn =~ m/^{.*}$/s ) { my $CMD = $a[0]; my $DEVICE = $hash->{DEVICE}; my $READING = $hash->{READING}; my $ARGS = join(" ", @a[1..$#a]); my ($get_fn,$direct_return) = eval $get_fn; Log3 $name, 3, $name .": getFn: ". $@ if($@); return $get_fn if($direct_return); return undef if( !defined($get_fn) ); $v = $get_fn if( $get_fn ne '' ); } if( $hash->{INGET} ) { Log3 $name, 2, "$name: ERROR: endless loop detected"; return "ERROR: endless loop detected for $hash->{NAME}"; } Log3 $name, 4, "$name: get hash->{DEVICE} $v"; $hash->{INSET} = 1; my$ret = CommandGet(undef,"$hash->{DEVICE} ".$v); delete($hash->{INSET}); return $ret; } sub readingsProxy_Attr($$$;$) { my ($cmd, $name, $attrName, $attrVal) = @_; if( $cmd eq "set" ) { if( $attrName eq 'getFn' || $attrName eq 'setFn' || $attrName eq 'valueFn' ) { my %specials= ( "%CMD" => $name, "%DEVICE" => $name, "%READING" => $name, "%ARGS" => $name, "%VALUE" => $name, "%LASTCMD" => $name, ); my $err = perlSyntaxCheck($attrVal, %specials); return $err if($err); } } } 1; =pod =item helper =item summary make (a subset of) a reading from one device available as a new device =item summary_DE Reading eines Gerätes (oder einen Teil daraus) als eigenes Gerät =begin html

readingsProxy

    Makes (a subset of) a reading from one device available as a new device.
    This can be used to map channels from 1-Wire, EnOcean or SWAP devices to independend devices that can have state,icons and webCmd different from the parent device and can be used in a floorplan.

    Define
      define <name> readingsProxy <device>:<reading>

      Examples:
        define myProxy readingsProxy myDS2406:latch.A

    Set

    Get

    Attributes
    • disable
      1 -> disable notify processing. Notice: this also disables rename and delete handling.
    • getList
      Space separated list of commands, which will be returned upon "get name ?", so the FHEMWEB frontend can construct a dropdown. %PARENT% will result in the complete list of commands from the parent device. get commands not in this list will be rejected.
    • setList
      Space separated list of commands, which will be returned upon "set name ?", so the FHEMWEB frontend can construct a dropdown and offer on/off switches. %PARENT% will result in the complete list of commands from the parent device. set commands not in this list will be rejected. Example: attr proxyName setList on off
    • readingFnAttributes
    • getFn
      perl expresion that will return the get command forwarded to the parent device. has access to $DEVICE, $READING, $CMD and $ARGS.
      undef -> do nothing
      "" -> pass through
      (<value>,1) -> directly return <value>, don't call parent getFn
      everything else -> use this instead
    • setFn
      perl expresion that will return the set command forwarded to the parent device. has access to $CMD, $DEVICE, $READING and $ARGS.
      undef -> do nothing
      "" -> pass through
      everything else -> use this instead
      Examples:
      attr myProxy setFn {($CMD eq "on")?"off":"on"}
    • valueFn
      perl expresion that will return the value that sould be used as state. has access to $LASTCMD, $DEVICE, $READING and $VALUE.
      undef -> do nothing
      "" -> pass through
      everything else -> use this instead
      Examples:
      attr myProxy valueFn {($VALUE == 0)?"off":"on"}

    • perlSyntaxCheck

=end html =cut