integrated onecall usage into existing types

This commit is contained in:
Bryan Zhu
2020-08-01 02:59:08 -04:00
48 changed files with 1526 additions and 1536 deletions

View File

@@ -1,4 +1,7 @@
{% if current %}
{% if current or weatherData %}
{% if weatherData %}
{% set current = weatherData.current %}
{% endif %}
{% if not config.onlyTemp %}
<div class="normal medium">
<span class="wi wi-strong-wind dimmed"></span>

View File

@@ -1,4 +1,7 @@
{% if forecast %}
{% if forecast or weatherData %}
{% if weatherData %}
{% set forecast = weatherData.days %}
{% endif %}
{% set numSteps = forecast | calcNumSteps %}
{% set currentStep = 0 %}
<table class="{{ config.tableClass }}">

View File

@@ -1,8 +1,11 @@
{% if wData %}
{% set numSteps = wData.hours | calcNumEntries %}
{% if hourly or weatherData %}
{% if weatherData %}
{% set hourly = weatherData.hours %}
{% endif %}
{% set numSteps = hourly | calcNumEntries %}
{% set currentStep = 0 %}
<table class="{{ config.tableClass }}">
{% set hours = wData.hours.slice(0, numSteps) %}
{% set hours = hourly.slice(0, numSteps) %}
{% for hour in hours %}
<tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
<td class="day">{{ hour.date | formatTime }}</td>
@@ -25,5 +28,5 @@
</div>
{% endif %}
<!-- Uncomment the line below to see the contents of the `wData` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{wData | dump}}</div> -->
<!-- Uncomment the line below to see the contents of the `hourly` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{weatherData | dump}}</div> -->

View File

