97_TrashCal.pm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. ##############################################
  2. # $Id: 97_TrashCal.pm 12850 2016-12-20 09:39:58Z Tobias $
  3. #
  4. # 97_TrashCal.pm
  5. #
  6. # written by Tobias Faust 2013-10-23
  7. # e-mail: tobias dot faust at online dot de
  8. #
  9. ##############################################
  10. #
  11. ##############################################
  12. #
  13. # Log-Levels
  14. # 0 - server start/stop
  15. # 1 - error messages or unknown packets
  16. # 2 - major events/alarms.
  17. # 3 - commands sent out will be logged.
  18. # 4 - you'll see whats received by the different devices.
  19. # 5 - debugging.
  20. ##############################################
  21. ###############################################
  22. # parser for the Trash
  23. package MyTrashCalParser;
  24. use base qw(HTML::Parser);
  25. our %dates = ();
  26. my $lookupTag = "td";
  27. my $curTag = "";
  28. my $category = "--";
  29. # here HTML::text/start/end are overridden
  30. sub text
  31. {
  32. my ( $self, $text ) = @_;
  33. if ( $curTag eq $lookupTag )
  34. {
  35. #print "MyTrashCalParser_Text: original: $text \n";
  36. $text =~ s/[^0-9]*([0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4}).*/$1/;
  37. #print "MyTrashCalParser_Text: Modifiziert: $text \n";
  38. if($category ne "" && $text =~ m/([0-9]{1,2}\.[0-9]{1,2}\.[0-9]{4})/) {
  39. push(@{$dates{$category}}, $text);
  40. #print "MyTrashCalParser_Text: Values of $category: ". keys(%{$data{$category}}) ."\n";
  41. }
  42. } elsif ($curTag eq "h3") {
  43. $category = $text;
  44. #print "MyTrashCalParser_Text: neue Kategorie: $text \n";
  45. }
  46. }
  47. sub start
  48. {
  49. my ( $self, $tagname, $attr, $attrseq, $origtext ) = @_;
  50. $curTag = $tagname;
  51. #print "MyTrashCalParser_Start: $tagname, $attr, $attrseq, $origtext";
  52. }
  53. sub end
  54. {
  55. $curTag = "";
  56. #print "MyTrashCalParser_End: ----- done -----";
  57. }
  58. ##############################################
  59. package main;
  60. use strict;
  61. use feature qw/say switch/;
  62. use warnings;
  63. use Data::Dumper;
  64. use LWP::UserAgent;
  65. use HTTP::Request;
  66. require 'Blocking.pm';
  67. require 'HttpUtils.pm';
  68. use vars qw($readingFnAttributes);
  69. # use vars qw(%attr);
  70. use vars qw(%defs);
  71. my $MODUL = "TrashCal";
  72. ###################################
  73. sub TrashCal_Initialize($)
  74. {
  75. my ($hash) = @_;
  76. $hash->{DefFn} = "TrashCal_Define";
  77. $hash->{UndefFn} = "TrashCal_Undef";
  78. $hash->{AttrList} = " TrashCal_Link".
  79. " TrashCal_Interval".
  80. " disable:0,1".
  81. " ".$readingFnAttributes;
  82. }
  83. ###################################
  84. sub TrashCal_Define($$)
  85. {
  86. my ( $hash, $def ) = @_;
  87. my $name = $hash->{NAME};
  88. my @a = split( "[ \t][ \t]*", $def );
  89. my $type = $a[2];
  90. if ( int(@a) < 3 )
  91. {
  92. return "Wrong syntax: use define <name> TrashCal <type>";
  93. }
  94. my $nt = time;
  95. $nt += 20; # aquire in 20sec
  96. my @lt = localtime($nt);
  97. my $ntm = sprintf("%02d.%02d.%04d %02d:%02d:%02d", $lt[3], ($lt[4]+1), ($lt[5]+1900), $lt[2], $lt[1], $lt[0]);
  98. $hash->{TriggerTime_FMT} = $ntm;
  99. $hash->{TriggerTime} = $nt;
  100. InternalTimer( $nt, "TrashCal_Timer", $hash, 0 );
  101. Log3 $hash, 4, "TrashCal_Define: InternalTimer auf in 20sek gesetzt: $ntm";
  102. return undef;
  103. }
  104. #####################################
  105. sub TrashCal_Undef($$)
  106. {
  107. my ( $hash, $arg ) = @_;
  108. RemoveInternalTimer( $hash );
  109. BlockingKill( $hash->{helper}{RUNNING_PID} ) if ( defined( $hash->{helper}{RUNNING_PID} ) );
  110. Log3 $hash, 4, "TrashCal_Undef: --- done ---";
  111. return undef;
  112. }
  113. #####################################
  114. sub TrashCal_Timer(@)
  115. {
  116. my ($hash) = @_;
  117. my $me = $hash->{NAME};
  118. Log3 $hash, 4, "TrashCal_Timer: GrTimer aufgerufen.....";
  119. return unless (defined($hash->{NAME}));
  120. Log3 $hash, 4, "TrashCal_Timer: --- started ---";
  121. $hash->{helper}{TimerGRInterval} = AttrVal( $me, "TrashCal_Interval", 3600 );
  122. TrashCal_Start($hash) if(!IsDisabled($me));
  123. # setup timer
  124. RemoveInternalTimer( $hash );
  125. my $nt = time;
  126. $nt += $hash->{helper}{TimerGRInterval};
  127. my @lt = localtime($nt);
  128. my $ntm = sprintf("%02d.%02d.%04d %02d:%02d:%02d", $lt[3], ($lt[4]+1), ($lt[5]+1900), $lt[2], $lt[1], $lt[0]);
  129. $hash->{TriggerTime_FMT} = $ntm;
  130. $hash->{TriggerTime} = $nt;
  131. InternalTimer($nt, "TrashCal_Timer", $hash, 0 );
  132. Log3 $hash, 4, "TrashCal_Timer: --- done ---";
  133. }
  134. #####################################
  135. # acquires the html page
  136. sub TrashCal_HtmlAcquire($)
  137. {
  138. my ($hash) = @_;
  139. my $name = $hash->{NAME};
  140. return unless (defined($hash->{NAME}));
  141. my $URL = AttrVal( $name, 'TrashCal_Link', "" );
  142. # abbrechen, wenn wichtig parameter nicht definiert sind
  143. return "" if ( !defined($URL) );
  144. return "" if ( $URL eq "" );
  145. my $err_log = "";
  146. my $agent = LWP::UserAgent->new( env_proxy => 1, keep_alive => 1, timeout => 3 );
  147. my $header = HTTP::Request->new( GET => $URL );
  148. my $request = HTTP::Request->new( 'GET', $URL, $header );
  149. my $response = $agent->request($request);
  150. $err_log = "Can't get $URL -- " . $response->status_line
  151. unless $response->is_success;
  152. if ( $err_log ne "" )
  153. {
  154. Log3 $hash, 1, "TrashCal_HtmlAcquire: Error: $err_log";
  155. return "";
  156. }
  157. return $response->content;
  158. }
  159. #####################################
  160. sub TrashCal_Start($)
  161. {
  162. my ($hash) = @_;
  163. return unless (defined($hash->{NAME}));
  164. my $me = $hash->{NAME};
  165. if (AttrVal( $me, 'TrashCal_Link', "" ) eq "") {
  166. Log3 $hash, 3, "$me: Kein Link im Attribut 'TrashCal_Link' angebeben, breche Ausführung ab.";
  167. return;
  168. }
  169. while (1)
  170. {
  171. Log3 $hash, 4, "TrashCal_Start: --- started ---";
  172. $hash->{helper}{RUNNING_PID} =
  173. BlockingCall(
  174. "TrashCal_Run", # callback worker task
  175. $me, # name of the device
  176. "TrashCal_Done", # callback result method
  177. 50, # timeout seconds
  178. "TrashCal_Aborted", # callback for abortion
  179. $hash ); # parameter for abortion
  180. last;
  181. }
  182. Log3 $hash, 4, "TrashCal_Start: --- done ---";
  183. }
  184. #####################################
  185. sub TrashCal_Run($) {
  186. my ($string) = @_;
  187. my ( $me, $server ) = split( "\\|", $string );
  188. my $ptext = $me ."+";
  189. return unless ( defined($me) );
  190. my $hash = $defs{$me};
  191. return unless (defined($hash->{NAME}));
  192. Log3 $hash, 4, "TrashCal_Run: --- started ---";
  193. while (1)
  194. {
  195. # acquire the html-page
  196. my $response = TrashCal_HtmlAcquire($hash);
  197. last if ($response eq "");
  198. my $parser = MyTrashCalParser->new;
  199. %MyTrashCalParser::dates = ();
  200. # parsing the complete html-page-response, needs some time
  201. # only <td> tags will be regarded
  202. $parser->parse($response);
  203. Log3 $hash, 4, "TrashCal_Run: parsed terms:" . keys(%MyTrashCalParser::dates);
  204. foreach my $cat (keys(%MyTrashCalParser::dates) ) {
  205. $ptext .= $cat . '|';
  206. #Log3 $hash, 4, "TrashCal_Run: Values of $cat: ". keys(%{$MyTrashCalParser::data{$cat}});
  207. #foreach my $dat (keys(%{$MyTrashCalParser::dates{$cat}}) ) {
  208. # $ptext .= "|".$dat;
  209. #}
  210. $ptext .= join('|', @{$MyTrashCalParser::dates{$cat}});
  211. $ptext .= "+";
  212. }
  213. last;
  214. }
  215. Log3 $hash, 4, "TrashCal_Run: return value: $ptext";
  216. Log3 $hash, 4, "TrashCal_Run: --- done ---";
  217. return $ptext;
  218. #return "TrashCal|Altpapier|12.09.2014|07.11.2014|05.12.2014|18.07.2014|10.10.2014|15.08.2014";
  219. }
  220. #####################################
  221. # assyncronous callback by blocking
  222. sub TrashCal_Done($) {
  223. my ($string) = @_;
  224. Log3 undef, 4, "TrashCal_Done: --- begin ---";
  225. return unless ( defined($string) );
  226. # all term are separated by "#" , the first is the name of the instance
  227. my ( $me, @values ) = split("\\+", $string );
  228. my $hash = $defs{$me};
  229. return unless (defined($hash->{NAME}));
  230. # delete the marker for running process
  231. delete( $hash->{helper}{RUNNING_PID} );
  232. Log3 $hash, 4, "TrashCal_Done: --- started ---";
  233. #Log3 $hash, 5, "TrashCal_Done: values:".join(', ', @values);
  234. # Aktualisierung der Readings
  235. my $category;
  236. my $tstamp;
  237. my $NextEvent_Tstamp = time() + (28*86400); #hoher Initialwert
  238. my $NextEvent_Cat = "";
  239. my $NextEvent_Dat = "";
  240. my %hashValues = ();
  241. # Durch jede Kategorie
  242. for(my $i=0; $i<int(@values); $i++) {
  243. # show the values
  244. Log3 $hash, 5, "TrashCal_Done: Category with all values:".$values[$i];
  245. my @dates = split( "\\|", $values[$i] );
  246. $category = shift(@dates);
  247. for(my $j=0; $j<int(@dates); $j++) {
  248. if ( $dates[$j] =~ /([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{4})/i ) {
  249. #berechne dazu passenden Unix-Timestamp
  250. $tstamp = mktime(0, 0, 12, $1, ($2 -1), ($3 -1900), 0, 0, -1);
  251. if($tstamp < $NextEvent_Tstamp) {
  252. # das nächste/jüngste Event ermitteln
  253. $NextEvent_Dat = $dates[$j];
  254. $NextEvent_Cat = $category;
  255. $NextEvent_Tstamp = $tstamp;
  256. }
  257. if(!defined($hashValues{$category}{TSTAMP}) || (($tstamp > time()) && ($tstamp < $hashValues{$category}{TSTAMP}))) {
  258. # nur den zeitlich nächsten Wert in die Readings pro Categorie uebernehmen
  259. $hashValues{$category}{DATE} = $dates[$j];
  260. $hashValues{$category}{TSTAMP} = $tstamp;
  261. }
  262. }
  263. # show the values
  264. Log3 $hash, 5, "TrashCal_Done: category:$category - date: ".$dates[$j];
  265. }
  266. }
  267. readingsBeginUpdate($hash);
  268. foreach my $xxx ( sort keys %hashValues ) {
  269. #my $daysleft = ($hashValues{$xxx}{TSTAMP} - time()) / 60 / 24;
  270. readingsBulkUpdate( $hash, $xxx, $hashValues{$xxx}{DATE} );
  271. readingsBulkUpdate( $hash, $xxx."_Tstamp", $hashValues{$xxx}{TSTAMP} );
  272. #readingsBulkUpdate( $hash, $xxx."_DaysLeft", $daysleft);
  273. }
  274. readingsBulkUpdate( $hash, "NextEvent Category", $NextEvent_Cat );
  275. readingsBulkUpdate( $hash, "NextEvent Date", $NextEvent_Dat );
  276. readingsBulkUpdate( $hash, "NextEvent Tstamp", $NextEvent_Tstamp );
  277. readingsBulkUpdate( $hash, "state", $NextEvent_Cat .": ". $NextEvent_Dat );
  278. readingsEndUpdate( $hash, 1 );
  279. Log3 $hash, 4, "TrashCal_Done: --- done ---";
  280. }
  281. #####################################
  282. sub TrashCal_Aborted($)
  283. {
  284. my ($hash) = @_;
  285. delete( $hash->{helper}{RUNNING_PID} );
  286. Log3 $hash, 3, "TrashCal_Aborted: --- done ---";
  287. }
  288. #####################################
  289. 1;
  290. =pod
  291. =item helper
  292. =item summary fetches shared dates at an public webpage of waste disposal
  293. =item summary_DE holt auf einer Webseite bereitgestellte Abfalltermine ab
  294. =begin html
  295. <a name="TrashCal"></a>
  296. <h3>TrashCal</h3>
  297. <ul>
  298. Note: this module needs the HTTP::Request,HTML::Parser and LWP::UserAgent perl modules.
  299. <br>
  300. At this moment only city "Magdeburg" is supported at this site:<br>
  301. <i>http://sab.metageneric.de/app/sab_i_tp/index.php</i>
  302. <br><br>
  303. <a name="TrashCal define"></a>
  304. <b>Define</b>
  305. <ul>
  306. <code>define &lt;name&gt; TrashCal &lt;type&gt; </code>
  307. <br><br>
  308. Defines a new instanze of Trashcalendar. At this time the &lt;type&gt; is not used
  309. <br>
  310. Examples:
  311. <ul>
  312. <code>define MyTrashCal TrashCal Restabfall</code><br>
  313. </ul>
  314. </ul>
  315. <br>
  316. <a name="TrashCalset"></a>
  317. <b>Set</b>
  318. <ul>N/A</ul><br>
  319. <a name="TrashCalget"></a>
  320. <b>Get</b>
  321. <ul>N/A</ul><br>
  322. <a name="TrashCalattr"></a>
  323. <b>Attributes</b>
  324. <ul>
  325. <li>TrashCal_Link<br>
  326. setting up the URL to grab the Trashcalendar
  327. <br>Example:
  328. <ul>
  329. <code>http://sab.metageneric.de/app/sab_i_tp/index.php?r=getHausnummerInfo&strasse=Torplatz&hausnummer=1&stadtteil_id=1609&dsd_behaelter_value=b120_b240</code>
  330. <br>
  331. </ul>
  332. </li>
  333. <li><a href="#readingFnAttributes">readingFnAttributes</a></li><br>
  334. <li><a href="#disable">disable</a><br>
  335. If this attribute is activated, the module will be disabled.<br>
  336. Possible values: 0 => not disabled , 1 => disabled<br>
  337. Default Value is 0 (not disabled)<br><br>
  338. </li>
  339. <li><a href="#verbose">verbose</a><br>
  340. <b>4:</b> each major step will be logged<br>
  341. <b>5:</b> Additionally some minor steps will be logged
  342. </li>
  343. </ul>
  344. </ul>
  345. =end html
  346. =cut