59_Wunderground.pm 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. ###############################################################################
  2. # $Id: 59_Wunderground.pm 17018 2018-07-22 17:10:31Z loredo $
  3. # http://api.wunderground.com/weather/api
  4. #
  5. package main;
  6. use strict;
  7. use warnings;
  8. use vars qw(%data);
  9. use HttpUtils;
  10. use utf8;
  11. use Encode qw(encode_utf8 decode_utf8);
  12. use Unit;
  13. use Data::Dumper;
  14. # initialize ##################################################################
  15. sub Wunderground_Initialize($) {
  16. my ($hash) = @_;
  17. Log3 $hash, 5, "Wunderground_Initialize: Entering";
  18. my $webhookFWinstance =
  19. join( ",", devspec2array('TYPE=FHEMWEB:FILTER=TEMPORARY!=1') );
  20. $hash->{DefFn} = "Wunderground_Define";
  21. $hash->{UndefFn} = "Wunderground_Undefine";
  22. $hash->{SetFn} = "Wunderground_Set";
  23. $hash->{AttrFn} = "Wunderground_Attr";
  24. $hash->{DbLog_splitFn} = "Unit_DbLog_split";
  25. $hash->{parseParams} = 1;
  26. $hash->{AttrList} =
  27. "disable:0,1 disabledForIntervals do_not_notify:1,0 timeout:1,2,3,4,5 pollInterval:300,450,600,750,900 wu_lang:en,de,at,ch,nl,fr,pl wu_pws:1,0 wu_bestfct:1,0 stateReadings stateReadingsFormat:0,1 "
  28. . "wu_features:multiple-strict,alerts,almanac,astronomy,conditions,currenthurricane,forecast,forecast10day,hourly,hourly10day "
  29. . $readingFnAttributes;
  30. $hash->{readingsDesc} = {
  31. 'UV' => { rtype => 'uvi' },
  32. 'dewpoint' => { rtype => 'c', formula_symbol => 'Td', },
  33. 'dewpoint_f' => { rtype => 'f', formula_symbol => 'Td', },
  34. 'fc0_heat_index_c' => {
  35. formula_symbol => 'HI',
  36. scope => { empty_replace => '--.-' }
  37. },
  38. 'fc0_heatindex_f' => {
  39. formula_symbol => 'HI',
  40. scope => { empty_replace => '--.-' }
  41. },
  42. 'fc0_high_c' =>
  43. { rtype => 'c', format => '%i', formula_symbol => 'Th', },
  44. 'fc0_high_f' =>
  45. { rtype => 'f', format => '%i', formula_symbol => 'Th', },
  46. 'fc0_humidity' => { rtype => 'pct', formula_symbol => 'H' },
  47. 'fc0_humidity_max' => { rtype => 'pct', formula_symbol => 'H' },
  48. 'fc0_humidity_min' => { rtype => 'pct', formula_symbol => 'H' },
  49. 'fc0_icon_url' => { rtype => 'url_http' },
  50. 'fc0_icon_url_night' => { rtype => 'url_http' },
  51. 'fc0_low_c' =>
  52. { rtype => 'c', format => '%i', formula_symbol => 'Tl', },
  53. 'fc0_low_f' =>
  54. { rtype => 'f', format => '%i', formula_symbol => 'Tl', },
  55. 'fc0_rain_day' => { rtype => 'mm' },
  56. 'fc0_rain_day_in' => { rtype => 'in' },
  57. 'fc0_rain_daytime' => { rtype => 'mm' },
  58. 'fc0_rain_daytime_in' => { rtype => 'in' },
  59. 'fc0_rain_night' => { rtype => 'mm' },
  60. 'fc0_rain_night_in' => { rtype => 'in' },
  61. 'fc0_snow_day' => { rtype => 'cm' },
  62. 'fc0_snow_day_in' => { rtype => 'in' },
  63. 'fc0_snow_daytime' => { rtype => 'cm' },
  64. 'fc0_snow_daytime_in' => { rtype => 'in' },
  65. 'fc0_snow_night' => { rtype => 'cm' },
  66. 'fc0_snow_night_in' => { rtype => 'in' },
  67. 'fc0_title' => { rtype => 'weekday', showLong => 1 },
  68. 'fc0_title_night' => { rtype => 'weekday_night', showLong => 1 },
  69. 'fc0_wind_chill' => {
  70. rtype => 'c',
  71. formula_symbol => 'Wc',
  72. scope => { empty_replace => '--.-' },
  73. },
  74. 'fc0_wind_chill_f' => {
  75. rtype => 'f',
  76. formula_symbol => 'Wc',
  77. scope => { empty_replace => '--.-' }
  78. },
  79. 'fc0_wind_direction' => { rtype => 'compasspoint' },
  80. 'fc0_wind_direction_max' => { rtype => 'compasspoint' },
  81. 'fc0_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' },
  82. 'fc0_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' },
  83. 'fc0_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  84. 'fc0_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  85. 'fc1_heat_index_c' => {
  86. formula_symbol => 'HI',
  87. scope => { empty_replace => '--.-' }
  88. },
  89. 'fc1_heat_index_f' => {
  90. formula_symbol => 'HI',
  91. scope => { empty_replace => '--.-' }
  92. },
  93. 'fc1_high_c' =>
  94. { rtype => 'c', format => '%i', formula_symbol => 'Th', },
  95. 'fc1_high_f' =>
  96. { rtype => 'f', format => '%i', formula_symbol => 'Th', },
  97. 'fc1_humidity' => { rtype => 'pct', formula_symbol => 'H' },
  98. 'fc1_humidity_max' => { rtype => 'pct', formula_symbol => 'H' },
  99. 'fc1_humidity_min' => { rtype => 'pct', formula_symbol => 'H' },
  100. 'fc1_icon_url' => { rtype => 'url_http' },
  101. 'fc1_icon_url_night' => { rtype => 'url_http' },
  102. 'fc1_low_c' =>
  103. { rtype => 'c', format => '%i', formula_symbol => 'Tl', },
  104. 'fc1_low_f' =>
  105. { rtype => 'f', format => '%i', formula_symbol => 'Tl', },
  106. 'fc1_rain_day' => { rtype => 'mm' },
  107. 'fc1_rain_day_in' => { rtype => 'in' },
  108. 'fc1_rain_daytime' => { rtype => 'mm' },
  109. 'fc1_rain_daytime_in' => { rtype => 'in' },
  110. 'fc1_rain_night' => { rtype => 'mm' },
  111. 'fc1_rain_night_in' => { rtype => 'in' },
  112. 'fc1_snow_day' => { rtype => 'cm' },
  113. 'fc1_snow_day_in' => { rtype => 'in' },
  114. 'fc1_snow_night' => { rtype => 'cm' },
  115. 'fc1_snow_night_in' => { rtype => 'in' },
  116. 'fc1_title' => { rtype => 'weekday', showLong => 1 },
  117. 'fc1_title_night' => { rtype => 'weekday_night', showLong => 1 },
  118. 'fc1_wind_chill' => {
  119. rtype => 'c',
  120. formula_symbol => 'Wc',
  121. scope => { empty_replace => '--.-' },
  122. },
  123. 'fc1_wind_chill_f' => {
  124. rtype => 'f',
  125. formula_symbol => 'Wc',
  126. scope => { empty_replace => '--.-' }
  127. },
  128. 'fc1_wind_direction' => { rtype => 'compasspoint' },
  129. 'fc1_wind_direction_max' => { rtype => 'compasspoint' },
  130. 'fc1_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' },
  131. 'fc1_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' },
  132. 'fc1_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  133. 'fc1_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  134. 'fc2_heat_index_c' => {
  135. formula_symbol => 'HI',
  136. scope => { empty_replace => '--.-' }
  137. },
  138. 'fc2_heat_index_f' => {
  139. formula_symbol => 'HI',
  140. scope => { empty_replace => '--.-' }
  141. },
  142. 'fc2_high_c' =>
  143. { rtype => 'c', format => '%i', formula_symbol => 'Th', },
  144. 'fc2_high_f' =>
  145. { rtype => 'f', format => '%i', formula_symbol => 'Th', },
  146. 'fc2_humidity' => { rtype => 'pct', formula_symbol => 'H' },
  147. 'fc2_humidity_max' => { rtype => 'pct', formula_symbol => 'H' },
  148. 'fc2_humidity_min' => { rtype => 'pct', formula_symbol => 'H' },
  149. 'fc2_icon_url' => { rtype => 'url_http' },
  150. 'fc2_icon_url_night' => { rtype => 'url_http' },
  151. 'fc2_low_c' =>
  152. { rtype => 'c', format => '%i', formula_symbol => 'Tl', },
  153. 'fc2_low_f' =>
  154. { rtype => 'f', format => '%i', formula_symbol => 'Tl', },
  155. 'fc2_rain_day' => { rtype => 'mm' },
  156. 'fc2_rain_day_in' => { rtype => 'in' },
  157. 'fc2_rain_daytime' => { rtype => 'mm' },
  158. 'fc2_rain_daytime_in' => { rtype => 'in' },
  159. 'fc2_rain_night' => { rtype => 'mm' },
  160. 'fc2_rain_night_in' => { rtype => 'in' },
  161. 'fc2_snow_day' => { rtype => 'cm' },
  162. 'fc2_snow_day_in' => { rtype => 'in' },
  163. 'fc2_snow_night' => { rtype => 'cm' },
  164. 'fc2_snow_night_in' => { rtype => 'in' },
  165. 'fc2_title' => { rtype => 'weekday', showLong => 1 },
  166. 'fc2_title_night' => { rtype => 'weekday_night', showLong => 1, },
  167. 'fc2_wind_chill' => {
  168. rtype => 'c',
  169. formula_symbol => 'Wc',
  170. scope => { empty_replace => '--.-' },
  171. },
  172. 'fc2_wind_chill_f' => {
  173. rtype => 'f',
  174. formula_symbol => 'Wc',
  175. scope => { empty_replace => '--.-' }
  176. },
  177. 'fc2_wind_direction' => { rtype => 'compasspoint' },
  178. 'fc2_wind_direction_max' => { rtype => 'compasspoint' },
  179. 'fc2_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' },
  180. 'fc2_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' },
  181. 'fc2_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  182. 'fc2_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  183. 'fc3_heat_index_c' => {
  184. formula_symbol => 'HI',
  185. scope => { empty_replace => '--.-' }
  186. },
  187. 'fc3_heat_index_f' => {
  188. formula_symbol => 'HI',
  189. scope => { empty_replace => '--.-' }
  190. },
  191. 'fc3_high_c' =>
  192. { rtype => 'c', format => '%i', formula_symbol => 'Th', },
  193. 'fc3_high_f' =>
  194. { rtype => 'f', format => '%i', formula_symbol => 'Th', },
  195. 'fc3_humidity' => { rtype => 'pct', formula_symbol => 'H' },
  196. 'fc3_humidity_max' => { rtype => 'pct', formula_symbol => 'H' },
  197. 'fc3_humidity_min' => { rtype => 'pct', formula_symbol => 'H' },
  198. 'fc3_icon_url' => { rtype => 'url_http' },
  199. 'fc3_icon_url_night' => { rtype => 'url_http' },
  200. 'fc3_low_c' =>
  201. { rtype => 'c', format => '%i', formula_symbol => 'Tl', },
  202. 'fc3_low_f' =>
  203. { rtype => 'f', format => '%i', formula_symbol => 'Tl', },
  204. 'fc3_rain_day' => { rtype => 'mm' },
  205. 'fc3_rain_day_in' => { rtype => 'in' },
  206. 'fc3_rain_daytime' => { rtype => 'mm' },
  207. 'fc3_rain_daytime_in' => { rtype => 'in' },
  208. 'fc3_rain_night' => { rtype => 'mm' },
  209. 'fc3_rain_night_in' => { rtype => 'in' },
  210. 'fc3_snow_day' => { rtype => 'cm' },
  211. 'fc3_snow_day_in' => { rtype => 'in' },
  212. 'fc3_snow_night' => { rtype => 'cm' },
  213. 'fc3_snow_night_in' => { rtype => 'in' },
  214. 'fc3_title' => { rtype => 'weekday', showLong => 1 },
  215. 'fc3_title_night' => { rtype => 'weekday_night', showLong => 1 },
  216. 'fc3_wind_chill' => {
  217. rtype => 'c',
  218. formula_symbol => 'Wc',
  219. scope => { empty_replace => '--.-' },
  220. },
  221. 'fc3_wind_chill_f' => {
  222. rtype => 'f',
  223. formula_symbol => 'Wc',
  224. scope => { empty_replace => '--.-' }
  225. },
  226. 'fc3_wind_direction' => { rtype => 'compasspoint' },
  227. 'fc3_wind_direction_max' => { rtype => 'compasspoint' },
  228. 'fc3_wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' },
  229. 'fc3_wind_speed_max' => { rtype => 'kmph', formula_symbol => 'Ws' },
  230. 'fc3_wind_speed_max_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  231. 'fc3_wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' },
  232. 'feelslike_c' => { rtype => 'c', formula_symbol => 'Tf', },
  233. 'feelslike_f' => { rtype => 'f', formula_symbol => 'Tf', },
  234. 'forecast_url' => { rtype => 'url_http' },
  235. 'heat_index_c' => {
  236. formula_symbol => 'HI',
  237. scope => { empty_replace => '--.-' }
  238. },
  239. 'heat_index_f' => {
  240. formula_symbol => 'HI',
  241. scope => { empty_replace => '--.-' }
  242. },
  243. 'history_url' => { rtype => 'url_http' },
  244. 'humidity' => { rtype => 'pct', formula_symbol => 'H' },
  245. 'icon_url' => { rtype => 'url_http' },
  246. 'israining' => { rtype => 'bool', },
  247. 'lastQueryResult' => { rtype => 'oknok' },
  248. 'moon_age' => { rtype => 'd' },
  249. 'moon_pct' => { rtype => 'pct' },
  250. 'moon_rise' => { rtype => 'time' },
  251. 'moon_set' => { rtype => 'time' },
  252. 'ob_url' => { rtype => 'url_http' },
  253. 'pressure' => { rtype => 'hpamb' },
  254. 'pressure_in' => { rtype => 'inhg' },
  255. 'pressure_trend' => { rtype => 'trend' },
  256. 'rain' => { rtype => 'mm' },
  257. 'rain_day' => { rtype => 'mm' },
  258. 'rain_day_in' => { rtype => 'in' },
  259. 'rain_in' => { rtype => 'in' },
  260. 'solarradiation' => { rtype => 'wpsm' },
  261. 'sunrise' => { rtype => 'time' },
  262. 'sunset' => { rtype => 'time' },
  263. 'temp_c' => { rtype => 'c' },
  264. 'temp_f' => { rtype => 'f' },
  265. 'visibility' => {
  266. rtype => 'km',
  267. scope => { empty_replace => '--.-' }
  268. },
  269. 'visibility_mi' => {
  270. rtype => 'mi',
  271. scope => { empty_replace => '--.-' }
  272. },
  273. 'wind_chill' => {
  274. rtype => 'c',
  275. formula_symbol => 'Wc',
  276. scope => { empty_replace => '--.-' },
  277. },
  278. 'wind_chill_f' => {
  279. rtype => 'f',
  280. formula_symbol => 'Wc',
  281. scope => { empty_replace => '--.-' }
  282. },
  283. 'wind_direction' => { rtype => 'compasspoint' },
  284. 'wind_gust' => { rtype => 'kmph', formula_symbol => 'Wg' },
  285. 'wind_gust_mph' => { rtype => 'mph', formula_symbol => 'Wg' },
  286. 'wind_speed' => { rtype => 'kmph', formula_symbol => 'Ws' },
  287. 'wind_speed_mph' => { rtype => 'mph', formula_symbol => 'Ws' }
  288. };
  289. }
  290. # regular Fn ##################################################################
  291. sub Wunderground_Define($$$) {
  292. my ( $hash, $a, $h ) = @_;
  293. my $name = $hash->{NAME};
  294. my $infix = "Wunderground";
  295. Log3 $name, 5, "Wunderground $name: called function Wunderground_Define()";
  296. eval {
  297. require JSON;
  298. import JSON qw( decode_json );
  299. };
  300. return "Please install Perl JSON to use module Wunderground"
  301. if ($@);
  302. if ( int(@$a) < 4 ) {
  303. my $msg = "Wrong syntax: define <name> Wunderground <api-key> <pws-id>";
  304. Log3 $name, 4, $msg;
  305. return $msg;
  306. }
  307. $hash->{TYPE} = "Wunderground";
  308. $hash->{API_KEY} = @$a[2];
  309. $hash->{QUERY} = @$a[3];
  310. $hash->{QUERY} = "pws:" . $hash->{QUERY}
  311. if ( $hash->{QUERY} =~ /^[A-Z]{3,}\d{1,}$/ );
  312. if ( $init_done && !defined( $hash->{OLDDEF} ) ) {
  313. fhem 'attr ' . $name . ' stateReadings temp_c humidity';
  314. fhem 'attr ' . $name . ' stateReadingsFormat 1';
  315. fhem 'attr ' . $name . ' wu_features astronomy,conditions,forecast';
  316. }
  317. # start the status update timer
  318. Wunderground_GetStatus( $hash, 2 );
  319. return undef;
  320. }
  321. sub Wunderground_Undefine($$$) {
  322. my ( $hash, $a, $h ) = @_;
  323. my $name = $hash->{NAME};
  324. if ( defined( $hash->{fhem}{infix} ) ) {
  325. Wunderground_removeExtension( $hash->{fhem}{infix} );
  326. }
  327. Log3 $name, 5,
  328. "Wunderground $name: called function Wunderground_Undefine()";
  329. # Stop the internal GetStatus-Loop and exit
  330. RemoveInternalTimer($hash);
  331. # release reverse pointer
  332. delete $modules{Wunderground}{defptr}{$name};
  333. return undef;
  334. }
  335. sub Wunderground_Set($$$) {
  336. my ( $hash, $a, $h ) = @_;
  337. my $name = $hash->{NAME};
  338. Log3 $name, 5, "Wunderground $name: called function Wunderground_Set()";
  339. return "Argument is missing" if ( int(@$a) < 1 );
  340. my $usage = "Unknown argument " . @$a[1] . ", choose one of update:noArg";
  341. my $cmd = '';
  342. my $result;
  343. # update
  344. if ( lc( @$a[1] ) eq "update" ) {
  345. Log3 $name, 3, "Wunderground set $name " . @$a[1];
  346. Wunderground_GetStatus($hash);
  347. }
  348. # return usage hint
  349. else {
  350. return $usage;
  351. }
  352. return $result;
  353. }
  354. sub Wunderground_Attr(@) {
  355. my ( $cmd, $name, $attrName, $attrVal ) = @_;
  356. my $hash = $defs{$name};
  357. Log3 $name, 5, "Wunderground $name: called function Wunderground_Attr()";
  358. return
  359. "Invalid value for attribute $attrName: minimum value is 1 second, maximum 5 seconds"
  360. if ( $attrVal
  361. && $attrName eq "timeout"
  362. && ( $attrVal < 1 || $attrVal > 5 ) );
  363. return
  364. "Invalid value for attribute $attrName: minimum value is 300 seconds"
  365. if ( $attrVal && $attrName eq "pollInterval" && $attrVal < 300 );
  366. return undef;
  367. }
  368. # module Fn ####################################################################
  369. sub Wunderground_GetStatus($;$) {
  370. my ( $hash, $delay ) = @_;
  371. my $name = $hash->{NAME};
  372. $hash->{INTERVAL} = AttrVal( $name, "pollInterval", "300" );
  373. my $bestfct = AttrVal( $name, "wu_bestfct", undef );
  374. my $features =
  375. AttrVal( $name, "wu_features", "astronomy,conditions,forecast" );
  376. my $lang = AttrVal( $name, "wu_lang", "en" );
  377. my $pws = AttrVal( $name, "wu_pws", undef );
  378. my $interval = (
  379. $delay
  380. ? $delay
  381. : $hash->{INTERVAL}
  382. );
  383. Log3 $name, 5,
  384. "Wunderground $name: called function Wunderground_GetStatus()";
  385. RemoveInternalTimer($hash);
  386. InternalTimer( gettimeofday() + $interval,
  387. "Wunderground_GetStatus", $hash, 0 );
  388. return
  389. if ( $delay || AttrVal( $name, "disable", 0 ) == 1 );
  390. my $langmap = {
  391. at => 'OS',
  392. de => 'DL',
  393. 'en-gb' => 'LI',
  394. gb => 'LI',
  395. uk => 'LI',
  396. };
  397. if ( defined( $langmap->{ lc($lang) } ) ) {
  398. $hash->{LANG} = $langmap->{ lc($lang) };
  399. }
  400. else {
  401. $hash->{LANG} = uc($lang);
  402. }
  403. $features =~ s/,{2,}/,/g;
  404. $features =~ s/\s//g;
  405. $features =~ s/,/\//g;
  406. $features .= "/lang:" . $hash->{LANG};
  407. $features .= "/pws:$pws" if ( defined($pws) );
  408. $features .= "/bestfct:$bestfct" if ( defined($bestfct) );
  409. $hash->{FEATURES} = $features;
  410. Wunderground_SendCommand( $hash, $features );
  411. return;
  412. }
  413. sub Wunderground_SendCommand($$) {
  414. my ( $hash, $features ) = @_;
  415. my $name = $hash->{NAME};
  416. my $apikey = $hash->{API_KEY};
  417. my $query = $hash->{QUERY};
  418. my $URL =
  419. "https://api.wunderground.com/api/%APIKEY%/%FEATURES%/q/%QUERY%.json";
  420. Log3 $name, 5,
  421. "Wunderground $name: called function Wunderground_SendCommand()";
  422. $URL =~ s/%APIKEY%/$apikey/;
  423. $URL =~ s/%FEATURES%/$features/;
  424. $URL =~ s/%QUERY%/$query/;
  425. Log3 $name, 5, "Wunderground $name: GET " . urlDecode($URL);
  426. HttpUtils_NonblockingGet(
  427. {
  428. url => $URL,
  429. timeout => AttrVal( $name, "timeout", "3" ),
  430. hash => $hash,
  431. method => "GET",
  432. callback => \&Wunderground_ReceiveCommand,
  433. httpversion => "1.1",
  434. loglevel => AttrVal( $name, "httpLoglevel", 4 ),
  435. header => {
  436. Agent => 'FHEM-Wunderground/1.0.0',
  437. 'User-Agent' => 'FHEM-Wunderground/1.0.0',
  438. Accept => 'application/json;charset=UTF-8',
  439. 'Accept-Charset' => 'UTF-8',
  440. },
  441. }
  442. );
  443. return;
  444. }
  445. sub Wunderground_ReceiveCommand($$$) {
  446. my ( $param, $err, $data ) = @_;
  447. my $hash = $param->{hash};
  448. my $name = $hash->{NAME};
  449. my $lastQueryResult =
  450. ReadingsVal( $name, "lastQueryResult", "Initialized" );
  451. my $state = "Initialized";
  452. my $return;
  453. Log3 $name, 5,
  454. "Wunderground $name: called function Wunderground_ReceiveCommand()";
  455. readingsBeginUpdate($hash);
  456. # service not reachable
  457. if ($err) {
  458. Log3 $name, 4, "Wunderground $name: RCV TIMEOUT: $err";
  459. $lastQueryResult = "unavailable";
  460. }
  461. # data received
  462. elsif ($data) {
  463. Log3 $name, 4, "Wunderground $name: RCV";
  464. if ( $data ne "" ) {
  465. # fix malformed JSON ...
  466. $data =~ s/^[\s\r\n0-9a-zA-Z]*//;
  467. $data =~ s/[\s\r\n0-9a-zA-Z]*$//;
  468. $data =~ s/[\r\n]+[0-9a-zA-Z]+[\r\n]+//g;
  469. eval '$return = decode_json( Encode::encode_utf8($data) ); 1';
  470. if ($@) {
  471. Log3 $name, 5,
  472. "Wunderground $name: RES ERROR - unable to parse malformed JSON: $@\n"
  473. . $data;
  474. return undef;
  475. }
  476. else {
  477. Log3 $name, 5, "Wunderground $name: RES\n$data";
  478. }
  479. }
  480. $lastQueryResult = "undefined";
  481. #######################
  482. # process return data
  483. #
  484. if ( $return && ref($return) eq "HASH" ) {
  485. $lastQueryResult = Wunderground_Hash2Readings( $hash, $return );
  486. }
  487. }
  488. # state
  489. my $stateReadings = AttrVal( $name, "stateReadings", "" );
  490. my $stateReadingsLang = AttrVal( $name, "stateReadingsLang", "en" );
  491. my $stateReadingsFormat = AttrVal( $name, "stateReadingsFormat", "0" );
  492. # $state =
  493. # makeSTATE( $name, $stateReadings,
  494. # $stateReadingsLang, $stateReadingsFormat );
  495. $state = makeSTATE( $name, $stateReadings, $stateReadingsFormat );
  496. readingsBulkUpdate( $hash, "state", $state );
  497. readingsBulkUpdateIfChanged( $hash, "lastQueryResult", $lastQueryResult );
  498. readingsEndUpdate( $hash, 1 );
  499. return;
  500. }
  501. sub Wunderground_Hash2Readings($$;$);
  502. sub Wunderground_Hash2Readings($$;$) {
  503. my ( $hash, $h, $r ) = @_;
  504. my $name = $hash->{NAME};
  505. my $lang = AttrVal( $name, "wu_lang", "en" );
  506. my $loop = defined($r) ? 1 : 0;
  507. if ( ref($h) eq "HASH" ) {
  508. foreach my $k ( keys %{$h} ) {
  509. # error
  510. return $h->{response}{error}{type}
  511. if ( $k eq "response"
  512. && defined( $h->{response}{error}{type} ) );
  513. next
  514. if ( $k eq "image"
  515. || $k eq "response"
  516. || $k eq "station_id"
  517. || $k =~ /^.*_string$/ );
  518. my $reading;
  519. my $cr = $k;
  520. # hash level1 renaming
  521. $cr = "" if ( $cr eq "current_observation" );
  522. # custom reading
  523. $cr = "condition" if ( $cr eq "weather" );
  524. $cr = "dewpoint" if ( $cr eq "dewpoint_c" );
  525. $cr = "humidity" if ( $cr eq "relative_humidity" );
  526. $cr = "pressure" if ( $cr eq "pressure_mb" );
  527. $cr = "rain" if ( $cr eq "precip_1hr_metric" );
  528. $cr = "rain_day" if ( $cr eq "precip_today_metric" );
  529. $cr = "rain_in" if ( $cr eq "precip_1hr_in" );
  530. $cr = "rain_day_in" if ( $cr eq "precip_today_in" );
  531. $cr = "wind_direction" if ( $cr eq "wind_degrees" );
  532. $cr = "wind_gust" if ( $cr eq "wind_gust_kph" );
  533. $cr = "wind_gust_mph" if ( $cr eq "wind_gust_mph" );
  534. $cr = "wind_speed" if ( $cr eq "wind_kph" );
  535. $cr = "wind_speed_mph" if ( $cr eq "wind_mph" );
  536. $cr = "wind_chill" if ( $cr eq "windchill_c" );
  537. $cr = "wind_chill_f" if ( $cr eq "windchill_f" );
  538. $cr = "visibility" if ( $cr eq "visibility_km" );
  539. next
  540. if ( $cr =~ /^sun_phase(.*)$/
  541. || $cr eq "date"
  542. || $cr eq "wind_dir" );
  543. next if ( $r && $r =~ /^display_location.*$/ );
  544. next
  545. if ( $k eq "wind_degrees"
  546. && ( !looks_like_number( $h->{$k} ) || $h->{$k} < 0 ) );
  547. # observation_*
  548. if ( $cr =~ /^observation_.*$/ ) {
  549. $hash->{LAST_OBSERVATION} = $h->{observation_epoch};
  550. next;
  551. }
  552. # local_*
  553. elsif ( $cr =~ /^local_.*$/ ) {
  554. $hash->{LAST} = $h->{local_epoch};
  555. next;
  556. }
  557. # moon_phase
  558. elsif ( $cr =~ /^moon_phase(.*)$/ ) {
  559. my $sunrise = $h->{moon_phase}{sunrise}{hour} . ":"
  560. . $h->{moon_phase}{sunrise}{minute};
  561. my $sunset = $h->{moon_phase}{sunset}{hour} . ":"
  562. . $h->{moon_phase}{sunset}{minute};
  563. my $moonrise = $h->{moon_phase}{moonrise}{hour} . ":"
  564. . $h->{moon_phase}{moonrise}{minute};
  565. my $moonset = $h->{moon_phase}{moonset}{hour} . ":"
  566. . $h->{moon_phase}{moonset}{minute};
  567. $sunrise =~ s/^(\d):(\d\d)$/0$1:$2/;
  568. $sunrise =~ s/^(\d\d):(\d)$/$1:0$2/;
  569. $sunset =~ s/^(\d):(\d\d)$/0$1:$2/;
  570. $sunset =~ s/^(\d\d):(\d)$/$1:0$2/;
  571. $moonrise =~ s/^(\d):(\d\d)$/0$1:$2/;
  572. $moonrise =~ s/^(\d\d):(\d)$/$1:0$2/;
  573. $moonset =~ s/^(\d):(\d\d)$/0$1:$2/;
  574. $moonset =~ s/^(\d\d):(\d)$/$1:0$2/;
  575. readingsBulkUpdate( $hash, "sunrise", $sunrise );
  576. readingsBulkUpdate( $hash, "sunset", $sunset );
  577. readingsBulkUpdate( $hash, "moonrise", $moonrise );
  578. readingsBulkUpdate( $hash, "moonset", $moonset );
  579. readingsBulkUpdate( $hash, "moon_age",
  580. $h->{moon_phase}{ageOfMoon} );
  581. readingsBulkUpdate( $hash, "moon_pct",
  582. $h->{moon_phase}{percentIlluminated} );
  583. readingsBulkUpdate( $hash, "moon_phase",
  584. $h->{moon_phase}{phaseofMoon} );
  585. }
  586. # hourly_forecast
  587. elsif ($r
  588. && $r =~ /^hourly_forecast(\d+)$/ )
  589. {
  590. my $period = $r;
  591. $period =~ s/[^\d]//g;
  592. $period++;
  593. $reading = "hfc" . $period . "_";
  594. readingsBulkUpdate( $hash, $reading . "condition",
  595. $h->{conditions} );
  596. readingsBulkUpdate(
  597. $hash,
  598. $reading . "dewpoint_c",
  599. $h->{dewpoint}{metric}
  600. );
  601. readingsBulkUpdate(
  602. $hash,
  603. $reading . "dewpoint_f",
  604. $h->{dewpoint}{english}
  605. );
  606. readingsBulkUpdate(
  607. $hash,
  608. $reading . "feelslike_c",
  609. $h->{feelslike}{metric}
  610. );
  611. readingsBulkUpdate(
  612. $hash,
  613. $reading . "feelslike_f",
  614. $h->{feelslike}{english}
  615. );
  616. readingsBulkUpdate(
  617. $hash,
  618. $reading . "heat_index_c",
  619. $h->{heatindex}{metric}
  620. ) if ( $h->{heatindex}{metric} ne "-9999" );
  621. readingsBulkUpdateIfChanged( $hash, $reading . "heat_index_c",
  622. " " )
  623. if ( $h->{heatindex}{metric} eq "-9999" );
  624. readingsBulkUpdate(
  625. $hash,
  626. $reading . "heat_index_f",
  627. $h->{heatindex}{english}
  628. ) if ( $h->{heatindex}{english} ne "-9999" );
  629. readingsBulkUpdateIfChanged( $hash, $reading . "heat_index_f",
  630. " " )
  631. if ( $h->{heatindex}{english} eq "-9999" );
  632. readingsBulkUpdate( $hash, $reading . "humidity",
  633. $h->{humidity} );
  634. readingsBulkUpdate( $hash, $reading . "icon", $h->{icon} );
  635. readingsBulkUpdate( $hash, $reading . "icon_url",
  636. $h->{icon_url} );
  637. readingsBulkUpdate(
  638. $hash,
  639. $reading . "mslp_c",
  640. $h->{mslp}{metric}
  641. );
  642. readingsBulkUpdate(
  643. $hash,
  644. $reading . "mslp_f",
  645. $h->{mslp}{english}
  646. );
  647. readingsBulkUpdate( $hash, $reading . "pop", $h->{pop} );
  648. readingsBulkUpdate(
  649. $hash,
  650. $reading . "temp_c",
  651. $h->{temp}{metric}
  652. );
  653. readingsBulkUpdate(
  654. $hash,
  655. $reading . "temp_f",
  656. $h->{temp}{english}
  657. );
  658. readingsBulkUpdate(
  659. $hash,
  660. $reading . "rain",
  661. $h->{qpf}{metric}
  662. );
  663. readingsBulkUpdate(
  664. $hash,
  665. $reading . "rain_in",
  666. $h->{qpf}{english}
  667. );
  668. readingsBulkUpdate(
  669. $hash,
  670. $reading . "snow",
  671. $h->{snow}{metric}
  672. );
  673. readingsBulkUpdate(
  674. $hash,
  675. $reading . "snow_in",
  676. $h->{snow}{english}
  677. );
  678. readingsBulkUpdate(
  679. $hash,
  680. $reading . "wind_direction",
  681. $h->{wdir}{degrees}
  682. );
  683. readingsBulkUpdate(
  684. $hash,
  685. $reading . "wind_chill",
  686. $h->{windchill}{metric}
  687. ) if ( $h->{windchill}{metric} ne "-9999" );
  688. readingsBulkUpdateIfChanged( $hash, $reading . "wind_chill",
  689. " " )
  690. if ( $h->{windchill}{metric} eq "-9999" );
  691. readingsBulkUpdate(
  692. $hash,
  693. $reading . "wind_chill_f",
  694. $h->{windchill}{english}
  695. ) if ( $h->{windchill}{english} ne "-9999" );
  696. readingsBulkUpdateIfChanged( $hash, $reading . "wind_chill_f",
  697. " " )
  698. if ( $h->{windchill}{english} eq "-9999" );
  699. readingsBulkUpdate(
  700. $hash,
  701. $reading . "wind_speed",
  702. $h->{wspd}{metric}
  703. );
  704. readingsBulkUpdate(
  705. $hash,
  706. $reading . "wind_speed_mph",
  707. $h->{wspd}{english}
  708. );
  709. readingsBulkUpdate( $hash, $reading . "sky", $h->{sky} );
  710. readingsBulkUpdate( $hash, $reading . "UV", $h->{uvi} );
  711. my $time = $h->{FCTTIME}{hour} . ":" . $h->{FCTTIME}{min};
  712. $time =~ s/^(\d):(\d\d)$/0$1:$2/;
  713. readingsBulkUpdate( $hash, $reading . "time", $time );
  714. }
  715. # simpleforecast
  716. elsif ($r
  717. && $r =~ /^forecast\/simpleforecast\/forecastday(\d+)$/ )
  718. {
  719. my $period = $h->{period} - 1;
  720. $reading = "fc" . $period . "_";
  721. readingsBulkUpdate( $hash, $reading . "condition",
  722. $h->{conditions} );
  723. readingsBulkUpdate(
  724. $hash,
  725. $reading . "high_c",
  726. $h->{high}{celsius}
  727. );
  728. readingsBulkUpdate(
  729. $hash,
  730. $reading . "high_f",
  731. $h->{high}{fahrenheit}
  732. );
  733. readingsBulkUpdate( $hash, $reading . "humidity",
  734. $h->{avehumidity} );
  735. readingsBulkUpdate( $hash, $reading . "humidity_min",
  736. $h->{minhumidity} );
  737. readingsBulkUpdate( $hash, $reading . "humidity_max",
  738. $h->{maxhumidity} );
  739. readingsBulkUpdate( $hash, $reading . "icon", $h->{icon} );
  740. readingsBulkUpdate( $hash, $reading . "icon_url",
  741. $h->{icon_url} );
  742. readingsBulkUpdate(
  743. $hash,
  744. $reading . "low_c",
  745. $h->{low}{celsius}
  746. );
  747. readingsBulkUpdate(
  748. $hash,
  749. $reading . "low_f",
  750. $h->{low}{fahrenheit}
  751. );
  752. readingsBulkUpdate( $hash, $reading . "pop", $h->{pop} );
  753. readingsBulkUpdate(
  754. $hash,
  755. $reading . "rain_day",
  756. $h->{qpf_allday}{mm}
  757. );
  758. readingsBulkUpdate(
  759. $hash,
  760. $reading . "rain_day_in",
  761. $h->{qpf_allday}{in}
  762. );
  763. readingsBulkUpdate(
  764. $hash,
  765. $reading . "rain_daytime",
  766. $h->{qpf_day}{mm}
  767. );
  768. readingsBulkUpdate(
  769. $hash,
  770. $reading . "rain_daytime_in",
  771. $h->{qpf_day}{in}
  772. );
  773. readingsBulkUpdate(
  774. $hash,
  775. $reading . "rain_night",
  776. $h->{qpf_night}{mm}
  777. );
  778. readingsBulkUpdate(
  779. $hash,
  780. $reading . "rain_night_in",
  781. $h->{qpf_night}{in}
  782. );
  783. readingsBulkUpdate(
  784. $hash,
  785. $reading . "snow_day",
  786. $h->{snow_allday}{cm}
  787. );
  788. readingsBulkUpdate(
  789. $hash,
  790. $reading . "snow_day_in",
  791. $h->{snow_allday}{in}
  792. );
  793. readingsBulkUpdate(
  794. $hash,
  795. $reading . "snow_daytime",
  796. $h->{snow_day}{cm}
  797. );
  798. readingsBulkUpdate(
  799. $hash,
  800. $reading . "snow_daytime_in",
  801. $h->{snow_day}{in}
  802. );
  803. readingsBulkUpdate(
  804. $hash,
  805. $reading . "snow_night",
  806. $h->{snow_night}{cm}
  807. );
  808. readingsBulkUpdate(
  809. $hash,
  810. $reading . "snow_night_in",
  811. $h->{snow_night}{in}
  812. );
  813. readingsBulkUpdate(
  814. $hash,
  815. $reading . "wind_direction",
  816. $h->{avewind}{degrees}
  817. );
  818. readingsBulkUpdate(
  819. $hash,
  820. $reading . "wind_direction_max",
  821. $h->{maxwind}{degrees}
  822. );
  823. readingsBulkUpdate(
  824. $hash,
  825. $reading . "wind_speed",
  826. $h->{avewind}{kph}
  827. );
  828. readingsBulkUpdate(
  829. $hash,
  830. $reading . "wind_speed_mph",
  831. $h->{avewind}{mph}
  832. );
  833. readingsBulkUpdate(
  834. $hash,
  835. $reading . "wind_speed_max",
  836. $h->{maxwind}{kph}
  837. );
  838. readingsBulkUpdate(
  839. $hash,
  840. $reading . "wind_speed_max_mph",
  841. $h->{maxwind}{mph}
  842. );
  843. last;
  844. }
  845. # txt_forecast
  846. elsif ($r
  847. && $r =~ /^forecast\/txt_forecast\/forecastday(\d+)$/ )
  848. {
  849. my $period = $h->{period};
  850. my $night =
  851. ( $period eq "1"
  852. || $period eq "3"
  853. || $period eq "5"
  854. || $period eq "7" ? "_night" : "" );
  855. if ( $period < 2 ) {
  856. $period = "0";
  857. }
  858. elsif ( $period < 4 ) {
  859. $period = "1";
  860. }
  861. elsif ( $period < 6 ) {
  862. $period = "2";
  863. }
  864. elsif ( $period < 8 ) {
  865. $period = "3";
  866. }
  867. $reading = "fc" . $period . "_";
  868. my $symbol_c =
  869. Encode::encode_utf8( chr(0x202F) . chr(0x00B0) . 'C' );
  870. my $symbol_f =
  871. Encode::encode_utf8( chr(0x202F) . chr(0x00B0) . 'F' );
  872. my $symbol_pct = Encode::encode_utf8( chr(0x202F) . '%' );
  873. # my $symbol_kmh = Encode::encode_utf8(chr(0x00A0) . 'km/h');
  874. # my $symbol_mph = Encode::encode_utf8(chr(0x00A0) . 'mph');
  875. $h->{fcttext_metric} =~ s/(\d+)C/$1$symbol_c/g;
  876. $h->{fcttext} =~ s/(\d+)F/$1$symbol_f/g;
  877. $h->{fcttext_metric} =~ s/(\d+)\s*%/$1$symbol_pct/g;
  878. $h->{fcttext} =~ s/(\d+)\s*%/$1$symbol_pct/g;
  879. # $h->{fcttext_metric} =~ s/(\d)\s*km\/h/$1$symbol_kmh/g;
  880. # $h->{fcttext} =~ s/(\d+)\s*km\/h/$1$symbol_kmh/g;
  881. # $h->{fcttext_metric} =~ s/(\d)\s*mph/$1$symbol_mph/g;
  882. # $h->{fcttext} =~ s/(\d+)\s*mph/$1$symbol_mph/g;
  883. readingsBulkUpdate( $hash, $reading . "icon$night",
  884. $h->{icon} );
  885. readingsBulkUpdate( $hash, $reading . "icon_url$night",
  886. $h->{icon_url} );
  887. readingsBulkUpdate( $hash, $reading . "pop$night", $h->{pop} );
  888. readingsBulkUpdate( $hash, $reading . "title$night",
  889. $h->{title} );
  890. readingsBulkUpdate( $hash, $reading . "text$night",
  891. $h->{fcttext_metric} );
  892. readingsBulkUpdate( $hash, $reading . "text_f$night",
  893. $h->{fcttext} );
  894. $hash->{readingDesc}{"title$night"}{lang} = $lang if ($lang);
  895. $hash->{readingDesc}{"text$night"}{lang} = $lang if ($lang);
  896. $hash->{readingDesc}{"text_f$night"}{lang} = $lang if ($lang);
  897. last;
  898. }
  899. # almanac/temp_high
  900. elsif ($r
  901. && $r =~ /^almanac\/temp_high(.*)$/ )
  902. {
  903. $reading = "almanac_high_";
  904. readingsBulkUpdate( $hash,
  905. $reading . "year", $h->{recordyear} );
  906. readingsBulkUpdate( $hash, $reading . "c", $h->{normal}{C} );
  907. readingsBulkUpdate( $hash, $reading . "f", $h->{normal}{F} );
  908. readingsBulkUpdate( $hash, $reading . "record_c",
  909. $h->{record}{C} );
  910. readingsBulkUpdate( $hash, $reading . "record_f",
  911. $h->{record}{F} );
  912. }
  913. elsif ($r
  914. && $r =~ /^almanac\/temp_low(.*)$/ )
  915. {
  916. $reading = "almanac_low_";
  917. readingsBulkUpdate( $hash,
  918. $reading . "year", $h->{recordyear} );
  919. readingsBulkUpdate( $hash, $reading . "c", $h->{normal}{C} );
  920. readingsBulkUpdate( $hash, $reading . "f", $h->{normal}{F} );
  921. readingsBulkUpdate( $hash, $reading . "record_c",
  922. $h->{record}{C} );
  923. readingsBulkUpdate( $hash, $reading . "record_f",
  924. $h->{record}{F} );
  925. }
  926. elsif ( ref( $h->{$k} ) eq "HASH" || ref( $h->{$k} ) eq "ARRAY" ) {
  927. $reading .= $r . "/" if ( $r && $r ne "" );
  928. $reading .= $cr;
  929. Wunderground_Hash2Readings( $hash, $h->{$k}, $reading );
  930. }
  931. else {
  932. $reading .= $r . "_." if ( $r && $r ne "" );
  933. $reading .= $cr;
  934. my $value = $h->{$k};
  935. $value = "" if ( !defined($value) || $value =~ m/^n\/?a$/i );
  936. if ( $reading eq "icon" || $reading eq "icon_url" ) {
  937. my $sunrise = ReadingsVal( $hash->{NAME}, "sunrise", 0 );
  938. my $sunset = ReadingsVal( $hash->{NAME}, "sunset", 0 );
  939. $sunrise = time_str2num("1970-01-01 $sunrise:00")
  940. if ($sunrise);
  941. $sunset = time_str2num("1970-01-01 $sunset:00")
  942. if ($sunset);
  943. my (
  944. $second, $minute, $hour,
  945. $dayOfMonth, $month, $yearOffset,
  946. $dayOfWeek, $dayOfYear, $daylightSavings
  947. ) = localtime();
  948. my $currentTime = ( ( $hour * 3600 ) + ( $minute * 60 ) );
  949. # for icon and icon_url, make sure to add nt_ prefix
  950. # during the night
  951. if (
  952. ( $currentTime > 43200 && $currentTime >= $sunset )
  953. || ( $currentTime <= 43200
  954. && $currentTime < $sunrise )
  955. )
  956. {
  957. $value = "nt_" . $value if ( $reading eq "icon" );
  958. $value = $1 . "nt_" . $2 . $3
  959. if ( $reading eq "icon_url"
  960. && $value =~ /^(.*\/)([A-Za-z0-9]+)(\.[a-z]+)$/ );
  961. }
  962. # for icon and icon_url, make sure to remove nt_ prefix
  963. # during the daytime
  964. else {
  965. $value =~ s/^nt_// if ( $reading eq "icon" );
  966. $value =~ s/\/nt_// if ( $reading eq "icon_url" );
  967. }
  968. }
  969. elsif ( $reading eq "almanac_.airport_code" ) {
  970. $reading = "almanac_airport_code";
  971. }
  972. $value = "0"
  973. if ( $reading =~ /^wind_(gust|speed).*$/
  974. && looks_like_number($value)
  975. && $value < 0 );
  976. $value =~ s/^(\d+)%$/$1/;
  977. readingsBulkUpdate( $hash, $reading, $value );
  978. }
  979. }
  980. }
  981. elsif ( ref($h) eq "ARRAY" ) {
  982. my $i = 0;
  983. foreach ( @{$h} ) {
  984. if ( ref($_) eq "HASH" || ref($_) eq "ARRAY" ) {
  985. Wunderground_Hash2Readings( $hash, $_, $r . $i );
  986. }
  987. else {
  988. readingsBulkUpdate( $hash, $r . $i, $_ );
  989. }
  990. $i++;
  991. }
  992. }
  993. return "ok" if ( !$loop );
  994. }
  995. 1;
  996. =pod
  997. =item device
  998. =item summary Get weather data and forecast from Weather Underground
  999. =item summary_DE Ruft Wetterdaten und Vorhersage von Weather Underground ab
  1000. =begin html
  1001. <a name="Wunderground" id="Wunderground"></a>
  1002. <h3>Wunderground</h3>
  1003. <ul>
  1004. This module gets weather data and forecast from <a href="http://www.wunderground.com/">Weather Underground</a> weather service.
  1005. <br><br>
  1006. <a name="Wundergrounddefine"></a>
  1007. <b>Define</b>
  1008. <ul><br>
  1009. <code>define &lt;name&gt; Wunderground &lt;api-key&gt; &lt;query&gt;</code>
  1010. <br><br>
  1011. Example:
  1012. <ul><br>
  1013. <code>
  1014. define WUweather Wunderground d123ab11bb2c3456 EDDF<br>
  1015. define WUweather Wunderground d123ab11bb2c3456 pws:IBAYERNM70<br>
  1016. define WUweather Wunderground d123ab11bb2c3456 Germany/Berlin<br>
  1017. </code><br>
  1018. </ul>
  1019. <br>
  1020. </ul>
  1021. <br><br>
  1022. <a name="Wundergroundset"></a>
  1023. <b>Set</b>
  1024. <ul>
  1025. <li>update - refresh data</li>
  1026. </ul>
  1027. <br><br>
  1028. <a name="Wundergroundattr"></a>
  1029. <b>Attributes</b>
  1030. <ul>
  1031. <li>pollInterval - Set regular polling interval in seconds (default=300)</li>
  1032. <li>wu_bestfct - Use Weather Undergrond Best Forecast for forecast (default=1)</li>
  1033. <li>wu_features - One or more of the data features to be fetched (default=astronomy,conditions,forecast)</li>
  1034. <li>wu_lang - Returns the API response in the specified language (default=en)</li>
  1035. <li>wu_pws - Use personal weather stations for conditions (default=1)</li>
  1036. </ul>
  1037. <br><br>
  1038. </ul>
  1039. =end html
  1040. =begin html_DE
  1041. <a name="Wunderground" id="Wunderground"></a>
  1042. <h3>Wunderground</h3>
  1043. <ul>
  1044. Eine deutsche Version der Dokumentation ist derzeit nicht vorhanden. Die englische Version ist hier zu finden:
  1045. </ul>
  1046. <ul>
  1047. <a href='http://fhem.de/commandref.html#Wunderground'>Wunderground</a>
  1048. </ul>
  1049. =end html_DE
  1050. =cut