| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968 |
- # $Id: 33_readingsGroup.pm 15100 2017-09-19 21:21:27Z justme1968 $
- ##############################################################################
- #
- # 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 <http://www.gnu.org/licenses/>.
- #
- ##############################################################################
- 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/^</ && $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, ["<<ERROR>>"];
- 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/^</ && $regex !~ m/>$/) #handle , in <...>
- || ($regex =~ m/@\{/ && $regex !~ m/\}$/) #handle , in reading@{...}
- || ($regex =~ m/^\$.*\(/ && $regex !~ m/\)/) ) #handle , in $<calc>(...)
- && 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/^</ );
- $bb = "#" if( $bb =~ m/^</ );
- lc(AttrVal($aa,"sortby",AttrVal($aa,"alias",$aa))) cmp
- lc(AttrVal($bb,"sortby",AttrVal($bb,"alias",$bb))) } @devices;
- }
- $hash->{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 <name> readingsGroup <device>+" 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 = "<a style=\"cursor:pointer\" onClick=\"FW_cmd('$FW_ME$FW_subdir?XHR=1&$link')\">$txt</a>";
- } 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 = "<a href=\"$FW_ME$FW_subdir?$link$srf\">$txt</a>";
- }
- 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: $<operator>[(<zellen>)][@<alias>]
- 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>(.*)<\/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 = "<div $value_style>$v</div>" 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 .= "<table>";
- my $txt = AttrVal($d, "alias", $d);
- $txt = "<a href=\"$FW_ME$FW_subdir?detail=$d\">$txt</a>" if( $show_links );
- $ret .= "<tr><td><div class=\"devType\">$txt</a></div></td></tr>" if( $show_heading );
- $ret .= "<tr><td><table class=\"block wide\">";
- #$ret .= "<div class=\"devType\"><a style=\"color:#ff8888\" href=\"$FW_ME$FW_subdir?detail=$d\">readingsGroup $txt is disabled.</a></div>";
- $ret .= "<td><div style=\"color:#ff8888;text-align:center\">disabled</div></td>";
- $ret .= "</table></td></tr>";
- $ret .= "</table>";
- 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 .= "<a style=\"cursor:pointer\" onClick=\"FW_readingsGroupToggle('$d')\">></a>";
- }
- }
- my $row = 1;
- my $cell_row = 1;
- my $ret;
- $ret .= "<table>";
- my $txt = AttrVal($d, "alias", $d);
- $txt = "<a href=\"$FW_ME$FW_subdir?detail=$d\">$txt</a>" if( $show_links );
- $ret .= "<tr><td><div class=\"devType\">$show_hide $txt</div></td></tr>" if( $show_heading );
- $ret .= "<tr><td><table $style id='readingsGroup-$d'".
- (defined($sort_column)?" sortColumn=\"$sort_column\"":'').
- " groupId=\"$group\" class=\"block wide readingsGroup".
- (defined($sort_column)?' sortable':'') ."\">";
- $ret .= "<thead>" if( $header_rows );
- $ret .= "<tr><td colspan=\"99\"><div style=\"color:#ff8888;text-align:center\">updates disabled</div></tr>" 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 = "<ERROR>";
- 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}}, '<br2>' 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/^</ && $regex !~ m/>$/) #handle , in <...>
- || ($regex =~ m/@\{/ && $regex !~ m/\}$/) #handle , in reading@{...}
- || ($regex =~ m/^\$.*\(/ && $regex !~ m/\)/) ) #handle , in $<calc>(...)
- && 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 = "<ERROR>";
- 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("<tr class=\"%s\">", ($row-1&1)?"odd":"even");
- $ret .= "<td $value_columns><div $cell_style $name_style class=\"dname\"></div></td>" if( $show_names );
- $first = 0;
- ++$cell_row;
- $cell_column = 1;
- next;
- } elsif( $txt eq 'hr' ) {
- $ret .= sprintf("<tr $row_style class=\"%s\">", ($row&1)?"odd":"even");
- $row++;
- $ret .= "<td style='padding:0px' colspan='99'><hr/></td>";
- next;
- } elsif( $txt eq 'tfoot' ) {
- $ret .= "</tbody>" if( $header_rows && !$in_footer );
- $ret .= "<tfoot>" 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("<tr $row_style class=\"%s\">", ($row-1&1)?"odd":"even") if( $first );
- if( $visibility && ( $visibility eq "collapsed" || $visibility eq "collapsible" ) ) {
- $ret .= "<td $value_columns><div $id style=\"cursor:pointer\" onClick=\"FW_readingsGroupToggle2('$d')\">$txt</div></td>";
- } else {
- $ret .= "<td $value_columns><div>$txt</div></td>";
- }
- $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("<tr $row_style class=\"%s\">", ($row&1)?"odd":"even");
- $row++;
- }
- } elsif( $first || $multi == 1 ) {
- $ret .= sprintf("<tr $row_style class=\"%s\">", ($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 .= "<td $value_columns><div $cell_style0 $name_style class=\"dname\">$txt</div></td>" 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/^<td>(.*)<\/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 .= "<td $value_columns><div $cell_style $name_style $informid>$txt</div></td>";
- $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: $<operator>[(<zellen>)][@<alias>]
- 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 );
- 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("<tr $row_style class=\"%s\">", ($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 = "<div $cell_style0>$txt</div>" if( !$show_links );
- $txt = "<a $cell_style0 href=\"$FW_ME$FW_subdir?detail=$name\">$txt</a>" if( defined($txt) && $show_links );
- $ret .= "<td $value_columns><div $name_style class=\"dname\">$txt</div></td>" if( defined($txt) );
- }
- }
- my $value_column = lookup2($hash->{helper}{valueColumn},$name,$n,undef);
- if( $value_column && $multi ) {
- while ($cell_column < $value_column ) {
- $ret .= "<td></td>";
- ++$cell_column;
- }
- }
- $ret .= "<td $value_columns $informid>$devStateIcon</td>" if( $devStateIcon );
- $ret .= "<td $value_columns><div $cell_style $informid>$v</div></td>" if( !$devStateIcon );
- $ret .= "<td><div $timestamp_style informId=\"$d-$name.$n-ts\">$t</div></td>" 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 .= "</thead>";
- $ret .= "<tbody>";
- }
- ++$cell_row;
- }
- $ret .= "</tbody>" if( $header_rows && !$in_footer );
- if( $disable > 0 ) {
- $ret .= "<tfoot>" if( !$in_footer );
- $in_footer = 1;
- $ret .= sprintf("<tr class=\"%s\">", ($row&1)?"odd":"even");
- $ret .= "<td colspan=\"99\"><div style=\"color:#ff8888;text-align:center\">updates disabled</div></td></tr>";
- }
- $ret .= "</tfoot>" if( $in_footer );
- $ret .= "</table></td></tr>";
- $ret .= "</table>";
- #$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_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} );
- 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/^</ && $regex !~ m/>$/) #handle , in <...>
- || ($regex =~ m/@\{/ && $regex !~ m/\}$/) #handle , in reading@{...}
- || ($regex =~ m/^\$.*\(/ && $regex !~ m/\)/) ) #handle , in $<calc>(...)
- && 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 = "<ERROR>";
- 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);
- }
- DoTrigger( $name, "item:$cell_row:$item: <html>$txt</html>" );
- }
- next;
- }
- next if( defined($regex) && $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 );
- }
- }
- DoTrigger( $name, "$n.$reading: <html>$devStateIcon</html>" );
- next;
- }
- }
- $cmd = lookup2($hash->{helper}{commands},$n,$reading,$value);
- if( $cmd && $cmd =~ m/^(\w.*):(\S.*)?$/ ) {
- if( $reading eq "state" ) {
- DoTrigger( $name, "$n: $value" );
- #DoTrigger( $name, "$n: <html>$value</html>" );
- } else {
- DoTrigger( $name, "$n.$reading: $value" );
- #DoTrigger( $name, "$n.$reading: <html>$value</html>" );
- }
- 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 = "<div $value_style>$value</div>" if( $value_style );
- #FIXME: create {'$n.$reading'} = $value hash to avaid multiple events and calculations if same reading is included multiple times
- $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;
- }
- }
- }
- }
- }
- }
- readingsBeginUpdate($hash) if( $hash->{alwaysTrigger} && $hash->{alwaysTrigger} > 1 );
- foreach my $trigger (keys %triggers) {
- DoTrigger( $name, "$trigger: <html>$triggers{$trigger}</html>" );
- 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: $<operator>[(<zellen>)][@<alias>]
- 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) );
- #FIXME: use FW_directNotify
- DoTrigger( $name, "calc:$row:$col: <html>$v</html>" ) if( $hash->{mayBeVisible} );
- if( $hash->{alwaysTrigger} && $hash->{alwaysTrigger} > 1 ) {
- #DoTrigger( $name, "$func: $hash->{helper}{values}{formated}[$col][$row]" );
- readingsBulkUpdate($hash, $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 );
- }
- }
- }
- }
- readingsEndUpdate($hash,1) if( $hash->{alwaysTrigger} && $hash->{alwaysTrigger} > 1 );
- if( %triggers ) {
- my $sort_column = AttrVal( $hash->{NAME}, 'sortColumn', undef );
- DoTrigger( $hash->{NAME}, "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);
- DoTrigger( $hash->{NAME}, "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 Readings von Geräte dar
- =begin html
- <a name="readingsGroup"></a>
- <h3>readingsGroup</h3>
- <ul>
- Displays a collection of readings from on or more devices.
- <br><br>
- <a name="readingsGroup_Define"></a>
- <b>Define</b>
- <ul>
- <code>define <name> readingsGroup <device>[:regex] [<device-2>[:regex-2]] ... [<device-n>[:regex-n]]</code><br>
- <br>
- Notes:
- <ul>
- <li><device> can be of the form INTERNAL=VALUE where INTERNAL is the name of an internal value and VALUE is a regex.</li>
- <li><device> can be of the form ATTRIBUTE&VALUE where ATTRIBUTE is the name of an attribute and VALUE is a regex.</li>
- <li><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.</li>
- <li><device> can be a devspec (see <a href="#devspec">devspec</a>) with at least one FILTER expression.</li>
- <li>If regex is a comma separatet list the reading values will be shown on a single line.</li>
- <li>If regex starts with a '+' it will be matched against the internal values of the device instead of the readings.</li>
- <li>If regex starts with a '?' it will be matched against the attributes of the device instead of the readings.</li>
- <li>If regex starts with a '!' the display of the value will be forced even if no reading with this name is available.</li>
- <li>If regex starts with a '$' the calculation with value columns and rows is possible.</li>
- <li>The following <a href="#set">"set magic"</a> prefixes and suffixes can be used with regex:
- <ul>
- <li>You can use an i:, r: or a: prefix instead of + and ? analogue to the devspec filtering.</li>
- <li>The suffix :d retrieves the first number.</li>
- <li>The suffix :i retrieves the integer part of the first number.</li>
- <li>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.</li>
- <li>The suffix :t returns the timestamp (works only for readings).</li>
- <li>The suffix :sec returns the number of seconds since the reading was set. probably not realy usefull with readingsGroups.</li>
- </ul></li>
- <li>regex can be of the form <regex>@device to use readings from a different device.<br>
- if the device name part starts with a '!' the display will be foreced.
- use in conjunction with ! in front of the reading name.</li>
- <li>regex can be of the form <regex>@{perl} to use readings from a different device.</li>
- <li>regex can be of the form <STRING> or <{perl}[@readings]> where STRING or the string returned by perl is
- inserted as a reading or:
- <ul><li>the item will be skipped if STRING is undef</li>
- <li>if STRING is br a new line will be started</li>
- <li>if STRING is hr a horizontal line will be inserted</li>
- <li>if STRING is tfoot the table footer is started</li>
- <li>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.</li></ul>
- if readings is given the perl expression will be reevaluated during longpoll updates.</li>
- <li>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:<br><ul>
- <code><IP-Adress><Hostname><MAC><Vendor><br>
- nmap:@2,<#1>,(.*)_hostname,#1_macAddress,#1_macVendor</code></ul></li>
- <li>For internal values and attributes longpoll update is not possible. Refresh the page to update the values.</li>
- <li>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.</li>
- </ul><br>
- Examples:
- <ul>
- <code>
- define batteries readingsGroup .*:battery</code><br>
- <br>
- <code>define temperatures readingsGroup s300th.*:temperature</code><br>
- <code>define temperatures readingsGroup TYPE=CUL_WS:temperature</code><br>
- <br>
- <code>define culRSSI readingsGroup cul_RSSI=.*:+cul_RSSI</code><br>
- <br>
- <code>define heizung readingsGroup t1:temperature t2:temperature t3:temperature<br>
- attr heizung notime 1<br>
- attr heizung mapping {'t1.temperature' => 'Vorlauf', 't2.temperature' => 'R&uuml;cklauf', 't3.temperature' => 'Zirkulation'}<br>
- attr heizung style style="font-size:20px"<br>
- <br>
- define systemStatus readingsGroup sysstat<br>
- attr systemStatus notime 1<br>
- attr systemStatus nostate 1<br>
- attr systemStatus mapping {'load' => 'Systemauslastung', 'temperature' => 'Systemtemperatur in &deg;C'}<br>
- <br>
- define Verbrauch readingsGroup TYPE=PCA301:state,power,consumption<br>
- attr Verbrauch mapping %ALIAS<br>
- attr Verbrauch nameStyle style="font-weight:bold"<br>
- attr Verbrauch style style="font-size:20px"<br>
- attr Verbrauch valueFormat {power => "%.1f W", consumption => "%.2f kWh"}<br>
- attr Verbrauch valueIcon { state => '%devStateIcon' }<br>
- attr Verbrauch valueStyle {($READING eq "power" && $VALUE > 150)?'style="color:red"':'style="color:green"'}<br>
- <br>
- define rg_battery readingsGroup TYPE=LaCrosse:[Bb]attery<br>
- attr rg_battery alias Batteriestatus<br>
- attr rg_battery commands { "battery.low" => "set %DEVICE replaceBatteryForSec 60" }<br>
- attr rg_battery valueIcon {'battery.ok' => 'batterie', 'battery.low' => 'batterie@red'}<br>
- <br>
- define rgMediaPlayer readingsGroup myMediaPlayer:currentTitle,<>,totaltime,<br>,currentAlbum,<>,currentArtist,<br>,volume,<{if(ReadingsVal($DEVICE,"playStatus","")eq"paused"){"%rc_PLAY%set+$DEVICE+play"}else{"%rc_PAUSE%set+$DEVICE+pause"}}@playStatus>,playStatus<br>
- attr rgMediaPlayer commands { "playStatus.paused" => "set %DEVICE play", "playStatus.playing" => "set %DEVICE pause" }<br>
- attr rgMediaPlayer mapping <br>
- attr rgMediaPlayer notime 1<br>
- attr rgMediaPlayer valueFormat { "volume" => "Volume: %i" }<br>
- #attr rgMediaPlayer valueIcon { "playStatus.paused" => "rc_PLAY", "playStatus.playing" => "rc_PAUSE" }<br>
- </code><br>
- </ul>
- </ul><br>
- <a name="readingsGroup_Set"></a>
- <b>Set</b>
- <ul>
- <li>hide<br>
- will hide all visible instances of this readingsGroup</li>
- <li>show<br>
- will show all visible instances of this readingsGroup</li>
- <li>toggle<br>
- will toggle the hidden/shown state of all visible instances of this readingsGroup</li>
- <li>toggle2<br>
- will toggle the expanded/collapsed state of all visible instances of this readingsGroup</li>
- </ul><br>
- <a name="readingsGroup_Get"></a>
- <b>Get</b>
- <ul>
- </ul><br>
- <a name="readingsGroup_Attr"></a>
- <b>Attributes</b>
- <ul>
- <li>alwaysTrigger<br>
- 1 -> alwaysTrigger update events. even if not visible.<br>
- 2 -> trigger events for calculated values.</li><br>
- <li>disable<br>
- 1 -> disable notify processing and longpoll updates. Notice: this also disables rename and delete handling.<br>
- 2 -> also disable html table creation<br>
- 3 -> also disable html creation completely</li><br>
- <li>sortDevices<br>
- 1 -> sort the device lines alphabetically. use the first of sortby or alias or name that is defined for each device.</li>
- <li>noheading<br>
- If set to 1 the readings table will have no heading.</li><br>
- <li>nolinks<br>
- Disables the html links from the heading and the reading names.</li><br>
- <li>nostate<br>
- If set to 1 the state reading is excluded.</li><br>
- <li>nonames<br>
- If set to 1 the reading name / row title is not displayed.</li><br>
- <li>notime<br>
- If set to 1 the reading timestamp is not displayed.</li><br>
- <li>mapping<br>
- 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:<br>
- <code>attr temperatures mapping $DEVICE-$READING</code><br>
- <code>attr temperatures mapping {temperature => "%DEVICE Temperatur"}</code>
- </li><br>
- <li>separator<br>
- 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 <code>&nbsp;</code></li><br>
- <li>setList<br>
- 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.</li><br>
- <li>setFn<br>
- perl expresion that will be executed for the commands from the setList.
- has access to $CMD and $ARGS.</li><br>
- <li>style<br>
- Specify an HTML style for the readings table, e.g.:<br>
- <code>attr temperatures style style="font-size:20px"</code></li><br>
- <li>cellStyle<br>
- 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.:<br>
- <code>attr temperatures cellStyle { "c:0" => 'style="text-align:right"' }</code></li><br>
- <li>nameStyle<br>
- Specify an HTML style for the reading names, e.g.:<br>
- <code>attr temperatures nameStyle style="font-weight:bold"</code></li><br>
- <li>valueStyle<br>
- Specify an HTML style for the reading values, e.g.:<br>
- <code>attr temperatures valueStyle style="text-align:right"</code></li><br>
- <li>valueColumn<br>
- Specify the minimum column in which a reading should appear. <br>
- <code>attr temperatures valueColumn { temperature => 2 }</code></li><br>
- <li>valueColumns<br>
- Specify an HTML colspan for the reading values, e.g.:<br>
- <code>attr wzReceiverRG valueColumns { eventdescription => 'colspan="4"' }</code></li><br>
- <li>valueFormat<br>
- 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.:<br>
- <code>attr temperatures valueFormat %.1f °C</code><br>
- <code>attr temperatures valueFormat { temperature => "%.1f °C", humidity => "%i %" }</code><br>
- <code>attr temperatures valueFormat { ($READING eq 'temperature')?"%.1f °C":undef }</code></li><br>
- <li>valuePrefix<br>
- text to be prepended to the reading value</li><br>
- <li>valueSuffix<br>
- text to be appended after the reading value<br>
- <code>attr temperatures valueFormat { temperature => "%.1f", humidity => "%i" }</code><br>
- <code>attr temperatures valueSuffix { temperature => "°C", humidity => " %" }</code></li><br>
- <li>nameIcon<br>
- 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.:<br>
- <code>attr devices nameIcon $DEVICE</code></li><br>
- <li>valueIcon<br>
- 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.:<br>
- <code>attr devices valueIcon $VALUE</code><br>
- <code>attr devices valueIcon {state => '%VALUE'}</code><br>
- <code>attr devices valueIcon {state => '%devStateIcon'}</code><br>
- <code>attr rgMediaPlayer valueIcon { "playStatus.paused" => "rc_PLAY", "playStatus.playing" => "rc_PAUSE" }</code></li><br>
- <li>commands<br>
- Can be used in to different ways:
- <ul>
- <li>To make a reading or icon clickable by directly specifying the command that should be executed. eg.:<br>
- <code>attr rgMediaPlayer commands { "playStatus.paused" => "set %DEVICE play", "playStatus.playing" => "set %DEVICE pause" }</code></li>
- <li>Or if the mapped command is of the form <command>:[<modifier>] then the normal <a href="#FHEMWEB">FHEMWEB</a>
- 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:<br>
- <code>attr rgMediaPlayer commands { volume => "volume:slider,0,1,100" }</code><br>
- <code>attr lights commands { pct => "pct:", dim => "dim:" }</code></li>
- <li>commands can be used for attribtues. eg:<br>
- <code>attr <rg> commands { disable => "disable:" }</code></li>
- </ul></li><br>
- <li>visibility<br>
- 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.<br>
- <ul>
- hidden -> default state is hidden but can be expanded<br>
- hideable -> default state is visible but can be hidden<br><br>
- </ul>
- 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.
- <ul>
- - -> line will be visible in expanded state<br>
- + -> line will be visible in collapsed state<br>
- +- -> line will be visible in both states<br>
- <br>
- collapsed -> default state is collapsed but can be expanded<br>
- collapsible -> default state is visible but can be collapsed </li>
- </ul>
- <li>headerRows<br>
- </li>
- <li>sortColumn<br>
- > 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
- </li>
- <br><li><a href="#perlSyntaxCheck">perlSyntaxCheck</a></li>
- </ul><br>
- For the hash version of all mapping attributes it is possible to give a default value
- with <code>{ '' => <default> }</code>.<br><br>
- 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.:<br>
- <ul>
- <code>attr batteries valueStyle {($VALUE ne "ok")?'style="color:red"':'style="color:green"'}</code><br>
- <code>attr temperatures valueStyle {($DEVICE =~ m/aussen/)?'style="color:green"':'style="color:red"'}</code>
- </ul><br>
- 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.<br><br>
- Calculation: to be written...<br>
- eg: <code>define rg readingsGroup .*:temperature rg:$avg</code><br>
- please see a description <a href="http://www.fhemwiki.de/wiki/ReadingsGroup#Berechnungen">in the wiki</a>
- </ul>
- =end html
- =cut
|