01_FHEMWEB.pm 152 KB

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