From 4a162543f60a7445eb4d39e9edd61c3ca2338400 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Tue, 30 Jun 2020 02:40:41 -0400 Subject: [PATCH 01/11] added OpenWeatherMap One Call API function to default Weather module, added wDataHourly type --- .../weather/providers/openweathermap.js | 184 +++++++++++++++++- modules/default/weather/wdataHourly.njk | 29 +++ modules/default/weather/weather.js | 36 +++- modules/default/weather/weatherprovider.js | 18 ++ 4 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 modules/default/weather/wdataHourly.njk diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js index 70b715a0..2cdd6d96 100755 --- a/modules/default/weather/providers/openweathermap.js +++ b/modules/default/weather/providers/openweathermap.js @@ -35,7 +35,7 @@ WeatherProvider.register("openweathermap", { .finally(() => this.updateAvailable()); }, - // Overwrite the fetchCurrentWeather method. + // Overwrite the fetchWeatherForecast method. fetchWeatherForecast() { this.fetchData(this.getUrl()) .then((data) => { @@ -56,6 +56,27 @@ WeatherProvider.register("openweathermap", { .finally(() => this.updateAvailable()); }, + // Overwrite the fetchWeatherData method. + fetchWeatherData() { + this.fetchData(this.getUrl()) + .then((data) => { + if (!data || !data.list || !data.list.length) { + // Did not receive usable new data. + // Maybe this needs a better check? + return; + } + + this.setFetchedLocation(`(${data.lat},${data.lon})`); + + const wData = this.generateWeatherObjectsFromOnecall(data); + this.setWeatherData(wData); + }) + .catch(function (request) { + Log.error("Could not load data ... ", request); + }) + .finally(() => this.updateAvailable()); + }, + /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */ /* * Gets the complete url for the request @@ -95,6 +116,153 @@ WeatherProvider.register("openweathermap", { return days; }, + /* + * Generate WeatherObjects based on One Call forecast information + */ + generateWeatherObjectsFromOnecall(data) { + if (this.config.weatherEndpoint === "/onecall") { + return this.fetchOneCall(data); + } + // if weatherEndpoint does not match onecall, what should be returned? + const wData = {current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits), hours: [], days: []}; + return wData; + }, + + /* + * fetch One Call forecast information (available for free subscription). + * factors in timezone offsets. + * minutely forecasts are excluded for the moment, see getParams(). + */ + fetchOnecall(data) { + let precip = false; + // get current weather + const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + if (!isNaN(data.current)) { + current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset/60); + current.windSpeed = data.current.wind_speed; + current.windDirection = data.current.wind_deg; + current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset/60); + current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset/60); + current.temperature = data.current.temp; + current.weatherType = this.convertWeatherType(data.current.weather[0].icon); + current.humidity = data.current.humidity; + if (!isNaN(data.current.rain)) { + current.rain = data.current.rain.1h; + precip = true; + } + if (!isNaN(data.current.snow)) { + current.snow = data.current.snow.1h; + precip = true; + } + if (precip) { + current.precipitation = current.rain+current.snow; + } + current.feelsLikeTemp = data.current.feels_like; + } + + let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + + // let onecallDailyFormat = "MMM DD" + // let onecallHourlyFormat = "HH" + // let onecallMinutelyFormat = "HH:mm" + // if (this.config.timeFormat === 12) { + // if (this.config.showPeriod === true) { + // if (this.config.showPeriodUpper === true) { + // onecallHourlyFormat = "hhA" + // onecallMinutelyFormat = "hh:mmA" + // } else { + // onecallHourlyFormat = "hha" + // onecallMinutelyFormat = "hh:mma" + // } + // } else { + // onecallHourlyFormat = "hh" + // onecallMinutelyFormat = "hh:mm" + // } + // } + + // get hourly weather + const hours = []; + if (!isNaN(data.hourly)) { + for (const hour of data.hourly) { + weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60); + // weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat+","+onecallHourlyFormat); + weather.temperature = hour.temp; + weather.feelsLikeTemp = hour.feels_like; + weather.humidity = hour.humidity; + weather.windSpeed = hour.wind_speed; + weather.windDirection = hour.wind_deg; + weather.weatherType = this.convertWeatherType(hour.weather[0].icon); + precip = false; + if (!isNaN(hour.rain)) { + if (this.config.units === "imperial") { + weather.rain = hour.rain.1h / 25.4; + } else { + weather.rain = hour.rain.1h; + } + precip = true; + } + if (!isNaN(hour.snow)) { + if (this.config.units === "imperial") { + weather.snow = hour.snow.1h / 25.4; + } else { + weather.snow = hour.snow.1h; + } + precip = true; + } + if (precip) { + weather.precipitation = weather.rain+weather.snow; + } + + hours.push(weather); + weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + } + } + + // get daily weather + const days = []; + if (!isNaN(data.daily)) { + for (const day of data.daily) { + weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60); + weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60); + weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60); + // weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat); + // weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60).format(onecallMinutelyFormat); + // weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60).format(onecallMinutelyFormat); + weather.minTemperature = day.temp.min; + weather.maxTemperature = day.temp.max; + weather.humidity = day.humidity; + weather.windSpeed = day.wind_speed; + weather.windDirection = day.wind_deg; + weather.weatherType = this.convertWeatherType(day.weather[0].icon); + precip = false; + if (!isNaN(day.rain)) { + if (this.config.units === "imperial") { + weather.rain = day.rain / 25.4; + } else { + weather.rain = day.rain; + } + precip = true; + } + if (!isNaN(day.snow)) { + if (this.config.units === "imperial") { + weather.snow = day.snow / 25.4; + } else { + weather.snow = day.snow; + } + precip = true; + } + if (precip) { + weather.precipitation = weather.rain+weather.snow; + } + + days.push(weather); + weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + } + } + + return {current: current, hours: hours, days: days}; + }, + /* * fetch forecast information for 3-hourly forecast (available for free subscription). */ @@ -256,7 +424,19 @@ WeatherProvider.register("openweathermap", { */ getParams() { let params = "?"; - if (this.config.locationID) { + if (this.config.weatherEndpoint === "/onecall") { + params += "lat=" + this.config.lat; + params += "&lon=" + this.config.lon; + if (this.config.type === "wDataCurrent") { + params += "&exclude=minutely,hourly,daily"; + } else if (this.config.type === "wDataHourly") { + params += "&exclude=current,minutely,daily"; + } else if (this.config.type === "wDataDaily") { + params += "&exclude=current,minutely,hourly"; + } else { + params += "&exclude=minutely"; + } + } else if (this.config.locationID) { params += "id=" + this.config.locationID; } else if (this.config.location) { params += "q=" + this.config.location; diff --git a/modules/default/weather/wdataHourly.njk b/modules/default/weather/wdataHourly.njk new file mode 100644 index 00000000..f69ca8e7 --- /dev/null +++ b/modules/default/weather/wdataHourly.njk @@ -0,0 +1,29 @@ +{% if wData %} + {% set numSteps = wData.hours | calcNumEntries %} + {% set currentStep = 0 %} + + {% set hours = wData.hours.slice(0, numSteps) %} + {% for hour in hours %} + + + + + {% if config.showPrecipitationAmount %} + + {% endif %} + + {% set currentStep = currentStep + 1 %} + {% endfor %} +
{{ hour.date | formatTimeMoment }} + {{ hour.temperature | roundValue | unit("temperature") }} + + {{ hour.precipitation | unit("precip") }} +
+{% else %} +
+ {{ "LOADING" | translate | safe }} +
+{% endif %} + + + diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js index 150ba084..1ec7251d 100644 --- a/modules/default/weather/weather.js +++ b/modules/default/weather/weather.js @@ -11,7 +11,7 @@ Module.register("weather", { defaults: { weatherProvider: "openweathermap", roundTemp: false, - type: "current", //current, forecast + type: "current", //current, forecast, wDataCurrent, wDataHourly, wDataDaily location: false, locationID: false, @@ -37,6 +37,7 @@ Module.register("weather", { showIndoorTemperature: false, showIndoorHumidity: false, maxNumberOfDays: 5, + maxEntries: 5, fade: true, fadePoint: 0.25, // Start on 1/4th of the list. @@ -132,6 +133,7 @@ Module.register("weather", { config: this.config, current: this.weatherProvider.currentWeather(), forecast: this.weatherProvider.weatherForecast(), + wData: this.weatherProvider.weatherData(), indoor: { humidity: this.indoorHumidity, temperature: this.indoorTemperature @@ -153,7 +155,11 @@ Module.register("weather", { } setTimeout(() => { - if (this.config.type === "forecast") { + if (this.config.type === "wDataCurrent" + || this.config.type === "wDataHourly" + || this.config.type === "wDataDaily") { + this.weatherProvider.fetchWeatherData(); + } else if (this.config.type === "forecast") { this.weatherProvider.fetchWeatherForecast(); } else { this.weatherProvider.fetchCurrentWeather(); @@ -187,6 +193,25 @@ Module.register("weather", { return date.format("HH:mm"); }.bind(this) ); + this.nunjucksEnvironment().addFilter( + "formatTimeMoment", + function (date) { + + if (this.config.timeFormat !== 24) { + if (this.config.showPeriod) { + if (this.config.showPeriodUpper) { + return date.format("h:mm A"); + } else { + return date.format("h:mm a"); + } + } else { + return date.format("h:mm"); + } + } + + return date.format("HH:mm"); + }.bind(this) + ); this.nunjucksEnvironment().addFilter( "unit", @@ -243,6 +268,13 @@ Module.register("weather", { }.bind(this) ); + this.nunjucksEnvironment().addFilter( + "calcNumEntries", + function (dataArray) { + return Math.min(dataArray.length, this.config.maxNumberOfEntries); + }.bind(this) + ); + this.nunjucksEnvironment().addFilter( "opacity", function (currentStep, numSteps) { diff --git a/modules/default/weather/weatherprovider.js b/modules/default/weather/weatherprovider.js index cfcb3ef6..ac8618eb 100644 --- a/modules/default/weather/weatherprovider.js +++ b/modules/default/weather/weatherprovider.js @@ -16,6 +16,7 @@ var WeatherProvider = Class.extend({ // Try to not access them directly. currentWeatherObject: null, weatherForecastArray: null, + weatherDataObject: null, fetchedLocationName: null, // The following properties will be set automatically. @@ -56,6 +57,12 @@ var WeatherProvider = Class.extend({ Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`); }, + // This method should start the API request to fetch the weather forecast. + // This method should definitely be overwritten in the provider. + fetchWeatherData: function () { + Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherData method.`); + }, + // This returns a WeatherDay object for the current weather. currentWeather: function () { return this.currentWeatherObject; @@ -66,6 +73,11 @@ var WeatherProvider = Class.extend({ return this.weatherForecastArray; }, + // This returns an object containing WeatherDay object(s) depending on the type of call. + weatherData: function () { + return this.weatherDataObject; + }, + // This returns the name of the fetched location or an empty string. fetchedLocation: function () { return this.fetchedLocationName || ""; @@ -83,6 +95,12 @@ var WeatherProvider = Class.extend({ this.weatherForecastArray = weatherForecastArray; }, + // Set the weatherDataObject and notify the delegate that new information is available. + setWeatherData: function (weatherDataObject) { + // We should check here if we are passing a WeatherDay + this.weatherDataObject = weatherDataObject; + }, + // Set the fetched location name. setFetchedLocation: function (name) { this.fetchedLocationName = name; From a4df38d963774ebb660f4e681e5039cf07df2652 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Tue, 30 Jun 2020 04:18:03 -0400 Subject: [PATCH 02/11] fixed config parameter typo --- modules/default/weather/providers/openweathermap.js | 2 +- modules/default/weather/weather.js | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js index 2cdd6d96..55e2ebfe 100755 --- a/modules/default/weather/providers/openweathermap.js +++ b/modules/default/weather/providers/openweathermap.js @@ -66,7 +66,7 @@ WeatherProvider.register("openweathermap", { return; } - this.setFetchedLocation(`(${data.lat},${data.lon})`); + this.setFetchedLocation(`${data.lat},${data.lon}`); const wData = this.generateWeatherObjectsFromOnecall(data); this.setWeatherData(wData); diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js index 1ec7251d..574c3863 100644 --- a/modules/default/weather/weather.js +++ b/modules/default/weather/weather.js @@ -155,14 +155,12 @@ Module.register("weather", { } setTimeout(() => { - if (this.config.type === "wDataCurrent" - || this.config.type === "wDataHourly" - || this.config.type === "wDataDaily") { - this.weatherProvider.fetchWeatherData(); - } else if (this.config.type === "forecast") { + if (this.config.type === "forecast") { this.weatherProvider.fetchWeatherForecast(); - } else { + } else if (this.config.type === "current") { this.weatherProvider.fetchCurrentWeather(); + } else { + this.weatherProvider.fetchWeatherData(); } }, nextLoad); }, @@ -271,7 +269,7 @@ Module.register("weather", { this.nunjucksEnvironment().addFilter( "calcNumEntries", function (dataArray) { - return Math.min(dataArray.length, this.config.maxNumberOfEntries); + return Math.min(dataArray.length, this.config.maxEntries); }.bind(this) ); From f73520559efa8e2b12526a4b331984fec3563f56 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Tue, 30 Jun 2020 12:06:16 -0400 Subject: [PATCH 03/11] typo and bug fixes --- .../weather/providers/openweathermap.js | 30 ++++++++++++------- modules/default/weather/wdataHourly.njk | 2 +- modules/default/weather/weatherprovider.js | 1 - 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js index 55e2ebfe..5f73dfb0 100755 --- a/modules/default/weather/providers/openweathermap.js +++ b/modules/default/weather/providers/openweathermap.js @@ -146,12 +146,20 @@ WeatherProvider.register("openweathermap", { current.temperature = data.current.temp; current.weatherType = this.convertWeatherType(data.current.weather[0].icon); current.humidity = data.current.humidity; - if (!isNaN(data.current.rain)) { - current.rain = data.current.rain.1h; + if (current.hasOwnProperty("rain") && !isNaN(current.rain["1h"])) { + if (this.config.units === "imperial") { + weather.rain = current.rain["1h"] / 25.4; + } else { + weather.rain = current.rain["1h"]; + } precip = true; } - if (!isNaN(data.current.snow)) { - current.snow = data.current.snow.1h; + if (current.hasOwnProperty("snow") && !isNaN(current.snow["1h"])) { + if (this.config.units === "imperial") { + weather.snow = current.snow["1h"] / 25.4; + } else { + weather.snow = current.snow["1h"]; + } precip = true; } if (precip) { @@ -193,19 +201,19 @@ WeatherProvider.register("openweathermap", { weather.windDirection = hour.wind_deg; weather.weatherType = this.convertWeatherType(hour.weather[0].icon); precip = false; - if (!isNaN(hour.rain)) { + if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) { if (this.config.units === "imperial") { - weather.rain = hour.rain.1h / 25.4; + weather.rain = hour.rain["1h"] / 25.4; } else { - weather.rain = hour.rain.1h; + weather.rain = hour.rain["1h"]; } precip = true; } - if (!isNaN(hour.snow)) { + if (hour.hasOwnProperty("snow") && !isNaN(hour.snow["1h"])) { if (this.config.units === "imperial") { - weather.snow = hour.snow.1h / 25.4; + weather.snow = hour.snow["1h"] / 25.4; } else { - weather.snow = hour.snow.1h; + weather.snow = hour.snow["1h"]; } precip = true; } @@ -451,7 +459,7 @@ WeatherProvider.register("openweathermap", { params += "&units=" + this.config.units; params += "&lang=" + this.config.lang; - params += "&APPID=" + this.config.apiKey; + params += "&APPID=" + this.config.appid; return params; } diff --git a/modules/default/weather/wdataHourly.njk b/modules/default/weather/wdataHourly.njk index f69ca8e7..c69e425f 100644 --- a/modules/default/weather/wdataHourly.njk +++ b/modules/default/weather/wdataHourly.njk @@ -5,7 +5,7 @@ {% set hours = wData.hours.slice(0, numSteps) %} {% for hour in hours %} - {{ hour.date | formatTimeMoment }} + {{ hour.date | formatTimeMoment }} {{ hour.temperature | roundValue | unit("temperature") }} diff --git a/modules/default/weather/weatherprovider.js b/modules/default/weather/weatherprovider.js index ac8618eb..dd874be4 100644 --- a/modules/default/weather/weatherprovider.js +++ b/modules/default/weather/weatherprovider.js @@ -97,7 +97,6 @@ var WeatherProvider = Class.extend({ // Set the weatherDataObject and notify the delegate that new information is available. setWeatherData: function (weatherDataObject) { - // We should check here if we are passing a WeatherDay this.weatherDataObject = weatherDataObject; }, From ca0b89ecd373834deac39bd556f447967a6bd7f3 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Tue, 30 Jun 2020 12:36:23 -0400 Subject: [PATCH 04/11] backtracked apiKey change to appid --- modules/default/weather/providers/openweathermap.js | 2 +- modules/default/weather/weather.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js index 5f73dfb0..bf1dddfc 100755 --- a/modules/default/weather/providers/openweathermap.js +++ b/modules/default/weather/providers/openweathermap.js @@ -459,7 +459,7 @@ WeatherProvider.register("openweathermap", { params += "&units=" + this.config.units; params += "&lang=" + this.config.lang; - params += "&APPID=" + this.config.appid; + params += "&APPID=" + this.config.apiKey; return params; } diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js index 574c3863..549eddcf 100644 --- a/modules/default/weather/weather.js +++ b/modules/default/weather/weather.js @@ -15,7 +15,7 @@ Module.register("weather", { location: false, locationID: false, - appid: "", + apiKey: "", units: config.units, tempUnits: config.units, From ffbf0804d92e31f4547164cba67837bb487cea19 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Tue, 30 Jun 2020 12:41:14 -0400 Subject: [PATCH 05/11] added default values for `lat` and `lon` --- modules/default/weather/weather.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js index 549eddcf..09046071 100644 --- a/modules/default/weather/weather.js +++ b/modules/default/weather/weather.js @@ -13,6 +13,8 @@ Module.register("weather", { roundTemp: false, type: "current", //current, forecast, wDataCurrent, wDataHourly, wDataDaily + lat: 0, + lon: 0, location: false, locationID: false, apiKey: "", From 1d2f929d3ffe3d190bc69cad72433ed2fb991872 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Tue, 30 Jun 2020 13:21:50 -0400 Subject: [PATCH 06/11] Rename wdataHourly.njk to wdatahourly.njk --- modules/default/weather/{wdataHourly.njk => wdatahourly.njk} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/default/weather/{wdataHourly.njk => wdatahourly.njk} (100%) diff --git a/modules/default/weather/wdataHourly.njk b/modules/default/weather/wdatahourly.njk similarity index 100% rename from modules/default/weather/wdataHourly.njk rename to modules/default/weather/wdatahourly.njk From 85ed1b85ae3a171ce9737c878e90ba9591c63c2b Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Wed, 1 Jul 2020 05:08:04 -0400 Subject: [PATCH 07/11] added functional current, hourly, and daily forecasts via OpenWeatherMap One Call API --- .../weather/providers/openweathermap.js | 272 ++++++++---------- modules/default/weather/wdatacurrent.njk | 83 ++++++ modules/default/weather/wdatadaily.njk | 32 +++ modules/default/weather/wdatahourly.njk | 4 +- modules/default/weather/weather.js | 21 +- 5 files changed, 244 insertions(+), 168 deletions(-) create mode 100644 modules/default/weather/wdatacurrent.njk create mode 100644 modules/default/weather/wdatadaily.njk diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js index bf1dddfc..92551ca5 100755 --- a/modules/default/weather/providers/openweathermap.js +++ b/modules/default/weather/providers/openweathermap.js @@ -60,13 +60,13 @@ WeatherProvider.register("openweathermap", { fetchWeatherData() { this.fetchData(this.getUrl()) .then((data) => { - if (!data || !data.list || !data.list.length) { + if (!data) { // Did not receive usable new data. // Maybe this needs a better check? return; } - this.setFetchedLocation(`${data.lat},${data.lon}`); + this.setFetchedLocation(`(${data.lat},${data.lon})`); const wData = this.generateWeatherObjectsFromOnecall(data); this.setWeatherData(wData); @@ -121,156 +121,13 @@ WeatherProvider.register("openweathermap", { */ generateWeatherObjectsFromOnecall(data) { if (this.config.weatherEndpoint === "/onecall") { - return this.fetchOneCall(data); + return this.fetchOnecall(data); } // if weatherEndpoint does not match onecall, what should be returned? const wData = {current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits), hours: [], days: []}; return wData; }, - /* - * fetch One Call forecast information (available for free subscription). - * factors in timezone offsets. - * minutely forecasts are excluded for the moment, see getParams(). - */ - fetchOnecall(data) { - let precip = false; - // get current weather - const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); - if (!isNaN(data.current)) { - current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset/60); - current.windSpeed = data.current.wind_speed; - current.windDirection = data.current.wind_deg; - current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset/60); - current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset/60); - current.temperature = data.current.temp; - current.weatherType = this.convertWeatherType(data.current.weather[0].icon); - current.humidity = data.current.humidity; - if (current.hasOwnProperty("rain") && !isNaN(current.rain["1h"])) { - if (this.config.units === "imperial") { - weather.rain = current.rain["1h"] / 25.4; - } else { - weather.rain = current.rain["1h"]; - } - precip = true; - } - if (current.hasOwnProperty("snow") && !isNaN(current.snow["1h"])) { - if (this.config.units === "imperial") { - weather.snow = current.snow["1h"] / 25.4; - } else { - weather.snow = current.snow["1h"]; - } - precip = true; - } - if (precip) { - current.precipitation = current.rain+current.snow; - } - current.feelsLikeTemp = data.current.feels_like; - } - - let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); - - // let onecallDailyFormat = "MMM DD" - // let onecallHourlyFormat = "HH" - // let onecallMinutelyFormat = "HH:mm" - // if (this.config.timeFormat === 12) { - // if (this.config.showPeriod === true) { - // if (this.config.showPeriodUpper === true) { - // onecallHourlyFormat = "hhA" - // onecallMinutelyFormat = "hh:mmA" - // } else { - // onecallHourlyFormat = "hha" - // onecallMinutelyFormat = "hh:mma" - // } - // } else { - // onecallHourlyFormat = "hh" - // onecallMinutelyFormat = "hh:mm" - // } - // } - - // get hourly weather - const hours = []; - if (!isNaN(data.hourly)) { - for (const hour of data.hourly) { - weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60); - // weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat+","+onecallHourlyFormat); - weather.temperature = hour.temp; - weather.feelsLikeTemp = hour.feels_like; - weather.humidity = hour.humidity; - weather.windSpeed = hour.wind_speed; - weather.windDirection = hour.wind_deg; - weather.weatherType = this.convertWeatherType(hour.weather[0].icon); - precip = false; - if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) { - if (this.config.units === "imperial") { - weather.rain = hour.rain["1h"] / 25.4; - } else { - weather.rain = hour.rain["1h"]; - } - precip = true; - } - if (hour.hasOwnProperty("snow") && !isNaN(hour.snow["1h"])) { - if (this.config.units === "imperial") { - weather.snow = hour.snow["1h"] / 25.4; - } else { - weather.snow = hour.snow["1h"]; - } - precip = true; - } - if (precip) { - weather.precipitation = weather.rain+weather.snow; - } - - hours.push(weather); - weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); - } - } - - // get daily weather - const days = []; - if (!isNaN(data.daily)) { - for (const day of data.daily) { - weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60); - weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60); - weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60); - // weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat); - // weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60).format(onecallMinutelyFormat); - // weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60).format(onecallMinutelyFormat); - weather.minTemperature = day.temp.min; - weather.maxTemperature = day.temp.max; - weather.humidity = day.humidity; - weather.windSpeed = day.wind_speed; - weather.windDirection = day.wind_deg; - weather.weatherType = this.convertWeatherType(day.weather[0].icon); - precip = false; - if (!isNaN(day.rain)) { - if (this.config.units === "imperial") { - weather.rain = day.rain / 25.4; - } else { - weather.rain = day.rain; - } - precip = true; - } - if (!isNaN(day.snow)) { - if (this.config.units === "imperial") { - weather.snow = day.snow / 25.4; - } else { - weather.snow = day.snow; - } - precip = true; - } - if (precip) { - weather.precipitation = weather.rain+weather.snow; - } - - days.push(weather); - weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); - } - } - - return {current: current, hours: hours, days: days}; - }, - /* * fetch forecast information for 3-hourly forecast (available for free subscription). */ @@ -397,6 +254,129 @@ WeatherProvider.register("openweathermap", { return days; }, + /* + * Fetch One Call forecast information (available for free subscription). + * Factors in timezone offsets. + * Minutely forecasts are excluded for the moment, see getParams(). + */ + fetchOnecall(data) { + let precip = false; + + // get current weather, if requested + const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + if (data.hasOwnProperty("current")) { + current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset/60); + current.windSpeed = data.current.wind_speed; + current.windDirection = data.current.wind_deg; + current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset/60); + current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset/60); + current.temperature = data.current.temp; + current.weatherType = this.convertWeatherType(data.current.weather[0].icon); + current.humidity = data.current.humidity; + if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) { + if (this.config.units === "imperial") { + current.rain = data.current["rain"]["1h"] / 25.4; + } else { + current.rain = data.current["rain"]["1h"]; + } + precip = true; + } + if (data.current.hasOwnProperty("snow") && !isNaN(data.current["snow"]["1h"])) { + if (this.config.units === "imperial") { + current.snow = data.current["snow"]["1h"] / 25.4; + } else { + current.snow = data.current["snow"]["1h"]; + } + precip = true; + } + if (precip) { + current.precipitation = current.rain+current.snow; + } + current.feelsLikeTemp = data.current.feels_like; + } + + let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + + // get hourly weather, if requested + const hours = []; + if (data.hasOwnProperty("hourly")) { + for (const hour of data.hourly) { + weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60); + // weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat+","+onecallHourlyFormat); + weather.temperature = hour.temp; + weather.feelsLikeTemp = hour.feels_like; + weather.humidity = hour.humidity; + weather.windSpeed = hour.wind_speed; + weather.windDirection = hour.wind_deg; + weather.weatherType = this.convertWeatherType(hour.weather[0].icon); + precip = false; + if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) { + if (this.config.units === "imperial") { + weather.rain = hour.rain["1h"] / 25.4; + } else { + weather.rain = hour.rain["1h"]; + } + precip = true; + } + if (hour.hasOwnProperty("snow") && !isNaN(hour.snow["1h"])) { + if (this.config.units === "imperial") { + weather.snow = hour.snow["1h"] / 25.4; + } else { + weather.snow = hour.snow["1h"]; + } + precip = true; + } + if (precip) { + weather.precipitation = weather.rain+weather.snow; + } + + hours.push(weather); + weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + } + } + + // get daily weather, if requested + const days = []; + if (data.hasOwnProperty("daily")) { + for (const day of data.daily) { + weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60); + weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60); + weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60); + weather.minTemperature = day.temp.min; + weather.maxTemperature = day.temp.max; + weather.humidity = day.humidity; + weather.windSpeed = day.wind_speed; + weather.windDirection = day.wind_deg; + weather.weatherType = this.convertWeatherType(day.weather[0].icon); + precip = false; + if (!isNaN(day.rain)) { + if (this.config.units === "imperial") { + weather.rain = day.rain / 25.4; + } else { + weather.rain = day.rain; + } + precip = true; + } + if (!isNaN(day.snow)) { + if (this.config.units === "imperial") { + weather.snow = day.snow / 25.4; + } else { + weather.snow = day.snow; + } + precip = true; + } + if (precip) { + weather.precipitation = weather.rain+weather.snow; + } + + days.push(weather); + weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); + } + } + + return {current: current, hours: hours, days: days}; + }, + /* * Convert the OpenWeatherMap icons to a more usable name. */ diff --git a/modules/default/weather/wdatacurrent.njk b/modules/default/weather/wdatacurrent.njk new file mode 100644 index 00000000..700b4355 --- /dev/null +++ b/modules/default/weather/wdatacurrent.njk @@ -0,0 +1,83 @@ +{% if wData %} + {% set current = wData.current %} + {% if not config.onlyTemp %} +
+ + + {% if config.useBeaufort %} + {{ current.beaufortWindSpeed() | round }} + {% else %} + {{ current.windSpeed | round }} + {% endif %} + {% if config.showWindDirection %} + + {% if config.showWindDirectionAsArrow %} + + {% else %} + {{ current.cardinalWindDirection() | translate }} + {% endif %} +   + + {% endif %} + + {% if config.showHumidity and current.humidity %} + {{ current.humidity | decimalSymbol }}  + {% endif %} + {% if config.showSun %} + + + {% if current.nextSunAction() === "sunset" %} + {{ current.sunset | formatTime }} + {% else %} + {{ current.sunrise | formatTime }} + {% endif %} + + {% endif %} +
+ {% endif %} +
+ + + {{ current.temperature | roundValue | unit("temperature") | decimalSymbol }} + +
+
+ {% if config.showIndoorTemperature and indoor.temperature %} +
+ + + {{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }} + +
+ {% endif %} + {% if config.showIndoorHumidity and indoor.humidity %} +
+ + + {{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }} + +
+ {% endif %} +
+ {% if (config.showFeelsLike or config.showPrecipitationAmount) and not config.onlyTemp %} +
+ {% if config.showFeelsLike %} + + {{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }} + + {% endif %} + {% if config.showPrecipitationAmount %} + + {{ "PRECIP" | translate }} {{ current.precipitation | unit("precip") }} + + {% endif %} +
+ {% endif %} +{% else %} +
+ {{ "LOADING" | translate | safe }} +
+{% endif %} + + + diff --git a/modules/default/weather/wdatadaily.njk b/modules/default/weather/wdatadaily.njk new file mode 100644 index 00000000..99d588e7 --- /dev/null +++ b/modules/default/weather/wdatadaily.njk @@ -0,0 +1,32 @@ +{% if wData %} + {% set numSteps = wData.days | calcNumEntries %} + {% set currentStep = 0 %} + + {% set days = wData.days.slice(0, numSteps) %} + {% for day in days %} + + + + + + {% if config.showPrecipitationAmount %} + + {% endif %} + + {% set currentStep = currentStep + 1 %} + {% endfor %} +
{{ day.date.format('ddd') }} + {{ day.maxTemperature | roundValue | unit("temperature") }} + + {{ day.minTemperature | roundValue | unit("temperature") }} + + {{ day.precipitation | unit("precip") }} +
+{% else %} +
+ {{ "LOADING" | translate | safe }} +
+{% endif %} + + + diff --git a/modules/default/weather/wdatahourly.njk b/modules/default/weather/wdatahourly.njk index c69e425f..140c8b0d 100644 --- a/modules/default/weather/wdatahourly.njk +++ b/modules/default/weather/wdatahourly.njk @@ -5,9 +5,9 @@ {% set hours = wData.hours.slice(0, numSteps) %} {% for hour in hours %} - {{ hour.date | formatTimeMoment }} + {{ hour.date | formatTime }} - + {{ hour.temperature | roundValue | unit("temperature") }} {% if config.showPrecipitationAmount %} diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js index 09046071..ef2a3bd6 100644 --- a/modules/default/weather/weather.js +++ b/modules/default/weather/weather.js @@ -193,25 +193,6 @@ Module.register("weather", { return date.format("HH:mm"); }.bind(this) ); - this.nunjucksEnvironment().addFilter( - "formatTimeMoment", - function (date) { - - if (this.config.timeFormat !== 24) { - if (this.config.showPeriod) { - if (this.config.showPeriodUpper) { - return date.format("h:mm A"); - } else { - return date.format("h:mm a"); - } - } else { - return date.format("h:mm"); - } - } - - return date.format("HH:mm"); - }.bind(this) - ); this.nunjucksEnvironment().addFilter( "unit", @@ -230,7 +211,7 @@ Module.register("weather", { } } } else if (type === "precip") { - if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") { + if (value === null || isNaN(value) || value === 0 || value.toFixed(2) === "0.00") { value = ""; } else { if (this.config.weatherProvider === "ukmetoffice" || this.config.weatherProvider === "ukmetofficedatahub") { From 8f2731911b5ab7663ea135b70c5c57504fee0421 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Wed, 8 Jul 2020 23:12:01 -0400 Subject: [PATCH 08/11] added changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d5bc9b4..7799978b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ _This release is scheduled to be released on 2020-10-01._ ### Added +- Added current, hourly (max 48), and daily (max 7) weather forecasts to the default Weather module via the OpenWeatherMap One Call API + ### Updated - Change incorrect weather.js default properties. From f6854f58ff43abff9ca7aceaa4aceb73d5b7c1f3 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Sat, 1 Aug 2020 14:03:48 -0400 Subject: [PATCH 09/11] ran prettier on weather module code --- .../weather/providers/openweathermap.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js index 414c23c0..12635f1b 100755 --- a/modules/default/weather/providers/openweathermap.js +++ b/modules/default/weather/providers/openweathermap.js @@ -124,7 +124,7 @@ WeatherProvider.register("openweathermap", { return this.fetchOnecall(data); } // if weatherEndpoint does not match onecall, what should be returned? - const weatherData = {current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits), hours: [], days: []}; + const weatherData = { current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits), hours: [], days: [] }; return weatherData; }, @@ -265,11 +265,11 @@ WeatherProvider.register("openweathermap", { // get current weather, if requested const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); if (data.hasOwnProperty("current")) { - current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset/60); + current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset / 60); current.windSpeed = data.current.wind_speed; current.windDirection = data.current.wind_deg; - current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset/60); - current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset/60); + current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset / 60); + current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset / 60); current.temperature = data.current.temp; current.weatherType = this.convertWeatherType(data.current.weather[0].icon); current.humidity = data.current.humidity; @@ -290,7 +290,7 @@ WeatherProvider.register("openweathermap", { precip = true; } if (precip) { - current.precipitation = current.rain+current.snow; + current.precipitation = current.rain + current.snow; } current.feelsLikeTemp = data.current.feels_like; } @@ -301,7 +301,7 @@ WeatherProvider.register("openweathermap", { const hours = []; if (data.hasOwnProperty("hourly")) { for (const hour of data.hourly) { - weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60); + weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset / 60); // weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat+","+onecallHourlyFormat); weather.temperature = hour.temp; weather.feelsLikeTemp = hour.feels_like; @@ -327,7 +327,7 @@ WeatherProvider.register("openweathermap", { precip = true; } if (precip) { - weather.precipitation = weather.rain+weather.snow; + weather.precipitation = weather.rain + weather.snow; } hours.push(weather); @@ -339,9 +339,9 @@ WeatherProvider.register("openweathermap", { const days = []; if (data.hasOwnProperty("daily")) { for (const day of data.daily) { - weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60); - weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60); - weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60); + weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset / 60); + weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset / 60); + weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset / 60); weather.minTemperature = day.temp.min; weather.maxTemperature = day.temp.max; weather.humidity = day.humidity; @@ -366,7 +366,7 @@ WeatherProvider.register("openweathermap", { precip = true; } if (precip) { - weather.precipitation = weather.rain+weather.snow; + weather.precipitation = weather.rain + weather.snow; } days.push(weather); @@ -374,7 +374,7 @@ WeatherProvider.register("openweathermap", { } } - return {current: current, hours: hours, days: days}; + return { current: current, hours: hours, days: days }; }, /* From a4d73e2a67a8f6b507a76a878221682c7666a89b Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Sat, 1 Aug 2020 17:39:58 -0400 Subject: [PATCH 10/11] amended code according to pull request reviews --- modules/default/weather/weather.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js index 83db10d2..6fbbc418 100644 --- a/modules/default/weather/weather.js +++ b/modules/default/weather/weather.js @@ -11,7 +11,7 @@ Module.register("weather", { defaults: { weatherProvider: "openweathermap", roundTemp: false, - type: "current", + type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint) lat: 0, lon: 0, @@ -127,10 +127,17 @@ Module.register("weather", { // Select the template depending on the display type. getTemplate: function () { - if (this.config.type === "daily") { - return `forecast.njk`; + switch (this.config.type.toLowerCase()) { + case "current": + return `current.njk`; + case "hourly": + return `hourly.njk`; + case "daily": + case "forecast": + return `forecast.njk`; + default: + return `${this.config.type.toLowerCase()}.njk`; } - return `${this.config.type.toLowerCase()}.njk`; }, // Add all the data to the template. From 3901543697758b3126d150d0e195e51a6b11a662 Mon Sep 17 00:00:00 2001 From: Bryan Zhu Date: Sun, 2 Aug 2020 00:22:19 -0400 Subject: [PATCH 11/11] readded processing for numEntries option that I accidentally took out --- modules/default/weather/forecast.njk | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/default/weather/forecast.njk b/modules/default/weather/forecast.njk index bd223101..7d0f515d 100644 --- a/modules/default/weather/forecast.njk +++ b/modules/default/weather/forecast.njk @@ -1,8 +1,10 @@ {% if forecast or weatherData %} {% if weatherData %} {% set forecast = weatherData.days %} + {% set numSteps = forecast | calcNumEntries %} + {% else %} + {% set numSteps = forecast | calcNumSteps %} {% endif %} - {% set numSteps = forecast | calcNumSteps %} {% set currentStep = 0 %} {% set forecast = forecast.slice(0, numSteps) %}