mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-07-14 19:18:00 +00:00
Merge branch 'weather-refactor' of https://github.com/MichMich/MagicMirror into weather-refactor
This commit is contained in:
commit
7be6031e19
17
modules/default/weather/README.md
Normal file
17
modules/default/weather/README.md
Normal 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
|
@ -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
|
||||
},
|
||||
|
46
modules/default/weather/weather.css
Normal file
46
modules/default/weather/weather.css
Normal 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;
|
||||
}
|
@ -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()) + " " : " ", "", "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) + "°", "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)
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -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
|
||||
}
|
||||
};
|
63
modules/default/weather/weatherobject.js
Normal file
63
modules/default/weather/weatherobject.js
Normal 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";
|
||||
}
|
||||
}
|
||||
};
|
@ -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()
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user