diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8526de..061106b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Aligned indoor values in current weather vertical [#1499](https://github.com/MichMich/MagicMirror/issues/1499). - Added humidity support to nunjuck unit filter. - Do not display degree symbol for temperature in Kelvin [#1503](https://github.com/MichMich/MagicMirror/issues/1503). +- Weather forecast now works with openweathermap for both, `/forecast` and `/forecast/daily`, in new weather module. If you use the `/forecast`-weatherEndpoint, the hourly data are converted to daily data, see issues [#1504](https://github.com/MichMich/MagicMirror/issues/1504), [#1513](https://github.com/MichMich/MagicMirror/issues/1513). - Added fade, fadePoint and maxNumberOfDays properties to the forecast mode [#1516](https://github.com/MichMich/MagicMirror/issues/1516) - Fixed Loading string and decimalSymbol string replace [#1538](https://github.com/MichMich/MagicMirror/issues/1538) diff --git a/modules/default/weather/README.md b/modules/default/weather/README.md index 4f488d7a..9de4df7c 100644 --- a/modules/default/weather/README.md +++ b/modules/default/weather/README.md @@ -81,17 +81,17 @@ The following properties can be configured: | ---------------------------- | ----------- | `apiVersion` | The OpenWeatherMap API version to use.

**Default value:** `2.5` | `apiBase` | The OpenWeatherMap base URL.

**Default value:** `'http://api.openweathermap.org/data/'` -| `weatherEndpoint` | The OpenWeatherMap API endPoint.

**Possible values:** `/weather`, `/forecast` or `/forecast/daily` (paying users only)
**Default value:** `'/weather'` +| `weatherEndpoint` | The OpenWeatherMap API endPoint.

**Possible values:** `/weather`, `/forecast` (free users) or `/forecast/daily` (paying users or old apiKey only)
**Default value:** `'/weather'` | `locationID` | Location ID from [OpenWeatherMap](https://openweathermap.org/find) **This will override anything you put in location.**
Leave blank if you want to use location.
**Example:** `1234567`
**Default value:** `false`

**Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used. | `location` | The location used for weather information.

**Example:** `'Amsterdam,Netherlands'`
**Default value:** `false`

**Note:** When the `location` and `locationID` are both not set, the location will be based on the information provided by the calendar module. The first upcoming event with location data will be used. -| `apiKey` | The [OpenWeatherMap](https://home.openweathermap.org) API key, which can be obtained by creating an OpenWeatherMap account.

This value is **REQUIRED** +| `apiKey` | The [OpenWeatherMap](https://home.openweathermap.org) API key, which can be obtained by creating an OpenWeatherMap account.

This value is **REQUIRED** ### Darksky options | Option | Description | ---------------------------- | ----------- | `apiBase` | The DarkSky base URL. The darksky api has disabled [cors](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), therefore a proxy is required.

**Possible value:** `'https://cors-anywhere.herokuapp.com/https://api.darksky.net'`
This value is **REQUIRED** -| `weatherEndpoint` | The DarkSky API endPoint.

**Possible values:** `/forecast`
This value is **REQUIRED** +| `weatherEndpoint` | The DarkSky API endPoint.

**Possible values:** `/forecast`
This value is **REQUIRED** | `apiKey` | The [DarkSky](https://darksky.net/dev/register) API key, which can be obtained by creating an DarkSky account.

This value is **REQUIRED** | `lat` | The geo coordinate latitude.

This value is **REQUIRED** | `lon` | The geo coordinate longitude.

This value is **REQUIRED** diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js index 64df4104..856a52c1 100644 --- a/modules/default/weather/providers/openweathermap.js +++ b/modules/default/weather/providers/openweathermap.js @@ -5,7 +5,7 @@ * * By Michael Teeuw http://michaelteeuw.nl * MIT Licensed. - * + * * This class is the blueprint for a weather provider. */ @@ -56,8 +56,6 @@ WeatherProvider.register("openweathermap", { }) }, - - /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */ /* * Gets the complete url for the request @@ -66,7 +64,7 @@ WeatherProvider.register("openweathermap", { return this.config.apiBase + this.config.apiVersion + this.config.weatherEndpoint + this.getParams(); }, - /* + /* * Generate a WeatherObject based on currentWeatherInformation */ generateWeatherObjectFromCurrentWeather(currentWeatherData) { @@ -87,6 +85,21 @@ WeatherProvider.register("openweathermap", { * Generate WeatherObjects based on forecast information */ generateWeatherObjectsFromForecast(forecasts) { + + if (this.config.weatherEndpoint == "/forecast") { + return this.fetchForecastHourly(forecasts); + } else if (this.config.weatherEndpoint == "/forecast/daily") { + return this.fetchForecastDaily(forecasts); + } + // if weatherEndpoint does not match forecast or forecast/daily, what should be returned? + const days = [new WeatherObject(this.config.units)]; + return days; + }, + + /* + * fetch forecast information for 3-hourly forecast (available for free subscription). + */ + fetchForecastHourly(forecasts) { // initial variable declaration const days = []; // variables for temperature range and rain @@ -98,19 +111,8 @@ WeatherProvider.register("openweathermap", { var weather = new WeatherObject(this.config.units); for (const forecast of forecasts) { - - if (date === moment(forecast.dt, "X").format("YYYY-MM-DD")) { - // the same day as before - // add values from forecast to corresponding variables - minTemp.push(forecast.main.temp_min); - maxTemp.push(forecast.main.temp_max); - if (this.config.units === "imperial" && !isNaN(forecast.rain["3h"])) { - rain += forecast.rain["3h"] / 25.4; - } else { - rain += forecast.rain["3h"]; - } - } else { - // a new day + + if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) { // calculate minimum/maximum temperature, specify rain amount weather.minTemperature = Math.min.apply(null, minTemp); weather.maxTemperature = Math.max.apply(null, maxTemp); @@ -119,34 +121,87 @@ WeatherProvider.register("openweathermap", { days.push(weather); // create new weather-object weather = new WeatherObject(this.config.units); - + minTemp = []; maxTemp = []; - rain *= 0; - + rain = 0; + // set new date date = moment(forecast.dt, "X").format("YYYY-MM-DD"); - + // specify date weather.date = moment(forecast.dt, "X"); - - // select weather type by first forecast value of a day, is this reasonable? + + // If the first value of today is later than 17:00, we have an icon at least! weather.weatherType = this.convertWeatherType(forecast.weather[0].icon); + + } - // add values from first forecast of this day to corresponding variables - minTemp.push(forecast.main.temp_min); - maxTemp.push(forecast.main.temp_max); + if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) { + weather.weatherType = this.convertWeatherType(forecast.weather[0].icon); + } + + // the same day as before + // add values from forecast to corresponding variables + minTemp.push(forecast.main.temp_min); + maxTemp.push(forecast.main.temp_max); + + if (forecast.hasOwnProperty("rain")) { if (this.config.units === "imperial" && !isNaN(forecast.rain["3h"])) { rain += forecast.rain["3h"] / 25.4; - } else { + } else if (!isNaN(forecast.rain["3h"])){ rain += forecast.rain["3h"]; + } else { + rain += 0; } + } else { + rain += 0; } } - + // last day + // calculate minimum/maximum temperature, specify rain amount + weather.minTemperature = Math.min.apply(null, minTemp); + weather.maxTemperature = Math.max.apply(null, maxTemp); + weather.rain = rain; + // push weather information to days array + days.push(weather); return days.slice(1); }, + /* + * fetch forecast information for daily forecast (available for paid subscription or old apiKey). + */ + fetchForecastDaily(forecasts) { + // initial variable declaration + const days = []; + + for (const forecast of forecasts) { + const weather = new WeatherObject(this.config.units); + + weather.date = moment(forecast.dt, "X"); + weather.minTemperature = forecast.temp.min; + weather.maxTemperature = forecast.temp.max; + weather.weatherType = this.convertWeatherType(forecast.weather[0].icon); + + // forecast.rain not available if amount is zero + if (forecast.hasOwnProperty("rain")) { + if (this.config.units === "imperial" && !isNaN(forecast.rain)) { + weather.rain = forecast.rain / 25.4; + } else if (!isNaN(forecast.rain)){ + weather.rain = forecast.rain; + } else { + weather.rain = 0; + } + } else { + weather.rain = 0; + } + + days.push(weather); + } + + return days; + }, + /* * Convert the OpenWeatherMap icons to a more usable name. */