diff --git a/.eslintrc.json b/.eslintrc.json index 31aae27f..637b2eb7 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,7 +23,9 @@ }, "rules": { "prettier/prettier": "error", + "eqeqeq": "error", "no-prototype-builtins": "off", - "no-unused-vars": "off" + "no-unused-vars": "off", + "no-useless-return": "error" } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 06693572..d2c00f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,9 +20,10 @@ _This release is scheduled to be released on 2021-10-01._ - Refactor test configs, use default test config for all tests. - Updated github templates. - Actually test all js and css files when lint script is run. -- Update jsdocs and print warnings during testing too +- Update jsdocs and print warnings during testing too. - Update weathergov provider to try fetching not just current, but also foreacst, when API URLs available. -- Refactored clock layout +- Refactored clock layout. +- Refactored methods from weatherproviders into weatherobject (isDaytime, updateSunTime). ### Fixed diff --git a/modules/default/weather/providers/envcanada.js b/modules/default/weather/providers/envcanada.js index 0f9ef21e..868ee6e2 100644 --- a/modules/default/weather/providers/envcanada.js +++ b/modules/default/weather/providers/envcanada.js @@ -164,9 +164,7 @@ WeatherProvider.register("envcanada", { // CORS errors when accessing EC // getUrl() { - const path = "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml"; - - return path; + return "https://thingproxy.freeboard.io/fetch/https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml"; }, // @@ -513,8 +511,6 @@ WeatherProvider.register("envcanada", { weather.maxTemperature = this.convertTemp(nextTemp); } } - - return; }, // @@ -560,8 +556,6 @@ WeatherProvider.register("envcanada", { weather.precipitation = foreGroup[today].querySelector("abbreviatedForecast pop").textContent; weather.precipitationUnits = foreGroup[today].querySelector("abbreviatedForecast pop").getAttribute("units"); } - - return; }, // diff --git a/modules/default/weather/providers/smhi.js b/modules/default/weather/providers/smhi.js index 244fc1de..83af249b 100644 --- a/modules/default/weather/providers/smhi.js +++ b/modules/default/weather/providers/smhi.js @@ -1,4 +1,4 @@ -/* global WeatherProvider, WeatherObject, SunCalc */ +/* global WeatherProvider, WeatherObject */ /* Magic Mirror * Module: Weather @@ -105,19 +105,20 @@ WeatherProvider.register("smhi", { * @returns {WeatherObject} The converted weatherdata at the specified location */ convertWeatherDataToObject(weatherData, coordinates) { - let currentWeather = new WeatherObject("metric", "metric", "metric"); //Weather data is only for Sweden and nobody in Sweden would use imperial + // Weather data is only for Sweden and nobody in Sweden would use imperial + let currentWeather = new WeatherObject("metric", "metric", "metric"); currentWeather.date = moment(weatherData.validTime); - let times = SunCalc.getTimes(currentWeather.date.toDate(), coordinates.lat, coordinates.lon); - currentWeather.sunrise = moment(times.sunrise, "X"); - currentWeather.sunset = moment(times.sunset, "X"); + currentWeather.updateSunTime(coordinates.lat, coordinates.lon); currentWeather.humidity = this.paramValue(weatherData, "r"); currentWeather.temperature = this.paramValue(weatherData, "t"); currentWeather.windSpeed = this.paramValue(weatherData, "ws"); currentWeather.windDirection = this.paramValue(weatherData, "wd"); - currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), this.isDayTime(currentWeather)); + currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), currentWeather.isDayTime()); - //Determine the precipitation amount and category and update the weatherObject with it, the valuetype to use can be configured or uses median as default. + // Determine the precipitation amount and category and update the + // weatherObject with it, the valuetype to use can be configured or uses + // median as default. let precipitationValue = this.paramValue(weatherData, this.config.precipitationValue); switch (this.paramValue(weatherData, "pcat")) { // 0 = No precipitation @@ -171,7 +172,7 @@ WeatherProvider.register("smhi", { } //Keep track of what icons has been used for each hour of daytime and use the middle one for the forecast - if (this.isDayTime(weatherObject)) { + if (weatherObject.isDayTime()) { dayWeatherTypes.push(weatherObject.weatherType); } if (dayWeatherTypes.length > 0) { @@ -201,16 +202,6 @@ WeatherProvider.register("smhi", { return { lat: data.geometry.coordinates[0][1], lon: data.geometry.coordinates[0][0] }; }, - /** - * Checks if the weatherObject is at dayTime. - * - * @param {WeatherObject} weatherObject The weatherObject to look at - * @returns {boolean} true if it is at dayTime - */ - isDayTime(weatherObject) { - return weatherObject.date.isBetween(weatherObject.sunrise, weatherObject.sunset, undefined, "[]"); - }, - /** * The distance between the data points is increasing in the data the more distant the prediction is. * Find these gaps and fill them with the previous hours data to make the data returned a complete set. diff --git a/modules/default/weather/providers/ukmetoffice.js b/modules/default/weather/providers/ukmetoffice.js index 27127806..158592bd 100755 --- a/modules/default/weather/providers/ukmetoffice.js +++ b/modules/default/weather/providers/ukmetoffice.js @@ -1,4 +1,4 @@ -/* global WeatherProvider, WeatherObject, SunCalc */ +/* global WeatherProvider, WeatherObject */ /* Magic Mirror * Module: Weather @@ -116,9 +116,7 @@ WeatherProvider.register("ukmetoffice", { } // determine the sunrise/sunset times - not supplied in UK Met Office data - let times = this.calcAstroData(location); - currentWeather.sunrise = times[0]; - currentWeather.sunset = times[1]; + currentWeather.updateSunTime(location.lat, location.lon); return currentWeather; }, @@ -154,20 +152,6 @@ WeatherProvider.register("ukmetoffice", { return days; }, - /* - * calculate the astronomical data - */ - calcAstroData(location) { - const sunTimes = []; - - // determine the sunrise/sunset times - let times = SunCalc.getTimes(new Date(), location.lat, location.lon); - sunTimes.push(moment(times.sunrise, "X")); - sunTimes.push(moment(times.sunset, "X")); - - return sunTimes; - }, - /* * Convert the Met Office icons to a more usable name. */ @@ -248,16 +232,16 @@ WeatherProvider.register("ukmetoffice", { return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null; }, - /* + /** * Generates an url with api parameters based on the config. * - * return String - URL params. + * @param {string} forecastType daily or 3hourly forecast + * @returns {string} url */ getParams(forecastType) { let params = "?"; params += "res=" + forecastType; params += "&key=" + this.config.apiKey; - return params; } }); diff --git a/modules/default/weather/providers/ukmetofficedatahub.js b/modules/default/weather/providers/ukmetofficedatahub.js index 054dce78..9603841b 100644 --- a/modules/default/weather/providers/ukmetofficedatahub.js +++ b/modules/default/weather/providers/ukmetofficedatahub.js @@ -1,4 +1,4 @@ -/* global WeatherProvider, WeatherObject, SunCalc */ +/* global WeatherProvider, WeatherObject */ /* Magic Mirror * Module: Weather @@ -71,13 +71,11 @@ WeatherProvider.register("ukmetofficedatahub", { // For DataHub requests, the API key/secret are sent in the headers rather than as query strings. // Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api) getHeaders() { - let headers = { + return { accept: "application/json", "x-ibm-client-id": this.config.apiKey, "x-ibm-client-secret": this.config.apiSecret }; - - return headers; }, // Fetch data using supplied URL and request headers @@ -150,11 +148,9 @@ WeatherProvider.register("ukmetofficedatahub", { } // Determine the sunrise/sunset times - (still) not supplied in UK Met Office data - // Passes {longitude, latitude, height} to calcAstroData - // Could just pass lat/long from this.config, but returned data from MO also contains elevation - let times = this.calcAstroData(currentWeatherData.features[0].geometry.coordinates); - currentWeather.sunrise = times[0]; - currentWeather.sunset = times[1]; + // Passes {longitude, latitude} to SunCalc, could pass height to, but + // SunCalc.getTimes doesnt take that into account + currentWeather.updateSunTime(this.config.lat, this.config.lon); return currentWeather; }, @@ -223,7 +219,6 @@ WeatherProvider.register("ukmetofficedatahub", { // Pass on full details so they can be used in custom templates // Note the units of the supplied data when using this (see top of file) - forecastWeather.rawData = forecastDataDays[day]; dailyForecasts.push(forecastWeather); @@ -238,18 +233,6 @@ WeatherProvider.register("ukmetofficedatahub", { this.fetchedLocationName = name; }, - // Calculate sunrise/sunset times - calcAstroData(location) { - const sunTimes = []; - - // Careful to pass values to SunCalc in correct order (latitude, longitude, elevation) - let times = SunCalc.getTimes(new Date(), location[1], location[0], location[2]); - sunTimes.push(moment(times.sunrise, "X")); - sunTimes.push(moment(times.sunset, "X")); - - return sunTimes; - }, - // Convert temperatures to Fahrenheit (from degrees C), if required convertTemp(tempInC) { return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC; diff --git a/modules/default/weather/providers/weatherbit.js b/modules/default/weather/providers/weatherbit.js index afca0c83..94b9caea 100644 --- a/modules/default/weather/providers/weatherbit.js +++ b/modules/default/weather/providers/weatherbit.js @@ -89,7 +89,6 @@ WeatherProvider.register("weatherbit", { currentWeather.windSpeed = parseFloat(currentWeatherData.data[0].wind_spd); currentWeather.windDirection = currentWeatherData.data[0].wind_dir; currentWeather.weatherType = this.convertWeatherType(currentWeatherData.data[0].weather.icon); - Log.log("Wx Icon: " + currentWeatherData.data[0].weather.icon); currentWeather.sunrise = moment(currentWeatherData.data[0].sunrise, "HH:mm").add(tzOffset, "m"); currentWeather.sunset = moment(currentWeatherData.data[0].sunset, "HH:mm").add(tzOffset, "m"); diff --git a/modules/default/weather/providers/weathergov.js b/modules/default/weather/providers/weathergov.js index ab46b4ee..31934784 100755 --- a/modules/default/weather/providers/weathergov.js +++ b/modules/default/weather/providers/weathergov.js @@ -1,4 +1,4 @@ -/* global WeatherProvider, WeatherObject, SunCalc */ +/* global WeatherProvider, WeatherObject */ /* Magic Mirror * Module: Weather @@ -130,7 +130,7 @@ WeatherProvider.register("weathergov", { // excellent, let's fetch some actual wx data this.configURLs = true; // handle 'forecast' config, fall back to 'current' - if (config.type == "forecast") { + if (config.type === "forecast") { this.fetchWeatherForecast(); } else { this.fetchCurrentWeather(); @@ -158,18 +158,11 @@ WeatherProvider.register("weathergov", { currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value); currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value); - let isDaytime = true; - if (currentWeatherData.icon.includes("day")) { - isDaytime = true; - } else { - isDaytime = false; - } - currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, isDaytime); - // determine the sunrise/sunset times - not supplied in weather.gov data - let times = this.calcAstroData(this.config.lat, this.config.lon); - currentWeather.sunrise = times[0]; - currentWeather.sunset = times[1]; + currentWeather.updateSunTime(this.config.lat, this.config.lon); + + // update weatherType + currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, currentWeather.isDayTime()); return currentWeather; }, @@ -272,20 +265,6 @@ WeatherProvider.register("weathergov", { } }, - /* - * Calculate the astronomical data - */ - calcAstroData(lat, lon) { - const sunTimes = []; - - // determine the sunrise/sunset times - let times = SunCalc.getTimes(new Date(), lat, lon); - sunTimes.push(moment(times.sunrise, "X")); - sunTimes.push(moment(times.sunset, "X")); - - return sunTimes; - }, - /* * Convert the icons to a more usable name. */ diff --git a/modules/default/weather/weatherobject.js b/modules/default/weather/weatherobject.js index 5bd5be9a..9224208a 100755 --- a/modules/default/weather/weatherobject.js +++ b/modules/default/weather/weatherobject.js @@ -1,3 +1,5 @@ +/* global SunCalc */ + /* Magic Mirror * Module: Weather * @@ -10,6 +12,14 @@ * As soon as we start implementing the forecast, mode properties will be added. */ class WeatherObject { + /** + * Constructor for a WeatherObject + * + * @param {string} units what units to use, "imperial" or "metric" + * @param {string} tempUnits what tempunits to use + * @param {string} windUnits what windunits to use + * @param {boolean} useKmh use kmh if true, mps if false + */ constructor(units, tempUnits, windUnits, useKmh) { this.units = units; this.tempUnits = tempUnits; @@ -80,8 +90,7 @@ class WeatherObject { } kmhWindSpeed() { - const windInKmh = this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000; - return windInKmh; + return this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000; } nextSunAction() { @@ -113,4 +122,31 @@ class WeatherObject { return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9; } + + /** + * Checks if the weatherObject is at dayTime. + * + * @returns {boolean} true if it is at dayTime + */ + isDayTime() { + return this.date.isBetween(this.sunrise, this.sunset, undefined, "[]"); + } + + /** + * Update the sunrise / sunset time depending on the location + * + * @param {number} lat latitude + * @param {number} lon longitude + */ + updateSunTime(lat, lon) { + let now = !this.date ? new Date() : this.date.toDate(); + let times = SunCalc.getTimes(now, lat, lon); + this.sunrise = moment(times.sunrise, "X"); + this.sunset = moment(times.sunset, "X"); + } +} + +/*************** DO NOT EDIT THE LINE BELOW ***************/ +if (typeof module !== "undefined") { + module.exports = WeatherObject; } diff --git a/package-lock.json b/package-lock.json index 68a80731..ae6ab35e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,8 @@ "stylelint": "^13.13.1", "stylelint-config-prettier": "^8.0.2", "stylelint-config-standard": "^22.0.0", - "stylelint-prettier": "^1.2.0" + "stylelint-prettier": "^1.2.0", + "suncalc": "^1.8.0" }, "engines": { "node": ">=12" @@ -9373,6 +9374,12 @@ "node": ">= 8.0" } }, + "node_modules/suncalc": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.8.0.tgz", + "integrity": "sha1-HZiYEJVjB4dQ9JlKlZ5lTYdqy/U=", + "dev": true + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -17712,6 +17719,12 @@ "debug": "^4.1.0" } }, + "suncalc": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/suncalc/-/suncalc-1.8.0.tgz", + "integrity": "sha1-HZiYEJVjB4dQ9JlKlZ5lTYdqy/U=", + "dev": true + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/package.json b/package.json index dd96c7af..6bce27aa 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "stylelint": "^13.13.1", "stylelint-config-prettier": "^8.0.2", "stylelint-config-standard": "^22.0.0", - "stylelint-prettier": "^1.2.0" + "stylelint-prettier": "^1.2.0", + "suncalc": "^1.8.0" }, "optionalDependencies": { "electron": "^13.2.3" diff --git a/tests/unit/functions/weather_object_spec.js b/tests/unit/functions/weather_object_spec.js new file mode 100644 index 00000000..8878f377 --- /dev/null +++ b/tests/unit/functions/weather_object_spec.js @@ -0,0 +1,24 @@ +const WeatherObject = require("../../../modules/default/weather/weatherobject.js"); + +global.moment = require("moment"); +global.SunCalc = require("suncalc"); + +describe("WeatherObject", function () { + let weatherobject; + + beforeAll(function () { + weatherobject = new WeatherObject("metric", "metric", "metric", true); + }); + + it("should return true for daytime at noon", function () { + weatherobject.date = moment(12, "HH"); + weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327); + expect(weatherobject.isDayTime()).toBe(true); + }); + + it("should return false for daytime at midnight", function () { + weatherobject.date = moment(0, "HH"); + weatherobject.updateSunTime(-6.774877582342688, 37.63345667023327); + expect(weatherobject.isDayTime()).toBe(false); + }); +});