From 86d6185fa573335f04d26198f8b2e996d1c66b30 Mon Sep 17 00:00:00 2001 From: Gerard Martin <344915+gerjomarty@users.noreply.github.com> Date: Thu, 2 Jul 2020 21:13:39 +0100 Subject: [PATCH] Switch from DarkSky to OpenWeather One Call As DarkSky is no longer open to new applicants. Most elements are replicated, though precipitation percentage is no longer available. --- MMM-DarkSkyForecast.js | 190 +++++++++++++++++++++++---------------- README.md | 14 +-- mmm-darksky-forecast.njk | 31 +++---- node_helper.js | 31 +++---- 4 files changed, 149 insertions(+), 117 deletions(-) diff --git a/MMM-DarkSkyForecast.js b/MMM-DarkSkyForecast.js index bf012cd..6e9c309 100644 --- a/MMM-DarkSkyForecast.js +++ b/MMM-DarkSkyForecast.js @@ -5,7 +5,7 @@ https://github.com/jclarke0000/MMM-DarkSkyForecast Icons in use by this module: - + Skycons - Animated icon set by Dark Sky http://darkskyapp.github.io/skycons/ (using the fork created by Maxime Warner @@ -54,7 +54,7 @@ Module.register("MMM-DarkSkyForecast", { longitude: "", updateInterval: 10, // minutes requestDelay: 0, - units: "ca", + units: config.units, showCurrentConditions: true, showExtraCurrentConditions: true, showSummary: true, @@ -85,10 +85,10 @@ Module.register("MMM-DarkSkyForecast", { label_timeFormat: "h a", label_days: ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"], label_ordinals: ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"], - moduleTimestampIdPrefix: "DARK_SKY_TIMESTAMP_" + moduleTimestampIdPrefix: "OPENWEATHER_ONE_CALL_TIMESTAMP_" }, - validUnits: ["ca","si","uk2","us"], + validUnits: ["imperial", "metric", ""], validLayouts: ["tiled", "table"], getScripts: function() { @@ -129,7 +129,6 @@ Module.register("MMM-DarkSkyForecast", { moduleTimestampIdPrefix: this.config.moduleTimestampIdPrefix, identifier: this.identifier, timeStamp: this.dataRefreshTimeStamp - }; }, @@ -145,7 +144,7 @@ Module.register("MMM-DarkSkyForecast", { /* Optionally, Dark Sky's Skycons animated icon set can be used. If so, it is drawn to the DOM - and animated on demand as opposed to being + and animated on demand as opposed to being contained in animated images such as GIFs or SVGs. This initializes the colours for the icons to use. */ @@ -154,7 +153,7 @@ Module.register("MMM-DarkSkyForecast", { "monochrome": false, "colors" : { "main" : "#FFFFFF", - "moon" : this.config.colored ? "#FFFDC2" : "#FFFFFF", + "moon" : this.config.colored ? "#FFFDC2" : "#FFFFFF", "fog" : "#FFFFFF", "fogbank" : "#FFFFFF", "cloud" : this.config.colored ? "#BEBEBE" : "#999999", @@ -168,11 +167,11 @@ Module.register("MMM-DarkSkyForecast", { //sanitize optional parameters if (this.validUnits.indexOf(this.config.units) == -1) { - this.config.units = "ca"; - } + this.config.units = "imperial"; + } if (this.validLayouts.indexOf(this.config.forecastLayout) == -1) { this.config.forecastLayout = "tiled"; - } + } if (this.iconsets[this.config.iconset] == null) { this.config.iconset = "1c"; } @@ -188,11 +187,9 @@ Module.register("MMM-DarkSkyForecast", { "animatedIconPlayDelay" ]); - - //force icon set to mono version whern config.coloured = false if (this.config.colored == false) { - this.config.iconset = this.config.iconset.replace("c","m"); + this.config.iconset = this.config.iconset.replace("c","m"); } //start data poll @@ -207,12 +204,10 @@ Module.register("MMM-DarkSkyForecast", { }, self.config.updateInterval * 60 * 1000); //convert to milliseconds }, this.config.requestDelay); - - }, getData: function() { - this.sendSocketNotification("DARK_SKY_FORECAST_GET", { + this.sendSocketNotification("OPENWEATHER_ONE_CALL_FORECAST_GET", { apikey: this.config.apikey, latitude: this.config.latitude, longitude: this.config.longitude, @@ -226,7 +221,7 @@ Module.register("MMM-DarkSkyForecast", { socketNotificationReceived: function(notification, payload) { - if (notification == "DARK_SKY_FORECAST_DATA" && payload.instanceId == this.identifier) { + if (notification === "OPENWEATHER_ONE_CALL_FORECAST_DATA" && payload.instanceId === this.identifier) { //clear animated icon cache if (this.config.useAnimatedIcons) { @@ -241,7 +236,7 @@ Module.register("MMM-DarkSkyForecast", { this.updateDom(this.config.updateFadeSpeed); //broadcast weather update - this.sendNotification("DARK_SKY_FORECAST_WEATHER_UPDATE", payload); + this.sendNotification("OPENWEATHER_ONE_CALL_FORECAST_WEATHER_UPDATE", payload); /* Start icon playback. We need to wait until the DOM update @@ -260,7 +255,7 @@ Module.register("MMM-DarkSkyForecast", { self.playIcons(self); } }, 100); - } + } } @@ -276,11 +271,11 @@ Module.register("MMM-DarkSkyForecast", { var summary; if (this.config.concise) { - summary = this.weatherData.hourly ? this.weatherData.hourly.summary : this.weatherData.currently.summary; + summary = this.weatherData.hourly ? this.weatherData.hourly[0].weather[0].description : this.weatherData.current.weather[0].description; } else { - summary = (this.weatherData.minutely ? this.weatherData.minutely.summary : this.weatherData.currently.summary + ".") + " " + - (this.weatherData.hourly ? this.weatherData.hourly.summary + " " : "") + - (this.weatherData.daily ? this.weatherData.daily.summary : ""); + summary = (this.weatherData.current.weather[0].description + ".") + " " + + (this.weatherData.hourly ? this.weatherData.hourly[0].weather[0].description + " " : "") + + (this.weatherData.daily ? this.weatherData.daily[0].weather[0].description : ""); } var hourlies = []; @@ -289,11 +284,11 @@ Module.register("MMM-DarkSkyForecast", { var displayCounter = 0; var currentIndex = this.config.hourlyForecastInterval; while (displayCounter < this.config.maxHourliesToShow) { - if (this.weatherData.hourly.data[currentIndex] == null) { + if (this.weatherData.hourly[currentIndex] == null) { break; } - hourlies.push(this.forecastItemFactory(this.weatherData.hourly.data[currentIndex], "hourly")); + hourlies.push(this.forecastItemFactory(this.weatherData.hourly[currentIndex], "hourly")); currentIndex += this.config.hourlyForecastInterval; displayCounter++; @@ -306,11 +301,11 @@ Module.register("MMM-DarkSkyForecast", { if (this.config.showDailyForecast) { for (var i = 1; i <= this.config.maxDailiesToShow; i++) { - if (this.weatherData.daily.data[i] == null) { + if (this.weatherData.daily[i] == null) { break; } - dailies.push(this.forecastItemFactory(this.weatherData.daily.data[i], "daily")); + dailies.push(this.forecastItemFactory(this.weatherData.daily[i], "daily")); } } @@ -318,20 +313,20 @@ Module.register("MMM-DarkSkyForecast", { return { "currently" : { - temperature: Math.round(this.weatherData.currently.temperature) + "°", + temperature: Math.round(this.weatherData.current.temp) + "°", animatedIconId: this.config.useAnimatedIcons ? this.getAnimatedIconId() : null, - animatedIconName: this.weatherData.currently.icon, - iconPath: this.generateIconSrc(this.weatherData.currently.icon), - tempRange: this.formatHiLowTemperature(this.weatherData.daily.data[0].temperatureMax,this.weatherData.daily.data[0].temperatureMin), - precipitation: this.formatPrecipitation(this.weatherData.currently.precipProbability, this.weatherData.currently.precipAccumulation, this.weatherData.currently.precipIntensityMax, this.weatherData.currently.precipIntensity), - wind: this.formatWind(this.weatherData.currently.windSpeed, this.weatherData.currently.windBearing, this.weatherData.currently.windGust), + animatedIconName: this.convertOpenWeatherIdToIcon(this.weatherData.current.weather[0].id, this.weatherData.current.weather[0].icon), + iconPath: this.generateIconSrc(this.convertOpenWeatherIdToIcon(this.weatherData.current.weather[0].id, this.weatherData.current.weather[0].icon)), + tempRange: this.formatHiLowTemperature(this.weatherData.daily[0].temp.max, this.weatherData.daily[0].temp.min), + precipitation: this.formatPrecipitation(this.weatherData.current.rain, this.weatherData.current.snow), + wind: this.formatWind(this.weatherData.current.wind_speed, this.weatherData.current.wind_deg, this.weatherData.current.wind_gust), }, "summary" : summary, "hourly" : hourlies, "daily" : dailies, }; - }, + }, /* @@ -346,34 +341,34 @@ Module.register("MMM-DarkSkyForecast", { if (type == "daily") { //day name (e.g.: "MON") - fItem.day = this.config.label_days[moment(fData.time * 1000).format("d")]; + fItem.day = this.config.label_days[moment(fData.dt * 1000).format("d")]; } else { //hourly //time (e.g.: "5 PM") - fItem.time = moment(fData.time * 1000).format(this.config.label_timeFormat); + fItem.time = moment(fData.dt * 1000).format(this.config.label_timeFormat); } // --------- Icon --------- if (this.config.useAnimatedIcons && !this.config.animateMainIconOnly) { fItem.animatedIconId = this.getAnimatedIconId(); - fItem.animatedIconName = fData.icon; + fItem.animatedIconName = this.convertOpenWeatherIdToIcon(fData.weather[0].id, fData.weather[0].icon); } - fItem.iconPath = this.generateIconSrc(fData.icon); + fItem.iconPath = this.generateIconSrc(this.convertOpenWeatherIdToIcon(fData.weather[0].id, fData.weather[0].icon)); // --------- Temperature --------- if (type == "hourly") { //just display projected temperature for that hour - fItem.temperature = Math.round(fData.temperature) + "°"; + fItem.temperature = Math.round(fData.temp) + "°"; } else { //display High / Low temperatures - fItem.tempRange = this.formatHiLowTemperature(fData.temperatureMax,fData.temperatureMin); + fItem.tempRange = this.formatHiLowTemperature(fData.temp.max, fData.temp.min); } // --------- Precipitation --------- - fItem.precipitation = this.formatPrecipitation(fData.precipProbability,fData.precipAccumulation,fData.precipIntensityMax,fData.precipIntensity); + fItem.precipitation = this.formatPrecipitation(fData.rain, fData.snow); // --------- Wind --------- - fItem.wind = (this.formatWind(fData.windSpeed, fData.windBearing, fData.windGust)); + fItem.wind = (this.formatWind(fData.wind_speed, fData.wind_deg, fData.wind_gust)); return fItem; }, @@ -391,24 +386,26 @@ Module.register("MMM-DarkSkyForecast", { /* Returns a formatted data object for precipitation */ - formatPrecipitation: function(percentChance, snowAccumulation, rainIntensityMax, rainIntensity) { + formatPrecipitation: function(rainAccumulation, snowAccumulation) { var accumulation = null; //accumulation - if (!this.config.concise && percentChance > 0) { - if (snowAccumulation) { //snow + if (snowAccumulation) { + if (typeof snowAccumulation === "number") { accumulation = Math.round(snowAccumulation) + " " + this.getUnit("accumulationSnow"); - } else if (rainIntensityMax){ //max rate for the day - accumulation = (Math.round(rainIntensityMax * 10) / 10) + " " + this.getUnit("accumulationRain"); - } else { //rate for the hour - accumulation = (Math.round(rainIntensity * 10) / 10) + " " + this.getUnit("accumulationRain"); + } else if (typeof snowAccumulation === "object" && snowAccumulation["1h"]) { + accumulation = Math.round(snowAccumulation["1h"]) + " " + this.getUnit("accumulationSnow"); + } + } else if (rainAccumulation) { + if (typeof rainAccumulation === "number") { + accumulation = Math.round(rainAccumulation) + " " + this.getUnit("accumulationRain"); + } else if (typeof rainAccumulation === "object" && rainAccumulation["1h"]) { + accumulation = Math.round(rainAccumulation["1h"]) + " " + this.getUnit("accumulationRain"); } - accumulation = "(" + accumulation + ")"; } return { - pop: Math.round(percentChance * 100) + "%", accumulation: accumulation }; @@ -423,7 +420,7 @@ Module.register("MMM-DarkSkyForecast", { var windGust = null; if (!this.config.concise && gust) { windGust = " (" + this.config.label_maximum + " " + Math.round(gust) + " " + this.getUnit("windSpeed") + ")"; - } + } return { windSpeed: Math.round(speed) + " " + this.getUnit("windSpeed") + (!this.config.concise ? " " + this.getOrdinal(bearing) : ""), @@ -432,10 +429,10 @@ Module.register("MMM-DarkSkyForecast", { }, /* - Returns the units in use for the data pull from Dark Sky + Returns the units in use for the data pull from OpenWeather */ getUnit: function(metric) { - return this.units[metric][this.weatherData.flags.units]; + return this.units[metric][this.config.units]; }, /* @@ -447,27 +444,24 @@ Module.register("MMM-DarkSkyForecast", { }, /* - Some display items need the unti beside them. This returns the correct + Some display items need the unit beside them. This returns the correct unit for the given metric based on the unit set in use. */ units: { accumulationRain: { - si: "mm", - ca: "mm", - uk2: "mm", - us: "in" + imperial: "mm", + metric: "mm", + "": "mm" }, accumulationSnow: { - si: "cm", - ca: "cm", - uk2: "cm", - us: "in" + imperial: "mm", + metric: "mm", + "": "mm" }, windSpeed: { - si: "m/s", - ca: "km/h", - uk2: "mph", - us: "mph" + imperial: "mph", + metric: "m/s", + "": "m/s" } }, @@ -491,7 +485,7 @@ Module.register("MMM-DarkSkyForecast", { snow wind - All of the icon sets below support these ten plus an + All of the icon sets below support these ten plus an additional three in anticipation of Dark Sky enabling a few more: @@ -520,6 +514,58 @@ Module.register("MMM-DarkSkyForecast", { "5c": {path:"5c", format:"svg"}, }, + /* + This converts OpenWeather icon names to DarkSky icon names + */ + convertOpenWeatherIdToIcon: function(id, openweather_icon) { + if (id >= 200 && id < 300) { + // Thunderstorm + return "thunderstorm"; + } else if (id >= 300 && id < 400) { + // Drizzle + return "rain"; + } else if (id === 511) { + // Rain - freezing rain + return "sleet"; + } else if (id >= 500 && id < 600) { + // Rain + return "rain"; + } else if (id >= 610 && id < 620) { + // Snow - sleet or with rain + return "sleet"; + } else if (id >= 600 && id < 700) { + // Snow + return "snow"; + } else if (id === 781) { + // Atmosphere - tornado + return "tornado"; + } else if (id >= 700 && id < 800) { + // Atmosphere + return "fog"; + } else if (id >= 800 && id < 810) { + var isDay = openweather_icon.slice(-1) === "d"; + + if (id === 800) { + // Clear + if (isDay) { + return "clear-day"; + } else { + return "clear-night"; + } + } else if (id === 801 || id == 802) { + // Clouds - few or scattered + if (isDay) { + return "partly-cloudy-day"; + } else { + return "partly-cloudy-night"; + } + } else if (id === 803 || id === 804) { + // Clouds - broken or overcast + return "cloudy"; + } + } + }, + /* This generates a URL to the icon file */ @@ -529,8 +575,6 @@ Module.register("MMM-DarkSkyForecast", { }, - - /* When the Skycons animated set is in use, the icons need to be rebuilt with each data refresh. This traverses the @@ -578,7 +622,6 @@ Module.register("MMM-DarkSkyForecast", { inst.skycons.add(icon.id, icon.getAttribute("data-animated-icon-name")); }); inst.skycons.play(); - }, /* @@ -587,7 +630,6 @@ Module.register("MMM-DarkSkyForecast", { converted to integers, then the module defaults are used. */ sanitizeNumbers: function(keys) { - var self = this; keys.forEach(function(key) { if (isNaN(parseInt(self.config[key]))) { @@ -597,8 +639,4 @@ Module.register("MMM-DarkSkyForecast", { } }); } - - - - }); diff --git a/README.md b/README.md index b1fee41..15d7543 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ https://github.com/MichMich/MagicMirror ![Screenshot](/../screenshots/MMM-DarkSkyForecast.png?raw=true "Screenshot") A weather module that displays current, hourly and daily forecast information -using data from the Dark Sky API. This is a replacement module for MMM-MyWeather, now that Weather Underground no longer allows free API access. This a complete rewrite from scratch but maintains +using data from the OpenWeather One Call API. This is a replacement module for MMM-MyWeather, now that Weather Underground no longer allows free API access. This a complete rewrite from scratch but maintains much of the same functionality. **NOTE:** This module uses the Nunjucks templating system introduced in version 2.2.0 of MagicMirror. If you're seeing nothing on your display where you expect this module to appear, make sure your MagicMirror version is at least 2.2.0. @@ -29,8 +29,8 @@ At a minimum you need to supply the following required configuration parameters: * `latitude` * `longitude` -You can request an API key to access Dark Sky data here: -`https://darksky.net/dev`. +You can request an API key to access data here: +`https://openweathermap.org/api/one-call-api`. Free tier is fine -- this module will not make any where near 1000 request on one day. @@ -61,7 +61,7 @@ Find out your latitude and longitude here: language - The language to be used for display.

