Advertisement
Guest User

Untitled

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