Merge pull request #2774 from khassel/remove-old-weather-modules

This commit is contained in:
Michael Teeuw 2022-01-14 23:11:12 +01:00 committed by GitHub
commit fcb0d33e29
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 16 additions and 3163 deletions

View File

@ -20,6 +20,7 @@ _This release is scheduled to be released on 2022-04-01._
- deprecated roboto fonts package `roboto-fontface-bower` replaced with `fontsource`.
- update `helmet` to v5, use defaults of v4.
- updates Font Awesome css class to new default style (fixes #2768)
- replaced deprecated modules `currentweather` and `weatherforecast` with dummy modules only displaying that they have to be replaced.
### Fixed

View File

@ -1,8 +0,0 @@
# Module: Current Weather
> :warning: **This module is deprecated in favor of the [weather](https://docs.magicmirror.builders/modules/weather.html) module.**
The `currentweather` module is one of the default modules of the MagicMirror.
This module displays the current weather, including the windspeed, the sunset or sunrise time, the temperature and an icon to display the current conditions.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/currentweather.html).

View File

@ -1,15 +0,0 @@
.currentweather .weathericon,
.currentweather .fa-home {
font-size: 75%;
line-height: 65px;
display: inline-block;
transform: translate(0, -3px);
}
.currentweather .humidityIcon {
padding-right: 4px;
}
.currentweather .humidity-padding {
padding-bottom: 6px;
}

View File

@ -9,592 +9,25 @@
* This module is deprecated. Any additional feature will no longer be merged.
*/
Module.register("currentweather", {
// Default module config.
defaults: {
location: false,
locationID: false,
appid: "",
units: config.units,
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
timeFormat: config.timeFormat,
showPeriod: true,
showPeriodUpper: false,
showWindDirection: true,
showWindDirectionAsArrow: false,
useBeaufort: true,
useKMPHwind: false,
lang: config.language,
decimalSymbol: ".",
showHumidity: false,
showSun: true,
degreeLabel: false,
showIndoorTemperature: false,
showIndoorHumidity: false,
showFeelsLike: true,
initialLoadDelay: 0, // 0 seconds delay
retryDelay: 2500,
apiVersion: "2.5",
apiBase: "https://api.openweathermap.org/data/",
weatherEndpoint: "weather",
appendLocationNameToHeader: true,
useLocationAsHeader: false,
calendarClass: "calendar",
tableClass: "large",
onlyTemp: false,
hideTemp: false,
roundTemp: false,
iconTable: {
"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"
}
},
// create a variable for the first upcoming calendar event. Used if no location is specified.
firstEvent: false,
// create a variable to hold the location name based on the API result.
fetchedLocationName: "",
// Define required scripts.
getScripts: function () {
return ["moment.js"];
},
// Define required scripts.
getStyles: function () {
return ["weather-icons.css", "currentweather.css"];
},
// Define required translations.
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation.
return false;
},
// Define start sequence.
start: function () {
Log.info("Starting module: " + this.name);
// Set locale.
moment.locale(config.language);
this.windSpeed = null;
this.windDirection = null;
this.windDeg = null;
this.sunriseSunsetTime = null;
this.sunriseSunsetIcon = null;
this.temperature = null;
this.indoorTemperature = null;
this.indoorHumidity = null;
this.weatherType = null;
this.feelsLike = null;
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
},
// add extra information of current weather
// windDirection, humidity, sunrise and sunset
addExtraInfoWeather: function (wrapper) {
var small = document.createElement("div");
small.className = "normal medium";
var windIcon = document.createElement("span");
windIcon.className = "wi wi-strong-wind dimmed";
small.appendChild(windIcon);
var windSpeed = document.createElement("span");
windSpeed.innerHTML = " " + this.windSpeed;
small.appendChild(windSpeed);
if (this.config.showWindDirection) {
var windDirection = document.createElement("sup");
if (this.config.showWindDirectionAsArrow) {
if (this.windDeg !== null) {
windDirection.innerHTML = ' &nbsp;<i class="fas fa-long-arrow-alt-down" style="transform:rotate(' + this.windDeg + 'deg);"></i>&nbsp;';
}
} else {
windDirection.innerHTML = " " + this.translate(this.windDirection);
}
small.appendChild(windDirection);
}
var spacer = document.createElement("span");
spacer.innerHTML = "&nbsp;";
small.appendChild(spacer);
if (this.config.showHumidity) {
var humidity = document.createElement("span");
humidity.innerHTML = this.humidity;
var supspacer = document.createElement("sup");
supspacer.innerHTML = "&nbsp;";
var humidityIcon = document.createElement("sup");
humidityIcon.className = "wi wi-humidity humidityIcon";
humidityIcon.innerHTML = "&nbsp;";
small.appendChild(humidity);
small.appendChild(supspacer);
small.appendChild(humidityIcon);
}
if (this.config.showSun) {
var sunriseSunsetIcon = document.createElement("span");
sunriseSunsetIcon.className = "wi dimmed " + this.sunriseSunsetIcon;
small.appendChild(sunriseSunsetIcon);
var sunriseSunsetTime = document.createElement("span");
sunriseSunsetTime.innerHTML = " " + this.sunriseSunsetTime;
small.appendChild(sunriseSunsetTime);
}
wrapper.appendChild(small);
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
wrapper.className = this.config.tableClass;
if (this.config.appid === "") {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
wrapper.innerHTML =
"<style>text-decoration: none</style>" +
"This module is deprecated since release v2.15 and removed with v2.19." +
'<br>Please use the `weather` module as replacement, more info in the <a href="https://docs.magicmirror.builders/modules/weather.html" style="color: #ffffff">documentation</a>.';
wrapper.className = "dimmed light small";
return wrapper;
}
if (!this.loaded) {
wrapper.innerHTML = this.translate("LOADING");
wrapper.className = "dimmed light small";
return wrapper;
}
if (this.config.onlyTemp === false) {
this.addExtraInfoWeather(wrapper);
}
var large = document.createElement("div");
large.className = "light";
var degreeLabel = "";
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if (this.config.degreeLabel) {
switch (this.config.units) {
case "metric":
degreeLabel += "C";
break;
case "imperial":
degreeLabel += "F";
break;
case "default":
degreeLabel += "K";
break;
}
}
if (this.config.decimalSymbol === "") {
this.config.decimalSymbol = ".";
}
if (this.config.hideTemp === false) {
var weatherIcon = document.createElement("span");
weatherIcon.className = "wi weathericon wi-" + this.weatherType;
large.appendChild(weatherIcon);
var temperature = document.createElement("span");
temperature.className = "bright";
temperature.innerHTML = " " + this.temperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(temperature);
}
if (this.config.showIndoorTemperature && this.indoorTemperature) {
var indoorIcon = document.createElement("span");
indoorIcon.className = "fas fa-home";
large.appendChild(indoorIcon);
var indoorTemperatureElem = document.createElement("span");
indoorTemperatureElem.className = "bright";
indoorTemperatureElem.innerHTML = " " + this.indoorTemperature.replace(".", this.config.decimalSymbol) + degreeLabel;
large.appendChild(indoorTemperatureElem);
}
if (this.config.showIndoorHumidity && this.indoorHumidity) {
var indoorHumidityIcon = document.createElement("span");
indoorHumidityIcon.className = "fas fa-tint";
large.appendChild(indoorHumidityIcon);
var indoorHumidityElem = document.createElement("span");
indoorHumidityElem.className = "bright";
indoorHumidityElem.innerHTML = " " + this.indoorHumidity + "%";
large.appendChild(indoorHumidityElem);
}
wrapper.appendChild(large);
if (this.config.showFeelsLike && this.config.onlyTemp === false) {
var small = document.createElement("div");
small.className = "normal medium";
var feelsLike = document.createElement("span");
feelsLike.className = "dimmed";
feelsLike.innerHTML = this.translate("FEELS", {
DEGREE: this.feelsLike + degreeLabel
});
small.appendChild(feelsLike);
wrapper.appendChild(small);
}
return wrapper;
},
// Override getHeader method.
getHeader: function () {
if (this.config.useLocationAsHeader && this.config.location !== false) {
return this.config.location;
}
if (this.config.appendLocationNameToHeader) {
if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
else return this.fetchedLocationName;
}
return this.data.header ? this.data.header : "";
},
// Override notification handler.
notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
if (this.config.appendLocationNameToHeader) {
this.hide(0, { lockString: this.identifier });
}
}
if (notification === "CALENDAR_EVENTS") {
var senderClasses = sender.data.classes.toLowerCase().split(" ");
if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
this.firstEvent = false;
for (var e in payload) {
var event = payload[e];
if (event.location || event.geo) {
this.firstEvent = event;
//Log.log("First upcoming event with location: ", event);
break;
}
}
}
}
if (notification === "INDOOR_TEMPERATURE") {
this.indoorTemperature = this.roundValue(payload);
this.updateDom(this.config.animationSpeed);
}
if (notification === "INDOOR_HUMIDITY") {
this.indoorHumidity = this.roundValue(payload);
this.updateDom(this.config.animationSpeed);
}
},
/* updateWeather(compliments)
* Requests new data from openweather.org.
* Calls processWeather on succesfull response.
*/
updateWeather: function () {
if (this.config.appid === "") {
Log.error("CurrentWeather: APPID not set!");
return;
}
var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.weatherEndpoint + this.getParams();
var self = this;
var retry = true;
var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
self.processWeather(JSON.parse(this.response));
} else if (this.status === 401) {
self.updateDom(self.config.animationSpeed);
Log.error(self.name + ": Incorrect APPID.");
retry = true;
} else {
Log.error(self.name + ": Could not load weather.");
}
if (retry) {
self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
}
}
};
weatherRequest.send();
},
/* 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.appid;
return params;
},
/* processWeather(data)
* Uses the received data to set the various values.
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function (data) {
if (!data || !data.main || typeof data.main.temp === "undefined") {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.humidity = parseFloat(data.main.humidity);
this.temperature = this.roundValue(data.main.temp);
this.fetchedLocationName = data.name;
this.feelsLike = 0;
if (this.config.useBeaufort) {
this.windSpeed = this.ms2Beaufort(this.roundValue(data.wind.speed));
} else if (this.config.useKMPHwind) {
this.windSpeed = parseFloat((data.wind.speed * 60 * 60) / 1000).toFixed(0);
} else {
this.windSpeed = parseFloat(data.wind.speed).toFixed(0);
}
// ONLY WORKS IF TEMP IN C //
var windInMph = parseFloat(data.wind.speed * 2.23694);
var tempInF = 0;
switch (this.config.units) {
case "metric":
tempInF = 1.8 * this.temperature + 32;
break;
case "imperial":
tempInF = this.temperature;
break;
case "default":
tempInF = 1.8 * (this.temperature - 273.15) + 32;
break;
}
if (windInMph > 3 && tempInF < 50) {
// windchill
var windChillInF = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
var windChillInC = (windChillInF - 32) * (5 / 9);
// this.feelsLike = windChillInC.toFixed(0);
switch (this.config.units) {
case "metric":
this.feelsLike = windChillInC.toFixed(0);
break;
case "imperial":
this.feelsLike = windChillInF.toFixed(0);
break;
case "default":
this.feelsLike = (windChillInC + 273.15).toFixed(0);
break;
}
} else if (tempInF > 80 && this.humidity > 40) {
// heat index
var Hindex =
-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;
switch (this.config.units) {
case "metric":
this.feelsLike = parseFloat((Hindex - 32) / 1.8).toFixed(0);
break;
case "imperial":
this.feelsLike = Hindex.toFixed(0);
break;
case "default":
var tc = parseFloat((Hindex - 32) / 1.8) + 273.15;
this.feelsLike = tc.toFixed(0);
break;
}
} else {
this.feelsLike = parseFloat(this.temperature).toFixed(0);
}
this.windDirection = this.deg2Cardinal(data.wind.deg);
this.windDeg = data.wind.deg;
this.weatherType = this.config.iconTable[data.weather[0].icon];
var now = new Date();
var sunrise = new Date(data.sys.sunrise * 1000);
var sunset = new Date(data.sys.sunset * 1000);
// The moment().format('h') method has a bug on the Raspberry Pi.
// So we need to generate the timestring manually.
// See issue: https://github.com/MichMich/MagicMirror/issues/181
var sunriseSunsetDateObject = sunrise < now && sunset > now ? sunset : sunrise;
var timeString = moment(sunriseSunsetDateObject).format("HH:mm");
if (this.config.timeFormat !== 24) {
//var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
//timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
timeString = moment(sunriseSunsetDateObject).format("h:mm A");
} else {
//timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
timeString = moment(sunriseSunsetDateObject).format("h:mm a");
}
} else {
//timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
timeString = moment(sunriseSunsetDateObject).format("h:mm");
}
}
this.sunriseSunsetTime = timeString;
this.sunriseSunsetIcon = sunrise < now && sunset > now ? "wi-sunset" : "wi-sunrise";
this.show(this.config.animationSpeed, { lockString: this.identifier });
this.loaded = true;
this.updateDom(this.config.animationSpeed);
this.sendNotification("CURRENTWEATHER_DATA", { data: data });
this.sendNotification("CURRENTWEATHER_TYPE", { type: this.config.iconTable[data.weather[0].icon].replace("-", "_") });
},
/* scheduleUpdate()
* Schedule next update.
*
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/
scheduleUpdate: function (delay) {
var nextLoad = this.config.updateInterval;
if (typeof delay !== "undefined" && delay >= 0) {
nextLoad = delay;
}
var self = this;
setTimeout(function () {
self.updateWeather();
}, nextLoad);
},
/* ms2Beaufort(ms)
* Converts m2 to beaufort (windspeed).
*
* see:
* https://www.spc.noaa.gov/faq/tornado/beaufort.html
* https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
*
* argument ms number - Windspeed in m/s.
*
* return number - Windspeed in beaufort.
*/
ms2Beaufort: function (ms) {
var kmh = (ms * 60 * 60) / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
if (speed > kmh) {
return beaufort;
}
}
return 12;
},
deg2Cardinal: function (deg) {
if (deg > 11.25 && deg <= 33.75) {
return "NNE";
} else if (deg > 33.75 && deg <= 56.25) {
return "NE";
} else if (deg > 56.25 && deg <= 78.75) {
return "ENE";
} else if (deg > 78.75 && deg <= 101.25) {
return "E";
} else if (deg > 101.25 && deg <= 123.75) {
return "ESE";
} else if (deg > 123.75 && deg <= 146.25) {
return "SE";
} else if (deg > 146.25 && deg <= 168.75) {
return "SSE";
} else if (deg > 168.75 && deg <= 191.25) {
return "S";
} else if (deg > 191.25 && deg <= 213.75) {
return "SSW";
} else if (deg > 213.75 && deg <= 236.25) {
return "SW";
} else if (deg > 236.25 && deg <= 258.75) {
return "WSW";
} else if (deg > 258.75 && deg <= 281.25) {
return "W";
} else if (deg > 281.25 && deg <= 303.75) {
return "WNW";
} else if (deg > 303.75 && deg <= 326.25) {
return "NW";
} else if (deg > 326.25 && deg <= 348.75) {
return "NNW";
} else {
return "N";
}
},
/* function(temperature)
* Rounds a temperature to 1 decimal or integer (depending on config.roundTemp).
*
* argument temperature number - Temperature.
*
* return string - Rounded Temperature.
*/
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
return "deprecated currentweather";
}
});

