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:
Veeck 2022-10-24 19:41:34 +02:00 committed by GitHub
parent 64ed5a54cb
commit 2d3940a4ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 274 additions and 381 deletions

View File

@ -28,6 +28,7 @@ Special thanks to: @rejas, @sdetweil
- Updated da translation - Updated da translation
- Rework weather module - Rework weather module
- Use fetch instead of XMLHttpRequest in weatherprovider - Use fetch instead of XMLHttpRequest in weatherprovider
- Reworked how weatherproviders handle units
- Use unix() method for parsing times, fix suntimes on the way - Use unix() method for parsing times, fix suntimes on the way
### Fixed ### Fixed

View File

@ -3,15 +3,7 @@
<div class="normal medium"> <div class="normal medium">
<span class="wi wi-strong-wind dimmed"></span> <span class="wi wi-strong-wind dimmed"></span>
<span> <span>
{% if config.useBeaufort %} {{ current.windSpeed | unit("wind") | round }}
{{ current.beaufortWindSpeed() | round }}
{% else %}
{% if config.useKmh %}
{{ current.kmhWindSpeed() | round }}
{% else %}
{{ current.windSpeed | round }}
{% endif %}
{% endif %}
{% if config.showWindDirection %} {% if config.showWindDirection %}
<sup> <sup>
{% if config.showWindDirectionAsArrow %} {% if config.showWindDirectionAsArrow %}

View File

