33_readingsHistory.pm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. # $Id: 33_readingsHistory.pm 12295 2016-10-08 09:13:11Z justme1968 $
  2. ##############################################################################
  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. #
  11. # Fhem is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. ##############################################################################
  20. package main;
  21. use strict;
  22. use warnings;
  23. use POSIX qw(strftime);
  24. use vars qw($FW_ME);
  25. use vars qw($FW_wname);
  26. use vars qw($FW_subdir);
  27. use vars qw(%FW_hiddenroom);
  28. use vars qw(%FW_visibleDeviceHash);
  29. use vars qw(%FW_webArgs); # all arguments specified in the GET
  30. my $readingsHistory_hasJSON = 0;
  31. my $readingsHistory_hasDataDumper = 1;
  32. sub readingsHistory_Initialize($)
  33. {
  34. my ($hash) = @_;
  35. $hash->{DefFn} = "readingsHistory_Define";
  36. $hash->{NotifyFn} = "readingsHistory_Notify";
  37. $hash->{NotifyOrderPrefix}= "51-";
  38. $hash->{UndefFn} = "readingsHistory_Undefine";
  39. $hash->{SetFn} = "readingsHistory_Set";
  40. $hash->{GetFn} = "readingsHistory_Get";
  41. $hash->{AttrFn} = "readingsHistory_Attr";
  42. $hash->{AttrList} = "disable:1,2,3 mapping style timestampFormat valueFormat noheading:1 nohtml:1 nolinks:1 notime:1 nostate:1 alwaysTrigger:1 rows";
  43. $hash->{FW_detailFn} = "readingsHistory_detailFn";
  44. $hash->{FW_summaryFn} = "readingsHistory_detailFn";
  45. $hash->{FW_atPageEnd} = 1;
  46. eval "use Data::Dumper";
  47. $readingsHistory_hasDataDumper = 0 if($@);
  48. }
  49. sub
  50. readingsHistory_updateDevices($)
  51. {
  52. my ($hash) = @_;
  53. my %list;
  54. my @devices;
  55. my $def = $hash->{DEF};
  56. $def = "" if( !$def );
  57. my @params = split(" ", $def);
  58. while (@params) {
  59. my $param = shift(@params);
  60. my @device = split(":", $param);
  61. if($device[0] =~ m/(.*)=(.*)/) {
  62. my ($lattr,$re) = ($1, $2);
  63. foreach my $d (sort keys %defs) {
  64. next if( IsIgnored($d) );
  65. next if( !defined($defs{$d}{$lattr}) );
  66. next if( $defs{$d}{$lattr} !~ m/^$re$/);
  67. $list{$d} = 1;
  68. push @devices, [$d,$device[1]];
  69. }
  70. } elsif($device[0] =~ m/(.*)&(.*)/) {
  71. my ($lattr,$re) = ($1, $2);
  72. foreach my $d (sort keys %attr) {
  73. next if( IsIgnored($d) );
  74. next if( !defined($attr{$d}{$lattr}) );
  75. next if( $attr{$d}{$lattr} !~ m/^$re$/);
  76. $list{$d} = 1;
  77. push @devices, [$d,$device[1]];
  78. }
  79. } elsif( defined($defs{$device[0]}) ) {
  80. $list{$device[0]} = 1;
  81. push @devices, [@device];
  82. } else {
  83. foreach my $d (sort keys %defs) {
  84. next if( IsIgnored($d) );
  85. eval { $d =~ m/^$device[0]$/ };
  86. if( $@ ) {
  87. Log3 $hash->{NAME}, 3, $hash->{NAME} .": ". $device[0] .": ". $@;
  88. push @devices, ["<<ERROR>>"];
  89. last;
  90. }
  91. next if( $d !~ m/^$device[0]$/);
  92. $list{$d} = 1;
  93. push @devices, [$d,$device[1]];
  94. }
  95. }
  96. }
  97. $hash->{CONTENT} = \%list;
  98. $hash->{DEVICES} = \@devices;
  99. delete( $hash->{NOTIFYDEV} );
  100. if( scalar keys %list == 0 ) {
  101. $hash->{NOTIFYDEV} = "global";
  102. }
  103. $hash->{fhem}->{last_update} = gettimeofday();
  104. }
  105. sub
  106. readingsHistory_Define($$)
  107. {
  108. my ($hash, $def) = @_;
  109. my @args = split("[ \t]+", $def);
  110. return "Usage: define <name> readingsHistory [<device>[:<readings>]]+" if(@args < 2);
  111. my $name = shift(@args);
  112. my $type = shift(@args);
  113. $hash->{HAS_DataDumper} = $readingsHistory_hasDataDumper;
  114. $hash->{fhem}{history} = [] if( !defined($hash->{fhem}{history}) );
  115. readingsHistory_updateDevices($hash);
  116. $hash->{STATE} = 'Initialized';
  117. return undef;
  118. }
  119. sub
  120. readingsHistory_lookup($$$$$$$$)
  121. {
  122. my($mapping,$name,$alias,$reading,$value,$room,$group,$default) = @_;
  123. if( $mapping ) {
  124. if( !ref($mapping) && $mapping =~ m/^{.*}$/) {
  125. my $DEVICE = $name;
  126. my $READING = $reading;
  127. my $VALUE = $value;
  128. my $m = eval $mapping;
  129. if( $@ ) {
  130. Log 2, $@ if( $@ );
  131. } else {
  132. $mapping = $m;
  133. }
  134. }
  135. if( ref($mapping) eq 'HASH' ) {
  136. $default = $mapping->{$name} if( defined($mapping->{$name}) );
  137. $default = $mapping->{$reading} if( defined($mapping->{$reading}) );
  138. $default = $mapping->{$name.".".$reading} if( defined($mapping->{$name.".".$reading}) );
  139. $default = $mapping->{$reading.".".$value} if( defined($mapping->{$reading.".".$value}) );
  140. } else {
  141. $default = $mapping;
  142. }
  143. if( !ref($default) && $default =~ m/^{.*}$/) {
  144. my $DEVICE = $name;
  145. my $READING = $reading;
  146. my $VALUE = $value;
  147. $default = eval $default;
  148. $default = "" if( $@ );
  149. Log 2, $@ if( $@ );
  150. }
  151. return $default if( !defined($default) );
  152. $default =~ s/\%ALIAS/$alias/g;
  153. $default =~ s/\%DEVICE/$name/g;
  154. $default =~ s/\%READING/$reading/g;
  155. $default =~ s/\%VALUE/$value/g;
  156. $default =~ s/\%ROOM/$room/g;
  157. $default =~ s/\%GROUP/$group/g;
  158. $default =~ s/\$ALIAS/$alias/g;
  159. $default =~ s/\$DEVICE/$name/g;
  160. $default =~ s/\$READING/$reading/g;
  161. $default =~ s/\$VALUE/$value/g;
  162. $default =~ s/\$ROOM/$room/g;
  163. $default =~ s/\$GROUP/$group/g;
  164. }
  165. return $default;
  166. }
  167. sub
  168. readingsHistory_lookup2($$$$)
  169. {
  170. my($lookup,$name,$reading,$value) = @_;
  171. return $lookup if( !$lookup );
  172. if( !ref($lookup) && $lookup =~ m/^{.*}$/) {
  173. my $DEVICE = $name;
  174. my $READING = $reading;
  175. my $VALUE = $value;
  176. my $l = eval $lookup;
  177. if( $@ ) {
  178. Log 2, $@ if( $@ );
  179. } else {
  180. $lookup = $l;
  181. }
  182. }
  183. if( ref($lookup) eq 'HASH' ) {
  184. my $vf = "";
  185. $vf = $lookup->{$reading} if( defined($reading) && exists($lookup->{$reading}) );
  186. $vf = $lookup->{$name.".".$reading} if( defined($reading) && exists($lookup->{$name.".".$reading}) );
  187. $vf = $lookup->{$reading.".".$value} if( defined($value) && exists($lookup->{$reading.".".$value}) );
  188. $lookup = $vf;
  189. }
  190. return $lookup if( !defined($lookup) );
  191. if( !ref($lookup) && $lookup =~ m/^{.*}$/) {
  192. my $DEVICE = $name;
  193. my $READING = $reading;
  194. my $VALUE = $value;
  195. $lookup = eval $lookup;
  196. $lookup = "" if( $@ );
  197. Log 2, $@ if( $@ );
  198. }
  199. return $lookup if( !defined($lookup) );
  200. $lookup =~ s/\%DEVICE/$name/g;
  201. $lookup =~ s/\%READING/$reading/g;
  202. $lookup =~ s/\%VALUE/$value/g;
  203. $lookup =~ s/\$DEVICE/$name/g;
  204. $lookup =~ s/\$READING/$reading/g;
  205. $lookup =~ s/\$VALUE/$value/g;
  206. return $lookup;
  207. }
  208. sub readingsHistory_Undefine($$)
  209. {
  210. my ($hash,$arg) = @_;
  211. return undef;
  212. }
  213. sub
  214. readingsHistory_2html($)
  215. {
  216. my($hash) = @_;
  217. $hash = $defs{$hash} if( ref($hash) ne 'HASH' );
  218. return undef if( !$hash );
  219. if( $hash->{DEF} && $hash->{DEF} =~ m/=/ ) {
  220. if( !$hash->{fhem}->{last_update}
  221. || gettimeofday() - $hash->{fhem}->{last_update} > 600 ) {
  222. readingsHistory_updateDevices($hash);
  223. }
  224. }
  225. my $d = $hash->{NAME};
  226. my $show_heading = !AttrVal( $d, "noheading", "0" );
  227. my $show_links = !AttrVal( $d, "nolinks", "0" );
  228. $show_links = 0 if($FW_hiddenroom{detail});
  229. my $disable = AttrVal($d,"disable", 0);
  230. if( AttrVal($d,"disable", 0) > 2 ) {
  231. return undef;
  232. } elsif( AttrVal($d,"disable", 0) > 1 ) {
  233. my $ret;
  234. $ret .= "<table>";
  235. my $txt = AttrVal($d, "alias", $d);
  236. $txt = "<a href=\"$FW_ME$FW_subdir?detail=$d\">$txt</a>" if( $show_links );
  237. $ret .= "<tr><td><div class=\"devType\">$txt</a></div></td></tr>" if( $show_heading );
  238. $ret .= "<tr><td><table class=\"block wide\">";
  239. #$ret .= "<div class=\"devType\"><a style=\"color:#ff8888\" href=\"$FW_ME$FW_subdir?detail=$d\">readingsHistory $txt is disabled.</a></div>";
  240. $ret .= "<td><div style=\"color:#ff8888;text-align:center\">disabled</div></td>";
  241. $ret .= "</table></td></tr>";
  242. $ret .= "</table>";
  243. return $ret;
  244. }
  245. my $show_time = !AttrVal( $d, "notime", "0" );
  246. my $style = AttrVal( $d, "style", "" );
  247. my $devices = $hash->{DEVICES};
  248. my $row = 1;
  249. my $ret;
  250. $ret .= "<table>";
  251. my $txt = AttrVal($d, "alias", $d);
  252. $txt = "<a href=\"$FW_ME$FW_subdir?detail=$d\">$txt</a>" if( $show_links );
  253. $ret .= "<tr><td><div class=\"devType\">$txt</a></div></td></tr>" if( $show_heading );
  254. $ret .= "<tr><td><table $style class=\"block wide\">";
  255. $ret .= "<tr><td colspan=\"99\"><div style=\"color:#ff8888;text-align:center\">updates disabled</div></tr>" if( $disable > 0 );
  256. my $rows = AttrVal($d,"rows", 5 );
  257. $rows = 1 if( $rows < 1 );
  258. my $lines = "";
  259. for (my $i = 0; $i < $rows; $i++) {
  260. my $line = $hash->{fhem}{history}[$i];
  261. if( ref($line) eq 'ARRAY' ) {
  262. $lines .= $line->[3] if( $line );
  263. } else {
  264. $lines .= $line if( $line );
  265. }
  266. $lines .= "<br>";
  267. }
  268. $ret .= sprintf("<tr class=\"%s\">", ($row&1)?"odd":"even");
  269. $row++;
  270. $ret .= "<td><div id=\"$d-history\" rows=\"$rows\">$lines</div></td>";
  271. $ret .= sprintf("<tr class=\"%s\">", ($row&1)?"odd":"even");
  272. $ret .= "<td colspan=\"99\"><div style=\"color:#ff8888;text-align:center\">updates disabled</div></td></tr>" if( $disable > 0 );
  273. $ret .= "</table></td></tr>";
  274. $ret .= "</table>";
  275. return $ret;
  276. }
  277. sub
  278. readingsHistory_detailFn()
  279. {
  280. my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
  281. my $hash = $defs{$d};
  282. $hash->{mayBeVisible} = 1;
  283. return readingsHistory_2html($d);
  284. }
  285. sub
  286. readingsHistory_makeTimestamp($$) {
  287. my ($t, $timestampFormat) = @_;
  288. my @lt = localtime($t);
  289. return strftime( $timestampFormat, @lt ) if( $timestampFormat );
  290. return sprintf("%02d:%02d:%02d", $lt[2], $lt[1], $lt[0] );
  291. }
  292. sub
  293. readingsHistory_Notify($$)
  294. {
  295. my ($hash,$dev) = @_;
  296. my $name = $hash->{NAME};
  297. if( grep(m/^INITIALIZED$/, @{$dev->{CHANGED}}) ) {
  298. readingsHistory_updateDevices($hash);
  299. readingsHistory_Load($hash);
  300. return undef;
  301. } elsif( grep(m/^REREADCFG$/, @{$dev->{CHANGED}}) ) {
  302. readingsHistory_updateDevices($hash);
  303. readingsHistory_Load($hash);
  304. return undef;
  305. } elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
  306. readingsHistory_Save();
  307. }
  308. return if( AttrVal($name,"disable", 0) > 0 );
  309. return if($dev->{TYPE} eq $hash->{TYPE});
  310. #return if($dev->{NAME} eq $name);
  311. my $devices = $hash->{DEVICES};
  312. my $max = int(@{$dev->{CHANGED}});
  313. for (my $i = 0; $i < $max; $i++) {
  314. my $s = $dev->{CHANGED}[$i];
  315. $s = "" if(!defined($s));
  316. if( $dev->{NAME} eq "global" && $s =~ m/^RENAMED ([^ ]*) ([^ ]*)$/) {
  317. my ($old, $new) = ($1, $2);
  318. if( defined($hash->{CONTENT}{$old}) ) {
  319. $hash->{DEF} =~ s/(^|\s+)$old((:\S+)?\s*)/$1$new$2/g;
  320. }
  321. readingsHistory_updateDevices($hash);
  322. } elsif( $dev->{NAME} eq "global" && $s =~ m/^DELETED ([^ ]*)$/) {
  323. my ($name) = ($1);
  324. if( defined($hash->{CONTENT}{$name}) ) {
  325. $hash->{DEF} =~ s/(^|\s+)$name((:\S+)?\s*)/ /g;
  326. $hash->{DEF} =~ s/^ //;
  327. $hash->{DEF} =~ s/ $//;
  328. }
  329. readingsHistory_updateDevices($hash);
  330. } elsif( $dev->{NAME} eq "global" && $s =~ m/^DEFINED ([^ ]*)$/) {
  331. readingsHistory_updateDevices($hash);
  332. } else {
  333. next if(AttrVal($name,"disable", undef));
  334. next if (!$hash->{CONTENT}->{$dev->{NAME}});
  335. if( $hash->{alwaysTrigger} ) {
  336. $hash->{mayBeVisible} = 1;
  337. } elsif( !defined($hash->{mayBeVisible}) ) {
  338. Log3 $name, 5, "$name: not on any display, don't trigger";
  339. } else {
  340. if( defined($FW_visibleDeviceHash{$name}) ) {
  341. } else {
  342. Log3 $name, 5, "$name: no longer visible, don't trigger";
  343. delete( $hash->{mayBeVisible} );
  344. }
  345. }
  346. my ($reading,$value) = split(": ", $s, 2);
  347. $reading = "" if( !defined($reading) );
  348. next if( $reading =~ m/^\./);
  349. $value = "" if( !defined($value) );
  350. my $show_state = 1;
  351. if( $value eq "" ) {
  352. next if AttrVal( $name, "nostate", "0" );
  353. $reading = "state";
  354. $value = $s;
  355. }
  356. my $no_html = AttrVal( $name, "nohtml", "0" );
  357. my $spacing = "&nbsp;&nbsp;";
  358. $spacing = " " if $no_html;
  359. my $mapping = AttrVal( $name, "mapping", "");
  360. $mapping = eval $mapping if( $mapping =~ m/^{.*}$/ );
  361. #$mapping = undef if( ref($mapping) ne 'HASH' );
  362. my $timestampFormat = AttrVal( $name, "timestampFormat", undef);
  363. my $value_format = AttrVal( $name, "valueFormat", "" );
  364. if( $value_format =~ m/^{.*}$/ ) {
  365. my $vf = eval $value_format;
  366. $value_format = $vf if( $vf );
  367. }
  368. foreach my $device (@{$devices}) {
  369. my $item = 0;
  370. my $h = $defs{@{$device}[0]};
  371. next if( !$h );
  372. next if( $dev->{NAME} ne $h->{NAME} );
  373. my $n = $h->{NAME};
  374. my $regex = @{$device}[1];
  375. my @list = (undef);
  376. @list = split(",",$regex) if( $regex );
  377. foreach my $regex (@list) {
  378. next if( $reading eq "state" && !$show_state && (!defined($regex) || $regex ne "state") );
  379. next if( defined($regex) && $reading !~ m/^$regex$/);
  380. my $msg = $s;
  381. my $value_format = readingsHistory_lookup2($value_format,$n,$reading,$value);
  382. next if( !defined($value_format) );
  383. if( $value_format =~ m/%/ ) {
  384. $msg = sprintf( $value_format, $value );
  385. } elsif( $value_format ) {
  386. $msg = $value_format;
  387. }
  388. my $t = time;
  389. my $tm = readingsHistory_makeTimestamp( $t, $timestampFormat );
  390. my $show_links = !AttrVal( $name, "nolinks", "0" );
  391. $show_links = 0 if($FW_hiddenroom{detail});
  392. $show_links = 0 if(!defined($FW_ME));
  393. $show_links = 0 if($no_html);
  394. my $a = AttrVal($n, "alias", $n);
  395. my $m = $a;
  396. if( $mapping ) {
  397. my $room = AttrVal($n, "room", "");
  398. my $group = AttrVal($n, "group", "");
  399. $m = readingsHistory_lookup($mapping,$n,$a,$reading,$value,$room,$group,$m);
  400. }
  401. if( $show_links ) {
  402. $msg = "$tm$spacing<a href=\"$FW_ME$FW_subdir?detail=$n\">$m</a> $msg";
  403. } else {
  404. $msg = "$tm$spacing$m $msg";
  405. }
  406. my $entry = [$t, $n, $s,$msg];
  407. #my $entry = [$t, $n, "$reading: $value", $msg];
  408. while( @{$hash->{fhem}{history}} >= AttrVal($name,"rows", 5 ) ) {
  409. pop @{$hash->{fhem}{history}};
  410. }
  411. unshift( @{$hash->{fhem}{history}}, $entry );
  412. DoTrigger( "$name", "history: <html>$msg</html>" ) if( $hash->{mayBeVisible} );
  413. }
  414. }
  415. }
  416. }
  417. return undef;
  418. }
  419. sub
  420. readingsHistory_StatefileName()
  421. {
  422. my $statefile = $attr{global}{statefile};
  423. $statefile = substr $statefile,0,rindex($statefile,'/')+1;
  424. #return $statefile ."readingsHistorys.save" if( $readingsHistory_hasJSON );
  425. return $statefile ."readingsHistorys.dd.save" if( $readingsHistory_hasDataDumper );
  426. }
  427. my $readingsHistory_LastSaveTime="";
  428. sub
  429. readingsHistory_Save()
  430. {
  431. my $time_now = TimeNow();
  432. return if( $time_now eq $readingsHistory_LastSaveTime);
  433. $readingsHistory_LastSaveTime = $time_now;
  434. return "No statefile specified" if(!$attr{global}{statefile});
  435. my $statefile = readingsHistory_StatefileName();
  436. my $hash;
  437. for my $d (keys %defs) {
  438. next if($defs{$d}{TYPE} ne "readingsHistory");
  439. next if( !defined($defs{$d}{fhem}{history}) );
  440. $hash->{$d} = $defs{$d}{fhem}{history};
  441. }
  442. if(open(FH, ">$statefile")) {
  443. my $t = localtime;
  444. print FH "#$t\n";
  445. if( $readingsHistory_hasJSON ) {
  446. print FH encode_json($hash) if( defined($hash) );
  447. } elsif( $readingsHistory_hasDataDumper ) {
  448. my $dumper = Data::Dumper->new([]);
  449. $dumper->Terse(1);
  450. $dumper->Values([$hash]);
  451. print FH $dumper->Dump;
  452. }
  453. close(FH);
  454. } else {
  455. my $msg = "readingsHistory_Save: Cannot open $statefile: $!";
  456. Log3 undef, 1, $msg;
  457. }
  458. return undef;
  459. }
  460. sub
  461. readingsHistory_Load($)
  462. {
  463. my ($hash) = @_;
  464. return "No statefile specified" if(!$attr{global}{statefile});
  465. my $statefile = readingsHistory_StatefileName();
  466. if(open(FH, "<$statefile")) {
  467. my $encoded;
  468. while (my $line = <FH>) {
  469. chomp $line;
  470. next if($line =~ m/^#.*$/);
  471. $encoded .= $line;
  472. }
  473. close(FH);
  474. return if( !defined($encoded) );
  475. my $decoded;
  476. if( $readingsHistory_hasJSON ) {
  477. $decoded = decode_json( $encoded );
  478. } elsif( $readingsHistory_hasDataDumper ) {
  479. $decoded = eval $encoded;
  480. }
  481. $hash->{fhem}{history} = $decoded->{$hash->{NAME}} if( defined($decoded->{$hash->{NAME}}) );
  482. } else {
  483. my $msg = "readingsHistory_Load: Cannot open $statefile: $!";
  484. Log3 undef, 1, $msg;
  485. }
  486. return undef;
  487. }
  488. sub
  489. readingsHistory_Set($@)
  490. {
  491. my ($hash, $name, $cmd, $param, @a) = @_;
  492. my $list = "add clear:noArg";
  493. my $no_html = AttrVal( $name, "nohtml", "0" );
  494. my $spacing = "&nbsp;&nbsp;";
  495. $spacing = " " if $no_html;
  496. if( $cmd eq "clear" ) {
  497. $hash->{fhem}{history} = [];
  498. my $t = time;
  499. my $tm = readingsHistory_makeTimestamp( time, AttrVal( $name, "timestampFormat", undef) );
  500. my $msg = "$tm$spacing--clear--";
  501. my $entry = [$t,"", $cmd, $msg];
  502. unshift( @{$hash->{fhem}{history}}, $entry );
  503. DoTrigger( "$name", "clear: $msg" ) if( $hash->{mayBeVisible} );
  504. return undef;
  505. } elsif ( $cmd eq "add" ) {
  506. my $t = time;
  507. my $tm = readingsHistory_makeTimestamp( time, AttrVal( $name, "timestampFormat", undef) );
  508. my $msg = "$tm$spacing$param ". join( " ", @a );
  509. my $entry = [$t,"", "$param ". join( " ", @a ),$msg];
  510. while( @{$hash->{fhem}{history}} >= AttrVal($name,"rows", 5 ) ) {
  511. pop @{$hash->{fhem}{history}};
  512. }
  513. unshift( @{$hash->{fhem}{history}}, $entry );
  514. DoTrigger( "$name", "history: <html>$msg</html>" ) if( $hash->{mayBeVisible} );
  515. return undef;
  516. }
  517. return "Unknown argument $cmd, choose one of $list";
  518. }
  519. sub
  520. readingsHistory_Get($@)
  521. {
  522. my ($hash, @a) = @_;
  523. my $list = "history:noArg html:noArg";
  524. my $name = $a[0];
  525. return "$name: get needs at least one parameter" if(@a < 2);
  526. my $cmd= $a[1];
  527. my $ret = "";
  528. if( $cmd eq "html" ) {
  529. return readingsHistory_2html($hash);
  530. } elsif( $cmd eq "history" ) {
  531. my $rows = AttrVal($name,"rows", 5 );
  532. $rows = 1 if( $rows < 1 );
  533. my $timestampFormat = AttrVal( $name, "timestampFormat", undef);
  534. for (my $i = 0; $i < $rows; $i++) {
  535. my $line = $hash->{fhem}{history}[$i];
  536. if( ref($line) eq 'ARRAY' ) {
  537. my $tm = readingsHistory_makeTimestamp( $line->[0], $timestampFormat );
  538. $ret .= "$tm\t$line->[1]\t$line->[2]\t$line->[3]" if( $line );
  539. } else {
  540. $ret .= $line if( $line );
  541. }
  542. $ret .= "\n";
  543. }
  544. return $ret;
  545. }
  546. return "Unknown argument $cmd, choose one of $list";
  547. }
  548. sub
  549. readingsHistory_Attr($$$)
  550. {
  551. my ($cmd, $name, $attrName, $attrVal) = @_;
  552. my $orig = $attrVal;
  553. if( $attrName eq "alwaysTrigger" ) {
  554. my $hash = $defs{$name};
  555. $attrVal = 1 if($attrVal);
  556. if( $cmd eq "set" ) {
  557. $hash->{alwaysTrigger} = $attrVal;
  558. delete( $hash->{helper}->{myDisplay} ) if( $hash->{alwaysTrigger} );
  559. } else {
  560. delete $hash->{alwaysTrigger};
  561. }
  562. }
  563. if( $cmd eq "set" ) {
  564. if( $orig ne $attrVal ) {
  565. $attr{$name}{$attrName} = $attrVal;
  566. return $attrName ." set to ". $attrVal;
  567. }
  568. }
  569. return;
  570. }
  571. 1;
  572. =pod
  573. =item helper
  574. =item summary display a history of readings from on or more devices
  575. =item summary_DE Anzeige der Historie von Readings eines oder mehrerer Ger&auml;te
  576. =begin html
  577. <a name="readingsHistory"></a>
  578. <h3>readingsHistory</h3>
  579. <ul>
  580. Displays a history of readings from on or more devices.
  581. <br><br>
  582. <a name="readingsHistory_Define"></a>
  583. <b>Define</b>
  584. <ul>
  585. <code>define &lt;name&gt; readingsHistory [&lt;device&gt;[:regex] [&lt;device-2&gt;[:regex-2] ... [&lt;device-n&gt;[:regex-n]]]]</code><br>
  586. <br>
  587. Notes:
  588. <ul>
  589. <li>&lt;device&gt; can be of the form INTERNAL=VALUE where INTERNAL is the name of an internal value and VALUE is a regex.</li>
  590. <li>If regex is a comma separatet list it will be used as an enumeration of allowed readings.</li>
  591. <li>if no device/reading argument is given only lines with 'set <device> add ...' are displayed.</li>
  592. </ul><br>
  593. Examples:
  594. <ul>
  595. </ul>
  596. </ul><br>
  597. <a name="readingsHistory_Set"></a>
  598. <b>Set</b>
  599. <ul>
  600. <li>add ...<br>
  601. directly add text as new line to history.</li>
  602. <li>clear<br>
  603. clear the history.</li>
  604. </ul><br>
  605. <a name="readingsHistory_Get"></a>
  606. <b>Get</b>
  607. <ul>
  608. <li>history<br>
  609. list history</li>
  610. </ul><br>
  611. <a name="readingsHistory_Attr"></a>
  612. <b>Attributes</b>
  613. <ul>
  614. <li>alwaysTrigger<br>
  615. 1 -> alwaysTrigger update events. even if not visible.</li>
  616. <li>disable<br>
  617. 1 -> disable notify processing and longpoll updates. Notice: this also disables rename and delete handling.<br>
  618. 2 -> also disable html table creation<br>
  619. 3 -> also disable html creation completely</li>
  620. <li>noheading<br>
  621. If set to 1 the readings table will have no heading.</li>
  622. <li>nolinks<br>
  623. Disables the html links from the heading and the reading names.</li>
  624. <li>notime<br>
  625. If set to 1 the reading timestamp is not displayed.</li>
  626. <li>mapping<br>
  627. Can be a simple string or a perl expression enclosed in {} that returns a hash that maps device names
  628. to the displayed name. The keys can be either the name of the reading or &lt;device&gt;.&lt;reading&gt;.
  629. %DEVICE, %ALIAS, %ROOM and %GROUP are replaced by the device name, device alias, room attribute and
  630. group attribute respectively. You can also prefix these keywords with $ instead of %.
  631. </li>
  632. <li>style<br>
  633. Specify an HTML style for the readings table, e.g.:<br>
  634. <code>attr history style style="font-size:20px"</code></li>
  635. <li>timestampFormat<br>
  636. POSIX strftime compatible string for used as the timestamp for each line.
  637. </li>
  638. <li>valueFormat<br>
  639. Specify an sprintf style format string used to display the reading values. If the format string is undef
  640. this reading will be skipped. Can be given as a string, a perl expression returning a hash or a perl
  641. expression returning a string, e.g.:<br>
  642. <code>attr history valueFormat %.1f &deg;C</code><br>
  643. <code>attr history valueFormat { temperature => "%.1f &deg;C", humidity => "%.1f %" }</code><br>
  644. <code>attr history valueFormat { ($READING eq 'temperature')?"%.1f &deg;C":undef }</code></li>
  645. <li>rows<br>
  646. Number of history rows to show.</li>
  647. </ul>
  648. </ul>
  649. =end html
  650. =cut