############################################## # $Id$ package main; # Syntax of the AttrTemplate file: # - empty lines are ignored # - lines starting with # are comments (see also desc:) # - lines starting with the following keywords are special, all others are # regular FHEM commands # - name: name of the template, marks the end of the previous template. # - filter:, describing the list of devices this # template is applicable for. Well be executed when the set function is # executed. # - prereq:, where cond is a perl expression, or devspec2array returning # exactly one device. Evaluated at initialization(!). # - par:::. if there is an additional argument in the set, # name in alle commands will be replaced with it. Else will be # excuted: if returns a value, name in the commands will be replaced with it, # else an error message/dialog will request the user to enter a value for # name. # For each name starting with RADIO_, a radio select button is offered. Such # parameters must defined last. # - desc: additional text for the "set attrTemplate help ?". If missing, the # last comment before name: will be used for this purpose. # - farewell: to be shown after the commands are executed. # - order: sort the templates for help purposes. # - option: if perl code return false, skip all commands until next # option (or name:) my %templates; my $initialized; my %cachedUsage; use vars qw($FW_addJs); # Only for helper like AttrTemplate sub AttrTemplate_Initialize() { my $me = "AttrTemplate_Initialize"; my $dir = $attr{global}{modpath}."/FHEM/lib/AttrTemplate"; if(!opendir(dh, $dir)) { Log 1, "$me: cant open $dir: $!"; return; } my @files = grep /\.template$/, sort readdir dh; closedir(dh); %templates = (); %cachedUsage = (); my %prereqFailed; for my $file (@files) { if(!open(fh,"$dir/$file")) { Log 1, "$me: cant open $dir/$file: $!"; next; } my ($name, %h, $lastComment); while(my $line = ) { chomp($line); next if($line =~ m/^$/); if($line =~ m/^# *(.*)$/) { # just a replacement for missing desc $lastComment = $1; next; } elsif($line =~ m/^name:(.*)/) { $name = $1; my (@p,@c); $templates{$name}{pars} = \@p; $templates{$name}{cmds} = \@c; $templates{$name}{desc} = $lastComment if($lastComment); $lastComment = ""; } elsif($line =~ m/^filter:(.*)/) { $templates{$name}{filter} = $1; } elsif($line =~ m/^prereq:(.*)/) { my $prereq = $1; if($prereq =~ m/^{.*}$/) { $prereqFailed{$name} = 1 if(AnalyzePerlCommand(undef, $prereq) ne "1"); } else { $prereqFailed{$name} = 1 if(!$defs{devspec2array($prereq)}); } } elsif($line =~ m/^par:(.*)/) { push(@{$templates{$name}{pars}}, $1); } elsif($line =~ m/^desc:(.*)/) { $templates{$name}{desc} = $1; } elsif($line =~ m/^farewell:(.*)/) { $templates{$name}{farewell} = $1; } elsif($line =~ m/^order:(.*)/) { $templates{$name}{order} = $1; } else { push(@{$templates{$name}{cmds}}, $line); } } close(fh); } for my $name (keys %prereqFailed) { delete($templates{$name}); } @templates = sort { my $ao = $templates{$a}{order}; my $bo = $templates{$b}{order}; $ao = (defined($ao) ? $ao : $a); $bo = (defined($bo) ? $bo : $b); return $ao cmp $bo; } keys %templates; my $nr = @templates; $initialized = 1; Log 2, "AttrTemplates: got $nr entries" if($nr); $FW_addJs = "" if(!defined($FW_addJs)); return if($FW_addJs =~ m/attrTemplateHelp/); $FW_addJs .= << 'JSEND'; JSEND } sub AttrTemplate_Help($) { my ($n) = @_; return "" if(!$templates{$n}); my $ret = ""; $ret = $templates{$n}{desc} if($templates{$n}{desc}); $ret .= "
".join("\n",@{$templates{$n}{cmds}})."
"; return $ret; } sub AttrTemplate_Set($$@) { my ($hash, $list, $name, $cmd, @a) = @_; $list = "" if(!defined($list)); return "Unknown argument $cmd, choose one of $list" if(AttrVal("global", "disableFeatures", "") =~ m/\battrTemplate\b/); AttrTemplate_Initialize() if(!$initialized); my $haveDesc; if($cmd ne "attrTemplate") { if(!$cachedUsage{$name}) { my @list; for my $k (@templates) { my $h = $templates{$k}; my $matches; $matches = devspec2array($h->{filter}, undef, [$name]) if($h->{filter}); if(!$h->{filter} || $matches) { push @list, $k; $haveDesc = 1 if($h->{desc}); } } $cachedUsage{$name} = (@list ? "attrTemplate:".($haveDesc ? "?,":"").join(",",@list) : ""); } $list .= " " if($list ne ""); return "Unknown argument $cmd, choose one of $list$cachedUsage{$name}"; } return "Missing template_entry_name parameter for attrTemplate" if(@a < 1); my $entry = shift(@a); if($entry eq "?") { my @hlp; for my $k (@templates) { my $h = $templates{$k}; my $matches; $matches = devspec2array($h->{filter}, undef, [$name]) if($h->{filter}); if(!$h->{filter} || $matches) { push @hlp, "$k: $h->{desc}" if($h->{desc}); } } return "no help available" if(!@hlp); if($hash->{CL} && $hash->{CL}{TYPE} eq "FHEMWEB") { return "
  • ".join("

  • ", map { $_=~s/:/
    /; $_ } @hlp)."
