Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

currentweather.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. /* eslint-disable */
  2. /* Magic Mirror
  3. * Module: CurrentWeather
  4. *
  5. * By Michael Teeuw https://michaelteeuw.nl
  6. * MIT Licensed.
  7. *
  8. * This module is deprecated. Any additional feature will no longer be merged.
  9. */
  10. Module.register("currentweather", {
  11. // Default module config.
  12. defaults: {
  13. location: false,
  14. locationID: false,
  15. appid: "",
  16. units: config.units,
  17. updateInterval: 10 * 60 * 1000, // every 10 minutes
  18. animationSpeed: 1000,
  19. timeFormat: config.timeFormat,
  20. showPeriod: true,
  21. showPeriodUpper: false,
  22. showWindDirection: true,
  23. showWindDirectionAsArrow: false,
  24. useBeaufort: true,
  25. useKMPHwind: false,
  26. lang: config.language,
  27. decimalSymbol: ".",
  28. showHumidity: false,
  29. showSun: true,
  30. degreeLabel: false,
  31. showIndoorTemperature: false,
  32. showIndoorHumidity: false,
  33. showFeelsLike: true,
  34. initialLoadDelay: 0, // 0 seconds delay
  35. retryDelay: 2500,
  36. apiVersion: "2.5",
  37. apiBase: "https://api.openweathermap.org/data/",
  38. weatherEndpoint: "weather",
  39. appendLocationNameToHeader: true,
  40. useLocationAsHeader: false,
  41. calendarClass: "calendar",
  42. tableClass: "large",
  43. onlyTemp: false,
  44. hideTemp: false,
  45. roundTemp: false,
  46. iconTable: {
  47. "01d": "day-sunny",
  48. "02d": "day-cloudy",
  49. "03d": "cloudy",
  50. "04d": "cloudy-windy",
  51. "09d": "showers",
  52. "10d": "rain",
  53. "11d": "thunderstorm",
  54. "13d": "snow",
  55. "50d": "fog",
  56. "01n": "night-clear",
  57. "02n": "night-cloudy",
  58. "03n": "night-cloudy",
  59. "04n": "night-cloudy",
  60. "09n": "night-showers",
  61. "10n": "night-rain",
  62. "11n": "night-thunderstorm",
  63. "13n": "night-snow",
  64. "50n": "night-alt-cloudy-windy"
  65. }
  66. },
  67. // create a variable for the first upcoming calendar event. Used if no location is specified.
  68. firstEvent: false,
  69. // create a variable to hold the location name based on the API result.
  70. fetchedLocationName: "",
  71. // Define required scripts.
  72. getScripts: function () {
  73. return ["moment.js"];
  74. },
  75. // Define required scripts.
  76. getStyles: function () {
  77. return ["weather-icons.css", "currentweather.css"];
  78. },
  79. // Define required translations.
  80. getTranslations: function () {
  81. // The translations for the default modules are defined in the core translation files.
  82. // Therefor we can just return false. Otherwise we should have returned a dictionary.
  83. // If you're trying to build your own module including translations, check out the documentation.
  84. return false;
  85. },
  86. // Define start sequence.
  87. start: function () {
  88. Log.info("Starting module: " + this.name);
  89. // Set locale.
  90. moment.locale(config.language);
  91. this.windSpeed = null;
  92. this.windDirection = null;
  93. this.windDeg = null;
  94. this.sunriseSunsetTime = null;
  95. this.sunriseSunsetIcon = null;
  96. this.temperature = null;
  97. this.indoorTemperature = null;
  98. this.indoorHumidity = null;
  99. this.weatherType = null;
  100. this.feelsLike = null;
  101. this.loaded = false;
  102. this.scheduleUpdate(this.config.initialLoadDelay);
  103. },
  104. // add extra information of current weather
  105. // windDirection, humidity, sunrise and sunset
  106. addExtraInfoWeather: function (wrapper) {
  107. var small = document.createElement("div");
  108. small.className = "normal medium";
  109. var windIcon = document.createElement("span");
  110. windIcon.className = "wi wi-strong-wind dimmed";
  111. small.appendChild(windIcon);
  112. var windSpeed = document.createElement("span");
  113. windSpeed.innerHTML = " " + this.windSpeed;
  114. small.appendChild(windSpeed);
  115. if (this.config.showWindDirection) {
  116. var windDirection = document.createElement("sup");
  117. if (this.config.showWindDirectionAsArrow) {
  118. if (this.windDeg !== null) {
  119. windDirection.innerHTML = ' &nbsp;<i class="fa fa-long-arrow-down" style="transform:rotate(' + this.windDeg + 'deg);"></i>&nbsp;';
  120. }
  121. } else {
  122. windDirection.innerHTML = " " + this.translate(this.windDirection);
  123. }
  124. small.appendChild(windDirection);
  125. }
  126. var spacer = document.createElement("span");
  127. spacer.innerHTML = "&nbsp;";
  128. small.appendChild(spacer);
  129. if (this.config.showHumidity) {
  130. var humidity = document.createElement("span");
  131. humidity.innerHTML = this.humidity;
  132. var supspacer = document.createElement("sup");
  133. supspacer.innerHTML = "&nbsp;";
  134. var humidityIcon = document.createElement("sup");
  135. humidityIcon.className = "wi wi-humidity humidityIcon";
  136. humidityIcon.innerHTML = "&nbsp;";
  137. small.appendChild(humidity);
  138. small.appendChild(supspacer);
  139. small.appendChild(humidityIcon);
  140. }
  141. if (this.config.showSun) {
  142. var sunriseSunsetIcon = document.createElement("span");
  143. sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
  144. small.appendChild(sunriseSunsetIcon);
  145. var sunriseSunsetTime = document.createElement("span");
  146. sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
  147. small.appendChild(sunriseSunsetTime);
  148. }
  149. wrapper.appendChild(small);
  150. },
  151. // Override dom generator.
  152. getDom: function () {
  153. var wrapper = document.createElement("div");
  154. wrapper.className = this.config.tableClass;
  155. if (this.config.appid === "") {
  156. wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
  157. wrapper.className = "dimmed light small";
  158. return wrapper;
  159. }
  160. if (!this.loaded) {
  161. wrapper.innerHTML = this.translate("LOADING");
  162. wrapper.className = "dimmed light small";
  163. return wrapper;
  164. }
  165. if (this.config.onlyTemp === false) {
  166. this.addExtraInfoWeather(wrapper);
  167. }
  168. var large = document.createElement("div");
  169. large.className = "light";
  170. var degreeLabel = "";
  171. if (this.config.units === "metric" || this.config.units === "imperial") {
  172. degreeLabel += "°";
  173. }
  174. if (this.config.degreeLabel) {
  175. switch (this.config.units) {
  176. case "metric":
  177. degreeLabel += "C";
  178. break;
  179. case "imperial":
  180. degreeLabel += "F";
  181. break;
  182. case "default":
  183. degreeLabel += "K";
  184. break;
  185. }
  186. }
  187. if (this.config.decimalSymbol === "") {
  188. this.config.decimalSymbol = ".";
  189. }
  190. if (this.config.hideTemp === false) {
  191. var weatherIcon = document.createElement("span");
  192. weatherIcon.className = "wi weathericon wi-" + this.weatherType;
  193. large.appendChild(weatherIcon);
  194. var temperature = document.createElement("span");
  195. temperature.className = "bright";
  196. temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + degreeLabel;
  197. large.appendChild(temperature);
  198. }
  199. if (this.config.showIndoorTemperature && this.indoorTemperature) {
  200. var indoorIcon = document.createElement("span");
  201. indoorIcon.className = "fa fa-home";
  202. large.appendChild(indoorIcon);
  203. var indoorTemperatureElem = document.createElement("span");
  204. indoorTemperatureElem.className = "bright";
  205. indoorTemperatureElem.innerHTML = " " + this.indoorTemperature.replace(".", this.config.decimalSymbol) + degreeLabel;
  206. large.appendChild(indoorTemperatureElem);
  207. }
  208. if (this.config.showIndoorHumidity && this.indoorHumidity) {
  209. var indoorHumidityIcon = document.createElement("span");
  210. indoorHumidityIcon.className = "fa fa-tint";
  211. large.appendChild(indoorHumidityIcon);
  212. var indoorHumidityElem = document.createElement("span");
  213. indoorHumidityElem.className = "bright";
  214. indoorHumidityElem.innerHTML = " " + this.indoorHumidity + "%";
  215. large.appendChild(indoorHumidityElem);
  216. }
  217. wrapper.appendChild(large);
  218. if (this.config.showFeelsLike && this.config.onlyTemp === false) {
  219. var small = document.createElement("div");
  220. small.className = "normal medium";
  221. var feelsLike = document.createElement("span");
  222. feelsLike.className = "dimmed";
  223. feelsLike.innerHTML = this.translate("FEELS", {
  224. DEGREE: this.feelsLike + degreeLabel
  225. });
  226. small.appendChild(feelsLike);
  227. wrapper.appendChild(small);
  228. }
  229. return wrapper;
  230. },
  231. // Override getHeader method.
  232. getHeader: function () {
  233. if (this.config.useLocationAsHeader && this.config.location !== false) {
  234. return this.config.location;
  235. }
  236. if (this.config.appendLocationNameToHeader) {
  237. if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
  238. else return this.fetchedLocationName;
  239. }
  240. return this.data.header ? this.data.header : "";
  241. },
  242. // Override notification handler.
  243. notificationReceived: function (notification, payload, sender) {
  244. if (notification === "DOM_OBJECTS_CREATED") {
  245. if (this.config.appendLocationNameToHeader) {
  246. this.hide(0, { lockString: this.identifier });
  247. }
  248. }
  249. if (notification === "CALENDAR_EVENTS") {
  250. var senderClasses = sender.data.classes.toLowerCase().split(" ");
  251. if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
  252. this.firstEvent = false;
  253. for (var e in payload) {
  254. var event = payload[e];
  255. if (event.location || event.geo) {
  256. this.firstEvent = event;
  257. //Log.log("First upcoming event with location: ", event);
  258. break;
  259. }
  260. }
  261. }
  262. }
  263. if (notification === "INDOOR_TEMPERATURE") {
  264. this.indoorTemperature = this.roundValue(payload);
  265. this.updateDom(this.config.animationSpeed);
  266. }
  267. if (notification === "INDOOR_HUMIDITY") {
  268. this.indoorHumidity = this.roundValue(payload);
  269. this.updateDom(this.config.animationSpeed);
  270. }
  271. },
  272. /* updateWeather(compliments)
  273. * Requests new data from openweather.org.
  274. * Calls processWeather on succesfull response.
  275. */
  276. updateWeather: function () {
  277. if (this.config.appid === "") {
  278. Log.error("CurrentWeather: APPID not set!");
  279. return;
  280. }
  281. var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.weatherEndpoint + this.getParams();
  282. var self = this;
  283. var retry = true;
  284. var weatherRequest = new XMLHttpRequest();
  285. weatherRequest.open("GET", url, true);
  286. weatherRequest.onreadystatechange = function () {
  287. if (this.readyState === 4) {
  288. if (this.status === 200) {
  289. self.processWeather(JSON.parse(this.response));
  290. } else if (this.status === 401) {
  291. self.updateDom(self.config.animationSpeed);
  292. Log.error(self.name + ": Incorrect APPID.");
  293. retry = true;
  294. } else {
  295. Log.error(self.name + ": Could not load weather.");
  296. }
  297. if (retry) {
  298. self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
  299. }
  300. }
  301. };
  302. weatherRequest.send();
  303. },
  304. /* getParams(compliments)
  305. * Generates an url with api parameters based on the config.
  306. *
  307. * return String - URL params.
  308. */
  309. getParams: function () {
  310. var params = "?";
  311. if (this.config.locationID) {
  312. params += "id=" + this.config.locationID;
  313. } else if (this.config.location) {
  314. params += "q=" + this.config.location;
  315. } else if (this.firstEvent && this.firstEvent.geo) {
  316. params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
  317. } else if (this.firstEvent && this.firstEvent.location) {
  318. params += "q=" + this.firstEvent.location;
  319. } else {
  320. this.hide(this.config.animationSpeed, { lockString: this.identifier });
  321. return;
  322. }
  323. params += "&units=" + this.config.units;
  324. params += "&lang=" + this.config.lang;
  325. params += "&APPID=" + this.config.appid;
  326. return params;
  327. },
  328. /* processWeather(data)
  329. * Uses the received data to set the various values.
  330. *
  331. * argument data object - Weather information received form openweather.org.
  332. */
  333. processWeather: function (data) {
  334. if (!data || !data.main || typeof data.main.temp === "undefined") {
  335. // Did not receive usable new data.
  336. // Maybe this needs a better check?
  337. return;
  338. }
  339. this.humidity = parseFloat(data.main.humidity);
  340. this.temperature = this.roundValue(data.main.temp);
  341. this.fetchedLocationName = data.name;
  342. this.feelsLike = 0;
  343. if (this.config.useBeaufort) {
  344. this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
  345. } else if (this.config.useKMPHwind) {
  346. this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0);
  347. } else {
  348. this.windSpeed = parseFloat(data.wind.speed).toFixed(0);
  349. }
  350. // ONLY WORKS IF TEMP IN C //
  351. var windInMph = parseFloat(data.wind.speed * 2.23694);
  352. var tempInF = 0;
  353. switch (this.config.units) {
  354. case "metric":
  355. tempInF = 1.8 * this.temperature + 32;
  356. break;
  357. case "imperial":
  358. tempInF = this.temperature;
  359. break;
  360. case "default":
  361. tempInF = 1.8 * (this.temperature - 273.15) + 32;
  362. break;
  363. }
  364. if (windInMph > 3 && tempInF < 50) {
  365. // windchill
  366. var windChillInF = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
  367. var windChillInC = (windChillInF - 32) * (5 / 9);
  368. // this.feelsLike = windChillInC.toFixed(0);
  369. switch (this.config.units) {
  370. case "metric":
  371. this.feelsLike = windChillInC.toFixed(0);
  372. break;
  373. case "imperial":
  374. this.feelsLike = windChillInF.toFixed(0);
  375. break;
  376. case "default":
  377. this.feelsLike = (windChillInC + 273.15).toFixed(0);
  378. break;
  379. }
  380. } else if (tempInF > 80 && this.humidity > 40) {
  381. // heat index
  382. var Hindex =
  383. -42.379 +
  384. 2.04901523 * tempInF +
  385. 10.14333127 * this.humidity -
  386. 0.22475541 * tempInF * this.humidity -
  387. 6.83783 * Math.pow(10, -3) * tempInF * tempInF -
  388. 5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
  389. 1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
  390. 8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
  391. 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
  392. switch (this.config.units) {
  393. case "metric":
  394. this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
  395. break;
  396. case "imperial":
  397. this.feelsLike = Hindex.toFixed(0);
  398. break;
  399. case "default":
  400. var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
  401. this.feelsLike = tc.toFixed(0);
  402. break;
  403. }
  404. } else {
  405. this.feelsLike = parseFloat(this.temperature).toFixed(0);
  406. }
  407. this.windDirection = this.deg2Cardinal(data.wind.deg);
  408. this.windDeg = data.wind.deg;
  409. this.weatherType = this.config.iconTable[data.weather[0].icon];
  410. var now = new Date();
  411. var sunrise = new Date(data.sys.sunrise * 1000);
  412. var sunset = new Date(data.sys.sunset * 1000);
  413. // The moment().format('h') method has a bug on the Raspberry Pi.
  414. // So we need to generate the timestring manually.
  415. // See issue: https://github.com/MichMich/MagicMirror/issues/181
  416. var sunriseSunsetDateObject = sunrise < now && sunset > now ? sunset : sunrise;
  417. var timeString = moment(sunriseSunsetDateObject).format("HH:mm");
  418. if (this.config.timeFormat !== 24) {
  419. //var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
  420. if (this.config.showPeriod) {
  421. if (this.config.showPeriodUpper) {
  422. //timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
  423. timeString = moment(sunriseSunsetDateObject).format("h:mm A");
  424. } else {
  425. //timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
  426. timeString = moment(sunriseSunsetDateObject).format("h:mm a");
  427. }
  428. } else {
  429. //timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
  430. timeString = moment(sunriseSunsetDateObject).format("h:mm");
  431. }
  432. }
  433. this.sunriseSunsetTime = timeString;
  434. this.sunriseSunsetIcon = sunrise < now && sunset > now ? "wi-sunset" : "wi-sunrise";
  435. this.show(this.config.animationSpeed, { lockString: this.identifier });
  436. this.loaded = true;
  437. this.updateDom(this.config.animationSpeed);
  438. this.sendNotification("CURRENTWEATHER_DATA", { data: data });
  439. this.sendNotification("CURRENTWEATHER_TYPE", { type: this.config.iconTable[data.weather[0].icon].replace("-", "_") });
  440. },
  441. /* scheduleUpdate()
  442. * Schedule next update.
  443. *
  444. * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
  445. */
  446. scheduleUpdate: function (delay) {
  447. var nextLoad = this.config.updateInterval;
  448. if (typeof delay !== "undefined" && delay >= 0) {
  449. nextLoad = delay;
  450. }
  451. var self = this;
  452. setTimeout(function () {
  453. self.updateWeather();
  454. }, nextLoad);
  455. },
  456. /* ms2Beaufort(ms)
  457. * Converts m2 to beaufort (windspeed).
  458. *
  459. * see:
  460. * https://www.spc.noaa.gov/faq/tornado/beaufort.html
  461. * https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
  462. *
  463. * argument ms number - Windspeed in m/s.
  464. *
  465. * return number - Windspeed in beaufort.
  466. */
  467. ms2Beaufort: function (ms) {
  468. var kmh = (ms * 60 * 60) / 1000;
  469. var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
  470. for (var beaufort in speeds) {
  471. var speed = speeds[beaufort];
  472. if (speed > kmh) {
  473. return beaufort;
  474. }
  475. }
  476. return 12;
  477. },
  478. deg2Cardinal: function (deg) {
  479. if (deg > 11.25 && deg <= 33.75) {
  480. return "NNE";
  481. } else if (deg > 33.75 && deg <= 56.25) {
  482. return "NE";
  483. } else if (deg > 56.25 && deg <= 78.75) {
  484. return "ENE";
  485. } else if (deg > 78.75 && deg <= 101.25) {
  486. return "E";
  487. } else if (deg > 101.25 && deg <= 123.75) {
  488. return "ESE";
  489. } else if (deg > 123.75 && deg <= 146.25) {
  490. return "SE";
  491. } else if (deg > 146.25 && deg <= 168.75) {
  492. return "SSE";
  493. } else if (deg > 168.75 && deg <= 191.25) {
  494. return "S";
  495. } else if (deg > 191.25 && deg <= 213.75) {
  496. return "SSW";
  497. } else if (deg > 213.75 && deg <= 236.25) {
  498. return "SW";
  499. } else if (deg > 236.25 && deg <= 258.75) {
  500. return "WSW";
  501. } else if (deg > 258.75 && deg <= 281.25) {
  502. return "W";
  503. } else if (deg > 281.25 && deg <= 303.75) {
  504. return "WNW";
  505. } else if (deg > 303.75 && deg <= 326.25) {
  506. return "NW";
  507. } else if (deg > 326.25 && deg <= 348.75) {
  508. return "NNW";
  509. } else {
  510. return "N";
  511. }
  512. },
  513. /* function(temperature)
  514. * Rounds a temperature to 1 decimal or integer (depending on config.roundTemp).
  515. *
  516. * argument temperature number - Temperature.
  517. *
  518. * return string - Rounded Temperature.
  519. */
  520. roundValue: function (temperature) {
  521. var decimals = this.config.roundTemp ? 0 : 1;
  522. var roundValue = parseFloat(temperature).toFixed(decimals);
  523. return roundValue === "-0" ? 0 : roundValue;
  524. }
  525. });