mirror of
https://github.com/MichMich/MagicMirror.git
synced 2025-08-21 04:45:17 +00:00
Release 2.29.0 (#3568)
## [2.29.0] - 2024-10-01 Thanks to: @bugsounet, @dkallen78, @jargordon, @khassel, @KristjanESPERANTO, @MarcLandis, @rejas, @ryan-d-williams, @sdetweil, @skpanagiotis. > ⚠️ This release needs nodejs version `v20` or `v22`, minimum version is `v20.9.0` ### Added - [compliments] Added support for cron type date/time format entries mm hh DD MM dow (minutes/hours/days/months and day of week) see https://crontab.cronhub.io for construction (#3481) - [core] Check config at every start of MagicMirror² (#3450) - [core] Add spelling check (cspell): `npm run test:spelling` and handle spelling issues (#3544) - [core] removed `config.paths.vendor` (could not work because `vendor` is hardcoded in `index.html`), renamed `config.paths.modules` to `config.foreignModulesDir`, added variable `MM_CUSTOMCSS_FILE` which - if set - overrides `config.customCss`, added variable `MM_MODULES_DIR` which - if set - overrides `config.foreignModulesDir`, added test for `MM_MODULES_DIR` (#3530) - [core] elements are now removed from `index.html` when loading script or stylesheet files fails - [core] Added `MODULE_DOM_UPDATED` notification each time the DOM is re-rendered via `updateDom` (#3534) - [tests] added minimal needed node version to tests (currently v20.9.0) to avoid releases with wrong node version info - [tests] Added `node-libgpiod` library to electron-rebuild tests (#3563) ### Removed - [core] removed installer only files (#3492) - [core] removed raspberry object from systeminformation (#3505) - [linter] removed `eslint-plugin-import`, because it doesn't support ESLint v9. We will reenter it later when it does. - [tests] removed `onoff` library from electron-rebuild tests (#3563) ### Updated - [weather] Updated `apiVersion` default from 2.5 to 3.0 (#3424) - [core] Updated dependencies including stylistic-eslint - [core] nail down `node-ical` version to `0.18.0` with exception `allow-ghsas: GHSA-8hc4-vh64-cxmj` in `dep-review.yaml` (which should removed after next `node-ical` update) - [core] Updated SocketIO catch all to new API - [core] Allow custom modules positions by scanning index.html for the defined regions, instead of hard coded (PR #3518 fixes issue #3504) - [core] Detail optimizations in `config_check.js` - [core] Updated minimal needed node version in `package.json` (currently v20.9.0) (#3559) and except for v21 (no security updates) (#3561) - [linter] Switch to ESLint v9 and flat config and replace `eslint-plugin-unicorn` by `@eslint/js` - [core] fix discovering module positions twice after #3450 ### Fixed - Fixed `checks` badge in README.md - [weather] Fixed issue with the UK Met Office provider following a change in their API paths and header info. - [core] add check for node_helper loading for multiple instances of same module (#3502) - [weather] Fixed issue for respecting unit config on broadcasted notifications - [tests] Fixes calendar test by moving it from e2e to electron with fixed date (#3532) - [calendar] fixed sliceMultiDayEvents getting wrong count and displaying incorrect entries, Europe/Berlin (#3542) - [tests] ignore `js/positions.js` when linting (this file is created at runtime) - [calendar] fixed sliceMultiDayEvents showing previous day without config enabled --------- 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: Ross Younger <crazyscot@gmail.com> Co-authored-by: Veeck <github@veeck.de> 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: veeck <michael.veeck@nebenan.de> 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>
This commit is contained in:
@@ -82,9 +82,12 @@ Module.register("calendar", {
|
||||
|
||||
// Define required translations.
|
||||
getTranslations () {
|
||||
// The translations for the default modules are defined in the core translation files.
|
||||
// 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.
|
||||
|
||||
/*
|
||||
* The translations for the default modules are defined in the core translation files.
|
||||
* 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;
|
||||
},
|
||||
|
||||
@@ -148,8 +151,10 @@ Module.register("calendar", {
|
||||
};
|
||||
}
|
||||
|
||||
// tell helper to start a fetcher for this calendar
|
||||
// fetcher till cycle
|
||||
/*
|
||||
* tell helper to start a fetcher for this calendar
|
||||
* fetcher till cycle
|
||||
*/
|
||||
this.addCalendar(calendar.url, calendar.auth, calendarConfig);
|
||||
});
|
||||
|
||||
@@ -627,10 +632,11 @@ Module.register("calendar", {
|
||||
event.tomorrow = !event.today && event.startDate >= today + ONE_DAY && event.startDate < today + 2 * ONE_DAY;
|
||||
event.dayAfterTomorrow = !event.tomorrow && event.startDate >= today + ONE_DAY * 2 && event.startDate < today + 3 * ONE_DAY;
|
||||
|
||||
/* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
|
||||
/*
|
||||
* if sliceMultiDayEvents is set to true, multiday events (events exceeding at least one midnight) are sliced into days,
|
||||
* otherwise, esp. in dateheaders mode it is not clear how long these events are.
|
||||
*/
|
||||
const maxCount = Math.ceil((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / ONE_DAY) + 1;
|
||||
const maxCount = Math.round((event.endDate - 1 - moment(event.startDate, "x").endOf("day").format("x")) / ONE_DAY) + 1;
|
||||
if (this.config.sliceMultiDayEvents && maxCount > 1) {
|
||||
const splitEvents = [];
|
||||
let midnight
|
||||
@@ -638,19 +644,20 @@ Module.register("calendar", {
|
||||
.clone()
|
||||
.startOf("day")
|
||||
.add(1, "day")
|
||||
.endOf("day")
|
||||
.format("x");
|
||||
let count = 1;
|
||||
while (event.endDate > midnight) {
|
||||
const thisEvent = JSON.parse(JSON.stringify(event)); // clone object
|
||||
thisEvent.today = thisEvent.startDate >= today && thisEvent.startDate < today + ONE_DAY;
|
||||
thisEvent.tomorrow = !thisEvent.today && thisEvent.startDate >= today + ONE_DAY && thisEvent.startDate < today + 2 * ONE_DAY;
|
||||
thisEvent.endDate = midnight;
|
||||
thisEvent.endDate = moment(midnight, "x").clone().subtract(1, "day").format("x");
|
||||
thisEvent.title += ` (${count}/${maxCount})`;
|
||||
splitEvents.push(thisEvent);
|
||||
|
||||
event.startDate = midnight;
|
||||
count += 1;
|
||||
midnight = moment(midnight, "x").add(1, "day").format("x"); // next day
|
||||
midnight = moment(midnight, "x").add(1, "day").endOf("day").format("x"); // next day
|
||||
}
|
||||
// Last day
|
||||
event.title += ` (${count}/${maxCount})`;
|
||||
@@ -677,16 +684,21 @@ Module.register("calendar", {
|
||||
return events;
|
||||
}
|
||||
|
||||
// Limit the number of days displayed
|
||||
// If limitDays is set > 0, limit display to that number of days
|
||||
/*
|
||||
* Limit the number of days displayed
|
||||
* If limitDays is set > 0, limit display to that number of days
|
||||
*/
|
||||
if (this.config.limitDays > 0) {
|
||||
let newEvents = [];
|
||||
let lastDate = today.clone().subtract(1, "days").format("YYYYMMDD");
|
||||
let days = 0;
|
||||
for (const ev of events) {
|
||||
let eventDate = moment(ev.startDate, "x").format("YYYYMMDD");
|
||||
// if date of event is later than lastdate
|
||||
// check if we already are showing max unique days
|
||||
|
||||
/*
|
||||
* if date of event is later than lastdate
|
||||
* check if we already are showing max unique days
|
||||
*/
|
||||
if (eventDate > lastDate) {
|
||||
// if the only entry in the first day is a full day event that day is not counted as unique
|
||||
if (!this.config.limitDaysNeverSkip && newEvents.length === 1 && days === 1 && newEvents[0].fullDayEvent) {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/* CalendarFetcher Tester
|
||||
/*
|
||||
* CalendarFetcher Tester
|
||||
* use this script with `node debug.js` to test the fetcher without the need
|
||||
* of starting the MagicMirror² core. Adjust the values below to your desire.
|
||||
*/
|
||||
|
@@ -1,3 +1,5 @@
|
||||
/* global Cron */
|
||||
|
||||
Module.register("compliments", {
|
||||
// Module config defaults.
|
||||
defaults: {
|
||||
@@ -21,10 +23,12 @@ Module.register("compliments", {
|
||||
lastIndexUsed: -1,
|
||||
// Set currentweather from module
|
||||
currentWeatherType: "",
|
||||
|
||||
cron_regex: /^(((\d+,)+\d+|((\d+|[*])[/]\d+|((JAN|FEB|APR|MA[RY]|JU[LN]|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|APR|MA[RY]|JU[LN]|AUG|SEP|OCT|NOV|DEC))?))|(\d+-\d+)|\d+(-\d+)?[/]\d+(-\d+)?|\d+|[*]|(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?) ?){5}$/i,
|
||||
date_regex: "[1-9.][0-9.][0-9.]{2}-([0][1-9]|[1][0-2])-([1-2][0-9]|[0][1-9]|[3][0-1])",
|
||||
pre_defined_types: ["anytime", "morning", "afternoon", "evening"],
|
||||
// Define required scripts.
|
||||
getScripts () {
|
||||
return ["moment.js"];
|
||||
return ["croner.js", "moment.js"];
|
||||
},
|
||||
|
||||
// Define start sequence.
|
||||
@@ -38,11 +42,45 @@ Module.register("compliments", {
|
||||
this.config.compliments = JSON.parse(response);
|
||||
this.updateDom();
|
||||
}
|
||||
let minute_sync_delay = 1;
|
||||
// loop thru all the configured when events
|
||||
for (let m of Object.keys(this.config.compliments)) {
|
||||
// if it is a cron entry
|
||||
if (this.isCronEntry(m)) {
|
||||
// we need to synch our interval cycle to the minute
|
||||
minute_sync_delay = (60 - (moment().second())) * 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Schedule update timer. sync to the minute start (if needed), so minute based events happen on the minute start
|
||||
setTimeout(() => {
|
||||
setInterval(() => {
|
||||
this.updateDom(this.config.fadeSpeed);
|
||||
}, this.config.updateInterval);
|
||||
},
|
||||
minute_sync_delay);
|
||||
},
|
||||
|
||||
// Schedule update timer.
|
||||
setInterval(() => {
|
||||
this.updateDom(this.config.fadeSpeed);
|
||||
}, this.config.updateInterval);
|
||||
// check to see if this entry could be a cron entry wich contains spaces
|
||||
isCronEntry (entry) {
|
||||
return entry.includes(" ");
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} cronExpression The cron expression. See https://croner.56k.guru/usage/pattern/
|
||||
* @param {Date} [timestamp] The timestamp to check. Defaults to the current time.
|
||||
* @returns {number} The number of seconds until the next cron run.
|
||||
*/
|
||||
getSecondsUntilNextCronRun (cronExpression, timestamp = new Date()) {
|
||||
// Required for seconds precision
|
||||
const adjustedTimestamp = new Date(timestamp.getTime() - 1000);
|
||||
|
||||
// https://www.npmjs.com/package/croner
|
||||
const cronJob = new Cron(cronExpression);
|
||||
const nextRunTime = cronJob.nextRun(adjustedTimestamp);
|
||||
|
||||
const secondsDelta = (nextRunTime - adjustedTimestamp) / 1000;
|
||||
return secondsDelta;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -75,8 +113,9 @@ Module.register("compliments", {
|
||||
* @returns {string[]} array with compliments for the time of the day.
|
||||
*/
|
||||
complimentArray () {
|
||||
const hour = moment().hour();
|
||||
const date = moment().format("YYYY-MM-DD");
|
||||
const now = moment();
|
||||
const hour = now.hour();
|
||||
const date = now.format("YYYY-MM-DD");
|
||||
let compliments = [];
|
||||
|
||||
// Add time of day compliments
|
||||
@@ -91,22 +130,51 @@ Module.register("compliments", {
|
||||
// Add compliments based on weather
|
||||
if (this.currentWeatherType in this.config.compliments) {
|
||||
Array.prototype.push.apply(compliments, this.config.compliments[this.currentWeatherType]);
|
||||
// if the predefine list doesn't include it (yet)
|
||||
if (!this.pre_defined_types.includes(this.currentWeatherType)) {
|
||||
// add it
|
||||
this.pre_defined_types.push(this.currentWeatherType);
|
||||
}
|
||||
}
|
||||
|
||||
// Add compliments for anytime
|
||||
Array.prototype.push.apply(compliments, this.config.compliments.anytime);
|
||||
|
||||
// Add compliments for special days
|
||||
for (let entry in this.config.compliments) {
|
||||
if (new RegExp(entry).test(date)) {
|
||||
// Only display compliments configured for the day if specialDayUnique is set to true
|
||||
if (this.config.specialDayUnique) {
|
||||
compliments.length = 0;
|
||||
}
|
||||
Array.prototype.push.apply(compliments, this.config.compliments[entry]);
|
||||
// get the list of just date entry keys
|
||||
let temp_list = Object.keys(this.config.compliments).filter((k) => {
|
||||
if (this.pre_defined_types.includes(k)) return false;
|
||||
else return true;
|
||||
});
|
||||
|
||||
let date_compliments = [];
|
||||
// Add compliments for special day/times
|
||||
for (let entry of temp_list) {
|
||||
// check if this could be a cron type entry
|
||||
if (this.isCronEntry(entry)) {
|
||||
// make sure the regex is valid
|
||||
if (new RegExp(this.cron_regex).test(entry)) {
|
||||
// check if we are in the time range for the cron entry
|
||||
if (this.getSecondsUntilNextCronRun(entry, now.set("seconds", 0).toDate()) <= 1) {
|
||||
// if so, use its notice entries
|
||||
Array.prototype.push.apply(date_compliments, this.config.compliments[entry]);
|
||||
}
|
||||
} else Log.error(`compliments cron syntax invalid=${JSON.stringify(entry)}`);
|
||||
} else if (new RegExp(entry).test(date)) {
|
||||
Array.prototype.push.apply(date_compliments, this.config.compliments[entry]);
|
||||
}
|
||||
}
|
||||
|
||||
// if we found any date compliments
|
||||
if (date_compliments.length) {
|
||||
// and the special flag is true
|
||||
if (this.config.specialDayUnique) {
|
||||
// clear the non-date compliments if any
|
||||
compliments.length = 0;
|
||||
}
|
||||
// put the date based compliments on the list
|
||||
Array.prototype.push.apply(compliments, date_compliments);
|
||||
}
|
||||
|
||||
return compliments;
|
||||
},
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/* Default Modules List
|
||||
/*
|
||||
* Default Modules List
|
||||
* Modules listed below can be loaded without the 'default/' prefix. Omitting the default folder name.
|
||||
*/
|
||||
const defaultModules = ["alert", "calendar", "clock", "compliments", "helloworld", "newsfeed", "updatenotification", "weather"];
|
||||
|
@@ -4,7 +4,8 @@ const fs = require("node:fs");
|
||||
|
||||
const Log = require("logger");
|
||||
|
||||
/* class Updater
|
||||
/*
|
||||
* class Updater
|
||||
* Allow to self updating 3rd party modules from command defined in config
|
||||
*
|
||||
* [constructor] read value in config:
|
||||
@@ -84,13 +85,15 @@ class Updater {
|
||||
return updater;
|
||||
}
|
||||
|
||||
// module updater with his proper command
|
||||
// return object as result
|
||||
//{
|
||||
// error: <boolean>, // if error detected
|
||||
// updated: <boolean>, // if updated successfully
|
||||
// needRestart: <boolean> // if magicmirror restart required
|
||||
//};
|
||||
/*
|
||||
* module updater with his proper command
|
||||
* return object as result
|
||||
* {
|
||||
* error: <boolean>, // if error detected
|
||||
* updated: <boolean>, // if updated successfully
|
||||
* needRestart: <boolean> // if magicmirror restart required
|
||||
* };
|
||||
*/
|
||||
updateProcess (module) {
|
||||
let Result = {
|
||||
error: false,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* This class is a provider for Environment Canada MSC Datamart
|
||||
/*
|
||||
* This class is a provider for Environment Canada MSC Datamart
|
||||
* Note that this is only for Canadian locations and does not require an API key (access is anonymous)
|
||||
*
|
||||
* EC Documentation at following links:
|
||||
@@ -25,9 +26,7 @@
|
||||
*
|
||||
* License to use Environment Canada (EC) data is detailed here:
|
||||
* https://eccc-msc.github.io/open-data/licence/readme_en/
|
||||
*
|
||||
*/
|
||||
|
||||
WeatherProvider.register("envcanada", {
|
||||
// Set the name of the provider for debugging and alerting purposes (eg. provide eye-catcher)
|
||||
providerName: "Environment Canada",
|
||||
@@ -39,10 +38,10 @@ WeatherProvider.register("envcanada", {
|
||||
provCode: "ON"
|
||||
},
|
||||
|
||||
//
|
||||
// Set config values (equates to weather module config values). Also set values pertaining to caching of
|
||||
// Today's temperature forecast (for use in the Forecast functions below)
|
||||
//
|
||||
/*
|
||||
* Set config values (equates to weather module config values). Also set values pertaining to caching of
|
||||
* Today's temperature forecast (for use in the Forecast functions below)
|
||||
*/
|
||||
setConfig (config) {
|
||||
this.config = config;
|
||||
|
||||
@@ -52,17 +51,17 @@ WeatherProvider.register("envcanada", {
|
||||
this.cacheCurrentTemp = 999;
|
||||
},
|
||||
|
||||
//
|
||||
// Called when the weather provider is started
|
||||
//
|
||||
/*
|
||||
* Called when the weather provider is started
|
||||
*/
|
||||
start () {
|
||||
Log.info(`Weather provider: ${this.providerName} started.`);
|
||||
this.setFetchedLocation(this.config.location);
|
||||
},
|
||||
|
||||
//
|
||||
// Override the fetchCurrentWeather method to query EC and construct a Current weather object
|
||||
//
|
||||
/*
|
||||
* Override the fetchCurrentWeather method to query EC and construct a Current weather object
|
||||
*/
|
||||
fetchCurrentWeather () {
|
||||
this.fetchData(this.getUrl(), "xml")
|
||||
.then((data) => {
|
||||
@@ -80,9 +79,9 @@ WeatherProvider.register("envcanada", {
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
//
|
||||
// Override the fetchWeatherForecast method to query EC and construct Forecast weather objects
|
||||
//
|
||||
/*
|
||||
* Override the fetchWeatherForecast method to query EC and construct Forecast weather objects
|
||||
*/
|
||||
fetchWeatherForecast () {
|
||||
this.fetchData(this.getUrl(), "xml")
|
||||
.then((data) => {
|
||||
@@ -100,9 +99,9 @@ WeatherProvider.register("envcanada", {
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
//
|
||||
// Override the fetchWeatherHourly method to query EC and construct Forecast weather objects
|
||||
//
|
||||
/*
|
||||
* Override the fetchWeatherHourly method to query EC and construct Forecast weather objects
|
||||
*/
|
||||
fetchWeatherHourly () {
|
||||
this.fetchData(this.getUrl(), "xml")
|
||||
.then((data) => {
|
||||
@@ -120,36 +119,30 @@ WeatherProvider.register("envcanada", {
|
||||
.finally(() => this.updateAvailable());
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Environment Canada methods - not part of the standard Provider methods
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//
|
||||
// Build the EC URL based on the Site Code and Province Code specified in the config params. Note that the
|
||||
// URL defaults to the English version simply because there is no language dependency in the data
|
||||
// being accessed. This is only pertinent when using the EC data elements that contain a textual forecast.
|
||||
//
|
||||
/*
|
||||
* Build the EC URL based on the Site Code and Province Code specified in the config params. Note that the
|
||||
* URL defaults to the English version simply because there is no language dependency in the data
|
||||
* 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`;
|
||||
},
|
||||
|
||||
//
|
||||
// Generate a WeatherObject based on current EC weather conditions
|
||||
//
|
||||
|
||||
/*
|
||||
* Generate a WeatherObject based on current EC weather conditions
|
||||
*/
|
||||
generateWeatherObjectFromCurrentWeather (ECdoc) {
|
||||
const currentWeather = new WeatherObject();
|
||||
|
||||
// There are instances where EC will update weather data and current temperature will not be
|
||||
// provided. While this is a defect in the EC systems, we need to accommodate to avoid a current temp
|
||||
// of NaN being displayed. Therefore... whenever we get a valid current temp from EC, we will cache
|
||||
// the value. Whenever EC data is missing current temp, we will provide the cached value
|
||||
// instead. This is reasonable since the cached value will typically be accurate within the previous
|
||||
// hour. The only time this does not work as expected is when MM is restarted and the first query to
|
||||
// EC finds no current temp. In this scenario, MM will end up displaying a current temp of null;
|
||||
|
||||
/*
|
||||
* There are instances where EC will update weather data and current temperature will not be
|
||||
* provided. While this is a defect in the EC systems, we need to accommodate to avoid a current temp
|
||||
* of NaN being displayed. Therefore... whenever we get a valid current temp from EC, we will cache
|
||||
* the value. Whenever EC data is missing current temp, we will provide the cached value
|
||||
* instead. This is reasonable since the cached value will typically be accurate within the previous
|
||||
* hour. The only time this does not work as expected is when MM is restarted and the first query to
|
||||
* EC finds no current temp. In this scenario, MM will end up displaying a current temp of null;
|
||||
*/
|
||||
if (ECdoc.querySelector("siteData currentConditions temperature").textContent) {
|
||||
currentWeather.temperature = ECdoc.querySelector("siteData currentConditions temperature").textContent;
|
||||
this.cacheCurrentTemp = currentWeather.temperature;
|
||||
@@ -163,19 +156,19 @@ WeatherProvider.register("envcanada", {
|
||||
|
||||
currentWeather.humidity = ECdoc.querySelector("siteData currentConditions relativeHumidity").textContent;
|
||||
|
||||
// Ensure showPrecipitationAmount is forced to false. EC does not really provide POP for current day
|
||||
// and this feature for the weather module (current only) is sort of broken in that it wants
|
||||
// to say POP but will display precip as an accumulated amount vs. a percentage.
|
||||
|
||||
/*
|
||||
* Ensure showPrecipitationAmount is forced to false. EC does not really provide POP for current day
|
||||
* and this feature for the weather module (current only) is sort of broken in that it wants
|
||||
* to say POP but will display precip as an accumulated amount vs. a percentage.
|
||||
*/
|
||||
this.config.showPrecipitationAmount = false;
|
||||
|
||||
//
|
||||
// If the module config wants to showFeelsLike... default to the current temperature.
|
||||
// Check for EC wind chill and humidex values and overwrite the feelsLikeTemp value.
|
||||
// This assumes that the EC current conditions will never contain both a wind chill
|
||||
// and humidex temperature.
|
||||
//
|
||||
|
||||
/*
|
||||
* If the module config wants to showFeelsLike... default to the current temperature.
|
||||
* Check for EC wind chill and humidex values and overwrite the feelsLikeTemp value.
|
||||
* This assumes that the EC current conditions will never contain both a wind chill
|
||||
* and humidex temperature.
|
||||
*/
|
||||
if (this.config.showFeelsLike) {
|
||||
currentWeather.feelsLikeTemp = currentWeather.temperature;
|
||||
|
||||
@@ -188,16 +181,10 @@ WeatherProvider.register("envcanada", {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Need to map EC weather icon to MM weatherType values
|
||||
//
|
||||
|
||||
currentWeather.weatherType = this.convertWeatherType(ECdoc.querySelector("siteData currentConditions iconCode").textContent);
|
||||
|
||||
//
|
||||
// Capture the sunrise and sunset values from EC data
|
||||
//
|
||||
|
||||
const sunList = ECdoc.querySelectorAll("siteData riseSet dateTime");
|
||||
|
||||
currentWeather.sunrise = moment(sunList[1].querySelector("timeStamp").textContent, "YYYYMMDDhhmmss");
|
||||
@@ -206,13 +193,11 @@ WeatherProvider.register("envcanada", {
|
||||
return currentWeather;
|
||||
},
|
||||
|
||||
//
|
||||
// Generate an array of WeatherObjects based on EC weather forecast
|
||||
//
|
||||
|
||||
/*
|
||||
* Generate an array of WeatherObjects based on EC weather forecast
|
||||
*/
|
||||
generateWeatherObjectsFromForecast (ECdoc) {
|
||||
// Declare an array to hold each day's forecast object
|
||||
|
||||
const days = [];
|
||||
|
||||
const weather = new WeatherObject();
|
||||
@@ -226,37 +211,33 @@ WeatherProvider.register("envcanada", {
|
||||
|
||||
weather.precipitationAmount = null;
|
||||
|
||||
//
|
||||
// The EC forecast is held in a 12-element array - Elements 0 to 11 - with each day encompassing
|
||||
// 2 elements. the first element for a day details the Today (daytime) forecast while the second
|
||||
// element details the Tonight (nightime) forecast. Element 0 is always for the current day.
|
||||
//
|
||||
// However... the forecast is somewhat 'rolling'.
|
||||
//
|
||||
// If the EC forecast is queried in the morning, then Element 0 will contain Current
|
||||
// Today and Element 1 will contain Current Tonight. From there, the next 5 days of forecast will be
|
||||
// contained in Elements 2/3, 4/5, 6/7, 8/9, and 10/11. This module will create a 6-day forecast using
|
||||
// all of these Elements.
|
||||
//
|
||||
// But, if the EC forecast is queried in late afternoon, the Current Today forecast will be rolled
|
||||
// off and Element 0 will contain Current Tonight. From there, the next 5 days will be contained in
|
||||
// Elements 1/2, 3/4, 5/6, 7/8, and 9/10. As well, Elelement 11 will contain a forecast for a 6th day,
|
||||
// but only for the Today portion (not Tonight). This module will create a 6-day forecast using
|
||||
// Elements 0 to 11, and will ignore the additional Todat forecast in Element 11.
|
||||
//
|
||||
// We need to determine if Element 0 is showing the forecast for Current Today or Current Tonight.
|
||||
// This is required to understand how Min and Max temperature will be determined, and to understand
|
||||
// where the next day's (aka Tomorrow's) forecast is located in the forecast array.
|
||||
//
|
||||
|
||||
/*
|
||||
* The EC forecast is held in a 12-element array - Elements 0 to 11 - with each day encompassing
|
||||
* 2 elements. the first element for a day details the Today (daytime) forecast while the second
|
||||
* element details the Tonight (nightime) forecast. Element 0 is always for the current day.
|
||||
*
|
||||
* However... the forecast is somewhat 'rolling'.
|
||||
*
|
||||
* If the EC forecast is queried in the morning, then Element 0 will contain Current
|
||||
* Today and Element 1 will contain Current Tonight. From there, the next 5 days of forecast will be
|
||||
* contained in Elements 2/3, 4/5, 6/7, 8/9, and 10/11. This module will create a 6-day forecast using
|
||||
* all of these Elements.
|
||||
*
|
||||
* But, if the EC forecast is queried in late afternoon, the Current Today forecast will be rolled
|
||||
* off and Element 0 will contain Current Tonight. From there, the next 5 days will be contained in
|
||||
* Elements 1/2, 3/4, 5/6, 7/8, and 9/10. As well, Elelement 11 will contain a forecast for a 6th day,
|
||||
* but only for the Today portion (not Tonight). This module will create a 6-day forecast using
|
||||
* Elements 0 to 11, and will ignore the additional Todat forecast in Element 11.
|
||||
*
|
||||
* We need to determine if Element 0 is showing the forecast for Current Today or Current Tonight.
|
||||
* This is required to understand how Min and Max temperature will be determined, and to understand
|
||||
* where the next day's (aka Tomorrow's) forecast is located in the forecast array.
|
||||
*/
|
||||
let nextDay = 0;
|
||||
let lastDay = 0;
|
||||
const currentTemp = ECdoc.querySelector("siteData currentConditions temperature").textContent;
|
||||
|
||||
//
|
||||
// If the first Element is Current Today, look at Current Today and Current Tonight for the current day.
|
||||
//
|
||||
|
||||
if (foreGroup[0].querySelector("period[textForecastName='Today']")) {
|
||||
this.todaytempCacheMin = 0;
|
||||
this.todaytempCacheMax = 0;
|
||||
@@ -266,167 +247,144 @@ WeatherProvider.register("envcanada", {
|
||||
|
||||
this.setPrecipitation(weather, foreGroup, 0);
|
||||
|
||||
//
|
||||
// Set the Element number that will reflect where the next day's forecast is located. Also set
|
||||
// the Element number where the end of the forecast will be. This is important because of the
|
||||
// rolling nature of the EC forecast. In the current scenario (Today and Tonight are present
|
||||
// in elements 0 and 11, we know that we will have 6 full days of forecasts and we will use
|
||||
// them. We will set lastDay such that we iterate through all 12 elements of the forecast.
|
||||
//
|
||||
|
||||
/*
|
||||
* Set the Element number that will reflect where the next day's forecast is located. Also set
|
||||
* the Element number where the end of the forecast will be. This is important because of the
|
||||
* rolling nature of the EC forecast. In the current scenario (Today and Tonight are present
|
||||
* in elements 0 and 11, we know that we will have 6 full days of forecasts and we will use
|
||||
* them. We will set lastDay such that we iterate through all 12 elements of the forecast.
|
||||
*/
|
||||
nextDay = 2;
|
||||
lastDay = 12;
|
||||
}
|
||||
|
||||
//
|
||||
// If the first Element is Current Tonight, look at Tonight only for the current day.
|
||||
//
|
||||
if (foreGroup[0].querySelector("period[textForecastName='Tonight']")) {
|
||||
this.setMinMaxTemps(weather, foreGroup, 0, false, currentTemp);
|
||||
|
||||
this.setPrecipitation(weather, foreGroup, 0);
|
||||
|
||||
//
|
||||
// Set the Element number that will reflect where the next day's forecast is located. Also set
|
||||
// the Element number where the end of the forecast will be. This is important because of the
|
||||
// rolling nature of the EC forecast. In the current scenario (only Current Tonight is present
|
||||
// in Element 0, we know that we will have 6 full days of forecasts PLUS a half-day and
|
||||
// forecast in the final element. Because we will only use full day forecasts, we set the
|
||||
// lastDay number to ensure we ignore that final half-day (in forecast Element 11).
|
||||
//
|
||||
|
||||
/*
|
||||
* Set the Element number that will reflect where the next day's forecast is located. Also set
|
||||
* the Element number where the end of the forecast will be. This is important because of the
|
||||
* rolling nature of the EC forecast. In the current scenario (only Current Tonight is present
|
||||
* in Element 0, we know that we will have 6 full days of forecasts PLUS a half-day and
|
||||
* forecast in the final element. Because we will only use full day forecasts, we set the
|
||||
* lastDay number to ensure we ignore that final half-day (in forecast Element 11).
|
||||
*/
|
||||
nextDay = 1;
|
||||
lastDay = 11;
|
||||
}
|
||||
|
||||
//
|
||||
// Need to map EC weather icon to MM weatherType values. Always pick the first Element's icon to
|
||||
// reflect either Today or Tonight depending on what the forecast is showing in Element 0.
|
||||
//
|
||||
|
||||
/*
|
||||
* Need to map EC weather icon to MM weatherType values. Always pick the first Element's icon to
|
||||
* reflect either Today or Tonight depending on what the forecast is showing in Element 0.
|
||||
*/
|
||||
weather.weatherType = this.convertWeatherType(foreGroup[0].querySelector("abbreviatedForecast iconCode").textContent);
|
||||
|
||||
// Push the weather object into the forecast array.
|
||||
|
||||
days.push(weather);
|
||||
|
||||
//
|
||||
// Now do the rest of the forecast starting at nextDay. We will process each day using 2 EC
|
||||
// forecast Elements. This will address the fact that the EC forecast always includes Today and
|
||||
// Tonight for each day. This is why we iterate through the forecast by a a count of 2, with each
|
||||
// iteration looking at the current Element and the next Element.
|
||||
//
|
||||
|
||||
/*
|
||||
* Now do the rest of the forecast starting at nextDay. We will process each day using 2 EC
|
||||
* forecast Elements. This will address the fact that the EC forecast always includes Today and
|
||||
* Tonight for each day. This is why we iterate through the forecast by a a count of 2, with each
|
||||
* iteration looking at the current Element and the next Element.
|
||||
*/
|
||||
let lastDate = moment(baseDate, "YYYYMMDDhhmmss");
|
||||
|
||||
for (let stepDay = nextDay; stepDay < lastDay; stepDay += 2) {
|
||||
let weather = new WeatherObject();
|
||||
|
||||
// Add 1 to the date to reflect the current forecast day we are building
|
||||
|
||||
lastDate = lastDate.add(1, "day");
|
||||
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
|
||||
|
||||
/*
|
||||
* 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.precipitationAmount = null;
|
||||
|
||||
this.setPrecipitation(weather, foreGroup, stepDay);
|
||||
|
||||
//
|
||||
// Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
|
||||
//
|
||||
|
||||
weather.weatherType = this.convertWeatherType(foreGroup[stepDay].querySelector("abbreviatedForecast iconCode").textContent);
|
||||
|
||||
// Push the weather object into the forecast array.
|
||||
|
||||
days.push(weather);
|
||||
}
|
||||
|
||||
return days;
|
||||
},
|
||||
|
||||
//
|
||||
// Generate an array of WeatherObjects based on EC hourly weather forecast
|
||||
//
|
||||
|
||||
/*
|
||||
* Generate an array of WeatherObjects based on EC hourly weather forecast
|
||||
*/
|
||||
generateWeatherObjectsFromHourly (ECdoc) {
|
||||
// Declare an array to hold each hour's forecast object
|
||||
|
||||
const hours = [];
|
||||
|
||||
// Get local timezone UTC offset so that each hourly time can be calculated properly
|
||||
|
||||
const baseHours = ECdoc.querySelectorAll("siteData hourlyForecastGroup dateTime");
|
||||
const hourOffset = baseHours[1].getAttribute("UTCOffset");
|
||||
|
||||
//
|
||||
// The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding
|
||||
// the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours.
|
||||
//
|
||||
|
||||
/*
|
||||
* The EC hourly forecast is held in a 24-element array - Elements 0 to 23 - with Element 0 holding
|
||||
* the forecast for the next 'on the hour' timeslot. This means the array is a rolling 24 hours.
|
||||
*/
|
||||
const hourGroup = ECdoc.querySelectorAll("siteData hourlyForecastGroup hourlyForecast");
|
||||
|
||||
for (let stepHour = 0; stepHour < 24; stepHour += 1) {
|
||||
const weather = new WeatherObject();
|
||||
|
||||
// Determine local time by applying UTC offset to the forecast timestamp
|
||||
|
||||
const foreTime = moment(hourGroup[stepHour].getAttribute("dateTimeUTC"), "YYYYMMDDhhmmss");
|
||||
const currTime = foreTime.add(hourOffset, "hours");
|
||||
weather.date = moment(currTime);
|
||||
|
||||
// Capture the temperature
|
||||
|
||||
weather.temperature = hourGroup[stepHour].querySelector("temperature").textContent;
|
||||
|
||||
// Capture Likelihood of Precipitation (LOP) and unit-of-measure values
|
||||
|
||||
const precipLOP = hourGroup[stepHour].querySelector("lop").textContent * 1.0;
|
||||
|
||||
if (precipLOP > 0) {
|
||||
weather.precipitationProbability = precipLOP;
|
||||
}
|
||||
|
||||
//
|
||||
// Need to map EC weather icon to MM weatherType values. Always pick the first Element icon.
|
||||
//
|
||||
|
||||
weather.weatherType = this.convertWeatherType(hourGroup[stepHour].querySelector("iconCode").textContent);
|
||||
|
||||
// Push the weather object into the forecast array.
|
||||
|
||||
hours.push(weather);
|
||||
}
|
||||
|
||||
return hours;
|
||||
},
|
||||
//
|
||||
// Determine Min and Max temp based on a supplied Forecast Element index and a boolen that denotes if
|
||||
// the next Forecast element should be considered - i.e. look at Today *and* Tonight vs.Tonight-only
|
||||
//
|
||||
|
||||
/*
|
||||
* Determine Min and Max temp based on a supplied Forecast Element index and a boolen that denotes if
|
||||
* the next Forecast element should be considered - i.e. look at Today *and* Tonight vs.Tonight-only
|
||||
*/
|
||||
setMinMaxTemps (weather, foreGroup, today, fullDay, currentTemp) {
|
||||
const todayTemp = foreGroup[today].querySelector("temperatures temperature").textContent;
|
||||
|
||||
const todayClass = foreGroup[today].querySelector("temperatures temperature").getAttribute("class");
|
||||
|
||||
//
|
||||
// The following logic is largely aimed at accommodating the Current day's forecast whereby we
|
||||
// can have either Current Today+Current Tonight or only Current Tonight.
|
||||
//
|
||||
// If fullDay is false, then we only have Tonight for the current day's forecast - meaning we have
|
||||
// lost a min or max temp value for the day. Therefore, we will see if we were able to cache the the
|
||||
// Today forecast for the current day. If we have, we will use them. If we do not have the cached values,
|
||||
// it means that MM or the Computer has been restarted since the time EC rolled off Today from the
|
||||
// forecast. In this scenario, we will simply default to the Current Conditions temperature and then
|
||||
// check the Tonight temperature.
|
||||
//
|
||||
|
||||
/*
|
||||
* The following logic is largely aimed at accommodating the Current day's forecast whereby we
|
||||
* can have either Current Today+Current Tonight or only Current Tonight.
|
||||
*
|
||||
* If fullDay is false, then we only have Tonight for the current day's forecast - meaning we have
|
||||
* lost a min or max temp value for the day. Therefore, we will see if we were able to cache the the
|
||||
* Today forecast for the current day. If we have, we will use them. If we do not have the cached values,
|
||||
* it means that MM or the Computer has been restarted since the time EC rolled off Today from the
|
||||
* forecast. In this scenario, we will simply default to the Current Conditions temperature and then
|
||||
* check the Tonight temperature.x
|
||||
*/
|
||||
if (fullDay === false) {
|
||||
if (this.todayCached === true) {
|
||||
weather.minTemperature = this.todayTempCacheMin;
|
||||
@@ -437,13 +395,12 @@ WeatherProvider.register("envcanada", {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// We will check to see if the current Element's temperature is Low or High and set weather values
|
||||
// accordingly. We will also check the condition where fullDay is true *and* we are looking at forecast
|
||||
// element 0. This is a special case where we will cache temperature values so that we have them later
|
||||
// in the current day when the Current Today element rolls off and we have Current Tonight only.
|
||||
//
|
||||
|
||||
/*
|
||||
* We will check to see if the current Element's temperature is Low or High and set weather values
|
||||
* accordingly. We will also check the condition where fullDay is true *and* we are looking at forecast
|
||||
* element 0. This is a special case where we will cache temperature values so that we have them later
|
||||
* in the current day when the Current Today element rolls off and we have Current Tonight only.
|
||||
*/
|
||||
if (todayClass === "low") {
|
||||
weather.minTemperature = todayTemp;
|
||||
if (today === 0 && fullDay === true) {
|
||||
@@ -473,25 +430,24 @@ WeatherProvider.register("envcanada", {
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Check for a Precipitation forecast. EC can provide a forecast in 2 ways: either an accumulation figure
|
||||
// or a POP percentage. If there is a POP, then that is what the module will show. If there is an accumulation,
|
||||
// then it will be displayed ONLY if no POP is present.
|
||||
//
|
||||
// POP Logic: By default, we want to show the POP for 'daytime' since we are presuming that is what
|
||||
// people are more interested in seeing. While EC provides a separate POP for daytime and nightime portions
|
||||
// of each day, the weather module does not really allow for that view of a daily forecast. There we will
|
||||
// ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
|
||||
// the nightime forecast after a certain point in the afternoon. As such, we will be showing the nightime POP
|
||||
// (if one exists) in that specific scenario.
|
||||
//
|
||||
// Accumulation Logic: Similar to POP, we want to show accumulation for 'daytime' since we presume that is what
|
||||
// people are interested in seeing. While EC provides a separate accumulation for daytime and nightime portions
|
||||
// of each day, the weather module does not really allow for that view of a daily forecast. There we will
|
||||
// ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
|
||||
// the nightime forecast after a certain point in that specific scenario.
|
||||
//
|
||||
|
||||
/*
|
||||
* Check for a Precipitation forecast. EC can provide a forecast in 2 ways: either an accumulation figure
|
||||
* or a POP percentage. If there is a POP, then that is what the module will show. If there is an accumulation,
|
||||
* then it will be displayed ONLY if no POP is present.
|
||||
*
|
||||
* POP Logic: By default, we want to show the POP for 'daytime' since we are presuming that is what
|
||||
* people are more interested in seeing. While EC provides a separate POP for daytime and nightime portions
|
||||
* of each day, the weather module does not really allow for that view of a daily forecast. There we will
|
||||
* ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
|
||||
* the nightime forecast after a certain point in the afternoon. As such, we will be showing the nightime POP
|
||||
* (if one exists) in that specific scenario.
|
||||
*
|
||||
* Accumulation Logic: Similar to POP, we want to show accumulation for 'daytime' since we presume that is what
|
||||
* people are interested in seeing. While EC provides a separate accumulation for daytime and nightime portions
|
||||
* of each day, the weather module does not really allow for that view of a daily forecast. There we will
|
||||
* ignore any nightime portion. There is an exception however! For the Current day, the EC data will only show
|
||||
* the nightime forecast after a certain point in that specific scenario.
|
||||
*/
|
||||
setPrecipitation (weather, foreGroup, today) {
|
||||
if (foreGroup[today].querySelector("precipitation accumulation")) {
|
||||
weather.precipitationAmount = foreGroup[today].querySelector("precipitation accumulation amount").textContent * 1.0;
|
||||
@@ -505,9 +461,9 @@ WeatherProvider.register("envcanada", {
|
||||
}
|
||||
},
|
||||
|
||||
//
|
||||
// Convert the icons to a more usable name.
|
||||
//
|
||||
/*
|
||||
* Convert the icons to a more usable name.
|
||||
*/
|
||||
convertWeatherType (weatherType) {
|
||||
const weatherTypes = {
|
||||
"00": "day-sunny",
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* This class is a provider for Open-Meteo,
|
||||
/*
|
||||
* This class is a provider for Open-Meteo,
|
||||
* see https://open-meteo.com/
|
||||
*/
|
||||
|
||||
@@ -9,8 +10,11 @@ const GEOCODE_BASE = "https://api.bigdatacloud.net/data/reverse-geocode-client";
|
||||
const OPEN_METEO_BASE = "https://api.open-meteo.com/v1";
|
||||
|
||||
WeatherProvider.register("openmeteo", {
|
||||
// Set the name of the provider.
|
||||
// Not strictly required, but helps for debugging.
|
||||
|
||||
/*
|
||||
* Set the name of the provider.
|
||||
* Not strictly required, but helps for debugging.
|
||||
*/
|
||||
providerName: "Open-Meteo",
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
|
@@ -1,17 +1,21 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* This class is a provider for Openweathermap,
|
||||
/*
|
||||
* This class is a provider for Openweathermap,
|
||||
* see https://openweathermap.org/
|
||||
*/
|
||||
WeatherProvider.register("openweathermap", {
|
||||
// Set the name of the provider.
|
||||
// This isn't strictly necessary, since it will fallback to the provider identifier
|
||||
// But for debugging (and future alerts) it would be nice to have the real name.
|
||||
|
||||
/*
|
||||
* Set the name of the provider.
|
||||
* This isn't strictly necessary, since it will fallback to the provider identifier
|
||||
* But for debugging (and future alerts) it would be nice to have the real name.
|
||||
*/
|
||||
providerName: "OpenWeatherMap",
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
defaults: {
|
||||
apiVersion: "2.5",
|
||||
apiVersion: "3.0",
|
||||
apiBase: "https://api.openweathermap.org/data/",
|
||||
weatherEndpoint: "", // can be "onecall", "forecast" or "weather" (for current)
|
||||
locationID: false,
|
||||
@@ -67,8 +71,11 @@ WeatherProvider.register("openweathermap", {
|
||||
this.fetchData(this.getUrl())
|
||||
.then((data) => {
|
||||
if (!data) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
|
||||
/*
|
||||
* Did not receive usable new data.
|
||||
* Maybe this needs a better check?
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -206,8 +213,10 @@ WeatherProvider.register("openweathermap", {
|
||||
weather.weatherType = this.convertWeatherType(forecast.weather[0].icon);
|
||||
}
|
||||
|
||||
// the same day as before
|
||||
// add values from forecast to corresponding variables
|
||||
/*
|
||||
* the same day as before
|
||||
* add values from forecast to corresponding variables
|
||||
*/
|
||||
minTemp.push(forecast.main.temp_min);
|
||||
maxTemp.push(forecast.main.temp_max);
|
||||
|
||||
@@ -220,8 +229,10 @@ WeatherProvider.register("openweathermap", {
|
||||
}
|
||||
}
|
||||
|
||||
// last day
|
||||
// calculate minimum/maximum temperature, specify rain amount
|
||||
/*
|
||||
* last day
|
||||
* calculate minimum/maximum temperature, specify rain amount
|
||||
*/
|
||||
weather.minTemperature = Math.min.apply(null, minTemp);
|
||||
weather.maxTemperature = Math.max.apply(null, maxTemp);
|
||||
weather.rain = rain;
|
||||
@@ -250,14 +261,18 @@ WeatherProvider.register("openweathermap", {
|
||||
weather.rain = 0;
|
||||
weather.snow = 0;
|
||||
|
||||
// forecast.rain not available if amount is zero
|
||||
// The API always returns in millimeters
|
||||
/*
|
||||
* forecast.rain not available if amount is zero
|
||||
* The API always returns in millimeters
|
||||
*/
|
||||
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
|
||||
/*
|
||||
* forecast.snow not available if amount is zero
|
||||
* The API always returns in millimeters
|
||||
*/
|
||||
if (forecast.hasOwnProperty("snow") && !isNaN(forecast.snow)) {
|
||||
weather.snow = forecast.snow;
|
||||
}
|
||||
@@ -402,7 +417,8 @@ WeatherProvider.register("openweathermap", {
|
||||
return weatherTypes.hasOwnProperty(weatherType) ? weatherTypes[weatherType] : null;
|
||||
},
|
||||
|
||||
/* getParams(compliments)
|
||||
/*
|
||||
* getParams(compliments)
|
||||
* Generates an url with api parameters based on the config.
|
||||
*
|
||||
* return String - URL params.
|
||||
|
@@ -1,11 +1,15 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* This class is a provider for Pirate Weather, it is a replacement for Dark Sky (same api),
|
||||
/*
|
||||
* This class is a provider for Pirate Weather, it is a replacement for Dark Sky (same api),
|
||||
* see http://pirateweather.net/en/latest/
|
||||
*/
|
||||
WeatherProvider.register("pirateweather", {
|
||||
// Set the name of the provider.
|
||||
// Not strictly required, but helps for debugging.
|
||||
|
||||
/*
|
||||
* Set the name of the provider.
|
||||
* Not strictly required, but helps for debugging.
|
||||
*/
|
||||
providerName: "pirateweather",
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* This class is a provider for SMHI (Sweden only).
|
||||
/*
|
||||
* This class is a provider for SMHI (Sweden only).
|
||||
* Metric system is the only supported unit,
|
||||
* see https://www.smhi.se/
|
||||
*/
|
||||
@@ -138,9 +139,11 @@ WeatherProvider.register("smhi", {
|
||||
currentWeather.weatherType = this.convertWeatherType(this.paramValue(weatherData, "Wsymb2"), currentWeather.isDayTime());
|
||||
currentWeather.feelsLikeTemp = this.calculateApparentTemperature(weatherData);
|
||||
|
||||
// Determine the precipitation amount and category and update the
|
||||
// weatherObject with it, the valuetype to use can be configured or uses
|
||||
// median as default.
|
||||
/*
|
||||
* Determine the precipitation amount and category and update the
|
||||
* weatherObject with it, the valuetype to use can be configured or uses
|
||||
* median as default.
|
||||
*/
|
||||
let precipitationValue = this.paramValue(weatherData, this.config.precipitationValue);
|
||||
switch (this.paramValue(weatherData, "pcat")) {
|
||||
// 0 = No precipitation
|
||||
|
@@ -1,12 +1,16 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* This class is a provider for UK Met Office Datapoint,
|
||||
/*
|
||||
* This class is a provider for UK Met Office Datapoint,
|
||||
* see https://www.metoffice.gov.uk/
|
||||
*/
|
||||
WeatherProvider.register("ukmetoffice", {
|
||||
// Set the name of the provider.
|
||||
// This isn't strictly necessary, since it will fallback to the provider identifier
|
||||
// But for debugging (and future alerts) it would be nice to have the real name.
|
||||
|
||||
/*
|
||||
* Set the name of the provider.
|
||||
* This isn't strictly necessary, since it will fallback to the provider identifier
|
||||
* But for debugging (and future alerts) it would be nice to have the real name.
|
||||
*/
|
||||
providerName: "UK Met Office",
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
@@ -21,8 +25,11 @@ WeatherProvider.register("ukmetoffice", {
|
||||
this.fetchData(this.getUrl("3hourly"))
|
||||
.then((data) => {
|
||||
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
|
||||
/*
|
||||
* Did not receive usable new data.
|
||||
* Maybe this needs a better check?
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,8 +49,11 @@ WeatherProvider.register("ukmetoffice", {
|
||||
this.fetchData(this.getUrl("daily"))
|
||||
.then((data) => {
|
||||
if (!data || !data.SiteRep || !data.SiteRep.DV || !data.SiteRep.DV.Location || !data.SiteRep.DV.Location.Period || data.SiteRep.DV.Location.Period.length === 0) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
|
||||
/*
|
||||
* Did not receive usable new data.
|
||||
* Maybe this needs a better check?
|
||||
*/
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -86,8 +96,11 @@ WeatherProvider.register("ukmetoffice", {
|
||||
if (periodDate.isSameOrAfter(moment.utc().startOf("day"))) {
|
||||
// check this is the period we want, after today the diff will be -ve
|
||||
if (moment().diff(periodDate, "minutes") > 0) {
|
||||
// loop round the reports looking for the one we are in
|
||||
// $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
|
||||
|
||||
/*
|
||||
* loop round the reports looking for the one we are in
|
||||
* $ value specifies the time in minutes-of-the-day: 0, 180, 360,...1260
|
||||
*/
|
||||
for (const rep of period.Rep) {
|
||||
const p = rep.$;
|
||||
if (timeInMins >= p && timeInMins - 180 < p) {
|
||||
@@ -117,8 +130,10 @@ WeatherProvider.register("ukmetoffice", {
|
||||
generateWeatherObjectsFromForecast (forecasts) {
|
||||
const days = [];
|
||||
|
||||
// loop round the (5) periods getting the data
|
||||
// for each period array, Day is [0], Night is [1]
|
||||
/*
|
||||
* loop round the (5) periods getting the data
|
||||
* for each period array, Day is [0], Night is [1]
|
||||
*/
|
||||
for (const period of forecasts.SiteRep.DV.Location.Period) {
|
||||
const weather = new WeatherObject();
|
||||
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* This class is a provider for UK Met Office Data Hub (the replacement for their Data Point services).
|
||||
/*
|
||||
* 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
|
||||
@@ -11,9 +12,8 @@
|
||||
* 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/",
|
||||
* apiBase: "https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/",
|
||||
* apiKey: "[YOUR API KEY]",
|
||||
* apiSecret: "[YOUR API SECRET]",
|
||||
* lat: [LATITUDE (DECIMAL)],
|
||||
* lon: [LONGITUDE (DECIMAL)]
|
||||
*
|
||||
@@ -38,14 +38,13 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
defaults: {
|
||||
apiBase: "https://api-metoffice.apiconnect.ibmcloud.com/metoffice/production/v0/forecasts/point/",
|
||||
apiBase: "https://data.hub.api.metoffice.gov.uk/sitespecific/v0/point/",
|
||||
apiKey: "",
|
||||
apiSecret: "",
|
||||
lat: 0,
|
||||
lon: 0
|
||||
},
|
||||
|
||||
// Build URL with query strings according to DataHub API (https://metoffice.apiconnect.ibmcloud.com/metoffice/production/api)
|
||||
// Build URL with query strings according to DataHub API (https://datahub.metoffice.gov.uk/docs/f/category/site-specific/type/site-specific/api-documentation#get-/point/hourly)
|
||||
getUrl (forecastType) {
|
||||
let queryStrings = "?";
|
||||
queryStrings += `latitude=${this.config.lat}`;
|
||||
@@ -56,14 +55,15 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
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)
|
||||
/*
|
||||
* 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://datahub.metoffice.gov.uk/docs/f/category/site-specific/type/site-specific/api-documentation#get-/point/hourly)
|
||||
*/
|
||||
getHeaders () {
|
||||
return {
|
||||
accept: "application/json",
|
||||
"x-ibm-client-id": this.config.apiKey,
|
||||
"x-ibm-client-secret": this.config.apiSecret
|
||||
apikey: this.config.apiKey
|
||||
};
|
||||
},
|
||||
|
||||
@@ -81,8 +81,11 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
.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?
|
||||
|
||||
/*
|
||||
* Did not receive usable new data.
|
||||
* Maybe this needs a better check?
|
||||
*/
|
||||
Log.error("Possibly bad current/hourly data?");
|
||||
Log.error(data);
|
||||
return;
|
||||
@@ -130,15 +133,19 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
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)
|
||||
/*
|
||||
* 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
|
||||
/*
|
||||
* 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;
|
||||
@@ -150,8 +157,11 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
.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?
|
||||
|
||||
/*
|
||||
* Did not receive usable new data.
|
||||
* Maybe this needs a better check?
|
||||
*/
|
||||
Log.error("Possibly bad forecast data?");
|
||||
Log.error(data);
|
||||
return;
|
||||
@@ -206,8 +216,10 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
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)
|
||||
/*
|
||||
* 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);
|
||||
@@ -222,9 +234,11 @@ WeatherProvider.register("ukmetofficedatahub", {
|
||||
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/
|
||||
/*
|
||||
* 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,11 +1,15 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* This class is a provider for Weatherbit,
|
||||
/*
|
||||
* This class is a provider for Weatherbit,
|
||||
* see https://www.weatherbit.io/
|
||||
*/
|
||||
WeatherProvider.register("weatherbit", {
|
||||
// Set the name of the provider.
|
||||
// Not strictly required, but helps for debugging.
|
||||
|
||||
/*
|
||||
* Set the name of the provider.
|
||||
* Not strictly required, but helps for debugging.
|
||||
*/
|
||||
providerName: "Weatherbit",
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
|
@@ -1,11 +1,15 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* This class is a provider for Weatherflow.
|
||||
/*
|
||||
* This class is a provider for Weatherflow.
|
||||
* Note that the Weatherflow API does not provide snowfall.
|
||||
*/
|
||||
WeatherProvider.register("weatherflow", {
|
||||
// Set the name of the provider.
|
||||
// Not strictly required, but helps for debugging
|
||||
|
||||
/*
|
||||
* Set the name of the provider.
|
||||
* Not strictly required, but helps for debugging
|
||||
*/
|
||||
providerName: "WeatherFlow",
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject, WeatherUtils */
|
||||
|
||||
/* Provider: weather.gov
|
||||
/*
|
||||
* Provider: weather.gov
|
||||
* https://weather-gov.github.io/api/general-faqs
|
||||
*
|
||||
* This class is a provider for weather.gov.
|
||||
@@ -9,9 +10,12 @@
|
||||
*/
|
||||
|
||||
WeatherProvider.register("weathergov", {
|
||||
// Set the name of the provider.
|
||||
// This isn't strictly necessary, since it will fallback to the provider identifier
|
||||
// But for debugging (and future alerts) it would be nice to have the real name.
|
||||
|
||||
/*
|
||||
* Set the name of the provider.
|
||||
* This isn't strictly necessary, since it will fallback to the provider identifier
|
||||
* But for debugging (and future alerts) it would be nice to have the real name.
|
||||
*/
|
||||
providerName: "Weather.gov",
|
||||
|
||||
// Set the default config properties that is specific to this provider
|
||||
@@ -98,8 +102,11 @@ WeatherProvider.register("weathergov", {
|
||||
this.fetchData(this.forecastHourlyURL)
|
||||
.then((data) => {
|
||||
if (!data) {
|
||||
// Did not receive usable new data.
|
||||
// Maybe this needs a better check?
|
||||
|
||||
/*
|
||||
* Did not receive usable new data.
|
||||
* Maybe this needs a better check?
|
||||
*/
|
||||
return;
|
||||
}
|
||||
const hourly = this.generateWeatherObjectsFromHourly(data.properties.periods);
|
||||
@@ -282,14 +289,18 @@ WeatherProvider.register("weathergov", {
|
||||
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
|
||||
}
|
||||
|
||||
// the same day as before
|
||||
// add values from forecast to corresponding variables
|
||||
/*
|
||||
* the same day as before
|
||||
* add values from forecast to corresponding variables
|
||||
*/
|
||||
minTemp.push(forecast.temperature);
|
||||
maxTemp.push(forecast.temperature);
|
||||
}
|
||||
|
||||
// last day
|
||||
// calculate minimum/maximum temperature
|
||||
/*
|
||||
* last day
|
||||
* calculate minimum/maximum temperature
|
||||
*/
|
||||
weather.minTemperature = Math.min.apply(null, minTemp);
|
||||
weather.maxTemperature = Math.max.apply(null, maxTemp);
|
||||
|
||||
@@ -302,8 +313,11 @@ WeatherProvider.register("weathergov", {
|
||||
* Convert the icons to a more usable name.
|
||||
*/
|
||||
convertWeatherType (weatherType, isDaytime) {
|
||||
//https://w1.weather.gov/xml/current_obs/weather.php
|
||||
// There are way too many types to create, so lets just look for certain strings
|
||||
|
||||
/*
|
||||
* https://w1.weather.gov/xml/current_obs/weather.php
|
||||
* There are way too many types to create, so lets just look for certain strings
|
||||
*/
|
||||
|
||||
if (weatherType.includes("Cloudy") || weatherType.includes("Partly")) {
|
||||
if (isDaytime) {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/* global WeatherProvider, WeatherObject */
|
||||
|
||||
/* This class is a provider for Yr.no, a norwegian weather service.
|
||||
/*
|
||||
* This class is a provider for Yr.no, a norwegian weather service.
|
||||
* Terms of service: https://developer.yr.no/doc/TermsOfService/
|
||||
*/
|
||||
WeatherProvider.register("yr", {
|
||||
@@ -67,8 +68,11 @@ WeatherProvider.register("yr", {
|
||||
|
||||
getWeatherData () {
|
||||
return new Promise((resolve, reject) => {
|
||||
// If a user has several Yr-modules, for instance one current and one forecast, the API calls must be synchronized across classes.
|
||||
// This is to avoid multiple similar calls to the API.
|
||||
|
||||
/*
|
||||
* If a user has several Yr-modules, for instance one current and one forecast, the API calls must be synchronized across classes.
|
||||
* This is to avoid multiple similar calls to the API.
|
||||
*/
|
||||
let shouldWait = localStorage.getItem("yrIsFetchingWeatherData");
|
||||
if (shouldWait) {
|
||||
const checkForGo = setInterval(function () {
|
||||
@@ -201,8 +205,11 @@ WeatherProvider.register("yr", {
|
||||
},
|
||||
|
||||
getStellarData () {
|
||||
// If a user has several Yr-modules, for instance one current and one forecast, the API calls must be synchronized across classes.
|
||||
// This is to avoid multiple similar calls to the API.
|
||||
|
||||
/*
|
||||
* If a user has several Yr-modules, for instance one current and one forecast, the API calls must be synchronized across classes.
|
||||
* This is to avoid multiple similar calls to the API.
|
||||
*/
|
||||
return new Promise((resolve, reject) => {
|
||||
let shouldWait = localStorage.getItem("yrIsFetchingStellarData");
|
||||
if (shouldWait) {
|
||||
|
@@ -170,12 +170,19 @@ Module.register("weather", {
|
||||
}
|
||||
|
||||
const notificationPayload = {
|
||||
currentWeather: this.weatherProvider?.currentWeatherObject?.simpleClone() ?? null,
|
||||
forecastArray: this.weatherProvider?.weatherForecastArray?.map((ar) => ar.simpleClone()) ?? [],
|
||||
hourlyArray: this.weatherProvider?.weatherHourlyArray?.map((ar) => ar.simpleClone()) ?? [],
|
||||
currentWeather: this.config.units === "imperial"
|
||||
? WeatherUtils.convertWeatherObjectToImperial(this.weatherProvider?.currentWeatherObject?.simpleClone()) ?? null
|
||||
: this.weatherProvider?.currentWeatherObject?.simpleClone() ?? null,
|
||||
forecastArray: this.config.units === "imperial"
|
||||
? this.weatherProvider?.weatherForecastArray?.map((ar) => WeatherUtils.convertWeatherObjectToImperial(ar.simpleClone())) ?? []
|
||||
: this.weatherProvider?.weatherForecastArray?.map((ar) => ar.simpleClone()) ?? [],
|
||||
hourlyArray: this.config.units === "imperial"
|
||||
? this.weatherProvider?.weatherHourlyArray?.map((ar) => WeatherUtils.convertWeatherObjectToImperial(ar.simpleClone())) ?? []
|
||||
: this.weatherProvider?.weatherHourlyArray?.map((ar) => ar.simpleClone()) ?? [],
|
||||
locationName: this.weatherProvider?.fetchedLocationName,
|
||||
providerName: this.weatherProvider.providerName
|
||||
};
|
||||
|
||||
this.sendNotification("WEATHER_UPDATED", notificationPayload);
|
||||
},
|
||||
|
||||
|
@@ -30,8 +30,7 @@ const WeatherUtils = {
|
||||
let convertedValue = value;
|
||||
let conversionUnit = valueUnit;
|
||||
if (outputUnit === "imperial") {
|
||||
if (valueUnit && valueUnit.toLowerCase() === "cm") convertedValue = convertedValue * 0.3937007874;
|
||||
else convertedValue = convertedValue * 0.03937007874;
|
||||
convertedValue = this.convertPrecipitationToInch(value, valueUnit);
|
||||
conversionUnit = "in";
|
||||
} else {
|
||||
conversionUnit = valueUnit ? valueUnit : "mm";
|
||||
@@ -40,6 +39,17 @@ const WeatherUtils = {
|
||||
return `${convertedValue.toFixed(2)} ${conversionUnit}`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert precipitation value into inch
|
||||
* @param {number} value the precipitation value for convert
|
||||
* @param {string} valueUnit can be 'mm' or 'cm'
|
||||
* @returns {number} the converted precipitation value
|
||||
*/
|
||||
convertPrecipitationToInch (value, valueUnit) {
|
||||
if (valueUnit && valueUnit.toLowerCase() === "cm") return value * 0.3937007874;
|
||||
else return value * 0.03937007874;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert temp (from degrees C) into imperial or metric unit depending on
|
||||
* your config
|
||||
@@ -129,6 +139,28 @@ const WeatherUtils = {
|
||||
}
|
||||
|
||||
return ((feelsLike - 32) * 5) / 9;
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts the Weather Object's values into imperial unit
|
||||
* @param {object} weatherObject the weather object
|
||||
* @returns {object} the weather object with converted values to imperial
|
||||
*/
|
||||
convertWeatherObjectToImperial (weatherObject) {
|
||||
if (!weatherObject || Object.keys(weatherObject).length === 0) return null;
|
||||
|
||||
let imperialWeatherObject = { ...weatherObject };
|
||||
|
||||
if (imperialWeatherObject) {
|
||||
if (imperialWeatherObject.feelsLikeTemp) imperialWeatherObject.feelsLikeTemp = this.convertTemp(imperialWeatherObject.feelsLikeTemp, "imperial");
|
||||
if (imperialWeatherObject.maxTemperature) imperialWeatherObject.maxTemperature = this.convertTemp(imperialWeatherObject.maxTemperature, "imperial");
|
||||
if (imperialWeatherObject.minTemperature) imperialWeatherObject.minTemperature = this.convertTemp(imperialWeatherObject.minTemperature, "imperial");
|
||||
if (imperialWeatherObject.precipitationAmount) imperialWeatherObject.precipitationAmount = this.convertPrecipitationToInch(imperialWeatherObject.precipitationAmount, imperialWeatherObject.precipitationUnits);
|
||||
if (imperialWeatherObject.temperature) imperialWeatherObject.temperature = this.convertTemp(imperialWeatherObject.temperature, "imperial");
|
||||
if (imperialWeatherObject.windSpeed) imperialWeatherObject.windSpeed = this.convertWind(imperialWeatherObject.windSpeed, "imperial");
|
||||
}
|
||||
|
||||
return imperialWeatherObject;
|
||||
}
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user