Merge branch 'develop' into weather-module

This commit is contained in:
Kristof Rado
2020-05-29 23:56:09 +02:00
199 changed files with 4724 additions and 6927 deletions

View File

@@ -1,5 +1,5 @@
# 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.
This module aims 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.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weather.html).

View File

@@ -13,17 +13,17 @@ Table of Contents:
## The weather provider file: yourprovider.js
This is the script in which the weather provider will be defined. In it's most simple form, the weather provider must implement the following:
This is the script in which the weather provider will be defined. In its most simple form, the weather provider must implement the following:
````javascript
```javascript
WeatherProvider.register("yourprovider", {
providerName: "YourProvider",
providerName: "YourProvider",
fetchCurrentWeather() {},
fetchCurrentWeather() {},
fetchWeatherForecast() {}
fetchWeatherForecast() {}
});
````
```
### Weather provider methods to implement
@@ -36,7 +36,7 @@ It will then automatically refresh the module DOM with the new data.
#### `fetchWeatherForecast()`
This method is called when the weather module tries to fetch the weather weather of your provider. The implementation of this method is required.
This method is called when the weather module tries to fetch the weather of your provider. The implementation of this method is required.
The implementation can make use of the already implemented function `this.fetchData(url, method, data);`, which is returning a promise.
After the response is processed, the weather forecast information (as an array of [WeatherObject](#weatherobject)s) needs to be set with `this.setCurrentWeather(forecast);`.
It will then automatically refresh the module DOM with the new data.
@@ -89,24 +89,24 @@ A convenience function to make requests. It returns a promise.
### WeatherObject
| Property | Type | Value/Unit |
| --- | --- | --- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
| windSpeed |`number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
| windDirection |`number` | Direction of the wind in degrees. |
| sunrise |`object` | [Moment.js](https://momentjs.com/) object of sunrise. |
| sunset |`object` | [Moment.js](https://momentjs.com/) object of sunset. |
| temperature | `number` | Current temperature |
| minTemperature | `number` | Lowest temperature of the day. |
| maxTemperature | `number` | Highest temperature of the day. |
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| humidity | `number` | Percentage of humidity |
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
| Property | Type | Value/Unit |
| -------------- | -------- | --------------------------------------------------------------------------------------------------------------- |
| units | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| tempUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| windUnits | `string` | Gets initialized with the constructor. <br> Possible values: `metric`, `imperial` |
| date | `object` | [Moment.js](https://momentjs.com/) object of the time/date. |
| windSpeed | `number` | Metric: `meter/second` <br> Imperial: `miles/hour` |
| windDirection | `number` | Direction of the wind in degrees. |
| sunrise | `object` | [Moment.js](https://momentjs.com/) object of sunrise. |
| sunset | `object` | [Moment.js](https://momentjs.com/) object of sunset. |
| temperature | `number` | Current temperature |
| minTemperature | `number` | Lowest temperature of the day. |
| maxTemperature | `number` | Highest temperature of the day. |
| weatherType | `string` | Icon name of the weather type. <br> Possible values: [WeatherIcons](https://www.npmjs.com/package/weathericons) |
| humidity | `number` | Percentage of humidity |
| rain | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| snow | `number` | Metric: `millimeters` <br> Imperial: `inches` |
| precipitation | `number` | Metric: `millimeters` <br> Imperial: `inches` <br> UK Met Office provider: `percent` |
#### Current weather

View File

@@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherDay */
/* global WeatherProvider, WeatherObject */
/* Magic Mirror
* Module: Weather
@@ -16,40 +16,42 @@ WeatherProvider.register("darksky", {
providerName: "Dark Sky",
units: {
imperial: 'us',
metric: 'si'
imperial: "us",
metric: "si"
},
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
if(!data || !data.currently || typeof data.currently.temperature === "undefined") {
.then((data) => {
if (!data || !data.currently || typeof data.currently.temperature === "undefined") {
// No usable data?
return;
}
const currentWeather = this.generateWeatherDayFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
}).catch(function(request) {
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
if(!data || !data.daily || !data.daily.data.length) {
.then((data) => {
if (!data || !data.daily || !data.daily.data.length) {
// No usable data?
return;
}
const forecast = this.generateWeatherObjectsFromForecast(data.daily.data);
this.setWeatherForecast(forecast);
}).catch(function(request) {
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
// Create a URL from the config and base URL.
@@ -109,12 +111,12 @@ WeatherProvider.register("darksky", {
const weatherTypes = {
"clear-day": "day-sunny",
"clear-night": "night-clear",
"rain": "rain",
"snow": "snow",
"sleet": "snow",
"wind": "wind",
"fog": "fog",
"cloudy": "cloudy",
rain: "rain",
snow: "snow",
sleet: "snow",
wind: "wind",
fog: "fog",
cloudy: "cloudy",
"partly-cloudy-day": "day-cloudy",
"partly-cloudy-night": "night-cloudy"
};

View File

@@ -3,14 +3,12 @@
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* By Michael Teeuw https://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 necessary, since it will fallback to the provider identifier
// But for debugging (and future alerts) it would be nice to have the real name.
@@ -19,7 +17,7 @@ WeatherProvider.register("openweathermap", {
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
.then((data) => {
if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -31,16 +29,16 @@ WeatherProvider.register("openweathermap", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
.then((data) => {
if (!data || !data.list || !data.list.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -52,10 +50,10 @@ WeatherProvider.register("openweathermap", {
const forecast = this.generateWeatherObjectsFromForecast(data.list);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
@@ -87,7 +85,6 @@ WeatherProvider.register("openweathermap", {
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
if (this.config.weatherEndpoint === "/forecast") {
return this.fetchForecastHourly(forecasts);
} else if (this.config.weatherEndpoint === "/forecast/daily") {
@@ -114,7 +111,6 @@ WeatherProvider.register("openweathermap", {
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
for (const forecast of forecasts) {
if (date !== moment(forecast.dt, "X").format("YYYY-MM-DD")) {
// calculate minimum/maximum temperature, specify rain amount
weather.minTemperature = Math.min.apply(null, minTemp);
@@ -140,7 +136,6 @@ WeatherProvider.register("openweathermap", {
// If the first value of today is later than 17:00, we have an icon at least!
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
}
if (moment(forecast.dt, "X").format("H") >= 8 && moment(forecast.dt, "X").format("H") <= 17) {
@@ -223,7 +218,7 @@ WeatherProvider.register("openweathermap", {
days.push(weather);
}
return days;
return days;
},
/*
@@ -261,16 +256,16 @@ WeatherProvider.register("openweathermap", {
*/
getParams() {
let params = "?";
if(this.config.locationID) {
if (this.config.locationID) {
params += "id=" + this.config.locationID;
} else if(this.config.location) {
} 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});
this.hide(this.config.animationSpeed, { lockString: this.identifier });
return;
}

View File

@@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherObject */
/* global WeatherProvider, WeatherObject, SunCalc */
/* Magic Mirror
* Module: Weather
@@ -8,10 +8,7 @@
*
* This class is a provider for UK Met Office Datapoint.
*/
WeatherProvider.register("ukmetoffice", {
// Set the name of the provider.
// This isn't strictly necessary, since it will fallback to the provider identifier
// But for debugging (and future alerts) it would be nice to have the real name.
@@ -25,9 +22,8 @@ WeatherProvider.register("ukmetoffice", {
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl("3hourly"))
.then(data => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location ||
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
.then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
@@ -38,18 +34,17 @@ WeatherProvider.register("ukmetoffice", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl("daily"))
.then(data => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location ||
!data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length == 0) {
.then((data) => {
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
@@ -60,14 +55,12 @@ WeatherProvider.register("ukmetoffice", {
const forecast = this.generateWeatherObjectsFromForecast(data);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
/** UK Met Office Specific Methods - These are not part of the default provider methods */
/*
* Gets the complete url for the request
@@ -83,24 +76,23 @@ WeatherProvider.register("ukmetoffice", {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// data times are always UTC
let nowUtc = moment.utc()
let midnightUtc = nowUtc.clone().startOf("day")
let nowUtc = moment.utc();
let midnightUtc = nowUtc.clone().startOf("day");
let timeInMins = nowUtc.diff(midnightUtc, "minutes");
// loop round each of the (5) periods, look for today (the first period may be yesterday)
for (i in currentWeatherData.SiteRep.DV.Location.Period) {
let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0,10), "YYYY-MM-DD")
for (var i in currentWeatherData.SiteRep.DV.Location.Period) {
let periodDate = moment.utc(currentWeatherData.SiteRep.DV.Location.Period[i].value.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// check this is the period we want, after today the diff will be -ve
if (moment().diff(periodDate, "minutes") > 0) {
// loop round the reports looking for the one we are in
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
for (j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep){
for (var j in currentWeatherData.SiteRep.DV.Location.Period[i].Rep) {
let p = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].$;
if (timeInMins >= p && timeInMins-180 < p) {
if (timeInMins >= p && timeInMins - 180 < p) {
// finally got the one we want, so populate weather object
currentWeather.humidity = currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].H;
currentWeather.temperature = this.convertTemp(currentWeatherData.SiteRep.DV.Location.Period[i].Rep[j].T);
@@ -116,7 +108,7 @@ WeatherProvider.register("ukmetoffice", {
}
// determine the sunrise/sunset times - not supplied in UK Met Office data
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location)
let times = this.calcAstroData(currentWeatherData.SiteRep.DV.Location);
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1];
@@ -127,22 +119,21 @@ WeatherProvider.register("ukmetoffice", {
* Generate WeatherObjects based on forecast information
*/
generateWeatherObjectsFromForecast(forecasts) {
const days = [];
// loop round the (5) periods getting the data
// for each period array, Day is [0], Night is [1]
for (j in forecasts.SiteRep.DV.Location.Period) {
for (var j in forecasts.SiteRep.DV.Location.Period) {
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// data times are always UTC
dateStr = forecasts.SiteRep.DV.Location.Period[j].value
let periodDate = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD")
const dateStr = forecasts.SiteRep.DV.Location.Period[j].value;
let periodDate = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
// ignore if period is before today
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// populate the weather object
weather.date = moment.utc(dateStr.substr(0,10), "YYYY-MM-DD");
weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
weather.minTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[1].Nm);
weather.maxTemperature = this.convertTemp(forecasts.SiteRep.DV.Location.Period[j].Rep[0].Dm);
weather.weatherType = this.convertWeatherType(forecasts.SiteRep.DV.Location.Period[j].Rep[0].W);
@@ -213,7 +204,7 @@ WeatherProvider.register("ukmetoffice", {
* Convert temp (from degrees C) if required
*/
convertTemp(tempInC) {
return this.tempUnits === "imperial" ? tempInC * 9 / 5 + 32 : tempInC;
return this.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
},
/*
@@ -228,22 +219,22 @@ WeatherProvider.register("ukmetoffice", {
*/
convertWindDirection(windDirection) {
const windCardinals = {
"N": 0,
"NNE": 22,
"NE": 45,
"ENE": 67,
"E": 90,
"ESE": 112,
"SE": 135,
"SSE": 157,
"S": 180,
"SSW": 202,
"SW": 225,
"WSW": 247,
"W": 270,
"WNW": 292,
"NW": 315,
"NNW": 337
N: 0,
NNE: 22,
NE: 45,
ENE: 67,
E: 90,
ESE: 112,
SE: 135,
SSE: 157,
S: 180,
SSW: 202,
SW: 225,
WSW: 247,
W: 270,
WNW: 292,
NW: 315,
NNW: 337
};
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;

View File

@@ -1,4 +1,4 @@
/* global WeatherProvider, WeatherObject */
/* global WeatherProvider, WeatherObject, SunCalc */
/* Magic Mirror
* Module: Weather
@@ -11,9 +11,7 @@
* Note that this is only for US locations (lat and lon) and does not require an API key
* Since it is free, there are some items missing - like sunrise, sunset, humidity, etc.
*/
WeatherProvider.register("weathergov", {
// Set the name of the provider.
// This isn't strictly necessary, since it will fallback to the provider identifier
// But for debugging (and future alerts) it would be nice to have the real name.
@@ -22,7 +20,7 @@ WeatherProvider.register("weathergov", {
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
.then(data => {
.then((data) => {
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -32,16 +30,16 @@ WeatherProvider.register("weathergov", {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties.periods[0]);
this.setCurrentWeather(currentWeather);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
// Overwrite the fetchCurrentWeather method.
fetchWeatherForecast() {
this.fetchData(this.getUrl())
.then(data => {
.then((data) => {
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
@@ -51,10 +49,10 @@ WeatherProvider.register("weathergov", {
const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
this.setWeatherForecast(forecast);
})
.catch(function(request) {
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable())
.finally(() => this.updateAvailable());
},
/** Weather.gov Specific Methods - These are not part of the default provider methods */
@@ -77,7 +75,7 @@ WeatherProvider.register("weathergov", {
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.shortForecast, currentWeatherData.isDaytime);
// determine the sunrise/sunset times - not supplied in weather.gov data
let times = this.calcAstroData(this.config.lat, this.config.lon)
let times = this.calcAstroData(this.config.lat, this.config.lon);
currentWeather.sunrise = times[0];
currentWeather.sunset = times[1];
@@ -106,9 +104,7 @@ WeatherProvider.register("weathergov", {
weather.precipitation = 0;
for (const forecast of forecasts) {
if (date !== moment(forecast.startTime).format("YYYY-MM-DD")) {
// calculate minimum/maximum temperature, specify rain amount
weather.minTemperature = Math.min.apply(null, minTemp);
weather.maxTemperature = Math.max.apply(null, maxTemp);
@@ -241,22 +237,22 @@ WeatherProvider.register("weathergov", {
*/
convertWindDirection(windDirection) {
const windCardinals = {
"N": 0,
"NNE": 22,
"NE": 45,
"ENE": 67,
"E": 90,
"ESE": 112,
"SE": 135,
"SSE": 157,
"S": 180,
"SSW": 202,
"SW": 225,
"WSW": 247,
"W": 270,
"WNW": 292,
"NW": 315,
"NNW": 337
N: 0,
NNE: 22,
NE: 45,
ENE: 67,
E: 90,
ESE: 112,
SE: 135,
SSE: 157,
S: 180,
SSW: 202,
SW: 225,
WSW: 247,
W: 270,
WNW: 292,
NW: 315,
NNW: 337
};
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;

View File

@@ -3,8 +3,6 @@
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);
}

View File

@@ -1,16 +1,14 @@
/* global Module, WeatherProvider */
/* global WeatherProvider */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
*/
Module.register("weather",{
Module.register("weather", {
// Default module config.
defaults: {
updateInterval: 10 * 60 * 1000,
weatherProvider: "openweathermap",
roundTemp: false,
type: "current", //current, forecast
@@ -46,7 +44,7 @@ Module.register("weather",{
retryDelay: 2500,
apiVersion: "2.5",
apiBase: "http://api.openweathermap.org/data/",
apiBase: "https://api.openweathermap.org/data/",
weatherEndpoint: "/weather",
appendLocationNameToHeader: true,
@@ -63,23 +61,17 @@ Module.register("weather",{
weatherProvider: null,
// Define required scripts.
getStyles: function() {
getStyles: function () {
return ["font-awesome.css", "weather-icons.css", "weather.css"];
},
// Return the scripts that are necessary for the weather module.
getScripts: function () {
return [
"moment.js",
"weatherprovider.js",
"weatherobject.js",
"suncalc.js",
this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")
];
return ["moment.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")];
},
// Override getHeader method.
getHeader: function() {
getHeader: function () {
if (this.config.appendLocationNameToHeader && this.data.header !== undefined && this.weatherProvider) {
return this.data.header + " " + this.weatherProvider.fetchedLocation();
}
@@ -105,7 +97,7 @@ Module.register("weather",{
},
// Override notification handler.
notificationReceived: function(notification, payload, sender) {
notificationReceived: function (notification, payload, sender) {
if (notification === "CALENDAR_EVENTS") {
var senderClasses = sender.data.classes.toLowerCase().split(" ");
if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
@@ -148,13 +140,13 @@ Module.register("weather",{
},
// What to do when the weather provider has new information available?
updateAvailable: function() {
updateAvailable: function () {
Log.log("New weather information available.");
this.updateDom(0);
this.scheduleUpdate();
},
scheduleUpdate: function(delay = null) {
scheduleUpdate: function (delay = null) {
var nextLoad = this.config.updateInterval;
if (delay !== null && delay >= 0) {
nextLoad = delay;
@@ -169,88 +161,106 @@ Module.register("weather",{
}, nextLoad);
},
roundValue: function(temperature) {
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
return parseFloat(temperature).toFixed(decimals);
},
addFilters() {
this.nunjucksEnvironment().addFilter("formatTime", function(date) {
date = moment(date);
this.nunjucksEnvironment().addFilter(
"formatTime",
function (date) {
date = moment(date);
if (this.config.timeFormat !== 24) {
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
return date.format("h:mm A");
if (this.config.timeFormat !== 24) {
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
return date.format("h:mm A");
} else {
return date.format("h:mm a");
}
} else {
return date.format("h:mm a");
}
} else {
return date.format("h:mm");
}
}
return date.format("HH:mm");
}.bind(this));
this.nunjucksEnvironment().addFilter("unit", function (value, type) {
if (type === "temperature") {
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
value += "°";
}
if (this.config.degreeLabel) {
if (this.config.tempUnits === "metric") {
value += "C";
} else if (this.config.tempUnits === "imperial") {
value += "F";
} else {
value += "K";
return date.format("h:mm");
}
}
} else if (type === "precip") {
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
value = "";
} else {
if (this.config.weatherProvider === "ukmetoffice") {
value += "%";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
}
return date.format("HH:mm");
}.bind(this)
);
this.nunjucksEnvironment().addFilter(
"unit",
function (value, type) {
if (type === "temperature") {
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
value += "°";
}
if (this.config.degreeLabel) {
if (this.config.tempUnits === "metric") {
value += "C";
} else if (this.config.tempUnits === "imperial") {
value += "F";
} else {
value += "K";
}
}
} else if (type === "precip") {
if (isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
value = "";
} else {
if (this.config.weatherProvider === "ukmetoffice") {
value += "%";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
}
}
} else if (type === "humidity") {
value += "%";
}
} else if (type === "humidity") {
value += "%";
}
return value;
}.bind(this));
return value;
}.bind(this)
);
this.nunjucksEnvironment().addFilter("roundValue", function(value) {
return this.roundValue(value);
}.bind(this));
this.nunjucksEnvironment().addFilter(
"roundValue",
function (value) {
return this.roundValue(value);
}.bind(this)
);
this.nunjucksEnvironment().addFilter("decimalSymbol", function(value) {
return value.toString().replace(/\./g, this.config.decimalSymbol);
}.bind(this));
this.nunjucksEnvironment().addFilter(
"decimalSymbol",
function (value) {
return value.toString().replace(/\./g, this.config.decimalSymbol);
}.bind(this)
);
this.nunjucksEnvironment().addFilter("calcNumSteps", function(forecast) {
return Math.min(forecast.length, this.config.maxNumberOfDays);
}.bind(this));
this.nunjucksEnvironment().addFilter(
"calcNumSteps",
function (forecast) {
return Math.min(forecast.length, this.config.maxNumberOfDays);
}.bind(this)
);
this.nunjucksEnvironment().addFilter("opacity", function(currentStep, numSteps) {
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = numSteps * this.config.fadePoint;
var numFadesteps = numSteps - startingPoint;
if (currentStep >= startingPoint) {
return 1 - (currentStep - startingPoint) / numFadesteps;
this.nunjucksEnvironment().addFilter(
"opacity",
function (currentStep, numSteps) {
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = numSteps * this.config.fadePoint;
var numFadesteps = numSteps - startingPoint;
if (currentStep >= startingPoint) {
return 1 - (currentStep - startingPoint) / numFadesteps;
} else {
return 1;
}
} else {
return 1;
}
} else {
return 1;
}
}.bind(this));
}.bind(this)
);
}
});

View File

@@ -1,20 +1,16 @@
/* global Class */
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* By Michael Teeuw https://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 necessary for the current weather.
* As soon as we start implementing the forecast, mode properties will be added.
*/
// Currently this is focused on the information which is necessary for the current weather.
// As soon as we start implementing the forecast, mode properties will be added.
class WeatherObject {
constructor(units, tempUnits, windUnits) {
this.units = units;
this.tempUnits = tempUnits;
this.windUnits = windUnits;
@@ -32,11 +28,10 @@ class WeatherObject {
this.snow = null;
this.precipitation = null;
this.feelsLikeTemp = null;
}
cardinalWindDirection() {
if (this.windDirection > 11.25 && this.windDirection <= 33.75){
if (this.windDirection > 11.25 && this.windDirection <= 33.75) {
return "NNE";
} else if (this.windDirection > 33.75 && this.windDirection <= 56.25) {
return "NE";
@@ -72,7 +67,7 @@ class WeatherObject {
}
beaufortWindSpeed() {
const windInKmh = (this.windUnits === "imperial") ? this.windSpeed * 1.609344 : this.windSpeed * 60 * 60 / 1000;
const windInKmh = this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000;
const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (const [index, speed] of speeds.entries()) {
if (speed > windInKmh) {
@@ -87,24 +82,28 @@ class WeatherObject {
}
feelsLike() {
if (this.feelsLikeTemp) {
return this.feelsLikeTemp;
if (this.feelsLikeTemp) {
return this.feelsLikeTemp;
}
const windInMph = (this.windUnits === "imperial") ? this.windSpeed : this.windSpeed * 2.23694;
const tempInF = this.tempUnits === "imperial" ? this.temperature : this.temperature * 9 / 5 + 32;
const windInMph = this.windUnits === "imperial" ? this.windSpeed : this.windSpeed * 2.23694;
const tempInF = this.tempUnits === "imperial" ? this.temperature : (this.temperature * 9) / 5 + 32;
let feelsLike = tempInF;
if (windInMph > 3 && tempInF < 50) {
feelsLike = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
} else if (tempInF > 80 && this.humidity > 40) {
feelsLike = -42.379 + 2.04901523 * tempInF + 10.14333127 * this.humidity
- 0.22475541 * tempInF * this.humidity - 6.83783 * Math.pow(10, -3) * tempInF * tempInF
- 5.481717 * Math.pow(10, -2) * this.humidity * this.humidity
+ 1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity
+ 8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity
- 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
feelsLike =
-42.379 +
2.04901523 * tempInF +
10.14333127 * this.humidity -
0.22475541 * tempInF * this.humidity -
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
}
return this.tempUnits === "imperial" ? feelsLike : (feelsLike - 32) * 5 / 9;
return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9;
}
}

View File

@@ -3,15 +3,11 @@
/* Magic Mirror
* Module: Weather
*
* By Michael Teeuw http://michaelteeuw.nl
* By Michael Teeuw https://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,
@@ -32,77 +28,77 @@ var WeatherProvider = Class.extend({
// All the following methods can be overwritten, although most are good as they are.
// Called when a weather provider is initialized.
init: function(config) {
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) {
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() {
start: function () {
Log.info(`Weather provider: ${this.providerName} started.`);
},
// This method should start the API request to fetch the current weather.
// This method should definitely be overwritten in the provider.
fetchCurrentWeather: function() {
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 definitely be overwritten in the provider.
fetchWeatherForecast: function() {
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() {
currentWeather: function () {
return this.currentWeatherObject;
},
// This returns an array of WeatherDay objects for the weather forecast.
weatherForecast: function() {
weatherForecast: function () {
return this.weatherForecastArray;
},
// This returns the name of the fetched location or an empty string.
fetchedLocation: function() {
fetchedLocation: function () {
return this.fetchedLocationName || "";
},
// Set the currentWeather and notify the delegate that new information is available.
setCurrentWeather: function(currentWeatherObject) {
setCurrentWeather: function (currentWeatherObject) {
// We should check here if we are passing a WeatherDay
this.currentWeatherObject = currentWeatherObject;
},
// Set the weatherForecastArray and notify the delegate that new information is available.
setWeatherForecast: function(weatherForecastArray) {
setWeatherForecast: function (weatherForecastArray) {
// We should check here if we are passing a WeatherDay
this.weatherForecastArray = weatherForecastArray;
},
// Set the fetched location name.
setFetchedLocation: function(name) {
setFetchedLocation: function (name) {
this.fetchedLocationName = name;
},
// Notify the delegate that new weather is available.
updateAvailable: function() {
updateAvailable: function () {
this.delegate.updateAvailable(this);
},
// A convenience function to make requests. It returns a promise.
fetchData: function(url, method = "GET", data = null) {
return new Promise(function(resolve, reject) {
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() {
request.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
resolve(JSON.parse(this.response));
@@ -124,14 +120,14 @@ WeatherProvider.providers = [];
/**
* Static method to register a new weather provider.
*/
WeatherProvider.register = function(providerIdentifier, providerDetails) {
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) {
WeatherProvider.initialize = function (providerIdentifier, delegate) {
providerIdentifier = providerIdentifier.toLowerCase();
var provider = new WeatherProvider.providers[providerIdentifier]();