"; } return join("\n", @hlp); } if($entry eq "checkPar") { my @ret; foreach $entry (sort keys %templates) { my $h = $templates{$entry}; for my $k (@{$h->{pars}}) { my ($parname, $comment, $perl_code) = split(";",$k,3); nex if(!$perl_code); $perl_code =~ s/(?{pars}}) { my ($parname, $comment, $perl_code) = split(";",$k,3); if($a[$i1] =~ m/$parname=(.*)/) { $repl{$parname} = $1; return "Empty parameters are not allowed" if($1 eq ""); $fnd=1; last; } } push(@na,$a[$i1]) if(!$fnd); } @a = @na; for my $k (@{$h->{pars}}) { my ($parname, $comment, $perl_code) = split(";",$k,3); next if(defined($repl{$parname})); if(@a) { # old-style, without prefix $repl{$parname} = $a[0]; shift(@a); next; } if($perl_code) { $perl_code =~ s/(?{CL} && $hash->{CL}{TYPE} eq "FHEMWEB") { return "". "". "

Specify the unknown parameters for $entry:

". "". join("", map { my @t=split("= with ",$_,2); "") } @mComm)."
$t[1]" .($t[0] =~ m/^RADIO_/ ? "": "
". ' '; } else { return "Usage: set $name attrTemplate $entry @mList\n". "Replace the ... after\n".join("\n", @mComm); } } my $cmdlist = join("\n",@{$h->{cmds}}); $repl{DEVICE} = $name; map { $cmdlist =~ s/(?{CL}; my $cmd = ""; my @ret; my $option = 1; map { if($_ =~ m/^(.*)\\$/) { $cmd .= "$1\n"; } else { $cmd .= $_; if($cmd =~ m/^option:(.*)$/s) { my $optVal = $1; if($optVal =~ m/^{.*}$/) { $option = (AnalyzePerlCommand(undef, $optVal) eq "1"); } else { $option = defined($defs{devspec2array($optVal)}); } } elsif($option) { my $r = AnalyzeCommand($cl, $cmd); push(@ret, $r) if($r); } $cmd = ""; } } split("\n", $cmdlist); return join("\n", @ret) if(@ret); if($h->{farewell}) { my $fw = $h->{farewell}; if(!$cl || $cl->{TYPE} ne "FHEMWEB") { $fw =~ s/
/\n/gi; $fw =~ s/<[^>]+>//g; # remove html tags } return $fw if(!$cl); InternalTimer(gettimeofday()+1, sub{asyncOutput($cl, $fw)}, undef, 0); } return undef; } 1;