Read the statement by Michael Teeuw here.
Problem with Weather forecast
-
OK, it makes sense.
Is there any option to get only one forecast for one day? Or to display somehow the time periods? -
I made a quick’n’dirty hack to make it only display the weather at noon each day. Around line 340, I added a simple if statement. I’m in UTC+2, so I had to put 14 instead of 12 in the comparison.
this.parserDataWeather(forecast); // hack issue #1017 if ( moment(forecast.dt, "X").format("HH") != "14") continue; //new if statement this.forecast.push({
The limit of 16 no longer means 16 days, but 16 forecasts. Therefore, we need to change the enforced limit, around line 300, I set it to something higher (I chose 57):
params += "&cnt=" + (((this.config.maxNumberOfDays < 1) || (this.config.maxNumberOfDays > 16)) ? 57
Don’t forget to change the value in the actual config to the same number (57 in my case).
I chose 57 to get a full week, but it seems the API won’t return more than 5 days’ worth of data anyway. -
@Henrik said in Problem with Weather forecast:
but it seems the API won’t return more than 5 days’ worth of data anyway.
The newest weather module to hit the MM scene. Take a look. I like it, I like it a lot!
-
Hi,
I made the module compatible for the “daily” api of openweather. It basically aggregate data of all “3h parts” of each day.
The min/max are global min/max of each parts
The icon shows the worst-case (you can reorder what is worse for you in config.iconTableOrdered)Hope that will help devs
Here is the corresponding code of weatherforecast.js
/* global Module */ /* Magic Mirror * Module: WeatherForecast * * By Michael Teeuw http://michaelteeuw.nl * MIT Licensed. */ Module.register("weatherforecast",{ // Default module config. defaults: { location: false, locationID: false, appid: "", units: config.units, maxNumberOfDays: 7, showRainAmount: false, updateInterval: 10 * 60 * 1000, // every 10 minutes animationSpeed: 1000, timeFormat: config.timeFormat, lang: config.language, fade: true, fadePoint: 0.25, // Start on 1/4th of the list. colored: false, initialLoadDelay: 2500, // 2.5 seconds delay. This delay is used to keep the OpenWeather API happy. retryDelay: 2500, apiVersion: "2.5", apiBase: "http://api.openweathermap.org/data/", forecastEndpoint: "forecast", //forecast/daily appendLocationNameToHeader: true, calendarClass: "calendar", roundTemp: false, iconTable: { "01d": "wi-day-sunny", "02d": "wi-day-cloudy", "03d": "wi-cloudy", "04d": "wi-cloudy-windy", "09d": "wi-showers", "10d": "wi-rain", "11d": "wi-thunderstorm", "13d": "wi-snow", "50d": "wi-fog", "01n": "wi-night-clear", "02n": "wi-night-cloudy", "03n": "wi-night-cloudy", "04n": "wi-night-cloudy", "09n": "wi-night-showers", "10n": "wi-night-rain", "11n": "wi-night-thunderstorm", "13n": "wi-night-snow", "50n": "wi-night-alt-cloudy-windy" }, iconTableOrdered: [ "11d", "09d", "13d", "10d", "50d", "04d", "03d", "02d", "01d", "11n", "09n", "13n", "10n", "50n", "02n", "03n", "04n", "01n" ], }, // create a variable for the first upcoming calendaar event. Used if no location is specified. firstEvent: false, // create a variable to hold the location name based on the API result. fetchedLocationName: "", // Define required scripts. getScripts: function() { return ["moment.js"]; }, // Define required scripts. getStyles: function() { return ["weather-icons.css", "weatherforecast.css"]; }, // Define required translations. getTranslations: function() { // The translations for the default modules are defined in the core translation files. // Therefor we can just return false. Otherwise we should have returned a dictionary. // If you're trying to build yiur own module including translations, check out the documentation. return false; }, // Define start sequence. start: function() { Log.info("Starting module: " + this.name); // Set locale. moment.locale(config.language); this.forecast = []; this.loaded = false; this.scheduleUpdate(this.config.initialLoadDelay); this.updateTimer = null; }, // Override dom generator. getDom: function() { var wrapper = document.createElement("div"); if (this.config.appid === "") { wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + "."; wrapper.className = "dimmed light small"; return wrapper; } if (!this.loaded) { wrapper.innerHTML = this.translate("LOADING"); wrapper.className = "dimmed light small"; return wrapper; } var table = document.createElement("table"); table.className = "small"; for (var f in this.forecast) { var forecast = this.forecast[f]; var row = document.createElement("tr"); if (this.config.colored) { row.className = "colored"; } table.appendChild(row); var dayCell = document.createElement("td"); dayCell.className = "day"; dayCell.innerHTML = forecast.day; row.appendChild(dayCell); var iconCell = document.createElement("td"); iconCell.className = "bright weather-icon"; row.appendChild(iconCell); var icon = document.createElement("span"); icon.className = "wi weathericon " + forecast.icon; iconCell.appendChild(icon); var maxTempCell = document.createElement("td"); maxTempCell.innerHTML = forecast.maxTemp; maxTempCell.className = "align-right bright max-temp"; row.appendChild(maxTempCell); var minTempCell = document.createElement("td"); minTempCell.innerHTML = forecast.minTemp; minTempCell.className = "align-right min-temp"; row.appendChild(minTempCell); if (this.config.showRainAmount) { var rainCell = document.createElement("td"); if (isNaN(forecast.rain)) { rainCell.innerHTML = ""; } else { if(config.units !== "imperial") { rainCell.innerHTML = forecast.rain + " mm"; } else { rainCell.innerHTML = (parseFloat(forecast.rain) / 25.4).toFixed(2) + " in"; } } rainCell.className = "align-right bright rain"; row.appendChild(rainCell); } if (this.config.fade && this.config.fadePoint < 1) { if (this.config.fadePoint < 0) { this.config.fadePoint = 0; } var startingPoint = this.forecast.length * this.config.fadePoint; var steps = this.forecast.length - startingPoint; if (f >= startingPoint) { var currentStep = f - startingPoint; row.style.opacity = 1 - (1 / steps * currentStep); } } } return table; }, // Override getHeader method. getHeader: function() { if (this.config.appendLocationNameToHeader) { return this.data.header + " " + this.fetchedLocationName; } return this.data.header; }, // Override notification handler. notificationReceived: function(notification, payload, sender) { if (notification === "DOM_OBJECTS_CREATED") { if (this.config.appendLocationNameToHeader) { this.hide(0, {lockString: this.identifier}); } } if (notification === "CALENDAR_EVENTS") { var senderClasses = sender.data.classes.toLowerCase().split(" "); if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) { this.firstEvent = false; for (var e in payload) { var event = payload[e]; if (event.location || event.geo) { this.firstEvent = event; //Log.log("First upcoming event with location: ", event); break; } } } } }, /* updateWeather(compliments) * Requests new data from openweather.org. * Calls processWeather on succesfull response. */ updateWeather: function() { if (this.config.appid === "") { Log.error("WeatherForecast: APPID not set!"); return; } var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.forecastEndpoint + this.getParams(); var self = this; var retry = true; var weatherRequest = new XMLHttpRequest(); weatherRequest.open("GET", url, true); weatherRequest.onreadystatechange = function() { if (this.readyState === 4) { if (this.status === 200) { self.processWeather(JSON.parse(this.response)); } else if (this.status === 401) { self.updateDom(self.config.animationSpeed); Log.error(self.name + ": Incorrect APPID."); retry = true; } else { Log.error(self.name + ": Could not load weather."); } if (retry) { self.scheduleUpdate((self.loaded) ? -1 : self.config.retryDelay); } } }; weatherRequest.send(); }, /* getParams(compliments) * Generates an url with api parameters based on the config. * * return String - URL params. */ getParams: function() { var params = "?"; if(this.config.locationID) { params += "id=" + this.config.locationID; } else if(this.config.location) { params += "q=" + this.config.location; } else if (this.firstEvent && this.firstEvent.geo) { params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon } else if (this.firstEvent && this.firstEvent.location) { params += "q=" + this.firstEvent.location; } else { this.hide(this.config.animationSpeed, {lockString:this.identifier}); return; } params += "&units=" + this.config.units; params += "&lang=" + this.config.lang; /* * Submit a specific number of days to forecast, between 1 to 16 days. * The OpenWeatherMap API properly handles values outside of the 1 - 16 range and returns 7 days by default. * This is simply being pedantic and doing it ourselves. */ params += "&cnt=" + (((this.config.maxNumberOfDays < 1) || (this.config.maxNumberOfDays > 121)) ? 40 : this.config.maxNumberOfDays); params += "&APPID=" + this.config.appid; return params; }, /* processWeather(data) * Uses the received data to set the various values. * * argument data object - Weather information received form openweather.org. */ processWeather: function(data) { this.fetchedLocationName = data.city.name + ", " + data.city.country; var days_forecast = []; for (var i = 0, count = data.list.length; i < count; i++) { let forecast = data.list[i]; let day = forecast.dt_txt.split(" ")[0]; if (!(day in days_forecast)) days_forecast[day] = {"dt":[], "icon":[], "temp":[]}; days_forecast[day].dt.push(forecast.dt); days_forecast[day].icon.push(forecast.weather[0].icon); days_forecast[day].temp.push(forecast.main.temp_min); days_forecast[day].temp.push(forecast.main.temp_max); } for (let i in days_forecast) { let min = days_forecast[i].temp.reduce((previous, current) => current = previous < current ? previous : current); let max = days_forecast[i].temp.reduce((previous, current) => current = previous > current ? previous : current); let icon = days_forecast[i].icon.reduce((previous, current) => current = this.config.iconTableOrdered.indexOf(previous) < this.config.iconTableOrdered.indexOf(current) ? previous : current); this.forecast.push({ day: moment(days_forecast[i].dt[0], "X").format("ddd"), icon: this.config.iconTable[icon], maxTemp: this.roundValue(min), minTemp: this.roundValue(max) // rain: this.roundValue(forecast.rain) }); } //Log.log(this.forecast); this.show(this.config.animationSpeed, {lockString:this.identifier}); this.loaded = true; this.updateDom(this.config.animationSpeed); }, /* scheduleUpdate() * Schedule next update. * * argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used. */ scheduleUpdate: function(delay) { var nextLoad = this.config.updateInterval; if (typeof delay !== "undefined" && delay >= 0) { nextLoad = delay; } var self = this; clearTimeout(this.updateTimer); this.updateTimer = setTimeout(function() { self.updateWeather(); }, nextLoad); }, /* ms2Beaufort(ms) * Converts m2 to beaufort (windspeed). * * see: * http://www.spc.noaa.gov/faq/tornado/beaufort.html * https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale * * argument ms number - Windspeed in m/s. * * return number - Windspeed in beaufort. */ ms2Beaufort: function(ms) { var kmh = ms * 60 * 60 / 1000; var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000]; for (var beaufort in speeds) { var speed = speeds[beaufort]; if (speed > kmh) { return beaufort; } } return 12; }, /* function(temperature) * Rounds a temperature to 1 decimal or integer (depending on config.roundTemp). * * argument temperature number - Temperature. * * return number - Rounded Temperature. */ roundValue: function(temperature) { var decimals = this.config.roundTemp ? 0 : 1; return parseFloat(temperature).toFixed(decimals); } });
-
@Mykle1 Thanks, will check that out!
-
@Sp4M Good work! :)
-
@Sp4M
Yes it looks better now
For me only 2 forecasts are visible. How can I show more ? -
@Sp4M
Oh, I was pleased to early.
It is repeating now the two forecasts ?
see attachment
-
@Sp4M
Hi it’s even worse.
the two forecast are repeated endless -
@frank61BS
Try to increase the maxNumberOfDays parameter.
Actually maxNumberOfDays = 2 means “2 slices of time” and not 2 days with this api, as I said in my first posts (eg: 1 day = 8 x 3h)
Maybe it will be better by simple use maxNumberOfDays x 8 in the source code