diff --git a/fhem/contrib/betateilchen/01_FHEMWEB.pm b/fhem/contrib/betateilchen/01_FHEMWEB.pm deleted file mode 100644 index ed950fecc..000000000 --- a/fhem/contrib/betateilchen/01_FHEMWEB.pm +++ /dev/null @@ -1,4828 +0,0 @@ -############################################## -# $Id: 01_FHEMWEB.pm 18366 2019-01-21 21:23:08Z rudolfkoenig $ -package main; - -use strict; -use warnings; -use TcpServerUtils; -use HttpUtils; -use Blocking; -use Time::HiRes qw(gettimeofday); - -######################### -# Forward declaration -sub FW_IconURL($); -sub FW_addContent(;$); -sub FW_addToWritebuffer($$@); -sub FW_answerCall($); -sub FW_confFiles($); -sub FW_dev2image($;$); -sub FW_devState($$@); -sub FW_digestCgi($); -sub FW_directNotify($@); -sub FW_doDetail($); -sub FW_fatal($); -sub FW_fileList($;$); -sub FW_htmlEscape($); -sub FW_iconName($); -sub FW_iconPath($); -sub FW_logWrapper($); -sub FW_makeEdit($$$); -sub FW_makeImage(@); -sub FW_makeTable($$$@); -sub FW_makeTableFromArray($$@); -sub FW_pF($@); -sub FW_pH(@); -sub FW_pHPlain(@); -sub FW_pO(@); -sub FW_parseColumns($); -sub FW_readIcons($); -sub FW_readIconsFrom($$); -sub FW_returnFileAsStream($$$$$); -sub FW_roomOverview($); -#sub FW_roomStatesForInform($$); # Forum 30515 -sub FW_select($$$$$@); -sub FW_serveSpecial($$$$); -sub FW_showRoom(); -sub FW_style($$); -sub FW_submit($$@); -sub FW_textfield($$$); -sub FW_textfieldv($$$$); -sub FW_updateHashes(); -sub FW_visibleDevices(;$); -sub FW_widgetOverride($$); -sub FW_Read($$); - -use vars qw($FW_dir); # base directory for web server -use vars qw($FW_icondir); # icon base directory -use vars qw($FW_cssdir); # css directory -use vars qw($FW_gplotdir);# gplot directory -use vars qw($FW_confdir); # conf dir -use vars qw($MW_dir); # moddir (./FHEM), needed by edit Files in new - # structure - -use vars qw($FW_ME); # webname (default is fhem), used by 97_GROUP/weblink -use vars qw($FW_CSRF); # CSRF Token or empty -use vars qw($FW_ss); # is smallscreen, needed by 97_GROUP/95_VIEW -use vars qw($FW_tp); # is touchpad (iPad / etc) -use vars qw($FW_sp); # stylesheetPrefix - -# global variables, also used by 97_GROUP/95_VIEW/95_FLOORPLAN -use vars qw(%FW_types); # device types, -use vars qw($FW_RET); # Returned data (html) -use vars qw($FW_RETTYPE); # image/png or the like -use vars qw($FW_wname); # Web instance -use vars qw($FW_subdir); # Sub-path in URL, used by FLOORPLAN/weblink -use vars qw(%FW_pos); # scroll position -use vars qw($FW_cname); # Current connection name -use vars qw(%FW_hiddenroom); # hash of hidden rooms, used by weblink -use vars qw($FW_plotmode);# Global plot mode (WEB attribute), used by SVG -use vars qw($FW_plotsize);# Global plot size (WEB attribute), used by SVG -use vars qw(%FW_webArgs); # all arguments specified in the GET -use vars qw(@FW_fhemwebjs);# List of fhemweb*js scripts to load -use vars qw($FW_fhemwebjs);# List of fhemweb*js scripts to load -use vars qw($FW_detail); # currently selected device for detail view -use vars qw($FW_cmdret); # Returned data by the fhem call -use vars qw($FW_room); # currently selected room -use vars qw($FW_formmethod); -use vars qw(%FW_visibleDeviceHash); -use vars qw(@FW_httpheader); # HTTP header, line by line -use vars qw(%FW_httpheader); # HTTP header, as hash -use vars qw($FW_userAgent); # user agent string - -$FW_formmethod = "post"; - -my %FW_use; -my $FW_activateInform = 0; -my $FW_lastWebName = ""; # Name of last FHEMWEB instance, for caching -my $FW_lastHashUpdate = 0; -my $FW_httpRetCode = ""; -my %FW_csrfTokenCache; -my %FW_id2inform; - -######################### -# As we are _not_ multithreaded, it is safe to use global variables. -# Note: for delivering SVG plots we fork -my $FW_data; # Filecontent from browser when editing a file -my %FW_icons; # List of icons -my @FW_iconDirs; # Directory search order for icons -my $FW_RETTYPE; # image/png or the like -my %FW_rooms; # hash of all rooms -my @FW_roomsArr; # ordered list of rooms -my %FW_groups; # hash of all groups -my %FW_types; # device types, for sorting -my %FW_hiddengroup;# hash of hidden groups -my $FW_inform; -my $FW_XHR; # Data only answer, no HTML -my $FW_id=""; # id of current page -my $FW_jsonp; # jasonp answer (sending function calls to the client) -my $FW_headerlines; # -my $FW_chash; # client fhem hash -my $FW_encoding="UTF-8"; -my $FW_styleStamp=time(); -my %FW_svgData; - - -##################################### -sub -FHEMWEB_Initialize($) -{ - my ($hash) = @_; - - $hash->{ReadFn} = "FW_Read"; - $hash->{GetFn} = "FW_Get"; - $hash->{SetFn} = "FW_Set"; - $hash->{AttrFn} = "FW_Attr"; - $hash->{DefFn} = "FW_Define"; - $hash->{UndefFn} = "FW_Undef"; - $hash->{NotifyFn}= "FW_Notify"; - $hash->{AsyncOutputFn} = "FW_AsyncOutput"; - $hash->{ActivateInformFn} = "FW_ActivateInform"; - $hash->{CanAuthenticate} = 1; - no warnings 'qw'; - my @attrList = qw( - CORS:0,1 - HTTPS:1,0 - CssFiles - Css:textField-long - JavaScripts - SVGcache:1,0 - addHtmlTitle:1,0 - addStateEvent - csrfToken - csrfTokenHTTPHeader:0,1 - alarmTimeout - allowedHttpMethods - allowedCommands - allowfrom - basicAuth - basicAuthMsg - closeConn:1,0 - column - confirmDelete:0,1 - confirmJSError:0,1 - defaultRoom - deviceOverview:always,iconOnly,onClick,never - editConfig:1,0 - editFileList:textField-long - endPlotNow:1,0 - endPlotToday:1,0 - forbiddenroom - fwcompress:0,1 - hiddengroup - hiddengroupRegexp - hiddenroom - hiddenroomRegexp - httpHeader - iconPath - longpoll:0,1,websocket - longpollSVG:1,0 - menuEntries - mainInputLength - nameDisplay - ploteditor:always,onClick,never - plotfork:1,0 - plotmode:gnuplot-scroll,gnuplot-scroll-svg,SVG - plotEmbed:0,1 - plotsize - plotWeekStartDay:0,1,2,3,4,5,6 - nrAxis - redirectCmds:0,1 - refresh - reverseLogs:0,1 - roomIcons - showUsedFiles:0,1 - sortRooms - sslVersion - sslCertPrefix - smallscreen:unused - smallscreenCommands:0,1 - stylesheetPrefix - styleData:textField-long - title - touchpad:unused - viewport - webname - ); - use warnings 'qw'; - $hash->{AttrList} = join(" ", @attrList); - - - ############### - # Initialize internal structures - map { addToAttrList($_) } ( - "cmdIcon", - "devStateIcon:textField-long", - "devStateStyle", - "icon", - "sortby", - "webCmd", - "webCmdLabel:textField-long", - "widgetOverride" - ); - - $FW_confdir = "$attr{global}{modpath}/conf"; - $FW_dir = "$attr{global}{modpath}/www"; - $FW_icondir = "$FW_dir/images"; - $FW_cssdir = "$FW_dir/pgm2"; - $FW_gplotdir = "$FW_dir/gplot"; - - if(opendir(DH, "$FW_dir/pgm2")) { - $FW_fhemwebjs = join(",", map { $_ = ~m/^fhemweb_(.*).js$/; $1 } - grep { /fhemweb_(.*).js$/ } - readdir(DH)); - closedir(DH); - } - - $data{webCmdFn}{"~"} = "FW_widgetFallbackFn"; # Should be the last - - if($init_done) { # reload workaround - foreach my $pe ("fhemSVG", "openautomation", "default") { - FW_readIcons($pe); - } - } - - my %optMod = ( - zlib => { mod=>"Compress::Zlib", txt=>"compressed HTTP transfer" }, - sha => { mod=>"Digest::SHA", txt=>"longpoll via websocket" }, - base64 => { mod=>"MIME::Base64", txt=>"parallel SVG computing" } - ); - foreach my $mod (keys %optMod) { - eval "require $optMod{$mod}{mod}"; - if($@) { - Log 4, $@; - Log 3, "FHEMWEB: Can't load $optMod{$mod}{mod}, ". - "$optMod{$mod}{txt} is not available"; - } else { - $FW_use{$mod} = 1; - } - } -} - -##################################### -sub -FW_Define($$) -{ - my ($hash, $def) = @_; - my ($name, $type, $port, $global) = split("[ \t]+", $def); - return "Usage: define FHEMWEB [IPV6:] [global]" - if($port !~ m/^(IPV6:)?\d+$/); - - FW_Undef($hash, undef) if($hash->{OLDDEF}); # modify - - RemoveInternalTimer(0, "FW_closeInactiveClients"); - InternalTimer(time()+60, "FW_closeInactiveClients", 0, 0); - - - foreach my $pe ("fhemSVG", "openautomation", "default") { - FW_readIcons($pe); - } - - my $ret = TcpServer_Open($hash, $port, $global); - - # Make sure that fhem only runs once - if($ret && !$init_done) { - Log3 $hash, 1, "$ret. Exiting."; - exit(1); - } - - $hash->{CSRFTOKEN} = $FW_csrfTokenCache{$name}; - if(!defined($hash->{CSRFTOKEN})) { # preserve over rereadcfg - InternalTimer(1, sub(){ - if($featurelevel >= 5.8 && !AttrVal($name, "csrfToken", undef)) { - my ($x,$y) = gettimeofday(); - ($defs{$name}{CSRFTOKEN}="csrf_".(rand($y)*rand($x))) =~s/[^a-z_0-9]//g; - $FW_csrfTokenCache{$name} = $hash->{CSRFTOKEN}; - } - }, $hash, 0); - } - - return $ret; -} - -##################################### -sub -FW_Undef($$) -{ - my ($hash, $arg) = @_; - my $ret = TcpServer_Close($hash); - if($hash->{inform}) { - delete $FW_id2inform{$hash->{FW_ID}} if($hash->{FW_ID}); - %FW_visibleDeviceHash = FW_visibleDevices(); - delete($logInform{$hash->{NAME}}); - } - return $ret; -} - -##################################### -sub -FW_Read($$) -{ - my ($hash, $reread) = @_; - my $name = $hash->{NAME}; - - if($hash->{SERVERSOCKET}) { # Accept and create a child - my $nhash = TcpServer_Accept($hash, "FHEMWEB"); - return if(!$nhash); - my $wt = AttrVal($name, "alarmTimeout", undef); - $nhash->{ALARMTIMEOUT} = $wt if($wt); - $nhash->{CD}->blocking(0); - return; - } - - $FW_chash = $hash; - $FW_wname = $hash->{SNAME}; - $FW_cname = $name; - $FW_subdir = ""; - - my $c = $hash->{CD}; - - if(!$reread) { - # Data from HTTP Client - my $buf; - my $ret = sysread($c, $buf, 1024); - - if(!defined($ret) && $! == EWOULDBLOCK ){ - $hash->{wantWrite} = 1 - if(TcpServer_WantWrite($hash)); - return; - } elsif(!$ret) { # 0==EOF, undef=error - CommandDelete(undef, $name); - Log3 $FW_wname, 4, "Connection closed for $name: ". - (defined($ret) ? 'EOF' : $!); - return; - } - $hash->{BUF} .= $buf; - if($hash->{SSL} && $c->can('pending')) { - while($c->pending()) { - sysread($c, $buf, 1024); - $hash->{BUF} .= $buf; - } - } - } - - if($hash->{websocket}) { # 59713 - my $fin = (ord(substr($hash->{BUF},0,1)) & 0x80)?1:0; - my $op = (ord(substr($hash->{BUF},0,1)) & 0x0F); - my $mask = (ord(substr($hash->{BUF},1,1)) & 0x80)?1:0; - my $len = (ord(substr($hash->{BUF},1,1)) & 0x7F); - my $i = 2; - - if($op == 8) { - TcpServer_Close($hash, 1); - return; - } - - if( $len == 126 ) { - $len = unpack( 'n', substr($hash->{BUF},$i,2) ); - $i += 2; - } elsif( $len == 127 ) { - $len = unpack( 'q', substr($hash->{BUF},$i,8) ); - $i += 8; - } - - if( $mask ) { - $mask = substr($hash->{BUF},$i,4); - $i += 4; - } - - #my $data = substr($hash->{BUF}, $i, $len); - #for( my $i = 0; $i < $len; $i++ ) { - # substr( $data, $i, 1, substr( $data, $i, 1, ) ^ substr($mask, $i% , 1) ); - #} - #Log 1, "Received via websocket: ".unpack("H*",$data); - $hash->{BUF} = ""; - return; - } - - - - if(!$hash->{HDR}) { - return if($hash->{BUF} !~ m/^(.*?)(\n\n|\r\n\r\n)(.*)$/s); - $hash->{HDR} = $1; - $hash->{BUF} = $3; - if($hash->{HDR} =~ m/Content-Length:\s*([^\r\n]*)/si) { - $hash->{CONTENT_LENGTH} = $1; - } - } - - my $POSTdata = ""; - if($hash->{CONTENT_LENGTH}) { - return if(length($hash->{BUF})<$hash->{CONTENT_LENGTH}); - $POSTdata = substr($hash->{BUF}, 0, $hash->{CONTENT_LENGTH}); - $hash->{BUF} = substr($hash->{BUF}, $hash->{CONTENT_LENGTH}); - } - - @FW_httpheader = split(/[\r\n]+/, $hash->{HDR}); - %FW_httpheader = map { - my ($k,$v) = split(/: */, $_, 2); - $k =~ s/(\w+)/\u$1/g; # Forum #39203 - $k=>(defined($v) ? $v : 1); - } @FW_httpheader; - delete($hash->{HDR}); - - my @origin = grep /Origin/i, @FW_httpheader; - $FW_headerlines = (AttrVal($FW_wname, "CORS", 0) ? - (($#origin<0) ? "": "Access-Control-Allow-".$origin[0]."\r\n"). - "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n". - "Access-Control-Allow-Headers: Origin, Authorization, Accept\r\n". - "Access-Control-Allow-Credentials: true\r\n". - "Access-Control-Max-Age:86400\r\n". - "Access-Control-Expose-Headers: X-FHEM-csrfToken\r\n": ""); - $FW_headerlines .= "X-FHEM-csrfToken: $defs{$FW_wname}{CSRFTOKEN}\r\n" - if(defined($defs{$FW_wname}{CSRFTOKEN}) && - AttrVal($FW_wname, "csrfTokenHTTPHeader", 1)); - - my $hh = AttrVal($FW_wname, "httpHeader", undef); - $FW_headerlines .= "$hh\r\n" if($hh); - - ######################### - # Return 200 for OPTIONS or 405 for unsupported method - my ($method, $arg, $httpvers) = split(" ", $FW_httpheader[0], 3) - if($FW_httpheader[0]); - $method = "" if(!$method); - my $ahm = AttrVal($FW_wname, "allowedHttpMethods", "GET|POST"); - if($method !~ m/^($ahm)$/i){ - my $retCode = ($method eq "OPTIONS") ? "200 OK" : "405 Method Not Allowed"; - TcpServer_WriteBlocking($FW_chash, - "HTTP/1.1 $retCode\r\n" . - $FW_headerlines. - "Content-Length: 0\r\n\r\n"); - delete $hash->{CONTENT_LENGTH}; - FW_Read($hash, 1) if($hash->{BUF}); - Log 3, "$FW_cname: unsupported HTTP method $method, rejecting it." - if($retCode ne "200 OK"); - FW_closeConn($hash); - return; - } - - ############################# - # AUTH - if(!defined($FW_chash->{Authenticated})) { - my $ret = Authenticate($FW_chash, \%FW_httpheader); - if($ret == 0) { - $FW_chash->{Authenticated} = 0; # not needed - - } elsif($ret == 1) { - $FW_chash->{Authenticated} = 1; # ok - # Need to send set-cookie (if set) after succesful authentication - my $ah = $FW_chash->{".httpAuthHeader"}; - $FW_headerlines .= $ah if($ah); - delete $FW_chash->{".httpAuthHeader"}; - - } else { - my $ah = $FW_chash->{".httpAuthHeader"}; - TcpServer_WriteBlocking($hash, - ($ah ? $ah : ""). - $FW_headerlines. - "Content-Length: 0\r\n\r\n"); - delete $hash->{CONTENT_LENGTH}; - FW_Read($hash, 1) if($hash->{BUF}); - return; - } - } else { - my $ah = $FW_chash->{".httpAuthHeader"}; - $FW_headerlines .= $ah if($ah); - } - ############################# - - my $now = time(); - $arg .= "&".$POSTdata if($POSTdata); - delete $hash->{CONTENT_LENGTH}; - $hash->{LASTACCESS} = $now; - - $FW_userAgent = $FW_httpheader{"User-Agent"}; - $FW_userAgent = "" if(!defined($FW_userAgent)); - - $FW_ME = "/" . AttrVal($FW_wname, "webname", "fhem"); - $FW_CSRF = (defined($defs{$FW_wname}{CSRFTOKEN}) ? - "&fwcsrf=".$defs{$FW_wname}{CSRFTOKEN} : ""); - - if($FW_use{sha} && $method eq 'GET' && - $FW_httpheader{Connection} && $FW_httpheader{Connection} =~ /Upgrade/i) { - - my $shastr = Digest::SHA::sha1_base64($FW_httpheader{'Sec-WebSocket-Key'}. - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); - - TcpServer_WriteBlocking($FW_chash, - "HTTP/1.1 101 Switching Protocols\r\n" . - "Upgrade: websocket\r\n" . - "Connection: Upgrade\r\n" . - "Sec-WebSocket-Accept:$shastr=\r\n". - $FW_headerlines. - "\r\n" ); - $FW_chash->{websocket} = 1; - - my $me = $FW_chash; - my ($cmd, $cmddev) = FW_digestCgi($arg); - if($FW_id) { - $me->{FW_ID} = $FW_id; - $me->{canAsyncOutput} = 1; - } - FW_initInform($me, 0) if($FW_inform); - return -1; - } - - $arg = "" if(!defined($arg)); - Log3 $FW_wname, 4, "$name $method $arg; BUFLEN:".length($hash->{BUF}); - my $pf = AttrVal($FW_wname, "plotfork", 0); - if($pf) { # 0 disables - # Process SVG rendering as a parallel process - my $p = $data{FWEXT}; - if(grep { $p->{$_}{FORKABLE} && $arg =~ m+^$FW_ME$_+ } keys %{$p}) { - my $pid = fhemFork(); - if($pid) { # success, parent - use constant PRIO_PROCESS => 0; - setpriority(PRIO_PROCESS, $pid, getpriority(PRIO_PROCESS,$pid) + $pf) - if($^O !~ m/Win/); - # a) while child writes a new request might arrive if client uses - # pipelining or - # b) parent doesn't know about ssl-session changes due to child writing - # to socket - # -> have to close socket in parent... so that its only used in this - # child. - TcpServer_Disown( $hash ); - delete($defs{$name}); - delete($attr{$name}); - FW_Read($hash, 1) if($hash->{BUF}); - return; - - } elsif(defined($pid)){ # child - delete $hash->{BUF}; - $hash->{isChild} = 1; - - } # fork failed and continue in parent - } - } - - $FW_httpRetCode = "200 OK"; - my $cacheable = FW_answerCall($arg); - if($cacheable == -1) { - FW_closeConn($hash); - return; - } - return if($cacheable == -2); # async op, well be answered later - FW_finishRead($hash, $cacheable, $arg); - -} - -sub -FW_finishRead($$$) -{ - my ($hash, $cacheable, $arg) = @_; - my $name = $hash->{NAME}; - - my $compressed = ""; - if($FW_RETTYPE =~ m/(text|xml|json|svg|script)/i && - ($FW_httpheader{"Accept-Encoding"} && - $FW_httpheader{"Accept-Encoding"} =~ m/gzip/) && - $FW_use{zlib}) { - utf8::encode($FW_RET) - if(utf8::is_utf8($FW_RET) && $FW_RET =~ m/[^\x00-\xFF]/ ); - eval { $FW_RET = Compress::Zlib::memGzip($FW_RET); }; - if($@) { - Log 1, "memGzip: $@"; $FW_RET=""; #Forum #29939 - } else { - $compressed = "Content-Encoding: gzip\r\n"; - } - } - - my $length = length($FW_RET); - my $expires = ($cacheable ? - "Expires: ".FmtDateTimeRFC1123($hash->{LASTACCESS}+900)."\r\n" : - "Cache-Control: no-cache, no-store, must-revalidate\r\n"); - Log3 $FW_wname, 4, - "$FW_wname: $arg / RL:$length / $FW_RETTYPE / $compressed / $expires"; - if( ! FW_addToWritebuffer($hash, - "HTTP/1.1 $FW_httpRetCode\r\n" . - "Content-Length: $length\r\n" . - $expires . $compressed . $FW_headerlines . - "Content-Type: $FW_RETTYPE\r\n\r\n" . - $FW_RET, "FW_closeConn", 1) ){ - Log3 $name, 4, "Closing connection $name due to full buffer in FW_Read" - if(!$hash->{isChild}); - FW_closeConn($hash); - TcpServer_Close($hash, 1); - } -} - -sub -FW_initInform($$) -{ - my ($me, $longpoll) = @_; - - if($FW_inform =~ /type=/) { - foreach my $kv (split(";", $FW_inform)) { - my ($key,$value) = split("=", $kv, 2); - $me->{inform}{$key} = $value; - } - - } else { # Compatibility mode - $me->{inform}{type} = ($FW_room ? "status" : "raw"); - $me->{inform}{filter} = ($FW_room ? $FW_room : ".*"); - } - $FW_id2inform{$FW_id} = $me if($FW_id); - - my $filter = $me->{inform}{filter}; - $filter =~ s/([[\]().+?])/\\$1/g if($filter =~ m/room=/); # Forum #80390 - $filter = "NAME=.*" if($filter eq "room=all"); - $filter = "room!=.+" if($filter eq "room=Unsorted"); - - my %h = map { $_ => 1 } devspec2array($filter); - $h{global} = 1 if( $me->{inform}{addglobal} ); - $h{"#FHEMWEB:$FW_wname"} = 1; - $me->{inform}{devices} = \%h; - %FW_visibleDeviceHash = FW_visibleDevices(); - - # NTFY_ORDER is larger than the normal order (50-) - $me->{NTFY_ORDER} = $FW_cname; # else notifyfn won't be called - %ntfyHash = (); - $me->{inform}{since} = time()-5 - if(!defined($me->{inform}{since}) || $me->{inform}{since} !~ m/^\d+$/); - - my $sinceTimestamp = FmtDateTime($me->{inform}{since}); - if($longpoll) { - TcpServer_WriteBlocking($me, - "HTTP/1.1 200 OK\r\n". - $FW_headerlines. - "Content-Type: application/octet-stream; charset=$FW_encoding\r\n\r\n". - FW_roomStatesForInform($me, $sinceTimestamp)); - - } else { # websocket - FW_addToWritebuffer($me, - FW_roomStatesForInform($me, $sinceTimestamp)); - } - - if($FW_id && $defs{$FW_wname}{asyncOutput}) { - my $data = $defs{$FW_wname}{asyncOutput}{$FW_id}; - if($data) { - FW_addToWritebuffer($me, $data."\n"); - delete $defs{$FW_wname}{asyncOutput}{$FW_id}; - } - } - - if($me->{inform}{withLog}) { - $logInform{$me->{NAME}} = "FW_logInform"; - } else { - delete($logInform{$me->{NAME}}); - } -} - - -sub -FW_addToWritebuffer($$@) -{ - my ($hash, $txt, $callback, $nolimit) = @_; - - if( $hash->{websocket} ) { - my $len = length($txt); - if( $len < 126 ) { - $txt = chr(0x81) . chr($len) . $txt; - } else { - if ( $len < 65536 ) { - $txt = chr(0x81) . chr(0x7E) . pack('n', $len) . $txt; - } else { - $txt = chr(0x81) . chr(0x7F) . chr(0x00) . chr(0x00) . - chr(0x00) . chr(0x00) . pack('N', $len) . $txt; - } - } - } - return addToWritebuffer($hash, $txt, $callback, $nolimit); -} - -sub -FW_AsyncOutput($$) -{ - my ($hash, $ret) = @_; - - return if(!$hash || !$hash->{FW_ID}); - if( $ret =~ m/^(.*)<\/html>$/s ) { - $ret = $1; - - } else { - $ret = FW_htmlEscape($ret); - $ret = "
$ret
" if($ret =~ m/\n/ ); - $ret =~ s/\n/
/g; - } - - my $data = FW_longpollInfo('JSON', - "#FHEMWEB:$FW_wname","FW_okDialog('$ret')",""); - - # find the longpoll connection with the same fw_id as the page that was the - # origin of the get command - my $fwid = $hash->{FW_ID}; - if(!$fwid) { - Log3 $hash->{SNAME}, 4, "AsyncOutput from $hash->{NAME} without FW_ID"; - return; - } - Log3 $hash->{SNAME}, 4, "AsyncOutput from $hash->{NAME}"; - $hash = $FW_id2inform{$fwid}; - if($hash) { - FW_addToWritebuffer($hash, $data."\n"); - } else { - $defs{$FW_wname}{asyncOutput}{$fwid} = $data; - } - return undef; -} - -sub -FW_closeConn($) -{ - my ($hash) = @_; - if(!$hash->{inform} && !$hash->{BUF}) { # Forum #41125 - my $cc = AttrVal($hash->{SNAME}, "closeConn", - $FW_userAgent =~ m/(iPhone|iPad|iPod)/); - if(!$FW_httpheader{Connection} || $cc) { - TcpServer_Close($hash, 1); - } - } - - POSIX::exit(0) if($hash->{isChild}); - FW_Read($hash, 1) if($hash->{BUF}); -} - -########################### -sub -FW_serveSpecial($$$$) -{ - my ($file,$ext,$dir,$cacheable)= @_; - $file =~ s,\.\./,,g; # little bit of security - - $file = "$FW_sp$file" if($ext eq "css" && -f "$dir/$FW_sp$file.$ext"); - $FW_RETTYPE = ext2MIMEType($ext); - my $fname = ($ext ? "$file.$ext" : $file); - return FW_returnFileAsStream("$dir/$fname", "", $FW_RETTYPE, 0, $cacheable); -} - -sub -FW_answerCall($) -{ - my ($arg) = @_; - my $me=$defs{$FW_cname}; # cache, else rereadcfg will delete us - - $FW_RET = ""; - $FW_RETTYPE = "text/html; charset=$FW_encoding"; - - $MW_dir = "$attr{global}{modpath}/FHEM"; - $FW_sp = AttrVal($FW_wname, "stylesheetPrefix", "f18"); - $FW_ss = ($FW_sp =~ m/smallscreen/); - $FW_tp = ($FW_sp =~ m/smallscreen|touchpad/); - my $spDir = ($FW_sp eq "default" ? "" : "$FW_sp:"); - @FW_iconDirs = grep { $_ } split(":", AttrVal($FW_wname, "iconPath", - "${spDir}fhemSVG:openautomation:default")); - @FW_fhemwebjs = ("fhemweb.js"); - push(@FW_fhemwebjs, "$FW_sp.js") if(-r "$FW_dir/pgm2/$FW_sp.js"); - - if($arg =~ m,$FW_ME/floorplan/([a-z0-9.:_]+),i) { # FLOORPLAN: special icondir - unshift @FW_iconDirs, $1; - FW_readIcons($1); - } - - # /icons/... => current state of ... - # also used for static images: unintended, but too late to change - - my ($dir1, $dirN, $ofile) = ($1, $2, $3) - if($arg =~ m,^$FW_ME/([^/]*)(.*/)([^/]*)$,); - if($arg =~ m,\brobots.txt$,) { - Log3 $FW_wname, 1, "NOTE: $FW_wname is probed by a search engine"; - $FW_RETTYPE = "text/plain; charset=$FW_encoding"; - FW_pO "User-agent: *\r"; - FW_pO "Disallow: *\r"; - return 0; - - } elsif($arg =~ m,^$FW_ME/icons/(.*)$,) { - my ($icon,$cacheable) = (urlDecode($1), 1); - my $iconPath = FW_iconPath($icon); - - # if we do not have the icon, we convert the device state to the icon name - if(!$iconPath) { - my ($img, $link, $isHtml) = FW_dev2image($icon); - $cacheable = 0; - return 0 if(!$img); - $iconPath = FW_iconPath($img); - if($iconPath =~ m/\.svg$/i) { - $FW_RETTYPE = ext2MIMEType("svg"); - FW_pO FW_makeImage($img, $img); - return 0; - } - } elsif($iconPath =~ m/\.svg$/i && $icon=~ m/@/) { - $FW_RETTYPE = ext2MIMEType("svg"); - FW_pO FW_makeImage($icon, $icon); - return 0; - } - $iconPath =~ m/(.*)\.([^.]*)/; - return FW_serveSpecial($1, $2, $FW_icondir, $cacheable); - - } elsif($dir1 && !$data{FWEXT}{"/$dir1"}) { - my $dir = "$dir1$dirN"; - my $ext = ""; - $dir =~ s,/$,,; - $dir =~ s/\.\.//g; - $dir =~ s,www/,,g; # Want commandref.html to work from file://... - - my $file = urlDecode($ofile); # 69164 - $file =~ s/\?.*//; # Remove timestamp of CSS reloader - if($file =~ m/^(.*)\.([^.]*)$/) { - $file = $1; $ext = $2; - } - my $ldir = "$FW_dir/$dir"; - $ldir = "$FW_dir/pgm2" if($dir eq "css" || $dir eq "js"); # FLOORPLAN compat - $ldir = "$attr{global}{modpath}/docs" if($dir eq "docs"); - - # pgm2 check is for jquery-ui images - my $static = ($ext =~ m/(css|js|png|jpg)/i || $dir =~ m/^pgm2/); - my $fname = ($ext ? "$file.$ext" : $file); - return FW_serveSpecial($file, $ext, $ldir, ($arg =~ m/nocache/) ? 0 : 1) - if(-r "$ldir/$fname" || $static); # no return for FLOORPLAN - $arg = "/$dir/$ofile"; - - } elsif($arg =~ m/^$FW_ME(.*)/s) { - $arg = $1; # The stuff behind FW_ME, continue to check for commands/FWEXT - - } else { - Log3 $FW_wname, 4, "$FW_wname: redirecting $arg to $FW_ME"; - TcpServer_WriteBlocking($me, - "HTTP/1.1 302 Found\r\n". - "Content-Length: 0\r\n". - $FW_headerlines. - "Location: $FW_ME\r\n\r\n"); - FW_closeConn($FW_chash); - return -1; - } - - - $FW_plotmode = AttrVal($FW_wname, "plotmode", "SVG"); - $FW_plotsize = AttrVal($FW_wname, "plotsize", $FW_ss ? "480,160" : - $FW_tp ? "640,160" : "800,160"); - my ($cmd, $cmddev) = FW_digestCgi($arg); - if($cmd && $FW_CSRF && $cmd !~ m/style (list|select|eventMonitor)/) { - my $supplied = defined($FW_webArgs{fwcsrf}) ? $FW_webArgs{fwcsrf} : ""; - my $want = $defs{$FW_wname}{CSRFTOKEN}; - if($supplied ne $want) { - Log3 $FW_wname, 3, "FHEMWEB $FW_wname CSRF error: $supplied ne $want ". - "for client $FW_chash->{NAME} / command $cmd. ". - "For details see the csrfToken FHEMWEB attribute."; - $FW_httpRetCode = "400 Bad Request"; - return 0; - } - } - - if( $FW_id ) { - $me->{FW_ID} = $FW_id; - $me->{canAsyncOutput} = 1; - } - - if($FW_inform) { # Longpoll header - FW_initInform($me, 1); - return -1; - } - - my $docmd = 0; - $docmd = 1 if($cmd && - $cmd !~ /^showlog/ && - $cmd !~ /^style / && - $cmd !~ /^edit/); - - #If we are in XHR or json mode, execute the command directly - if($FW_XHR || $FW_jsonp) { - $FW_cmdret = $docmd ? FW_fC($cmd, $cmddev) : undef; - $FW_RETTYPE = $FW_chash->{contenttype} ? - $FW_chash->{contenttype} : "text/plain; charset=$FW_encoding"; - delete($FW_chash->{contenttype}); - - if($FW_jsonp) { - $FW_cmdret =~ s/'/\\'/g; - # Escape newlines in JavaScript string - $FW_cmdret =~ s/\n/\\\n/g; - FW_pO "$FW_jsonp('$FW_cmdret');"; - - } else { - $FW_cmdret = FW_addLinks($FW_cmdret) if($FW_webArgs{addLinks}); - FW_pO $FW_cmdret; - - } - return 0; - } - ############################## - # FHEMWEB extensions (FLOORPLOAN, SVG_WriteGplot, etc) - my $FW_contentFunc; - if(defined($data{FWEXT})) { - foreach my $k (sort keys %{$data{FWEXT}}) { - my $h = $data{FWEXT}{$k}; - next if($arg !~ m/^$k/); - $FW_contentFunc = $h->{CONTENTFUNC}; - next if($h !~ m/HASH/ || !$h->{FUNC}); - #Returns undef as FW_RETTYPE if it already sent a HTTP header - no strict "refs"; - ($FW_RETTYPE, $FW_RET) = &{$h->{FUNC}}($arg); - if(defined($FW_RETTYPE) && $FW_RETTYPE =~ m,text/html,) { - my $dataAttr = FW_dataAttr(); - $FW_RET =~ s/'; - FW_pO ''; - FW_pO "\n$t"; - FW_pO ''; - FW_pO ""; # Forum 28666 - FW_pO "";#Forum 18316 - - # Enable WebApps - if($FW_tp || $FW_ss) { - my $icon = FW_iconPath("fhemicon_ios.png"); - $icon = $FW_ME."/images/".($icon ? $icon : "default/fhemicon_ios.png"); - my $viewport = ''; - if($FW_ss) { - my $stf = $FW_userAgent =~ m/iPad|iPhone|iPod/ ? ",shrink-to-fit=no" :""; - $viewport = "initial-scale=1.0,user-scalable=1$stf"; - } elsif($FW_tp) { - $viewport = "width=768"; - } - $viewport = AttrVal($FW_wname, "viewport", $viewport); - FW_pO '' if ($viewport); - FW_pO ''; - FW_pO ''; # Forum #36183 - FW_pO ''; - FW_pO ''; - } - - if(!$FW_detail) { - my $rf = AttrVal($FW_wname, "refresh", ""); - FW_pO "" if($rf); - } - - ######################## - # CSS - my $cssTemplate = ""; - FW_pO sprintf($cssTemplate, "pgm2/style.css?v=$FW_styleStamp"); - FW_pO sprintf($cssTemplate, "pgm2/jquery-ui.min.css"); - map { FW_pO sprintf($cssTemplate, $_); } - split(" ", AttrVal($FW_wname, "CssFiles", "")); - - my $sd = AttrVal($FW_wname, "styleData", ""); # Avoid flicker in f18 - if($sd && $sd =~ m/"$FW_sp":/s) { - my $bg; - $bg = $1 if($FW_room && $sd =~ m/"Room\.$FW_room\.cols.bg": "([^"]*)"/s); - $bg = $1 if(!defined($bg) && $sd =~ m/"cols.bg": "([^"]*)"/s); - - my $bgImg; - $bgImg = $1 if($FW_room && $sd =~ m/"Room\.$FW_room\.bgImg": "([^"]*)"/s); - $bgImg = $1 if(!defined($bgImg) && $sd =~ m/"bgImg": "([^"]*)"/s); - - FW_pO ""; - } - - my $css = AttrVal($FW_wname, "Css", ""); - FW_pO "\n" if($css); - - ######################## - # JavaScripts - my $jsTemplate = - ''; - FW_pO sprintf($jsTemplate, "", "$FW_ME/pgm2/jquery.min.js"); - FW_pO sprintf($jsTemplate, "", "$FW_ME/pgm2/jquery-ui.min.js"); - - my (%jsNeg, @jsList); # jsNeg was used to exclude automatically loaded files - map { $_ =~ m/^-(.*)$/ ? $jsNeg{$1} = 1 : push(@jsList, $_); } - split(" ", AttrVal($FW_wname, "JavaScripts", "")); - map { FW_pO sprintf($jsTemplate, "", "$FW_ME/pgm2/$_") if(!$jsNeg{$_}); } - @FW_fhemwebjs; - - ####################### - # "Own" JavaScripts + their Attributes - map { - my $n = $_; $n =~ s+.*/++; $n =~ s/.js$//; $n =~ s/fhem_//; $n .= "Param"; - FW_pO sprintf($jsTemplate, AttrVal($FW_wname, $n, ""), "$FW_ME/$_"); - } @jsList; - - ######################## - # FW Extensions - if(defined($data{FWEXT})) { - foreach my $k (sort keys %{$data{FWEXT}}) { - my $h = $data{FWEXT}{$k}; - next if($h !~ m/HASH/ || !$h->{SCRIPT} || $h->{SCRIPT} =~ m+pgm2/jquery+); - my $script = $h->{SCRIPT}; - $script = ($script =~ m,^/,) ? "$FW_ME$script" : "$FW_ME/pgm2/$script"; - FW_pO sprintf($jsTemplate, "", $script); - } - } - - my $csrf= ($FW_CSRF ? "fwcsrf='$defs{$FW_wname}{CSRFTOKEN}'" : ""); - my $gen = 'generated="'.(time()-1).'"'; - my $lp = 'longpoll="'.AttrVal($FW_wname,"longpoll", - $FW_use{sha} && $FW_userAgent=~m/Chrome/ ? "websocket": 1).'"'; - $FW_id = $FW_chash->{NR} if( !$FW_id ); - - my $dataAttr = FW_dataAttr(); - FW_pO "\n"; - - if($FW_activateInform) { - $cmd = "style eventMonitor $FW_activateInform"; - $FW_cmdret = undef; - $FW_activateInform = ""; - } - - FW_roomOverview($cmd); - - if(defined($FW_cmdret)) { - $FW_detail = ""; - $FW_room = ""; - - if( $FW_cmdret =~ m/^(.*)<\/html>$/s ) { - $FW_cmdret = $1; - - } else { # "linkify" output (e.g. for list) - $FW_cmdret = FW_addLinks(FW_htmlEscape($FW_cmdret)); - $FW_cmdret =~ s/:\S+//g if($FW_cmdret =~ m/unknown.*choose one of/i); - $FW_cmdret = "
$FW_cmdret
" if($FW_cmdret =~ m/\n/); - } - - FW_addContent(); - if($FW_ss) { - FW_pO "
$FW_cmdret
"; - } else { - FW_pO $FW_cmdret; - } - FW_pO ""; - - } - - if($FW_contentFunc) { - no strict "refs"; - my $ret = &{$FW_contentFunc}($arg); - use strict "refs"; - return $ret if($ret); - } - - my $srVal = 0; - if($cmd =~ m/^style /) { FW_style($cmd,undef); } - elsif($FW_detail) { FW_doDetail($FW_detail); } - elsif($FW_room) { $srVal = FW_showRoom(); } - elsif(!defined($FW_cmdret) && - !$FW_contentFunc) { - - $FW_room = AttrVal($FW_wname, "defaultRoom", ''); - if($FW_room ne '') { - $srVal = FW_showRoom(); - - } else { - my $motd = AttrVal("global","motd","none"); - if($motd ne "none") { - FW_addContent(">
$motd
"; - return 0; -} - -sub -FW_dataAttr() -{ - sub - addParam($$) - { - my ($p, $default) = @_; - my $val = AttrVal($FW_wname,$p, $default); - $val =~ s/&/&/g; - $val =~ s/'/"/g; - return "data-$p='$val' "; - } - - return - addParam("confirmDelete", 1). - addParam("confirmJSError", 1). - addParam("addHtmlTitle", 1). - addParam("styleData", ""). - "data-availableJs='$FW_fhemwebjs' ". - "data-webName='$FW_wname '"; -} - -sub -FW_addContent(;$) -{ - my $add = ($_[0] ? " $_[0]" : ""); - FW_pO "
"; -} - -sub -FW_addLinks($) -{ - my ($txt) = @_; - return undef if(!defined($txt)); - $txt =~ s,\b([a-z0-9._]+)\b, - $defs{$1} ? "$1" : $1,gei; - return $txt; -} - - -########################### -# Digest CGI parameters -sub -FW_digestCgi($) -{ - my ($arg) = @_; - my (%arg, %val, %dev); - my ($cmd, $c) = ("","",""); - - %FW_pos = (); - $FW_room = ""; - $FW_detail = ""; - $FW_XHR = undef; - $FW_id = ""; - $FW_jsonp = undef; - $FW_inform = undef; - - %FW_webArgs = (); - #Remove (nongreedy) everything including the first '?' - $arg =~ s,^.*?[?],,; - foreach my $pv (split("&", $arg)) { - next if($pv eq ""); # happens when post forgot to set FW_ME - $pv =~ s/\+/ /g; - $pv =~ s/%([\dA-F][\dA-F])/chr(hex($1))/ige; - my ($p,$v) = split("=",$pv, 2); - $v = "" if(!defined($v)); - - # Multiline: escape the NL for fhem - $v =~ s/[\r]//g if($v && $p && $p ne "data"); - $FW_webArgs{$p} = $v; - - if($p eq "detail") { $FW_detail = $v; } - if($p eq "room") { $FW_room = $v; } - if($p eq "cmd") { $cmd = $v; } - if($p =~ m/^arg\.(.*)$/) { $arg{$1} = $v; } - if($p =~ m/^val\.(.*)$/) { $val{$1} = ($val{$1} ? $val{$1}.",$v" : $v) } - if($p =~ m/^dev\.(.*)$/) { $dev{$1} = $v; } - if($p =~ m/^cmd\.(.*)$/) { $cmd = $v; $c = $1; } - if($p eq "pos") { %FW_pos = split(/[=;]/, $v); } - if($p eq "data") { $FW_data = $v; } - if($p eq "XHR") { $FW_XHR = 1; } - if($p eq "fw_id") { $FW_id = $v; } - if($p eq "jsonp") { $FW_jsonp = $v; } - if($p eq "inform") { $FW_inform = $v; } - - } - $cmd.=" $dev{$c}" if(defined($dev{$c})); - $cmd.=" $arg{$c}" if(defined($arg{$c})); - $cmd.=" $val{$c}" if(defined($val{$c})); - - #replace unicode newline symbol \u2424 with real newline - my $nl = chr(226) . chr(144) . chr(164); - $cmd =~ s/$nl/\n/g; - - return ($cmd, $c); -} - -##################### -# create FW_rooms && FW_types -sub -FW_updateHashes() -{ - %FW_rooms = (); # Make a room hash - %FW_groups = (); # Make a group hash - %FW_types = (); # Needed for type sorting - - my $hre = AttrVal($FW_wname, "hiddenroomRegexp", ""); - foreach my $d (keys %defs ) { - next if(IsIgnored($d)); - - foreach my $r (split(",", AttrVal($d, "room", "Unsorted"))) { - next if($hre && $r =~ m/$hre/); - $FW_rooms{$r}{$d} = 1; - } - foreach my $r (split(",", AttrVal($d, "group", ""))) { - $FW_groups{$r}{$d} = 1; - } - my $t = AttrVal($d, "subType", $defs{$d}{TYPE}); - $t = AttrVal($d, "model", $t) if($t && $t eq "unknown"); # RKO: ??? - $FW_types{$d} = $t; - } - - $FW_room = AttrVal($FW_detail, "room", "Unsorted") if($FW_detail); - - if(AttrVal($FW_wname, "sortRooms", "")) { # Slow! - my @sortBy = split( " ", AttrVal( $FW_wname, "sortRooms", "" ) ); - my %sHash; - map { $sHash{$_} = FW_roomIdx(\@sortBy,$_) } keys %FW_rooms; - @FW_roomsArr = sort { $sHash{$a} cmp $sHash{$b} } keys %FW_rooms; - - } else { - @FW_roomsArr = sort keys %FW_rooms; - - } -} - -############################## -sub -FW_makeTable($$$@) -{ - my($title, $name, $hash, $cmd) = (@_); - - return if(!$hash || !int(keys %{$hash})); - my $class = lc($title); - $class =~ s/[^A-Za-z]/_/g; - FW_pO "
"; - FW_pO "$title"; - FW_pO ""; - my $si = AttrVal("global", "showInternalValues", 0); - - my $row = 1; - my $prefix = ($title eq "Attributes" ? "a-" : ""); - foreach my $n (sort keys %{$hash}) { - next if(!$si && $n =~ m/^\./); # Skip "hidden" Values - my $val = $hash->{$n}; - $val = "" if(!defined($val)); - - $val = $hash->{$n}{NAME} # Exception - if($n eq "IODev" && ref($val) eq "HASH" && defined($hash->{$n}{NAME})); - - my $r = ref($val); - next if($r && ($r ne "HASH" || !defined($hash->{$n}{VAL}))); - - FW_pF "", ($row&1)?"odd":"even"; - $row++; - - if($n eq "DEF" && !$FW_hiddenroom{input}) { - FW_makeEdit($name, $n, $val); - - } else { - FW_pO ""; - - if(ref($val)) { #handle readings - my ($v, $t) = ($val->{VAL}, $val->{TIME}); - if($v =~ m,^(.*)$,) { - $v = $1; - } else { - $v = FW_htmlEscape($v); - $v = "
$v
" if($v =~ m/\n/); - } - - my $ifid = "class='dval' informId='$name-$prefix$n'"; - my $ifidts = "informId='$name-$prefix$n-ts'"; - if($FW_ss) { - $t = ($t ? "
$t
" : ""); - FW_pO ""; - } else { - $t = "" if(!$t); - FW_pO ""; - FW_pO ""; - } - } else { - $val = FW_htmlEscape($val); - my $tattr = "informId=\"$name-$prefix$n\" class=\"dval\""; - - # if possible provide some links - if ($n eq "room"){ - FW_pO ""; - - } elsif ($n =~ m/^fp_(.*)/ && $defs{$1}){ #special for Floorplan - FW_pH "detail=$1", $val,1; - - } elsif ($modules{$val} ) { - FW_pH "cmd=list%20TYPE=$val", $val,1; - - } else { - $val = "
$val
" if($val =~ m/\n/); - FW_pO ""; - } - } - - } - - FW_pH "cmd.$name=$cmd $name $n&detail=$name", $cmd, 1 - if($cmd && !$FW_ss); - FW_pO ""; - } - FW_pO "
$n
$v$t
$v
$t
". - join(",", map { FW_pH("room=$_",$_,0,"",1,1) } split(",",$val)). - "
". - join(",", map { ($_ ne $name && $defs{$_}) ? - FW_pH( "detail=$_", $_ ,0,"",1,1) : $_ } split(",",$val)). - "
"; - FW_pO "
"; - -} - -############################## -# Used only for set or attr lists. -sub -FW_detailSelect(@) -{ - my ($d, $cmd, $list, $param) = @_; - return "" if(!$list || $FW_hiddenroom{input}); - my %al = map { s/:.*//;$_ => 1 } split(" ", $list); - my @al = sort keys %al; # remove duplicate items in list - - my $selEl = (defined($al[0]) ? $al[0] : " "); - $selEl = $1 if($list =~ m/([^ ]*):slider,/); # promote a slider if available - $selEl = "room" if($list =~ m/room:/); - $list =~ s/"/"/g; - - my $ret =""; - my $psc = AttrVal("global", "perlSyntaxCheck", ($featurelevel>5.7) ? 1 : 0); - $ret .= "
"; - $ret .= "
"; - $ret .= FW_hidden("detail", $d); - $ret .= FW_hidden("dev.$cmd$d", $d.($param ? " $param":"")); - $ret .= FW_submit("cmd.$cmd$d", $cmd, $cmd.($psc?" psc":"")); - $ret .= "
 $d ". - ($param ? " $param":"")."
"; - $ret .= FW_select("sel_$cmd$d","arg.$cmd$d",\@al, $selEl, $cmd); - $ret .= FW_textfield("val.$cmd$d", 30, $cmd); - $ret .= "
"; - return $ret; -} - -############################## -sub -FW_doDetail($) -{ - my ($d) = @_; - - return if($FW_hiddenroom{detail}); - return if(!defined($defs{$d})); - my $h = $defs{$d}; - my $t = $h->{TYPE}; - $t = "MISSING" if(!defined($t)); - FW_addContent(); - - if($FW_ss) { - my $webCmd = AttrVal($d, "webCmd", undef); - if($webCmd) { - FW_pO ""; - foreach my $cmd (split(":", $webCmd)) { - FW_pO ""; - FW_pH "cmd.$d=set $d $cmd&detail=$d", $cmd, 1, "col1"; - FW_pO ""; - } - FW_pO "
"; - } - } - FW_pO "
"; - - if(!$modules{$t}{FW_detailFn} || $modules{$t}{FW_deviceOverview}) { - my $show = AttrVal($FW_wname, "deviceOverview", "always"); - - if( $show ne 'never' ) { - my %extPage = (); - - if( $show eq 'iconOnly' ) { - my ($allSets, $cmdlist, $txt) = FW_devState($d, $FW_room, \%extPage); - FW_pO "
$txt
"; - - } else { - my $nameDisplay = AttrVal($FW_wname,"nameDisplay",undef); - my %usuallyAtEnd = (); - - my $style = ""; - if( $show eq 'onClick' ) { - my $pgm = "Javascript:" . - "s=document.getElementById('ddtable').style;". - "s.display = s.display=='none' ? 'block' : 'none';". - "s=document.getElementById('ddisp').style;". - "s.display = s.display=='none' ? 'block' : 'none';"; - FW_pO ""; - $style = 'style="display:none"'; - } - - FW_pO "
"; - FW_pO "DeviceOverview"; - FW_pO ""; - FW_makeDeviceLine($d,1,\%extPage,$nameDisplay,\%usuallyAtEnd); - FW_pO "
"; - } - } - } - if($modules{$t}{FW_detailFn}) { - no strict "refs"; - my $txt = &{$modules{$t}{FW_detailFn}}($FW_wname, $d, $FW_room); - FW_pO "
$txt
" if(defined($txt)); - use strict "refs"; - } - - - FW_pO FW_detailSelect($d, "set", - FW_widgetOverride($d, getAllSets($d, $FW_chash))); - FW_pO FW_detailSelect($d, "get", - FW_widgetOverride($d, getAllGets($d, $FW_chash))); - - FW_makeTable("Internals", $d, $h); - FW_makeTable("Readings", $d, $h->{READINGS}); - - my $attrList = getAllAttr($d); - my $roomList = "multiple,".join(",", - sort map { $_ =~ s/ /#/g ;$_} keys %FW_rooms); - my $groupList = "multiple,".join(",", - sort map { $_ =~ s/ /#/g ;$_} keys %FW_groups); - $attrList =~ s/room /room:$roomList /; - $attrList =~ s/group /group:$groupList /; - $attrList = FW_widgetOverride($d, $attrList); - $attrList =~ s/\\/\\\\/g; - $attrList =~ s/'/\\'/g; - FW_pO FW_detailSelect($d, "attr", $attrList); - - FW_makeTable("Attributes", $d, $attr{$d}, "deleteattr"); - FW_makeTableFromArray("Probably associated with", "assoc", getPawList($d)); - - FW_pO "
"; - - my ($link, $txt, $td, $class, $doRet,$nonl) = @_; - - FW_pH "cmd=style iconFor $d", "Select icon", undef, "detLink iconFor"; - FW_pH "cmd=style showDSI $d", "Extend devStateIcon", undef, "detLink showDSI"; - FW_pH "cmd=rawDef $d", "Raw definition", undef, "detLink rawDef"; - FW_pH "cmd=delete $d", "Delete this device ($d)", undef, "detLink delDev" - if($d ne "global"); - my $sfx = AttrVal("global", "language", "EN"); - $sfx = ($sfx eq "EN" ? "" : "_$sfx"); - FW_pH "$FW_ME/docs/commandref${sfx}.html#${t}", "Device specific help", - undef, "detLink devSpecHelp"; - FW_pO "

"; - FW_pO "
"; - -} - -############################## -sub -FW_makeTableFromArray($$@) { - my ($txt,$class,@obj) = @_; - if (@obj>0) { - my $row=1; - FW_pO "
"; - FW_pO "$txt"; - FW_pO ""; - foreach (sort @obj) { - FW_pF ""; - FW_pO ""; - } - FW_pO "
", (($row++)&1)?"odd":"even"; - FW_pH "detail=$_", $_; - FW_pO ""; - FW_pO $defs{$_}{STATE} if(defined($defs{$_}{STATE})); - FW_pO ""; - FW_pH "cmd=list TYPE=$defs{$_}{TYPE}", $defs{$_}{TYPE}; - FW_pO "
"; - } -} - -sub -FW_roomIdx($$) -{ - my ($arr,$v) = @_; - my ($index) = grep { $v =~ /^$arr->[$_]$/ } 0..$#$arr; - - if( !defined($index) ) { - $index = 9999; - } else { - $index = sprintf( "%03i", $index ); - } - - return "$index-$v"; -} - - -############## -# Header, Zoom-Icons & list of rooms at the left. -sub -FW_roomOverview($) -{ - my ($cmd) = @_; - - %FW_hiddenroom = (); - map { $FW_hiddenroom{$_}=1 } split(",",AttrVal($FW_wname,"hiddenroom", "")); - map { $FW_hiddenroom{$_}=1 } split(",",AttrVal($FW_wname,"forbiddenroom","")); - - ############## - # LOGO - my $hasMenuScroll; - if($FW_detail && $FW_ss) { - $FW_room = AttrVal($FW_detail, "room", undef); - $FW_room = $1 if($FW_room && $FW_room =~ m/^([^,]*),/); - $FW_room = "" if(!$FW_room); - FW_pO(FW_pHPlain("room=$FW_room", - "
" . FW_makeImage("back") . "
")); - FW_pO "
$FW_detail details
"; - return; - - } else { - $hasMenuScroll = 1; - FW_pO '" if($hasMenuScroll); - - ############## - # HEADER - FW_pO "
"; - FW_pO '
'; - FW_pO "
"; - FW_pO FW_hidden("fw_id", $FW_id) if($FW_id); - FW_pO FW_hidden("room", $FW_room) if($FW_room); - FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF); - FW_pO FW_textfield("cmd", - AttrVal($FW_wname, "mainInputLength", $FW_ss ? 25 : 40), "maininput"); - FW_pO "
"; - FW_pO "
"; - FW_pO "
"; - -} - -sub -FW_alias($) -{ - my ($d) = @_; - if($FW_room) { - return AttrVal($d, "alias_$FW_room", AttrVal($d, "alias", $d)); - } else { - return AttrVal($d, "alias", $d); - } -} - -sub -FW_makeDeviceLine($$$$$) -{ - my ($d,$row,$extPage,$nameDisplay,$usuallyAtEnd) = @_; - my $rf = ($FW_room ? "&room=$FW_room" : ""); # stay in the room - - FW_pF "\n", ($row&1)?"odd":"even"; - my $devName = FW_alias($d); - if(defined($nameDisplay)) { - my ($DEVICE, $ALIAS) = ($d, $devName); - $devName = eval $nameDisplay; - } - my $icon = AttrVal($d, "icon", ""); - $icon = FW_makeImage($icon,$icon,"icon") . " " if($icon); - - $devName="" if($modules{$defs{$d}{TYPE}}{FW_hideDisplayName}); # Forum 88667 - if(!$usuallyAtEnd->{$d}) { - if($FW_hiddenroom{detail}) { - FW_pO "
$icon$devName
"; - } else { - FW_pH "detail=$d", "$icon$devName", 1, "col1"; - } - } - - my ($allSets, $cmdlist, $txt) = FW_devState($d, $rf, $extPage); - if($cmdlist) { - my $cl2 = $cmdlist; $cl2 =~ s/ [^:]*//g; $cl2 =~ s/:/ /g; # Forum #74053 - $allSets = "$allSets $cl2"; - } - $allSets = FW_widgetOverride($d, $allSets); - - my $colSpan = ($usuallyAtEnd->{$d} ? ' colspan="2"' : ''); - FW_pO "$txt"; - - ###### - # Commands, slider, dropdown - my $smallscreenCommands = AttrVal($FW_wname, "smallscreenCommands", ""); - if((!$FW_ss || $smallscreenCommands) && $cmdlist) { - my @a = split("[: ]", AttrVal($d, "cmdIcon", "")); - Log 1, "ERROR: bad cmdIcon definition for $d" if(@a % 2); - my %cmdIcon = @a; - - my @cl = split(":", $cmdlist); - my @wcl = split(":", AttrVal($d, "webCmdLabel", "")); - my $nRows; - $nRows = split("\n", AttrVal($d, "webCmdLabel", "")) if(@wcl); - @wcl = () if(@wcl != @cl); # some safety - - for(my $i1=0; $i1<@cl; $i1++) { - my $cmd = $cl[$i1]; - my $htmlTxt; - my @c = split(' ', $cmd); # @c==0 if $cmd==" "; - if(int(@c) && $allSets && $allSets =~ m/\b$c[0]:([^ ]*)/) { - my $values = $1; - foreach my $fn (sort keys %{$data{webCmdFn}}) { - no strict "refs"; - $htmlTxt = &{$data{webCmdFn}{$fn}}($FW_wname, - $d, $FW_room, $cmd, $values); - use strict "refs"; - last if(defined($htmlTxt)); - } - } - - if($htmlTxt) { - $htmlTxt =~ s,^]*>(.*)$,$1,; - } else { - my $nCmd = $cmdIcon{$cmd} ? - FW_makeImage($cmdIcon{$cmd},$cmd,"webCmd") : $cmd; - $htmlTxt = FW_pH "cmd.$d=set $d $cmd$rf", $nCmd, 0, "", 1, 1; - } - - if(@wcl > $i1) { - if($nRows > 1) { - FW_pO "" if($i1 == 0); - FW_pO ""; - FW_pO "" if($wcl[$i1] =~ m/\n/); - FW_pO "
$wcl[$i1]$htmlTxt
" if($i1 == @cl-1); - } else { - FW_pO "
$wcl[$i1]$ htmlTxt
"; - } - - } else { - FW_pO "
$htmlTxt
"; - } - } - } - FW_pO ""; -} - -sub -FW_sortIndex($) -{ - my ($d) = @_; - return $d if(!$attr{$d}); - - my $val = $attr{$d}{sortby}; - if($val) { - if($val =~ m/^{.*}/) { - my %specials=("%NAME" => $d); - my $exec = EvalSpecials($val, %specials); - return AnalyzePerlCommand($FW_chash, $exec); - } - return lc($val); - } - - if($FW_room) { - $val = $attr{$d}{"alias_$FW_room"}; - return $val if($val); - } - - $val = $attr{$d}{"alias"}; - return $val if($val); - return $d; -} - -######################## -# Show the overview of devices in one room -# room can be a room, all or Unsorted -sub -FW_showRoom() -{ - return 0 if(!$FW_room || - AttrVal($FW_wname,"forbiddenroom","") =~ m/\b$FW_room\b/); - - %FW_hiddengroup = (); - foreach my $r (split(",",AttrVal($FW_wname, "hiddengroup", ""))) { - $FW_hiddengroup{$r} = 1; - } - my $hge = AttrVal($FW_wname, "hiddengroupRegexp", undef); - - FW_pO "
"; - FW_addContent("room='$FW_room'"); - FW_pO ""; # Need for equal width of subtables - - # array of all device names in the room (exception weblinks without group - # attribute) - my @devs= grep { (($FW_rooms{$FW_room} && $FW_rooms{$FW_room}{$_}) || - $FW_room eq "all") && !IsIgnored($_) } keys %defs; - my (%group, @atEnds, %usuallyAtEnd, %sortIndex); - my $nDevsInRoom = 0; - foreach my $dev (@devs) { - if($modules{$defs{$dev}{TYPE}}{FW_atPageEnd}) { - $usuallyAtEnd{$dev} = 1; - if(!AttrVal($dev, "group", undef)) { - $sortIndex{$dev} = FW_sortIndex($dev); - push @atEnds, $dev; - next; - } - } - next if(!$FW_types{$dev}); # FHEMWEB connection, missed due to caching - foreach my $grp (split(",", AttrVal($dev, "group", $FW_types{$dev}))) { - next if($FW_hiddengroup{$grp}); - next if($hge && $grp =~ m/$hge/); - $sortIndex{$dev} = FW_sortIndex($dev); - $group{$grp}{$dev} = 1; - $nDevsInRoom++; - } - } - - # row counter - my $row=1; - my %extPage = (); - my $nameDisplay = AttrVal($FW_wname,"nameDisplay",undef); - - my ($columns, $maxc) = FW_parseColumns(\%group); - FW_pO "" if($maxc != -1); - for(my $col=1; $col < ($maxc==-1 ? 2 : $maxc); $col++) { - FW_pO "" if($maxc != -1); # Column - } - FW_pO "" if($maxc != -1); - - FW_pO "
" if($maxc != -1); - - # iterate over the distinct groups - foreach my $g (sort { $maxc==-1 ? - $a cmp $b : - ($columns->{$a} ? $columns->{$a}->[0] : 99) <=> - ($columns->{$b} ? $columns->{$b}->[0] : 99) } keys %group) { - - next if($maxc != -1 && (!$columns->{$g} || $columns->{$g}->[1] != $col)); - - ################# - # Check if there is a device of this type in the room - FW_pO ""; - FW_pO ""; - } - FW_pO "
$g
"; - FW_pO ""; - - foreach my $d (sort { $sortIndex{$a} cmp $sortIndex{$b} } - keys %{$group{$g}}) { - my $type = $defs{$d}{TYPE}; - $extPage{group} = $g; - - FW_makeDeviceLine($d,$row,\%extPage,$nameDisplay,\%usuallyAtEnd); - - if($modules{$type}{FW_addDetailToSummary}) { - no strict "refs"; - my $txt = &{$modules{$type}{FW_detailFn}}($FW_wname, $d, $FW_room); - use strict "refs"; - if(defined($txt)) { - FW_pO ""; - } - } - $row++; - } - FW_pO "
$txt
"; - FW_pO "
"; - FW_pO "
" if(@atEnds && $nDevsInRoom); - - # Now the "atEnds" - my $doBC = (AttrVal($FW_wname, "plotfork", 0) && - AttrVal($FW_wname, "plotEmbed", 0) == 0); - my %res; - my ($idx,$svgIdx) = (1,1); - @atEnds = sort { $sortIndex{$a} cmp $sortIndex{$b} } @atEnds; - $FW_svgData{$FW_cname} = { FW_RET=>$FW_RET, RES=>\%res, ATENDS=>\@atEnds }; - foreach my $d (@atEnds) { - no strict "refs"; - my $fn = $modules{$defs{$d}{TYPE}}{FW_summaryFn}; - $extPage{group} = "atEnd"; - $extPage{index} = $idx++; - if($doBC && $defs{$d}{TYPE} eq "SVG" && $FW_use{base64}) { - $extPage{svgIdx} = $svgIdx++; - BlockingCall(sub { - return "$FW_cname,$d,". - encode_base64(&{$fn}($FW_wname,$d,$FW_room,\%extPage),''); - }, undef, "FW_svgCollect"); - } else { - $res{$d} = &{$fn}($FW_wname,$d,$FW_room,\%extPage); - } - use strict "refs"; - } - return FW_svgDone(\%res, \@atEnds, undef); -} - -sub -FW_svgDone($$$) -{ - my ($res, $atEnds, $delayedReturn) = @_; - return -2 if(int(keys %{$res}) != int(@{$atEnds})); - - foreach my $d (@{$atEnds}) { - FW_pO $res->{$d}; - } - FW_pO ""; - FW_pO "
"; - FW_pO "" if($delayedReturn); - return 0; -} - -sub -FW_svgCollect($) -{ - my ($cname,$d,$enc) = split(",",$_[0],3); - my $h = $FW_svgData{$cname}; - my ($res, $atEnds) = ($h->{RES}, $h->{ATENDS}); - $res->{$d} = decode_base64($enc); - return if(int(keys %{$res}) != int(@{$atEnds})); - $FW_RET = $h->{FW_RET}; - delete($FW_svgData{$cname}); - FW_svgDone($res, $atEnds, 1); - FW_finishRead($defs{$cname}, 0, ""); -} - -# Room1:col1group1,col1group2|col2group1,col2group2 Room2:... -sub -FW_parseColumns($) -{ - my ($aGroup) = @_; - my %columns; - my $colNo = -1; - - foreach my $roomgroup (split("[ \t\r\n]+", AttrVal($FW_wname,"column",""))) { - my ($room, $groupcolumn)=split(":",$roomgroup,2); - $room =~ s/%20/ /g; # Space - next if(!defined($groupcolumn) || $FW_room !~ m/^$room$/); - $colNo = 1; - my @grouplist = keys %$aGroup; - my %handled; - - foreach my $groups (split(/\|/,$groupcolumn)) { - my $lineNo = 1; - foreach my $group (split(",",$groups)) { - $group =~ s/%20/ /g; # Forum #33612 - $group = "^$group\$"; #71381 - eval { "Hallo" =~ m/^$group$/ }; - if($@) { - Log3 $FW_wname, 1, "Bad regexp in column spec: $@"; - } else { - foreach my $g (grep /$group/ ,@grouplist) { - next if($handled{$g}); - $handled{$g} = 1; - $columns{$g} = [$lineNo++, $colNo]; #23212 - } - } - } - $colNo++; - } - last; - } - return (\%columns, $colNo); -} - - -################# -# return a sorted list of actual files for a given regexp -sub -FW_fileList($;$) -{ - my ($fname,$mtime) = @_; - $fname =~ s/%L/$attr{global}{logdir}/g #Forum #89744 - if($fname =~ m/%/ && $attr{global}{logdir}); - $fname =~ m,^(.*)/([^/]*)$,; # Split into dir and file - my ($dir,$re) = ($1, $2); - return $fname if(!$re); - $re =~ s/%./[A-Za-z0-9]*/g; # logfile magic (%Y, etc) - my @ret; - return @ret if(!opendir(DH, $dir)); - while(my $f = readdir(DH)) { - next if($f !~ m,^$re$, || $f eq "99_Utils.pm"); - push(@ret, $f); - } - closedir(DH); - return sort { (CORE::stat("$dir/$a"))[9] <=> (CORE::stat("$dir/$b"))[9] } - @ret if($mtime); - @ret = cfgDB_FW_fileList($dir,$re,@ret) if (configDBUsed()); - return sort @ret; -} - - -################################### -# Stream big files in chunks, to avoid bloating ourselves. -# This is a "terminal" function, no data can be appended after it is called. -sub -FW_outputChunk($$$) -{ - my ($hash, $buf, $d) = @_; - $buf = $d->deflate($buf) if($d); - if( length($buf) ){ - TcpServer_WriteBlocking($hash, sprintf("%x\r\n",length($buf)) .$buf."\r\n"); - } -} - -sub -FW_returnFileAsStream($$$$$) -{ - my ($path, $suffix, $type, $doEsc, $cacheable) = @_; - my $etag; - - if($cacheable) { - #Check for If-None-Match header (ETag) - my $if_none_match = $FW_httpheader{"If-None-Match"}; - $if_none_match =~ s/"(.*)"/$1/ if($if_none_match); - $etag = (stat($path))[9]; #mtime - if(defined($etag) && defined($if_none_match) && $etag eq $if_none_match) { - my $now = time(); - my $rsp = "Date: ".FmtDateTimeRFC1123($now)."\r\n". - "ETag: $etag\r\n". - "Expires: ".FmtDateTimeRFC1123($now+900)."\r\n"; - Log3 $FW_wname, 4, "$FW_chash->{NAME} => 304 Not Modified"; - TcpServer_WriteBlocking($FW_chash,"HTTP/1.1 304 Not Modified\r\n". - $rsp . $FW_headerlines . "\r\n"); - return -1; - } - } - - if(!open(FH, $path)) { - Log3 $FW_wname, 4, "FHEMWEB $FW_wname $path: $!"; - TcpServer_WriteBlocking($FW_chash, - "HTTP/1.1 404 Not Found\r\n". - "Content-Length:0\r\n\r\n"); - FW_closeConn($FW_chash); - return -1; - } - binmode(FH) if($type !~ m/text/); # necessary for Windows - my $sz = -s $path; - - $etag = defined($etag) ? "ETag: \"$etag\"\r\n" : ""; - my $expires = $cacheable ? ("Expires: ".gmtime(time()+900)." GMT\r\n"): ""; - my $compr = ($FW_httpheader{"Accept-Encoding"} && - $FW_httpheader{"Accept-Encoding"} =~ m/gzip/ && $FW_use{zlib}) ? - "Content-Encoding: gzip\r\n" : ""; - TcpServer_WriteBlocking($FW_chash, "HTTP/1.1 200 OK\r\n". - $compr . $expires . $FW_headerlines . $etag . - "Transfer-Encoding: chunked\r\n" . - "Content-Type: $type; charset=$FW_encoding\r\n\r\n"); - - my $d = Compress::Zlib::deflateInit(-WindowBits=>31) if($compr); - FW_outputChunk($FW_chash, $FW_RET, $d); - FW_outputChunk($FW_chash, "
". - "jump to the end

", $d) - if($doEsc && $sz > 2048); - my $buf; - while(sysread(FH, $buf, 2048)) { - if($doEsc) { # FileLog special - $buf =~ s//>/g; - } - FW_outputChunk($FW_chash, $buf, $d); - } - close(FH); - FW_outputChunk($FW_chash, "
". - "jump to the top

", $d) - if($doEsc && $sz > 2048); - FW_outputChunk($FW_chash, $suffix, $d); - - if($compr) { - $buf = $d->flush(); - if($buf){ - TcpServer_WriteBlocking($FW_chash, - sprintf("%x\r\n",length($buf)) .$buf."\r\n"); - } - } - TcpServer_WriteBlocking($FW_chash, "0\r\n\r\n"); - FW_closeConn($FW_chash); - return -1; -} - - -################## -sub -FW_fatal($) -{ - my ($msg) = @_; - FW_pO "$msg"; -} - -################## -sub -FW_hidden($$) -{ - my ($n, $v) = @_; - return ""; -} - -################## -# Generate a select field with option list -sub -FW_select($$$$$@) -{ - my ($id, $name, $valueArray, $selected, $class, $jSelFn) = @_; - $jSelFn = ($jSelFn ? "onchange=\"$jSelFn\"" : ""); - $id =~ s/\./_/g if($id); # to avoid problems in JS DOM Search - $id = ($id ? "id=\"$id\" informId=\"$id\"" : ""); - my $s = ""; - return $s; -} - -################## -sub -FW_textfieldv($$$$) -{ - my ($n, $z, $class, $value) = @_; - my $v; - $v=" value='$value'" if(defined($value)); - return if($FW_hiddenroom{input}); - my $s = ""; - return $s; -} - -sub -FW_textfield($$$) -{ - return FW_textfieldv($_[0], $_[1], $_[2], ""); -} - -################## -sub -FW_submit($$@) -{ - my ($n, $v, $class) = @_; - $class = ($class ? "class=\"$class\"" : ""); - my $s =""; - $s = FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}).$s if($FW_CSRF); - return $s; -} - -################## -sub -FW_displayFileList($@) -{ - my ($heading,@files)= @_; - return if(!@files); - my $hid = lc($heading); - $hid =~ s/[^A-Za-z]/_/g; - FW_pO "
$heading
"; - FW_pO ""; - my $cfgDB = ""; - my $row = 0; - foreach my $f (@files) { - $cfgDB = ($f =~ s,\.configDB$,,); - $cfgDB = ($cfgDB) ? "configDB" : ""; - FW_pO ""; - FW_pH "cmd=style edit $f $cfgDB", $f, 1; - FW_pO ""; - $row = ($row+1)%2; - } - FW_pO "
"; - FW_pO "
"; -} - -################## -sub -FW_fileNameToPath($) -{ - my $name = shift; - - my @f = FW_confFiles(2); - return "$FW_confdir/$name" if ( map { $name =~ $_ } @f ); - - $attr{global}{configfile} =~ m,([^/]*)$,; - my $cfgFileName = $1; - if($name eq $cfgFileName) { - return $attr{global}{configfile}; - } elsif($name =~ m/.*(js|css|_defs.svg)$/) { - return "$FW_cssdir/$name"; - } elsif($name =~ m/.*(png|svg)$/) { - my $d=""; - map { $d = $_ if(!$d && -d "$FW_icondir/$_") } @FW_iconDirs; - return "$FW_icondir/$d/$name"; - } elsif($name =~ m/.*gplot$/) { - return "$FW_gplotdir/$name"; - } elsif($name =~ m/.*log$/) { - return AttrVal("global", "logdir", "log")."/$name"; - } else { - return "$MW_dir/$name"; - } -} - -sub FW_confFiles($) { - my ($param) = @_; - # create and return regexp for editFileList - return "(".join ( "|" , sort keys %{$data{confFiles}} ).")" if $param == 1; - # create and return array with filenames - return sort keys %{$data{confFiles}} if $param == 2; -} - -################## -# List/Edit/Save files -sub -FW_style($$) -{ - my ($cmd, $msg) = @_; - my @a = split(" ", $cmd); - - return if(!Authorized($FW_chash, "cmd", $a[0])); - - my $start = '>
" if($msg); - - $attr{global}{configfile} =~ m,([^/]*)$,; - my $cfgFileName = $1; - FW_displayFileList("config file", $cfgFileName) - if(!configDBUsed()); - - my $efl = AttrVal($FW_wname, 'editFileList', - "Own modules and helper files:\$MW_dir:^(.*sh|[0-9][0-9].*Util.*pm|". - ".*cfg|.*\.holiday|myUtilsTemplate.pm|.*layout)\$\n". - "Config files for external Programs:\$FW_confdir:^".FW_confFiles(1)."\$\n". - "Gplot files:\$FW_gplotdir:^.*gplot\$\n". - "Style files:\$FW_cssdir:^.*(css|svg)\$"); - foreach my $l (split(/[\r\n]/, $efl)) { - my ($t, $v, $re) = split(":", $l, 3); - $v = eval $v; - my @fList; - if($v eq $FW_gplotdir && AttrVal($FW_wname,'showUsedFiles',0)) { - @fList = defInfo('TYPE=SVG','GPLOTFILE'); - @fList = map { "$_.gplot" } @fList; - @fList = map { "$_.configDB" } @fList if configDBUsed(); - my %fListUnique = map { $_, 1 } @fList; - @fList = sort keys %fListUnique; - } else { - @fList = FW_fileList("$v/$re"); - } - FW_displayFileList($t, @fList); - } - FW_pO $end; - - } elsif($a[1] eq "select") { - my @fl = grep { $_ !~ m/(floorplan|dashboard)/ } - FW_fileList("$FW_cssdir/.*style.css"); - FW_addContent($start); - FW_pO "
Styles
"; - FW_pO "
"; - my $row = 0; - foreach my $file (@fl) { - next if($file =~ m/svg_/); - $file =~ s/style.css//; - $file = "default" if($file eq ""); - FW_pO ""; - FW_pH "cmd=style set $file", "$file", 1; - FW_pO ""; - $row = ($row+1)%2; - } - FW_pO "
$end"; - - } elsif($a[1] eq "set") { - CommandAttr(undef, "$FW_wname stylesheetPrefix $a[2]"); - $FW_styleStamp = time(); - $FW_RET =~ s,/style.css\?v=\d+,/style.css?v=$FW_styleStamp,; - FW_addContent($start); - FW_pO "Reload the page in the browser.$end"; - - } elsif($a[1] eq "edit") { - my $fileName = $a[2]; - my $data = ""; - my $cfgDB = defined($a[3]) ? $a[3] : ""; - my $forceType = ($cfgDB eq 'configDB') ? $cfgDB : "file"; - $fileName =~ s,.*/,,g; # Little bit of security - my $filePath = FW_fileNameToPath($fileName); - my($err, @content) = FileRead({FileName=>$filePath, ForceType=>$forceType}); - if($err) { - FW_addContent(">$err"; - if($readOnly) { - FW_pO "You can enable saving this file by setting the editConfig "; - FW_pO "attribute, but read the documentation first for the side effects."; - FW_pO "

"; - } else { - FW_pO FW_submit("save", "Save $fileName"); - FW_pO "  "; - FW_pO FW_submit("saveAs", "Save as"); - FW_pO FW_textfieldv("saveName", 30, "saveName", $fileName); - FW_pO "

"; - } - FW_pO FW_hidden("cmd", "style save $fileName $cfgDB"); - FW_pO ""; - FW_pO ""; - FW_pO ""; - - } elsif($a[1] eq "save") { - my $fileName = $a[2]; - my $cfgDB = defined($a[3]) ? $a[3] : ""; - $fileName = $FW_webArgs{saveName} - if($FW_webArgs{saveAs} && $FW_webArgs{saveName}); - $fileName =~ s,.*/,,g; # Little bit of security - my $filePath = FW_fileNameToPath($fileName); - my $isImg = ($fileName =~ m,\.(svg|png)$,i); - my $forceType = ($cfgDB eq 'configDB' && !$isImg) ? $cfgDB : "file"; - - $FW_data =~ s/\r//g if(!$isImg); - my $err; - if($fileName =~ m,\.png$,) { - $err = FileWrite({FileName=>$filePath,ForceType=>$forceType,NoNL=>1}, - $FW_data); - } else { - $err = FileWrite({ FileName=>$filePath, ForceType=>$forceType }, - split("\n", $FW_data)); - } - - if($err) { - FW_addContent(">$filePath: $!ERROR:$ret" : "Saved $fileName$sfx"); - FW_style("style list", $ret); - $ret = ""; - - } elsif($a[1] eq "iconFor") { - FW_iconTable("iconFor", "icon", "style setIF $a[2] %s", undef); - - } elsif($a[1] eq "setIF") { - FW_fC("attr $a[2] icon $a[3]"); - FW_doDetail($a[2]); - - } elsif($a[1] eq "showDSI") { - FW_iconTable("devStateIcon", "", - "style addDSI $a[2] %s", "Enter value/regexp for STATE"); - - } elsif($a[1] eq "addDSI") { - my $dsi = AttrVal($a[2], "devStateIcon", ""); - $dsi .= " " if($dsi); - FW_fC("attr $a[2] devStateIcon $dsi$FW_data:$a[3]"); - FW_doDetail($a[2]); - - } elsif($a[1] eq "eventMonitor") { - FW_pO ""; - FW_addContent(); - my $filter = $a[2] ? ($a[2] eq "log" ? "global" : $a[2]) : ".*"; - FW_pO "Events (Filter: $filter) ". - "  FHEM log ". - "". - "  

\n"; - FW_pO "
"; - FW_pO ""; - - } - -} - -sub -FW_iconTable($$$$) -{ - my ($name, $class, $cmdFmt, $textfield) = @_; - - my %icoList = (); - foreach my $style (@FW_iconDirs) { - foreach my $imgName (sort keys %{$FW_icons{$style}}) { - next if($imgName =~ m+^\.+ || $imgName =~ m+/\.+); # Skip dot files - $imgName =~ s/\.[^.]*$//; # Cut extension - next if(!$FW_icons{$style}{$imgName}); # Dont cut it twice: FS20.on.png - next if($FW_icons{$style}{$imgName} !~ m/$imgName/); # Skip alias - next if($imgName=~m+^(weather/|shutter.*big|fhemicon|favicon|ws_.*_kl)+); - next if($imgName=~m+^(dashboardicons)+); - $icoList{$imgName} = 1; - } - } - - FW_addContent(); - FW_pO "
"; - FW_pO "Filter: ".FW_textfieldv("icon-filter",20,"iconTable","")."
"; - if($textfield) { - FW_pO "$textfield: ".FW_textfieldv("data",20,"iconTable",".*")."
"; - } - foreach my $i (sort keys %icoList) { - FW_pF "", $i, $i, FW_makeImage($i,$i,$class); - } - FW_pO "
"; - FW_pO ""; -} - -################## -# print (append) to output -sub -FW_pO(@) -{ - my $arg = shift; - return if(!defined($arg)); - $FW_RET .= $arg; - $FW_RET .= "\n"; -} - -################# -# add href -sub -FW_pH(@) -{ - my ($link, $txt, $td, $class, $doRet,$nonl) = @_; - my $ret; - - $link .= $FW_CSRF if($link =~ m/cmd/ && - $link !~m/cmd=style%20(list|select|eventMonitor)/); - $link = ($link =~ m,^/,) ? $link : "$FW_ME$FW_subdir?$link"; - - # Using onclick, as href starts safari in a webapp. - # Known issue: the pointer won't change - if($FW_ss || $FW_tp) { - $ret = "$txt"; - } else { - $ret = "$txt"; - } - - #actually 'div' should be removed if no class is defined - # as I can't check all code for consistancy I add nonl instead - $class = ($class)?" class=\"$class\"":""; - $ret = "$ret" if (!$nonl); - - $ret = "$ret" if($td); - return $ret if($doRet); - FW_pO $ret; -} - -################# -# href without class/div, returned as a string -sub -FW_pHPlain(@) -{ - my ($link, $txt, $td) = @_; - - $link = "?$link" if($link !~ m+^/+); - my $ret = ""; - $ret .= "" if($td); - $link .= $FW_CSRF; - if($FW_ss || $FW_tp) { - $ret .= "$txt"; - } else { - $ret .= "$txt"; - } - $ret .= "" if($td); - return $ret; -} - - -############################## -sub -FW_makeImage(@) -{ - my ($name, $txt, $class)= @_; - - $txt = $name if(!defined($txt)); - $class = "" if(!$class); - $class = "$class $name"; - $class =~ s/\./_/g; - $class =~ s/@/ /g; - - my $p = FW_iconPath($name); - return $name if(!$p); - if($p =~ m/\.svg$/i) { - if(open(FH, "$FW_icondir/$p")) { - my $data; - do { - $data = ; - if(!defined($data)) { - Log 1, "$FW_icondir/$p is not useable"; - return ""; - } - } until( $data =~ m/^); - close(FH); - $data =~ s/[\r\n]/ /g; - $data =~ s/ *$//g; - $data =~ s/"; - } -} - -#### -sub -FW_IconURL($) -{ - my ($name)= @_; - return "$FW_ME/icons/$name"; -} - -################## -# print formatted -sub -FW_pF($@) -{ - my $fmt = shift; - $FW_RET .= sprintf $fmt, @_; -} - -################## -# fhem command -sub -FW_fC($@) -{ - my ($cmd, $unique) = @_; - my $ret; - if($unique) { - $ret = AnalyzeCommand($FW_chash, $cmd); - } else { - $ret = AnalyzeCommandChain($FW_chash, $cmd); - } - return $ret; -} - -sub -FW_Attr(@) -{ - my ($type, $devName, $attrName, @param) = @_; - my $hash = $defs{$devName}; - my $sP = "stylesheetPrefix"; - my $retMsg; - - if($type eq "set" && $attrName eq "HTTPS") { - TcpServer_SetSSL($hash); - } - - if($type eq "set") { # Converting styles - if($attrName eq "smallscreen" || $attrName eq "touchpad") { - $attr{$devName}{$sP} = $attrName; - $retMsg="$devName: attribute $attrName deprecated, converted to $sP"; - $param[0] = $attrName; $attrName = $sP; - } - } - - if($attrName eq $sP) { - # AttrFn is called too early, we have to set/del the attr here - if($type eq "set") { - $attr{$devName}{$sP} = (defined($param[0]) ? $param[0] : "default"); - FW_readIcons($attr{$devName}{$sP}); - } else { - delete $attr{$devName}{$sP}; - } - } - - if(($attrName eq "allowedCommands" || - $attrName eq "basicAuth" || - $attrName eq "basicAuthMsg") - && $type eq "set") { - my $aName = "allowed_$devName"; - my $exists = ($defs{$aName} ? 1 : 0); - AnalyzeCommand(undef, "defmod $aName allowed"); - AnalyzeCommand(undef, "attr $aName validFor $devName"); - AnalyzeCommand(undef, "attr $aName $attrName ".join(" ",@param)); - return "$devName: ".($exists ? "modifying":"creating"). - " device $aName for attribute $attrName"; - } - - if($attrName eq "iconPath" && $type eq "set") { - foreach my $pe (split(":", $param[0])) { - $pe =~ s+\.\.++g; - FW_readIcons($pe); - } - } - - if($attrName eq "JavaScripts" && $type eq "set") { # create some attributes - my (%a, @add); - map { $a{$_} = 1 } split(" ", $modules{FHEMWEB}{AttrList}); - map { - $_ =~ s+.*/++; $_ =~ s/.js$//; $_ =~ s/fhem_//; $_ .= "Param"; - push @add, $_ if(!$a{$_} && $_ !~ m/^-/); - } split(" ", $param[0]); - $modules{FHEMWEB}{AttrList} .= " ".join(" ",@add) if(@add); - } - - if($attrName eq "csrfToken") { - return undef if($FW_csrfTokenCache{$devName} && !$init_done); - my $csrf = $param[0]; - if($type eq "del" || $csrf eq "random") { - my ($x,$y) = gettimeofday(); - ($csrf = "csrf_".(rand($y)*rand($x))) =~ s/[^a-z_0-9]//g; - } - - if($csrf eq "none") { - delete($hash->{CSRFTOKEN}); - delete($FW_csrfTokenCache{$devName}); - } else { - $hash->{CSRFTOKEN} = $csrf; - $FW_csrfTokenCache{$devName} = $hash->{CSRFTOKEN}; - } - } - - if($attrName eq "longpoll" && $type eq "set" && $param[0] eq "websocket") { - return "$devName: Could not load Digest::SHA on startup, no websocket" - if(!$FW_use{sha}); - } - - return $retMsg; -} - - -# recursion starts at $FW_icondir/$dir -# filenames are relative to $FW_icondir -sub -FW_readIconsFrom($$) -{ - my ($dir,$subdir)= @_; - - my $ldir = ($subdir ? "$dir/$subdir" : $dir); - my @entries; - if(opendir(DH, "$FW_icondir/$ldir")) { - @entries= sort readdir(DH); # assures order: .gif .ico .jpg .png .svg - closedir(DH); - } - - foreach my $entry (@entries) { - if( -d "$FW_icondir/$ldir/$entry" ) { # directory -> recurse - FW_readIconsFrom($dir, $subdir ? "$subdir/$entry" : $entry) - unless($entry eq "." || $entry eq ".." || $entry eq ".svn"); - - } else { - if($entry =~ m/^iconalias.txt$/i && open(FH, "$FW_icondir/$ldir/$entry")){ - while(my $l = ) { - chomp($l); - my @a = split(" ", $l); - next if($l =~ m/^#/ || @a < 2); - $FW_icons{$dir}{$a[0]} = $a[1]; - } - close(FH); - } elsif($entry =~ m/(gif|ico|jpg|png|jpeg|svg)$/i) { - my $filename = $subdir ? "$subdir/$entry" : $entry; - $FW_icons{$dir}{$filename} = $filename; - - my $tag = $filename; # Add it without extension too - $tag =~ s/\.[^.]*$//; - $FW_icons{$dir}{$tag} = $filename; - } - } - } - $FW_icons{$dir}{""} = 1; # Do not check empty directories again. -} - -sub -FW_readIcons($) -{ - my ($dir)= @_; - return if($FW_icons{$dir}); - FW_readIconsFrom($dir, ""); -} - - -# check if the icon exists, and if yes, returns its "logical" name; -sub -FW_iconName($) -{ - my ($oname)= @_; - return undef if(!defined($oname)); - my $name = $oname; - $name =~ s/@.*//; - foreach my $pe (@FW_iconDirs) { - return $oname if($pe && $FW_icons{$pe} && $FW_icons{$pe}{$name}); - } - return undef; -} - -# returns the physical absolute path relative for the logical path -# examples: -# FS20.on -> dark/FS20.on.png -# weather/sunny -> default/weather/sunny.gif -sub -FW_iconPath($) -{ - my ($name) = @_; - $name =~ s/@.*//; - foreach my $pe (@FW_iconDirs) { - return "$pe/$FW_icons{$pe}{$name}" - if($pe && $FW_icons{$pe} && $FW_icons{$pe}{$name}); - } - return undef; -} - -sub -FW_dev2image($;$) -{ - my ($name, $state) = @_; - my $d = $defs{$name}; - return "" if(!$name || !$d); - my $devStateIcon = AttrVal($name, "devStateIcon", undef); - return "" if(defined($devStateIcon) && lc($devStateIcon) eq 'none'); - - my $type = $d->{TYPE}; - $state = $d->{STATE} if(!defined($state)); - return "" if(!$type || !defined($state)); - - my $model = AttrVal($name, "model", ""); - my (undef, $rstate) = ReplaceEventMap($name, [undef, $state], 0); - - my ($icon, $rlink); - if(defined($devStateIcon) && $devStateIcon =~ m/^{.*}$/s) { - my ($html, $link) = eval $devStateIcon; - Log3 $FW_wname, 1, "devStateIcon $name: $@" if($@); - return ($html, $link, 1) if(defined($html) && $html =~ m/^<.*>$/s); - $devStateIcon = $html; - } - - if(defined($devStateIcon)) { - my @list = split(" ", $devStateIcon); - foreach my $l (@list) { - my ($re, $iconName, $link) = split(":", $l, 3); - if(defined($re) && $state =~ m/^$re$/) { - if(defined($iconName) && $iconName eq "") { - $rlink = $link; - last; - } - if(defined($iconName) && defined(FW_iconName($iconName))) { - return ($iconName, $link, 0); - } else { - return ($state, $link, 1); - } - } - } - } - - $state =~ s/ .*//; # Want to be able to have icons for "on-for-timer xxx" - - $icon = FW_iconName("$name.$state") if(!$icon); # lamp.Aus.png - $icon = FW_iconName("$name.$rstate") if(!$icon); # lamp.on.png - $icon = FW_iconName($name) if(!$icon); # lamp.png - $icon = FW_iconName("$model.$state") if(!$icon && $model); # fs20st.off.png - $icon = FW_iconName($model) if(!$icon && $model); # fs20st.png - $icon = FW_iconName("$type.$state") if(!$icon); # FS20.Aus.png - $icon = FW_iconName("$type.$rstate") if(!$icon); # FS20.on.png - $icon = FW_iconName($type) if(!$icon); # FS20.png - $icon = FW_iconName($state) if(!$icon); # Aus.png - $icon = FW_iconName($rstate) if(!$icon); # on.png - return ($icon, $rlink, 0); -} - -sub -FW_makeEdit($$$) -{ - my ($name, $n, $val) = @_; - - # Toggle Edit-Window visibility script. - my $psc = AttrVal("global", "perlSyntaxCheck", ($featurelevel>5.7) ? 1 : 0); - FW_pO ""; - FW_pO "$n"; - FW_pO ""; - - $val =~ s,\\\n,\n,g; - $val = FW_htmlEscape($val); - my $eval = $val; - $eval = "
$eval
" if($eval =~ m/\n/); - FW_pO ""; - FW_pO "
$eval
"; - FW_pO ""; - - FW_pO ""; - FW_pO "
"; - FW_pO "
"; - FW_pO FW_hidden("detail", $name); - my $cmd = "modify"; - my $ncols = $FW_ss ? 30 : 60; - FW_pO ""; - FW_pO "
" . FW_submit("cmd.${cmd}$name", "$cmd $name",($psc?"psc":"")); - FW_pO "
"; - FW_pO ""; -} - - -sub -FW_longpollInfo($@) -{ - my $fmt = shift; - if($fmt && $fmt eq "JSON") { - my @a; - map { my $x = $_; #Forum 57377, ASCII 0-19 \ " - $x=~ s/([\x00-\x1f\x22\x5c\x7f])/sprintf '\u%04x', ord($1)/ge; - push @a,$x; } @_; - return '["'.join('","', @a).'"]'; - } else { - return join('<<', @_); - } -} - -sub -FW_roomStatesForInform($$) -{ - my ($me, $sinceTimestamp ) = @_; - return "" if($me->{inform}{type} !~ m/status/); - - my %extPage = (); - my @data; - foreach my $dn (keys %{$me->{inform}{devices}}) { - next if(!defined($defs{$dn})); - my $t = $defs{$dn}{TYPE}; - next if(!$t || $modules{$t}{FW_atPageEnd}); - - my $lastChanged = OldTimestamp( $dn ); - next if(!defined($lastChanged) || $lastChanged lt $sinceTimestamp); - - my ($allSet, $cmdlist, $txt) = FW_devState($dn, "", \%extPage); - if($defs{$dn} && $defs{$dn}{STATE} && $defs{$dn}{TYPE} ne "weblink") { - push @data, - FW_longpollInfo($me->{inform}{fmt}, $dn, $defs{$dn}{STATE}, $txt); - } - } - my $data = join("\n", map { s/\n/ /gm; $_ } @data)."\n"; - return $data; -} - -sub -FW_logInform($$) -{ - my ($me, $msg) = @_; # _NO_ Log3 here! - - my $ntfy = $defs{$me}; - if(!$ntfy) { - delete $logInform{$me}; - return; - } - $msg = FW_htmlEscape($msg); - if(!FW_addToWritebuffer($ntfy, "
$msg
") ){ - TcpServer_Close($ntfy, 1); - delete $logInform{$me}; - } -} - -sub -FW_Notify($$) -{ - my ($ntfy, $dev) = @_; - my $h = $ntfy->{inform}; - return undef if(!$h); - my $isStatus = ($h->{type} =~ m/status/); - my $events; - - my $dn = $dev->{NAME}; - if($dn eq "global" && $isStatus) { - my $vs = int(@structChangeHist) ? 'visible' : 'hidden'; - my $data = FW_longpollInfo($h->{fmt}, - "#FHEMWEB:$ntfy->{NAME}","\$('#saveCheck').css('visibility','$vs')",""); - FW_addToWritebuffer($ntfy, $data."\n"); - - if($dev->{CHANGED}) { - $dn = $1 if($dev->{CHANGED}->[0] =~ m/^MODIFIED (.*)$/); - if($dev->{CHANGED}->[0] =~ m/^ATTR ([^ ]+) ([^ ]+) (.*)$/s) { - $dn = $1; - my @a = ("a-$2: $3"); - $events = \@a; - } - } - } - - if($dn eq $ntfy->{SNAME} && - $dev->{CHANGED} && - $dev->{CHANGED}->[0] =~ m/^JS(#([^:]*))?:(.*)$/) { - my $data = $3; - return if( $2 && $ntfy->{PEER} !~ m/$2/ ); - $data = FW_longpollInfo($h->{fmt}, "#FHEMWEB:$ntfy->{NAME}",$data,""); - FW_addToWritebuffer($ntfy, $data."\n"); - return; - } - - return undef if($isStatus && !$h->{devices}{$dn}); - - my @data; - my %extPage; - my $isRaw = ($h->{type} =~ m/raw/); - $events = deviceEvents($dev, AttrVal($FW_wname, "addStateEvent",!$isRaw)) - if(!$events); - - if($isStatus) { - # Why is saving this stuff needed? FLOORPLAN? - my @old = ($FW_wname, $FW_ME, $FW_ss, $FW_tp, $FW_subdir); - $FW_wname = $ntfy->{SNAME}; - $FW_ME = "/" . AttrVal($FW_wname, "webname", "fhem"); - $FW_subdir = ($h->{iconPath} ? "/floorplan/$h->{iconPath}" : ""); # 47864 - $FW_sp = AttrVal($FW_wname, "stylesheetPrefix", "f18"); - $FW_sp = "" if($FW_sp eq "default"); - $FW_ss = ($FW_sp =~ m/smallscreen/); - $FW_tp = ($FW_sp =~ m/smallscreen|touchpad/); - my $spDir = ($FW_sp eq "default" ? "" : "$FW_sp:"); - @FW_iconDirs = grep { $_ } split(":", AttrVal($FW_wname, "iconPath", - "${spDir}fhemSVG:openautomation:default")); - if($h->{iconPath}) { - unshift @FW_iconDirs, $h->{iconPath}; - FW_readIcons($h->{iconPath}); - } - - if( !$modules{$defs{$dn}{TYPE}}{FW_atPageEnd} ) { - my ($allSet, $cmdlist, $txt) = FW_devState($dn, "", \%extPage); - ($FW_wname, $FW_ME, $FW_ss, $FW_tp, $FW_subdir) = @old; - push @data, FW_longpollInfo($h->{fmt}, $dn, $dev->{STATE}, $txt); - } - - #Add READINGS - if($events) { # It gets deleted sometimes (?) - my $tn = TimeNow(); - my $max = int(@{$events}); - for(my $i = 0; $i < $max; $i++) { - if($events->[$i] !~ /: /) { - if($dev->{NAME} eq 'global') { # Forum #47634 - my($type,$args) = split(' ', $events->[$i], 2); - $args = "" if(!defined($args)); # global SAVE - push @data, FW_longpollInfo($h->{fmt}, "$dn-$type", $args, $args); - } - - next; #ignore 'set' commands - } - my ($readingName,$readingVal) = split(": ",$events->[$i],2); - next if($readingName !~ m/^[A-Za-z\d_\.\-\/:]+$/); # Forum #70608,70844 - push @data, FW_longpollInfo($h->{fmt}, - "$dn-$readingName", $readingVal,$readingVal); - push @data, FW_longpollInfo($h->{fmt}, "$dn-$readingName-ts", $tn, $tn); - } - } - } - - if($isRaw) { - if($events) { # It gets deleted sometimes (?) - my $tn = TimeNow(); - if($attr{global}{mseclog}) { - my ($seconds, $microseconds) = gettimeofday(); - $tn .= sprintf(".%03d", $microseconds/1000); - } - my $max = int(@{$events}); - my $dt = $dev->{TYPE}; - for(my $i = 0; $i < $max; $i++) { - my $line = "$tn $dt $dn ".$events->[$i]."
"; - eval { - my $ok; - if($h->{filterType} && $h->{filterType} eq "notify") { - $ok = ($dn =~ m/^$h->{filter}$/ || - "$dn:$events->[$i]" =~ m/^$h->{filter}$/) ; - } else { - $ok = ($line =~ m/$h->{filter}/) ; - } - push @data,$line if($ok); - } - } - } - } - - if(@data){ - if(!FW_addToWritebuffer($ntfy, - join("\n", map { s/\n/ /gm; $_ } @data)."\n") ){ - my $name = $ntfy->{NAME}; - Log3 $name, 4, "Closing connection $name due to full buffer in FW_Notify"; - TcpServer_Close($ntfy, 1); - } - } - - return undef; -} - -sub -FW_directNotify($@) # Notify without the event overhead (Forum #31293) -{ - my $filter; - if($_[0] =~ m/^FILTER=(.*)/) { - $filter = "^$1\$"; - shift; - } - my $dev = $_[0]; - foreach my $ntfy (values(%defs)) { - next if(!$ntfy->{TYPE} || - $ntfy->{TYPE} ne "FHEMWEB" || - !$ntfy->{inform} || - !$ntfy->{inform}{devices}{$dev} || - $ntfy->{inform}{type} ne "status"); - next if($filter && $ntfy->{inform}{filter} !~ m/$filter/); - if(!FW_addToWritebuffer($ntfy, - FW_longpollInfo($ntfy->{inform}{fmt}, @_)."\n")) { - my $name = $ntfy->{NAME}; - Log3 $name, 4, "Closing connection $name due to full buffer in FW_Notify"; - TcpServer_Close($ntfy, 1); - } - } -} - -################### -# Compute the state (==second) column -sub -FW_devState($$@) -{ - my ($d, $rf, $extPage) = @_; - - my ($hasOnOff, $link); - - my $cmdList = AttrVal($d, "webCmd", ""); - my $allSets = FW_widgetOverride($d, getAllSets($d, $FW_chash)); - my $state = $defs{$d}{STATE}; - $state = "" if(!defined($state)); - - my $txt = $state; - my $dsi = ($attr{$d} && ($attr{$d}{stateFormat} || $attr{$d}{devStateIcon})); - - $hasOnOff = ($allSets =~ m/(^| )on(:[^ ]*)?( |$)/i && - $allSets =~ m/(^| )off(:[^ ]*)?( |$)/i); - if(AttrVal($d, "showtime", undef)) { - my $v = $defs{$d}{READINGS}{state}{TIME}; - $txt = $v if(defined($v)); - - } elsif(!$dsi && $allSets =~ m/\bdesired-temp:/) { - $txt = "$1 °C" if($txt =~ m/^measured-temp: (.*)/); # FHT fix - $cmdList = "desired-temp" if(!$cmdList); - - } elsif(!$dsi && $allSets =~ m/\bdesiredTemperature:/) { - $txt = ReadingsVal($d, "temperature", ""); # ignores stateFormat!!! - $txt =~ s/ .*//; - $txt .= "°C"; - $cmdList = "desiredTemperature" if(!$cmdList); - - } else { - my ($icon, $isHtml); - ($icon, $link, $isHtml) = FW_dev2image($d); - $txt = ($isHtml ? $icon : FW_makeImage($icon, $state)) if($icon); - - my $cmdlist = (defined($link) ? $link : ""); - my $h = ""; - foreach my $cmd (split(":", $cmdlist)) { - my $htmlTxt; - my @c = split(' ', $cmd); # @c==0 if $cmd==" "; - if(int(@c) && $allSets && $allSets =~ m/\b$c[0]:([^ ]*)/) { - my $values = $1; - foreach my $fn (sort keys %{$data{webCmdFn}}) { - no strict "refs"; - $htmlTxt = &{$data{webCmdFn}{$fn}}($FW_wname, - $d, $FW_room, $cmd, $values); - use strict "refs"; - last if(defined($htmlTxt)); - } - } - - if( $htmlTxt ) { - $h .= "

$htmlTxt

"; - } - } - - if( $h ) { - $link = undef; - $h =~ s/'/\\"/g; - $txt = "$txt"; - } else { - $link = "cmd.$d=set $d $link" if(defined($link)); - } - - } - - - if($hasOnOff) { - my $isUpperCase = ($allSets =~ m/(^| )ON(:[^ ]*)?( |$)/ && - $allSets =~ m/(^| )OFF(:[^ ]*)?( |$)/); - # Have to cover: "on:An off:Aus", "A0:Aus AI:An Aus:off An:on" - my $on = ReplaceEventMap($d, $isUpperCase ? "ON" :"on" , 1); - my $off = ReplaceEventMap($d, $isUpperCase ? "OFF":"off", 1); - $link = "cmd.$d=set $d " . ($state eq $on ? $off : $on) if(!defined($link)); - $cmdList = "$on:$off" if(!$cmdList); - - } - - if(defined($link)) { # Have command to execute - my $room = AttrVal($d, "room", undef); - if($room) { - if($FW_room && $room =~ m/\b$FW_room\b/) { - $room = $FW_room; - } else { - $room =~ s/,.*//; - } - $link .= "&room=".urlEncode($room); - } - $txt = "$txt" - if($link !~ m/ noFhemwebLink\b/); - } - - my $style = AttrVal($d, "devStateStyle", ""); - - $state =~ s/"//g; - $state =~ s/<.*?>/ /g; # remove HTML tags for the title - $txt = "
$txt
"; - - my $type = $defs{$d}{TYPE}; - my $sfn = $modules{$type}{FW_summaryFn}; - if($sfn) { - if(!defined($extPage)) { - my %hash; - $extPage = \%hash; - } - no strict "refs"; - my $newtxt = &{$sfn}($FW_wname, $d, $FW_room, $extPage); - use strict "refs"; - $txt = $newtxt if(defined($newtxt)); # As specified - } - - return ($allSets, $cmdList, $txt); -} - - -sub -FW_Get($@) -{ - my ($hash, @a) = @_; - - my $arg = (defined($a[1]) ? $a[1] : ""); - if($arg eq "icon") { - return "need one icon as argument" if(int(@a) != 3); - my $ofn = $FW_wname; - $FW_wname = $hash->{NAME}; - my $icon = FW_iconPath($a[2]); - $FW_wname = $ofn; - return defined($icon) ? "$FW_icondir/$icon" : "no such icon"; - - } elsif($arg eq "pathlist") { - return "web server root: $FW_dir\n". - "icon directory: $FW_icondir\n". - "css directory: $FW_cssdir\n". - "gplot directory: $FW_gplotdir"; - - } else { - return "Unknown argument $arg choose one of icon pathlist:noArg"; - - } -} - - -##################################### -sub -FW_Set($@) -{ - my ($hash, @a) = @_; - my %cmd = ("rereadicons" => 1, "clearSvgCache" => 1); - - return "no set value specified" if(@a < 2); - return ("Unknown argument $a[1], choose one of ". - join(" ", map { "$_:noArg" } sort keys %cmd)) - if(!$cmd{$a[1]}); - - if($a[1] eq "rereadicons") { - my @dirs = keys %FW_icons; - %FW_icons = (); - foreach my $d (@dirs) { - FW_readIcons($d); - } - } - if($a[1] eq "clearSvgCache") { - my $cDir = "$FW_dir/SVGcache"; - if(opendir(DH, $cDir)) { - map { my $n="$cDir/$_"; unlink($n) if(-f $n); } readdir(DH); - closedir(DH); - } else { - return "Can't open $cDir: $!"; - } - } - return undef; -} - -##################################### -sub -FW_closeInactiveClients() -{ - my $now = time(); - foreach my $dev (keys %defs) { - next if(!$defs{$dev}{TYPE} || $defs{$dev}{TYPE} ne "FHEMWEB" || - !$defs{$dev}{LASTACCESS} || $defs{$dev}{inform} || - ($now - $defs{$dev}{LASTACCESS}) < 60); - Log3 $FW_wname, 4, "Closing inactive connection $dev"; - FW_Undef($defs{$dev}, undef); - delete $defs{$dev}; - delete $attr{$dev}; - } - InternalTimer($now+60, "FW_closeInactiveClients", 0, 0); -} - -sub -FW_htmlEscape($) -{ - my ($txt) = @_; - $txt =~ s/&/&/g; - $txt =~ s//>/g; - $txt =~ s/'/'/g; -# $txt =~ s/\n/
/g; - return $txt; -} - -########################### -# Widgets START -sub -FW_widgetFallbackFn() -{ - my ($FW_wname, $d, $FW_room, $cmd, $values) = @_; - - # webCmd "temp 30" should remain text - # noArg is needed for fhem.cfg.demo / Cinema - return "" if(!$values || $values eq "noArg"); - - my($reading) = split( ' ', $cmd, 2 ); - my $current; - if($cmd eq "desired-temp" || $cmd eq "desiredTemperature") { - $current = ReadingsVal($d, $cmd, 20); - $current =~ s/ .*//; # Cut off Celsius - $current = sprintf("%2.1f", int(2*$current)/2) if($current =~ m/[0-9.-]/); - } else { - $current = ReadingsVal($d, $reading, undef); - if( !defined($current) ) { - $reading = 'state'; - $current = Value($d); - } - $current =~ s/$cmd //; - $current = ReplaceEventMap($d, $current, 1); - } - return "
"; -} -# Widgets END -########################### - -sub -FW_visibleDevices(;$) -{ - my($FW_wname) = @_; - - my %devices = (); - foreach my $d (sort keys %defs) { - next if(!defined($defs{$d})); - my $h = $defs{$d}; - next if(!$h->{TEMPORARY}); - next if($h->{TYPE} ne "FHEMWEB"); - next if(defined($FW_wname) && $h->{SNAME} ne $FW_wname); - - next if(!defined($h->{inform})); - - @devices{ keys %{$h->{inform}->{devices}} } = - values %{$h->{inform}->{devices}}; - } - return %devices; -} - -sub -FW_ActivateInform($;$) -{ - my ($cl, $arg) = @_; - $FW_activateInform = ($arg ? $arg : 1); -} - -sub -FW_widgetOverride($$) -{ - my ($d, $str) = @_; - - return $str if(!$str); - - my $da = AttrVal($d, "widgetOverride", ""); - my $fa = AttrVal($FW_wname, "widgetOverride", ""); - return $str if(!$da && !$fa); - - my @list; - push @list, split(" ", $fa) if($fa); - push @list, split(" ", $da) if($da); - foreach my $na (@list) { - my ($n,$a) = split(":", $na, 2); - $str =~ s/\b($n)\b(:[^ ]*)?/$1:$a/g; - } - return $str; -} - - -1; - -=pod -=item helper -=item summary HTTP Server and FHEM Frontend -=item summary_DE HTTP Server und FHEM Frontend -=begin html - - -

