72_Spritpreis.pm 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  1. ##############################################
  2. # $Id: 72_Spritpreis.pm 0 2017-01-10 12:00:00Z pjakobs $
  3. # v0.0: inital testing
  4. # v0.1: basic functionality for pre-configured Tankerkoenig IDs
  5. package main;
  6. use strict;
  7. use warnings;
  8. use Time::HiRes;
  9. use Time::HiRes qw(usleep nanosleep);
  10. use Time::HiRes qw(time);
  11. use JSON::XS;
  12. use URI::URL;
  13. use Data::Dumper;
  14. require "HttpUtils.pm";
  15. $Data::Dumper::Indent = 1;
  16. $Data::Dumper::Sortkeys = 1;
  17. #####################################
  18. #
  19. # fhem skeleton functions
  20. #
  21. #####################################
  22. sub
  23. Spritpreis_Initialize(@) {
  24. my ($hash) = @_;
  25. $hash->{DefFn} = 'Spritpreis_Define';
  26. $hash->{UndefFn} = 'Spritpreis_Undef';
  27. $hash->{ShutdownFn} = 'Spritpreis_Undef';
  28. $hash->{SetFn} = 'Spritpreis_Set';
  29. $hash->{GetFn} = 'Spritpreis_Get';
  30. $hash->{AttrFn} = 'Spritpreis_Attr';
  31. $hash->{NotifyFn} = 'Spritpreis_Notify';
  32. $hash->{ReadFn} = 'Spritpreis_Read';
  33. $hash->{AttrList} = "lat lon rad IDs type sortby apikey interval address priceformat:2dezCut,2dezRound,3dez"." $readingFnAttributes";
  34. #$hash->{AttrList} = "IDs type interval"." $readingFnAttributes";
  35. return undef;
  36. }
  37. sub
  38. Spritpreis_Define($$) {
  39. #####################################
  40. #
  41. # how to define this
  42. #
  43. # for Tankerkönig:
  44. # define <myName> Spritpreis Tankerkoenig <TankerkönigAPI_ID>
  45. #
  46. # for Spritpreisrechner.at
  47. # define <myName> Spritpreis Spritpreisrechner
  48. #
  49. #####################################
  50. my $apiKey;
  51. my ($hash, $def)=@_;
  52. my @parts=split("[ \t][ \t]*", $def);
  53. my $name=$parts[0];
  54. if(defined $parts[2]){
  55. if($parts[2] eq "Tankerkoenig"){
  56. ##
  57. if(defined $parts[3]){
  58. $apiKey=$parts[3];
  59. }else{
  60. Log3 ($hash, 2, "$hash->{NAME} Module $parts[1] requires a valid apikey");
  61. return undef;
  62. }
  63. my $result;
  64. my $url="https://creativecommons.tankerkoenig.de/json/prices.php?ids=12121212-1212-1212-1212-121212121212&apikey=".$apiKey;
  65. my $param= {
  66. url => $url,
  67. timeout => 1,
  68. method => "GET",
  69. header => "User-Agent: fhem\r\nAccept: application/json",
  70. };
  71. my ($err, $data)=HttpUtils_BlockingGet($param);
  72. if ($err){
  73. Log3($hash,2,"$hash->{NAME}: Error verifying APIKey: $err");
  74. return undef;
  75. }else{
  76. eval {
  77. $result = JSON->new->utf8(1)->decode($data);
  78. };
  79. if ($@) {
  80. Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
  81. } else {
  82. if ($result->{ok} ne "true" && $result->{ok} != 1){
  83. Log3 ($hash, 2, "$hash->{name}: error: $result->{message}");
  84. return undef;
  85. }
  86. }
  87. $hash->{helper}->{apiKey}=$apiKey;
  88. $hash->{helper}->{service}="Tankerkoenig";
  89. }
  90. if(AttrVal($hash->{NAME}, "IDs","")){
  91. #
  92. # if IDs attribute is defined, populate list of stations at startup
  93. #
  94. my $ret=Spritpreis_Tankerkoenig_populateStationsFromAttr($hash);
  95. }
  96. #
  97. # start initial update
  98. #
  99. Spritpreis_Tankerkoenig_updateAll($hash);
  100. } elsif($parts[2] eq "Spritpreisrechner"){
  101. $hash->{helper}->{service}="Spritpreisrechner";
  102. }
  103. }else{
  104. Log3($hash,2,"$hash->{NAME} Module $parts[1] requires a provider specification. Currently either \"Tankerkoenig\" (for de) or \"Spritpreisrechner\" (for at)");
  105. }
  106. return undef;
  107. }
  108. sub
  109. Spritpreis_Undef(@){
  110. my ($hash,$name)=@_;
  111. RemoveInternalTimer($hash);
  112. return undef;
  113. }
  114. sub
  115. Spritpreis_Set(@) {
  116. my ($hash , $name, $cmd, @args) = @_;
  117. return "Unknown command $cmd, choose one of update add delete" if ($cmd eq '?');
  118. Log3($hash, 3,"$hash->{NAME}: get $hash->{NAME} $cmd $args[0]");
  119. if ($cmd eq "update"){
  120. if(defined $args[0]){
  121. if($args[0] eq "all"){
  122. # removing the timer so we don't get a flurry of requests
  123. RemoveInternalTimer($hash);
  124. Spritpreis_Tankerkoenig_updateAll($hash);
  125. }elsif($args[0] eq "id"){
  126. if(defined $args[1]){
  127. Spritpreis_Tankerkoenig_updatePricesForIDs($hash, $args[1]);
  128. }else{
  129. my $r="update id requires an id parameter!";
  130. Log3($hash, 2,"$hash->{NAME} $r");
  131. return $r;
  132. }
  133. }
  134. }else{
  135. #
  136. # default behaviour if no ID or "all" is given is to update all existing IDs
  137. #
  138. Spritpreis_Tankerkoenig_updateAll($hash);
  139. }
  140. }elsif($cmd eq "add"){
  141. if(defined $args[0]){
  142. Log3($hash, 4,"$hash->{NAME} add: args[0]=$args[0]");
  143. if($args[0] eq "id"){
  144. #
  145. # add station by providing a single Tankerkoenig ID
  146. #
  147. if(defined($args[1])){
  148. Spritpreis_Tankerkoenig_GetDetailsForID($hash, $args[1]);
  149. }else{
  150. my $ret="add by id requires a station id";
  151. return $ret;
  152. }
  153. }
  154. }else{
  155. my $ret="add requires id or (some other method here soon)";
  156. return $ret;
  157. }
  158. }elsif($cmd eq "delete"){
  159. #
  160. # not sure how to "remove" readings through the fhem api
  161. #
  162. }
  163. return undef;
  164. }
  165. sub
  166. Spritpreis_Get(@) {
  167. my ($hash, $name, $cmd, @args) = @_;
  168. return "Unknown command $cmd, choose one of search test" if ($cmd eq '?');
  169. Log3($hash, 3,"$hash->{NAME}: get $hash->{NAME} $cmd $args[0]");
  170. if ($cmd eq "search"){
  171. my $str='';
  172. my $i=0;
  173. while($args[$i++]){
  174. $str=$str." ".$args[$i];
  175. }
  176. Log3($hash,4,"$hash->{NAME}: search string: $str");
  177. my @loc=Spritpreis_GetCoordinatesForAddress($hash, $str);
  178. my ($lat, $lng, $str)=@loc;
  179. if($lat==0 && $lng==0){
  180. return $str;
  181. }else{
  182. if($hash->{helper}->{service} eq "Tankerkoenig"){
  183. my $ret=Spritpreis_Tankerkoenig_GetStationIDsForLocation($hash, @loc);
  184. return $ret;
  185. }
  186. }
  187. }elsif($cmd eq "test"){
  188. my $ret=Spritpreis_Tankerkoenig_populateStationsFromAttr($hash);
  189. return $ret;
  190. }else{
  191. return undef;
  192. }
  193. #Spritpreis_Tankerkoenig_GetPricesForLocation($hash);
  194. #Spritpreis_GetCoordinatesForAddress($hash,"Hamburg, Elbphilharmonie");
  195. # add price trigger here
  196. return undef;
  197. }
  198. sub
  199. Spritpreis_Attr(@) {
  200. my ($cmd, $device, $attrName, $attrVal)=@_;
  201. my $hash = $defs{$device};
  202. if ($cmd eq 'set' and $attrName eq 'interval'){
  203. Spritpreis_updateAll($hash);
  204. }
  205. return undef;
  206. }
  207. sub
  208. Spritpreis_Notify(@) {
  209. return undef;
  210. }
  211. sub
  212. Spritpreis_Read(@) {
  213. return undef;
  214. }
  215. #####################################
  216. #
  217. # generalized functions
  218. # these functions will call the
  219. # specific functions for the defined
  220. # provider.
  221. #
  222. #####################################
  223. sub
  224. Spritpreis_GetDetailsForID(){
  225. }
  226. sub
  227. Spritpreis_updateAll(@){
  228. my ($hash)=@_;
  229. if($hash->{helper}->{service} eq "Tankerkoenig"){
  230. Spritpreis_Tankerkoenig_updateAll();
  231. }elsif($hash->{helper}->{service} eq "Spritpreisrechner"){
  232. }
  233. }
  234. #####################################
  235. #
  236. # functions to create requests
  237. #
  238. #####################################
  239. sub
  240. Spritpreis_Tankerkoenig_GetIDsForLocation(@){
  241. my ($hash) = @_;
  242. my $lat=AttrVal($hash->{'NAME'}, "lat",0);
  243. my $lng=AttrVal($hash->{'NAME'}, "lon",0);
  244. my $rad=AttrVal($hash->{'NAME'}, "rad",5);
  245. my $type=AttrVal($hash->{'NAME'}, "type","diesel");
  246. my $apiKey=$hash->{helper}->{apiKey};
  247. Log3($hash,4,"$hash->{'NAME'}: apiKey: $apiKey");
  248. if($apiKey eq "") {
  249. Log3($hash,3,"$hash->{'NAME'}: please provide a valid apikey, you can get it from https://creativecommons.tankerkoenig.de/#register. This function can't work without it");
  250. my $r="err no APIKEY";
  251. return $r;
  252. }
  253. my $url="https://creativecommons.tankerkoenig.de/json/list.php?lat=$lat&lng=$lng&rad=$rad&type=$type&apikey=$apiKey";
  254. my $param = {
  255. url => $url,
  256. timeout => 2,
  257. hash => $hash,
  258. method => "GET",
  259. header => "User-Agent: fhem\r\nAccept: application/json",
  260. parser => \&Spritpreis_ParseIDsForLocation,
  261. callback => \&Spritpreis_callback
  262. };
  263. HttpUtils_NonblockingGet($param);
  264. return undef;
  265. }
  266. #--------------------------------------------------
  267. # sub
  268. # Spritpreis_Tankerkoenig_GetIDs(@){
  269. # my ($hash) = @_;
  270. # Log3($hash, 4, "$hash->{NAME} called Spritpreis_Tankerkoenig_updatePricesForIDs");
  271. # my $IDstring=AttrVal($hash->{NAME}, "IDs","");
  272. # Log3($hash,4,"$hash->{NAME}: got ID String $IDstring");
  273. # my @IDs=split(",", $IDstring);
  274. # my $i=1;
  275. # my $j=1;
  276. # my $IDList;
  277. # do {
  278. # $IDList=$IDs[0];
  279. # #
  280. # # todo hier stimmt was mit den Indizes nicht!
  281. # #
  282. # do {
  283. # $IDList=$IDList.",".$IDs[$i];
  284. # }while($j++ < 9 && defined($IDs[$i++]));
  285. # Spritpreis_Tankerkoenig_updatePricesForIDs($hash, $IDList);
  286. # Log3($hash, 4,"$hash->{NAME}: Set ending at $i IDList=$IDList");
  287. # $j=1;
  288. # }while(defined($IDs[$i]));
  289. # return undef;
  290. # }
  291. #--------------------------------------------------
  292. sub
  293. Spritpreis_Tankerkoenig_populateStationsFromAttr(@){
  294. #
  295. # This walks through the IDs Attribute and adds the stations listed there to the station readings list,
  296. # initially getting full details
  297. #
  298. my ($hash) =@_;
  299. Log3($hash,4, "$hash->{NAME}: called Spritpreis_Tankerkoenig_populateStationsFromAttr ");
  300. my $IDstring=AttrVal($hash->{NAME}, "IDs","");
  301. Log3($hash,4,"$hash->{NAME}: got ID String $IDstring");
  302. my @IDs=split(",", $IDstring);
  303. my $i;
  304. do{
  305. Spritpreis_Tankerkoenig_GetDetailsForID($hash, $IDs[$i]);
  306. }while(defined($IDs[$i++]));
  307. }
  308. sub
  309. Spritpreis_Tankerkoenig_updateAll(@){
  310. #
  311. # this walks through the list of ID Readings and updates the fuel prices for those stations
  312. # it does this in blocks of 10 as suggested by the Tankerkoenig API
  313. #
  314. my ($hash) = @_;
  315. Log3($hash,4, "$hash->{NAME}: called Spritpreis_Tankerkoenig_updateAll ");
  316. my $i=1;
  317. my $j=0;
  318. my $id;
  319. my $IDList;
  320. do {
  321. $IDList=ReadingsVal($hash->{NAME}, $j."_id", "");
  322. while($j++<9 && ReadingsVal($hash->{NAME}, $i."_id", "") ne "" ){
  323. Log3($hash, 5, "$hash->{NAME}: i: $i, j: $j, id: ".ReadingsVal($hash->{NAME}, $i."_id", "") );
  324. $IDList=$IDList.",".ReadingsVal($hash->{NAME}, $i."_id", "");
  325. $i++;
  326. }
  327. if($IDList ne ""){
  328. Spritpreis_Tankerkoenig_updatePricesForIDs($hash, $IDList);
  329. Log3($hash, 4,"$hash->{NAME}(update all): Set ending at $i IDList=$IDList");
  330. }
  331. $j=1;
  332. }while(ReadingsVal($hash->{NAME}, $i."_id", "") ne "" );
  333. Log3($hash, 4, "$hash->{NAME}: updateAll set timer for ".(gettimeofday()+AttrVal($hash->{NAME},"interval",15)*60)." delay ".(AttrVal($hash->{NAME},"interval", 15)*60));
  334. InternalTimer(gettimeofday()+AttrVal($hash->{NAME}, "interval",15)*60, "Spritpreis_Tankerkoenig_updateAll",$hash);
  335. return undef;
  336. }
  337. sub
  338. Spritpreis_Tankerkoenig_GetDetailsForID(@){
  339. #
  340. # This queries the Tankerkoenig API for the details for a specific ID
  341. # It does not verify the provided ID
  342. # The parser function is responsible for handling the response
  343. #
  344. my ($hash, $id)=@_;
  345. my $apiKey=$hash->{helper}->{apiKey};
  346. if($apiKey eq "") {
  347. Log3($hash,3,"$hash->{'NAME'}: please provide a valid apikey, you can get it from https://creativecommons.tankerkoenig.de/#register. This function can't work without it");
  348. my $r="err no APIKEY";
  349. return $r;
  350. }
  351. my $url="https://creativecommons.tankerkoenig.de/json/detail.php?id=".$id."&apikey=$apiKey";
  352. Log3($hash, 4,"$hash->{NAME}: called $url");
  353. my $param={
  354. url => $url,
  355. hash => $hash,
  356. timeout => 10,
  357. method => "GET",
  358. header => "User-Agent: fhem\r\nAccept: application/json",
  359. parser => \&Spritpreis_Tankerkoenig_ParseDetailsForID,
  360. callback=> \&Spritpreis_callback
  361. };
  362. HttpUtils_NonblockingGet($param);
  363. return undef;
  364. }
  365. sub
  366. Spritpreis_Tankerkoenig_updatePricesForIDs(@){
  367. #
  368. # This queries the Tankerkoenig API for an update on all prices. It takes a list of up to 10 IDs.
  369. # It will not verify the validity of those IDs nor will it check that the number is 10 or less
  370. # The parser function is responsible for handling the response
  371. #
  372. my ($hash, $IDList) = @_;
  373. my $apiKey=$hash->{helper}->{apiKey};
  374. my $url="https://creativecommons.tankerkoenig.de/json/prices.php?ids=".$IDList."&apikey=$apiKey";
  375. Log3($hash, 4,"$hash->{NAME}: called $url");
  376. my $param={
  377. url => $url,
  378. hash => $hash,
  379. timeout => 10,
  380. method => "GET",
  381. header => "User-Agent: fhem\r\nAccept: application/json",
  382. parser => \&Spritpreis_Tankerkoenig_ParsePricesForIDs,
  383. callback=> \&Spritpreis_callback
  384. };
  385. HttpUtils_NonblockingGet($param);
  386. return undef;
  387. }
  388. sub
  389. Spritpreis_Spritpreisrechner_updatePricesForLocation(@){
  390. #
  391. # for the Austrian Spritpreisrechner, there's not concept of IDs. The only method
  392. # is to query for prices by location which will make it difficult to follow the
  393. # price trend at any specific station.
  394. #
  395. my ($hash)=@_;
  396. my $url="http://www.spritpreisrechner.at/espritmap-app/GasStationServlet";
  397. my $lat=AttrVal($hash->{'NAME'}, "lat",0);
  398. my $lng=AttrVal($hash->{'NAME'}, "lon",0);
  399. my $param={
  400. url => $url,
  401. timeout => 1,
  402. method => "POST",
  403. header => "User-Agent: fhem\r\nAccept: application/json",
  404. data => {
  405. "",
  406. "DIE",
  407. "15.409674251128",
  408. "47.051201316374",
  409. "15.489496791403",
  410. "47.074588294516"
  411. }
  412. };
  413. my ($err,$data)=HttpUtils_BlockingGet($param);
  414. Log3($hash,5,"$hash->{'NAME'}: Dumper($data)");
  415. return undef;
  416. }
  417. sub
  418. Spritpreis_Tankerkoenig_GetStationIDsForLocation(@){
  419. #
  420. # This is currently not being used. The idea is to provide a lat/long location and a radius and have
  421. # the stations within this radius are presented as a list and, upon selecting them, will be added
  422. # to the readings list
  423. #
  424. my ($hash,@location) = @_;
  425. # my $lat=AttrVal($hash->{'NAME'}, "lat",0);
  426. # my $lng=AttrVal($hash->{'NAME'}, "lon",0);
  427. my $rad=AttrVal($hash->{'NAME'}, "rad",5);
  428. my $type=AttrVal($hash->{'NAME'}, "type","all");
  429. # my $sort=AttrVal($hash->{'NAME'}, "sortby","price");
  430. my $apiKey=$hash->{helper}->{apiKey};
  431. my ($lat, $lng, $formattedAddress)=@location;
  432. my $result;
  433. if($apiKey eq "") {
  434. Log3($hash,3,"$hash->{'NAME'}: please provide a valid apikey, you can get it from https://creativecommons.tankerkoenig.de/#register. This function can't work without it");
  435. my $r="err no APIKEY";
  436. return $r;
  437. }
  438. my $url="https://creativecommons.tankerkoenig.de/json/list.php?lat=$lat&lng=$lng&rad=$rad&type=$type&apikey=$apiKey";
  439. Log3($hash, 4,"$hash->{NAME}: sending request with url $url");
  440. my $param= {
  441. url => $url,
  442. hash => $hash,
  443. timeout => 1,
  444. method => "GET",
  445. header => "User-Agent: fhem\r\nAccept: application/json",
  446. };
  447. my ($err, $data) = HttpUtils_BlockingGet($param);
  448. if($err){
  449. Log3($hash, 4, "$hash->{NAME}: error fetching nformation");
  450. } elsif($data){
  451. Log3($hash, 4, "$hash->{NAME}: got data");
  452. Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
  453. eval {
  454. $result = JSON->new->utf8(1)->decode($data);
  455. };
  456. if ($@) {
  457. Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
  458. } else {
  459. my @headerHost = grep /Host/, @FW_httpheader;
  460. $headerHost[0] =~ s/Host: //g;
  461. my ($stations) = $result->{stations};
  462. my $ret="<html><p><h3>Stations for Address</h3></p><p><h2>$formattedAddress</h2></p><table><tr><td>Name</td><td>Ort</td><td>Straße</td></tr>";
  463. foreach (@{$stations}){
  464. (my $station)=$_;
  465. Log3($hash, 2, "Name: $station->{name}, id: $station->{id}");
  466. $ret=$ret . "<tr><td><a href=http://" .
  467. $headerHost[0] .
  468. "/fhem?cmd=set+" .
  469. $hash->{NAME} .
  470. "+add+id+" .
  471. $station->{id} .
  472. ">";
  473. $ret=$ret . $station->{name} . "</td><td>" . $station->{place} . "</td><td>" . $station->{street} . " " . $station->{houseNumber} . "</td></tr>";
  474. }
  475. $ret=$ret . "</table>";
  476. Log3($hash,2,"$hash->{NAME}: ############# ret: $ret");
  477. return $ret;
  478. }
  479. }else {
  480. Log3 ($hash, 4, "$hash->{NAME}: something's very odd");
  481. }
  482. return $data;
  483. # InternalTimer(gettimeofday()+AttrVal($hash->{NAME}, "interval",15)*60, "Spritpreis_Tankerkoenig_GetPricesForLocation",$hash);
  484. return undef;
  485. }
  486. #####################################
  487. #
  488. # functions to handle responses
  489. #
  490. #####################################
  491. sub
  492. Spritpreis_callback(@) {
  493. #
  494. # the generalized callback function. This should check all the general API errors and
  495. # handle them centrally, leaving the parser functions to handle response specific errors
  496. #
  497. my ($param, $err, $data) = @_;
  498. my ($hash) = $param->{hash};
  499. # TODO generic error handling
  500. #Log3($hash, 5, "$hash->{NAME}: received callback with $data");
  501. # do the result-parser callback
  502. if ($err){
  503. Log3($hash, 4, "$hash->{NAME}: error fetching information: $err");
  504. return undef;
  505. }
  506. my $parser = $param->{parser};
  507. #Log3($hash, 4, "$hash->{NAME}: calling parser $parser with err $err and data $data");
  508. &$parser($hash, $err, $data);
  509. if( $err || $err ne ""){
  510. Log3 ($hash, 3, "$hash->{NAME} Readings NOT updated, received Error: ".$err);
  511. }
  512. return undef;
  513. }
  514. sub
  515. Spritpreis_ParseIDsForLocation(@){
  516. return undef;
  517. }
  518. sub
  519. Spritpreis_Tankerkoenig_ParseDetailsForID(@){
  520. #
  521. # this parses the response generated by the query Spritpreis_Tankerkoenig_GetDetailsForID
  522. # The response will contain the ID for a single station being, so no need to go through
  523. # multiple parts here. It will work whether or not that ID is currently already in the list
  524. # of readings. If it is, the details will be updated, if it is not, the new station will be
  525. # added at the end of the list
  526. #
  527. my ($hash, $err, $data)=@_;
  528. my $result;
  529. if($data){
  530. Log3($hash, 4, "$hash->{NAME}: got StationDetail reply");
  531. Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
  532. eval { $result = JSON->new->utf8(1)->decode($data); };
  533. if ($@) {
  534. Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
  535. } else {
  536. my $i=0;
  537. my $station = $result->{station};
  538. while(ReadingsVal($hash->{NAME},$i."_id",$station->{id}) ne $station->{id})
  539. {
  540. #
  541. # this loop iterates through the readings until either an id is equal to the current
  542. # response $station->{id} or, if no id is, it will come up with the default which is set
  543. # to $station->{id}, thus it will be added
  544. #
  545. $i++;
  546. }
  547. readingsBeginUpdate($hash);
  548. readingsBulkUpdate($hash,$i."_name",$station->{name});
  549. my @types=("e5", "e10", "diesel");
  550. foreach my $type (@types){
  551. Log3($hash,4,"$hash->{NAME}: checking type $type");
  552. if(defined($station->{$type})){
  553. if(AttrVal($hash->{NAME}, "priceformat","") eq "2dezCut"){
  554. chop($station->{$type});
  555. }elsif(AttrVal($hash->{NAME}, "priceformat","") eq "2dezRound"){
  556. $station->{$type}=sprintf("%.2f", $station->{$type});
  557. }
  558. if(ReadingsVal($hash->{NAME}, $i."_".$type."_trend",0)!=0){
  559. my $p=ReadingsVal($hash->{NAME}, $i."_".$type."_price",0);
  560. Log3($hash,4,"$hash->{NAME}:parseDetailsForID $type price old: $p");
  561. if($p>$station->{$type}){
  562. readingsBulkUpdate($hash,$i."_".$type."_trend","fällt");
  563. Log3($hash,4,"$hash->{NAME}:parseDetailsForID trend: fällt");
  564. }elsif($p < $station->{$type}){
  565. readingsBulkUpdate($hash,$i."_".$type."_trend","steigt");
  566. Log3($hash,4,"$hash->{NAME}:parseDetailsForID trend: konstant");
  567. }else{
  568. }
  569. readingsBulkUpdate($hash,$i."_".$type."_price",$station->{$type})
  570. }
  571. }
  572. }
  573. readingsBulkUpdate($hash,$i."_place",$station->{place});
  574. readingsBulkUpdate($hash,$i."_street",$station->{street}." ".$station->{houseNumber});
  575. readingsBulkUpdate($hash,$i."_distance",$station->{dist});
  576. readingsBulkUpdate($hash,$i."_brand",$station->{brand});
  577. readingsBulkUpdate($hash,$i."_lat",$station->{lat});
  578. readingsBulkUpdate($hash,$i."_lon",$station->{lng});
  579. readingsBulkUpdate($hash,$i."_id",$station->{id});
  580. readingsBulkUpdate($hash,$i."_isOpen",$station->{isOpen});
  581. readingsEndUpdate($hash,1);
  582. }
  583. }
  584. }
  585. sub
  586. Spritpreis_Tankerkoenig_ParsePricesForIDs(@){
  587. #
  588. # This parses the response to Spritpreis_Tankerkoenig_updatePricesForIDs
  589. # this response contains price updates for the requested stations listed by ID
  590. # since we don't keep a context between the API request and the response,
  591. # in order to update the correct readings, this routine has to go through the
  592. # readings list and make sure it does find matching IDs. It will not add new
  593. # stations to the list
  594. #
  595. my ($hash, $err, $data)=@_;
  596. my $result;
  597. if($err){
  598. Log3($hash, 4, "$hash->{NAME}: error fetching information");
  599. } elsif($data){
  600. Log3($hash, 4, "$hash->{NAME}: got PricesForLocation reply");
  601. Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
  602. eval {
  603. $result = JSON->new->utf8(1)->decode($data);
  604. };
  605. if ($@) {
  606. Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
  607. } else {
  608. my ($stations) = $result->{prices};
  609. Log3($hash, 5, "$hash->{NAME}: stations:".Dumper($stations));
  610. #
  611. # the return value is keyed by stations, therefore, I'll have
  612. # to fetch the stations from the existing readings and run
  613. # through it along those ids.
  614. #
  615. my $i=0;
  616. while(ReadingsVal($hash->{NAME}, $i."_id", "") ne "" ){
  617. my $id=ReadingsVal($hash->{NAME}, $i."_id", "");
  618. Log3($hash, 4, "$hash->{NAME}: checking ID $id");
  619. if(defined($stations->{$id})){
  620. Log3($hash, 4, "$hash->{NAME}: updating readings-set $i (ID $id)" );
  621. Log3($hash, 5, "$hash->{NAME} Update set:\nprice: $stations->{$id}->{price}\ne5: $stations->{$id}->{e5}\ne10: $stations->{$id}->{e10}\ndiesel: $stations->{$id}->{diesel}\n");
  622. readingsBeginUpdate($hash);
  623. my @types=("e5", "e10", "diesel");
  624. foreach my $type (@types){
  625. Log3($hash, 4, "$hash->{NAME} ParsePricesForIDs checking type $type");
  626. if(defined($stations->{$id}->{$type})){
  627. if(AttrVal($hash->{NAME}, "priceformat","") eq "2dezCut"){
  628. chop($stations->{$id}->{$type});
  629. }elsif(AttrVal($hash->{NAME}, "priceformat","") eq "2dezRound"){
  630. $stations->{$id}->{$type}=sprintf("%.2f", $stations->{$id}->{$type});
  631. }
  632. Log3($hash, 4, "$hash->{NAME} ParsePricesForIDs updating type $type");
  633. #if(ReadingsVal($hash->{NAME}, $i."_".$type."_trend","") ne ""){
  634. my $p=ReadingsVal($hash->{NAME}, $i."_".$type."_price",0);
  635. Log3($hash,4,"$hash->{NAME}:parseDetailsForID $type price old: $p");
  636. if($p>$stations->{$id}->{$type}){
  637. readingsBulkUpdate($hash,$i."_".$type."_trend","fällt");
  638. }elsif($p < $stations->{$id}->{$type}){
  639. readingsBulkUpdate($hash,$i."_".$type."_trend","steigt");
  640. }else{
  641. }
  642. #}
  643. readingsBulkUpdate($hash,$i."_".$type."_price",$stations->{$id}->{$type})
  644. }
  645. }
  646. readingsBulkUpdate($hash,$i."_isOpen",$stations->{$id}->{status});
  647. readingsEndUpdate($hash, 1);
  648. }
  649. $i++;
  650. }
  651. }
  652. }
  653. return undef;
  654. }
  655. sub
  656. Spritpreis_Tankerkoening_ParseStationIDsForLocation(@){
  657. my ($hash, $err, $data)=@_;
  658. my $result;
  659. Log3($hash,5,"$hash->{NAME}: ParsePricesForLocation has been called with err $err and data $data");
  660. if($err){
  661. Log3($hash, 4, "$hash->{NAME}: error fetching information");
  662. } elsif($data){
  663. Log3($hash, 4, "$hash->{NAME}: got PricesForLocation reply");
  664. Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
  665. eval {
  666. $result = JSON->new->utf8(1)->decode($data);
  667. };
  668. if ($@) {
  669. Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
  670. } else {
  671. my ($stations) = $result->{stations};
  672. #Log3($hash, 5, "$hash->{NAME}: stations:".Dumper($stations));
  673. my $ret="<html><form action=fhem/cmd?set ".$hash->{NAME}." station method='get'><select multiple name='id'>";
  674. foreach (@{$stations}){
  675. (my $station)=$_;
  676. #Log3($hash, 5, "$hash->{NAME}: Station hash:".Dumper($station));
  677. Log3($hash, 2, "Name: $station->{name}, id: $station->{id}");
  678. $ret=$ret."<option value=".$station->{id}.">".$station->{name}." ".$station->{place}." ".$station->{street}." ".$station->{houseNumber}."</option>";
  679. }
  680. # readingsEndUpdate($hash,1);
  681. $ret=$ret."<button type='submit'>submit</button></html>";
  682. Log3($hash,2,"$hash->{NAME}: ############# ret: $ret");
  683. return $ret;
  684. }
  685. }else {
  686. Log3 ($hash, 4, "$hash->{NAME}: something's very odd");
  687. }
  688. return $data;
  689. }
  690. sub
  691. Spritpreis_ParsePricesForIDs(@){
  692. }
  693. #####################################
  694. #
  695. # geolocation functions
  696. #
  697. #####################################
  698. sub
  699. Spritpreis_GetCoordinatesForAddress(@){
  700. my ($hash, $address)=@_;
  701. my $result;
  702. my $url=new URI::URL 'https://maps.google.com/maps/api/geocode/json';
  703. $url->query_form("address",$address);
  704. Log3($hash, 3, "$hash->{NAME}: request URL: ".$url);
  705. my $param= {
  706. url => $url,
  707. timeout => 1,
  708. method => "GET",
  709. header => "User-Agent: fhem\r\nAccept: application/json",
  710. };
  711. my ($err, $data)=HttpUtils_BlockingGet($param);
  712. if($err){
  713. Log3($hash, 4, "$hash->{NAME}: error fetching nformation");
  714. } elsif($data){
  715. Log3($hash, 4, "$hash->{NAME}: got CoordinatesForAddress reply");
  716. Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
  717. eval {
  718. $result = JSON->new->utf8(1)->decode($data);
  719. };
  720. if ($@) {
  721. Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
  722. } else {
  723. if ($result->{status} eq "ZERO_RESULTS"){
  724. return(0,0,"error: could not find address");
  725. }else{
  726. my $lat=$result->{results}->[0]->{geometry}->{location}->{lat};
  727. my $lon=$result->{results}->[0]->{geometry}->{location}->{lng};
  728. my $formattedAddress=$result->{results}->[0]->{formatted_address};
  729. Log3($hash,3,"$hash->{NAME}: got coordinates for address as lat: $lat, lon: $lon");
  730. return ($lat, $lon, $formattedAddress);
  731. }
  732. }
  733. }else {
  734. Log3 ($hash, 4, "$hash->{NAME}: something is very odd");
  735. }
  736. return undef;
  737. }
  738. sub
  739. Spritpreis_ParseCoordinatesForAddress(@){
  740. my ($hash, $err, $data)=@_;
  741. my $result;
  742. Log3($hash,5,"$hash->{NAME}: ParseCoordinatesForAddress has been called with err $err and data $data");
  743. if($err){
  744. Log3($hash, 4, "$hash->{NAME}: error fetching nformation");
  745. } elsif($data){
  746. Log3($hash, 4, "$hash->{NAME}: got CoordinatesForAddress reply");
  747. Log3($hash, 5, "$hash->{NAME}: got data $data\n\n\n");
  748. eval {
  749. $result = JSON->new->utf8(1)->decode($data);
  750. };
  751. if ($@) {
  752. Log3 ($hash, 4, "$hash->{NAME}: error decoding response $@");
  753. } else {
  754. my $lat=$result->{results}->[0]->{geometry}->{location}->{lat};
  755. my $lon=$result->{results}->[0]->{geometry}->{location}->{lng};
  756. Log3($hash,3,"$hash->{NAME}: got coordinates for address as lat: $lat, lon: $lon");
  757. return ($lat, $lon);
  758. }
  759. }else {
  760. Log3 ($hash, 4, "$hash->{NAME}: something is very odd");
  761. }
  762. return undef;
  763. }
  764. #####################################
  765. #
  766. # helper functions
  767. #
  768. #####################################
  769. 1;