View File

@ -1,9 +0,0 @@
const NodeHelper = require("node_helper");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
start: function () {
Log.warn(`The module '${this.name}' is deprecated in favor of the 'weather'-module, please refer to the documentation for a migration path`);
}
});

View File

@ -1,8 +0,0 @@
# Module: Weather Forecast
> :warning: **This module is deprecated in favor of the [weather](https://docs.magicmirror.builders/modules/weather.html) module.**
The `weatherforecast` module is one of the default modules of the MagicMirror.
This module displays the weather forecast for the coming week, including an an icon to display the current conditions, the minimum temperature and the maximum temperature.
For configuration options, please check the [MagicMirror² documentation](https://docs.magicmirror.builders/modules/weatherforecast.html).

View File

@ -1,9 +0,0 @@
const NodeHelper = require("node_helper");
const Log = require("logger");
module.exports = NodeHelper.create({
// Override start method.
start: function () {
Log.warn(`The module '${this.name}' is deprecated in favor of the 'weather'-module, please refer to the documentation for a migration path`);
}
});

View File

@ -1,27 +0,0 @@
.weatherforecast .day {
padding-left: 0;
padding-right: 25px;
}
.weatherforecast .weather-icon {
padding-right: 30px;
text-align: center;
}
.weatherforecast .min-temp {
padding-left: 20px;
padding-right: 0;
}
.weatherforecast .rain {
padding-left: 20px;
padding-right: 0;
}
.weatherforecast tr.colored .min-temp {
color: #bcddff;
}
.weatherforecast tr.colored .max-temp {
color: #ff8e99;
}

View File

@ -1,7 +1,7 @@
/* eslint-disable */
/* Magic Mirror
* Module: WeatherForecast
* Module: CurrentWeather
*
* By Michael Teeuw https://michaelteeuw.nl
* MIT Licensed.
@ -9,510 +9,25 @@
* This module is deprecated. Any additional feature will no longer be merged.
*/
Module.register("weatherforecast", {
// Default module config.
defaults: {
location: false,
locationID: false,
lat: false,
lon: false,
appid: "",
units: config.units,
maxNumberOfDays: 7,
showRainAmount: false,
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
timeFormat: config.timeFormat,
lang: config.language,
decimalSymbol: ".",
fade: true,
fadePoint: 0.25, // Start on 1/4th of the list.
colored: false,
scale: false,
initialLoadDelay: 2500, // 2.5 seconds delay. This delay is used to keep the OpenWeather API happy.
retryDelay: 2500,
apiVersion: "2.5",
apiBase: "https://api.openweathermap.org/data/",
forecastEndpoint: "forecast/daily",
excludes: false,
appendLocationNameToHeader: true,
calendarClass: "calendar",
tableClass: "small",
roundTemp: false,
iconTable: {
"01d": "wi-day-sunny",
"02d": "wi-day-cloudy",
"03d": "wi-cloudy",
"04d": "wi-cloudy-windy",
"09d": "wi-showers",
"10d": "wi-rain",
"11d": "wi-thunderstorm",
"13d": "wi-snow",
"50d": "wi-fog",
"01n": "wi-night-clear",
"02n": "wi-night-cloudy",
"03n": "wi-night-cloudy",
"04n": "wi-night-cloudy",
"09n": "wi-night-showers",
"10n": "wi-night-rain",
"11n": "wi-night-thunderstorm",
"13n": "wi-night-snow",
"50n": "wi-night-alt-cloudy-windy"
}
},
// create a variable for the first upcoming calendar event. Used if no location is specified.
firstEvent: false,
// create a variable to hold the location name based on the API result.
fetchedLocationName: "",
// Define required scripts.
getScripts: function () {
return ["moment.js"];
},
// Define required scripts.
getStyles: function () {
return ["weather-icons.css", "weatherforecast.css"];
},
// Define required translations.
getTranslations: function () {
// The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation.
return false;
},
// Define start sequence.
start: function () {
Log.info("Starting module: " + this.name);
// Set locale.
moment.locale(config.language);
this.forecast = [];
this.loaded = false;
this.scheduleUpdate(this.config.initialLoadDelay);
this.updateTimer = null;
},
// Override dom generator.
getDom: function () {
var wrapper = document.createElement("div");
if (this.config.appid === "" || this.config.appid === "YOUR_OPENWEATHER_API_KEY") {
wrapper.innerHTML = "Please set the correct openweather <i>appid</i> in the config for module: " + this.name + ".";
wrapper.className = this.config.tableClass;
wrapper.innerHTML =
"<style>text-decoration: none</style>" +
"This module is deprecated since release v2.15 and removed with v2.19." +
'<br>Please use the `weather` module as replacement, more info in the <a href="https://docs.magicmirror.builders/modules/weather.html" style="color: #ffffff">documentation</a>.';
wrapper.className = "dimmed light small";
return wrapper;
}
if (!this.loaded) {
wrapper.innerHTML = this.translate("LOADING");
wrapper.className = "dimmed light small";
return wrapper;
}
var table = document.createElement("table");
table.className = this.config.tableClass;
for (var f in this.forecast) {
var forecast = this.forecast[f];
var row = document.createElement("tr");
if (this.config.colored) {
row.className = "colored";
}
table.appendChild(row);
var dayCell = document.createElement("td");
dayCell.className = "day";
dayCell.innerHTML = forecast.day;
row.appendChild(dayCell);
var iconCell = document.createElement("td");
iconCell.className = "bright weather-icon";
row.appendChild(iconCell);
var icon = document.createElement("span");
icon.className = "wi weathericon " + forecast.icon;
iconCell.appendChild(icon);
var degreeLabel = "";
if (this.config.units === "metric" || this.config.units === "imperial") {
degreeLabel += "°";
}
if (this.config.scale) {
switch (this.config.units) {
case "metric":
degreeLabel += "C";
break;
case "imperial":
degreeLabel += "F";
break;
case "default":
degreeLabel = "K";
break;
}
}
if (this.config.decimalSymbol === "" || this.config.decimalSymbol === " ") {
this.config.decimalSymbol = ".";
}
var maxTempCell = document.createElement("td");
maxTempCell.innerHTML = forecast.maxTemp.replace(".", this.config.decimalSymbol) + degreeLabel;
maxTempCell.className = "align-right bright max-temp";
row.appendChild(maxTempCell);
var minTempCell = document.createElement("td");
minTempCell.innerHTML = forecast.minTemp.replace(".", this.config.decimalSymbol) + degreeLabel;
minTempCell.className = "align-right min-temp";
row.appendChild(minTempCell);
if (this.config.showRainAmount) {
var rainCell = document.createElement("td");
if (isNaN(forecast.rain)) {
rainCell.innerHTML = "";
} else {
if (config.units !== "imperial") {
rainCell.innerHTML = parseFloat(forecast.rain).toFixed(1).replace(".", this.config.decimalSymbol) + " mm";
} else {
rainCell.innerHTML = (parseFloat(forecast.rain) / 25.4).toFixed(2).replace(".", this.config.decimalSymbol) + " in";
}
}
rainCell.className = "align-right bright rain";
row.appendChild(rainCell);
}
if (this.config.fade && this.config.fadePoint < 1) {
if (this.config.fadePoint < 0) {
this.config.fadePoint = 0;
}
var startingPoint = this.forecast.length * this.config.fadePoint;
var steps = this.forecast.length - startingPoint;
if (f >= startingPoint) {
var currentStep = f - startingPoint;
row.style.opacity = 1 - (1 / steps) * currentStep;
}
}
}
return table;
},
// Override getHeader method.
getHeader: function () {
if (this.config.appendLocationNameToHeader) {
if (this.data.header) return this.data.header + " " + this.fetchedLocationName;
else return this.fetchedLocationName;
}
return this.data.header ? this.data.header : "";
},
// Override notification handler.
notificationReceived: function (notification, payload, sender) {
if (notification === "DOM_OBJECTS_CREATED") {
if (this.config.appendLocationNameToHeader) {
this.hide(0, { lockString: this.identifier });
}
}
if (notification === "CALENDAR_EVENTS") {
var senderClasses = sender.data.classes.toLowerCase().split(" ");
if (senderClasses.indexOf(this.config.calendarClass.toLowerCase()) !== -1) {
this.firstEvent = false;
for (var e in payload) {
var event = payload[e];
if (event.location || event.geo) {
this.firstEvent = event;
//Log.log("First upcoming event with location: ", event);
break;
}
}
}
}
},
/* updateWeather(compliments)
* Requests new data from openweather.org.
* Calls processWeather on successful response.
*/
updateWeather: function () {
if (this.config.appid === "") {
Log.error("WeatherForecast: APPID not set!");
return;
}
var url = this.config.apiBase + this.config.apiVersion + "/" + this.config.forecastEndpoint + this.getParams();
var self = this;
var retry = true;
var weatherRequest = new XMLHttpRequest();
weatherRequest.open("GET", url, true);
weatherRequest.onreadystatechange = function () {
if (this.readyState === 4) {
if (this.status === 200) {
self.processWeather(JSON.parse(this.response));
} else if (this.status === 401) {
self.updateDom(self.config.animationSpeed);
if (self.config.forecastEndpoint === "forecast/daily") {
self.config.forecastEndpoint = "forecast";
Log.warn(self.name + ": Your AppID does not support long term forecasts. Switching to fallback endpoint.");
}
retry = true;
} else {
Log.error(self.name + ": Could not load weather.");
}
if (retry) {
self.scheduleUpdate(self.loaded ? -1 : self.config.retryDelay);
}
}
};
weatherRequest.send();
},
/* 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.lat && this.config.lon) {
params += "lat=" + this.config.lat + "&lon=" + this.config.lon;
} 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;
}
let numberOfDays;
if (this.config.forecastEndpoint === "forecast") {
numberOfDays = this.config.maxNumberOfDays < 1 || this.config.maxNumberOfDays > 5 ? 5 : this.config.maxNumberOfDays;
// don't get forecasts for the next day, as it would not represent the whole day
numberOfDays = numberOfDays * 8 - (Math.round(new Date().getHours() / 3) % 8);
} else {
numberOfDays = this.config.maxNumberOfDays < 1 || this.config.maxNumberOfDays > 17 ? 7 : this.config.maxNumberOfDays;
}
params += "&cnt=" + numberOfDays;
params += "&exclude=" + this.config.excludes;
params += "&units=" + this.config.units;
params += "&lang=" + this.config.lang;
params += "&APPID=" + this.config.appid;
return params;
},
/*
* parserDataWeather(data)
*
* Use the parse to keep the same struct between daily and forecast Endpoint
* from openweather.org
*
*/
parserDataWeather: function (data) {
if (data.hasOwnProperty("main")) {
data["temp"] = { min: data.main.temp_min, max: data.main.temp_max };
}
return data;
},
/* processWeather(data)
* Uses the received data to set the various values.
*
* argument data object - Weather information received form openweather.org.
*/
processWeather: function (data, momenttz) {
let mom = momenttz ? momenttz : moment; // Exception last.
// Forcast16 (paid) API endpoint provides this data. Onecall endpoint
// does not.
if (data.city) {
this.fetchedLocationName = data.city.name + ", " + data.city.country;
} else if (this.config.location) {
this.fetchedLocationName = this.config.location;
} else {
this.fetchedLocationName = "Unknown";
}
this.forecast = [];
var lastDay = null;
var forecastData = {};
var dayStarts = 8;
var dayEnds = 17;
if (data.city && data.city.sunrise && data.city.sunset) {
dayStarts = new Date(mom.unix(data.city.sunrise).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
dayEnds = new Date(mom.unix(data.city.sunset).locale("en").format("YYYY/MM/DD HH:mm:ss")).getHours();
}
// Handle different structs between forecast16 and onecall endpoints
var forecastList = null;
if (data.list) {
forecastList = data.list;
} else if (data.daily) {
forecastList = data.daily;
} else {
Log.error("Unexpected forecast data");
return undefined;
}
for (var i = 0, count = forecastList.length; i < count; i++) {
var forecast = forecastList[i];
forecast = this.parserDataWeather(forecast); // hack issue #1017
var day;
var hour;
if (forecast.dt_txt) {
day = mom(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss").format("ddd");
hour = new Date(mom(forecast.dt_txt).locale("en").format("YYYY-MM-DD HH:mm:ss")).getHours();
} else {
day = mom(forecast.dt, "X").format("ddd");
hour = new Date(mom(forecast.dt, "X")).getHours();
}
if (day !== lastDay) {
forecastData = {
day: day,
icon: this.config.iconTable[forecast.weather[0].icon],
maxTemp: this.roundValue(forecast.temp.max),
minTemp: this.roundValue(forecast.temp.min),
rain: this.processRain(forecast, forecastList, mom)
};
this.forecast.push(forecastData);
lastDay = day;
// Stop processing when maxNumberOfDays is reached
if (this.forecast.length === this.config.maxNumberOfDays) {
break;
}
} else {
//Log.log("Compare max: ", forecast.temp.max, parseFloat(forecastData.maxTemp));
forecastData.maxTemp = forecast.temp.max > parseFloat(forecastData.maxTemp) ? this.roundValue(forecast.temp.max) : forecastData.maxTemp;
//Log.log("Compare min: ", forecast.temp.min, parseFloat(forecastData.minTemp));
forecastData.minTemp = forecast.temp.min < parseFloat(forecastData.minTemp) ? this.roundValue(forecast.temp.min) : forecastData.minTemp;
// Since we don't want an icon from the start of the day (in the middle of the night)
// we update the icon as long as it's somewhere during the day.
if (hour > dayStarts && hour < dayEnds) {
forecastData.icon = this.config.iconTable[forecast.weather[0].icon];
}
}
}
//Log.log(this.forecast);
this.show(this.config.animationSpeed, { lockString: this.identifier });
this.loaded = true;
this.updateDom(this.config.animationSpeed);
},
/* scheduleUpdate()
* Schedule next update.
*
* argument delay number - Milliseconds before next update. If empty, this.config.updateInterval is used.
*/
scheduleUpdate: function (delay) {
var nextLoad = this.config.updateInterval;
if (typeof delay !== "undefined" && delay >= 0) {
nextLoad = delay;
}
var self = this;
clearTimeout(this.updateTimer);
this.updateTimer = setTimeout(function () {
self.updateWeather();
}, nextLoad);
},
/* ms2Beaufort(ms)
* Converts m2 to beaufort (windspeed).
*
* see:
* https://www.spc.noaa.gov/faq/tornado/beaufort.html
* https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
*
* argument ms number - Windspeed in m/s.
*
* return number - Windspeed in beaufort.
*/
ms2Beaufort: function (ms) {
var kmh = (ms * 60 * 60) / 1000;
var speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
for (var beaufort in speeds) {
var speed = speeds[beaufort];
if (speed > kmh) {
return beaufort;
}
}
return 12;
},
/* function(temperature)
* Rounds a temperature to 1 decimal or integer (depending on config.roundTemp).
*
* argument temperature number - Temperature.
*
* return string - Rounded Temperature.
*/
roundValue: function (temperature) {
var decimals = this.config.roundTemp ? 0 : 1;
var roundValue = parseFloat(temperature).toFixed(decimals);
return roundValue === "-0" ? 0 : roundValue;
},
/* processRain(forecast, allForecasts)
* Calculates the amount of rain for a whole day even if long term forecasts isn't available for the appid.
*
* When using the the fallback endpoint forecasts are provided in 3h intervals and the rain-property is an object instead of number.
* That object has a property "3h" which contains the amount of rain since the previous forecast in the list.
* This code finds all forecasts that is for the same day and sums the amount of rain and returns that.
*/
processRain: function (forecast, allForecasts, momenttz) {
let mom = momenttz ? momenttz : moment; // Exception last.
//If the amount of rain actually is a number, return it
if (!isNaN(forecast.rain)) {
return forecast.rain;
}
//Find all forecasts that is for the same day
var checkDateTime = forecast.dt_txt ? mom(forecast.dt_txt, "YYYY-MM-DD hh:mm:ss") : mom(forecast.dt, "X");
var daysForecasts = allForecasts.filter(function (item) {
var itemDateTime = item.dt_txt ? mom(item.dt_txt, "YYYY-MM-DD hh:mm:ss") : mom(item.dt, "X");
return itemDateTime.isSame(checkDateTime, "day") && item.rain instanceof Object;
});
//If no rain this day return undefined so it wont be displayed for this day
if (daysForecasts.length === 0) {
return undefined;
}
//Summarize all the rain from the matching days
return daysForecasts
.map(function (item) {
return Object.values(item.rain)[0];
})
.reduce(function (a, b) {
return a + b;
}, 0);
return "deprecated weatherforecast";
}
});

File diff suppressed because it is too large Load Diff

View File

@ -1,66 +0,0 @@
/* eslint no-multi-spaces: 0 */
describe("Functions module currentweather", function () {
// Fake for use by currentweather.js
Module = {};
config = {};
Module.definitions = {};
Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition;
};
beforeAll(function () {
require("../../../modules/default/currentweather/currentweather.js");
Module.definitions.currentweather.config = {};
});
describe("roundValue", function () {
describe("this.config.roundTemp is true", function () {
beforeAll(function () {
Module.definitions.currentweather.config.roundTemp = true;
});
const values = [
// index 0 value
// index 1 expect
[1, "1"],
[1.0, "1"],
[1.02, "1"],
[10.12, "10"],
[2.0, "2"],
["2.12", "2"],
[10.1, "10"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.currentweather.roundValue(value[0])).toBe(value[1]);
});
});
});
describe("this.config.roundTemp is false", function () {
beforeAll(function () {
Module.definitions.currentweather.config.roundTemp = false;
});
const values = [
// index 0 value
// index 1 expect
[1, "1.0"],
[1.0, "1.0"],
[1.02, "1.0"],
[10.12, "10.1"],
[2.0, "2.0"],
["2.12", "2.1"],
[10.1, "10.1"],
[10.1, "10.1"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.currentweather.roundValue(value[0])).toBe(value[1]);
});
});
});
});
});

