59_Weather.pm 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862
  1. # $Id: 59_Weather.pm 16644 2018-04-22 08:07:35Z neubert $
  2. ##############################################################################
  3. #
  4. # 59_Weather.pm
  5. # Copyright by Dr. Boris Neubert
  6. # e-mail: omega at online dot de
  7. #
  8. # This file is part of fhem.
  9. #
  10. # Fhem is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # Fhem is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with fhem. If not, see <http://www.gnu.org/licenses/>.
  22. #
  23. ##############################################################################
  24. package main;
  25. use strict;
  26. use warnings;
  27. use Time::HiRes qw(gettimeofday);
  28. use HttpUtils;
  29. use vars qw($FW_ss);
  30. my %pressure_trend_txt_en = ( 0 => "steady", 1 => "rising", 2 => "falling" );
  31. my %pressure_trend_txt_de = ( 0 => "gleichbleibend", 1 => "steigend", 2 => "fallend" );
  32. my %pressure_trend_txt_nl = ( 0 => "stabiel", 1 => "stijgend", 2 => "dalend" );
  33. my %pressure_trend_txt_fr = ( 0 => "stable", 1 => "croissant", 2 => "décroissant" );
  34. my %pressure_trend_txt_pl = ( 0 => "stabilne", 1 => "rośnie", 2 => "spada" );
  35. my %pressure_trend_txt_it = ( 0 => "stabile", 1 => "in aumento", 2 => "in diminuzione" );
  36. my %pressure_trend_sym = ( 0 => "=", 1 => "+", 2 => "-" );
  37. my @directions_txt_en = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW');
  38. my @directions_txt_de = ('N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW');
  39. my @directions_txt_nl = ('N', 'NNO', 'NO', 'ONO', 'O', 'OZO', 'ZO', 'ZZO', 'Z', 'ZZW', 'ZW', 'WZW', 'W', 'WNW', 'NW', 'NNW');
  40. my @directions_txt_fr = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSO', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO');
  41. my @directions_txt_pl = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW');
  42. my @directions_txt_it = ('N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSO', 'SO', 'OSO', 'O', 'ONO', 'NO', 'NNO');
  43. my %wdays_txt_en = ('Mon' => 'Mon', 'Tue' => 'Tue', 'Wed'=> 'Wed', 'Thu' => 'Thu', 'Fri' => 'Fri', 'Sat' => 'Sat', 'Sun' => 'Sun');
  44. my %wdays_txt_de = ('Mon' => 'Mo', 'Tue' => 'Di', 'Wed'=> 'Mi', 'Thu' => 'Do', 'Fri' => 'Fr', 'Sat' => 'Sa', 'Sun' => 'So');
  45. my %wdays_txt_nl = ('Mon' => 'Maa', 'Tue' => 'Din', 'Wed'=> 'Woe', 'Thu' => 'Don', 'Fri' => 'Vri', 'Sat' => 'Zat', 'Sun' => 'Zon');
  46. my %wdays_txt_fr= ('Mon' => 'Lun', 'Tue' => 'Mar', 'Wed'=> 'Mer', 'Thu' => 'Jeu', 'Fri' => 'Ven', 'Sat' => 'Sam', 'Sun' => 'Dim');
  47. my %wdays_txt_pl = ('Mon' => 'Pon', 'Tue' => 'Wt', 'Wed'=> 'Śr', 'Thu' => 'Czw', 'Fri' => 'Pt', 'Sat' => 'Sob', 'Sun' => 'Nie');
  48. my %wdays_txt_it = ('Mon' => 'Lun', 'Tue' => 'Mar', 'Wed'=> 'Mer', 'Thu' => 'Gio', 'Fri' => 'Ven', 'Sat' => 'Sab', 'Sun' => 'Dom');
  49. my %status_items_txt_en = ( 0 => "Wind", 1 => "Humidity", 2 => "Temperature", 3 => "Right Now", 4 => "Weather forecast for " );
  50. my %status_items_txt_de = ( 0 => "Wind", 1 => "Feuchtigkeit", 2 => "Temperatur", 3 => "Jetzt Sofort", 4 => "Wettervorhersage für " );
  51. my %status_items_txt_nl = ( 0 => "Wind", 1 => "Vochtigheid", 2 => "Temperatuur", 3 => "Direct", 4 => "Weersvoorspelling voor " );
  52. my %status_items_txt_fr = ( 0 => "Vent", 1 => "Humidité", 2 => "Température", 3 => "Maintenant", 4 => "Prévisions météo pour " );
  53. my %status_items_txt_pl = ( 0 => "Wiatr", 1 => "Wilgotność", 2 => "Temperatura", 3 => "Teraz", 4 => "Prognoza pogody w " );
  54. my %status_items_txt_it = ( 0 => "Vento", 1 => "Umidità", 2 => "Temperatura", 3 => "Adesso", 4 => "Previsioni del tempo per " );
  55. my %wdays_txt_i18n;
  56. my @directions_txt_i18n;
  57. my %pressure_trend_txt_i18n;
  58. my %status_items_txt_i18n;
  59. my @iconlist = (
  60. 'storm', 'storm', 'storm', 'thunderstorm', 'thunderstorm', 'rainsnow',
  61. 'sleet', 'snow', 'drizzle', 'drizzle', 'icy' ,'chance_of_rain',
  62. 'chance_of_rain', 'snowflurries', 'chance_of_snow', 'heavysnow', 'snow', 'sleet',
  63. 'sleet', 'dust', 'fog', 'haze', 'smoke', 'flurries',
  64. 'windy', 'icy', 'cloudy', 'mostlycloudy_night', 'mostlycloudy', 'partly_cloudy_night',
  65. 'partly_cloudy', 'sunny', 'sunny', 'mostly_clear_night', 'mostly_sunny', 'heavyrain',
  66. 'sunny', 'scatteredthunderstorms', 'scatteredthunderstorms', 'scatteredthunderstorms', 'scatteredshowers', 'heavysnow',
  67. 'chance_of_snow', 'heavysnow', 'partly_cloudy', 'heavyrain', 'chance_of_snow', 'scatteredshowers');
  68. ###################################
  69. sub Weather_LanguageInitialize($) {
  70. my ($lang) = @_;
  71. if($lang eq "de") {
  72. %wdays_txt_i18n= %wdays_txt_de;
  73. @directions_txt_i18n= @directions_txt_de;
  74. %pressure_trend_txt_i18n= %pressure_trend_txt_de;
  75. %status_items_txt_i18n= %status_items_txt_de;
  76. } elsif($lang eq "nl") {
  77. %wdays_txt_i18n= %wdays_txt_nl;
  78. @directions_txt_i18n= @directions_txt_nl;
  79. %pressure_trend_txt_i18n= %pressure_trend_txt_nl;
  80. %status_items_txt_i18n= %status_items_txt_nl;
  81. } elsif($lang eq "fr") {
  82. %wdays_txt_i18n= %wdays_txt_fr;
  83. @directions_txt_i18n= @directions_txt_fr;
  84. %pressure_trend_txt_i18n= %pressure_trend_txt_fr;
  85. %status_items_txt_i18n= %status_items_txt_fr;
  86. } elsif($lang eq "pl") {
  87. %wdays_txt_i18n= %wdays_txt_pl;
  88. @directions_txt_i18n= @directions_txt_pl;
  89. %pressure_trend_txt_i18n= %pressure_trend_txt_pl;
  90. %status_items_txt_i18n= %status_items_txt_pl;
  91. } elsif($lang eq "it") {
  92. %wdays_txt_i18n= %wdays_txt_it;
  93. @directions_txt_i18n= @directions_txt_it;
  94. %pressure_trend_txt_i18n= %pressure_trend_txt_it;
  95. %status_items_txt_i18n= %status_items_txt_it;
  96. } else {
  97. %wdays_txt_i18n= %wdays_txt_en;
  98. @directions_txt_i18n= @directions_txt_en;
  99. %pressure_trend_txt_i18n= %pressure_trend_txt_en;
  100. %status_items_txt_i18n= %status_items_txt_en;
  101. }
  102. }
  103. ###################################
  104. sub Weather_DebugCodes($) {
  105. my ($lang)= @_;
  106. my @YahooCodes_i18n= YahooWeatherAPI_getYahooCodes($lang);
  107. Debug "Weather Code List, see http://developer.yahoo.com/weather/#codes";
  108. for(my $c= 0; $c<= 47; $c++) {
  109. Debug sprintf("%2d %30s %30s", $c, $iconlist[$c], $YahooCodes_i18n[$c]);
  110. }
  111. }
  112. #####################################
  113. sub Weather_Initialize($) {
  114. my ($hash) = @_;
  115. $hash->{DefFn} = "Weather_Define";
  116. $hash->{UndefFn} = "Weather_Undef";
  117. $hash->{GetFn} = "Weather_Get";
  118. $hash->{SetFn} = "Weather_Set";
  119. $hash->{AttrList}= "disable " . $readingFnAttributes;
  120. $hash->{NotifyFn}= "Weather_Notify";
  121. #Weather_DebugCodes('de');
  122. }
  123. ###################################
  124. sub degrees_to_direction($@) {
  125. my ($degrees,@directions_txt_i18n) = @_;
  126. my $mod = int((($degrees + 11.25) % 360) / 22.5);
  127. return $directions_txt_i18n[$mod];
  128. }
  129. ###################################
  130. sub Weather_RetrieveData($$) {
  131. my ($name, $blocking) = @_;
  132. my $hash = $defs{$name};
  133. # WOEID [WHERE-ON-EARTH-ID], go to http://weather.yahoo.com to find out
  134. my $location= $hash->{LOCATION};
  135. my $units= $hash->{UNITS};
  136. my %args= (
  137. woeid => $location,
  138. format => "json",
  139. blocking => $blocking,
  140. callbackFnRef => \&Weather_RetrieveDataFinished,
  141. hash => $hash,
  142. );
  143. # this needs to be finalized to use the APIOPTIONS
  144. my $maxage= $hash->{fhem}{allowCache} ? 600 : 0; # use cached data if allowed
  145. $hash->{fhem}{allowCache}= 1;
  146. YahooWeatherAPI_RetrieveDataWithCache($maxage, \%args);
  147. }
  148. sub Weather_ReturnWithError($$$$$) {
  149. my ($hash, $doTrigger, $err, $pubDate, $pubDateComment)= @_;
  150. my $name= $hash->{NAME};
  151. $hash->{fhem}{allowCache}= 0; # do not use cache on next try
  152. Log3 $hash, 3, "$name: $err";
  153. readingsBeginUpdate($hash);
  154. readingsBulkUpdate($hash, "lastError", $err);
  155. readingsBulkUpdate($hash, "pubDateComment", $pubDateComment) if(defined($pubDateComment));
  156. readingsBulkUpdate($hash, "pubDateRemote", $pubDate) if(defined($pubDate));
  157. readingsBulkUpdate($hash, "validity", "stale");
  158. readingsEndUpdate($hash, $doTrigger);
  159. my $next= 60; # $next= $hash->{INTERVAL};
  160. Weather_RearmTimer($hash, gettimeofday()+$next);
  161. return;
  162. }
  163. sub Weather_RetrieveDataFinished($$$) {
  164. my ($argsRef, $err, $response)= @_;
  165. my $hash= $argsRef->{hash};
  166. my $name= $hash->{NAME};
  167. my $doTrigger= $argsRef->{blocking} ? 0 : 1;
  168. # check for error from retrieving data
  169. return Weather_ReturnWithError($hash, $doTrigger, $err, undef, undef) if($err);
  170. # decode JSON data from Weather Channel
  171. my $data;
  172. ($err, $data)= YahooWeatherAPI_JSONReturnChannelData($response);
  173. return Weather_ReturnWithError($hash, $doTrigger, $err, undef, undef) if($err);
  174. # check if up-to-date
  175. my ($pubDateComment, $pubDate, $pubDateTs)= YahooWeatherAPI_pubDate($data);
  176. return Weather_ReturnWithError($hash, $doTrigger, $pubDateComment, $pubDate, $pubDateComment)
  177. unless(defined($pubDateTs));
  178. my $ts= defined($hash->{READINGS}{pubDateTs}) ? $hash->{READINGS}{pubDateTs}{VAL} : 0;
  179. return Weather_ReturnWithError($hash, $doTrigger, "stale data received", $pubDate, $pubDateComment)
  180. if($ts> $pubDateTs);
  181. #
  182. # from here on we assume that $data is complete and correct
  183. #
  184. my $lang= $hash->{LANG};
  185. my @YahooCodes_i18n= YahooWeatherAPI_getYahooCodes($lang);
  186. my $item= $data->{item};
  187. readingsBeginUpdate($hash);
  188. # delete some unused readings
  189. delete($hash->{READINGS}{temp_f}) if(defined($hash->{READINGS}{temp_f}));
  190. delete($hash->{READINGS}{unit_distance}) if(defined($hash->{READINGS}{unit_distance}));
  191. delete($hash->{READINGS}{unit_speed}) if(defined($hash->{READINGS}{unit_speed}));
  192. delete($hash->{READINGS}{unit_pressuree}) if(defined($hash->{READINGS}{unit_pressuree}));
  193. delete($hash->{READINGS}{unit_temperature}) if(defined($hash->{READINGS}{unit_temperature}));
  194. # convert to metric units as far as required
  195. my $isConverted= YahooWeatherAPI_ConvertChannelData($data);
  196. # housekeeping information
  197. readingsBulkUpdate($hash, "lastError", "");
  198. readingsBulkUpdate($hash, "pubDateComment", $pubDateComment);
  199. readingsBulkUpdate($hash, "pubDate", $pubDate);
  200. readingsBulkUpdate($hash, "pubDateRemote", $pubDate);
  201. readingsBulkUpdate($hash, "pubDateTs", $pubDateTs);
  202. readingsBulkUpdate($hash, "isConverted", $isConverted);
  203. readingsBulkUpdate($hash, "validity", "up-to-date");
  204. # description
  205. readingsBulkUpdate($hash, "description", $data->{description});
  206. # location
  207. readingsBulkUpdate($hash, "city", $data->{location}{city});
  208. readingsBulkUpdate($hash, "region", $data->{location}{region});
  209. readingsBulkUpdate($hash, "country", $data->{location}{country});
  210. readingsBulkUpdate($hash, "lat", $item->{lat});
  211. readingsBulkUpdate($hash, "long", $item->{long});
  212. # wind
  213. my $windspeed= int($data->{wind}{speed}+0.5);
  214. readingsBulkUpdate($hash, "wind", $windspeed);
  215. readingsBulkUpdate($hash, "wind_speed", $windspeed);
  216. readingsBulkUpdate($hash, "wind_chill", $data->{wind}{chill});
  217. my $winddir= $data->{wind}{direction};
  218. readingsBulkUpdate($hash, "wind_direction", $winddir);
  219. my $wdir= degrees_to_direction($winddir, @directions_txt_i18n);
  220. readingsBulkUpdate($hash, "wind_condition", "Wind: $wdir $windspeed km/h");
  221. # atmosphere
  222. my $humidity= $data->{atmosphere}{humidity};
  223. readingsBulkUpdate($hash, "humidity", $humidity);
  224. my $pressure= $data->{atmosphere}{pressure};
  225. readingsBulkUpdate($hash, "pressure", $pressure);
  226. readingsBulkUpdate($hash, "visibility", int($data->{atmosphere}{visibility}+0.5));
  227. my $pressure_trend= $data->{atmosphere}{rising};
  228. readingsBulkUpdate($hash, "pressure_trend", $pressure_trend);
  229. readingsBulkUpdate($hash, "pressure_trend_txt", $pressure_trend_txt_i18n{$pressure_trend});
  230. readingsBulkUpdate($hash, "pressure_trend_sym", $pressure_trend_sym{$pressure_trend});
  231. # condition
  232. my $date= $item->{condition}{date};
  233. readingsBulkUpdate($hash, "current_date_time", $date);
  234. readingsBulkUpdate($hash, "day_of_week", $wdays_txt_i18n{substr($date,0,3)});
  235. my $code= $item->{condition}{code};
  236. readingsBulkUpdate($hash, "code", $code);
  237. readingsBulkUpdate($hash, "condition", $YahooCodes_i18n[$code]);
  238. readingsBulkUpdate($hash, "icon", $iconlist[$code]);
  239. my $temp= $item->{condition}{temp};
  240. readingsBulkUpdate($hash, "temp_c", $temp);
  241. readingsBulkUpdate($hash, "temperature", $temp);
  242. # forecast
  243. my $forecast= $item->{forecast};
  244. my $i= 0;
  245. foreach my $fc (@{$forecast}) {
  246. $i++;
  247. my $f= "fc" . $i ."_";
  248. readingsBulkUpdate($hash, $f . "day_of_week", $wdays_txt_i18n{$fc->{day}});
  249. readingsBulkUpdate($hash, $f . "date", $fc->{date});
  250. readingsBulkUpdate($hash, $f . "low_c", $fc->{low});
  251. readingsBulkUpdate($hash, $f . "high_c", $fc->{high});
  252. my $fccode= $fc->{code};
  253. readingsBulkUpdate($hash, $f . "code", $fccode);
  254. readingsBulkUpdate($hash, $f . "condition", $YahooCodes_i18n[$fccode]);
  255. readingsBulkUpdate($hash, $f . "icon", $iconlist[$fccode]);
  256. }
  257. #my $val= "T:$temp°C " . substr($status_items_txt_i18n{1}, 0, 1) .":$humidity% " . substr($status_items_txt_i18n{0}, 0, 1) . ":$windspeed km/h P:$pressure mbar";
  258. my $val= "T: $temp H: $humidity W: $windspeed P: $pressure";
  259. Log3 $hash, 4, "$name: $val";
  260. readingsBulkUpdate($hash, "state", $val);
  261. readingsEndUpdate($hash, $doTrigger);
  262. Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL});
  263. return;
  264. }
  265. ###################################
  266. sub Weather_GetUpdate($) {
  267. my ($hash) = @_;
  268. my $name = $hash->{NAME};
  269. if($attr{$name} && $attr{$name}{disable}) {
  270. Log3 $hash, 5, "Weather $name: retrieval of weather data is disabled by attribute.";
  271. readingsBeginUpdate($hash);
  272. readingsBulkUpdate($hash, "pubDateComment", "disabled by attribute");
  273. readingsBulkUpdate($hash, "validity", "stale");
  274. readingsEndUpdate($hash, 1);
  275. Weather_RearmTimer($hash, gettimeofday()+$hash->{INTERVAL});
  276. } else {
  277. Weather_RetrieveData($name, 0);
  278. }
  279. return 1;
  280. }
  281. ###################################
  282. sub Weather_Get($@) {
  283. my ($hash, @a) = @_;
  284. return "argument is missing" if(int(@a) != 2);
  285. my $reading= $a[1];
  286. my $value;
  287. if(defined($hash->{READINGS}{$reading})) {
  288. $value= $hash->{READINGS}{$reading}{VAL};
  289. } else {
  290. my $rt= "";
  291. if(defined($hash->{READINGS})) {
  292. $rt= join(" ", sort keys %{$hash->{READINGS}});
  293. }
  294. return "Unknown reading $reading, choose one of " . $rt;
  295. }
  296. return "$a[0] $reading => $value";
  297. }
  298. ###################################
  299. sub Weather_Set($@) {
  300. my ($hash, @a) = @_;
  301. my $cmd= $a[1];
  302. # usage check
  303. if((@a == 2) && ($a[1] eq "update")) {
  304. Weather_DisarmTimer($hash);
  305. Weather_GetUpdate($hash);
  306. return undef;
  307. } else {
  308. return "Unknown argument $cmd, choose one of update";
  309. }
  310. }
  311. ###################################
  312. sub Weather_RearmTimer($$) {
  313. my ($hash, $t) = @_;
  314. InternalTimer($t, "Weather_GetUpdate", $hash, 0) ;
  315. }
  316. sub Weather_DisarmTimer($) {
  317. my ($hash)= @_;
  318. RemoveInternalTimer($hash);
  319. }
  320. sub Weather_Notify($$) {
  321. my ($hash,$dev) = @_;
  322. my $name = $hash->{NAME};
  323. my $type = $hash->{TYPE};
  324. return if($dev->{NAME} ne "global");
  325. return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}}));
  326. # return if($attr{$name} && $attr{$name}{disable});
  327. # update weather after initialization or change of configuration
  328. # wait 10 to 29 seconds to avoid congestion due to concurrent activities
  329. Weather_DisarmTimer($hash);
  330. my $delay= 10+int(rand(20));
  331. #$delay= 3; # delay removed until further notice
  332. Log3 $hash, 5, "Weather $name: FHEM initialization or rereadcfg triggered update, delay $delay seconds.";
  333. Weather_RearmTimer($hash, gettimeofday()+$delay) ;
  334. return undef;
  335. }
  336. #####################################
  337. sub Weather_Define($$) {
  338. my ($hash, $def) = @_;
  339. # define <name> Weather <location> [interval]
  340. # define MyWeather Weather "Maintal,HE" 3600
  341. # define <name> Weather location=<location> [API=<API>] [interval=<interval>] [lang=<lang>]
  342. my $name;
  343. my $API="YahooWeatherAPI,transport:https,cachemaxage:600";
  344. my $location;
  345. my $interval = 3600;
  346. my $lang = "en";
  347. if($def =~ /=/) {
  348. my $usage= "syntax: define <name> Weather location=<location> [API=<API>] [lang=<lang>]";
  349. my ($arrayref, $hashref)= parseParams($def);
  350. my @a= @{$arrayref};
  351. my %h= %{$hashref};
  352. return $usage unless(scalar @a == 2);
  353. $name= $a[0];
  354. return $usage unless exists $h{location};
  355. $location= $h{location};
  356. $lang= $h{lang} if exists $h{lang};
  357. $interval= $h{interval} if exists $h{interval};
  358. $API= $h{API} if exists $h{API};
  359. } else {
  360. my @a = split("[ \t][ \t]*", $def);
  361. return "syntax: define <name> Weather <location> [interval [en|de|nl|fr|pl|it]]"
  362. if(int(@a) < 3 && int(@a) > 5);
  363. $name = $a[0];
  364. $location = $a[2];
  365. if(int(@a)>=4) { $interval= $a[3]; }
  366. if(int(@a)==5) { $lang= $a[4]; }
  367. }
  368. my ($api,$apioptions)= split(',', $API, 2);
  369. $apioptions= "" unless(defined($apioptions));
  370. eval {
  371. require "$api.pm";
  372. };
  373. return "$name: cannot load API $api: $@" if($@);
  374. $hash->{NOTIFYDEV} = "global";
  375. $hash->{STATE} = "Initialized";
  376. $hash->{fhem}{interfaces}= "temperature;humidity;wind";
  377. $hash->{LOCATION} = $location;
  378. $hash->{INTERVAL} = $interval;
  379. $hash->{LANG} = $lang;
  380. $hash->{API} = $api;
  381. $hash->{APIOPTIONS} = $apioptions;
  382. $hash->{UNITS} = "c"; # hardcoded to use degrees centigrade (Celsius)
  383. $hash->{READINGS}{current_date_time}{TIME}= TimeNow();
  384. $hash->{READINGS}{current_date_time}{VAL}= "none";
  385. $hash->{fhem}{allowCache}= 1;
  386. Weather_LanguageInitialize($lang);
  387. Weather_GetUpdate($hash) if($init_done);
  388. return undef;
  389. }
  390. #####################################
  391. sub Weather_Undef($$) {
  392. my ($hash, $arg) = @_;
  393. RemoveInternalTimer($hash);
  394. return undef;
  395. }
  396. #####################################
  397. # Icon Parameter
  398. use constant ICONHIGHT => 120;
  399. use constant ICONWIDTH => 175;
  400. use constant ICONSCALE => 0.5;
  401. #####################################
  402. sub
  403. WeatherIconIMGTag($) {
  404. my $width= int(ICONSCALE*ICONWIDTH);
  405. my ($icon)= @_;
  406. my $url= FW_IconURL("weather/$icon");
  407. my $style= " width=$width";
  408. return "<img src=\"$url\"$style alt=\"$icon\">";
  409. }
  410. #####################################
  411. sub
  412. WeatherAsHtmlV($;$)
  413. {
  414. my ($d,$items) = @_;
  415. $d = "<none>" if(!$d);
  416. $items = 10 if( !$items );
  417. return "$d is not a Weather instance<br>"
  418. if(!$defs{$d} || $defs{$d}{TYPE} ne "Weather");
  419. my $width= int(ICONSCALE*ICONWIDTH);
  420. my $ret = '<table class="weather">';
  421. $ret .= sprintf('<tr><td class="weatherIcon" width=%d>%s</td><td class="weatherValue">%s<br>%s°C %s%%<br>%s</td></tr>',
  422. $width,
  423. WeatherIconIMGTag(ReadingsVal($d, "icon", "")),
  424. ReadingsVal($d, "condition", ""),
  425. ReadingsVal($d, "temp_c", ""), ReadingsVal($d, "humidity", ""),
  426. ReadingsVal($d, "wind_condition", ""));
  427. for(my $i=1; $i<$items; $i++) {
  428. $ret .= sprintf('<tr><td class="weatherIcon" width=%d>%s</td><td class="weatherValue"><span class="weatherDay">%s: %s</span><br><span class="weatherMin">min %s°C</span> <span class="weatherMax">max %s°C</span></td></tr>',
  429. $width,
  430. WeatherIconIMGTag(ReadingsVal($d, "fc${i}_icon", "")),
  431. ReadingsVal($d, "fc${i}_day_of_week", ""),
  432. ReadingsVal($d, "fc${i}_condition", ""),
  433. ReadingsVal($d, "fc${i}_low_c", ""), ReadingsVal($d, "fc${i}_high_c", ""));
  434. }
  435. $ret .= "</table>";
  436. return $ret;
  437. }
  438. sub
  439. WeatherAsHtml($;$)
  440. {
  441. my ($d,$i) = @_;
  442. WeatherAsHtmlV($d,$i);
  443. }
  444. sub
  445. WeatherAsHtmlH($;$)
  446. {
  447. my ($d,$items) = @_;
  448. $d = "<none>" if(!$d);
  449. $items = 10 if( !$items );
  450. return "$d is not a Weather instance<br>"
  451. if(!$defs{$d} || $defs{$d}{TYPE} ne "Weather");
  452. my $width= int(ICONSCALE*ICONWIDTH);
  453. my $format= '<td><table border=1><tr><td class="weatherIcon" width=%d>%s</td></tr><tr><td class="weatherValue">%s</td></tr><tr><td class="weatherValue">%s°C %s%%</td></tr><tr><td class="weatherValue">%s</td></tr></table></td>';
  454. my $ret = '<table class="weather">';
  455. # icons
  456. $ret .= sprintf('<tr><td class="weatherIcon" width=%d>%s</td>', $width, WeatherIconIMGTag(ReadingsVal($d, "icon", "")));
  457. for(my $i=1; $i<$items; $i++) {
  458. $ret .= sprintf('<td class="weatherIcon" width=%d>%s</td>', $width, WeatherIconIMGTag(ReadingsVal($d, "fc${i}_icon", "")));
  459. }
  460. $ret .= '</tr>';
  461. # condition
  462. $ret .= sprintf('<tr><td class="weatherDay">%s</td>', ReadingsVal($d, "condition", ""));
  463. for(my $i=1; $i<$items; $i++) {
  464. $ret .= sprintf('<td class="weatherDay">%s: %s</td>', ReadingsVal($d, "fc${i}_day_of_week", ""),
  465. ReadingsVal($d, "fc${i}_condition", ""));
  466. }
  467. $ret .= '</tr>';
  468. # temp/hum | min
  469. $ret .= sprintf('<tr><td class="weatherMin">%s°C %s%%</td>', ReadingsVal($d, "temp_c", ""), ReadingsVal($d, "humidity", ""));
  470. for(my $i=1; $i<$items; $i++) {
  471. $ret .= sprintf('<td class="weatherMin">min %s°C</td>', ReadingsVal($d, "fc${i}_low_c", ""));
  472. }
  473. $ret .= '</tr>';
  474. # wind | max
  475. $ret .= sprintf('<tr><td class="weatherMax">%s</td>', ReadingsVal($d, "wind_condition", ""));
  476. for(my $i=1; $i<$items; $i++) {
  477. $ret .= sprintf('<td class="weatherMax">max %s°C</td>', ReadingsVal($d, "fc${i}_high_c", ""));
  478. }
  479. $ret .= "</tr></table>";
  480. return $ret;
  481. }
  482. sub
  483. WeatherAsHtmlD($;$)
  484. {
  485. my ($d,$i) = @_;
  486. if($FW_ss) {
  487. WeatherAsHtmlV($d,$i);
  488. } else {
  489. WeatherAsHtmlH($d,$i);
  490. }
  491. }
  492. #####################################
  493. 1;
  494. =pod
  495. =item device
  496. =item summary provides current weather condition and forecast (source: Yahoo Weather API)
  497. =item summary_DE stellt Wetterbericht und -vorhersage bereit (Quelle: Yahoo Weather API)
  498. =begin html
  499. <a name="Weather"></a>
  500. <h3>Weather</h3>
  501. <ul>
  502. You need the JSON perl module. Use <code>apt-get install libjson-perl</code> on Debian and derivatives.<br><br>
  503. <a name="Weatherdefine"></a>
  504. <b>Define</b>
  505. <ul>
  506. <code>define &lt;name&gt; Weather &lt;location&gt; [&lt;interval&gt; [&lt;language&gt;]]</code><br>
  507. <br>
  508. Defines a virtual device for weather forecasts.<br><br>
  509. A Weather device periodically gathers current and forecast weather conditions
  510. from the Yahoo Weather API.<br><br>
  511. The parameter <code>location</code> is the WOEID (WHERE-ON-EARTH-ID), go to
  512. <a href="http://weather.yahoo.com">http://weather.yahoo.com</a> to find it out for your location.<br><br>
  513. The optional parameter <code>interval</code> is the time between subsequent updates
  514. in seconds. It defaults to 3600 (1 hour).<br><br>
  515. The optional language parameter may be one of
  516. <code>de</code>,
  517. <code>en</code>,
  518. <code>pl</code>,
  519. <code>fr</code>,
  520. <code>nl</code>,
  521. <code>it</code>,
  522. It determines the natural language in which the forecast information appears.
  523. It defaults to <code>en</code>. If you want to set the language you also have to set the interval.<br><br>
  524. Examples:
  525. <pre>
  526. define MyWeather Weather 673513
  527. define Forecast Weather 673513 1800
  528. </pre>
  529. The module provides four additional functions <code>WeatherAsHtml</code>, <code>WeatherAsHtmlV</code>, <code>WeatherAsHtmlH</code> and
  530. <code>WeatherAsHtmlD</code>. The former two functions are identical: they return the HTML code for a
  531. vertically arranged weather forecast. The third function returns the HTML code for a horizontally arranged weather forecast. The
  532. latter function dynamically picks the orientation depending on wether a smallscreen style is set (vertical layout) or not (horizontal layout). Each version accepts an additional paramter to limit the numer of icons to display.<br><br>
  533. Example:
  534. <pre>
  535. define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather") }
  536. </pre>
  537. </ul>
  538. <br>
  539. <a name="Weatherset"></a>
  540. <b>Set </b>
  541. <ul>
  542. <code>set &lt;name&gt; update</code><br><br>
  543. Forces the retrieval of the weather data. The next automatic retrieval is scheduled to occur
  544. <code>interval</code> seconds later.<br><br>
  545. </ul>
  546. <br>
  547. <a name="Weatherget"></a>
  548. <b>Get</b>
  549. <ul>
  550. <code>get &lt;name&gt; &lt;reading&gt;</code><br><br>
  551. Valid readings and their meaning (? can be one of 1, 2, 3, 4, 5 and stands
  552. for today, tomorrow, etc.):<br>
  553. <table>
  554. <tr><td>city</td><td>name of town returned for location</td></tr>
  555. <tr><td>code</td><td>current condition code</td></tr>
  556. <tr><td>condition</td><td>current condition</td></tr>
  557. <tr><td>current_date_time</td><td>last update of forecast on server</td></tr>
  558. <tr><td>fc?_code</td><td>forecast condition code</td></tr>
  559. <tr><td>fc?_condition</td><td>forecast condition</td></tr>
  560. <tr><td>fc?_day_of_week</td><td>day of week for day +?</td></tr>
  561. <tr><td>fc?_high_c</td><td>forecasted daily high in degrees centigrade</td></tr>
  562. <tr><td>fc?_icon</td><td>forecast icon</td></tr>
  563. <tr><td>fc?_low_c</td><td>forecasted daily low in degrees centigrade</td></tr>
  564. <tr><td>humidity</td><td>current humidity in %</td></tr>
  565. <tr><td>icon</td><td>relative path for current icon</td></tr>
  566. <tr><td>pressure</td><td>air pressure in hPa</td></tr>
  567. <tr><td>pressure_trend</td><td>air pressure trend (0= steady, 1= rising, 2= falling)</td></tr>
  568. <tr><td>pressure_trend_txt</td><td>textual representation of air pressure trend</td></tr>
  569. <tr><td>pressure_trend_sym</td><td>symbolic representation of air pressure trend</td></tr>
  570. <tr><td>temperature</td><td>current temperature in degrees centigrade</td></tr>
  571. <tr><td>temp_c</td><td>current temperature in degrees centigrade</td></tr>
  572. <tr><td>temp_f</td><td>current temperature in degrees Fahrenheit</td></tr>
  573. <tr><td>visibility</td><td>visibility in km</td></tr>
  574. <tr><td>wind</td><td>wind speed in km/h</td></tr>
  575. <tr><td>wind_chill</td><td>wind chill in degrees centigrade</td></tr>
  576. <tr><td>wind_condition</td><td>wind direction and speed</td></tr>
  577. <tr><td>wind_direction</td><td>direction wind comes from in degrees (0 = north wind)</td></tr>
  578. <tr><td>wind_speed</td><td>same as wind</td></tr>
  579. </table>
  580. <br>
  581. The following readings help to identify whether a workaround has kicked in to avoid the retrieval of
  582. stale data from the remote server:
  583. <table>
  584. <tr><td>pubDate</td><td>publication time of forecast for current set of readings</td></tr>
  585. <tr><td>pubDateRemote</td><td>publication time of forecast as seen on remote server</td></tr>
  586. <tr><td>validity</td><td>stale, if publication time as seen on remote server is before that of current set of readings</td></tr>
  587. </table>
  588. </ul>
  589. <br>
  590. <a name="Weatherattr"></a>
  591. <b>Attributes</b>
  592. <ul>
  593. <li>disable: disables the retrieval of weather data - the timer runs according to schedule,
  594. though no data is requested from the API.</li>
  595. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  596. </ul>
  597. <br>
  598. </ul>
  599. =end html
  600. =begin html_DE
  601. <a name="Weather"></a>
  602. <h3>Weather</h3>
  603. <ul>
  604. Es wird das Perl-Modul JSON ben&ouml;tigt. Mit <code>apt-get install libjson-perl</code> kann es unter Debian und Derivaten installiert werden.<br><br>
  605. <a name="Weatherdefine"></a>
  606. <b>Define</b>
  607. <ul>
  608. <code>define &lt;name&gt; Weather &lt;location&gt; [&lt;interval&gt; [&lt;language&gt;]]</code><br>
  609. <br>
  610. Bezechnet ein virtuelles Gerät für Wettervorhersagen.<br><br>
  611. Eine solche virtuelle Wetterstation sammelt periodisch aktuelle und zukünftige Wetterdaten aus der Yahoo-Wetter-API.<br><br>
  612. Der Parameter <code>location</code> entspricht der sechsstelligen WOEID (WHERE-ON-EARTH-ID). Die WOEID für den eigenen Standort kann auf <a href="http://weather.yahoo.com">http://weather.yahoo.com</a> gefunden werden.<br><br>
  613. Der optionale Parameter <code>interval</code> gibt die Dauer in Sekunden zwischen den einzelnen Aktualisierungen der Wetterdaten an. Der Standardwert ist 3600 (1 Stunde). Wird kein Wert angegeben, gilt der Standardwert.<br><br>
  614. Der optionale Parameter für die möglichen Sprachen darf einen der folgende Werte annehmen: <code>de</code>, <code>en</code>, <code>pl</code>, <code>fr</code> oder <code>nl</code>. Er bezeichnet die natürliche Sprache, in der die Wetterinformationen dargestellt werden. Der Standardwert ist <code>en</code>. Wird für die Sprache kein Wert angegeben, gilt der Standardwert. Wird allerdings der Parameter für die Sprache gesetzt, muss ebenfalls ein Wert für das Abfrageintervall gesetzt werden.<br><br>
  615. Beispiele:
  616. <pre>
  617. define MyWeather Weather 673513
  618. define Forecast Weather 673513 1800
  619. </pre>
  620. Das Modul unterstützt zusätzlich vier verschiedene Funktionen <code>WeatherAsHtml</code>, <code>WeatherAsHtmlV</code>, <code>WeatherAsHtmlH</code> und <code>WeatherAsHtmlD</code>. Die ersten beiden Funktionen sind identisch: sie erzeugen den HTML-Code für eine vertikale Darstellung des Wetterberichtes. Die dritte Funktion liefert den HTML-Code für eine horizontale Darstellung des Wetterberichtes. Die letztgenannte Funktion wählt automatisch eine Ausrichtung, die abhängig davon ist, ob ein Smallcreen Style ausgewählt ist (vertikale Darstellung) oder nicht (horizontale Darstellung). Alle vier Funnktionen akzeptieren einen zusätzlichen optionalen Paramter um die Anzahl der darzustellenden Icons anzugeben.<br><br>
  621. Beispiel:
  622. <pre>
  623. define MyWeatherWeblink weblink htmlCode { WeatherAsHtmlH("MyWeather") }
  624. </pre>
  625. </ul>
  626. <br>
  627. <a name="Weatherset"></a>
  628. <b>Set </b>
  629. <ul>
  630. <code>set &lt;name&gt; update</code><br><br>
  631. Erzwingt eine Abfrage der Wetterdaten. Die darauffolgende Abfrage wird gemäß dem eingestellten Intervall <code>interval</code> Sekunden später durchgeführt.<br><br>
  632. </ul>
  633. <br>
  634. <a name="Weatherget"></a>
  635. <b>Get</b>
  636. <ul>
  637. <code>get &lt;name&gt; &lt;reading&gt;</code><br><br>
  638. Gültige ausgelesene Daten (readings) und ihre Bedeutung (das ? kann einen der Werte 1, 2, 3 , 4 oder 5 annehmen und steht für heute, morgen, übermorgen etc.):<br><br>
  639. <table>
  640. <tr><td>city</td><td>Name der Stadt, der aufgrund der WOEID übermittelt wird</td></tr>
  641. <tr><td>code</td><td>Code für die aktuellen Wetterverhältnisse</td></tr>
  642. <tr><td>condition</td><td>aktuelle Wetterverhältnisse</td></tr>
  643. <tr><td>current_date_time</td><td>Zeitstempel der letzten Aktualisierung der Wetterdaten vom Server</td></tr>
  644. <tr><td>fc?_code</td><td>Code für die vorhergesagten Wetterverhältnisse</td></tr>
  645. <tr><td>fc?_condition</td><td>vorhergesagte Wetterverhältnisse</td></tr>
  646. <tr><td>fc?_day_of_week</td><td>Wochentag des Tages, der durch ? dargestellt wird</td></tr>
  647. <tr><td>fc?_high_c</td><td>vorhergesagte maximale Tagestemperatur in Grad Celsius</td></tr>
  648. <tr><td>fc?_icon</td><td>Icon für Vorhersage</td></tr>
  649. <tr><td>fc?_low_c</td><td>vorhergesagte niedrigste Tagestemperatur in Grad Celsius</td></tr>
  650. <tr><td>humidity</td><td>gegenwärtige Luftfeuchtgkeit in %</td></tr>
  651. <tr><td>icon</td><td>relativer Pfad für das aktuelle Icon</td></tr>
  652. <tr><td>pressure</td><td>Luftdruck in hPa</td></tr>
  653. <tr><td>pressure_trend</td><td>Luftdrucktendenz (0= gleichbleibend, 1= steigend, 2= fallend)</td></tr>
  654. <tr><td>pressure_trend_txt</td><td>textliche Darstellung der Luftdrucktendenz</td></tr>
  655. <tr><td>pressure_trend_sym</td><td>symbolische Darstellung der Luftdrucktendenz</td></tr>
  656. <tr><td>temperature</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
  657. <tr><td>temp_c</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
  658. <tr><td>temp_f</td><td>gegenwärtige Temperatur in Grad Celsius</td></tr>
  659. <tr><td>visibility</td><td>Sichtweite in km</td></tr>
  660. <tr><td>wind</td><td>Windgeschwindigkeit in km/h</td></tr>
  661. <tr><td>wind_chill</td><td>gefühlte Temperatur in Grad Celsius</td></tr>
  662. <tr><td>wind_condition</td><td>Windrichtung und -geschwindigkeit</td></tr>
  663. <tr><td>wind_direction</td><td>Gradangabe der Windrichtung (0 = Nordwind)</td></tr>
  664. <tr><td>wind_speed</td><td>Windgeschwindigkeit in km/h (mit wind identisch)</td></tr>
  665. </table>
  666. <br>
  667. Die folgenden Daten helfen zu identifizieren, ob ein Workaround angeschlagen hat, der die Verwendung von
  668. veralteten Daten auf dem entfernten Server verhindert:
  669. <table>
  670. <tr><td>pubDate</td><td>Ver&ouml;ffentlichungszeitpunkt der Wettervorhersage in den aktuellen Daten (readings)</td></tr>
  671. <tr><td>pubDateRemote</td><td>Ver&ouml;ffentlichungszeitpunkt der Wettervorhersage auf dem entfernten Server</td></tr>
  672. <tr><td>validity</td><td>stale, wenn der Ver&ouml;ffentlichungszeitpunkt auf dem entfernten Server vor dem Zeitpunkt der aktuellen Daten (readings) liegt</td></tr>
  673. </table>
  674. </ul>
  675. <br>
  676. <a name="Weatherattr"></a>
  677. <b>Attribute</b>
  678. <ul>
  679. <li>disable: stellt die Abfrage der Wetterdaten ab - der Timer l&auml;ft gem&auml;&szlig Plan doch es werden keine Daten vom
  680. API angefordert.</li>
  681. <li><a href="#readingFnAttributes">readingFnAttributes</a></li>
  682. </ul>
  683. <br>
  684. </ul>
  685. =end html_DE
  686. =cut