# $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 vars qw($FW_ME); use vars qw($FW_wname); use vars qw($FW_subdir); use vars qw(%FW_hiddenroom); use vars qw(%FW_visibleDeviceHash); use vars qw(%FW_webArgs); # all arguments specified in the GET sub readingsGroup_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "readingsGroup_Define"; $hash->{NotifyFn} = "readingsGroup_Notify"; $hash->{UndefFn} = "readingsGroup_Undefine"; #$hash->{SetFn} = "readingsGroup_Set"; $hash->{GetFn} = "readingsGroup_Get"; $hash->{AttrFn} = "readingsGroup_Attr"; $hash->{AttrList} = "disable:1,2,3 cacheHtml:1 nameIcon valueIcon mapping separator style nameStyle valueColumn valueColumns valueStyle valueFormat commands timestampStyle noheading:1 nolinks:1 nonames:1 notime:1 nostate:1 alwaysTrigger:1 sortDevices:1"; $hash->{FW_detailFn} = "readingsGroup_detailFn"; $hash->{FW_summaryFn} = "readingsGroup_detailFn"; $hash->{FW_atPageEnd} = 1; } sub readingsGroup_updateDevices($) { my ($hash) = @_; my %list; my %list2; my @devices; my @devices2; my @params = split(" ", $hash->{DEF}); while (@params) { my $param = shift(@params); while ($param && $param =~ m/^$/ ) { my $next = shift(@params); last if( !defined($next) ); $param .= " ". $next; } # for backwards compatibility with weblink readings if( $param eq '*noheading' ) { $attr{$hash->{NAME}}{noheading} = 1; $hash->{DEF} =~ s/(\s*)\\$param((:\S+)?\s*)/ /g; $hash->{DEF} =~ s/^ //; $hash->{DEF} =~ s/ $//; } elsif( $param eq '*notime' ) { $attr{$hash->{NAME}}{notime} = 1; $hash->{DEF} =~ s/(\s*)\\$param((:\S+)?\s*)/ /g; $hash->{DEF} =~ s/^ //; $hash->{DEF} =~ s/ $//; } elsif( $param eq '*nostate' ) { $attr{$hash->{NAME}}{nostate} = 1; $hash->{DEF} =~ s/(\s*)\\$param((:\S+)?\s*)/ /g; $hash->{DEF} =~ s/^ //; $hash->{DEF} =~ s/ $//; } elsif( $param =~ m/^{/) { $attr{$hash->{NAME}}{mapping} = $param ." ". join( " ", @params ); $hash->{DEF} =~ s/\s*[{].*$//g; last; } else { my @device = split(":", $param); if($device[0] =~ m/(.*)=(.*)/) { my ($lattr,$re) = ($1, $2); foreach my $d (sort keys %defs) { next if( IsIgnored($d) ); next if( !defined($defs{$d}{$lattr}) ); next if( $defs{$d}{$lattr} !~ m/^$re$/); $list{$d} = 1; push @devices, [$d,$device[1]]; } } elsif($device[0] =~ m/(.*)&(.*)/) { my ($lattr,$re) = ($1, $2); foreach my $d (sort keys %attr) { next if( IsIgnored($d) ); next if( !defined($attr{$d}{$lattr}) ); next if( $attr{$d}{$lattr} !~ m/^$re$/); $list{$d} = 1; push @devices, [$d,$device[1]]; } } elsif($device[0] =~ m/^<.*>$/) { push @devices, [$device[0]]; } elsif( defined($defs{$device[0]}) ) { $list{$device[0]} = 1; push @devices, [@device]; } else { foreach my $d (sort keys %defs) { next if( IsIgnored($d) ); eval { $d =~ m/^$device[0]$/ }; if( $@ ) { Log3 $hash->{NAME}, 3, $hash->{NAME} .": ". $device[0] .": ". $@; push @devices, ["<>"]; last; } next if( $d !~ m/^$device[0]$/); $list{$d} = 1; push @devices, [$d,$device[1]]; } } } } foreach my $device (@devices) { my $regex = $device->[1]; my @list = (undef); @list = split(",",$regex) if( $regex ); my $first = 1; my $multi = @list; for( my $i = 0; $i <= $#list; ++$i ) { my $regex = $list[$i]; while ($regex && $regex =~ m/^$/ && defined($list[++$i]) ) { $regex .= ",". $list[$i]; } next if( !$regex ); if( $regex =~ m/^<.*>$/ ) { # handle <{...}@reading>@device } elsif( $regex =~ m/(.*)@(.*)/ ) { $regex = $1; next if( $regex && $regex =~ m/^\+(.*)/ ); next if( $regex && $regex =~ m/^\?(.*)/ ); my $name = $2; if( $name =~ m/^{(.*)}$/ ) { my $DEVICE = $device->[0]; $name = eval $name; } next if( !$name ); next if( !defined($defs{$name}) ); $list2{$name} = 1; @devices2 = @devices if( !@devices2 ); my $found = 0; foreach my $device (@devices2) { $found = 1 if( $device->[0] eq $name && $device->[1] eq $regex ); last if $found; } next if $found; push @devices2, [$name,$regex]; } } } if( AttrVal( $hash->{NAME}, "sortDevices", 0 ) == 1 ) { @devices = sort { my $aa = @{$a}[0]; my $bb = @{$b}[0]; $aa = "#" if( $aa =~ m/^{CONTENT} = \%list; $hash->{DEVICES} = \@devices; $hash->{CONTENT2} = \%list2; delete $hash->{DEVICES2}; $hash->{DEVICES2} = \@devices2 if( @devices2 ); $hash->{fhem}->{last_update} = gettimeofday(); $hash->{fhem}->{lastDefChange} = $lastDefChange; } sub readingsGroup_Define($$) { my ($hash, $def) = @_; my @args = split("[ \t]+", $def); return "Usage: define readingsGroup +" if(@args < 3); my $name = shift(@args); my $type = shift(@args); readingsGroup_updateDevices($hash); $hash->{STATE} = 'Initialized'; return undef; } sub readingsGroup_Undefine($$) { my ($hash,$arg) = @_; return undef; } sub lookup($$$$$$$$$) { my($mapping,$name,$alias,$reading,$value,$room,$group,$row,$default) = @_; if( $mapping ) { if( ref($mapping) eq 'HASH' ) { $default = $mapping->{$name} if( defined($mapping->{$name}) ); $default = $mapping->{$reading} if( defined($mapping->{$reading}) ); $default = $mapping->{$name.".".$reading} if( defined($mapping->{$name.".".$reading}) ); $default = $mapping->{$reading.".".$value} if( defined($mapping->{$reading.".".$value}) ); #} elsif( $mapping =~ m/^{.*}$/) { # my $DEVICE = $name; # my $READING = $reading; # my $VALUE = $value; # $mapping = eval $mapping; # $default = $mapping if( $mapping ); } else { $default = $mapping; } return $default if( !defined($default) ); $default =~ s/\%ALIAS/$alias/g; $default =~ s/\%DEVICE/$name/g; $default =~ s/\%READING/$reading/g; $default =~ s/\%VALUE/$value/g; $default =~ s/\%ROOM/$room/g; $default =~ s/\%GROUP/$group/g; $default =~ s/\%ROW/$row/g; $default =~ s/\$ALIAS/$alias/g; $default =~ s/\$DEVICE/$name/g; $default =~ s/\$READING/$reading/g; $default =~ s/\$VALUE/$value/g; $default =~ s/\$ROOM/$room/g; $default =~ s/\$GROUP/$group/g; $default =~ s/\$ROW/$row/g; } return $default; } sub lookup2($$$$) { my($lookup,$name,$reading,$value) = @_; return $lookup if( !$lookup ); if( ref($lookup) eq 'HASH' ) { my $vf = ""; $vf = $lookup->{$reading} if( exists($lookup->{$reading}) ); $vf = $lookup->{$name.".".$reading} if( exists($lookup->{$name.".".$reading}) ); $vf = $lookup->{$reading.".".$value} if( defined($value) && exists($lookup->{$reading.".".$value}) ); $lookup = $vf; } if( !ref($lookup) && $lookup =~ m/^{.*}$/) { my $DEVICE = $name; my $READING = $reading; my $VALUE = $value; $lookup = eval $lookup; $lookup = "" if( $@ ); } return $lookup if( !defined($lookup) ); $lookup =~ s/\%DEVICE/$name/g; $lookup =~ s/\%READING/$reading/g; $lookup =~ s/\%VALUE/$value/g; $lookup =~ s/\$DEVICE/$name/g; $lookup =~ s/\$READING/$reading/g; $lookup =~ s/\$VALUE/$value/g; return $lookup; } sub readingsGroup_makeLink($$$) { my($v,$devStateIcon,$cmd) = @_; if( $cmd ) { my $txt = $v; $txt = $devStateIcon if( $devStateIcon ); my $link = "cmd=$cmd"; if( AttrVal($FW_wname, "longpoll", 1)) { $txt = "$txt"; } else { my $room = $FW_webArgs{room}; $room = "&detail=$FW_webArgs{detail}" if( $FW_webArgs{"detail"} ); my $srf = $room ? "&room=$room" : ""; $srf = $room if( $room && $room =~ m/^&/ ); $txt = "$txt"; } if( !$devStateIcon ) { $v = $txt; } else { $devStateIcon = $txt; } } return ($v, $devStateIcon); } sub readingsGroup_2html($) { my($hash) = @_; $hash = $defs{$hash} if( ref($hash) ne 'HASH' ); return undef if( !$hash ); #if( $hash->{fhem}->{cached} && $hash->{fhem}->{lastDefChange} && $hash->{fhem}->{lastDefChange} != $lastDefChange ) { # return $hash->{fhem}->{cached}; #} if( $hash->{DEF} =~ m/=/ ) { if( !$hash->{fhem}->{last_update} || $hash->{fhem}->{lastDefChange} != $lastDefChange || gettimeofday() - $hash->{fhem}->{last_update} > 600 ) { readingsGroup_updateDevices($hash); } } my $d = $hash->{NAME}; my $show_heading = !AttrVal( $d, "noheading", "0" ); my $show_links = !AttrVal( $d, "nolinks", "0" ); $show_links = 0 if($FW_hiddenroom{detail}); my $show_names = !AttrVal($d, "nonames", "0" ); my $disable = AttrVal($d,"disable", 0); if( AttrVal($d,"disable", 0) > 2 ) { return ""; } elsif( AttrVal($d,"disable", 0) > 1 ) { my $ret; $ret .= ""; my $txt = AttrVal($d, "alias", $d); $txt = "$txt" if( $show_links ); $ret .= "" if( $show_heading ); $ret .= ""; $ret .= "
$txt
"; #$ret .= ""; $ret .= ""; $ret .= "
disabled
"; return $ret; } my $show_state = !AttrVal( $d, "nostate", "0" ); my $show_time = !AttrVal( $d, "notime", "0" ); my $separator = AttrVal( $d, "separator", ":" ); my $style = AttrVal( $d, "style", "" ); if( $style =~ m/^{.*}$/ ) { my $s = eval $style; $style = $s if( $s ); } my $name_style = AttrVal( $d, "nameStyle", "" ); my $value_style = AttrVal( $d, "valueStyle", "" ); my $timestamp_style = AttrVal( $d, "timestampStyle", "" ); my $value_format = AttrVal( $d, "valueFormat", "" ); if( $value_format =~ m/^{.*}$/ ) { my $vf = eval $value_format; $value_format = $vf if( $vf ); } my $value_column = AttrVal( $d, "valueColumn", "" ); if( $value_column =~ m/^{.*}$/ ) { my $vc = eval $value_column; $value_column = $vc if( $vc ); } my $value_columns = AttrVal( $d, "valueColumns", "" ); if( $value_columns =~ m/^{.*}$/ ) { my $vc = eval $value_columns; $value_columns = $vc if( $vc ); } my $mapping = AttrVal( $d, "mapping", ""); $mapping = eval $mapping if( $mapping =~ m/^{.*}$/ ); #$mapping = undef if( ref($mapping) ne 'HASH' ); my $nameIcon = AttrVal( $d, "nameIcon", ""); $nameIcon = eval $nameIcon if( $nameIcon =~ m/^{.*}$/ ); #$nameIcon = undef if( ref($nameIcon) ne 'HASH' ); my $valueIcon = AttrVal( $d, "valueIcon", ""); $valueIcon = eval $valueIcon if( $valueIcon =~ m/^{.*}$/ ); #$valueIcon = undef if( ref($valueIcon) ne 'HASH' ); my $commands = AttrVal( $d, "commands", "" ); $commands = eval $commands if( $commands =~ m/^{.*}$/ ); my $devices = $hash->{DEVICES}; my $row = 1; my $ret; $ret .= ""; my $txt = AttrVal($d, "alias", $d); $txt = "$txt" if( $show_links ); $ret .= "" if( $show_heading ); $ret .= ""; $ret .= "
$txt
"; $ret .= "" if( $disable > 0 ); foreach my $device (@{$devices}) { my $item = 0; my $h = $defs{$device->[0]}; my $regex = $device->[1]; if( !$h && $device->[0] =~ m/^<.*>$/ ) { $h = $hash if( !$h ); $regex = $device->[0]; } next if( !$h ); my $name = $h->{NAME}; my $name2 = $h->{NAME}; my @list = (undef); @list = split(",",$regex) if( $regex ); my $first = 1; my $multi = @list; my $column = 1; #foreach my $regex (@list) { for( my $i = 0; $i <= $#list; ++$i ) { my $regex = $list[$i]; while ($regex && $regex =~ m/^$/ && defined($list[++$i]) ) { $regex .= ",". $list[$i]; } my $h = $h; if( $regex && $regex =~ m/^<(.*)>$/ ) { my $txt = $1; my $readings; $item++; if( $txt =~ m/^{(.*)}(@[\w\-|.*]+)?$/ ) { $txt = "{$1}"; $readings = $2; my $new_line = $first; my $DEVICE = $name; ($txt,$new_line) = eval $txt; $first = $new_line if( defined($new_line) ); if( $@ ) { $txt = ""; Log3 $d, 3, $d .": ". $regex .": ". $@; } next if( !defined($txt) ); } my $name_style = lookup2($name_style,$name,$1,undef); if( $txt eq 'br' ) { $ret .= sprintf("", ($row-1&1)?"odd":"even"); $ret .= ""; $first = 0; next; } elsif( $txt && $txt =~ m/^%([^%]*)(%(.*))?/ ) { my $icon = $1; my $cmd = $3; $txt = FW_makeImage( $icon, $icon, "icon" ); $cmd = lookup2($commands,$name,$d,$icon) if( !defined($cmd) ); ($txt,undef) = readingsGroup_makeLink($txt,undef,$cmd); if( $first || $multi == 1 ) { $ret .= sprintf("", ($row&1)?"odd":"even"); $row++; } } elsif( $first || $multi == 1 ) { $ret .= sprintf("", ($row&1)?"odd":"even"); $row++; if( $h != $hash ) { my $a = AttrVal($name2, "alias", $name2); my $m = "$a"; $m = $a if( $multi != 1 ); $m = "" if( !$show_names ); my $room = AttrVal($name2, "room", ""); my $group = AttrVal($name2, "group", ""); my $txt = lookup($mapping,$name2,$a,"","",$room,$group,$row,$m); $ret .= "" if( $show_names ); } } else { my $cmd = lookup2($commands,$name,$d,$txt); if( $cmd && $cmd =~ m/^([\w-]*):(\S*)?$/ ) { my $set = $1; my $values = $2; if( !$values ) { my %extPage = (); my ($allSets, undef, undef) = FW_devState($name, "", \%extPage); if( $allSets && $allSets =~ m/$set:([^ ]*)/) { $values = $1; } } my $room = $FW_webArgs{room}; $room = "&detail=$FW_webArgs{detail}" if( $FW_webArgs{"detail"} ); my $htmlTxt; foreach my $fn (sort keys %{$data{webCmdFn}}) { no strict "refs"; $htmlTxt = &{$data{webCmdFn}{$fn}}($FW_wname,$name,$room,$set,$values); use strict "refs"; last if(defined($htmlTxt)); } if( $htmlTxt =~ m/"; $first = 0; next; } else { if( $regex && $regex =~ m/(.*)@(.*)/ ) { $regex = $1; $name = $2; if( $name =~ m/^{(.*)}$/ ) { my $DEVICE = $device->[0]; $name = eval $name; } next if( !$name ); $h = $defs{$name}; next if( !$h ); } if( $regex && $regex =~ m/^\+(.*)/ ) { $regex = $1; } elsif( $regex && $regex =~ m/^\?(.*)/ ) { $regex = $1; $h = $attr{$name}; } else { $h = $h->{READINGS}; } } foreach my $n (sort keys %{$h}) { next if( $n =~ m/^\./); next if( $n eq "state" && !$show_state && (!defined($regex) || $regex ne "state") ); if( defined($regex) ) { eval { $n =~ m/^$regex$/ }; if( $@ ) { Log3 $name, 3, $name .": ". $regex .": ". $@; last; } next if( $n !~ m/^$regex$/); } my $val = $h->{$n}; my ($v, $t); if(ref($val)) { next if( ref($val) ne "HASH" || !defined($val->{VAL}) ); ($v, $t) = ($val->{VAL}, $val->{TIME}); $v = FW_htmlEscape($v); $t = "" if(!$t); $t = "" if( $multi != 1 ); } else { $v = FW_htmlEscape($val); } my $name_style = lookup2($name_style,$name,$n,$v); my $value_style = lookup2($value_style,$name,$n,$v); my $value_format = lookup2($value_format,$name,$n,$v); next if( !defined($value_format) ); if( $value_format =~ m/%/ ) { $v = sprintf( $value_format, $v ); } elsif( $value_format ) { $v = $value_format; } my $value_column = lookup2($value_column,$name,$n,undef); my $value_columns = lookup2($value_columns,$name,$n,$v); my $a = AttrVal($name2, "alias", $name2); my $m = "$a$separator$n"; $m = $a if( $multi != 1 ); my $room = AttrVal($name2, "room", ""); my $group = AttrVal($name2, "group", ""); my $txt = lookup($mapping,$name2,$a,($multi!=1?"":$n),$v,$room,$group,$row,$m); if( $nameIcon ) { if( my $icon = lookup($nameIcon,$name,$a,$n,$v,$room,$group,$row,"") ) { $txt = FW_makeImage( $icon, $txt, "icon" ); } } my $cmd; my $devStateIcon; if( $valueIcon ) { if( my $icon = lookup($valueIcon,$name,$a,$n,$v,$room,$group,$row,"") ) { if( $icon =~ m/^[\%\$]devStateIcon$/ ) { my %extPage = (); my ($allSets, $cmdlist, $txt) = FW_devState($name, $room, \%extPage); $devStateIcon = $txt; } else { $devStateIcon = FW_makeImage( $icon, $v, "icon" ); $cmd = lookup2($commands,$name,$n,$icon); $cmd = lookup2($commands,$name,$n,$v) if( !$cmd ); } } } my $webCmdFn = 0; if( !$devStateIcon ) { $cmd = lookup2($commands,$name,$n,$v) if( !$devStateIcon ); if( $cmd && $cmd =~ m/^([\w-]*):(\S*)?$/ ) { my $set = $1; my $values = $2; if( !$values ) { my %extPage = (); my ($allSets, undef, undef) = FW_devState($name, $room, \%extPage); if( $allSets && $allSets =~ m/$set:([^ ]*)/) { $values = $1; } } my $room = $FW_webArgs{room}; $room = "&detail=$FW_webArgs{detail}" if( $FW_webArgs{"detail"} ); my $htmlTxt; foreach my $fn (sort keys %{$data{webCmdFn}}) { no strict "refs"; $htmlTxt = &{$data{webCmdFn}{$fn}}($FW_wname,$name,$room,$set,$values); use strict "refs"; last if(defined($htmlTxt)); } if( $htmlTxt =~ m/", ($row&1)?"odd":"even"); $row++; } $txt = "$txt" if( $show_links ); $v = "
$v
" if( $value_style && !$devStateIcon ); $ret .= "" if( $show_names && ($first || $multi == 1) ); if( $value_column && $multi ) { while ($column < $value_column ) { $ret .= ""; ++$column; } } $ret .= "" if( $devStateIcon ); $ret .= "" if( !$devStateIcon ); $ret .= "" if( $show_time && $t ); $first = 0; ++$column; } } } $ret .= sprintf("", ($row&1)?"odd":"even"); $ret .= "" if( $disable > 0 ); $ret .= "
updates disabled
$txt
(.*)<\/td>/s ) { $txt = $1; my $a = AttrVal($name, "alias", $name); my $room = AttrVal($name, "room", ""); my $group = AttrVal($name, "group", ""); my $mapped = lookup($mapping,$name,$a,$set,"",$room,$group,$row,undef); if( defined($mapped) ) { $txt =~ s/$set /$mapped /; } } elsif( $htmlTxt ) { $txt = $htmlTxt; } } } my $inform_id = ""; $inform_id = "informId=\"$d-$name.i$item.item\"" if( $readings ); $ret .= "
$txt
(.*)<\/td>/s ) { $v = $1; my $mapped = lookup($mapping,$name,$a,$set,"",$room,$group,$row,undef); if( defined($mapped) ) { $v =~ s/$set /$mapped /; } $webCmdFn = 1; } elsif( $htmlTxt ) { $v = $htmlTxt; $webCmdFn = 1; } } } ($v,$devStateIcon) = readingsGroup_makeLink($v,$devStateIcon,$cmd) if( !$webCmdFn ); if( $first || $multi == 1 ) { $ret .= sprintf("
$txt
$devStateIcon
$v
$t
updates disabled
"; #$hash->{fhem}->{cached} = $ret; return $ret; } sub readingsGroup_detailFn() { my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn. my $hash = $defs{$d}; $hash->{mayBeVisible} = 1; return readingsGroup_2html($d); } sub readingsGroup_Notify($$) { my ($hash,$dev) = @_; my $name = $hash->{NAME}; if( grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}) ) { readingsGroup_updateDevices($hash); return undef; } elsif( grep(m/^REREADCFG$/, @{$dev->{CHANGED}}) ) { readingsGroup_updateDevices($hash); return undef; } return if( AttrVal($name,"disable", 0) > 0 ); return if($dev->{TYPE} eq $hash->{TYPE}); #return if($dev->{NAME} eq $name); my $devices = $hash->{DEVICES}; $devices = $hash->{DEVICES2} if( $hash->{DEVICES2} ); my $max = int(@{$dev->{CHANGED}}); for (my $i = 0; $i < $max; $i++) { my $s = $dev->{CHANGED}[$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; } #readingsGroup_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/ $//; } #readingsGroup_updateDevices($hash); } elsif( $dev->{NAME} eq "global" && $s =~ m/^DEFINED ([^ ]*)$/) { #readingsGroup_updateDevices($hash); } else { next if(AttrVal($name,"disable", undef)); next if (!$hash->{CONTENT}->{$dev->{NAME}} && !$hash->{CONTENT2}->{$dev->{NAME}}); if( $hash->{alwaysTrigger} ) { } elsif( !defined($hash->{mayBeVisible}) ) { Log3 $name, 5, "$name: not on any display, ignoring notify"; return undef; } else { if( defined($FW_visibleDeviceHash{$name}) ) { } else { Log3 $name, 5, "$name: no longer visible, ignoring notify"; delete( $hash->{mayBeVisible} ); return undef; } } my @parts = split(/: /,$s); my $reading = shift @parts; my $value = join(": ", @parts); $reading = "" if( !defined($reading) ); next if( $reading =~ m/^\./); $value = "" if( !defined($value) ); my $show_state = 1; if( $value eq "" ) { $show_state = !AttrVal( $name, "nostate", "0" ); #next if( !$show_state ); $reading = "state"; $value = $s; } my $value_style = AttrVal( $name, "valueStyle", "" ); my $value_format = AttrVal( $name, "valueFormat", "" ); if( $value_format =~ m/^{.*}$/ ) { my $vf = eval $value_format; $value_format = $vf if( $vf ); } my $valueIcon = AttrVal( $name, "valueIcon", ""); $valueIcon = eval $valueIcon if( $valueIcon =~ m/^{.*}$/ ); my $commands = AttrVal( $name, "commands", "" ); $commands = eval $commands if( $commands =~ m/^{.*}$/ ); foreach my $device (@{$devices}) { my $item = 0; my $h = $defs{@{$device}[0]}; next if( !$h ); next if( $dev->{NAME} ne $h->{NAME} ); my $n = $h->{NAME}; my $regex = @{$device}[1]; my @list = (undef); @list = split(",",$regex) if( $regex ); #foreach my $regex (@list) { for( my $i = 0; $i <= $#list; ++$i ) { my $regex = $list[$i]; while ($regex && $regex =~ m/^$/ && defined($list[++$i]) ) { $regex .= ",". $list[$i]; } next if( $reading eq "state" && !$show_state && (!defined($regex) || $regex ne "state") ); next if( $regex && $regex =~ m/^\+/ ); next if( $regex && $regex =~ m/^\?/ ); if( $regex && $regex =~ m/^<(.*)>$/ ) { my $txt = $1; my $readings; $item++; if( $txt =~ m/^{(.*)}(@([\w\-|.*]+))?$/ ) { $txt = "{$1}"; $readings = $3; next if( !$readings ); next if( $reading !~ m/^$readings$/); my $new_line; my $DEVICE = $n; ($txt,$new_line) = eval $txt; if( $@ ) { $txt = ""; Log3 $name, 3, $name .": ". $regex .": ". $@; } $txt = "" if( !defined($txt) ); if( $txt && $txt =~ m/^%([^%]*)(%(.*))?/ ) { my $icon = $1; my $cmd = $3; $cmd = lookup2($commands,$name,$n,$icon) if( !defined($cmd) ); $txt = FW_makeImage( $icon, $icon, "icon" ); ($txt,undef) = readingsGroup_makeLink($txt,undef,$cmd); } DoTrigger( $name, "$n.i$item.item: $txt" ); } next; } next if( defined($regex) && $reading !~ m/^$regex$/); my $value_style = lookup2($value_style,$n,$reading,$value); my $value = $value; if( $value_format ) { my $value_format = lookup2($value_format,$n,$reading,$value); if( !defined($value_format) ) { $value = ""; } elsif( $value_format =~ m/%/ ) { $value = sprintf( $value_format, $value ); } elsif( $value_format ) { $value = $value_format; } } my $cmd; my $devStateIcon; if( $valueIcon ) { my $a = AttrVal($n, "alias", $n); my $room = AttrVal($n, "room", ""); my $group = AttrVal($n, "group", ""); if( my $icon = lookup($valueIcon,$n,$a,$reading,$value,$room,$group,1,"") ) { if( $icon eq "%devStateIcon" ) { my %extPage = (); my ($allSets, $cmdlist, $txt) = FW_devState($n, $room, \%extPage); $devStateIcon = $txt; } else { $devStateIcon = FW_makeImage( $icon, $value, "icon" ); $cmd = lookup2($commands,$n,$reading,$icon); $cmd = lookup2($commands,$n,$reading,$value) if( !$cmd ); } } if( $devStateIcon ) { (undef,$devStateIcon) = readingsGroup_makeLink(undef,$devStateIcon,$cmd); DoTrigger( $name, "$n.$reading: $devStateIcon" ); next; } } $cmd = lookup2($commands,$n,$reading,$value); if( $cmd && $cmd =~ m/^(\w.*):(\S.*)?$/ ) { next; } ($value,undef) = readingsGroup_makeLink($value,undef,$cmd); $value = "
$value
" if( $value_style ); DoTrigger( $name, "$n.$reading: $value" ); } } } } return undef; } sub readingsGroup_Set($@) { my ($hash, $name, $cmd, $param, @a) = @_; my $list = "refresh:noArg"; if( $cmd eq "refresh" ) { readingsGroup_updateDevices($hash); return undef; } return "Unknown argument $cmd, choose one of $list"; } sub readingsGroup_Get($@) { my ($hash, @a) = @_; my $name = $a[0]; return "$name: get needs at least one parameter" if(@a < 2); my $cmd= $a[1]; my $ret = ""; if( $cmd eq "html" ) { return readingsGroup_2html($hash); } return undef; return "Unknown argument $cmd, choose one of html:noArg"; } sub readingsGroup_Attr($$$) { my ($cmd, $name, $attrName, $attrVal) = @_; my $orig = $attrVal; if( $attrName eq "alwaysTrigger" ) { my $hash = $defs{$name}; $attrVal = 1 if($attrVal); if( $cmd eq "set" ) { $hash->{alwaysTrigger} = $attrVal; } else { delete $hash->{alwaysTrigger}; } } elsif( $attrName eq "sortDevices" ) { if( $cmd eq "set" ) { $attrVal = 1 if($attrVal); $attr{$name}{$attrName} = $attrVal; } else { delete $attr{$name}{$attrName}; } my $hash = $defs{$name}; readingsGroup_updateDevices($hash); } if( $cmd eq "set" ) { if( $orig ne $attrVal ) { $attr{$name}{$attrName} = $attrVal; return $attrName ." set to ". $attrVal; } } return; } 1; =pod =begin html

readingsGroup

    Displays a collection of readings from on or more devices.

    Define
      define <name> readingsGroup <device>[:regex] [<device-2>[:regex-2]] ... [<device-n>[:regex-n]]

      Notes:
      • <device> can be of the form INTERNAL=VALUE where INTERNAL is the name of an internal value and VALUE is a regex.
      • <device> can be of the form <STRING> or <{perl}> where STRING or the string returned by perl is inserted as a line in the readings list. skipped if STRING is undef.
      • If regex is a comma separatet list the reading values will be shown on a single line.
      • If regex starts with a '+' it will be matched against the internal values of the device instead of the readings.
      • If regex starts with a '?' it will be matched against the attributes of the device instead of the readings.
      • regex can be of the form <regex>@device to use readings from a different device.
      • regex can be of the form <regex>@{perl} to use readings from a different device.
      • regex can be of the form <STRING> or <{perl}[@readings]> where STRING or the string returned by perl is inserted as a reading or:
        • the item will be skipped if STRING is undef
        • if STRING is br a new line will be started
        • if STRING is of the form %ICON[%CMD] ICON will be used as the name of an icon instead of a text and CMD as the command to be executed if the icon is clicked. also see the commands attribute.
        if readings is given the perl expression will be reevaluated during longpoll updates.
      • For internal values and attributes longpoll update is not possible. Refresh the page to update the values.
      • the <{perl}> expression is limited to expressions without a space. it is best just to call a small sub in 99_myUtils.pm instead of having a compex expression in the define.

      Examples:
        define batteries readingsGroup .*:battery

        define temperatures readingsGroup s300th.*:temperature
        define temperatures readingsGroup TYPE=CUL_WS:temperature

        define culRSSI readingsGroup cul_RSSI=.*:+cul_RSSI

        define heizung readingsGroup t1:temperature t2:temperature t3:temperature
        attr heizung notime 1
        attr heizung mapping {'t1.temperature' => 'Vorlauf', 't2.temperature' => 'R&uuml;cklauf', 't3.temperature' => 'Zirkulation'}
        attr heizung style style="font-size:20px"

        define systemStatus readingsGroup sysstat
        attr systemStatus notime 1
        attr systemStatus nostate 1
        attr systemStatus mapping {'load' => 'Systemauslastung', 'temperature' => 'Systemtemperatur in &deg;C'}

        define Verbrauch readingsGroup TYPE=PCA301:state,power,consumption
        attr Verbrauch mapping %ALIAS
        attr Verbrauch nameStyle style="font-weight:bold"
        attr Verbrauch style style="font-size:20px"
        attr Verbrauch valueFormat {power => "%.1f W", consumption => "%.2f kWh"}
        attr Verbrauch valueIcon { state => '%devStateIcon' }
        attr Verbrauch valueStyle {($READING eq "power" && $VALUE > 150)?'style="color:red"':'style="color:green"'}

        define rg_battery readingsGroup TYPE=LaCrosse:[Bb]attery
        attr rg_battery alias Batteriestatus
        attr rg_battery commands { "battery.low" => "set %DEVICE replaceBatteryForSec 60" }
        attr rg_battery valueIcon {'battery.ok' => 'batterie', 'battery.low' => 'batterie@red'}

        define rgMediaPlayer readingsGroup myMediaPlayer:currentTitle,<>,totaltime,
        ,currentAlbum,<>,currentArtist,
        ,volume,<{if(ReadingsVal($DEVICE,"playStatus","")eq"paused"){"%rc_PLAY%set+$DEVICE+play"}else{"%rc_PAUSE%set+$DEVICE+pause"}}@playStatus>,playStatus
        attr rgMediaPlayer commands { "playStatus.paused" => "set %DEVICE play", "playStatus.playing" => "set %DEVICE pause" }
        attr rgMediaPlayer mapping  
        attr rgMediaPlayer notime 1
        attr rgMediaPlayer valueFormat { "volume" => "Volume: %i" }
        #attr rgMediaPlayer valueIcon { "playStatus.paused" => "rc_PLAY", "playStatus.playing" => "rc_PAUSE" }


    Set

    Get

    Attributes
    • alwaysTrigger
      1 -> alwaysTrigger update events. even if not visible.
    • disable
      1 -> disable notify processing and longpoll updates. Notice: this also disables rename and delete handling.
      2 -> also disable html table creation
      3 -> also disable html creation completely
    • sortDevices
      1 -> sort the device lines alphabetically. use the first of sortby or alias or name that is defined for each device.
    • noheading
      If set to 1 the readings table will have no heading.
    • nolinks
      Disables the html links from the heading and the reading names.
    • nostate
      If set to 1 the state reading is excluded.
    • nonames
      If set to 1 the reading name / row title is not displayed.
    • notime
      If set to 1 the reading timestamp is not displayed.
    • mapping
      Can be a simple string or a perl expression enclosed in {} that returns a hash that maps reading names to the displayed name. The keys can be either the name of the reading or <device>.<reading>. %DEVICE, %ALIAS, %ROOM, %GROUP, %ROW and %READING are replaced by the device name, device alias, room attribute, group attribute and reading name respectively. You can also prefix these keywords with $ instead of %. Examples:
      attr temperatures mapping $DEVICE-$READING
      attr temperatures mapping {temperature => "%DEVICE Temperatur"}
    • separator
      The separator to use between the device alias and the reading name if no mapping is given. Defaults to ':' a space can be enteread as &nbsp;
    • style
      Specify an HTML style for the readings table, e.g.:
      attr temperatures style style="font-size:20px"
    • nameStyle
      Specify an HTML style for the reading names, e.g.:
      attr temperatures nameStyle style="font-weight:bold"
    • valueStyle
      Specify an HTML style for the reading values, e.g.:
      attr temperatures valueStyle style="text-align:right"
    • valueColumn
      Specify the minimum column in which a reading should appear.
      attr temperatures valueColumn { temperature => 2 }
    • valueColumns
      Specify an HTML colspan for the reading values, e.g.:
      attr wzReceiverRG valueColumns { eventdescription => 'colspan="4"' }
    • valueFormat
      Specify an sprintf style format string used to display the reading values. If the format string is undef this reading will be skipped. Can be given as a string, a perl expression returning a hash or a perl expression returning a string, e.g.:
      attr temperatures valueFormat %.1f °C
      attr temperatures valueFormat { temperature => "%.1f °C", humidity => "%.1f %" }
      attr temperatures valueFormat { ($READING eq 'temperature')?"%.1f °C":undef }
    • nameIcon
      Specify the icon to be used instead of the reading name. Can be a simple string or a perl expression enclosed in {} that returns a hash that maps reading names to the icon name. e.g.:
      attr devices nameIcon $DEVICE
    • valueIcon
      Specify an icon to be used instead of the reading value. Can be a simple string or a perl expression enclosed in {} that returns a hash that maps reading value to the icon name. e.g.:
      attr devices valueIcon $VALUE
      attr devices valueIcon {state => '%VALUE'}
      attr devices valueIcon {state => '%devStateIcon'} attr rgMediaPlayer valueIcon { "playStatus.paused" => "rc_PLAY", "playStatus.playing" => "rc_PAUSE" }
    • commands
      Can be used in to different ways:
      • To make a reading or icon clickable by directly specifying the command that should be executed. eg.:
        attr rgMediaPlayer commands { "playStatus.paused" => "set %DEVICE play", "playStatus.playing" => "set %DEVICE pause" }

      • Or if the mapped command is of the form <command>:[<modifier>] then the normal FHEMWEB webCmd widget for <modifier> will be used for this command. if <modifier> is omitted then the FHEMWEB lookup mechanism for <command> will be used. eg:
        attr rgMediaPlayer commands { volume => "volume:slider,0,1,100" }
        attr lights commands { pct => "pct:", dim => "dim:" }

    The style, nameStyle and valueStyle attributes can also contain a perl expression enclosed in {} that returns the style string to use. For nameStyle and valueStyle The perl code can use $DEVICE,$READING and $VALUE, e.g.:
      attr batteries valueStyle {($VALUE ne "ok")?'style="color:red"':'style="color:green"'}
      attr temperatures valueStyle {($DEVICE =~ m/aussen/)?'style="color:green"':'style="color:red"'}
    Note: Only valueStyle, valueFomat, valueIcon and <{...}@reading> are evaluated during longpoll updates and valueStyle has to return a non empty style for every possible value. All other perl expressions are evaluated only once during html creation and will not reflect value updates with longpoll. Refresh the page to update the dynamic style. For nameStyle the color attribut is not working at the moment, the font-... and background attributes do work.
=end html =cut