View File

@ -1,116 +0,0 @@
/* eslint no-multi-spaces: 0 */
const moment = require("moment-timezone");
const data = require("../../configs/data/weatherforecast_data.json");
describe("Functions module weatherforecast", function () {
beforeAll(function () {
Module = {};
config = {};
Module.definitions = {};
Module.register = function (name, moduleDefinition) {
Module.definitions[name] = moduleDefinition;
};
require("../../../modules/default/weatherforecast/weatherforecast.js");
Module.definitions.weatherforecast.config = {};
});
describe("roundValue", function () {
describe("this.config.roundTemp is true", function () {
beforeAll(function () {
Module.definitions.weatherforecast.config.roundTemp = true;
});
const values = [
// index 0 value
// index 1 expect
[1, "1"],
[1.0, "1"],
[1.02, "1"],
[10.12, "10"],
[2.0, "2"],
["2.12", "2"],
[10.1, "10"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.weatherforecast.roundValue(value[0])).toBe(value[1]);
});
});
});
describe("this.config.roundTemp is false", function () {
beforeAll(function () {
Module.definitions.weatherforecast.config.roundTemp = false;
});
const values = [
// index 0 value
// index 1 expect
[1, "1.0"],
[1.0, "1.0"],
[1.02, "1.0"],
[10.12, "10.1"],
[2.0, "2.0"],
["2.12", "2.1"],
[10.1, "10.1"],
[10.1, "10.1"]
];
values.forEach((value) => {
it(`for ${value[0]} should be return ${value[1]}`, function () {
expect(Module.definitions.weatherforecast.roundValue(value[0])).toBe(value[1]);
});
});
});
});
describe("forecastIcons", function () {
Log = {
error: function () {}
};
let originalLocale;
let originalTimeZone;
beforeAll(function () {
originalLocale = moment.locale();
originalTimeZone = moment.tz.guess();
moment.locale("hi");
moment.tz.setDefault("Europe/Warsaw");
});
describe("forecastIcons sunset specified", function () {
beforeAll(function () {
Module.definitions.weatherforecast.Log = {};
Module.definitions.weatherforecast.forecast = [];
Module.definitions.weatherforecast.show = Module.definitions.weatherforecast.updateDom = function () {};
Module.definitions.weatherforecast.config = Module.definitions.weatherforecast.defaults;
});
it(`returns correct icons with sunset time`, function () {
Module.definitions.weatherforecast.processWeather(data.withSunset, moment);
let forecastData = Module.definitions.weatherforecast.forecast;
expect(forecastData.length).toBe(4);
expect(forecastData[2].icon).toBe("wi-rain");
});
});
describe("forecastIcons sunset not specified", function () {
beforeAll(function () {
Module.definitions.weatherforecast.forecast = [];
});
it(`returns correct icons with out sunset time`, function () {
Module.definitions.weatherforecast.processWeather(data.withoutSunset, moment);
let forecastData = Module.definitions.weatherforecast.forecast;
expect(forecastData.length).toBe(4);
expect(forecastData[2].icon).toBe("wi-rain");
});
});
afterAll(function () {
moment.locale(originalLocale);
moment.tz.setDefault(originalTimeZone);
});
});
});