98_DOIFtools.pm 107 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011
  1. #############################################
  2. # $Id: 98_DOIFtools.pm 15374 2017-11-01 11:02:53Z Ellert $
  3. #
  4. # This file is part of fhem.
  5. #
  6. # Fhem is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 2 of the License, or
  9. # (at your option) any later version.
  10. # Fhem is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. ###############################################
  19. package main;
  20. use strict;
  21. use warnings;
  22. use Time::Local;
  23. use Color;
  24. sub DOIFtools_Initialize($);
  25. sub DOIFtools_Set($@);
  26. sub DOIFtools_Get($@);
  27. sub DOIFtools_Undef;
  28. sub DOIFtools_Define($$$);
  29. sub DOIFtools_Attr(@);
  30. sub DOIFtools_Notify($$);
  31. sub DOIFtoolsRg;
  32. sub DOIFtoolsNxTimer;
  33. sub DOIFtoolsNextTimer;
  34. sub DOIFtoolsGetAssocDev;
  35. sub DOIFtoolsCheckDOIF;
  36. sub DOIFtoolsCheckDOIFcoll;
  37. sub DOIFtools_fhemwebFn($$$$);
  38. sub DOIFtools_eM($$$$);
  39. sub DOIFtools_dO ($$$$);
  40. sub DOIFtoolsSetNotifyDev;
  41. sub DOIFtools_logWrapper($);
  42. sub DOIFtoolsCounterReset($);
  43. sub DOIFtoolsDeleteStatReadings;
  44. my @DOIFtools_we = [0,0,0,0,0,0,0,0];
  45. my $DOIFtoolsJSfuncEM = <<'EOF';
  46. <script type="text/javascript">
  47. //functions
  48. function doiftoolsCopyToClipboard() {
  49. var r = $("head").attr("root");
  50. var myFW_root = FW_root;
  51. if(r)
  52. myFW_root = r;
  53. var lang = $('#doiftoolstype').attr('lang');
  54. var txtarea = document.getElementById("console");
  55. var start = txtarea.selectionStart;
  56. var finish = txtarea.selectionEnd;
  57. var txt = $("textarea#console").text().substring(start, finish);
  58. var hlp = lang ? "Bitte, genau eine komplette Eventzeile markieren." : "Please highlight exactly one complete event line.";
  59. $('#console').attr('disabled', 'disabled');
  60. $('#console').removeAttr('disabled');
  61. if(!txt)
  62. return FW_okDialog(hlp);
  63. var redi=/^....-..-..\s..:..:..(\....)?\s([^\s]+)\s([^\s]+)\s([^\s]+:\s)?(.*)([\n]*)?$/;
  64. var retdi = txt.match(redi);
  65. if(!retdi)
  66. return FW_okDialog("\""+txt+"\" "+(lang ? "ist keine gültige Auswahl." : "is not a valid selection.")+"<br>"+hlp);
  67. var evtDev = retdi[3];
  68. var retdi1;
  69. var evtRead ="";
  70. var evtVal ="";
  71. if (retdi[4]) {
  72. retdi1 = retdi[4].match(/(.*):\s$/);
  73. evtRead = retdi1[1];
  74. }
  75. evtVal = retdi[5];
  76. var treffer = evtVal.match(/(-?\d+(\.\d+)?)/);
  77. var evtNum;
  78. try {
  79. evtNum = treffer[1];
  80. } catch (e) {
  81. evtNum = "";
  82. }
  83. var treffer = evtVal.match(/(\d\d:\d\d)/);
  84. var evtHM;
  85. try {
  86. evtHM = treffer[1];
  87. } catch (e) {
  88. evtHM = "";
  89. }
  90. var treffer = evtVal.match(/^(\d\d:\d\d(:\d\d)?)$/);
  91. var evtHMex;
  92. try {
  93. evtHMex = treffer[1];
  94. } catch (e) {
  95. evtHMex = "";
  96. }
  97. var evtEvt = evtVal.replace(/\s/g, ".")
  98. .replace(/[\^\$\[\]\(\)\\]/g, function(s){return"\\"+s});
  99. var diop = [];
  100. var diophlp = [];
  101. var icnt = 0;
  102. diophlp[icnt] = lang ? "a) einfacher auslösender Zugriff auf ein Reading-Wert eines Gerätes oder auf den Wert des Internal STATE, wenn kein Reading im Ereignis vorkommt" : "a) simple triggering access to device reading or internal STATE";
  103. diop[icnt] = "["+evtDev+(evtRead ? ":"+evtRead : "")+"]"; icnt++;
  104. diophlp[icnt] = lang ? "b) wie a), zusätzlich mit Angabe eines Vergleichsoperators für Zeichenketten (eq &#8793; equal) und Vergleichswert" : "b) like a) additionally with string operator (eq &#8793; equal) and reference value";
  105. diop[icnt] = "["+evtDev+(evtRead ? ":"+evtRead : "")+"] eq \""+evtVal+"\""; icnt++;
  106. if (evtNum != "") {
  107. diophlp[icnt] = lang ? "c) wie a) aber mit Zugriff nur auf die erste Zahl der Wertes und eines Vergleichsoperators für Zahlen (==) und numerischem Vergleichswert" : "c) like a) but with access to the first number and a relational operator for numbers (==) and a numeric reference value";
  108. diop[icnt] = "["+evtDev+(evtRead ? ":"+evtRead : ":state")+":d] == "+evtNum; icnt++;}
  109. if (evtHM != "") {
  110. diophlp[icnt] = lang ? "d) wie a) aber mit Filter für eine Zeitangabe (hh:mm), einer Zeitvorgabe für nicht existierende Readings/Internals, zusätzlich mit Angabe eines Vergleichsoperators für Zeichenketten (ge &#8793; greater equal) und Vergleichswert" : "d) like a) with filter for time (hh:mm), default value for nonexisting readings or Internals and a relational string operator (ge &#8793; greater equal) and a reference value";
  111. diop[icnt] = "["+evtDev+(evtRead ? ":"+evtRead : ":state")+":\"(\\d\\d:\\d\\d)\",\"00:00\"] ge $hm"; icnt++;
  112. diophlp[icnt] = lang ? "e1) Zeitpunkt (hh:mm) als Auslöser" : "e1) time specification (hh:mm) as trigger";
  113. diop[icnt] = "["+evtHM+"]"; icnt++;}
  114. if (evtHMex != "") {
  115. diophlp[icnt] = lang ? "e2) indirekte Angabe eines Zeitpunktes als Auslöser" : "e2) indirect time specification as trigger";
  116. diop[icnt] = "[["+evtDev+(evtRead ? ":"+evtRead : "")+"]]"; icnt++;}
  117. diophlp[icnt] = lang ? "f) auslösender Zugriff auf ein Gerät mit Angabe eines \"regulären Ausdrucks\" für ein Reading mit beliebigen Reading-Wert" : "f) triggering access to a device with \"regular expression\" for a reading with arbitrary value";
  118. diop[icnt] = "["+evtDev+(evtRead ? ":\"^"+evtRead+": " : ":\"")+"\"]"; icnt++;
  119. diophlp[icnt] = lang ? "g) Zugriff mit Angabe eines \"regulären Ausdrucks\" für ein Gerät und ein Reading mit beliebigen Reading-Wert" : "g) access by a \"regular expression\" for a device and a reading with arbitrary value";
  120. diop[icnt] = "[\"^"+evtDev+(evtRead ? "$:^"+evtRead+": " : "$: ")+"\"]"; icnt++;
  121. diophlp[icnt] = lang ? "h) Zugriff mit Angabe eines \"regulären Ausdrucks\" für ein Gerät und ein Reading mit exaktem Reding-Wert" : "h) access by a \"regular expression\" for a device and a reading with distinct value";
  122. diop[icnt] = "[\"^"+evtDev+(evtRead ? "$:^"+evtRead+": " : "$:^")+evtEvt+"$\"]"; icnt++;
  123. if (evtHM != "") {
  124. diophlp[icnt] = lang ? "i) Zugriff mit Angabe eines \"regulären Ausdrucks\" für ein Gerät und ein Reading mit Filter für eine Zeitangabe (hh:mm), einer Zeitvorgabe falls ein anderer Operand auslöst" : "i) access by a \"regular expression\" for a device and a reading and a filter for a time value (hh:mm), a default value in case a different operator triggers and a relational string operator (ge &#8793; greater equal) and a reference value";
  125. diop[icnt] = "[\"^"+evtDev+(evtRead ? "$:^"+evtRead+"\"" : "$:\"")+":\"(\\d\\d:\\d\\d)\",\"00:00\"] ge $hm"; icnt++}
  126. var maxlength = 33;
  127. for (var i = 0; i < diop.length; i++)
  128. maxlength = diop[i].length > maxlength ? diop[i].length : maxlength;
  129. // build the dialog
  130. var txt = '<style type="text/css">\n'+
  131. 'div.opdi label { display:block; margin-left:2em; font-family:Courier}\n'+
  132. 'div.opdi input { float:left; }\n'+
  133. '</style>\n';
  134. var inputPrf = "<input type='radio' name=";
  135. txt += (lang ? "Bitte einen Opranden wählen." : "Select an Operand please.") + "<br><br>";
  136. for (var i = 0; i < diop.length; i++) {
  137. txt += "<div class='opdi'>"+inputPrf+"'opType' id='di"+i+"' />"+
  138. "<label title='"+diophlp[i]+"' >"+diop[i]+"</label></div><br>";
  139. }
  140. if ($('#doiftoolstype').attr('devtype') == 'doif') {
  141. txt += "<input class='opdi' id='opditmp' type='text' size='"+(maxlength+10)+"' style='font-family:Courier' title='"+
  142. (lang ? "Der gewählte Operand könnte vor dem Kopieren geändert werden." : "The selected operand may be changed before copying.")+
  143. "' ></input>";
  144. } else if ($('#doiftoolstype').attr('devtype') == 'doiftools') {
  145. txt += "<input newdev='' class='opdi' id='opditmp' type='text' size='"+(maxlength+36)+"' style='font-family:Courier' title='"+
  146. (lang ? "Die Definition kann vor der Weiterverarbeitung angepasst werden." : "The definition may be changed before processing.")+
  147. "' ></input>";
  148. }
  149. $('body').append('<div id="evtCoM" style="display:none">'+txt+'</div>');
  150. if ($('#doiftoolstype').attr('devtype') == 'doif') {
  151. $('#evtCoM').dialog(
  152. { modal:true, closeOnEscape:true, width:"auto",
  153. close:function(){ $('#evtCoM').remove(); },
  154. buttons:[
  155. { text:"Cancel", click:function(){ $(this).dialog('close'); }},
  156. { text:"Open DEF-Editor", title:(lang ? "Kopiert die Eingabezeile in die Zwischenablage und öffnet den DEF-Editor der aktuellen Detailansicht. Mit Strg-v kann der Inhalt der Zwischenablage in die Definition eingefügt werden." : "Copies the input line to clipboard and opens the DEF editor of the current detail view. Paste the content of the clipboard to the editor by using ctrl-v"), click:function(){
  157. $("input#opditmp").select();
  158. document.execCommand("copy");
  159. if ($("#edit").css("display") == "none")
  160. $("#DEFa").click();
  161. $(this).dialog('close');
  162. }}],
  163. open:function(){
  164. $("#evtCoM input[name='opType'],#evtCoM select").change(doiftoolsOptChanged);
  165. }
  166. });
  167. } else if ($('#doiftoolstype').attr('devtype') == 'doiftools') {
  168. $('#evtCoM').dialog(
  169. { modal:true, closeOnEscape:true, width:"auto",
  170. close:function(){ $('#evtCoM').remove(); },
  171. buttons:[
  172. { text:"Cancel", click:function(){ $(this).dialog('close'); }},
  173. { text:"Execute Definition", title:(lang ? "Führt den define-Befehl aus und öffnet die Detailansicht des erzeugten Gerätes." : "Executes the define command and opens the detail view of the created device."), click:function(){
  174. FW_cmd(myFW_root+"?cmd="+$("input#opditmp").val()+"&XHR=1");
  175. $("input[class='maininput'][name='cmd']").val($("input#opditmp").val());
  176. var newDev = $("input#opditmp").val();
  177. $(this).dialog('close');
  178. var rex = newDev.match(/define\s+(.*)\s+DOIF/);
  179. try {
  180. location = myFW_root+'?detail='+rex[1];
  181. } catch (e) {
  182. }
  183. }}],
  184. open:function(){
  185. $("#evtCoM input[name='opType'],#evtCoM select").change(doiftoolsOptChanged);
  186. }
  187. });
  188. }
  189. }
  190. function doiftoolsOptChanged() {
  191. if ($('#doiftoolstype').attr('devtype') == 'doif') {
  192. $("input#opditmp").val($("#evtCoM input:checked").next("label").text());
  193. } else if ($('#doiftoolstype').attr('devtype') == 'doiftools') {
  194. var N = 8;
  195. var newDev = Array(N+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, N);
  196. $("input#opditmp").val('define newDevice_'+newDev+' DOIF ('+$("#evtCoM input:checked").next("label").text()+') ()');
  197. var inpt = document.getElementById("opditmp");
  198. inpt.focus();
  199. inpt.setSelectionRange(7,17+N);
  200. }
  201. }
  202. function doiftoolsReplaceBR() {
  203. $("textarea#console").html($("textarea#console").html().replace(/<br(.*)?>/g,""));
  204. }
  205. function delbutton() {
  206. if ($('#doiftoolstype').attr('embefore') == 1) {
  207. var ins = document.getElementsByClassName('makeTable wide readings');
  208. var del = document.getElementById('doiftoolscons');
  209. if (del) {
  210. ins[0].parentNode.insertBefore(del,ins[0]);
  211. }
  212. }
  213. var del = document.getElementById('addRegexpPart');
  214. if (del) {
  215. $( window ).off( "load", delbutton );
  216. del.parentNode.removeChild(del);
  217. }
  218. }
  219. //execute
  220. $( window ).on( "load", delbutton );
  221. $('#console').on('select', doiftoolsCopyToClipboard);
  222. $('#console').on('mouseover',doiftoolsReplaceBR);
  223. </script>
  224. EOF
  225. my $DOIFtoolsJSfuncStart = <<'EOF';
  226. <script type="text/javascript">
  227. //functions
  228. function doiftoolsRemoveLookUp () {
  229. $('#addLookUp').dialog( "close" );
  230. }
  231. function doiftoolsAddLookUp () {
  232. var tn = $(this).text();
  233. var target = this;
  234. var txt = "Internals<table class='block wide internals' style='font-size:12px'>";
  235. FW_cmd(FW_root+"?cmd=jsonlist2 "+tn+"&XHR=1", function(data){
  236. var devList = JSON.parse(data);
  237. var dev = devList.Results[0];
  238. var row = 0;
  239. for (var item in dev.Internals) {
  240. if (item == "DEF") {dev.Internals[item] = "<pre>"+dev.Internals[item]+"</pre>"}
  241. var cla = ((row++&1)?"odd":"even");
  242. txt += "<tr class='"+cla+"'><td>"+item+"</td><td>"+dev.Internals[item].replace(/\n/g,"<br>")+"</td></tr>\n";
  243. }
  244. txt += "</table>Readings<table class='block wide readings' style='font-size:12px'><br>";
  245. row = 0;
  246. for (var item in dev.Readings) {
  247. var cla = ((row++&1)?"odd":"even");
  248. txt += "<tr class='"+cla+"'><td>"+item+"</td><td>"+dev.Readings[item].Value+"</td><td>"+dev.Readings[item].Time+"</td></tr>\n";
  249. }
  250. txt += "</table>Attributes<table class='block wide attributes' style='font-size:12px'><br>";
  251. row = 0;
  252. for (var item in dev.Attributes) {
  253. if (item.match(/(userReadings|wait|setList)/) ) {dev.Attributes[item] = "<pre>"+dev.Attributes[item]+"</pre>"}
  254. var cla = ((row++&1)?"odd":"even");
  255. txt += "<tr class='"+cla+"'><td>"+item+"</td><td>"+dev.Attributes[item]+"</td></tr>\n";
  256. }
  257. txt += "</table>";
  258. $('#addLookUp').html(txt);
  259. $('#addLookUp').dialog("open");
  260. });
  261. }
  262. $(document).ready(function(){
  263. $('body').append('<div id="addLookUp" style="display:none"></div>');
  264. $('#addLookUp').dialog({
  265. width:"60%",
  266. height:"auto",
  267. maxHeight:900,
  268. modal: false,
  269. position: { at: "right"},
  270. collusion: "fit fit",
  271. buttons: [
  272. {
  273. text: "Ok",
  274. style:"margin-right: 100%",
  275. click: function() {
  276. $( this ).dialog( "close" );
  277. }
  278. }
  279. ]
  280. });
  281. $('#addLookUp').dialog( "close" );
  282. $(".assoc").find("a:even").each(function() {
  283. $(this).on("mouseover",doiftoolsAddLookUp);
  284. });
  285. $("table[class*='block wide']").each(function() {
  286. $(this).on("mouseenter",doiftoolsRemoveLookUp);
  287. });
  288. });
  289. </script>
  290. EOF
  291. #########################
  292. sub DOIFtools_Initialize($)
  293. {
  294. my ($hash) = @_;
  295. $hash->{DefFn} = "DOIFtools_Define";
  296. $hash->{SetFn} = "DOIFtools_Set";
  297. $hash->{GetFn} = "DOIFtools_Get";
  298. $hash->{UndefFn} = "DOIFtools_Undef";
  299. $hash->{AttrFn} = "DOIFtools_Attr";
  300. $hash->{NotifyFn} = "DOIFtools_Notify";
  301. $hash->{FW_detailFn} = "DOIFtools_fhemwebFn";
  302. $data{FWEXT}{"/DOIFtools_logWrapper"}{CONTENTFUNC} = "DOIFtools_logWrapper";
  303. my $oldAttr = "target_room:noArg target_group:noArg executeDefinition:noArg executeSave:noArg eventMonitorInDOIF:noArg readingsPrefix:noArg";
  304. $hash->{AttrList} = "DOIFtoolsExecuteDefinition:1,0 DOIFtoolsTargetRoom DOIFtoolsTargetGroup DOIFtoolsExecuteSave:1,0 DOIFtoolsReadingsPrefix DOIFtoolsEventMonitorInDOIF:1,0 DOIFtoolsHideModulShortcuts:1,0 DOIFtoolsHideGetSet:1,0 DOIFtoolsMyShortcuts:textField-long DOIFtoolsMenuEntry:1,0 DOIFtoolsHideStatReadings:1,0 DOIFtoolsEventOnDeleted:1,0 DOIFtoolsEMbeforeReadings:1,0 DOIFtoolsNoLookUp:1,0 DOIFtoolsNoLookUpInDOIF:1,0 DOIFtoolsLogDir disabledForIntervals ".$oldAttr; #DOIFtoolsForceGet:true
  305. }
  306. sub DOIFtools_dO ($$$$){
  307. return "";}
  308. # FW_detailFn for DOIF injecting event monitor
  309. sub DOIFtools_eM($$$$) {
  310. my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
  311. my @dtn = devspec2array("TYPE=DOIFtools");
  312. my $lang = AttrVal("global","language","EN");
  313. my $ret = "";
  314. # call DOIF_detailFn
  315. no strict "refs";
  316. my $retfn = &{ReadingsVal($dtn[0],".DOIF_detailFn","")}($FW_wname, $d, $room, $pageHash) if (ReadingsVal($dtn[0],".DOIF_detailFn",""));
  317. $ret .= $retfn if ($retfn);
  318. use strict "refs";
  319. if (!$room) {
  320. # LookUp in probably associated with
  321. $ret .= $DOIFtoolsJSfuncStart if (!AttrVal($dtn[0],"DOIFtoolsNoLookUpInDOIF",""));
  322. # Event Monitor
  323. if (AttrVal($dtn[0],"DOIFtoolsEventMonitorInDOIF","")) {
  324. my $a0 = ReadingsVal($d,".eM", "off") eq "on" ? "off" : "on";
  325. $ret .= "<br>" if (ReadingsVal($dtn[0],".DOIF_detailFn",""));
  326. $ret .= "<table class=\"block\"><tr><td><div class=\"dval\"><span title=\"".($lang eq "DE" ? "toggle schaltet den Event-Monitor ein/aus" : "toggle switches event monitor on/off")."\">Event monitor: <a href=\"$FW_ME?detail=$d&amp;cmd.$d=setreading $d .eM $a0$FW_CSRF\">toggle</a>&nbsp;&nbsp;</span>";
  327. $ret .= "</div></td>";
  328. $ret .= "</tr></table>";
  329. my $a = "";
  330. if (ReadingsVal($d,".eM","off") eq "on") {
  331. $ret .= "<script type=\"text/javascript\" src=\"$FW_ME/pgm2/console.js\"></script>";
  332. my $filter = $a ? ($a eq "log" ? "global" : $a) : ".*";
  333. $ret .= "<div id='doiftoolscons'>";
  334. my $embefore = AttrVal($dtn[0],"DOIFtoolsEMbeforeReadings","0") ? "1" : "";
  335. $ret .= "<div id='doiftoolstype' devtype='doif' embefore='".$embefore."' lang='".($lang eq "DE" ? 1 : 0)."'><br>";
  336. $ret .= "Events (Filter: <a href=\"#\" id=\"eventFilter\">$filter</a>) ".
  337. "&nbsp;&nbsp;<span id=\"doiftoolsdel\" class='fhemlog'>FHEM log ".
  338. "<input id='eventWithLog' type='checkbox'".
  339. ($a && $a eq "log" ? " checked":"")."></span>".
  340. "&nbsp;&nbsp;<button id='eventReset'>Reset</button>".($lang eq "DE" ? "&emsp;<b>Hinweis:</b> Eventzeile markieren, Operanden auswählen, Definition ergänzen" : "&emsp;<b>Hint:</b> select event line, choose operand, modify definition")."</div>\n";
  341. $ret .= "<textarea id=\"console\" style=\"width:99%; top:.1em; bottom:1em; position:relative;\" readonly=\"readonly\" rows=\"25\" cols=\"60\" title=\"".($lang eq "DE" ? "Die Auswahl einer Event-Zeile zeigt Operanden für DOIF an, sie können im DEF-Editor eingefügt werden (Strg V)." : "Selecting an event line displays operands for DOIFs definition, they can be inserted to DEF-Editor (Ctrl V).")."\" ></textarea>";
  342. $ret .= "</div>";
  343. $ret .= $DOIFtoolsJSfuncEM;
  344. }
  345. }
  346. }
  347. return $ret ? $ret : undef;
  348. }
  349. ######################
  350. # Show the content of the log (plain text), or an image and offer a link
  351. # to convert it to an SVG instance
  352. # If text and no reverse required, try to return the data as a stream;
  353. sub DOIFtools_logWrapper($) {
  354. my ($cmd) = @_;
  355. my $d = $FW_webArgs{dev};
  356. my $type = $FW_webArgs{type};
  357. my $file = $FW_webArgs{file};
  358. my $ret = "";
  359. if(!$d || !$type || !$file) {
  360. FW_pO '<div id="content">DOIFtools_logWrapper: bad arguments</div>';
  361. return 0;
  362. }
  363. if(defined($type) && $type eq "text") {
  364. $defs{$d}{logfile} =~ m,^(.*)/([^/]*)$,; # Dir and File
  365. my $path = "$1/$file";
  366. $path =~ s/%L/$attr{global}{logdir}/g
  367. if($path =~ m/%/ && $attr{global}{logdir});
  368. $path = AttrVal($d,"archivedir","") . "/$file" if(!-f $path);
  369. FW_pO "<div id=\"content\">";
  370. FW_pO "<div class=\"tiny\">" if($FW_ss);
  371. FW_pO "<pre class=\"log\"><b>jump to: <a name='top'></a><a href=\"#end_of_file\">the end</a> <a href=\"#listing\">top listing</a></b><br>";
  372. my $suffix = "<br/><b>jump to: <a name='end_of_file'></a><a href='#top'>the top</a> <a href=\"#listing\">top listing</a></b><br/></pre>".($FW_ss ? "</div>" : "")."</div>";
  373. my $reverseLogs = AttrVal($FW_wname, "reverseLogs", 0);
  374. if(!$reverseLogs) {
  375. $suffix .= "</body></html>";
  376. return FW_returnFileAsStream($path, $suffix, "text/html", 0, 0);
  377. }
  378. if(!open(FH, $path)) {
  379. FW_pO "<div id=\"content\">$path: $!</div></body></html>";
  380. return 0;
  381. }
  382. my $cnt = join("", reverse <FH>);
  383. close(FH);
  384. # $cnt = FW_htmlEscape($cnt);
  385. FW_pO $cnt;
  386. FW_pO $suffix;
  387. return 1;
  388. }
  389. return 0;
  390. }
  391. sub DOIFtools_fhemwebFn($$$$) {
  392. my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
  393. my $ret = "";
  394. # $ret .= "<script type=\"text/javascript\" src=\"$FW_ME/pgm2/myfunction.js\"></script>";
  395. $ret .= $DOIFtoolsJSfuncStart if ($DOIFtoolsJSfuncStart && !AttrVal($d,"DOIFtoolsNoLookUp",""));
  396. # Logfile Liste
  397. if($FW_ss && $pageHash) {
  398. $ret.= "<div id=\"$d\" align=\"center\" class=\"FileLog col2\">".
  399. "$defs{$d}{STATE}</div>";
  400. } else {
  401. my $row = 0;
  402. $ret .= sprintf("<table class=\"FileLog %swide\">",
  403. $pageHash ? "" : "block ");
  404. foreach my $f (FW_fileList($defs{$d}{logfile})) {
  405. my $class = (!$pageHash ? (($row++&1)?"odd":"even") : "");
  406. $ret .= "<tr class=\"$class\">";
  407. $ret .= "<td><div class=\"dname\">$f</div></td>";
  408. my $idx = 0;
  409. foreach my $ln (split(",", AttrVal($d, "logtype", "text"))) {
  410. if($FW_ss && $idx++) {
  411. $ret .= "</tr><tr class=\"".(($row++&1)?"odd":"even")."\"><td>";
  412. }
  413. my ($lt, $name) = split(":", $ln);
  414. $name = $lt if(!$name);
  415. $ret .= FW_pH("$FW_ME/DOIFtools_logWrapper&dev=$d&type=$lt&file=$f",
  416. "<div class=\"dval\">$name</div>", 1, "dval", 1);
  417. }
  418. }
  419. $ret .= "</table>";
  420. }
  421. # Event Monitor
  422. my $a0 = ReadingsVal($d,".eM", "off") eq "on" ? "off" : "on";
  423. $ret .= "<div class=\"dval\"><table>";
  424. $ret .= "<tr><td><span title=\"toggle to switch event monitor on/off\">Event monitor: <a href=\"$FW_ME?detail=$d&amp;cmd.$d=setreading $d .eM $a0$FW_CSRF\">toggle</a>&nbsp;&nbsp;</span>";
  425. if (!AttrVal($d,"DOIFtoolsHideModulShortcuts",0)) {
  426. $ret .= "Shortcuts: ";
  427. $ret .= "<a href=\"$FW_ME?detail=$d&amp;cmd.$d=reload 98_DOIFtools.pm$FW_CSRF\">reload DOIFtools</a>&nbsp;&nbsp;" if(ReadingsVal($d,".debug",""));
  428. $ret .= "<a href=\"$FW_ME?detail=$d&amp;cmd.$d=update check$FW_CSRF\">update check</a>&nbsp;&nbsp;";
  429. $ret .= "<a href=\"$FW_ME?detail=$d&amp;cmd.$d=update$FW_CSRF\">update</a>&nbsp;&nbsp;";
  430. $ret .= "<a href=\"$FW_ME?detail=$d&amp;cmd.$d=shutdown restart$FW_CSRF\">shutdown restart</a>&nbsp;&nbsp;";
  431. $ret .= "<a href=\"$FW_ME?detail=$d&amp;cmd.$d=fheminfo send$FW_CSRF\">fheminfo send</a>&nbsp;&nbsp;";
  432. }
  433. $ret .= "</td></tr>";
  434. if (AttrVal($d,"DOIFtoolsMyShortcuts","")) {
  435. $ret .= "<tr><td>";
  436. my @sc = split(",",AttrVal($d,"DOIFtoolsMyShortcuts",""));
  437. for (my $i = 0; $i < @sc; $i+=2) {
  438. if ($sc[$i] =~ m/^\#\#(.*)/) {
  439. $ret .= "$1&nbsp;&nbsp;";
  440. } else {
  441. $ret .= "<a href=\"/$sc[$i+1]$FW_CSRF\">$sc[$i]</a>&nbsp;&nbsp;" if($sc[$i] and $sc[$i+1]);
  442. }
  443. }
  444. $ret .= "</td></tr>";
  445. }
  446. $ret .= "</table>";
  447. if (!AttrVal($d, "DOIFtoolsHideGetSet", 0)) {
  448. my $a1 = ReadingsVal($d,"doStatistics", "disabled") =~ "disabled|deleted" ? "enabled" : "disabled";
  449. my $a2 = ReadingsVal($d,"specialLog", 0) ? 0 : 1;
  450. $ret .= "<table ><tr>";
  451. # set doStatistics enabled/disabled
  452. $ret .= "<td><form method=\"post\" action=\"$FW_ME\" autocomplete=\"off\">
  453. <input name=\"detail\" value=\"$d\" type=\"hidden\">";
  454. $ret .= FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
  455. $ret .= "<input name=\"dev.set$d\" value=\"$d\" type=\"hidden\">
  456. <input name=\"cmd.set$d\" value=\"set\" class=\"set\" type=\"submit\">
  457. <div class=\"set downText\">&nbsp;doStatistics $a1&emsp;</div>
  458. <div style=\"display:none\" class=\"noArg_widget\" informid=\"$d-doStatistics\">
  459. <input name=\"val.set$d\" value=\"doStatistics $a1\" type=\"hidden\">
  460. </div></form></td>";
  461. # set doStatistics deleted
  462. $ret .= "<td><form method=\"post\" action=\"$FW_ME\" autocomplete=\"off\">
  463. <input name=\"detail\" value=\"$d\" type=\"hidden\">";
  464. $ret .= FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
  465. $ret .= "<input name=\"dev.set$d\" value=\"$d\" type=\"hidden\">
  466. <input name=\"cmd.set$d\" value=\"set\" class=\"set\" type=\"submit\">
  467. <div class=\"set downText\">&nbsp;doStatistics deleted&emsp;</div>
  468. <div style=\"display:none\" class=\"noArg_widget\" informid=\"$d-doStatistics\">
  469. <input name=\"val.set$d\" value=\"doStatistics deleted\" type=\"hidden\">
  470. </div></form></td>";
  471. # set specialLog 0/1
  472. $ret .= "<td><form method=\"post\" action=\"$FW_ME\" autocomplete=\"off\">
  473. <input name=\"detail\" value=\"$d\" type=\"hidden\">";
  474. $ret .= FW_hidden("fwcsrf", $defs{$FW_wname}{CSRFTOKEN}) if($FW_CSRF);
  475. $ret .= "<input name=\"dev.set$d\" value=\"$d\" type=\"hidden\">
  476. <input name=\"cmd.set$d\" value=\"set\" class=\"set\" type=\"submit\">
  477. <div class=\"set downText\">&nbsp;specialLog $a2&emsp;</div>
  478. <div style=\"display:none\" class=\"noArg_widget\" informid=\"$d-doStatistics\">
  479. <input name=\"val.set$d\" value=\"specialLog $a2\" type=\"hidden\">
  480. </div></form></td>";
  481. $ret .= "</tr><tr>";
  482. # get statisticsReport
  483. $ret .= "<td><form method=\"post\" action=\"$FW_ME\" autocomplete=\"off\">
  484. <input name=\"detail\" value=\"$d\" type=\"hidden\">
  485. <input name=\"dev.get$d\" value=\"$d\" type=\"hidden\">
  486. <input name=\"cmd.get$d\" value=\"get\" class=\"get\" type=\"submit\">
  487. <div class=\"get downText\">&nbsp;statisticsReport&emsp;</div>
  488. <div style=\"display:none\" class=\"noArg_widget\" informid=\"$d-statisticsReport\">
  489. <input name=\"val.get$d\" value=\"statisticsReport\" type=\"hidden\">
  490. </div></form></td>";
  491. # get checkDOIF
  492. $ret .= "<td><form method=\"post\" action=\"$FW_ME\" autocomplete=\"off\">
  493. <input name=\"detail\" value=\"$d\" type=\"hidden\">
  494. <input name=\"dev.get$d\" value=\"$d\" type=\"hidden\">
  495. <input name=\"cmd.get$d\" value=\"get\" class=\"get\" type=\"submit\">
  496. <div class=\"get downText\">&nbsp;checkDOIF&emsp;</div>
  497. <div style=\"display:none\" class=\"noArg_widget\" informid=\"$d-checkDOIF\">
  498. <input name=\"val.get$d\" value=\"checkDOIF\" type=\"hidden\">
  499. </div></form></td>";
  500. # get runningTimerInDOIF
  501. $ret .= "<td><form method=\"post\" action=\"$FW_ME\" autocomplete=\"off\">
  502. <input name=\"detail\" value=\"$d\" type=\"hidden\">
  503. <input name=\"dev.get$d\" value=\"$d\" type=\"hidden\">
  504. <input name=\"cmd.get$d\" value=\"get\" class=\"get\" type=\"submit\">
  505. <div class=\"get downText\">&nbsp;runningTimerInDOIF&emsp;</div>
  506. <div style=\"display:none\" class=\"noArg_widget\" informid=\"$d-runningTimerInDOIF\">
  507. <input name=\"val.get$d\" value=\"runningTimerInDOIF\" type=\"hidden\">
  508. </div></form></td>";
  509. $ret .= "</tr></table>";
  510. }
  511. $ret .= "</div>";
  512. my $a = "";
  513. if (ReadingsVal($d,".eM","off") eq "on") {
  514. my $lang = AttrVal("global","language","EN");
  515. $ret .= "<script type=\"text/javascript\" src=\"$FW_ME/pgm2/console.js\"></script>";
  516. # $ret .= "<script type=\"text/javascript\" src=\"$FW_ME/pgm2/doiftools.js\"></script>";
  517. my $filter = $a ? ($a eq "log" ? "global" : $a) : ".*";
  518. $ret .= "<div><table><tr><td>";
  519. $ret .= "Events (Filter: <a href=\"#\" id=\"eventFilter\">$filter</a>) ".
  520. "&nbsp;&nbsp;<span id=\"doiftoolsdel\" class='fhemlog'>FHEM log ".
  521. "<input id='eventWithLog' type='checkbox'".
  522. ($a && $a eq "log" ? " checked":"")."></span>".
  523. "&nbsp;&nbsp;<button id='eventReset'>Reset</button>".($lang eq "DE" ? "&emsp;<b>Hinweis:</b> Eventzeile markieren, Operanden auswählen, neue Definition erzeugen" : "&emsp;<b>Hint:</b> select event line, choose operand, create definition")."</td></tr></table></div>\n";
  524. my $embefore = AttrVal($d,"DOIFtoolsEMbeforeReadings","0") ? "1" : "";
  525. $ret .= "<div id='doiftoolstype' devtype='doiftools' embefore='".$embefore."' lang='".($lang eq "DE" ? 1 : 0)."'>";
  526. $ret .= "<textarea id=\"console\" style=\"width:99%; top:.1em; bottom:1em; position:relative;\" readonly=\"readonly\" rows=\"25\" cols=\"60\" title=\"".($lang eq "DE" ? "Die Auswahl einer Event-Zeile zeigt Operanden für DOIF an, mit ihnen kann eine neue DOIF-Definition erzeugt werden." : "Selecting an event line displays operands for DOIFs definition, they are used to create a new DOIF definition.")."\"></textarea>";
  527. $ret .= "</div>";
  528. $ret .= $DOIFtoolsJSfuncEM;
  529. }
  530. return $ret;
  531. }
  532. sub DOIFtools_Notify($$) {
  533. my ($hash, $source) = @_;
  534. my $pn = $hash->{NAME};
  535. my $sn = $source->{NAME};
  536. my $events = deviceEvents($source,1);
  537. return if( !$events );
  538. # \@DOIFtools_we aktualisieren
  539. if ($sn eq AttrVal("global","holiday2we","")) {
  540. my $we;
  541. my $val;
  542. my $a;
  543. my $b;
  544. for (my $i = 0; $i < 8; $i++) {
  545. $DOIFtools_we[$i] = 0;
  546. $val = CommandGet(undef,"$sn days $i");
  547. if($val) {
  548. ($a, $b) = ReplaceEventMap($sn, [$sn, $val], 0);
  549. $DOIFtools_we[$i] = 1 if($b ne "none");
  550. }
  551. }
  552. }
  553. my $ldi = ReadingsVal($pn,"specialLog","") ? ReadingsVal($pn,"doif_to_log","") : "";
  554. foreach my $event (@{$events}) {
  555. $event = "" if(!defined($event));
  556. # add list to DOIFtoolsLog
  557. if ($ldi and $ldi =~ "$sn" and $event =~ m/(^cmd: \d+(\.\d+)?|^wait_timer: \d\d.*)/) {
  558. $hash->{helper}{counter}{0}++;
  559. my $trig = "<a name=\"list$hash->{helper}{counter}{0}\"><a name=\"listing\">";
  560. $trig .= "</a><strong>\[$hash->{helper}{counter}{0}\] +++++ Listing $sn:$1 +++++</strong>\n";
  561. my $prev = $hash->{helper}{counter}{0} - 1;
  562. my $next = $hash->{helper}{counter}{0} + 1;
  563. $trig .= $prev ? "<b>jump to: <a href=\"#list$prev\">prev</a>&nbsp;&nbsp;<a href=\"#list$next\">next</a> Listing</b><br>" : "<b>jump to: prev&nbsp;&nbsp;<a href=\"#list$next\">next</a> Listing</b><br>";
  564. $trig .= "DOIF-Version: ".ReadingsVal($pn,"DOIF_version","n/a")."<br>";
  565. $trig .= CommandList(undef,$sn);
  566. foreach my $itm (keys %defs) {
  567. $trig =~ s,([\[\" ])$itm([\"\:\] ]),$1<a href="$FW_ME?detail=$itm">$itm</a>$2,g;
  568. }
  569. CommandTrigger(undef,"$hash->{TYPE}Log $trig");
  570. }
  571. # DOIFtools DEF addition
  572. if ($sn eq "global" and $event =~ "^INITIALIZED\$|^MODIFIED|^DEFINED|^DELETED|^RENAMED|^UNDEFINED") {
  573. my @doifList = devspec2array("TYPE=DOIF");
  574. $hash->{DEF} = "associated DOIF: ".join(" ",sort @doifList);
  575. readingsSingleUpdate($hash,"DOIF_version",fhem("version 98_DOIF.pm noheader",1),0);
  576. }
  577. # get DOIF version, FHEM revision and default values
  578. if ($sn eq "global" and $event =~ "^INITIALIZED\$|^MODIFIED $pn") {
  579. readingsBeginUpdate($hash);
  580. readingsBulkUpdate($hash,"DOIF_version",fhem("version 98_DOIF.pm noheader",1));
  581. readingsBulkUpdate($hash,"FHEM_revision",fhem("version revision noheader",1));
  582. readingsBulkUpdate($hash,"sourceAttribute","readingList") unless ReadingsVal($pn,"sourceAttribute","");
  583. readingsBulkUpdate($hash,"recording_target_duration",0) unless ReadingsVal($pn,"recording_target_duration","0");
  584. readingsBulkUpdate($hash,"doStatistics","disabled") unless ReadingsVal($pn,"doStatistics","");
  585. readingsBulkUpdate($hash,".eM", ReadingsVal($pn,".eM","off"));
  586. readingsBulkUpdate($hash,"statisticsDeviceFilterRegex", ".*") unless ReadingsVal($pn,"statisticsDeviceFilterRegex","");
  587. readingsEndUpdate($hash,0);
  588. $defs{$pn}{VERSION} = fhem("version 98_DOIFtools.pm noheader",1);
  589. DOIFtoolsSetNotifyDev($hash,1,1);
  590. #set new attributes and delete old ones
  591. CommandAttr(undef,"$pn DOIFtoolsExecuteDefinition ".AttrVal($pn,"executeDefinition","")) if (AttrVal($pn,"executeDefinition",""));
  592. CommandDeleteAttr(undef,"$pn executeDefinition") if (AttrVal($pn,"executeDefinition",""));
  593. CommandAttr(undef,"$pn DOIFtoolsExecuteSave ".AttrVal($pn,"executeSave","")) if (AttrVal($pn,"executeSave",""));
  594. CommandDeleteAttr(undef,"$pn executeSave") if (AttrVal($pn,"executeSave",""));
  595. CommandAttr(undef,"$pn DOIFtoolsTargetRoom ".AttrVal($pn,"target_room","")) if (AttrVal($pn,"target_room",""));
  596. CommandDeleteAttr(undef,"$pn target_room") if (AttrVal($pn,"target_room",""));
  597. CommandAttr(undef,"$pn DOIFtoolsTargetGroup ".AttrVal($pn,"target_group","")) if (AttrVal($pn,"target_group",""));
  598. CommandDeleteAttr(undef,"$pn target_group") if (AttrVal($pn,"target_group",""));
  599. CommandAttr(undef,"$pn DOIFtoolsReadingsPrefix ".AttrVal($pn,"readingsPrefix","")) if (AttrVal($pn,"readingsPrefix",""));
  600. CommandDeleteAttr(undef,"$pn readingsPrefix") if (AttrVal($pn,"readingsPrefix",""));
  601. CommandAttr(undef,"$pn DOIFtoolsEventMonitorInDOIF ".AttrVal($pn,"eventMonitorInDOIF","")) if (AttrVal($pn,"eventMonitorInDOIF",""));
  602. CommandDeleteAttr(undef,"$pn eventMonitorInDOIF") if (AttrVal($pn,"eventMonitorInDOIF",""));
  603. # CommandSave(undef,undef);
  604. }
  605. # Event monitor in DOIF FW_detailFn
  606. if ($modules{DOIF}{LOADED} and (!$modules{DOIF}->{FW_detailFn} or $modules{DOIF}->{FW_detailFn} and $modules{DOIF}->{FW_detailFn} ne "DOIFtools_eM") and $sn eq "global" and $event =~ "^INITIALIZED\$" ) {
  607. readingsBeginUpdate($hash);
  608. readingsBulkUpdate($hash,".DOIF_detailFn",$modules{DOIF}->{FW_detailFn});
  609. $modules{DOIF}->{FW_detailFn} = "DOIFtools_eM";
  610. readingsBulkUpdate($hash,".DOIFdO",$modules{DOIF}->{FW_deviceOverview});
  611. $modules{DOIF}->{FW_deviceOverview} = 1;
  612. readingsEndUpdate($hash,0);
  613. }
  614. # Statistics event recording
  615. if (ReadingsVal($pn,"doStatistics","disabled") eq "enabled" and !IsDisabled($pn) and $sn ne "global" and (ReadingsVal($pn,"statisticHours",0) <= ReadingsVal($pn,"recording_target_duration",0) or !ReadingsVal($pn,"recording_target_duration",0))) {
  616. my $st = AttrVal($pn,"DOIFtoolsHideStatReadings","") ? ".stat_" : "stat_";
  617. readingsSingleUpdate($hash,"$st$sn",ReadingsVal($pn,"$st$sn",0)+1,0);
  618. }
  619. }
  620. #statistics time counter updating
  621. if (ReadingsVal($pn,"doStatistics","disabled") eq "enabled" and !IsDisabled($pn) and $sn ne "global") {
  622. if (!ReadingsVal($pn,"recording_target_duration",0) or ReadingsVal($pn,"statisticHours",0) <= ReadingsVal($pn,"recording_target_duration",0)) {
  623. my $t = gettimeofday();
  624. my $te = ReadingsVal($pn,".te",gettimeofday()) + $t - ReadingsVal($pn,".t0",gettimeofday());
  625. my $tH = int($te*100/3600 +.5)/100;
  626. readingsBeginUpdate($hash);
  627. readingsBulkUpdate($hash,".te",$te);
  628. readingsBulkUpdate($hash,".t0",$t);
  629. readingsBulkUpdate($hash,"statisticHours",sprintf("%.2f",$tH));
  630. readingsEndUpdate($hash,0);
  631. } else {
  632. DOIFtoolsSetNotifyDev($hash,1,0);
  633. readingsBeginUpdate($hash);
  634. readingsBulkUpdate($hash,"Action","event recording target duration reached");
  635. readingsBulkUpdate($hash,"doStatistics","disabled");
  636. readingsEndUpdate($hash,0);
  637. }
  638. }
  639. return undef;
  640. }
  641. # DOIFtoolsLinColorGrad(start_color,end_color,percent|[$min,max,current])
  642. # start_color, end_color: 6 hexadecimal values as string with or without leading #
  643. # percent: from 0 to 1
  644. # min: minmal value
  645. # max: maximal value
  646. # current: current value
  647. # return: 6 hexadecimal value as string, prefix depends on input
  648. sub DOIFtoolsLinColorGrad {
  649. my ($sc,$ec,$pct,$max,$cur) = @_;
  650. $pct = ($cur-$pct)/($max-$pct) if (@_ == 5);
  651. my $prefix = "";
  652. $prefix = "#" if ("$sc $ec"=~"#");
  653. $sc =~ s/^#//;
  654. $ec =~ s/^#//;
  655. $pct = $pct > 1 ? 1 : $pct;
  656. $pct = $pct < 0 ? 0 : $pct;
  657. $sc =~/([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})/;
  658. my @sc = (hex($1),hex($2),hex($3));
  659. $ec =~/([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})/;
  660. my @ec = (hex($1),hex($2),hex($3));
  661. my @rgb;
  662. for (0..2) {
  663. $rgb[$_] = sprintf("%02X", int(($ec[$_] - $sc[$_])*$pct + $sc[$_] + .5));
  664. }
  665. return $prefix.join("",@rgb);
  666. }
  667. sub DOIFtoolsHsvColorGrad {
  668. my ($cur,$min,$max,$min_s,$max_s,$s,$v)=@_;
  669. my $m=($max_s-$min_s)/($max-$min);
  670. my $n=$min_s-$min*$m;
  671. if ($cur>$max) {
  672. $cur=$max;
  673. } elsif ($cur<$min) {
  674. $cur=$min;
  675. }
  676. my $h=$cur*$m+$n;
  677. $h /=360;
  678. $s /=100;
  679. $v /=100;
  680. my($r,$g,$b)=Color::hsv2rgb ($h,$s,$v);
  681. $r *= 255;
  682. $g *= 255;
  683. $b *= 255;
  684. return sprintf("#%02X%02X%02X", $r+0.5, $g+0.5, $b+0.5);
  685. }
  686. sub DOIFtoolsRg
  687. {
  688. my ($hash,$arg) = @_;
  689. my $pn = $hash->{NAME};
  690. my $pnRg= "rg_$arg";
  691. my $ret = "";
  692. my @ret;
  693. my $defRg = "";
  694. my @defRg;
  695. my $cL = "";
  696. my @rL = split(/ /,AttrVal($arg,"readingList",""));
  697. for (my $i=0; $i<@rL; $i++) {
  698. $defRg .= ",<$rL[$i]>,$rL[$i]";
  699. $cL .= "\"$rL[$i]\"=>\"$rL[$i]:\",";
  700. }
  701. push @defRg, "$pnRg readingsGroup $arg:+STATE$defRg";
  702. my $rooms = AttrVal($pn,"DOIFtoolsTargetRoom","") ? AttrVal($pn,"DOIFtoolsTargetRoom","") : AttrVal($arg,"room","");
  703. push @defRg, "$pnRg room $rooms" if($rooms);
  704. my $groups = AttrVal($pn,"DOIFtoolsTargetGroup","") ? AttrVal($pn,"DOIFtoolsTargetGroup","") : AttrVal($arg,"group","");
  705. push @defRg, "$pnRg group $groups" if($groups);
  706. push @defRg, "$pnRg commands {$cL}" if ($cL);
  707. push @defRg, "$pnRg noheading 1";
  708. $defRg = "defmod $defRg[0]\rattr ".join("\rattr ",@defRg[1..@defRg-1]);
  709. if (AttrVal($pn,"DOIFtoolsExecuteDefinition","")) {
  710. $ret = CommandDefMod(undef,$defRg[0]);
  711. push @ret, $ret if ($ret);
  712. for (my $i = 1; $i < @defRg; $i++) {
  713. $ret = CommandAttr(undef,$defRg[$i]);
  714. push @ret, $ret if ($ret);
  715. }
  716. if (@ret) {
  717. $ret = join("\n", @ret);
  718. return $ret;
  719. } else {
  720. $ret = "Created device <b>$pnRg</b>.\n";
  721. $ret .= CommandSave(undef,undef) if (AttrVal($pn,"DOIFtoolsExecuteSave",""));
  722. return $ret;
  723. }
  724. } else {
  725. $defRg =~ s/</&lt;/g;
  726. $defRg =~ s/>/&gt;/g;
  727. return $defRg;
  728. }
  729. }
  730. # calculate real date in userReadings
  731. sub DOIFtoolsNextTimer {
  732. my ($timer_str,$tn) = @_;
  733. $timer_str =~ /(\d\d).(\d\d).(\d\d\d\d) (\d\d):(\d\d):(\d\d)\|?(.*)/;
  734. my $tstr = "$1.$2.$3 $4:$5:$6";
  735. return $tstr if (length($7) == 0);
  736. my $timer = timelocal($6,$5,$4,$1,$2-1,$3);
  737. my $tdays = "";
  738. $tdays = $tn ? DOIF_weekdays($defs{$tn},$7) : $7;
  739. $tdays =~/([0-8])/;
  740. return $tstr if (length($1) == 0);
  741. my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($timer);
  742. my $ilook = 0;
  743. my $we;
  744. for (my $iday = $wday; $iday < 7; $iday++) {
  745. $we = (($iday==0 || $iday==6) ? 1 : 0);
  746. if(!$we) {
  747. $we = $DOIFtools_we[$ilook + 1];
  748. }
  749. if ($tdays =~ /$iday/ or ($tdays =~ /7/ and $we) or ($tdays =~ /8/ and !$we)) {
  750. return strftime("%d.%m.%Y %H:%M:%S",localtime($timer + $ilook * 86400));
  751. }
  752. $ilook++;
  753. }
  754. for (my $iday = 0; $iday < $wday; $iday++) {
  755. $we = (($iday==0 || $iday==6) ? 1 : 0);
  756. if(!$we) {
  757. $we = $DOIFtools_we[$ilook + 1];
  758. }
  759. if ($tdays =~ /$iday/ or ($tdays =~ /7/ and $we) or ($tdays =~ /8/ and !$we)) {
  760. return strftime("%d.%m.%Y %H:%M:%S",localtime($timer + $ilook * 86400));
  761. }
  762. $ilook++;
  763. }
  764. return "no timer next 7 days";
  765. }
  766. sub DOIFtoolsNxTimer {
  767. my ($hash,$arg) = @_;
  768. my $pn = $hash->{NAME};
  769. my $tn= $arg;
  770. my $thash = $defs{$arg};
  771. my $ret = "";
  772. my @ret;
  773. foreach my $key (keys %{$thash->{READINGS}}) {
  774. if ($key =~ m/^timer_\d\d_c\d\d/ && $thash->{READINGS}{$key}{VAL} =~ m/\d\d.\d\d.\d\d\d\d \d\d:\d\d:\d\d\|.*/) {
  775. $ret = AttrVal($pn,"DOIFtoolsReadingsPrefix","N_")."$key:$key.* \{DOIFtoolsNextTimer(ReadingsVal(\"$tn\",\"$key\",\"none\"),\"$tn\")\}";
  776. push @ret, $ret if ($ret);
  777. }
  778. }
  779. if (@ret) {
  780. $ret = join(",", @ret);
  781. if (!AttrVal($tn,"userReadings","")) {
  782. CommandAttr(undef,"$tn userReadings $ret");
  783. $ret = "Created userReadings for <b>$tn</b>.\n";
  784. $ret .= CommandSave(undef,undef) if (AttrVal($pn,"DOIFtoolsExecuteSave",""));
  785. return $ret;
  786. } else {
  787. $ret = "A userReadings attribute already exists, adding is not implemented, try it manually.\r\r $ret\r";
  788. return $ret;
  789. }
  790. }
  791. return join("\n", @ret);
  792. }
  793. sub DOIFtoolsGetAssocDev {
  794. my ($hash,$arg) = @_;
  795. my $pn = $hash->{NAME};
  796. my $tn= $arg;
  797. my $thash = $defs{$arg};
  798. my $ret = "";
  799. my @ret = ();
  800. push @ret ,$arg;
  801. $ret .= $thash->{devices}{all} if ($thash->{devices}{all});
  802. $ret =~ s/^\s|\s$//;
  803. push @ret, split(/ /,$ret);
  804. push @ret, getPawList($tn);
  805. return @ret;
  806. }
  807. sub DOIFtoolsCheckDOIFcoll {
  808. my ($hash,$tn) = @_;
  809. my $ret = "";
  810. my $tail = $defs{$tn}{DEF};
  811. if (!$tail) {
  812. $tail="";
  813. } else {
  814. $tail =~ s/(##.*\n)|(##.*$)|\n/ /g;
  815. }
  816. return("") if ($tail =~ /^ *$/);
  817. $ret .= $tn if ($tail =~ m/(DOELSEIF )/ and !($tail =~ m/(DOELSE )/) and AttrVal($tn,"do","") !~ "always");
  818. return $ret;
  819. }
  820. sub DOIFtoolsCheckDOIF {
  821. my ($hash,$tn) = @_;
  822. my $ret = "";
  823. my $tail = $defs{$tn}{DEF};
  824. if (!$tail) {
  825. $tail="";
  826. } else {
  827. $tail =~ s/(##.*\n)|(##.*$)|\n/ /g;
  828. }
  829. return("") if ($tail =~ /^ *$/);
  830. my $DE = AttrVal("global", "language", "") eq "DE" ? 1 : 0;
  831. if ($DE) {
  832. $ret .= "<li>ersetze <b>DOIF name</b> durch <b>\$SELF</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung_ueber_Auswertung_von_Events\">Auswertung von Events</a>)</li>\n" if ($tail =~ m/[\[|\?]($tn)/);
  833. $ret .= "<li>ersetze <b>ReadingsVal(...)</b> durch <b>[</b>name<b>:</b>reading<b>,</b>default value<b>]</b>, wenn es nicht in einem <b><a href=\"https://fhem.de/commandref.html#IF\">IF-Befehl</a></b> verwendet wird, dort ist es nicht anders möglich einen Default-Wert anzugeben. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung\">Steuerung durch Events</a>)</li>\n" if ($tail =~ m/(ReadingsVal)/);
  834. $ret .= "<li>ersetze <b>ReadingsNum(...)</b> durch <b>[</b>name<b>:</b>reading<b>:d,</b>default value]</b>, wenn es nicht in einem <b><a href=\"https://fhem.de/commandref.html#IF\">IF-Befehl</a></b> verwendet wird, dort ist es nicht anders möglich einen Default-Wert anzugeben. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Filtern_nach_Zahlen\">Filtern nach Zahlen</a>)</li>\n" if ($tail =~ m/(ReadingsNum)/);
  835. $ret .= "<li>ersetze <b>InternalVal(...)</b> durch <b>[</b>name<b>:</b>&amp;internal,</b>default value<b>]</b>, wenn es nicht in einem <b><a href=\"https://fhem.de/commandref.html#IF\">IF-Befehl</a></b> verwendet wird, dort ist es nicht anders möglich einen Default-Wert anzugeben. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung\">Steuerung durch Events</a>)</li>\n" if ($tail =~ m/(InternalVal)/);
  836. $ret .= "<li>ersetze <b>$1...\")}</b> durch <b>$2...</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#command\">FHEM-Befehl</a>)</li>\n" if ($tail =~ m/(\{\s*fhem.*?\"\s*(set|get))/);
  837. $ret .= "<li>ersetze <b>{system \"</b>&lt;SHELL-Befehl&gt;<b>\"}</b> durch <b>\"</b>\&lt;SHELL-Befehl&gt;<b>\"</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#command\">FHEM SHELL-Befehl, nicht blockierend</a>)</li>\n" if ($tail =~ m/(\{\s*system.*?\})/);
  838. $ret .= "<li><b>sleep</b> im DOIF zu nutzen, wird nicht empfohlen, nutze das Attribut <b>wait</b> für (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_wait\">Verzögerungen</a>)</li>\n" if ($tail =~ m/(sleep\s\d+\.?\d+\s*[;|,]?)/);
  839. $ret .= "<li>ersetze <b>[</b>name<b>:?</b>regex<b>]</b> durch <b>[</b>name<b>:\"</b>regex<b>\"]</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung_ueber_Auswertung_von_Events\">Vermeidung veralteter Syntax</a>)</li>\n" if ($tail =~ m/(\[.*?[^"]?:[^"]?\?.*?\])/);
  840. $ret .= "<li>der erste <b>Befehl</b> nach <b>DOELSE</b> scheint eine <b>Bedingung</b> zu sein, weil <b>$2</b> enthalten ist, bitte prüfen.</li>\n" if ($tail =~ m/(DOELSE .*?\]\s*?(\!\S|\=\~|\!\~|and|or|xor|not|\|\||\&\&|\=\=|\!\=|ne|eq|lt|gt|le|ge)\s*?).*?\)/);
  841. my @wait = SplitDoIf(":",AttrVal($tn,"wait",""));
  842. my @sub0 = ();
  843. my @tmp = ();
  844. if (@wait and !AttrVal($tn,"timerWithWait","")) {
  845. for (my $i = 0; $i < @wait; $i++) {
  846. ($sub0[$i],@tmp) = SplitDoIf(",",$wait[$i]);
  847. $sub0[$i] =~ s/\s// if($sub0[$i]);
  848. }
  849. if (defined $defs{$tn}{timeCond}) {
  850. foreach my $key (sort keys %{$defs{$tn}{timeCond}}) {
  851. if (defined($defs{$tn}{timeCond}{$key}) and $defs{$tn}{timeCond}{$key} and $sub0[$defs{$tn}{timeCond}{$key}]) {
  852. $ret .= "<li><b>Timer</b> in der <b>Bedingung</b> and <b>Wait-Timer</b> für <b>Befehle</b> im selben <b>DOIF-Zweig</b>.<br>Wenn ein unerwartetes Verhalten beobachtet wird, nutze das Attribut <b>timerWithWait</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_timerWithWait\">Verzögerung von Timern</a>)</li>\n";
  853. last;
  854. }
  855. }
  856. }
  857. }
  858. my $wait = AttrVal($tn,"wait","");
  859. if ($wait) {
  860. $ret .= "<li>Mindestens ein <b>indirekter Timer</b> im Attribut <b>wait</b> bezieht sich auf den <b>DOIF-Namen</b> ( $tn ) und hat keinen <b>Default-Wert</b>, er sollte angegeben werden.</b>. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_notexist\">Default-Wert</a>)</li>\n"
  861. if($wait =~ m/(\[(\$SELF|$tn).*?(\,.*?)?\])/ and $2 and !$3);
  862. }
  863. if (defined $defs{$tn}{time}) {
  864. foreach my $key (sort keys %{$defs{$tn}{time}}) {
  865. if (defined $defs{$tn}{time}{$key} and $defs{$tn}{time}{$key} =~ m/(\[(\$SELF|$tn).*?(\,.*?)?\])/ and $2 and !$3) {
  866. $ret .= "<li>Mindestens ein <b>indirekter Timer</b> in einer <b>Bedingung</b> bezieht sich auf den <b>DOIF-Namen</b> ( $tn ) und hat keinen <b>Default-Wert</b>, er sollte angegeben werden. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_notexist\">Default-Wert</a>)</li>\n";
  867. last;
  868. }
  869. }
  870. }
  871. if (defined $defs{$tn}{devices}{all}) {
  872. @tmp = ();
  873. my $devi = $defs{$tn}{devices}{all};
  874. $devi =~ s/^ | $//g;
  875. my @devi = split(/ /,$defs{$tn}{devices}{all});
  876. foreach my $key (@devi) {
  877. push @tmp, $key if (defined $defs{$key} and $defs{$key}{TYPE} eq "dummy");
  878. }
  879. if (@tmp) {
  880. @tmp = keys %{{ map { $_ => 1 } @tmp}};
  881. my $tmp = join(" ",sort @tmp);
  882. $ret .= "<li>Dummy-Geräte ( $tmp ) in der Bedingung von DOIF $tn können durch <b>benutzerdefinierte Readings des DOIF</b> ersetzt werden, wenn sie als Frontend-Elemente genutzt werden. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#https://fhem.de/commandref_DE.html#DOIF_setList__readingList\">readingList, setList, webCmd</a>)</li>\n";
  883. }
  884. }
  885. if (defined $defs{$tn}{do}) {
  886. @tmp = ();
  887. foreach my $key (keys %{$defs{$tn}{do}}) {
  888. foreach my $subkey (keys %{$defs{$tn}{do}{$key}}) {
  889. push @tmp, $1 if ($defs{$tn}{do}{$key}{$subkey} =~ m/set (.*?) / and defined $defs{$1} and $defs{$1}{TYPE} eq "dummy");
  890. }
  891. }
  892. if (@tmp) {
  893. @tmp = keys %{{ map { $_ => 1 } @tmp}};
  894. my $tmp = join(" ",sort @tmp);
  895. $ret .= "<li>Statt Dummys ( $tmp ) zu setzen, könnte ggf. der Status des DOIF $tn zur Anzeige im Frontend genutzt werden. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#https://fhem.de/commandref_DE.html#DOIF_cmdState\">DOIF-Status ersetzen</a>)</li>\n";
  896. }
  897. }
  898. } else {
  899. $ret .= "<li>replace <b>DOIF name</b> with <b>\$SELF</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung_ueber_Auswertung_von_Events\">utilization of events</a>)</li>\n" if ($tail =~ m/[\[|\?]($tn)/);
  900. $ret .= "<li>replace <b>ReadingsVal(...)</b> with <b>[</b>name<b>:</b>reading<b>,</b>default value<b>]</b>, if not used in an <b><a href=\"https://fhem.de/commandref.html#IF\">IF command</a></b>, otherwise there is no possibility to use a default value (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung\">controlling by events</a>)</li>\n" if ($tail =~ m/(ReadingsVal)/);
  901. $ret .= "<li>replace <b>ReadingsNum(...)</b> with <b>[</b>name<b>:</b>reading<b>:d,</b>default value]</b>, if not used in an <b><a href=\"https://fhem.de/commandref.html#IF\">IF command</a></b>, otherwise there is no possibility to use a default value (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Filtern_nach_Zahlen\">filtering numbers</a>)</li>\n" if ($tail =~ m/(ReadingsNum)/);
  902. $ret .= "<li>replace <b>InternalVal(...)</b> with <b>[</b>name<b>:</b>&amp;internal,</b>default value<b>]</b>, if not used in an <b><a href=\"https://fhem.de/commandref.html#IF\">IF command</a></b>, otherwise there is no possibility to use a default value (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung\">controlling by events</a>)</li>\n" if ($tail =~ m/(InternalVal)/);
  903. $ret .= "<li>replace <b>$1...\")}</b> with <b>$2...</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref.html#command\">plain FHEM command</a>)</li>\n" if ($tail =~ m/(\{\s*fhem.*?\"\s*(set|get))/);
  904. $ret .= "<li>replace <b>{system \"</b>&lt;shell command&gt;<b>\"}</b> with <b>\"</b>\&lt;shell command&gt;<b>\"</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref.html#command\">plain FHEM shell command, non blocking</a>)</li>\n" if ($tail =~ m/(\{\s*system.*?\})/);
  905. $ret .= "<li><b>sleep</b> is not recommended in DOIF, use attribute <b>wait</b> for (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_wait\">delay</a>)</li>\n" if ($tail =~ m/(sleep\s\d+\.?\d+\s*[;|,]?)/);
  906. $ret .= "<li>replace <b>[</b>name<b>:?</b>regex<b>]</b> by <b>[</b>name<b>:\"</b>regex<b>\"]</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_Ereignissteuerung_ueber_Auswertung_von_Events\">avoid old syntax</a>)</li>\n" if ($tail =~ m/(\[.*?[^"]?:[^"]?\?.*?\])/);
  907. $ret .= "<li>the first <b>command</b> after <b>DOELSE</b> seems to be a <b>condition</b> indicated by <b>$2</b>, check it.</li>\n" if ($tail =~ m/(DOELSE .*?\]\s*?(\!\S|\=\~|\!\~|and|or|xor|not|\|\||\&\&|\=\=|\!\=|ne|eq|lt|gt|le|ge)\s*?).*?\)/);
  908. my @wait = SplitDoIf(":",AttrVal($tn,"wait",""));
  909. my @sub0 = ();
  910. my @tmp = ();
  911. if (@wait and !AttrVal($tn,"timerWithWait","")) {
  912. for (my $i = 0; $i < @wait; $i++) {
  913. ($sub0[$i],@tmp) = SplitDoIf(",",$wait[$i]);
  914. $sub0[$i] =~ s/\s// if($sub0[$i]);
  915. }
  916. if (defined $defs{$tn}{timeCond}) {
  917. foreach my $key (sort keys %{$defs{$tn}{timeCond}}) {
  918. if (defined($defs{$tn}{timeCond}{$key}) and $defs{$tn}{timeCond}{$key} and $sub0[$defs{$tn}{timeCond}{$key}]) {
  919. $ret .= "<li><b>Timer</b> in <b>condition</b> and <b>wait timer</b> for <b>commands</b> in the same <b>DOIF branch</b>.<br>If you observe unexpected behaviour, try attribute <b>timerWithWait</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_timerWithWait\">delay of Timer</a>)</li>\n";
  920. last;
  921. }
  922. }
  923. }
  924. }
  925. my $wait = AttrVal($tn,"wait","");
  926. if ($wait) {
  927. $ret .= "<li>At least one <b>indirect timer</b> in attribute <b>wait</b> is referring <b>DOIF's name</b> ( $tn ) and has no <b>default value</b>, you should add <b>default values</b>. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_notexist\">default value</a>)</li>\n"
  928. if($wait =~ m/(\[(\$SELF|$tn).*?(\,.*?)?\])/ and $2 and !$3);
  929. }
  930. if (defined $defs{$tn}{time}) {
  931. foreach my $key (sort keys %{$defs{$tn}{time}}) {
  932. if (defined $defs{$tn}{time}{$key} and $defs{$tn}{time}{$key} =~ m/(\[(\$SELF|$tn).*?(\,.*?)?\])/ and $2 and !$3) {
  933. $ret .= "<li>At least one <b>indirect timer</b> in <b>condition</b> is referring <b>DOIF's name</b> ( $tn ) and has no <b>default value</b>, you should add <b>default values</b>. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_notexist\">default value</a>)</li>\n";
  934. last;
  935. }
  936. }
  937. }
  938. if (defined $defs{$tn}{devices}{all}) {
  939. @tmp = ();
  940. my $devi = $defs{$tn}{devices}{all};
  941. $devi =~ s/^ | $//g;
  942. my @devi = split(/ /,$defs{$tn}{devices}{all});
  943. foreach my $key (@devi) {
  944. push @tmp, $key if (defined $defs{$key} and $defs{$key}{TYPE} eq "dummy");
  945. }
  946. if (@tmp) {
  947. my $tmp = join(" ",sort @tmp);
  948. $ret .= "<li>dummy devices in DOIF $tn condition could replaced by <b>user defined readings</b> in DOIF, if they are used as frontend elements. (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#https://fhem.de/commandref_DE.html#DOIF_setList__readingList\">readingList, setList, webCmd</a>)</li>\n";
  949. }
  950. }
  951. if (defined $defs{$tn}{do}) {
  952. @tmp = ();
  953. foreach my $key (keys %{$defs{$tn}{do}}) {
  954. foreach my $subkey (keys %{$defs{$tn}{do}{$key}}) {
  955. push @tmp, $1 if ($defs{$tn}{do}{$key}{$subkey} =~ m/set (.*?) / and defined $defs{$1} and $defs{$1}{TYPE} eq "dummy");
  956. }
  957. }
  958. if (@tmp) {
  959. my $tmp = join(" ",sort @tmp);
  960. $ret .= "<li>The state of DOIF $tn could be eventually used as display element in frontend, instead of setting a dummy device ( $tmp ). (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#https://fhem.de/commandref_DE.html#DOIF_cmdState\">replace DOIF state</a>)</li>\n";
  961. }
  962. }
  963. }
  964. $ret = $ret ? "$tn\n<ul>$ret</ul> " : "";
  965. return $ret;
  966. }
  967. # param: $hash, doif_to_log, statisticsTypes as 1 or 0
  968. sub DOIFtoolsSetNotifyDev {
  969. my ($hash,@a) = @_;
  970. my $pn = $hash->{NAME};
  971. $hash->{NOTIFYDEV} = "global";
  972. $hash->{NOTIFYDEV} .= ",$attr{global}{holiday2we}" if ($attr{global}{holiday2we});
  973. $hash->{NOTIFYDEV} .= ",".ReadingsVal($pn,"doif_to_log","") if ($a[0] and ReadingsVal($pn,"doif_to_log","") and ReadingsVal($pn,"specialLog",0));
  974. $hash->{NOTIFYDEV} .= ",TYPE=".ReadingsVal($pn,"statisticsTYPEs","") if ($a[1] and ReadingsVal($pn,"statisticsTYPEs","") and ReadingsVal($pn,"doStatistics","deleted") eq "enabled");
  975. return undef;
  976. }
  977. sub DOIFtoolsCounterReset($) {
  978. my ($pn) = @_;
  979. RemoveInternalTimer($pn,"DOIFtoolsCounterReset");
  980. $defs{$pn}->{helper}{counter}{0} = 0;
  981. my $nt = gettimeofday();
  982. my @lt = localtime($nt);
  983. $nt -= ($lt[2]*3600+$lt[1]*60+$lt[0]); # Midnight
  984. $nt += 86400 + 3; # Tomorrow
  985. InternalTimer($nt, "DOIFtoolsCounterReset", $pn, 0);
  986. return undef;
  987. }
  988. sub DOIFtoolsDeleteStatReadings {
  989. my ($hash, @a) = @_;
  990. my $pn = $hash->{NAME};
  991. my $st = AttrVal($pn,"DOIFtoolsHideStatReadings","") ? ".stat_" : "stat_"; readingsBeginUpdate($hash);
  992. readingsBulkUpdate($hash,"Action","event recording stopped and data deleted");
  993. readingsBulkUpdate($hash,"doStatistics","disabled");
  994. readingsBulkUpdate($hash,"statisticHours","0.00");
  995. readingsBulkUpdate($hash,".t0",gettimeofday());
  996. readingsBulkUpdate($hash,".te",0);
  997. readingsEndUpdate($hash,0);
  998. if (AttrVal($pn,"DOIFtoolsEventOnDeleted","")){
  999. readingsBeginUpdate($hash);
  1000. foreach my $key (keys %{$hash->{READINGS}}) {
  1001. readingsBulkUpdate($hash,"stat_$1",ReadingsVal($pn,"$key",0)) if ($key =~ m/^$st(.*)/);
  1002. }
  1003. readingsEndUpdate($hash,1);
  1004. }
  1005. foreach my $key (keys %{$hash->{READINGS}}) {
  1006. delete $hash->{READINGS}{$key} if ($key =~ "^(stat_|\.stat_)");
  1007. }
  1008. }
  1009. #################################
  1010. sub DOIFtools_Define($$$)
  1011. {
  1012. my ($hash, $def) = @_;
  1013. my ($pn, $type, $cmd) = split(/[\s]+/, $def, 3);
  1014. my @Liste = devspec2array("TYPE=DOIFtools");
  1015. if (@Liste > 1) {
  1016. CommandDelete(undef,$pn);
  1017. # CommandSave(undef,undef);
  1018. return "Only one instance of DOIFtools is allowed per FHEM installation. Delete the old one first.";
  1019. }
  1020. $hash->{STATE} = "initialized";
  1021. $hash->{logfile} = AttrVal($pn,"DOIFtoolsLogDir",AttrVal("global","logdir","./log/"))."$hash->{TYPE}Log-%Y-%j.log";
  1022. DOIFtoolsCounterReset($pn);
  1023. return undef;
  1024. }
  1025. sub DOIFtools_Attr(@)
  1026. {
  1027. my @a = @_;
  1028. my $cmd = $a[0];
  1029. my $pn = $a[1];
  1030. my $attr = $a[2];
  1031. my $value = (defined $a[3]) ? $a[3] : "";
  1032. my $hash = $defs{$pn};
  1033. my $ret="";
  1034. if ($init_done and $attr eq "DOIFtoolsMenuEntry") {
  1035. if ($cmd eq "set" and $value) {
  1036. if (!(AttrVal($FW_wname, "menuEntries","") =~ m/(DOIFtools\,$FW_ME\?detail\=DOIFtools\,)/)) {
  1037. CommandAttr(undef, "$FW_wname menuEntries DOIFtools,$FW_ME?detail=DOIFtools,".AttrVal($FW_wname, "menuEntries",""));
  1038. # CommandSave(undef, undef);
  1039. }
  1040. } elsif ($init_done and $cmd eq "del" or !$value) {
  1041. if (AttrVal($FW_wname, "menuEntries","") =~ m/(DOIFtools\,$FW_ME\?detail\=DOIFtools\,)/) {
  1042. my $me = AttrVal($FW_wname, "menuEntries","");
  1043. $me =~ s/DOIFtools\,$FW_ME\?detail\=DOIFtools\,//;
  1044. CommandAttr(undef, "$FW_wname menuEntries $me");
  1045. # CommandSave(undef, undef);
  1046. }
  1047. }
  1048. } elsif ($init_done and $attr eq "DOIFtoolsLogDir") {
  1049. if ($cmd eq "set") {
  1050. if ($value and -d $value) {
  1051. $value =~ m,^(.*)/$,;
  1052. return "Path \"$value\" needs a final slash." if (!$1);
  1053. $hash->{logfile} = "$value$hash->{TYPE}Log-%Y-%j.log";
  1054. } else {
  1055. return "\"$value\" is not a valid directory";
  1056. }
  1057. } elsif ($cmd eq "del" or !$value) {
  1058. $hash->{logfile} = AttrVal("global","logdir","./log/")."$hash->{TYPE}Log-%Y-%j.log";
  1059. }
  1060. } elsif ($init_done and $attr eq "DOIFtoolsHideStatReadings") {
  1061. DOIFtoolsSetNotifyDev($hash,1,0);
  1062. DOIFtoolsDeleteStatReadings($hash);
  1063. } elsif ($init_done and $cmd eq "set" and
  1064. $attr =~ m/^(executeDefinition|executeSave|target_room|target_group|readingsPrefix|eventMonitorInDOIF)$/) {
  1065. $ret .= "\n$1 is an old attribute name use a new one beginning with DOIFtools...";
  1066. return $ret;
  1067. }
  1068. return undef;
  1069. }
  1070. sub DOIFtools_Undef
  1071. {
  1072. my ($hash, $pn) = @_;
  1073. $hash->{DELETED} = 1;
  1074. if (devspec2array("TYPE=DOIFtools") <=1 and defined($modules{DOIF}->{FW_detailFn}) and $modules{DOIF}->{FW_detailFn} eq "DOIFtools_eM") {
  1075. $modules{DOIF}->{FW_detailFn} = ReadingsVal($pn,".DOIF_detailFn","");
  1076. $modules{DOIF}->{FW_deviceOverview} = ReadingsVal($pn,".DOIFdO","");
  1077. }
  1078. if (AttrVal($pn,"DOIFtoolsMenuEntry","")) {
  1079. CommandDeleteAttr(undef, "$pn DOIFtoolsMenuEntry");
  1080. }
  1081. RemoveInternalTimer($pn,"DOIFtoolsCounterReset");
  1082. return undef;
  1083. }
  1084. sub DOIFtools_Set($@)
  1085. {
  1086. my ($hash, @a) = @_;
  1087. my $pn = $hash->{NAME};
  1088. my $arg = $a[1];
  1089. my $value = (defined $a[2]) ? $a[2] : "";
  1090. my $ret = "";
  1091. my @ret = ();
  1092. my @doifList = devspec2array("TYPE=DOIF");
  1093. my @deviList = devspec2array("TYPE!=DOIF");
  1094. my @ntL =();
  1095. my $dL = join(",",sort @doifList);
  1096. my $deL = join(",",sort @deviList);
  1097. my $st = AttrVal($pn,"DOIFtoolsHideStatReadings","") ? ".stat_" : "stat_";
  1098. my %types = ();
  1099. foreach my $d (keys %defs ) {
  1100. next if(IsIgnored($d));
  1101. my $t = $defs{$d}{TYPE};
  1102. $types{$t} = "";
  1103. }
  1104. my $tL = join(",",sort keys %types);
  1105. if ($arg eq "sourceAttribute") {
  1106. readingsSingleUpdate($hash,"sourceAttribute",$value,0);
  1107. return $ret;
  1108. } elsif ($arg eq "targetDOIF") {
  1109. readingsSingleUpdate($hash,"targetDOIF",$value,0);
  1110. FW_directNotify("#FHEMWEB:$FW_wname", "location.reload('".AttrVal($pn,"DOIFtoolsForceGet","")."')", "");
  1111. } elsif ($arg eq "deleteReadingsInTargetDOIF") {
  1112. if ($value) {
  1113. my @i = split(",",$value);
  1114. foreach my $i (@i) {
  1115. $ret = CommandDeleteReading(undef,ReadingsVal($pn,"targetDOIF","")." $i");
  1116. push @ret, $ret if($ret);
  1117. }
  1118. $ret = join("\n", @ret);
  1119. readingsSingleUpdate($hash,"targetDOIF","",0);
  1120. return $ret;
  1121. } else {
  1122. readingsSingleUpdate($hash,"targetDOIF","",0);
  1123. return "no reading selected.";
  1124. }
  1125. } elsif ($arg eq "targetDevice") {
  1126. readingsSingleUpdate($hash,"targetDevice",$value,0);
  1127. FW_directNotify("#FHEMWEB:$FW_wname", "location.reload('".AttrVal($pn,"DOIFtoolsForceGet","")."')", "");
  1128. } elsif ($arg eq "deleteReadingsInTargetDevice") {
  1129. if ($value) {
  1130. my @i = split(",",$value);
  1131. foreach my $i (@i) {
  1132. $ret = CommandDeleteReading(undef,ReadingsVal($pn,"targetDevice","")." $i");
  1133. push @ret, $ret if($ret);
  1134. }
  1135. $ret = join("\n", @ret);
  1136. readingsSingleUpdate($hash,"targetDevice","",0);
  1137. return $ret;
  1138. } else {
  1139. readingsSingleUpdate($hash,"targetDevice","",0);
  1140. return "no reading selected.";
  1141. }
  1142. } elsif ($arg eq "doStatistics") {
  1143. if ($value eq "deleted") {
  1144. DOIFtoolsSetNotifyDev($hash,1,0);
  1145. DOIFtoolsDeleteStatReadings($hash);
  1146. } elsif ($value eq "disabled") {
  1147. readingsBeginUpdate($hash);
  1148. readingsBulkUpdate($hash,"Action","event recording paused");
  1149. readingsBulkUpdate($hash,"doStatistics","disabled");
  1150. readingsEndUpdate($hash,0);
  1151. DOIFtoolsSetNotifyDev($hash,1,0);
  1152. } elsif ($value eq "enabled") {
  1153. readingsBeginUpdate($hash);
  1154. readingsBulkUpdate($hash,"Action","<html><div style=\"color:red;\" >recording events</div></html>");
  1155. readingsBulkUpdate($hash,"doStatistics","enabled");
  1156. readingsBulkUpdate($hash,".t0",gettimeofday());
  1157. readingsEndUpdate($hash,0);
  1158. DOIFtoolsSetNotifyDev($hash,1,1);
  1159. }
  1160. } elsif ($arg eq "statisticsTYPEs") {
  1161. $value =~ s/\,/|/g;
  1162. readingsBeginUpdate($hash);
  1163. readingsBulkUpdate($hash,"statisticsTYPEs",$value);
  1164. readingsEndUpdate($hash,0);
  1165. DOIFtoolsDeleteStatReadings($hash);
  1166. DOIFtoolsSetNotifyDev($hash,1,0);
  1167. } elsif ($arg eq "recording_target_duration") {
  1168. $value =~ m/(\d+)/;
  1169. readingsSingleUpdate($hash,"recording_target_duration",$1 ? $1 : 0,0);
  1170. } elsif ($arg eq "statisticsShowRate_ge") {
  1171. $value =~ m/(\d+)/;
  1172. readingsSingleUpdate($hash,"statisticsShowRate_ge",$1 ? $1 : 0,0);
  1173. } elsif ($arg eq "specialLog") {
  1174. if ($value) {
  1175. readingsSingleUpdate($hash,"specialLog",1,0);
  1176. DOIFtoolsSetNotifyDev($hash,1,1);
  1177. } else {
  1178. readingsSingleUpdate($hash,"specialLog",0,0);
  1179. DOIFtoolsSetNotifyDev($hash,0,1);
  1180. }
  1181. } elsif ($arg eq "statisticsDeviceFilterRegex") {
  1182. $ret = "Bad regexp: starting with *" if($value =~ m/^\*/);
  1183. eval { "Hallo" =~ m/^$value$/ };
  1184. $ret .= "\nBad regexp: $@" if($@);
  1185. if ($ret or !$value) {
  1186. readingsSingleUpdate($hash,"statisticsDeviceFilterRegex", ".*",0);
  1187. return "$ret\nRegexp is set to: .*";
  1188. } else {
  1189. readingsSingleUpdate($hash,"statisticsDeviceFilterRegex", $value,0);
  1190. }
  1191. } else {
  1192. my $hardcoded = "doStatistics:disabled,enabled,deleted specialLog:0,1";
  1193. my $retL = "unknown argument $arg for $pn, choose one of statisticsTYPEs:multiple-strict,.*,$tL sourceAttribute:readingList targetDOIF:$dL targetDevice:$deL recording_target_duration:0,1,6,12,24,168 statisticsDeviceFilterRegex statisticsShowRate_ge ".(AttrVal($pn,"DOIFtoolsHideGetSet",0) ? $hardcoded :"");
  1194. if (ReadingsVal($pn,"targetDOIF","")) {
  1195. my $tn = ReadingsVal($pn,"targetDOIF","");
  1196. my @rL = ();
  1197. foreach my $key (keys %{$defs{$tn}->{READINGS}}) {
  1198. push @rL, $key if ($key !~ "^(Device|state|error|cmd|e_|timer_|wait_|matched_|last_cmd|mode|\.eM)");
  1199. }
  1200. $retL .= " deleteReadingsInTargetDOIF:multiple-strict,".join(",", sort @rL);
  1201. }
  1202. if (ReadingsVal($pn,"targetDevice","")) {
  1203. my $tn = ReadingsVal($pn,"targetDevice","");
  1204. my @rL = ();
  1205. my $rx = ReadingsVal($pn,".debug","") ? "^(state)" : "^(state|[.])";
  1206. foreach my $key (keys %{$defs{$tn}->{READINGS}}) {
  1207. push @rL, $key if ($key !~ $rx);
  1208. }
  1209. $retL .= " deleteReadingsInTargetDevice:multiple-strict,".join(",", sort @rL);
  1210. }
  1211. return $retL;
  1212. }
  1213. return $ret;
  1214. }
  1215. sub DOIFtools_Get($@)
  1216. {
  1217. my ($hash, @a) = @_;
  1218. my $pn = $hash->{NAME};
  1219. my $arg = $a[1];
  1220. my $value = (defined $a[2]) ? $a[2] : "";
  1221. my $ret="";
  1222. my @ret=();
  1223. my @doifList = devspec2array("TYPE=DOIF");
  1224. my @ntL =();
  1225. my $dL = join(",",sort @doifList);
  1226. my $DE = AttrVal("global", "language", "") eq "DE" ? 1 : 0;
  1227. foreach my $i (@doifList) {
  1228. foreach my $key (keys %{$defs{$i}{READINGS}}) {
  1229. if ($key =~ m/^timer_\d\d_c\d\d/ && $defs{$i}{READINGS}{$key}{VAL} =~ m/\d\d.\d\d.\d\d\d\d \d\d:\d\d:\d\d\|.*/) {
  1230. push @ntL, $i;
  1231. last;
  1232. }
  1233. }
  1234. }
  1235. my $ntL = join(",",@ntL);
  1236. my %types = ();
  1237. foreach my $d (keys %defs ) {
  1238. next if(IsIgnored($d));
  1239. my $t = $defs{$d}{TYPE};
  1240. $types{$t} = "";
  1241. }
  1242. if ($arg eq "readingsGroup_for") {
  1243. foreach my $i (split(",",$value)) {
  1244. push @ret, DOIFtoolsRg($hash,$i);
  1245. }
  1246. $ret .= join("\n",@ret);
  1247. $ret = "<b>Definition for a simple readingsGroup prepared for import with \"Raw definition\":</b>\r--->\r$ret\r<---\r\r";
  1248. $ret = "<b>Die Definition einer einfachen readingsGroup ist für den Import mit \"Raw definition\"</b> vorbereitet:\r--->\r$ret\r<---\r\r" if ($DE);
  1249. Log3 $pn, 3, $ret if($ret);
  1250. return $ret;
  1251. } elsif ($arg eq "DOIF_to_Log") {
  1252. my @regex = ();
  1253. my $regex = "";
  1254. my $pnLog = "$hash->{TYPE}Log";
  1255. push @regex, $pnLog;
  1256. readingsSingleUpdate($hash,"doif_to_log",$value,0);
  1257. readingsSingleUpdate($hash,"specialLog",0,0) if (!$value);
  1258. DOIFtoolsSetNotifyDev($hash,0,1);
  1259. # return unless($value);
  1260. foreach my $i (split(",",$value)) {
  1261. push @regex, DOIFtoolsGetAssocDev($hash,$i);
  1262. }
  1263. @regex = keys %{{ map { $_ => 1 } @regex}};
  1264. $regex = join("|",@regex).":.*";
  1265. if (AttrVal($pn,"DOIFtoolsExecuteDefinition","")) {
  1266. push @ret, "Create device <b>$pnLog</b>.\n";
  1267. $ret = CommandDefMod(undef,"$pnLog FileLog ".InternalVal($pn,"logfile","./log/$pnLog-%Y-%j.log")." $regex");
  1268. push @ret, $ret if($ret);
  1269. $ret = CommandAttr(undef,"$pnLog mseclog ".AttrVal($pnLog,"mseclog","1"));
  1270. push @ret, $ret if($ret);
  1271. $ret = CommandAttr(undef,"$pnLog nrarchive ".AttrVal($pnLog,"nrarchive","3"));
  1272. push @ret, $ret if($ret);
  1273. $ret = CommandAttr(undef,"$pnLog disable ".($value ? "0" : "1"));
  1274. push @ret, $ret if($ret);
  1275. $ret = CommandSave(undef,undef) if (AttrVal($pn,"DOIFtoolsExecuteSave",""));
  1276. push @ret, $ret if($ret);
  1277. $ret = join("\n", @ret);
  1278. Log3 $pn, 3, $ret if($ret);
  1279. return $ret;
  1280. } else {
  1281. $ret = "<b>Definition for a FileLog prepared for import with \"Raw definition\":</b>\r--->\r";
  1282. $ret = "<b>Die FileLog-Definition ist zum Import mit \"Raw definition\"</b>vorbereitet:\r--->\r" if ($DE);
  1283. $ret .= "defmod $pnLog FileLog ".InternalVal($pn,"logfile","./log/$pnLog-%Y-%j.log")." $regex\r";
  1284. $ret .= "attr $pnLog mseclog 1\r<---\r\r";
  1285. return $ret;
  1286. }
  1287. } elsif ($arg eq "userReading_nextTimer_for") {
  1288. foreach my $i (split(",",$value)) {
  1289. push @ret, DOIFtoolsNxTimer($hash,$i);
  1290. }
  1291. $ret .= join("\n",@ret);
  1292. Log3 $pn, 3, $ret if($ret);
  1293. return $ret;
  1294. } elsif ($arg eq "statisticsReport") {
  1295. # event statistics
  1296. my $regex = ReadingsVal($pn,"statisticsDeviceFilterRegex",".*");
  1297. my $evtsum = 0;
  1298. my $evtlen = 15 + 2;
  1299. my $rate = 0;
  1300. my $typsum = 0;
  1301. my $typlen = 10 + 2;
  1302. my $typerate = 0;
  1303. my $allattr = "";
  1304. my $rx = AttrVal($pn,"DOIFtoolsHideStatReadings","") ? "\.stat_" : "stat_";
  1305. my $te = ReadingsVal($pn,".te",0)/3600;
  1306. my $compRate = ReadingsNum($pn,"statisticsShowRate_ge",0);
  1307. foreach my $typ ( keys %types) {
  1308. $typlen = length($typ)+2 > $typlen ? length($typ)+2 : $typlen;
  1309. }
  1310. foreach my $key (sort keys %{$defs{$pn}->{READINGS}}) {
  1311. $rate = ($te ? int($hash->{READINGS}{$key}{VAL}/$te + 0.5) : 0) if ($key =~ m/^$rx($regex)/);
  1312. if ($key =~ m/^$rx($regex)/ and $rate >= $compRate) {
  1313. $evtlen = length($1)+2 > $evtlen ? length($1)+2 : $evtlen;
  1314. }
  1315. }
  1316. $ret = "<b>".sprintf("%-".$typlen."s","TYPE").sprintf("%-".$evtlen."s","NAME").sprintf("%-12s","Number").sprintf("%-8s","Rate").sprintf("%-12s","<a href=\"https://wiki.fhem.de/wiki/Event#Beschr.C3.A4nken_von_Events\">Restriction</a>")."</b>\n";
  1317. $ret = "<b>".sprintf("%-".$typlen."s","TYPE").sprintf("%-".$evtlen."s","NAME").sprintf("%-12s","Anzahl").sprintf("%-8s","Rate").sprintf("%-12s","<a href=\"https://wiki.fhem.de/wiki/Event#Beschr.C3.A4nken_von_Events\">Begrenzung</a>")."</b>\n" if ($DE);
  1318. $ret .= sprintf("%-".$typlen."s","").sprintf("%-".$evtlen."s","").sprintf("%-12s","Events").sprintf("%-8s","1/h").sprintf("%-12s","event-on...")."\n";
  1319. $ret .= sprintf("-"x($typlen+$evtlen+33))."\n";
  1320. my $i = 0;
  1321. my $t = 0;
  1322. foreach my $typ (sort keys %types) {
  1323. $typsum = 0;
  1324. $t=0;
  1325. foreach my $key (sort keys %{$defs{$pn}->{READINGS}}) {
  1326. $rate = ($te ? int($hash->{READINGS}{$key}{VAL}/$te + 0.5) : 0) if ($key =~ m/^$rx($regex)/ and defined($defs{$1}) and $defs{$1}->{TYPE} eq $typ);
  1327. if ($key =~ m/^$rx($regex)/ and defined($defs{$1}) and $defs{$1}->{TYPE} eq $typ and $rate >= $compRate) {
  1328. $evtsum += $hash->{READINGS}{$key}{VAL};
  1329. $typsum += $hash->{READINGS}{$key}{VAL};
  1330. $allattr = " ".join(" ",keys %{$attr{$1}});
  1331. $ret .= sprintf("%-".$typlen."s",$typ).sprintf("%-".$evtlen."s",$1).sprintf("%-12s",$hash->{READINGS}{$key}{VAL}).sprintf("%-8s",$rate).sprintf("%-12s",($DE ? ($allattr =~ " event-on" ? "ja" : "nein") : ($allattr =~ " event-on" ? "yes" : "no")))."\n";
  1332. $i++;
  1333. $t++;
  1334. }
  1335. }
  1336. if ($t) {
  1337. $typerate = $te ? int($typsum/$te + 0.5) : 0;
  1338. if($typerate >= $compRate) {
  1339. $ret .= sprintf("%".($typlen+$evtlen+10)."s","="x10).sprintf("%2s"," ").sprintf("="x6)."\n";
  1340. if ($DE) {
  1341. $ret .= sprintf("%".($typlen+$evtlen)."s","Summe: ").sprintf("%-10s",$typsum).sprintf("%2s","&empty;:").sprintf("%-8s",$typerate)."\n";
  1342. $ret .= sprintf("%".($typlen+$evtlen+1)."s","Geräte: ").sprintf("%-10s",$t)."\n";
  1343. $ret .= sprintf("%".($typlen+$evtlen+1)."s","Events/Gerät: ").sprintf("%-10s",int($typsum/$t + 0.5))."\n";
  1344. } else {
  1345. $ret .= sprintf("%".($typlen+$evtlen)."s","Total: ").sprintf("%-10s",$typsum).sprintf("%2s","&empty;:").sprintf("%-8s",$typerate)."\n";
  1346. $ret .= sprintf("%".($typlen+$evtlen)."s","Devices: ").sprintf("%-10s",$t)."\n";
  1347. $ret .= sprintf("%".($typlen+$evtlen)."s","Events/device: ").sprintf("%-10s",int($typsum/$t + 0.5))."\n";
  1348. }
  1349. $ret .= "<div style=\"color:#d9d9d9\" >".sprintf("-"x($typlen+$evtlen+33))."</div>";
  1350. }
  1351. }
  1352. }
  1353. if ($DE) {
  1354. $ret .= sprintf("%".($typlen+$evtlen+10)."s","="x10).sprintf("%2s"," ").sprintf("="x6)."\n";
  1355. $ret .= sprintf("%".($typlen+$evtlen)."s","Summe: ").sprintf("%-10s",$evtsum).sprintf("%2s","&empty;:").sprintf("%-8s",$te ? int($evtsum/$te + 0.5) : "")."\n";
  1356. $ret .= sprintf("%".($typlen+$evtlen)."s","Dauer: ").sprintf("%d:%02d",int($te),int(($te-int($te))*60+.5))."\n";
  1357. $ret .= sprintf("%".($typlen+$evtlen+1)."s","Geräte: ").sprintf("%-10s",$i)."\n";
  1358. $ret .= sprintf("%".($typlen+$evtlen+1)."s","Events/Gerät: ").sprintf("%-10s",int($evtsum/$i + 0.5))."\n\n" if ($i);
  1359. fhem("count",1) =~ m/(\d+)/;
  1360. $ret .= sprintf("%".($typlen+$evtlen+1)."s","Geräte total: ").sprintf("%-10s","$1\n\n");
  1361. $ret .= sprintf("%".($typlen+$evtlen+1)."s","<u>Filter</u>\n");
  1362. $ret .= sprintf("%".($typlen+$evtlen)."s","TYPE: ").sprintf("%-10s",ReadingsVal($pn,"statisticsTYPEs","")."\n");
  1363. $ret .= sprintf("%".($typlen+$evtlen-7)."s","NAME: ").sprintf("%-10s",ReadingsVal($pn,"statisticsDeviceFilterRegex",".*")."\n");
  1364. $ret .= sprintf("%".($typlen+$evtlen-7)."s","Rate: ").sprintf("%-10s","&gt;= $compRate\n\n");
  1365. } else {
  1366. $ret .= sprintf("%".($typlen+$evtlen+10)."s","="x10).sprintf("%2s"," ").sprintf("="x6)."\n";
  1367. $ret .= sprintf("%".($typlen+$evtlen)."s","Total: ").sprintf("%-10s",$evtsum).sprintf("%2s","&empty;:").sprintf("%-8s",$te ? int($evtsum/$te + 0.5) : "")."\n";
  1368. $ret .= sprintf("%".($typlen+$evtlen)."s","Duration: ").sprintf("%d:%02d",int($te),int(($te-int($te))*60+.5))."\n";
  1369. $ret .= sprintf("%".($typlen+$evtlen)."s","Devices: ").sprintf("%-10s",$i)."\n";
  1370. $ret .= sprintf("%".($typlen+$evtlen)."s","Events/device: ").sprintf("%-10s",int($evtsum/$i + 0.5))."\n\n" if ($i);
  1371. fhem("count",1) =~ m/(\d+)/;
  1372. $ret .= sprintf("%".($typlen+$evtlen)."s","Devices total: ").sprintf("%-10s","$1\n\n");
  1373. $ret .= sprintf("%".($typlen+$evtlen+1)."s","<u>Filter</u>\n");
  1374. $ret .= sprintf("%".($typlen+$evtlen)."s","TYPE: ").sprintf("%-10s",ReadingsVal($pn,"statisticsTYPEs","")."\n");
  1375. $ret .= sprintf("%".($typlen+$evtlen-7)."s","NAME: ").sprintf("%-10s",ReadingsVal($pn,"statisticsDeviceFilterRegex",".*")."\n");
  1376. $ret .= sprintf("%".($typlen+$evtlen-7)."s","Rate: ").sprintf("%-10s","&gt;= $compRate\n\n");
  1377. }
  1378. $ret .= "<div style=\"color:#d9d9d9\" >".sprintf("-"x($typlen+$evtlen+33))."</div>";
  1379. # attibute statistics
  1380. if ($DE) {
  1381. $ret .= "<b>".sprintf("%-30s","genutzte Attribute in DOIF").sprintf("%-12s","Anzahl")."</b>\n";
  1382. } else {
  1383. $ret .= "<b>".sprintf("%-30s","used attributes in DOIF").sprintf("%-12s","Number")."</b>\n";
  1384. }
  1385. $ret .= sprintf("-"x42)."\n";
  1386. my %da = ();
  1387. foreach my $di (@doifList) {
  1388. foreach my $dia (keys %{$attr{$di}}) {
  1389. if ($modules{DOIF}{AttrList} =~ m/(^|\s)$dia(:|\s)/) {
  1390. if ($dia =~ "do|selftrigger|checkall") {
  1391. $dia = "* $dia ".AttrVal($di,$dia,"");
  1392. $da{$dia} = ($da{$dia} ? $da{$dia} : 0) + 1;
  1393. } else {
  1394. $dia = "* $dia";
  1395. $da{$dia} = ($da{$dia} ? $da{$dia} : 0) + 1;
  1396. }
  1397. } else {
  1398. $da{$dia} = ($da{$dia} ? $da{$dia} : 0) + 1;
  1399. }
  1400. }
  1401. }
  1402. foreach $i (sort keys %da) {
  1403. $ret .= sprintf("%-30s","$i").sprintf("%-12s","$da{$i}")."\n";
  1404. }
  1405. } elsif ($arg eq "checkDOIF") {
  1406. my @coll = ();
  1407. my $coll = "";
  1408. foreach my $di (@doifList) {
  1409. $coll = DOIFtoolsCheckDOIFcoll($hash,$di);
  1410. push @coll, $coll if($coll);
  1411. }
  1412. $ret .= join(" ",@coll);
  1413. if ($DE) {
  1414. $ret .= "\n<ul><li><b>DOELSEIF</b> ohne <b>DOELSE</b> ist o.k., wenn der Status wechselt, bevor die selbe Bedingung wiederholt wahr wird,<br> andernfalls sollte <b>do always</b> genutzt werden (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_do_always\">Steuerung durch Events</a>, <a target=\"_blank\" href=\"https://wiki.fhem.de/wiki/DOIF/Einsteigerleitfaden,_Grundfunktionen_und_Erl%C3%A4uterungen#Verhaltensweise_ohne_steuernde_Attribute\">Verhalten ohne Attribute</a>)</li></ul> \n" if (@coll);
  1415. } else {
  1416. $ret .= "\n<ul><li><b>DOELSEIF</b> without <b>DOELSE</b> is o.k., if state changes between, the same condition becomes true again,<br>otherwise use attribute <b>do always</b> (<a target=\"_blank\" href=\"https://fhem.de/commandref_DE.html#DOIF_do_always\">controlling by events</a>, <a target=\"_blank\" href=\"https://wiki.fhem.de/wiki/DOIF/Einsteigerleitfaden,_Grundfunktionen_und_Erl%C3%A4uterungen#Verhaltensweise_ohne_steuernde_Attribute\">behaviour without attributes</a>)</li></ul> \n" if (@coll);
  1417. }
  1418. foreach my $di (@doifList) {
  1419. $ret .= DOIFtoolsCheckDOIF($hash,$di);
  1420. }
  1421. $ret = $DE ? ($ret ? "Empfehlung gefunden für:\n\n$ret" : "Keine Empfehlung gefunden.") : ($ret ? "Found recommendation for:\n\n$ret" : "No recommendation found.");
  1422. return $ret;
  1423. } elsif ($arg eq "runningTimerInDOIF") {
  1424. my $erg ="";
  1425. foreach my $di (@doifList) {
  1426. push @ret, sprintf("%-28s","$di").sprintf("%-40s",ReadingsVal($di,"wait_timer",""))."\n" if (ReadingsVal($di,"wait_timer","no timer") ne "no timer");
  1427. }
  1428. $ret .= join("",@ret);
  1429. $ret = $ret ? "Found running wait_timer for:\n\n$ret" : "No running wait_timer found.";
  1430. return $ret;
  1431. } elsif ($arg eq "SetAttrIconForDOIF") {
  1432. $ret .= CommandAttr(undef,"$value icon helper_doif");
  1433. $ret .= CommandSave(undef,undef) if (AttrVal($pn,"DOIFtoolsExecuteSave",""));
  1434. return $ret;
  1435. } elsif ($arg eq "linearColorGradient") {
  1436. my ($sc,$ec,$min,$max,$step) = split(",",$value);
  1437. if ($value && $sc =~ /[0-9A-F]{6}/ && $ec =~ /[0-9A-F]{6}/ && $min =~ /(-?\d+(\.\d+)?)/ && $max =~ /(-?\d+(\.\d+)?)/ && $step =~ /(-?\d+(\.\d+)?)/) {
  1438. $ret .= "<br></pre><table>";
  1439. $ret .= "<tr><td colspan=4 style='font-weight:bold;'>Color Table</td></tr>";
  1440. $ret .= "<tr><td colspan=4><div>";
  1441. for (my $i=0;$i<=127;$i++) {
  1442. my $col = DOIFtoolsLinColorGrad($sc,$ec,0,127,$i);
  1443. $ret .= "<span style='background-color:$col;'>&nbsp;</span>";
  1444. }
  1445. $ret .= "</div></td></tr>";
  1446. $ret .= "<tr style='text-align:center;'><td> Value </td><td> Color Number </td><td> RGB values </td><td> Color</td> </tr>";
  1447. for (my $i=$min;$i<=$max;$i+=$step) {
  1448. my $col = DOIFtoolsLinColorGrad($sc,$ec,$min,$max,$i);
  1449. $col =~ /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/;
  1450. $ret .= "<tr style='text-align:center;'><td>".sprintf("%.1f",$i)."</td><td>$col</td><td> ".hex($1).",".hex($2).",".hex($3)." </td><td style='background-color:$col;'>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;</td></tr>";
  1451. }
  1452. $ret .= "</table><pre>";
  1453. return $ret;
  1454. } else {
  1455. $ret = $DE ? "<br></pre>
  1456. Falsche Eingabe: <code>$value</code><br>
  1457. Syntax: <code>&lt;Startfarbnummer&gt;,&lt;Endfarbnummer&gt;,&lt;Minimalwert&gt;,&lt;Maximalwert&gt;,&lt;Schrittweite&gt;</code><br>
  1458. <ul>
  1459. <li><code>&lt;Startfarbnummer&gt;</code>, ist eine HTML-Farbnummer, Beispiel: #0000FF für Blau.</li>
  1460. <li><code>&lt;Endfarbnummer&gt;</code>, ist eine HTML-Farbnummer, Beispiel: #FF0000 für Rot.</li>
  1461. <li><code>&lt;Minimalwert&gt;</code>, der Minimalwert auf den die Startfarbnummer skaliert wird, Beispiel: 7.</li>
  1462. <li><code>&lt;Maximalwert&gt;</code>, der Maximalwert auf den die Endfarbnummer skaliert wird, Beispiel: 30.</li>
  1463. <li><code>&lt;Schrittweite&gt;</code>, für jeden Schritt wird ein Farbwert erzeugt, Beispiel: 1.</li>
  1464. </ul>
  1465. Beispielangabe: <code>#0000FF,#FF0000,7,30,1</code>
  1466. <pre>":"<br></pre>
  1467. Wrong input: <code>$value</code><br>
  1468. Syntax: <code>&lt;start color number&gt;,&lt;end color number&gt;,&lt;minimal value&gt;,&lt;maximal value&gt;,&lt;step width&gt;</code><br>
  1469. <ul>
  1470. <li><code>&lt;start color number&gt;</code>, a HTML color number, example: #0000FF for blue.</li>
  1471. <li><code>&lt;end color number&gt;</code>, a HTML color number, example: #FF0000 for red.</li>
  1472. <li><code>&lt;minimal value&gt;</code>, the start color number will be scaled to it, example: 7.</li>
  1473. <li><code>&lt;maximal value&gt;</code>, the end color number will be scaled to it, example: 30.</li>
  1474. <li><code>&lt;step width&gt;</code>, for each step a color number will be generated, example: 1.</li>
  1475. </ul>
  1476. Example specification: <code>#0000FF,#FF0000,7,30,1</code>
  1477. <pre>";
  1478. return $ret
  1479. }
  1480. } elsif ($arg eq "hsvColorGradient") {
  1481. my ($min_s,$max_s,$min,$max,$step,$s,$v)=split(",",$value);
  1482. if ($value && $s >= 0 && $s <= 100 && $v >= 0 && $v <= 100 && $min_s >= 0 && $min_s <= 360 && $max_s >= 0 && $max_s <= 360) {
  1483. $ret .= "<br></pre><table>";
  1484. $ret .= "<tr><td colspan=4 style='font-weight:bold;'>Color Table</td></tr>";
  1485. $ret .= "<tr><td colspan=4><div>";
  1486. for (my $i=0;$i<=127;$i++) {
  1487. my $col = DOIFtoolsHsvColorGrad($i,0,127,$min_s,$max_s,$s,$v);
  1488. $ret .= "<span style='background-color:$col;'>&nbsp;</span>";
  1489. }
  1490. $ret .= "</div></td></tr>";
  1491. $ret .= "<tr style='text-align:center;'><td> Value </td><td> Color Number </td><td> RGB values </td><td> Color</td> </tr>";
  1492. for (my $i=$min;$i<=$max;$i+=$step) {
  1493. my $col = DOIFtoolsHsvColorGrad($i,$min,$max,$min_s,$max_s,$s,$v);
  1494. $col =~ /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/;
  1495. $ret .= "<tr style='text-align:center;'><td>".sprintf("%.1f",$i)."</td><td>$col</td><td> ".hex($1).",".hex($2).",".hex($3)." </td><td style='background-color:$col;'>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;</td></tr>";
  1496. }
  1497. $ret .= "</table><pre>";
  1498. return $ret;
  1499. } else {
  1500. $ret = $DE ? "<br></pre>
  1501. Falsche Eingabe: <code>$value</code><br>
  1502. Syntax: <code>&lt;HUE-Startwert&gt;,&lt;HUE-Endwert&gt;,&lt;Minimalwert&gt;,&lt;Maximalwert&gt;,&lt;Schrittweite&gt;,&lt;Sättigung&gt;,&lt;Hellwert&gt;</code><br>
  1503. <ul>
  1504. <li><code>&lt;HUE-Startwert&gt;</code>, ist ein HUE-Wert <code>0-360</code>, Beispiel: 240 für Blau.</li>
  1505. <li><code>&lt;HUE-Endwert&gt;</code>, ist ein HUE-Wert <code>0-360</code>, Beispiel: 360 für Rot.</li>
  1506. <li><code>&lt;Minimalwert&gt;</code>, der Minimalwert auf den der HUE-Startwert skaliert wird, Beispiel: 7.</li>
  1507. <li><code>&lt;Maximalwert&gt;</code>, der Maximalwert auf den der HUE-Endwert skaliert wird, Beispiel: 30.</li>
  1508. <li><code>&lt;Schrittweite&gt;</code>, für jeden Schritt wird ein Farbwert erzeugt, Beispiel: 1.</li>
  1509. <li><code>&lt;Sättigung&gt;</code>, die verwendete Farbsätigung <code>0-100</code>, Beispiel: 80.</li>
  1510. <li><code>&lt;Hellwert&gt;</code>, Angabe der Helligkeit <code>0-100</code>, Beispiel: 80.</li>
  1511. </ul>
  1512. Beispielangabe: <code>240,360,7,30,1,80,80</code>
  1513. <pre>":"<br></pre>
  1514. Wrong input: <code>$value</code><br>
  1515. Syntax: <code>&lt;HUE start value&gt;,&lt;HUE end value&gt;,&lt;minimal value&gt;,&lt;maximal value&gt;,&lt;step width&gt;,&lt;saturation&gt;,&lt;lightness&gt;</code><br>
  1516. <ul>
  1517. <li><code>&lt;HUE start value&gt;</code>, a HUE value <code>0-360</code>, example: 240 for blue.</li>
  1518. <li><code>&lt;HUE end value&gt;</code>, a HUE value <code>0-360</code>, example: 360 for red.</li>
  1519. <li><code>&lt;minimal value&gt;</code>, the HUE start value will be scaled to it, example: 7.</li>
  1520. <li><code>&lt;maximal value&gt;</code>, the HUE end value will be scaled to it, example: 30.</li>
  1521. <li><code>&lt;step width&gt;</code>, for each step a color number will be generated, example: 1.</li>
  1522. <li><code>&lt;saturation&gt;</code>, a value of saturation <code>0-100</code>, example: 80.</li>
  1523. <li><code>&lt;lightness&gt;</code>, a value of lightness <code>0-100</code>, example: 80.</li>
  1524. </ul>
  1525. Example specification: <code>240,360,7,30,1,80,80</code>
  1526. <pre>";
  1527. return $ret
  1528. }
  1529. } elsif ($arg eq "modelColorGradient") {
  1530. my $err_ret = $DE ? "<br></pre>
  1531. Falsche Eingabe: <code>$value</code><br>
  1532. Syntax: <code>&lt;Minimalwert&gt;,&lt;Zwischenwert&gt;,&lt;Maximalwert&gt;,&lt;Schrittweite&gt;&lt;Farbmodel&gt;</code><br>
  1533. <ul>
  1534. <li><code>&lt;Minimalwert&gt;</code>, der Minimalwert auf den die Startfarbnummer skaliert wird, Beispiel: 7.</li>
  1535. <li><code>&lt;Zwischenwert&gt;</code>, der Fixpunkt zwischen Start- u. Endwert, Beispiel: 20.</li>
  1536. <li><code>&lt;Maximalwert&gt;</code>, der Maximalwert auf den die Endfarbnummer skaliert wird, Beispiel: 30.</li>
  1537. <li><code>&lt;Schrittweite&gt;</code>, für jeden Schritt wird ein Farbwert erzeugt, Beispiel: 1.</li>
  1538. <li><code>&lt;Farbmodel&gt;</code>, die Angabe eines vordefinierten Modells <code>&lt;0|1|2&gt;</code> oder fünf RGB-Werte <br>als Array <code>[r1,g1,b1,r2,g2,b2,r3,g3,b3,r4,g4,b4,r5,g5,b5]</code> für ein eigenes Model.</li>
  1539. </ul>
  1540. Beispiele:<br>
  1541. <code>30,60,100,5,[255,255,0,127,255,0,0,255,0,0,255,255,0,127,255]</code>, z.B. Luftfeuchte<br>
  1542. <code>7,20,30,1,[0,0,255,63,0,192,127,0,127,192,0,63,255,0,0]</code>, z.B. Temperatur<br>
  1543. <code>0,2.6,5.2,0.0625,[192,0,0,208,63,0,224,127,0,240,192,0,255,255,0]</code>, z.B. Exponent der Helligkeit<br>
  1544. <code>7,20,30,1,0</code>
  1545. <pre>":"<br></pre>
  1546. Wrong input: <code>$value</code><br>
  1547. Syntax: <code>&lt;minimal value&gt;,&lt;middle value&gt;,&lt;maximal value&gt;,&lt;step width&gt;,&lt;color model&gt;</code><br>
  1548. <ul>
  1549. <li><code>&lt;minimal value&gt;</code>, the start color number will be scaled to it, example: 7.</li>
  1550. <li><code>&lt;middle value&gt;</code>, a fix point between min and max, example: 20.</li>
  1551. <li><code>&lt;maximal value&gt;</code>, the end color number will be scaled to it, example: 30.</li>
  1552. <li><code>&lt;step width&gt;</code>, for each step a color number will be generated, example: 1.</li>
  1553. <li><code>&lt;color model&gt;</code>, a predefined number &lt;0|1|2&gt; or an array of five RGB values, <br><code>[r1,g1,b1,r2,g2,b2,r3,g3,b3,r4,g4,b4,r5,g5,b5]</code></li>
  1554. </ul>
  1555. Example specifications:<br>
  1556. <code>0,50,100,5,[255,255,0,127,255,0,0,255,0,0,255,255,0,127,255]</code> e.g. humidity<br>
  1557. <code>7,20,30,1,[0,0,255,63,0,192,127,0,127,192,0,63,255,0,0]</code>, e.g. temperature<br>
  1558. <code>0,2.6,5.2,0.0625,[192,0,0,208,63,0,224,127,0,240,192,0,255,255,0]</code>, e.g. brightness exponent<br>
  1559. <code>7,20,30,1,0</code>
  1560. <pre>";
  1561. return $err_ret if (!$value);
  1562. my ($min,$mid,$max,$step,$colors);
  1563. my $err = "";
  1564. $value =~ s/,(\[.*\])//;
  1565. if ($1) {
  1566. $colors = eval($1);
  1567. if ($@) {
  1568. $err="Error eval 1567: $@\n".$err_ret;
  1569. Log3 $hash->{NAME},3,"modelColorGradient \n".$err;
  1570. return $err;
  1571. }
  1572. ($min,$mid,$max,$step) = split(",",$value);
  1573. } else {
  1574. ($min,$mid,$max,$step,$colors) = split(",",$value);
  1575. }
  1576. return $err_ret if ($min>=$mid or $mid >= $max or $step <= 0 or (ref($colors) ne "ARRAY" && $colors !~ "0|1|2"));
  1577. my $erg=eval("\"".Color::pahColor($min,$mid,$max,$min+$step,$colors)."\"");
  1578. if ($@) {
  1579. $err="Error eval 1577: $@\n".$err_ret;
  1580. Log3 $hash->{NAME},3,"modelColorGradient \n".$err;
  1581. return $err;
  1582. }
  1583. $ret .= "<br></pre><table>";
  1584. $ret .= "<tr><td colspan=4 style='font-weight:bold;'>Color Table</td></tr>";
  1585. $ret .= "<tr><td colspan=4><div>";
  1586. for (my $i=0;$i<=127;$i++) {
  1587. my $col = eval("\"".Color::pahColor($min,$mid,$max,$min+$i*($max-$min)/127,$colors)."\"");
  1588. if ($@) {
  1589. $err="Error eval 1567: $@\n".$err_ret;
  1590. Log3 $hash->{NAME},3,"modelColorGradient \n".$err;
  1591. return $err;
  1592. }
  1593. $col = "#".substr($col,0,6);
  1594. $ret .= "<span style='background-color:$col;'>&nbsp;</span>";
  1595. }
  1596. $ret .= "</div></td></tr>";
  1597. $ret .= "<tr style='text-align:center;'><td> Value </td><td> Color Number </td><td> RGB values </td><td> Color</td> </tr>";
  1598. for (my $i=$min;$i<=$max;$i+=$step) {
  1599. my $col = eval("\"".Color::pahColor($min,$mid,$max,$i,$colors)."\"");
  1600. if ($@) {
  1601. $err="Error eval 1567: $@\n".$err_ret;
  1602. Log3 $hash->{NAME},3,"modelColorGradient \n".$err;
  1603. return $err;
  1604. }
  1605. $col = "#".substr($col,0,6);
  1606. $col =~ /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/;
  1607. $ret .= "<tr style='text-align:center;'><td>".sprintf("%.1f",$i)."</td><td>$col</td><td> ".hex($1).",".hex($2).",".hex($3)." </td><td style='background-color:$col;'>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;</td></tr>";
  1608. }
  1609. $ret .= "</table><pre>";
  1610. return $ret;
  1611. } else {
  1612. my $hardcoded = "checkDOIF:noArg statisticsReport:noArg runningTimerInDOIF:noArg";
  1613. return "unknown argument $arg for $pn, choose one of readingsGroup_for:multiple-strict,$dL DOIF_to_Log:multiple-strict,$dL SetAttrIconForDOIF:multiple-strict,$dL userReading_nextTimer_for:multiple-strict,$ntL ".(AttrVal($pn,"DOIFtoolsHideGetSet",0) ? $hardcoded :"")." linearColorGradient:textField modelColorGradient:textField hsvColorGradient:textField";
  1614. }
  1615. return $ret;
  1616. }
  1617. 1;
  1618. =pod
  1619. =item helper
  1620. =item summary tools to support DOIF
  1621. =item summary_DE Werkzeuge zur Unterstützung von DOIF
  1622. =begin html
  1623. <a name="DOIFtools"></a>
  1624. <h3>DOIFtools</h3>
  1625. <ul>
  1626. DOIFtools contains tools to support DOIF.<br>
  1627. <br>
  1628. <ul>
  1629. <li>create readingsGroup definitions for labeling frontend widgets.</li>
  1630. <li>create a debug logfile for some DOIF and quoted devices with optional device listing each state or wait timer update.</li>
  1631. <li>optional device listing in debug logfile each state or wait timer update.</li>
  1632. <li>navigation between device listings in logfile if opened via DOIFtools.</li>
  1633. <li>create userReadings in DOIF devices displaying real dates for weekday restricted timer.</li>
  1634. <li>delete user defined readings in DOIF devices with multiple choice.</li>
  1635. <li>delete visible readings in other devices with multiple choice, but not <i>state</i>.</li>
  1636. <li>record statistics data about events.</li>
  1637. <li>limitting recordig duration.</li>
  1638. <li>generate a statistics report.</li>
  1639. <li>lists every DOIF definition in <i>probably associated with</i>.</li>
  1640. <li>access to DOIFtools from any DOIF device via <i>probably associated with</i></li>
  1641. <li>access from DOIFtools to existing DOIFtoolsLog logfiles</li>
  1642. <li>show event monitor in device detail view and optionally in DOIFs detail view</li>
  1643. <li>convert events to DOIF operands, a selected operand is copied to clipboard and the DEF editor will open</li>
  1644. <li>check definitions and offer recommendations</li>
  1645. <li>create shortcuts</li>
  1646. <li>optionally create a menu entry</li>
  1647. <li>show a list of running wait timer</li>
  1648. <li>scale values to color numbers and RGB values for coloration</li>
  1649. </ul>
  1650. <br>
  1651. Just one definition per FHEM-installation is allowed. <a href="https://fhem.de/commandref_DE.html#DOIFtools">More in the german section.</a>
  1652. <br>
  1653. </ul>
  1654. =end html
  1655. =begin html_DE
  1656. <a name="DOIFtools"></a>
  1657. <h3>DOIFtools</h3>
  1658. <ul>
  1659. DOIFtools stellt Funktionen zur Unterstützung von DOIF-Geräten bereit.<br>
  1660. <br>
  1661. <ul>
  1662. <li>erstellen von readingsGroup Definitionen, zur Beschriftung von Frontendelementen.</li>
  1663. <li>erstellen eines Debug-Logfiles, in dem mehrere DOIF und zugehörige Geräte geloggt werden.</li>
  1664. <li>optionales DOIF-Listing bei jeder Status und Wait-Timer Aktualisierung im Debug-Logfile.</li>
  1665. <li>Navigation zwischen den DOIF-Listings im Logfile, wenn es über DOIFtools geöffnet wird.</li>
  1666. <li>erstellen von userReadings in DOIF-Geräten zur Anzeige des realen Datums bei Wochentag behafteten Timern.</li>
  1667. <li>löschen von benutzerdefinierten Readings in DOIF-Definitionen über eine Mehrfachauswahl.</li>
  1668. <li>löschen von Readings in anderen Geräten über eine Mehrfachauswahl, nicht <i>state</i>.</li>
  1669. <li>erfassen statistischer Daten über Events.</li>
  1670. <li>Begrenzung der Datenaufzeichnungsdauer.</li>
  1671. <li>erstellen eines Statistikreports.</li>
  1672. <li>Liste aller DOIF-Definitionen in <i>probably associated with</i>.</li>
  1673. <li>Zugriff auf DOIFtools aus jeder DOIF-Definition über die Liste in <i>probably associated with</i>.</li>
  1674. <li>Zugriff aus DOIFtools auf vorhandene DOIFtoolsLog-Logdateien.</li>
  1675. <li>zeigt den Event Monitor in der Detailansicht von DOIFtools.</li>
  1676. <li>ermöglicht den Zugriff auf den Event Monitor in der Detailansicht von DOIF.</li>
  1677. <li>erzeugt DOIF-Operanden aus einer Event-Zeile des Event-Monitors.</li>
  1678. <ul>
  1679. <li>Ist der <b>Event-Monitor in DOIF</b> geöffnet, dann kann die Definition des <b>DOIF geändert</b> werden.</li>
  1680. <li>Ist der <b>Event-Monitor in DOIFtools</b> geöffnet, dann kann die Definition eines <b>DOIF erzeugt</b> werden.</li>
  1681. </ul>
  1682. <li>prüfen der DOIF Definitionen mit Empfehlungen.</li>
  1683. <li>erstellen von Shortcuts</li>
  1684. <li>optionalen Menüeintrag erstellen</li>
  1685. <li>Liste der laufenden Wait-Timer anzeigen</li>
  1686. <li>skaliert Werte zu Farbnummern und RGB Werten zum Einfärben, z.B. von Icons.</li>
  1687. </ul>
  1688. <br>
  1689. <b>Inhalt</b><br>
  1690. <ul>
  1691. <a href="#DOIFtoolsBedienungsanleitung">Bedienungsanleitung</a><br>
  1692. <a href="#DOIFtoolsDefinition">Definition</a><br>
  1693. <a href="#DOIFtoolsSet">Set-Befehl</a><br>
  1694. <a href="#DOIFtoolsGet">Get-Befehl</a><br>
  1695. <a href="#DOIFtoolsAttribute">Attribute</a><br>
  1696. <a href="#DOIFtoolsReadings">Readings</a><br>
  1697. <a href="#DOIFtoolsLinks">Links</a><br>
  1698. </ul><br>
  1699. <a name="DOIFtoolsBedienungsanleitung"></a>
  1700. <b>Bedienungsanleitung</b>
  1701. <br>
  1702. <ul>
  1703. Eine <a href="https://wiki.fhem.de/wiki/DOIFtools">Bedienungsanleitung für DOIFtools</a> gibt es im FHEM-Wiki.
  1704. </ul>
  1705. <br>
  1706. <a name="DOIFtoolsDefinition"></a>
  1707. <b>Definition</b>
  1708. <br>
  1709. <ul>
  1710. <code>define &lt;name&gt; DOIFtools</code><br>
  1711. Es ist nur eine Definition pro FHEM Installation möglich. Die Definition wird mit den vorhanden DOIF-Namen ergänzt, daher erscheinen alle DOIF-Geräte in der Liste <i>probably associated with</i>. Zusätzlich wird in jedem DOIF-Gerät in dieser Liste auf das DOIFtool verwiesen.<br>
  1712. <br>
  1713. <u>Definitionsvorschlag</u> zum Import mit <a href="https://wiki.fhem.de/wiki/DOIF/Import_von_Code_Snippets">Raw definition</a>:<br>
  1714. <code>
  1715. defmod DOIFtools DOIFtools<br>
  1716. attr DOIFtools DOIFtoolsEventMonitorInDOIF 1<br>
  1717. attr DOIFtools DOIFtoolsExecuteDefinition 1<br>
  1718. attr DOIFtools DOIFtoolsExecuteSave 1<br>
  1719. attr DOIFtools DOIFtoolsMenuEntry 1<br>
  1720. attr DOIFtools DOIFtoolsMyShortcuts ##My Shortcuts:,,list DOIFtools,fhem?cmd=list DOIFtools<br>
  1721. </code>
  1722. </ul>
  1723. <br>
  1724. <a name="DOIFtoolsSet"></a>
  1725. <b>Set</b>
  1726. <br>
  1727. <ul>
  1728. <code>set &lt;name&gt; deleteReadingInTargetDOIF &lt;readings to delete name&gt;</code><br>
  1729. <b>deleteReadingInTargetDOIF</b> löscht die benutzerdefinierten Readings im Ziel-DOIF<br>
  1730. <br>
  1731. <code>set &lt;name&gt; targetDOIF &lt;target name&gt;</code><br>
  1732. <b>targetDOIF</b> vor dem Löschen der Readings muss das Ziel-DOIF gesetzt werden.<br>
  1733. <br>
  1734. <code>set &lt;name&gt; deleteReadingInTargetDevice &lt;readings to delete name&gt;</code><br>
  1735. <b>deleteReadingInTargetDevice</b> löscht sichtbare Readings, ausser <i>state</i> im Ziel-Gerät. Bitte den Gefahrenhinweis zum Befehl <a href="https://fhem.de/commandref_DE.html#deletereading">deletereading</a> beachten!<br>
  1736. <br>
  1737. <code>set &lt;name&gt; targetDevice &lt;target name&gt;</code><br>
  1738. <b>targetDevice</b> vor dem Löschen der Readings muss das Ziel-Gerät gesetzt werden.<br>
  1739. <br>
  1740. <code>set &lt;name&gt; sourceAttribute &lt;readingList&gt; </code><br>
  1741. <b>sourceAttribute</b> vor dem Erstellen einer ReadingsGroup muss das Attribut gesetzt werden aus dem die Readings gelesen werden, um die ReadingsGroup zu erstellen und zu beschriften. <b>Default, readingsList</b><br>
  1742. <br>
  1743. <code>set &lt;name&gt; statisticsDeviceFilterRegex &lt;regular expression as device filter&gt;</code><br>
  1744. <b>statisticsDeviceFilterRegex</b> setzt einen Filter auf Gerätenamen, nur die gefilterten Geräte werden im Bericht ausgewertet. <b>Default, ".*"</b>.<br>
  1745. <br>
  1746. <code>set &lt;name&gt; statisticsTYPEs &lt;List of TYPE used for statistics generation&gt;</code><br>
  1747. <b>statisticsTYPEs</b> setzt eine Liste von TYPE für die Statistikdaten erfasst werden, bestehende Statistikdaten werden gelöscht. <b>Default, ""</b>.<br>
  1748. <br>
  1749. <code>set &lt;name&gt; statisticsShowRate_ge &lt;integer value for event rate&gt;</code><br>
  1750. <b>statisticsShowRate_ge</b> setzt eine Event-Rate, ab der ein Gerät in die Auswertung einbezogen wird. <b>Default, 0</b>.<br>
  1751. <br>
  1752. <code>set &lt;name&gt; specialLog &lt;0|1&gt;</code><br>
  1753. <b>specialLog</b> <b>1</b> DOIF-Listing bei Status und Wait-Timer Aktualisierung im Debug-Logfile. <b>Default, 0</b>.<br>
  1754. <br>
  1755. <code>set &lt;name&gt; doStatistics &lt;enabled|disabled|deleted&gt;</code><br>
  1756. <b>doStatistics</b><br>
  1757. &emsp;<b>deleted</b> setzt die Statistik zurück und löscht alle <i>stat_</i> Readings.<br>
  1758. &emsp;<b>disabled</b> pausiert die Statistikdatenerfassung.<br>
  1759. &emsp;<b>enabled</b> startet die Statistikdatenerfassung.<br>
  1760. <br>
  1761. <code>set &lt;name&gt; recording_target_duration &lt;hours&gt;</code><br>
  1762. <b>recording_target_duration</b> gibt an wie lange Daten erfasst werden sollen. <b>Default, 0</b> die Dauer ist nicht begrenzt.<br>
  1763. <br>
  1764. </ul>
  1765. <a name="DOIFtoolsGet"></a>
  1766. <b>Get</b>
  1767. <br>
  1768. <ul>
  1769. <code>get &lt;name&gt; DOIF_to_Log &lt;DOIF names for logging&gt;</code><br>
  1770. <b>DOIF_to_Log</b> erstellt eine FileLog-Definition, die für alle angegebenen DOIF-Definitionen loggt. Der <i>Reguläre Ausdruck</i> wird aus den, direkt in den DOIF-Greräte angegebenen und den wahrscheinlich verbundenen Geräten, ermittelt.<br>
  1771. <br>
  1772. <code>get &lt;name&gt; checkDOIF</code><br>
  1773. <b>checkDOIF</b> führt eine einfache Syntaxprüfung durch und empfiehlt Änderungen.<br>
  1774. <br>
  1775. <code>get &lt;name&gt; readingsGroup_for &lt;DOIF names to create readings groups&gt;</code><br>
  1776. <b>readingsGroup_for</b> erstellt readingsGroup-Definitionen für die angegebenen DOIF-namen. <b>sourceAttribute</b> verweist auf das Attribut, dessen Readingsliste als Basis verwendet wird. Die Eingabeelemente im Frontend werden mit den Readingsnamen beschriftet.<br>
  1777. <br>
  1778. <code>get &lt;name&gt; userReading_nextTimer_for &lt;DOIF names where to create real date timer readings&gt;</code><br>
  1779. <b>userReading_nextTimer_for</b> erstellt userReadings-Attribute für Timer-Readings mit realem Datum für Timer, die mit Wochentagangaben angegeben sind, davon ausgenommen sind indirekte Wochentagsangaben.<br>
  1780. <br>
  1781. <code>get &lt;name&gt; statisticsReport </code><br>
  1782. <b>statisticsReport</b> erstellt einen Bericht aus der laufenden Datenerfassung.<br><br>Die Statistik kann genutzt werden, um Geräte mit hohen Ereignisaufkommen zu erkennen. Bei einer hohen Rate, sollte im Interesse der Systemperformance geprüft werden, ob die <a href="https://wiki.fhem.de/wiki/Event">Events</a> eingeschränkt werden können. Werden keine Events eines Gerätes weiterverarbeitet, kann das Attribut <i>event-on-change-reading</i> auf <i>none</i> oder eine andere Zeichenfolge, die im Gerät nicht als Readingname vorkommt, gesetzt werden.<br>
  1783. <br>
  1784. <code>get &lt;name&gt; runningTimerInDOIF</code><br>
  1785. <b>runningTimerInDOIF</b> zeigt eine Liste der laufenden Timer. Damit kann entschieden werden, ob bei einem Neustart wichtige Timer gelöscht werden und der Neustart ggf. verschoben werden sollte.<br>
  1786. <br>
  1787. <code>get &lt;name&gt; SetAttrIconForDOIF &lt;DOIF names for setting the attribute icon to helper_doif&gt;</code><br>
  1788. <b>SetAttrIconForDOIF</b> setzt für die ausgewählten DOIF das Attribut <i>icon</i> auf <i>helper_doif</i>.<br>
  1789. <br>
  1790. <code>get &lt;name&gt; linearColorGradient &lt;start color number&gt;,&lt;end color number&gt;,&lt;minimal value&gt;,&lt;maximal value&gt;,&lt;step width&gt;</code><br>
  1791. <b>linearColorGradient</b> erzeugt eine Tabelle mit linear abgestuften Farbnummern und RGB-Werten.<br>
  1792. &lt;start color number&gt;, ist eine HTML-Farbnummer, Beispiel: #0000FF für Blau.<br>
  1793. &lt;end color number&gt;, , ist eine HTML-Farbnummer, Beispiel: #FF0000 für Rot.<br>
  1794. &lt;minimal value&gt;, der Minimalwert auf den die Startfarbnummer skaliert wird, Beispiel: 7.<br>
  1795. &lt;maximal value&gt;, der Maximalwert auf den die Endfarbnummer skaliert wird, Beispiel: 30.<br>
  1796. &lt;step width&gt;, für jeden Schritt wird ein Farbwert erzeugt, Beispiel: 0.5.
  1797. <br>
  1798. Beispiel: <code>get DOIFtools linearColorGradient #0000FF,#FF0000,7,30,0.5</code><br>
  1799. <br>
  1800. <code>get &lt;name&gt; modelColorGradient &lt;minimal value&gt;,&lt;middle value&gt;,&lt;maximal value&gt;,&lt;step width&gt;,&lt;color model&gt;</code><br>
  1801. <b>modelColorGradient</b> erzeugt eine Tabelle mit modellbedingt abgestuften Farbnummern und RGB-Werten, siehe FHEM-Wiki<a href="https://wiki.fhem.de/wiki/Color#Farbskala_mit_Color::pahColor"> Farbskala mit Color::pahColor </a><br>
  1802. &lt;minimal value&gt;, der Minimalwert auf den die Startfarbnummer skaliert wird, Beispiel: 7.<br>
  1803. &lt;middle value&gt;, der Mittenwert ist ein Fixpunkt zwischen Minimal- u. Maximalwert, Beispiel: 20.<br>
  1804. &lt;maximal value&gt;, der Maximalwert auf den die Endfarbnummer skaliert wird, Beispiel: 30.<br>
  1805. &lt;step width&gt;, für jeden Schritt wird ein Farbwert erzeugt, Beispiel: 1.<br>
  1806. &lt;color model&gt;, die Angabe eines vordefinierten Modells &lt;0|1|2&gt; oder fünf RGB-Werte als Array [r1,g1,b1,r2,g2,b2,r3,g3,b3,r4,g4,b4,r5,g5,b5] für ein eigenes Model.<br>
  1807. <br>
  1808. Beispiele:<br>
  1809. <code>get DOIFtools modelColorGradient 7,20,30,1,0</code><br>
  1810. <code>get DOIFtools modelColorGradient 0,50,100,5,[255,255,0,127,255,0,0,255,0,0,255,255,0,127,255]</code><br>
  1811. <br>
  1812. <code>get &lt;name&gt; hsvColorGradient &lt;HUE start value&gt;,&lt;HUE end value&gt;,&lt;minimal value&gt;,&lt;maximal value&gt;,&lt;step width&gt;,&lt;saturation&gt;,&lt;lightness&gt;</code><br>
  1813. <b>hsvColorGradient</b> erzeugt eine Tabelle über HUE-Werte abgestufte Farbnummern und RGB-Werten.<br>
  1814. &lt;Hue start value&gt;, der HUE-Startwert, Beispiel: 240 für Blau.<br>
  1815. &lt;HUE end value&gt;, der HUE-Endwert, Beispiel: 360 für Rot.<br>
  1816. &lt;minimal value&gt;, der Minimalwert auf den der HUE-Startwert skaliert wird, Beispiel: 7.<br 20.<br>
  1817. &lt;maximal value&gt;, der Maximalwert auf den der HUE-Endwert skaliert wird, Beispiel: 30.<br>
  1818. &lt;step width&gt;, für jeden Schritt wird ein Farbwert erzeugt, Beispiel: 1.<br>
  1819. &lt;saturation&gt;, die Angabe eines Wertes für die Farbsättigung &lt;0-100&gt;, Beispiel 80.<br>
  1820. &lt;lightness&gt;, die Angabe eines Wertes für die Helligkeit &lt;0-100&gt;, Beispiel 80.<br>
  1821. <br>
  1822. Beispiele:<br>
  1823. <code>get DOIFtools hsvColorGradient 240,360,7,30,1,80,80</code><br>
  1824. <br>
  1825. </ul>
  1826. <a name="DOIFtoolsAttribute"></a>
  1827. <b>Attribute</b><br>
  1828. <ul>
  1829. <code>attr &lt;name&gt; DOIFtoolsExecuteDefinition &lt;0|1&gt;</code><br>
  1830. <b>DOIFtoolsExecuteDefinition</b> <b>1</b> führt die erzeugten Definitionen aus. <b>Default 0</b>, zeigt die erzeugten Definitionen an, sie können mit <i>Raw definition</i> importiert werden.<br>
  1831. <br>
  1832. <code>attr &lt;name&gt; DOIFtoolsExecuteSave &lt;0|1&gt;</code><br>
  1833. <b>DOIFtoolsExecuteSave</b> <b>1</b>, die Definitionen werden automatisch gespeichert. <b>Default 0</b>, der Benutzer kann die Definitionen speichern.<br>
  1834. <br>
  1835. <code>attr &lt;name&gt; DOIFtoolsTargetGroup &lt;group names for target&gt;</code><br>
  1836. <b>DOIFtoolsTargetGroup</b> gibt die Gruppen für die zu erstellenden Definitionen an. <b>Default</b>, die Gruppe der Ursprungs Definition.<br>
  1837. <br>
  1838. <code>attr &lt;name&gt; DOIFtoolsTargetRoom &lt;room names for target&gt;</code><br>
  1839. <b>DOIFtoolsTargetRoom</b> gibt die Räume für die zu erstellenden Definitionen an. <b>Default</b>, der Raum der Ursprungs Definition.<br>
  1840. <br>
  1841. <code>attr &lt;name&gt; DOIFtoolsReadingsPrefix &lt;user defined prefix&gt;</code><br>
  1842. <b>DOIFtoolsReadingsPrefix</b> legt den Präfix der benutzerdefinierten Readingsnamen für die Zieldefinition fest. <b>Default</b>, DOIFtools bestimmt den Präfix.<br>
  1843. <br>
  1844. <code>attr &lt;name&gt; DOIFtoolsEventMonitorInDOIF &lt;1|0&gt;</code><br>
  1845. <b>DOIFtoolsEventMonitorInDOIF</b> <b>1</b>, die Anzeige des Event-Monitors wird in DOIF ermöglicht. <b>Default 0</b>, kein Zugriff auf den Event-Monitor im DOIF.<br>
  1846. <br>
  1847. <code>attr &lt;name&gt; DOIFtoolsEMbeforeReadings &lt;1|0&gt;</code><br>
  1848. <b>DOIFtoolsEMbeforeReading</b> <b>1</b>, die Anzeige des Event-Monitors wird in DOIF direkt über den Readings angezeigt. <b>Default 0</b>, anzeige des Event-Monitors über den Internals.<br>
  1849. <br>
  1850. <code>attr &lt;name&gt; DOIFtoolsHideGetSet &lt;0|1&gt;</code><br>
  1851. <b>DOIFtoolsHideModulGetSet</b> <b>1</b>, verstecken der Set- und Get-Shortcuts. <b>Default 0</b>.<br>
  1852. <br>
  1853. <code>attr &lt;name&gt; DOIFtoolsNoLookUp &lt;0|1&gt;</code><br>
  1854. <b>DOIFtoolsNoLookUp</b> <b>1</b>, es werden keine Lookup-Fenster in DOIFtools geöffnet. <b>Default 0</b>.<br>
  1855. <br>
  1856. <code>attr &lt;name&gt; DOIFtoolsNoLookUpInDOIF &lt;0|1&gt;</code><br>
  1857. <b>DOIFtoolsNoLookUpInDOIF</b> <b>1</b>, es werden keine Lookup-Fenster in DOIF geöffnet. <b>Default 0</b>.<br>
  1858. <br>
  1859. <code>attr &lt;name&gt; DOIFtoolsHideModulShortcuts &lt;0|1&gt;</code><br>
  1860. <b>DOIFtoolsHideModulShortcuts</b> <b>1</b>, verstecken der DOIFtools Shortcuts. <b>Default 0</b>.<br>
  1861. <br>
  1862. <code>attr &lt;name&gt; DOIFtoolsHideStatReadings &lt;0|1&gt;</code><br>
  1863. <b>DOIFtoolsHideStatReadings</b> <b>1</b>, verstecken der <i>stat_</i> Readings. Das Ändern des Attributs löscht eine bestehende Event-Aufzeichnung. <b>Default 0</b>.<br>
  1864. <br>
  1865. <code>attr &lt;name&gt; DOIFtoolsEventOnDeleted &lt;0|1&gt;</code><br>
  1866. <b>DOIFtoolsEventOnDeleted</b> <b>1</b>, es werden Events für alle <i>stat_</i> erzeugt, bevor sie gelöscht werden. Damit könnten die erfassten Daten geloggt werden. <b>Default 0</b>.<br>
  1867. <br>
  1868. <code>attr &lt;name&gt; DOIFtoolsMyShortcuts &lt;shortcut name&gt,&lt;command&gt;, ...</code><br>
  1869. <b>DOIFtoolsMyShortcuts</b> &lt;Bezeichnung&gt;<b>,</b>&lt;Befehl&gt;<b>,...</b> anzeigen eigener Shortcuts, siehe globales Attribut <a href="#menuEntries">menuEntries</a>.<br>
  1870. Zusätzlich gilt, wenn ein Eintrag mit ## beginnt und mit ,, endet, wird er als HTML interpretiert.<br>
  1871. <u>Beispiel:</u><br>
  1872. <code>attr DOIFtools DOIFtoolsMyShortcuts ##&lt;br&gt;My Shortcuts:,,list DOIFtools,fhem?cmd=list DOIFtools</code><br>
  1873. <br>
  1874. <code>attr &lt;name&gt; DOIFtoolsMenuEntry &lt;0|1&gt;</code><br>
  1875. <b>DOIFtoolsMenuEntry</b> <b>1</b>, erzeugt einen Menüeintrag im FHEM-Menü. <b>Default 0</b>.<br>
  1876. <br>
  1877. <code>attr &lt;name&gt; DOIFtoolsLogDir &lt;path to DOIFtools logfile&gt;</code><br>
  1878. <b>DOIFtoolsLogDir</b> <b>&lt;path&gt;</b>, gibt den Pfad zum Logfile an <b>Default <i>./log</i> oder der Pfad aus dem Attribut <i>global logdir</i></b>.<br>
  1879. <br>
  1880. <a href="#disabledForIntervals"><b>disabledForIntervals</b></a> pausiert die Statistikdatenerfassung.<br>
  1881. <br>
  1882. </ul>
  1883. <a name="DOIFtoolsReadings"></a>
  1884. <b>Readings</b>
  1885. <br>
  1886. <ul>
  1887. DOIFtools erzeugt bei der Aktualisierung von Readings keine Events, daher muss die Seite im Browser aktualisiert werden, um aktuelle Werte zu sehen.<br>
  1888. <br>
  1889. <li><b>Action</b> zeigt den Status der Event-Aufzeichnung an.</li>
  1890. <li><b>DOIF_version</b> zeigt die Version des DOIF an.</li>
  1891. <li><b>FHEM_revision</b> zeigt die Revision von FHEM an.</li>
  1892. <li><b>doStatistics</b> zeigt den Status der Statistikerzeugung an</li>
  1893. <li><b>logfile</b> gibt den Pfad und den Dateinamen mit Ersetzungszeichen an.</li>
  1894. <li><b>recording_target_duration</b> gibt an wie lange Daten erfasst werden sollen.</li>
  1895. <li><b>stat_</b>&lt;<b>devicename</b>&gt; zeigt die Anzahl der gezählten Ereignisse, die das jeweilige Gerät erzeugt hat.</li>
  1896. <li><b>statisticHours</b> zeigt die kumulierte Zeit für den Status <i>enabled</i> an, während der, Statistikdaten erfasst werden.</li>
  1897. <li><b>statisticShowRate_ge</b> zeigt die Event-Rate, ab der Geräte in die Auswertung einbezogen werden.</li>
  1898. <li><b>statisticsDeviceFilterRegex</b> zeigt den aktuellen Gerätefilterausdruck an.</li>
  1899. <li><b>statisticsTYPEs</b> zeigt eine Liste von <i>TYPE</i> an, für deren Geräte die Statistik erzeugt wird.</li>
  1900. <li><b>targetDOIF</b> zeigt das Ziel-DOIF, bei dem Readings gelöscht werden sollen.</li>
  1901. <li><b>targetDevice</b> zeigt das Ziel-Gerät, bei dem Readings gelöscht werden sollen.</li>
  1902. </ul>
  1903. </br>
  1904. <a name="DOIFtoolsLinks"></a>
  1905. <b>Links</b>
  1906. <br>
  1907. <ul>
  1908. <a href="https://forum.fhem.de/index.php/topic,63938.0.html">DOIFtools im FHEM-Forum</a><br>
  1909. <a href="https://wiki.fhem.de/wiki/DOIFtools">DOIFtools im FHEM-Wiki</a><br>
  1910. <br>
  1911. <a href="https://wiki.fhem.de/wiki/DOIF">DOIF im FHEM-Wiki</a><br>
  1912. <a href="https://wiki.fhem.de/wiki/DOIF/Einsteigerleitfaden,_Grundfunktionen_und_Erl%C3%A4uterungen#Erste_Schritte_mit_DOIF:_Zeit-_und_Ereignissteuerung">Erste Schritte mit DOIF</a><br>
  1913. <a href="https://wiki.fhem.de/wiki/DOIF/Einsteigerleitfaden,_Grundfunktionen_und_Erl%C3%A4uterungen">DOIF: Einsteigerleitfaden, Grundfunktionen und Erläuterungen</a><br>
  1914. <a href="https://wiki.fhem.de/wiki/DOIF/Labor_-_ausf%C3%BChrbare,_praxisnahe_Beispiele_als_Probleml%C3%B6sung_zum_Experimentieren">DOIF-Labor - ausführbare, praxisnahe Beispiele als Problemlösung zum Experimentieren</a><br>
  1915. <a href="https://wiki.fhem.de/wiki/DOIF/Tipps_zur_leichteren_Bedienung">DOIF: Tipps zur leichteren Bedienung</a><br>
  1916. <a href="https://wiki.fhem.de/wiki/DOIF/Tools_und_Fehlersuche">DOIF: Tools und Fehlersuche</a><br>
  1917. </ul>
  1918. </ul>
  1919. =end html_DE
  1920. =cut