Type String
Defaults to the language set for Magic Mirror, but can be overridden with any of the language codes listed here: https://darksky.net/dev/docs#request-parameters. + The language to be used for display.

Type String
Defaults to the language set for Magic Mirror, but can be overridden with any of the language codes listed here: https://openweathermap.org/api/one-call-api#multi. colored @@ -69,7 +69,7 @@ Find out your latitude and longitude here: units - One of the following: si, ca, uk2, or us.

Type String
Defaults to ca
See https://darksky.net/dev/docs#request-parameters for details on units. + One of the following: imperial, metric, or [blank].

Type String
Defaults to units set for Magic Mirror.
See https://openweathermap.org/api/one-call-api#data for details on units. showCurrentConditions @@ -247,6 +247,6 @@ https://www.graphberry.com/item/weathera-weather-forecast-icons Some of the icons were modified to better work with the module's structure and aesthetic. -**Weather data provided by Dark Sky**
-https://darksky.net/ +**Weather data provided by OpenWeather**
+https://openweathermap.org diff --git a/mmm-darksky-forecast.njk b/mmm-darksky-forecast.njk index 031ae3d..9231119 100644 --- a/mmm-darksky-forecast.njk +++ b/mmm-darksky-forecast.njk @@ -30,15 +30,13 @@ {% if config.showPrecipitation %} - - - {% if (config.showInlineIcons) %} - - {% endif %} - {{ forecast.currently.precipitation.pop }} - + {% if forecast.currently.precipitation.accumulation %} + {% if (config.showInlineIcons) %} + + {% endif %} + {{ forecast.currently.precipitation.accumulation }} {% endif %} @@ -52,7 +50,7 @@ {% endif %} {{ forecast.currently.wind.windSpeed }} - + {% if forecast.currently.wind.windGust %} {{ forecast.currently.wind.windGust }} {% endif %} @@ -120,14 +118,12 @@ {% if config.showPrecipitation %} - + {% if (config.showInlineIcons and config.forecastLayout == "tiled" ) %} {% endif %} - {{ h.precipitation.pop }} - {% if h.precipitation.accumulation %} {{ h.precipitation.accumulation }} {% endif %} @@ -142,7 +138,7 @@ {% endif %} {{ h.wind.windSpeed }} - + {% if h.wind.windGust %} {{ h.wind.windGust }} {% endif %} @@ -179,14 +175,12 @@ {% if config.showPrecipitation %} - + {% if (config.showInlineIcons and config.forecastLayout == "tiled" ) %} {% endif %} - {{ d.precipitation.pop }} - {% if d.precipitation.accumulation %} {{ d.precipitation.accumulation }} {% endif %} @@ -201,7 +195,7 @@ {% endif %} {{ d.wind.windSpeed }} - + {% if d.wind.windGust %} {{ d.wind.windGust }} {% endif %} @@ -216,10 +210,9 @@ {% endif %} - {% endif %} + {% endif %}
-
Powered by Dark Sky
{% endif %} - \ No newline at end of file + diff --git a/node_helper.js b/node_helper.js index 84f68c0..964ed50 100644 --- a/node_helper.js +++ b/node_helper.js @@ -2,20 +2,20 @@ Node Helper for MMM-DarkSkyForecast. - This helper is responsible for the data pull from Dark Sky. + This helper is responsible for the DarkSky-compatible data pull from OpenWeather. At a minimum the API key, Latitude and Longitude parameters must be provided. If any of these are missing, the request - to Dark Sky will not be executed, and instead an error + to OpenWeather will not be executed, and instead an error will be output the the MagicMirror log. Additional, this module supplies two optional parameters: - units - one of "ca", "uk2", "us", or "si" - lang - Any of the languages Dark Sky supports, as listed here: https://darksky.net/dev/docs#response-format + units - one of "metric", "imperial", or "" (blank) + lang - Any of the languages OpenWeather supports, as listed here: https://openweathermap.org/api/one-call-api#multi - The Dark Sky API request looks like this: + The DarkSky-compatible API request looks like this: - https://api.darksky.net/forecast/API_KEY/LATITUDE,LONGITUDE?units=XXX&lang=YY + https://api.openweathermap.org/data/2.5/onecall?lat=LATITUDE&lon=LONGITUDE&units=XXX&lang=YY&appid=API_KEY *********************************/ @@ -30,21 +30,22 @@ module.exports = NodeHelper.create({ }, socketNotificationReceived: function(notification, payload){ - if (notification === "DARK_SKY_FORECAST_GET") { + if (notification === "OPENWEATHER_ONE_CALL_FORECAST_GET") { var self = this; if (payload.apikey == null || payload.apikey == "") { - console.log( "[MMM-DarkSkyForecast] " + moment().format("D-MMM-YY HH:mm") + " ** ERROR ** No API key configured. Get an API key at https://darksky.net" ); + console.log( "[MMM-DarkSkyForecast] " + moment().format("D-MMM-YY HH:mm") + " ** ERROR ** No API key configured. Get an API key at https://openweathermap.org/api/one-call-api" ); } else if (payload.latitude == null || payload.latitude == "" || payload.longitude == null || payload.longitude == "") { console.log( "[MMM-DarkSkyForecast] " + moment().format("D-MMM-YY HH:mm") + " ** ERROR ** Latitude and/or longitude not provided." ); } else { - //make request to Dark Sky API - var url = "https://api.darksky.net/forecast/" + - payload.apikey + "/" + - payload.latitude + "," + payload.longitude + - "?units=" + payload.units + + //make request to OpenWeather onecall API + var url = "https://api.openweathermap.org/data/2.5/onecall" + + "?appid=" + payload.apikey + + "&lat=" + payload.latitude + + "&lon=" + payload.longitude + + (payload.units !== "" ? "&units=" + payload.units : "") + "&lang=" + payload.language; // "&exclude=minutely" @@ -56,7 +57,7 @@ module.exports = NodeHelper.create({ //Good response var resp = JSON.parse(body); resp.instanceId = payload.instanceId; - self.sendSocketNotification("DARK_SKY_FORECAST_DATA", resp); + self.sendSocketNotification("OPENWEATHER_ONE_CALL_FORECAST_DATA", resp); } else { console.log( "[MMM-DarkSkyForecast] " + moment().format("D-MMM-YY HH:mm") + " ** ERROR ** " + error ); @@ -69,4 +70,4 @@ module.exports = NodeHelper.create({ }, -}); \ No newline at end of file +});