diff --git a/modules/default/defaultmodules.js b/modules/default/defaultmodules.js index fccf3c52..656ba6b8 100644 --- a/modules/default/defaultmodules.js +++ b/modules/default/defaultmodules.js @@ -16,7 +16,8 @@ var defaultModules = [ "helloworld", "newsfeed", "weatherforecast", - "updatenotification" + "updatenotification", + "weather" ]; /*************** DO NOT EDIT THE LINE BELOW ***************/ diff --git a/modules/default/weather/providers/openweathermap.js b/modules/default/weather/providers/openweathermap.js new file mode 100644 index 00000000..4f3651a6 --- /dev/null +++ b/modules/default/weather/providers/openweathermap.js @@ -0,0 +1,120 @@ +/* global WeatherProvider, WeatherDay */ + +/* Magic Mirror + * Module: Weather + * + * By Michael Teeuw http://michaelteeuw.nl + * MIT Licensed. + * + * This class is the blueprint for a weather provider. + */ + +WeatherProvider.register("openweathermap", { + + // Set the name of the provider. + // This isn't strictly nessecery, since it will fallback to the provider identifier + // But for debugging (and future alerts) it would be nice to have the real name. + providerName: "OpenWeatherMap", + + // Overwrite the fetchCurrentWeather method. + fetchCurrentWeather: function() { + var apiVersion = "2.5" + var apiBase = "http://api.openweathermap.org/data/" + var weatherEndpoint = "weather" + + var url = apiBase + apiVersion + "/" + weatherEndpoint + this.getParams() + + this.fetchData(url) + .then(data => { + Log.log(data) + + if (!data || !data.main || typeof data.main.temp === "undefined") { + // Did not receive usable new data. + // Maybe this needs a better check? + return; + } + + var currentWeather = this.generateWeatherDayFromCurrentWeather(data) + this.setCurrentWeather(currentWeather) + }) + .catch(function(request) { + Log.error("Could not load data ... ", request) + }) + }, + + + /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */ + + + /* + * Generate a WeatherDay based on currentWeatherInformation + */ + generateWeatherDayFromCurrentWeather: function(currentWeatherData) { + var currentWeather = new WeatherDay() + + 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) + + return currentWeather + }, + + /* + * Convert the OpenWeatherMap icons to a more usable name. + */ + convertWeatherType: function(weatherType) { + var weatherTypes = { + "01d": "day-sunny", + "02d": "day-cloudy", + "03d": "cloudy", + "04d": "cloudy-windy", + "09d": "showers", + "10d": "rain", + "11d": "thunderstorm", + "13d": "snow", + "50d": "fog", + "01n": "night-clear", + "02n": "night-cloudy", + "03n": "night-cloudy", + "04n": "night-cloudy", + "09n": "night-showers", + "10n": "night-rain", + "11n": "night-thunderstorm", + "13n": "night-snow", + "50n": "night-alt-cloudy-windy" + } + + return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null + }, + + /* getParams(compliments) + * Generates an url with api parameters based on the config. + * + * return String - URL params. + */ + getParams: function() { + var params = "?"; + if(this.config.locationID) { + params += "id=" + this.config.locationID; + } else if(this.config.location) { + params += "q=" + this.config.location; + } else if (this.firstEvent && this.firstEvent.geo) { + params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon + } else if (this.firstEvent && this.firstEvent.location) { + params += "q=" + this.firstEvent.location; + } else { + this.hide(this.config.animationSpeed, {lockString:this.identifier}); + return; + } + + params += "&units=" + this.config.units; + params += "&lang=" + this.config.lang; + params += "&APPID=" + this.config.apiKey; + + return params; + }, +}); \ No newline at end of file diff --git a/modules/default/weather/weather.js b/modules/default/weather/weather.js new file mode 100644 index 00000000..2be3ff0b --- /dev/null +++ b/modules/default/weather/weather.js @@ -0,0 +1,62 @@ +/* global Module, WeatherProvider */ + +/* Magic Mirror + * Module: Weather + * + * By Michael Teeuw http://michaelteeuw.nl + * MIT Licensed. + */ + +Module.register("weather",{ + + // Default module config. + defaults: { + foo: "bar", + weatherProvider: "openweathermap" + }, + + // Module properties. + weatherProvider: null, + + // Return the scripts that are nessecery for the weather module. + getScripts: function () { + var scripts = [ + "weatherprovider.js", + "weatherday.js" + ]; + + // Add the provider file to the required scripts. + scripts.push(this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")) + + return scripts + }, + + // Start the weather module. + start: function () { + // Initialize the weather provider. + this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this) + + // 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() + }, + + // Generate the dom. This is now pretty simple for debugging. + getDom: function() { + var wrapper = document.createElement("div") + + wrapper.innerHTML += "Name: " + this.weatherProvider.providerName + "
" + wrapper.innerHTML += JSON.stringify(this.weatherProvider.currentWeather()) + + return wrapper + }, + + // 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); + } +}); diff --git a/modules/default/weather/weatherday.js b/modules/default/weather/weatherday.js new file mode 100644 index 00000000..c8f63057 --- /dev/null +++ b/modules/default/weather/weatherday.js @@ -0,0 +1,24 @@ +/* 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 + } +}; \ No newline at end of file diff --git a/modules/default/weather/weatherprovider.js b/modules/default/weather/weatherprovider.js new file mode 100644 index 00000000..08489aeb --- /dev/null +++ b/modules/default/weather/weatherprovider.js @@ -0,0 +1,135 @@ +/* global Class */ + +/* Magic Mirror + * Module: Weather + * + * By Michael Teeuw http://michaelteeuw.nl + * MIT Licensed. + * + * This class is the blueprint for a weather provider. + */ + + +/** + * Base BluePrint for the WeatherProvider + */ +var WeatherProvider = Class.extend({ + // Weather Provider Properties + providerName: null, + + // The following properties have accestor methods. + // Try to not access them directly. + currentWeatherDay: null, + weatherForecastArray: null, + + // The following properties will be set automaticly. + // You do not need to overwrite these properties. + config: null, + delegate: null, + providerIdentifier: null, + + + // Weather Provider Methods + // All the following methods can be overwrited, although most are good as they are. + + // Called when a weather provider is initialized. + init: function(config) { + this.config = config; + Log.info("Weather provider: " + this.providerName + " initialized.") + }, + + // Called to set the config, this config is the same as the weather module's config. + setConfig: function(config) { + this.config = config + Log.info("Weather provider: " + this.providerName + " config set.", this.config) + }, + + // Called when the weather provider is about to start. + start: function(config) { + Log.info("Weather provider: " + this.providerName + " started.") + }, + + // This method should start the API request to fetch the current weather. + // This method should definetly be overwritten in the provider. + fetchCurrentWeather: function() { + Log.warn("Weather provider: " + this.providerName + " does not subclass the fetchCurrentWeather method.") + }, + + // 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.") + }, + + // This returns a WeatherDay object for the current weather. + currentWeather: function() { + return this.currentWeatherDay + }, + + // This returns an array of WeatherDay objects for the weather forecast. + weatherForecast: function() { + return this.weatherForecastArray + }, + + // Set the currentWeather and notify the delegate that new information is availabe. + setCurrentWeather: function(currentWeatherDay) { + // We should check here if we are passing a WeatherDay + this.currentWeatherDay = currentWeatherDay + + this.updateAvailable() + }, + + // Notify the delegate that new weather is available. + updateAvailable: function() { + this.delegate.updateAvailable(this) + }, + + // A convinience function to make requests. It returns a promise. + fetchData: function(url, method = "GET", data = null) { + return new Promise(function(resolve, reject) { + var request = new XMLHttpRequest(); + request.open(method, url, true); + request.onreadystatechange = function() { + if (this.readyState === 4) { + if (this.status === 200) { + resolve(JSON.parse(this.response)); + } else { + reject(request) + } + } + }; + request.send(); + }) + } +}); + +/** + * Collection of registered weather providers. + */ +WeatherProvider.providers = [] + +/** + * Static method to register a new weather provider. + */ +WeatherProvider.register = function(providerIdentifier, providerDetails) { + WeatherProvider.providers[providerIdentifier.toLowerCase()] = WeatherProvider.extend(providerDetails) +} + +/** + * Static method to initialize a new weather provider. + */ +WeatherProvider.initialize = function(providerIdentifier, delegate) { + providerIdentifier = providerIdentifier.toLowerCase() + + var provider = new WeatherProvider.providers[providerIdentifier]() + + provider.delegate = delegate + provider.setConfig(delegate.config) + + provider.providerIdentifier = providerIdentifier; + if (!provider.providerName) { + provider.providerName = providerIdentifier + } + + return provider; +} \ No newline at end of file