SHARE
TWEET

Untitled

a guest Jan 24th, 2020 83 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  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. });
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top