Merge branch 'weather-refactor' of https://github.com/MichMich/MagicMirror into weather-refactor

This commit is contained in:
Nicholas Hubbard 2017-09-29 10:11:22 -04:00
commit 7be6031e19
7 changed files with 320 additions and 55 deletions

View File

@ -0,0 +1,17 @@
# Weather Module
This module is aimed to be the replacement for the current `currentweather` and `weatherforcast` modules. The module will be configurable to be used as a current weather view, or to show the forecast. This way the module can be used twice to fullfil both purposes.
The biggest cange is the use of weather providers. This way we are not bound to one API source. And users can choose which API they want to use as their source. Initially the current OpenWeatherMap will be added as the first source, but more will follow soon.
The module is in a very early stage, and needs a lot of work. It's API isn't set in stone, so keep that in mind when you want to contribute.
## TODO
- [ ] Add Current Weather View
- [ ] Add Weather Forecast View
- [ ] Add Forecast API Call
- [ ] Add all original configuration options
- [ ] Add more providers
- [ ] Finish thi Todo list
- [ ] Write the documentation

View File

@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherDay */
/* global WeatherProvider, WeatherObject */
/* Magic Mirror
* Module: Weather
@ -34,7 +34,7 @@ WeatherProvider.register("openweathermap", {
return;
}
var currentWeather = this.generateWeatherDayFromCurrentWeather(data)
var currentWeather = this.generateWeatherObjectFromCurrentWeather(data)
this.setCurrentWeather(currentWeather)
})
.catch(function(request) {
@ -42,23 +42,47 @@ WeatherProvider.register("openweathermap", {
})
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast: function() {
// I haven't yet implemented the real api call, so let's just generate some random data.
var forecast = []
var today = moment()
for (var i = 0; i < 5; i++ ) {
var weatherObject = new WeatherObject()
weatherObject.date = moment(today).add(i, "days")
weatherObject.minTemperature = Math.random() * 10 + 10
weatherObject.maxTemperature = Math.random() * 15 + 10
forecast.push(weatherObject)
}
this.setWeatherForecast(forecast)
},
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
/*
* Generate a WeatherDay based on currentWeatherInformation
* Generate a WeatherObject based on currentWeatherInformation
*/
generateWeatherDayFromCurrentWeather: function(currentWeatherData) {
var currentWeather = new WeatherDay()
generateWeatherObjectFromCurrentWeather: function(currentWeatherData) {
var currentWeather = new WeatherObject()
currentWeather.humidity = parseFloat(currentWeatherData.main.humidity)
currentWeather.temperature = parseFloat(currentWeatherData.main.temp)
currentWeather.windSpeed = parseFloat(currentWeatherData.wind.speed)
currentWeather.windDirection = currentWeatherData.wind.deg
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.weather[0].icon)
currentWeather.sunrise = new Date(currentWeatherData.sys.sunrise * 1000)
currentWeather.sunset = new Date(currentWeatherData.sys.sunset * 1000)
currentWeather.date = new Date
currentWeather.humidity = currentWeatherData.main.humidity ? parseFloat(currentWeatherData.main.humidity) : null
currentWeather.temperature = currentWeatherData.main.temp ? parseFloat(currentWeatherData.main.temp) : null
currentWeather.windSpeed = currentWeatherData.wind.speed ? parseFloat(currentWeatherData.wind.speed) : null
currentWeather.windDirection = currentWeatherData.wind.deg ? currentWeatherData.wind.deg : null
currentWeather.weatherType = currentWeatherData.weather[0].icon ? this.convertWeatherType(currentWeatherData.weather[0].icon) : null
currentWeather.sunrise = currentWeatherData.sys.sunrise ? new Date(currentWeatherData.sys.sunrise * 1000) : null
currentWeather.sunset = currentWeatherData.sys.sunset ? new Date(currentWeatherData.sys.sunset * 1000) : null
return currentWeather
},

View File

@ -0,0 +1,46 @@
.weather .weathericon,
.weather .fa-home {
font-size: 75%;
line-height: 65px;
display: inline-block;
-ms-transform: translate(0, -3px); /* IE 9 */
-webkit-transform: translate(0, -3px); /* Safari */
transform: translate(0, -3px);
}
.weather .humidityIcon {
padding-right: 4px;
}
.weather .humidity-padding {
padding-bottom: 6px;
}
.weather .day {
padding-left: 0;
padding-right: 25px;
}
.weather .weather-icon {
padding-right: 30px;
text-align: center;
}
.weather .min-temp {
padding-left: 20px;
padding-right: 0;
}
.weather .rain {
padding-left: 20px;
padding-right: 0;
}
.weather tr.colored .min-temp {
color: #BCDDFF;
}
.weather tr.colored .max-temp {
color: #FF8E99;
}

View File

