01_FHEMWEB.pm 151 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769
  1. ##############################################
  2. # $Id: 01_FHEMWEB.pm 17657 2018-11-01 10:34:37Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use TcpServerUtils;
  7. use HttpUtils;
  8. use Blocking;
  9. use Time::HiRes qw(gettimeofday);
  10. #########################
  11. # Forward declaration
  12. sub FW_IconURL($);
  13. sub FW_addContent(;$);
  14. sub FW_addToWritebuffer($$@);
  15. sub FW_answerCall($);
  16. sub FW_dev2image($;$);
  17. sub FW_devState($$@);
  18. sub FW_digestCgi($);
  19. sub FW_directNotify($@);
  20. sub FW_doDetail($);
  21. sub FW_fatal($);
  22. sub FW_fileList($;$);
  23. sub FW_htmlEscape($);
  24. sub FW_iconName($);
  25. sub FW_iconPath($);
  26. sub FW_logWrapper($);
  27. sub FW_makeEdit($$$);
  28. sub FW_makeImage(@);
  29. sub FW_makeTable($$$@);
  30. sub FW_makeTableFromArray($$@);
  31. sub FW_pF($@);
  32. sub FW_pH(@);
  33. sub FW_pHPlain(@);
  34. sub FW_pO(@);
  35. sub FW_parseColumns($);
  36. sub FW_readIcons($);
  37. sub FW_readIconsFrom($$);
  38. sub FW_returnFileAsStream($$$$$);
  39. sub FW_roomOverview($);
  40. #sub FW_roomStatesForInform($$); # Forum 30515
  41. sub FW_select($$$$$@);
  42. sub FW_serveSpecial($$$$);
  43. sub FW_showRoom();
  44. sub FW_style($$);
  45. sub FW_submit($$@);
  46. sub FW_textfield($$$);
  47. sub FW_textfieldv($$$$);
  48. sub FW_updateHashes();
  49. sub FW_visibleDevices(;$);
  50. sub FW_widgetOverride($$);
  51. sub FW_Read($$);
  52. use vars qw($FW_dir); # base directory for web server
  53. use vars qw($FW_icondir); # icon base directory
  54. use vars qw($FW_cssdir); # css directory
  55. use vars qw($FW_gplotdir);# gplot directory
  56. use vars qw($MW_dir); # moddir (./FHEM), needed by edit Files in new
  57. # structure
  58. use vars qw($FW_ME); # webname (default is fhem), used by 97_GROUP/weblink
  59. use vars qw($FW_CSRF); # CSRF Token or empty
  60. use vars qw($FW_ss); # is smallscreen, needed by 97_GROUP/95_VIEW
  61. use vars qw($FW_tp); # is touchpad (iPad / etc)
  62. use vars qw($FW_sp); # stylesheetPrefix
  63. # global variables, also used by 97_GROUP/95_VIEW/95_FLOORPLAN
  64. use vars qw(%FW_types); # device types,
  65. use vars qw($FW_RET); # Returned data (html)
  66. use vars qw($FW_RETTYPE); # image/png or the like
  67. use vars qw($FW_wname); # Web instance
  68. use vars qw($FW_subdir); # Sub-path in URL, used by FLOORPLAN/weblink
  69. use vars qw(%FW_pos); # scroll position
  70. use vars qw($FW_cname); # Current connection name
  71. use vars qw(%FW_hiddenroom); # hash of hidden rooms, used by weblink
  72. use vars qw($FW_plotmode);# Global plot mode (WEB attribute), used by SVG
  73. use vars qw($FW_plotsize);# Global plot size (WEB attribute), used by SVG
  74. use vars qw(%FW_webArgs); # all arguments specified in the GET
  75. use vars qw(@FW_fhemwebjs);# List of fhemweb*js scripts to load
  76. use vars qw($FW_fhemwebjs);# List of fhemweb*js scripts to load
  77. use vars qw($FW_detail); # currently selected device for detail view
  78. use vars qw($FW_cmdret); # Returned data by the fhem call
  79. use vars qw($FW_room); # currently selected room
  80. use vars qw($FW_formmethod);
  81. use vars qw(%FW_visibleDeviceHash);
  82. use vars qw(@FW_httpheader); # HTTP header, line by line
  83. use vars qw(%FW_httpheader); # HTTP header, as hash
  84. use vars qw($FW_userAgent); # user agent string
  85. $FW_formmethod = "post";
  86. my %FW_use;
  87. my $FW_activateInform = 0;
  88. my $FW_lastWebName = ""; # Name of last FHEMWEB instance, for caching
  89. my $FW_lastHashUpdate = 0;
  90. my $FW_httpRetCode = "";
  91. my %FW_csrfTokenCache;
  92. my %FW_id2inform;
  93. #########################
  94. # As we are _not_ multithreaded, it is safe to use global variables.
  95. # Note: for delivering SVG plots we fork
  96. my $FW_data; # Filecontent from browser when editing a file
  97. my %FW_icons; # List of icons
  98. my @FW_iconDirs; # Directory search order for icons
  99. my $FW_RETTYPE; # image/png or the like
  100. my %FW_rooms; # hash of all rooms
  101. my @FW_roomsArr; # ordered list of rooms
  102. my %FW_groups; # hash of all groups
  103. my %FW_types; # device types, for sorting
  104. my %FW_hiddengroup;# hash of hidden groups
  105. my $FW_inform;
  106. my $FW_XHR; # Data only answer, no HTML
  107. my $FW_id=""; # id of current page
  108. my $FW_jsonp; # jasonp answer (sending function calls to the client)
  109. my $FW_headerlines; #
  110. my $FW_chash; # client fhem hash
  111. my $FW_encoding="UTF-8";
  112. my $FW_styleStamp=time();
  113. my %FW_svgData;
  114. #####################################
  115. sub
  116. FHEMWEB_Initialize($)
  117. {
  118. my ($hash) = @_;
  119. $hash->{ReadFn} = "FW_Read";
  120. $hash->{GetFn} = "FW_Get";
  121. $hash->{SetFn} = "FW_Set";
  122. $hash->{AttrFn} = "FW_Attr";
  123. $hash->{DefFn} = "FW_Define";
  124. $hash->{UndefFn} = "FW_Undef";
  125. $hash->{NotifyFn}= "FW_Notify";
  126. $hash->{AsyncOutputFn} = "FW_AsyncOutput";
  127. $hash->{ActivateInformFn} = "FW_ActivateInform";
  128. $hash->{CanAuthenticate} = 1;
  129. no warnings 'qw';
  130. my @attrList = qw(
  131. CORS:0,1
  132. HTTPS:1,0
  133. CssFiles
  134. Css:textField-long
  135. JavaScripts
  136. SVGcache:1,0
  137. addHtmlTitle:1,0
  138. addStateEvent
  139. csrfToken
  140. csrfTokenHTTPHeader:0,1
  141. alarmTimeout
  142. allowedHttpMethods
  143. allowedCommands
  144. allowfrom
  145. basicAuth
  146. basicAuthMsg
  147. closeConn:1,0
  148. column
  149. confirmDelete:0,1
  150. confirmJSError:0,1
  151. defaultRoom
  152. deviceOverview:always,iconOnly,onClick,never
  153. editConfig:1,0
  154. editFileList:textField-long
  155. endPlotNow:1,0
  156. endPlotToday:1,0
  157. forbiddenroom
  158. fwcompress:0,1
  159. hiddengroup
  160. hiddengroupRegexp
  161. hiddenroom
  162. hiddenroomRegexp
  163. iconPath
  164. longpoll:0,1,websocket
  165. longpollSVG:1,0
  166. menuEntries
  167. mainInputLength
  168. nameDisplay
  169. ploteditor:always,onClick,never
  170. plotfork:1,0
  171. plotmode:gnuplot-scroll,gnuplot-scroll-svg,SVG
  172. plotEmbed:0,1
  173. plotsize
  174. plotWeekStartDay:0,1,2,3,4,5,6
  175. nrAxis
  176. redirectCmds:0,1
  177. refresh
  178. reverseLogs:0,1
  179. roomIcons
  180. showUsedFiles:0,1
  181. sortRooms
  182. sslVersion
  183. sslCertPrefix
  184. smallscreen:unused
  185. smallscreenCommands:0,1
  186. stylesheetPrefix
  187. styleData:textField-long
  188. title
  189. touchpad:unused
  190. viewport
  191. webname
  192. );
  193. use warnings 'qw';
  194. $hash->{AttrList} = join(" ", @attrList);
  195. ###############
  196. # Initialize internal structures
  197. map { addToAttrList($_) } ( "webCmd", "webCmdLabel:textField-long", "icon",
  198. "cmdIcon", "devStateIcon", "widgetOverride", "sortby", "devStateStyle");
  199. InternalTimer(time()+60, "FW_closeInactiveClients", 0, 0);
  200. $FW_dir = "$attr{global}{modpath}/www";
  201. $FW_icondir = "$FW_dir/images";
  202. $FW_cssdir = "$FW_dir/pgm2";
  203. $FW_gplotdir = "$FW_dir/gplot";
  204. if(opendir(DH, "$FW_dir/pgm2")) {
  205. $FW_fhemwebjs = join(",", map { $_ = ~m/^fhemweb_(.*).js$/; $1 }
  206. grep { /fhemweb_(.*).js$/ }
  207. readdir(DH));
  208. closedir(DH);
  209. }
  210. $data{webCmdFn}{"~"} = "FW_widgetFallbackFn"; # Should be the last
  211. if($init_done) { # reload workaround
  212. foreach my $pe ("fhemSVG", "openautomation", "default") {
  213. FW_readIcons($pe);
  214. }
  215. }
  216. my %optMod = (
  217. zlib => { mod=>"Compress::Zlib", txt=>"compressed HTTP transfer" },
  218. sha => { mod=>"Digest::SHA", txt=>"longpoll via websocket" },
  219. base64 => { mod=>"MIME::Base64", txt=>"parallel SVG computing" }
  220. );
  221. foreach my $mod (keys %optMod) {
  222. eval "require $optMod{$mod}{mod}";
  223. if($@) {
  224. Log 4, $@;
  225. Log 3, "FHEMWEB: Can't load $optMod{$mod}{mod}, ".
  226. "$optMod{$mod}{txt} is not available";
  227. } else {
  228. $FW_use{$mod} = 1;
  229. }
  230. }
  231. }
  232. #####################################
  233. sub
  234. FW_Define($$)
  235. {
  236. my ($hash, $def) = @_;
  237. my ($name, $type, $port, $global) = split("[ \t]+", $def);
  238. return "Usage: define <name> FHEMWEB [IPV6:]<tcp-portnr> [global]"
  239. if($port !~ m/^(IPV6:)?\d+$/);
  240. FW_Undef($hash, undef) if($hash->{OLDDEF}); # modify
  241. foreach my $pe ("fhemSVG", "openautomation", "default") {
  242. FW_readIcons($pe);
  243. }
  244. my $ret = TcpServer_Open($hash, $port, $global);
  245. # Make sure that fhem only runs once
  246. if($ret && !$init_done) {
  247. Log3 $hash, 1, "$ret. Exiting.";
  248. exit(1);
  249. }
  250. $hash->{CSRFTOKEN} = $FW_csrfTokenCache{$name};
  251. if(!defined($hash->{CSRFTOKEN})) { # preserve over rereadcfg
  252. InternalTimer(1, sub(){
  253. if($featurelevel >= 5.8 && !AttrVal($name, "csrfToken", undef)) {
  254. my ($x,$y) = gettimeofday();
  255. ($defs{$name}{CSRFTOKEN}="csrf_".(rand($y)*rand($x))) =~s/[^a-z_0-9]//g;
  256. $FW_csrfTokenCache{$name} = $hash->{CSRFTOKEN};
  257. }
  258. }, $hash, 0);
  259. }
  260. return $ret;
  261. }
  262. #####################################
  263. sub
  264. FW_Undef($$)
  265. {
  266. my ($hash, $arg) = @_;
  267. my $ret = TcpServer_Close($hash);
  268. if($hash->{inform}) {
  269. delete $FW_id2inform{$hash->{FW_ID}} if($hash->{FW_ID});
  270. %FW_visibleDeviceHash = FW_visibleDevices();
  271. delete($logInform{$hash->{NAME}});
  272. }
  273. return $ret;
  274. }
  275. #####################################
  276. sub
  277. FW_Read($$)
  278. {
  279. my ($hash, $reread) = @_;
  280. my $name = $hash->{NAME};
  281. if($hash->{SERVERSOCKET}) { # Accept and create a child
  282. my $nhash = TcpServer_Accept($hash, "FHEMWEB");
  283. return if(!$nhash);
  284. my $wt = AttrVal($name, "alarmTimeout", undef);
  285. $nhash->{ALARMTIMEOUT} = $wt if($wt);
  286. $nhash->{CD}->blocking(0);
  287. return;
  288. }
  289. $FW_chash = $hash;
  290. $FW_wname = $hash->{SNAME};
  291. $FW_cname = $name;
  292. $FW_subdir = "";
  293. my $c = $hash->{CD};
  294. if(!$reread) {
  295. # Data from HTTP Client
  296. my $buf;
  297. my $ret = sysread($c, $buf, 1024);
  298. if(!defined($ret) && $! == EWOULDBLOCK ){
  299. $hash->{wantWrite} = 1
  300. if(TcpServer_WantWrite($hash));
  301. return;
  302. } elsif(!$ret) { # 0==EOF, undef=error
  303. CommandDelete(undef, $name);
  304. Log3 $FW_wname, 4, "Connection closed for $name: ".
  305. (defined($ret) ? 'EOF' : $!);
  306. return;
  307. }
  308. $hash->{BUF} .= $buf;
  309. if($hash->{SSL} && $c->can('pending')) {
  310. while($c->pending()) {
  311. sysread($c, $buf, 1024);
  312. $hash->{BUF} .= $buf;
  313. }
  314. }
  315. }
  316. if($hash->{websocket}) { # 59713
  317. my $fin = (ord(substr($hash->{BUF},0,1)) & 0x80)?1:0;
  318. my $op = (ord(substr($hash->{BUF},0,1)) & 0x0F);
  319. my $mask = (ord(substr($hash->{BUF},1,1)) & 0x80)?1:0;
  320. my $len = (ord(substr($hash->{BUF},1,1)) & 0x7F);
  321. my $i = 2;
  322. if( $len == 126 ) {
  323. $len = unpack( 'n', substr($hash->{BUF},$i,2) );
  324. $i += 2;
  325. } elsif( $len == 127 ) {
  326. $len = unpack( 'q', substr($hash->{BUF},$i,8) );
  327. $i += 8;
  328. }
  329. if( $mask ) {
  330. $mask = substr($hash->{BUF},$i,4);
  331. $i += 4;
  332. }
  333. #my $data = substr($hash->{BUF}, $i, $len);
  334. #for( my $i = 0; $i < $len; $i++ ) {
  335. # substr( $data, $i, 1, substr( $data, $i, 1, ) ^ substr($mask, $i% , 1) );
  336. #}
  337. #Log 1, "Received via websocket: ".unpack("H*",$data);
  338. $hash->{BUF} = "";
  339. return;
  340. }
  341. if(!$hash->{HDR}) {
  342. return if($hash->{BUF} !~ m/^(.*?)(\n\n|\r\n\r\n)(.*)$/s);
  343. $hash->{HDR} = $1;
  344. $hash->{BUF} = $3;
  345. if($hash->{HDR} =~ m/Content-Length:\s*([^\r\n]*)/si) {
  346. $hash->{CONTENT_LENGTH} = $1;
  347. }
  348. }
  349. my $POSTdata = "";
  350. if($hash->{CONTENT_LENGTH}) {
  351. return if(length($hash->{BUF})<$hash->{CONTENT_LENGTH});
  352. $POSTdata = substr($hash->{BUF}, 0, $hash->{CONTENT_LENGTH});
  353. $hash->{BUF} = substr($hash->{BUF}, $hash->{CONTENT_LENGTH});
  354. }
  355. @FW_httpheader = split(/[\r\n]+/, $hash->{HDR});
  356. %FW_httpheader = map {
  357. my ($k,$v) = split(/: */, $_, 2);
  358. $k =~ s/(\w+)/\u$1/g; # Forum #39203
  359. $k=>(defined($v) ? $v : 1);
  360. } @FW_httpheader;
  361. delete($hash->{HDR});
  362. my @origin = grep /Origin/i, @FW_httpheader;
  363. $FW_headerlines = (AttrVal($FW_wname, "CORS", 0) ?
  364. (($#origin<0) ? "": "Access-Control-Allow-".$origin[0]."\r\n").
  365. "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n".
  366. "Access-Control-Allow-Headers: Origin, Authorization, Accept\r\n".
  367. "Access-Control-Allow-Credentials: true\r\n".
  368. "Access-Control-Max-Age:86400\r\n".
  369. "Access-Control-Expose-Headers: X-FHEM-csrfToken\r\n": "");
  370. $FW_headerlines .= "X-FHEM-csrfToken: $defs{$FW_wname}{CSRFTOKEN}\r\n"
  371. if(defined($defs{$FW_wname}{CSRFTOKEN}) &&
  372. AttrVal($FW_wname, "csrfTokenHTTPHeader", 1));
  373. #########################
  374. # Return 200 for OPTIONS or 405 for unsupported method
  375. my ($method, $arg, $httpvers) = split(" ", $FW_httpheader[0], 3)
  376. if($FW_httpheader[0]);
  377. $method = "" if(!$method);
  378. my $ahm = AttrVal($FW_wname, "allowedHttpMethods", "GET|POST");
  379. if($method !~ m/^($ahm)$/i){
  380. my $retCode = ($method eq "OPTIONS") ? "200 OK" : "405 Method Not Allowed";
  381. TcpServer_WriteBlocking($FW_chash,
  382. "HTTP/1.1 $retCode\r\n" .
  383. $FW_headerlines.
  384. "Content-Length: 0\r\n\r\n");
  385. delete $hash->{CONTENT_LENGTH};
  386. FW_Read($hash, 1) if($hash->{BUF});
  387. Log 3, "$FW_cname: unsupported HTTP method $method, rejecting it."
  388. if($retCode ne "200 OK");
  389. FW_closeConn($hash);
  390. return;
  391. }
  392. #############################
  393. # AUTH
  394. if(!defined($FW_chash->{Authenticated})) {
  395. my $ret = Authenticate($FW_chash, \%FW_httpheader);
  396. if($ret == 0) {
  397. $FW_chash->{Authenticated} = 0; # not needed
  398. } elsif($ret == 1) {
  399. $FW_chash->{Authenticated} = 1; # ok
  400. # Need to send set-cookie (if set) after succesful authentication
  401. my $ah = $FW_chash->{".httpAuthHeader"};
  402. $FW_headerlines .= $ah if($ah);
  403. delete $FW_chash->{".httpAuthHeader"};
  404. } else {
  405. my $ah = $FW_chash->{".httpAuthHeader"};
  406. TcpServer_WriteBlocking($hash,
  407. ($ah ? $ah : "").
  408. $FW_headerlines.
  409. "Content-Length: 0\r\n\r\n");
  410. delete $hash->{CONTENT_LENGTH};
  411. FW_Read($hash, 1) if($hash->{BUF});
  412. return;
  413. }
  414. } else {
  415. my $ah = $FW_chash->{".httpAuthHeader"};
  416. $FW_headerlines .= $ah if($ah);
  417. }
  418. #############################
  419. my $now = time();
  420. $arg .= "&".$POSTdata if($POSTdata);
  421. delete $hash->{CONTENT_LENGTH};
  422. $hash->{LASTACCESS} = $now;
  423. $FW_userAgent = $FW_httpheader{"User-Agent"};
  424. $FW_userAgent = "" if(!defined($FW_userAgent));
  425. $FW_ME = "/" . AttrVal($FW_wname, "webname", "fhem");
  426. $FW_CSRF = (defined($defs{$FW_wname}{CSRFTOKEN}) ?
  427. "&fwcsrf=".$defs{$FW_wname}{CSRFTOKEN} : "");
  428. if($FW_use{sha} && $method eq 'GET' &&
  429. $FW_httpheader{Connection} && $FW_httpheader{Connection} =~ /Upgrade/i) {
  430. my $shastr = Digest::SHA::sha1_base64($FW_httpheader{'Sec-WebSocket-Key'}.
  431. "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
  432. TcpServer_WriteBlocking($FW_chash,
  433. "HTTP/1.1 101 Switching Protocols\r\n" .
  434. "Upgrade: websocket\r\n" .
  435. "Connection: Upgrade\r\n" .
  436. "Sec-WebSocket-Accept:$shastr=\r\n".
  437. "\r\n" );
  438. $FW_chash->{websocket} = 1;
  439. my $me = $FW_chash;
  440. my ($cmd, $cmddev) = FW_digestCgi($arg);
  441. if($FW_id) {
  442. $me->{FW_ID} = $FW_id;
  443. $me->{canAsyncOutput} = 1;
  444. }
  445. FW_initInform($me, 0) if($FW_inform);
  446. return -1;
  447. }
  448. $arg = "" if(!defined($arg));
  449. Log3 $FW_wname, 4, "$name $method $arg; BUFLEN:".length($hash->{BUF});
  450. my $pf = AttrVal($FW_wname, "plotfork", 0);
  451. if($pf) { # 0 disables
  452. # Process SVG rendering as a parallel process
  453. my $p = $data{FWEXT};
  454. if(grep { $p->{$_}{FORKABLE} && $arg =~ m+^$FW_ME$_+ } keys %{$p}) {
  455. my $pid = fhemFork();
  456. if($pid) { # success, parent
  457. use constant PRIO_PROCESS => 0;
  458. setpriority(PRIO_PROCESS, $pid, getpriority(PRIO_PROCESS,$pid) + $pf)
  459. if($^O !~ m/Win/);
  460. # a) while child writes a new request might arrive if client uses
  461. # pipelining or
  462. # b) parent doesn't know about ssl-session changes due to child writing
  463. # to socket
  464. # -> have to close socket in parent... so that its only used in this
  465. # child.
  466. TcpServer_Disown( $hash );
  467. delete($defs{$name});
  468. delete($attr{$name});
  469. FW_Read($hash, 1) if($hash->{BUF});
  470. return;
  471. } elsif(defined($pid)){ # child
  472. delete $hash->{BUF};
  473. $hash->{isChild} = 1;
  474. } # fork failed and continue in parent
  475. }
  476. }
  477. $FW_httpRetCode = "200 OK";
  478. my $cacheable = FW_answerCall($arg);
  479. if($cacheable == -1) {
  480. FW_closeConn($hash);
  481. return;
  482. }
  483. return if($cacheable == -2); # async op, well be answered later
  484. FW_finishRead($hash, $cacheable, $arg);
  485. }
  486. sub
  487. FW_finishRead($$$)
  488. {
  489. my ($hash, $cacheable, $arg) = @_;
  490. my $name = $hash->{NAME};
  491. my $compressed = "";
  492. if($FW_RETTYPE =~ m/(text|xml|json|svg|script)/i &&
  493. ($FW_httpheader{"Accept-Encoding"} &&
  494. $FW_httpheader{"Accept-Encoding"} =~ m/gzip/) &&
  495. $FW_use{zlib}) {
  496. utf8::encode($FW_RET)
  497. if(utf8::is_utf8($FW_RET) && $FW_RET =~ m/[^\x00-\xFF]/ );
  498. eval { $FW_RET = Compress::Zlib::memGzip($FW_RET); };
  499. if($@) {
  500. Log 1, "memGzip: $@"; $FW_RET=""; #Forum #29939
  501. } else {
  502. $compressed = "Content-Encoding: gzip\r\n";
  503. }
  504. }
  505. my $length = length($FW_RET);
  506. my $expires = ($cacheable?
  507. ("Expires: ".FmtDateTimeRFC1123($hash->{LASTACCESS}+900)."\r\n") : "");
  508. Log3 $FW_wname, 4,
  509. "$FW_wname: $arg / RL:$length / $FW_RETTYPE / $compressed / $expires";
  510. if( ! FW_addToWritebuffer($hash,
  511. "HTTP/1.1 $FW_httpRetCode\r\n" .
  512. "Content-Length: $length\r\n" .
  513. $expires . $compressed . $FW_headerlines .
  514. "Content-Type: $FW_RETTYPE\r\n\r\n" .
  515. $FW_RET, "FW_closeConn", 1) ){
  516. Log3 $name, 4, "Closing connection $name due to full buffer in FW_Read"
  517. if(!$hash->{isChild});
  518. FW_closeConn($hash);
  519. TcpServer_Close($hash, 1);
  520. }
  521. }
  522. sub
  523. FW_initInform($$)
  524. {
  525. my ($me, $longpoll) = @_;
  526. if($FW_inform =~ /type=/) {
  527. foreach my $kv (split(";", $FW_inform)) {
  528. my ($key,$value) = split("=", $kv, 2);
  529. $me->{inform}{$key} = $value;
  530. }
  531. } else { # Compatibility mode
  532. $me->{inform}{type} = ($FW_room ? "status" : "raw");
  533. $me->{inform}{filter} = ($FW_room ? $FW_room : ".*");
  534. }
  535. $FW_id2inform{$FW_id} = $me if($FW_id);
  536. my $filter = $me->{inform}{filter};
  537. $filter =~ s/([[\]().+?])/\\$1/g if($filter =~ m/room=/); # Forum #80390
  538. $filter = "NAME=.*" if($filter eq "room=all");
  539. $filter = "room!=.+" if($filter eq "room=Unsorted");
  540. my %h = map { $_ => 1 } devspec2array($filter);
  541. $h{global} = 1 if( $me->{inform}{addglobal} );
  542. $h{"#FHEMWEB:$FW_wname"} = 1;
  543. $me->{inform}{devices} = \%h;
  544. %FW_visibleDeviceHash = FW_visibleDevices();
  545. # NTFY_ORDER is larger than the normal order (50-)
  546. $me->{NTFY_ORDER} = $FW_cname; # else notifyfn won't be called
  547. %ntfyHash = ();
  548. $me->{inform}{since} = time()-5
  549. if(!defined($me->{inform}{since}) || $me->{inform}{since} !~ m/^\d+$/);
  550. my $sinceTimestamp = FmtDateTime($me->{inform}{since});
  551. if($longpoll) {
  552. TcpServer_WriteBlocking($me,
  553. "HTTP/1.1 200 OK\r\n".
  554. $FW_headerlines.
  555. "Content-Type: application/octet-stream; charset=$FW_encoding\r\n\r\n".
  556. FW_roomStatesForInform($me, $sinceTimestamp));
  557. } else { # websocket
  558. FW_addToWritebuffer($me,
  559. FW_roomStatesForInform($me, $sinceTimestamp));
  560. }
  561. if($FW_id && $defs{$FW_wname}{asyncOutput}) {
  562. my $data = $defs{$FW_wname}{asyncOutput}{$FW_id};
  563. if($data) {
  564. FW_addToWritebuffer($me, $data."\n");
  565. delete $defs{$FW_wname}{asyncOutput}{$FW_id};
  566. }
  567. }
  568. if($me->{inform}{withLog}) {
  569. $logInform{$me->{NAME}} = "FW_logInform";
  570. } else {
  571. delete($logInform{$me->{NAME}});
  572. }
  573. }
  574. sub
  575. FW_addToWritebuffer($$@)
  576. {
  577. my ($hash, $txt, $callback, $nolimit) = @_;
  578. if( $hash->{websocket} ) {
  579. my $len = length($txt);
  580. if( $len < 126 ) {
  581. $txt = chr(0x81) . chr($len) . $txt;
  582. } else {
  583. if ( $len < 65536 ) {
  584. $txt = chr(0x81) . chr(0x7E) . pack('n', $len) . $txt;
  585. } else {
  586. $txt = chr(0x81) . chr(0x7F) . chr(0x00) . chr(0x00) .
  587. chr(0x00) . chr(0x00) . pack('N', $len) . $txt;
  588. }
  589. }
  590. }
  591. return addToWritebuffer($hash, $txt, $callback, $nolimit);
  592. }
  593. sub
  594. FW_AsyncOutput($$)
  595. {
  596. my ($hash, $ret) = @_;
  597. return if(!$hash || !$hash->{FW_ID});
  598. if( $ret =~ m/^<html>(.*)<\/html>$/s ) {
  599. $ret = $1;
  600. } else {
  601. $ret = FW_htmlEscape($ret);
  602. $ret = "<pre>$ret</pre>" if($ret =~ m/\n/ );
  603. $ret =~ s/\n/<br>/g;
  604. }
  605. my $data = FW_longpollInfo('JSON',
  606. "#FHEMWEB:$FW_wname","FW_okDialog('$ret')","");
  607. # find the longpoll connection with the same fw_id as the page that was the
  608. # origin of the get command
  609. my $fwid = $hash->{FW_ID};
  610. if(!$fwid) {
  611. Log3 $hash->{SNAME}, 4, "AsyncOutput from $hash->{NAME} without FW_ID";
  612. return;
  613. }
  614. Log3 $hash->{SNAME}, 4, "AsyncOutput from $hash->{NAME}";
  615. $hash = $FW_id2inform{$fwid};
  616. if($hash) {
  617. FW_addToWritebuffer($hash, $data."\n");
  618. } else {
  619. $defs{$FW_wname}{asyncOutput}{$fwid} = $data;
  620. }
  621. return undef;
  622. }
  623. sub
  624. FW_closeConn($)
  625. {
  626. my ($hash) = @_;
  627. if(!$hash->{inform} && !$hash->{BUF}) { # Forum #41125
  628. my $cc = AttrVal($hash->{SNAME}, "closeConn",
  629. $FW_userAgent =~ m/(iPhone|iPad|iPod)/);
  630. if(!$FW_httpheader{Connection} || $cc) {
  631. TcpServer_Close($hash, 1);
  632. }
  633. }
  634. POSIX::exit(0) if($hash->{isChild});
  635. FW_Read($hash, 1) if($hash->{BUF});
  636. }
  637. ###########################
  638. sub
  639. FW_serveSpecial($$$$)
  640. {
  641. my ($file,$ext,$dir,$cacheable)= @_;
  642. $file =~ s,\.\./,,g; # little bit of security
  643. $file = "$FW_sp$file" if($ext eq "css" && -f "$dir/$FW_sp$file.$ext");
  644. $FW_RETTYPE = ext2MIMEType($ext);
  645. my $fname = ($ext ? "$file.$ext" : $file);
  646. return FW_returnFileAsStream("$dir/$fname", "", $FW_RETTYPE, 0, $cacheable);
  647. }
  648. sub
  649. FW_answerCall($)
  650. {
  651. my ($arg) = @_;
  652. my $me=$defs{$FW_cname}; # cache, else rereadcfg will delete us
  653. $FW_RET = "";
  654. $FW_RETTYPE = "text/html; charset=$FW_encoding";
  655. $MW_dir = "$attr{global}{modpath}/FHEM";
  656. $FW_sp = AttrVal($FW_wname, "stylesheetPrefix", "f18");
  657. $FW_ss = ($FW_sp =~ m/smallscreen/);
  658. $FW_tp = ($FW_sp =~ m/smallscreen|touchpad/);
  659. my $spDir = ($FW_sp eq "default" ? "" : "$FW_sp:");
  660. @FW_iconDirs = grep { $_ } split(":", AttrVal($FW_wname, "iconPath",
  661. "${spDir}fhemSVG:openautomation:default"));
  662. @FW_fhemwebjs = ("fhemweb.js");
  663. push(@FW_fhemwebjs, "$FW_sp.js") if(-r "$FW_dir/pgm2/$FW_sp.js");
  664. if($arg =~ m,$FW_ME/floorplan/([a-z0-9.:_]+),i) { # FLOORPLAN: special icondir
  665. unshift @FW_iconDirs, $1;
  666. FW_readIcons($1);
  667. }
  668. # /icons/... => current state of ...
  669. # also used for static images: unintended, but too late to change
  670. my ($dir1, $dirN, $ofile) = ($1, $2, $3)
  671. if($arg =~ m,^$FW_ME/([^/]*)(.*/)([^/]*)$,);
  672. if($arg =~ m,\brobots.txt$,) {
  673. Log3 $FW_wname, 1, "NOTE: $FW_wname is probed by a search engine";
  674. $FW_RETTYPE = "text/plain; charset=$FW_encoding";
  675. FW_pO "User-agent: *\r";
  676. FW_pO "Disallow: *\r";
  677. return 0;
  678. } elsif($arg =~ m,^$FW_ME/icons/(.*)$,) {
  679. my ($icon,$cacheable) = (urlDecode($1), 1);
  680. my $iconPath = FW_iconPath($icon);
  681. # if we do not have the icon, we convert the device state to the icon name
  682. if(!$iconPath) {
  683. my ($img, $link, $isHtml) = FW_dev2image($icon);
  684. $cacheable = 0;
  685. return 0 if(!$img);
  686. $iconPath = FW_iconPath($img);
  687. if($iconPath =~ m/\.svg$/i) {
  688. $FW_RETTYPE = ext2MIMEType("svg");
  689. FW_pO FW_makeImage($img, $img);
  690. return 0;
  691. }
  692. } elsif($iconPath =~ m/\.svg$/i && $icon=~ m/@/) {
  693. $FW_RETTYPE = ext2MIMEType("svg");
  694. FW_pO FW_makeImage($icon, $icon);
  695. return 0;
  696. }
  697. $iconPath =~ m/(.*)\.([^.]*)/;
  698. return FW_serveSpecial($1, $2, $FW_icondir, $cacheable);
  699. } elsif($dir1 && !$data{FWEXT}{"/$dir1"}) {
  700. my $dir = "$dir1$dirN";
  701. my $ext = "";
  702. $dir =~ s,/$,,;
  703. $dir =~ s/\.\.//g;
  704. $dir =~ s,www/,,g; # Want commandref.html to work from file://...
  705. my $file = urlDecode($ofile); # 69164
  706. $file =~ s/\?.*//; # Remove timestamp of CSS reloader
  707. if($file =~ m/^(.*)\.([^.]*)$/) {
  708. $file = $1; $ext = $2;
  709. }
  710. my $ldir = "$FW_dir/$dir";
  711. $ldir = "$FW_dir/pgm2" if($dir eq "css" || $dir eq "js"); # FLOORPLAN compat
  712. $ldir = "$attr{global}{modpath}/docs" if($dir eq "docs");
  713. # pgm2 check is for jquery-ui images
  714. my $static = ($ext =~ m/(css|js|png|jpg)/i || $dir =~ m/^pgm2/);
  715. my $fname = ($ext ? "$file.$ext" : $file);
  716. return FW_serveSpecial($file, $ext, $ldir, ($arg =~ m/nocache/) ? 0 : 1)
  717. if(-r "$ldir/$fname" || $static); # no return for FLOORPLAN
  718. $arg = "/$dir/$ofile";
  719. } elsif($arg =~ m/^$FW_ME(.*)/s) {
  720. $arg = $1; # The stuff behind FW_ME, continue to check for commands/FWEXT
  721. } else {
  722. Log3 $FW_wname, 4, "$FW_wname: redirecting $arg to $FW_ME";
  723. TcpServer_WriteBlocking($me,
  724. "HTTP/1.1 302 Found\r\n".
  725. "Content-Length: 0\r\n".
  726. $FW_headerlines.
  727. "Location: $FW_ME\r\n\r\n");
  728. FW_closeConn($FW_chash);
  729. return -1;
  730. }
  731. $FW_plotmode = AttrVal($FW_wname, "plotmode", "SVG");
  732. $FW_plotsize = AttrVal($FW_wname, "plotsize", $FW_ss ? "480,160" :
  733. $FW_tp ? "640,160" : "800,160");
  734. my ($cmd, $cmddev) = FW_digestCgi($arg);
  735. if($cmd && $FW_CSRF && $cmd !~ m/style (list|select|eventMonitor)/) {
  736. my $supplied = defined($FW_webArgs{fwcsrf}) ? $FW_webArgs{fwcsrf} : "";
  737. my $want = $defs{$FW_wname}{CSRFTOKEN};
  738. if($supplied ne $want) {
  739. Log3 $FW_wname, 3, "FHEMWEB $FW_wname CSRF error: $supplied ne $want ".
  740. "for client $FW_chash->{NAME} / command $cmd. ".
  741. "For details see the csrfToken FHEMWEB attribute.";
  742. $FW_httpRetCode = "400 Bad Request";
  743. return 0;
  744. }
  745. }
  746. if( $FW_id ) {
  747. $me->{FW_ID} = $FW_id;
  748. $me->{canAsyncOutput} = 1;
  749. }
  750. if($FW_inform) { # Longpoll header
  751. FW_initInform($me, 1);
  752. return -1;
  753. }
  754. my $docmd = 0;
  755. $docmd = 1 if($cmd &&
  756. $cmd !~ /^showlog/ &&
  757. $cmd !~ /^style / &&
  758. $cmd !~ /^edit/);
  759. #If we are in XHR or json mode, execute the command directly
  760. if($FW_XHR || $FW_jsonp) {
  761. $FW_cmdret = $docmd ? FW_fC($cmd, $cmddev) : undef;
  762. $FW_RETTYPE = $FW_chash->{contenttype} ?
  763. $FW_chash->{contenttype} : "text/plain; charset=$FW_encoding";
  764. delete($FW_chash->{contenttype});
  765. if($FW_jsonp) {
  766. $FW_cmdret =~ s/'/\\'/g;
  767. # Escape newlines in JavaScript string
  768. $FW_cmdret =~ s/\n/\\\n/g;
  769. FW_pO "$FW_jsonp('$FW_cmdret');";
  770. } else {
  771. $FW_cmdret = FW_addLinks($FW_cmdret) if($FW_webArgs{addLinks});
  772. FW_pO $FW_cmdret;
  773. }
  774. return 0;
  775. }
  776. ##############################
  777. # FHEMWEB extensions (FLOORPLOAN, SVG_WriteGplot, etc)
  778. my $FW_contentFunc;
  779. if(defined($data{FWEXT})) {
  780. foreach my $k (sort keys %{$data{FWEXT}}) {
  781. my $h = $data{FWEXT}{$k};
  782. next if($arg !~ m/^$k/);
  783. $FW_contentFunc = $h->{CONTENTFUNC};
  784. next if($h !~ m/HASH/ || !$h->{FUNC});
  785. #Returns undef as FW_RETTYPE if it already sent a HTTP header
  786. no strict "refs";
  787. ($FW_RETTYPE, $FW_RET) = &{$h->{FUNC}}($arg);
  788. if(defined($FW_RETTYPE) && $FW_RETTYPE =~ m,text/html,) {
  789. my $dataAttr = FW_dataAttr();
  790. $FW_RET =~ s/<body/<body $dataAttr/;
  791. }
  792. use strict "refs";
  793. return defined($FW_RETTYPE) ? 0 : -1;
  794. }
  795. }
  796. #Now execute the command
  797. $FW_cmdret = undef;
  798. if($docmd) {
  799. $FW_cmdret = FW_fC($cmd, $cmddev);
  800. if($cmd =~ m/^define +([^ ]+) /) { # "redirect" after define to details
  801. $FW_detail = $1;
  802. }
  803. elsif($cmd =~ m/^copy +([^ ]+) +([^ ]+)/) { # redirect define to details
  804. $FW_detail = $2;
  805. }
  806. }
  807. # Redirect after a command, to clean the browser URL window
  808. if($docmd && !defined($FW_cmdret) && AttrVal($FW_wname, "redirectCmds", 1)) {
  809. my $tgt = $FW_ME;
  810. if($FW_detail) { $tgt .= "?detail=$FW_detail&fw_id=$FW_id" }
  811. elsif($FW_room) { $tgt .= "?room=".urlEncode($FW_room)."&fw_id=$FW_id" }
  812. else { $tgt .= "?fw_id=$FW_id" }
  813. TcpServer_WriteBlocking($me,
  814. "HTTP/1.1 302 Found\r\n".
  815. "Content-Length: 0\r\n". $FW_headerlines.
  816. "Location: $tgt\r\n".
  817. "\r\n");
  818. return -1;
  819. }
  820. if($FW_lastWebName ne $FW_wname || $FW_lastHashUpdate != $lastDefChange) {
  821. FW_updateHashes();
  822. $FW_lastWebName = $FW_wname;
  823. $FW_lastHashUpdate = $lastDefChange;
  824. }
  825. my $hsh = "Home, Sweet Home";
  826. my $t = AttrVal($FW_wname, "title", AttrVal("global", "title", $hsh));
  827. $t = eval $t if($t =~ m/^{.*}$/s); # Forum #48668
  828. $t = $hsh if(!defined($t));
  829. FW_pO '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '.
  830. '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">';
  831. FW_pO '<html xmlns="http://www.w3.org/1999/xhtml">';
  832. FW_pO "<head root=\"$FW_ME\">\n<title>$t</title>";
  833. FW_pO '<link rel="shortcut icon" href="'.FW_IconURL("favicon").'" />';
  834. FW_pO "<meta charset=\"$FW_encoding\">"; # Forum 28666
  835. FW_pO "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">";#Forum 18316
  836. # Enable WebApps
  837. if($FW_tp || $FW_ss) {
  838. my $icon = FW_iconPath("fhemicon_ios.png");
  839. $icon = $FW_ME."/images/".($icon ? $icon : "default/fhemicon_ios.png");
  840. my $viewport = '';
  841. if($FW_ss) {
  842. my $stf = $FW_userAgent =~ m/iPad|iPhone|iPod/ ? ",shrink-to-fit=no" :"";
  843. $viewport = "initial-scale=1.0,user-scalable=1$stf";
  844. } elsif($FW_tp) {
  845. $viewport = "width=768";
  846. }
  847. $viewport = AttrVal($FW_wname, "viewport", $viewport);
  848. FW_pO '<meta name="viewport" content="'.$viewport.'"/>' if ($viewport);
  849. FW_pO '<meta name="apple-mobile-web-app-capable" content="yes"/>';
  850. FW_pO '<meta name="mobile-web-app-capable" content="yes"/>'; # Forum #36183
  851. FW_pO '<link rel="apple-touch-icon" href="'.$icon.'"/>';
  852. FW_pO '<link rel="shortcut-icon" href="'.$icon.'"/>';
  853. }
  854. if(!$FW_detail) {
  855. my $rf = AttrVal($FW_wname, "refresh", "");
  856. FW_pO "<meta http-equiv=\"refresh\" content=\"$rf\">" if($rf);
  857. }
  858. ########################
  859. # CSS
  860. my $cssTemplate = "<link href=\"$FW_ME/%s\" rel=\"stylesheet\"/>";
  861. FW_pO sprintf($cssTemplate, "pgm2/style.css?v=$FW_styleStamp");
  862. FW_pO sprintf($cssTemplate, "pgm2/jquery-ui.min.css");
  863. map { FW_pO sprintf($cssTemplate, $_); }
  864. split(" ", AttrVal($FW_wname, "CssFiles", ""));
  865. my $sd = AttrVal($FW_wname, "styleData", ""); # Avoid flicker in f18
  866. if($sd && $sd =~ m/"$FW_sp":/s) {
  867. my $bg;
  868. $bg = $1 if($FW_room && $sd =~ m/"Room\.$FW_room\.cols.bg": "([^"]*)"/s);
  869. $bg = $1 if(!defined($bg) && $sd =~ m/"cols.bg": "([^"]*)"/s);
  870. my $bgImg;
  871. $bgImg = $1 if($FW_room && $sd =~ m/"Room\.$FW_room\.bgImg": "([^"]*)"/s);
  872. $bgImg = $1 if(!defined($bgImg) && $sd =~ m/"bgImg": "([^"]*)"/s);
  873. FW_pO "<style id='style_css'>";
  874. FW_pO "body { background-color:#$bg; }" if($bg);
  875. FW_pO "body { background-image:url($FW_ME/images/background/$bgImg); }"
  876. if($bgImg);
  877. FW_pO "</style>";
  878. }
  879. my $css = AttrVal($FW_wname, "Css", "");
  880. FW_pO "<style id='fhemweb_css'>$css</style>\n" if($css);
  881. ########################
  882. # JavaScripts
  883. my $jsTemplate =
  884. '<script attr=\'%s\' type="text/javascript" src="%s"></script>';
  885. FW_pO sprintf($jsTemplate, "", "$FW_ME/pgm2/jquery.min.js");
  886. FW_pO sprintf($jsTemplate, "", "$FW_ME/pgm2/jquery-ui.min.js");
  887. my (%jsNeg, @jsList); # jsNeg was used to exclude automatically loaded files
  888. map { $_ =~ m/^-(.*)$/ ? $jsNeg{$1} = 1 : push(@jsList, $_); }
  889. split(" ", AttrVal($FW_wname, "JavaScripts", ""));
  890. map { FW_pO sprintf($jsTemplate, "", "$FW_ME/pgm2/$_") if(!$jsNeg{$_}); }
  891. @FW_fhemwebjs;
  892. #######################
  893. # "Own" JavaScripts + their Attributes
  894. map {
  895. my $n = $_; $n =~ s+.*/++; $n =~ s/.js$//; $n =~ s/fhem_//; $n .= "Param";
  896. FW_pO sprintf($jsTemplate, AttrVal($FW_wname, $n, ""), "$FW_ME/$_");
  897. } @jsList;
  898. ########################
  899. # FW Extensions
  900. if(defined($data{FWEXT})) {
  901. foreach my $k (sort keys %{$data{FWEXT}}) {
  902. my $h = $data{FWEXT}{$k};
  903. next if($h !~ m/HASH/ || !$h->{SCRIPT} || $h->{SCRIPT} =~ m+pgm2/jquery+);
  904. my $script = $h->{SCRIPT};
  905. $script = ($script =~ m,^/,) ? "$FW_ME$script" : "$FW_ME/pgm2/$script";
  906. FW_pO sprintf($jsTemplate, "", $script);
  907. }
  908. }
  909. my $csrf= ($FW_CSRF ? "fwcsrf='$defs{$FW_wname}{CSRFTOKEN}'" : "");
  910. my $gen = 'generated="'.(time()-1).'"';
  911. my $lp = 'longpoll="'.AttrVal($FW_wname,"longpoll",
  912. $FW_use{sha} && $FW_userAgent=~m/Chrome/ ? "websocket": 1).'"';
  913. $FW_id = $FW_chash->{NR} if( !$FW_id );
  914. my $dataAttr = FW_dataAttr();
  915. FW_pO "</head>\n<body name='$t' fw_id='$FW_id' $gen $lp $csrf $dataAttr>";
  916. if($FW_activateInform) {
  917. $cmd = "style eventMonitor $FW_activateInform";
  918. $FW_cmdret = undef;
  919. $FW_activateInform = "";
  920. }
  921. FW_roomOverview($cmd);
  922. if(defined($FW_cmdret)) {
  923. $FW_detail = "";
  924. $FW_room = "";
  925. if( $FW_cmdret =~ m/^<html>(.*)<\/html>$/s ) {
  926. $FW_cmdret = $1;
  927. } else { # "linkify" output (e.g. for list)
  928. $FW_cmdret = FW_addLinks(FW_htmlEscape($FW_cmdret));
  929. $FW_cmdret =~ s/:\S+//g if($FW_cmdret =~ m/unknown.*choose one of/i);
  930. $FW_cmdret = "<pre>$FW_cmdret</pre>" if($FW_cmdret =~ m/\n/);
  931. }
  932. FW_addContent();
  933. if($FW_ss) {
  934. FW_pO "<div class=\"tiny\">$FW_cmdret</div>";
  935. } else {
  936. FW_pO $FW_cmdret;
  937. }
  938. FW_pO "</div>";
  939. }
  940. if($FW_contentFunc) {
  941. no strict "refs";
  942. my $ret = &{$FW_contentFunc}($arg);
  943. use strict "refs";
  944. return $ret if($ret);
  945. }
  946. my $srVal = 0;
  947. if($cmd =~ m/^style /) { FW_style($cmd,undef); }
  948. elsif($FW_detail) { FW_doDetail($FW_detail); }
  949. elsif($FW_room) { $srVal = FW_showRoom(); }
  950. elsif(!defined($FW_cmdret) &&
  951. !$FW_contentFunc) {
  952. $FW_room = AttrVal($FW_wname, "defaultRoom", '');
  953. if($FW_room ne '') {
  954. $srVal = FW_showRoom();
  955. } else {
  956. my $motd = AttrVal("global","motd","none");
  957. if($motd ne "none") {
  958. FW_addContent("><pre class='motd'>$motd</pre></div");
  959. }
  960. }
  961. }
  962. return $srVal if($srVal);
  963. FW_pO "</body></html>";
  964. return 0;
  965. }
  966. sub
  967. FW_dataAttr()
  968. {
  969. sub
  970. addParam($$)
  971. {
  972. my ($p, $default) = @_;
  973. my $val = AttrVal($FW_wname,$p, $default);
  974. $val =~ s/&/&amp;/g;
  975. $val =~ s/'/&quot;/g;
  976. return "data-$p='$val' ";
  977. }
  978. return
  979. addParam("confirmDelete", 1).
  980. addParam("confirmJSError", 1).
  981. addParam("addHtmlTitle", 1).
  982. addParam("styleData", "").
  983. "data-availableJs='$FW_fhemwebjs' ".
  984. "data-webName='$FW_wname '";
  985. }
  986. sub
  987. FW_addContent(;$)
  988. {
  989. my $add = ($_[0] ? " $_[0]" : "");
  990. FW_pO "<div id='content' $add>";
  991. }
  992. sub
  993. FW_addLinks($)
  994. {
  995. my ($txt) = @_;
  996. return undef if(!defined($txt));
  997. $txt =~ s,\b([a-z0-9._]+)\b,
  998. $defs{$1} ? "<a href='$FW_ME$FW_subdir?detail=$1'>$1</a>" : $1,gei;
  999. return $txt;
  1000. }
  1001. ###########################
  1002. # Digest CGI parameters
  1003. sub
  1004. FW_digestCgi($)
  1005. {
  1006. my ($arg) = @_;
  1007. my (%arg, %val, %dev);
  1008. my ($cmd, $c) = ("","","");
  1009. %FW_pos = ();
  1010. $FW_room = "";
  1011. $FW_detail = "";
  1012. $FW_XHR = undef;
  1013. $FW_id = "";
  1014. $FW_jsonp = undef;
  1015. $FW_inform = undef;
  1016. %FW_webArgs = ();
  1017. #Remove (nongreedy) everything including the first '?'
  1018. $arg =~ s,^.*?[?],,;
  1019. foreach my $pv (split("&", $arg)) {
  1020. next if($pv eq ""); # happens when post forgot to set FW_ME
  1021. $pv =~ s/\+/ /g;
  1022. $pv =~ s/%([\dA-F][\dA-F])/chr(hex($1))/ige;
  1023. my ($p,$v) = split("=",$pv, 2);
  1024. $v = "" if(!defined($v));
  1025. # Multiline: escape the NL for fhem
  1026. $v =~ s/[\r]//g if($v && $p && $p ne "data");
  1027. $FW_webArgs{$p} = $v;
  1028. if($p eq "detail") { $FW_detail = $v; }
  1029. if($p eq "room") { $FW_room = $v; }
  1030. if($p eq "cmd") { $cmd = $v; }
  1031. if($p =~ m/^arg\.(.*)$/) { $arg{$1} = $v; }
  1032. if($p =~ m/^val\.(.*)$/) { $val{$1} = ($val{$1} ? $val{$1}.",$v" : $v) }
  1033. if($p =~ m/^dev\.(.*)$/) { $dev{$1} = $v; }
  1034. if($p =~ m/^cmd\.(.*)$/) { $cmd = $v; $c = $1; }
  1035. if($p eq "pos") { %FW_pos = split(/[=;]/, $v); }
  1036. if($p eq "data") { $FW_data = $v; }
  1037. if($p eq "XHR") { $FW_XHR = 1; }
  1038. if($p eq "fw_id") { $FW_id = $v; }
  1039. if($p eq "jsonp") { $FW_jsonp = $v; }
  1040. if($p eq "inform") { $FW_inform = $v; }
  1041. }
  1042. $cmd.=" $dev{$c}" if(defined($dev{$c}));
  1043. $cmd.=" $arg{$c}" if(defined($arg{$c}));
  1044. $cmd.=" $val{$c}" if(defined($val{$c}));
  1045. #replace unicode newline symbol \u2424 with real newline
  1046. my $nl = chr(226) . chr(144) . chr(164);
  1047. $cmd =~ s/$nl/\n/g;
  1048. return ($cmd, $c);
  1049. }
  1050. #####################
  1051. # create FW_rooms && FW_types
  1052. sub
  1053. FW_updateHashes()
  1054. {
  1055. %FW_rooms = (); # Make a room hash
  1056. %FW_groups = (); # Make a group hash
  1057. %FW_types = (); # Needed for type sorting
  1058. my $hre = AttrVal($FW_wname, "hiddenroomRegexp", "");
  1059. foreach my $d (keys %defs ) {
  1060. next if(IsIgnored($d));
  1061. foreach my $r (split(",", AttrVal($d, "room", "Unsorted"))) {
  1062. next if($hre && $r =~ m/$hre/);
  1063. $FW_rooms{$r}{$d} = 1;
  1064. }
  1065. foreach my $r (split(",", AttrVal($d, "group", ""))) {
  1066. $FW_groups{$r}{$d} = 1;
  1067. }
  1068. my $t = AttrVal($d, "subType", $defs{$d}{TYPE});
  1069. $t = AttrVal($d, "model", $t) if($t && $t eq "unknown"); # RKO: ???
  1070. $FW_types{$d} = $t;
  1071. }
  1072. $FW_room = AttrVal($FW_detail, "room", "Unsorted") if($FW_detail);
  1073. if(AttrVal($FW_wname, "sortRooms", "")) { # Slow!
  1074. my @sortBy = split( " ", AttrVal( $FW_wname, "sortRooms", "" ) );
  1075. my %sHash;
  1076. map { $sHash{$_} = FW_roomIdx(\@sortBy,$_) } keys %FW_rooms;
  1077. @FW_roomsArr = sort { $sHash{$a} cmp $sHash{$b} } keys %FW_rooms;
  1078. } else {
  1079. @FW_roomsArr = sort keys %FW_rooms;
  1080. }
  1081. }
  1082. ##############################
  1083. sub
  1084. FW_makeTable($$$@)
  1085. {
  1086. my($title, $name, $hash, $cmd) = (@_);
  1087. return if(!$hash || !int(keys %{$hash}));
  1088. my $class = lc($title);
  1089. $class =~ s/[^A-Za-z]/_/g;
  1090. FW_pO "<div class='makeTable wide ".lc($title)."'>";
  1091. FW_pO "<span class='mkTitle'>$title</span>";
  1092. FW_pO "<table class=\"block wide $class\">";
  1093. my $si = AttrVal("global", "showInternalValues", 0);
  1094. my $row = 1;
  1095. my $prefix = ($title eq "Attributes" ? "a-" : "");
  1096. foreach my $n (sort keys %{$hash}) {
  1097. next if(!$si && $n =~ m/^\./); # Skip "hidden" Values
  1098. my $val = $hash->{$n};
  1099. $val = "" if(!defined($val));
  1100. $val = $hash->{$n}{NAME} # Exception
  1101. if($n eq "IODev" && ref($val) eq "HASH" && defined($hash->{$n}{NAME}));
  1102. my $r = ref($val);
  1103. next if($r && ($r ne "HASH" || !defined($hash->{$n}{VAL})));
  1104. FW_pF "<tr class=\"%s\">", ($row&1)?"odd":"even";
  1105. $row++;
  1106. if($n eq "DEF" && !$FW_hiddenroom{input}) {
  1107. FW_makeEdit($name, $n, $val);
  1108. } else {
  1109. FW_pO "<td><div class=\"dname\" data-name=\"$name\">$n</div></td>";
  1110. if(ref($val)) { #handle readings
  1111. my ($v, $t) = ($val->{VAL}, $val->{TIME});
  1112. if($v =~ m,^<html>(.*)</html>$,) {
  1113. $v = $1;
  1114. } else {
  1115. $v = FW_htmlEscape($v);
  1116. $v = "<pre>$v</pre>" if($v =~ m/\n/);
  1117. }
  1118. my $ifid = "class='dval' informId='$name-$prefix$n'";
  1119. my $ifidts = "informId='$name-$prefix$n-ts'";
  1120. if($FW_ss) {
  1121. $t = ($t ? "<br><div class='tiny' $ifidts>$t</div>" : "");
  1122. FW_pO "<td><span $ifid>$v</span>$t</td>";
  1123. } else {
  1124. $t = "" if(!$t);
  1125. FW_pO "<td><div class='dval' $ifid>$v</div></td>";
  1126. FW_pO "<td><div $ifidts>$t</div></td>";
  1127. }
  1128. } else {
  1129. $val = FW_htmlEscape($val);
  1130. my $tattr = "informId=\"$name-$prefix$n\" class=\"dval\"";
  1131. # if possible provide some links
  1132. if ($n eq "room"){
  1133. FW_pO "<td><div $tattr>".
  1134. join(",", map { FW_pH("room=$_",$_,0,"",1,1) } split(",",$val)).
  1135. "</div></td>";
  1136. } elsif ($n =~ m/^fp_(.*)/ && $defs{$1}){ #special for Floorplan
  1137. FW_pH "detail=$1", $val,1;
  1138. } elsif ($modules{$val} ) {
  1139. FW_pH "cmd=list%20TYPE=$val", $val,1;
  1140. } else {
  1141. $val = "<pre>$val</pre>" if($val =~ m/\n/);
  1142. FW_pO "<td><div $tattr>".
  1143. join(",", map { ($_ ne $name && $defs{$_}) ?
  1144. FW_pH( "detail=$_", $_ ,0,"",1,1) : $_ } split(",",$val)).
  1145. "</div></td>";
  1146. }
  1147. }
  1148. }
  1149. FW_pH "cmd.$name=$cmd $name $n&amp;detail=$name", $cmd, 1
  1150. if($cmd && !$FW_ss);
  1151. FW_pO "</tr>";
  1152. }
  1153. FW_pO "</table>";
  1154. FW_pO "</div>";
  1155. }
  1156. ##############################
  1157. # Used only for set or attr lists.
  1158. sub
  1159. FW_detailSelect(@)
  1160. {
  1161. my ($d, $cmd, $list, $param) = @_;
  1162. return "" if(!$list || $FW_hiddenroom{input});
  1163. my %al = map { s/:.*//;$_ => 1 } split(" ", $list);
  1164. my @al = sort keys %al; # remove duplicate items in list
  1165. my $selEl = (defined($al[0]) ? $al[0] : " ");
  1166. $selEl = $1 if($list =~ m/([^ ]*):slider,/); # promote a slider if available
  1167. $selEl = "room" if($list =~ m/room:/);
  1168. $list =~ s/"/&quot;/g;
  1169. my $ret ="";
  1170. my $psc = AttrVal("global", "perlSyntaxCheck", ($featurelevel>5.7) ? 1 : 0);
  1171. $ret .= "<div class='makeSelect' dev=\"$d\" cmd=\"$cmd\" list=\"$list\">";
  1172. $ret .= "<form method=\"$FW_formmethod\" ".
  1173. "action=\"$FW_ME$FW_subdir\" autocomplete=\"off\">";
  1174. $ret .= FW_hidden("detail", $d);
  1175. $ret .= FW_hidden("dev.$cmd$d", $d.($param ? " $param":""));
  1176. $ret .= FW_submit("cmd.$cmd$d", $cmd, $cmd.($psc?" psc":""));
  1177. $ret .= "<div class=\"$cmd downText\">&nbsp;$d&nbsp;".
  1178. ($param ? "&nbsp;$param":"")."</div>";
  1179. $ret .= FW_select("sel_$cmd$d","arg.$cmd$d",\@al, $selEl, $cmd);
  1180. $ret .= FW_textfield("val.$cmd$d", 30, $cmd);
  1181. $ret .= "</form></div>";
  1182. return $ret;
  1183. }
  1184. ##############################
  1185. sub
  1186. FW_doDetail($)
  1187. {
  1188. my ($d) = @_;
  1189. return if($FW_hiddenroom{detail});
  1190. return if(!defined($defs{$d}));
  1191. my $h = $defs{$d};
  1192. my $t = $h->{TYPE};
  1193. $t = "MISSING" if(!defined($t));
  1194. FW_addContent();
  1195. if($FW_ss) {
  1196. my $webCmd = AttrVal($d, "webCmd", undef);
  1197. if($webCmd) {
  1198. FW_pO "<table class=\"webcmd\">";
  1199. foreach my $cmd (split(":", $webCmd)) {
  1200. FW_pO "<tr>";
  1201. FW_pH "cmd.$d=set $d $cmd&detail=$d", $cmd, 1, "col1";
  1202. FW_pO "</tr>";
  1203. }
  1204. FW_pO "</table>";
  1205. }
  1206. }
  1207. FW_pO "<table><tr><td>";
  1208. if(!$modules{$t}{FW_detailFn} || $modules{$t}{FW_deviceOverview}) {
  1209. my $show = AttrVal($FW_wname, "deviceOverview", "always");
  1210. if( $show ne 'never' ) {
  1211. my %extPage = ();
  1212. if( $show eq 'iconOnly' ) {
  1213. my ($allSets, $cmdlist, $txt) = FW_devState($d, $FW_room, \%extPage);
  1214. FW_pO "<div informId='$d'".
  1215. ($FW_tp?"":" style='float:right'").">$txt</div>";
  1216. } else {
  1217. my $nameDisplay = AttrVal($FW_wname,"nameDisplay",undef);
  1218. my %usuallyAtEnd = ();
  1219. my $style = "";
  1220. if( $show eq 'onClick' ) {
  1221. my $pgm = "Javascript:" .
  1222. "s=document.getElementById('ddtable').style;".
  1223. "s.display = s.display=='none' ? 'block' : 'none';".
  1224. "s=document.getElementById('ddisp').style;".
  1225. "s.display = s.display=='none' ? 'block' : 'none';";
  1226. FW_pO "<div id=\"ddisp\"><br><a style=\"cursor:pointer\" ".
  1227. "onClick=\"$pgm\">Show DeviceOverview</a><br><br></div>";
  1228. $style = 'style="display:none"';
  1229. }
  1230. FW_pO "<div $style id=\"ddtable\" class='makeTable wide'>";
  1231. FW_pO "<span class='mkTitle'>DeviceOverview</span>";
  1232. FW_pO "<table class=\"block wide\">";
  1233. FW_makeDeviceLine($d,1,\%extPage,$nameDisplay,\%usuallyAtEnd);
  1234. FW_pO "</table></div>";
  1235. }
  1236. }
  1237. }
  1238. if($modules{$t}{FW_detailFn}) {
  1239. no strict "refs";
  1240. my $txt = &{$modules{$t}{FW_detailFn}}($FW_wname, $d, $FW_room);
  1241. FW_pO "</td></tr><tr><td>$txt<br>" if(defined($txt));
  1242. use strict "refs";
  1243. }
  1244. FW_pO FW_detailSelect($d, "set",
  1245. FW_widgetOverride($d, getAllSets($d, $FW_chash)));
  1246. FW_pO FW_detailSelect($d, "get",
  1247. FW_widgetOverride($d, getAllGets($d, $FW_chash)));
  1248. FW_makeTable("Internals", $d, $h);
  1249. FW_makeTable("Readings", $d, $h->{READINGS});
  1250. my $attrList = getAllAttr($d);
  1251. my $roomList = "multiple,".join(",",
  1252. sort map { $_ =~ s/ /#/g ;$_} keys %FW_rooms);
  1253. my $groupList = "multiple,".join(",",
  1254. sort map { $_ =~ s/ /#/g ;$_} keys %FW_groups);
  1255. $attrList =~ s/room /room:$roomList /;
  1256. $attrList =~ s/group /group:$groupList /;
  1257. $attrList = FW_widgetOverride($d, $attrList);
  1258. $attrList =~ s/\\/\\\\/g;
  1259. $attrList =~ s/'/\\'/g;
  1260. FW_pO FW_detailSelect($d, "attr", $attrList);
  1261. FW_makeTable("Attributes", $d, $attr{$d}, "deleteattr");
  1262. FW_makeTableFromArray("Probably associated with", "assoc", getPawList($d));
  1263. FW_pO "</td></tr></table>";
  1264. my ($link, $txt, $td, $class, $doRet,$nonl) = @_;
  1265. FW_pH "cmd=style iconFor $d", "Select icon", undef, "detLink iconFor";
  1266. FW_pH "cmd=style showDSI $d", "Extend devStateIcon", undef, "detLink showDSI";
  1267. FW_pH "cmd=rawDef $d", "Raw definition", undef, "detLink rawDef";
  1268. FW_pH "cmd=delete $d", "Delete this device ($d)", undef, "detLink delDev"
  1269. if($d ne "global");
  1270. my $sfx = AttrVal("global", "language", "EN");
  1271. $sfx = ($sfx eq "EN" ? "" : "_$sfx");
  1272. FW_pH "$FW_ME/docs/commandref${sfx}.html#${t}", "Device specific help",
  1273. undef, "detLink devSpecHelp";
  1274. FW_pO "<br><br>";
  1275. FW_pO "</div>";
  1276. }
  1277. ##############################
  1278. sub
  1279. FW_makeTableFromArray($$@) {
  1280. my ($txt,$class,@obj) = @_;
  1281. if (@obj>0) {
  1282. my $row=1;
  1283. FW_pO "<div class='makeTable wide'>";
  1284. FW_pO "<span class='mkTitle'>$txt</span>";
  1285. FW_pO "<table class=\"block wide $class\">";
  1286. foreach (sort @obj) {
  1287. FW_pF "<tr class=\"%s\"><td>", (($row++)&1)?"odd":"even";
  1288. FW_pH "detail=$_", $_;
  1289. FW_pO "</td><td>";
  1290. FW_pO $defs{$_}{STATE} if(defined($defs{$_}{STATE}));
  1291. FW_pO "</td><td>";
  1292. FW_pH "cmd=list TYPE=$defs{$_}{TYPE}", $defs{$_}{TYPE};
  1293. FW_pO "</td>";
  1294. FW_pO "</tr>";
  1295. }
  1296. FW_pO "</table></div>";
  1297. }
  1298. }
  1299. sub
  1300. FW_roomIdx($$)
  1301. {
  1302. my ($arr,$v) = @_;
  1303. my ($index) = grep { $v =~ /^$arr->[$_]$/ } 0..$#$arr;
  1304. if( !defined($index) ) {
  1305. $index = 9999;
  1306. } else {
  1307. $index = sprintf( "%03i", $index );
  1308. }
  1309. return "$index-$v";
  1310. }
  1311. ##############
  1312. # Header, Zoom-Icons & list of rooms at the left.
  1313. sub
  1314. FW_roomOverview($)
  1315. {
  1316. my ($cmd) = @_;
  1317. %FW_hiddenroom = ();
  1318. map { $FW_hiddenroom{$_}=1 } split(",",AttrVal($FW_wname,"hiddenroom", ""));
  1319. map { $FW_hiddenroom{$_}=1 } split(",",AttrVal($FW_wname,"forbiddenroom",""));
  1320. ##############
  1321. # LOGO
  1322. my $hasMenuScroll;
  1323. if($FW_detail && $FW_ss) {
  1324. $FW_room = AttrVal($FW_detail, "room", undef);
  1325. $FW_room = $1 if($FW_room && $FW_room =~ m/^([^,]*),/);
  1326. $FW_room = "" if(!$FW_room);
  1327. FW_pO(FW_pHPlain("room=$FW_room",
  1328. "<div id=\"back\">" . FW_makeImage("back") . "</div>"));
  1329. FW_pO "<div id=\"menu\">$FW_detail details</div>";
  1330. return;
  1331. } else {
  1332. $hasMenuScroll = 1;
  1333. FW_pO '<div id="menuScrollArea">';
  1334. FW_pH "", '<div id="logo"></div>';
  1335. }
  1336. ##############
  1337. # MENU
  1338. my (@list1, @list2);
  1339. push(@list1, ""); push(@list2, "");
  1340. if(!$FW_hiddenroom{save} && !$FW_hiddenroom{"Save config"}) {
  1341. push(@list1, "Save config");
  1342. push(@list2, "$FW_ME?cmd=save");
  1343. push(@list1, ""); push(@list2, "");
  1344. }
  1345. ########################
  1346. # Show FW Extensions in the menu
  1347. if(defined($data{FWEXT})) {
  1348. my $cnt = 0;
  1349. foreach my $k (sort keys %{$data{FWEXT}}) {
  1350. my $h = $data{FWEXT}{$k};
  1351. next if($h !~ m/HASH/ || !$h->{LINK} || !$h->{NAME});
  1352. next if($FW_hiddenroom{$h->{NAME}});
  1353. push(@list1, $h->{NAME});
  1354. push(@list2, $FW_ME ."/".$h->{LINK});
  1355. $cnt++;
  1356. }
  1357. if($cnt > 0) {
  1358. push(@list1, ""); push(@list2, "");
  1359. }
  1360. }
  1361. $FW_room = "" if(!$FW_room);
  1362. ##########################
  1363. # Rooms and other links
  1364. foreach my $r (@FW_roomsArr) {
  1365. next if($r eq "hidden" || $FW_hiddenroom{$r});
  1366. $FW_room = AttrVal($FW_wname, "defaultRoom", $r)
  1367. if(!$FW_room && $FW_ss);
  1368. push @list1, FW_htmlEscape($r);
  1369. push @list2, "$FW_ME?room=".urlEncode($r);
  1370. }
  1371. my $sfx = AttrVal("global", "language", "EN");
  1372. $sfx = ($sfx eq "EN" ? "" : "_$sfx");
  1373. my @list = (
  1374. "Everything", "$FW_ME?room=all",
  1375. "", "",
  1376. "Commandref", "$FW_ME/docs/commandref${sfx}.html",
  1377. "Remote doc", "http://fhem.de/fhem.html#Documentation",
  1378. "Edit files", "$FW_ME?cmd=style%20list",
  1379. "Select style", "$FW_ME?cmd=style%20select",
  1380. "Event monitor", "$FW_ME?cmd=style%20eventMonitor",
  1381. "", "");
  1382. my $lastname = ","; # Avoid double "".
  1383. my $lfn = "Logfile";
  1384. if($defs{$lfn}) { # Add the current Logfile to the list if defined
  1385. my @l = FW_fileList($defs{$lfn}{logfile},1);
  1386. my $fn = pop @l;
  1387. splice @list, 4,0, ("Logfile",
  1388. "$FW_ME/FileLog_logWrapper?dev=$lfn&type=text&file=$fn");
  1389. }
  1390. my @me = split(",", AttrVal($FW_wname, "menuEntries", ""));
  1391. push @list, @me, "", "" if(@me);
  1392. for(my $idx = 0; $idx < @list; $idx+= 2) {
  1393. next if($FW_hiddenroom{$list[$idx]} || $list[$idx] eq $lastname);
  1394. push @list1, $list[$idx];
  1395. push @list2, $list[$idx+1];
  1396. $lastname = $list[$idx];
  1397. }
  1398. FW_pO "<div id=\"menu\">";
  1399. FW_pO "<table>";
  1400. if($FW_ss) { # Make a selection sensitive dropdown list
  1401. FW_pO "<tr><td><select OnChange=\"location.href=" .
  1402. "this.options[this.selectedIndex].value\">";
  1403. foreach(my $idx = 0; $idx < @list1; $idx++) {
  1404. next if(!$list1[$idx]);
  1405. my $sel = ($list1[$idx] eq $FW_room ? " selected=\"selected\"" : "");
  1406. FW_pO "<option value='$list2[$idx]'$sel>$list1[$idx]</option>";
  1407. }
  1408. FW_pO "</select></td>";
  1409. FW_pO "</tr>";
  1410. } else {
  1411. my $tblnr = 1;
  1412. my $roomEscaped = FW_htmlEscape($FW_room);
  1413. foreach(my $idx = 0; $idx < @list1; $idx++) {
  1414. my ($l1, $l2) = ($list1[$idx], $list2[$idx]);
  1415. if(!$l1) {
  1416. FW_pO "</table></td></tr>" if($idx);
  1417. if($idx<int(@list1)-1) {
  1418. FW_pO "<tr><td><table class=\"room roomBlock$tblnr\">";
  1419. $tblnr++;
  1420. }
  1421. } else {
  1422. FW_pF "<tr%s>", $l1 eq $roomEscaped ? " class=\"sel\"" : "";
  1423. my $class = "menu_$l1";
  1424. $class =~ s/[^A-Z0-9]/_/gi;
  1425. # image tag if we have an icon, else empty
  1426. my $icoName = "ico$l1";
  1427. map { my ($n,$v) = split(":",$_); $icoName=$v if($l1 =~ m/^$n$/); }
  1428. split(" ", AttrVal($FW_wname, "roomIcons", ""));
  1429. my $icon = FW_iconName($icoName) ?
  1430. FW_makeImage($icoName,$icoName,"icon")."&nbsp;" : "";
  1431. if($l1 eq "Save config") {
  1432. $l1 .= '</span></a> '.
  1433. '<a id="saveCheck" class="changed" style="visibility:'.
  1434. (int(@structChangeHist) ? 'visible' : 'hidden').'"><span>?';
  1435. }
  1436. # Force external browser if FHEMWEB is installed as an offline app.
  1437. my $target = ''; # Forum 33066, 39854
  1438. $target = 'target="_blank"' if($l2 =~ s/^$FW_ME\/\+/$FW_ME\//);
  1439. $target = 'target="_blank"' if($l2 =~ m/commandref|fhem.de.fhem.html/);
  1440. if($l2 =~ m/.html$/ || $l2 =~ m/^(http|javascript)/ || length($target)){
  1441. FW_pO "<td><div><a href='$l2' $target>$icon<span>$l1</span></a>".
  1442. "</div></td>";
  1443. } else {
  1444. FW_pH $l2, "$icon<span>$l1</span>", 1, $class;
  1445. }
  1446. FW_pO "</tr>";
  1447. }
  1448. }
  1449. }
  1450. FW_pO "</table>";
  1451. FW_pO "</div>";
  1452. FW_pO "</div>" if($hasMenuScroll);
  1453. ##############
  1454. # HEADER
  1455. FW_pO "<div id=\"hdr\">";
  1456. FW_pO '<table border="0" class="header"><tr><td style="padding:0">';
  1457. FW_pO "<form method=\"$FW_formmethod\" action=\"$FW_ME\">";
  1458. FW_pO FW_hidden("fw_id", $FW_id) if($FW_id);
  1459. FW_pO FW_hidden("room", $FW_room) if($FW_room);
  1460. FW_pO FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
  1461. FW_pO FW_textfield("cmd",
  1462. AttrVal($FW_wname, "mainInputLength", $FW_ss ? 25 : 40), "maininput");
  1463. FW_pO "</form>";
  1464. FW_pO "</td></tr></table>";
  1465. FW_pO "</div>";
  1466. }
  1467. sub
  1468. FW_alias($)
  1469. {
  1470. my ($d) = @_;
  1471. if($FW_room) {
  1472. return AttrVal($d, "alias_$FW_room", AttrVal($d, "alias", $d));
  1473. } else {
  1474. return AttrVal($d, "alias", $d);
  1475. }
  1476. }
  1477. sub
  1478. FW_makeDeviceLine($$$$$)
  1479. {
  1480. my ($d,$row,$extPage,$nameDisplay,$usuallyAtEnd) = @_;
  1481. my $rf = ($FW_room ? "&amp;room=$FW_room" : ""); # stay in the room
  1482. FW_pF "\n<tr class=\"%s\">", ($row&1)?"odd":"even";
  1483. my $devName = FW_alias($d);
  1484. if(defined($nameDisplay)) {
  1485. my ($DEVICE, $ALIAS) = ($d, $devName);
  1486. $devName = eval $nameDisplay;
  1487. }
  1488. my $icon = AttrVal($d, "icon", "");
  1489. $icon = FW_makeImage($icon,$icon,"icon") . "&nbsp;" if($icon);
  1490. $devName="" if($modules{$defs{$d}{TYPE}}{FW_hideDisplayName}); # Forum 88667
  1491. if(!$usuallyAtEnd->{$d}) {
  1492. if($FW_hiddenroom{detail}) {
  1493. FW_pO "<td><div class=\"col1\">$icon$devName</div></td>";
  1494. } else {
  1495. FW_pH "detail=$d", "$icon$devName", 1, "col1";
  1496. }
  1497. }
  1498. my ($allSets, $cmdlist, $txt) = FW_devState($d, $rf, $extPage);
  1499. if($cmdlist) {
  1500. my $cl2 = $cmdlist; $cl2 =~ s/ [^:]*//g; $cl2 =~ s/:/ /g; # Forum #74053
  1501. $allSets = "$allSets $cl2";
  1502. }
  1503. $allSets = FW_widgetOverride($d, $allSets);
  1504. my $colSpan = ($usuallyAtEnd->{$d} ? ' colspan="2"' : '');
  1505. FW_pO "<td informId=\"$d\"$colSpan>$txt</td>";
  1506. ######
  1507. # Commands, slider, dropdown
  1508. my $smallscreenCommands = AttrVal($FW_wname, "smallscreenCommands", "");
  1509. if((!$FW_ss || $smallscreenCommands) && $cmdlist) {
  1510. my @a = split("[: ]", AttrVal($d, "cmdIcon", ""));
  1511. Log 1, "ERROR: bad cmdIcon definition for $d" if(@a % 2);
  1512. my %cmdIcon = @a;
  1513. my @cl = split(":", $cmdlist);
  1514. my @wcl = split(":", AttrVal($d, "webCmdLabel", ""));
  1515. my $nRows;
  1516. $nRows = split("\n", AttrVal($d, "webCmdLabel", "")) if(@wcl);
  1517. @wcl = () if(@wcl != @cl); # some safety
  1518. for(my $i1=0; $i1<@cl; $i1++) {
  1519. my $cmd = $cl[$i1];
  1520. my $htmlTxt;
  1521. my @c = split(' ', $cmd); # @c==0 if $cmd==" ";
  1522. if(int(@c) && $allSets && $allSets =~ m/\b$c[0]:([^ ]*)/) {
  1523. my $values = $1;
  1524. foreach my $fn (sort keys %{$data{webCmdFn}}) {
  1525. no strict "refs";
  1526. $htmlTxt = &{$data{webCmdFn}{$fn}}($FW_wname,
  1527. $d, $FW_room, $cmd, $values);
  1528. use strict "refs";
  1529. last if(defined($htmlTxt));
  1530. }
  1531. }
  1532. if($htmlTxt) {
  1533. $htmlTxt =~ s,^<td[^>]*>(.*)</td>$,$1,;
  1534. } else {
  1535. my $nCmd = $cmdIcon{$cmd} ?
  1536. FW_makeImage($cmdIcon{$cmd},$cmd,"webCmd") : $cmd;
  1537. $htmlTxt = FW_pH "cmd.$d=set $d $cmd$rf", $nCmd, 0, "", 1, 1;
  1538. }
  1539. if(@wcl > $i1) {
  1540. if($nRows > 1) {
  1541. FW_pO "<td><table class='wide'><tr>" if($i1 == 0);
  1542. FW_pO "<td>$wcl[$i1]</td><td>$htmlTxt</td>";
  1543. FW_pO "</tr><tr>" if($wcl[$i1] =~ m/\n/);
  1544. FW_pO "</tr></table></td>" if($i1 == @cl-1);
  1545. } else {
  1546. FW_pO "<td><div class='col3'>$wcl[$i1]$ htmlTxt</div></td>";
  1547. }
  1548. } else {
  1549. FW_pO "<td><div class='col3'>$htmlTxt</div></td>";
  1550. }
  1551. }
  1552. }
  1553. FW_pO "</tr>";
  1554. }
  1555. sub
  1556. FW_sortIndex($)
  1557. {
  1558. my ($d) = @_;
  1559. return $d if(!$attr{$d});
  1560. my $val = $attr{$d}{sortby};
  1561. if($val) {
  1562. if($val =~ m/^{.*}/) {
  1563. my %specials=("%NAME" => $d);
  1564. my $exec = EvalSpecials($val, %specials);
  1565. return AnalyzePerlCommand($FW_chash, $exec);
  1566. }
  1567. return lc($val);
  1568. }
  1569. if($FW_room) {
  1570. $val = $attr{$d}{"alias_$FW_room"};
  1571. return $val if($val);
  1572. }
  1573. $val = $attr{$d}{"alias"};
  1574. return $val if($val);
  1575. return $d;
  1576. }
  1577. ########################
  1578. # Show the overview of devices in one room
  1579. # room can be a room, all or Unsorted
  1580. sub
  1581. FW_showRoom()
  1582. {
  1583. return 0 if(!$FW_room ||
  1584. AttrVal($FW_wname,"forbiddenroom","") =~ m/\b$FW_room\b/);
  1585. %FW_hiddengroup = ();
  1586. foreach my $r (split(",",AttrVal($FW_wname, "hiddengroup", ""))) {
  1587. $FW_hiddengroup{$r} = 1;
  1588. }
  1589. my $hge = AttrVal($FW_wname, "hiddengroupRegexp", undef);
  1590. FW_pO "<form method=\"$FW_formmethod\" ". # Why do we need a form here?
  1591. "action=\"$FW_ME\" autocomplete=\"off\">";
  1592. FW_addContent("room='$FW_room'");
  1593. FW_pO "<table class=\"roomoverview\">"; # Need for equal width of subtables
  1594. # array of all device names in the room (exception weblinks without group
  1595. # attribute)
  1596. my @devs= grep { (($FW_rooms{$FW_room} && $FW_rooms{$FW_room}{$_}) ||
  1597. $FW_room eq "all") && !IsIgnored($_) } keys %defs;
  1598. my (%group, @atEnds, %usuallyAtEnd, %sortIndex);
  1599. my $nDevsInRoom = 0;
  1600. foreach my $dev (@devs) {
  1601. if($modules{$defs{$dev}{TYPE}}{FW_atPageEnd}) {
  1602. $usuallyAtEnd{$dev} = 1;
  1603. if(!AttrVal($dev, "group", undef)) {
  1604. $sortIndex{$dev} = FW_sortIndex($dev);
  1605. push @atEnds, $dev;
  1606. next;
  1607. }
  1608. }
  1609. next if(!$FW_types{$dev}); # FHEMWEB connection, missed due to caching
  1610. foreach my $grp (split(",", AttrVal($dev, "group", $FW_types{$dev}))) {
  1611. next if($FW_hiddengroup{$grp});
  1612. next if($hge && $grp =~ m/$hge/);
  1613. $sortIndex{$dev} = FW_sortIndex($dev);
  1614. $group{$grp}{$dev} = 1;
  1615. $nDevsInRoom++;
  1616. }
  1617. }
  1618. # row counter
  1619. my $row=1;
  1620. my %extPage = ();
  1621. my $nameDisplay = AttrVal($FW_wname,"nameDisplay",undef);
  1622. my ($columns, $maxc) = FW_parseColumns(\%group);
  1623. FW_pO "<tr class=\"column\">" if($maxc != -1);
  1624. for(my $col=1; $col < ($maxc==-1 ? 2 : $maxc); $col++) {
  1625. FW_pO "<td><table class=\"column tblcol_$col\">" if($maxc != -1);
  1626. # iterate over the distinct groups
  1627. foreach my $g (sort { $maxc==-1 ?
  1628. $a cmp $b :
  1629. ($columns->{$a} ? $columns->{$a}->[0] : 99) <=>
  1630. ($columns->{$b} ? $columns->{$b}->[0] : 99) } keys %group) {
  1631. next if($maxc != -1 && (!$columns->{$g} || $columns->{$g}->[1] != $col));
  1632. #################
  1633. # Check if there is a device of this type in the room
  1634. FW_pO "<tr class='devTypeTr'><td><div class='devType'>$g</div></td></tr>";
  1635. FW_pO "<tr><td>";
  1636. FW_pO "<table class=\"block wide\" id=\"TYPE_$g\">";
  1637. foreach my $d (sort { $sortIndex{$a} cmp $sortIndex{$b} }
  1638. keys %{$group{$g}}) {
  1639. my $type = $defs{$d}{TYPE};
  1640. $extPage{group} = $g;
  1641. FW_makeDeviceLine($d,$row,\%extPage,$nameDisplay,\%usuallyAtEnd);
  1642. if($modules{$type}{FW_addDetailToSummary}) {
  1643. no strict "refs";
  1644. my $txt = &{$modules{$type}{FW_detailFn}}($FW_wname, $d, $FW_room);
  1645. use strict "refs";
  1646. if(defined($txt)) {
  1647. FW_pO "<tr class='".($row&1?"odd":"even").
  1648. "'><td colspan='50'>$txt</td></tr>";
  1649. }
  1650. }
  1651. $row++;
  1652. }
  1653. FW_pO "</table>";
  1654. FW_pO "</td></tr>";
  1655. }
  1656. FW_pO "</table></td>" if($maxc != -1); # Column
  1657. }
  1658. FW_pO "</tr>" if($maxc != -1);
  1659. FW_pO "</table>";
  1660. FW_pO "<br>" if(@atEnds && $nDevsInRoom);
  1661. # Now the "atEnds"
  1662. my $doBC = (AttrVal($FW_wname, "plotfork", 0) &&
  1663. AttrVal($FW_wname, "plotEmbed", 0) == 0);
  1664. my %res;
  1665. my ($idx,$svgIdx) = (1,1);
  1666. @atEnds = sort { $sortIndex{$a} cmp $sortIndex{$b} } @atEnds;
  1667. $FW_svgData{$FW_cname} = { FW_RET=>$FW_RET, RES=>\%res, ATENDS=>\@atEnds };
  1668. foreach my $d (@atEnds) {
  1669. no strict "refs";
  1670. my $fn = $modules{$defs{$d}{TYPE}}{FW_summaryFn};
  1671. $extPage{group} = "atEnd";
  1672. $extPage{index} = $idx++;
  1673. if($doBC && $defs{$d}{TYPE} eq "SVG" && $FW_use{base64}) {
  1674. $extPage{svgIdx} = $svgIdx++;
  1675. BlockingCall(sub {
  1676. return "$FW_cname,$d,".
  1677. encode_base64(&{$fn}($FW_wname,$d,$FW_room,\%extPage),'');
  1678. }, undef, "FW_svgCollect");
  1679. } else {
  1680. $res{$d} = &{$fn}($FW_wname,$d,$FW_room,\%extPage);
  1681. }
  1682. use strict "refs";
  1683. }
  1684. return FW_svgDone(\%res, \@atEnds, undef);
  1685. }
  1686. sub
  1687. FW_svgDone($$$)
  1688. {
  1689. my ($res, $atEnds, $delayedReturn) = @_;
  1690. return -2 if(int(keys %{$res}) != int(@{$atEnds}));
  1691. foreach my $d (@{$atEnds}) {
  1692. FW_pO $res->{$d};
  1693. }
  1694. FW_pO "</div>";
  1695. FW_pO "</form>";
  1696. FW_pO "</body></html>" if($delayedReturn);
  1697. return 0;
  1698. }
  1699. sub
  1700. FW_svgCollect($)
  1701. {
  1702. my ($cname,$d,$enc) = split(",",$_[0],3);
  1703. my $h = $FW_svgData{$cname};
  1704. my ($res, $atEnds) = ($h->{RES}, $h->{ATENDS});
  1705. $res->{$d} = decode_base64($enc);
  1706. return if(int(keys %{$res}) != int(@{$atEnds}));
  1707. $FW_RET = $h->{FW_RET};
  1708. delete($FW_svgData{$cname});
  1709. FW_svgDone($res, $atEnds, 1);
  1710. FW_finishRead($defs{$cname}, 0, "");
  1711. }
  1712. # Room1:col1group1,col1group2|col2group1,col2group2 Room2:...
  1713. sub
  1714. FW_parseColumns($)
  1715. {
  1716. my ($aGroup) = @_;
  1717. my %columns;
  1718. my $colNo = -1;
  1719. foreach my $roomgroup (split("[ \t\r\n]+", AttrVal($FW_wname,"column",""))) {
  1720. my ($room, $groupcolumn)=split(":",$roomgroup,2);
  1721. $room =~ s/%20/ /g; # Space
  1722. next if(!defined($groupcolumn) || $FW_room !~ m/^$room$/);
  1723. $colNo = 1;
  1724. my @grouplist = keys %$aGroup;
  1725. my %handled;
  1726. foreach my $groups (split(/\|/,$groupcolumn)) {
  1727. my $lineNo = 1;
  1728. foreach my $group (split(",",$groups)) {
  1729. $group =~ s/%20/ /g; # Forum #33612
  1730. $group = "^$group\$"; #71381
  1731. eval { "Hallo" =~ m/^$group$/ };
  1732. if($@) {
  1733. Log3 $FW_wname, 1, "Bad regexp in column spec: $@";
  1734. } else {
  1735. foreach my $g (grep /$group/ ,@grouplist) {
  1736. next if($handled{$g});
  1737. $handled{$g} = 1;
  1738. $columns{$g} = [$lineNo++, $colNo]; #23212
  1739. }
  1740. }
  1741. }
  1742. $colNo++;
  1743. }
  1744. last;
  1745. }
  1746. return (\%columns, $colNo);
  1747. }
  1748. #################
  1749. # return a sorted list of actual files for a given regexp
  1750. sub
  1751. FW_fileList($;$)
  1752. {
  1753. my ($fname,$mtime) = @_;
  1754. $fname =~ s/%L/$attr{global}{logdir}/g #Forum #89744
  1755. if($fname =~ m/%/ && $attr{global}{logdir});
  1756. $fname =~ m,^(.*)/([^/]*)$,; # Split into dir and file
  1757. my ($dir,$re) = ($1, $2);
  1758. return $fname if(!$re);
  1759. $re =~ s/%./[A-Za-z0-9]*/g; # logfile magic (%Y, etc)
  1760. my @ret;
  1761. return @ret if(!opendir(DH, $dir));
  1762. while(my $f = readdir(DH)) {
  1763. next if($f !~ m,^$re$, || $f eq "99_Utils.pm");
  1764. push(@ret, $f);
  1765. }
  1766. closedir(DH);
  1767. return sort { (CORE::stat("$dir/$a"))[9] <=> (CORE::stat("$dir/$b"))[9] }
  1768. @ret if($mtime);
  1769. @ret = cfgDB_FW_fileList($dir,$re,@ret) if (configDBUsed());
  1770. return sort @ret;
  1771. }
  1772. ###################################
  1773. # Stream big files in chunks, to avoid bloating ourselves.
  1774. # This is a "terminal" function, no data can be appended after it is called.
  1775. sub
  1776. FW_outputChunk($$$)
  1777. {
  1778. my ($hash, $buf, $d) = @_;
  1779. $buf = $d->deflate($buf) if($d);
  1780. if( length($buf) ){
  1781. TcpServer_WriteBlocking($hash, sprintf("%x\r\n",length($buf)) .$buf."\r\n");
  1782. }
  1783. }
  1784. sub
  1785. FW_returnFileAsStream($$$$$)
  1786. {
  1787. my ($path, $suffix, $type, $doEsc, $cacheable) = @_;
  1788. my $etag;
  1789. if($cacheable) {
  1790. #Check for If-None-Match header (ETag)
  1791. my $if_none_match = $FW_httpheader{"If-None-Match"};
  1792. $if_none_match =~ s/"(.*)"/$1/ if($if_none_match);
  1793. $etag = (stat($path))[9]; #mtime
  1794. if(defined($etag) && defined($if_none_match) && $etag eq $if_none_match) {
  1795. my $now = time();
  1796. my $rsp = "Date: ".FmtDateTimeRFC1123($now)."\r\n".
  1797. "ETag: $etag\r\n".
  1798. "Expires: ".FmtDateTimeRFC1123($now+900)."\r\n";
  1799. Log3 $FW_wname, 4, "$FW_chash->{NAME} => 304 Not Modified";
  1800. TcpServer_WriteBlocking($FW_chash,"HTTP/1.1 304 Not Modified\r\n".
  1801. $rsp . $FW_headerlines . "\r\n");
  1802. return -1;
  1803. }
  1804. }
  1805. if(!open(FH, $path)) {
  1806. Log3 $FW_wname, 4, "FHEMWEB $FW_wname $path: $!";
  1807. TcpServer_WriteBlocking($FW_chash,
  1808. "HTTP/1.1 404 Not Found\r\n".
  1809. "Content-Length:0\r\n\r\n");
  1810. FW_closeConn($FW_chash);
  1811. return -1;
  1812. }
  1813. binmode(FH) if($type !~ m/text/); # necessary for Windows
  1814. my $sz = -s $path;
  1815. $etag = defined($etag) ? "ETag: \"$etag\"\r\n" : "";
  1816. my $expires = $cacheable ? ("Expires: ".gmtime(time()+900)." GMT\r\n"): "";
  1817. my $compr = ($FW_httpheader{"Accept-Encoding"} &&
  1818. $FW_httpheader{"Accept-Encoding"} =~ m/gzip/ && $FW_use{zlib}) ?
  1819. "Content-Encoding: gzip\r\n" : "";
  1820. TcpServer_WriteBlocking($FW_chash, "HTTP/1.1 200 OK\r\n".
  1821. $compr . $expires . $FW_headerlines . $etag .
  1822. "Transfer-Encoding: chunked\r\n" .
  1823. "Content-Type: $type; charset=$FW_encoding\r\n\r\n");
  1824. my $d = Compress::Zlib::deflateInit(-WindowBits=>31) if($compr);
  1825. FW_outputChunk($FW_chash, $FW_RET, $d);
  1826. FW_outputChunk($FW_chash, "<a name='top'></a>".
  1827. "<a href='#end_of_file'>jump to the end</a><br><br>", $d)
  1828. if($doEsc && $sz > 2048);
  1829. my $buf;
  1830. while(sysread(FH, $buf, 2048)) {
  1831. if($doEsc) { # FileLog special
  1832. $buf =~ s/</&lt;/g;
  1833. $buf =~ s/>/&gt;/g;
  1834. }
  1835. FW_outputChunk($FW_chash, $buf, $d);
  1836. }
  1837. close(FH);
  1838. FW_outputChunk($FW_chash, "<br/><a name='end_of_file'></a>".
  1839. "<a href='#top'>jump to the top</a><br/><br/>", $d)
  1840. if($doEsc && $sz > 2048);
  1841. FW_outputChunk($FW_chash, $suffix, $d);
  1842. if($compr) {
  1843. $buf = $d->flush();
  1844. if($buf){
  1845. TcpServer_WriteBlocking($FW_chash,
  1846. sprintf("%x\r\n",length($buf)) .$buf."\r\n");
  1847. }
  1848. }
  1849. TcpServer_WriteBlocking($FW_chash, "0\r\n\r\n");
  1850. FW_closeConn($FW_chash);
  1851. return -1;
  1852. }
  1853. ##################
  1854. sub
  1855. FW_fatal($)
  1856. {
  1857. my ($msg) = @_;
  1858. FW_pO "<html><body>$msg</body></html>";
  1859. }
  1860. ##################
  1861. sub
  1862. FW_hidden($$)
  1863. {
  1864. my ($n, $v) = @_;
  1865. return "<input type=\"hidden\" name=\"$n\" value=\"$v\"/>";
  1866. }
  1867. ##################
  1868. # Generate a select field with option list
  1869. sub
  1870. FW_select($$$$$@)
  1871. {
  1872. my ($id, $name, $valueArray, $selected, $class, $jSelFn) = @_;
  1873. $jSelFn = ($jSelFn ? "onchange=\"$jSelFn\"" : "");
  1874. $id =~ s/\./_/g if($id); # to avoid problems in JS DOM Search
  1875. $id = ($id ? "id=\"$id\" informId=\"$id\"" : "");
  1876. my $s = "<select $jSelFn $id name=\"$name\" class=\"$class\">";
  1877. foreach my $v (@{$valueArray}) {
  1878. if(defined($selected) && $v eq $selected) {
  1879. $s .= "<option selected=\"selected\" value='$v'>$v</option>\n";
  1880. } else {
  1881. $s .= "<option value='$v'>$v</option>\n";
  1882. }
  1883. }
  1884. $s .= "</select>";
  1885. return $s;
  1886. }
  1887. ##################
  1888. sub
  1889. FW_textfieldv($$$$)
  1890. {
  1891. my ($n, $z, $class, $value) = @_;
  1892. my $v;
  1893. $v=" value='$value'" if(defined($value));
  1894. return if($FW_hiddenroom{input});
  1895. my $s = "<input type='text' name='$n' class='$class' size='$z'$v ".
  1896. "autocorrect='off' autocapitalize='off'/>";
  1897. return $s;
  1898. }
  1899. sub
  1900. FW_textfield($$$)
  1901. {
  1902. return FW_textfieldv($_[0], $_[1], $_[2], "");
  1903. }
  1904. ##################
  1905. sub
  1906. FW_submit($$@)
  1907. {
  1908. my ($n, $v, $class) = @_;
  1909. $class = ($class ? "class=\"$class\"" : "");
  1910. my $s ="<input type=\"submit\" name=\"$n\" value=\"$v\" $class/>";
  1911. $s = FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}).$s if($FW_CSRF);
  1912. return $s;
  1913. }
  1914. ##################
  1915. sub
  1916. FW_displayFileList($@)
  1917. {
  1918. my ($heading,@files)= @_;
  1919. my $hid = lc($heading);
  1920. $hid =~ s/[^A-Za-z]/_/g;
  1921. FW_pO "<div class=\"fileList $hid\">$heading</div>";
  1922. FW_pO "<table class=\"block wide fileList\">";
  1923. my $cfgDB = "";
  1924. my $row = 0;
  1925. foreach my $f (@files) {
  1926. $cfgDB = ($f =~ s,\.configDB$,,);
  1927. $cfgDB = ($cfgDB) ? "configDB" : "";
  1928. FW_pO "<tr class=\"" . ($row?"odd":"even") . "\">";
  1929. FW_pH "cmd=style edit $f $cfgDB", $f, 1;
  1930. FW_pO "</tr>";
  1931. $row = ($row+1)%2;
  1932. }
  1933. FW_pO "</table>";
  1934. FW_pO "<br>";
  1935. }
  1936. ##################
  1937. sub
  1938. FW_fileNameToPath($)
  1939. {
  1940. my $name = shift;
  1941. $attr{global}{configfile} =~ m,([^/]*)$,;
  1942. my $cfgFileName = $1;
  1943. if($name eq $cfgFileName) {
  1944. return $attr{global}{configfile};
  1945. } elsif($name =~ m/.*(js|css|_defs.svg)$/) {
  1946. return "$FW_cssdir/$name";
  1947. } elsif($name =~ m/.*(png|svg)$/) {
  1948. my $d="";
  1949. map { $d = $_ if(!$d && -d "$FW_icondir/$_") } @FW_iconDirs;
  1950. return "$FW_icondir/$d/$name";
  1951. } elsif($name =~ m/.*gplot$/) {
  1952. return "$FW_gplotdir/$name";
  1953. } elsif($name =~ m/.*log$/) {
  1954. return AttrVal("global", "logdir", "log")."/$name";
  1955. } else {
  1956. return "$MW_dir/$name";
  1957. }
  1958. }
  1959. ##################
  1960. # List/Edit/Save css and gnuplot files
  1961. sub
  1962. FW_style($$)
  1963. {
  1964. my ($cmd, $msg) = @_;
  1965. my @a = split(" ", $cmd);
  1966. return if(!Authorized($FW_chash, "cmd", $a[0]));
  1967. my $start = '><table><tr><td';
  1968. my $end = "</td></tr></table></div>";
  1969. if($a[1] eq "list") {
  1970. FW_addContent($start);
  1971. FW_pO "$msg<br><br>" if($msg);
  1972. $attr{global}{configfile} =~ m,([^/]*)$,;
  1973. my $cfgFileName = $1;
  1974. FW_displayFileList("config file", $cfgFileName)
  1975. if(!configDBUsed());
  1976. my $efl = AttrVal($FW_wname, 'editFileList',
  1977. "Own modules and helper files:\$MW_dir:^(.*sh|[0-9][0-9].*Util.*pm|".
  1978. ".*cfg|.*\.holiday|myUtilsTemplate.pm|.*layout)\$\n".
  1979. "Gplot files:\$FW_gplotdir:^.*gplot\$\n".
  1980. "Style files:\$FW_cssdir:^.*(css|svg)\$");
  1981. foreach my $l (split(/[\r\n]/, $efl)) {
  1982. my ($t, $v, $re) = split(":", $l, 3);
  1983. $v = eval $v;
  1984. my @fList;
  1985. if($v eq $FW_gplotdir && AttrVal($FW_wname,'showUsedFiles',0)) {
  1986. @fList = defInfo('TYPE=SVG','GPLOTFILE');
  1987. @fList = map { "$_.gplot" } @fList;
  1988. @fList = map { "$_.configDB" } @fList if configDBUsed();
  1989. my %fListUnique = map { $_, 1 } @fList;
  1990. @fList = sort keys %fListUnique;
  1991. } else {
  1992. @fList = FW_fileList("$v/$re");
  1993. }
  1994. FW_displayFileList($t, @fList);
  1995. }
  1996. FW_pO $end;
  1997. } elsif($a[1] eq "select") {
  1998. my @fl = grep { $_ !~ m/(floorplan|dashboard)/ }
  1999. FW_fileList("$FW_cssdir/.*style.css");
  2000. FW_addContent($start);
  2001. FW_pO "<div class='fileList styles'>Styles</div>";
  2002. FW_pO "<table class='block wide fileList'>";
  2003. my $row = 0;
  2004. foreach my $file (@fl) {
  2005. next if($file =~ m/svg_/);
  2006. $file =~ s/style.css//;
  2007. $file = "default" if($file eq "");
  2008. FW_pO "<tr class=\"" . ($row?"odd":"even") . "\">";
  2009. FW_pH "cmd=style set $file", "$file", 1;
  2010. FW_pO "</tr>";
  2011. $row = ($row+1)%2;
  2012. }
  2013. FW_pO "</table>$end";
  2014. } elsif($a[1] eq "set") {
  2015. CommandAttr(undef, "$FW_wname stylesheetPrefix $a[2]");
  2016. $FW_styleStamp = time();
  2017. $FW_RET =~ s,/style.css\?v=\d+,/style.css?v=$FW_styleStamp,;
  2018. FW_addContent($start);
  2019. FW_pO "Reload the page in the browser.$end";
  2020. } elsif($a[1] eq "edit") {
  2021. my $fileName = $a[2];
  2022. my $data = "";
  2023. my $cfgDB = defined($a[3]) ? $a[3] : "";
  2024. my $forceType = ($cfgDB eq 'configDB') ? $cfgDB : "file";
  2025. $fileName =~ s,.*/,,g; # Little bit of security
  2026. my $filePath = FW_fileNameToPath($fileName);
  2027. my($err, @content) = FileRead({FileName=>$filePath, ForceType=>$forceType});
  2028. if($err) {
  2029. FW_addContent(">$err</div");
  2030. return;
  2031. }
  2032. $data = join("\n", @content);
  2033. $data =~ s/&/&amp;/g;
  2034. $attr{global}{configfile} =~ m,([^/]*)$,;
  2035. my $readOnly = (AttrVal($FW_wname, "editConfig", ($1 ne $fileName)) ?
  2036. "" : "readonly");
  2037. my $ncols = $FW_ss ? 40 : 80;
  2038. FW_addContent();
  2039. FW_pO "<form method=\"$FW_formmethod\">";
  2040. if($readOnly) {
  2041. FW_pO "You can enable saving this file by setting the editConfig ";
  2042. FW_pO "attribute, but read the documentation first for the side effects.";
  2043. FW_pO "<br><br>";
  2044. } else {
  2045. FW_pO FW_submit("save", "Save $fileName");
  2046. FW_pO "&nbsp;&nbsp;";
  2047. FW_pO FW_submit("saveAs", "Save as");
  2048. FW_pO FW_textfieldv("saveName", 30, "saveName", $fileName);
  2049. FW_pO "<br><br>";
  2050. }
  2051. FW_pO FW_hidden("cmd", "style save $fileName $cfgDB");
  2052. FW_pO "<textarea $readOnly name=\"data\" cols=\"$ncols\" rows=\"30\">" .
  2053. "$data</textarea>";
  2054. FW_pO "</form>";
  2055. FW_pO "</div>";
  2056. } elsif($a[1] eq "save") {
  2057. my $fileName = $a[2];
  2058. my $cfgDB = defined($a[3]) ? $a[3] : "";
  2059. $fileName = $FW_webArgs{saveName}
  2060. if($FW_webArgs{saveAs} && $FW_webArgs{saveName});
  2061. $fileName =~ s,.*/,,g; # Little bit of security
  2062. my $filePath = FW_fileNameToPath($fileName);
  2063. my $isImg = ($fileName =~ m,\.(svg|png)$,i);
  2064. my $forceType = ($cfgDB eq 'configDB' && !$isImg) ? $cfgDB : "file";
  2065. $FW_data =~ s/\r//g if(!$isImg);
  2066. my $err;
  2067. if($fileName =~ m,\.png$,) {
  2068. $err = FileWrite({FileName=>$filePath,ForceType=>$forceType,NoNL=>1},
  2069. $FW_data);
  2070. } else {
  2071. $err = FileWrite({ FileName=>$filePath, ForceType=>$forceType },
  2072. split("\n", $FW_data));
  2073. }
  2074. if($err) {
  2075. FW_addContent(">$filePath: $!</div");
  2076. return;
  2077. }
  2078. my $ret = FW_fC("rereadcfg") if($filePath eq $attr{global}{configfile});
  2079. $ret = FW_fC("reload $fileName") if($fileName =~ m,\.pm$,);
  2080. $ret = FW_Set("","","rereadicons") if($isImg);
  2081. DoTrigger("global", "FILEWRITE $filePath", 1) if(!$ret); # Forum #32592
  2082. my $sfx = ($forceType eq "configDB" ? " to configDB" : "");
  2083. $ret = ($ret ? "<h3>ERROR:</h3><b>$ret</b>" : "Saved $fileName$sfx");
  2084. FW_style("style list", $ret);
  2085. $ret = "";
  2086. } elsif($a[1] eq "iconFor") {
  2087. FW_iconTable("iconFor", "icon", "style setIF $a[2] %s", undef);
  2088. } elsif($a[1] eq "setIF") {
  2089. FW_fC("attr $a[2] icon $a[3]");
  2090. FW_doDetail($a[2]);
  2091. } elsif($a[1] eq "showDSI") {
  2092. FW_iconTable("devStateIcon", "",
  2093. "style addDSI $a[2] %s", "Enter value/regexp for STATE");
  2094. } elsif($a[1] eq "addDSI") {
  2095. my $dsi = AttrVal($a[2], "devStateIcon", "");
  2096. $dsi .= " " if($dsi);
  2097. FW_fC("attr $a[2] devStateIcon $dsi$FW_data:$a[3]");
  2098. FW_doDetail($a[2]);
  2099. } elsif($a[1] eq "eventMonitor") {
  2100. FW_pO "<script type=\"text/javascript\" src=\"$FW_ME/pgm2/console.js\">".
  2101. "</script>";
  2102. FW_addContent();
  2103. my $filter = $a[2] ? ($a[2] eq "log" ? "global" : $a[2]) : ".*";
  2104. FW_pO "Events (Filter: <a href=\"#\" id=\"eventFilter\">$filter</a>) ".
  2105. "&nbsp;&nbsp;<span class='fhemlog'>FHEM log ".
  2106. "<input id='eventWithLog' type='checkbox'".
  2107. ($a[2] && $a[2] eq "log" ? " checked":"")."></span>".
  2108. "&nbsp;&nbsp;<button id='eventReset'>Reset</button><br><br>\n";
  2109. FW_pO "<div id=\"console\"></div>";
  2110. FW_pO "</div>";
  2111. }
  2112. }
  2113. sub
  2114. FW_iconTable($$$$)
  2115. {
  2116. my ($name, $class, $cmdFmt, $textfield) = @_;
  2117. my %icoList = ();
  2118. foreach my $style (@FW_iconDirs) {
  2119. foreach my $imgName (sort keys %{$FW_icons{$style}}) {
  2120. $imgName =~ s/\.[^.]*$//; # Cut extension
  2121. next if(!$FW_icons{$style}{$imgName}); # Dont cut it twice: FS20.on.png
  2122. next if($FW_icons{$style}{$imgName} !~ m/$imgName/); # Skip alias
  2123. next if($imgName=~m+^(weather/|shutter.*big|fhemicon|favicon|ws_.*_kl)+);
  2124. next if($imgName=~m+^(dashboardicons)+);
  2125. $icoList{$imgName} = 1;
  2126. }
  2127. }
  2128. FW_addContent();
  2129. FW_pO "<form method=\"$FW_formmethod\">";
  2130. FW_pO "Filter:&nbsp;".FW_textfieldv("icon-filter",20,"iconTable","")."<br>";
  2131. if($textfield) {
  2132. FW_pO "$textfield:&nbsp;".FW_textfieldv("data",20,"iconTable",".*")."<br>";
  2133. }
  2134. foreach my $i (sort keys %icoList) {
  2135. FW_pF "<button title='%s' type='submit' class='dist' name='cmd' ".
  2136. "value='$cmdFmt'>%s</button>", $i, $i, FW_makeImage($i,$i,$class);
  2137. }
  2138. FW_pO "</form>";
  2139. FW_pO "</div>";
  2140. }
  2141. ##################
  2142. # print (append) to output
  2143. sub
  2144. FW_pO(@)
  2145. {
  2146. my $arg = shift;
  2147. return if(!defined($arg));
  2148. $FW_RET .= $arg;
  2149. $FW_RET .= "\n";
  2150. }
  2151. #################
  2152. # add href
  2153. sub
  2154. FW_pH(@)
  2155. {
  2156. my ($link, $txt, $td, $class, $doRet,$nonl) = @_;
  2157. my $ret;
  2158. $link .= $FW_CSRF if($link =~ m/cmd/ &&
  2159. $link !~m/cmd=style%20(list|select|eventMonitor)/);
  2160. $link = ($link =~ m,^/,) ? $link : "$FW_ME$FW_subdir?$link";
  2161. # Using onclick, as href starts safari in a webapp.
  2162. # Known issue: the pointer won't change
  2163. if($FW_ss || $FW_tp) {
  2164. $ret = "<a onClick=\"location.href='$link'\">$txt</a>";
  2165. } else {
  2166. $ret = "<a href=\"$link\">$txt</a>";
  2167. }
  2168. #actually 'div' should be removed if no class is defined
  2169. # as I can't check all code for consistancy I add nonl instead
  2170. $class = ($class)?" class=\"$class\"":"";
  2171. $ret = "<div$class>$ret</div>" if (!$nonl);
  2172. $ret = "<td>$ret</td>" if($td);
  2173. return $ret if($doRet);
  2174. FW_pO $ret;
  2175. }
  2176. #################
  2177. # href without class/div, returned as a string
  2178. sub
  2179. FW_pHPlain(@)
  2180. {
  2181. my ($link, $txt, $td) = @_;
  2182. $link = "?$link" if($link !~ m+^/+);
  2183. my $ret = "";
  2184. $ret .= "<td>" if($td);
  2185. $link .= $FW_CSRF;
  2186. if($FW_ss || $FW_tp) {
  2187. $ret .= "<a onClick=\"location.href='$FW_ME$FW_subdir$link'\">$txt</a>";
  2188. } else {
  2189. $ret .= "<a href=\"$FW_ME$FW_subdir$link\">$txt</a>";
  2190. }
  2191. $ret .= "</td>" if($td);
  2192. return $ret;
  2193. }
  2194. ##############################
  2195. sub
  2196. FW_makeImage(@)
  2197. {
  2198. my ($name, $txt, $class)= @_;
  2199. $txt = $name if(!defined($txt));
  2200. $class = "" if(!$class);
  2201. $class = "$class $name";
  2202. $class =~ s/\./_/g;
  2203. $class =~ s/@/ /g;
  2204. my $p = FW_iconPath($name);
  2205. return $name if(!$p);
  2206. if($p =~ m/\.svg$/i) {
  2207. if(open(FH, "$FW_icondir/$p")) {
  2208. my $data;
  2209. do {
  2210. $data = <FH>;
  2211. if(!defined($data)) {
  2212. Log 1, "$FW_icondir/$p is not useable";
  2213. return "";
  2214. }
  2215. } until( $data =~ m/^<svg/ );
  2216. $data .= join("", <FH>);
  2217. close(FH);
  2218. $data =~ s/[\r\n]/ /g;
  2219. $data =~ s/ *$//g;
  2220. $data =~ s/<svg/<svg class="$class" data-txt="$txt"/; #52967
  2221. $name =~ m/(@.*)$/;
  2222. my $col = $1 if($1);
  2223. if($col) {
  2224. $col =~ s/@//;
  2225. $col = "#$col" if($col =~ m/^([A-F0-9]{6})$/);
  2226. $data =~ s/fill="#000000"/fill="$col"/g;
  2227. $data =~ s/fill:#000000/fill:$col/g;
  2228. } else {
  2229. $data =~ s/fill="#000000"//g;
  2230. $data =~ s/fill:#000000//g;
  2231. }
  2232. return $data;
  2233. } else {
  2234. return $name;
  2235. }
  2236. } else {
  2237. $class = "class='$class'" if($class);
  2238. $p = urlEncodePath($p);
  2239. return "<img $class src=\"$FW_ME/images/$p\" alt=\"$txt\" title=\"$txt\">";
  2240. }
  2241. }
  2242. ####
  2243. sub
  2244. FW_IconURL($)
  2245. {
  2246. my ($name)= @_;
  2247. return "$FW_ME/icons/$name";
  2248. }
  2249. ##################
  2250. # print formatted
  2251. sub
  2252. FW_pF($@)
  2253. {
  2254. my $fmt = shift;
  2255. $FW_RET .= sprintf $fmt, @_;
  2256. }
  2257. ##################
  2258. # fhem command
  2259. sub
  2260. FW_fC($@)
  2261. {
  2262. my ($cmd, $unique) = @_;
  2263. my $ret;
  2264. if($unique) {
  2265. $ret = AnalyzeCommand($FW_chash, $cmd);
  2266. } else {
  2267. $ret = AnalyzeCommandChain($FW_chash, $cmd);
  2268. }
  2269. return $ret;
  2270. }
  2271. sub
  2272. FW_Attr(@)
  2273. {
  2274. my ($type, $devName, $attrName, @param) = @_;
  2275. my $hash = $defs{$devName};
  2276. my $sP = "stylesheetPrefix";
  2277. my $retMsg;
  2278. if($type eq "set" && $attrName eq "HTTPS") {
  2279. TcpServer_SetSSL($hash);
  2280. }
  2281. if($type eq "set") { # Converting styles
  2282. if($attrName eq "smallscreen" || $attrName eq "touchpad") {
  2283. $attr{$devName}{$sP} = $attrName;
  2284. $retMsg="$devName: attribute $attrName deprecated, converted to $sP";
  2285. $param[0] = $attrName; $attrName = $sP;
  2286. }
  2287. }
  2288. if($attrName eq $sP) {
  2289. # AttrFn is called too early, we have to set/del the attr here
  2290. if($type eq "set") {
  2291. $attr{$devName}{$sP} = (defined($param[0]) ? $param[0] : "default");
  2292. FW_readIcons($attr{$devName}{$sP});
  2293. } else {
  2294. delete $attr{$devName}{$sP};
  2295. }
  2296. }
  2297. if(($attrName eq "allowedCommands" ||
  2298. $attrName eq "basicAuth" ||
  2299. $attrName eq "basicAuthMsg")
  2300. && $type eq "set") {
  2301. my $aName = "allowed_$devName";
  2302. my $exists = ($defs{$aName} ? 1 : 0);
  2303. AnalyzeCommand(undef, "defmod $aName allowed");
  2304. AnalyzeCommand(undef, "attr $aName validFor $devName");
  2305. AnalyzeCommand(undef, "attr $aName $attrName ".join(" ",@param));
  2306. return "$devName: ".($exists ? "modifying":"creating").
  2307. " device $aName for attribute $attrName";
  2308. }
  2309. if($attrName eq "iconPath" && $type eq "set") {
  2310. foreach my $pe (split(":", $param[0])) {
  2311. $pe =~ s+\.\.++g;
  2312. FW_readIcons($pe);
  2313. }
  2314. }
  2315. if($attrName eq "JavaScripts" && $type eq "set") { # create some attributes
  2316. my (%a, @add);
  2317. map { $a{$_} = 1 } split(" ", $modules{FHEMWEB}{AttrList});
  2318. map {
  2319. $_ =~ s+.*/++; $_ =~ s/.js$//; $_ =~ s/fhem_//; $_ .= "Param";
  2320. push @add, $_ if(!$a{$_} && $_ !~ m/^-/);
  2321. } split(" ", $param[0]);
  2322. $modules{FHEMWEB}{AttrList} .= " ".join(" ",@add) if(@add);
  2323. }
  2324. if($attrName eq "csrfToken") {
  2325. return undef if($FW_csrfTokenCache{$devName} && !$init_done);
  2326. my $csrf = $param[0];
  2327. if($type eq "del" || $csrf eq "random") {
  2328. my ($x,$y) = gettimeofday();
  2329. ($csrf = "csrf_".(rand($y)*rand($x))) =~ s/[^a-z_0-9]//g;
  2330. }
  2331. if($csrf eq "none") {
  2332. delete($hash->{CSRFTOKEN});
  2333. delete($FW_csrfTokenCache{$devName});
  2334. } else {
  2335. $hash->{CSRFTOKEN} = $csrf;
  2336. $FW_csrfTokenCache{$devName} = $hash->{CSRFTOKEN};
  2337. }
  2338. }
  2339. if($attrName eq "longpoll" && $type eq "set" && $param[0] eq "websocket") {
  2340. return "$devName: Could not load Digest::SHA on startup, no websocket"
  2341. if(!$FW_use{sha});
  2342. }
  2343. return $retMsg;
  2344. }
  2345. # recursion starts at $FW_icondir/$dir
  2346. # filenames are relative to $FW_icondir
  2347. sub
  2348. FW_readIconsFrom($$)
  2349. {
  2350. my ($dir,$subdir)= @_;
  2351. my $ldir = ($subdir ? "$dir/$subdir" : $dir);
  2352. my @entries;
  2353. if(opendir(DH, "$FW_icondir/$ldir")) {
  2354. @entries= sort readdir(DH); # assures order: .gif .ico .jpg .png .svg
  2355. closedir(DH);
  2356. }
  2357. foreach my $entry (@entries) {
  2358. if( -d "$FW_icondir/$ldir/$entry" ) { # directory -> recurse
  2359. FW_readIconsFrom($dir, $subdir ? "$subdir/$entry" : $entry)
  2360. unless($entry eq "." || $entry eq ".." || $entry eq ".svn");
  2361. } else {
  2362. if($entry =~ m/^iconalias.txt$/i && open(FH, "$FW_icondir/$ldir/$entry")){
  2363. while(my $l = <FH>) {
  2364. chomp($l);
  2365. my @a = split(" ", $l);
  2366. next if($l =~ m/^#/ || @a < 2);
  2367. $FW_icons{$dir}{$a[0]} = $a[1];
  2368. }
  2369. close(FH);
  2370. } elsif($entry =~ m/(gif|ico|jpg|png|jpeg|svg)$/i) {
  2371. my $filename = $subdir ? "$subdir/$entry" : $entry;
  2372. $FW_icons{$dir}{$filename} = $filename;
  2373. my $tag = $filename; # Add it without extension too
  2374. $tag =~ s/\.[^.]*$//;
  2375. $FW_icons{$dir}{$tag} = $filename;
  2376. }
  2377. }
  2378. }
  2379. $FW_icons{$dir}{""} = 1; # Do not check empty directories again.
  2380. }
  2381. sub
  2382. FW_readIcons($)
  2383. {
  2384. my ($dir)= @_;
  2385. return if($FW_icons{$dir});
  2386. FW_readIconsFrom($dir, "");
  2387. }
  2388. # check if the icon exists, and if yes, returns its "logical" name;
  2389. sub
  2390. FW_iconName($)
  2391. {
  2392. my ($oname)= @_;
  2393. return undef if(!defined($oname));
  2394. my $name = $oname;
  2395. $name =~ s/@.*//;
  2396. foreach my $pe (@FW_iconDirs) {
  2397. return $oname if($pe && $FW_icons{$pe} && $FW_icons{$pe}{$name});
  2398. }
  2399. return undef;
  2400. }
  2401. # returns the physical absolute path relative for the logical path
  2402. # examples:
  2403. # FS20.on -> dark/FS20.on.png
  2404. # weather/sunny -> default/weather/sunny.gif
  2405. sub
  2406. FW_iconPath($)
  2407. {
  2408. my ($name) = @_;
  2409. $name =~ s/@.*//;
  2410. foreach my $pe (@FW_iconDirs) {
  2411. return "$pe/$FW_icons{$pe}{$name}"
  2412. if($pe && $FW_icons{$pe} && $FW_icons{$pe}{$name});
  2413. }
  2414. return undef;
  2415. }
  2416. sub
  2417. FW_dev2image($;$)
  2418. {
  2419. my ($name, $state) = @_;
  2420. my $d = $defs{$name};
  2421. return "" if(!$name || !$d);
  2422. my $type = $d->{TYPE};
  2423. $state = $d->{STATE} if(!defined($state));
  2424. return "" if(!$type || !defined($state));
  2425. my $model = AttrVal($name, "model", "");
  2426. my (undef, $rstate) = ReplaceEventMap($name, [undef, $state], 0);
  2427. my ($icon, $rlink);
  2428. my $devStateIcon = AttrVal($name, "devStateIcon", undef);
  2429. if(defined($devStateIcon) && $devStateIcon =~ m/^{.*}$/) {
  2430. my ($html, $link) = eval $devStateIcon;
  2431. Log3 $FW_wname, 1, "devStateIcon $name: $@" if($@);
  2432. return ($html, $link, 1) if(defined($html) && $html =~ m/^<.*>$/s);
  2433. $devStateIcon = $html;
  2434. }
  2435. if(defined($devStateIcon)) {
  2436. my @list = split(" ", $devStateIcon);
  2437. foreach my $l (@list) {
  2438. my ($re, $iconName, $link) = split(":", $l, 3);
  2439. if(defined($re) && $state =~ m/^$re$/) {
  2440. if(defined($iconName) && $iconName eq "") {
  2441. $rlink = $link;
  2442. last;
  2443. }
  2444. if(defined($iconName) && defined(FW_iconName($iconName))) {
  2445. return ($iconName, $link, 0);
  2446. } else {
  2447. return ($state, $link, 1);
  2448. }
  2449. }
  2450. }
  2451. }
  2452. $state =~ s/ .*//; # Want to be able to have icons for "on-for-timer xxx"
  2453. $icon = FW_iconName("$name.$state") if(!$icon); # lamp.Aus.png
  2454. $icon = FW_iconName("$name.$rstate") if(!$icon); # lamp.on.png
  2455. $icon = FW_iconName($name) if(!$icon); # lamp.png
  2456. $icon = FW_iconName("$model.$state") if(!$icon && $model); # fs20st.off.png
  2457. $icon = FW_iconName($model) if(!$icon && $model); # fs20st.png
  2458. $icon = FW_iconName("$type.$state") if(!$icon); # FS20.Aus.png
  2459. $icon = FW_iconName("$type.$rstate") if(!$icon); # FS20.on.png
  2460. $icon = FW_iconName($type) if(!$icon); # FS20.png
  2461. $icon = FW_iconName($state) if(!$icon); # Aus.png
  2462. $icon = FW_iconName($rstate) if(!$icon); # on.png
  2463. return ($icon, $rlink, 0);
  2464. }
  2465. sub
  2466. FW_makeEdit($$$)
  2467. {
  2468. my ($name, $n, $val) = @_;
  2469. # Toggle Edit-Window visibility script.
  2470. my $psc = AttrVal("global", "perlSyntaxCheck", ($featurelevel>5.7) ? 1 : 0);
  2471. FW_pO "<td>";
  2472. FW_pO "<a id=\"DEFa\" style=\"cursor:pointer\">$n</a>";
  2473. FW_pO "</td>";
  2474. $val =~ s,\\\n,\n,g;
  2475. $val = FW_htmlEscape($val);
  2476. my $eval = $val;
  2477. $eval = "<pre>$eval</pre>" if($eval =~ m/\n/);
  2478. FW_pO "<td>";
  2479. FW_pO "<div class=\"dval\" id=\"disp\">$eval</div>";
  2480. FW_pO "</td>";
  2481. FW_pO "</tr><tr><td colspan=\"2\">";
  2482. FW_pO "<div id=\"edit\" style=\"display:none\">";
  2483. FW_pO "<form method=\"$FW_formmethod\">";
  2484. FW_pO FW_hidden("detail", $name);
  2485. my $cmd = "modify";
  2486. my $ncols = $FW_ss ? 30 : 60;
  2487. FW_pO "<textarea name=\"val.${cmd}$name\" ".
  2488. "cols=\"$ncols\" rows=\"10\">$val</textarea>";
  2489. FW_pO "<br>" . FW_submit("cmd.${cmd}$name", "$cmd $name",($psc?"psc":""));
  2490. FW_pO "</form></div>";
  2491. FW_pO "</td>";
  2492. }
  2493. sub
  2494. FW_longpollInfo($@)
  2495. {
  2496. my $fmt = shift;
  2497. if($fmt && $fmt eq "JSON") {
  2498. my @a;
  2499. map { my $x = $_; #Forum 57377, ASCII 0-19 \ "
  2500. $x=~ s/([\x00-\x1f\x22\x5c\x7f])/sprintf '\u%04x', ord($1)/ge;
  2501. push @a,$x; } @_;
  2502. return '["'.join('","', @a).'"]';
  2503. } else {
  2504. return join('<<', @_);
  2505. }
  2506. }
  2507. sub
  2508. FW_roomStatesForInform($$)
  2509. {
  2510. my ($me, $sinceTimestamp ) = @_;
  2511. return "" if($me->{inform}{type} !~ m/status/);
  2512. my %extPage = ();
  2513. my @data;
  2514. foreach my $dn (keys %{$me->{inform}{devices}}) {
  2515. next if(!defined($defs{$dn}));
  2516. my $t = $defs{$dn}{TYPE};
  2517. next if(!$t || $modules{$t}{FW_atPageEnd});
  2518. my $lastChanged = OldTimestamp( $dn );
  2519. next if(!defined($lastChanged) || $lastChanged lt $sinceTimestamp);
  2520. my ($allSet, $cmdlist, $txt) = FW_devState($dn, "", \%extPage);
  2521. if($defs{$dn} && $defs{$dn}{STATE} && $defs{$dn}{TYPE} ne "weblink") {
  2522. push @data,
  2523. FW_longpollInfo($me->{inform}{fmt}, $dn, $defs{$dn}{STATE}, $txt);
  2524. }
  2525. }
  2526. my $data = join("\n", map { s/\n/ /gm; $_ } @data)."\n";
  2527. return $data;
  2528. }
  2529. sub
  2530. FW_logInform($$)
  2531. {
  2532. my ($me, $msg) = @_; # _NO_ Log3 here!
  2533. my $ntfy = $defs{$me};
  2534. if(!$ntfy) {
  2535. delete $logInform{$me};
  2536. return;
  2537. }
  2538. $msg = FW_htmlEscape($msg);
  2539. if(!FW_addToWritebuffer($ntfy, "<div class='fhemlog'>$msg</div>") ){
  2540. TcpServer_Close($ntfy, 1);
  2541. delete $logInform{$me};
  2542. }
  2543. }
  2544. sub
  2545. FW_Notify($$)
  2546. {
  2547. my ($ntfy, $dev) = @_;
  2548. my $h = $ntfy->{inform};
  2549. return undef if(!$h);
  2550. my $isStatus = ($h->{type} =~ m/status/);
  2551. my $events;
  2552. my $dn = $dev->{NAME};
  2553. if($dn eq "global" && $isStatus) {
  2554. my $vs = int(@structChangeHist) ? 'visible' : 'hidden';
  2555. my $data = FW_longpollInfo($h->{fmt},
  2556. "#FHEMWEB:$ntfy->{NAME}","\$('#saveCheck').css('visibility','$vs')","");
  2557. FW_addToWritebuffer($ntfy, $data."\n");
  2558. if($dev->{CHANGED}) {
  2559. $dn = $1 if($dev->{CHANGED}->[0] =~ m/^MODIFIED (.*)$/);
  2560. if($dev->{CHANGED}->[0] =~ m/^ATTR ([^ ]+) ([^ ]+) (.*)$/s) {
  2561. $dn = $1;
  2562. my @a = ("a-$2: $3");
  2563. $events = \@a;
  2564. }
  2565. }
  2566. }
  2567. if($dn eq $ntfy->{SNAME} &&
  2568. $dev->{CHANGED} &&
  2569. $dev->{CHANGED}->[0] =~ m/^JS(#([^:]*))?:(.*)$/) {
  2570. my $data = $3;
  2571. return if( $2 && $ntfy->{PEER} !~ m/$2/ );
  2572. $data = FW_longpollInfo($h->{fmt}, "#FHEMWEB:$ntfy->{NAME}",$data,"");
  2573. FW_addToWritebuffer($ntfy, $data."\n");
  2574. return;
  2575. }
  2576. return undef if($isStatus && !$h->{devices}{$dn});
  2577. my @data;
  2578. my %extPage;
  2579. my $isRaw = ($h->{type} =~ m/raw/);
  2580. $events = deviceEvents($dev, AttrVal($FW_wname, "addStateEvent",!$isRaw))
  2581. if(!$events);
  2582. if($isStatus) {
  2583. # Why is saving this stuff needed? FLOORPLAN?
  2584. my @old = ($FW_wname, $FW_ME, $FW_ss, $FW_tp, $FW_subdir);
  2585. $FW_wname = $ntfy->{SNAME};
  2586. $FW_ME = "/" . AttrVal($FW_wname, "webname", "fhem");
  2587. $FW_subdir = ($h->{iconPath} ? "/floorplan/$h->{iconPath}" : ""); # 47864
  2588. $FW_sp = AttrVal($FW_wname, "stylesheetPrefix", "f18");
  2589. $FW_sp = "" if($FW_sp eq "default");
  2590. $FW_ss = ($FW_sp =~ m/smallscreen/);
  2591. $FW_tp = ($FW_sp =~ m/smallscreen|touchpad/);
  2592. my $spDir = ($FW_sp eq "default" ? "" : "$FW_sp:");
  2593. @FW_iconDirs = grep { $_ } split(":", AttrVal($FW_wname, "iconPath",
  2594. "${spDir}fhemSVG:openautomation:default"));
  2595. if($h->{iconPath}) {
  2596. unshift @FW_iconDirs, $h->{iconPath};
  2597. FW_readIcons($h->{iconPath});
  2598. }
  2599. if( !$modules{$defs{$dn}{TYPE}}{FW_atPageEnd} ) {
  2600. my ($allSet, $cmdlist, $txt) = FW_devState($dn, "", \%extPage);
  2601. ($FW_wname, $FW_ME, $FW_ss, $FW_tp, $FW_subdir) = @old;
  2602. push @data, FW_longpollInfo($h->{fmt}, $dn, $dev->{STATE}, $txt);
  2603. }
  2604. #Add READINGS
  2605. if($events) { # It gets deleted sometimes (?)
  2606. my $tn = TimeNow();
  2607. my $max = int(@{$events});
  2608. for(my $i = 0; $i < $max; $i++) {
  2609. if($events->[$i] !~ /: /) {
  2610. if($dev->{NAME} eq 'global') { # Forum #47634
  2611. my($type,$args) = split(' ', $events->[$i], 2);
  2612. $args = "" if(!defined($args)); # global SAVE
  2613. push @data, FW_longpollInfo($h->{fmt}, "$dn-$type", $args, $args);
  2614. }
  2615. next; #ignore 'set' commands
  2616. }
  2617. my ($readingName,$readingVal) = split(": ",$events->[$i],2);
  2618. next if($readingName !~ m/^[A-Za-z\d_\.\-\/:]+$/); # Forum #70608,70844
  2619. push @data, FW_longpollInfo($h->{fmt},
  2620. "$dn-$readingName", $readingVal,$readingVal);
  2621. push @data, FW_longpollInfo($h->{fmt}, "$dn-$readingName-ts", $tn, $tn);
  2622. }
  2623. }
  2624. }
  2625. if($isRaw) {
  2626. if($events) { # It gets deleted sometimes (?)
  2627. my $tn = TimeNow();
  2628. if($attr{global}{mseclog}) {
  2629. my ($seconds, $microseconds) = gettimeofday();
  2630. $tn .= sprintf(".%03d", $microseconds/1000);
  2631. }
  2632. my $max = int(@{$events});
  2633. my $dt = $dev->{TYPE};
  2634. for(my $i = 0; $i < $max; $i++) {
  2635. my $line = "$tn $dt $dn ".$events->[$i]."<br>";
  2636. eval {
  2637. my $ok;
  2638. if($h->{filterType} && $h->{filterType} eq "notify") {
  2639. $ok = ($dn =~ m/^$h->{filter}$/ ||
  2640. "$dn:$events->[$i]" =~ m/^$h->{filter}$/) ;
  2641. } else {
  2642. $ok = ($line =~ m/$h->{filter}/) ;
  2643. }
  2644. push @data,$line if($ok);
  2645. }
  2646. }
  2647. }
  2648. }
  2649. if(@data){
  2650. if(!FW_addToWritebuffer($ntfy,
  2651. join("\n", map { s/\n/ /gm; $_ } @data)."\n") ){
  2652. my $name = $ntfy->{NAME};
  2653. Log3 $name, 4, "Closing connection $name due to full buffer in FW_Notify";
  2654. TcpServer_Close($ntfy, 1);
  2655. }
  2656. }
  2657. return undef;
  2658. }
  2659. sub
  2660. FW_directNotify($@) # Notify without the event overhead (Forum #31293)
  2661. {
  2662. my $filter;
  2663. if($_[0] =~ m/^FILTER=(.*)/) {
  2664. $filter = "^$1\$";
  2665. shift;
  2666. }
  2667. my $dev = $_[0];
  2668. foreach my $ntfy (values(%defs)) {
  2669. next if(!$ntfy->{TYPE} ||
  2670. $ntfy->{TYPE} ne "FHEMWEB" ||
  2671. !$ntfy->{inform} ||
  2672. !$ntfy->{inform}{devices}{$dev} ||
  2673. $ntfy->{inform}{type} ne "status");
  2674. next if($filter && $ntfy->{inform}{filter} !~ m/$filter/);
  2675. if(!FW_addToWritebuffer($ntfy,
  2676. FW_longpollInfo($ntfy->{inform}{fmt}, @_)."\n")) {
  2677. my $name = $ntfy->{NAME};
  2678. Log3 $name, 4, "Closing connection $name due to full buffer in FW_Notify";
  2679. TcpServer_Close($ntfy, 1);
  2680. }
  2681. }
  2682. }
  2683. ###################
  2684. # Compute the state (==second) column
  2685. sub
  2686. FW_devState($$@)
  2687. {
  2688. my ($d, $rf, $extPage) = @_;
  2689. my ($hasOnOff, $link);
  2690. my $cmdList = AttrVal($d, "webCmd", "");
  2691. my $allSets = FW_widgetOverride($d, getAllSets($d, $FW_chash));
  2692. my $state = $defs{$d}{STATE};
  2693. $state = "" if(!defined($state));
  2694. $hasOnOff = ($allSets =~ m/(^| )on(:[^ ]*)?( |$)/ &&
  2695. $allSets =~ m/(^| )off(:[^ ]*)?( |$)/);
  2696. my $txt = $state;
  2697. my $dsi = ($attr{$d} && ($attr{$d}{stateFormat} || $attr{$d}{devStateIcon}));
  2698. if(AttrVal($d, "showtime", undef)) {
  2699. my $v = $defs{$d}{READINGS}{state}{TIME};
  2700. $txt = $v if(defined($v));
  2701. } elsif(!$dsi && $allSets =~ m/\bdesired-temp:/) {
  2702. $txt = "$1 &deg;C" if($txt =~ m/^measured-temp: (.*)/); # FHT fix
  2703. $cmdList = "desired-temp" if(!$cmdList);
  2704. } elsif(!$dsi && $allSets =~ m/\bdesiredTemperature:/) {
  2705. $txt = ReadingsVal($d, "temperature", ""); # ignores stateFormat!!!
  2706. $txt =~ s/ .*//;
  2707. $txt .= "&deg;C";
  2708. $cmdList = "desiredTemperature" if(!$cmdList);
  2709. } else {
  2710. my ($icon, $isHtml);
  2711. ($icon, $link, $isHtml) = FW_dev2image($d);
  2712. $txt = ($isHtml ? $icon : FW_makeImage($icon, $state)) if($icon);
  2713. my $cmdlist = (defined($link) ? $link : "");
  2714. my $h = "";
  2715. foreach my $cmd (split(":", $cmdlist)) {
  2716. my $htmlTxt;
  2717. my @c = split(' ', $cmd); # @c==0 if $cmd==" ";
  2718. if(int(@c) && $allSets && $allSets =~ m/\b$c[0]:([^ ]*)/) {
  2719. my $values = $1;
  2720. foreach my $fn (sort keys %{$data{webCmdFn}}) {
  2721. no strict "refs";
  2722. $htmlTxt = &{$data{webCmdFn}{$fn}}($FW_wname,
  2723. $d, $FW_room, $cmd, $values);
  2724. use strict "refs";
  2725. last if(defined($htmlTxt));
  2726. }
  2727. }
  2728. if( $htmlTxt ) {
  2729. $h .= "<p>$htmlTxt</p>";
  2730. }
  2731. }
  2732. if( $h ) {
  2733. $link = undef;
  2734. $h =~ s/'/\\"/g;
  2735. $txt = "<a onClick='FW_okDialog(\"$h\",this)'\>$txt</a>";
  2736. } else {
  2737. $link = "cmd.$d=set $d $link" if(defined($link));
  2738. }
  2739. }
  2740. if($hasOnOff) {
  2741. # Have to cover: "on:An off:Aus", "A0:Aus AI:An Aus:off An:on"
  2742. my $on = ReplaceEventMap($d, "on", 1);
  2743. my $off = ReplaceEventMap($d, "off", 1);
  2744. $link = "cmd.$d=set $d " . ($state eq $on ? $off : $on) if(!defined($link));
  2745. $cmdList = "$on:$off" if(!$cmdList);
  2746. }
  2747. if(defined($link)) { # Have command to execute
  2748. my $room = AttrVal($d, "room", undef);
  2749. if($room) {
  2750. if($FW_room && $room =~ m/\b$FW_room\b/) {
  2751. $room = $FW_room;
  2752. } else {
  2753. $room =~ s/,.*//;
  2754. }
  2755. $link .= "&room=".urlEncode($room);
  2756. }
  2757. $txt = "<a href=\"$FW_ME$FW_subdir?$link$rf$FW_CSRF\">$txt</a>"
  2758. if($link !~ m/ noFhemwebLink\b/);
  2759. }
  2760. my $style = AttrVal($d, "devStateStyle", "");
  2761. $state =~ s/"//g;
  2762. $state =~ s/<.*?>/ /g; # remove HTML tags for the title
  2763. $txt = "<div id=\"$d\" $style title=\"$state\" class=\"col2\">$txt</div>";
  2764. my $type = $defs{$d}{TYPE};
  2765. my $sfn = $modules{$type}{FW_summaryFn};
  2766. if($sfn) {
  2767. if(!defined($extPage)) {
  2768. my %hash;
  2769. $extPage = \%hash;
  2770. }
  2771. no strict "refs";
  2772. my $newtxt = &{$sfn}($FW_wname, $d, $FW_room, $extPage);
  2773. use strict "refs";
  2774. $txt = $newtxt if(defined($newtxt)); # As specified
  2775. }
  2776. return ($allSets, $cmdList, $txt);
  2777. }
  2778. sub
  2779. FW_Get($@)
  2780. {
  2781. my ($hash, @a) = @_;
  2782. my $arg = (defined($a[1]) ? $a[1] : "");
  2783. if($arg eq "icon") {
  2784. return "need one icon as argument" if(int(@a) != 3);
  2785. my $ofn = $FW_wname;
  2786. $FW_wname = $hash->{NAME};
  2787. my $icon = FW_iconPath($a[2]);
  2788. $FW_wname = $ofn;
  2789. return defined($icon) ? "$FW_icondir/$icon" : "no such icon";
  2790. } elsif($arg eq "pathlist") {
  2791. return "web server root: $FW_dir\n".
  2792. "icon directory: $FW_icondir\n".
  2793. "css directory: $FW_cssdir\n".
  2794. "gplot directory: $FW_gplotdir";
  2795. } else {
  2796. return "Unknown argument $arg choose one of icon pathlist:noArg";
  2797. }
  2798. }
  2799. #####################################
  2800. sub
  2801. FW_Set($@)
  2802. {
  2803. my ($hash, @a) = @_;
  2804. my %cmd = ("rereadicons" => 1, "clearSvgCache" => 1);
  2805. return "no set value specified" if(@a < 2);
  2806. return ("Unknown argument $a[1], choose one of ".
  2807. join(" ", map { "$_:noArg" } sort keys %cmd))
  2808. if(!$cmd{$a[1]});
  2809. if($a[1] eq "rereadicons") {
  2810. my @dirs = keys %FW_icons;
  2811. %FW_icons = ();
  2812. foreach my $d (@dirs) {
  2813. FW_readIcons($d);
  2814. }
  2815. }
  2816. if($a[1] eq "clearSvgCache") {
  2817. my $cDir = "$FW_dir/SVGcache";
  2818. if(opendir(DH, $cDir)) {
  2819. map { my $n="$cDir/$_"; unlink($n) if(-f $n); } readdir(DH);
  2820. closedir(DH);
  2821. } else {
  2822. return "Can't open $cDir: $!";
  2823. }
  2824. }
  2825. return undef;
  2826. }
  2827. #####################################
  2828. sub
  2829. FW_closeInactiveClients()
  2830. {
  2831. my $now = time();
  2832. foreach my $dev (keys %defs) {
  2833. next if(!$defs{$dev}{TYPE} || $defs{$dev}{TYPE} ne "FHEMWEB" ||
  2834. !$defs{$dev}{LASTACCESS} || $defs{$dev}{inform} ||
  2835. ($now - $defs{$dev}{LASTACCESS}) < 60);
  2836. Log3 $FW_wname, 4, "Closing inactive connection $dev";
  2837. FW_Undef($defs{$dev}, undef);
  2838. delete $defs{$dev};
  2839. delete $attr{$dev};
  2840. }
  2841. InternalTimer($now+60, "FW_closeInactiveClients", 0, 0);
  2842. }
  2843. sub
  2844. FW_htmlEscape($)
  2845. {
  2846. my ($txt) = @_;
  2847. $txt =~ s/&/&amp;/g;
  2848. $txt =~ s/</&lt;/g;
  2849. $txt =~ s/>/&gt;/g;
  2850. $txt =~ s/'/&apos;/g;
  2851. # $txt =~ s/\n/<br>/g;
  2852. return $txt;
  2853. }
  2854. ###########################
  2855. # Widgets START
  2856. sub
  2857. FW_widgetFallbackFn()
  2858. {
  2859. my ($FW_wname, $d, $FW_room, $cmd, $values) = @_;
  2860. # webCmd "temp 30" should remain text
  2861. # noArg is needed for fhem.cfg.demo / Cinema
  2862. return "" if(!$values || $values eq "noArg");
  2863. my($reading) = split( ' ', $cmd, 2 );
  2864. my $current;
  2865. if($cmd eq "desired-temp" || $cmd eq "desiredTemperature") {
  2866. $current = ReadingsVal($d, $cmd, 20);
  2867. $current =~ s/ .*//; # Cut off Celsius
  2868. $current = sprintf("%2.1f", int(2*$current)/2) if($current =~ m/[0-9.-]/);
  2869. } else {
  2870. $current = ReadingsVal($d, $reading, undef);
  2871. if( !defined($current) ) {
  2872. $reading = 'state';
  2873. $current = Value($d);
  2874. }
  2875. $current =~ s/$cmd //;
  2876. $current = ReplaceEventMap($d, $current, 1);
  2877. }
  2878. return "<td><div class='fhemWidget' cmd='$cmd' reading='$reading' ".
  2879. "dev='$d' arg='$values' current='$current'></div></td>";
  2880. }
  2881. # Widgets END
  2882. ###########################
  2883. sub
  2884. FW_visibleDevices(;$)
  2885. {
  2886. my($FW_wname) = @_;
  2887. my %devices = ();
  2888. foreach my $d (sort keys %defs) {
  2889. next if(!defined($defs{$d}));
  2890. my $h = $defs{$d};
  2891. next if(!$h->{TEMPORARY});
  2892. next if($h->{TYPE} ne "FHEMWEB");
  2893. next if(defined($FW_wname) && $h->{SNAME} ne $FW_wname);
  2894. next if(!defined($h->{inform}));
  2895. @devices{ keys %{$h->{inform}->{devices}} } =
  2896. values %{$h->{inform}->{devices}};
  2897. }
  2898. return %devices;
  2899. }
  2900. sub
  2901. FW_ActivateInform($;$)
  2902. {
  2903. my ($cl, $arg) = @_;
  2904. $FW_activateInform = ($arg ? $arg : 1);
  2905. }
  2906. sub
  2907. FW_widgetOverride($$)
  2908. {
  2909. my ($d, $str) = @_;
  2910. return $str if(!$str);
  2911. my $da = AttrVal($d, "widgetOverride", "");
  2912. my $fa = AttrVal($FW_wname, "widgetOverride", "");
  2913. return $str if(!$da && !$fa);
  2914. my @list;
  2915. push @list, split(" ", $fa) if($fa);
  2916. push @list, split(" ", $da) if($da);
  2917. foreach my $na (@list) {
  2918. my ($n,$a) = split(":", $na, 2);
  2919. $str =~ s/\b($n)\b(:[^ ]*)?/$1:$a/g;
  2920. }
  2921. return $str;
  2922. }
  2923. 1;
  2924. =pod
  2925. =item helper
  2926. =item summary HTTP Server and FHEM Frontend
  2927. =item summary_DE HTTP Server und FHEM Frontend
  2928. =begin html
  2929. <a name="FHEMWEB"></a>
  2930. <h3>FHEMWEB</h3>
  2931. <ul>
  2932. FHEMWEB is the builtin web-frontend, it also implements a simple web
  2933. server (optionally with Basic-Auth and HTTPS).
  2934. <br> <br>
  2935. <a name="FHEMWEBdefine"></a>
  2936. <b>Define</b>
  2937. <ul>
  2938. <code>define &lt;name&gt; FHEMWEB &lt;tcp-portnr&gt; [global|IP]</code>
  2939. <br><br>
  2940. Enable the webfrontend on port &lt;tcp-portnr&gt;. If global is specified,
  2941. then requests from all interfaces (not only localhost / 127.0.0.1) are
  2942. serviced. If IP is specified, then FHEMWEB will only listen on this IP.<br>
  2943. To enable listening on IPV6 see the comments <a href="#telnet">here</a>.
  2944. <br>
  2945. </ul>
  2946. <br>
  2947. <a name="FHEMWEBset"></a>
  2948. <b>Set</b>
  2949. <ul>
  2950. <li>rereadicons<br>
  2951. reads the names of the icons from the icon path. Use after adding or
  2952. deleting icons.
  2953. </li>
  2954. <li>clearSvgCache<br>
  2955. delete all files found in the www/SVGcache directory, which is used to
  2956. cache SVG data, if the SVGcache attribute is set.
  2957. </li>
  2958. </ul>
  2959. <br>
  2960. <a name="FHEMWEBget"></a>
  2961. <b>Get</b>
  2962. <ul>
  2963. <li>icon &lt;logical icon&gt;<br>
  2964. returns the absolute path to the logical icon. Example:
  2965. <ul>
  2966. <code>get myFHEMWEB icon FS20.on<br>
  2967. /data/Homeautomation/fhem/FHEM/FS20.on.png
  2968. </code>
  2969. </ul>
  2970. </li>
  2971. <li>pathlist<br>
  2972. return FHEMWEB specific directories, where files for given types are
  2973. located
  2974. <br><br>
  2975. </ul>
  2976. <a name="FHEMWEBattr"></a>
  2977. <b>Attributes</b>
  2978. <ul>
  2979. <a name="addHtmlTitle"></a>
  2980. <li>addHtmlTitle<br>
  2981. If set to 0, do not add a title Attribute to the set/get/attr detail
  2982. widgets. This might be necessary for some screenreaders. Default is 1.
  2983. </li><br>
  2984. <li><a href="#addStateEvent">addStateEvent</a></li><br>
  2985. <li>alias_&lt;RoomName&gt;<br>
  2986. If you define a userattr alias_&lt;RoomName&gt; and set this attribute
  2987. for a device assgined to &lt;RoomName&gt;, then this value will be used
  2988. when displaying &lt;RoomName&gt;.<br>
  2989. Note: you can use the userattr alias_.* to allow all rooms, but in this
  2990. case the attribute dropdown in the device detail view won't work for the
  2991. alias_.* attributes.
  2992. </li><br>
  2993. <li><a href="#allowfrom">allowfrom</a></li>
  2994. </li><br>
  2995. <li>allowedCommands, basicAuth, basicAuthMsg<br>
  2996. Please create these attributes for the corresponding <a
  2997. href="#allowed">allowed</a> device, they are deprecated for the FHEMWEB
  2998. instance from now on.
  2999. </li><br>
  3000. <a name="allowedHttpMethods"></a>
  3001. <li>allowedHttpMethods<br>
  3002. FHEMWEB implements the GET, POST and OPTIONS HTTP methods. Some external
  3003. devices require the HEAD method, which is not implemented correctly in
  3004. FHEMWEB, as FHEMWEB always returns a body, which, according to the spec,
  3005. is wrong. As in some cases this not a problem, enabling GET may work.
  3006. To do this, set this attribute to GET|POST|HEAD, default ist GET|POST.
  3007. OPTIONS is always enabled.
  3008. </li><br>
  3009. <a name="closeConn"></a>
  3010. <li>closeConn<br>
  3011. If set, a TCP Connection will only serve one HTTP request. Seems to
  3012. solve problems on iOS9 for WebApp startup.
  3013. </li><br>
  3014. <a name="column"></a>
  3015. <li>column<br>
  3016. Allows to display more than one column per room overview, by specifying
  3017. the groups for the columns. Example:<br>
  3018. <ul><code>
  3019. attr WEB column LivingRoom:FS20,notify|FHZ,notify DiningRoom:FS20|FHZ
  3020. </code></ul>
  3021. In this example in the LivingRoom the FS20 and the notify group is in
  3022. the first column, the FHZ and the notify in the second.<br>
  3023. Notes: some elements like SVG plots and readingsGroup can only be part of
  3024. a column if they are part of a <a href="#group">group</a>.
  3025. This attribute can be used to sort the groups in a room, just specify
  3026. the groups in one column.
  3027. Space in the room and group name has to be written as %20 for this
  3028. attribute. Both the room name and the groups are regular expressions.
  3029. </li>
  3030. <br>
  3031. <a name="confirmDelete"></a>
  3032. <li>confirmDelete<br>
  3033. confirm delete actions with a dialog. Default is 1, set it to 0 to
  3034. disable the feature.
  3035. </li>
  3036. <br>
  3037. <a name="confirmJSError"></a>
  3038. <li>confirmJSError<br>
  3039. JavaScript errors are reported in a dialog as default.
  3040. Set this attribute to 0 to disable the reporting.
  3041. </li>
  3042. <br>
  3043. <a name="CORS"></a>
  3044. <li>CORS<br>
  3045. If set to 1, FHEMWEB will supply a "Cross origin resource sharing"
  3046. header, see the wikipedia for details.
  3047. </li>
  3048. <br>
  3049. <a name="csrfToken"></a>
  3050. <li>csrfToken<br>
  3051. If set, FHEMWEB requires the value of this attribute as fwcsrf Parameter
  3052. for each command. It is used as countermeasure for Cross Site Resource
  3053. Forgery attacks. If the value is random, then a random number will be
  3054. generated on each FHEMWEB start. If it is set to the literal string
  3055. none, no token is expected. Default is random for featurelevel 5.8 and
  3056. greater, and none for featurelevel below 5.8 </li><br>
  3057. <a name="csrfTokenHTTPHeader"></a>
  3058. <li>csrfTokenHTTPHeader<br>
  3059. If set (default), FHEMWEB sends the token with the X-FHEM-csrfToken HTTP
  3060. header, which is used by some clients. Set it to 0 to switch it off, as
  3061. a measurre against shodan.io like FHEM-detection.</li><br>
  3062. <a name="CssFiles"></a>
  3063. <li>CssFiles<br>
  3064. Space separated list of .css files to be included. The filenames
  3065. are relative to the www directory. Example:
  3066. <ul><code>
  3067. attr WEB CssFiles pgm2/mystyle.css
  3068. </code></ul>
  3069. </li><br>
  3070. <a name="Css"></a>
  3071. <li>Css<br>
  3072. CSS included in the header after the CssFiles section.
  3073. </li><br>
  3074. <a name="cmdIcon"></a>
  3075. <li>cmdIcon<br>
  3076. Space separated list of cmd:iconName pairs. If set, the webCmd text is
  3077. replaced with the icon. An easy method to set this value is to use
  3078. "Extend devStateIcon" in the detail-view, and copy its value.<br>
  3079. Example:<ul>
  3080. attr lamp cmdIcon on:control_centr_arrow_up off:control_centr_arrow_down
  3081. </ul>
  3082. </li><br>
  3083. <a name="defaultRoom"></a>
  3084. <li>defaultRoom<br>
  3085. show the specified room if no room selected, e.g. on execution of some
  3086. commands. If set hides the <a href="#motd">motd</a>. Example:<br>
  3087. attr WEB defaultRoom Zentrale
  3088. </li>
  3089. <br>
  3090. <a name="devStateIcon"></a>
  3091. <li>devStateIcon<br>
  3092. First form:<br>
  3093. <ul>
  3094. Space separated list of regexp:icon-name:cmd triples, icon-name and cmd
  3095. may be empty.<br>
  3096. If the state of the device matches regexp, then icon-name will be
  3097. displayed as the status icon in the room, and (if specified) clicking
  3098. on the icon executes cmd. If fhem cannot find icon-name, then the
  3099. status text will be displayed.
  3100. Example:<br>
  3101. <ul>
  3102. attr lamp devStateIcon on:closed off:open<br>
  3103. attr lamp devStateIcon on::A0 off::AI<br>
  3104. attr lamp devStateIcon .*:noIcon<br>
  3105. </ul>
  3106. Note: if the image is referencing an SVG icon, then you can use the
  3107. @colorname suffix to color the image. E.g.:<br>
  3108. <ul>
  3109. attr Fax devStateIcon on:control_building_empty@red
  3110. off:control_building_filled:278727
  3111. </ul>
  3112. If the cmd is noFhemwebLink, then no HTML-link will be generated, i.e.
  3113. nothing will happen when clicking on the icon or text.
  3114. </ul>
  3115. Second form:<br>
  3116. <ul>
  3117. Perl code enclosed in {}. If the code returns undef, then the default
  3118. icon is used, if it retuns a string enclosed in <>, then it is
  3119. interpreted as an html string. Else the string is interpreted as a
  3120. devStateIcon of the first fom, see above.
  3121. Example:<br>
  3122. {'&lt;div
  3123. style="width:32px;height:32px;background-color:green"&gt;&lt;/div&gt;'}
  3124. </ul>
  3125. </li>
  3126. <br>
  3127. <a name="devStateStyle"></a>
  3128. <li>devStateStyle<br>
  3129. Specify an HTML style for the given device, e.g.:<br>
  3130. <ul>
  3131. attr sensor devStateStyle style="text-align:left;;font-weight:bold;;"<br>
  3132. </ul>
  3133. </li>
  3134. <br>
  3135. <li>deviceOverview<br>
  3136. Configures if the device line from the room view (device icon, state
  3137. icon and webCmds/cmdIcons) should also be shown in the device detail
  3138. view. Can be set to always, onClick, iconOnly or never. Default is
  3139. always.
  3140. </li><br>
  3141. <a name="editConfig"></a>
  3142. <li>editConfig<br>
  3143. If this FHEMWEB attribute is set to 1, then you will be able to edit
  3144. the FHEM configuration file (fhem.cfg) in the "Edit files" section.
  3145. After saving this file a rereadcfg is executed automatically, which has
  3146. a lot of side effects.<br>
  3147. </li><br>
  3148. <a name="editFileList"></a>
  3149. <li>editFileList<br>
  3150. Specify the list of Files shown in "Edit Files" section. It is a
  3151. newline separated list of triples, the first is the Title, the next is
  3152. the directory to search for as a perl expression(!), the third the
  3153. regular expression. Default
  3154. is:
  3155. <ul>
  3156. <code>
  3157. Own modules and helper files:$MW_dir:^(.*sh|[0-9][0-9].*Util.*pm|.*cfg|.*holiday|myUtilsTemplate.pm|.*layout)$<br>
  3158. Gplot files:$FW_gplotdir:^.*gplot$<br>
  3159. Styles:$FW_cssdir:^.*(css|svg)$<br>
  3160. </code>
  3161. </ul>
  3162. NOTE: The directory spec is not flexible: all .js/.css/_defs.svg files
  3163. come from www/pgm2 ($FW_cssdir), .gplot files from $FW_gplotdir
  3164. (www/gplot), everything else from $MW_dir (FHEM).
  3165. </li><br>
  3166. <a name="endPlotNow"></a>
  3167. <li>endPlotNow<br>
  3168. If this FHEMWEB attribute is set to 1, then day and hour plots will
  3169. end at current time. Else the whole day, the 6 hour period starting at
  3170. 0, 6, 12 or 18 hour or the whole hour will be shown. This attribute
  3171. is not used if the SVG has the attribute startDate defined.<br>
  3172. </li><br>
  3173. <a name="endPlotToday"></a>
  3174. <li>endPlotToday<br>
  3175. If this FHEMWEB attribute is set to 1, then week and month plots will
  3176. end today. Else the current week or the current month will be shown.
  3177. <br>
  3178. </li><br>
  3179. <a name="fwcompress"></a>
  3180. <li>fwcompress<br>
  3181. Enable compressing the HTML data (default is 1, i.e. yes, use 0 to switch it off).
  3182. </li>
  3183. <br>
  3184. <a name="forbiddenroom"></a>
  3185. <li>forbiddenroom<br>
  3186. just like hiddenroom (see below), but accessing the room or the
  3187. detailed view via direct URL is prohibited.
  3188. </li>
  3189. <br>
  3190. <a name="hiddengroup"></a>
  3191. <li>hiddengroup<br>
  3192. Comma separated list of groups to "hide", i.e. not to show in any room
  3193. of this FHEMWEB instance.<br>
  3194. Example: attr WEBtablet hiddengroup FileLog,dummy,at,notify
  3195. </li>
  3196. <br>
  3197. <a name="hiddengroupRegexp"></a>
  3198. <li>hiddengroupRegexp<br>
  3199. One regexp for the same purpose as hiddengroup.
  3200. </li>
  3201. <br>
  3202. <a name="hiddenroom"></a>
  3203. <li>hiddenroom<br>
  3204. Comma separated list of rooms to "hide", i.e. not to show. Special
  3205. values are input, detail and save, in which case the input areas, link
  3206. to the detailed views or save button are hidden (although each aspect
  3207. still can be addressed through URL manipulation).<br>
  3208. The list can also contain values from the additional "Howto/Wiki/FAQ"
  3209. block.
  3210. </li>
  3211. <br>
  3212. <a name="hiddenroomRegexp"></a>
  3213. <li>hiddenroomRegexp<br>
  3214. One regexp for the same purpose as hiddenroom. Example:
  3215. <ul>
  3216. attr WEB hiddenroomRegexp .*config
  3217. </ul>
  3218. Note: the special values input, detail and save cannot be specified
  3219. with hiddenroomRegexp.
  3220. </li>
  3221. <br>
  3222. <a name="HTTPS"></a>
  3223. <li>HTTPS<br>
  3224. Enable HTTPS connections. This feature requires the perl module
  3225. IO::Socket::SSL, to be installed with cpan -i IO::Socket::SSL or
  3226. apt-get install libio-socket-ssl-perl; OSX and the FritzBox-7390
  3227. already have this module.<br>
  3228. A local certificate has to be generated into a directory called certs,
  3229. this directory <b>must</b> be in the <a href="#modpath">modpath</a>
  3230. directory, at the same level as the FHEM directory.
  3231. <ul>
  3232. mkdir certs<br>
  3233. cd certs<br>
  3234. openssl req -new -x509 -nodes -out server-cert.pem -days 3650 -keyout server-key.pem
  3235. </ul>
  3236. <br>
  3237. </li>
  3238. <a name="icon"></a>
  3239. <li>icon<br>
  3240. Set the icon for a device in the room overview. There is an
  3241. icon-chooser in FHEMWEB to ease this task. Setting icons for the room
  3242. itself is indirect: there must exist an icon with the name
  3243. ico&lt;ROOMNAME&gt;.png in the iconPath.
  3244. </li>
  3245. <br>
  3246. <a name="iconPath"></a>
  3247. <li>iconPath<br>
  3248. colon separated list of directories where the icons are read from.
  3249. The directories start in the fhem/www/images directory. The default is
  3250. $styleSheetPrefix:fhemSVG:openautomation:default<br>
  3251. Set it to fhemSVG:openautomation to get only SVG images.
  3252. </li>
  3253. <br>
  3254. <a name="JavaScripts"></a>
  3255. <li>JavaScripts<br>
  3256. Space separated list of JavaScript files to be included. The filenames
  3257. are relative to the www directory. For each file an additional
  3258. user-settable FHEMWEB attribute will be created, to pass parameters to
  3259. the script. The name of this additional attribute gets the Param
  3260. suffix, directory and the fhem_ prefix will be deleted. Example:
  3261. <ul><code>
  3262. attr WEB JavaScripts codemirror/fhem_codemirror.js<br>
  3263. attr WEB codemirrorParam { "theme":"blackboard", "lineNumbers":true }
  3264. </code></ul>
  3265. </li><br>
  3266. <a name="longpoll"></a>
  3267. <li>longpoll [0|1|websocket]<br>
  3268. If activated, the browser is notifed when device states, readings or
  3269. attributes are changed, a reload of the page is not necessary.
  3270. Default is 1 (on), use 0 to deactivate it.<br>
  3271. If websocket is specified, then this API is used to notify the browser,
  3272. else HTTP longpoll. Note: some older browser do not implement websocket.
  3273. </li>
  3274. <br>
  3275. <a name="longpollSVG"></a>
  3276. <li>longpollSVG<br>
  3277. Reloads an SVG weblink, if an event should modify its content. Since
  3278. an exact determination of the affected events is too complicated, we
  3279. need some help from the definition in the .gplot file: the filter used
  3280. there (second parameter if the source is FileLog) must either contain
  3281. only the deviceName or have the form deviceName.event or deviceName.*.
  3282. This is always the case when using the <a href="#plotEditor">Plot
  3283. editor</a>. The SVG will be reloaded for <b>any</b> event triggered by
  3284. this deviceName. Default is off. Note: the plotEmbed attribute must be
  3285. set.
  3286. </li>
  3287. <br>
  3288. <a name="mainInputLength"></a>
  3289. <li>mainInputLength<br>
  3290. length of the maininput text widget in characters (decimal number).
  3291. </li>
  3292. <br>
  3293. <a name="menuEntries"></a>
  3294. <li>menuEntries<br>
  3295. Comma separated list of name,html-link pairs to display in the
  3296. left-side list. Example:<br>
  3297. attr WEB menuEntries fhem.de,http://fhem.de,culfw.de,http://culfw.de<br>
  3298. attr WEB menuEntries
  3299. AlarmOn,http://fhemhost:8083/fhem?cmd=set%20alarm%20on<br>
  3300. </li>
  3301. <br>
  3302. <a name="nameDisplay"></a>
  3303. <li>nameDisplay<br>
  3304. The argument is perl code, which is executed for each single device in
  3305. the room to determine the name displayed. $DEVICE is the name of the
  3306. current device, and $ALIAS is the value of the alias attribute or the
  3307. name of the device, if no alias is set. E.g. you can add a a global
  3308. userattr named alias_hu for the Hungarian translation, and specify
  3309. nameDisplay for the hungarian FHEMWEB instance as
  3310. <ul>
  3311. AttrVal($DEVICE, "alias_hu", $ALIAS)
  3312. </ul>
  3313. </li>
  3314. <br>
  3315. <a name="nrAxis"></a>
  3316. <li>nrAxis<br>
  3317. the number of axis for which space should be reserved on the left and
  3318. right sides of a plot and optionaly how many axes should realy be used
  3319. on each side, separated by comma: left,right[,useLeft,useRight]. You
  3320. can set individual numbers by setting the nrAxis of the SVG. Default is
  3321. 1,1.
  3322. </li><br>
  3323. <a name="ploteditor"></a>
  3324. <li>ploteditor<br>
  3325. Configures if the <a href="#plotEditor">Plot editor</a> should be shown
  3326. in the SVG detail view.
  3327. Can be set to always, onClick or never. Default is always.
  3328. </li><br>
  3329. <a name="plotEmbed"></a>
  3330. <li>plotEmbed<br>
  3331. If set (to 1), SVG plots will be rendered as part of &lt;embed&gt;
  3332. tags, as in the past this was the only way to display SVG. Setting
  3333. plotEmbed to 0 (the default) will render SVG in-place.<br>
  3334. </li><br>
  3335. <a name="plotfork"></a>
  3336. <li>plotfork<br>
  3337. If set to a nonzero value, run part of the processing (e.g. <a
  3338. href="#SVG">SVG</a> plot generation or <a href="#RSS">RSS</a> feeds) in
  3339. parallel processes, default is 0. Note: do not use it on systems with
  3340. small memory footprint.
  3341. </li><br>
  3342. <a name="plotmode"></a>
  3343. <li>plotmode<br>
  3344. Specifies how to generate the plots:
  3345. <ul>
  3346. <li>SVG<br>
  3347. The plots are created with the <a href="#SVG">SVG</a> module.
  3348. This is the default.</li>
  3349. <li>gnuplot-scroll<br>
  3350. The plots are created with the gnuplot program. The gnuplot
  3351. output terminal PNG is assumed. Scrolling to historical values
  3352. is also possible, just like with SVG.</li>
  3353. <li>gnuplot-scroll-svg<br>
  3354. Like gnuplot-scroll, but the output terminal SVG is assumed.</li>
  3355. </ul>
  3356. </li><br>
  3357. <a name="plotsize"></a>
  3358. <li>plotsize<br>
  3359. the default size of the plot, in pixels, separated by comma:
  3360. width,height. You can set individual sizes by setting the plotsize of
  3361. the SVG. Default is 800,160 for desktop, and 480,160 for
  3362. smallscreen.
  3363. </li><br>
  3364. <a name="plotWeekStartDay"></a>
  3365. <li>plotWeekStartDay<br>
  3366. Start the week-zoom of the SVG plots with this day.
  3367. 0 is Sunday, 1 is Monday, etc.<br>
  3368. </li><br>
  3369. <a name="redirectCmds"></a>
  3370. <li>redirectCmds<br>
  3371. Clear the browser URL window after issuing the command by redirecting
  3372. the browser, as a reload for the same site might have unintended
  3373. side-effects. Default is 1 (enabled). Disable it by setting this
  3374. attribute to 0 if you want to study the command syntax, in order to
  3375. communicate with FHEMWEB.
  3376. </li>
  3377. <br>
  3378. <a name="refresh"></a>
  3379. <li>refresh<br>
  3380. If set, a http-equiv="refresh" entry will be genererated with the given
  3381. argument (i.e. the browser will reload the page after the given
  3382. seconds).
  3383. </li><br>
  3384. <a name="reverseLogs"></a>
  3385. <li>reverseLogs<br>
  3386. Display the lines from the logfile in a reversed order, newest on the
  3387. top, so that you dont have to scroll down to look at the latest entries.
  3388. Note: enabling this attribute will prevent FHEMWEB from streaming
  3389. logfiles, resulting in a considerably increased memory consumption
  3390. (about 6 times the size of the file on the disk).
  3391. </li>
  3392. <br>
  3393. <a name="roomIcons"></a>
  3394. <li>roomIcons<br>
  3395. Space separated list of room:icon pairs, to override the default
  3396. behaviour of showing an icon, if there is one with the name of
  3397. "icoRoomName". This is the correct way to remove the icon for the room
  3398. Everything, or to set one for rooms with / in the name (e.g.
  3399. Anlagen/EDV). The first part is treated as regexp, so space is
  3400. represented by a dot. Example:<br>
  3401. attr WEB roomIcons Anlagen.EDV:icoEverything
  3402. </li>
  3403. <br>
  3404. <a name="smallscreenCommands"></a>
  3405. <li>smallscreenCommands<br>
  3406. If set to 1, commands, slider and dropdown menues will appear in
  3407. smallscreen landscape mode.
  3408. </li><br>
  3409. <a name="sortby"></a>
  3410. <li>sortby<br>
  3411. Take the value of this attribute when sorting the devices in the room
  3412. overview instead of the alias, or if that is missing the devicename
  3413. itself. If the sortby value is enclosed in {} than it is evaluated as a
  3414. perl expression. $NAME is set to the device name.
  3415. </li>
  3416. <br>
  3417. <a name="showUsedFiles"></a>
  3418. <li>showUsedFiles<br>
  3419. In the Edit files section, show only the used files.
  3420. Note: currently this is only working for the "Gplot files" section.
  3421. </li>
  3422. <br>
  3423. <a name="sortRooms"></a>
  3424. <li>sortRooms<br>
  3425. Space separated list of rooms to override the default sort order of the
  3426. room links. As the rooms in this attribute are actually regexps, space
  3427. in the roomname has to be specified as dot (.).
  3428. Example:<br>
  3429. attr WEB sortRooms DG OG EG Keller
  3430. </li>
  3431. <br>
  3432. <li>sslVersion<br>
  3433. See the global attribute sslVersion.
  3434. </li><br>
  3435. <a name="sslCertPrefix"></a>
  3436. <li>sslCertPrefix<br>
  3437. Set the prefix for the SSL certificate, default is certs/server-, see
  3438. also the HTTPS attribute.
  3439. </li><br>
  3440. <a name="styleData"></a>
  3441. <li>styleData<br>
  3442. data-storage used by dynamic styles like f18
  3443. </li><br>
  3444. <a name="stylesheetPrefix"></a>
  3445. <li>stylesheetPrefix<br>
  3446. prefix for the files style.css, svg_style.css and svg_defs.svg. If the
  3447. file with the prefix is missing, the default file (without prefix) will
  3448. be used. These files have to be placed into the FHEM directory, and can
  3449. be selected directly from the "Select style" FHEMWEB menu entry. Example:
  3450. <ul>
  3451. attr WEB stylesheetPrefix dark<br>
  3452. <br>
  3453. Referenced files:<br>
  3454. <ul>
  3455. darksvg_defs.svg<br>
  3456. darksvg_style.css<br>
  3457. darkstyle.css<br>
  3458. </ul>
  3459. <br>
  3460. </ul>
  3461. <b>Note:</b>if the argument contains the string smallscreen or touchpad,
  3462. then FHEMWEB will optimize the layout/access for small screen size (i.e.
  3463. smartphones) or touchpad devices (i.e. tablets)<br>
  3464. The default configuration installs 3 FHEMWEB instances: port 8083 for
  3465. desktop browsers, port 8084 for smallscreen, and 8085 for touchpad.<br>
  3466. If touchpad or smallscreen is specified, then WebApp support is
  3467. activated: After viewing the site on the iPhone or iPad in Safari, you
  3468. can add a link to the home-screen to get full-screen support. Links are
  3469. rendered differently in this mode to avoid switching back to the "normal"
  3470. browser.
  3471. </li>
  3472. <br>
  3473. <a name="SVGcache"></a>
  3474. <li>SVGcache<br>
  3475. if set, cache plots which won't change any more (the end-date is prior
  3476. to the current timestamp). The files are written to the www/SVGcache
  3477. directory. Default is off.<br>
  3478. See also the clearSvgCache command for clearing the cache.
  3479. </li><br>
  3480. <a name="title"></a>
  3481. <li>title<br>
  3482. Sets the title of the page. If enclosed in {} the content is evaluated.
  3483. </li><br>
  3484. <a name="viewport"></a>
  3485. <li>viewport<br>
  3486. Sets the &quot;viewport&quot; attribute in the HTML header. This can for
  3487. example be used to force the width of the page or disable zooming.<br>
  3488. Example: attr WEB viewport
  3489. width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no
  3490. </li><br>
  3491. <a name="webCmd"></a>
  3492. <li>webCmd<br>
  3493. Colon separated list of commands to be shown in the room overview for a
  3494. certain device. Has no effect on smallscreen devices, see the
  3495. devStateIcon command for an alternative.<br>
  3496. Example:
  3497. <ul>
  3498. attr lamp webCmd on:off:on-for-timer 10<br>
  3499. </ul>
  3500. <br>
  3501. The first specified command is looked up in the "set device ?" list
  3502. (see the <a href="#setList">setList</a> attribute for dummy devices).
  3503. If <b>there</b> it contains some known modifiers (colon, followed
  3504. by a comma separated list), then a different widget will be displayed.
  3505. See also the widgetOverride attribute below. Examples:
  3506. <ul>
  3507. define d1 dummy<br>
  3508. attr d1 webCmd state<br>
  3509. attr d1 readingList state<br>
  3510. attr d1 setList state:on,off<br><br>
  3511. define d2 dummy<br>
  3512. attr d2 webCmd state<br>
  3513. attr d2 readingList state<br>
  3514. attr d2 setList state:slider,0,1,10<br><br>
  3515. define d3 dummy<br>
  3516. attr d3 webCmd state<br>
  3517. attr d3 readingList state<br>
  3518. attr d3 setList state:time<br>
  3519. </ul>
  3520. If the command is state, then the value will be used as a command.<br>
  3521. Note: this is an attribute for the displayed device, not for the FHEMWEB
  3522. instance.
  3523. </li>
  3524. <br>
  3525. <a name="webCmdLabel"></a>
  3526. <li>webCmdLabel<br>
  3527. Colon separated list of labels, used to prefix each webCmd. The number
  3528. of labels must exactly match the number of webCmds. To implement
  3529. multiple rows, insert a return character after the text and before the
  3530. colon.</li></br>
  3531. <a name="webname"></a>
  3532. <li>webname<br>
  3533. Path after the http://hostname:port/ specification. Defaults to fhem,
  3534. i.e the default http address is http://localhost:8083/fhem
  3535. </li><br>
  3536. <a name="widgetOverride"></a>
  3537. <li>widgetOverride<br>
  3538. Space separated list of name:modifier pairs, to override the widget
  3539. for a set/get/attribute specified by the module author.
  3540. Following is the list of known modifiers:
  3541. <ul>
  3542. <!-- INSERT_DOC_FROM: www/pgm2/fhemweb.*.js -->
  3543. </ul>
  3544. </li>
  3545. <br>
  3546. </ul>
  3547. </ul>
  3548. =end html
  3549. =begin html_DE
  3550. <a name="FHEMWEB"></a>
  3551. <h3>FHEMWEB</h3>
  3552. <ul>
  3553. FHEMWEB ist das default WEB-Frontend, es implementiert auch einen einfachen
  3554. Webserver (optional mit Basic-Auth und HTTPS).
  3555. <br> <br>
  3556. <a name="FHEMWEBdefine"></a>
  3557. <b>Define</b>
  3558. <ul>
  3559. <code>define &lt;name&gt; FHEMWEB &lt;tcp-portnr&gt; [global|IP]</code>
  3560. <br><br>
  3561. Aktiviert das Webfrontend auf dem Port &lt;tcp-portnr&gt;. Mit dem
  3562. Parameter global werden Anfragen von allen Netzwerkschnittstellen
  3563. akzeptiert (nicht nur vom localhost / 127.0.0.1). Falls IP angegeben wurde,
  3564. dann werden nur Anfragen an diese IP Adresse akzeptiert. <br>
  3565. Informationen f&uuml;r den Betrieb mit IPv6 finden Sie <a
  3566. href="#telnet">hier</a>.<br>
  3567. </ul>
  3568. <br>
  3569. <a name="FHEMWEBset"></a>
  3570. <b>Set</b>
  3571. <ul>
  3572. <li>rereadicons<br>
  3573. Damit wird die Liste der Icons neu eingelesen, f&uuml;r den Fall, dass
  3574. Sie Icons l&ouml;schen oder hinzuf&uuml;gen.
  3575. </li>
  3576. <li>clearSvgCache<br>
  3577. Im Verzeichnis www/SVGcache werden SVG Daten zwischengespeichert, wenn
  3578. das Attribut SVGcache gesetzt ist. Mit diesem Befehl leeren Sie diesen
  3579. Zwischenspeicher.
  3580. </li>
  3581. </ul>
  3582. <br>
  3583. <a name="FHEMWEBget"></a>
  3584. <b>Get</b>
  3585. <ul>
  3586. <li>icon &lt;logical icon&gt;<br>
  3587. Liefert den absoluten Pfad des (logischen) Icons zur&uuml;ck. Beispiel:
  3588. <ul>
  3589. <code>get myFHEMWEB icon FS20.on<br>
  3590. /data/Homeautomation/fhem/FHEM/FS20.on.png
  3591. </code>
  3592. </ul></li>
  3593. <li>pathlist<br>
  3594. Zeigt diejenigen Verzeichnisse an, in welchen die verschiedenen Dateien
  3595. f&uuml;r FHEMWEB liegen.
  3596. </li>
  3597. <br><br>
  3598. </ul>
  3599. <a name="FHEMWEBattr"></a>
  3600. <b>Attribute</b>
  3601. <ul>
  3602. <a name="addHtmlTitle"></a>
  3603. <li>addHtmlTitle<br>
  3604. Falls der Wert 0 ist, wird bei den set/get/attr Parametern in der
  3605. DetailAnsicht der Ger&auml;te kein title Attribut gesetzt. Das is bei
  3606. manchen Screenreadern erforderlich. Die Voreinstellung ist 1.
  3607. </li><br>
  3608. <li><a href="#addStateEvent">addStateEvent</a></li><br>
  3609. <li>alias_&lt;RoomName&gt;<br>
  3610. Falls man das Attribut alias_&lt;RoomName&gt; definiert, und dieses
  3611. Attribut f&uuml;r ein Ger&auml;t setzt, dann wird dieser Wert bei
  3612. Anzeige von &lt;RoomName&gt; verwendet.<br>
  3613. Achtung: man kann im userattr auch alias_.* verwenden um alle
  3614. m&ouml;glichen R&auml;ume abzudecken, in diesem Fall wird aber die
  3615. Attributauswahl in der Detailansicht f&uuml;r alias_.* nicht
  3616. funktionieren.
  3617. </li><br>
  3618. <li><a href="#allowfrom">allowfrom</a>
  3619. </li><br>
  3620. <li>allowedCommands, basicAuth, basicAuthMsg<br>
  3621. Diese Attribute m&uuml;ssen ab sofort bei dem passenden <a
  3622. href="#allowed">allowed</a> Ger&auml;t angelegt werden, und sind
  3623. f&uuml;r eine FHEMWEB Instanz unerw&uuml;nscht.
  3624. </li><br>
  3625. <a name="allowedHttpMethods"></a>
  3626. <li>allowedHttpMethods</br>
  3627. FHEMWEB implementiert die HTTP Methoden GET, POST und OPTIONS. Manche
  3628. externe Ger&auml;te ben&ouml;tigen HEAD, das ist aber in FHEMWEB nicht
  3629. korrekt implementiert, da FHEMWEB immer ein body zur&uuml;ckliefert, was
  3630. laut Spec falsch ist. Da ein body in manchen F&auml;llen kein Problem
  3631. ist, kann man HEAD durch setzen dieses Attributes auf GET|POST|HEAD
  3632. aktivieren, die Voreinstellung ist GET|POST. OPTIONS ist immer
  3633. aktiviert.
  3634. </li><br>
  3635. <a name="closeConn"></a>
  3636. <li>closeConn<br>
  3637. Falls gesetzt, wird pro TCP Verbindung nur ein HTTP Request
  3638. durchgef&uuml;hrt. F&uuml;r iOS9 WebApp startups scheint es zu helfen.
  3639. </li><br>
  3640. <a name="cmdIcon"></a>
  3641. <li>cmdIcon<br>
  3642. Leerzeichen getrennte Auflistung von cmd:iconName Paaren.
  3643. Falls gesetzt, wird das webCmd text durch den icon gesetzt.
  3644. Am einfachsten setzt man cmdIcon indem man "Extend devStateIcon" im
  3645. Detail-Ansicht verwendet, und den Wert nach cmdIcon kopiert.<br>
  3646. Beispiel:<ul>
  3647. attr lamp cmdIcon on:control_centr_arrow_up off:control_centr_arrow_down
  3648. </ul>
  3649. </li><br>
  3650. <a name="column"></a>
  3651. <li>column<br>
  3652. Damit werden mehrere Spalten f&uuml;r einen Raum angezeigt, indem
  3653. sie verschiedene Gruppen Spalten zuordnen. Beispiel:<br>
  3654. <ul><code>
  3655. attr WEB column LivingRoom:FS20,notify|FHZ,notify DiningRoom:FS20|FHZ
  3656. </code></ul>
  3657. In diesem Beispiel werden im Raum LivingRoom die FS20 sowie die notify
  3658. Gruppe in der ersten Spalte, die FHZ und das notify in der zweiten
  3659. Spalte angezeigt.<br>
  3660. Anmerkungen: einige Elemente, wie SVG Plots und readingsGroup
  3661. k&ouml;nnen nur dann Teil einer Spalte sein wenn sie in <a
  3662. href="#group">group</a> stehen. Dieses Attribut kann man zum sortieren
  3663. der Gruppen auch dann verwenden, wenn man nur eine Spalte hat.
  3664. Leerzeichen im Raum- und Gruppennamen sind f&uuml;r dieses Attribut als
  3665. %20 zu schreiben. Raum- und Gruppenspezifikation ist jeweils ein
  3666. %regul&auml;rer Ausdruck.
  3667. </li><br>
  3668. <a name="confirmDelete"></a>
  3669. <li>confirmDelete<br>
  3670. L&ouml;schaktionen weden mit einem Dialog best&auml;tigt.
  3671. Falls dieses Attribut auf 0 gesetzt ist, entf&auml;llt das.
  3672. </li>
  3673. <br>
  3674. <a name="confirmJSError"></a>
  3675. <li>confirmJSError<br>
  3676. JavaScript Fehler werden per Voreinstellung in einem Dialog gemeldet.
  3677. Durch setzen dieses Attributes auf 0 werden solche Fehler nicht
  3678. gemeldet.
  3679. </li>
  3680. <br>
  3681. <a name="CORS"></a>
  3682. <li>CORS<br>
  3683. Wenn auf 1 gestellt, wird FHEMWEB einen "Cross origin resource sharing"
  3684. Header bereitstellen, n&auml;heres siehe Wikipedia.
  3685. </li><br>
  3686. <a name="csrfToken"></a>
  3687. <li>csrfToken<br>
  3688. Falls gesetzt, wird der Wert des Attributes als fwcsrf Parameter bei
  3689. jedem &uuml;ber FHEMWEB abgesetzten Kommando verlangt, es dient zum
  3690. Schutz von Cross Site Resource Forgery Angriffen.
  3691. Falls der Wert random ist, dann wird ein Zufallswert beim jeden FHEMWEB
  3692. Start neu generiert, falls er none ist, dann wird kein Parameter
  3693. verlangt. Default ist random f&uuml;r featurelevel 5.8 und
  3694. gr&ouml;&szlig;er, und none f&uuml;r featurelevel kleiner 5.8
  3695. </li><br>
  3696. <a name="csrfTokenHTTPHeader"></a>
  3697. <li>csrfTokenHTTPHeader<br>
  3698. Falls gesetzt (Voreinstellung), FHEMWEB sendet im HTTP Header den
  3699. csrfToken als X-FHEM-csrfToken, das wird von manchen FHEM-Clients
  3700. benutzt. Mit 0 kann man das abstellen, um Sites wie shodan.io die
  3701. Erkennung von FHEM zu erschweren.</li><br>
  3702. <a name="CssFiles"></a>
  3703. <li>CssFiles<br>
  3704. Leerzeichen getrennte Liste von .css Dateien, die geladen werden.
  3705. Die Dateinamen sind relativ zum www Verzeichnis anzugeben. Beispiel:
  3706. <ul><code>
  3707. attr WEB CssFiles pgm2/mystyle.css
  3708. </code></ul>
  3709. </li><br>
  3710. <a name="Css"></a>
  3711. <li>Css<br>
  3712. CSS, was nach dem CssFiles Abschnitt im Header eingefuegt wird.
  3713. </li><br>
  3714. <a name="defaultRoom"></a>
  3715. <li>defaultRoom<br>
  3716. Zeigt den angegebenen Raum an falls kein Raum explizit ausgew&auml;hlt
  3717. wurde. Achtung: falls gesetzt, wird motd nicht mehr angezeigt.
  3718. Beispiel:<br>
  3719. attr WEB defaultRoom Zentrale
  3720. </li><br>
  3721. <a name="devStateIcon"></a>
  3722. <li>devStateIcon<br>
  3723. Erste Variante:<br>
  3724. <ul>
  3725. Leerzeichen getrennte Auflistung von regexp:icon-name:cmd
  3726. Dreierp&auml;rchen, icon-name und cmd d&uuml;rfen leer sein.<br>
  3727. Wenn der Zustand des Ger&auml;tes mit der regexp &uuml;bereinstimmt,
  3728. wird als icon-name das entsprechende Status Icon angezeigt, und (falls
  3729. definiert), l&ouml;st ein Klick auf das Icon das entsprechende cmd aus.
  3730. Wenn fhem icon-name nicht finden kann, wird der Status als Text
  3731. angezeigt.
  3732. Beispiel:<br>
  3733. <ul>
  3734. attr lamp devStateIcon on:closed off:open<br>
  3735. attr lamp devStateIcon on::A0 off::AI<br>
  3736. attr lamp devStateIcon .*:noIcon<br>
  3737. </ul>
  3738. Anmerkung: Wenn das Icon ein SVG Bild ist, kann das @colorname Suffix
  3739. verwendet werden um das Icon einzuf&auml;rben. Z.B.:<br>
  3740. <ul>
  3741. attr Fax devStateIcon on:control_building_empty@red
  3742. off:control_building_filled:278727
  3743. </ul>
  3744. Falls cmd noFhemwebLink ist, dann wird kein HTML-Link generiert, d.h.
  3745. es passiert nichts, wenn man auf das Icon/Text klickt.
  3746. </ul>
  3747. Zweite Variante:<br>
  3748. <ul>
  3749. Perl regexp eingeschlossen in {}. Wenn der Code undef
  3750. zur&uuml;ckliefert, wird das Standard Icon verwendet; wird ein String
  3751. in <> zur&uuml;ck geliefert, wird dieser als HTML String interpretiert.
  3752. Andernfalls wird der String als devStateIcon gem&auml;&szlig; der
  3753. ersten Variante interpretiert, siehe oben. Beispiel:<br>
  3754. {'&lt;div style="width:32px;height:32px;background-color:green"&gt;&lt;/div&gt;'}
  3755. </ul>
  3756. </li><br>
  3757. <a name="devStateStyle"></a>
  3758. <li>devStateStyle<br>
  3759. F&uuml;r ein best. Ger&auml;t einen best. HTML-Style benutzen.
  3760. Beispiel:<br>
  3761. <ul>
  3762. attr sensor devStateStyle style="text-align:left;;font-weight:bold;;"<br>
  3763. </ul>
  3764. </li><br>
  3765. <li>deviceOverview<br>
  3766. Gibt an ob die Darstellung aus der Raum-Ansicht (Zeile mit
  3767. Ger&uuml;teicon, Stateicon und webCmds/cmdIcons) auch in der
  3768. Detail-Ansicht angezeigt werden soll. Kann auf always, onClick,
  3769. iconOnly oder never gesetzt werden. Der Default ist always.
  3770. </li><br>
  3771. <a name="editConfig"></a>
  3772. <li>editConfig<br>
  3773. Falls dieses FHEMWEB Attribut (auf 1) gesetzt ist, dann kann man die
  3774. FHEM Konfigurationsdatei in dem "Edit files" Abschnitt bearbeiten. Beim
  3775. Speichern dieser Datei wird automatisch rereadcfg ausgefuehrt, was
  3776. diverse Nebeneffekte hat.<br>
  3777. </li><br>
  3778. <a name="editFileList"></a>
  3779. <li>editFileList<br>
  3780. Definiert die Liste der angezeigten Dateien in der "Edit Files"
  3781. Abschnitt. Es ist eine Newline getrennte Liste von Tripeln bestehend
  3782. aus Titel, Verzeichnis f&uuml;r die Suche als perl Ausdruck(!), und
  3783. Regexp. Die Voreinstellung ist:
  3784. <ul>
  3785. <code>
  3786. Own modules and helper files:$MW_dir:^(.*sh|[0-9][0-9].*Util.*pm|.*cfg|.*holiday|myUtilsTemplate.pm|.*layout)$<br>
  3787. Gplot files:$FW_gplotdir:^.*gplot$<br>
  3788. Styles:$FW_cssdir:^.*(css|svg)$<br>
  3789. </code>
  3790. </ul>
  3791. Achtung: die Verzeichnis Angabe ist nicht flexibel: alle
  3792. .js/.css/_defs.svg Dateien sind in www/pgm2 ($FW_cssdir), .gplot
  3793. Dateien in $FW_gplotdir (www/gplot), alles andere in $MW_dir (FHEM).
  3794. </li><br>
  3795. <a name="endPlotNow"></a>
  3796. <li>endPlotNow<br>
  3797. Wenn Sie dieses FHEMWEB Attribut auf 1 setzen, werden Tages und
  3798. Stunden-Plots zur aktuellen Zeit beendet. (&Auml;hnlich wie
  3799. endPlotToday, nur eben min&uuml;tlich).
  3800. Ansonsten wird der gesamte Tag oder eine 6 Stunden Periode (0, 6, 12,
  3801. 18 Stunde) gezeigt. Dieses Attribut wird nicht verwendet, wenn das SVG
  3802. Attribut startDate benutzt wird.<br>
  3803. </li><br>
  3804. <a name="endPlotToday"></a>
  3805. <li>endPlotToday<br>
  3806. Wird dieses FHEMWEB Attribut gesetzt, so enden Wochen- bzw. Monatsplots
  3807. am aktuellen Tag, sonst wird die aktuelle Woche/Monat angezeigt.
  3808. </li><br>
  3809. <a name="forbiddenroom"></a>
  3810. <li>forbiddenroom<br>
  3811. Wie hiddenroom, aber der Zugriff auf die Raum- oder Detailansicht
  3812. &uuml;ber direkte URL-Eingabe wird unterbunden.
  3813. </li><br>
  3814. <a name="fwcompress"></a>
  3815. <li>fwcompress<br>
  3816. Aktiviert die HTML Datenkompression (Standard ist 1, also ja, 0 stellt
  3817. die Kompression aus).
  3818. </li><br>
  3819. <a name="hiddengroup"></a>
  3820. <li>hiddengroup<br>
  3821. Wie hiddenroom (siehe unten), jedoch auf Ger&auml;tegruppen bezogen.
  3822. <br>
  3823. Beispiel: attr WEBtablet hiddengroup FileLog,dummy,at,notify
  3824. </li><br>
  3825. <a name="hiddengroupRegexp"></a>
  3826. <li>hiddengroupRegexp<br>
  3827. Ein regul&auml;rer Ausdruck, um Gruppen zu verstecken.
  3828. </li>
  3829. <br>
  3830. <a name="hiddenroom"></a>
  3831. <li>hiddenroom<br>
  3832. Eine Komma getrennte Liste, um R&auml;ume zu verstecken, d.h. nicht
  3833. anzuzeigen. Besondere Werte sind input, detail und save. In diesem
  3834. Fall werden diverse Eingabefelder ausgeblendent. Durch direktes Aufrufen
  3835. der URL sind diese R&auml;ume weiterhin erreichbar!<br>
  3836. Ebenso k&ouml;nnen Eintr&auml;ge in den Logfile/Commandref/etc Block
  3837. versteckt werden. </li><br>
  3838. <a name="hiddenroomRegexp"></a>
  3839. <li>hiddenroomRegexp<br>
  3840. Ein regul&auml;rer Ausdruck, um R&auml;ume zu verstecken. Beispiel:
  3841. <ul>
  3842. attr WEB hiddenroomRegexp .*config
  3843. </ul>
  3844. Achtung: die besonderen Werte input, detail und save m&uuml;ssen mit
  3845. hiddenroom spezifiziert werden.
  3846. </li>
  3847. <br>
  3848. <a name="HTTPS"></a>
  3849. <li>HTTPS<br>
  3850. Erm&ouml;glicht HTTPS Verbindungen. Es werden die Perl Module
  3851. IO::Socket::SSL ben&ouml;tigt, installierbar mit cpan -i
  3852. IO::Socket::SSL oder apt-get install libio-socket-ssl-perl; (OSX und
  3853. die FritzBox-7390 haben dieses Modul schon installiert.)<br>
  3854. Ein lokales Zertifikat muss im Verzeichis certs erzeugt werden.
  3855. Dieses Verzeichnis <b>muss</b> im <a href="#modpath">modpath</a>
  3856. angegeben werden, also auf der gleichen Ebene wie das FHEM Verzeichnis.
  3857. Beispiel:
  3858. <ul>
  3859. mkdir certs<br>
  3860. cd certs<br>
  3861. openssl req -new -x509 -nodes -out server-cert.pem -days 3650 -keyout
  3862. server-key.pem
  3863. </ul>
  3864. <br>
  3865. </li>
  3866. <a name="icon"></a>
  3867. <li>icon<br>
  3868. Damit definiert man ein Icon f&uuml;r die einzelnen Ger&auml;te in der
  3869. Raum&uuml;bersicht. Es gibt einen passenden Link in der Detailansicht
  3870. um das zu vereinfachen. Um ein Bild f&uuml;r die R&auml;ume selbst zu
  3871. definieren muss ein Icon mit dem Namen ico&lt;Raumname&gt;.png im
  3872. iconPath existieren (oder man verwendet roomIcons, s.u.)
  3873. </li><br>
  3874. <a name="iconPath"></a>
  3875. <li>iconPath<br>
  3876. Durch Doppelpunkt getrennte Aufz&auml;hlung der Verzeichnisse, in
  3877. welchen nach Icons gesucht wird. Die Verzeichnisse m&uuml;ssen unter
  3878. fhem/www/images angelegt sein. Standardeinstellung ist:
  3879. $styleSheetPrefix:fhemSVG:openautomation:default<br>
  3880. Setzen Sie den Wert auf fhemSVG:openautomation um nur SVG Bilder zu
  3881. benutzen.
  3882. </li><br>
  3883. <a name="JavaScripts"></a>
  3884. <li>JavaScripts<br>
  3885. Leerzeichen getrennte Liste von JavaScript Dateien, die geladen werden.
  3886. Die Dateinamen sind relativ zum www Verzeichnis anzugeben. F&uuml;r
  3887. jede Datei wird ein zus&auml;tzliches Attribut angelegt, damit der
  3888. Benutzer dem Skript Parameter weiterreichen kann. Bei diesem
  3889. Attributnamen werden Verzeichnisname und fhem_ Pr&auml;fix entfernt
  3890. und Param als Suffix hinzugef&uuml;gt. Beispiel:
  3891. <ul><code>
  3892. attr WEB JavaScripts codemirror/fhem_codemirror.js<br>
  3893. attr WEB codemirrorParam { "theme":"blackboard", "lineNumbers":true }
  3894. </code></ul>
  3895. </li><br>
  3896. <a name="longpoll"></a>
  3897. <li>longpoll [0|1|websocket]<br>
  3898. Falls gesetzt, FHEMWEB benachrichtigt den Browser, wenn
  3899. Ger&auml;testatuus, Readings or Attribute sich &auml;ndern, ein
  3900. Neuladen der Seite ist nicht notwendig. Zum deaktivieren 0 verwenden.
  3901. <br>
  3902. Falls websocket spezifiziert ist, l&auml;uft die Benachrichtigung des
  3903. Browsers &uuml;ber dieses Verfahren sonst &uuml;ber HTTP longpoll.
  3904. Achtung: &auml;ltere Browser haben keine websocket Implementierung.
  3905. </li><br>
  3906. <a name="longpollSVG"></a>
  3907. <li>longpollSVG<br>
  3908. L&auml;dt SVG Instanzen erneut, falls ein Ereignis dessen Inhalt
  3909. &auml;ndert. Funktioniert nur, falls die dazugeh&ouml;rige Definition
  3910. der Quelle in der .gplot Datei folgenden Form hat: deviceName.Event
  3911. bzw. deviceName.*. Wenn man den <a href="#plotEditor">Plot Editor</a>
  3912. benutzt, ist das &uuml;brigens immer der Fall. Die SVG Datei wird bei
  3913. <b>jedem</b> ausl&ouml;senden Event dieses Ger&auml;tes neu geladen.
  3914. Die Voreinstellung ist aus. Achtung: das plotEmbed Attribute muss
  3915. gesetzt sein.
  3916. </li><br>
  3917. <a name="mainInputLength"></a>
  3918. <li>mainInputLength<br>
  3919. L&auml;nge des maininput Eingabefeldes (Anzahl der Buchstaben,
  3920. Ganzzahl).
  3921. </li> <br>
  3922. <a name="menuEntries"></a>
  3923. <li>menuEntries<br>
  3924. Komma getrennte Liste; diese Links werden im linken Men&uuml; angezeigt.
  3925. Beispiel:<br>
  3926. attr WEB menuEntries fhem.de,http://fhem.de,culfw.de,http://culfw.de<br>
  3927. attr WEB menuEntries
  3928. AlarmOn,http://fhemhost:8083/fhem?cmd=set%20alarm%20on<br>
  3929. </li><br>
  3930. <a name="nameDisplay"></a>
  3931. <li>nameDisplay<br>
  3932. Das Argument ist Perl-Code, was f&uuml;r jedes Ger&auml;t in der
  3933. Raum-&Uuml;bersicht ausgef&uuml;hrt wird, um den angezeigten Namen zu
  3934. berechnen. Dabei kann man die Variable $DEVICE f&uuml;r den aktuellen
  3935. Ger&auml;tenamen, und $ALIAS f&uuml;r den aktuellen alias bzw. Name,
  3936. falls alias nicht gesetzt ist, verwenden. Z.Bsp. f&uuml;r eine FHEMWEB
  3937. Instanz mit ungarischer Anzeige f&uuml;gt man ein global userattr
  3938. alias_hu hinzu, und man setzt nameDisplay f&uuml;r diese FHEMWEB
  3939. Instanz auf dem Wert:
  3940. <ul>
  3941. AttrVal($DEVICE, "alias_hu", $ALIAS)
  3942. </ul>
  3943. </li>
  3944. <br>
  3945. <a name="nrAxis"></a>
  3946. <li>nrAxis<br>
  3947. (bei mehrfach-Y-Achsen im SVG-Plot) Die Darstellung der Y Achsen
  3948. ben&ouml;tigt Platz. Hierdurch geben Sie an wie viele Achsen Sie
  3949. links,rechts [useLeft,useRight] ben&ouml;tigen. Default ist 1,1 (also 1
  3950. Achse links, 1 Achse rechts).
  3951. </li><br>
  3952. <a name="ploteditor"></a>
  3953. <li>ploteditor<br>
  3954. Gibt an ob der <a href="#plotEditor">Plot Editor</a> in der SVG detail
  3955. ansicht angezeigt werden soll. Kann auf always, onClick oder never
  3956. gesetzt werden. Der Default ist always.
  3957. </li><br>
  3958. <a name="plotEmbed"></a>
  3959. <li>plotEmbed 0<br>
  3960. Falls gesetzt (auf 1), dann werden SVG Grafiken mit &lt;embed&gt; Tags
  3961. gerendert, da auf &auml;lteren Browsern das die einzige
  3962. M&ouml;glichkeit war, SVG dastellen zu k&ouml;nnen. Falls 0 (die
  3963. Voreinstellung), dann werden die SVG Grafiken "in-place" gezeichnet.
  3964. </li><br>
  3965. <a name="plotfork"></a>
  3966. <li>plotfork<br>
  3967. Falls gesetzt, dann werden bestimmte Berechnungen (z.Bsp. SVG und RSS)
  3968. auf nebenl&auml;ufige Prozesse verteilt. Voreinstellung ist 0. Achtung:
  3969. nicht auf Systemen mit wenig Hauptspeicher verwenden.
  3970. </li><br>
  3971. <a name="plotmode"></a>
  3972. <li>plotmode<br>
  3973. Spezifiziert, wie Plots erzeugt werden sollen:
  3974. <ul>
  3975. <li>SVG<br>
  3976. Die Plots werden mit Hilfe des <a href="#SVG">SVG</a> Moduls als SVG
  3977. Grafik gerendert. Das ist die Standardeinstellung.</li>
  3978. <li>gnuplot-scroll<br>
  3979. Die plots werden mit dem Programm gnuplot erstellt. Das output
  3980. terminal ist PNG. Der einfache Zugriff auf historische Daten
  3981. ist m&ouml;glich (analog SVG).
  3982. </li>
  3983. <li>gnuplot-scroll-svg<br>
  3984. Wie gnuplot-scroll, aber als output terminal wird SVG angenommen.
  3985. </li>
  3986. </ul>
  3987. </li><br>
  3988. <a name="plotsize"></a>
  3989. <li>plotsize<br>
  3990. gibt die Standardbildgr&ouml;&szlig;e aller erzeugten Plots an als
  3991. Breite,H&ouml;he an. Um einem individuellen Plot die Gr&ouml;&szlig;e zu
  3992. &auml;ndern muss dieses Attribut bei der entsprechenden SVG Instanz
  3993. gesetzt werden. Default sind 800,160 f&uuml;r Desktop und 480,160
  3994. f&uuml;r Smallscreen
  3995. </li><br>
  3996. <a name="plotWeekStartDay"></a>
  3997. <li>plotWeekStartDay<br>
  3998. Starte das Plot in der Wochen-Ansicht mit diesem Tag.
  3999. 0 ist Sonntag, 1 ist Montag, usw.
  4000. </li><br>
  4001. <a name="redirectCmds"></a>
  4002. <li>redirectCmds<br>
  4003. Damit wird das URL Eingabefeld des Browser nach einem Befehl geleert.
  4004. Standard ist eingeschaltet (1), ausschalten kann man es durch
  4005. setzen des Attributs auf 0, z.Bsp. um den Syntax der Kommunikation mit
  4006. FHEMWEB zu untersuchen.
  4007. </li><br>
  4008. <a name="refresh"></a>
  4009. <li>refresh<br>
  4010. Damit erzeugen Sie auf den ausgegebenen Webseiten einen automatischen
  4011. Refresh, z.B. nach 5 Sekunden.
  4012. </li><br>
  4013. <a name="reverseLogs"></a>
  4014. <li>reverseLogs<br>
  4015. Damit wird das Logfile umsortiert, die neuesten Eintr&auml;ge stehen
  4016. oben. Der Vorteil ist, dass man nicht runterscrollen muss um den
  4017. neuesten Eintrag zu sehen, der Nachteil dass FHEM damit deutlich mehr
  4018. Hauptspeicher ben&ouml;tigt, etwa 6 mal so viel, wie das Logfile auf
  4019. dem Datentr&auml;ger gro&szlig; ist. Das kann auf Systemen mit wenig
  4020. Speicher (FRITZ!Box) zum Terminieren des FHEM Prozesses durch das
  4021. Betriebssystem f&uuml;hren.
  4022. </li><br>
  4023. <a name="roomIcons"></a>
  4024. <li>roomIcons<br>
  4025. Leerzeichen getrennte Liste von room:icon Zuordnungen
  4026. Der erste Teil wird als regexp interpretiert, daher muss ein
  4027. Leerzeichen als Punkt geschrieben werden. Beispiel:<br>
  4028. attr WEB roomIcons Anlagen.EDV:icoEverything
  4029. </li><br>
  4030. <a name="sortby"></a>
  4031. <li>sortby<br>
  4032. Der Wert dieses Attributs wird zum sortieren von Ger&auml;ten in
  4033. R&auml;umen verwendet, sonst w&auml;re es der Alias oder, wenn keiner
  4034. da ist, der Ger&auml;tename selbst. Falls der Wert des sortby
  4035. Attributes in {} eingeschlossen ist, dann wird er als ein perl Ausdruck
  4036. evaluiert. $NAME wird auf dem Ger&auml;tenamen gesetzt.
  4037. </li><br>
  4038. <a name="showUsedFiles"></a>
  4039. <li>showUsedFiles<br>
  4040. Zeige nur die verwendeten Dateien in der "Edit files" Abschnitt.
  4041. Achtung: aktuell ist das nur f&uuml;r den "Gplot files" Abschnitt
  4042. implementiert.
  4043. </li>
  4044. <br>
  4045. <a name="sortRooms"></a>
  4046. <li>sortRooms<br>
  4047. Durch Leerzeichen getrennte Liste von R&auml;umen, um deren Reihenfolge
  4048. zu definieren.
  4049. Da die R&auml;ume in diesem Attribut als Regexp interpretiert werden,
  4050. sind Leerzeichen im Raumnamen als Punkt (.) zu hinterlegen.
  4051. Beispiel:<br>
  4052. attr WEB sortRooms DG OG EG Keller
  4053. </li><br>
  4054. <a name="smallscreenCommands"></a>
  4055. <li>smallscreenCommands<br>
  4056. Falls auf 1 gesetzt werden Kommandos, Slider und Dropdown Men&uuml;s im
  4057. Smallscreen Landscape Modus angezeigt.
  4058. </li><br>
  4059. <li>sslVersion<br>
  4060. Siehe das global Attribut sslVersion.
  4061. </li><br>
  4062. <a name="sslCertPrefix"></a>
  4063. <li>sslCertPrefix<br>
  4064. Setzt das Pr&auml;fix der SSL-Zertifikate, die Voreinstellung ist
  4065. certs/server-, siehe auch das HTTP Attribut.
  4066. </li><br>
  4067. <a name="styleData"></a>
  4068. <li>styleData<br>
  4069. wird von dynamischen styles wie f18 werwendet
  4070. </li><br>
  4071. <a name="stylesheetPrefix"></a>
  4072. <li>stylesheetPrefix<br>
  4073. Pr&auml;fix f&uuml;r die Dateien style.css, svg_style.css und
  4074. svg_defs.svg. Wenn die Datei mit dem Pr&auml;fix fehlt, wird die Default
  4075. Datei (ohne Pr&auml;fix) verwendet. Diese Dateien m&uuml;ssen im FHEM
  4076. Ordner liegen und k&ouml;nnen direkt mit "Select style" im FHEMWEB
  4077. Men&uuml;eintrag ausgew&auml;hlt werden. Beispiel:
  4078. <ul>
  4079. attr WEB stylesheetPrefix dark<br>
  4080. <br>
  4081. Referenzdateien:<br>
  4082. <ul>
  4083. darksvg_defs.svg<br>
  4084. darksvg_style.css<br>
  4085. darkstyle.css<br>
  4086. </ul>
  4087. <br>
  4088. </ul>
  4089. <b>Anmerkung:</b>Wenn der Parametername smallscreen oder touchpad
  4090. enth&auml;lt, wird FHEMWEB das Layout/den Zugriff f&uuml;r entsprechende
  4091. Ger&auml;te (Smartphones oder Touchpads) optimieren<br>
  4092. Standardm&auml;&szlig;ig werden 3 FHEMWEB Instanzen aktiviert: Port 8083
  4093. f&uuml;r Desktop Browser, Port 8084 f&uuml;r Smallscreen, und 8085
  4094. f&uuml;r Touchpad.<br>
  4095. Wenn touchpad oder smallscreen benutzt werden, wird WebApp support
  4096. aktiviert: Nachdem Sie eine Seite am iPhone oder iPad mit Safari
  4097. angesehen haben, k&ouml;nnen Sie einen Link auf den Homescreen anlegen um
  4098. die Seite im Fullscreen Modus zu sehen. Links werden in diesem Modus
  4099. anders gerendert, um ein "Zur&uuml;ckfallen" in den "normalen" Browser zu
  4100. verhindern.
  4101. </li><br>
  4102. <a name="SVGcache"></a>
  4103. <li>SVGcache<br>
  4104. Plots die sich nicht mehr &auml;ndern, werden im SVGCache Verzeichnis
  4105. (www/SVGcache) gespeichert, um die erneute, rechenintensive
  4106. Berechnung der Grafiken zu vermeiden. Default ist 0, d.h. aus.<br>
  4107. Siehe den clearSvgCache Befehl um diese Daten zu l&ouml;schen.
  4108. </li><br>
  4109. <a name="title"></a>
  4110. <li>title<br>
  4111. Setzt den Titel der Seite. Falls in {} eingeschlossen, dann wird es
  4112. als Perl Ausdruck evaluiert.
  4113. </li><br>
  4114. <a name="viewport"></a>
  4115. <li>viewport<br>
  4116. Setzt das &quot;viewport&quot; Attribut im HTML Header. Das kann benutzt
  4117. werden um z.B. die Breite fest vorzugeben oder Zoomen zu verhindern.<br>
  4118. Beispiel: attr WEB viewport
  4119. width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no
  4120. </li><br>
  4121. <a name="webCmd"></a>
  4122. <li>webCmd<br>
  4123. Durch Doppelpunkte getrennte Auflistung von Befehlen, die f&uuml;r ein
  4124. bestimmtes Ger&auml;t gelten sollen. Funktioniert nicht mit
  4125. smallscreen, ein Ersatz daf&uuml;r ist der devStateIcon Befehl.<br>
  4126. Beispiel:
  4127. <ul>
  4128. attr lamp webCmd on:off:on-for-timer 10<br>
  4129. </ul>
  4130. <br>
  4131. Der erste angegebene Befehl wird in der "set device ?" list
  4132. nachgeschlagen (Siehe das <a href="#setList">setList</a> Attrib
  4133. f&uuml;r Dummy Ger&auml;te). Wenn <b>dort</b> bekannte Modifier sind,
  4134. wird ein anderes Widget angezeigt. Siehe auch widgetOverride.<br>
  4135. Wenn der Befehl state ist, wird der Wert als Kommando interpretiert.<br>
  4136. Beispiele:
  4137. <ul>
  4138. define d1 dummy<br>
  4139. attr d1 webCmd state<br>
  4140. attr d1 setList state:on,off<br>
  4141. define d2 dummy<br>
  4142. attr d2 webCmd state<br>
  4143. attr d2 setList state:slider,0,1,10<br>
  4144. define d3 dummy<br>
  4145. attr d3 webCmd state<br>
  4146. attr d3 setList state:time<br>
  4147. </ul>
  4148. Anmerkung: dies ist ein Attribut f&uuml;r das anzuzeigende Ger&auml;t,
  4149. nicht f&uuml;r die FHEMWEBInstanz.
  4150. </li><br>
  4151. <a name="webCmdLabel"></a>
  4152. <li>webCmdLabel<br>
  4153. Durch Doppelpunkte getrennte Auflistung von Texten, die vor dem
  4154. jeweiligen webCmd angezeigt werden. Der Anzahl der Texte muss exakt den
  4155. Anzahl der webCmds entsprechen. Um mehrzeilige Anzeige zu realisieren,
  4156. kann ein Return nach dem Text und vor dem Doppelpunkt eingefuehrt
  4157. werden.</li><br>
  4158. <a name="webname"></a>
  4159. <li>webname<br>
  4160. Der Pfad nach http://hostname:port/ . Standard ist fhem,
  4161. so ist die Standard HTTP Adresse http://localhost:8083/fhem
  4162. </li><br>
  4163. <a name="widgetOverride"></a>
  4164. <li>widgetOverride<br>
  4165. Leerzeichen separierte Liste von Name/Modifier Paaren, mit dem man den
  4166. vom Modulautor f&uuml;r einen bestimmten Parameter (Set/Get/Attribut)
  4167. vorgesehene Widgets &auml;ndern kann. Folgendes ist die Liste der
  4168. bekannten Modifier:
  4169. <ul>
  4170. <!-- INSERT_DOC_FROM: www/pgm2/fhemweb.*.js -->
  4171. </ul></li>
  4172. </ul>
  4173. </ul>
  4174. =end html_DE
  4175. =cut