FHEMWEB

-
    - FHEMWEB is the builtin web-frontend, it also implements a simple web - server (optionally with Basic-Auth and HTTPS). -

    - - - Define -
      - define <name> FHEMWEB <tcp-portnr> [global|IP] -

      - Enable the webfrontend on port <tcp-portnr>. If global is specified, - then requests from all interfaces (not only localhost / 127.0.0.1) are - serviced. If IP is specified, then FHEMWEB will only listen on this IP.
      - To enable listening on IPV6 see the comments here. -
      -
    -
    - - - Set -
      -
    • rereadicons
      - reads the names of the icons from the icon path. Use after adding or - deleting icons. -
    • -
    • clearSvgCache
      - delete all files found in the www/SVGcache directory, which is used to - cache SVG data, if the SVGcache attribute is set. -
    • -
    -
    - - - Get -
      -
    • icon <logical icon>
      - returns the absolute path to the logical icon. Example: -
        - get myFHEMWEB icon FS20.on
        - /data/Homeautomation/fhem/FHEM/FS20.on.png -
        -
      -
    • -
    • pathlist
      - return FHEMWEB specific directories, where files for given types are - located -

      - -
    - - - Attributes -
      - -
    • addHtmlTitle
      - If set to 0, do not add a title Attribute to the set/get/attr detail - widgets. This might be necessary for some screenreaders. Default is 1. -

    • - - - -
    • addStateEvent

    • - -
    • alias_<RoomName>
      - If you define a userattr alias_<RoomName> and set this attribute - for a device assgined to <RoomName>, then this value will be used - when displaying <RoomName>.
      - Note: you can use the userattr alias_.* to allow all rooms, but in this - case the attribute dropdown in the device detail view won't work for the - alias_.* attributes. -

    • - -
    • allowfrom
    • -
      - -
    • allowedCommands, basicAuth, basicAuthMsg
      - Please create these attributes for the corresponding allowed device, they are deprecated for the FHEMWEB - instance from now on. -

    • - - -
    • allowedHttpMethods
      - FHEMWEB implements the GET, POST and OPTIONS HTTP methods. Some external - devices require the HEAD method, which is not implemented correctly in - FHEMWEB, as FHEMWEB always returns a body, which, according to the spec, - is wrong. As in some cases this not a problem, enabling GET may work. - To do this, set this attribute to GET|POST|HEAD, default ist GET|POST. - OPTIONS is always enabled. -

    • - - -
    • closeConn
      - If set, a TCP Connection will only serve one HTTP request. Seems to - solve problems on iOS9 for WebApp startup. -

    • - - - -
    • column
      - Allows to display more than one column per room overview, by specifying - the groups for the columns. Example:
      -
        - attr WEB column LivingRoom:FS20,notify|FHZ,notify DiningRoom:FS20|FHZ -
      - In this example in the LivingRoom the FS20 and the notify group is in - the first column, the FHZ and the notify in the second.
      - Notes: some elements like SVG plots and readingsGroup can only be part of - a column if they are part of a group. - This attribute can be used to sort the groups in a room, just specify - the groups in one column. - Space in the room and group name has to be written as %20 for this - attribute. Both the room name and the groups are regular expressions. -
    • -
      - - -
    • confirmDelete
      - confirm delete actions with a dialog. Default is 1, set it to 0 to - disable the feature. -
    • -
      - - -
    • confirmJSError
      - JavaScript errors are reported in a dialog as default. - Set this attribute to 0 to disable the reporting. -
    • -
      - - -
    • CORS
      - If set to 1, FHEMWEB will supply a "Cross origin resource sharing" - header, see the wikipedia for details. -
    • -
      - - -
    • csrfToken
      - If set, FHEMWEB requires the value of this attribute as fwcsrf Parameter - for each command. It is used as countermeasure for Cross Site Resource - Forgery attacks. If the value is random, then a random number will be - generated on each FHEMWEB start. If it is set to the literal string - none, no token is expected. Default is random for featurelevel 5.8 and - greater, and none for featurelevel below 5.8

    • - - -
    • csrfTokenHTTPHeader
      - If set (default), FHEMWEB sends the token with the X-FHEM-csrfToken HTTP - header, which is used by some clients. Set it to 0 to switch it off, as - a measurre against shodan.io like FHEM-detection.

    • - - -
    • CssFiles
      - Space separated list of .css files to be included. The filenames - are relative to the www directory. Example: -
        - attr WEB CssFiles pgm2/mystyle.css -
      -

    • - - -
    • Css
      - CSS included in the header after the CssFiles section. -

    • - - -
    • cmdIcon
      - Space separated list of cmd:iconName pairs. If set, the webCmd text is - replaced with the icon. An easy method to set this value is to use - "Extend devStateIcon" in the detail-view, and copy its value.
      - Example:
        - attr lamp cmdIcon on:control_centr_arrow_up off:control_centr_arrow_down -
      -

    • - - -
    • defaultRoom
      - show the specified room if no room selected, e.g. on execution of some - commands. If set hides the motd. Example:
      - attr WEB defaultRoom Zentrale -
    • -
      - - -
    • devStateIcon
      - First form:
      -
        - Space separated list of regexp:icon-name:cmd triples, icon-name and cmd - may be empty.
        - If the state of the device matches regexp, then icon-name will be - displayed as the status icon in the room, and (if specified) clicking - on the icon executes cmd. If fhem cannot find icon-name, then the - status text will be displayed. - Example:
        -
          - attr lamp devStateIcon on:closed off:open
          - attr lamp devStateIcon on::A0 off::AI
          - attr lamp devStateIcon .*:noIcon
          -
        - Note: if the image is referencing an SVG icon, then you can use the - @colorname suffix to color the image. E.g.:
        -
          - attr Fax devStateIcon on:control_building_empty@red - off:control_building_filled:278727 -
        - If the cmd is noFhemwebLink, then no HTML-link will be generated, i.e. - nothing will happen when clicking on the icon or text. - -
      - Second form:
      -
        - Perl code enclosed in {}. If the code returns undef, then the default - icon is used, if it retuns a string enclosed in <>, then it is - interpreted as an html string. Else the string is interpreted as a - devStateIcon of the first fom, see above. - Example:
        - {'<div - style="width:32px;height:32px;background-color:green"></div>'} -
      -
    • -
      - - -
    • devStateStyle
      - Specify an HTML style for the given device, e.g.:
      -
        - attr sensor devStateStyle style="text-align:left;;font-weight:bold;;"
        -
      -
    • -
      - -
    • deviceOverview
      - Configures if the device line from the room view (device icon, state - icon and webCmds/cmdIcons) should also be shown in the device detail - view. Can be set to always, onClick, iconOnly or never. Default is - always. -

    • - - -
    • editConfig
      - If this FHEMWEB attribute is set to 1, then you will be able to edit - the FHEM configuration file (fhem.cfg) in the "Edit files" section. - After saving this file a rereadcfg is executed automatically, which has - a lot of side effects.
      -

    • - - -
    • editFileList
      - Specify the list of Files shown in "Edit Files" section. It is a - newline separated list of triples, the first is the Title, the next is - the directory to search for as a perl expression(!), the third the - regular expression. Default - is: -
        - - Own modules and helper files:$MW_dir:^(.*sh|[0-9][0-9].*Util.*pm|.*cfg|.*holiday|myUtilsTemplate.pm|.*layout)$
        - Gplot files:$FW_gplotdir:^.*gplot$
        - Styles:$FW_cssdir:^.*(css|svg)$
        -
        -
      - NOTE: The directory spec is not flexible: all .js/.css/_defs.svg files - come from www/pgm2 ($FW_cssdir), .gplot files from $FW_gplotdir - (www/gplot), everything else from $MW_dir (FHEM). -

    • - - -
    • endPlotNow
      - If this FHEMWEB attribute is set to 1, then day and hour plots will - end at current time. Else the whole day, the 6 hour period starting at - 0, 6, 12 or 18 hour or the whole hour will be shown. This attribute - is not used if the SVG has the attribute startDate defined.
      -

    • - - -
    • endPlotToday
      - If this FHEMWEB attribute is set to 1, then week and month plots will - end today. Else the current week or the current month will be shown. -
      -

    • - - -
    • fwcompress
      - Enable compressing the HTML data (default is 1, i.e. yes, use 0 to switch it off). -
    • -
      - - -
    • forbiddenroom
      - just like hiddenroom (see below), but accessing the room or the - detailed view via direct URL is prohibited. -
    • -
      - - -
    • hiddengroup
      - Comma separated list of groups to "hide", i.e. not to show in any room - of this FHEMWEB instance.
      - Example: attr WEBtablet hiddengroup FileLog,dummy,at,notify -
    • -
      - - -
    • hiddengroupRegexp
      - One regexp for the same purpose as hiddengroup. -
    • -
      - - -
    • hiddenroom
      - Comma separated list of rooms to "hide", i.e. not to show. Special - values are input, detail and save, in which case the input areas, link - to the detailed views or save button are hidden (although each aspect - still can be addressed through URL manipulation).
      - The list can also contain values from the additional "Howto/Wiki/FAQ" - block. -
    • -
      - - -
    • hiddenroomRegexp
      - One regexp for the same purpose as hiddenroom. Example: -
        - attr WEB hiddenroomRegexp .*config -
      - Note: the special values input, detail and save cannot be specified - with hiddenroomRegexp. -
    • -
      - - -
    • httpHeader
      - One or more HTTP header lines to be sent out with each answer. Example: -
        - attr WEB httpHeader X-Clacks-Overhead: GNU Terry Pratchett -
      -
    • -
      - - - -
    • HTTPS
      - Enable HTTPS connections. This feature requires the perl module - IO::Socket::SSL, to be installed with cpan -i IO::Socket::SSL or - apt-get install libio-socket-ssl-perl; OSX and the FritzBox-7390 - already have this module.
      - - A local certificate has to be generated into a directory called certs, - this directory must be in the modpath - directory, at the same level as the FHEM directory. -
        - mkdir certs
        - cd certs
        - openssl req -new -x509 -nodes -out server-cert.pem -days 3650 -keyout server-key.pem -
      -
      -
    • - - -
    • icon
      - Set the icon for a device in the room overview. There is an - icon-chooser in FHEMWEB to ease this task. Setting icons for the room - itself is indirect: there must exist an icon with the name - ico<ROOMNAME>.png in the iconPath. -
    • -
      - - -
    • iconPath
      - colon separated list of directories where the icons are read from. - The directories start in the fhem/www/images directory. The default is - $styleSheetPrefix:fhemSVG:openautomation:default
      - Set it to fhemSVG:openautomation to get only SVG images. -
    • -
      - - -
    • JavaScripts
      - Space separated list of JavaScript files to be included. The filenames - are relative to the www directory. For each file an additional - user-settable FHEMWEB attribute will be created, to pass parameters to - the script. The name of this additional attribute gets the Param - suffix, directory and the fhem_ prefix will be deleted. Example: -
        - attr WEB JavaScripts codemirror/fhem_codemirror.js
        - attr WEB codemirrorParam { "theme":"blackboard", "lineNumbers":true } -
      -

    • - - -
    • longpoll [0|1|websocket]
      - If activated, the browser is notifed when device states, readings or - attributes are changed, a reload of the page is not necessary. - Default is 1 (on), use 0 to deactivate it.
      - If websocket is specified, then this API is used to notify the browser, - else HTTP longpoll. Note: some older browser do not implement websocket. -
    • -
      - - -
    • longpollSVG
      - Reloads an SVG weblink, if an event should modify its content. Since - an exact determination of the affected events is too complicated, we - need some help from the definition in the .gplot file: the filter used - there (second parameter if the source is FileLog) must either contain - only the deviceName or have the form deviceName.event or deviceName.*. - This is always the case when using the Plot - editor. The SVG will be reloaded for any event triggered by - this deviceName. Default is off. Note: the plotEmbed attribute must be - set. -
    • -
      - - - -
    • mainInputLength
      - length of the maininput text widget in characters (decimal number). -
    • -
      - - -
    • menuEntries
      - Comma separated list of name,html-link pairs to display in the - left-side list. Example:
      - attr WEB menuEntries fhem.de,http://fhem.de,culfw.de,http://culfw.de
      - attr WEB menuEntries - AlarmOn,http://fhemhost:8083/fhem?cmd=set%20alarm%20on
      -
    • -
      - - - -
    • nameDisplay
      - The argument is perl code, which is executed for each single device in - the room to determine the name displayed. $DEVICE is the name of the - current device, and $ALIAS is the value of the alias attribute or the - name of the device, if no alias is set. E.g. you can add a a global - userattr named alias_hu for the Hungarian translation, and specify - nameDisplay for the hungarian FHEMWEB instance as -
        - AttrVal($DEVICE, "alias_hu", $ALIAS) -
      -
    • -
      - - -
    • nrAxis
      - the number of axis for which space should be reserved on the left and - right sides of a plot and optionaly how many axes should realy be used - on each side, separated by comma: left,right[,useLeft,useRight]. You - can set individual numbers by setting the nrAxis of the SVG. Default is - 1,1. -

    • - - -
    • ploteditor
      - Configures if the Plot editor should be shown - in the SVG detail view. - Can be set to always, onClick or never. Default is always. -

    • - - -
    • plotEmbed
      - If set (to 1), SVG plots will be rendered as part of <embed> - tags, as in the past this was the only way to display SVG. Setting - plotEmbed to 0 (the default) will render SVG in-place.
      -

    • - - -
    • plotfork
      - If set to a nonzero value, run part of the processing (e.g. SVG plot generation or RSS feeds) in - parallel processes, default is 0. Note: do not use it on systems with - small memory footprint. -

    • - - -
    • plotmode
      - Specifies how to generate the plots: -
        -
      • SVG
        - The plots are created with the SVG module. - This is the default.
      • -
      • gnuplot-scroll
        - The plots are created with the gnuplot program. The gnuplot - output terminal PNG is assumed. Scrolling to historical values - is also possible, just like with SVG.
      • -
      • gnuplot-scroll-svg
        - Like gnuplot-scroll, but the output terminal SVG is assumed.
      • -
      -

    • - - -
    • plotsize
      - the default size of the plot, in pixels, separated by comma: - width,height. You can set individual sizes by setting the plotsize of - the SVG. Default is 800,160 for desktop, and 480,160 for - smallscreen. -

    • - - -
    • plotWeekStartDay
      - Start the week-zoom of the SVG plots with this day. - 0 is Sunday, 1 is Monday, etc.
      -

    • - - -
    • redirectCmds
      - Clear the browser URL window after issuing the command by redirecting - the browser, as a reload for the same site might have unintended - side-effects. Default is 1 (enabled). Disable it by setting this - attribute to 0 if you want to study the command syntax, in order to - communicate with FHEMWEB. -
    • -
      - - -
    • refresh
      - If set, a http-equiv="refresh" entry will be genererated with the given - argument (i.e. the browser will reload the page after the given - seconds). -

    • - - -
    • reverseLogs
      - Display the lines from the logfile in a reversed order, newest on the - top, so that you dont have to scroll down to look at the latest entries. - Note: enabling this attribute will prevent FHEMWEB from streaming - logfiles, resulting in a considerably increased memory consumption - (about 6 times the size of the file on the disk). -
    • -
      - - -
    • roomIcons
      - Space separated list of room:icon pairs, to override the default - behaviour of showing an icon, if there is one with the name of - "icoRoomName". This is the correct way to remove the icon for the room - Everything, or to set one for rooms with / in the name (e.g. - Anlagen/EDV). The first part is treated as regexp, so space is - represented by a dot. Example:
      - attr WEB roomIcons Anlagen.EDV:icoEverything -
    • -
      - - -
    • smallscreenCommands
      - If set to 1, commands, slider and dropdown menues will appear in - smallscreen landscape mode. -

    • - - -
    • sortby
      - Take the value of this attribute when sorting the devices in the room - overview instead of the alias, or if that is missing the devicename - itself. If the sortby value is enclosed in {} than it is evaluated as a - perl expression. $NAME is set to the device name. -
    • -
      - - -
    • showUsedFiles
      - In the Edit files section, show only the used files. - Note: currently this is only working for the "Gplot files" section. -
    • -
      - - -
    • sortRooms
      - Space separated list of rooms to override the default sort order of the - room links. As the rooms in this attribute are actually regexps, space - in the roomname has to be specified as dot (.). - Example:
      - attr WEB sortRooms DG OG EG Keller -
    • -
      - -
    • sslVersion
      - See the global attribute sslVersion. -

    • - - -
    • sslCertPrefix
      - Set the prefix for the SSL certificate, default is certs/server-, see - also the HTTPS attribute. -

    • - - -
    • styleData
      - data-storage used by dynamic styles like f18 -

    • - - -
    • stylesheetPrefix
      - prefix for the files style.css, svg_style.css and svg_defs.svg. If the - file with the prefix is missing, the default file (without prefix) will - be used. These files have to be placed into the FHEM directory, and can - be selected directly from the "Select style" FHEMWEB menu entry. Example: -
        - attr WEB stylesheetPrefix dark
        -
        - Referenced files:
        -
          - darksvg_defs.svg
          - darksvg_style.css
          - darkstyle.css
          -
        -
        -
      - Note:if the argument contains the string smallscreen or touchpad, - then FHEMWEB will optimize the layout/access for small screen size (i.e. - smartphones) or touchpad devices (i.e. tablets)
      - - The default configuration installs 3 FHEMWEB instances: port 8083 for - desktop browsers, port 8084 for smallscreen, and 8085 for touchpad.
      - - If touchpad or smallscreen is specified, then WebApp support is - activated: After viewing the site on the iPhone or iPad in Safari, you - can add a link to the home-screen to get full-screen support. Links are - rendered differently in this mode to avoid switching back to the "normal" - browser. -
    • -
      - - -
    • SVGcache
      - if set, cache plots which won't change any more (the end-date is prior - to the current timestamp). The files are written to the www/SVGcache - directory. Default is off.
      - See also the clearSvgCache command for clearing the cache. -

    • - - -
    • title
      - Sets the title of the page. If enclosed in {} the content is evaluated. -

    • - - -
    • viewport
      - Sets the "viewport" attribute in the HTML header. This can for - example be used to force the width of the page or disable zooming.
      - Example: attr WEB viewport - width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no -

    • - - -
    • webCmd
      - Colon separated list of commands to be shown in the room overview for a - certain device. Has no effect on smallscreen devices, see the - devStateIcon command for an alternative.
      - Example: -
        - attr lamp webCmd on:off:on-for-timer 10
        -
      -
      - - The first specified command is looked up in the "set device ?" list - (see the setList attribute for dummy devices). - If there it contains some known modifiers (colon, followed - by a comma separated list), then a different widget will be displayed. - See also the widgetOverride attribute below. Examples: -
        - define d1 dummy
        - attr d1 webCmd state
        - attr d1 readingList state
        - attr d1 setList state:on,off

        - - define d2 dummy
        - attr d2 webCmd state
        - attr d2 readingList state
        - attr d2 setList state:slider,0,1,10

        - - define d3 dummy
        - attr d3 webCmd state
        - attr d3 readingList state
        - attr d3 setList state:time
        -
      - If the command is state, then the value will be used as a command.
      - Note: this is an attribute for the displayed device, not for the FHEMWEB - instance. -
    • -
      - - -
    • webCmdLabel
      - Colon separated list of labels, used to prefix each webCmd. The number - of labels must exactly match the number of webCmds. To implement - multiple rows, insert a return character after the text and before the - colon.

    • - - -
    • webname
      - Path after the http://hostname:port/ specification. Defaults to fhem, - i.e the default http address is http://localhost:8083/fhem -

    • - - -
    • widgetOverride
      - Space separated list of name:modifier pairs, to override the widget - for a set/get/attribute specified by the module author. - Following is the list of known modifiers: -
        - -
      -
    • -
      -
    -
