# $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(%modules); use vars qw(%defs); use vars qw(%attr); use vars qw($init_done); use vars qw($lastDefChange); sub Log($$); sub Log3($$$); use vars qw(%data); 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 my @mapping_attrs = qw( commands:textField-long mapping:textField-long nameIcon:textField-long cellStyle:textField-long nameStyle:textField-long valueColumn:textField-long valueColumns:textField-long valueFormat:textField-long valuePrefix:textField-long valueSuffix:textField-long valueIcon:textField-long valueStyle:textField-long ); 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 style timestampStyle ". join( " ", @mapping_attrs ) ." separator nolinks:1 noheading:1 nonames:1 notime:1 nostate:1 firstCalcRow:1,2,3,4 alwaysTrigger:1,2 sortDevices:1 sortFn visibility:hidden,hideable,collapsed,collapsible setList setFn:textField-long headerRows sortColumn"; $hash->{FW_detailFn} = "readingsGroup_detailFn"; $hash->{FW_summaryFn} = "readingsGroup_detailFn"; $data{FWEXT}{"readingsGroup"}{SCRIPT} = "fhemweb_readingsGroup.js"; $hash->{FW_atPageEnd} = 1; } sub readingsGroup_updateDevices($;$) { my ($hash,$def) = @_; $def = $hash->{helper}{DEF} if( !defined($def) ); $hash->{helper}{DEF} = $def; $def = $hash->{DEF} if( !defined($def) ); my %list; my %list2; my @devices; my @devices2; my @params = split(" ", $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, 2); # 2 -> to allow : in calc expressions if( $device[1] && $device[1] =~ m/^FILTER=/ ) { my @device = split(":", $param); # split all to get multiple FILTER but exclude the : before the readings my $devspec = shift(@device); while( @device && $device[0] =~ m/^FILTER=/ ) { $devspec .= ":"; $devspec .= shift(@device); } my $regex = join(':', @device); # merge the rest back again foreach my $d (devspec2array($devspec)) { $list{$d} = 1; push @devices, [$d,$regex]; } } elsif($device[0] =~ m/^<.*>$/) { push @devices, [$device[0]]; } elsif($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( $lattr ne 'IODev' && $defs{$d}{$lattr} !~ m/^$re$/); next if( $lattr eq 'IODev' && $defs{$d}{$lattr}{NAME} && $defs{$d}{$lattr}{NAME} !~ 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( 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 = ('.*'); @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/^$/) #handle , in <...> || ($regex =~ m/@\{/ && $regex !~ m/\}$/) #handle , in reading@{...} || ($regex =~ m/^\$.*\(/ && $regex !~ m/\)/) ) #handle , in $(...) && defined($list[++$i]) ) { $regex .= ",". $list[$i]; } next if( !$regex ); if( $regex =~ m/^<.*>$/ ) { } elsif( $regex !~ m/^\$/ && $regex =~ m/(.*)@(.*)/ ) { $regex = $1; my $name = $2; next if( $regex && $regex =~ m/^\+(.*)/ ); next if( $regex && $regex =~ m/^\?(.*)/ ); if( $name =~ m/^\{(.*)\}$/s ) { 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); $hash->{STATE} = 'Initialized'; if( $init_done ) { readingsGroup_updateDevices($hash); $hash->{fhem}->{lastDefChange} = $lastDefChange+1; readingsGroup_inithtml($hash); } return undef; } sub readingsGroup_Undefine($$) { my ($hash,$arg) = @_; return undef; } sub rgVal2Num($) { my ($val) = @_; return $val if( !defined($val) ); #$val =~ s/[^-\.\d]//g if( defined($val) ); $val = ($val =~ /(-?\d+(\.\d+)?)/ ? $1 : ""); return $val; } sub lookup($$$$$$$$$) { my($mapping,$name,$alias,$reading,$value,$room,$group,$row,$default) = @_; if( $mapping ) { if( !ref($mapping) && $mapping =~ m/^\{.*\}$/s) { my $DEVICE = $name; my $READING = $reading; my $VALUE = $value; my $NUM = rgVal2Num($value); my $ROW = $row; my $m = eval $mapping; if( $@ ) { Log 2, $@ if( $@ ); } else { $mapping = $m; } } 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"}) ); $default = $mapping->{"$name.$reading.$value"} if( defined($mapping->{"$name.$reading.$value"}) ); } else { $default = $mapping; } return $default if( !defined($default) ); if( !ref($default) && $default =~ m/^\{.*\}$/s) { my $DEVICE = $name; my $READING = $reading; my $VALUE = $value; my $NUM = rgVal2Num($value); my $ROW = $row; $default = eval $default; $default = "" if( $@ ); Log 2, $@ if( $@ ); } 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,$row,$column) = @_; return "" if( !$lookup ); if( !ref($lookup) && $lookup =~ m/^\{.*\}$/s) { my $DEVICE = $name; my $READING = $reading; my $VALUE = $value; my $NUM = rgVal2Num($value); my $ROW = $row; my $COLUMN = $column; my $l = eval $lookup; if( $@ ) { Log 2, $@ if( $@ ); } else { $lookup = $l; } } if( ref($lookup) eq 'HASH' ) { my $vf = ""; $vf = $lookup->{""} if( defined( $lookup->{""} ) ); $vf = $lookup->{$reading} if( defined($reading) && exists($lookup->{$reading}) ); $vf = $lookup->{"$name.$reading"} if( defined($reading) && exists($lookup->{"$name.$reading"}) ); $vf = $lookup->{"$reading.$value"} if( defined($value) && exists($lookup->{"$reading.$value"}) ); $vf = $lookup->{"$name.$reading.$value"} if( defined($value) && exists($lookup->{"$name.$reading.$value"}) ); $vf = $lookup->{"r:$row"} if( defined($row) && exists($lookup->{"r:$row"}) ); $vf = $lookup->{"c:$column"} if( defined($column) && exists($lookup->{"c:$column"}) ); $vf = $lookup->{"r:$row,c:$column"} if( defined($row) && defined($column) && exists($lookup->{"r:$row,c:$column"}) ); $lookup = $vf; } return undef if( !defined($lookup) ); if( !ref($lookup) && $lookup =~ m/^\{.*\}$/s) { my $DEVICE = $name; my $READING = $reading; my $VALUE = $value; my $NUM = rgVal2Num($value); my $ROW = $row; my $COLUMN = $column; $lookup = eval $lookup; $lookup = "" if( $@ ); Log 2, $@ if( $@ ); } return undef 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 $fw_room = $FW_webArgs{room}; $fw_room = "&detail=$FW_webArgs{detail}" if( $FW_webArgs{"detail"} ); my $srf = $fw_room ? "&room=$fw_room" : ""; $srf = $fw_room if( $fw_room && $fw_room =~ m/^&/ ); $txt = "$txt"; } if( !$devStateIcon ) { $v = $txt; } else { $devStateIcon = $txt; } } return ($v, $devStateIcon); } package readingsGroup; sub rgCount($$) { my ($val,$values) = @_; my $count = 0; if( $val =~ m/^\/(.*)\/$/ ) { my $regex = $1; foreach my $v (@{$values}) { ++$count if( $v =~ m/$regex/ ); } } elsif( $val =~ m/^!(.*)/ ) { my $val = $1; foreach my $v (@{$values}) { ++$count if( $v ne $val ); } } else { foreach my $v (@{$values}) { ++$count if( $v eq $val ); } } return $count; } use List::Util qw(min max sum); sub rgCalc($$$$) { my ($hash,$calc,$cell_row,$cell_column) = @_; my $name = $hash->{NAME}; return undef if( !defined($hash->{helper}{values}) ); my $args; my $cells; # format: $[()][@] if( $calc =~ m/([^@\(]*)(\(([^\(]*)\))?(\(([^\(]*)\))?(@(.*))?/ ) { $calc = $1; $cells = $5; $args = $3 if( defined($cells) ); $cells = $3 if( !defined($cells) ); } my $firstCalcRow = main::AttrVal($name, "firstCalcRow", 1); $cells = '$firstCalcRow..$ROW-1' if( !$cells ); my @values = (); foreach my $cell ( split( ';', $cells ) ) { my ($rows,$cols) = split( ':', $cell ); $rows = '$firstCalcRow..$ROW-1' if( !$rows ); $cols = $cell_column if( !defined($cols) ); my $ROW = $cell_row; my $COLUMN = $cell_column; foreach my $col (eval "($cols)") { foreach my $row (eval "($rows)") { my $value = $hash->{helper}{values}{orig}[$col][$row]; if( defined($value) && $value ne '-' ) { #$value =~ s/[^-\.\d]//g; push @values, $value; } if( ${hash}->{inDetailFn} ) { #FIXME: also add indirect cells $hash->{helper}{recalc}[$col][$row] .= "," if( $hash->{helper}{recalc}[$col][$row] ); $hash->{helper}{recalc}[$col][$row] .= "$cell_row:$cell_column"; } } } } if( $calc eq 'avg' ) { my $cnt = scalar @values; return undef if( !$cnt ); return ( sum @values ) / $cnt; } elsif( $calc eq 'count' ) { return rgCount( $args, \@values ); } return eval $calc .' @values'; } package main; sub readingsGroup_value2html($$$$$$$$$) { my ($hash,$calc,$name,$name2,$n,$v,$cell_row,$cell_column,$type) = @_; my $d = $hash->{NAME}; my $informid = "informId=\"$d-$name.$n\""; my $value_orig = $v; if( $calc ) { $v = readingsGroup::rgCalc($hash,$calc,$cell_row,$cell_column); $hash->{helper}{values}{calc}[$cell_column][$cell_row] = $calc; $informid = "informId=\"$d-calc:$cell_row:$cell_column\""; $value_orig = $v; $v = "" if( !defined($v) ); } my $value_format = lookup2($hash->{helper}{valueFormat},$name,$n,$v,$cell_row,$cell_column); return (undef) if( !defined($value_format) ); if( $value_format =~ m/%/ ) { $v = sprintf( $value_format, $v ); } elsif( $value_format ne "" ) { $v = $value_format; } my $value_formated = $v; my $room = AttrVal($name2, "room", ""); my $alias = AttrVal($name2, "alias", $name2); my $group = AttrVal($name2, "group", ""); my $cmd; my $devStateIcon; if( my $value_icon = $hash->{helper}{valueIcon} ) { if( my $icon = lookup($value_icon,$name,$alias,$n,$value_formated,$room,$group,$cell_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($hash->{helper}{commands},$name,$n,$icon); $cmd = lookup2($hash->{helper}{commands},$name,$n,$value_formated) if( !$cmd ); } } } my $webCmdFn = 0; if( !$devStateIcon ) { $cmd = lookup2($hash->{helper}{commands},$name,$n,$value_formated); if( $cmd && $cmd =~ m/^([\w\/.-]*):(\S*)?(\s\S*)?$/ ) { my $set = $1; my $values = $2; $set .= $3 if( $3 ); if( !$values ) { my %extPage = (); my ($allSets, undef, undef) = FW_devState($name, $room, \%extPage); $allSets = getAllAttr($name) if( $type && $type eq 'attr' ); my ($set) = split( ' ', $set, 2 ); if( $allSets && $allSets =~ m/\b$set:([^ ]*)/) { $values = $1; } } my $fw_room = $FW_webArgs{room}; $fw_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,$fw_room,$set,$values); use strict "refs"; last if(defined($htmlTxt)); } if( $htmlTxt && $htmlTxt =~ m/^(.*)<\/td>$/ ) { $htmlTxt = $1; } if( $htmlTxt && $htmlTxt =~ m/class='fhemWidget'/ ) { $htmlTxt =~ s/class='fhemWidget'/class='fhemWidget' informId='$d-$name.$n'/; $informid = ""; my $txt = lookup($hash->{helper}{mapping},$name,$name,$n,"",$room,$group,$cell_row,undef); if( defined($txt) ) { $informid = "rg-fhemWidget-label=\"$txt\""; } } if( $htmlTxt ) { if( $type && $type eq 'attr' ) { my $current = AttrVal( $name, $n, 'unknown' ); $htmlTxt =~ s/cmd=/type='attr' cmd=/; $htmlTxt =~ s/current='[^']*'/current='$current'/; } $v = $htmlTxt; $webCmdFn = 1; } } } ($v,$devStateIcon) = readingsGroup_makeLink($v,$devStateIcon,$cmd) if( !$webCmdFn ); if( my $value_prefix = $hash->{helper}{valuePrefix} ) { if( my $value_prefix = lookup2($value_prefix,$name,$n,$value_formated) ) { $v = $value_prefix . $v; $devStateIcon = $value_prefix . $devStateIcon if( $devStateIcon ); } } if( my $value_suffix = $hash->{helper}{valueSuffix} ) { if( my $value_suffix = lookup2($value_suffix,$name,$n,$value_formated) ) { $v .= $value_suffix; $devStateIcon .= $value_suffix if( $devStateIcon ); } } my $value_prefix_suffix = $v; $hash->{helper}{values}{orig}[$cell_column][$cell_row] = $value_orig; $hash->{helper}{values}{formated}[$cell_column][$cell_row] = $value_formated; $hash->{helper}{values}{prefixsuffix}[$cell_column][$cell_row] = $value_prefix_suffix; my $value_style = lookup2($hash->{helper}{valueStyle},$name,$n,$value_orig,$cell_row,$cell_column); $v = "
$v
" if( $value_style && !$devStateIcon ); return($informid,$v,$devStateIcon) } sub readingsGroup_inithtml($) { my($hash) = @_; if( $hash->{alwaysTrigger} && $hash->{alwaysTrigger} > 1 ) { ${hash}->{inDetailFn} = 1; readingsGroup_2html($hash); delete ${hash}->{inDetailFn}; } } sub readingsGroup_2html($;$) { my($hash,$extPage) = @_; $hash = $defs{$hash} if( ref($hash) ne 'HASH' ); $FW_ME = "" if( !$FW_ME ); $FW_subdir = "" if( !$FW_subdir ); return undef if( !$hash ); return undef if( !$init_done ); #if( $hash->{fhem}->{cached} && $hash->{fhem}->{lastDefChange} && $hash->{fhem}->{lastDefChange} == $lastDefChange ) { # return $hash->{fhem}->{cached}; #} my $def = $hash->{helper}{DEF}; $def = $hash->{DEF} if( !defined($def) ); if( $def && $def =~ m/=/ || $hash->{fhem}->{lastDefChange} != $lastDefChange ) { if( !$hash->{fhem}->{last_update} || $hash->{fhem}->{lastDefChange} != $lastDefChange || gettimeofday() - $hash->{fhem}->{last_update} > 600 ) { readingsGroup_updateDevices($hash); } } delete( $hash->{helper}{recalc} ); delete( $hash->{helper}{values} ); delete( $hash->{helper}{positions} ); my $d = $hash->{NAME}; my $show_links = !AttrVal( $d, "nolinks", "0" ); $show_links = 0 if($FW_hiddenroom{detail}); my $show_heading = !AttrVal( $d, "noheading", "0" ); 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_time = !AttrVal( $d, "notime", "0" ); my $show_state = !AttrVal( $d, "nostate", "0" ); my $separator = AttrVal( $d, "separator", ":" ); my $style = AttrVal( $d, "style", "" ); if( $style =~ m/^\{.*\}$/s ) { my $s = eval $style; $style = $s if( $s ); } my $timestamp_style = AttrVal( $d, "timestampStyle", "" ); my $header_rows = AttrVal( $d, 'headerRows', 0 ); my $in_footer = 0; my $sort_column = AttrVal( $d, 'sortColumn', undef ); my $devices = $hash->{DEVICES}; my $group; $group = $extPage->{group} if( $extPage ); $group = AttrVal( $d, "group", undef ) if( !$group ); $group = "" if( !$group ); $group =~ s/,/_/g; my $show_hide = ""; my $visibility = AttrVal($d, "visibility", undef ); if( !$FW_webArgs{"detail"} ) { if( $visibility && ( $visibility eq "hidden" || $visibility eq "hideable" ) ) { $style = 'style=""' if( !$style ); $style =~ s/style=(.)/style=$1display:none;/ if( $visibility eq "hidden" ); $show_hide .= ">"; } } my $row = 1; my $cell_row = 1; my $ret; $ret .= ""; my $txt = AttrVal($d, "alias", $d); $txt = "$txt" if( $show_links ); $ret .= "" if( $show_heading ); $ret .= ""; $ret .= "
$show_hide $txt
"; $ret .= "" if( $header_rows ); $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}; #FIXME: name/name2 confusion my $name2 = $h->{NAME}; delete $hash->{groupedList}; my @list = ('.*'); @list = split(",",$regex) if( $regex ); if( @list && $list[0] =~ m/^@(.*)/ ) { my $index = $1; my $regex = $list[$index]; if( $regex && $regex =~ m/^r:(.*)/ ) { $regex = $1; } my @l; foreach my $n (keys %{$h->{READINGS}}) { eval { $n =~ m/^$regex$/ }; if( $@ ) { Log3 $name, 3, $name .": ". $regex .": ". $@; last; } next if( $n !~ m/^$regex$/); push @l, [$n, $1, $2, $3, $4]; } if( my $sortFn = AttrVal($d, "sortFn", '') ) { sub rgSortIP { return inet_aton(@{$a}[1]) cmp inet_aton(@{$b}[1]); }; @l = eval "sort { $sortFn } \@l"; if( $@ ) { $txt = ""; Log3 $d, 3, $d .": ". $regex .": ". $@; next; } } else { sub rgSort { return @{$a}[1] cmp @{$b}[1]; }; @l = sort rgSort @l; } $hash->{groupedList} = []; foreach my $n (@l) { my $cg1 = @{$n}[1]; my $cg2 = @{$n}[2]; my $cg3 = @{$n}[3]; my $cg4 = @{$n}[4]; my @l = @list[1..@list-1]; $l[$index-1] = @{$n}[0]; s/#1/$cg1/ for @l; s/#2/$cg2/ for @l; s/#3/$cg3/ for @l; s/#4/$cg4/ for @l; push @{$hash->{groupedList}}, '' if( $hash->{groupedList} ); push @{$hash->{groupedList}}, @l; } @list = @{$hash->{groupedList}} if( $hash->{groupedList} ); } my $first = 1; my $multi = @list; my $cell_column = 1; for( my $i = 0; $i <= $#list; ++$i ) { my $name = $name; my $name2 = $name2; my $regex = $list[$i]; while ($regex && ( ($regex =~ m/^$/) #handle , in <...> || ($regex =~ m/@\{/ && $regex !~ m/\}$/) #handle , in reading@{...} || ($regex =~ m/^\$.*\(/ && $regex !~ m/\)/) ) #handle , in $(...) && defined($list[++$i]) ) { $regex .= ",". $list[$i]; } $item++; my $h = $h; my $type; my $force_show = 0; my $calc; my $format; if( $regex && $regex =~ m/^<(.*)>$/ ) { my $txt = $1; my $readings; 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 $cell_style0 = lookup2($hash->{helper}{cellStyle},$name,$1,undef,$cell_row,0); my $cell_style = lookup2($hash->{helper}{cellStyle},$name,$1,undef,$cell_row,$cell_column); my $name_style = lookup2($hash->{helper}{nameStyle},$name,$1,undef,$cell_row,$cell_column); my $value_columns = lookup2($hash->{helper}{valueColumns},$name,$1,undef,$cell_row,$cell_column); my $row_style = lookup2($hash->{helper}{rowStyle},$name,$1,undef,$cell_row,undef); if( !$FW_webArgs{"detail"} ) { if( $visibility && $visibility eq "collapsed" && $txt ne '-' && $txt ne '+' && $txt ne '+-' ) { $row_style = 'style=""' if( !$row_style ); $row_style =~ s/style=(.)/style=$1display:none;/; } } $row++ if( $txt eq 'br2' ); if( $txt eq 'br' || $txt eq 'br2' ) { $ret .= sprintf("", ($row-1&1)?"odd":"even"); $ret .= "" if( $show_names ); $first = 0; ++$cell_row; $cell_column = 1; next; } elsif( $txt eq 'hr' ) { $ret .= sprintf("", ($row&1)?"odd":"even"); $row++; $ret .= ""; next; } elsif( $txt eq 'tfoot' ) { $ret .= "" if( $header_rows && !$in_footer ); $ret .= "" if( !$in_footer ); $in_footer = 1; next; } elsif( $txt eq '-' || $txt eq '+' || $txt eq '+-' ) { my $collapsed = $visibility && ( $visibility eq "collapsed" ) && !$FW_webArgs{"detail"}; my $id = ''; if( ($txt eq '+' && !$collapsed) || ($txt eq '-' && $collapsed ) ) { $id = ''; $row_style = 'style=""' if( !$row_style ); $row_style =~ s/style=(.)/style=$1display:none;/; } elsif( $txt eq '+-' ) { if( $collapsed ) { $txt = '+'; } else { $txt = '-'; } $id = "id='plusminus'"; } elsif( $txt ne '+' && $collapsed ) { $row_style = 'style=""' if( !$row_style ); $row_style =~ s/style=(.)/style=$1display:none;/; } $ret .= sprintf("", ($row-1&1)?"odd":"even") if( $first ); if( $visibility && ( $visibility eq "collapsed" || $visibility eq "collapsible" ) ) { $ret .= ""; } else { $ret .= ""; } $first = 0; ++$cell_column; next; } elsif( $txt && $txt =~ m/^%([^%]*)(%(.*))?/ ) { my $icon = $1; my $cmd = $3; $txt = FW_makeImage( $icon, $icon, "icon" ); $cmd = lookup2($hash->{helper}{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($hash->{helper}{mapping},$name2,$a,"","",$room,$group,$cell_row,$m); $ret .= "" if( $show_names ); } } else { my $webCmdFn = 0; my $cmd = lookup2($hash->{helper}{commands},$name,$d,$txt); if( $cmd && $cmd =~ m/^([\w\/.-]*):(\S*)?(\s\S*)?$/ ) { my $set = $1; my $values = $2; $set .= $3 if( $3 ); if( !$values ) { my %extPage = (); my ($allSets, undef, undef) = FW_devState($name, "", \%extPage); my ($set) = split( ' ', $set, 2 ); if( $allSets && $allSets =~ m/\b$set:([^ ]*)/) { $values = $1; } } my $fw_room = $FW_webArgs{room}; $fw_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,$fw_room,$set,$values); use strict "refs"; last if(defined($htmlTxt)); } if( $htmlTxt && $htmlTxt =~ m/^"; $first = 0; ++$cell_column; next; } else { if( $regex && $regex !~ m/^\$/ && $regex =~ m/(.*)@([!]?)(.*)/ ) { $regex = $1; my $force_device = $2; $name = $3; if( $name =~ m/^\{(.*)\}$/s ) { my $DEVICE = $device->[0]; $name = eval $name; } next if( !$name ); $h = $defs{$name}; next if( !$h && !$force_device ); } $force_show = 0; $type = undef; $calc = undef; $format = ""; my $modifier = ""; if( $regex ) { if( $regex =~ m/^([ira]):(.*)/ ) { $modifier = $1; $regex = $2; } if( $regex =~ m/^([+?!\$]*)(.*)/ ) { $modifier .= $1; $regex = $2; } if( $regex =~ m/^(.*):(t|sec|i|d|r|r\d)$/ ) { $regex = $1; $format = $2; } } if( $modifier =~ m/[i+]/ ) { } elsif( $modifier =~ m/[a?]/ ) { $type = 'attr'; $h = $attr{$name}; } else { $h = $h->{READINGS} if( $h ); } if( $modifier =~ m/\$/ ) { $force_show = 1; $h = undef; $calc = $regex; $name = $d; # format: $[()][@] if( $calc =~ m/([^@\(]*)(\(([^\(]*)\))?(\(([^\(]*)\))?(@(.*))?/ ) { $regex = $7; $regex = $1 if( !defined($regex) ); } } $force_show = 1 if( $modifier =~ m/\!/ ); } my @keys = keys %{$h}; push (@keys, $regex) if( $force_show && (!@keys || !defined($h->{$regex}) ) ); foreach my $n (sort @keys) { #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}); if( $format eq 't' || $format eq 'sec' ) { $v = $t; $v = time() - time_str2num($v) if($format eq 'sec'); } $t = "" if(!$t); $t = "" if( $multi != 1 ); } else { $v = $val; $v = $n if( !$val && $force_show ); } if( $format =~ m/^[dir]/ ) { $v = rgVal2Num($v); $v = int($v) if( $format eq 'i' ); $v = round($v, defined($1) ? $1 : 1) if($format =~ /^r(\d)?/); } $v = FW_htmlEscape($v); my($informid,$devStateIcon); ($informid,$v,$devStateIcon) = readingsGroup_value2html($hash,$calc,$name,$name2,$n,$v,$cell_row,$cell_column,$type); next if( !defined($informid) ); #$informid = "informId=\"$d-item:$cell_row:$item\"" if( $format ); $informid =~ s/"$/:$format"/ if( $format ); my $cell_style0 = lookup2($hash->{helper}{cellStyle},$name,$n,$v,$cell_row,0); my $cell_style = lookup2($hash->{helper}{cellStyle},$name,$n,$v,$cell_row,$cell_column); my $name_style = lookup2($hash->{helper}{nameStyle},$name,$n,$v,$cell_row,$cell_column); #my $value_style = lookup2($hash->{helper}{valueStyle},$name,$n,$v,$cell_row,$cell_column); my $row_style = lookup2($hash->{helper}{rowStyle},$name,$n,$v,$cell_row,undef); if( !$FW_webArgs{"detail"} ) { if( $visibility && $visibility eq "collapsed" ) { $row_style = 'style=""' if( !$row_style ); $row_style =~ s/style=(.)/style=$1display:none;/; } } my $value_columns = lookup2($hash->{helper}{valueColumns},$name,$n,$v); if( $first || $multi == 1 ) { $ret .= sprintf("", ($row&1)?"odd":"even"); $row++; if( $show_names ) { my $room = AttrVal($name2, "room", ""); my $alias = AttrVal($name2, "alias", $name2); my $group = AttrVal($name2, "group", ""); my $m = "$alias$separator$n"; $m = $alias if( $multi != 1 ); my $txt = lookup($hash->{helper}{mapping},$name2,$alias,($multi!=1?"":$n),$v,$room,$group,$cell_row,$m); if( my $name_icon = $hash->{helper}{nameIcon} ) { if( my $icon = lookup($name_icon ,$name,$alias,$n,$v,$room,$group,$cell_row,"") ) { $txt = FW_makeImage( $icon, $txt, "icon" ); } } $txt = "
$txt
" if( !$show_links ); $txt = "$txt" if( defined($txt) && $show_links ); $ret .= "" if( defined($txt) ); } } my $value_column = lookup2($hash->{helper}{valueColumn},$name,$n,undef); if( $value_column && $multi ) { while ($cell_column < $value_column ) { $ret .= ""; ++$cell_column; } } $ret .= "" if( $devStateIcon ); $ret .= "" if( !$devStateIcon ); $ret .= "" if( $show_time && $t ); if( !$calc ) { $hash->{helper}{positions}{"$name.$n"} .= "," if( $hash->{helper}{positions}{"$name.$n"} ); $hash->{helper}{positions}{"$name.$n"} .= "$cell_row:$cell_column"; } $first = 0; ++$cell_column; } } if( $cell_row == $header_rows ) { $ret .= ""; $ret .= ""; } ++$cell_row; } $ret .= "" if( $header_rows && !$in_footer ); if( $disable > 0 ) { $ret .= "" if( !$in_footer ); $in_footer = 1; $ret .= sprintf("", ($row&1)?"odd":"even"); $ret .= ""; } $ret .= "" if( $in_footer ); $ret .= "
updates disabled

$txt
$txt
$txt
(.*)<\/td>$/ ) { $htmlTxt = $1; } if( $htmlTxt ) { $txt = $htmlTxt; $webCmdFn = 1; } } ($txt,undef) = readingsGroup_makeLink($txt,undef,$cmd) if( !$webCmdFn ); } my $informid = ""; $informid = "informId=\"$d-item:$cell_row:$item\"" if( $readings ); $ret .= "
$txt
$txt
$devStateIcon
$v
$t
updates disabled
"; #$hash->{fhem}->{cached} = $ret; return $ret; } sub readingsGroup_detailFn() { my ($FW_wname, $d, $room, $extPage) = @_; # extPage is set for summaryFn. my $hash = $defs{$d}; return undef if( ${hash}->{inDetailFn} ); $hash->{mayBeVisible} = 1; ${hash}->{inDetailFn} = 1; my $html = readingsGroup_2html($d,$extPage); delete ${hash}->{inDetailFn}; return $html; } sub readingsGroup_Update($$$) { my ($hash, $item, $value) = @_; my $name = $hash->{NAME}; $hash->{changed} = 1; if( $hash->{alwaysTrigger} ) { DoTrigger( $name, "$item: $value" ); } else { foreach my $ntfy (values(%defs)) { next if(!$ntfy->{TYPE} || $ntfy->{TYPE} ne "FHEMWEB" || !$ntfy->{inform} || !$ntfy->{inform}{devices}{$name} || $ntfy->{inform}{type} ne "status"); next if( !$ntfy->{inform}{devices}{$name} ); if(!FW_addToWritebuffer($ntfy, FW_longpollInfo($ntfy->{inform}{fmt}, "$name-$item", $value, $value ) ."\n" )) { my $name = $ntfy->{NAME}; Log3 $name, 4, "Closing connection $name due to full buffer in FW_Notify"; TcpServer_Close($ntfy, 1); } if(!FW_addToWritebuffer($ntfy, FW_longpollInfo($ntfy->{inform}{fmt}, "$name-$item-ts", "", TimeNow() ) ."\n" )) { my $name = $ntfy->{NAME}; Log3 $name, 4, "Closing connection $name due to full buffer in FW_Notify"; TcpServer_Close($ntfy, 1); } } } } sub readingsGroup_Notify($$) { my ($hash,$dev) = @_; my $name = $hash->{NAME}; my $events = deviceEvents($dev,1); return if( !$events ); if( grep(m/^INITIALIZED$/, @{$events}) ) { readingsGroup_updateDevices($hash); readingsGroup_inithtml($hash); return undef; } elsif( grep(m/^REREADCFG$/, @{$events}) ) { readingsGroup_updateDevices($hash); readingsGroup_inithtml($hash); return undef; } return if( !$init_done ); 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} ); $hash->{changed} = 0; my %triggers = (); 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; } 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} ); delete( $hash->{helper}{recalc} ); delete( $hash->{helper}{values} ); delete( $hash->{helper}{positions} ); return undef; } } my ($reading,$value) = split(": ",$events->[$i], 2); next if( !defined($value) ); next if( $reading =~ m/^\./); $reading = "" if( !defined($reading) ); $value = "" if( !defined($value) ); my $show_state = !AttrVal( $name, "nostate", "0" ); my $cell_row = 0; foreach my $device (@{$devices}) { my $item = 0; ++$cell_row; 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 = ('.*'); @list = split(",",$regex) if( $regex ); if( $hash->{groupedList} ) { @list = @{$hash->{groupedList}}; } for( my $i = 0; $i <= $#list; ++$i ) { my $regex = $list[$i]; while ($regex && ( ($regex =~ m/^$/) #handle , in <...> || ($regex =~ m/@\{/ && $regex !~ m/\}$/) #handle , in reading@{...} || ($regex =~ m/^\$.*\(/ && $regex !~ m/\)/) ) #handle , in $(...) && defined($list[++$i]) ) { $regex .= ",". $list[$i]; } ++$item; next if( $reading eq "state" && !$show_state && (!defined($regex) || $regex ne "state") ); my $modifier = ""; my $format = ""; if( $regex ) { if( $regex =~ m/^([ira]):(.*)/ ) { $modifier = $1; $regex = $2; } if( $regex && $regex =~ m/^([+?!\$]*)(.*)/ ) { $modifier = $1; $regex = $2; } next if( $modifier =~ m/[i+]/ ); next if( $modifier =~ m/[a?]/ ); if( $regex =~ m/^(.*):(t|sec|i|d|r|r\d)$/ ) { $regex = $1; $format = $2; } } my $calc = undef; if( $modifier =~ m/\$/ ) { $calc = $regex; } if( $regex && $regex =~ m/^<(.*)>$/ ) { my $txt = $1; my $readings; 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($hash->{helper}{commands},$name,$n,$icon) if( !defined($cmd) ); $txt = FW_makeImage( $icon, $icon, "icon" ); ($txt,undef) = readingsGroup_makeLink($txt,undef,$cmd); } readingsGroup_Update( $hash, "item:$cell_row:$item", "$txt" ); } next; } if( $regex ) { my $regex = $regex; $regex =~ s/\{/\\\{/g; $regex =~ s/\}/\\\}/g; next if( $reading !~ m/^$regex$/); } my $value = $value; if( $format eq 't' || $format eq 'sec' ) { $value = TimeNow(); $value = time() - time_str2num($value) if($format eq 'sec'); } elsif( $format =~ m/^[dir]/ ) { $value = rgVal2Num($value); $value = int($value) if( $format eq 'i' ); $value = round($value, defined($1) ? $1 : 1) if($format =~ /^r(\d)?/); } my $value_style = lookup2($hash->{helper}{valueStyle},$n,$reading,$value); my $value_orig = $value; if( my $value_format = $hash->{helper}{valueFormat} ) { my $value_format = lookup2($hash->{helper}{valueFormat},$n,$reading,$value); if( !defined($value_format) ) { $value = ""; } elsif( $value_format =~ m/%/ ) { $value = sprintf( $value_format, $value ); } elsif( $value_format ne "" ) { $value = $value_format; } } my $value_formated = $value; my $cmd; my $devStateIcon; if( my $value_icon = $hash->{helper}{valueIcon} ) { my $alias = AttrVal($n, "alias", $n); my $room = AttrVal($n, "room", ""); my $group = AttrVal($n, "group", ""); if( my $icon = lookup($value_icon,$n,$alias,$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($hash->{helper}{commands},$n,$reading,$icon); $cmd = lookup2($hash->{helper}{commands},$n,$reading,$value) if( !$cmd ); } } if( $devStateIcon ) { (undef,$devStateIcon) = readingsGroup_makeLink(undef,$devStateIcon,$cmd); if( $hash->{helper}{valuePrefix} ) { if( my $value_prefix = lookup2($hash->{helper}{valuePrefix},$n,$reading,$value) ) { $devStateIcon = $value_prefix . $devStateIcon if( $devStateIcon ); } } if( $hash->{helper}{valueSuffix} ) { if( my $value_suffix = lookup2($hash->{helper}{valueSuffix},$n,$reading,$value) ) { $devStateIcon .= $value_suffix if( $devStateIcon ); } } readingsGroup_Update( $hash, "$n.$reading", "$devStateIcon" ); next; } } $cmd = lookup2($hash->{helper}{commands},$n,$reading,$value); if( $cmd && $cmd =~ m/^(\w.*):(\S.*)?$/ ) { if( $reading eq "state" ) { readingsGroup_Update( $hash, $n, $value ); } else { readingsGroup_Update( $hash, "$n.$reading", $value ); } next; } ($value,undef) = readingsGroup_makeLink($value,undef,$cmd); if( $hash->{helper}{valuePrefix} ) { if( my $value_prefix = lookup2($hash->{helper}{valuePrefix},$n,$reading,$value) ) { $value = $value_prefix . $value; $devStateIcon = $value_prefix . $devStateIcon if( $devStateIcon ); } } my $value_prefix_suffix = $value; if( $hash->{helper}{valueSuffix} ) { if( my $value_suffix = lookup2($hash->{helper}{valueSuffix},$n,$reading,$value) ) { $value .= $value_suffix; $devStateIcon .= $value_suffix if( $devStateIcon ); } } $value = "
$value
" if( $value_style ); #FIXME: create {'$n.$reading'} = $value hash to avaid multiple events and calculations if same reading is included multiple times if( $format ) { $triggers{"$n.$reading:$format"} = $value; } else { $triggers{"$n.$reading"} = $value; } if( my $cells = $hash->{helper}{positions}{"$n.$reading"} ) { foreach my $cell ( split( ',', $cells ) ) { my ($cell_row,$cell_column) = split( ':', $cell ); $hash->{helper}{values}{orig}[$cell_column][$cell_row] = $value_orig; $hash->{helper}{values}{formated}[$cell_column][$cell_row] = $value_formated; $hash->{helper}{values}{prefixsuffix}[$cell_column][$cell_row] = $value_prefix_suffix; } } } } } } our %readings; foreach my $trigger (keys %triggers) { readingsGroup_Update( $hash, $trigger, "$triggers{$trigger}" ); our $count = 0; sub updateRefs($$); sub updateRefs($$) { my( $hash, $refs ) = @_; my $name = $hash->{NAME}; if( ++$count > 20 ) { Log3 $name, 2, "$name: recursionDetected: $refs"; return; } foreach my $ref ( split( ',', $refs ) ) { my ($row,$col) = split( ':', $ref ); my $calc = $hash->{helper}{values}{calc}[$col][$row]; my $func = $calc; # format: $[()][@] if( $calc =~ m/([^@\(]*)(\(([^\(]*)\))?(\(([^\(]*)\))?(@(.*))?/ ) { $func = $7; $func = $1 if( !defined($func) ); } my($informid,$v,$devStateIcon) = readingsGroup_value2html($hash,$calc,$name,$name,$func,$func,$row,$col,undef); $v = "" if( !defined($v) ); readingsGroup_Update( $hash, "calc:$row:$col", "$v" ); $readings{$func} = $hash->{helper}{values}{formated}[$col][$row]; if( my $refs = $hash->{helper}{recalc}[$col][$row] ) { updateRefs( $hash, $refs ); } } --$count; } if( my $cells = $hash->{helper}{positions}{$trigger} ) { foreach my $cell ( split( ',', $cells ) ) { my ($cell_row,$cell_column) = split( ':', $cell ); if( my $refs = $hash->{helper}{recalc}[$cell_column][$cell_row] ) { updateRefs( $hash, $refs ); } } } } return undef if( !$hash->{changed} ); delete $hash->{changed}; if( $hash->{alwaysTrigger} && $hash->{alwaysTrigger} > 1 ) { readingsBeginUpdate($hash); foreach my $key ( keys %readings ) { if( defined($readings{$key}) ) { readingsBulkUpdate($hash, $key, $readings{$key}, 1); #if( !defined($hash->{helper}{$key}) || $hash->{helper}{$key} ne $readings{$key} ); $hash->{helper}{$key} = $readings{$key}; } } readingsEndUpdate($hash,1); } if( %triggers ) { my $sort_column = AttrVal( $hash, 'sortColumn', undef ); readingsGroup_Update( $hash, "sort", $sort_column ) if( defined($sort_column) ) } return undef; } sub readingsGroup_Set($@) { my ($hash, $name, $cmd, $param, @a) = @_; my $list = "visibility:toggle,toggle2,show,hide"; if( $cmd eq "refresh" ) { readingsGroup_updateDevices($hash); return undef; } elsif( $cmd eq "visibility" ) { readingsGroup_updateDevices($hash); readingsGroup_Update( $hash, "visibility", $param ); return undef; } if( my $setList = AttrVal($name, "setList", undef) ) { $list .= " ". $setList; return "Unknown argument $cmd, choose one of $list" if( $cmd eq '?' ); foreach my $set (split(" ", $setList)) { if( "$set " =~ m/^${cmd}[ :]/ ) { my $v = join(" ", @a); my $set_fn = AttrVal( $hash->{NAME}, "setFn", "" ); if( $set_fn =~ m/^\{.*\}$/s ) { my $CMD = $cmd; my $ARGS = $param ." ". join(" ", @a); my $set_fn = eval $set_fn; Log3 $name, 3, $name .": setFn: ". $@ if($@); return $set_fn; } } } } 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}; } readingsGroup_inithtml($hash); } elsif( grep { $_ =~ m/$attrName(:.*)?/ } @mapping_attrs ) { my $hash = $defs{$name}; if( $cmd eq "set" ) { my $attrVal = $attrVal; my %specials= ( "%DEVICE" => $name, "%READING" => $name, "%VALUE" => "1", "%NUM" => "1", "%ROW" => "1", "%COLUMN" => "1", ); my $err = perlSyntaxCheck($attrVal, %specials); return $err if($err); if( $attrVal =~ m/^\{.*\}$/s && $attrVal =~ m/=>/ && $attrVal !~ m/\$/ ) { my $av = eval $attrVal; if( $@ ) { Log3 $hash->{NAME}, 3, $hash->{NAME} .": ". $@; } else { $attrVal = $av if( ref($av) eq "HASH" ); } } $hash->{helper}{$attrName} = $attrVal; } else { delete $hash->{helper}{$attrName}; } } 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 =item helper =item summary display a formated collection of readings from devices =item summary_DE Stellt eine formatierte Darstellung aus Readings von Geräte bereit =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 ATTRIBUTE&VALUE where ATTRIBUTE is the name of an attribute 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.
      • <device> can be a devspec (see devspec) with at least one FILTER expression.
      • 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.
      • If regex starts with a '!' the display of the value will be forced even if no reading with this name is available.
      • If regex starts with a '$' the calculation with value columns and rows is possible.
      • The following "set magic" prefixes and suffixes can be used with regex:
        • You can use an i:, r: or a: prefix instead of + and ? analogue to the devspec filtering.
        • The suffix :d retrieves the first number.
        • The suffix :i retrieves the integer part of the first number.
        • The suffix :r<n> retrieves the first number and rounds it to <n> decimal places. If <n> is missing, then rounds it to one decimal place.
        • The suffix :t returns the timestamp (works only for readings).
        • The suffix :sec returns the number of seconds since the reading was set. probably not realy usefull with readingsGroups.
      • regex can be of the form <regex>@device to use readings from a different device.
        if the device name part starts with a '!' the display will be foreced. use in conjunction with ! in front of the reading name.
      • 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 hr a horizontal line will be inserted
        • if STRING is tfoot the table footer is 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.
      • If the first regex is '@<index>' it gives the index of the following regex by which the readings are to be grouped. if capture groups are used they can be refferenced by #<number>. eg:
          <IP-Adress><Hostname><MAC><Vendor>
          nmap:@2,<#1>,(.*)_hostname,#1_macAddress,#1_macVendor
      • 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
    • hide
      will hide all visible instances of this readingsGroup
    • show
      will show all visible instances of this readingsGroup
    • toggle
      will toggle the hidden/shown state of all visible instances of this readingsGroup
    • toggle2
      will toggle the expanded/collapsed state of all visible instances of this readingsGroup

    Get

    Attributes
    • alwaysTrigger
      1 -> always trigger update events. even if not visible.
      2 -> trigger events for calculated values.

    • 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> or <reading>.<value> or <device>.<reading>.<value>. %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;

    • 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. set commands not in this list will be rejected.

    • setFn
      perl expresion that will be executed for the commands from the setList. has access to $CMD and $ARGS.

    • style
      Specify an HTML style for the readings table, e.g.:
      attr temperatures style style="font-size:20px"

    • cellStyle
      Specify an HTML style for a cell of the readings table. regular rows and colums are counted starting with 1, the row headings are column number 0. perl code has access to $ROW and $COLUMN. keys for hash lookup can be r:#, c:# or r:#,c:# , e.g.:
      attr temperatures cellStyle { "c:0" => 'style="text-align:right"' }

    • 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 => "%i %%" }
      attr temperatures valueFormat { ($READING eq 'temperature')?"%.1f °C":undef }

    • valuePrefix
      text to be prepended to the reading value

    • valueSuffix
      text to be appended after the reading value
      attr temperatures valueFormat { temperature => "%.1f", humidity => "%i" }
      attr temperatures valueSuffix { temperature => "°C", humidity => " %" }

    • 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:" }
      • commands can be used for attribtues. eg:
        attr commands { disable => "disable:" }

    • visibility
      if set to hidden or hideable will display a small button to the left of the readingsGroup name to expand/hide the contents of the readingsGroup. if a readingsGroup is expanded then all others in the same group will be hidden.
        hidden -> default state is hidden but can be expanded
        hideable -> default state is visible but can be hidden

      if set to collapsed or collapsible readingsGroup will recognise the specials <->,<+> and <+-> as the first elements of a line to add a + or - symbol to this line. clicking on the + or - symbol will toggle between expanded and collapsed state. if a readingsGroup is expanded then all others in the same group will be collapsed.
        - -> line will be visible in expanded state
        + -> line will be visible in collapsed state
        +- -> line will be visible in both states

        collapsed -> default state is collapsed but can be expanded
        collapsible -> default state is visible but can be collapsed
    • headerRows
    • sortColumn
      > 0 -> automatically sort the table by this column after page loading 0 -> do not sort automatically but allow sorting of the table by clicking on a column header < 0 -> automatically sort the table in reverse by this column after page loading

    • perlSyntaxCheck

    For the hash version of all mapping attributes it is possible to give a default value with { '' => <default> }.

    The style 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,$VALUE and $NUM, 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.

    Calculation: to be written...
    eg: define rg readingsGroup .*:temperature rg:$avg
    please see a description in the wiki