@@ -68,8 +68,8 @@ WeatherProvider.register("openweathermap", {
this.setFetchedLocation(`(${data.lat},${data.lon})`);
const wData = this.generateWeatherObjectsFromOnecall(data);
this.setWeatherData(wData);
const weatherData = this.generateWeatherObjectsFromOnecall(data);
this.setWeatherData(weatherData);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
@@ -124,8 +124,8 @@ WeatherProvider.register("openweathermap", {
return this.fetchOnecall(data);
}
// if weatherEndpoint does not match onecall, what should be returned?
const wData = {current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits), hours: [], days: []};
return wData;
const weatherData = {current: new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits), hours: [], days: []};
return weatherData;
},
/*
@@ -415,11 +415,11 @@ WeatherProvider.register("openweathermap", {
if (this.config.weatherEndpoint === "/onecall") {
params += "lat=" + this.config.lat;
params += "&lon=" + this.config.lon;
if (this.config.type === "wDataCurrent") {
if (this.config.type === "current") {
params += "&exclude=minutely,hourly,daily";
} else if (this.config.type === "wDataHourly") {
} else if (this.config.type === "hourly") {
params += "&exclude=current,minutely,daily";
} else if (this.config.type === "wDataDaily") {
} else if (this.config.type === "daily" || this.config.type === "forecast") {
params += "&exclude=current,minutely,hourly";
} else {
params += "&exclude=minutely";

View File

@@ -3,76 +3,155 @@
/* Magic Mirror
* Module: Weather
* Provider: weather.gov
* https://weather-gov.github.io/api/general-faqs
*
* By Vince Peri
* Original by Vince Peri
* MIT Licensed.
*
* This class is a provider for weather.gov.
* Note that this is only for US locations (lat and lon) and does not require an API key
* Since it is free, there are some items missing - like sunrise, sunset, humidity, etc.
* Since it is free, there are some items missing - like sunrise, sunset
*/
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.
providerName: "Weather.gov",
// Flag all needed URLs availability
configURLs: false,
//This API has multiple urls involved
forecastURL: "tbd",
forecastHourlyURL: "tbd",
forecastGridDataURL: "tbd",
observationStationsURL: "tbd",
stationObsURL: "tbd",
// Called to set the config, this config is the same as the weather module's config.
setConfig: function (config) {
this.config = config;
(this.config.apiBase = "https://api.weather.gov"), this.fetchWxGovURLs(this.config);
},
// Called when the weather provider is about to start.
start: function () {
Log.info(`Weather provider: ${this.providerName} started.`);
},
// This returns the name of the fetched location or an empty string.
fetchedLocation: function () {
return this.fetchedLocationName || "";
},
// Overwrite the fetchCurrentWeather method.
fetchCurrentWeather() {
this.fetchData(this.getUrl())
if (!this.configURLs) {
Log.info("fetch wx waiting on config URLs");
return;
}
this.fetchData(this.stationObsURL)
.then((data) => {
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
if (!data || !data.properties) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties.periods[0]);
const currentWeather = this.generateWeatherObjectFromCurrentWeather(data.properties);
this.setCurrentWeather(currentWeather);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
Log.error("Could not load station obs data ... ", request);
})
.finally(() => this.updateAvailable());
},
// Overwrite the fetchCurrentWeather method.
// Overwrite the fetchWeatherForecast method.
fetchWeatherForecast() {
this.fetchData(this.getUrl())
if (!this.configURLs) {
Log.info("fetch wx waiting on config URLs");
return;
}
this.fetchData(this.forecastURL)
.then((data) => {
if (!data || !data.properties || !data.properties.periods || !data.properties.periods.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
const forecast = this.generateWeatherObjectsFromForecast(data.properties.periods);
this.setWeatherForecast(forecast);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
Log.error("Could not load forecast hourly data ... ", request);
})
.finally(() => this.updateAvailable());
},
/** Weather.gov Specific Methods - These are not part of the default provider methods */
/*
* Gets the complete url for the request
* Get specific URLs
*/
getUrl() {
return this.config.apiBase + this.config.lat + "," + this.config.lon + this.config.weatherEndpoint;
fetchWxGovURLs(config) {
this.fetchData(`${config.apiBase}/points/${config.lat},${config.lon}`)
.then((data) => {
if (!data || !data.properties) {
// points URL did not respond with usable data.
return;
}
this.fetchedLocationName = data.properties.relativeLocation.properties.city + ", " + data.properties.relativeLocation.properties.state;
Log.log("Forecast location is " + this.fetchedLocationName);
this.forecastURL = data.properties.forecast;
this.forecastHourlyURL = data.properties.forecastHourly;
this.forecastGridDataURL = data.properties.forecastGridData;
this.observationStationsURL = data.properties.observationStations;
// with this URL, we chain another promise for the station obs URL
return this.fetchData(data.properties.observationStations);
})
.then((obsData) => {
if (!obsData || !obsData.features) {
// obs station URL did not respond with usable data.
return;
}
this.stationObsURL = obsData.features[0].id + "/observations/latest";
})
.catch((err) => {
Log.error(err);
})
.finally(() => {
// excellent, let's fetch some actual wx data
this.configURLs = true;
this.fetchCurrentWeather();
});
},
/*
* Generate a WeatherObject based on currentWeatherInformation
* Weather.gov API uses specific units; API does not include choice of units
* ... object needs data in units based on config!
*/
generateWeatherObjectFromCurrentWeather(currentWeatherData) {
const currentWeather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
currentWeather.temperature = currentWeatherData.temperature;
currentWeather.windSpeed = currentWeatherData.windSpeed.split(" ", 1);
currentWeather.windDirection = this.convertWindDirection(currentWeatherData.windDirection);
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.shortForecast, currentWeatherData.isDaytime);
currentWeather.date = moment(currentWeatherData.timestamp);
currentWeather.temperature = this.convertTemp(currentWeatherData.temperature.value);
currentWeather.windSpeed = this.covertSpeed(currentWeatherData.windSpeed.value);
currentWeather.windDirection = currentWeatherData.windDirection.value;
currentWeather.minTemperature = this.convertTemp(currentWeatherData.minTemperatureLast24Hours.value);
currentWeather.maxTemperature = this.convertTemp(currentWeatherData.maxTemperatureLast24Hours.value);
currentWeather.humidity = Math.round(currentWeatherData.relativeHumidity.value);
currentWeather.rain = null;
currentWeather.snow = null;
currentWeather.precipitation = this.convertLength(currentWeatherData.precipitationLastHour.value);
currentWeather.feelsLikeTemp = this.convertTemp(currentWeatherData.heatIndex.value);
let isDaytime = true;
if (currentWeatherData.icon.includes("day")) {
isDaytime = true;
} else {
isDaytime = false;
}
currentWeather.weatherType = this.convertWeatherType(currentWeatherData.textDescription, isDaytime);
// determine the sunrise/sunset times - not supplied in weather.gov data
let times = this.calcAstroData(this.config.lat, this.config.lon);
@@ -124,7 +203,7 @@ WeatherProvider.register("weathergov", {
// specify date
weather.date = moment(forecast.startTime);
// If the first value of today is later than 17:00, we have an icon at least!
// use the forecast isDayTime attribute to help build the weatherType label
weather.weatherType = this.convertWeatherType(forecast.shortForecast, forecast.isDaytime);
}
@@ -148,6 +227,34 @@ WeatherProvider.register("weathergov", {
return days.slice(1);
},
/*
* Unit conversions
*/
// conversion to fahrenheit
convertTemp(temp) {
if (this.config.tempUnits === "imperial") {
return (9 / 5) * temp + 32;
} else {
return temp;
}
},
// conversion to mph
covertSpeed(metSec) {
if (this.config.windUnits === "imperial") {
return metSec * 2.23694;
} else {
return metSec;
}
},
// conversion to inches
convertLength(meters) {
if (this.config.units === "imperial") {
return meters * 39.3701;
} else {
return meters;
}
},
/*
* Calculate the astronomical data
*/

View File

@@ -1,83 +0,0 @@
{% if wData %}
{% set current = wData.current %}
{% if not config.onlyTemp %}
<div class="normal medium">
<span class="wi wi-strong-wind dimmed"></span>
<span>
{% if config.useBeaufort %}
{{ current.beaufortWindSpeed() | round }}
{% else %}
{{ current.windSpeed | round }}
{% endif %}
{% if config.showWindDirection %}
<sup>
{% if config.showWindDirectionAsArrow %}
<i class="fa fa-long-arrow-up" style="transform:rotate({{ current.windDirection }}deg);"></i>
{% else %}
{{ current.cardinalWindDirection() | translate }}
{% endif %}
&nbsp;
</sup>
{% endif %}
</span>
{% if config.showHumidity and current.humidity %}
<span>{{ current.humidity | decimalSymbol }}</span><sup>&nbsp;<i class="wi wi-humidity humidityIcon"></i></sup>
{% endif %}
{% if config.showSun %}
<span class="wi dimmed wi-{{ current.nextSunAction() }}"></span>
<span>
{% if current.nextSunAction() === "sunset" %}
{{ current.sunset | formatTime }}
{% else %}
{{ current.sunrise | formatTime }}
{% endif %}
</span>
{% endif %}
</div>
{% endif %}
<div class="large light">
<span class="wi weathericon wi-{{current.weatherType}}"></span>
<span class="bright">
{{ current.temperature | roundValue | unit("temperature") | decimalSymbol }}
</span>
</div>
<div class="normal light indoor">
{% if config.showIndoorTemperature and indoor.temperature %}
<div>
<span class="fa fa-home"></span>
<span class="bright">
{{ indoor.temperature | roundValue | unit("temperature") | decimalSymbol }}
</span>
</div>
{% endif %}
{% if config.showIndoorHumidity and indoor.humidity %}
<div>
<span class="fa fa-tint"></span>
<span class="bright">
{{ indoor.humidity | roundValue | unit("humidity") | decimalSymbol }}
</span>
</div>
{% endif %}
</div>
{% if (config.showFeelsLike or config.showPrecipitationAmount) and not config.onlyTemp %}
<div class="normal medium">
{% if config.showFeelsLike %}
<span class="dimmed">
{{ "FEELS" | translate }} {{ current.feelsLike() | roundValue | unit("temperature") | decimalSymbol }}
</span>
{% endif %}
{% if config.showPrecipitationAmount %}
<span class="dimmed">
{{ "PRECIP" | translate }} {{ current.precipitation | unit("precip") }}
</span>
{% endif %}
</div>
{% endif %}
{% else %}
<div class="dimmed light small">
{{ "LOADING" | translate | safe }}
</div>
{% endif %}
<!-- Uncomment the line below to see the contents of the `wData` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{wData | dump}}</div> -->

View File

@@ -1,32 +0,0 @@
{% if wData %}
{% set numSteps = wData.days | calcNumEntries %}
{% set currentStep = 0 %}
<table class="{{ config.tableClass }}">
{% set days = wData.days.slice(0, numSteps) %}
{% for day in days %}
<tr {% if config.colored %}class="colored"{% endif %} {% if config.fade %}style="opacity: {{ currentStep | opacity(numSteps) }};"{% endif %}>
<td class="day">{{ day.date.format('ddd') }}</td>
<td class="bright weather-icon"><span class="wi weathericon wi-{{ day.weatherType }}"></span></td>
<td class="align-right bright max-temp">
{{ day.maxTemperature | roundValue | unit("temperature") }}
</td>
<td class="align-right min-temp">
{{ day.minTemperature | roundValue | unit("temperature") }}
</td>
{% if config.showPrecipitationAmount %}
<td class="align-right bright precipitation">
{{ day.precipitation | unit("precip") }}
</td>
{% endif %}
</tr>
{% set currentStep = currentStep + 1 %}
{% endfor %}
</table>
{% else %}
<div class="dimmed light small">
{{ "LOADING" | translate | safe }}
</div>
{% endif %}
<!-- Uncomment the line below to see the contents of the `wData` object. -->
<!-- <div style="word-wrap:break-word" class="xsmall dimmed">{{wData | dump}}</div> -->

View File

@@ -11,7 +11,7 @@ Module.register("weather", {
defaults: {
weatherProvider: "openweathermap",
roundTemp: false,
type: "current", //current, forecast, wDataCurrent, wDataHourly, wDataDaily
type: "current",
lat: 0,
lon: 0,
@@ -127,6 +127,9 @@ Module.register("weather", {
// Select the template depending on the display type.
getTemplate: function () {
if (this.config.type === "daily") {
return `forecast.njk`;
}
return `${this.config.type.toLowerCase()}.njk`;
},
@@ -136,7 +139,7 @@ Module.register("weather", {
config: this.config,
current: this.weatherProvider.currentWeather(),
forecast: this.weatherProvider.weatherForecast(),
wData: this.weatherProvider.weatherData(),
weatherData: this.weatherProvider.weatherData(),
indoor: {
humidity: this.indoorHumidity,
temperature: this.indoorTemperature
@@ -158,12 +161,12 @@ Module.register("weather", {
}
setTimeout(() => {
if (this.config.type === "forecast") {
this.weatherProvider.fetchWeatherForecast();
} else if (this.config.type === "current") {
this.weatherProvider.fetchCurrentWeather();
} else {
if (this.config.weatherEndpoint === "/onecall") {
this.weatherProvider.fetchWeatherData();
} else if (this.config.type === "forecast") {
this.weatherProvider.fetchWeatherForecast();
} else {
this.weatherProvider.fetchCurrentWeather();
}
}, nextLoad);
},