Release 2.31.0 (#3758)

Thanks to: @Developer-Incoming, @eltociear, @geraki, @khassel,
@KristjanESPERANTO, @MagMar94, @mixasgr, @n8many, @OWL4C, @rejas,
@savvadam, @sdetweil.

> ⚠️ This release needs nodejs version `v22.14.0 or higher`

### Added

- Add CSS support to the digital clock hour/minute/second through the
use of the classes `clock-hour-digital`, `clock-minute-digital`, and
`clock-second-digital`.
- Add Arabic (#3719) and Esperanto translation.
- Mark option `secondsColor` as deprecated in clock module.
- Add Greek translation to Alerts module.
- [newsfeed] Add specific ignoreOlderThan value (override) per feed
(#3360)
- [weather] Added option Humidity to hourly View
- [weather] Added option to hide hourly entries that are Zero, hiding
the entire column if empty.
- [updatenotification] Added option to iterate over modules directory
instead using modules defined in `config.js` (#3739)

### Changed

- [core] starting clientonly now checks for needed env var
`WAYLAND_DISPLAY` or `DISPLAY` and starts electron with needed
parameters (if both are set wayland is used) (#3677)
- [core] Optimize systeminformation calls and output (#3689)
- [core] Add issue templates for feature requests and bug reports
(#3695)
- [core] Adapt `start:x11:dev` script
- [weather/yr] The Yr weather provider now enforces a minimum
`updateInterval` of 600 000 ms (10 minutes) to comply with the terms of
service. If a lower value is set, it will be automatically increased to
this minimum.
- [weather/weatherflow] Fixed icons and added hourly support as well as
UV, precipitation, and location name support.
- [workflow] Run `sudo apt-get update` before installing packages to
avoid install errors
- [workflow] Exclude issues with label `ready (coming with next
release)` from stale job

### Removed

### Updated

- [core] Update requirements and dependencies including electron to v35
and formatting (#3593, #3693, #3717)
- [core] Update prettier, ESLint and simplify config
- Update Greek translation

### Fixed

- [calendar] Fix clipping events being broadcast (#3678)
- [tests] Fix Electron tests by running them under new github image
ubuntu-24.04, replace xserver with labwc, running under xserver and
labwc depending on env variable WAYLAND_DISPLAY is set (#3676)
- [calendar] Fix arrayed symbols, #3267, again, add testcase, add
testcase for #3678
- [weather] Fix wrong weatherCondition name in openmeteo provider which
lead to n/a icon (#3691)
- [core] Fix wrong port in log message when starting server only (#3696)
- [calendar] Fix NewYork event processed on system in Central timezone
shows wrong time #3701
- [weather/yr] The Yr weather provider is now able to recover from bad
API responses instead of freezing (#3296)
- [compliments] Fix evening events being shown during the day (#3727)
- [weather] Fixed minor spacing issues when using UV Index in Hourly
- [workflow] Fix command to run spellcheck

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Michael Teeuw <michael@xonaymedia.nl>
Co-authored-by: Kristjan ESPERANTO <35647502+KristjanESPERANTO@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Karsten Hassel <hassel@gmx.de>
Co-authored-by: Ross Younger <crazyscot@gmail.com>
Co-authored-by: Bugsounet - Cédric <github@bugsounet.fr>
Co-authored-by: jkriegshauser <joshuakr@nvidia.com>
Co-authored-by: illimarkangur <116028111+illimarkangur@users.noreply.github.com>
Co-authored-by: sam detweiler <sdetweil@gmail.com>
Co-authored-by: vppencilsharpener <tim.pray@gmail.com>
Co-authored-by: Paranoid93 <6515818+Paranoid93@users.noreply.github.com>
Co-authored-by: Brian O'Connor <btoconnor@users.noreply.github.com>
Co-authored-by: WallysWellies <59727507+WallysWellies@users.noreply.github.com>
Co-authored-by: Jason Stieber <jrstieber@gmail.com>
Co-authored-by: jargordon <50050429+jargordon@users.noreply.github.com>
Co-authored-by: Daniel <32464403+dkallen78@users.noreply.github.com>
Co-authored-by: Ryan Williams <65094007+ryan-d-williams@users.noreply.github.com>
Co-authored-by: Panagiotis Skias <panagiotis.skias@gmail.com>
Co-authored-by: Marc Landis <dirk.rettschlag@gmail.com>
Co-authored-by: HeikoGr <20295490+HeikoGr@users.noreply.github.com>
Co-authored-by: Pedro Lamas <pedrolamas@gmail.com>
Co-authored-by: veeck <gitkraken@veeck.de>
Co-authored-by: Magnus <34011212+MagMar94@users.noreply.github.com>
Co-authored-by: Ikko Eltociear Ashimine <eltociear@gmail.com>
Co-authored-by: DevIncomin <56730075+Developer-Incoming@users.noreply.github.com>
Co-authored-by: Nathan <n8nyoung@gmail.com>
Co-authored-by: mixasgr <mixasgr@users.noreply.github.com>
Co-authored-by: Savvas Adamtziloglou <savvas-gr@greeklug.gr>
Co-authored-by: Konstantinos <geraki@gmail.com>
Co-authored-by: OWL4C <124401812+OWL4C@users.noreply.github.com>
This commit is contained in:
Veeck
2025-04-01 20:11:02 +02:00
committed by GitHub
parent 9c9a5359dd
commit 39a614e0de
54 changed files with 2252 additions and 1093 deletions

View File

@@ -25,6 +25,7 @@ Module.register("alert", {
da: "translations/da.json",
de: "translations/de.json",
en: "translations/en.json",
eo: "translations/eo.json",
es: "translations/es.json",
fr: "translations/fr.json",
hu: "translations/hu.json",

View File

@@ -0,0 +1,4 @@
{
"sysTitle": "MagicMirror² Ειδοποίηση",
"welcome": "Καλώς ήρθατε, η εκκίνηση ήταν επιτυχής!"
}

View File

@@ -0,0 +1,4 @@
{
"sysTitle": "MagicMirror²-sciigo",
"welcome": "Bonvenon, lanĉo sukcesis!"
}

View File

@@ -689,12 +689,17 @@ Module.register("calendar", {
by_url_calevents.push(event);
}
}
by_url_calevents.sort(function (a, b) {
return a.startDate - b.startDate;
});
Log.debug(`pushing ${by_url_calevents.length} events to total with room for ${remainingEntries}`);
events = events.concat(by_url_calevents.slice(0, remainingEntries));
Log.debug(`events for calendar=${events.length}`);
if (limitNumberOfEntries) {
// sort entries before clipping
by_url_calevents.sort(function (a, b) {
return a.startDate - b.startDate;
});
Log.debug(`pushing ${by_url_calevents.length} events to total with room for ${remainingEntries}`);
events = events.concat(by_url_calevents.slice(0, remainingEntries));
Log.debug(`events for calendar=${events.length}`);
} else {
events = events.concat(by_url_calevents);
}
}
Log.info(`sorting events count=${events.length}`);
events.sort(function (a, b) {
@@ -907,7 +912,11 @@ Module.register("calendar", {
let p = this.getCalendarProperty(url, property, defaultValue);
if (property === "symbol" || property === "recurringSymbol" || property === "fullDaySymbol") {
const className = this.getCalendarProperty(url, "symbolClassName", this.config.defaultSymbolClassName);
if (p instanceof Array) p.push(className);
if (p instanceof Array) {
let t = [];
p.forEach((n) => { t.push(className + n); });
p = t;
}
else p = className + p;
}
if (!(p instanceof Array)) p = [p];

View File

@@ -662,9 +662,11 @@ const CalendarFetcherUtils = {
Log.debug("signs are the same");
if (Math.sign(eventDiff) === -1) {
//if west, looking at more west
// -350 <-300
if (nowDiff < eventDiff) {
//-600 -420
eventDiff = -(eventDiff - (nowDiff - eventDiff)); //-180
//300 -300 -360 +300
eventDiff = nowDiff - eventDiff; //-180
Log.debug("now looking back east delta diff=", eventDiff);
}
else {

View File

@@ -23,7 +23,7 @@ Module.register("clock", {
analogFace: "simple", // options: 'none', 'simple', 'face-###' (where ### is 001 to 012 inclusive)
analogPlacement: "bottom", // options: 'top', 'bottom', 'left', 'right'
analogShowDate: "top", // OBSOLETE, can be replaced with analogPlacement and showTime, options: false, 'top', or 'bottom'
secondsColor: "#888888",
secondsColor: "#888888", // DEPRECATED, use CSS instead. Class "clock-second-digital" for digital clock, "clock-second" for analog clock.
showSunTimes: false,
showMoonTimes: false, // options: false, 'times' (rise/set), 'percent' (lit percent), 'phase' (current phase), or 'both' (percent & phase)
@@ -105,6 +105,8 @@ Module.register("clock", {
*/
const dateWrapper = document.createElement("div");
const timeWrapper = document.createElement("div");
const hoursWrapper = document.createElement("span");
const minutesWrapper = document.createElement("span");
const secondsWrapper = document.createElement("sup");
const periodWrapper = document.createElement("span");
const sunWrapper = document.createElement("div");
@@ -114,39 +116,40 @@ Module.register("clock", {
// Style Wrappers
dateWrapper.className = "date normal medium";
timeWrapper.className = "time bright large light";
secondsWrapper.className = "seconds dimmed";
hoursWrapper.className = "clock-hour-digital";
minutesWrapper.className = "clock-minute-digital";
secondsWrapper.className = "clock-second-digital dimmed";
sunWrapper.className = "sun dimmed small";
moonWrapper.className = "moon dimmed small";
weekWrapper.className = "week dimmed medium";
// Set content of wrappers.
// The moment().format("h") method has a bug on the Raspberry Pi.
// So we need to generate the timestring manually.
// See issue: https://github.com/MagicMirrorOrg/MagicMirror/issues/181
let timeString;
const now = moment();
if (this.config.timezone) {
now.tz(this.config.timezone);
}
let hourSymbol = "HH";
if (this.config.timeFormat !== 24) {
hourSymbol = "h";
}
if (this.config.clockBold) {
timeString = now.format(`${hourSymbol}[<span class="bold">]mm[</span>]`);
} else {
timeString = now.format(`${hourSymbol}:mm`);
}
if (this.config.showDate) {
dateWrapper.innerHTML = now.format(this.config.dateFormat);
digitalWrapper.appendChild(dateWrapper);
}
if (this.config.displayType !== "analog" && this.config.showTime) {
timeWrapper.innerHTML = timeString;
let hourSymbol = "HH";
if (this.config.timeFormat !== 24) {
hourSymbol = "h";
}
hoursWrapper.innerHTML = now.format(hourSymbol);
minutesWrapper.innerHTML = now.format("mm");
timeWrapper.appendChild(hoursWrapper);
if (this.config.clockBold) {
minutesWrapper.classList.add("bold");
} else {
timeWrapper.innerHTML += ":";
}
timeWrapper.appendChild(minutesWrapper);
secondsWrapper.innerHTML = now.format("ss");
if (this.config.showPeriodUpper) {
periodWrapper.innerHTML = now.format("A");
@@ -181,8 +184,8 @@ Module.register("clock", {
const untilNextEventString = `${untilNextEvent.hours()}h ${untilNextEvent.minutes()}m`;
sunWrapper.innerHTML
= `<span class="${isVisible ? "bright" : ""}"><i class="fas fa-sun" aria-hidden="true"></i> ${untilNextEventString}</span>`
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunrise)}</span>`
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunset)}</span>`;
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunrise)}</span>`
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${formatTime(this.config, sunTimes.sunset)}</span>`;
digitalWrapper.appendChild(sunWrapper);
}
@@ -208,8 +211,8 @@ Module.register("clock", {
moonWrapper.innerHTML
= `<span class="${isVisible ? "bright" : ""}">${image} ${showFraction ? illuminatedFractionString : ""}</span>`
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${moonRise ? formatTime(this.config, moonRise) : "..."}</span>`
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${moonSet ? formatTime(this.config, moonSet) : "..."}</span>`;
+ `<span><i class="fas fa-arrow-up" aria-hidden="true"></i> ${moonRise ? formatTime(this.config, moonRise) : "..."}</span>`
+ `<span><i class="fas fa-arrow-down" aria-hidden="true"></i> ${moonSet ? formatTime(this.config, moonSet) : "..."}</span>`;
digitalWrapper.appendChild(moonWrapper);
}
@@ -267,7 +270,7 @@ Module.register("clock", {
clockSecond.id = "clock-second";
clockSecond.style.transform = `rotate(${second}deg)`;
clockSecond.className = "clock-second";
clockSecond.style.backgroundColor = this.config.secondsColor;
clockSecond.style.backgroundColor = this.config.secondsColor; /* DEPRECATED, to be removed in a future version , use CSS instead */
clockFace.appendChild(clockSecond);
}
analogWrapper.appendChild(clockFace);

View File

@@ -78,7 +78,12 @@
left: 50%;
margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
padding: 38% 1px 0 0; /* indicator length & thickness */
background: var(--color-text);
/* background: #888888 !important; */
/* use this instead of secondsColor */
/* have to use !important, because the code explicitly sets the color currently */
transform-origin: 50% 100%;
}
@@ -91,3 +96,15 @@
.module.clock .moon > * {
flex: 1;
}
.module.clock .clock-hour-digital {
color: var(--color-text-bright);
}
.module.clock .clock-minute-digital {
color: var(--color-text-bright);
}
.module.clock .clock-second-digital {
color: var(--color-text-dimmed);
}

View File

@@ -139,12 +139,17 @@ Module.register("compliments", {
let compliments = [];
// Add time of day compliments
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime && this.config.compliments.hasOwnProperty("morning")) {
compliments = [...this.config.compliments.morning];
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime && this.config.compliments.hasOwnProperty("afternoon")) {
compliments = [...this.config.compliments.afternoon];
} else if (this.config.compliments.hasOwnProperty("evening")) {
compliments = [...this.config.compliments.evening];
let timeOfDay;
if (hour >= this.config.morningStartTime && hour < this.config.morningEndTime) {
timeOfDay = "morning";
} else if (hour >= this.config.afternoonStartTime && hour < this.config.afternoonEndTime) {
timeOfDay = "afternoon";
} else {
timeOfDay = "evening";
}
if (timeOfDay && this.config.compliments.hasOwnProperty(timeOfDay)) {
compliments = [...this.config.compliments[timeOfDay]];
}
// Add compliments based on weather

View File

@@ -57,7 +57,7 @@ Module.register("newsfeed", {
// Define required translations.
getTranslations () {
// The translations for the default modules are defined in the core translation files.
// Therefor we can just return false. Otherwise we should have returned a dictionary.
// Therefore we can just return false. Otherwise we should have returned a dictionary.
// If you're trying to build your own module including translations, check out the documentation.
return false;
},
@@ -177,6 +177,18 @@ Module.register("newsfeed", {
}
},
/**
* Gets a feed property by name
* @param {object} feed A feed object.
* @param {string} property The name of the property.
*/
getFeedProperty (feed, property) {
let res = this.config[property];
const f = this.config.feeds.find((feedItem) => feedItem.url === feed);
if (f && f[property]) res = f[property];
return res;
},
/**
* Generate an ordered list of items for this configured module.
* @param {object} feeds An object with feeds returned by the node helper.
@@ -188,7 +200,7 @@ Module.register("newsfeed", {
if (this.subscribedToFeed(feed)) {
for (let item of feedItems) {
item.sourceTitle = this.titleForFeed(feed);
if (!(this.config.ignoreOldItems && Date.now() - new Date(item.pubdate) > this.config.ignoreOlderThan)) {
if (!(this.getFeedProperty(feed, "ignoreOldItems") && Date.now() - new Date(item.pubdate) > this.getFeedProperty(feed, "ignoreOlderThan"))) {
newsItems.push(item);
}
}

View File

@@ -1,3 +1,5 @@
const fs = require("node:fs");
const path = require("node:path");
const NodeHelper = require("node_helper");
const defaultModules = require("../defaultmodules");
const GitHelper = require("./git_helper");
@@ -14,8 +16,23 @@ module.exports = NodeHelper.create({
gitHelper: new GitHelper(),
updateHelper: null,
getModules (modules) {
if (this.config.useModulesFromConfig) {
return modules;
} else {
// get modules from modules-directory
const moduleDir = path.normalize(`${__dirname}/../../`);
const getDirectories = (source) => {
return fs.readdirSync(source, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory() && dirent.name !== "default")
.map((dirent) => dirent.name);
};
return getDirectories(moduleDir);
}
},
async configureModules (modules) {
for (const moduleName of modules) {
for (const moduleName of this.getModules(modules)) {
if (!this.ignoreUpdateChecking(moduleName)) {
await this.gitHelper.add(moduleName);
}

View File

@@ -6,7 +6,8 @@ Module.register("updatenotification", {
sendUpdatesNotifications: false,
updates: [],
updateTimeout: 2 * 60 * 1000, // max update duration
updateAutorestart: false // autoRestart MM when update done ?
updateAutorestart: false, // autoRestart MM when update done ?
useModulesFromConfig: true // if `false` iterate over modules directory
},
suspended: false,

View File

@@ -21,15 +21,25 @@
{% endif %}
</td>
{% endif %}
{% if config.showHumidity != "none" %}
<td class="align-left bright humidity-hourly">
{{ hour.humidity }}
<span class="wi wi-humidity humidity-icon"></span>
</td>
{% endif %}
{% if config.showPrecipitationAmount %}
<td class="align-right bright precipitation-amount">
{{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }}
</td>
{% if (not config.hideZeroes or hour.precipitationAmount>0) %}
<td class="align-right bright precipitation-amount">
{{ hour.precipitationAmount | unit("precip", hour.precipitationUnits) }}
</td>
{% endif %}
{% endif %}
{% if config.showPrecipitationProbability %}
{% if (not config.hideZeroes or hour.precipitationAmount>0) %}
<td class="align-right bright precipitation-prob">
{{ hour.precipitationProbability | unit('precip', '%') }}
{{ hour.precipitationProbability | unit('precip', '%') }}
</td>
{% endif %}
{% endif %}
</tr>
{% set currentStep = currentStep + 1 %}

View File

@@ -470,7 +470,7 @@ WeatherProvider.register("openmeteo", {
61: "rain-slight-intensity",
63: "rain-moderate-intensity",
65: "rain-heavy-intensity",
66: "freezing-rain-light-heavy-intensity",
66: "freezing-rain-light-intensity",
67: "freezing-rain-heavy-intensity",
71: "snow-fall-slight-intensity",
73: "snow-fall-moderate-intensity",

View File

@@ -25,14 +25,20 @@ WeatherProvider.register("weatherflow", {
const currentWeather = new WeatherObject();
currentWeather.date = moment();
// Other available values: air_density, brightness, delta_t, dew_point,
// pressure_trend (i.e. rising/falling), sea_level_pressure, wind gust, and more.
currentWeather.humidity = data.current_conditions.relative_humidity;
currentWeather.temperature = data.current_conditions.air_temperature;
currentWeather.feelsLikeTemp = data.current_conditions.feels_like;
currentWeather.windSpeed = WeatherUtils.convertWindToMs(data.current_conditions.wind_avg);
currentWeather.windFromDirection = data.current_conditions.wind_direction;
currentWeather.weatherType = data.forecast.daily[0].icon;
currentWeather.weatherType = this.convertWeatherType(data.current_conditions.icon);
currentWeather.uv_index = data.current_conditions.uv;
currentWeather.sunrise = moment.unix(data.forecast.daily[0].sunrise);
currentWeather.sunset = moment.unix(data.forecast.daily[0].sunset);
this.setCurrentWeather(currentWeather);
this.fetchedLocationName = data.location_name;
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
@@ -52,13 +58,27 @@ WeatherProvider.register("weatherflow", {
weather.minTemperature = forecast.air_temp_low;
weather.maxTemperature = forecast.air_temp_high;
weather.precipitationProbability = forecast.precip_probability;
weather.weatherType = forecast.icon;
weather.snow = 0;
weather.weatherType = this.convertWeatherType(forecast.icon);
// Must manually build UV and Precipitation from hourly
weather.precipitationAmount = 0.0; // This will sum up rain and snow
weather.precipitationUnits = "mm";
weather.uv_index = 0;
for (const hour of data.forecast.hourly) {
const hour_time = moment.unix(hour.time);
if (hour_time.day() === weather.date.day()) { // Iterate though until day is reached
// Get data from today
weather.uv_index = Math.max(weather.uv_index, hour.uv);
weather.precipitationAmount += (hour.precip ?? 0);
} else if (hour_time.diff(weather.date) >= 86400) {
break; // No more data to be found
}
}
days.push(weather);
}
this.setWeatherForecast(days);
this.fetchedLocationName = data.location_name;
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
@@ -66,6 +86,63 @@ WeatherProvider.register("weatherflow", {
.finally(() => this.updateAvailable());
},
fetchWeatherHourly () {
this.fetchData(this.getUrl())
.then((data) => {
const hours = [];
for (const hour of data.forecast.hourly) {
const weather = new WeatherObject();
weather.date = moment.unix(hour.time);
weather.temperature = hour.air_temperature;
weather.feelsLikeTemp = hour.feels_like;
weather.humidity = hour.relative_humidity;
weather.windSpeed = hour.wind_avg;
weather.windFromDirection = hour.wind_direction;
weather.weatherType = this.convertWeatherType(hour.icon);
weather.precipitationProbability = hour.precip_probability;
weather.precipitationAmount = hour.precip; // NOTE: precipitation type is available
weather.precipitationUnits = "mm"; // Hardcoded via request, TODO: Add conversion
weather.uv_index = hour.uv;
hours.push(weather);
if (hours.length >= 48) break; // 10 days of hours are available, best to trim down.
}
this.setWeatherHourly(hours);
this.fetchedLocationName = data.location_name;
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
},
convertWeatherType (weatherType) {
const weatherTypes = {
"clear-day": "day-sunny",
"clear-night": "night-clear",
cloudy: "cloudy",
foggy: "fog",
"partly-cloudy-day": "day-cloudy",
"partly-cloudy-night": "night-alt-cloudy",
"possibly-rainy-day": "day-rain",
"possibly-rainy-night": "night-alt-rain",
"possibly-sleet-day": "day-sleet",
"possibly-sleet-night": "night-alt-sleet",
"possibly-snow-day": "day-snow",
"possibly-snow-night": "night-alt-snow",
"possibly-thunderstorm-day": "day-thunderstorm",
"possibly-thunderstorm-night": "night-alt-thunderstorm",
rainy: "rain",
sleet: "sleet",
snow: "snow",
thunderstorm: "thunderstorm",
windy: "strong-wind"
};
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
},
// Create a URL from the config and base URL.
getUrl () {
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}`;

View File

@@ -23,6 +23,10 @@ WeatherProvider.register("yr", {
Log.error("The Yr weather provider requires local storage.");
throw new Error("Local storage not available");
}
if (this.config.updateInterval < 600000) {
Log.warn("The Yr weather provider requires a minimum update interval of 10 minutes (600 000 ms). The configuration has been adjusted to meet this requirement.");
this.delegate.config.updateInterval = 600000;
}
Log.info(`Weather provider: ${this.providerName} started.`);
},
@@ -34,7 +38,7 @@ WeatherProvider.register("yr", {
})
.catch((error) => {
Log.error(error);
throw new Error(error);
this.updateAvailable();
});
},
@@ -119,7 +123,12 @@ WeatherProvider.register("yr", {
})
.catch((err) => {
Log.error(err);
reject("Unable to get weather data from Yr.");
if (weatherData) {
Log.warn("Using outdated cached weather data.");
resolve(weatherData);
} else {
reject("Unable to get weather data from Yr.");
}
})
.finally(() => {
localStorage.removeItem("yrIsFetchingWeatherData");
@@ -497,7 +506,7 @@ WeatherProvider.register("yr", {
})
.catch((error) => {
Log.error(error);
throw new Error(error);
this.updateAvailable();
});
},
@@ -530,7 +539,15 @@ WeatherProvider.register("yr", {
getHourlyForecastFrom (weatherData) {
const series = [];
const now = moment({
year: moment().year(),
month: moment().month(),
day: moment().date(),
hour: moment().hour()
});
for (const forecast of weatherData.properties.timeseries) {
if (now.isAfter(moment(forecast.time))) continue;
forecast.symbol = forecast.data.next_1_hours?.summary?.symbol_code;
forecast.precipitationAmount = forecast.data.next_1_hours?.details?.precipitation_amount;
forecast.precipitationProbability = forecast.data.next_1_hours?.details?.probability_of_precipitation;
@@ -600,7 +617,7 @@ WeatherProvider.register("yr", {
})
.catch((error) => {
Log.error(error);
throw new Error(error);
this.updateAvailable();
});
}
});

View File

@@ -31,6 +31,7 @@
.weather .precipitation-amount,
.weather .precipitation-prob,
.weather .humidity-hourly,
.weather .uv-index {
padding-left: 20px;
padding-right: 0;

View File

@@ -14,7 +14,8 @@ Module.register("weather", {
updateInterval: 10 * 60 * 1000, // every 10 minutes
animationSpeed: 1000,
showFeelsLike: true,
showHumidity: "none", // this is now a string; see current.njk
showHumidity: "none", // possible options for "current" weather are "none", "wind", "temp", "feelslike" or "below", for "hourly" weather "none" or "true"
hideZeroes: false, // hide zeroes (and empty columns) in hourly, currently only for precipitation
showIndoorHumidity: false,
showIndoorTemperature: false,
allowOverrideNotification: false,

View File

@@ -128,14 +128,14 @@ const WeatherUtils = {
} 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;
+ 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;