=end html =begin html_DE

readingsGroup

    Zeigt eine Sammlung von Messwerten von einem oder mehreren Geräten an.

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

      Anmerkungen:
      • <device> kann die Form INTERNAL=VALUE haben, wobei INTERNAL der Name eines internen Wertes ist und VALUE ein Regex.
      • <device> kann die Form ATTRIBUTE&VALUE haben, wobei ATTRIBUTE der Name eines Attributs ist und VALUE ein Regex.
      • <device> kann die Form <STRING> oder <{perl}> haben, wobei STRING oder die von Perl zurückgegebene Zeichenfolge als Zeile in die Readings List eingefügt wird. Wird übersprungen, wenn STRING undef ist.
      • <device> kann ein devspec sein (siehe devspec) mit mindestens einem FILTER-Ausdruck sein.
      • Wenn Regex eine Komma separarierte Liste ist, werden die Reading-Values in einer einzelnen Zeile angezeigt.
      • Wenn Regex mit einem "+" beginnt, wird es mit den internen Werten (Internals) des Geräts anstelle der Readings verglichen.
      • Wenn Regex mit einem '?' beginnt, wird es mit den Attributen des Geräts verglichen und nicht mit den Werten (Readings) verglichen.
      • Wenn Regex mit einem '!' beginnt, wird die Anzeige des Wertes erzwungen, auch wenn kein Reading mit diesem Namen verfügbar ist.
      • Wenn Regex mit einem '$' beginnt, ist die Berechnung mit Wert-Spalten und Zeilen möglich.
      • Die folgenden "set magic" Präfixe und Suffixe können mit Regex verwendet werden:
        • Sie können anstelle von + und ? ein Präfix i :, r: oder a: verwenden. Analog zur devspec-Filterung.
        • Der Suffix :d ruft die erste Nummer ab.
        • Der Suffix :i ruft den ganzzahligen Teil der ersten Zahl ab.
        • Der Suffix :r<n> ruft die erste Zahl ab und rundet sie auf <n> Nachkommastellen ab. Wenn <n> fehlt, wird es auf eine Dezimalstelle gerundet.
        • Der Suffix :t gibt den Zeitstempel zurück (funktioniert nur mit Readings).
        • Der Suffix :sec gibt die Anzahl der Sekunden seit dem das Reading gesetzt wurde zurück. Wahrscheinlich nicht nützlich mit readingsGroups.
      • Regex kann von der Form <regex>@device sein, um Readings von einem anderen Gerät zu verwenden.
        Wenn der Gerätename mit einem '!' beginnt, wird die Anzeige deaktiviert. Verwenden Sie in Verbindung mit ! den Reading-Name.
      • Regex kann die Form <regex>@{perl} haben, um Readings von einem anderen Gerät zu verwenden.
      • Regex kann von der Form <STRING> oder <{perl}[@readings]> sein, wobei STRING oder die von Perl zurückgegebene Zeichenfolge als Reading eingefügt wird, oder:
        • das Element wird übersprungen, wenn STRING undef ist
        • wenn STRING br ist, wird eine neue Zeile gestartet
        • wenn STRING hr ist, wird eine horizontale Linie eingefügt
        • wenn STRING tfoot ist, wird der Tabellenfuß gestartet
        • wenn STRING die Form hat, %ICON[%CMD] ICON wird als Name eines Symbols anstelle von Text und CMD als der Befehl verwendet, der ausgeführt werden soll, wenn auf das Symbol geklickt wird. Siehe auch die Befehlsattribute.
        Wenn Readings aktualisiert werden, wird der Perl-Ausdruck bei Longpoll-Aktualisierungen erneut ausgewertet.
      • Wenn der erste Regex '@<index>' ist, gibt es den Index der folgenden Regex an, mit dem die Messwerte gruppiert werden sollen. Wenn Erfassungsgruppen verwendet werden, können sie durch #<number> refferenziert werden. z.Bsp:
          <IP-Adress><Hostname><MAC><Vendor>
          nmap:@2,<#1>,(.*)_hostname,#1_macAddress,#1_macVendor
      • Für interne Werte (Internals) und Attribute ist longpoll update nicht möglich. Aktualisieren Sie die Seite, um die Werte zu aktualisieren.
      • Der Ausdruck <{perl}> ist auf Ausdrücke ohne Leerzeichen beschränkt. Es ist am besten, eine kleine Sub in 99_myUtils.pm aufzurufen, anstatt einen complexen Ausdruck im Define zu haben.

      Beispiele:
        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
    • hide
      Alle sichtbaren Instanzen dieser ReadingsGroup werden ausgeblendet
    • show
      Zeigt alle sichtbaren Instanzen dieser ReadingsGroup an
    • toggle
      Schaltet den versteckten / angezeigten Zustand aller sichtbaren Instanzen dieser ReadingsGroup an.
    • toggle2
      schaltet den erweiterten / kollabierten Zustand aller sichtbaren Instanzen dieser ReadingsGroup an.

    Get

    Attribute
    • alwaysTrigger
      1 -> Events auch wenn nicht sichtbar.
      2 -> Events für berechnete Werte.

    • disable
      1 -> Deaktivieren der Benachrichtigung Verarbeitung und Longpoll-Updates. Hinweis: Dadurch wird auch die Umbenennung und Löschbehandlung deaktiviert.
      2 -> Deaktivieren der HTML-Tabellenerstellung
      3 -> Deaktivieren der HTML-Erstellung vollständig

    • sortDevices
      1 -> Sortieren der Geräteliste alphabetisch. Verwenden Sie das erste von sortby oder alias oder name, das für jedes Gerät definiert ist.
    • noheading
      Wenn sie auf 1 gesetzt ist, hat die Readings-Tabelle keine Überschrift.

    • nolinks
      Deaktiviert die HTML-Links von der Überschrift und den Readings-Namen.

    • nostate
      Wenn der Wert 1 ist, wird der Status nicht berücksichtigt.

    • nonames
      Wenn der Wert auf 1 gesetzt ist, wird der Readings-Name / Zeilentitel nicht angezeigt.

    • notime
      Wenn der Wert auf 1 gesetzt, wird der Readings-Timestamp nicht angezeigt.

    • mapping
      Kann ein einfacher String oder ein in {} eingeschlossener Perl-Ausdruck sein, der einen Hash zurückgibt, der den Reading-Name dem angezeigten Namen zuordnet. Der Schlüssel kann entweder der Name des Readings oder <device>.<reading> oder <reading>.<value> oder <device>.<reading>.<value> sein. %DEVICE, %ALIAS, %ROOM, %GROUP, %ROW und %READING werden durch den Gerätenamen, Gerätealias, Raumattribut ersetzt. Sie können diesen Keywords auch ein Präfix voranstellen $ anstatt von %. Beispiele:
      attr temperatures mapping $DEVICE-$READING
      attr temperatures mapping {temperature => "%DEVICE Temperatur"}

    • separator
      Das zu verwendende Trennzeichen zwischen dem Gerätealias und dem Reading-Namen, wenn keine Zuordnung angegeben ist, standardgemäß ':' Ein Leerzeichen wird so dargestellt &nbsp;

    • setList
      Eine durch Leerzeichen getrennte Liste von Befehlen, die zurückgegeben werden "set name ?", Das FHEMWEB-Frontend kann also ein Dropdown-Menü erstellen und An / Aus-Schalter anbieten. Set-Befehle, die nicht in dieser Liste enthalten sind, werden zurückgewiesen.

    • setFn
      Perl-Ausdruck, der für die Befehle aus der setList ausgeführt wird. Es hat Zugriff auf $CMD und $ARGS.

    • style
      Geben Sie einen HTML-Stil für die Readings-Tabelle an, z.Bsp:
      attr temperatures style style="font-size:20px"

    • cellStyle
      Geben Sie einen HTML-Stil für eine Zelle der Readings-Tabelle an. Normale Zeilen und Spalten werden gezählt beginnend mit 1, Die Zeilenüberschriften beginnt mit der Spaltennummer 0. Perl-Code hat Zugriff auf $ROW und $COLUMN. Schlüssel für Hash-Lookup können sein: r:#, c:# oder r:#,c:# , z.Bsp:
      attr temperatures cellStyle { "c:0" => 'style="text-align:right"' }

    • nameStyle
      Geben Sie einen HTML-Stil für die Readings-Namen an, z.Bsp:
      attr temperatures nameStyle style="font-weight:bold"

    • valueStyle
      Geben Sie einen HTML-Stil für die Readings-Werte an, z.Bsp:
      attr temperatures valueStyle style="text-align:right"

    • valueColumn
      Geben Sie die Mindestspalte an, in der ein Messwert angezeigt werden soll. z.Bsp:
      attr temperatures valueColumn { temperature => 2 }

    • valueColumns
      Geben Sie einen HTML-Colspan für die Readings-Werte an, z.Bsp:
      attr wzReceiverRG valueColumns { eventdescription => 'colspan="4"' }

    • valueFormat
      Geben Sie eine Sprintf-Stilformat-Zeichenfolge an, die zum Anzeigen der Readings-Werte verwendet wird. Wenn die Formatzeichenfolge undef ist wird dieser Messwert übersprungen. Es kann als String angegeben werden, ein Perl-Ausdruck, der einen Hash- oder Perl-Ausdruck zurückgibt, der einen String zurückgibt, z.Bsp:
      attr temperatures valueFormat %.1f °C
      attr temperatures valueFormat { temperature => "%.1f °C", humidity => "%i %%" }
      attr temperatures valueFormat { ($READING eq 'temperature')?"%.1f °C":undef }

    • valuePrefix
      Text, der dem Readings-Wert vorangestellt wird

    • valueSuffix
      Text, der nach dem Readings-Wert angehängt wird
      attr temperatures valueFormat { temperature => "%.1f", humidity => "%i" }
      attr temperatures valueSuffix { temperature => "°C", humidity => " %" }

    • nameIcon
      Geben Sie das Symbol an, das anstelle des Readings-Name verwendet werden soll. Es kann ein einfacher String oder ein in {} eingeschlossener Perl-Ausdruck sein, der einen Hash zurückgibt, der dem Readings-Name den Icon-Namen zuordnet. z.Bsp:
      attr devices nameIcon $DEVICE

    • valueIcon
      Geben Sie ein Symbol an, das anstelle des Readings-Wert verwendet werden soll. Es kann ein einfacher String oder ein in {} eingeschlossener Perl-Ausdruck sein, der einen Hash zurückgibt, der dem Readings-Wert dem Symbolnamen zuordnet. z.Bsp:
      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
      Kann auf verschiedene Arten verwendet werden:
      • Um ein Reading oder ein Symbol anklickbar zu machen, indem Sie direkt den Befehl angeben, der ausgeführt werden soll. z.Bsp:
        attr rgMediaPlayer commands { "playStatus.paused" => "set %DEVICE play", "playStatus.playing" => "set %DEVICE pause" }
      • Wenn der zugeordnete Befehl die Form <command>:[<modifier>] hat, wird das normale FHEMWEB webCmd-Widget für für diesen commands verwendet. z.Bsp:
        attr rgMediaPlayer commands { volume => "volume:slider,0,1,100" }
        attr lights commands { pct => "pct:", dim => "dim:" }
      • commands können für Attribute verwendet werden. z.Bsp:
        attr commands { disable => "disable:" }

    • visibility
      Wenn sie auf hidden oder hideable eingestellt ist, wird eine kleine Schaltfläche links neben dem Namen der Readings-Group angezeigt, um den Inhalt der Readings-Group zu erweitern / auszublenden. Wenn eine Readings-Group erweitert wird, werden alle anderen Gruppen derselben Gruppe ausgeblendet.
        hidden -> Standardmäßig ist hidden aktiv, kann jedoch erweitert werden.
        hideable -> Standardmäßig ist hideable aktiv, kann jedoch ausgeblendet werden.

      Wenn diese Option auf "collapsed" oder "collapsible" eingestellt ist, erkennt readingsGroup die Specials <->,<+> und <+-> als die ersten Elemente von eine Linie, um dieser Linie ein + oder - Symbol hinzuzufügen. Durch Klicken auf das + oder - Symbol wird zwischen erweitertem und reduziertem Zustand umgeschaltet. Wenn eine Readings-Group erweitert wird, werden alle anderen Gruppen in der gleichen Gruppe ausgeblendet.
        - -> Die Linie wird im expandierten Zustand sichtbar sein.
        + -> Die Linie wird im zusammengefalteten Zustand angezeigt.
        +- -> Die Linie wird in beiden Zuständen sichtbar sein.

        collapsed -> Der Standardstatus ist reduziert, kann jedoch erweitert werden.
        collapsible -> Der Standardstatus ist sichtbar, kann jedoch minimiert werden.

    • headerRows

    • sortColumn
      > 0 -> sortiert die Tabelle automatisch nach dem Laden der Seite nach dieser Spalte
      0 -> sortiert Sie nicht automatisch, sondern durch Klicken auf eine Spaltenüberschrift
      < 0 -> sortiert die Tabelle automatisch nach dem Laden der Seite nach dieser Spalte

    • perlSyntaxCheck

    Für die Hash-Version aller Zuordnungsattribute kann ein Standardwert angegeben werden mit { '' => <default> }.

    Die Stilattribute können auch einen in {} eingeschlossenen Perl-Ausdruck enthalten, der die zu verwendende Stilzeichenfolge zurückgibt. Für nameStyle und valueStyle, kann der Perl-Code $DEVICE,$READING,$VALUE und $NUM verwendet werden. z.Bsp:
      attr batteries valueStyle {($VALUE ne "ok")?'style="color:red"':'style="color:green"'}
      attr temperatures valueStyle {($DEVICE =~ m/aussen/)?'style="color:green"':'style="color:red"'}

    Hinweis: Nur valueStyle, valueFomat, valueIcon und <{...}@reading> werden bei Longpoll-Updates ausgewertet und valueStyle muss für jeden möglichen Wert einen nicht leeren Stil zurückgeben. Alle anderen Perl-Ausdrücke werden nur einmal während der HTML-Erstellung ausgewertet und geben keine Wertupdates mit longpoll wieder. Aktualisieren Sie die Seite, um den dynamischen Stil zu aktualisieren. Für nameStyle funktioniert das Farbattribut momentan nicht, die font -... und background Attribute funktionieren.

    Berechnung: Bitte sehen Sie sich dafür diese Beschreibung an in der Wiki.
    z.Bsp: define rg readingsGroup .*:temperature rg:$avg
=end html_DE =cut