mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-07-10 01:04:28 +00:00
## [2.27.0] - 2024-04-01 Thanks to: @bugsounet, @crazyscot, @illimarkangur, @jkriegshauser, @khassel, @KristjanESPERANTO, @Paranoid93, @rejas, @sdetweil and @vppencilsharpener. This release marks the first release without Michael Teeuw (@michmich). A very special thanks to him for creating MagicMirror and leading the project for so many years. For more info, please read the following post: [A New Chapter for MagicMirror: The Community Takes the Lead](https://forum.magicmirror.builders/topic/18329/a-new-chapter-for-magicmirror-the-community-takes-the-lead). ### Added - Output of system information to the console for troubleshooting (#3328 and #3337), ignore errors under aarch64 (#3349) - [chore] Add `eslint-plugin-package-json` to lint the `package.json` files (#3368) - [weather] `showHumidity` config is now a string describing where to show this element. Supported values: "wind", "temp", "feelslike", "below", "none". (#3330) - electron-rebuild test suite for electron and 3rd party modules compatibility (#3392) - Create MM² icon and attach it to electron process (#3407) ### Updated - Update updatenotification (update_helper.js): Recode with pm2 library (#3332) - Removing lodash dependency by replacing merge by spread operator (#3339) - Use node prefix for build-in modules (#3340) - Rework logging colors (#3350) - Update pm2 to v5.3.1 with no allow-ghsas (#3364) - [chore] Update husky and let lint-staged fix ESLint issues - [chore] Update dependencies including electron to v29 (#3357) and node-ical - Update translations for estonian (#3371) - Update electron to v29 and update other dependencies - [calendar] fullDay events over several days now show the left days from the first day on and 'today' on the last day - Update layout of current weather indoor values ### Fixed - Correct apibase of weathergov weatherprovider to match documentation (#2926) - Worked around several issues in the RRULE library that were causing deleted calender events to still show, some initial and recurring events to not show, and some event times to be off an hour. (#3291) - Skip changelog requirement when running tests for dependency updates (#3320) - Display precipitation probability when it is 0% instead of blank/empty (#3345) - [newsfeed] Suppress unsightly animation cases when there are 0 or 1 active news items (#3336) - [newsfeed] Always compute the feed item URL using the same helper function (#3336) - Ignore all custom css files (#3359) - [newsfeed] Fix newsfeed stall issue introduced by #3336 (#3361) - Changed `log.debug` to `log.log` in `app.js` where logLevel is not set because config is not loaded at this time (#3353) - [calendar] deny fetch interval < 60000 and set 60000 in this case (prevent fetch loop failed) (#3382) - added message in case where config.js is missing the module.export line PR #3383 - Fixed an issue where recurring events could extend past their recurrence end date (#3393) - Don't display any `npm WARN <....>` on install (#3399) - Fixed move suncalc dependency to production from dev, as it is used by clock module - [compliments] Fix mirror not responding anymore when no compliments are to be shown (#3385) ### Deleted - Unneeded file headers (#3358) --------- 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>
265 lines
10 KiB
JavaScript
265 lines
10 KiB
JavaScript
/* global WeatherProvider, WeatherObject */
|
|
|
|
/* This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
|
|
* For more information on Data Hub, see https://www.metoffice.gov.uk/services/data/datapoint/notifications/weather-datahub
|
|
* Data available:
|
|
* Hourly data for next 2 days ("hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-hourly.pdf
|
|
* 3-hourly data for the next 7 days ("3hourly") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-3-hourly.pdf
|
|
* Daily data for the next 7 days ("daily") - https://www.metoffice.gov.uk/binaries/content/assets/metofficegovuk/pdf/data/global-spot-data-daily.pdf
|
|
*
|
|
* NOTES
|
|
* This provider requires longitude/latitude coordinates, rather than a location ID (as with the previous Met Office provider)
|
|
* Provide the following in your config.js file:
|
|
* weatherProvider: "ukmetofficedatahub",
|
|
* apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
|
|
* apiKey: "[YOUR API KEY]",
|
|
* apiSecret: "[YOUR API SECRET]",
|
|
* lat: [LATITUDE (DECIMAL)],
|
|
* lon: [LONGITUDE (DECIMAL)]
|
|
*
|
|
* At time of writing, free accounts are limited to 360 requests a day per service (hourly, 3hourly, daily); take this in mind when
|
|
* setting your update intervals. For reference, 360 requests per day is once every 4 minutes.
|
|
*
|
|
* Pay attention to the units of the supplied data from the Met Office - it is given in SI/metric units where applicable:
|
|
* - Temperatures are in degrees Celsius (°C)
|
|
* - Wind speeds are in metres per second (m/s)
|
|
* - Wind direction given in degrees (°)
|
|
* - Pressures are in Pascals (Pa)
|
|
* - Distances are in metres (m)
|
|
* - Probabilities and humidity are given as percentages (%)
|
|
* - Precipitation is measured in millimetres (mm) with rates per hour (mm/h)
|
|
*
|
|
* See the PDFs linked above for more information on the data their corresponding units.
|
|
*/
|
|
|
|
WeatherProvider.register("ukmetofficedatahub", {
|
|
// Set the name of the provider.
|
|
providerName: "UK Met Office (DataHub)",
|
|
|
|
// Set the default config properties that is specific to this provider
|
|
defaults: {
|
|
apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
|
|
apiKey: "",
|
|
apiSecret: "",
|
|
lat: 0,
|
|
lon: 0
|
|
},
|
|
|
|
// 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}`;
|
|
|
|
// Return URL, making sure there is a trailing "/" in the base URL.
|
|
return this.config.apiBase + (this.config.apiBase.endsWith("/") ? "" : "/") + forecastType + queryStrings;
|
|
},
|
|
|
|
// Build the list of headers for the request
|
|
// For DataHub requests, the API key/secret are sent in the headers rather than as query strings.
|
|
// Headers defined according to Data Hub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
|
|
getHeaders () {
|
|
return {
|
|
accept: "application/json",
|
|
"x-ibm-client-id": this.config.apiKey,
|
|
"x-ibm-client-secret": this.config.apiSecret
|
|
};
|
|
},
|
|
|
|
// Fetch data using supplied URL and request headers
|
|
async fetchWeather (url, headers) {
|
|
const response = await fetch(url, { headers: headers });
|
|
|
|
// Return JSON data
|
|
return response.json();
|
|
},
|
|
|
|
// Fetch hourly forecast data (to use for current weather)
|
|
fetchCurrentWeather () {
|
|
this.fetchWeather(this.getUrl("hourly"), this.getHeaders())
|
|
.then((data) => {
|
|
// Check data is usable
|
|
if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) {
|
|
// Did not receive usable new data.
|
|
// Maybe this needs a better check?
|
|
Log.error("Possibly bad current/hourly data?");
|
|
Log.error(data);
|
|
return;
|
|
}
|
|
|
|
// Set location name
|
|
this.setFetchedLocation(`${data.features[0].properties.location.name}`);
|
|
|
|
// Generate current weather data
|
|
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data);
|
|
this.setCurrentWeather(currentWeather);
|
|
})
|
|
|
|
// Catch any error(s)
|
|
.catch((error) => Log.error(`Could not load data: ${error.message}`))
|
|
|
|
// Let the module know there is data available
|
|
.finally(() => this.updateAvailable());
|
|
},
|
|
|
|
// Create a WeatherObject using current weather data (data for the current hour)
|
|
generateWeatherObjectFromCurrentWeather (currentWeatherData) {
|
|
const currentWeather = new WeatherObject();
|
|
|
|
// Extract the actual forecasts
|
|
let forecastDataHours = currentWeatherData.features[0].properties.timeSeries;
|
|
|
|
// Define now
|
|
let nowUtc = moment.utc();
|
|
|
|
// Find hour that contains the current time
|
|
for (let hour in forecastDataHours) {
|
|
let forecastTime = moment.utc(forecastDataHours[hour].time);
|
|
if (nowUtc.isSameOrAfter(forecastTime) && nowUtc.isBefore(moment(forecastTime.add(1, "h")))) {
|
|
currentWeather.date = forecastTime;
|
|
currentWeather.windSpeed = forecastDataHours[hour].windSpeed10m;
|
|
currentWeather.windFromDirection = forecastDataHours[hour].windDirectionFrom10m;
|
|
currentWeather.temperature = forecastDataHours[hour].screenTemperature;
|
|
currentWeather.minTemperature = forecastDataHours[hour].minScreenAirTemp;
|
|
currentWeather.maxTemperature = forecastDataHours[hour].maxScreenAirTemp;
|
|
currentWeather.weatherType = this.convertWeatherType(forecastDataHours[hour].significantWeatherCode);
|
|
currentWeather.humidity = forecastDataHours[hour].screenRelativeHumidity;
|
|
currentWeather.rain = forecastDataHours[hour].totalPrecipAmount;
|
|
currentWeather.snow = forecastDataHours[hour].totalSnowAmount;
|
|
currentWeather.precipitationProbability = forecastDataHours[hour].probOfPrecipitation;
|
|
currentWeather.feelsLikeTemp = forecastDataHours[hour].feelsLikeTemperature;
|
|
|
|
// Pass on full details, so they can be used in custom templates
|
|
// Note the units of the supplied data when using this (see top of file)
|
|
currentWeather.rawData = forecastDataHours[hour];
|
|
}
|
|
}
|
|
|
|
// Determine the sunrise/sunset times - (still) not supplied in UK Met Office data
|
|
// Passes {longitude, latitude} to SunCalc, could pass height to, but
|
|
// SunCalc.getTimes doesn't take that into account
|
|
currentWeather.updateSunTime(this.config.lat, this.config.lon);
|
|
|
|
return currentWeather;
|
|
},
|
|
|
|
// Fetch daily forecast data
|
|
fetchWeatherForecast () {
|
|
this.fetchWeather(this.getUrl("daily"), this.getHeaders())
|
|
.then((data) => {
|
|
// Check data is usable
|
|
if (!data || !data.features || !data.features[0].properties || !data.features[0].properties.timeSeries || data.features[0].properties.timeSeries.length === 0) {
|
|
// Did not receive usable new data.
|
|
// Maybe this needs a better check?
|
|
Log.error("Possibly bad forecast data?");
|
|
Log.error(data);
|
|
return;
|
|
}
|
|
|
|
// Set location name
|
|
this.setFetchedLocation(`${data.features[0].properties.location.name}`);
|
|
|
|
// Generate the forecast data
|
|
const forecast = this.generateWeatherObjectsFromForecast(data);
|
|
this.setWeatherForecast(forecast);
|
|
})
|
|
|
|
// Catch any error(s)
|
|
.catch((error) => Log.error(`Could not load data: ${error.message}`))
|
|
|
|
// Let the module know there is new data available
|
|
.finally(() => this.updateAvailable());
|
|
},
|
|
|
|
// Create a WeatherObject for each day using daily forecast data
|
|
generateWeatherObjectsFromForecast (forecasts) {
|
|
const dailyForecasts = [];
|
|
|
|
// Extract the actual forecasts
|
|
let forecastDataDays = forecasts.features[0].properties.timeSeries;
|
|
|
|
// Define today
|
|
let today = moment.utc().startOf("date");
|
|
|
|
// Go through each day in the forecasts
|
|
for (let day in forecastDataDays) {
|
|
const forecastWeather = new WeatherObject();
|
|
|
|
// Get date of forecast
|
|
let forecastDate = moment.utc(forecastDataDays[day].time);
|
|
|
|
// Check if forecast is for today or in the future (i.e., ignore yesterday's forecast)
|
|
if (forecastDate.isSameOrAfter(today)) {
|
|
forecastWeather.date = forecastDate;
|
|
forecastWeather.minTemperature = forecastDataDays[day].nightMinScreenTemperature;
|
|
forecastWeather.maxTemperature = forecastDataDays[day].dayMaxScreenTemperature;
|
|
|
|
// Using daytime forecast values
|
|
forecastWeather.windSpeed = forecastDataDays[day].midday10MWindSpeed;
|
|
forecastWeather.windFromDirection = forecastDataDays[day].midday10MWindDirection;
|
|
forecastWeather.weatherType = this.convertWeatherType(forecastDataDays[day].daySignificantWeatherCode);
|
|
forecastWeather.precipitationProbability = forecastDataDays[day].dayProbabilityOfPrecipitation;
|
|
forecastWeather.temperature = forecastDataDays[day].dayMaxScreenTemperature;
|
|
forecastWeather.humidity = forecastDataDays[day].middayRelativeHumidity;
|
|
forecastWeather.rain = forecastDataDays[day].dayProbabilityOfRain;
|
|
forecastWeather.snow = forecastDataDays[day].dayProbabilityOfSnow;
|
|
forecastWeather.feelsLikeTemp = forecastDataDays[day].dayMaxFeelsLikeTemp;
|
|
|
|
// Pass on full details, so they can be used in custom templates
|
|
// Note the units of the supplied data when using this (see top of file)
|
|
forecastWeather.rawData = forecastDataDays[day];
|
|
|
|
dailyForecasts.push(forecastWeather);
|
|
}
|
|
}
|
|
|
|
return dailyForecasts;
|
|
},
|
|
|
|
// Set the fetched location name.
|
|
setFetchedLocation (name) {
|
|
this.fetchedLocationName = name;
|
|
},
|
|
|
|
// Match the Met Office "significant weather code" to a weathericons.css icon
|
|
// Use: https://metoffice.apiconnect.ibmcloud.com/metoffice/production/node/264
|
|
// and: https://erikflowers.github.io/weather-icons/
|
|
convertWeatherType (weatherType) {
|
|
const weatherTypes = {
|
|
0: "night-clear",
|
|
1: "day-sunny",
|
|
2: "night-alt-cloudy",
|
|
3: "day-cloudy",
|
|
5: "fog",
|
|
6: "fog",
|
|
7: "cloudy",
|
|
8: "cloud",
|
|
9: "night-sprinkle",
|
|
10: "day-sprinkle",
|
|
11: "raindrops",
|
|
12: "sprinkle",
|
|
13: "night-alt-showers",
|
|
14: "day-showers",
|
|
15: "rain",
|
|
16: "night-alt-sleet",
|
|
17: "day-sleet",
|
|
18: "sleet",
|
|
19: "night-alt-hail",
|
|
20: "day-hail",
|
|
21: "hail",
|
|
22: "night-alt-snow",
|
|
23: "day-snow",
|
|
24: "snow",
|
|
25: "night-alt-snow",
|
|
26: "day-snow",
|
|
27: "snow",
|
|
28: "night-alt-thunderstorm",
|
|
29: "day-thunderstorm",
|
|
30: "thunderstorm"
|
|
};
|
|
|
|
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
|
|
}
|
|
});
|