Release 2.23.0 (#3078)

## [2.23.0] - 2023-04-04

Thanks to: @angeldeejay, @buxxi, @CarJem, @dariom, @DaveChild, @dWoolridge, @grenagit, @Hirschberger, @KristjanESPERANTO, @MagMar94, @naveensrinivasan, @nfogal, @psieg, @rajniszp, @retroflex, @SkySails and @tomzt.

Special thanks to @khassel, @rejas and @sdetweil for taking over most (if not all) of the work on this release as project collaborators. This version would not be there without their effort. Thank you guys! You are awesome!

### Added

- Added increments for hourly forecasts in weather module (#2996)
- Added tests for hourly weather forecast
- Added possibility to ignore MagicMirror repo in updatenotification module
- Added Pirate Weather as new weather provider (#3005)
- Added possibility to use your own templates in Alert module
- Added error message if `<modulename>.js` file is missing in module folder to get a hint in the logs (#2403)
- Added possibility to use environment variables in `config.js` (#1756)
- Added option `pastDaysCount` to default calendar module to control of how many days past events should be displayed
- Added thai language to alert module
- Added option `sendNotifications` in clock module (#3056)

### Removed

- Removed darksky weather provider
- Removed unneeded (and unwanted) '.' after the year in calendar repeatingCountTitle (#2896)

### Updated

- Use develop as target branch for dependabot
- Update issue template, contributing doc and sample config
- The weather modules clearly separates precipitation amount and probability (risk of rain/snow)
  - This requires all providers that only supports probability to change the config from `showPrecipitationAmount` to `showPrecipitationProbability`.
- Update tests for weather and calendar module
- Changed updatenotification module for MagicMirror repo only: Send only notifications for `master` if there is a tag on a newer commit
- Update dates in Calendar widgets every minute
- Cleanup jest coverage for patches
- Update `stylelint` dependencies, switch to `stylelint-config-standard` and handle `stylelint` issues, update `main.css` matching new rules
- Update Eslint config, add new rule and handle issue
- Convert lots of callbacks to async/await
- Revise require imports (#3071 and #3072)

### Fixed

- Fix wrong day labels in envcanada forecast (#2987)
- Fix for missing default class name prefix for customEvents in calendar
- Fix electron flashing white screen on startup (#1919)
- Fix weathergov provider hourly forecast (#3008)
- Fix message display with HTML code into alert module (#2828)
- Fix typo in french translation
- Yr wind direction is no longer inverted
- Fix async node_helper stopping electron start (#2487)
- The wind direction arrow now points in the direction the wind is flowing, not into the wind (#3019)
- Fix precipitation css styles and rounding value
- Fix wrong vertical alignment of calendar title column when wrapEvents is true (#3053)
- Fix empty news feed stopping the reload forever
- Fix e2e tests (failed after async changes) by running calendar and newsfeed tests last
- Lint: Use template literals instead of string concatenation
- Fix default alert module to render HTML for title and message
- Fix Open-Meteo wind speed units
This commit is contained in:
Michael Teeuw
2023-04-04 20:44:32 +02:00
committed by GitHub
parent f14e956166
commit abe5c08a52
162 changed files with 6619 additions and 3019 deletions

View File

@@ -7,7 +7,7 @@
{% if config.showWindDirection %}
<sup>
{% if config.showWindDirectionAsArrow %}
<i class="fas fa-long-arrow-alt-up" style="transform:rotate({{ current.windDirection }}deg);"></i>
<i class="fas fa-long-arrow-alt-down" style="transform:rotate({{ current.windFromDirection }}deg);"></i>
{% else %}
{{ current.cardinalWindDirection() | translate }}
{% endif %}
@@ -16,7 +16,7 @@
{% endif %}
</span>
{% if config.showHumidity and current.humidity %}
<span>{{ current.humidity | decimalSymbol }}</span><sup>&nbsp;<i class="wi wi-humidity humidityIcon"></i></sup>
<span>{{ current.humidity | decimalSymbol }}</span><sup>&nbsp;<i class="wi wi-humidity humidity-icon"></i></sup>
{% endif %}
{% if config.showSun %}
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
@@ -54,16 +54,21 @@
</div>
{% endif %}
</div>
{% if (config.showFeelsLike or config.showPrecipitationAmount) and not config.onlyTemp %}
{% if (config.showFeelsLike or config.showPrecipitationAmount or config.showPrecipitationProbability) and not config.onlyTemp %}
<div class="normal medium feelslike">
{% if config.showFeelsLike %}
<span class="dimmed">
{{ "FEELS" | translate({DEGREE: current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }) }}
</span>
</span><br/>
{% endif %}
{% if config.showPrecipitationAmount %}
{% if config.showPrecipitationAmount and current.precipitationAmount %}
<span class="dimmed">
{{ "PRECIP" | translate }} {{ current.precipitation | unit("precip") }}
<span class="precipitationLeadText">{{ "PRECIP_AMOUNT" | translate }}</span> {{ current.precipitationAmount | unit("precip", current.precipitationUnits) }}
</span><br/>
{% endif %}
{% if config.showPrecipitationProbability and current.precipitationProbability %}
<span class="dimmed">
<span class="precipitationLeadText">{{ "PRECIP_POP" | translate }}</span> {{ current.precipitationProbability }}%
</span>
{% endif %}
</div>

View File

@@ -23,15 +23,14 @@
{{ f.minTemperature | roundValue | unit("temperature") | decimalSymbol }}
</td>
{% if config.showPrecipitationAmount %}
{% if f.precipitationUnits %}
<td class="align-right bright precipitation">
{{ f.precipitation }}{{ f.precipitationUnits }}
</td>
{% else %}
<td class="align-right bright precipitation">
{{ f.precipitation | unit("precip") }}
</td>
{% endif %}
<td class="align-right bright precipitation-amount">
{{ f.precipitationAmount | unit("precip", f.precipitationUnits) }}
</td>
{% endif %}
{% if config.showPrecipitationProbability %}
<td class="align-right bright precipitation-prob">
{{ f.precipitationProbability | unit("precip", "%") }}
</td>
{% endif %}
</tr>
{% set currentStep = currentStep + 1 %}

View File

@@ -11,15 +11,14 @@
{{ hour.temperature | roundValue | unit("temperature") }}
</td>
{% if config.showPrecipitationAmount %}
{% if hour.precipitationUnits %}
<td class="align-right bright precipitation">
{{ hour.precipitation }}{{ hour.precipitationUnits }}
</td>
{% else %}
<td class="align-right bright precipitation">
{{ hour.precipitation | unit("precip") }}
</td>
{% endif %}
<td class="align-right bright precipitation-amount">
{{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }}
</td>
{% endif %}
{% if config.showPrecipitationProbability %}
<td class="align-right bright precipitation-prob">
{{ hour.precipitationProbability | unit("precip", "%") }}
</td>
{% endif %}
</tr>
{% set currentStep = currentStep + 1 %}

View File

@@ -138,7 +138,7 @@ WeatherProvider.register("envcanada", {
// being accessed. This is only pertinent when using the EC data elements that contain a textual forecast.
//
getUrl() {
return "https://dd.weather.gc.ca/citypage_weather/xml/" + this.config.provCode + "/" + this.config.siteCode + "_e.xml";
return `https://dd.weather.gc.ca/citypage_weather/xml/${this.config.provCode}/${this.config.siteCode}_e.xml`;
},
//
@@ -165,7 +165,7 @@ WeatherProvider.register("envcanada", {
currentWeather.windSpeed = WeatherUtils.convertWindToMs(ECdoc.querySelector("siteData currentConditions wind speed").textContent);
currentWeather.windDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent;
currentWeather.windFromDirection = ECdoc.querySelector("siteData currentConditions wind bearing").textContent;
currentWeather.humidity = ECdoc.querySelector("siteData currentConditions relativeHumidity").textContent;
@@ -230,12 +230,7 @@ WeatherProvider.register("envcanada", {
const foreGroup = ECdoc.querySelectorAll("siteData forecastGroup forecast");
// For simplicity, we will only accumulate precipitation and will not try to break out
// rain vs snow accumulations
weather.rain = null;
weather.snow = null;
weather.precipitation = null;
weather.precipitationAmount = null;
//
// The EC forecast is held in a 12-element array - Elements 0 to 11 - with each day encompassing
@@ -336,16 +331,14 @@ WeatherProvider.register("envcanada", {
// Add 1 to the date to reflect the current forecast day we are building
lastDate = lastDate.add(1, "day");
weather.date = moment.unix(lastDate);
weather.date = moment(lastDate);
// Capture the temperatures for the current Element and the next Element in order to set
// the Min and Max temperatures for the forecast
this.setMinMaxTemps(weather, foreGroup, stepDay, true, currentTemp);
weather.rain = null;
weather.snow = null;
weather.precipitation = null;
weather.precipitationAmount = null;
this.setPrecipitation(weather, foreGroup, stepDay);
@@ -402,8 +395,7 @@ WeatherProvider.register("envcanada", {
const precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0;
if (precipLOP > 0) {
weather.precipitation = precipLOP;
weather.precipitationUnits = hourGroup[stepHour].querySelector("lop").getAttribute("units");
weather.precipitationProbability = precipLOP;
}
//
@@ -508,27 +500,14 @@ WeatherProvider.register("envcanada", {
setPrecipitation(weather, foreGroup, today) {
if (foreGroup[today].querySelector("precipitation accumulation")) {
weather.precipitation = foreGroup[today].querySelector("precipitation accumulation amount").textContent * 1.0;
weather.precipitationUnits = " " + foreGroup[today].querySelector("precipitation accumulation amount").getAttribute("units");
if (this.config.units === "imperial") {
if (weather.precipitationUnits === " cm") {
weather.precipitation = (weather.precipitation * 0.394).toFixed(2);
weather.precipitationUnits = " in";
}
if (weather.precipitationUnits === " mm") {
weather.precipitation = (weather.precipitation * 0.0394).toFixed(2);
weather.precipitationUnits = " in";
}
}
weather.precipitationAmount = foreGroup[today].querySelector("precipitation accumulation amount").textContent * 1.0;
weather.precipitationUnits = foreGroup[today].querySelector("precipitation accumulation amount").getAttribute("units");
}
// Check Today element for POP
if (foreGroup[today].querySelector("abbreviatedForecast pop").textContent > 0) {
weather.precipitation = foreGroup[today].querySelector("abbreviatedForecast pop").textContent;
weather.precipitationUnits = foreGroup[today].querySelector("abbreviatedForecast pop").getAttribute("units");
weather.precipitationProbability = foreGroup[today].querySelector("abbreviatedForecast pop").textContent;
}
},

View File

@@ -24,7 +24,7 @@ WeatherProvider.register("openmeteo", {
apiBase: OPEN_METEO_BASE,
lat: 0,
lon: 0,
past_days: 0,
pastDays: 0,
type: "current"
},
@@ -227,12 +227,12 @@ WeatherProvider.register("openmeteo", {
longitude: this.config.lon,
timeformat: "unixtime",
timezone: "auto",
past_days: this.config.past_days ?? 0,
past_days: this.config.pastDays ?? 0,
daily: this.dailyParams,
hourly: this.hourlyParams,
// Fixed units as metric
temperature_unit: "celsius",
windspeed_unit: "kmh",
windspeed_unit: "ms",
precipitation_unit: "mm"
};
@@ -264,9 +264,9 @@ WeatherProvider.register("openmeteo", {
switch (key) {
case "hourly":
case "daily":
return encodeURIComponent(key) + "=" + params[key].join(",");
return `${encodeURIComponent(key)}=${params[key].join(",")}`;
default:
return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
return `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`;
}
})
.join("&");
@@ -367,11 +367,11 @@ WeatherProvider.register("openmeteo", {
* `current_weather` object.
*/
const h = moment().hour();
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
const currentWeather = new WeatherObject();
currentWeather.date = weather.current_weather.time;
currentWeather.windSpeed = weather.current_weather.windspeed;
currentWeather.windDirection = weather.current_weather.winddirection;
currentWeather.windFromDirection = weather.current_weather.winddirection;
currentWeather.sunrise = weather.daily[0].sunrise;
currentWeather.sunset = weather.daily[0].sunset;
currentWeather.temperature = parseFloat(weather.current_weather.temperature);
@@ -381,7 +381,7 @@ WeatherProvider.register("openmeteo", {
currentWeather.humidity = parseFloat(weather.hourly[h].relativehumidity_2m);
currentWeather.rain = parseFloat(weather.hourly[h].rain);
currentWeather.snow = parseFloat(weather.hourly[h].snowfall * 10);
currentWeather.precipitation = parseFloat(weather.hourly[h].precipitation);
currentWeather.precipitationAmount = parseFloat(weather.hourly[h].precipitation);
return currentWeather;
},
@@ -391,11 +391,11 @@ WeatherProvider.register("openmeteo", {
const days = [];
weathers.daily.forEach((weather, i) => {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
const currentWeather = new WeatherObject();
currentWeather.date = weather.time;
currentWeather.windSpeed = weather.windspeed_10m_max;
currentWeather.windDirection = weather.winddirection_10m_dominant;
currentWeather.windFromDirection = weather.winddirection_10m_dominant;
currentWeather.sunrise = weather.sunrise;
currentWeather.sunset = weather.sunset;
currentWeather.temperature = parseFloat((weather.apparent_temperature_max + weather.apparent_temperature_min) / 2);
@@ -404,7 +404,7 @@ WeatherProvider.register("openmeteo", {
currentWeather.weatherType = this.convertWeatherType(weather.weathercode, currentWeather.isDayTime());
currentWeather.rain = parseFloat(weather.rain_sum);
currentWeather.snow = parseFloat(weather.snowfall_sum * 10);
currentWeather.precipitation = parseFloat(weather.precipitation_sum);
currentWeather.precipitationAmount = parseFloat(weather.precipitation_sum);
days.push(currentWeather);
});
@@ -422,12 +422,12 @@ WeatherProvider.register("openmeteo", {
return;
}
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
const currentWeather = new WeatherObject();
const h = Math.ceil((i + 1) / 24) - 1;
currentWeather.date = weather.time;
currentWeather.windSpeed = weather.windspeed_10m;
currentWeather.windDirection = weather.winddirection_10m;
currentWeather.windFromDirection = weather.winddirection_10m;
currentWeather.sunrise = weathers.daily[h].sunrise;
currentWeather.sunset = weathers.daily[h].sunset;
currentWeather.temperature = parseFloat(weather.apparent_temperature);
@@ -437,7 +437,7 @@ WeatherProvider.register("openmeteo", {
currentWeather.humidity = parseFloat(weather.relativehumidity_2m);
currentWeather.rain = parseFloat(weather.rain);
currentWeather.snow = parseFloat(weather.snowfall * 10);
currentWeather.precipitation = parseFloat(weather.precipitation);
currentWeather.precipitationAmount = parseFloat(weather.precipitation);
hours.push(currentWeather);
});

View File

@@ -132,7 +132,7 @@ WeatherProvider.register("openweathermap", {
currentWeather.temperature = currentWeatherData.main.temp;
currentWeather.feelsLikeTemp = currentWeatherData.main.feels_like;
currentWeather.windSpeed = currentWeatherData.wind.speed;
currentWeather.windDirection = currentWeatherData.wind.deg;
currentWeather.windFromDirection = currentWeatherData.wind.deg;
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.weather[0].icon);
currentWeather.sunrise = moment.unix(currentWeatherData.sys.sunrise);
currentWeather.sunset = moment.unix(currentWeatherData.sys.sunset);
@@ -145,9 +145,9 @@ WeatherProvider.register("openweathermap", {
*/
generateWeatherObjectsFromForecast(forecasts) {
if (this.config.weatherEndpoint === "/forecast") {
return this.fetchForecastHourly(forecasts);
return this.generateForecastHourly(forecasts);
} else if (this.config.weatherEndpoint === "/forecast/daily") {
return this.fetchForecastDaily(forecasts);
return this.generateForecastDaily(forecasts);
}
// if weatherEndpoint does not match forecast or forecast/daily, what should be returned?
return [new WeatherObject()];
@@ -165,9 +165,10 @@ WeatherProvider.register("openweathermap", {
},
/*
* fetch forecast information for 3-hourly forecast (available for free subscription).
* Generate forecast information for 3-hourly forecast (available for free
* subscription).
*/
fetchForecastHourly(forecasts) {
generateForecastHourly(forecasts) {
// initial variable declaration
const days = [];
// variables for temperature range and rain
@@ -186,7 +187,7 @@ WeatherProvider.register("openweathermap", {
weather.maxTemperature = Math.max.apply(null, maxTemp);
weather.rain = rain;
weather.snow = snow;
weather.precipitation = weather.rain + weather.snow;
weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0);
// push weather information to days array
days.push(weather);
// create new weather-object
@@ -216,20 +217,12 @@ WeatherProvider.register("openweathermap", {
minTemp.push(forecast.main.temp_min);
maxTemp.push(forecast.main.temp_max);
if (forecast.hasOwnProperty("rain")) {
if (this.config.units === "imperial" && !isNaN(forecast.rain["3h"])) {
rain += forecast.rain["3h"] / 25.4;
} else if (!isNaN(forecast.rain["3h"])) {
rain += forecast.rain["3h"];
}
if (forecast.hasOwnProperty("rain") && !isNaN(forecast.rain["3h"])) {
rain += forecast.rain["3h"];
}
if (forecast.hasOwnProperty("snow")) {
if (this.config.units === "imperial" && !isNaN(forecast.snow["3h"])) {
snow += forecast.snow["3h"] / 25.4;
} else if (!isNaN(forecast.snow["3h"])) {
snow += forecast.snow["3h"];
}
if (forecast.hasOwnProperty("snow") && !isNaN(forecast.snow["3h"])) {
snow += forecast.snow["3h"];
}
}
@@ -239,16 +232,17 @@ WeatherProvider.register("openweathermap", {
weather.maxTemperature = Math.max.apply(null, maxTemp);
weather.rain = rain;
weather.snow = snow;
weather.precipitation = weather.rain + weather.snow;
weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0);
// push weather information to days array
days.push(weather);
return days.slice(1);
},
/*
* fetch forecast information for daily forecast (available for paid subscription or old apiKey).
* Generate forecast information for daily forecast (available for paid
* subscription or old apiKey).
*/
fetchForecastDaily(forecasts) {
generateForecastDaily(forecasts) {
// initial variable declaration
const days = [];
@@ -264,25 +258,18 @@ WeatherProvider.register("openweathermap", {
// forecast.rain not available if amount is zero
// The API always returns in millimeters
if (forecast.hasOwnProperty("rain")) {
if (this.config.units === "imperial" && !isNaN(forecast.rain)) {
weather.rain = forecast.rain / 25.4;
} else if (!isNaN(forecast.rain)) {
weather.rain = forecast.rain;
}
if (forecast.hasOwnProperty("rain") && !isNaN(forecast.rain)) {
weather.rain = forecast.rain;
}
// forecast.snow not available if amount is zero
// The API always returns in millimeters
if (forecast.hasOwnProperty("snow")) {
if (this.config.units === "imperial" && !isNaN(forecast.snow)) {
weather.snow = forecast.snow / 25.4;
} else if (!isNaN(forecast.snow)) {
weather.snow = forecast.snow;
}
if (forecast.hasOwnProperty("snow") && !isNaN(forecast.snow)) {
weather.snow = forecast.snow;
}
weather.precipitation = weather.rain + weather.snow;
weather.precipitationAmount = weather.rain + weather.snow;
weather.precipitationProbability = forecast.pop ? forecast.pop * 100 : undefined;
days.push(weather);
}
@@ -303,30 +290,22 @@ WeatherProvider.register("openweathermap", {
if (data.hasOwnProperty("current")) {
current.date = moment.unix(data.current.dt).utcOffset(data.timezone_offset / 60);
current.windSpeed = data.current.wind_speed;
current.windDirection = data.current.wind_deg;
current.windFromDirection = data.current.wind_deg;
current.sunrise = moment.unix(data.current.sunrise).utcOffset(data.timezone_offset / 60);
current.sunset = moment.unix(data.current.sunset).utcOffset(data.timezone_offset / 60);
current.temperature = data.current.temp;
current.weatherType = this.convertWeatherType(data.current.weather[0].icon);
current.humidity = data.current.humidity;
if (data.current.hasOwnProperty("rain") && !isNaN(data.current["rain"]["1h"])) {
if (this.config.units === "imperial") {
current.rain = data.current["rain"]["1h"] / 25.4;
} else {
current.rain = data.current["rain"]["1h"];
}
current.rain = data.current["rain"]["1h"];
precip = true;
}
if (data.current.hasOwnProperty("snow") && !isNaN(data.current["snow"]["1h"])) {
if (this.config.units === "imperial") {
current.snow = data.current["snow"]["1h"] / 25.4;
} else {
current.snow = data.current["snow"]["1h"];
}
current.snow = data.current["snow"]["1h"];
precip = true;
}
if (precip) {
current.precipitation = current.rain + current.snow;
current.precipitationAmount = (current.rain ?? 0) + (current.snow ?? 0);
}
current.feelsLikeTemp = data.current.feels_like;
}
@@ -342,27 +321,20 @@ WeatherProvider.register("openweathermap", {
weather.feelsLikeTemp = hour.feels_like;
weather.humidity = hour.humidity;
weather.windSpeed = hour.wind_speed;
weather.windDirection = hour.wind_deg;
weather.windFromDirection = hour.wind_deg;
weather.weatherType = this.convertWeatherType(hour.weather[0].icon);
weather.precipitationProbability = hour.pop ? hour.pop * 100 : undefined;
precip = false;
if (hour.hasOwnProperty("rain") && !isNaN(hour.rain["1h"])) {
if (this.config.units === "imperial") {
weather.rain = hour.rain["1h"] / 25.4;
} else {
weather.rain = hour.rain["1h"];
}
weather.rain = hour.rain["1h"];
precip = true;
}
if (hour.hasOwnProperty("snow") && !isNaN(hour.snow["1h"])) {
if (this.config.units === "imperial") {
weather.snow = hour.snow["1h"] / 25.4;
} else {
weather.snow = hour.snow["1h"];
}
weather.snow = hour.snow["1h"];
precip = true;
}
if (precip) {
weather.precipitation = weather.rain + weather.snow;
weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0);
}
hours.push(weather);
@@ -381,27 +353,20 @@ WeatherProvider.register("openweathermap", {
weather.maxTemperature = day.temp.max;
weather.humidity = day.humidity;
weather.windSpeed = day.wind_speed;
weather.windDirection = day.wind_deg;
weather.windFromDirection = day.wind_deg;
weather.weatherType = this.convertWeatherType(day.weather[0].icon);
weather.precipitationProbability = day.pop ? day.pop * 100 : undefined;
precip = false;
if (!isNaN(day.rain)) {
if (this.config.units === "imperial") {
weather.rain = day.rain / 25.4;
} else {
weather.rain = day.rain;
}
weather.rain = day.rain;
precip = true;
}
if (!isNaN(day.snow)) {
if (this.config.units === "imperial") {
weather.snow = day.snow / 25.4;
} else {
weather.snow = day.snow;
}
weather.snow = day.snow;
precip = true;
}
if (precip) {
weather.precipitation = weather.rain + weather.snow;
weather.precipitationAmount = (weather.rain ?? 0) + (weather.snow ?? 0);
}
days.push(weather);
@@ -448,8 +413,8 @@ WeatherProvider.register("openweathermap", {
getParams() {
let params = "?";
if (this.config.weatherEndpoint === "/onecall") {
params += "lat=" + this.config.lat;
params += "&lon=" + this.config.lon;
params += `lat=${this.config.lat}`;
params += `&lon=${this.config.lon}`;
if (this.config.type === "current") {
params += "&exclude=minutely,hourly,daily";
} else if (this.config.type === "hourly") {
@@ -460,23 +425,23 @@ WeatherProvider.register("openweathermap", {
params += "&exclude=minutely";
}
} else if (this.config.lat && this.config.lon) {
params += "lat=" + this.config.lat + "&lon=" + this.config.lon;
params += `lat=${this.config.lat}&lon=${this.config.lon}`;
} else if (this.config.locationID) {
params += "id=" + this.config.locationID;
params += `id=${this.config.locationID}`;
} else if (this.config.location) {
params += "q=" + this.config.location;
params += `q=${this.config.location}`;
} else if (this.firstEvent && this.firstEvent.geo) {
params += "lat=" + this.firstEvent.geo.lat + "&lon=" + this.firstEvent.geo.lon;
params += `lat=${this.firstEvent.geo.lat}&lon=${this.firstEvent.geo.lon}`;
} else if (this.firstEvent && this.firstEvent.location) {
params += "q=" + this.firstEvent.location;
params += `q=${this.firstEvent.location}`;
} else {
this.hide(this.config.animationSpeed, { lockString: this.identifier });
return;
}
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;
params += `&lang=${this.config.lang}`;
params += `&APPID=${this.config.apiKey}`;
return params;
}

View File

@@ -2,24 +2,23 @@
/* MagicMirror²
* Module: Weather
* Provider: Dark Sky
* Provider: Pirate Weather
*
* By Nicholas Hubbard https://github.com/nhubbard
* Written by Nicholas Hubbard https://github.com/nhubbard for formerly Dark Sky Provider
* Modified by Karsten Hassel for Pirate Weather
* MIT Licensed
*
* This class is a provider for Dark Sky.
* Note that the Dark Sky API does not provide rainfall. Instead it provides
* snowfall and precipitation probability
* This class is a provider for Pirate Weather, it is a replacement for Dark Sky (same api).
*/
WeatherProvider.register("darksky", {
WeatherProvider.register("pirateweather", {
// Set the name of the provider.
// Not strictly required, but helps for debugging.
providerName: "Dark Sky",
providerName: "pirateweather",
// Set the default config properties that is specific to this provider
defaults: {
useCorsProxy: true,
apiBase: "https://api.darksky.net",
apiBase: "https://api.pirateweather.net",
weatherEndpoint: "/forecast",
apiKey: "",
lat: 0,
@@ -73,7 +72,7 @@ WeatherProvider.register("darksky", {
currentWeather.humidity = parseFloat(currentWeatherData.currently.humidity);
currentWeather.temperature = parseFloat(currentWeatherData.currently.temperature);
currentWeather.windSpeed = parseFloat(currentWeatherData.currently.windSpeed);
currentWeather.windDirection = currentWeatherData.currently.windBearing;
currentWeather.windFromDirection = currentWeatherData.currently.windBearing;
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.currently.icon);
currentWeather.sunrise = moment.unix(currentWeatherData.daily.data[0].sunriseTime);
currentWeather.sunset = moment.unix(currentWeatherData.daily.data[0].sunsetTime);
@@ -92,19 +91,21 @@ WeatherProvider.register("darksky", {
weather.maxTemperature = forecast.temperatureMax;
weather.weatherType = this.convertWeatherType(forecast.icon);
weather.snow = 0;
weather.rain = 0;
// The API will return centimeters if units is 'si' and will return inches for 'us'
// Note that the Dark Sky API does not provide rainfall.
// Instead it provides snowfall and precipitation probability
let precip = 0;
if (forecast.hasOwnProperty("precipAccumulation")) {
if (this.config.units === "imperial" && !isNaN(forecast.precipAccumulation)) {
weather.snow = forecast.precipAccumulation;
} else if (!isNaN(forecast.precipAccumulation)) {
weather.snow = forecast.precipAccumulation * 10;
}
precip = forecast.precipAccumulation * 10;
}
weather.precipitation = weather.snow;
weather.precipitationAmount = precip;
if (forecast.hasOwnProperty("precipType")) {
if (forecast.precipType === "snow") {
weather.snow = precip;
} else {
weather.rain = precip;
}
}
days.push(weather);
}
@@ -112,7 +113,7 @@ WeatherProvider.register("darksky", {
return days;
},
// Map icons from Dark Sky to our icons.
// Map icons from Pirate Weather to our icons.
convertWeatherType(weatherType) {
const weatherTypes = {
"clear-day": "day-sunny",

View File

@@ -33,7 +33,7 @@ WeatherProvider.register("smhi", {
this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
this.setCurrentWeather(weatherObject);
})
.catch((error) => Log.error("Could not load data: " + error.message))
.catch((error) => Log.error(`Could not load data: ${error.message}`))
.finally(() => this.updateAvailable());
},
@@ -48,7 +48,7 @@ WeatherProvider.register("smhi", {
this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
this.setWeatherForecast(weatherObjects);
})
.catch((error) => Log.error("Could not load data: " + error.message))
.catch((error) => Log.error(`Could not load data: ${error.message}`))
.finally(() => this.updateAvailable());
},
@@ -63,7 +63,7 @@ WeatherProvider.register("smhi", {
this.setFetchedLocation(this.config.location || `(${coordinates.lat},${coordinates.lon})`);
this.setWeatherHourly(weatherObjects);
})
.catch((error) => Log.error("Could not load data: " + error.message))
.catch((error) => Log.error(`Could not load data: ${error.message}`))
.finally(() => this.updateAvailable());
},
@@ -75,7 +75,7 @@ WeatherProvider.register("smhi", {
setConfig(config) {
this.config = config;
if (!config.precipitationValue || ["pmin", "pmean", "pmedian", "pmax"].indexOf(config.precipitationValue) === -1) {
Log.log("invalid or not set: " + config.precipitationValue);
Log.log(`invalid or not set: ${config.precipitationValue}`);
config.precipitationValue = this.defaults.precipitationValue;
}
},
@@ -145,7 +145,7 @@ WeatherProvider.register("smhi", {
currentWeather.humidity = this.paramValue(weatherData, "r");
currentWeather.temperature = this.paramValue(weatherData, "t");
currentWeather.windSpeed = this.paramValue(weatherData, "ws");
currentWeather.windDirection = this.paramValue(weatherData, "wd");
currentWeather.windFromDirection = this.paramValue(weatherData, "wd");
currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), currentWeather.isDayTime());
currentWeather.feelsLikeTemp = this.calculateApparentTemperature(weatherData);
@@ -157,19 +157,19 @@ WeatherProvider.register("smhi", {
// 0 = No precipitation
case 1: // Snow
currentWeather.snow += precipitationValue;
currentWeather.precipitation += precipitationValue;
currentWeather.precipitationAmount += precipitationValue;
break;
case 2: // Snow and rain, treat it as 50/50 snow and rain
currentWeather.snow += precipitationValue / 2;
currentWeather.rain += precipitationValue / 2;
currentWeather.precipitation += precipitationValue;
currentWeather.precipitationAmount += precipitationValue;
break;
case 3: // Rain
case 4: // Drizzle
case 5: // Freezing rain
case 6: // Freezing drizzle
currentWeather.rain += precipitationValue;
currentWeather.precipitation += precipitationValue;
currentWeather.precipitationAmount += precipitationValue;
break;
}
@@ -202,7 +202,7 @@ WeatherProvider.register("smhi", {
currentWeather.maxTemperature = -Infinity;
currentWeather.snow = 0;
currentWeather.rain = 0;
currentWeather.precipitation = 0;
currentWeather.precipitationAmount = 0;
result.push(currentWeather);
}
@@ -221,7 +221,7 @@ WeatherProvider.register("smhi", {
currentWeather.maxTemperature = Math.max(currentWeather.maxTemperature, weatherObject.temperature);
currentWeather.snow += weatherObject.snow;
currentWeather.rain += weatherObject.rain;
currentWeather.precipitation += weatherObject.precipitation;
currentWeather.precipitationAmount += weatherObject.precipitationAmount;
}
return result;

View File

@@ -100,9 +100,9 @@ WeatherProvider.register("ukmetoffice", {
currentWeather.humidity = rep.H;
currentWeather.temperature = rep.T;
currentWeather.feelsLikeTemp = rep.F;
currentWeather.precipitation = parseInt(rep.Pp);
currentWeather.precipitationProbability = parseInt(rep.Pp);
currentWeather.windSpeed = WeatherUtils.convertWindToMetric(rep.S);
currentWeather.windDirection = WeatherUtils.convertWindDirection(rep.D);
currentWeather.windFromDirection = WeatherUtils.convertWindDirection(rep.D);
currentWeather.weatherType = this.convertWeatherType(rep.W);
}
}
@@ -138,7 +138,7 @@ WeatherProvider.register("ukmetoffice", {
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);
weather.precipitationProbability = parseInt(period.Rep[0].PPd);
days.push(weather);
}
@@ -195,8 +195,8 @@ WeatherProvider.register("ukmetoffice", {
*/
getParams(forecastType) {
let params = "?";
params += "res=" + forecastType;
params += "&key=" + this.config.apiKey;
params += `res=${forecastType}`;
params += `&key=${this.config.apiKey}`;
return params;
}
});

View File

@@ -55,9 +55,9 @@ WeatherProvider.register("ukmetofficedatahub", {
// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
getUrl(forecastType) {
let queryStrings = "?";
queryStrings += "latitude=" + this.config.lat;
queryStrings += "&longitude=" + this.config.lon;
queryStrings += "&includeLocationName=" + true;
queryStrings += `latitude=${this.config.lat}`;
queryStrings += `&longitude=${this.config.lon}`;
queryStrings += `&includeLocationName=${true}`;
// Return URL, making sure there is a trailing "/" in the base URL.
return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings;
@@ -104,7 +104,7 @@ WeatherProvider.register("ukmetofficedatahub", {
})
// Catch any error(s)
.catch((error) => Log.error("Could not load data: " + error.message))
.catch((error) => Log.error(`Could not load data: ${error.message}`))
// Let the module know there is data available
.finally(() => this.updateAvailable());
@@ -126,7 +126,7 @@ WeatherProvider.register("ukmetofficedatahub", {
if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
currentWeather.date = forecastTime;
currentWeather.windSpeed = forecastDataHours[hour].windSpeed10m;
currentWeather.windDirection = forecastDataHours[hour].windDirectionFrom10m;
currentWeather.windFromDirection = forecastDataHours[hour].windDirectionFrom10m;
currentWeather.temperature = forecastDataHours[hour].screenTemperature;
currentWeather.minTemperature = forecastDataHours[hour].minScreenAirTemp;
currentWeather.maxTemperature = forecastDataHours[hour].maxScreenAirTemp;
@@ -134,7 +134,7 @@ WeatherProvider.register("ukmetofficedatahub", {
currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
currentWeather.precipitation = forecastDataHours[hour].probOfPrecipitation;
currentWeather.precipitationProbability = forecastDataHours[hour].probOfPrecipitation;
currentWeather.feelsLikeTemp = forecastDataHours[hour].feelsLikeTemperature;
// Pass on full details, so they can be used in custom templates
@@ -173,7 +173,7 @@ WeatherProvider.register("ukmetofficedatahub", {
})
// Catch any error(s)
.catch((error) => Log.error("Could not load data: " + error.message))
.catch((error) => Log.error(`Could not load data: ${error.message}`))
// Let the module know there is new data available
.finally(() => this.updateAvailable());
@@ -204,9 +204,9 @@ WeatherProvider.register("ukmetofficedatahub", {
// Using daytime forecast values
forecastWeather.windSpeed = forecastDataDays[day].midday10MWindSpeed;
forecastWeather.windDirection = forecastDataDays[day].midday10MWindDirection;
forecastWeather.windFromDirection = forecastDataDays[day].midday10MWindDirection;
forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
forecastWeather.precipitation = forecastDataDays[day].dayProbabilityOfPrecipitation;
forecastWeather.precipitationProbability = forecastDataDays[day].dayProbabilityOfPrecipitation;
forecastWeather.temperature = forecastDataDays[day].dayMaxScreenTemperature;
forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;

View File

@@ -55,7 +55,7 @@ WeatherProvider.register("weatherbit", {
const forecast = this.generateWeatherObjectsFromForecast(data.data);
this.setWeatherForecast(forecast);
this.fetchedLocationName = data.city_name + ", " + data.state_code;
this.fetchedLocationName = `${data.city_name}, ${data.state_code}`;
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
@@ -106,12 +106,12 @@ WeatherProvider.register("weatherbit", {
currentWeather.humidity = parseFloat(currentWeatherData.data[0].rh);
currentWeather.temperature = parseFloat(currentWeatherData.data[0].temp);
currentWeather.windSpeed = parseFloat(currentWeatherData.data[0].wind_spd);
currentWeather.windDirection = currentWeatherData.data[0].wind_dir;
currentWeather.windFromDirection = currentWeatherData.data[0].wind_dir;
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.data[0].weather.icon);
currentWeather.sunrise = moment(currentWeatherData.data[0].sunrise, "HH:mm").add(tzOffset, "m");
currentWeather.sunset = moment(currentWeatherData.data[0].sunset, "HH:mm").add(tzOffset, "m");
this.fetchedLocationName = currentWeatherData.data[0].city_name + ", " + currentWeatherData.data[0].state_code;
this.fetchedLocationName = `${currentWeatherData.data[0].city_name}, ${currentWeatherData.data[0].state_code}`;
return currentWeather;
},
@@ -125,7 +125,8 @@ WeatherProvider.register("weatherbit", {
weather.date = moment(forecast.datetime, "YYYY-MM-DD");
weather.minTemperature = forecast.min_temp;
weather.maxTemperature = forecast.max_temp;
weather.precipitation = forecast.precip;
weather.precipitationAmount = forecast.precip;
weather.precipitationProbability = forecast.pop;
weather.weatherType = this.convertWeatherType(forecast.weather.icon);
days.push(weather);

View File

@@ -32,7 +32,7 @@ WeatherProvider.register("weatherflow", {
currentWeather.humidity = data.current_conditions.relative_humidity;
currentWeather.temperature = data.current_conditions.air_temperature;
currentWeather.windSpeed = WeatherUtils.convertWindToMs(data.current_conditions.wind_avg);
currentWeather.windDirection = data.current_conditions.wind_direction;
currentWeather.windFromDirection = data.current_conditions.wind_direction;
currentWeather.weatherType = data.forecast.daily[0].icon;
currentWeather.sunrise = moment.unix(data.forecast.daily[0].sunrise);
currentWeather.sunset = moment.unix(data.forecast.daily[0].sunset);
@@ -55,6 +55,7 @@ WeatherProvider.register("weatherflow", {
weather.date = moment.unix(forecast.day_start_local);
weather.minTemperature = forecast.air_temp_low;
weather.maxTemperature = forecast.air_temp_high;
weather.precipitationProbability = forecast.precip_probability;
weather.weatherType = forecast.icon;
weather.snow = 0;

View File

@@ -129,10 +129,10 @@ WeatherProvider.register("weathergov", {
// points URL did not respond with usable data.
return;
}
this.fetchedLocationName = data.properties.relativeLocation.properties.city + ", " + data.properties.relativeLocation.properties.state;
Log.log("Forecast location is " + this.fetchedLocationName);
this.forecastURL = data.properties.forecast + "?units=si";
this.forecastHourlyURL = data.properties.forecastHourly + "?units=si";
this.fetchedLocationName = `${data.properties.relativeLocation.properties.city}, ${data.properties.relativeLocation.properties.state}`;
Log.log(`Forecast location is ${this.fetchedLocationName}`);
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
@@ -143,7 +143,7 @@ WeatherProvider.register("weathergov", {
// obs station URL did not respond with usable data.
return;
}
this.stationObsURL = obsData.features[0].id + "/observations/latest";
this.stationObsURL = `${obsData.features[0].id}/observations/latest`;
})
.catch((err) => {
Log.error(err);
@@ -179,9 +179,9 @@ WeatherProvider.register("weathergov", {
} else {
weather.windSpeed = forecast.windSpeed.slice(0, forecast.windSpeed.search(" "));
}
weather.windDirection = this.convertWindDirection(forecast.windDirection);
weather.windSpeed = WeatherUtils.convertWindToMs(weather.windSpeed);
weather.windFromDirection = forecast.windDirection;
weather.temperature = forecast.temperature;
weather.tempUnits = forecast.temperatureUnit;
// use the forecast isDayTime attribute to help build the weatherType label
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
@@ -206,13 +206,11 @@ WeatherProvider.register("weathergov", {
currentWeather.date = moment(currentWeatherData.timestamp);
currentWeather.temperature = currentWeatherData.temperature.value;
currentWeather.windSpeed = WeatherUtils.convertWindToMs(currentWeatherData.windSpeed.value);
currentWeather.windDirection = currentWeatherData.windDirection.value;
currentWeather.windFromDirection = currentWeatherData.windDirection.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);
currentWeather.precipitationAmount = currentWeatherData.precipitationLastHour.value;
if (currentWeatherData.heatIndex.value !== null) {
currentWeather.feelsLikeTemp = currentWeatherData.heatIndex.value;
} else if (currentWeatherData.windChill.value !== null) {
@@ -240,6 +238,8 @@ WeatherProvider.register("weathergov", {
* fetch forecast information for daily forecast.
*/
fetchForecastDaily(forecasts) {
const precipitationProbabilityRegEx = "Chance of precipitation is ([0-9]+?)%";
// initial variable declaration
const days = [];
// variables for temperature range and rain
@@ -248,7 +248,6 @@ WeatherProvider.register("weathergov", {
// variable for date
let date = "";
let weather = new WeatherObject();
weather.precipitation = 0;
for (const forecast of forecasts) {
if (date !== moment(forecast.startTime).format("YYYY-MM-DD")) {
@@ -263,7 +262,8 @@ WeatherProvider.register("weathergov", {
minTemp = [];
maxTemp = [];
weather.precipitation = 0;
const precipitation = new RegExp(precipitationProbabilityRegEx, "g").exec(forecast.detailedForecast);
if (precipitation) weather.precipitationProbability = precipitation[1];
// set new date
date = moment(forecast.startTime).format("YYYY-MM-DD");
@@ -295,18 +295,6 @@ WeatherProvider.register("weathergov", {
return days.slice(1);
},
/*
* Unit conversions
*/
// conversion to inches
convertLength(meters) {
if (this.config.units === "imperial") {
return meters * 39.3701;
} else {
return meters;
}
},
/*
* Convert the icons to a more usable name.
*/

View File

@@ -7,7 +7,7 @@
* By Magnus Marthinsen
* MIT Licensed
*
* This class is a provider for Yr.no, a norwegian sweather service.
* This class is a provider for Yr.no, a norwegian weather service.
*
* Terms of service: https://developer.yr.no/doc/TermsOfService/
*/
@@ -47,7 +47,7 @@ WeatherProvider.register("yr", {
const getRequests = [this.getWeatherData(), this.getStellarData()];
const [weatherData, stellarData] = await Promise.all(getRequests);
if (!stellarData) {
Log.warn("No stelar data available.");
Log.warn("No stellar data available.");
}
if (!weatherData.properties.timeseries || !weatherData.properties.timeseries[0]) {
Log.error("No weather data available.");
@@ -65,7 +65,8 @@ WeatherProvider.register("yr", {
}
const forecastXHours = this.getForecastForXHoursFrom(forecast.data);
forecast.weatherType = this.convertWeatherType(forecastXHours.summary.symbol_code, forecast.time);
forecast.precipitation = forecastXHours.details?.precipitation_amount;
forecast.precipitationAmount = forecastXHours.details?.precipitation_amount;
forecast.precipitationProbability = forecastXHours.details?.probability_of_precipitation;
forecast.minTemperature = forecastXHours.details?.air_temperature_min;
forecast.maxTemperature = forecastXHours.details?.air_temperature_max;
return this.getWeatherDataFrom(forecast, stellarData, weatherData.properties.meta.units);
@@ -251,12 +252,12 @@ WeatherProvider.register("yr", {
this.cacheStellarData(stellarData);
resolve(stellarData);
} else {
reject("No stellar data returned from Yr for " + tomorrow);
reject(`No stellar data returned from Yr for ${tomorrow}`);
}
})
.catch((err) => {
Log.error(err);
reject("Unable to get stellar data from Yr for " + tomorrow);
reject(`Unable to get stellar data from Yr for ${tomorrow}`);
})
.finally(() => {
localStorage.removeItem("yrIsFetchingStellarData");
@@ -274,7 +275,7 @@ WeatherProvider.register("yr", {
this.cacheStellarData(stellarData);
resolve(stellarData);
} else {
Log.error("Something went wrong when fetching stellar data. Responses: " + stellarData);
Log.error(`Something went wrong when fetching stellar data. Responses: ${stellarData}`);
reject(stellarData);
}
})
@@ -358,19 +359,20 @@ WeatherProvider.register("yr", {
},
getWeatherDataFrom(forecast, stellarData, units) {
const weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits, this.config.useKmh);
const weather = new WeatherObject();
const stellarTimesToday = stellarData?.today ? this.getStellarTimesFrom(stellarData.today, moment().format("YYYY-MM-DD")) : undefined;
const stellarTimesTomorrow = stellarData?.tomorrow ? this.getStellarTimesFrom(stellarData.tomorrow, moment().add(1, "days").format("YYYY-MM-DD")) : undefined;
weather.date = moment(forecast.time);
weather.windSpeed = forecast.data.instant.details.wind_speed;
weather.windDirection = (forecast.data.instant.details.wind_from_direction + 180) % 360;
weather.windFromDirection = forecast.data.instant.details.wind_from_direction;
weather.temperature = forecast.data.instant.details.air_temperature;
weather.minTemperature = forecast.minTemperature;
weather.maxTemperature = forecast.maxTemperature;
weather.weatherType = forecast.weatherType;
weather.humidity = forecast.data.instant.details.relative_humidity;
weather.precipitation = forecast.precipitation;
weather.precipitationAmount = forecast.precipitationAmount;
weather.precipitationProbability = forecast.precipitationProbability;
weather.precipitationUnits = units.precipitation_amount;
if (stellarTimesToday) {
@@ -530,7 +532,7 @@ WeatherProvider.register("yr", {
return;
}
if (!stellarData) {
Log.warn("No stelar data available.");
Log.warn("No stellar data available.");
}
let forecasts;
switch (type) {
@@ -554,7 +556,8 @@ WeatherProvider.register("yr", {
for (const forecast of weatherData.properties.timeseries) {
forecast.symbol = forecast.data.next_1_hours?.summary?.symbol_code;
forecast.precipitation = forecast.data.next_1_hours?.details?.precipitation_amount;
forecast.precipitationAmount = forecast.data.next_1_hours?.details?.precipitation_amount;
forecast.precipitationProbability = forecast.data.next_1_hours?.details?.probability_of_precipitation;
forecast.minTemperature = forecast.data.next_1_hours?.details?.air_temperature_min;
forecast.maxTemperature = forecast.data.next_1_hours?.details?.air_temperature_max;
forecast.weatherType = this.convertWeatherType(forecast.symbol, forecast.time);
@@ -599,7 +602,8 @@ WeatherProvider.register("yr", {
const forecastXHours = forecast.data.next_12_hours ?? forecast.data.next_6_hours ?? forecast.data.next_1_hours;
if (forecastXHours) {
forecast.symbol = forecastXHours.summary?.symbol_code;
forecast.precipitation = forecastXHours.details?.precipitation_amount;
forecast.precipitationAmount = forecastXHours.details?.precipitation_amount ?? forecast.data.next_6_hours?.details?.precipitation_amount; // 6 hours is likely to have precipitation amount even if 12 hours does not
forecast.precipitationProbability = forecastXHours.details?.probability_of_precipitation;
forecast.minTemperature = minTemperature;
forecast.maxTemperature = maxTemperature;

View File

@@ -6,7 +6,7 @@
transform: translate(0, -3px);
}
.weather .humidityIcon {
.weather .humidity-icon {
padding-right: 4px;
}
@@ -29,7 +29,8 @@
padding-right: 0;
}
.weather .precipitation {
.weather .precipitation-amount,
.weather .precipitation-prob {
padding-left: 20px;
padding-right: 0;
}

View File

@@ -12,23 +12,26 @@ Module.register("weather", {
weatherProvider: "openweathermap",
roundTemp: false,
type: "current", // current, forecast, daily (equivalent to forecast), hourly (only with OpenWeatherMap /onecall endpoint)
lang: config.language,
units: config.units,
tempUnits: config.units,
windUnits: config.units,
timeFormat: config.timeFormat,
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
timeFormat: config.timeFormat,
showFeelsLike: true,
showHumidity: false,
showIndoorHumidity: false,
showIndoorTemperature: false,
showPeriod: true,
showPeriodUpper: false,
showPrecipitationAmount: false,
showPrecipitationProbability: false,
showSun: true,
showWindDirection: true,
showWindDirectionAsArrow: false,
lang: config.language,
showHumidity: false,
showSun: true,
degreeLabel: false,
decimalSymbol: ".",
showIndoorTemperature: false,
showIndoorHumidity: false,
maxNumberOfDays: 5,
maxEntries: 5,
ignoreToday: false,
@@ -39,10 +42,9 @@ Module.register("weather", {
calendarClass: "calendar",
tableClass: "small",
onlyTemp: false,
showPrecipitationAmount: false,
colored: false,
showFeelsLike: true,
absoluteDates: false
absoluteDates: false,
hourlyForecastIncrements: 1
},
// Module properties.
@@ -58,13 +60,13 @@ Module.register("weather", {
// Return the scripts that are necessary for the weather module.
getScripts: function () {
return ["moment.js", this.file("../utils.js"), "weatherutils.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file("providers/" + this.config.weatherProvider.toLowerCase() + ".js")];
return ["moment.js", this.file("../utils.js"), "weatherutils.js", "weatherprovider.js", "weatherobject.js", "suncalc.js", this.file(`providers/${this.config.weatherProvider.toLowerCase()}.js`)];
},
// Override getHeader method.
getHeader: function () {
if (this.config.appendLocationNameToHeader && this.weatherProvider) {
if (this.data.header) return this.data.header + " " + this.weatherProvider.fetchedLocation();
if (this.data.header) return `${this.data.header} ${this.weatherProvider.fetchedLocation()}`;
else return this.weatherProvider.fetchedLocation();
}
@@ -137,13 +139,17 @@ Module.register("weather", {
// Add all the data to the template.
getTemplateData: function () {
const forecast = this.weatherProvider.weatherForecast();
const currentData = this.weatherProvider.currentWeather();
const forecastData = this.weatherProvider.weatherForecast();
// Skip some hourly forecast entries if configured
const hourlyData = this.weatherProvider.weatherHourly()?.filter((e, i) => (i + 1) % this.config.hourlyForecastIncrements === this.config.hourlyForecastIncrements - 1);
return {
config: this.config,
current: this.weatherProvider.currentWeather(),
forecast: forecast,
hourly: this.weatherProvider.weatherHourly(),
current: currentData,
forecast: forecastData,
hourly: hourlyData,
indoor: {
humidity: this.indoorHumidity,
temperature: this.indoorTemperature
@@ -225,9 +231,9 @@ Module.register("weather", {
this.nunjucksEnvironment().addFilter(
"unit",
function (value, type) {
function (value, type, valueUnit) {
if (type === "temperature") {
value = this.roundValue(WeatherUtils.convertTemp(value, this.config.tempUnits)) + "°";
value = `${this.roundValue(WeatherUtils.convertTemp(value, this.config.tempUnits))}°`;
if (this.config.degreeLabel) {
if (this.config.tempUnits === "metric") {
value += "C";
@@ -241,11 +247,7 @@ Module.register("weather", {
if (value === null || isNaN(value) || value === 0 || value.toFixed(2) === "0.00") {
value = "";
} else {
if (this.config.weatherProvider === "ukmetoffice" || this.config.weatherProvider === "ukmetofficedatahub") {
value += "%";
} else {
value = `${value.toFixed(2)} ${this.config.units === "imperial" ? "in" : "mm"}`;
}
value = WeatherUtils.convertPrecipitationUnit(value, valueUnit, this.config.units);
}
} else if (type === "humidity") {
value += "%";

View File

@@ -11,6 +11,10 @@
* Currently this is focused on the information which is necessary for the current weather.
* As soon as we start implementing the forecast, mode properties will be added.
*/
/**
* @external Moment
*/
class WeatherObject {
/**
* Constructor for a WeatherObject
@@ -18,7 +22,7 @@ class WeatherObject {
constructor() {
this.date = null;
this.windSpeed = null;
this.windDirection = null;
this.windFromDirection = null;
this.sunrise = null;
this.sunset = null;
this.temperature = null;
@@ -26,77 +30,65 @@ class WeatherObject {
this.maxTemperature = null;
this.weatherType = null;
this.humidity = null;
this.rain = null;
this.snow = null;
this.precipitation = null;
this.precipitationAmount = null;
this.precipitationUnits = null;
this.precipitationProbability = null;
this.feelsLikeTemp = null;
}
cardinalWindDirection() {
if (this.windDirection > 11.25 && this.windDirection <= 33.75) {
if (this.windFromDirection > 11.25 && this.windFromDirection <= 33.75) {
return "NNE";
} else if (this.windDirection > 33.75 && this.windDirection <= 56.25) {
} else if (this.windFromDirection > 33.75 && this.windFromDirection <= 56.25) {
return "NE";
} else if (this.windDirection > 56.25 && this.windDirection <= 78.75) {
} else if (this.windFromDirection > 56.25 && this.windFromDirection <= 78.75) {
return "ENE";
} else if (this.windDirection > 78.75 && this.windDirection <= 101.25) {
} else if (this.windFromDirection > 78.75 && this.windFromDirection <= 101.25) {
return "E";
} else if (this.windDirection > 101.25 && this.windDirection <= 123.75) {
} else if (this.windFromDirection > 101.25 && this.windFromDirection <= 123.75) {
return "ESE";
} else if (this.windDirection > 123.75 && this.windDirection <= 146.25) {
} else if (this.windFromDirection > 123.75 && this.windFromDirection <= 146.25) {
return "SE";
} else if (this.windDirection > 146.25 && this.windDirection <= 168.75) {
} else if (this.windFromDirection > 146.25 && this.windFromDirection <= 168.75) {
return "SSE";
} else if (this.windDirection > 168.75 && this.windDirection <= 191.25) {
} else if (this.windFromDirection > 168.75 && this.windFromDirection <= 191.25) {
return "S";
} else if (this.windDirection > 191.25 && this.windDirection <= 213.75) {
} else if (this.windFromDirection > 191.25 && this.windFromDirection <= 213.75) {
return "SSW";
} else if (this.windDirection > 213.75 && this.windDirection <= 236.25) {
} else if (this.windFromDirection > 213.75 && this.windFromDirection <= 236.25) {
return "SW";
} else if (this.windDirection > 236.25 && this.windDirection <= 258.75) {
} else if (this.windFromDirection > 236.25 && this.windFromDirection <= 258.75) {
return "WSW";
} else if (this.windDirection > 258.75 && this.windDirection <= 281.25) {
} else if (this.windFromDirection > 258.75 && this.windFromDirection <= 281.25) {
return "W";
} else if (this.windDirection > 281.25 && this.windDirection <= 303.75) {
} else if (this.windFromDirection > 281.25 && this.windFromDirection <= 303.75) {
return "WNW";
} else if (this.windDirection > 303.75 && this.windDirection <= 326.25) {
} else if (this.windFromDirection > 303.75 && this.windFromDirection <= 326.25) {
return "NW";
} else if (this.windDirection > 326.25 && this.windDirection <= 348.75) {
} else if (this.windFromDirection > 326.25 && this.windFromDirection <= 348.75) {
return "NNW";
} else {
return "N";
}
}
nextSunAction() {
return moment().isBetween(this.sunrise, this.sunset) ? "sunset" : "sunrise";
/**
* Determines if the sun sets or rises next. Uses the current time and not
* the date from the weather-forecast.
*
* @param {Moment} date an optional date where you want to get the next
* action for. Useful only in tests, defaults to the current time.
* @returns {string} "sunset" or "sunrise"
*/
nextSunAction(date = moment()) {
return date.isBetween(this.sunrise, this.sunset) ? "sunset" : "sunrise";
}
feelsLike() {
if (this.feelsLikeTemp) {
return this.feelsLikeTemp;
}
const windInMph = WeatherUtils.convertWind(this.windSpeed, "imperial");
const tempInF = WeatherUtils.convertTemp(this.temperature, "imperial");
let feelsLike = tempInF;
if (windInMph > 3 && tempInF < 50) {
feelsLike = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
} else if (tempInF > 80 && this.humidity > 40) {
feelsLike =
-42.379 +
2.04901523 * tempInF +
10.14333127 * this.humidity -
0.22475541 * tempInF * this.humidity -
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
5.481717 * Math.pow(10, -2) * this.humidity * this.humidity +
1.22874 * Math.pow(10, -3) * tempInF * tempInF * this.humidity +
8.5282 * Math.pow(10, -4) * tempInF * this.humidity * this.humidity -
1.99 * Math.pow(10, -6) * tempInF * tempInF * this.humidity * this.humidity;
}
return ((feelsLike - 32) * 5) / 9;
return WeatherUtils.calculateFeelsLike(this.temperature, this.windSpeed, this.humidity);
}
/**
@@ -105,7 +97,8 @@ class WeatherObject {
* @returns {boolean} true if it is at dayTime
*/
isDayTime() {
return this.date.isBetween(this.sunrise, this.sunset, undefined, "[]");
const now = !this.date ? moment() : this.date;
return now.isBetween(this.sunrise, this.sunset, undefined, "[]");
}
/**

View File

@@ -12,7 +12,7 @@ const WeatherUtils = {
* @returns {number} the speed in beaufort
*/
beaufortWindSpeed(speedInMS) {
const windInKmh = (speedInMS * 3600) / 1000;
const windInKmh = this.convertWind(speedInMS, "kmh");
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) {
@@ -22,6 +22,29 @@ const WeatherUtils = {
return 12;
},
/**
* Convert a value in a given unit to a string with a converted
* value and a postfix matching the output unit system.
*
* @param {number} value - The value to convert.
* @param {string} valueUnit - The unit the values has. Default is mm.
* @param {string} outputUnit - The unit system (imperial/metric) the return value should have.
* @returns {string} - A string with tha value and a unit postfix.
*/
convertPrecipitationUnit(value, valueUnit, outputUnit) {
if (outputUnit === "imperial") {
if (valueUnit && valueUnit.toLowerCase() === "cm") value = value * 0.3937007874;
else value = value * 0.03937007874;
valueUnit = "in";
} else {
valueUnit = valueUnit ? valueUnit : "mm";
}
if (valueUnit === "%") return `${value.toFixed(0)} ${valueUnit}`;
return `${value.toFixed(2)} ${valueUnit}`;
},
/**
* Convert temp (from degrees C) into imperial or metric unit depending on
* your config
@@ -90,6 +113,29 @@ const WeatherUtils = {
convertWindToMs(kmh) {
return kmh * 0.27777777777778;
},
calculateFeelsLike(temperature, windSpeed, humidity) {
const windInMph = this.convertWind(windSpeed, "imperial");
const tempInF = this.convertTemp(temperature, "imperial");
let feelsLike = tempInF;
if (windInMph > 3 && tempInF < 50) {
feelsLike = Math.round(35.74 + 0.6215 * tempInF - 35.75 * Math.pow(windInMph, 0.16) + 0.4275 * tempInF * Math.pow(windInMph, 0.16));
} else if (tempInF > 80 && humidity > 40) {
feelsLike =
-42.379 +
2.04901523 * tempInF +
10.14333127 * humidity -
0.22475541 * tempInF * humidity -
6.83783 * Math.pow(10, -3) * tempInF * tempInF -
5.481717 * Math.pow(10, -2) * humidity * humidity +
1.22874 * Math.pow(10, -3) * tempInF * tempInF * humidity +
8.5282 * Math.pow(10, -4) * tempInF * humidity * humidity -
1.99 * Math.pow(10, -6) * tempInF * tempInF * humidity * humidity;
}
return ((feelsLike - 32) * 5) / 9;
}
};