@ -11,18 +11,26 @@ Module.register("weather",{
// Default module config.
defaults: {
foo: "bar",
weatherProvider: "openweathermap"
updateInterval: 10 * 60 * 1000,
weatherProvider: "openweathermap",
units: config.units,
roundTemp: false,
displayType: "full" //current, forecast, full
},
// Module properties.
weatherProvider: null,
// Define required scripts.
getStyles: function() {
return ["weather-icons.css", "weather.css"];
},
// Return the scripts that are nessecery for the weather module.
getScripts: function () {
var scripts = [
"weatherprovider.js",
"weatherday.js"
"weatherobject.js"
];
// Add the provider file to the required scripts.
@ -39,24 +47,147 @@ Module.register("weather",{
// Let the weather provider know we are starting.
this.weatherProvider.start()
// Fetch the current weather. This is something we need to schedule.
this.weatherProvider.fetchCurrentWeather()
// Schedule the first update.
this.scheduleUpdate(0)
},
// Generate the dom. This is now pretty simple for debugging.
getDom: function() {
var wrapper = document.createElement("div")
wrapper.innerHTML += "Name: " + this.weatherProvider.providerName + "<br>"
wrapper.innerHTML += JSON.stringify(this.weatherProvider.currentWeather())
return wrapper
switch (this.config.displayType) {
case "current":
return this.currentWeatherView()
break;
case "forecast":
return this.weatherForecastView()
break;
default:
return this.fullWeatherView()
break;
}
},
// What to do when the weather provider has new information available?
updateAvailable: function() {
Log.log("New weather information available.")
console.info(this.weatherProvider.currentWeather())
this.updateDom(0);
this.updateDom(0)
this.scheduleUpdate()
},
scheduleUpdate: function(delay = null) {
var nextLoad = this.config.updateInterval;
if (delay !== null && delay >= 0) {
nextLoad = delay;
}
setTimeout(() => {
switch (this.config.displayType) {
case "current":
this.weatherProvider.fetchCurrentWeather()
break;
case "forecast":
this.weatherProvider.fetchWeatherForecast()
break;
default:
this.weatherProvider.fetchCurrentWeather()
this.weatherProvider.fetchWeatherForecast()
break;
}
}, nextLoad);
},
/* Views */
// Generate the current weather view
currentWeatherView: function () {
var currentWeather = this.weatherProvider.currentWeather()
var wrapper = document.createElement("div")
if (currentWeather === null) {
return wrapper
}
// Detail bar.
var detailBar = document.createElement("div")
this.addValueToWrapper(detailBar, null, "wi wi-strong-wind dimmed", "span", true) // Wind Icon
this.addValueToWrapper(detailBar, currentWeather.windSpeed ? Math.round(currentWeather.windSpeed) : null) // WindSpeed
this.addValueToWrapper(detailBar, currentWeather.windDirection ? this.translate(currentWeather.cardinalWindDirection()) + "&nbsp;&nbsp;" : "&nbsp;", "", "sup") // WindDirection
var now = new Date();
var sunriseSunsetTime = (currentWeather.sunrise < now && currentWeather.sunset > now) ? currentWeather.sunset : currentWeather.sunrise
var sunriseSunsetIcon = (currentWeather.sunrise < now && currentWeather.sunset > now) ? "wi-sunset" : "wi-sunrise"
this.addValueToWrapper(detailBar, null, "wi dimmed " + sunriseSunsetIcon, "span", true) // Sunrise / Sunset Icon
this.addValueToWrapper(detailBar, moment(sunriseSunsetTime).format("HH:mm")) // Sunrise / Sunset Time
detailBar.className = "normal medium"
wrapper.appendChild(detailBar)
// Main info
var mainInfo = document.createElement("div")
this.addValueToWrapper(mainInfo, null, "weathericon wi wi-" + currentWeather.weatherType, "span", true) // Wind Icon
this.addValueToWrapper(mainInfo, currentWeather.temperature.toFixed(this.config.roundTemp ? 0 : 1) + "&deg;", "bright" ) // WindSpeed
mainInfo.className = "large light"
wrapper.appendChild(mainInfo)
return wrapper
},
weatherForecastView: function() {
// This is just a dummy view to test it ... it needs A LOT of work :)
// Currently it outputs a div, it should be a table.
var wrapper = document.createElement("div")
wrapper.className = "small"
if (this.weatherProvider.weatherForecast() === null) {
return wrapper
}
this.weatherProvider.weatherForecast().forEach((weatherObject) => {
var day = document.createElement("div")
this.addValueToWrapper(day, moment(weatherObject.date).format("dd"))
this.addValueToWrapper(day, weatherObject.maxTemperature ? weatherObject.maxTemperature.toFixed(this.config.roundTemp ? 0 : 1) : null, "bright")
this.addValueToWrapper(day, weatherObject.minTemperature ? weatherObject.minTemperature.toFixed(this.config.roundTemp ? 0 : 1) : null, "dimmed")
wrapper.appendChild(day)
});
return wrapper
},
fullWeatherView: function() {
var wrapper = document.createElement("div")
wrapper.appendChild(this.currentWeatherView())
wrapper.appendChild(this.weatherForecastView())
return wrapper
},
// A convenience function to add an element to a wrapper with a specific value and class.
addValueToWrapper: function(wrapper, value, classNames, element = "span", forceAdd = false, addSpacer = true) {
if (value === null && !forceAdd) {
return
}
var valueWrapper = document.createElement(element)
valueWrapper.className = classNames
if (addSpacer) {
valueWrapper.innerHTML = " "
}
if (value !== null) {
valueWrapper.innerHTML += value
}
wrapper.appendChild(valueWrapper)
}
});

View File

@ -1,24 +0,0 @@
/* global Class */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* This class is the blueprint for a day which includes weather information.
*/
// Currently this is focused on the information which is nessecery for the current weather.
// As soon as we start implementing the forecast, mode properties will be added.
class WeatherDay {
constructor() {
this.windSpeed = null
this.windDirection = null
this.sunrise = null
this.sunset = null
this.temperature = null
this.weatherType = null
}
};

View File

@ -0,0 +1,63 @@
/* global Class */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*
* This class is the blueprint for a day which includes weather information.
*/
// Currently this is focused on the information which is nessecery for the current weather.
// As soon as we start implementing the forecast, mode properties will be added.
class WeatherObject {
constructor() {
this.date = null
this.windSpeed = null
this.windDirection = null
this.sunrise = null
this.sunset = null
this.temperature = null
this.minTemperature = null,
this.maxTemperature = null,
this.weatherType = null
}
cardinalWindDirection () {
if (this.windDirection>11.25 && this.windDirection<=33.75){
return "NNE";
} else if (this.windDirection > 33.75 && this.windDirection <= 56.25) {
return "NE";
} else if (this.windDirection > 56.25 && this.windDirection <= 78.75) {
return "ENE";
} else if (this.windDirection > 78.75 && this.windDirection <= 101.25) {
return "E";
} else if (this.windDirection > 101.25 && this.windDirection <= 123.75) {
return "ESE";
} else if (this.windDirection > 123.75 && this.windDirection <= 146.25) {
return "SE";
} else if (this.windDirection > 146.25 && this.windDirection <= 168.75) {
return "SSE";
} else if (this.windDirection > 168.75 && this.windDirection <= 191.25) {
return "S";
} else if (this.windDirection > 191.25 && this.windDirection <= 213.75) {
return "SSW";
} else if (this.windDirection > 213.75 && this.windDirection <= 236.25) {
return "SW";
} else if (this.windDirection > 236.25 && this.windDirection <= 258.75) {
return "WSW";
} else if (this.windDirection > 258.75 && this.windDirection <= 281.25) {
return "W";
} else if (this.windDirection > 281.25 && this.windDirection <= 303.75) {
return "WNW";
} else if (this.windDirection > 303.75 && this.windDirection <= 326.25) {
return "NW";
} else if (this.windDirection > 326.25 && this.windDirection <= 348.75) {
return "NNW";
} else {
return "N";
}
}
};

View File

@ -19,7 +19,7 @@ var WeatherProvider = Class.extend({
// The following properties have accestor methods.
// Try to not access them directly.
currentWeatherDay: null,
currentWeatherObject: null,
weatherForecastArray: null,
// The following properties will be set automaticly.
@ -57,13 +57,13 @@ var WeatherProvider = Class.extend({
// This method should start the API request to fetch the weather forecast.
// This method should definetly be overwritten in the provider.
fetchWeatherForeCast: function() {
Log.warn("Weather provider: " + this.providerName + " does not subclass the fetchWeatherForeCast method.")
fetchWeatherForecast: function() {
Log.warn("Weather provider: " + this.providerName + " does not subclass the fetchWeatherForecast method.")
},
// This returns a WeatherDay object for the current weather.
currentWeather: function() {
return this.currentWeatherDay
return this.currentWeatherObject
},
// This returns an array of WeatherDay objects for the weather forecast.
@ -71,10 +71,18 @@ var WeatherProvider = Class.extend({
return this.weatherForecastArray
},
// Set the currentWeather and notify the delegate that new information is availabe.
setCurrentWeather: function(currentWeatherDay) {
// Set the currentWeather and notify the delegate that new information is available.
setCurrentWeather: function(currentWeatherObject) {
// We should check here if we are passing a WeatherDay
this.currentWeatherDay = currentWeatherDay
this.currentWeatherObject = currentWeatherObject
this.updateAvailable()
},
// Set the weatherForecastArray and notify the delegate that new information is available.
setWeatherForecast: function(weatherForecastArray) {
// We should check here if we are passing a WeatherDay
this.weatherForecastArray = weatherForecastArray
this.updateAvailable()
},