From 9d7c346d135d88c2aad0075d69a6e0c70974ba3e Mon Sep 17 00:00:00 2001 From: rudolfkoenig <> Date: Wed, 18 Nov 2015 18:53:43 +0000 Subject: [PATCH] fhem.pl: async get capability, if supported by the module (Forum #43771) git-svn-id: https://svn.fhem.de/fhem/trunk@9927 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/01_FHEMWEB.pm | 63 +++++++++++++++++++++++++++++++++++++--- fhem/FHEM/98_telnet.pm | 34 +++++++++++++++------- fhem/fhem.pl | 21 ++++++++++++++ fhem/www/pgm2/fhemweb.js | 37 ++++++++++++++++++----- 4 files changed, 134 insertions(+), 21 deletions(-) diff --git a/fhem/FHEM/01_FHEMWEB.pm b/fhem/FHEM/01_FHEMWEB.pm index 790f641c7..f0a8e3217 100755 --- a/fhem/FHEM/01_FHEMWEB.pm +++ b/fhem/FHEM/01_FHEMWEB.pm @@ -106,6 +106,7 @@ 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_headercors; # my $FW_chash; # client fhem hash @@ -125,6 +126,7 @@ FHEMWEB_Initialize($) $hash->{DefFn} = "FW_Define"; $hash->{UndefFn} = "FW_Undef"; $hash->{NotifyFn}= ($init_done ? "FW_Notify" : "FW_SecurityCheck"); + $hash->{AsyncOutputFn} = "FW_AsyncOutput"; $hash->{ActivateInformFn} = "FW_ActivateInform"; no warnings 'qw'; my @attrList = qw( @@ -475,6 +477,43 @@ FW_Read($$) } } +sub +FW_AsyncOutput($$) +{ + my ($hash, $ret) = @_; + + if( $ret =~ m/^(.*)<\/html>$/s ) { + $ret = $1; + + } else { + $ret =~ s/&/&/g; + $ret =~ s/'/'/g; + $ret =~ s//>/g; + $ret = "
$ret
" if($ret =~ m/\n/ ); + $ret =~ s/\n/
/g; + } + + # find the longpoll connection with the same fw_id as the page that was the + # origin of the get command + my $found = 0; + my $data = FW_longpollInfo('JSON', + "#FHEMWEB:$FW_wname","FW_okDialog('$ret')",""); + foreach my $d (keys %defs ) { + my $chash = $defs{$d}; + next if( $chash->{TYPE} ne 'FHEMWEB' ); + next if( !$chash->{inform} ); + next if( !$chash->{FW_ID} || $chash->{FW_ID} ne $hash->{FW_ID} ); + addToWritebuffer($chash, $data."\n"); + $found = 1; + last; + } + + $defs{$FW_wname}{asyncOutput}{$hash->{FW_ID}} = $data if( !$found ); + + return undef; +} + sub FW_closeConn($) { @@ -601,6 +640,11 @@ FW_answerCall($) } } + if( $FW_id ) { + $me->{FW_ID} = $FW_id; + $me->{canAsyncOutput} = 1; + } + if($FW_inform) { # Longpoll header if($FW_inform =~ /type=/) { foreach my $kv (split(";", $FW_inform)) { @@ -632,6 +676,12 @@ FW_answerCall($) $FW_headercors. "Content-Type: application/octet-stream; charset=$FW_encoding\r\n\r\n". FW_roomStatesForInform($me, $sinceTimestamp)); + + if( my $data = $defs{$FW_wname}{asyncOutput}{$FW_id} ) { + addToWritebuffer($me, $data."\n"); + delete $defs{$FW_wname}{asyncOutput}{$FW_id}; + } + return -1; } @@ -692,8 +742,9 @@ FW_answerCall($) # Redirect after a command, to clean the browser URL window if($docmd && !$FW_cmdret && AttrVal($FW_wname, "redirectCmds", 1)) { my $tgt = $FW_ME; - if($FW_detail) { $tgt .= "?detail=$FW_detail" } - elsif($FW_room) { $tgt .= "?room=$FW_room" } + if($FW_detail) { $tgt .= "?detail=$FW_detail&fw_id=$FW_id" } + elsif($FW_room) { $tgt .= "?room=$FW_room&fw_id=$FW_id" } + else { $tgt .= "?fw_id=$FW_id" } TcpServer_WriteBlocking($me, "HTTP/1.1 302 Found\r\n". "Content-Length: 0\r\n". $FW_headercors. @@ -782,7 +833,8 @@ FW_answerCall($) my $csrf= ($FW_CSRF ? "fwcsrf='$defs{$FW_wname}{CSRFTOKEN}'" : ""); my $gen = 'generated="'.(time()-1).'"'; my $lp = 'longpoll="'.AttrVal($FW_wname,"longpoll",1).'"'; - FW_pO "\n"; + $FW_id = $FW_chash->{NR} if( !$FW_id ); + FW_pO "\n"; if($FW_activateInform) { $cmd = "style eventMonitor $FW_activateInform"; @@ -874,6 +926,7 @@ FW_digestCgi($) $FW_room = ""; $FW_detail = ""; $FW_XHR = undef; + $FW_id = undef; $FW_jsonp = undef; $FW_inform = undef; @@ -900,6 +953,7 @@ FW_digestCgi($) 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; } @@ -1363,7 +1417,8 @@ FW_roomOverview($) FW_pO "
"; FW_pO '
'; FW_pO "
"; - FW_pO FW_hidden("room", "$FW_room") if($FW_room); + 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", $FW_ss ? 25 : 40, "maininput"); FW_pO "
"; diff --git a/fhem/FHEM/98_telnet.pm b/fhem/FHEM/98_telnet.pm index 31a29c736..d7a6bb1ae 100644 --- a/fhem/FHEM/98_telnet.pm +++ b/fhem/FHEM/98_telnet.pm @@ -17,6 +17,7 @@ telnet_Initialize($) $hash->{DefFn} = "telnet_Define"; $hash->{ReadFn} = "telnet_Read"; + $hash->{AsyncOutputFn} = "telnet_Output"; $hash->{UndefFn} = "telnet_Undef"; $hash->{AttrFn} = "telnet_Attr"; $hash->{NotifyFn}= "telnet_SecurityCheck"; @@ -187,6 +188,7 @@ telnet_Read($) if($hash->{SERVERSOCKET}) { # Accept and create a child my $chash = TcpServer_Accept($hash, "telnet"); return if(!$chash); + $chash->{canAsyncOutput} = 1; $chash->{encoding} = AttrVal($name, "encoding", "utf8"); $chash->{prompt} = AttrVal($name, "prompt", "fhem>"); syswrite($chash->{CD}, sprintf("%c%c%c", 255, 253, 0) ) @@ -203,6 +205,7 @@ telnet_Read($) if($hash->{isClient}) { telnet_ClientDisconnect($hash, 0); } else { + delete $hash->{canAsyncOutput}; CommandDelete(undef, $name); } return; @@ -284,17 +287,10 @@ telnet_Read($) $ret .= (join("\n", @ret) . "\n") if(@ret); $ret .= ($hash->{prevlines} ? "> " : $hash->{prompt}." ") if($gotCmd && $hash->{showPrompt} && !$hash->{rcvdQuit}); - if($ret) { - $ret = utf8ToLatin1($ret) if( $hash->{encoding} eq "latin1" ); - $ret =~ s/\n/\r\n/g if($pw); # only for DOS telnet - for(;;) { - my $l = syswrite($hash->{CD}, $ret); - last if(!$l || $l == length($ret)); - $ret = substr($ret, $l); - } - $hash->{CD}->flush(); - } + $ret =~ s/\n/\r\n/g if($pw); # only for DOS telnet + telnet_Output($hash,$ret); + if($hash->{rcvdQuit}) { if($hash->{isClient}) { delete($hash->{rcvdQuit}); @@ -304,6 +300,24 @@ telnet_Read($) } } } +sub +telnet_Output($$) +{ + my ($hash,$ret) = @_; + + if($ret) { + $ret = utf8ToLatin1($ret) if( $hash->{encoding} eq "latin1" ); + for(;;) { + my $l = syswrite($hash->{CD}, $ret); + last if(!$l || $l == length($ret)); + $ret = substr($ret, $l); + } + $hash->{CD}->flush(); + + } + + return undef; +} ########################## sub diff --git a/fhem/fhem.pl b/fhem/fhem.pl index 56d6341b0..a0e49d7a4 100755 --- a/fhem/fhem.pl +++ b/fhem/fhem.pl @@ -1601,12 +1601,33 @@ CommandGet($$) } $a[0] = $sdev; + $defs{$sdev}->{CL} = $cl; my $ret = CallFn($sdev, "GetFn", $defs{$sdev}, @a); + delete $defs{$sdev}->{CL}; push @rets, $ret if(defined($ret) && $ret ne ""); } return join("\n", @rets); } +sub +asyncOutput($$) +{ + my ($cl,$ret) = @_; + + return undef if( !$cl ); + + if( !$defs{$cl->{NAME}} + || $defs{$cl->{NAME}}->{NR} != $cl->{NR} + || $defs{$cl->{NAME}}->{NAME} ne $cl->{NAME} ) { + Log3 $cl->{NAME},3,"$cl->{NAME} asyncOutput: device gone, output was: $ret"; + return undef; + } + + $ret = CallFn($cl->{NAME}, "AsyncOutputFn", $defs{$cl->{NAME}}, $ret); + + return $ret; +} + ##################################### sub LoadModule($;$) diff --git a/fhem/www/pgm2/fhemweb.js b/fhem/www/pgm2/fhemweb.js index ea30d188c..ce3871bd9 100644 --- a/fhem/www/pgm2/fhemweb.js +++ b/fhem/www/pgm2/fhemweb.js @@ -52,8 +52,8 @@ function FW_jqueryReadyFn() { FW_docReady = true; - FW_serverGenerated = document.body.getAttribute("generated"); - if(document.body.getAttribute("longpoll")) + FW_serverGenerated = $("body").attr("generated"); + if($("body").attr("longpoll")) setTimeout("FW_longpoll()", 100); $("a").each(function() { FW_replaceLink(this); }) @@ -128,6 +128,7 @@ FW_jqueryReadyFn() }); FW_cmd(FW_root+"?"+cmd+"&XHR=1&addLinks=1", function(data) { if(!data.match(/^[\r\n]*$/)) // ignore empty answers + data = data.replace( '<', '<' ); FW_okDialog('
'+data+'
', el); }); }); @@ -146,12 +147,32 @@ FW_jqueryReadyFn() var input = $(this).find("input.maininput"); if(!input.length) return; - $(this).on("submit", function() { - if($(input).val().match(/^\s*shutdown/)) { - FW_cmd(FW_root+"?XHR=1&cmd="+$(input).val()); + $(this).on("submit", function(e) { + var val = $(input).val(); + if(val.match(/^\s*shutdown/)) { + FW_cmd(FW_root+"?XHR=1&cmd="+val); + $(input).val(""); + return false; + + } else if(val.match(/^\s*get\s+/)) { + // make get use xhr instead of reload + //return true; + FW_cmd(FW_root+"?cmd="+val+"&XHR=1", function(data) { + if( !data.match( /^.*<\/html>/ ) ) { + data = data.replace( '<', '<' ); + data = '
'+data+'
'; + } + if( location.href.indexOf('?') === -1 ) + $('#content').html(data); + else + FW_okDialog(data); + }); + + e.preventDefault(); $(input).val(""); return false; } + return true; }); }); @@ -215,7 +236,7 @@ log(txt) function addcsrf(arg) { - var csrf = document.body.getAttribute('fwcsrf'); + var csrf = $("body").attr('fwcsrf'); if(csrf && arg.indexOf('fwcsrf') < 0) arg += '&fwcsrf='+csrf; return arg; @@ -226,6 +247,7 @@ FW_cmd(arg, callback) { log("FW_cmd:"+arg); arg = addcsrf(arg); + arg += '&fw_id='+$("body").attr('fw_id'); var req = new XMLHttpRequest(); req.open("POST", arg, true); req.send(null); @@ -509,7 +531,7 @@ FW_longpoll() filter="room="+room; } } - var iP = document.body.getAttribute("iconPath"); + var iP = $("body").attr("iconPath"); if(iP != null) filter = filter +";iconPath="+iP; @@ -519,6 +541,7 @@ FW_longpoll() var query = location.pathname+"?XHR=1"+ "&inform=type=status;filter="+filter+";since="+since+";fmt=JSON"+ + '&fw_id='+$("body").attr('fw_id')+ "×tamp="+new Date().getTime(); query = addcsrf(query); FW_pollConn.open("GET", query, true);