mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-06-27 03:39:55 +00:00
Use metric units internally in all weatherproviders (#2849)
So finally I think this refactorin is ready to be reviewed :-) DONE: - [x] Removed all conversion functions for wind and temperature from specific weatherproviders - [x] Use internally only metric units: celsius for temperature, meters per seconds for wind - [x] Convert temp and wind into the configured units when displaying data on the UI - [x] look how beaufort calculation uses metrics, added knots as new windunit - [x] add more e2e tests Checked providers: - [x] Darksky - [x] EnvCanada - [x] OpenWeatherMap - [x] SMHI provider - [x] UK Met Office - [x] UK Met Office DataHub - [x] WeatherBit - [x] WeatherFlow - [x] WeatherGov TODO in different tickets: - check weatherproviders for usage of weatherEndpoint (as seen in https://github.com/MichMich/MagicMirror-Documentation/issues/131) -> see #2926 - cleanup precipations -> #2953 Co-authored-by: veeck <michael@veeck.de>
This commit is contained in:
parent
64ed5a54cb
commit
2d3940a4ff
@ -28,6 +28,7 @@ Special thanks to: @rejas, @sdetweil
|
||||
- Updated da translation
|
||||
- Rework weather module
|
||||
- Use fetch instead of XMLHttpRequest in weatherprovider
|
||||
- Reworked how weatherproviders handle units
|
||||
- Use unix() method for parsing times, fix suntimes on the way
|
||||
|
||||
### Fixed
|
||||
|
@ -3,15 +3,7 @@
|
||||
<div class="normal medium">
|
||||
<span class="wi wi-strong-wind dimmed"></span>
|
||||
<span>
|
||||
{% if config.useBeaufort %}
|
||||
{{ current.beaufortWindSpeed() | round }}
|
||||
{% else %}
|
||||
{% if config.useKmh %}
|
||||
{{ current.kmhWindSpeed() | round }}
|
||||
{% else %}
|
||||
{{ current.windSpeed | round }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{{ current.windSpeed | unit("wind") | round }}
|
||||
{% if config.showWindDirection %}
|
||||
<sup>
|
||||
{% if config.showWindDirectionAsArrow %}
|
||||
|
@ -26,11 +26,6 @@ WeatherProvider.register("darksky", {
|
||||
lon: 0
|
||||
},
|
||||
|
||||
units: {
|
||||
imperial: "us",
|
||||
metric: "si"
|
||||
},
|
||||
|
||||
fetchCurrentWeather() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
@ -67,13 +62,12 @@ WeatherProvider.register("darksky", {
|
||||
|
||||
// Create a URL from the config and base URL.
|
||||
getUrl() {
|
||||
const units = this.units[this.config.units] || "auto";
|
||||
return `${this.config.apiBase}${this.config.weatherEndpoint}/${this.config.apiKey}/${this.config.lat},${this.config.lon}?units=${units}&lang=${this.config.lang}`;
|
||||
return `${this.config.apiBase}${this.config.weatherEndpoint}/${this.config.apiKey}/${this.config.lat},${this.config.lon}?units=si&lang=${this.config.lang}`;
|
||||
},
|
||||
|
||||
// Implement WeatherDay generator.
|
||||
generateWeatherDayFromCurrentWeather(currentWeatherData) {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const currentWeather = new WeatherObject();
|
||||
|
||||
currentWeather.date = moment();
|
||||
currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity);
|
||||
@ -91,7 +85,7 @@ WeatherProvider.register("darksky", {
|
||||
const days = [];
|
||||
|
||||
for (const forecast of forecasts) {
|
||||
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const weather = new WeatherObject();
|
||||
|
||||
weather.date = moment.unix(forecast.time);
|
||||
weather.minTemperature = forecast.temperatureMin;
|
||||
|
@ -11,13 +11,13 @@
|
||||
* https://dd.weather.gc.ca/citypage_weather/schema/
|
||||
* https://eccc-msc.github.io/open-data/msc-datamart/readme_en/
|
||||
*
|
||||
* This module supports Canadian locations only and requires 2 additional config parms:
|
||||
* This module supports Canadian locations only and requires 2 additional config parameters:
|
||||
*
|
||||
* siteCode - the city/town unique identifier for which weather is to be displayed. Format is 's0000000'.
|
||||
*
|
||||
* provCode - the 2-character province code for the selected city/town.
|
||||
*
|
||||
* Example: for Toronto, Ontario, the following parms would be used
|
||||
* Example: for Toronto, Ontario, the following parameters would be used
|
||||
*
|
||||
* siteCode: 's0000458',
|
||||
* provCode: 'ON'
|
||||
@ -64,10 +64,6 @@ WeatherProvider.register("envcanada", {
|
||||
start: function () {
|
||||
Log.info(`Weather provider: ${this.providerName} started.`);
|
||||
this.setFetchedLocation(this.config.location);
|
||||
|
||||
// Ensure kmH are ignored since these are custom-handled by this Provider
|
||||
|
||||
this.config.useKmh = false;
|
||||
},
|
||||
|
||||
//
|
||||
@ -150,7 +146,7 @@ WeatherProvider.register("envcanada", {
|
||||
//
|
||||
|
||||
generateWeatherObjectFromCurrentWeather(ECdoc) {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
const currentWeather = new WeatherObject();
|
||||
|
||||
// There are instances where EC will update weather data and current temperature will not be
|
||||
// provided. While this is a defect in the EC systems, we need to accommodate to avoid a current temp
|
||||
@ -161,13 +157,13 @@ WeatherProvider.register("envcanada", {
|
||||
// EC finds no current temp. In this scenario, MM will end up displaying a current temp of null;
|
||||
|
||||
if (ECdoc.querySelector("siteData currentConditions temperature").textContent) {
|
||||
currentWeather.temperature = this.convertTemp(ECdoc.querySelector("siteData currentConditions temperature").textContent);
|
||||
currentWeather.temperature = ECdoc.querySelector("siteData currentConditions temperature").textContent;
|
||||
this.cacheCurrentTemp = currentWeather.temperature;
|
||||
} else {
|
||||
currentWeather.temperature = this.cacheCurrentTemp;
|
||||
}
|
||||
|
||||
currentWeather.windSpeed = this.convertWind(ECdoc.querySelector("siteData currentConditions wind speed").textContent);
|
||||
currentWeather.windSpeed = currentWeather.convertWindToMs(ECdoc.querySelector("siteData currentConditions wind speed").textContent);
|
||||
|
||||
currentWeather.windDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent;
|
||||
|
||||
@ -190,11 +186,11 @@ WeatherProvider.register("envcanada", {
|
||||
currentWeather.feelsLikeTemp = currentWeather.temperature;
|
||||
|
||||
if (ECdoc.querySelector("siteData currentConditions windChill")) {
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(ECdoc.querySelector("siteData currentConditions windChill").textContent);
|
||||
currentWeather.feelsLikeTemp = ECdoc.querySelector("siteData currentConditions windChill").textContent;
|
||||
}
|
||||
|
||||
if (ECdoc.querySelector("siteData currentConditions humidex")) {
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(ECdoc.querySelector("siteData currentConditions humidex").textContent);
|
||||
currentWeather.feelsLikeTemp = ECdoc.querySelector("siteData currentConditions humidex").textContent;
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +221,7 @@ WeatherProvider.register("envcanada", {
|
||||
|
||||
const days = [];
|
||||
|
||||
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
const weather = new WeatherObject();
|
||||
|
||||
const foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime");
|
||||
const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent;
|
||||
@ -326,7 +322,7 @@ WeatherProvider.register("envcanada", {
|
||||
days.push(weather);
|
||||
|
||||
//
|
||||
// Now do the the rest of the forecast starting at nextDay. We will process each day using 2 EC
|
||||
// Now do the rest of the forecast starting at nextDay. We will process each day using 2 EC
|
||||
// forecast Elements. This will address the fact that the EC forecast always includes Today and
|
||||
// Tonight for each day. This is why we iterate through the forecast by a a count of 2, with each
|
||||
// iteration looking at the current Element and the next Element.
|
||||
@ -335,7 +331,7 @@ WeatherProvider.register("envcanada", {
|
||||
let lastDate = moment(baseDate, "YYYYMMDDhhmmss");
|
||||
|
||||
for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) {
|
||||
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
let weather = new WeatherObject();
|
||||
|
||||
// Add 1 to the date to reflect the current forecast day we are building
|
||||
|
||||
@ -389,7 +385,7 @@ WeatherProvider.register("envcanada", {
|
||||
const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast");
|
||||
|
||||
for (let stepHour = 0; stepHour < 24; stepHour += 1) {
|
||||
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
const weather = new WeatherObject();
|
||||
|
||||
// Determine local time by applying UTC offset to the forecast timestamp
|
||||
|
||||
@ -399,7 +395,7 @@ WeatherProvider.register("envcanada", {
|
||||
|
||||
// Capture the temperature
|
||||
|
||||
weather.temperature = this.convertTemp(hourGroup[stepHour].querySelector("temperature").textContent);
|
||||
weather.temperature = hourGroup[stepHour].querySelector("temperature").textContent;
|
||||
|
||||
// Capture Likelihood of Precipitation (LOP) and unit-of-measure values
|
||||
|
||||
@ -450,7 +446,7 @@ WeatherProvider.register("envcanada", {
|
||||
weather.minTemperature = this.todayTempCacheMin;
|
||||
weather.maxTemperature = this.todayTempCacheMax;
|
||||
} else {
|
||||
weather.minTemperature = this.convertTemp(currentTemp);
|
||||
weather.minTemperature = currentTemp;
|
||||
weather.maxTemperature = weather.minTemperature;
|
||||
}
|
||||
}
|
||||
@ -463,14 +459,14 @@ WeatherProvider.register("envcanada", {
|
||||
//
|
||||
|
||||
if (todayClass === "low") {
|
||||
weather.minTemperature = this.convertTemp(todayTemp);
|
||||
weather.minTemperature = todayTemp;
|
||||
if (today === 0 && fullDay === true) {
|
||||
this.todayTempCacheMin = weather.minTemperature;
|
||||
}
|
||||
}
|
||||
|
||||
if (todayClass === "high") {
|
||||
weather.maxTemperature = this.convertTemp(todayTemp);
|
||||
weather.maxTemperature = todayTemp;
|
||||
if (today === 0 && fullDay === true) {
|
||||
this.todayTempCacheMax = weather.maxTemperature;
|
||||
}
|
||||
@ -482,11 +478,11 @@ WeatherProvider.register("envcanada", {
|
||||
|
||||
if (fullDay === true) {
|
||||
if (nextClass === "low") {
|
||||
weather.minTemperature = this.convertTemp(nextTemp);
|
||||
weather.minTemperature = nextTemp;
|
||||
}
|
||||
|
||||
if (nextClass === "high") {
|
||||
weather.maxTemperature = this.convertTemp(nextTemp);
|
||||
weather.maxTemperature = nextTemp;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -536,31 +532,6 @@ WeatherProvider.register("envcanada", {
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Unit conversions
|
||||
//
|
||||
//
|
||||
// Convert C to F temps
|
||||
//
|
||||
convertTemp(temp) {
|
||||
if (this.config.tempUnits === "imperial") {
|
||||
return 1.8 * temp + 32;
|
||||
} else {
|
||||
return temp;
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Convert km/h to mph
|
||||
//
|
||||
convertWind(kilo) {
|
||||
if (this.config.windUnits === "imperial") {
|
||||
return kilo / 1.609344;
|
||||
} else {
|
||||
return kilo;
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Convert the icons to a more usable name.
|
||||
//
|
||||
|
@ -30,14 +30,14 @@ WeatherProvider.register("openweathermap", {
|
||||
fetchCurrentWeather() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
let currentWeather;
|
||||
if (this.config.weatherEndpoint === "/onecall") {
|
||||
const weatherData = this.generateWeatherObjectsFromOnecall(data);
|
||||
this.setCurrentWeather(weatherData.current);
|
||||
currentWeather = this.generateWeatherObjectsFromOnecall(data).current;
|
||||
this.setFetchedLocation(`${data.timezone}`);
|
||||
} else {
|
||||
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
||||
this.setCurrentWeather(currentWeather);
|
||||
currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
||||
}
|
||||
this.setCurrentWeather(currentWeather);
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
@ -49,15 +49,17 @@ WeatherProvider.register("openweathermap", {
|
||||
fetchWeatherForecast() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
let forecast;
|
||||
let location;
|
||||
if (this.config.weatherEndpoint === "/onecall") {
|
||||
const weatherData = this.generateWeatherObjectsFromOnecall(data);
|
||||
this.setWeatherForecast(weatherData.days);
|
||||
this.setFetchedLocation(`${data.timezone}`);
|
||||
forecast = this.generateWeatherObjectsFromOnecall(data).days;
|
||||
location = `${data.timezone}`;
|
||||
} else {
|
||||
const forecast = this.generateWeatherObjectsFromForecast(data.list);
|
||||
this.setWeatherForecast(forecast);
|
||||
this.setFetchedLocation(`${data.city.name}, ${data.city.country}`);
|
||||
forecast = this.generateWeatherObjectsFromForecast(data.list);
|
||||
location = `${data.city.name}, ${data.city.country}`;
|
||||
}
|
||||
this.setWeatherForecast(forecast);
|
||||
this.setFetchedLocation(location);
|
||||
})
|
||||
.catch(function (request) {
|
||||
Log.error("Could not load data ... ", request);
|
||||
@ -123,8 +125,9 @@ WeatherProvider.register("openweathermap", {
|
||||
* Generate a WeatherObject based on currentWeatherInformation
|
||||
*/
|
||||
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const currentWeather = new WeatherObject();
|
||||
|
||||
currentWeather.date = moment.unix(currentWeatherData.dt);
|
||||
currentWeather.humidity = currentWeatherData.main.humidity;
|
||||
currentWeather.temperature = currentWeatherData.main.temp;
|
||||
currentWeather.feelsLikeTemp = currentWeatherData.main.feels_like;
|
||||
@ -147,7 +150,7 @@ WeatherProvider.register("openweathermap", {
|
||||
return this.fetchForecastDaily(forecasts);
|
||||
}
|
||||
// if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
|
||||
return [new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh)];
|
||||
return [new WeatherObject()];
|
||||
},
|
||||
|
||||
/*
|
||||
@ -158,7 +161,7 @@ WeatherProvider.register("openweathermap", {
|
||||
return this.fetchOnecall(data);
|
||||
}
|
||||
// if weatherEndpoint does not match onecall, what should be returned?
|
||||
return { current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh), hours: [], days: [] };
|
||||
return { current: new WeatherObject(), hours: [], days: [] };
|
||||
},
|
||||
|
||||
/*
|
||||
@ -174,7 +177,7 @@ WeatherProvider.register("openweathermap", {
|
||||
let snow = 0;
|
||||
// variable for date
|
||||
let date = "";
|
||||
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
let weather = new WeatherObject();
|
||||
|
||||
for (const forecast of forecasts) {
|
||||
if (date !== moment.unix(forecast.dt).format("YYYY-MM-DD")) {
|
||||
@ -187,7 +190,7 @@ WeatherProvider.register("openweathermap", {
|
||||
// push weather information to days array
|
||||
days.push(weather);
|
||||
// create new weather-object
|
||||
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
weather = new WeatherObject();
|
||||
|
||||
minTemp = [];
|
||||
maxTemp = [];
|
||||
@ -250,7 +253,7 @@ WeatherProvider.register("openweathermap", {
|
||||
const days = [];
|
||||
|
||||
for (const forecast of forecasts) {
|
||||
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const weather = new WeatherObject();
|
||||
|
||||
weather.date = moment.unix(forecast.dt);
|
||||
weather.minTemperature = forecast.temp.min;
|
||||
@ -296,7 +299,7 @@ WeatherProvider.register("openweathermap", {
|
||||
let precip = false;
|
||||
|
||||
// get current weather, if requested
|
||||
const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const current = new WeatherObject();
|
||||
if (data.hasOwnProperty("current")) {
|
||||
current.date = moment.unix(data.current.dt).utcOffset(data.timezone_offset / 60);
|
||||
current.windSpeed = data.current.wind_speed;
|
||||
@ -328,7 +331,7 @@ WeatherProvider.register("openweathermap", {
|
||||
current.feelsLikeTemp = data.current.feels_like;
|
||||
}
|
||||
|
||||
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
let weather = new WeatherObject();
|
||||
|
||||
// get hourly weather, if requested
|
||||
const hours = [];
|
||||
@ -363,7 +366,7 @@ WeatherProvider.register("openweathermap", {
|
||||
}
|
||||
|
||||
hours.push(weather);
|
||||
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
weather = new WeatherObject();
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,7 +405,7 @@ WeatherProvider.register("openweathermap", {
|
||||
}
|
||||
|
||||
days.push(weather);
|
||||
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
weather = new WeatherObject();
|
||||
}
|
||||
}
|
||||
|
||||
@ -471,7 +474,7 @@ WeatherProvider.register("openweathermap", {
|
||||
return;
|
||||
}
|
||||
|
||||
params += "&units=" + this.config.units;
|
||||
params += "&units=metric"; // WeatherProviders should use metric internally and use the units only for when displaying data
|
||||
params += "&lang=" + this.config.lang;
|
||||
params += "&APPID=" + this.config.apiKey;
|
||||
|
||||
|
@ -75,7 +75,7 @@ WeatherProvider.register("smhi", {
|
||||
setConfig(config) {
|
||||
this.config = config;
|
||||
if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) === -1) {
|
||||
console.log("invalid or not set: " + config.precipitationValue);
|
||||
Log.log("invalid or not set: " + config.precipitationValue);
|
||||
config.precipitationValue = this.defaults.precipitationValue;
|
||||
}
|
||||
},
|
||||
@ -134,8 +134,7 @@ WeatherProvider.register("smhi", {
|
||||
* @returns {WeatherObject} The converted weatherdata at the specified location
|
||||
*/
|
||||
convertWeatherDataToObject(weatherData, coordinates) {
|
||||
// Weather data is only for Sweden and nobody in Sweden would use imperial
|
||||
let currentWeather = new WeatherObject("metric", "metric", "metric");
|
||||
let currentWeather = new WeatherObject();
|
||||
|
||||
currentWeather.date = moment(weatherData.validTime);
|
||||
currentWeather.updateSunTime(coordinates.lat, coordinates.lon);
|
||||
@ -191,7 +190,7 @@ WeatherProvider.register("smhi", {
|
||||
for (const weatherObject of allWeatherObjects) {
|
||||
//If its the first object or if a day/hour change we need to reset the summary object
|
||||
if (!currentWeather || !currentWeather.date.isSame(weatherObject.date, groupBy)) {
|
||||
currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
currentWeather = new WeatherObject();
|
||||
dayWeatherTypes = [];
|
||||
currentWeather.temperature = weatherObject.temperature;
|
||||
currentWeather.date = weatherObject.date;
|
||||
|
@ -21,11 +21,6 @@ WeatherProvider.register("ukmetoffice", {
|
||||
apiKey: ""
|
||||
},
|
||||
|
||||
units: {
|
||||
imperial: "us",
|
||||
metric: "si"
|
||||
},
|
||||
|
||||
// Overwrite the fetchCurrentWeather method.
|
||||
fetchCurrentWeather() {
|
||||
this.fetchData(this.getUrl("3hourly"))
|
||||
@ -80,7 +75,7 @@ WeatherProvider.register("ukmetoffice", {
|
||||
* Generate a WeatherObject based on currentWeatherInformation
|
||||
*/
|
||||
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const currentWeather = new WeatherObject();
|
||||
const location = currentWeatherData.SiteRep.DV.Location;
|
||||
|
||||
// data times are always UTC
|
||||
@ -103,11 +98,11 @@ WeatherProvider.register("ukmetoffice", {
|
||||
if (timeInMins >= p && timeInMins - 180 < p) {
|
||||
// finally got the one we want, so populate weather object
|
||||
currentWeather.humidity = rep.H;
|
||||
currentWeather.temperature = this.convertTemp(rep.T);
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(rep.F);
|
||||
currentWeather.temperature = rep.T;
|
||||
currentWeather.feelsLikeTemp = rep.F;
|
||||
currentWeather.precipitation = parseInt(rep.Pp);
|
||||
currentWeather.windSpeed = this.convertWindSpeed(rep.S);
|
||||
currentWeather.windDirection = this.convertWindDirection(rep.D);
|
||||
currentWeather.windSpeed = currentWeather.convertWindToMetric(rep.S);
|
||||
currentWeather.windDirection = currentWeather.valueWindDirection(rep.D);
|
||||
currentWeather.weatherType = this.convertWeatherType(rep.W);
|
||||
}
|
||||
}
|
||||
@ -130,7 +125,7 @@ WeatherProvider.register("ukmetoffice", {
|
||||
// loop round the (5) periods getting the data
|
||||
// for each period array, Day is [0], Night is [1]
|
||||
for (const period of forecasts.SiteRep.DV.Location.Period) {
|
||||
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const weather = new WeatherObject();
|
||||
|
||||
// data times are always UTC
|
||||
const dateStr = period.value;
|
||||
@ -140,8 +135,8 @@ WeatherProvider.register("ukmetoffice", {
|
||||
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
|
||||
// populate the weather object
|
||||
weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
|
||||
weather.minTemperature = this.convertTemp(period.Rep[1].Nm);
|
||||
weather.maxTemperature = this.convertTemp(period.Rep[0].Dm);
|
||||
weather.minTemperature = period.Rep[1].Nm;
|
||||
weather.maxTemperature = period.Rep[0].Dm;
|
||||
weather.weatherType = this.convertWeatherType(period.Rep[0].W);
|
||||
weather.precipitation = parseInt(period.Rep[0].PPd);
|
||||
|
||||
@ -192,46 +187,6 @@ WeatherProvider.register("ukmetoffice", {
|
||||
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
|
||||
},
|
||||
|
||||
/*
|
||||
* Convert temp (from degrees C) if required
|
||||
*/
|
||||
convertTemp(tempInC) {
|
||||
return this.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
|
||||
},
|
||||
|
||||
/*
|
||||
* Convert wind speed (from mph to m/s or km/h) if required
|
||||
*/
|
||||
convertWindSpeed(windInMph) {
|
||||
return this.windUnits === "metric" ? (this.useKmh ? windInMph * 1.60934 : windInMph / 2.23694) : windInMph;
|
||||
},
|
||||
|
||||
/*
|
||||
* Convert the wind direction cardinal to value
|
||||
*/
|
||||
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
|
||||
};
|
||||
|
||||
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generates an url with api parameters based on the config.
|
||||
*
|
||||
|
@ -20,11 +20,9 @@
|
||||
* weatherProvider: "ukmetofficedatahub",
|
||||
* apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
|
||||
* apiKey: "[YOUR API KEY]",
|
||||
* apiSecret: "[YOUR API SECRET]]",
|
||||
* apiSecret: "[YOUR API SECRET]",
|
||||
* lat: [LATITUDE (DECIMAL)],
|
||||
* lon: [LONGITUDE (DECIMAL)],
|
||||
* windUnits: "mps" | "kph" | "mph" (default)
|
||||
* tempUnits: "imperial" | "metric" (default)
|
||||
* lon: [LONGITUDE (DECIMAL)]
|
||||
*
|
||||
* At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when
|
||||
* setting your update intervals. For reference, 360 requests per day is once every 4 minutes.
|
||||
@ -51,8 +49,7 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
apiKey: "",
|
||||
apiSecret: "",
|
||||
lat: 0,
|
||||
lon: 0,
|
||||
windUnits: "mph"
|
||||
lon: 0
|
||||
},
|
||||
|
||||
// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
|
||||
@ -115,7 +112,7 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
|
||||
// Create a WeatherObject using current weather data (data for the current hour)
|
||||
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const currentWeather = new WeatherObject();
|
||||
|
||||
// Extract the actual forecasts
|
||||
let forecastDataHours = currentWeatherData.features[0].properties.timeSeries;
|
||||
@ -128,17 +125,17 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
let forecastTime = moment.utc(forecastDataHours[hour].time);
|
||||
if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
|
||||
currentWeather.date = forecastTime;
|
||||
currentWeather.windSpeed = this.convertWindSpeed(forecastDataHours[hour].windSpeed10m);
|
||||
currentWeather.windSpeed = forecastDataHours[hour].windSpeed10m;
|
||||
currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m;
|
||||
currentWeather.temperature = this.convertTemp(forecastDataHours[hour].screenTemperature);
|
||||
currentWeather.minTemperature = this.convertTemp(forecastDataHours[hour].minScreenAirTemp);
|
||||
currentWeather.maxTemperature = this.convertTemp(forecastDataHours[hour].maxScreenAirTemp);
|
||||
currentWeather.temperature = forecastDataHours[hour].screenTemperature;
|
||||
currentWeather.minTemperature = forecastDataHours[hour].minScreenAirTemp;
|
||||
currentWeather.maxTemperature = forecastDataHours[hour].maxScreenAirTemp;
|
||||
currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode);
|
||||
currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
|
||||
currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
|
||||
currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
|
||||
currentWeather.precipitation = forecastDataHours[hour].probOfPrecipitation;
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(forecastDataHours[hour].feelsLikeTemperature);
|
||||
currentWeather.feelsLikeTemp = forecastDataHours[hour].feelsLikeTemperature;
|
||||
|
||||
// Pass on full details, so they can be used in custom templates
|
||||
// Note the units of the supplied data when using this (see top of file)
|
||||
@ -194,7 +191,7 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
|
||||
// Go through each day in the forecasts
|
||||
for (let day in forecastDataDays) {
|
||||
const forecastWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const forecastWeather = new WeatherObject();
|
||||
|
||||
// Get date of forecast
|
||||
let forecastDate = moment.utc(forecastDataDays[day].time);
|
||||
@ -202,11 +199,11 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
// Check if forecast is for today or in the future (i.e., ignore yesterday's forecast)
|
||||
if (forecastDate.isSameOrAfter(today)) {
|
||||
forecastWeather.date = forecastDate;
|
||||
forecastWeather.minTemperature = this.convertTemp(forecastDataDays[day].nightMinScreenTemperature);
|
||||
forecastWeather.maxTemperature = this.convertTemp(forecastDataDays[day].dayMaxScreenTemperature);
|
||||
forecastWeather.minTemperature = forecastDataDays[day].nightMinScreenTemperature;
|
||||
forecastWeather.maxTemperature = forecastDataDays[day].dayMaxScreenTemperature;
|
||||
|
||||
// Using daytime forecast values
|
||||
forecastWeather.windSpeed = this.convertWindSpeed(forecastDataDays[day].midday10MWindSpeed);
|
||||
forecastWeather.windSpeed = forecastDataDays[day].midday10MWindSpeed;
|
||||
forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection;
|
||||
forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
|
||||
forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation;
|
||||
@ -214,7 +211,7 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
|
||||
forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;
|
||||
forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow;
|
||||
forecastWeather.feelsLikeTemp = this.convertTemp(forecastDataDays[day].dayMaxFeelsLikeTemp);
|
||||
forecastWeather.feelsLikeTemp = forecastDataDays[day].dayMaxFeelsLikeTemp;
|
||||
|
||||
// Pass on full details, so they can be used in custom templates
|
||||
// Note the units of the supplied data when using this (see top of file)
|
||||
@ -232,27 +229,6 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
this.fetchedLocationName = name;
|
||||
},
|
||||
|
||||
// Convert temperatures to Fahrenheit (from degrees C), if required
|
||||
convertTemp(tempInC) {
|
||||
return this.config.tempUnits === "imperial" ? (tempInC * 9) / 5 + 32 : tempInC;
|
||||
},
|
||||
|
||||
// Convert wind speed from metres per second
|
||||
// To keep the supplied metres per second units, use "mps"
|
||||
// To use kilometres per hour, use "kph"
|
||||
// Else assumed imperial and the value is returned in miles per hour (a Met Office user is likely to be UK-based)
|
||||
convertWindSpeed(windInMpS) {
|
||||
if (this.config.windUnits === "mps") {
|
||||
return windInMpS;
|
||||
}
|
||||
|
||||
if (this.config.windUnits === "kph" || this.config.windUnits === "metric" || this.config.useKmh) {
|
||||
return windInMpS * 3.6;
|
||||
}
|
||||
|
||||
return windInMpS * 2.23694;
|
||||
},
|
||||
|
||||
// Match the Met Office "significant weather code" to a weathericons.css icon
|
||||
// Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264
|
||||
// and: https://erikflowers.github.io/weather-icons/
|
||||
|
@ -23,11 +23,6 @@ WeatherProvider.register("weatherbit", {
|
||||
lon: 0
|
||||
},
|
||||
|
||||
units: {
|
||||
imperial: "I",
|
||||
metric: "M"
|
||||
},
|
||||
|
||||
fetchedLocation: function () {
|
||||
return this.fetchedLocationName || "";
|
||||
},
|
||||
@ -95,8 +90,7 @@ WeatherProvider.register("weatherbit", {
|
||||
|
||||
// Create a URL from the config and base URL.
|
||||
getUrl() {
|
||||
const units = this.units[this.config.units] || "auto";
|
||||
return `${this.config.apiBase}${this.config.weatherEndpoint}?lat=${this.config.lat}&lon=${this.config.lon}&units=${units}&key=${this.config.apiKey}`;
|
||||
return `${this.config.apiBase}${this.config.weatherEndpoint}?lat=${this.config.lat}&lon=${this.config.lon}&units=M&key=${this.config.apiKey}`;
|
||||
},
|
||||
|
||||
// Implement WeatherDay generator.
|
||||
@ -106,7 +100,7 @@ WeatherProvider.register("weatherbit", {
|
||||
let tzOffset = d.getTimezoneOffset();
|
||||
tzOffset = tzOffset * -1;
|
||||
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
const currentWeather = new WeatherObject();
|
||||
|
||||
currentWeather.date = moment.unix(currentWeatherData.data[0].ts);
|
||||
currentWeather.humidity = parseFloat(currentWeatherData.data[0].rh);
|
||||
@ -126,7 +120,7 @@ WeatherProvider.register("weatherbit", {
|
||||
const days = [];
|
||||
|
||||
for (const forecast of forecasts) {
|
||||
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
|
||||
const weather = new WeatherObject();
|
||||
|
||||
weather.date = moment(forecast.datetime, "YYYY-MM-DD");
|
||||
weather.minTemperature = forecast.min_temp;
|
||||
|
@ -23,32 +23,15 @@ WeatherProvider.register("weatherflow", {
|
||||
stationid: ""
|
||||
},
|
||||
|
||||
units: {
|
||||
imperial: {
|
||||
temp: "f",
|
||||
wind: "mph",
|
||||
pressure: "hpa",
|
||||
precip: "in",
|
||||
distance: "mi"
|
||||
},
|
||||
metric: {
|
||||
temp: "c",
|
||||
wind: "kph",
|
||||
pressure: "mb",
|
||||
precip: "mm",
|
||||
distance: "km"
|
||||
}
|
||||
},
|
||||
|
||||
fetchCurrentWeather() {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const currentWeather = new WeatherObject();
|
||||
currentWeather.date = moment();
|
||||
|
||||
currentWeather.humidity = data.current_conditions.relative_humidity;
|
||||
currentWeather.temperature = data.current_conditions.air_temperature;
|
||||
currentWeather.windSpeed = data.current_conditions.wind_avg;
|
||||
currentWeather.windSpeed = currentWeather.convertWindToMs(data.current_conditions.wind_avg);
|
||||
currentWeather.windDirection = data.current_conditions.wind_direction;
|
||||
currentWeather.weatherType = data.forecast.daily[0].icon;
|
||||
currentWeather.sunrise = moment.unix(data.forecast.daily[0].sunrise);
|
||||
@ -67,7 +50,7 @@ WeatherProvider.register("weatherflow", {
|
||||
const days = [];
|
||||
|
||||
for (const forecast of data.forecast.daily) {
|
||||
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const weather = new WeatherObject();
|
||||
|
||||
weather.date = moment.unix(forecast.day_start_local);
|
||||
weather.minTemperature = forecast.air_temp_low;
|
||||
@ -88,22 +71,6 @@ WeatherProvider.register("weatherflow", {
|
||||
|
||||
// Create a URL from the config and base URL.
|
||||
getUrl() {
|
||||
return (
|
||||
this.config.apiBase +
|
||||
"better_forecast?station_id=" +
|
||||
this.config.stationid +
|
||||
"&units_temp=" +
|
||||
this.units[this.config.units].temp +
|
||||
"&units_wind=" +
|
||||
this.units[this.config.units].wind +
|
||||
"&units_pressure=" +
|
||||
this.units[this.config.units].pressure +
|
||||
"&units_precip=" +
|
||||
this.units[this.config.units].precip +
|
||||
"&units_distance=" +
|
||||
this.units[this.config.units].distance +
|
||||
"&token=" +
|
||||
this.config.token
|
||||
);
|
||||
return `${this.config.apiBase}better_forecast?station_id=${this.config.stationid}&units_temp=c&units_wind=kph&units_pressure=mb&units_precip=mm&units_distance=km&token=${this.config.token}`;
|
||||
}
|
||||
});
|
||||
|
@ -131,8 +131,8 @@ WeatherProvider.register("weathergov", {
|
||||
}
|
||||
this.fetchedLocationName = data.properties.relativeLocation.properties.city + ", " + data.properties.relativeLocation.properties.state;
|
||||
Log.log("Forecast location is " + this.fetchedLocationName);
|
||||
this.forecastURL = data.properties.forecast;
|
||||
this.forecastHourlyURL = data.properties.forecastHourly;
|
||||
this.forecastURL = data.properties.forecast + "?units=si";
|
||||
this.forecastHourlyURL = data.properties.forecastHourly + "?units=si";
|
||||
this.forecastGridDataURL = data.properties.forecastGridData;
|
||||
this.observationStationsURL = data.properties.observationStations;
|
||||
// with this URL, we chain another promise for the station obs URL
|
||||
@ -171,7 +171,7 @@ WeatherProvider.register("weathergov", {
|
||||
const days = [];
|
||||
|
||||
// variable for date
|
||||
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
let weather = new WeatherObject();
|
||||
for (const forecast of forecasts) {
|
||||
weather.date = moment(forecast.startTime.slice(0, 19));
|
||||
if (forecast.windSpeed.search(" ") < 0) {
|
||||
@ -187,7 +187,7 @@ WeatherProvider.register("weathergov", {
|
||||
|
||||
days.push(weather);
|
||||
|
||||
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
weather = new WeatherObject();
|
||||
}
|
||||
|
||||
// push weather information to days array
|
||||
@ -201,24 +201,24 @@ WeatherProvider.register("weathergov", {
|
||||
* ... object needs data in units based on config!
|
||||
*/
|
||||
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
|
||||
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
const currentWeather = new WeatherObject();
|
||||
|
||||
currentWeather.date = moment(currentWeatherData.timestamp);
|
||||
currentWeather.temperature = this.convertTemp(currentWeatherData.temperature.value);
|
||||
currentWeather.windSpeed = this.convertSpeed(currentWeatherData.windSpeed.value);
|
||||
currentWeather.temperature = currentWeatherData.temperature.value;
|
||||
currentWeather.windSpeed = currentWeather.convertWindToMs(currentWeatherData.windSpeed.value);
|
||||
currentWeather.windDirection = currentWeatherData.windDirection.value;
|
||||
currentWeather.minTemperature = this.convertTemp(currentWeatherData.minTemperatureLast24Hours.value);
|
||||
currentWeather.maxTemperature = this.convertTemp(currentWeatherData.maxTemperatureLast24Hours.value);
|
||||
currentWeather.minTemperature = currentWeatherData.minTemperatureLast24Hours.value;
|
||||
currentWeather.maxTemperature = currentWeatherData.maxTemperatureLast24Hours.value;
|
||||
currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value);
|
||||
currentWeather.rain = null;
|
||||
currentWeather.snow = null;
|
||||
currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value);
|
||||
if (currentWeatherData.heatIndex.value !== null) {
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value);
|
||||
currentWeather.feelsLikeTemp = currentWeatherData.heatIndex.value;
|
||||
} else if (currentWeatherData.windChill.value !== null) {
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.windChill.value);
|
||||
currentWeather.feelsLikeTemp = currentWeatherData.windChill.value;
|
||||
} else {
|
||||
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.temperature.value);
|
||||
currentWeather.feelsLikeTemp = currentWeatherData.temperature.value;
|
||||
}
|
||||
// determine the sunrise/sunset times - not supplied in weather.gov data
|
||||
currentWeather.updateSunTime(this.config.lat, this.config.lon);
|
||||
@ -247,7 +247,7 @@ WeatherProvider.register("weathergov", {
|
||||
let maxTemp = [];
|
||||
// variable for date
|
||||
let date = "";
|
||||
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
let weather = new WeatherObject();
|
||||
weather.precipitation = 0;
|
||||
|
||||
for (const forecast of forecasts) {
|
||||
@ -259,7 +259,7 @@ WeatherProvider.register("weathergov", {
|
||||
// push weather information to days array
|
||||
days.push(weather);
|
||||
// create new weather-object
|
||||
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
|
||||
weather = new WeatherObject();
|
||||
|
||||
minTemp = [];
|
||||
maxTemp = [];
|
||||
@ -298,26 +298,6 @@ WeatherProvider.register("weathergov", {
|
||||
/*
|
||||
* Unit conversions
|
||||
*/
|
||||
// conversion to fahrenheit
|
||||
convertTemp(temp) {
|
||||
if (this.config.tempUnits === "imperial") {
|
||||
return (9 / 5) * temp + 32;
|
||||
} else {
|
||||
return temp;
|
||||
}
|
||||
},
|
||||
// conversion to mph or kmh
|
||||
convertSpeed(metSec) {
|
||||
if (this.config.windUnits === "imperial") {
|
||||
return metSec * 2.23694;
|
||||
} else {
|
||||
if (this.config.useKmh) {
|
||||
return metSec * 3.6;
|
||||
} else {
|
||||
return metSec;
|
||||
}
|
||||
}
|
||||
},
|
||||
// conversion to inches
|
||||
convertLength(meters) {
|
||||
if (this.config.units === "imperial") {
|
||||
@ -395,31 +375,5 @@ WeatherProvider.register("weathergov", {
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/*
|
||||
Convert the direction into Degrees
|
||||
*/
|
||||
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
|
||||
};
|
||||
|
||||
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
|
||||
}
|
||||
});
|
||||
|
@ -13,7 +13,6 @@ Module.register("weather", {
|
||||
roundTemp: false,
|
||||
type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint)
|
||||
units: config.units,
|
||||
useKmh: false,
|
||||
tempUnits: config.units,
|
||||
windUnits: config.units,
|
||||
updateInterval: 10 * 60 * 1000, // every 10 minutes
|
||||
@ -23,7 +22,6 @@ Module.register("weather", {
|
||||
showPeriodUpper: false,
|
||||
showWindDirection: true,
|
||||
showWindDirectionAsArrow: false,
|
||||
useBeaufort: true,
|
||||
lang: config.language,
|
||||
showHumidity: false,
|
||||
showSun: true,
|
||||
@ -77,6 +75,14 @@ Module.register("weather", {
|
||||
start: function () {
|
||||
moment.locale(this.config.lang);
|
||||
|
||||
if (this.config.useKmh) {
|
||||
Log.warn("Your are using the deprecated config values 'useKmh'. Please switch to windUnits!");
|
||||
this.windUnits = "kmh";
|
||||
} else if (this.config.useBeaufort) {
|
||||
Log.warn("Your are using the deprecated config values 'useBeaufort'. Please switch to windUnits!");
|
||||
this.windUnits = "beaufort";
|
||||
}
|
||||
|
||||
// Initialize the weather provider.
|
||||
this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
|
||||
|
||||
@ -195,6 +201,59 @@ Module.register("weather", {
|
||||
return roundValue === "-0" ? 0 : roundValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert temp (from degrees C) into imperial or metric unit depending on
|
||||
* your config
|
||||
*
|
||||
* @param {number} tempInC the temperature you want to convert in celsius
|
||||
* @returns {number} the temperature converted to what is defined in the config
|
||||
*/
|
||||
convertTemp(tempInC) {
|
||||
return this.config.tempUnits === "imperial" ? this.roundValue(tempInC * 1.8 + 32) : tempInC;
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* Convert wind speed (from meters per second) into whatever is defined in
|
||||
* your config. Can be 'beaufort', 'kmh', 'knots, 'imperial' (mph) or
|
||||
* 'metric' (mps)
|
||||
*
|
||||
* @param {number} windInMS the windspeed you want to convert
|
||||
* @returns {number} the windspeed converted to what is defined in the config
|
||||
*/
|
||||
convertWind(windInMS) {
|
||||
switch (this.config.windUnits) {
|
||||
case "beaufort":
|
||||
return this.beaufortWindSpeed(windInMS);
|
||||
case "kmh":
|
||||
return (windInMS * 3600) / 1000;
|
||||
case "knots":
|
||||
return windInMS * 1.943844;
|
||||
case "imperial":
|
||||
return windInMS * 2.2369362920544;
|
||||
case "metric":
|
||||
default:
|
||||
return windInMS;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert wind (from m/s) to beaufort scale
|
||||
*
|
||||
* @param {number} speedInMS the windspeed you want to convert
|
||||
* @returns {number} the speed in beaufort
|
||||
*/
|
||||
beaufortWindSpeed(speedInMS) {
|
||||
const windInKmh = (speedInMS * 3600) / 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) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return 12;
|
||||
},
|
||||
|
||||
addFilters() {
|
||||
this.nunjucksEnvironment().addFilter(
|
||||
"formatTime",
|
||||
@ -221,9 +280,7 @@ Module.register("weather", {
|
||||
"unit",
|
||||
function (value, type) {
|
||||
if (type === "temperature") {
|
||||
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") {
|
||||
value += "°";
|
||||
}
|
||||
value = this.convertTemp(value) + "°";
|
||||
if (this.config.degreeLabel) {
|
||||
if (this.config.tempUnits === "metric") {
|
||||
value += "C";
|
||||
@ -245,8 +302,9 @@ Module.register("weather", {
|
||||
}
|
||||
} else if (type === "humidity") {
|
||||
value += "%";
|
||||
} else if (type === "wind") {
|
||||
value = this.convertWind(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}.bind(this)
|
||||
);
|
||||
|
@ -14,17 +14,8 @@
|
||||
class WeatherObject {
|
||||
/**
|
||||
* Constructor for a WeatherObject
|
||||
*
|
||||
* @param {string} units what units to use, "imperial" or "metric"
|
||||
* @param {string} tempUnits what tempunits to use
|
||||
* @param {string} windUnits what windunits to use
|
||||
* @param {boolean} useKmh use kmh if true, mps if false
|
||||
*/
|
||||
constructor(units, tempUnits, windUnits, useKmh) {
|
||||
this.units = units;
|
||||
this.tempUnits = tempUnits;
|
||||
this.windUnits = windUnits;
|
||||
this.useKmh = useKmh;
|
||||
constructor() {
|
||||
this.date = null;
|
||||
this.windSpeed = null;
|
||||
this.windDirection = null;
|
||||
@ -78,19 +69,38 @@ class WeatherObject {
|
||||
}
|
||||
}
|
||||
|
||||
beaufortWindSpeed() {
|
||||
const windInKmh = this.windUnits === "imperial" ? this.windSpeed * 1.609344 : this.useKmh ? this.windSpeed : (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) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return 12;
|
||||
/*
|
||||
* Convert the wind direction cardinal to value
|
||||
*/
|
||||
valueWindDirection(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
|
||||
};
|
||||
|
||||
return windCardinals.hasOwnProperty(windDirection) ? windCardinals[windDirection] : null;
|
||||
}
|
||||
|
||||
kmhWindSpeed() {
|
||||
return this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000;
|
||||
convertWindToMetric(mph) {
|
||||
return mph / 2.2369362920544;
|
||||
}
|
||||
|
||||
convertWindToMs(kmh) {
|
||||
return kmh * 0.27777777777778;
|
||||
}
|
||||
|
||||
nextSunAction() {
|
||||
@ -101,8 +111,8 @@ class WeatherObject {
|
||||
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.windSpeed * 2.2369362920544;
|
||||
const tempInF = (this.temperature * 9) / 5 + 32;
|
||||
let feelsLike = tempInF;
|
||||
|
||||
if (windInMph > 3 && tempInF < 50) {
|
||||
@ -120,7 +130,7 @@ class WeatherObject {
|
||||
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
|
||||
}
|
||||
|
||||
return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9;
|
||||
return ((feelsLike - 32) * 5) / 9;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,7 +11,7 @@ let config = {
|
||||
config: {
|
||||
location: "Munich",
|
||||
mockData: '"#####WEATHERDATA#####"',
|
||||
useBeaufort: false,
|
||||
windUnits: "beaufort",
|
||||
showWindDirectionAsArrow: true,
|
||||
showSun: false,
|
||||
showHumidity: true,
|
||||
|
@ -1,4 +1,3 @@
|
||||
const moment = require("moment");
|
||||
const helpers = require("../helpers/global-setup");
|
||||
const weatherFunc = require("../helpers/weather-functions");
|
||||
|
||||
@ -14,39 +13,15 @@ describe("Weather module", () => {
|
||||
});
|
||||
|
||||
it("should render wind speed and wind direction", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "6 WSW"); // now "12"
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "12 WSW");
|
||||
});
|
||||
|
||||
it("should render temperature with icon", async () => {
|
||||
await weatherFunc.getText(".weather .large.light span.bright", "1.5°"); // now "1°C"
|
||||
await weatherFunc.getText(".weather .large.light span.bright", "1.5°");
|
||||
});
|
||||
|
||||
it("should render feels like temperature", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°"); // now "Feels like -6°C"
|
||||
});
|
||||
});
|
||||
|
||||
describe("Default configuration with sunrise", () => {
|
||||
beforeAll(async () => {
|
||||
const sunrise = moment().startOf("day").unix();
|
||||
const sunset = moment().startOf("day").unix();
|
||||
await weatherFunc.startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } });
|
||||
});
|
||||
|
||||
it("should render sunrise", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(4)", "12:00 am");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Default configuration with sunset", () => {
|
||||
beforeAll(async () => {
|
||||
const sunrise = moment().startOf("day").unix();
|
||||
const sunset = moment().endOf("day").unix();
|
||||
await weatherFunc.startApp("tests/configs/modules/weather/currentweather_default.js", { sys: { sunrise, sunset } });
|
||||
});
|
||||
|
||||
it("should render sunset", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(4)", "11:59 pm");
|
||||
await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -66,65 +41,44 @@ describe("Weather module", () => {
|
||||
await weatherFunc.startApp("tests/configs/modules/weather/currentweather_options.js", {});
|
||||
});
|
||||
|
||||
it("should render useBeaufort = false", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "12");
|
||||
it("should render windUnits in beaufort", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "6");
|
||||
});
|
||||
|
||||
it("should render showWindDirectionAsArrow = true", async () => {
|
||||
it("should render windDirection with an arrow", async () => {
|
||||
const elem = await helpers.waitForElement(".weather .normal.medium sup i.fa-long-arrow-alt-up");
|
||||
expect(elem).not.toBe(null);
|
||||
expect(elem.outerHTML).toContain("transform:rotate(250deg);");
|
||||
});
|
||||
|
||||
it("should render showHumidity = true", async () => {
|
||||
it("should render humidity", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(3)", "93.7");
|
||||
});
|
||||
|
||||
it("should render degreeLabel = true for temp", async () => {
|
||||
it("should render degreeLabel for temp", async () => {
|
||||
await weatherFunc.getText(".weather .large.light span.bright", "1°C");
|
||||
});
|
||||
|
||||
it("should render degreeLabel = true for feels like", async () => {
|
||||
it("should render degreeLabel for feels like", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -6°C");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Current weather units", () => {
|
||||
describe("Current weather with imperial units", () => {
|
||||
beforeAll(async () => {
|
||||
await weatherFunc.startApp("tests/configs/modules/weather/currentweather_units.js", {
|
||||
main: {
|
||||
temp: (1.49 * 9) / 5 + 32,
|
||||
temp_min: (1 * 9) / 5 + 32,
|
||||
temp_max: (2 * 9) / 5 + 32
|
||||
},
|
||||
wind: {
|
||||
speed: 11.8 * 2.23694
|
||||
}
|
||||
});
|
||||
await weatherFunc.startApp("tests/configs/modules/weather/currentweather_units.js", {});
|
||||
});
|
||||
|
||||
it("should render imperial units for wind", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "6 WSW");
|
||||
it("should render wind in imperial units", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "26 WSW");
|
||||
});
|
||||
|
||||
it("should render imperial units for temp", async () => {
|
||||
it("should render temperatures in fahrenheit", async () => {
|
||||
await weatherFunc.getText(".weather .large.light span.bright", "34,7°");
|
||||
});
|
||||
|
||||
it("should render imperial units for feels like", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
|
||||
});
|
||||
|
||||
it("should render custom decimalSymbol = ',' for humidity", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium span:nth-child(3)", "93,7");
|
||||
});
|
||||
|
||||
it("should render custom decimalSymbol = ',' for temp", async () => {
|
||||
await weatherFunc.getText(".weather .large.light span.bright", "34,7°");
|
||||
});
|
||||
|
||||
it("should render custom decimalSymbol = ',' for feels like", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°");
|
||||
it("should render 'feels like' in fahrenheit", async () => {
|
||||
await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 21,9°");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -86,7 +86,7 @@ describe("Weather module: Weather Forecast", () => {
|
||||
await weatherFunc.startApp("tests/configs/modules/weather/forecastweather_units.js", {});
|
||||
});
|
||||
|
||||
const temperatures = ["24_4°", "21_0°", "22_9°", "23_4°", "20_6°"];
|
||||
const temperatures = ["75_9°", "69_8°", "73_2°", "74_1°", "69_1°"];
|
||||
for (const [index, temp] of temperatures.entries()) {
|
||||
it("should render custom decimalSymbol = '_' for temp " + temp, async () => {
|
||||
await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);
|
||||
|
29
tests/electron/helpers/weather-setup.js
Normal file
29
tests/electron/helpers/weather-setup.js
Normal file
@ -0,0 +1,29 @@
|
||||
const helpers = require("./global-setup");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const { generateWeather, generateWeatherForecast } = require("../../mocks/weather_test");
|
||||
|
||||
exports.getText = async (element, result) => {
|
||||
const elem = await helpers.getElement(element);
|
||||
await expect(elem).not.toBe(null);
|
||||
const text = await elem.textContent();
|
||||
await expect(
|
||||
text
|
||||
.trim()
|
||||
.replace(/(\r\n|\n|\r)/gm, "")
|
||||
.replace(/[ ]+/g, " ")
|
||||
).toBe(result);
|
||||
};
|
||||
|
||||
exports.startApp = async (configFile, systemDate) => {
|
||||
let mockWeather;
|
||||
if (configFile.includes("forecast")) {
|
||||
mockWeather = generateWeatherForecast();
|
||||
} else {
|
||||
mockWeather = generateWeather();
|
||||
}
|
||||
let content = fs.readFileSync(path.resolve(__dirname + "../../../../" + configFile)).toString();
|
||||
content = content.replace("#####WEATHERDATA#####", mockWeather);
|
||||
fs.writeFileSync(path.resolve(__dirname + "../../../../config/config.js"), content);
|
||||
await helpers.startApplication("", systemDate);
|
||||
};
|
28
tests/electron/modules/weather_spec.js
Normal file
28
tests/electron/modules/weather_spec.js
Normal file
@ -0,0 +1,28 @@
|
||||
const helpers = require("../helpers/global-setup");
|
||||
const weatherHelper = require("../helpers/weather-setup");
|
||||
|
||||
describe("Weather module", () => {
|
||||
afterEach(async () => {
|
||||
await helpers.stopApplication();
|
||||
});
|
||||
|
||||
describe("Current weather with sunrise", () => {
|
||||
beforeAll(async () => {
|
||||
await weatherHelper.startApp("tests/configs/modules/weather/currentweather_default.js", "13 Jan 2019 00:30:00 GMT");
|
||||
});
|
||||
|
||||
it("should render sunrise", async () => {
|
||||
await weatherHelper.getText(".weather .normal.medium span:nth-child(4)", "7:00 am");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Current weather with sunset", () => {
|
||||
beforeAll(async () => {
|
||||
await weatherHelper.startApp("tests/configs/modules/weather/currentweather_default.js", "13 Jan 2019 12:30:00 GMT");
|
||||
});
|
||||
|
||||
it("should render sunset", async () => {
|
||||
await weatherHelper.getText(".weather .normal.medium span:nth-child(4)", "3:45 pm");
|
||||
});
|
||||
});
|
||||
});
|
@ -10,7 +10,7 @@ describe("WeatherObject", () => {
|
||||
beforeAll(() => {
|
||||
originalTimeZone = moment.tz.guess();
|
||||
moment.tz.setDefault("Africa/Dar_es_Salaam");
|
||||
weatherobject = new WeatherObject("metric", "metric", "metric", true);
|
||||
weatherobject = new WeatherObject();
|
||||
});
|
||||
|
||||
it("should return true for daytime at noon", () => {
|
||||
@ -25,6 +25,14 @@ describe("WeatherObject", () => {
|
||||
expect(weatherobject.isDayTime()).toBe(false);
|
||||
});
|
||||
|
||||
it("should convert windspeed correctly from mph to mps", () => {
|
||||
expect(Math.round(weatherobject.convertWindToMetric(93.951324266285))).toBe(42);
|
||||
});
|
||||
|
||||
it("should convert wind direction correctly from cardinal to value", () => {
|
||||
expect(weatherobject.valueWindDirection("SSE")).toBe(157);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
moment.tz.setDefault(originalTimeZone);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user