-=end html - -=begin html_DE - - -

FHEMWEB

-
    - FHEMWEB ist das default WEB-Frontend, es implementiert auch einen einfachen - Webserver (optional mit Basic-Auth und HTTPS). -

    - - - Define -
      - define <name> FHEMWEB <tcp-portnr> [global|IP] -

      - Aktiviert das Webfrontend auf dem Port <tcp-portnr>. Mit dem - Parameter global werden Anfragen von allen Netzwerkschnittstellen - akzeptiert (nicht nur vom localhost / 127.0.0.1). Falls IP angegeben wurde, - dann werden nur Anfragen an diese IP Adresse akzeptiert.
      - - Informationen für den Betrieb mit IPv6 finden Sie hier.
      -
    -
    - - - Set -
      -
    • rereadicons
      - Damit wird die Liste der Icons neu eingelesen, für den Fall, dass - Sie Icons löschen oder hinzufügen. -
    • -
    • clearSvgCache
      - Im Verzeichnis www/SVGcache werden SVG Daten zwischengespeichert, wenn - das Attribut SVGcache gesetzt ist. Mit diesem Befehl leeren Sie diesen - Zwischenspeicher. -
    • -
    -
    - - - Get -
      -
    • icon <logical icon>
      - Liefert den absoluten Pfad des (logischen) Icons zurück. Beispiel: -
        - get myFHEMWEB icon FS20.on
        - /data/Homeautomation/fhem/FHEM/FS20.on.png -
        -
    • -
    • pathlist
      - Zeigt diejenigen Verzeichnisse an, in welchen die verschiedenen Dateien - für FHEMWEB liegen. -
    • -

      - -
    - - - Attribute -
      - -
    • addHtmlTitle
      - Falls der Wert 0 ist, wird bei den set/get/attr Parametern in der - DetailAnsicht der Geräte kein title Attribut gesetzt. Das is bei - manchen Screenreadern erforderlich. Die Voreinstellung ist 1. -

    • - -
    • addStateEvent

    • - -
    • alias_<RoomName>
      - Falls man das Attribut alias_<RoomName> definiert, und dieses - Attribut für ein Gerät setzt, dann wird dieser Wert bei - Anzeige von <RoomName> verwendet.
      - Achtung: man kann im userattr auch alias_.* verwenden um alle - möglichen Räume abzudecken, in diesem Fall wird aber die - Attributauswahl in der Detailansicht für alias_.* nicht - funktionieren. -

    • - -
    • allowfrom -

    • - -
    • allowedCommands, basicAuth, basicAuthMsg
      - Diese Attribute müssen ab sofort bei dem passenden allowed Gerät angelegt werden, und sind - für eine FHEMWEB Instanz unerwünscht. -

    • - - -
    • allowedHttpMethods
      - FHEMWEB implementiert die HTTP Methoden GET, POST und OPTIONS. Manche - externe Geräte benötigen HEAD, das ist aber in FHEMWEB nicht - korrekt implementiert, da FHEMWEB immer ein body zurückliefert, was - laut Spec falsch ist. Da ein body in manchen Fällen kein Problem - ist, kann man HEAD durch setzen dieses Attributes auf GET|POST|HEAD - aktivieren, die Voreinstellung ist GET|POST. OPTIONS ist immer - aktiviert. -

    • - - - -
    • closeConn
      - Falls gesetzt, wird pro TCP Verbindung nur ein HTTP Request - durchgeführt. Für iOS9 WebApp startups scheint es zu helfen. -

    • - - -
    • cmdIcon
      - Leerzeichen getrennte Auflistung von cmd:iconName Paaren. - Falls gesetzt, wird das webCmd text durch den icon gesetzt. - Am einfachsten setzt man cmdIcon indem man "Extend devStateIcon" im - Detail-Ansicht verwendet, und den Wert nach cmdIcon kopiert.
      - Beispiel:
        - attr lamp cmdIcon on:control_centr_arrow_up off:control_centr_arrow_down -
      -

    • - - -
    • column
      - Damit werden mehrere Spalten für einen Raum angezeigt, indem - sie verschiedene Gruppen Spalten zuordnen. Beispiel:
      -
        - attr WEB column LivingRoom:FS20,notify|FHZ,notify DiningRoom:FS20|FHZ -
      - - In diesem Beispiel werden im Raum LivingRoom die FS20 sowie die notify - Gruppe in der ersten Spalte, die FHZ und das notify in der zweiten - Spalte angezeigt.
      - - Anmerkungen: einige Elemente, wie SVG Plots und readingsGroup - können nur dann Teil einer Spalte sein wenn sie in group stehen. Dieses Attribut kann man zum sortieren - der Gruppen auch dann verwenden, wenn man nur eine Spalte hat. - Leerzeichen im Raum- und Gruppennamen sind für dieses Attribut als - %20 zu schreiben. Raum- und Gruppenspezifikation ist jeweils ein - %regulärer Ausdruck. -

    • - - -
    • confirmDelete
      - Löschaktionen weden mit einem Dialog bestätigt. - Falls dieses Attribut auf 0 gesetzt ist, entfällt das. -
    • -
      - - -
    • confirmJSError
      - JavaScript Fehler werden per Voreinstellung in einem Dialog gemeldet. - Durch setzen dieses Attributes auf 0 werden solche Fehler nicht - gemeldet. -
    • -
      - - -
    • CORS
      - Wenn auf 1 gestellt, wird FHEMWEB einen "Cross origin resource sharing" - Header bereitstellen, näheres siehe Wikipedia. -

    • - - -
    • csrfToken
      - Falls gesetzt, wird der Wert des Attributes als fwcsrf Parameter bei - jedem über FHEMWEB abgesetzten Kommando verlangt, es dient zum - Schutz von Cross Site Resource Forgery Angriffen. - Falls der Wert random ist, dann wird ein Zufallswert beim jeden FHEMWEB - Start neu generiert, falls er none ist, dann wird kein Parameter - verlangt. Default ist random für featurelevel 5.8 und - größer, und none für featurelevel kleiner 5.8 -

    • - - -
    • csrfTokenHTTPHeader
      - Falls gesetzt (Voreinstellung), FHEMWEB sendet im HTTP Header den - csrfToken als X-FHEM-csrfToken, das wird von manchen FHEM-Clients - benutzt. Mit 0 kann man das abstellen, um Sites wie shodan.io die - Erkennung von FHEM zu erschweren.

    • - - -
    • CssFiles
      - Leerzeichen getrennte Liste von .css Dateien, die geladen werden. - Die Dateinamen sind relativ zum www Verzeichnis anzugeben. Beispiel: -
        - attr WEB CssFiles pgm2/mystyle.css -
      -

    • - - -
    • Css
      - CSS, was nach dem CssFiles Abschnitt im Header eingefuegt wird. -

    • - - -
    • defaultRoom
      - Zeigt den angegebenen Raum an falls kein Raum explizit ausgewählt - wurde. Achtung: falls gesetzt, wird motd nicht mehr angezeigt. - Beispiel:
      - attr WEB defaultRoom Zentrale -

    • - - -
    • devStateIcon
      - Erste Variante:
      -
        - Leerzeichen getrennte Auflistung von regexp:icon-name:cmd - Dreierpärchen, icon-name und cmd dürfen leer sein.
        - - Wenn der Zustand des Gerätes mit der regexp übereinstimmt, - wird als icon-name das entsprechende Status Icon angezeigt, und (falls - definiert), löst ein Klick auf das Icon das entsprechende cmd aus. - Wenn fhem icon-name nicht finden kann, wird der Status als Text - angezeigt. - Beispiel:
        -
          - attr lamp devStateIcon on:closed off:open
          - attr lamp devStateIcon on::A0 off::AI
          - attr lamp devStateIcon .*:noIcon
          -
        - Anmerkung: Wenn das Icon ein SVG Bild ist, kann das @colorname Suffix - verwendet werden um das Icon einzufärben. Z.B.:
        -
          - attr Fax devStateIcon on:control_building_empty@red - off:control_building_filled:278727 -
        - Falls cmd noFhemwebLink ist, dann wird kein HTML-Link generiert, d.h. - es passiert nichts, wenn man auf das Icon/Text klickt. -
      - Zweite Variante:
      -
        - Perl regexp eingeschlossen in {}. Wenn der Code undef - zurückliefert, wird das Standard Icon verwendet; wird ein String - in <> zurück geliefert, wird dieser als HTML String interpretiert. - Andernfalls wird der String als devStateIcon gemäß der - ersten Variante interpretiert, siehe oben. Beispiel:
        - - {'<div style="width:32px;height:32px;background-color:green"></div>'} -
      -

    • - - -
    • devStateStyle
      - Für ein best. Gerät einen best. HTML-Style benutzen. - Beispiel:
      -
        - attr sensor devStateStyle style="text-align:left;;font-weight:bold;;"
        -
      -

    • - -
    • deviceOverview
      - Gibt an ob die Darstellung aus der Raum-Ansicht (Zeile mit - Gerüteicon, Stateicon und webCmds/cmdIcons) auch in der - Detail-Ansicht angezeigt werden soll. Kann auf always, onClick, - iconOnly oder never gesetzt werden. Der Default ist always. -

    • - - -
    • editConfig
      - Falls dieses FHEMWEB Attribut (auf 1) gesetzt ist, dann kann man die - FHEM Konfigurationsdatei in dem "Edit files" Abschnitt bearbeiten. Beim - Speichern dieser Datei wird automatisch rereadcfg ausgefuehrt, was - diverse Nebeneffekte hat.
      -

    • - - -
    • editFileList
      - Definiert die Liste der angezeigten Dateien in der "Edit Files" - Abschnitt. Es ist eine Newline getrennte Liste von Tripeln bestehend - aus Titel, Verzeichnis für die Suche als perl Ausdruck(!), und - Regexp. Die Voreinstellung ist: -
        - - Own modules and helper files:$MW_dir:^(.*sh|[0-9][0-9].*Util.*pm|.*cfg|.*holiday|myUtilsTemplate.pm|.*layout)$
        - Gplot files:$FW_gplotdir:^.*gplot$
        - Styles:$FW_cssdir:^.*(css|svg)$
        -
        -
      - Achtung: die Verzeichnis Angabe ist nicht flexibel: alle - .js/.css/_defs.svg Dateien sind in www/pgm2 ($FW_cssdir), .gplot - Dateien in $FW_gplotdir (www/gplot), alles andere in $MW_dir (FHEM). -

    • - - -
    • endPlotNow
      - Wenn Sie dieses FHEMWEB Attribut auf 1 setzen, werden Tages und - Stunden-Plots zur aktuellen Zeit beendet. (Ähnlich wie - endPlotToday, nur eben minütlich). - Ansonsten wird der gesamte Tag oder eine 6 Stunden Periode (0, 6, 12, - 18 Stunde) gezeigt. Dieses Attribut wird nicht verwendet, wenn das SVG - Attribut startDate benutzt wird.
      -

    • - - -
    • endPlotToday
      - Wird dieses FHEMWEB Attribut gesetzt, so enden Wochen- bzw. Monatsplots - am aktuellen Tag, sonst wird die aktuelle Woche/Monat angezeigt. -

    • - - -
    • forbiddenroom
      - Wie hiddenroom, aber der Zugriff auf die Raum- oder Detailansicht - über direkte URL-Eingabe wird unterbunden. -

    • - - -
    • fwcompress
      - Aktiviert die HTML Datenkompression (Standard ist 1, also ja, 0 stellt - die Kompression aus). -

    • - - -
    • hiddengroup
      - Wie hiddenroom (siehe unten), jedoch auf Gerätegruppen bezogen. -
      - Beispiel: attr WEBtablet hiddengroup FileLog,dummy,at,notify -

    • - - -
    • hiddengroupRegexp
      - Ein regulärer Ausdruck, um Gruppen zu verstecken. -
    • -
      - - -
    • hiddenroom
      - Eine Komma getrennte Liste, um Räume zu verstecken, d.h. nicht - anzuzeigen. Besondere Werte sind input, detail und save. In diesem - Fall werden diverse Eingabefelder ausgeblendent. Durch direktes Aufrufen - der URL sind diese Räume weiterhin erreichbar!
      - Ebenso können Einträge in den Logfile/Commandref/etc Block - versteckt werden.

    • - - -
    • hiddenroomRegexp
      - Ein regulärer Ausdruck, um Räume zu verstecken. Beispiel: -
        - attr WEB hiddenroomRegexp .*config -
      - Achtung: die besonderen Werte input, detail und save müssen mit - hiddenroom spezifiziert werden. -
    • -
      - - -
    • httpHeader
      - Eine oder mehrere HTTP-Header Zeile, die in jede Antwort eingebettet - wird. Beispiel: -
        - attr WEB httpHeader X-Clacks-Overhead: GNU Terry Pratchett -
      -
    • -
      - - -
    • HTTPS
      - Ermöglicht HTTPS Verbindungen. Es werden die Perl Module - IO::Socket::SSL benötigt, installierbar mit cpan -i - IO::Socket::SSL oder apt-get install libio-socket-ssl-perl; (OSX und - die FritzBox-7390 haben dieses Modul schon installiert.)
      - - Ein lokales Zertifikat muss im Verzeichis certs erzeugt werden. - Dieses Verzeichnis muss im modpath - angegeben werden, also auf der gleichen Ebene wie das FHEM Verzeichnis. - Beispiel: -
        - mkdir certs
        - cd certs
        - openssl req -new -x509 -nodes -out server-cert.pem -days 3650 -keyout - server-key.pem -
      - -
      -
    • - - -
    • icon
      - Damit definiert man ein Icon für die einzelnen Geräte in der - Raumübersicht. Es gibt einen passenden Link in der Detailansicht - um das zu vereinfachen. Um ein Bild für die Räume selbst zu - definieren muss ein Icon mit dem Namen ico<Raumname>.png im - iconPath existieren (oder man verwendet roomIcons, s.u.) -

    • - - -
    • iconPath
      - Durch Doppelpunkt getrennte Aufzählung der Verzeichnisse, in - welchen nach Icons gesucht wird. Die Verzeichnisse müssen unter - fhem/www/images angelegt sein. Standardeinstellung ist: - $styleSheetPrefix:fhemSVG:openautomation:default
      - Setzen Sie den Wert auf fhemSVG:openautomation um nur SVG Bilder zu - benutzen. -

    • - - -
    • JavaScripts
      - Leerzeichen getrennte Liste von JavaScript Dateien, die geladen werden. - Die Dateinamen sind relativ zum www Verzeichnis anzugeben. Für - jede Datei wird ein zusätzliches Attribut angelegt, damit der - Benutzer dem Skript Parameter weiterreichen kann. Bei diesem - Attributnamen werden Verzeichnisname und fhem_ Präfix entfernt - und Param als Suffix hinzugefügt. Beispiel: -
        - attr WEB JavaScripts codemirror/fhem_codemirror.js
        - attr WEB codemirrorParam { "theme":"blackboard", "lineNumbers":true } -
      -

    • - - -
    • longpoll [0|1|websocket]
      - Falls gesetzt, FHEMWEB benachrichtigt den Browser, wenn - Gerätestatuus, Readings or Attribute sich ändern, ein - Neuladen der Seite ist nicht notwendig. Zum deaktivieren 0 verwenden. -
      - Falls websocket spezifiziert ist, läuft die Benachrichtigung des - Browsers über dieses Verfahren sonst über HTTP longpoll. - Achtung: ältere Browser haben keine websocket Implementierung. -

    • - - - -
    • longpollSVG
      - Lädt SVG Instanzen erneut, falls ein Ereignis dessen Inhalt - ändert. Funktioniert nur, falls die dazugehörige Definition - der Quelle in der .gplot Datei folgenden Form hat: deviceName.Event - bzw. deviceName.*. Wenn man den Plot Editor - benutzt, ist das übrigens immer der Fall. Die SVG Datei wird bei - jedem auslösenden Event dieses Gerätes neu geladen. - Die Voreinstellung ist aus. Achtung: das plotEmbed Attribute muss - gesetzt sein. -

    • - - -
    • mainInputLength
      - Länge des maininput Eingabefeldes (Anzahl der Buchstaben, - Ganzzahl). -

    • - - -
    • menuEntries
      - Komma getrennte Liste; diese Links werden im linken Menü angezeigt. - Beispiel:
      - attr WEB menuEntries fhem.de,http://fhem.de,culfw.de,http://culfw.de
      - attr WEB menuEntries - AlarmOn,http://fhemhost:8083/fhem?cmd=set%20alarm%20on
      -

    • - - -
    • nameDisplay
      - Das Argument ist Perl-Code, was für jedes Gerät in der - Raum-Übersicht ausgeführt wird, um den angezeigten Namen zu - berechnen. Dabei kann man die Variable $DEVICE für den aktuellen - Gerätenamen, und $ALIAS für den aktuellen alias bzw. Name, - falls alias nicht gesetzt ist, verwenden. Z.Bsp. für eine FHEMWEB - Instanz mit ungarischer Anzeige fügt man ein global userattr - alias_hu hinzu, und man setzt nameDisplay für diese FHEMWEB - Instanz auf dem Wert: -
        - AttrVal($DEVICE, "alias_hu", $ALIAS) -
      -
    • -
      - - -
    • nrAxis
      - (bei mehrfach-Y-Achsen im SVG-Plot) Die Darstellung der Y Achsen - benötigt Platz. Hierdurch geben Sie an wie viele Achsen Sie - links,rechts [useLeft,useRight] benötigen. Default ist 1,1 (also 1 - Achse links, 1 Achse rechts). -

    • - - -
    • ploteditor
      - Gibt an ob der Plot Editor in der SVG detail - ansicht angezeigt werden soll. Kann auf always, onClick oder never - gesetzt werden. Der Default ist always. -

    • - - -
    • plotEmbed 0
      - Falls gesetzt (auf 1), dann werden SVG Grafiken mit <embed> Tags - gerendert, da auf älteren Browsern das die einzige - Möglichkeit war, SVG dastellen zu können. Falls 0 (die - Voreinstellung), dann werden die SVG Grafiken "in-place" gezeichnet. -

    • - - -
    • plotfork
      - Falls gesetzt, dann werden bestimmte Berechnungen (z.Bsp. SVG und RSS) - auf nebenläufige Prozesse verteilt. Voreinstellung ist 0. Achtung: - nicht auf Systemen mit wenig Hauptspeicher verwenden. -

    • - - -
    • plotmode
      - Spezifiziert, wie Plots erzeugt werden sollen: -
        -
      • SVG
        - Die Plots werden mit Hilfe des SVG Moduls als SVG - Grafik gerendert. Das ist die Standardeinstellung.
      • - -
      • gnuplot-scroll
        - Die plots werden mit dem Programm gnuplot erstellt. Das output - terminal ist PNG. Der einfache Zugriff auf historische Daten - ist möglich (analog SVG). -
      • - -
      • gnuplot-scroll-svg
        - Wie gnuplot-scroll, aber als output terminal wird SVG angenommen. -
      • -
      -

    • - - -
    • plotsize
      - gibt die Standardbildgröße aller erzeugten Plots an als - Breite,Höhe an. Um einem individuellen Plot die Größe zu - ändern muss dieses Attribut bei der entsprechenden SVG Instanz - gesetzt werden. Default sind 800,160 für Desktop und 480,160 - für Smallscreen -

    • - - -
    • plotWeekStartDay
      - Starte das Plot in der Wochen-Ansicht mit diesem Tag. - 0 ist Sonntag, 1 ist Montag, usw. -

    • - - -
    • redirectCmds
      - Damit wird das URL Eingabefeld des Browser nach einem Befehl geleert. - Standard ist eingeschaltet (1), ausschalten kann man es durch - setzen des Attributs auf 0, z.Bsp. um den Syntax der Kommunikation mit - FHEMWEB zu untersuchen. -

    • - - -
    • refresh
      - Damit erzeugen Sie auf den ausgegebenen Webseiten einen automatischen - Refresh, z.B. nach 5 Sekunden. -

    • - - -
    • reverseLogs
      - Damit wird das Logfile umsortiert, die neuesten Einträge stehen - oben. Der Vorteil ist, dass man nicht runterscrollen muss um den - neuesten Eintrag zu sehen, der Nachteil dass FHEM damit deutlich mehr - Hauptspeicher benötigt, etwa 6 mal so viel, wie das Logfile auf - dem Datenträger groß ist. Das kann auf Systemen mit wenig - Speicher (FRITZ!Box) zum Terminieren des FHEM Prozesses durch das - Betriebssystem führen. -

    • - - -
    • roomIcons
      - Leerzeichen getrennte Liste von room:icon Zuordnungen - Der erste Teil wird als regexp interpretiert, daher muss ein - Leerzeichen als Punkt geschrieben werden. Beispiel:
      - attr WEB roomIcons Anlagen.EDV:icoEverything -

    • - - -
    • sortby
      - Der Wert dieses Attributs wird zum sortieren von Geräten in - Räumen verwendet, sonst wäre es der Alias oder, wenn keiner - da ist, der Gerätename selbst. Falls der Wert des sortby - Attributes in {} eingeschlossen ist, dann wird er als ein perl Ausdruck - evaluiert. $NAME wird auf dem Gerätenamen gesetzt. -

    • - - -
    • showUsedFiles
      - Zeige nur die verwendeten Dateien in der "Edit files" Abschnitt. - Achtung: aktuell ist das nur für den "Gplot files" Abschnitt - implementiert. -
    • -
      - - -
    • sortRooms
      - Durch Leerzeichen getrennte Liste von Räumen, um deren Reihenfolge - zu definieren. - Da die Räume in diesem Attribut als Regexp interpretiert werden, - sind Leerzeichen im Raumnamen als Punkt (.) zu hinterlegen. - Beispiel:
      - attr WEB sortRooms DG OG EG Keller -

    • - - -
    • smallscreenCommands
      - Falls auf 1 gesetzt werden Kommandos, Slider und Dropdown Menüs im - Smallscreen Landscape Modus angezeigt. -

    • - -
    • sslVersion
      - Siehe das global Attribut sslVersion. -

    • - - -
    • sslCertPrefix
      - Setzt das Präfix der SSL-Zertifikate, die Voreinstellung ist - certs/server-, siehe auch das HTTP Attribut. -

    • - - -
    • styleData
      - wird von dynamischen styles wie f18 werwendet -

    • - - -
    • stylesheetPrefix
      - Präfix für die Dateien style.css, svg_style.css und - svg_defs.svg. Wenn die Datei mit dem Präfix fehlt, wird die Default - Datei (ohne Präfix) verwendet. Diese Dateien müssen im FHEM - Ordner liegen und können direkt mit "Select style" im FHEMWEB - Menüeintrag ausgewählt werden. Beispiel: -
        - attr WEB stylesheetPrefix dark
        -
        - Referenzdateien:
        -
          - darksvg_defs.svg
          - darksvg_style.css
          - darkstyle.css
          -
        -
        -
      - Anmerkung:Wenn der Parametername smallscreen oder touchpad - enthält, wird FHEMWEB das Layout/den Zugriff für entsprechende - Geräte (Smartphones oder Touchpads) optimieren
      - - Standardmäßig werden 3 FHEMWEB Instanzen aktiviert: Port 8083 - für Desktop Browser, Port 8084 für Smallscreen, und 8085 - für Touchpad.
      - - Wenn touchpad oder smallscreen benutzt werden, wird WebApp support - aktiviert: Nachdem Sie eine Seite am iPhone oder iPad mit Safari - angesehen haben, können Sie einen Link auf den Homescreen anlegen um - die Seite im Fullscreen Modus zu sehen. Links werden in diesem Modus - anders gerendert, um ein "Zurückfallen" in den "normalen" Browser zu - verhindern. -

    • - - -
    • SVGcache
      - Plots die sich nicht mehr ändern, werden im SVGCache Verzeichnis - (www/SVGcache) gespeichert, um die erneute, rechenintensive - Berechnung der Grafiken zu vermeiden. Default ist 0, d.h. aus.
      - Siehe den clearSvgCache Befehl um diese Daten zu löschen. -

    • - - -
    • title
      - Setzt den Titel der Seite. Falls in {} eingeschlossen, dann wird es - als Perl Ausdruck evaluiert. -

    • - - -
    • viewport
      - Setzt das "viewport" Attribut im HTML Header. Das kann benutzt - werden um z.B. die Breite fest vorzugeben oder Zoomen zu verhindern.
      - Beispiel: attr WEB viewport - width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no -

    • - - -
    • webCmd
      - Durch Doppelpunkte getrennte Auflistung von Befehlen, die für ein - bestimmtes Gerät gelten sollen. Funktioniert nicht mit - smallscreen, ein Ersatz dafür ist der devStateIcon Befehl.
      - Beispiel: -
        - attr lamp webCmd on:off:on-for-timer 10
        -
      -
      - - Der erste angegebene Befehl wird in der "set device ?" list - nachgeschlagen (Siehe das setList Attrib - für Dummy Geräte). Wenn dort bekannte Modifier sind, - wird ein anderes Widget angezeigt. Siehe auch widgetOverride.
      - Wenn der Befehl state ist, wird der Wert als Kommando interpretiert.
      - Beispiele: -
        - define d1 dummy
        - attr d1 webCmd state
        - attr d1 setList state:on,off
        - define d2 dummy
        - attr d2 webCmd state
        - attr d2 setList state:slider,0,1,10
        - define d3 dummy
        - attr d3 webCmd state
        - attr d3 setList state:time
        -
      - Anmerkung: dies ist ein Attribut für das anzuzeigende Gerät, - nicht für die FHEMWEBInstanz. -

    • - - -
    • webCmdLabel
      - Durch Doppelpunkte getrennte Auflistung von Texten, die vor dem - jeweiligen webCmd angezeigt werden. Der Anzahl der Texte muss exakt den - Anzahl der webCmds entsprechen. Um mehrzeilige Anzeige zu realisieren, - kann ein Return nach dem Text und vor dem Doppelpunkt eingefuehrt - werden.

    • - - -
    • webname
      - Der Pfad nach http://hostname:port/ . Standard ist fhem, - so ist die Standard HTTP Adresse http://localhost:8083/fhem -

    • - - -
    • widgetOverride
      - Leerzeichen separierte Liste von Name/Modifier Paaren, mit dem man den - vom Modulautor für einen bestimmten Parameter (Set/Get/Attribut) - vorgesehene Widgets ändern kann. Folgendes ist die Liste der - bekannten Modifier: -
        - -
    • - -
    -
- -=end html_DE - -=cut