diff --git a/fhem/CHANGED b/fhem/CHANGED index 579875d3b..0ce3c027e 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,24 +1,35 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: fhemweb.js rewrite based on jQuery, single-widget-implementation + - feature: SVG: multiple sources allowed, Plot-Editor + - feature: textfield-long and knob widgets - added: some new icons by Rampler - - feature: PRESENCE: new reading "presence" which contains the current (or last known) - presence state (can be "absent" or "present") - - bugfix: 70_Jabber.pm: hardening XML::Stream Process() call and fix of ssl_verify - - feature: readingsGroup: allow devspec :FILTER= expressions in device selection + - feature: PRESENCE: new reading "presence" which contains the current (or + last known) presence state (can be "absent" or "present") + - bugfix: 70_Jabber.pm: hardening XML::Stream Process() call and fix of + ssl_verify + - feature: readingsGroup: allow devspec :FILTER= expressions in device + selection - added: 73_km200.pm for the Buderus KM200 heating controller (Sailor) - feature: 70_XBMC: added command 'connect' to connect instantly - - change: FB_CALLMONITOR: use standard file read/write function to support use of configDb - - bugfix: FB_CALLMONITOR: fix phonebook file read when using configDb (Forum #30244) - - feature: 70_XBMC: added commands: openmovieid, openepisodeid, addon, jsonraw (thanks to siggi85) + - change: FB_CALLMONITOR: use standard file read/write function to support + use of configDb + - bugfix: FB_CALLMONITOR: fix phonebook file read when using configDb (Forum + #30244) + - feature: 70_XBMC: added commands: openmovieid, openepisodeid, addon, + jsonraw (thanks to siggi85) - fix: 70_XBMC: made fork attribute to close file handles correctly - feature: 70_XBMC: added mechanism to detect disconnects (TCP) - fix: 66_ECMD: avoid reading from a closed connection in ECMD_READ - - feature: 70_PIONEERAVR: readings for currentAlbum etc., more internals (network settings, moved some from readings to internals), new attributes volumeLimit & volumeLimitStraight + - feature: 70_PIONEERAVR: readings for currentAlbum etc., more internals + (network settings, moved some from readings to internals), new attributes + volumeLimit & volumeLimitStraight - added: some new icons (hourglass, frost, sani_heating_level_XX) - fix: sani_heating_boost (possibility to colorize) - feature: FB_CALLMONITOR: add remote phonebook lookup via telnet connection to FritzBox (JoWiemann). - - bugfix: 70_PIONEERAVR & 71_PIONEERAVRZONE: fixed not working set-extensions (on-for-timer,...) + - bugfix: 70_PIONEERAVR & 71_PIONEERAVRZONE: fixed not working set-extensions + (on-for-timer,...) - feature: fheminfo: report third-party modules - feature: 99_Utils.pm: add getUniqueID, getKeyValue, setKeyValue - feature: SMARTMON: additional parameters for smartctl @@ -33,7 +44,8 @@ - feature: HUEDevice: allow ct presets in webCmd new subTypes extcolordimer and ctdimer start support for Lightify bulbs - - added: SONOS and SONOSPLAYER to support Sonos Multiroom Audiosystems (Reinerlein) + - added: SONOS and SONOSPLAYER to support Sonos Multiroom Audiosystems + (Reinerlein) - change: 64_ESA2000.pm: add batterystate - added: 42_SMARTMON: Frontend to smartctl (maintainer: hexenmeister) - feature: 70_PushNotifier added line break in Messages (xusader) @@ -43,7 +55,8 @@ - bugfix: FB_CALLMONITOR: fixing not working company numbers reverse search for search.ch - bugfix: 70_PushNotifier repair set function (xusader) - - bugfix: PRESENCE: fixing not working timer, when using set [...] statusRequest + - bugfix: PRESENCE: fixing not working timer, when using set [...] + statusRequest - bugfix: FB_CALLMONITOR: fixing reverse search for klicktel.de - feature: new module 52_I2C_MCP342x.pm added (klausw) - feature: SYSMON: read cpu temp on FritzBox @@ -57,7 +70,8 @@ - feature: new module 98_logProxy.pm added (justme1968) - change: 66_ECMD: ReadyFn added (fixes issue under Windows) - change: 02_RSS: use a GUID in RSS; urlq source for img command - - feature: 70_PushNotifier improve usebility, configuration without cURL (xusader) + - feature: 70_PushNotifier improve usebility, configuration without cURL + (xusader) - bugfix: SYSMON: prevent empty line im log by userReadings - feature: 10_IT empfang (by bjoernh) - bugfix: PRESENCE: fix race condition, when delete disabled attribute and diff --git a/fhem/FHEM/01_FHEMWEB.pm b/fhem/FHEM/01_FHEMWEB.pm index 92f14ce11..f39a293e5 100755 --- a/fhem/FHEM/01_FHEMWEB.pm +++ b/fhem/FHEM/01_FHEMWEB.pm @@ -16,6 +16,7 @@ sub FW_answerCall($); sub FW_dev2image($;$); sub FW_devState($$@); sub FW_digestCgi($); +sub FW_directNotify($$); sub FW_doDetail($); sub FW_fatal($); sub FW_fileList($); @@ -187,11 +188,7 @@ FHEMWEB_Initialize($) closedir(DH); } - $data{webCmdFn}{slider} = "FW_sliderFn"; - $data{webCmdFn}{timepicker} = "FW_timepickerFn"; - $data{webCmdFn}{noArg} = "FW_noArgFn"; - $data{webCmdFn}{textField} = "FW_textFieldFn"; - $data{webCmdFn}{"~dropdown"}= "FW_dropdownFn"; # Should be the last + $data{webCmdFn}{"~"} = "FW_widgetFallbackFn"; # Should be the last if($init_done) { # reload workaround foreach my $pe ("fhemSVG", "openautomation", "default") { @@ -531,7 +528,9 @@ FW_answerCall($) $ldir = "$FW_dir/pgm2" if($dir eq "css" || $dir eq "js"); # FLOORPLAN compat $ldir = "$attr{global}{modpath}/docs" if($dir eq "docs"); - if(-r "$ldir/$file.$ext") { # no return for FLOORPLAN + # pgm2 check is for jquery-ui images + my $static = ($ext =~ m/(css|js|png|jpg)/i || $dir =~ m/^pgm2/); + if(-r "$ldir/$file.$ext" || $static) { # no return for FLOORPLAN return FW_serveSpecial($file, $ext, $ldir, ($arg =~ m/nocache/) ? 0 : 1); } $arg = "/$dir/$ofile"; @@ -617,7 +616,6 @@ FW_answerCall($) } return 0; } - ############################## # FHEMWEB extensions (FLOORPLOAN, SVG_WriteGplot, etc) my $FW_contentFunc; @@ -696,18 +694,26 @@ FW_answerCall($) FW_pO "" if($rf); } + ######################## + # CSS my $cssTemplate = ""; FW_pO sprintf($cssTemplate, "pgm2/style.css"); - my @cssFiles = split(" ", AttrVal($FW_wname, "CssFiles", "")); - map { FW_pO sprintf($cssTemplate, $_); } @cssFiles; + FW_pO sprintf($cssTemplate, "pgm2/jquery-ui.min.css"); + map { FW_pO sprintf($cssTemplate, $_); } + split(" ", AttrVal($FW_wname, "CssFiles", "")); + + ######################## + # JavaScripts + my $jsTemplate = ''; + FW_pO sprintf($jsTemplate, "$FW_ME/pgm2/jquery.min.js"); + FW_pO sprintf($jsTemplate, "$FW_ME/pgm2/jquery-ui.min.js"); ######################## # FW Extensions - my $jsTemplate = ''; if(defined($data{FWEXT})) { foreach my $k (sort keys %{$data{FWEXT}}) { my $h = $data{FWEXT}{$k}; - next if($h !~ m/HASH/ || !$h->{SCRIPT}); + 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); @@ -715,21 +721,18 @@ FW_answerCall($) } ####################### - # Other JavaScripts - FW_pO sprintf($jsTemplate, "$FW_ME/pgm2/svg.js") if($FW_plotmode eq "SVG"); + # Other JavaScripts + their Attributes map { FW_pO sprintf($jsTemplate, "$FW_ME/pgm2/$_") } @FW_fhemwebjs; - $jsTemplate = ''; map { my $n = $_; $n =~ s+.*/++; $n =~ s/.js$//; $n =~ s/fhem_//; $n .= "Param"; FW_pO sprintf($jsTemplate, AttrVal($FW_wname, $n, ""), "$FW_ME/$_"); } split(" ", AttrVal($FW_wname, "JavaScripts", "")); - my $onload = AttrVal($FW_wname, "longpoll", 1) ? - "onload=\"FW_delayedStart()\"" : ""; my $csrf= ($FW_CSRF ? "fwcsrf='$defs{$FW_wname}{CSRFTOKEN}'" : ""); - FW_pO "\n"; + my $gen = 'generated="'.(time()-1).'"'; + my $lp = 'longpoll="'.AttrVal($FW_wname,"longpoll",1).'"'; + FW_pO "\n"; if($FW_activateInform) { $cmd = "style eventMonitor $FW_activateInform"; @@ -921,7 +924,7 @@ FW_makeTable($$$@) } else { if( $title eq "Attributes" ) { FW_pO "
". - "". + "". "$n
"; } else { FW_pO "
$n
"; @@ -978,7 +981,7 @@ FW_makeTable($$$@) ############################## # Used only for set or attr lists. sub -FW_makeSelect($$$$) +FW_detailSelect($$$$) { my ($d, $cmd, $list,$class) = @_; return if(!$list || $FW_hiddenroom{input}); @@ -987,8 +990,9 @@ FW_makeSelect($$$$) 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; - FW_pO "
"; + FW_pO "
"; FW_pO "
"; FW_pO FW_hidden("detail", $d); @@ -996,12 +1000,8 @@ FW_makeSelect($$$$) FW_pO FW_hidden("dev.$cmd$d", $d); FW_pO FW_submit("cmd.$cmd$d", $cmd, $class); FW_pO "
 $d 
"; - FW_pO FW_select("sel.$cmd$d","arg.$cmd$d",\@al, $selEl, $class, - "FW_selChange(this.options[selectedIndex].text,'$list','val.$cmd$d')"); + FW_pO FW_select("sel_$cmd$d","arg.$cmd$d",\@al, $selEl, $class); FW_pO FW_textfield("val.$cmd$d", 30, $class); - # Initial setting - FW_pO ""; FW_pO "
"; } @@ -1037,12 +1037,8 @@ FW_doDetail($) use strict "refs"; } - FW_pO "
"; - FW_pO FW_hidden("detail", $d); - FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF); - - FW_makeSelect($d, "set", FW_widgetOverride($d, getAllSets($d)), "set"); - FW_makeSelect($d, "get", FW_widgetOverride($d, getAllGets($d)), "get"); + FW_detailSelect($d, "set", FW_widgetOverride($d, getAllSets($d)), "set"); + FW_detailSelect($d, "get", FW_widgetOverride($d, getAllGets($d)), "get"); FW_makeTable("Internals", $d, $h); FW_makeTable("Readings", $d, $h->{READINGS}); @@ -1057,7 +1053,7 @@ FW_doDetail($) $attrList = FW_widgetOverride($d, $attrList); $attrList =~ s/\\/\\\\/g; $attrList =~ s/'/\\'/g; - FW_makeSelect($d, "attr", $attrList,"attr"); + FW_detailSelect($d, "attr", $attrList,"attr"); FW_makeTable("Attributes", $d, $attr{$d}, "deleteattr"); ## dependent objects @@ -1070,7 +1066,6 @@ FW_doDetail($) push(@dob, $dn); } } - FW_pO "
"; FW_makeTableFromArray("Probably associated with", "assoc", @dob,); FW_pO ""; @@ -1078,6 +1073,7 @@ FW_doDetail($) FW_pH "cmd=style iconFor $d", "Select icon"; FW_pH "cmd=style showDSI $d", "Extend devStateIcon"; FW_pH "$FW_ME/docs/commandref.html#${t}", "Device specific help"; + FW_pH "cmd=delete $d", "Delete this device ($d)" if($d ne "global"); FW_pO "

"; FW_pO "
"; @@ -1300,7 +1296,7 @@ FW_showRoom() $FW_hiddengroup{$r} = 1; } - FW_pO "
"; FW_pO "
"; FW_pO ""; # Need for equal width of subtables @@ -1358,7 +1354,7 @@ FW_showRoom() $icon = FW_makeImage($icon,$icon,"icon") . " " if($icon); if($FW_hiddenroom{detail}) { - FW_pO ""; + FW_pO "" if(!$usuallyAtEnd{$d}); } else { FW_pH "detail=$d", "$icon$devName", 1, "col1" if(!$usuallyAtEnd{$d}); } @@ -1506,8 +1502,10 @@ FW_returnFileAsStream($$$$$) } if(!open(FH, $path)) { - Log3 $FW_wname, 2, "FHEMWEB $FW_wname $path: $!"; - FW_pO "
$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 0; } @@ -1569,12 +1567,12 @@ FW_hidden($$) sub FW_select($$$$$@) { - my ($id, $n, $va, $def, $class, $jSelFn) = @_; + my ($id, $name, $valueArray, $selected, $class, $jSelFn) = @_; $jSelFn = ($jSelFn ? "onchange=\"$jSelFn\"" : ""); $id = ($id ? "id=\"$id\" informId=\"$id\"" : ""); - my $s = ""; + foreach my $v (@{$valueArray}) { + if(defined($selected) && $v eq $selected) { $s .= "\n"; } else { $s .= "\n"; @@ -1750,14 +1748,16 @@ FW_style($$) my $filePath = FW_fileNameToPath($fileName); $FW_data =~ s/\r//g; - my $err = FileWrite({FileName=>$filePath, ForceType=>$forceType}, split("\n", $FW_data)); + my $err = FileWrite({FileName=>$filePath, ForceType=>$forceType}, + split("\n", $FW_data)); if($err) { FW_pO "
$filePath: $!
"; return; } my $ret = FW_fC("rereadcfg") if($filePath eq $attr{global}{configfile}); $ret = FW_fC("reload $fileName") if($fileName =~ m,\.pm$,); - $ret = ($ret ? "

ERROR:

$ret" : "Saved the file $fileName to $forceType"); + $ret = ($ret ? "

ERROR:

$ret" : + "Saved the file $fileName to $forceType"); FW_style("style list", $ret); $ret = ""; @@ -2201,6 +2201,16 @@ FW_makeEdit($$$) } +sub +FW_longpollInfo($$$) +{ + my ($dev, $state, $html) = @_; + $dev =~ s/([\\"])/\\$1/g; + $state =~ s/([\\"])/\\$1/g; + $html =~ s/([\\"])/\\$1/g; + return "[\"$dev\",\"$state\",\"$html\"]"; +} + sub FW_roomStatesForInform($$) { @@ -2219,7 +2229,7 @@ FW_roomStatesForInform($$) my ($allSet, $cmdlist, $txt) = FW_devState($dn, "", \%extPage); if($defs{$dn} && $defs{$dn}{STATE} && $defs{$dn}{TYPE} ne "weblink") { - push @data, "$dn<<$defs{$dn}{STATE}<<$txt"; + push @data, FW_longpollInfo($dn, $defs{$dn}{STATE}, $txt); } } my $data = join("\n", map { s/\n/ /gm; $_ } @data)."\n"; @@ -2265,7 +2275,7 @@ FW_Notify($$) 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, "$dn<<$dev->{STATE}<<$txt"; + push @data, FW_longpollInfo($dn, $dev->{STATE}, $txt); } #Add READINGS @@ -2277,8 +2287,8 @@ FW_Notify($$) next; #ignore 'set' commands } my ($readingName,$readingVal) = split(": ",$events->[$i],2); - push @data, "$dn-$readingName<<$readingVal<<$readingVal"; - push @data, "$dn-$readingName-ts<<$tn<<$tn"; + push @data, FW_longpollInfo("$dn-$readingName",$readingVal,$readingVal); + push @data, FW_longpollInfo("$dn-$readingName-ts", $tn, $tn); } } } @@ -2310,6 +2320,24 @@ FW_Notify($$) return undef; } +sub +FW_directNotify($$) # Notify without the event overhead (Forum #31293) +{ + my ($dev, $msg) = @_; + foreach my $ntfy (values(%defs)) { + next if(!$ntfy->{TYPE} || + $ntfy->{TYPE} ne "FHEMWEB" || + !$ntfy->{inform} || + !$ntfy->{inform}{devices}{$dev}); + if(!addToWritebuffer($ntfy, FW_longpollInfo($dev, $msg, "")."\n")){ + my $name = $ntfy->{NAME}; + Log3 $name, 4, "Closing connection $name due to full buffer in FW_Notify"; + TcpServer_Close($ntfy); + delete($defs{$name}); + } + } +} + ################### # Compute the state (==second) column sub @@ -2371,16 +2399,7 @@ FW_devState($$@) } $link .= "&room=$room"; } - if(AttrVal($FW_wname, "longpoll", 1)) { - $txt = "$txt"; - - } elsif($FW_ss || $FW_tp) { - $txt ="$txt"; - - } else { - $txt = "$txt"; - - } + $txt = "$txt"; } my $style = AttrVal($d, "devStateStyle", ""); @@ -2487,125 +2506,32 @@ FW_htmlEscape($) ########################### # Widgets START -sub -FW_sliderFn($$$$$) -{ - my ($FW_wname, $d, $FW_room, $cmd, $values) = @_; - - return undef if($values !~ m/^slider,([-\d.]*),([-\d.]*),([-\d.]*)(,1)?$/); - return "" if($cmd =~ m/ /); # webCmd pct 30 should generate a link - my ($min,$stp, $max, $flt) = ($1, $2, $3, $4); - $flt = ($flt ? 1 : 0); - my $srf = $FW_room ? "&room=$FW_room" : ""; - my $cv = ReadingsVal($d, $cmd, Value($d)); - my $id = ($cmd eq "state") ? "" : "-$cmd"; - $cmd = "" if($cmd eq "state"); - $cv =~ s/.*?([.\-\d]+).*/$1/; # get first number - $cv = 0 if($cv !~ m/\d/); - return ""; -} - -sub -FW_noArgFn($$$$$) -{ - my ($FW_wname, $d, $FW_room, $cmd, $values) = @_; - - return undef if($values !~ m/^noArg$/); - return ""; -} - -sub -FW_timepickerFn() -{ - my ($FW_wname, $d, $FW_room, $cmd, $values) = @_; - - return undef if($values ne "time"); - return "" if($cmd =~ m/ /); # webCmd on-for-timer 30 should generate a link - my $srf = $FW_room ? "&room=$FW_room" : ""; - my $cv = ReadingsVal($d, $cmd, Value($d)); - $cmd = "" if($cmd eq "state"); - my $c = "\"$FW_ME?cmd=set $d $cmd %$srf\""; - return ""; -} - sub -FW_dropdownFn() +FW_widgetFallbackFn() { my ($FW_wname, $d, $FW_room, $cmd, $values) = @_; - return "" if($cmd =~ m/ /); # webCmd temp 30 should generate a link - my @tv = split(",", $values); - # Hack: eventmap (translation only) should not result in a - # dropdown. eventMap/webCmd/etc handling must be cleaned up. - if(@tv > 1) { - my $txt; - if($cmd eq "desired-temp" || $cmd eq "desiredTemperature") { - $txt = ReadingsVal($d, $cmd, 20); - $txt =~ s/ .*//; # Cut off Celsius - $txt = sprintf("%2.1f", int(2*$txt)/2) if($txt =~ m/[0-9.-]/); - } else { - $txt = ReadingsVal($d, $cmd, Value($d)); - $txt =~ s/$cmd //; + # 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); } - - my $fpname = $FW_wname; - $fpname =~ s/.*floorplan\/(\w+)$/$1/; #allow usage of attr fp_setbutton - - my $readng = ($cmd eq "state" ? "" : "$cmd"." "); - - # TODO in case of running in a floorplan split $FW_wname to get name of - # webInstance. Actually in floorplan the dropdown will refresh the page - # always independently from setting in corresponding web instance, cause - # statement if( AttrVal($FW_wname, "longpoll", 0) == 1) will always fail. - my $selFunct=""; - if( AttrVal($FW_wname, "longpoll", 0) == 1) { - $selFunct = "FW_cmd('$FW_ME?XHR=1&cmd.$d=set $d $readng '+ ". - "this.options[this.selectedIndex].value+ ' &room=$FW_room')"; - - } else { - $selFunct = "window.location = addcsrf('$FW_ME?cmd.$d=set $d $readng '+". - "this.options[this.selectedIndex].value+ ' &room=$FW_room')"; - - } - my $fwsel; - $fwsel = ($cmd eq "state" ? "" : "$cmd ") . - FW_select("$d-$cmd","val.$d", \@tv, $txt,"dropdown","$selFunct"); - return ""; + $current =~ s/$cmd //; } - return undef; + return ""; } - -sub -FW_textFieldFn($$$$) -{ - my ($FW_wname, $d, $FW_room, $cmd, $values) = @_; - - my @args = split("[ \t]+", $cmd); - - return undef if($values !~ m/^textField$/); - return "" if($cmd =~ m/ /); - my $srf = $FW_room ? "&room=$FW_room" : ""; - my $cv = ReadingsVal($d, $cmd, ""); - my $id = ($cmd eq "state") ? "" : "-$cmd"; - - my $c = "$FW_ME?XHR=1&cmd=setreading $d $cmd %$srf"; - return ''; -} - # Widgets END ########################### @@ -3148,18 +3074,23 @@ FW_widgetOverride($$)
  • if the modifier is ":time", then a javascript driven timepicker is displayed.
  • if the modifier is ":textField", an input field is displayed.
  • +
  • if the modifier is ":textField-long", is like textField, but upon + clicking on the input field a textArea (60x25) will be opened.
  • +
  • if the modifier is of the form ":slider,<min>,<step>,<max>[,1]", then a javascript driven slider is displayed. The optional ,1 at the end avoids the rounding of floating-point numbers.
  • -
  • if the modifier is of the form ":multiple,val1,val2,...", then multiple values can be selected and own values can be written, the result is comma separated.
  • -
  • if the modifier is of the form ":multiple-strict,val1,val2,...", then - multiple values can be selected and no new values can be added, the - result is comma separated.
  • - +
  • if the modifier is of the form ":multiple-strict,val1,val2,...", + then multiple values can be selected and no new values can be + added, the result is comma separated.
  • +
  • if the modifier is of the form ":knob,min:1,max:100,...", then + the jQuery knob widget will be displayed. The parameters are + specified as a comma separated list of key:value pairs, where key + does not have to contain the "data-" prefix.
  • else a dropdown with all the modifier values is displayed
  • If this attribute is specified for a FHEMWEB instance, then it is @@ -3167,6 +3098,7 @@ FW_widgetOverride($$)
    @@ -3714,13 +3646,17 @@ FW_widgetOverride($$) vorgesehene Widgets aendern kann. Falls das Attribut für eine WEB Instanz gesetzt wurde, dann wird es bei allen von diesem Web-Instan angezeigten Geräten angewendet. @@ -3744,6 +3687,7 @@ FW_widgetOverride($$)
    diff --git a/fhem/FHEM/90_at.pm b/fhem/FHEM/90_at.pm index aff9dffe0..eb69bcb86 100755 --- a/fhem/FHEM/90_at.pm +++ b/fhem/FHEM/90_at.pm @@ -273,23 +273,18 @@ return "
    Timespec wizard:". - +
    $icon$devName
    $icon$devName
    ". - "
    ". - "
    $min
    ". - "
    ". - "". - "
    ". - "". - "". - "$fwsel
    '. - "
    $cmd:
    ". - '
    Timespec
    Timespec -
    "; + $pageHash->{jsLoaded} = 1 if($pageHash); + } + if(AttrVal($FW_wname, "plotmode", "SVG") eq "jsSVG") { my @d=split(":",$defs{$d}{DEF}); @@ -182,11 +207,10 @@ SVG_FwFn($$$$) "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos); if(AttrVal($d,"plotmode",$FW_plotmode) eq "SVG") { - my ($w, $h) = split(",", AttrVal($d,"plotsize",$FW_plotsize)); + my ($w, $h) = split(",", SVG_getplotsize($d)); $ret .= "
    "; - if(AttrVal($FW_wname, "plotEmbed", - $FW_userAgent !~ m/(iPhone|iPad|iPod).*OS (8|9)/)) { + if(SVG_isEmbed($FW_wname)) { $ret .= "\n"; @@ -236,12 +260,12 @@ SVG_txt($$$$) } sub -SVG_sel($$$@) +SVG_sel($$$;$$) { - my ($v,$l,$c,$fnData) = @_; + my ($v,$l,$c,$fnData,$class) = @_; my @al = split(",",$l); $c =~ s/\\x3a/:/g if($c); - return FW_select($v,$v,\@al,$c, "set", $fnData); + return FW_select(undef,$v,\@al,$c, $class?$class:"set", $fnData); } ############################ @@ -255,12 +279,10 @@ SVG_PEdit($$$$) return "" if( $pe eq 'never' ); - my $ld = $defs{$d}{LOGDEVICE}; - my $ldt = $defs{$ld}{TYPE}; - my $gp = "$FW_gplotdir/$defs{$d}{GPLOTFILE}.gplot"; + my $pm = AttrVal($d,"plotmode",$FW_plotmode); - my ($err, $cfg, $plot, $flog) = SVG_readgplotfile($d, $gp); + my ($err, $cfg, $plot, $srcDesc) = SVG_readgplotfile($d, $gp, $pm); my %conf = SVG_digestConf($cfg, $plot); my $ret = "
    "; @@ -280,8 +302,6 @@ SVG_PEdit($$$$) "action=\"$FW_ME/SVG_WriteGplot\">"; $ret .= "Plot Editor"; $ret .= FW_hidden("detail", $d); # go to detail after save - $ret .= FW_hidden("gplotName", $gp); - $ret .= FW_hidden("logdevicetype", $ldt); if(defined($FW_pos{zoom}) && defined($FW_pos{off})) { # for showData $ret .= FW_hidden("pos", "zoom=$FW_pos{zoom};off=$FW_pos{off}"); } @@ -314,22 +334,39 @@ SVG_PEdit($$$$) $ret .= ""; my $max = @{$conf{lType}}+1; - my ($desc, $htmlArr, $example) = ("Spec", undef, ""); - if($modules{$ldt}{SVG_sampleDataFn}) { - no strict "refs"; - ($desc, $htmlArr, $example) = - &{$modules{$ldt}{SVG_sampleDataFn}}($ld, $flog, $max,\%conf, $FW_wname); - use strict "refs"; - } else { - my @htmlArr; - @htmlArr = map { SVG_txt("par_${_}_0","",$flog->[$_] ? $flog->[$_]:"",20) } - (0..$max-1); - $htmlArr = \@htmlArr; - } + my ($desc, $cnt) = ("Spec", 0); + my (@srcHtml, @paramHtml, @exampleHtml, @revIdx); + my @srcNames = grep { $modules{$defs{$_}{TYPE}}{SVG_sampleDataFn} } + sort keys %defs; - $ret .= "Diagramm label"; - $ret .= "$desc"; - $ret .=" Y-Axis,Plot-Type,Style,Width"; + foreach my $src (@{$srcDesc->{order}}) { + my $lmax = $srcDesc->{src}{$src}{idx}+1; + my $fn = $modules{$defs{$src}{TYPE}}{SVG_sampleDataFn}; + my @argArr = split(" ", $srcDesc->{src}{$src}{arg}); + if($fn) { + no strict "refs"; + my ($ldesc, $paramHtml, $example) = + &{$fn}($src, \@argArr, $lmax,\%conf, $FW_wname); + use strict "refs"; + $desc = $ldesc; + push @paramHtml, @{$paramHtml}; + map { push @exampleHtml, $example } (0..$lmax-1); + + } else { + push @paramHtml, map { SVG_txt("par_${_}_0","",$_,20) } @argArr; + map { push @exampleHtml, "" } (0..$lmax-1); + } + + push @srcHtml, + map { FW_select(undef,"src_$_",\@srcNames,$src,"svgSrc");} (0..$lmax-1); + map { push @revIdx,$srcDesc->{rev}{$cnt}{$_}; } (0..$lmax-1); + $cnt++; + } + # Last, empty line + push @revIdx,int(@revIdx); + push @srcHtml, $srcHtml[0]; + push @paramHtml, $paramHtml[0]; + push @exampleHtml, $exampleHtml[0]; my @lineStyles; if(SVG_openFile($FW_cssdir, @@ -338,33 +375,46 @@ SVG_PEdit($$$$) close(FH); } - my $r = 0; + $ret .= "Diagramm label, Source"; + $ret .= "$desc"; + $ret .=" Y-Axis,Plot-Type,Style,Width"; + + + my ($r, $example, @output) = (0, ""); for($r=0; $r < $max; $r++) { - $ret .= ""; - $ret .= SVG_txt("title_${r}", "", !$conf{lTitle}[$r]&&$r<($max-1) ? - "notitle" : $conf{lTitle}[$r], 12); - $ret .= ""; - $ret .= $htmlArr->[$r] if($htmlArr && @{$htmlArr} > $r); - $ret .= ""; - my $v = $conf{lAxis}[$r]; - $ret .= SVG_sel("axes_${r}", "left,right", + my $idx = $revIdx[$r]; + $example .= "
    $exampleHtml[$r]
    "; + my $o = ""; + $o .= SVG_txt("title_$idx", "", !$conf{lTitle}[$idx]&&$idx<($max-1) ? + "notitle" : $conf{lTitle}[$idx], 12); + my $sh = $srcHtml[$r]; $sh =~ s/src_\d+/src_$idx/g; + $o .= $sh; + $o .= ""; + my $ph = $paramHtml[$r]; $ph =~ s/par_\d+_/par_${idx}_/g; + $o .= $ph; + $o .= ""; + my $v = $conf{lAxis}[$idx]; + $o .= SVG_sel("axes_${idx}", "left,right", ($v && $v eq "x1y1") ? "left" : "right"); - $ret .= SVG_sel("type_${r}", "lines,points,steps,fsteps,histeps,bars", - $conf{lType}[$r]); - my $ls = $conf{lStyle}[$r]; + $o .= SVG_sel("type_${idx}", "lines,points,steps,fsteps,histeps,bars", + $conf{lType}[$idx]); + my $ls = $conf{lStyle}[$idx]; if($ls) { $ls =~ s/class=//g; $ls =~ s/"//g; } - $ret .= SVG_sel("style_${r}", join(",", @lineStyles), $ls); - my $lw = $conf{lWidth}[$r]; + $o .= SVG_sel("style_$idx", join(",", @lineStyles), $ls); + my $lw = $conf{lWidth}[$idx]; if($lw) { $lw =~ s/.*stroke-width://g; $lw =~ s/"//g; } - $ret .= SVG_sel("width_${r}", "0.2,0.5,1,1.5,2,3,4", ($lw ? $lw : 1)); - $ret .= ""; + $o .= SVG_sel("width_$idx", "0.2,0.5,1,1.5,2,3,4", ($lw ? $lw : 1)); + $o .= ""; + $output[$idx] = $o; } + $ret .= join("", @output); $ret .= ""; $ret .= "Example lines for input:
    $example"; @@ -374,6 +424,32 @@ SVG_PEdit($$$$) ""; $ret .= ""; + + my $sl = "$FW_ME/SVG_WriteGplot?detail=$d&showFileLogData=1"; + if(defined($FW_pos{zoom}) && defined($FW_pos{off})) { + $sl .= "&pos=zoom=$FW_pos{zoom};off=$FW_pos{off}"; + } + + $ret .= <<'EOF'; + +EOF + return $ret; } ################## @@ -439,6 +515,7 @@ SVG_zoomLink($$$) } +# Debugging: show the data received from GET sub SVG_showData() { @@ -446,17 +523,15 @@ SVG_showData() my $hash = $defs{$wl}; my ($d, $gplotfile, $file) = split(":", $hash->{DEF}); $gplotfile = "$FW_gplotdir/$gplotfile.gplot"; - my ($err, $cfg, $plot, $flog) = SVG_readgplotfile($wl, $gplotfile); + my $pm = AttrVal($d,"plotmode",$FW_plotmode); + my ($err, $cfg, $plot, $srcDesc) = SVG_readgplotfile($wl, $gplotfile, $pm); if($err) { $FW_RET=$err; return 1; } SVG_calcOffsets($d, $wl); - my ($f,$t)=($SVG_devs{$d}{from}, $SVG_devs{$d}{to}); - my $cmd = "get $d $file - $f $t " . join(" ", @{$flog}); - my $ret = FW_fC($cmd, 1); - $ret =~ s/\n/
    /gs; - $FW_RET = "$cmd

    $ret"; + $FW_RET = SVG_getData($d, $SVG_devs{$d}{from}, $SVG_devs{$d}{to}, $srcDesc,1); + $FW_RET =~ s/\n/
    /gs; return 1; } @@ -480,7 +555,8 @@ SVG_WriteGplot($) } return 0 if(!$maxLines); - my $fName = $FW_webArgs{gplotName}; + my $wlName = $FW_webArgs{detail}; + my $fName = "$FW_gplotdir/$defs{$wlName}{GPLOTFILE}.gplot"; return if(!$fName); my @rows; @@ -501,7 +577,6 @@ SVG_WriteGplot($) push @rows, "set y2range $FW_webArgs{y2range}" if($FW_webArgs{y2range}); push @rows, ""; - my $ld = $FW_webArgs{logdevicetype}; my @plot; for(my $i=0; $i <= $maxLines; $i++) { next if(!$FW_webArgs{"title_$i"}); @@ -512,7 +587,8 @@ SVG_WriteGplot($) join(":", map { $v[$_] =~ s/:/\\x3a/g if($_<$#v); $v[$_] } 0..$#v) : $v[0]; - push @rows, "#$ld $r"; + my $src = $FW_webArgs{"src_$i"}; + push @rows, "#$src $r"; push @plot, "\"\" using 1:2 axes ". ($FW_webArgs{"axes_$i"} eq "right" ? "x1y2" : "x1y1"). ($FW_webArgs{"title_$i"} eq "notitle" ? " notitle" : @@ -536,31 +612,51 @@ SVG_WriteGplot($) return 0; } +####################################################### +# srcDesc: +# - {all} : space separated plot arguments, in the file order, without devname +# - {order}: unique name of the devs (FileLog,etc) in the .gplot order +# - {src}{X}: hash (X is an order element), consisting of +# {arg}: plot arguments for one dev, space separated +# {idx}: number of lines requested from the same source +# {num}: number or this src in the order array +# - {rev}{orderIdx}{localIdx} = N: reverse lookup of the plot argument index, +# using {src}{X}{num} as orderIdx and {src}{X}{idx} as localIdx sub -SVG_readgplotfile($$) +SVG_readgplotfile($$$) { - my ($wl, $gplot_pgm) = @_; + my ($wl, $gplot_pgm, $plotmode) = @_; ############################ # Read in the template gnuplot file. Digest the #FileLog lines. Replace # the plot directive with our own, as we offer a file for each line - my (@filelog, @data, $plot); + my (%srcDesc, @data, $plot); + my $ld = $defs{$wl}{LOGDEVICE} + if($defs{$wl} && $defs{$wl}{LOGDEVICE}); my $ldType = $defs{$defs{$wl}{LOGDEVICE}}{TYPE} - if($defs{$wl} && $defs{$wl}{LOGDEVICE} && $defs{$defs{$wl}{LOGDEVICE}}); - $ldType = $defs{$wl}{TYPE} - if(!$ldType && $defs{$wl}); + if($ld && $defs{$ld}); + if(!$ldType && $defs{$wl}) { + $ldType = $defs{$wl}{TYPE}; + $ld = $wl; + } my ($err, @svgplotfile) = FileRead($gplot_pgm); return ("$err", undef) if($err); + my ($plotfnCnt, $srcNum) = (0,0); + my @empty; + $srcDesc{all} = ""; + $srcDesc{order} = \@empty; foreach my $l (@svgplotfile) { $l = "$l\n" unless $l =~ m/\n$/; - my $plotfn = undef; - if($l =~ m/^#$ldType (.*)$/) { - $plotfn = $1; - Log 3, "$wl: space is not allowed in $ldType definition: $plotfn" - if($plotfn =~ m/\s/); + my ($src, $plotfn) = (undef, undef); + if($l =~ m/^#([^ ]*) (.*)$/) { + if($1 eq $ldType) { + $src = $ld; $plotfn = $2; + } elsif($defs{$1}) { + $src = $1; $plotfn = $2; + } } elsif($l =~ "^plot" || $plot) { $plot .= $l; } else { @@ -568,6 +664,8 @@ SVG_readgplotfile($$) } if($plotfn) { + Log 3, "$wl: space is not allowed in $ldType definition: $plotfn" + if($plotfn =~ m/\s/); my $specval = AttrVal($wl, "plotfunction", undef); if ($specval) { my @spec = split(" ",$specval); @@ -577,11 +675,22 @@ SVG_readgplotfile($$) $spec_count++; } } - push(@filelog, $plotfn); + + my $p = $srcDesc{src}{$src}; + if(!$p) { + $p = { arg => $plotfn, idx=>0, num=>$srcNum++ }; + $srcDesc{src}{$src} = $p; + push(@{$srcDesc{order}}, $src); + } else { + $p->{arg} .= " $plotfn"; + $p->{idx}++; + } + $srcDesc{rev}{$p->{num}}{$p->{idx}} = $plotfnCnt++; + $srcDesc{all} .= " $plotfn"; } } - return (undef, \@data, $plot, \@filelog); + return (undef, \@data, $plot, \%srcDesc); } sub @@ -592,9 +701,6 @@ SVG_substcfg($$$$$$) # interpret title and label as a perl command and make # to all internal values e.g. $value. - my $oll = $attr{global}{verbose}; - $attr{global}{verbose} = 0; # Else the filenames will be Log'ged - my $ldt = $defs{$defs{$wl}{LOGDEVICE}}{TYPE} if($defs{$wl} && $defs{$wl}{LOGDEVICE}); $ldt = "" if(!defined($ldt)); @@ -605,17 +711,17 @@ SVG_substcfg($$$$$$) my $fileesc = $file; $fileesc =~ s/\\/\\\\/g; # For Windows, by MarkusRR my $title = AttrVal($wl, "title", "\"$fileesc\""); + my $allowed = AttrVal($FW_wname,"allowedCommands",undef); - $title = AnalyzeCommand(undef, "{ $title }"); + $title = AnalyzeCommand(undef, "{ $title }", $allowed); my $label = AttrVal($wl, "label", undef); my @g_label; if ($label) { @g_label = split("::",$label); foreach (@g_label) { - $_ = AnalyzeCommand(undef, "{ $_ }"); + $_ = AnalyzeCommand(undef, "{ $_ }", $allowed); } } - $attr{global}{verbose} = $oll; my $gplot_script = join("", @{$cfg}); $gplot_script .= $plot if(!$splitret); @@ -623,7 +729,7 @@ SVG_substcfg($$$$$$) $gplot_script =~ s//$tmpfile/g; $gplot_script =~ s//$file/g; - my $ps = AttrVal($wl,"plotsize",$FW_plotsize); + my $ps = SVG_getplotsize($wl); $gplot_script =~ s//$ps/g; $gplot_script =~ s//$title/g; @@ -816,10 +922,9 @@ SVG_doShowLog($$$$;$$) { my ($wl, $d, $type, $file, $styleW, $styleH) = @_; my $pm = AttrVal($wl,"plotmode",$FW_plotmode); - my $gplot_pgm = "$FW_gplotdir/$type.gplot"; - my ($err, $cfg, $plot, $flog) = SVG_readgplotfile($wl, $gplot_pgm); + my ($err, $cfg, $plot, $srcDesc) = SVG_readgplotfile($wl, $gplot_pgm, $pm); if($err || !$defs{$d}) { my $msg = ($defs{$d} ? "Cannot read $gplot_pgm" : "No Logdevice $d"); Log3 $FW_wname, 1, $msg; @@ -851,8 +956,8 @@ SVG_doShowLog($$$$;$$) # Read the data from the filelog my $oll = $attr{global}{verbose}; $attr{global}{verbose} = 0; # Else the filenames will be Log'ged - my @path = split(" ", FW_fC("get $d $file $tmpfile $f $t " . - join(" ", @{$flog}))); + my @path = split(" ", + FW_fC("get $d $file $tmpfile $f $t $srcDesc->{all}")); $attr{global}{verbose} = $oll; # replace the path with the temporary filenames of the filelog output @@ -878,8 +983,8 @@ SVG_doShowLog($$$$;$$) my ($f,$t)=($SVG_devs{$d}{from}, $SVG_devs{$d}{to}); my $oll = $attr{global}{verbose}; $attr{global}{verbose} = 0; # Else the filenames will be Log'ged - my @path = split(" ", FW_fC("get $d $file $tmpfile $f $t " . - join(" ", @{$flog}))); + my @path = split(" ", + FW_fC("get $d $file $tmpfile $f $t $srcDesc->{all}")); $attr{global}{verbose} = $oll; # replace the path with the temporary filenames of the filelog output @@ -915,8 +1020,7 @@ SVG_doShowLog($$$$;$$) $f = 0 if(!$f); # From the beginning of time... $t = 9 if(!$t); # till the end - Log3 $FW_wname, 5, - "plotcommand: get $d $file INT $f $t " . join(" ", @{$flog}); + Log3 $FW_wname, 5, "plotcommand: get $d $file INT $f $t ".$srcDesc->{all}; $FW_RETTYPE = "image/svg+xml"; @@ -929,10 +1033,10 @@ SVG_doShowLog($$$$;$$) close(CFH); } else { - FW_fC("get $d $file INT $f $t " . join(" ", @{$flog}), 1); + my $da = SVG_getData($wl, $f, $t, $srcDesc, 0); # substcfg needs it(!) ($cfg, $plot) = SVG_substcfg(1, $wl, $cfg, $plot, $file, ""); - my $ret = SVG_render($wl, $f, $t, $cfg, - $internal_data, $plot, $FW_wname, $FW_cssdir, $flog, + my $ret = SVG_render($wl, $f, $t, $cfg, $da, + $plot, $FW_wname, $FW_cssdir, $srcDesc, $styleW, $styleH); $internal_data = ""; FW_pO $ret; @@ -950,6 +1054,58 @@ SVG_doShowLog($$$$;$$) } +sub +SVG_getData($$$$$) +{ + my ($d, $f,$t,$srcDesc,$showData) = @_; + my (@da, $ret, @vals); + my @keys = ("min","max","avg","cnt","currval","mindate","maxdate","lastraw"); + + foreach my $src (@{$srcDesc->{order}}) { + my $s = $srcDesc->{src}{$src}; + my $fname = ($src eq $defs{$d}{LOGDEVICE} ? $defs{$d}{LOGFILE} : "CURRENT"); + my $cmd = "get $src $fname INT $f $t ".$s->{arg}; + FW_fC($cmd); + if($showData) { + $ret .= "\n$cmd\n\n"; + $ret .= $$internal_data; + } else { + push(@da, $internal_data); + + for(my $i = 0; $i<=$s->{idx}; $i++) { + my %h; + foreach my $k (@keys) { + $h{$k} = $data{$k.($i+1)}; + } + push @vals, \%h; + } + + } + } + + # Reorder the $data{maxX} stuff + my ($min, $max) = (999999, -999999); + my $no = int(keys %{$srcDesc->{rev}}); + for(my $oi = 0; $oi < $no; $oi++) { + my $nl = int(keys %{$srcDesc->{rev}{$oi}}); + for(my $li = 0; $li < $nl; $li++) { + my $r = $srcDesc->{rev}{$oi}{$li}+1; + my $val = shift @vals; + foreach my $k (@keys) { + $min = $val->{$k} + if($k eq "min" && defined($val->{$k}) && $val->{$k} < $min); + $max = $val->{$k} + if($k eq "max" && defined($val->{$k}) && $val->{$k} > $max); + $data{"$k$r"} = $val->{$k}; + } + } + } + $data{maxAll} = $max; + $data{minAll} = $min; + + return $ret if($showData); + return \@da; +} ###################### # Convert the configuration to a "readable" form -> array to hash @@ -1051,17 +1207,16 @@ SVG_render($$$$$$$$$;$$) my $from = shift; # e.g. 2008-01-01 my $to = shift; # e.g. 2009-01-01 my $confp = shift; # lines from the .gplot file, w/o FileLog and plot - my $dp = shift; # pointer to data (one string) + my $da = shift; # data pointer array my $plot = shift; # Plot lines from the .gplot file my $parent_name = shift; # e.g. FHEMWEB instance name my $parent_dir = shift; # FW_dir - my $flog = shift; # #FileLog lines, as array pointer + my $srcDesc = shift; # #FileLog lines, as array pointer my $styleW = shift; my $styleH = shift; $SVG_RET=""; my $SVG_ss = AttrVal($parent_name, "smallscreen", 0); - return $SVG_RET if(!defined($dp) || $dp eq ""); my $nr_axis = AttrVal($parent_name,"nrAxis","1,1"); my ($nr_left_axis,$nr_right_axis,$use_left_axis,$use_right_axis) = @@ -1084,23 +1239,24 @@ SVG_render($$$$$$$$$;$$) my $w = $ow-$nr_left_axis*$axis_width-$nr_right_axis*$axis_width; my $h = $oh-2*$y; # Rect size - # Keep only the Filter part of the #FileLog - $flog = join(" ", map { my @a=split(":",$_); - $a[1]=~s/\.[^\.]*$//; $a[1]; } @{$flog}); - $flog = AttrVal($parent_name, "longpollSVG", 0) ? "flog=\" $flog \"" : ""; + my $filter = $srcDesc->{all}; + $filter =~ s/[^: ]*:([^: ]):[^ ]*/$1/g; + $filter = AttrVal($parent_name, "longpollSVG", 0) ? "flog=\" $filter \"" : ""; + + my %dataIdx; # Build a reverse Index for the dataSource + ###################### - # Html Header + # SVG Header + my $svghdr = 'version="1.1" xmlns="http://www.w3.org/2000/svg" '. + 'xmlns:xlink="http://www.w3.org/1999/xlink" '. + 'id="SVGPLOT_'.(++$SVG_id).'"'.$filter; if(!$styleW) { SVG_pO ''; SVG_pO ''; - SVG_pO ''; + SVG_pO ""; } else { - SVG_pO ''; + SVG_pO ""; } my $prf = AttrVal($parent_name, "stylesheetPrefix", ""); @@ -1134,17 +1290,6 @@ SVG_render($$$$$$$$$;$$) SVG_pO "$title"; - ###################### - # Copy and Paste labels, hidden by default - SVG_pO " "; - SVG_pO " "; - ###################### # Left label = ylabel and right label = y2label if(!$SVG_ss) { @@ -1198,63 +1343,69 @@ SVG_render($$$$$$$$$;$$) my ($dxp, $dyp) = (\(), \()); my ($d, $v, $ld, $lv) = ("","","",""); - - my ($dpl,$dpoff,$l) = (length($$dp), 0, ""); - while($dpoff < $dpl) { # using split instead is memory hog - my $ndpoff = index($$dp, "\n", $dpoff); - if($ndpoff == -1) { - $l = substr($$dp, $dpoff); - } else { - $l = substr($$dp, $dpoff, $ndpoff-$dpoff); - } - $dpoff = $ndpoff+1; - if($l =~ m/^#/) { - my $a = $conf{lAxis}[$idx]; - if(defined($a)) { - $hmin{$a} = $min if(!defined($hmin{$a}) || $hmin{$a} > $min); - $hmax{$a} = $max if(!defined($hmax{$a}) || $hmax{$a} < $max); + for(my $dIdx=0; $dIdx<@{$da}; $dIdx++) { + my $lIdx = 0; + $idx = $srcDesc->{rev}{$dIdx}{$lIdx}; + my $dp = $da->[$dIdx]; + my ($dpl,$dpoff,$l) = (length($$dp), 0, ""); + while($dpoff < $dpl) { # using split instead is memory hog + my $ndpoff = index($$dp, "\n", $dpoff); + if($ndpoff == -1) { + $l = substr($$dp, $dpoff); + } else { + $l = substr($$dp, $dpoff, $ndpoff-$dpoff); } - ($min, $max) = (99999999, -99999999); - $hdx[$idx] = $dxp; $hdy[$idx] = $dyp; - ($dxp, $dyp) = (\(), \()); - $idx++; - - } elsif( $l =~ /^;/ ) { #allow ;special lines - if( $l =~ m/^;p (\S+)\s(\S+)/ ) {# point - my $xmul = $w/($xmax-$xmin); - my $x1; - if( $conf{xrange} ) { - $x1 = int(($1-$xmin)*$xmul); - } else { - $x1 = $x1; + $dpoff = $ndpoff+1; + if($l =~ m/^#/) { + my $a = $conf{lAxis}[$idx]; + if(defined($a)) { + $hmin{$a} = $min if(!defined($hmin{$a}) || $hmin{$a} > $min); + $hmax{$a} = $max if(!defined($hmax{$a}) || $hmax{$a} < $max); } - my $y1 = $2; + ($min, $max) = (99999999, -99999999); + $hdx[$idx] = $dxp; $hdy[$idx] = $dyp; + ($dxp, $dyp) = (\(), \()); + $lIdx++; + $idx = $srcDesc->{rev}{$dIdx}{$lIdx}; + last if(!$idx); - push @{$dxp}, $x1; - push @{$dyp}, $y1; + } elsif( $l =~ /^;/ ) { #allow ;special lines + if( $l =~ m/^;p (\S+)\s(\S+)/ ) {# point + my $xmul = $w/($xmax-$xmin); + my $x1; + if( $conf{xrange} ) { + $x1 = int(($1-$xmin)*$xmul); + } else { + $x1 = $x1; + } + my $y1 = $2; - } elsif( $conf{lType}[$idx] eq "lines" ) { - push @{$dxp}, undef; - push @{$dyp}, $l; + push @{$dxp}, $x1; + push @{$dyp}, $y1; + } elsif( $conf{lType}[$idx] eq "lines" ) { + push @{$dxp}, undef; + push @{$dyp}, $l; + + } + + } else { + ($d, $v) = split(" ", $l); + $d = ($tmul ? int((SVG_time_to_sec($d)-$fromsec)*$tmul) : $d); + if($ld ne $d || $lv ne $v) { # Saves a lot on year zoomlevel + $ld = $d; $lv = $v; + push @{$dxp}, $d; + push @{$dyp}, $v; + $min = $v if($min > $v); + $max = $v if($max < $v); + } } - - } else { - ($d, $v) = split(" ", $l); - $d = ($tmul ? int((SVG_time_to_sec($d)-$fromsec)*$tmul) : $d); - if($ld ne $d || $lv ne $v) { # Saves a lot on year zoomlevel - $ld = $d; $lv = $v; - push @{$dxp}, $d; - push @{$dyp}, $v; - $min = $v if($min > $v); - $max = $v if($max < $v); - } + last if($ndpoff == -1); } - last if($ndpoff == -1); } $dxp = $hdx[0]; - if(($dxp && int(@{$dxp}) < 2 && !$tosec) || # not enough data and no range... + if(($dxp && int(@{$dxp}) < 2 && !$tosec) || # not enough data and no range... (!$tmul && !$dxp)) { SVG_pO ""; return $SVG_RET; @@ -1392,6 +1543,7 @@ SVG_render($$$$$$$$$;$$) my (%hstep,%htics,%axdrawn); + my $allowed = AttrVal($FW_wname,"allowedCommands",undef); #-- yrange handling for axes x1y1..x1y8 for my $idx (0..7) { my $a = "x1y".($idx+1); @@ -1399,10 +1551,15 @@ SVG_render($$$$$$$$$;$$) my $yra="y".($idx+1)."range"; $yra="yrange" if ($yra eq "y1range"); #-- yrange is specified in plotfile - if($conf{$yra} && $conf{$yra} =~ /\[(.*):(.*)\]/) { - $hmin{$a} = $1 if($1 ne ""); - $hmax{$a} = $2 if($2 ne ""); + if($conf{$yra}) { + $conf{$yra} = AnalyzeCommand(undef, $1, $allowed) + if($conf{$yra} =~ /^({.*})$/); + if($conf{$yra} =~ /\[(.*):(.*)\]/) { + $hmin{$a} = $1 if($1 ne ""); + $hmax{$a} = $2 if($2 ne ""); + } } + #-- tics handling my $yt="y".($idx+1)."tics"; $yt="ytics" if ($yt eq"y1tics"); @@ -1732,13 +1889,15 @@ SVG_render($$$$$$$$$;$$) } my $style = $conf{lStyle}[$i]; $style =~ s/class="/class="legend /; - SVG_pO "$t"; $txtoff2 += $th; } + my $fnName = SVG_isEmbed($FW_wname) ? "parent.window.svg_init" : "svg_init"; + SVG_pO ""; SVG_pO ""; return $SVG_RET; } @@ -1974,6 +2133,9 @@ plotAsPng(@) set title <L1>
    + The value minAll and maxAll (representing the minimum/maximum over all + values) is also available from the data hash. + @@ -2025,6 +2187,9 @@ plotAsPng(@) regexp switch.on, and "0" for the regexp switch.off.
    Write .gplot file again
    +
  • If the range is of the form {...}, then it will be evaluated with perl. + The result is a string, and must have the form [min:max] +
  • The visibility of the ploteditor can be configured with the FHEMWEB attribute ploteditor. @@ -2142,7 +2307,7 @@ plotAsPng(@) können ebenfalls die Werte der individuellen Kurve für min, max, mindate, maxdate, avg, cnt, sum, currval (letzter Wert) und currdate (letztes Datum) durch Zugriff der entsprechenden Werte über das - DataHash verwendet werden. Siehe untenstehendes Beispiel:
    + data Hash verwendet werden. Siehe untenstehendes Beispiel:
    • Beschriftunng der rechten und linken y-Achse:
        @@ -2164,6 +2329,8 @@ plotAsPng(@)
    + Die Werte minAll und maxAll (die das Minimum/Maximum aller Werte + repräsentieren) sind ebenfals im data hash vorhanden. @@ -2229,6 +2396,9 @@ plotAsPng(@) .gplot-Datei erneut speichern
    +
  • Falls Range der Form {...} entspricht, dann wird sie als Perl - + Expression ausgewertet. Das Ergebnis muss in der Form [min:max] sein. +
  • Die sichtbarkeit des Plot-Editors kann mit dem FHEMWEB Attribut ploteditor konfiguriert werden. diff --git a/fhem/FHEM/Color.pm b/fhem/FHEM/Color.pm index 23b28aaaf..ab748a1db 100644 --- a/fhem/FHEM/Color.pm +++ b/fhem/FHEM/Color.pm @@ -17,81 +17,9 @@ Color_Initialize() sub FHEM_colorpickerInit() { - $data{webCmdFn}{colorpicker} = "FHEM_colorpickerFn"; $data{FWEXT}{colorpicker}{SCRIPT} = "/jscolor/jscolor.js"; } -sub -FHEM_colorpickerFn($$$$$) -{ - #return undef; - my ($FW_wname, $d, $FW_room, $cmd, $values) = @_; - - my @args = split("[ \t]+", $cmd); - - my @value = split( ',', $values ); - return undef if($values !~ m/^colorpicker,([^,]*)/); - - my $mode = $1; - $mode = "RGB" if( !defined($mode) ); - - if( $mode eq "CT" ) { - if( !$args[1] && $data{webCmdFn}{slider} ) { - no strict "refs"; - my $values = "slider,". join( ',', @value[2..4] ); - my $htmlTxt = &{$data{webCmdFn}{slider}}($FW_wname, $d, $FW_room, $cmd, $values); - use strict "refs"; - - return $htmlTxt; - } - - return undef if( !$args[1] ); - } - - my $trigger = $cmd; #default trigger is the event from the reading with the same name as the command - my $cv = ReadingsVal($d,$cmd,""); #get default value from this reading - if( !$cv ) { #if this reading does not exist -> - $trigger = "RGB"; # trigger name will be RGB - $cv = CommandGet("","$d $cmd"); # get default value from get command - } - - $cmd = "" if($cmd eq "state"); - my $srf = $FW_room ? "&room=$FW_room" : ""; - if( $args[1] ) { - my $c = "cmd=set $d $cmd$srf"; - - if( $mode eq "CT" ) { - my $ct = $args[1]; - my ($r, $g, $b) = Color::ct2rgb( $ct ); - $args[1] = Color::rgb2hex( $r, $g, $b ); - } - - return ''. - "
    '. - '' if( AttrVal($FW_wname, "longpoll", 1)); - - return ''. - "". - '
    '. - '
    '. - ''; - } elsif(AttrVal($d,"realtimePicker",0)) { - my $c = "$FW_ME?XHR=1&cmd=set $d $cmd %$srf"; - my $ci = $c; - $ci = "$FW_ME?XHR=1&cmd=set $d $cmd % : transitiontime 0 : noUpdate$srf" if($defs{$d}->{TYPE} eq "HUEDevice"); - return ''. - "". - ''; - } else { - my $c = "$FW_ME?XHR=1&cmd=set $d $cmd %$srf"; - return ''. - "". - ''; - } -} - my %dim_values = ( 0 => "dim06%", 1 => "dim12%", diff --git a/fhem/demolog/LightScenes.dd.save b/fhem/demolog/LightScenes.dd.save index 2ac80d881..f97ef666c 100644 --- a/fhem/demolog/LightScenes.dd.save +++ b/fhem/demolog/LightScenes.dd.save @@ -1,30 +1,30 @@ -#Mon Aug 26 16:36:31 2013 +#Sun Jan 4 16:24:11 2015 { - 'lcCinema' => { - 'Break' => { - 'CeilingLight' => 'dim37%', - 'ReadingLight' => 'dim37%' - }, - 'Cinema' => { - 'Screen' => 'down', - 'Projector' => 'on', - 'CeilingLight' => 'off', - 'ReadingLight' => 'off', - 'TV' => 'off' - }, - 'WatchTV' => { - 'Screen' => 'up', - 'Projector' => 'off', - 'CeilingLight' => 'off', - 'ReadingLight' => 'dim12%', - 'TV' => 'on' - }, - 'AllOff' => { - 'Screen' => 'up', - 'Projector' => 'off', - 'CeilingLight' => 'off', - 'ReadingLight' => 'off', - 'TV' => 'off' - } - } - } + 'lcCinema' => { + 'Break' => { + 'CeilingLight' => 'dim37%', + 'ReadingLight' => 'dim37%' + }, + 'Cinema' => { + 'CeilingLight' => 'off', + 'Projector' => 'on', + 'Screen' => 'down', + 'ReadingLight' => 'off', + 'TV' => 'off' + }, + 'WatchTV' => { + 'CeilingLight' => 'off', + 'Projector' => 'off', + 'Screen' => 'up', + 'ReadingLight' => 'dim12%', + 'TV' => 'on' + }, + 'AllOff' => { + 'CeilingLight' => 'off', + 'Projector' => 'off', + 'Screen' => 'up', + 'ReadingLight' => 'off', + 'TV' => 'off' + } + } +} diff --git a/fhem/demolog/fhem.save b/fhem/demolog/fhem.save index 25febd756..d17920bd3 100644 --- a/fhem/demolog/fhem.save +++ b/fhem/demolog/fhem.save @@ -1,13 +1,17 @@ -#Mon Feb 17 20:54:16 2014 +#Sun Jan 4 16:24:11 2015 setstate Alarm on -setstate Alarm 2013-08-23 07:42:48 state on +setstate Alarm 2015-01-04 16:22:21 state on setstate AllLights on -setstate AllLights 2013-08-25 14:23:20 LastDevice Office -setstate AllLights 2013-08-25 14:23:20 LastDevice_Abs Office -setstate AllLights 2013-08-25 14:23:20 state on +setstate AllLights 2015-01-04 16:22:04 LastDevice Alarm +setstate AllLights 2015-01-04 16:22:04 LastDevice_Abs Alarm +setstate AllLights 2015-01-04 16:22:04 state undefined setstate AllResidentsAway active +setstate CT off +setstate CT 2015-01-03 01:10:50 ct 3703 +setstate CT 2015-01-04 13:30:52 lastCmd off +setstate CT 2015-01-04 16:22:04 state off setstate CeilingLight dim0% -setstate CeilingLight 2013-08-26 18:01:06 state off +setstate CeilingLight 2015-01-04 16:22:40 state off setstate Cellar T: 21.1 H: 54.6 setstate Cellar 2013-08-13 08:00:48 DEVFAMILY WS300 setstate Cellar 2013-08-13 08:00:48 DEVTYPE S300TH @@ -26,7 +30,7 @@ setstate Garden 2013-08-13 15:03:17 israining no setstate Garden 2013-08-13 15:03:17 rain 81.9 setstate Garden 2013-08-13 15:03:17 rain_raw 321 setstate Garden 2013-08-13 15:03:17 rain_raw_adj 321 -setstate Garden 2014-02-17 20:54:12 state defined +setstate Garden 2015-01-04 16:19:04 state defined setstate Garden 2013-08-13 15:03:17 temperature 18.3 setstate Garden 2013-08-13 15:03:17 tsecs 1376805797 setstate Garden 2013-08-13 15:03:17 unknown1 a @@ -34,36 +38,51 @@ setstate Garden 2013-08-13 15:03:17 unknown2 7 setstate Garden 2013-08-13 15:03:17 unknown3 1 setstate Garden 2013-08-13 15:03:17 wind 0.5 setstate Livingroom dim100% -setstate Livingroom 2013-08-23 07:57:21 state on +setstate Livingroom 2015-01-04 16:22:21 state on setstate Log.Cellar active setstate Log.Dewpoint active setstate Log.Garden active setstate Logfile active setstate Office on -setstate Office 2013-08-25 14:23:20 state on +setstate Office 2015-01-04 16:22:21 state on setstate Outdoor on -setstate Outdoor 2013-08-25 14:23:20 state on +setstate Outdoor 2015-01-04 16:22:21 state on setstate Projector off -setstate Projector 2013-08-26 18:01:06 state off -setstate ReadingLight dim12% -setstate ReadingLight 2013-08-26 18:01:06 state dim12% +setstate Projector 2015-01-04 16:22:40 state off +setstate RGB off +setstate RGB 2015-01-04 12:55:47 hue 220 +setstate RGB 2015-01-04 13:30:54 lastCmd off +setstate RGB 2015-01-04 12:55:47 rgb 0054FF +setstate RGB 2015-01-04 16:22:04 state off +setstate ReadingLight dim0% +setstate ReadingLight 2015-01-04 16:22:40 state off setstate ResidentsComeHome active setstate SVG_01_Garden initialized setstate SVG_02_Cellar initialized setstate SVG_03_Dewpoint initialized setstate Screen up -setstate Screen 2013-08-26 18:01:06 state up -setstate TV on -setstate TV 2013-08-26 18:01:06 state on +setstate Screen 2015-01-04 16:22:40 state up +setstate TV off +setstate TV 2015-01-04 16:22:40 state off +setstate anyViews 2015-01-04 16:24:11 lockstate 0 +setstate anyViews 2015-01-04 16:19:04 state Initialized setstate anyViews_weblink initialized setstate autocreate active +setstate colorInit 2015-01-04 16:19:04 setstate dew_all active +setstate eventTypes active setstate global -setstate outdoorNotifier active +setstate initialUsbCheck 2015-01-04 16:19:04 +setstate lcCinema AllOff +setstate lcCinema 2015-01-04 16:22:40 state AllOff +setstate outdoorNotifier 2015-01-04 16:22:21 setstate rg_Guest1 none -setstate rg_Guest1 2014-02-16 13:59:50 durTimerAbsence 0 -setstate rg_Guest1 2014-02-16 13:34:06 durTimerPresence 0 -setstate rg_Guest1 2014-02-15 16:15:34 durTimerSleep 0 +setstate rg_Guest1 2015-01-04 16:19:19 durTimerAbsence 00:00:00 +setstate rg_Guest1 2015-01-04 16:19:19 durTimerAbsence_cr 0 +setstate rg_Guest1 2015-01-04 16:19:19 durTimerPresence 00:00:00 +setstate rg_Guest1 2015-01-04 16:19:19 durTimerPresence_cr 0 +setstate rg_Guest1 2015-01-04 16:19:19 durTimerSleep 00:00:00 +setstate rg_Guest1 2015-01-04 16:19:19 durTimerSleep_cr 0 setstate rg_Guest1 2014-02-16 14:09:20 lastArrival - setstate rg_Guest1 2014-02-16 14:09:29 lastAwake 0 setstate rg_Guest1 2014-02-16 14:09:20 lastDeparture 2014-02-16 14:09:20 @@ -80,9 +99,12 @@ setstate rg_Guest1 2014-02-16 14:09:20 presence absent setstate rg_Guest1 2014-02-16 14:09:20 state none setstate rg_Guest1 2014-02-15 16:16:27 wayhome 0 setstate rg_Guest2 none -setstate rg_Guest2 2014-02-15 16:15:39 durTimerAbsence 0 -setstate rg_Guest2 2014-02-16 13:34:05 durTimerPresence 0 -setstate rg_Guest2 2014-02-15 16:15:39 durTimerSleep 0 +setstate rg_Guest2 2015-01-04 16:19:19 durTimerAbsence 00:00:00 +setstate rg_Guest2 2015-01-04 16:19:19 durTimerAbsence_cr 0 +setstate rg_Guest2 2015-01-04 16:19:19 durTimerPresence 00:00:00 +setstate rg_Guest2 2015-01-04 16:19:19 durTimerPresence_cr 0 +setstate rg_Guest2 2015-01-04 16:19:19 durTimerSleep 00:00:00 +setstate rg_Guest2 2015-01-04 16:19:19 durTimerSleep_cr 0 setstate rg_Guest2 2014-02-16 14:01:22 lastArrival - setstate rg_Guest2 2014-02-16 14:09:29 lastAwake 0 setstate rg_Guest2 2014-02-16 14:00:46 lastDeparture 2014-02-16 14:00:45 @@ -99,8 +121,8 @@ setstate rg_Guest2 2014-02-16 14:00:46 presence absent setstate rg_Guest2 2014-02-16 14:01:22 state none setstate rg_Guest2 2014-02-15 16:16:29 wayhome 0 setstate rgr_Children home -setstate rgr_Children 2014-02-17 20:45:12 lastActivity home -setstate rgr_Children 2014-02-17 20:45:12 lastActivityBy Baby +setstate rgr_Children 2015-01-04 16:19:19 lastActivity gone +setstate rgr_Children 2015-01-04 16:19:19 lastActivityBy Son setstate rgr_Children 2014-02-16 14:31:05 lastArrival 2014-02-16 14:31:05 setstate rgr_Children 2014-02-17 20:45:12 lastAwake 2014-02-17 20:45:12 setstate rgr_Children 2014-02-16 14:26:55 lastDeparture 2014-02-16 14:26:55 @@ -110,10 +132,10 @@ setstate rgr_Children 2014-02-17 20:45:12 lastDurSleep 29:47:11 setstate rgr_Children 2014-02-16 14:58:01 lastSleep 2014-02-16 14:58:01 setstate rgr_Children 2014-02-17 20:45:12 lastState asleep setstate rgr_Children 2014-02-16 14:31:05 presence present -setstate rgr_Children 2014-02-16 14:58:01 residentsAbsent 2 +setstate rgr_Children 2015-01-04 16:19:19 residentsAbsent 0 setstate rgr_Children 2014-02-17 20:45:12 residentsAsleep 0 setstate rgr_Children 2014-02-16 14:06:48 residentsAwoken 0 -setstate rgr_Children 2014-02-16 14:58:00 residentsGone 0 +setstate rgr_Children 2015-01-04 16:19:19 residentsGone 2 setstate rgr_Children 2014-02-16 14:31:13 residentsGotosleep 0 setstate rgr_Children 2014-02-15 16:16:25 residentsGuests 0 setstate rgr_Children 2014-02-17 20:45:12 residentsHome 1 @@ -144,8 +166,8 @@ setstate rgr_Guests 2014-02-16 14:09:20 residentsTotalPresent 0 setstate rgr_Guests 2014-02-15 16:16:27 residentsTotalWayhome 0 setstate rgr_Guests 2014-02-16 14:09:20 state none setstate rgr_Parents home -setstate rgr_Parents 2014-02-17 20:45:19 lastActivity absent -setstate rgr_Parents 2014-02-17 20:45:19 lastActivityBy Father +setstate rgr_Parents 2015-01-04 16:19:19 lastActivity gone +setstate rgr_Parents 2015-01-04 16:19:19 lastActivityBy Father setstate rgr_Parents 2014-02-16 14:16:17 lastArrival 2014-02-16 14:16:17 setstate rgr_Parents 2014-02-15 16:39:35 lastAwake 2014-02-15 16:39:35 setstate rgr_Parents 2014-02-16 14:16:16 lastDeparture 2014-02-16 14:16:16 @@ -155,10 +177,10 @@ setstate rgr_Parents 2014-02-15 16:39:35 lastDurSleep 00:02:44 setstate rgr_Parents 2014-02-15 16:36:51 lastSleep 2014-02-15 16:36:51 setstate rgr_Parents 2014-02-16 14:16:17 lastState absent setstate rgr_Parents 2014-02-16 14:16:17 presence present -setstate rgr_Parents 2014-02-17 20:45:19 residentsAbsent 1 +setstate rgr_Parents 2015-01-04 16:19:19 residentsAbsent 0 setstate rgr_Parents 2014-02-16 13:31:53 residentsAsleep 0 setstate rgr_Parents 2014-02-16 13:31:53 residentsAwoken 0 -setstate rgr_Parents 2014-02-17 20:45:19 residentsGone 0 +setstate rgr_Parents 2015-01-04 16:19:19 residentsGone 1 setstate rgr_Parents 2014-02-15 16:36:51 residentsGotosleep 0 setstate rgr_Parents 2014-02-15 16:13:39 residentsGuests 0 setstate rgr_Parents 2014-02-16 14:26:52 residentsHome 1 @@ -168,8 +190,8 @@ setstate rgr_Parents 2014-02-16 14:26:52 residentsTotalPresent 1 setstate rgr_Parents 2014-02-15 16:13:39 residentsTotalWayhome 0 setstate rgr_Parents 2014-02-16 14:16:17 state home setstate rgr_Residents home -setstate rgr_Residents 2014-02-17 20:45:19 lastActivity absent -setstate rgr_Residents 2014-02-17 20:45:19 lastActivityBy Father +setstate rgr_Residents 2015-01-04 16:19:19 lastActivity gone +setstate rgr_Residents 2015-01-04 16:19:19 lastActivityBy Son setstate rgr_Residents 2014-02-16 14:16:17 lastArrival 2014-02-16 14:16:17 setstate rgr_Residents 2014-02-16 14:02:48 lastAwake 2014-02-16 14:02:48 setstate rgr_Residents 2014-02-16 14:16:16 lastDeparture 2014-02-16 14:16:16 @@ -179,10 +201,10 @@ setstate rgr_Residents 2014-02-16 14:02:48 lastDurSleep 00:00:00 setstate rgr_Residents 2014-02-16 14:02:48 lastSleep 2014-02-16 14:02:48 setstate rgr_Residents 2014-02-16 14:16:17 lastState absent setstate rgr_Residents 2014-02-16 14:16:17 presence present -setstate rgr_Residents 2014-02-17 20:45:19 residentsAbsent 3 +setstate rgr_Residents 2015-01-04 16:19:19 residentsAbsent 0 setstate rgr_Residents 2014-02-17 20:45:12 residentsAsleep 0 setstate rgr_Residents 2014-02-16 14:06:49 residentsAwoken 0 -setstate rgr_Residents 2014-02-17 20:45:19 residentsGone 0 +setstate rgr_Residents 2015-01-04 16:19:19 residentsGone 3 setstate rgr_Residents 2014-02-16 14:31:13 residentsGotosleep 0 setstate rgr_Residents 2014-02-16 14:09:20 residentsGuests 0 setstate rgr_Residents 2014-02-17 20:45:12 residentsHome 2 @@ -192,9 +214,12 @@ setstate rgr_Residents 2014-02-16 14:58:01 residentsTotalPresent 2 setstate rgr_Residents 2014-02-15 16:13:39 residentsTotalWayhome 0 setstate rgr_Residents 2014-02-16 14:16:17 state home setstate rr_Baby home -setstate rr_Baby 2014-02-16 14:30:21 durTimerAbsence 0 -setstate rr_Baby 2014-02-17 20:44:42 durTimerPresence 1814 -setstate rr_Baby 2014-02-17 20:45:12 durTimerSleep 0 +setstate rr_Baby 2015-01-04 16:19:19 durTimerAbsence 00:00:00 +setstate rr_Baby 2015-01-04 16:19:19 durTimerAbsence_cr 0 +setstate rr_Baby 2015-01-04 16:23:19 durTimerPresence 7729:52:58 +setstate rr_Baby 2015-01-04 16:23:19 durTimerPresence_cr 463793 +setstate rr_Baby 2015-01-04 16:19:19 durTimerSleep 00:00:00 +setstate rr_Baby 2015-01-04 16:19:19 durTimerSleep_cr 0 setstate rr_Baby 2014-02-16 14:30:21 lastArrival 2014-02-16 14:30:21 setstate rr_Baby 2014-02-17 20:45:12 lastAwake 2014-02-17 20:45:12 setstate rr_Baby 2014-02-16 14:32:38 lastDeparture 0 @@ -208,10 +233,13 @@ setstate rr_Baby 2014-02-17 20:45:12 mood calm setstate rr_Baby 2014-02-16 14:30:21 presence present setstate rr_Baby 2014-02-17 20:45:12 state home setstate rr_Baby 2014-02-16 14:30:21 wayhome 0 -setstate rr_Daughter absent -setstate rr_Daughter 2014-02-17 20:45:42 durTimerAbsence 1787 -setstate rr_Daughter 2014-02-16 14:26:54 durTimerPresence 0 -setstate rr_Daughter 2014-02-16 14:02:48 durTimerSleep 0 +setstate rr_Daughter gone +setstate rr_Daughter 2015-01-04 16:23:19 durTimerAbsence 7729:25:20 +setstate rr_Daughter 2015-01-04 16:23:19 durTimerAbsence_cr 463765 +setstate rr_Daughter 2015-01-04 16:19:19 durTimerPresence 00:00:00 +setstate rr_Daughter 2015-01-04 16:19:19 durTimerPresence_cr 0 +setstate rr_Daughter 2015-01-04 16:19:19 durTimerSleep 00:00:00 +setstate rr_Daughter 2015-01-04 16:19:19 durTimerSleep_cr 0 setstate rr_Daughter 2014-02-16 14:57:58 lastArrival 2014-02-16 14:57:58 setstate rr_Daughter 2014-02-16 14:02:48 lastAwake 2014-02-16 14:02:48 setstate rr_Daughter 2014-02-16 14:57:59 lastDeparture 2014-02-16 14:57:59 @@ -221,32 +249,38 @@ setstate rr_Daughter 2014-02-16 14:02:48 lastDurSleep 00:08:12 setstate rr_Daughter 2014-02-16 14:57:59 lastLocation home setstate rr_Daughter 2014-02-16 14:57:59 lastMood calm setstate rr_Daughter 2014-02-16 13:54:36 lastSleep 2014-02-16 13:54:36 -setstate rr_Daughter 2014-02-16 14:57:59 lastState home +setstate rr_Daughter 2015-01-04 16:19:19 lastState absent setstate rr_Daughter 2014-02-16 14:57:59 location underway setstate rr_Daughter 2014-02-16 14:57:59 mood - setstate rr_Daughter 2014-02-16 14:57:59 presence absent -setstate rr_Daughter 2014-02-16 14:57:59 state absent +setstate rr_Daughter 2015-01-04 16:19:19 state gone setstate rr_Daughter 2014-02-16 13:46:26 wayhome 0 -setstate rr_Father absent -setstate rr_Father 2014-02-17 20:45:19 durTimerAbsence 1818 -setstate rr_Father 2014-02-16 14:26:52 durTimerPresence 0 -setstate rr_Father 2014-02-16 13:46:02 durTimerSleep 0 +setstate rr_Father gone +setstate rr_Father 2015-01-04 16:23:19 durTimerAbsence 7729:56:27 +setstate rr_Father 2015-01-04 16:23:19 durTimerAbsence_cr 463796 +setstate rr_Father 2015-01-04 16:19:19 durTimerPresence 00:00:00 +setstate rr_Father 2015-01-04 16:19:19 durTimerPresence_cr 0 +setstate rr_Father 2015-01-04 16:19:19 durTimerSleep 00:00:00 +setstate rr_Father 2015-01-04 16:19:19 durTimerSleep_cr 0 setstate rr_Father 2014-02-16 14:16:17 lastArrival 2014-02-16 14:16:17 setstate rr_Father 2014-02-16 14:26:52 lastDeparture 2014-02-16 14:26:52 setstate rr_Father 2014-02-16 14:16:17 lastDurAbsence 00:00:01 setstate rr_Father 2014-02-16 14:26:52 lastDurPresence 00:10:35 setstate rr_Father 2014-02-16 14:26:52 lastLocation home setstate rr_Father 2014-02-16 14:26:52 lastMood calm -setstate rr_Father 2014-02-17 20:45:19 lastState gone +setstate rr_Father 2015-01-04 16:19:19 lastState absent setstate rr_Father 2014-02-16 14:26:52 location underway setstate rr_Father 2014-02-16 14:26:52 mood - setstate rr_Father 2014-02-16 14:26:52 presence absent -setstate rr_Father 2014-02-17 20:45:19 state absent +setstate rr_Father 2015-01-04 16:19:19 state gone setstate rr_Father 2014-02-16 13:46:02 wayhome 0 setstate rr_Mother home -setstate rr_Mother 2014-02-16 14:04:26 durTimerAbsence 0 -setstate rr_Mother 2014-02-17 20:45:42 durTimerPresence 1829 -setstate rr_Mother 2014-02-16 13:46:09 durTimerSleep 0 +setstate rr_Mother 2015-01-04 16:19:19 durTimerAbsence 00:00:00 +setstate rr_Mother 2015-01-04 16:19:19 durTimerAbsence_cr 0 +setstate rr_Mother 2015-01-04 16:23:19 durTimerPresence 7730:07:02 +setstate rr_Mother 2015-01-04 16:23:19 durTimerPresence_cr 463807 +setstate rr_Mother 2015-01-04 16:19:19 durTimerSleep 00:00:00 +setstate rr_Mother 2015-01-04 16:19:19 durTimerSleep_cr 0 setstate rr_Mother 2014-02-16 14:16:17 lastArrival 2014-02-16 14:16:17 setstate rr_Mother 2014-02-16 14:16:16 lastDeparture 2014-02-16 14:16:16 setstate rr_Mother 2014-02-16 14:16:17 lastDurAbsence 00:00:01 @@ -259,10 +293,13 @@ setstate rr_Mother 2014-02-16 14:16:17 mood calm setstate rr_Mother 2014-02-16 14:16:17 presence present setstate rr_Mother 2014-02-16 14:16:17 state home setstate rr_Mother 2014-02-16 13:46:09 wayhome 0 -setstate rr_Son absent -setstate rr_Son 2014-02-17 20:45:42 durTimerAbsence 1787 -setstate rr_Son 2014-02-16 14:26:55 durTimerPresence 0 -setstate rr_Son 2014-02-16 14:00:53 durTimerSleep 0 +setstate rr_Son gone +setstate rr_Son 2015-01-04 16:23:19 durTimerAbsence 7729:25:18 +setstate rr_Son 2015-01-04 16:23:19 durTimerAbsence_cr 463765 +setstate rr_Son 2015-01-04 16:19:19 durTimerPresence 00:00:00 +setstate rr_Son 2015-01-04 16:19:19 durTimerPresence_cr 0 +setstate rr_Son 2015-01-04 16:19:19 durTimerSleep 00:00:00 +setstate rr_Son 2015-01-04 16:19:19 durTimerSleep_cr 0 setstate rr_Son 2014-02-16 14:58:00 lastArrival 2014-02-16 14:58:00 setstate rr_Son 2014-02-16 14:06:44 lastAwake 2014-02-16 14:06:44 setstate rr_Son 2014-02-16 14:58:01 lastDeparture 2014-02-16 14:58:01 @@ -272,12 +309,12 @@ setstate rr_Son 2014-02-16 14:06:44 lastDurSleep 00:00:11 setstate rr_Son 2014-02-16 14:58:01 lastLocation home setstate rr_Son 2014-02-16 14:58:01 lastMood calm setstate rr_Son 2014-02-16 14:06:33 lastSleep 2014-02-16 14:06:33 -setstate rr_Son 2014-02-16 14:58:01 lastState home +setstate rr_Son 2015-01-04 16:19:19 lastState absent setstate rr_Son 2014-02-16 14:58:01 location underway setstate rr_Son 2014-02-16 14:58:01 mood - setstate rr_Son 2014-02-16 14:58:01 presence absent -setstate rr_Son 2014-02-16 14:58:01 state absent +setstate rr_Son 2015-01-04 16:19:19 state gone setstate rr_Son 2014-02-16 13:46:38 wayhome 0 -setstate sunRise Next: 06:58:34 -setstate sunSet Next: 18:20:46 +setstate sunRise Next: 07:45:49 +setstate sunSet Next: 17:14:06 setstate wlCinema initialized diff --git a/fhem/fhem.cfg.demo b/fhem/fhem.cfg.demo index 36446dacb..dc4819a1d 100644 --- a/fhem/fhem.cfg.demo +++ b/fhem/fhem.cfg.demo @@ -73,7 +73,7 @@ attr Livingroom icon light_pendant_light attr Livingroom model fs20di attr Livingroom room Light attr Livingroom webCmd dim -define AllLights structure Light Alarm Livingroom Office Outdoor +define AllLights structure Light Alarm Livingroom Office Outdoor CT RGB attr AllLights devStateIcon undefined:light_question attr AllLights group Structure attr AllLights icon light_light @@ -164,7 +164,7 @@ attr ReadingLight eventMap off:dim0% on:dim100% attr ReadingLight group Light attr ReadingLight icon light_floor_lamp attr ReadingLight room Cinema -attr ReadingLight webCmd on:off:dim +attr ReadingLight webCmd on:off:dim:dim 50 define wlCinema weblink htmlCode {LightScene_2html("lcCinema")} attr wlCinema room Cinema define lcCinema LightScene Projector Screen TV CeilingLight ReadingLight @@ -185,6 +185,7 @@ attr anyViews dashboard_tab1name Dashboard Demo attr anyViews dashboard_tab1sorting t0c100,Light,true,518,129:t0c100,Home State,true,496,204:t0c0,Single Lights,true,522,209:t0c0,AV,true,221,170: attr anyViews dashboard_tabcount 1 attr anyViews dashboard_width 80% +attr anyViews room hidden define anyViews_weblink weblink htmlCode {DashboardAsHtml("anyViews")} attr anyViews_weblink room DashboardRoom @@ -288,3 +289,29 @@ attr rr_Baby icon status_available attr rr_Baby room Residents attr rr_Baby sortby 0 attr rr_Baby webCmd state +define RGB readingsProxy RGB +attr RGB userattr Light Light_map structexclude +attr RGB Light AllLights +attr RGB alias RGB Light +attr RGB comment light with the ability to change RGB color +attr RGB devStateIcon {Color::devStateIcon("RGB","rgb","rgb","state")} +attr RGB group Color Lights +attr RGB room Light +attr RGB setFn {if( $CMD =~ m/on|off/ ) { $ARGS=$CMD;;$CMD = "state" } else {fhem ("setreading $DEVICE state on");;} if( $CMD =~ m/hue/ ) {my ($r,$g,$b) = Color::hsv2rgb($ARGS/360,1,1);; my $rgb = Color::rgb2hex( $r*255, $g*255, $b*255 );; fhem ("setreading $DEVICE rgb $rgb");;} if( $CMD =~ m/rgb/ && $ARGS =~ m/^(..)(..)(..)/ ) {my( $r, $g, $b ) = (hex($1)/255.0, hex($2)/255.0, hex($3)/255.0);; my ($h,$s,$v) = Color::rgb2hsv($r,$g,$b);; my $hue = int($h*359);; fhem ("setreading $DEVICE hue $hue");;} fhem ("setreading $DEVICE $CMD $ARGS");;return undef;;} +attr RGB setList on:noArg off:noArg rgb:colorpicker,RGB hue:colorpicker,HUE,0,1,359 +attr RGB webCmd hue:rgb:rgb ff0000:rgb 00ff00:rgb 0000ff:rgb ffffff:on:off +define colorInit notify global:INITIALIZED {use Color;;Color_Initialize()} +attr colorInit room hidden +define CT readingsProxy CT +attr CT userattr Light Light_map structexclude +attr CT Light AllLights +attr CT alias CT Light +attr CT comment light with the ability to change the color temperature +attr CT devStateIcon {Color::devStateIcon("CT","rgb","rgb","state")} +attr CT getFn { my ($r,$g,$b) = Color::ct2rgb( ReadingsVal($DEVICE,"ct",333) );; return (Color::rgb2hex($r,$g,$b), 1);; } +attr CT getList rgb:noArg +attr CT group Color Lights +attr CT room Light +attr CT setFn {if( $CMD =~ m/on|off/ ) { $ARGS=$CMD;;$CMD = "state" } else {fhem ("setreading $DEVICE state on");;} fhem ("setreading $DEVICE $CMD $ARGS");;return undef;;} +attr CT setList on:noArg off:noArg ct:colorpicker,CT,2000,1,6500 +attr CT webCmd ct::ct 2040:ct 2630:ct 3703:ct 6250:on:off diff --git a/fhem/www/pgm2/defaultCommon.css b/fhem/www/pgm2/defaultCommon.css index f130da51d..3f0ff8e72 100644 --- a/fhem/www/pgm2/defaultCommon.css +++ b/fhem/www/pgm2/defaultCommon.css @@ -37,13 +37,18 @@ div.block { border:1px solid gray; background: #F8F8E0; padding:0.7em; } .makeSelect { display:inline; float:left; clear:left; } select { margin-left:5px; margin-right:5px; } .slider { float:left; width:250px; height:26px; } +.colorpicker_ct .slider { background: url(../jscolor/ct_background.svg); } +.colorpicker_hue .slider { background: url(../jscolor/hue_background.svg); } .get,.set,.attr { margin-bottom:5px; float:left; } +select.svgSrc { width:100px; } +select.svgColumn { width:50px; } +select.svgRegexp { width:120px; } + .handle { position:relative; cursor:pointer; width:50px; height:20px; line-height:20px; -webkit-user-select:none; -moz-user-select:none; -user-select:none; border:3px solid; color:#278727; text-align:center; } .downText { margin-top:2px; } -.makeSelect .slider {background:#F0F0D8; border-radius:8px;} /* detail only */ .set .set { margin-bottom:2px; margin-top:3px; } /* timepicker */ pre { white-space: pre-wrap; } @@ -60,8 +65,23 @@ svg.on,svg.FS20_on { fill:orange; } border-color:transparent; } .rc_button img:active { border-color: gray; } +table#atWizard td:first-child { width: 240px; } /* jQuery-UI mods */ div.ui-dialog { border:3px solid #278727; padding: 0.2em; } div.ui-dialog div.ui-dialog-titlebar { display:none; } div.ui-widget-content { background:#FFFFE7; } +#fwmenu { + position: absolute; z-index:1005; + text-align:left; max-width:600px; + font-weight: normal; font-size: 100%; + background:#FFFFE7; border:1px solid #278727; +} +#fwmenu li a { color:#278727; } + +div#svgmarker { + position: absolute; z-index:1005; padding: 6px 10px; + text-align:left; max-width:600px; + color:#278727; background:#FFFFE7; + border:2px solid #278727; border-radius:4px; +} diff --git a/fhem/www/pgm2/fhemweb.js b/fhem/www/pgm2/fhemweb.js index eec0efd1b..04c121018 100644 --- a/fhem/www/pgm2/fhemweb.js +++ b/fhem/www/pgm2/fhemweb.js @@ -1,16 +1,149 @@ -/*************** LONGPOLL START **************/ -var FW_pollConn; -var FW_curLine; // Number of the next line in FW_pollConn.responseText to parse -var FW_widgets = new Object(); // to be filled by fhemweb_*.js -var FW_leaving; -var isIE = (navigator.appVersion.indexOf("MSIE") > 0); -var isiOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/); +"use strict"; + +var FW_serverGenerated; +var FW_serverFirstMsg = (new Date()).getTime()/1000; +var FW_serverLastMsg = FW_serverFirstMsg; +var FW_isIE = (navigator.appVersion.indexOf("MSIE") > 0); +var FW_isiOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/); +var FW_scripts = {}, FW_links = {}; +var FW_docReady = false; +var FW_root = "/fhem"; // root + +// createFn returns an HTML Element, which may contain +// - setValueFn, which is called when data via longpoll arrives +// - activateFn, which is called after the HTML element is part of the DOM. +var FW_widgets = { + select: { createFn:FW_createSelect }, + slider: { createFn:FW_createSlider }, + time: { createFn:FW_createTime }, + noArg: { createFn:FW_createNoArg }, + multiple: { createFn:FW_createMultiple }, + "multiple-strict": { createFn:FW_createMultiple }, + textfield: { createFn:FW_createTextField }, + "textfield-long": { createFn:FW_createTextField } +}; + +window.onbeforeunload = function(e) +{ + FW_leaving = 1; + return undefined; +} + +function +FW_jqueryReadyFn() +{ + FW_docReady = true; + FW_serverGenerated = document.body.getAttribute("generated"); + if(document.body.getAttribute("longpoll")) + setTimeout("FW_longpoll()", 100); + + $("a[href]").each(function() { FW_replaceLink(this); }) + $("head script").each(function() { + var sname = $(this).attr("src"), + p = FW_scripts[sname]; + if(!p) { + FW_scripts[sname] = { loaded:true }; + return; + } + FW_scripts[sname].loaded = true; + if(p.callbacks && !p.called) { + p.called = true; // Avoid endless loop + for(var i1=0; i1< p.callbacks.length; i1++) + if(p.callbacks[i1]) // pushing undefined callbacks on the stack is ok + p.callbacks[i1](); + delete(p.callbacks); + } + + }); + $("head link").each(function() { FW_links[$(this).attr("href")] = 1 }); + + $("div.makeSelect select").each(function() { + FW_detailSelect(this); + $(this).change(FW_detailSelect); + }); + + + // Activate the widgets + var r = $("head").attr("root"); + if(r) + FW_root = r; + $("div.fhemWidget").each(function() { + var dev=$(this).attr("dev"); + var cmd=$(this).attr("cmd"); + var rd=$(this).attr("reading"); + var params = cmd.split(" "); + FW_replaceWidget(this, dev, $(this).attr("arg").split(","), + $(this).attr("current"), rd, params[0], params.slice(1), + function(arg) { + FW_cmd(FW_root+"?cmd=set "+dev+(params[0]=="state" ? "":" "+params[0])+ + " "+arg+"&XHR=1"); + }); + }); + + + // Fix the td count by setting colspan on the last column + $("table.block.wide").each(function(){ // table + var id = $(this).attr("id"); + if(!id || id.indexOf("TYPE") != 0) + return; + var maxTd=0, tdCount=[]; + $(this).find("tr").each(function(){ // count the td's + var cnt=0; + $(this).find("td").each(function(){ cnt++; }); + if(maxTd < cnt) maxTd = cnt; + tdCount.push(cnt); + }); + $(this).find("tr").each(function(){ // set the colspan + $(this).find("td").last().attr("colspan", maxTd-tdCount.shift()+1); + }); + }); + + // Replace the FORM-POST in detail-view by XHR + /* Inactive, as Internals and Attributes arent auto updated. + $("form input[type=submit]").click(function(e) { + var cmd = ""; + $(this).parent().find("[name]").each(function() { + cmd += (cmd?"&":"")+$(this).attr("name")+"="+$(this).val(); + }); + if(cmd.indexOf("detail=") < 0) + return; + e.preventDefault(); + FW_cmd(FW_root+"?"+cmd+"&XHR=1"); + }); + */ + +} + + +if(window.jQuery) { + $(document).ready(FW_jqueryReadyFn); + +} else { + // FLOORPLAN compatibility + loadScript("pgm2/jquery.min.js", function() { + loadScript("pgm2/jquery-ui.min.js", function() { + FW_jqueryReadyFn(); + }, true); + }, true); +} + +// FLOORPLAN compatibility +function +FW_delayedStart() +{ + setTimeout("FW_longpoll()", 100); +} + function log(txt) { - if(typeof window.console != "undefined") // IE + var d = new Date(); + var ms = ("000"+(d.getMilliseconds()%1000)); + ms = ms.substr(ms.length-3,3); + txt = d.toTimeString().substring(0,8)+"."+ms+" "+txt; + if(typeof window.console != "undefined") console.log(txt); } @@ -24,21 +157,27 @@ addcsrf(arg) } function -FW_cmd(arg) /* see also FW_devState */ +FW_cmd(arg, callback) { + log("FW_cmd:"+arg); arg = addcsrf(arg); var req = new XMLHttpRequest(); req.open("POST", arg, true); req.send(null); req.onreadystatechange = function(){ - if(req.readyState == 4) - FW_errmsg(req.responseText, 5000); + if(req.readyState == 4 && req.responseText) { + if(callback) + callback(req.responseText); + else + FW_errmsg(req.responseText, 5000); + } } } function FW_errmsg(txt, timeout) { + log("ERRMSG:"+txt+"<"); var errmsg = document.getElementById("errmsg"); if(!errmsg) { if(txt == "") @@ -56,6 +195,96 @@ FW_errmsg(txt, timeout) setTimeout("FW_errmsg('')", timeout); } +function +FW_okDialog(txt) +{ + var div = $("
    "); + $(div).html(txt); + $("body").append(div); + $(div).dialog({ + dialogClass:"no-close", modal:true, width:"auto", closeOnEscape:true, + maxWidth:$(window).width()*0.9, maxHeight:$(window).height()*0.9, + buttons: [{text:"OK", click:function(){ + $(this).dialog("close"); + $(div).remove(); + }}] + }); +} + +function +FW_menu(evt, el, arr, dis, fn, embedEl) +{ + if(!embedEl) + evt.stopPropagation(); + if($("#fwmenu").length) { + delfwmenu(); + return; + } + + var html = '
      '; + for(var i=0; i'+ + ''+arr[i]+''; + } + html += '
    '; + $("body").append(html); + + function + delfwmenu() + { + $("ul#fwmenu").remove(); + $('html').unbind('click.fwmenu'); + } + + var wt = $(window).scrollTop(); + $("#fwmenu") + .menu({ + select: function(e,ui) { // changes the scrollTop(); + e.stopPropagation(); + fn($(e.currentTarget).find("[row]").attr("row")); + delfwmenu(); + setTimeout(function(){ $(window).scrollTop(wt) }, 1); // Bug in select? + } + }); + + var off = $(el).offset(); + if(embedEl) { + var embOff = $(embedEl).offset(); + off.top += embOff.top; + off.left += embOff.left; + } + var dH = $("#fwmenu").height(), dW = $("#fwmenu").width(), + wH = $(window).height(), wW = $(window).width(); + var ey = off.top+dH+20, ex = off.left+dW; + if(ex>wW && ey>wH) { off.top -= dH; off.left -= (dW+16); + } else if(ey > wH) { off.top -= dH; off.left += 20; + } else if(ex > wW) { off.left -= (dW+16); + } else { off.top += 20; + } + + $("#fwmenu").css(off); + $('html').bind('click.fwmenu', function() { delfwmenu(); }); +} + + +function +FW_replaceLink(el) +{ + var attr = $(el).attr("href"); + var ma = attr.match(/^(.*\?)(cmd[^=]*=.*)$/); + if(ma == null || ma.length == 0 || !ma[2].match(/=(save|set)/)) + return; + $(el).removeAttr("href"); + $(el).click(function() { FW_cmd(attr+"&XHR=1"); }); + $(el).css("cursor", "pointer"); +} + + +/*************** LONGPOLL START **************/ +var FW_pollConn; +var FW_longpollOffset = 0; +var FW_leaving; + function FW_doUpdate() { @@ -68,61 +297,59 @@ FW_doUpdate() if(FW_pollConn.readyState != 3) return; - var lines = FW_pollConn.responseText.split("\n"); - //Pop the last (maybe empty) line after the last "\n" - //We wait until it is complete, i.e. terminated by "\n" - lines.pop(); + var input = FW_pollConn.responseText; var devs = new Array(); - for(var i=FW_curLine; i < lines.length; i++) { - var l = lines[i]; - log("Longpoll: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l)); - var d = l.split("<<", 3); // Complete arg + if(input.length <= FW_longpollOffset) + return; + FW_serverLastMsg = (new Date()).getTime()/1000; + for(;;) { + var nOff = input.indexOf("\n", FW_longpollOffset); + if(nOff < 0) + break; + var l = input.substr(FW_longpollOffset, nOff-FW_longpollOffset); + FW_longpollOffset = nOff+1; + + log("Rcvd: "+(l.length>132 ? l.substring(0,132)+"...("+l.length+")":l)); + if(!l.length) + continue; + var d = JSON.parse(l); if(d.length != 3) continue; - var elArr = document.querySelectorAll("[informId='"+d[0]+"']"); - for(var k=0; k 300*1024) + if(FW_longpollOffset > 1024*1024 && FW_longpollOffset==input.length) FW_longpoll(); } function FW_longpoll() { - log("Connecting..."); - FW_curLine = 0; + FW_longpollOffset = 0; if(FW_pollConn) { FW_leaving = 1; FW_pollConn.abort(); @@ -131,20 +358,17 @@ FW_longpoll() FW_pollConn = new XMLHttpRequest(); FW_leaving = 0; + // Build the notify filter for the backend var filter = document.body.getAttribute("longpollfilter"); if(filter == null) filter = ""; if(filter == "") { - var embArr = document.getElementsByTagName("embed"); - for(var i = 0; i < embArr.length; i++) { - var svg = embArr[i].getSVGDocument(); - if(svg && - svg.firstChild && - svg.firstChild.nextSibling && - svg.firstChild.nextSibling.getAttribute("flog")) + $("embed").each(function() { + if($(this.getSVGDocument()).find("svg[flog]").attr("flog")) filter=".*"; - } + }); } + if(filter == "") { var sa = location.search.substring(1).split("&"); for(var i = 0; i < sa.length; i++) { @@ -154,11 +378,12 @@ FW_longpoll() filter=sa[i].substring(7); } } + if(filter == "" && document.getElementById("floorplan")) { //floorplan special - var name = document.getElementsByTagName("body")[0].getAttribute("id"); - name = name.substring(0,name.length-5); - filter=".*;iconPath="+name; + var name = document.body.getAttribute("id"); + filter=".*;iconPath="+name.substring(0,name.length-5); } + if(filter == "") { var content = document.getElementById("content"); if(content) { @@ -167,198 +392,528 @@ FW_longpoll() filter="room="+room; } } - var iP = document.body.getAttribute("iconPath"); if(iP != null) filter = filter +";iconPath="+iP; + var since = "null"; + if(FW_serverGenerated) + since = FW_serverLastMsg + (FW_serverGenerated-FW_serverFirstMsg); + var query = location.pathname+"?XHR=1"+ - "&inform=type=status;filter="+filter+ - ";since="+document.body.getAttribute("generated")+ + "&inform=type=status;filter="+filter+";since="+since+ "×tamp="+new Date().getTime(); query = addcsrf(query); FW_pollConn.open("GET", query, true); FW_pollConn.onreadystatechange = FW_doUpdate; FW_pollConn.send(null); + + log("Longpoll with filter "+filter); } -function -FW_replaceLinks() -{ - var elArr = document.querySelectorAll("a[href]"); - for(var i1=0; i1< elArr.length; i1++) { - var a = elArr[i1]; - var ma = a.getAttribute("href").match(/^(.*\?)(cmd[^=]*=.*)$/); - if(ma == null || ma.length == 0 || !ma[2].match(/=(save|set)/)) - continue; - a.removeAttribute("href"); - a.setAttribute("onclick", "FW_cmd('"+ma[1]+"XHR=1&"+ma[2]+"')"); - a.setAttribute("style", "cursor:pointer"); - } -} - -function -FW_delayedStart() -{ - setTimeout("FW_longpoll()", 100); - FW_replaceLinks(); -} /*************** LONGPOLL END **************/ -/*************** Select **************/ -/** Change attr/set argument type to input:text or select **/ + +/*************** WIDGETS START **************/ +/*************** "Double" select in detail window ****/ function -FW_selChange(sel, list, elName) +FW_detailSelect(selEl) { - var cmd, value; - var l = list.split(" "); - for(var i=0; i < l.length; i++) { - cmd = l[i]; - var off = cmd.indexOf(":"); - if(off >= 0) - cmd = cmd.substring(0, off); - if(cmd == sel) { - if(off >= 0) - value = l[i].substring(off+1); + if(selEl.target) + selEl = selEl.target; + var selVal = $(selEl).val(); + + var div = $(selEl).closest("div.makeSelect"); + var arg, + listArr = $(div).attr("list").split(" "), + devName = $(div).attr("dev"), + cmd = $(div).attr("cmd"); + + for(var i1=0; i1 selVal.length) + vArr = arg.substr(selVal.length+1).split(","); - var o; - if(value==undefined) { - o = new Object(); - o.newEl = document.createElement('input'); - o.newEl.type='text'; - o.newEl.size=30; - o.qFn = 'qArg.setAttribute("value", "%")'; - o.qArg = o.newEl; + var newEl = FW_replaceWidget($(selEl).next(), devName, vArr,undefined,selVal); + if(cmd == "attr") + FW_queryValue('{AttrVal("'+devName+'","'+selVal+'","")}', newEl); + + if(cmd == "set") + FW_queryValue('{ReadingsVal("'+devName+'","'+selVal+'","")}', newEl); +} + +function +FW_replaceWidget(oldEl, devName, vArr, currVal, reading, set, params, cmd) +{ + var newEl, wn; + var elName = $(oldEl).attr("name"); + if(!elName) + elName = $(oldEl).find("[name]").attr("name"); + + if(vArr.length == 0) { // No parameters, input field + newEl = FW_createTextField(elName, devName, ["textField"], currVal, + set, params, cmd); + wn = "textField"; } else { - var vArr = value.split(","); - - for(var w in FW_widgets) { - if(FW_widgets[w].selChange) { - o = FW_widgets[w].selChange(elName, devName, vArr); - if(o) + for(wn in FW_widgets) { + if(FW_widgets[wn].createFn) { + newEl = FW_widgets[wn].createFn(elName, devName, vArr, currVal, + set, params, cmd); + if(newEl) break; } } - if(!o) { - o = new Object(); - o.newEl = document.createElement('select'); - for(var j=0; j < vArr.length; j++) { - o.newEl.options[j] = new Option(vArr[j], vArr[j]); - } - o.qFn = 'FW_querySetSelected(qArg, "%")'; - o.qArg = o.newEl; - } - - - } - - o.newEl.setAttribute('class', el.getAttribute('class')); - o.newEl.setAttribute('name', elName); - el.parentNode.replaceChild(o.newEl, el); - - if((typeof o.qFn == "string")) { - if(elName.indexOf("val.attr")==0) - FW_queryValue('{AttrVal("'+devName+'","'+sel+'","")}', o.qFn, o.qArg); - if(elName.indexOf("val.set")==0) { - qArg = o.qArg; - eval(o.qFn.replace("%", "")); - FW_queryValue('{ReadingsVal("'+devName+'","'+sel+'","")}', o.qFn, o.qArg); + if(!newEl) { // Select as fallback + vArr.unshift("select"); + newEl = FW_createSelect(elName, devName, vArr, currVal, set, params, cmd); + wn = "select"; } } + + if(!newEl) { // Simple link + newEl = $(''); + $(newEl).click(function(arg) { cmd(params[0]) }); + $(oldEl).replaceWith(newEl); + return newEl; + } + + $(newEl).addClass(wn+"_widget"); + + if( $(newEl).find("[informId]").length == 0 && !$(newEl).attr("informId") ) { + if(reading && reading == "state") + $(newEl).attr("informId", devName); + else if(reading) + $(newEl).attr("informId", devName+"-"+reading); + } + + $(oldEl).replaceWith(newEl); + + if(newEl.activateFn) // CSS is not applied if newEl is not in the document + newEl.activateFn(); + return newEl; } - -/*************** Fill attribute **************/ function -FW_queryValue(cmd, qFn, qArg) +FW_queryValue(cmd, el) { + var query = location.pathname+"?cmd="+cmd+"&XHR=1"; + query = addcsrf(query); var qConn = new XMLHttpRequest(); qConn.onreadystatechange = function() { if(qConn.readyState != 3) return; - var qResp = qConn.responseText.replace(/[\r\n]/g, "") - .replace(/\\/g, "\\\\") - .replace(/"/g, "\\\""); - eval(qFn.replace("%", qResp)); - delete qConn; + var qResp = qConn.responseText.replace(/[\r\n]/g, ""); + if(el.setValueFn) + el.setValueFn(qResp); + qConn.abort(); } - var query = location.pathname+"?cmd="+cmd+"&XHR=1" - query = addcsrf(query); qConn.open("GET", query, true); qConn.send(null); } - function -FW_querySetSelected(el, val) +FW_querySetSelected(el, val) // called by the attribute links { - if(typeof el == 'string') - el = document.getElementById(el); - for(var j=0;j").get(0); + if(set && set != "state") + $(newEl).append(set+":"); + $(newEl).append(''); + var inp = $(newEl).find("input").get(0); + if(elName) + $(inp).attr('name', elName); + if(currVal != undefined) + $(inp).val(currVal); + if(cmd) + $(inp).blur(function() { cmd($(inp).val()) }); + newEl.setValueFn = function(arg){ $(inp).val(arg) }; + + var myFunc = function(){ + $('body').append( + '