98_Verkehrsinfo.pm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. # $Id: 98_Verkehrsinfo.pm 17616 2018-10-24 21:57:44Z martins $
  2. ############################################################################
  3. #
  4. # 98_Verkehrsinfo.pm
  5. #
  6. # Copyright (C) 2016 by Martin Schubert
  7. # e-mail: martin@dermschub.de
  8. #
  9. # This program is free software; you can redistribute it and/or modify
  10. # it under the terms of the GNU General Public License as published by
  11. # the Free Software Foundation; either version 2 of the License, or
  12. # (at your option) any later version.
  13. #
  14. # This program is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. # GNU General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  21. #
  22. ############################################################################
  23. ############################################################################
  24. #
  25. # Changelog:
  26. # 2018-10-21, v2.3
  27. # Feature: Attribut disable added
  28. #
  29. # 2017-01-16, v2.2
  30. # Feature: Quelle http://www.radiosaw.de/verkehrsmeldungen added
  31. #
  32. # 2016-12-26, v2.1
  33. # Bugfix: update state with readings update
  34. # CHANGE: remove requirement perl-module json
  35. # CHANGE: update commandref with link to readingFn-Attributen
  36. #
  37. # Changelog:
  38. # 2016-12-13, v2.0RC1
  39. # Bugfix: Bugfix Hessenschau Message
  40. #
  41. # 2016-11-17, v2.0
  42. # CHANGE: Module change to HttpNonBlocking
  43. # CHANGE: remove requirement perl-module json
  44. # CHANGE: update commandref
  45. #
  46. # 2016-11-10, v1.9
  47. # Bugfix: hessenschau.de, messages end with a point
  48. #
  49. # Changelog:
  50. # 2016-10-24, v1.8
  51. # Bugfix: hessenschau.de, messages end with a point
  52. #
  53. # 2016-10-23, v1.7RC2
  54. # CHANGE: Module change to NonBlocking
  55. # CHANGE: Code optimization
  56. # CHANGE: update commandref
  57. # Bugfix: hessenschau.de, Cant call method "as_trimmed_text" fixed
  58. #
  59. # 2016-08-17, v1.7RC1
  60. # Feature: New Reading for humanly readable message
  61. # Feature: New Attribut for Messageformat
  62. # Feature: New Attribut for Sorting added
  63. # CHANGE: State Value
  64. # CHANGE: LWP::Simple replace to HttpUtils
  65. # CHANGE: check if HTML::TreeBuilder::XPath is installed
  66. # CHANGE: update commandref
  67. #
  68. # 2016-08-17, v1.6
  69. # Bugfix: verkehrsinfo.de, Display of the zone has been corrected
  70. # Bugfix: Characterset has been corrected
  71. #
  72. # 2016-08-16, v1.5
  73. # Bugfix: verkehrsinfo.de, URL changed to the new address
  74. #
  75. # 2016-08-04, v1.4
  76. # Bugfix: hessenschau.de, Message Sperrung added
  77. #
  78. # 2016-07-29, v1.3
  79. # Bugfix: hessenschau.de, Message Warnung added
  80. #
  81. # 2016-07-11, v1.2
  82. # Feature: Quelle http://hessenschau.de/verkehr/index.html added
  83. #
  84. # 2016-07-03, v1.1
  85. # Bugfix: Check if a valid URL was passed
  86. # Feature: Include Filterattribut added, regex available, Pipe as delimiter
  87. # Feature: Exclude Filterattribut added, regex available, Pipe as delimiter
  88. #
  89. # 2016-06-29, v1.0
  90. # Initzial Version
  91. #
  92. ############################################################################
  93. package main;
  94. use strict;
  95. use warnings;
  96. #use Encode qw(decode encode);
  97. use HttpUtils;
  98. my $missingModul = "";
  99. eval "use HTML::TreeBuilder::XPath;1" or $missingModul .= "HTML::TreeBuilder::XPath ";
  100. my %Verkehrsinfo_gets = (
  101. "update" => "noArg",
  102. "info" => "noArg"
  103. );
  104. #my $encode = 'UTF-8';
  105. sub Verkehrsinfo_Initialize($) {
  106. my ($hash) = @_;
  107. $hash->{DefFn} = 'Verkehrsinfo_Define';
  108. $hash->{UndefFn} = 'Verkehrsinfo_Undef';
  109. $hash->{SetFn} = 'Verkehrsinfo_Set';
  110. $hash->{GetFn} = 'Verkehrsinfo_Get';
  111. $hash->{AttrFn} = 'Verkehrsinfo_Attr';
  112. $hash->{ReadFn} = 'Verkehrsinfo_Read';
  113. $hash->{AttrList} =
  114. "filter_exclude filter_include orderby "
  115. . "msg_format:road,head,both "
  116. . "disable:1,0 "
  117. . $readingFnAttributes;
  118. }
  119. sub Verkehrsinfo_Define($$) {
  120. my ($hash, $def) = @_;
  121. my @param = split('[ \t]+', $def);
  122. if(int(@param) < 4) {
  123. return "too few parameters: define <name> Verkehrsinfo <url> <interval>";
  124. }
  125. $hash->{name} = $param[0];
  126. $hash->{url} = $param[2];
  127. $hash->{Interval} = $param[3];
  128. # check Module is installed
  129. if ( $missingModul ) {
  130. my $returnmsg = "Cannot define $hash->{name} device. Perl modul $missingModul is missing.";
  131. Log3 $hash->{name}, 1, $returnmsg;
  132. return $returnmsg;
  133. }
  134. # check if the url is ok
  135. if ($hash->{url} !~ /verkehrsinfo\.de\/httpsmobil\/index\.php/ &&
  136. $hash->{url} !~ /hessenschau\.de\/verkehr\/index\.html/ &&
  137. $hash->{url} !~ /radiosaw/){
  138. my $returnmsg = "Diese URL wird nicht unterstützt. Bitte schauen Sie in die Modulbeschreibung.";
  139. Log3 $hash->{name}, 1, $returnmsg;
  140. return $returnmsg;
  141. }
  142. # get Zone name
  143. if ($hash->{url} =~ /verkehrsinfo.de/i){
  144. my $param = {
  145. url => "https://www.verkehrsinfo.de/httpsmobil/index.php?c=1&lat=&lon=",
  146. timeout => 5,
  147. hash => $hash,
  148. callback => \&Verkehrsinfo_HttpNbDefineZone
  149. };
  150. HttpUtils_NonblockingGet($param);
  151. }
  152. elsif ($hash->{url} =~ /hessenschau.de/i) {
  153. readingsSingleUpdate( $hash, "zone", "Hessen", 1 );
  154. }
  155. elsif ($hash->{url} =~ /radiosaw/i) {
  156. readingsSingleUpdate( $hash, "zone", "SAW", 1 );
  157. $hash->{url} = 'http://www.radiosaw.de/verkehrsmeldungen';
  158. }
  159. InternalTimer(gettimeofday()+4, "Verkehrsinfo_GetUpdate", $hash, 0);
  160. readingsSingleUpdate($hash, "state", 'initialized',1 );
  161. return undef;
  162. }
  163. # recieve zone from www http nonblocking
  164. sub Verkehrsinfo_HttpNbDefineZone($) {
  165. my ($param, $err, $content) = @_;
  166. my $hash = $param->{hash};
  167. my $name = $hash->{NAME};
  168. Log3 $hash, 4, "Verkehrsinfo: ($name) Verkehrsinfo_HttpNbDefineZone start";
  169. if($err ne "") {
  170. Log3 $name, 3, "error while requesting ".$param->{url}." - $err";
  171. readingsSingleUpdate($hash, "state", 'ERROR Update Zone ' . FmtDateTime(time()), 1);
  172. }
  173. elsif($content ne "")
  174. {
  175. my $tree = HTML::TreeBuilder->new;
  176. my @arrzone = split(/[=&]/, $hash->{url});
  177. my $zone = '';
  178. if ($arrzone[2] eq "bl")
  179. {
  180. # prepare HTML Code
  181. $content =~ s/getLocation\(\)\;//g;
  182. #####################
  183. $tree->parse($content); #<-- Testen !
  184. #$tree->parse(encode($encode, $content));
  185. $zone = $tree->findnodes('//button[contains(@onclick, "'.$arrzone[3].'")]')->[0]->as_trimmed_text;
  186. $zone =~ s/\s\[.*\]//;
  187. }
  188. else {
  189. $zone = $arrzone[3];
  190. }
  191. readingsSingleUpdate( $hash, "zone", $zone, 1 );
  192. }
  193. Log3 $hash, 4, "Verkehrsinfo: ($name) Verkehrsinfo_HttpNbDefineZone done";
  194. }
  195. sub Verkehrsinfo_Undef($$) {
  196. my ($hash, $arg) = @_;
  197. RemoveInternalTimer($hash);
  198. return undef;
  199. }
  200. sub Verkehrsinfo_Get($@) {
  201. my ($hash, @param) = @_;
  202. return '"get Verkehrsinfo" needs at least one argument' if (int(@param) < 2);
  203. my $name = shift @param;
  204. my $opt = shift @param;
  205. if(!$Verkehrsinfo_gets{$opt}) {
  206. return "Unknown argument $opt, choose one of info:noArg";
  207. }
  208. if ($opt eq "info"){
  209. return Verkehrsinfo_GetData($hash->{NAME});
  210. }
  211. }
  212. sub Verkehrsinfo_Set($@) {
  213. my ($hash, @param) = @_;
  214. return '"set Verkehrsinfo" needs at least one argument' if (int(@param) < 2);
  215. my $name = shift @param;
  216. my $opt = shift @param;
  217. my $value = join("", @param);
  218. if(!defined($Verkehrsinfo_gets{$opt})) {
  219. return "Unknown argument $opt, choose one of update:noArg";
  220. }
  221. if ($opt eq "update"){
  222. Verkehrsinfo_GetUpdate($hash);
  223. #return "Update is runing";
  224. return undef;
  225. }
  226. }
  227. sub Verkehrsinfo_Attr(@) {
  228. my ($cmd,$name,$attr_name,$attr_value) = @_;
  229. my $hash = $defs{$name};
  230. if($cmd eq "set") {
  231. if($attr_name eq "filter_exclude" || $attr_name eq "filter_include") {
  232. eval { qr/$attr_value/ };
  233. if ($@) {
  234. my $err = "Verkehrsinfo: Ungültiger Filter in attr $name $attr_name $attr_value: $@";
  235. Log3 $name, 3, $err;
  236. return $err;
  237. }
  238. }
  239. elsif($attr_name eq "msg_format" && $attr_value !~ "road|head|both") {
  240. my $err = "Verkehrsinfo: Ungültiges Message Format in attr $name $attr_name $attr_value: $@";
  241. Log3 $name, 3, $err;
  242. return $err;
  243. }
  244. elsif($attr_name eq "msg_format" && InternalVal($name, 'url', '') =~ 'hessenschau.de/verkehr') {
  245. my $err = "Verkehrsinfo: Message Format ist für " . InternalVal($name, 'url', '') . " nicht Verfügbar";
  246. Log3 $name, 3, $err;
  247. return $err;
  248. }
  249. elsif ( $attr_name eq "disable" ) {
  250. if ( $attr_value == 1 ) {
  251. RemoveInternalTimer($hash);
  252. readingsSingleUpdate( $hash, "state", "inactive", 1 );
  253. $hash->{helper}{DISABLED} = 1;
  254. }
  255. elsif ( $attr_value == 0 ) {
  256. readingsSingleUpdate( $hash, "state", "active", 1 );
  257. Verkehrsinfo_GetUpdate($hash);
  258. }
  259. }
  260. }
  261. elsif ( $cmd eq "del" ) {
  262. if ( $attr_name eq "disable" ) {
  263. Verkehrsinfo_GetUpdate($hash);
  264. }
  265. }
  266. return undef;
  267. }
  268. # recieve data from www http nonblocking
  269. sub Verkehrsinfo_HttpNbUpdateData ($) {
  270. my ($param, $err, $content) = @_;
  271. my $hash = $param->{hash};
  272. my $name = $hash->{NAME};
  273. my $headline;
  274. my @toc;
  275. my @toc2;
  276. my $message = '';
  277. my $message_zone = '';
  278. my $message_head = '';
  279. my $dataarray;
  280. Log3 $hash, 4, "Verkehrsinfo: ($name) Verkehrsinfo_HttpNbUpdateData start";
  281. if($err ne "") {
  282. Log3 $name, 3, "error while requesting ".$param->{url}." - $err";
  283. readingsSingleUpdate($hash, "state", 'ERROR Update Readings ' . FmtDateTime(time()), 1);
  284. }
  285. elsif($content ne "")
  286. {
  287. # read attribut filter
  288. my $filterexclude = AttrVal($name,"filter_exclude","");
  289. my $filterinclude = AttrVal($name,"filter_include",".*");
  290. my $orderby = AttrVal($name,"orderby","");
  291. my $tree = HTML::TreeBuilder->new;
  292. my $i = 1;
  293. ##################
  294. # verkehrsinfo.de
  295. ##################
  296. if ($hash->{url} =~ /verkehrsinfo.de/i){
  297. # prepare HTML Code
  298. $content =~ s/getLocation\(\)\;//g;
  299. $tree->parse($content);
  300. @toc = $tree->findnodes('//div[contains(@class, "panel-body")]/ul/li');
  301. shift(@toc); # delete advertising
  302. for my $el ( Verkehrsinfo_hf_orderby($orderby, @toc) ) {
  303. if (grep(!/$filterexclude/i, $el->as_trimmed_text) && grep(/$filterinclude/i, $el->as_trimmed_text)){
  304. if (exists $el->findnodes('div/div')->[0] && exists $el->findnodes('div')->[1]->findnodes('span')->[0] && exists $el->findnodes('div')->[1]->findnodes('span')->[1]){
  305. $dataarray->{"e_".$i."_road"} = $el->findnodes('div/div')->[0]->as_trimmed_text;
  306. $dataarray->{"e_".$i."_head"} = $el->findnodes('div')->[1]->findnodes('span')->[0]->as_trimmed_text;
  307. $dataarray->{"e_".$i."_msg"} = $el->findnodes('div')->[1]->findnodes('span')->[1]->as_trimmed_text;
  308. $message .= (AttrVal($name,"msg_format","") =~ "road|both") ? $el->findnodes('div/div')->[0]->as_trimmed_text .', ' : '';
  309. $message .= (AttrVal($name,"msg_format","") =~ "head|both") ? $el->findnodes('div')->[1]->findnodes('span')->[1]->as_trimmed_text .', ' : '';
  310. $message .= $el->findnodes('div')->[1]->findnodes('span')->[1]->as_trimmed_text .'. ' ;
  311. $i++;
  312. }
  313. else{
  314. Log3 $hash, 3, "Verkehrsinfo: ($name) Verkehrsinfo_HttpNbUpdateData DataNodeElements not found";
  315. }
  316. }
  317. }
  318. $message =~ s/ \(.*?\)//g; # Remove number of exits
  319. $message_zone = (ReadingsVal($name, 'zone', '') =~ /[0-9]/i) ? ' für die ' : ' für ';
  320. $message_zone .= ReadingsVal($name, 'zone', '');
  321. }
  322. ##################
  323. # hessenschau.de
  324. ##################
  325. elsif ($hash->{url} =~ /hessenschau.de/i) {
  326. # prepare HTML Code
  327. $content =~ s/<\/title>/<\/span>/g;
  328. $content =~ s/<title id="iconTitle--[0-9]+">/<span>/g;
  329. $content =~ s/<text/<p/g;
  330. $content =~ s/<\/text>/<\/p>/g;
  331. $content =~ s/<use.*traffic-arrow.*>.*<\/use>/Richtung/g;
  332. #$tree->parse_content(encode($encode, $content));
  333. $tree->parse_content($content);
  334. $message_zone = ' für Hessen';
  335. @toc = $tree->findnodes('//li[contains(@class, "trafficInfo__item")]');
  336. for my $el ( Verkehrsinfo_hf_orderby($orderby, @toc) ) {
  337. if (grep(!/$filterexclude/i, $el->as_trimmed_text) && grep(/$filterinclude/i, $el->as_trimmed_text)){
  338. if (exists $el->findnodes('div/p')->[0] && exists $el->findnodes('div/span')->[0]){
  339. # check message if it's road or information
  340. if ($el->findnodes('div/p')->[0]->as_trimmed_text =~ /^[0-9]/){
  341. $dataarray->{"e_".$i."_road"} = substr($el->findnodes('div/span')->[0]->as_trimmed_text, 0 ,1).
  342. $el->findnodes('div/p')->[0]->as_trimmed_text;
  343. $dataarray->{"e_".$i."_head"} = (exists $el->findnodes('div/strong')->[0]) ? $el->findnodes('div/strong')->[0]->as_trimmed_text : '';
  344. $dataarray->{"e_".$i."_msg"} = (exists $el->findnodes('div/p')->[1]) ? $el->findnodes('div/p')->[1]->as_trimmed_text : '';
  345. $message .= (exists $el->findnodes('div/p')->[1]) ? $el->findnodes('div/p')->[1]->as_trimmed_text : '';
  346. $message .= ($el->findnodes('div/p')->[1]->as_trimmed_text =~ /\.$/) ? ' ' : '. ';
  347. }
  348. else {
  349. $dataarray->{"e_".$i."_road"} = '-';
  350. $dataarray->{"e_".$i."_head"} = $el->findnodes('div/span')->[0]->as_trimmed_text;
  351. $dataarray->{"e_".$i."_msg"} = $el->findnodes('div/p')->[0]->as_trimmed_text;
  352. $message .= (AttrVal($name,"msg_format","") =~ "head|both") ? $el->findnodes('div/span')->[0]->as_trimmed_text .', ' : '';
  353. $message .= $el->findnodes('div/p')->[0]->as_trimmed_text;
  354. $message .= ($el->findnodes('div/p')->[0]->as_trimmed_text =~ /\.$/) ? ' ' : '. ' ;
  355. }
  356. $i++;
  357. }
  358. else{
  359. Log3 $hash, 3, "Verkehrsinfo: ($name) Verkehrsinfo_HttpNbUpdateData DataNodeElements not found";
  360. }
  361. }
  362. }
  363. }
  364. ##################
  365. # radiosaw.de
  366. ##################
  367. elsif ($hash->{url} =~ /radiosaw.de/i) {
  368. $message_zone = ' für SAW';
  369. # part one meldungen
  370. $tree->parse_content($content);
  371. @toc = $tree->findnodes('//div[contains(@id, "block-system-main")]/div/div[2]/div[2]/div/div/div');
  372. # part two baustellen
  373. @toc2 = $tree->findnodes('//div[contains(@id, "block-system-main")]/div/div[3]/div[1]/div/div[1]/div/div[2]/div/div/div/ul/li');
  374. push (@toc, @toc2);
  375. for my $el ( Verkehrsinfo_hf_orderby($orderby, @toc) ) {
  376. if (grep(!/$filterexclude/i, $el->as_trimmed_text) && grep(/$filterinclude/i, $el->as_trimmed_text)){
  377. if ($el->as_trimmed_text =~ /^Aktuelle Baustelle/){
  378. my $tmp = $el->as_HTML;
  379. $tmp =~ s/<br \/>-+<br \/>/\|/g;
  380. $tmp =~ s/<br \/>/###/g;
  381. my $subtree = HTML::TreeBuilder->new;
  382. $subtree->parse_content($tmp);
  383. my @tmp2 = split(/\|/, $subtree->as_trimmed_text);
  384. shift @tmp2;
  385. for my $sel ( @tmp2 ){
  386. $dataarray->{"e_".$i."_road"} = ((split(/\s/, $sel))[0] =~ /^.[0-9]+/) ? (split(/\s/, $sel))[0] : '-';
  387. $dataarray->{"e_".$i."_head"} = (split(/\###/, $sel))[0];
  388. $dataarray->{"e_".$i."_msg"} = $sel;
  389. $dataarray->{"e_".$i."_msg"} =~ s/^.*?###//;
  390. $dataarray->{"e_".$i."_msg"} =~ s/###/ /g;
  391. $message .= (AttrVal($name,"msg_format","") =~ "road|both") ? $dataarray->{"e_".$i."_road"} .', ' : '';
  392. $message .= (AttrVal($name,"msg_format","") =~ "head|both") ? $dataarray->{"e_".$i."_head"} .', ' : '';
  393. $message .= $dataarray->{"e_".$i."_msg"};
  394. $message .= ($dataarray->{"e_".$i."_msg"} =~ /\.$/) ? ' ' : '. ' ;
  395. $i++;
  396. }
  397. $i--;
  398. }
  399. elsif ($el->findnodes('../li')->[0]){
  400. if (exists $el->findnodes('strong')->[0]){
  401. $dataarray->{"e_".$i."_road"} = ((split(/\s/, $el->findnodes('strong')->[0]->as_trimmed_text .' -'))[1] =~ /^[0-9]+/) ? (split(/\s/, $el->findnodes('strong')->[0]->as_trimmed_text))[0] . ' ' . (split(/\s/, $el->findnodes('strong')->[0]->as_trimmed_text))[1] : '-';
  402. $dataarray->{"e_".$i."_head"} = $el->findnodes('strong')->[0]->as_trimmed_text;
  403. $dataarray->{"e_".$i."_msg"} = $el->as_trimmed_text;
  404. $dataarray->{"e_".$i."_msg"} =~ s/\(/_ko_/g;
  405. $dataarray->{"e_".$i."_msg"} =~ s/\)/_kc_/g;
  406. my $tmp = $el->findnodes('strong')->[0]->as_trimmed_text;
  407. $tmp =~ s/\(/_ko_/g;
  408. $tmp =~ s/\)/_kc_/g;
  409. $dataarray->{"e_".$i."_msg"} =~ s/$tmp//;
  410. }
  411. else{
  412. $dataarray->{"e_".$i."_road"} = '-';
  413. $dataarray->{"e_".$i."_head"} = '-';
  414. $dataarray->{"e_".$i."_msg"} = $el->as_trimmed_text;
  415. }
  416. $dataarray->{"e_".$i."_msg"} =~ s/_ko_/\(/g;
  417. $dataarray->{"e_".$i."_msg"} =~ s/_kc_/\)/g;
  418. $message .= (AttrVal($name,"msg_format","") =~ "road|both") ? $dataarray->{"e_".$i."_road"} .', ' : '';
  419. $message .= (AttrVal($name,"msg_format","") =~ "head|both") ? $dataarray->{"e_".$i."_head"} .', ' : '';
  420. $message .= $dataarray->{"e_".$i."_msg"};
  421. $message .= ($dataarray->{"e_".$i."_msg"} =~ /\.$/) ? ' ' : '. ' ;
  422. $i++;
  423. }
  424. else {
  425. my $tmp = $el->as_HTML;
  426. $tmp =~ s/<br \/>/###/g;
  427. my $subtree = HTML::TreeBuilder->new;
  428. $subtree->parse_content($tmp);
  429. my $anz = scalar(split(/\###/, $subtree->as_trimmed_text));
  430. $dataarray->{"e_".$i."_road"} = ((split(/\s/, $subtree->as_trimmed_text))[0] =~ /^.[0-9]+/) ? (split(/\s/, $subtree->as_trimmed_text))[0] : '-';
  431. $dataarray->{"e_".$i."_head"} = ($anz == 2) ? (split(/\###/, $subtree->as_trimmed_text))[0] : '-';
  432. $dataarray->{"e_".$i."_msg"} = ($anz == 2) ? (split(/\###/, $subtree->as_trimmed_text))[1] : $subtree->as_trimmed_text;
  433. $message .= (AttrVal($name,"msg_format","") =~ "road|both") ? $dataarray->{"e_".$i."_road"} .', ' : '';
  434. $message .= (AttrVal($name,"msg_format","") =~ "head|both") ? $dataarray->{"e_".$i."_head"} .', ' : '';
  435. $message .= $dataarray->{"e_".$i."_msg"};
  436. $message .= ($dataarray->{"e_".$i."_msg"} =~ /\.$/) ? ' ' : '. ' ;
  437. $i++;
  438. }
  439. # else{
  440. # Log3 $hash, 3, "Verkehrsinfo: ($name) Verkehrsinfo_HttpNbUpdateData DataNodeElements not found";
  441. # }
  442. }
  443. }
  444. }
  445. if ($i - 1 == 0){
  446. $message_head = "Es liegen um " . strftime('%H:%M', localtime) . $message_zone . " keine Staumeldungen vor.";
  447. }
  448. elsif ($i - 1 == 1){
  449. $message_head = "Es liegt um " . strftime('%H:%M', localtime) . $message_zone . " eine Staumeldung vor:\n";
  450. }
  451. else{
  452. my $anz_msg = $i - 1;
  453. $message_head = "Es liegen um " . strftime('%H:%M', localtime) . $message_zone . ', ' . $anz_msg ." Staumeldungen vor:\n";
  454. }
  455. $dataarray->{'message'} = $message_head . ' ' . $message;
  456. $dataarray->{'message'} =~ s/\<pre\>//;
  457. $dataarray->{'message'} =~ s/\<\/pre\>//;
  458. $dataarray->{'count'} = $i - 1;
  459. # delete old readings
  460. Log3 $hash, 4, "Verkehrsinfo: ($name) Delete old Readings";
  461. CommandDeleteReading(undef, "$hash->{NAME} e_.*_.*");
  462. Log3 $hash, 4, "Verkehrsinfo: ($name) Create new Readings";
  463. # update readings
  464. readingsBeginUpdate($hash);
  465. readingsBulkUpdate($hash, "date_time", FmtDateTime(time()) );
  466. foreach my $readingName (keys %{$dataarray}){
  467. Log3 $hash, 4, "Verkehrsinfo: ($name) ReadingsUpdate: $readingName - ".$dataarray->{$readingName};
  468. readingsBulkUpdate($hash,$readingName,$dataarray->{$readingName});
  469. }
  470. readingsBulkUpdate($hash, "state", 'update ' . FmtDateTime(time()));
  471. readingsEndUpdate($hash, 1);
  472. }
  473. Log3 $hash, 4, "Verkehrsinfo: ($name) Verkehrsinfo_HttpNbUpdateData done";
  474. }
  475. sub Verkehrsinfo_GetUpdate($) {
  476. my ($hash) = @_;
  477. my $name = $hash->{NAME};
  478. if ( $hash->{Interval}) {
  479. RemoveInternalTimer ($hash);
  480. }
  481. InternalTimer(gettimeofday()+$hash->{Interval}, "Verkehrsinfo_GetUpdate", $hash, 1);
  482. Log3 $hash, 4, "Verkehrsinfo: ($name) internal interval timer set to call GetUpdate again in " . int($hash->{Interval}). " seconds";
  483. my $param = {
  484. url => $hash->{url},
  485. timeout => 5,
  486. hash => $hash,
  487. callback => \&Verkehrsinfo_HttpNbUpdateData
  488. };
  489. HttpUtils_NonblockingGet($param);
  490. }
  491. # give back all traffic information as formated text
  492. sub Verkehrsinfo_GetData($){
  493. my ($device) = @_;
  494. my $hash = $defs{$device};
  495. if (!defined $device){
  496. Log3 $hash, 1, "Verkehrsinfo: ($device) Device not found";
  497. return "Device not found";
  498. }
  499. my $msg = '';
  500. my $i = 1;
  501. $msg = ReadingsVal($device, 'count', '') . " Meldungen für ". ReadingsVal($device, 'zone', '') .":\n\n";
  502. for ($i=1; $i <= ReadingsVal($device, 'count', ''); $i++){
  503. $msg = $msg . ReadingsVal($device, 'e_'.$i.'_road', '') . " - ";
  504. $msg = $msg . ReadingsVal($device, 'e_'.$i.'_head', '') . "\n";
  505. $msg = $msg . ReadingsVal($device, 'e_'.$i.'_msg', '') . "\n\n";
  506. }
  507. return $msg;
  508. }
  509. ##################
  510. # helper function
  511. ##################
  512. # sort messages
  513. sub Verkehrsinfo_hf_orderby ($@) {
  514. my ($order, @inp) = @_;
  515. my @res;
  516. my %diff;
  517. for my $oel (split(/\|/, $order)) {
  518. for my $ael ( @inp ) {
  519. push(@res, $ael) if (grep (/$oel/i, $ael->as_trimmed_text));
  520. }
  521. }
  522. @diff{ @inp } = @inp;
  523. delete @diff{ @res };
  524. return (@res, values %diff);
  525. }
  526. 1;
  527. =pod
  528. =item device
  529. =item summary read trafficinformation from various sources
  530. =item summary_DE Verkehrsinformationen von verschiedenen Quellen auslesen.
  531. =begin html
  532. <a name="Verkehrsinfo"></a>
  533. <h3>Verkehrsinfo</h3>
  534. <ul>
  535. <i>Verkehrsinfo</i> can read trafficinformation from various source.
  536. <br><br>
  537. <ul>
  538. <li>Verkehrsinfo.de</li>
  539. For receiving the traffic informationen, following website https://www.verkehrsinfo.de/httpsmobil will be called on.<br>
  540. There you can select streets or federal states. Afterwards the URL will be committed as a parameter.
  541. <br><br>
  542. <li>Hessenschau.de</li>
  543. Here is no configuration necessary, the URL http://hessenschau.de/verkehr/index.html will be used as a parameter.
  544. <br><br>
  545. <li>RadioSAW.de</li>
  546. Here is no configuration necessary, the keyword radiosaw will be used as a parameter.
  547. </ul>
  548. <br><br>
  549. <b>Requirement:</b>
  550. <ul><br>
  551. For this module, following perl-modules are required:<br>
  552. <li>HTML::TreeBuilder::XPath<br>
  553. <code>sudo apt-get install libxml-treebuilder-perl libhtml-treebuilder-xpath-perl</code>
  554. </li>
  555. </ul>
  556. <br><br>
  557. <a name="Verkehrsinfodefine"></a>
  558. <b>Define</b>
  559. <ul>
  560. <code>define &lt;name&gt; Verkehrsinfo &lt;url&gt; &lt;interval&gt;</code>
  561. <br><br>
  562. example: <code>define A8 Verkehrsinfo https://www.verkehrsinfo.de/httpsmobil/index.php?c=staulist&street=A8&lat=&lon= 3600 </code>
  563. <br><br>
  564. Options:
  565. <ul>
  566. <li><i>url</i><br>
  567. URL regarding the traffic information</li>
  568. <li><i>interval</i><br>
  569. How often the data will be updated in seconds</li>
  570. </ul>
  571. </ul>
  572. <br>
  573. <a name="Verkehrsinfoset"></a>
  574. <b>Set</b><br>
  575. <ul>
  576. <code>set &lt;name&gt; &lt;option&gt;</code>
  577. <br><br>
  578. Options:
  579. <ul>
  580. <li><i>update</i><br>
  581. update will be executed right away</li>
  582. </ul>
  583. </ul>
  584. <br>
  585. <a name="Verkehrsinfoget"></a>
  586. <b>Get</b><br>
  587. <ul>
  588. <code>get &lt;name&gt; &lt;option&gt;</code>
  589. <br><br>
  590. Options:
  591. <ul>
  592. <li><i>info</i><br>
  593. output currently traffic information</li>
  594. </ul>
  595. </ul>
  596. <br>
  597. <a name="Verkehrsinfoattr"></a>
  598. <b>Attributes</b><br>
  599. <ul>
  600. <code>attr &lt;name&gt; &lt;option&gt; &lt;value&gt;</code>
  601. <br><br>
  602. Options:
  603. <ul>
  604. <li><i>filter_exclude</i><br>
  605. This is an exclusion filter. Traffic information containing these words, will not be displayed.<br>
  606. The filter supports regular expressions. Attention: regex control character, for example brackets have to be masked with a backslash "\".<br>
  607. Multiple searching keywords can be seperated with the pipe "|".<br><br></li>
  608. <li><i>filter_include</i><br>
  609. This is an inclusion filter. Traffic information containing these words, will be displayed.<br>
  610. The filter supports regular expressions. Attention: regex control character, for example brackets have to be masked with a backslash "\".<br>
  611. Multiple searching keywords can be seperated with the pipe "|".<br><br></li>
  612. <li>Hint: Both filters can be used at the same time, or optional just one.<br>
  613. The filters are linked with a logical and. That means, for example, when something is excluded, it can be reincluded with the other filter.<br><br></li>
  614. <li><i>orderby</i><br>
  615. Messages will be sorted by relevance by reference to the string.<br>
  616. The sort supports regular expressions.<br>
  617. Multiple searching keywords can be seperated with the pipe "|".<br><br></li>
  618. <li><i>msg_format [ road | head | both ]</i> (only Verkehrsinfo.de and RadioSAW.de)<br>
  619. Using this parameter you can format the output, regarding streets, direction or both.<br><br></li>
  620. <li><i>disable</i><br>
  621. 1 = inactive and 0 = active<br><br></li>
  622. <li><i><a href="#readingFnAttributes">readingFnAttributes</a></i><br><br></li>
  623. </ul>
  624. </ul>
  625. <br>
  626. <a name="Verkehrsinforeading"></a>
  627. <b>Readings</b>
  628. <ul>
  629. <br>
  630. <li><b>e_</b><i>0|1|2|3...|9</i><b>_...</b> - aktiv message</li>
  631. <li><b>count</b> - number of aktiv messages</li>
  632. <li><b>e_</b><i>0</i><b>_road</b> - street</li>
  633. <li><b>e_</b><i>0</i><b>_head</b> - direction</li>
  634. <li><b>e_</b><i>0</i><b>_msg</b> - message</li>
  635. </ul>
  636. <br>
  637. <a name="Verkehrsinfofunktion"></a>
  638. <b>Funktion</b>
  639. <ul>
  640. <code>Verkehrsinfo_GetData(&lt;devicename&gt;)</code>
  641. <br><br>
  642. The function can be accessed anywhere in FHEM.
  643. The output of this function is the same as get <name> info and the string can be used for further forwarding.
  644. <br><br>
  645. example: <code>my $result = Verkehrsinfo_GetData('A8')</code>
  646. </ul>
  647. <br>
  648. </ul>
  649. =end html
  650. =begin html_DE
  651. <a name="Verkehrsinfo"></a>
  652. <h3>Verkehrsinfo</h3>
  653. <ul>
  654. <i>Verkehrsinfo</i> kann die aktuellen Verkehrsinformationen von verschiedenen Quellen auslesen.
  655. <br><br>
  656. <ul>
  657. <li>Verkehrsinfo.de</li>
  658. Um die gewünschten Verkehrsinformation zu erhalten wird die Webseite https://www.verkehrsinfo.de/httpsmobil besucht.
  659. Hier können Sie dann entweder Straßen oder Bundesländer auswählen. Anschließend wird die URL als Parameter übergeben.
  660. <br><br>
  661. <li>Hessenschau.de</li>
  662. Hier ist keine Konfiguration notwendig, man verwendet die URL http://hessenschau.de/verkehr/index.html als Parameter.
  663. <br><br>
  664. <li>RadioSAW.de</li>
  665. Hier ist keine Konfiguration notwendig, man verwendet als Parameter radiosaw.
  666. </ul>
  667. <br><br>
  668. <b>Voraussetzung:</b>
  669. <ul><br>
  670. Für dieses Modul werden folgende Perlmodule benötigt:<br>
  671. <li>HTML::TreeBuilder::XPath<br>
  672. <code>sudo apt-get install libxml-treebuilder-perl libhtml-treebuilder-xpath-perl</code>
  673. </li>
  674. <li>JSON<br>
  675. <code>sudo apt-get install libjson-perl</code>
  676. </li>
  677. </ul>
  678. <br><br>
  679. <a name="Verkehrsinfodefine"></a>
  680. <b>Define</b>
  681. <ul>
  682. <code>define &lt;name&gt; Verkehrsinfo &lt;url&gt; &lt;interval&gt;</code>
  683. <br><br>
  684. Beispiel: <code>define A8 Verkehrsinfo https://www.verkehrsinfo.de/httpsmobil/index.php?c=staulist&street=A8&lat=&lon= 3600 </code>
  685. <br><br>
  686. Options:
  687. <ul>
  688. <li><i>url</i><br>
  689. URL der auszulesenden Verkehrsinformationen</li>
  690. <li><i>interval</i><br>
  691. Alle wieviel Sekunden die Daten aktualisiert werden</li>
  692. </ul>
  693. </ul>
  694. <br>
  695. <a name="Verkehrsinfoset"></a>
  696. <b>Set</b><br>
  697. <ul>
  698. <code>set &lt;name&gt; &lt;option&gt;</code>
  699. <br><br>
  700. Options:
  701. <ul>
  702. <li><i>update</i><br>
  703. Update wird sofort ausgeführt</li>
  704. </ul>
  705. </ul>
  706. <br>
  707. <a name="Verkehrsinfoget"></a>
  708. <b>Get</b><br>
  709. <ul>
  710. <code>get &lt;name&gt; &lt;option&gt;</code>
  711. <br><br>
  712. Options:
  713. <ul>
  714. <li><i>info</i><br>
  715. Ausgeben der aktuellen Verkehrsinformationen</li>
  716. </ul>
  717. </ul>
  718. <br>
  719. <a name="Verkehrsinfoattr"></a>
  720. <b>Attributes</b><br>
  721. <ul>
  722. <code>attr &lt;name&gt; &lt;option&gt; &lt;value&gt;</code>
  723. <br><br>
  724. Options:
  725. <ul>
  726. <li><i>filter_exclude</i><br>
  727. Dies ist ein Ausschlussfilter. Verkehrsmeldung die eines der Wörter enthalten, werden nicht angezeigt.<br>
  728. Der Filter unterstütz Regulärer Ausdrücke. Achtung: Regex Steuerzeichen, z.B. Klammern müssen mit einem Backslash "\" maskiert werden.<br>
  729. Mehrer Suchbegriffe können mit einer Pipe "|" getrennt werden.<br><br></li>
  730. <li><i>filter_include</i><br>
  731. Dies ist ein Einschlussfilter. Es werden nur Verkehrsmeldung angezeigt die eines der Wörter enthalten.<br>
  732. Der Filter unterstütz Regulärer Ausdrücke. Achtung: Regex Steuerzeichen, z.B. Klammern müssen mit einem Backslash "\" maskiert werden.<br>
  733. Mehrer Suchbegriffe können mit einer Pipe "|" getrennt werden.<br><br></li>
  734. <li>Hinweis: Beide Filter können gleichzeitig benutzt werden, aber es kann auch wahlweise nur einer verwendet werden.<br>
  735. Die Filter sind mit einem Logischen UND verknüpft. Das heist z.B.: wenn etwas ausgeschlossen wurde, kann es nicht mit dem Einschlussfilter wiedergeholt werden.<br><br></li>
  736. <li><i>orderby</i><br>
  737. Anhand von Zeichefolgen wird eine Sortierung der Meldungen nach Relevanz vorgenommen.<br>
  738. Die Sortierung unterstützt Regulärer Ausdrücke.<br>
  739. Mehrer Suchbegriffe können mit einer Pipe "|" getrennt werden.<br><br></li>
  740. <li><i>msg_format [ road | head | both ]</i> (Nur Verkehrsinfo.de und RadioSAW.de)<br>
  741. Über diesen Parameter kann die Meldung formatiert werden nach Strasse, Richtung oder beides<br><br></li>
  742. <li><i>disable</i><br>
  743. 1 = inactive and 0 = active<br><br></li>
  744. <li><i><a href="#readingFnAttributes">readingFnAttributes</a></i><br><br></li>
  745. </ul>
  746. </ul>
  747. <br>
  748. <a name="Verkehrsinforeading"></a>
  749. <b>Readings</b>
  750. <ul>
  751. <br>
  752. <li><b>e_</b><i>0|1|2|3...|9</i><b>_...</b> - aktive Meldungen</li>
  753. <li><b>count</b> - Anzahl der aktiven Meldungen</li>
  754. <li><b>e_</b><i>0</i><b>_road</b> - Straße</li>
  755. <li><b>e_</b><i>0</i><b>_head</b> - Fahrtrichtung</li>
  756. <li><b>e_</b><i>0</i><b>_msg</b> - Meldung</li>
  757. </ul>
  758. <br>
  759. <a name="Verkehrsinfofunktion"></a>
  760. <b>Funktion</b>
  761. <ul>
  762. <code>Verkehrsinfo_GetData(&lt;devicename&gt;)</code>
  763. <br><br>
  764. Die Funktion kann überall in FHEM aufgerufen werden und liefert als Rückgabewert das gleiche Ergebnis wie der get &lt;name&gt; info Aufruf.
  765. Der Rückgabewert als Text, kann dann für weiteres verwendet werden.
  766. <br><br>
  767. Beispiel: <code>my $result = Verkehrsinfo_GetData('A8')</code>
  768. </ul>
  769. <br>
  770. </ul>
  771. =end html_DE
  772. =cut