added OpenWeatherMap One Call API function to default Weather module, added wDataHourly type

This commit is contained in:
Bryan Zhu 2020-06-30 02:40:41 -04:00
parent fdd389d6b7
commit 4a162543f6
4 changed files with 263 additions and 4 deletions

View File

@ -35,7 +35,7 @@ WeatherProvider.register("openweathermap", {
.finally(() => this.updateAvailable()); .finally(() => this.updateAvailable());
}, },
// Overwrite the fetchCurrentWeather method. // Overwrite the fetchWeatherForecast method.
fetchWeatherForecast() { fetchWeatherForecast() {
this.fetchData(this.getUrl()) this.fetchData(this.getUrl())
.then((data) => { .then((data) => {
@ -56,6 +56,27 @@ WeatherProvider.register("openweathermap", {
.finally(() => this.updateAvailable()); .finally(() => this.updateAvailable());
}, },
// Overwrite the fetchWeatherData method.
fetchWeatherData() {
this.fetchData(this.getUrl())
.then((data) => {
if (!data || !data.list || !data.list.length) {
// Did not receive usable new data.
// Maybe this needs a better check?
return;
}
this.setFetchedLocation(`(${data.lat},${data.lon})`);
const wData = this.generateWeatherObjectsFromOnecall(data);
this.setWeatherData(wData);
})
.catch(function (request) {
Log.error("Could not load data ... ", request);
})
.finally(() => this.updateAvailable());
},
/** OpenWeatherMap Specific Methods - These are not part of the default provider methods */ /** OpenWeatherMap Specific Methods - These are not part of the default provider methods */
/* /*
* Gets the complete url for the request * Gets the complete url for the request
@ -95,6 +116,153 @@ WeatherProvider.register("openweathermap", {
return days; return days;
}, },
/*
* Generate WeatherObjects based on One Call forecast information
*/
generateWeatherObjectsFromOnecall(data) {
if (this.config.weatherEndpoint === "/onecall") {
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;
},
/*
* fetch One Call forecast information (available for free subscription).
* factors in timezone offsets.
* minutely forecasts are excluded for the moment, see getParams().
*/
fetchOnecall(data) {
let precip = false;
// get current weather
const current = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
if (!isNaN(data.current)) {
current.date = moment(data.current.dt, "X").utcOffset(data.timezone_offset/60);
current.windSpeed = data.current.wind_speed;
current.windDirection = data.current.wind_deg;
current.sunrise = moment(data.current.sunrise, "X").utcOffset(data.timezone_offset/60);
current.sunset = moment(data.current.sunset, "X").utcOffset(data.timezone_offset/60);
current.temperature = data.current.temp;
current.weatherType = this.convertWeatherType(data.current.weather[0].icon);
current.humidity = data.current.humidity;
if (!isNaN(data.current.rain)) {
current.rain = data.current.rain.1h;
precip = true;
}
if (!isNaN(data.current.snow)) {
current.snow = data.current.snow.1h;
precip = true;
}
if (precip) {
current.precipitation = current.rain+current.snow;
}
current.feelsLikeTemp = data.current.feels_like;
}
let weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
// let onecallDailyFormat = "MMM DD"
// let onecallHourlyFormat = "HH"
// let onecallMinutelyFormat = "HH:mm"
// if (this.config.timeFormat === 12) {
// if (this.config.showPeriod === true) {
// if (this.config.showPeriodUpper === true) {
// onecallHourlyFormat = "hhA"
// onecallMinutelyFormat = "hh:mmA"
// } else {
// onecallHourlyFormat = "hha"
// onecallMinutelyFormat = "hh:mma"
// }
// } else {
// onecallHourlyFormat = "hh"
// onecallMinutelyFormat = "hh:mm"
// }
// }
// get hourly weather
const hours = [];
if (!isNaN(data.hourly)) {
for (const hour of data.hourly) {
weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60);
// weather.date = moment(hour.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat+","+onecallHourlyFormat);
weather.temperature = hour.temp;
weather.feelsLikeTemp = hour.feels_like;
weather.humidity = hour.humidity;
weather.windSpeed = hour.wind_speed;
weather.windDirection = hour.wind_deg;
weather.weatherType = this.convertWeatherType(hour.weather[0].icon);
precip = false;
if (!isNaN(hour.rain)) {
if (this.config.units === "imperial") {
weather.rain = hour.rain.1h / 25.4;
} else {
weather.rain = hour.rain.1h;
}
precip = true;
}
if (!isNaN(hour.snow)) {
if (this.config.units === "imperial") {
weather.snow = hour.snow.1h / 25.4;
} else {
weather.snow = hour.snow.1h;
}
precip = true;
}
if (precip) {
weather.precipitation = weather.rain+weather.snow;
}
hours.push(weather);
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
}
}
// get daily weather
const days = [];
if (!isNaN(data.daily)) {
for (const day of data.daily) {
weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60);
weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60);
weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60);
// weather.date = moment(day.dt, "X").utcOffset(data.timezone_offset/60).format(onecallDailyFormat);
// weather.sunrise = moment(day.sunrise, "X").utcOffset(data.timezone_offset/60).format(onecallMinutelyFormat);
// weather.sunset = moment(day.sunset, "X").utcOffset(data.timezone_offset/60).format(onecallMinutelyFormat);
weather.minTemperature = day.temp.min;
weather.maxTemperature = day.temp.max;
weather.humidity = day.humidity;
weather.windSpeed = day.wind_speed;
weather.windDirection = day.wind_deg;
weather.weatherType = this.convertWeatherType(day.weather[0].icon);
precip = false;
if (!isNaN(day.rain)) {
if (this.config.units === "imperial") {
weather.rain = day.rain / 25.4;
} else {
weather.rain = day.rain;
}
precip = true;
}
if (!isNaN(day.snow)) {
if (this.config.units === "imperial") {
weather.snow = day.snow / 25.4;
} else {
weather.snow = day.snow;
}
precip = true;
}
if (precip) {
weather.precipitation = weather.rain+weather.snow;
}
days.push(weather);
weather = new WeatherObject(this.config.units, this.config.tempUnits, this.config.windUnits);
}
}
return {current: current, hours: hours, days: days};
},
/* /*
* fetch forecast information for 3-hourly forecast (available for free subscription). * fetch forecast information for 3-hourly forecast (available for free subscription).
*/ */
@ -256,7 +424,19 @@ WeatherProvider.register("openweathermap", {
*/ */
getParams() { getParams() {
let params = "?"; let params = "?";
if (this.config.locationID) { if (this.config.weatherEndpoint === "/onecall") {
params += "lat=" + this.config.lat;
params += "&lon=" + this.config.lon;
if (this.config.type === "wDataCurrent") {
params += "&exclude=minutely,hourly,daily";
} else if (this.config.type === "wDataHourly") {
params += "&exclude=current,minutely,daily";
} else if (this.config.type === "wDataDaily") {
params += "&exclude=current,minutely,hourly";
} else {
params += "&exclude=minutely";
}
} else if (this.config.locationID) {
params += "id=" + this.config.locationID; params += "id=" + this.config.locationID;
} else if (this.config.location) { } else if (this.config.location) {
params += "q=" + this.config.location; params += "q=" + this.config.location;

View File

@ -0,0 +1,29 @@
{% if wData %}
{% set numSteps = wData.hours | calcNumEntries %}
{% set currentStep = 0 %}
<table class="{{ config.tableClass }}">
{% set hours = wData.hours.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="time">{{ hour.date | formatTimeMoment }}</td>
<td class="bright weather-icon"><span class="wi weathericon wi-{{ hour.weatherType }}"></span></td>
<td class="align-right bright max-temp">
{{ hour.temperature | roundValue | unit("temperature") }}
</td>
{% if config.showPrecipitationAmount %}
<td class="align-right bright precipitation">
{{ hour.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: { defaults: {
weatherProvider: "openweathermap", weatherProvider: "openweathermap",
roundTemp: false, roundTemp: false,
type: "current", //current, forecast type: "current", //current, forecast, wDataCurrent, wDataHourly, wDataDaily
location: false, location: false,
locationID: false, locationID: false,
@ -37,6 +37,7 @@ Module.register("weather", {
showIndoorTemperature: false, showIndoorTemperature: false,
showIndoorHumidity: false, showIndoorHumidity: false,
maxNumberOfDays: 5, maxNumberOfDays: 5,
maxEntries: 5,
fade: true, fade: true,
fadePoint: 0.25, // Start on 1/4th of the list. fadePoint: 0.25, // Start on 1/4th of the list.
@ -132,6 +133,7 @@ Module.register("weather", {
config: this.config, config: this.config,
current: this.weatherProvider.currentWeather(), current: this.weatherProvider.currentWeather(),
forecast: this.weatherProvider.weatherForecast(), forecast: this.weatherProvider.weatherForecast(),
wData: this.weatherProvider.weatherData(),
indoor: { indoor: {
humidity: this.indoorHumidity, humidity: this.indoorHumidity,
temperature: this.indoorTemperature temperature: this.indoorTemperature
@ -153,7 +155,11 @@ Module.register("weather", {
} }
setTimeout(() => { setTimeout(() => {
if (this.config.type === "forecast") { if (this.config.type === "wDataCurrent"
|| this.config.type === "wDataHourly"
|| this.config.type === "wDataDaily") {
this.weatherProvider.fetchWeatherData();
} else if (this.config.type === "forecast") {
this.weatherProvider.fetchWeatherForecast(); this.weatherProvider.fetchWeatherForecast();
} else { } else {
this.weatherProvider.fetchCurrentWeather(); this.weatherProvider.fetchCurrentWeather();
@ -187,6 +193,25 @@ Module.register("weather", {
return date.format("HH:mm"); return date.format("HH:mm");
}.bind(this) }.bind(this)
); );
this.nunjucksEnvironment().addFilter(
"formatTimeMoment",
function (date) {
if (this.config.timeFormat !== 24) {
if (this.config.showPeriod) {
if (this.config.showPeriodUpper) {
return date.format("h:mm A");
} else {
return date.format("h:mm a");
}
} else {
return date.format("h:mm");
}
}
return date.format("HH:mm");
}.bind(this)
);
this.nunjucksEnvironment().addFilter( this.nunjucksEnvironment().addFilter(
"unit", "unit",
@ -243,6 +268,13 @@ Module.register("weather", {
}.bind(this) }.bind(this)
); );
this.nunjucksEnvironment().addFilter(
"calcNumEntries",
function (dataArray) {
return Math.min(dataArray.length, this.config.maxNumberOfEntries);
}.bind(this)
);
this.nunjucksEnvironment().addFilter( this.nunjucksEnvironment().addFilter(
"opacity", "opacity",
function (currentStep, numSteps) { function (currentStep, numSteps) {

View File

@ -16,6 +16,7 @@ var WeatherProvider = Class.extend({
// Try to not access them directly. // Try to not access them directly.
currentWeatherObject: null, currentWeatherObject: null,
weatherForecastArray: null, weatherForecastArray: null,
weatherDataObject: null,
fetchedLocationName: null, fetchedLocationName: null,
// The following properties will be set automatically. // The following properties will be set automatically.
@ -56,6 +57,12 @@ var WeatherProvider = Class.extend({
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`); Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherForecast method.`);
}, },
// This method should start the API request to fetch the weather forecast.
// This method should definitely be overwritten in the provider.
fetchWeatherData: function () {
Log.warn(`Weather provider: ${this.providerName} does not subclass the fetchWeatherData method.`);
},
// This returns a WeatherDay object for the current weather. // This returns a WeatherDay object for the current weather.
currentWeather: function () { currentWeather: function () {
return this.currentWeatherObject; return this.currentWeatherObject;
@ -66,6 +73,11 @@ var WeatherProvider = Class.extend({
return this.weatherForecastArray; return this.weatherForecastArray;
}, },
// This returns an object containing WeatherDay object(s) depending on the type of call.
weatherData: function () {
return this.weatherDataObject;
},
// This returns the name of the fetched location or an empty string. // This returns the name of the fetched location or an empty string.
fetchedLocation: function () { fetchedLocation: function () {
return this.fetchedLocationName || ""; return this.fetchedLocationName || "";
@ -83,6 +95,12 @@ var WeatherProvider = Class.extend({
this.weatherForecastArray = weatherForecastArray; this.weatherForecastArray = weatherForecastArray;
}, },
// Set the weatherDataObject and notify the delegate that new information is available.
setWeatherData: function (weatherDataObject) {
// We should check here if we are passing a WeatherDay
this.weatherDataObject = weatherDataObject;
},
// Set the fetched location name. // Set the fetched location name.
setFetchedLocation: function (name) { setFetchedLocation: function (name) {
this.fetchedLocationName = name; this.fetchedLocationName = name;