mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-21 21:00:57 +00:00
remove old weather modules
This commit is contained in:
@@ -1,518 +0,0 @@
|
||||
/* eslint-disable */
|
||||
|
||||
/* Magic Mirror
|
||||
* Module: WeatherForecast
|
||||
*
|
||||
* By Michael Teeuw https://michaelteeuw.nl
|
||||
* MIT Licensed.
|
||||
*
|
||||
* 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 = "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);
|
||||
}
|
||||
});
|
Reference in New Issue
Block a user