@ -26,11 +26,6 @@ WeatherProvider.register("darksky", {
lon: 0 lon: 0
}, },
units: {
imperial: "us",
metric: "si"
},
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then((data) => { .then((data) => {
@ -67,13 +62,12 @@ WeatherProvider.register("darksky", {
// Create a URL from the config and base URL. // Create a URL from the config and base URL.
getUrl() { 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=si&lang=${this.config.lang}`;
return `${this.config.apiBase}${this.config.weatherEndpoint}/${this.config.apiKey}/${this.config.lat},${this.config.lon}?units=${units}&lang=${this.config.lang}`;
}, },
// Implement WeatherDay generator. // Implement WeatherDay generator.
generateWeatherDayFromCurrentWeather(currentWeatherData) { 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.date = moment();
currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity); currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity);
@ -91,7 +85,7 @@ WeatherProvider.register("darksky", {
const days = []; const days = [];
for (const forecast of forecasts) { 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.date = moment.unix(forecast.time);
weather.minTemperature = forecast.temperatureMin; weather.minTemperature = forecast.temperatureMin;

View File

@ -11,13 +11,13 @@
* https://dd.weather.gc.ca/citypage_weather/schema/ * https://dd.weather.gc.ca/citypage_weather/schema/
* https://eccc-msc.github.io/open-data/msc-datamart/readme_en/ * 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'. * 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. * 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', * siteCode: 's0000458',
* provCode: 'ON' * provCode: 'ON'
@ -64,10 +64,6 @@ WeatherProvider.register("envcanada", {
start: function () { start: function () {
Log.info(`Weather provider: ${this.providerName} started.`); Log.info(`Weather provider: ${this.providerName} started.`);
this.setFetchedLocation(this.config.location); 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) { 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 // 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 // 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; // 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) { 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; this.cacheCurrentTemp = currentWeather.temperature;
} else { } else {
currentWeather.temperature = this.cacheCurrentTemp; 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; currentWeather.windDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent;
@ -190,11 +186,11 @@ WeatherProvider.register("envcanada", {
currentWeather.feelsLikeTemp = currentWeather.temperature; currentWeather.feelsLikeTemp = currentWeather.temperature;
if (ECdoc.querySelector("siteData currentConditions windChill")) { 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")) { 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 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 foreBaseDates = ECdoc.querySelectorAll("siteData forecastGroup dateTime");
const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent; const baseDate = foreBaseDates[1].querySelector("timeStamp").textContent;
@ -326,7 +322,7 @@ WeatherProvider.register("envcanada", {
days.push(weather); 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 // 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 // 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. // iteration looking at the current Element and the next Element.
@ -335,7 +331,7 @@ WeatherProvider.register("envcanada", {
let lastDate = moment(baseDate, "YYYYMMDDhhmmss"); let lastDate = moment(baseDate, "YYYYMMDDhhmmss");
for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) { 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 // 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"); const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast");
for (let stepHour = 0; stepHour < 24; stepHour += 1) { 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 // Determine local time by applying UTC offset to the forecast timestamp
@ -399,7 +395,7 @@ WeatherProvider.register("envcanada", {
// Capture the temperature // 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 // Capture Likelihood of Precipitation (LOP) and unit-of-measure values
@ -450,7 +446,7 @@ WeatherProvider.register("envcanada", {
weather.minTemperature = this.todayTempCacheMin; weather.minTemperature = this.todayTempCacheMin;
weather.maxTemperature = this.todayTempCacheMax; weather.maxTemperature = this.todayTempCacheMax;
} else { } else {
weather.minTemperature = this.convertTemp(currentTemp); weather.minTemperature = currentTemp;
weather.maxTemperature = weather.minTemperature; weather.maxTemperature = weather.minTemperature;
} }
} }
@ -463,14 +459,14 @@ WeatherProvider.register("envcanada", {
// //
if (todayClass === "low") { if (todayClass === "low") {
weather.minTemperature = this.convertTemp(todayTemp); weather.minTemperature = todayTemp;
if (today === 0 && fullDay === true) { if (today === 0 && fullDay === true) {
this.todayTempCacheMin = weather.minTemperature; this.todayTempCacheMin = weather.minTemperature;
} }
} }
if (todayClass === "high") { if (todayClass === "high") {
weather.maxTemperature = this.convertTemp(todayTemp); weather.maxTemperature = todayTemp;
if (today === 0 && fullDay === true) { if (today === 0 && fullDay === true) {
this.todayTempCacheMax = weather.maxTemperature; this.todayTempCacheMax = weather.maxTemperature;
} }
@ -482,11 +478,11 @@ WeatherProvider.register("envcanada", {
if (fullDay === true) { if (fullDay === true) {
if (nextClass === "low") { if (nextClass === "low") {
weather.minTemperature = this.convertTemp(nextTemp); weather.minTemperature = nextTemp;
} }
if (nextClass === "high") { 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. // Convert the icons to a more usable name.
// //

View File

@ -30,14 +30,14 @@ WeatherProvider.register("openweathermap", {
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then((data) => { .then((data) => {
let currentWeather;
if (this.config.weatherEndpoint === "/onecall") { if (this.config.weatherEndpoint === "/onecall") {
const weatherData = this.generateWeatherObjectsFromOnecall(data); currentWeather = this.generateWeatherObjectsFromOnecall(data).current;
this.setCurrentWeather(weatherData.current);
this.setFetchedLocation(`${data.timezone}`); this.setFetchedLocation(`${data.timezone}`);
} else { } else {
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data); currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
this.setCurrentWeather(currentWeather);
} }
this.setCurrentWeather(currentWeather);
}) })
.catch(function (request) { .catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
@ -49,15 +49,17 @@ WeatherProvider.register("openweathermap", {
fetchWeatherForecast() { fetchWeatherForecast() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then((data) => { .then((data) => {
let forecast;
let location;
if (this.config.weatherEndpoint === "/onecall") { if (this.config.weatherEndpoint === "/onecall") {
const weatherData = this.generateWeatherObjectsFromOnecall(data); forecast = this.generateWeatherObjectsFromOnecall(data).days;
this.setWeatherForecast(weatherData.days); location = `${data.timezone}`;
this.setFetchedLocation(`${data.timezone}`);
} else { } else {
const forecast = this.generateWeatherObjectsFromForecast(data.list); forecast = this.generateWeatherObjectsFromForecast(data.list);
this.setWeatherForecast(forecast); location = `${data.city.name}, ${data.city.country}`;
this.setFetchedLocation(`${data.city.name}, ${data.city.country}`);
} }
this.setWeatherForecast(forecast);
this.setFetchedLocation(location);
}) })
.catch(function (request) { .catch(function (request) {
Log.error("Could not load data ... ", request); Log.error("Could not load data ... ", request);
@ -123,8 +125,9 @@ WeatherProvider.register("openweathermap", {
* Generate a WeatherObject based on currentWeatherInformation * Generate a WeatherObject based on currentWeatherInformation
*/ */
generateWeatherObjectFromCurrentWeather(currentWeatherData) { 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.humidity = currentWeatherData.main.humidity;
currentWeather.temperature = currentWeatherData.main.temp; currentWeather.temperature = currentWeatherData.main.temp;
currentWeather.feelsLikeTemp = currentWeatherData.main.feels_like; currentWeather.feelsLikeTemp = currentWeatherData.main.feels_like;
@ -147,7 +150,7 @@ WeatherProvider.register("openweathermap", {
return this.fetchForecastDaily(forecasts); return this.fetchForecastDaily(forecasts);
} }
// if weatherEndpoint does not match forecast or forecast/daily, what should be returned? // 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); return this.fetchOnecall(data);
} }
// if weatherEndpoint does not match onecall, what should be returned? // 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; let snow = 0;
// variable for date // variable for date
let 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) { for (const forecast of forecasts) {
if (date !== moment.unix(forecast.dt).format("YYYY-MM-DD")) { if (date !== moment.unix(forecast.dt).format("YYYY-MM-DD")) {
@ -187,7 +190,7 @@ WeatherProvider.register("openweathermap", {
// push weather information to days array // push weather information to days array
days.push(weather); days.push(weather);
// create new weather-object // create new weather-object
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh); weather = new WeatherObject();
minTemp = []; minTemp = [];
maxTemp = []; maxTemp = [];
@ -250,7 +253,7 @@ WeatherProvider.register("openweathermap", {
const days = []; const days = [];
for (const forecast of forecasts) { 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.date = moment.unix(forecast.dt);
weather.minTemperature = forecast.temp.min; weather.minTemperature = forecast.temp.min;
@ -296,7 +299,7 @@ WeatherProvider.register("openweathermap", {
let precip = false; let precip = false;
// get current weather, if requested // 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")) { if (data.hasOwnProperty("current")) {
current.date = moment.unix(data.current.dt).utcOffset(data.timezone_offset / 60); current.date = moment.unix(data.current.dt).utcOffset(data.timezone_offset / 60);
current.windSpeed = data.current.wind_speed; current.windSpeed = data.current.wind_speed;
@ -328,7 +331,7 @@ WeatherProvider.register("openweathermap", {
current.feelsLikeTemp = data.current.feels_like; 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 // get hourly weather, if requested
const hours = []; const hours = [];
@ -363,7 +366,7 @@ WeatherProvider.register("openweathermap", {
} }
hours.push(weather); 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); 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; 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 += "&lang=" + this.config.lang;
params += "&APPID=" + this.config.apiKey; params += "&APPID=" + this.config.apiKey;

View File

@ -75,7 +75,7 @@ WeatherProvider.register("smhi", {
setConfig(config) { setConfig(config) {
this.config = config; this.config = config;
if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) === -1) { 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; config.precipitationValue = this.defaults.precipitationValue;
} }
}, },
@ -134,8 +134,7 @@ WeatherProvider.register("smhi", {
* @returns {WeatherObject} The converted weatherdata at the specified location * @returns {WeatherObject} The converted weatherdata at the specified location
*/ */
convertWeatherDataToObject(weatherData, coordinates) { convertWeatherDataToObject(weatherData, coordinates) {
// Weather data is only for Sweden and nobody in Sweden would use imperial let currentWeather = new WeatherObject();
let currentWeather = new WeatherObject("metric", "metric", "metric");
currentWeather.date = moment(weatherData.validTime); currentWeather.date = moment(weatherData.validTime);
currentWeather.updateSunTime(coordinates.lat, coordinates.lon); currentWeather.updateSunTime(coordinates.lat, coordinates.lon);
@ -191,7 +190,7 @@ WeatherProvider.register("smhi", {
for (const weatherObject of allWeatherObjects) { for (const weatherObject of allWeatherObjects) {
//If its the first object or if a day/hour change we need to reset the summary object //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)) { if (!currentWeather || !currentWeather.date.isSame(weatherObject.date, groupBy)) {
currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits); currentWeather = new WeatherObject();
dayWeatherTypes = []; dayWeatherTypes = [];
currentWeather.temperature = weatherObject.temperature; currentWeather.temperature = weatherObject.temperature;
currentWeather.date = weatherObject.date; currentWeather.date = weatherObject.date;

View File

@ -21,11 +21,6 @@ WeatherProvider.register("ukmetoffice", {
apiKey: "" apiKey: ""
}, },
units: {
imperial: "us",
metric: "si"
},
// Overwrite the fetchCurrentWeather method. // Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() { fetchCurrentWeather() {
this.fetchData(this.getUrl("3hourly")) this.fetchData(this.getUrl("3hourly"))
@ -80,7 +75,7 @@ WeatherProvider.register("ukmetoffice", {
* Generate a WeatherObject based on currentWeatherInformation * Generate a WeatherObject based on currentWeatherInformation
*/ */
generateWeatherObjectFromCurrentWeather(currentWeatherData) { 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; const location = currentWeatherData.SiteRep.DV.Location;
// data times are always UTC // data times are always UTC
@ -103,11 +98,11 @@ WeatherProvider.register("ukmetoffice", {
if (timeInMins >= p && timeInMins - 180 < p) { if (timeInMins >= p && timeInMins - 180 < p) {
// finally got the one we want, so populate weather object // finally got the one we want, so populate weather object
currentWeather.humidity = rep.H; currentWeather.humidity = rep.H;
currentWeather.temperature = this.convertTemp(rep.T); currentWeather.temperature = rep.T;
currentWeather.feelsLikeTemp = this.convertTemp(rep.F); currentWeather.feelsLikeTemp = rep.F;
currentWeather.precipitation = parseInt(rep.Pp); currentWeather.precipitation = parseInt(rep.Pp);
currentWeather.windSpeed = this.convertWindSpeed(rep.S); currentWeather.windSpeed = currentWeather.convertWindToMetric(rep.S);
currentWeather.windDirection = this.convertWindDirection(rep.D); currentWeather.windDirection = currentWeather.valueWindDirection(rep.D);
currentWeather.weatherType = this.convertWeatherType(rep.W); currentWeather.weatherType = this.convertWeatherType(rep.W);
} }
} }
@ -130,7 +125,7 @@ WeatherProvider.register("ukmetoffice", {
// loop round the (5) periods getting the data // loop round the (5) periods getting the data
// for each period array, Day is [0], Night is [1] // for each period array, Day is [0], Night is [1]
for (const period of forecasts.SiteRep.DV.Location.Period) { 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 // data times are always UTC
const dateStr = period.value; const dateStr = period.value;
@ -140,8 +135,8 @@ WeatherProvider.register("ukmetoffice", {
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) { if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
// populate the weather object // populate the weather object
weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD"); weather.date = moment.utc(dateStr.substr(0, 10), "YYYY-MM-DD");
weather.minTemperature = this.convertTemp(period.Rep[1].Nm); weather.minTemperature = period.Rep[1].Nm;
weather.maxTemperature = this.convertTemp(period.Rep[0].Dm); weather.maxTemperature = period.Rep[0].Dm;
weather.weatherType = this.convertWeatherType(period.Rep[0].W); weather.weatherType = this.convertWeatherType(period.Rep[0].W);
weather.precipitation = parseInt(period.Rep[0].PPd); weather.precipitation = parseInt(period.Rep[0].PPd);
@ -192,46 +187,6 @@ WeatherProvider.register("ukmetoffice", {
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null; 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. * Generates an url with api parameters based on the config.
* *

View File

@ -20,11 +20,9 @@
* weatherProvider: "ukmetofficedatahub", * weatherProvider: "ukmetofficedatahub",
* apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/", * apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
* apiKey: "[YOUR API KEY]", * apiKey: "[YOUR API KEY]",
* apiSecret: "[YOUR API SECRET]]", * apiSecret: "[YOUR API SECRET]",
* lat: [LATITUDE (DECIMAL)], * lat: [LATITUDE (DECIMAL)],
* lon: [LONGITUDE (DECIMAL)], * lon: [LONGITUDE (DECIMAL)]
* windUnits: "mps" | "kph" | "mph" (default)
* tempUnits: "imperial" | "metric" (default)
* *
* At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when * 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. * setting your update intervals. For reference, 360 requests per day is once every 4 minutes.
@ -51,8 +49,7 @@ WeatherProvider.register("ukmetofficedatahub", {
apiKey: "", apiKey: "",
apiSecret: "", apiSecret: "",
lat: 0, lat: 0,
lon: 0, lon: 0
windUnits: "mph"
}, },
// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api) // 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) // Create a WeatherObject using current weather data (data for the current hour)
generateWeatherObjectFromCurrentWeather(currentWeatherData) { 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 // Extract the actual forecasts
let forecastDataHours = currentWeatherData.features[0].properties.timeSeries; let forecastDataHours = currentWeatherData.features[0].properties.timeSeries;
@ -128,17 +125,17 @@ WeatherProvider.register("ukmetofficedatahub", {
let forecastTime = moment.utc(forecastDataHours[hour].time); let forecastTime = moment.utc(forecastDataHours[hour].time);
if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) { if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
currentWeather.date = forecastTime; currentWeather.date = forecastTime;
currentWeather.windSpeed = this.convertWindSpeed(forecastDataHours[hour].windSpeed10m); currentWeather.windSpeed = forecastDataHours[hour].windSpeed10m;
currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m; currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m;
currentWeather.temperature = this.convertTemp(forecastDataHours[hour].screenTemperature); currentWeather.temperature = forecastDataHours[hour].screenTemperature;
currentWeather.minTemperature = this.convertTemp(forecastDataHours[hour].minScreenAirTemp); currentWeather.minTemperature = forecastDataHours[hour].minScreenAirTemp;
currentWeather.maxTemperature = this.convertTemp(forecastDataHours[hour].maxScreenAirTemp); currentWeather.maxTemperature = forecastDataHours[hour].maxScreenAirTemp;
currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode); currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode);
currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity; currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
currentWeather.rain = forecastDataHours[hour].totalPrecipAmount; currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
currentWeather.snow = forecastDataHours[hour].totalSnowAmount; currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
currentWeather.precipitation = forecastDataHours[hour].probOfPrecipitation; 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 // 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) // 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 // Go through each day in the forecasts
for (let day in forecastDataDays) { 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 // Get date of forecast
let forecastDate = moment.utc(forecastDataDays[day].time); 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) // Check if forecast is for today or in the future (i.e., ignore yesterday's forecast)
if (forecastDate.isSameOrAfter(today)) { if (forecastDate.isSameOrAfter(today)) {
forecastWeather.date = forecastDate; forecastWeather.date = forecastDate;
forecastWeather.minTemperature = this.convertTemp(forecastDataDays[day].nightMinScreenTemperature); forecastWeather.minTemperature = forecastDataDays[day].nightMinScreenTemperature;
forecastWeather.maxTemperature = this.convertTemp(forecastDataDays[day].dayMaxScreenTemperature); forecastWeather.maxTemperature = forecastDataDays[day].dayMaxScreenTemperature;
// Using daytime forecast values // Using daytime forecast values
forecastWeather.windSpeed = this.convertWindSpeed(forecastDataDays[day].midday10MWindSpeed); forecastWeather.windSpeed = forecastDataDays[day].midday10MWindSpeed;
forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection; forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection;
forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode); forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation; forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation;
@ -214,7 +211,7 @@ WeatherProvider.register("ukmetofficedatahub", {
forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity; forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain; forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;
forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow; 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 // 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) // Note the units of the supplied data when using this (see top of file)
@ -232,27 +229,6 @@ WeatherProvider.register("ukmetofficedatahub", {
this.fetchedLocationName = name; 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 // Match the Met Office "significant weather code" to a weathericons.css icon
// Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264 // Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264
// and: https://erikflowers.github.io/weather-icons/ // and: https://erikflowers.github.io/weather-icons/

View File

@ -23,11 +23,6 @@ WeatherProvider.register("weatherbit", {
lon: 0 lon: 0
}, },
units: {
imperial: "I",
metric: "M"
},
fetchedLocation: function () { fetchedLocation: function () {
return this.fetchedLocationName || ""; return this.fetchedLocationName || "";
}, },
@ -95,8 +90,7 @@ WeatherProvider.register("weatherbit", {
// Create a URL from the config and base URL. // Create a URL from the config and base URL.
getUrl() { 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=M&key=${this.config.apiKey}`;
return `${this.config.apiBase}${this.config.weatherEndpoint}?lat=${this.config.lat}&lon=${this.config.lon}&units=${units}&key=${this.config.apiKey}`;
}, },
// Implement WeatherDay generator. // Implement WeatherDay generator.
@ -106,7 +100,7 @@ WeatherProvider.register("weatherbit", {
let tzOffset = d.getTimezoneOffset(); let tzOffset = d.getTimezoneOffset();
tzOffset = tzOffset * -1; 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.date = moment.unix(currentWeatherData.data[0].ts);
currentWeather.humidity = parseFloat(currentWeatherData.data[0].rh); currentWeather.humidity = parseFloat(currentWeatherData.data[0].rh);
@ -126,7 +120,7 @@ WeatherProvider.register("weatherbit", {
const days = []; const days = [];
for (const forecast of forecasts) { 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.date = moment(forecast.datetime, "YYYY-MM-DD");
weather.minTemperature = forecast.min_temp; weather.minTemperature = forecast.min_temp;

View File

@ -23,32 +23,15 @@ WeatherProvider.register("weatherflow", {
stationid: "" 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() { fetchCurrentWeather() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then((data) => { .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.date = moment();
currentWeather.humidity = data.current_conditions.relative_humidity; currentWeather.humidity = data.current_conditions.relative_humidity;
currentWeather.temperature = data.current_conditions.air_temperature; 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.windDirection = data.current_conditions.wind_direction;
currentWeather.weatherType = data.forecast.daily[0].icon; currentWeather.weatherType = data.forecast.daily[0].icon;
currentWeather.sunrise = moment.unix(data.forecast.daily[0].sunrise); currentWeather.sunrise = moment.unix(data.forecast.daily[0].sunrise);
@ -67,7 +50,7 @@ WeatherProvider.register("weatherflow", {
const days = []; const days = [];
for (const forecast of data.forecast.daily) { 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.date = moment.unix(forecast.day_start_local);
weather.minTemperature = forecast.air_temp_low; weather.minTemperature = forecast.air_temp_low;
@ -88,22 +71,6 @@ WeatherProvider.register("weatherflow", {
// Create a URL from the config and base URL. // Create a URL from the config and base URL.
getUrl() { getUrl() {
return ( 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}`;
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
);
} }
}); });

View File

@ -131,8 +131,8 @@ WeatherProvider.register("weathergov", {
} }
this.fetchedLocationName = data.properties.relativeLocation.properties.city + ", " + data.properties.relativeLocation.properties.state; this.fetchedLocationName = data.properties.relativeLocation.properties.city + ", " + data.properties.relativeLocation.properties.state;
Log.log("Forecast location is " + this.fetchedLocationName); Log.log("Forecast location is " + this.fetchedLocationName);
this.forecastURL = data.properties.forecast; this.forecastURL = data.properties.forecast + "?units=si";
this.forecastHourlyURL = data.properties.forecastHourly; this.forecastHourlyURL = data.properties.forecastHourly + "?units=si";
this.forecastGridDataURL = data.properties.forecastGridData; this.forecastGridDataURL = data.properties.forecastGridData;
this.observationStationsURL = data.properties.observationStations; this.observationStationsURL = data.properties.observationStations;
// with this URL, we chain another promise for the station obs URL // with this URL, we chain another promise for the station obs URL
@ -171,7 +171,7 @@ WeatherProvider.register("weathergov", {
const days = []; const days = [];
// variable for date // 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) { for (const forecast of forecasts) {
weather.date = moment(forecast.startTime.slice(0, 19)); weather.date = moment(forecast.startTime.slice(0, 19));
if (forecast.windSpeed.search(" ") < 0) { if (forecast.windSpeed.search(" ") < 0) {
@ -187,7 +187,7 @@ WeatherProvider.register("weathergov", {
days.push(weather); 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 // push weather information to days array
@ -201,24 +201,24 @@ WeatherProvider.register("weathergov", {
* ... object needs data in units based on config! * ... object needs data in units based on config!
*/ */
generateWeatherObjectFromCurrentWeather(currentWeatherData) { 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.date = moment(currentWeatherData.timestamp);
currentWeather.temperature = this.convertTemp(currentWeatherData.temperature.value); currentWeather.temperature = currentWeatherData.temperature.value;
currentWeather.windSpeed = this.convertSpeed(currentWeatherData.windSpeed.value); currentWeather.windSpeed = currentWeather.convertWindToMs(currentWeatherData.windSpeed.value);
currentWeather.windDirection = currentWeatherData.windDirection.value; currentWeather.windDirection = currentWeatherData.windDirection.value;
currentWeather.minTemperature = this.convertTemp(currentWeatherData.minTemperatureLast24Hours.value); currentWeather.minTemperature = currentWeatherData.minTemperatureLast24Hours.value;
currentWeather.maxTemperature = this.convertTemp(currentWeatherData.maxTemperatureLast24Hours.value); currentWeather.maxTemperature = currentWeatherData.maxTemperatureLast24Hours.value;
currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value); currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value);
currentWeather.rain = null; currentWeather.rain = null;
currentWeather.snow = null; currentWeather.snow = null;
currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value); currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value);
if (currentWeatherData.heatIndex.value !== null) { if (currentWeatherData.heatIndex.value !== null) {
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value); currentWeather.feelsLikeTemp = currentWeatherData.heatIndex.value;
} else if (currentWeatherData.windChill.value !== null) { } else if (currentWeatherData.windChill.value !== null) {
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.windChill.value); currentWeather.feelsLikeTemp = currentWeatherData.windChill.value;
} else { } else {
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.temperature.value); currentWeather.feelsLikeTemp = currentWeatherData.temperature.value;
} }
// determine the sunrise/sunset times - not supplied in weather.gov data // determine the sunrise/sunset times - not supplied in weather.gov data
currentWeather.updateSunTime(this.config.lat, this.config.lon); currentWeather.updateSunTime(this.config.lat, this.config.lon);
@ -247,7 +247,7 @@ WeatherProvider.register("weathergov", {
let maxTemp = []; let maxTemp = [];
// variable for date // variable for date
let 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; weather.precipitation = 0;
for (const forecast of forecasts) { for (const forecast of forecasts) {
@ -259,7 +259,7 @@ WeatherProvider.register("weathergov", {
// push weather information to days array // push weather information to days array
days.push(weather); days.push(weather);
// create new weather-object // create new weather-object
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh); weather = new WeatherObject();
minTemp = []; minTemp = [];
maxTemp = []; maxTemp = [];
@ -298,26 +298,6 @@ WeatherProvider.register("weathergov", {
/* /*
* Unit conversions * 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 // conversion to inches
convertLength(meters) { convertLength(meters) {
if (this.config.units === "imperial") { if (this.config.units === "imperial") {
@ -395,31 +375,5 @@ WeatherProvider.register("weathergov", {
} }
return null; 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;
} }
}); });

View File

@ -13,7 +13,6 @@ Module.register("weather", {
roundTemp: false, roundTemp: false,
type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint) type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint)
units: config.units, units: config.units,
useKmh: false,
tempUnits: config.units, tempUnits: config.units,
windUnits: config.units, windUnits: config.units,
updateInterval: 10 * 60 * 1000, // every 10 minutes updateInterval: 10 * 60 * 1000, // every 10 minutes
@ -23,7 +22,6 @@ Module.register("weather", {
showPeriodUpper: false, showPeriodUpper: false,
showWindDirection: true, showWindDirection: true,
showWindDirectionAsArrow: false, showWindDirectionAsArrow: false,
useBeaufort: true,
lang: config.language, lang: config.language,
showHumidity: false, showHumidity: false,
showSun: true, showSun: true,
@ -77,6 +75,14 @@ Module.register("weather", {
start: function () { start: function () {
moment.locale(this.config.lang); 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. // Initialize the weather provider.
this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this); this.weatherProvider = WeatherProvider.initialize(this.config.weatherProvider, this);
@ -195,6 +201,59 @@ Module.register("weather", {
return roundValue === "-0" ? 0 : roundValue; 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() { addFilters() {
this.nunjucksEnvironment().addFilter( this.nunjucksEnvironment().addFilter(
"formatTime", "formatTime",
@ -221,9 +280,7 @@ Module.register("weather", {
"unit", "unit",
function (value, type) { function (value, type) {
if (type === "temperature") { if (type === "temperature") {
if (this.config.tempUnits === "metric" || this.config.tempUnits === "imperial") { value = this.convertTemp(value) + "°";
value += "°";
}
if (this.config.degreeLabel) { if (this.config.degreeLabel) {
if (this.config.tempUnits === "metric") { if (this.config.tempUnits === "metric") {
value += "C"; value += "C";
@ -245,8 +302,9 @@ Module.register("weather", {
} }
} else if (type === "humidity") { } else if (type === "humidity") {
value += "%"; value += "%";
} else if (type === "wind") {
value = this.convertWind(value);
} }
return value; return value;
}.bind(this) }.bind(this)
); );

View File

@ -14,17 +14,8 @@
class WeatherObject { class WeatherObject {
/** /**
* Constructor for a 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) { constructor() {
this.units = units;
this.tempUnits = tempUnits;
this.windUnits = windUnits;
this.useKmh = useKmh;
this.date = null; this.date = null;
this.windSpeed = null; this.windSpeed = null;
this.windDirection = 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; * Convert the wind direction cardinal to value
const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000]; */
for (const [index, speed] of speeds.entries()) { valueWindDirection(windDirection) {
if (speed > windInKmh) { const windCardinals = {
return index; N: 0,
} NNE: 22,
} NE: 45,
return 12; 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() { convertWindToMetric(mph) {
return this.windUnits === "imperial" ? this.windSpeed * 1.609344 : (this.windSpeed * 60 * 60) / 1000; return mph / 2.2369362920544;
}
convertWindToMs(kmh) {
return kmh * 0.27777777777778;
} }
nextSunAction() { nextSunAction() {
@ -101,8 +111,8 @@ class WeatherObject {
if (this.feelsLikeTemp) { if (this.feelsLikeTemp) {
return this.feelsLikeTemp; return this.feelsLikeTemp;
} }
const windInMph = this.windUnits === "imperial" ? this.windSpeed : this.windSpeed * 2.23694; const windInMph = this.windSpeed * 2.2369362920544;
const tempInF = this.tempUnits === "imperial" ? this.temperature : (this.temperature * 9) / 5 + 32; const tempInF = (this.temperature * 9) / 5 + 32;
let feelsLike = tempInF; let feelsLike = tempInF;
if (windInMph > 3 && tempInF < 50) { if (windInMph > 3 && tempInF < 50) {
@ -120,7 +130,7 @@ class WeatherObject {
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity; 1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
} }
return this.tempUnits === "imperial" ? feelsLike : ((feelsLike - 32) * 5) / 9; return ((feelsLike - 32) * 5) / 9;
} }
/** /**

View File

@ -11,7 +11,7 @@ let config = {
config: { config: {
location: "Munich", location: "Munich",
mockData: '"#####WEATHERDATA#####"', mockData: '"#####WEATHERDATA#####"',
useBeaufort: false, windUnits: "beaufort",
showWindDirectionAsArrow: true, showWindDirectionAsArrow: true,
showSun: false, showSun: false,
showHumidity: true, showHumidity: true,

View File

@ -1,4 +1,3 @@
const moment = require("moment");
const helpers = require("../helpers/global-setup"); const helpers = require("../helpers/global-setup");
const weatherFunc = require("../helpers/weather-functions"); const weatherFunc = require("../helpers/weather-functions");
@ -14,39 +13,15 @@ describe("Weather module", () => {
}); });
it("should render wind speed and wind direction", async () => { 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 () => { 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 () => { 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" await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like -5.6°");
});
});
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");
}); });
}); });
}); });
@ -66,65 +41,44 @@ describe("Weather module", () => {
await weatherFunc.startApp("tests/configs/modules/weather/currentweather_options.js", {}); await weatherFunc.startApp("tests/configs/modules/weather/currentweather_options.js", {});
}); });
it("should render useBeaufort = false", async () => { it("should render windUnits in beaufort", async () => {
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "12"); 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"); const elem = await helpers.waitForElement(".weather .normal.medium sup i.fa-long-arrow-alt-up");
expect(elem).not.toBe(null); expect(elem).not.toBe(null);
expect(elem.outerHTML).toContain("transform:rotate(250deg);"); 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"); 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"); 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"); 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 () => { beforeAll(async () => {
await weatherFunc.startApp("tests/configs/modules/weather/currentweather_units.js", { 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
}
});
}); });
it("should render imperial units for wind", async () => { it("should render wind in imperial units", async () => {
await weatherFunc.getText(".weather .normal.medium span:nth-child(2)", "6 WSW"); 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°"); await weatherFunc.getText(".weather .large.light span.bright", "34,7°");
}); });
it("should render imperial units for feels like", async () => { it("should render 'feels like' in fahrenheit", async () => {
await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 22,0°"); await weatherFunc.getText(".weather .normal.medium.feelslike span.dimmed", "Feels like 21,9°");
});
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°");
}); });
}); });
}); });

View File

@ -86,7 +86,7 @@ describe("Weather module: Weather Forecast", () => {
await weatherFunc.startApp("tests/configs/modules/weather/forecastweather_units.js", {}); 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()) { for (const [index, temp] of temperatures.entries()) {
it("should render custom decimalSymbol = '_' for temp " + temp, async () => { 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); await weatherFunc.getText(`.weather table.small tr:nth-child(${index + 1}) td:nth-child(3)`, temp);

View 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);
};

View 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");
});
});
});

View File

@ -10,7 +10,7 @@ describe("WeatherObject", () => {
beforeAll(() => { beforeAll(() => {
originalTimeZone = moment.tz.guess(); originalTimeZone = moment.tz.guess();
moment.tz.setDefault("Africa/Dar_es_Salaam"); 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", () => { it("should return true for daytime at noon", () => {
@ -25,6 +25,14 @@ describe("WeatherObject", () => {
expect(weatherobject.isDayTime()).toBe(false); 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(() => { afterAll(() => {
moment.tz.setDefault(originalTimeZone); moment.tz.setDefault(originalTimeZone);
}); });