2022-11-08 06:18:47 +01:00
/* global WeatherProvider, WeatherObject */
/ * M a g i c M i r r o r ²
* Module : Weather
* Provider : Yr . no
*
* By Magnus Marthinsen
* MIT Licensed
*
2023-01-21 22:40:08 +01:00
* This class is a provider for Yr . no , a norwegian weather service .
2022-11-08 06:18:47 +01:00
*
* Terms of service : https : //developer.yr.no/doc/TermsOfService/
* /
WeatherProvider . register ( "yr" , {
providerName : "Yr" ,
// Set the default config properties that is specific to this provider
defaults : {
useCorsProxy : true ,
apiBase : "https://api.met.no/weatherapi" ,
2023-10-24 00:46:25 +02:00
forecastApiVersion : "2.0" ,
2023-10-15 13:25:44 +02:00
sunriseApiVersion : "3.0" ,
2022-11-08 06:18:47 +01:00
altitude : 0 ,
currentForecastHours : 1 //1, 6 or 12
} ,
2023-12-25 08:17:11 +01:00
start ( ) {
2022-11-08 06:18:47 +01:00
if ( typeof Storage === "undefined" ) {
//local storage unavailable
Log . error ( "The Yr weather provider requires local storage." ) ;
throw new Error ( "Local storage not available" ) ;
}
Log . info ( ` Weather provider: ${ this . providerName } started. ` ) ;
} ,
2023-12-25 08:17:11 +01:00
fetchCurrentWeather ( ) {
2022-11-08 06:18:47 +01:00
this . getCurrentWeather ( )
. then ( ( currentWeather ) => {
this . setCurrentWeather ( currentWeather ) ;
this . updateAvailable ( ) ;
} )
. catch ( ( error ) => {
Log . error ( error ) ;
throw new Error ( error ) ;
} ) ;
} ,
2023-12-25 08:17:11 +01:00
async getCurrentWeather ( ) {
2023-10-15 13:25:44 +02:00
const [ weatherData , stellarData ] = await Promise . all ( [ this . getWeatherData ( ) , this . getStellarData ( ) ] ) ;
2022-11-08 06:18:47 +01:00
if ( ! stellarData ) {
2023-01-21 22:40:08 +01:00
Log . warn ( "No stellar data available." ) ;
2022-11-08 06:18:47 +01:00
}
if ( ! weatherData . properties . timeseries || ! weatherData . properties . timeseries [ 0 ] ) {
Log . error ( "No weather data available." ) ;
return ;
}
const currentTime = moment ( ) ;
let forecast = weatherData . properties . timeseries [ 0 ] ;
let closestTimeInPast = currentTime . diff ( moment ( forecast . time ) ) ;
for ( const forecastTime of weatherData . properties . timeseries ) {
const comparison = currentTime . diff ( moment ( forecastTime . time ) ) ;
if ( 0 < comparison && comparison < closestTimeInPast ) {
closestTimeInPast = comparison ;
forecast = forecastTime ;
}
}
const forecastXHours = this . getForecastForXHoursFrom ( forecast . data ) ;
forecast . weatherType = this . convertWeatherType ( forecastXHours . summary . symbol _code , forecast . time ) ;
Tidy up precipitation (#3023)
Fixes #2953
This is an attempt to fix the issue with precipitation amount and
percentage mixup. I have created a separate
`precipitationPercentage`-variable where the probability of rain can be
stored.
The config options now has the old `showPrecipitationAmount` in addition
to a new setting: `showPrecipitationProbability` (shows the likelihood
of rain).
<details>
<summary>Examples</summary>
### Yr
I tested the Yr weather provider for a Norwegian city Bergen that has a
lot of rain. I have removed properties that are irrelevant for this demo
from the config-samples below.
Config:
```js
{
module: "weather",
config: {
weatherProvider: "yr",
type: "current",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "hourly",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "daily",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
}
```
Result:<br/>
<img width="444" alt="screenshot"
src="https://user-images.githubusercontent.com/34011212/216775423-4e37345c-f915-47e5-8551-7c544ebd24b1.png">
</details>
---------
Co-authored-by: Magnus Marthinsen <magmar@online.no>
Co-authored-by: Veeck <github@veeck.de>
2023-02-04 19:02:55 +01:00
forecast . precipitationAmount = forecastXHours . details ? . precipitation _amount ;
forecast . precipitationProbability = forecastXHours . details ? . probability _of _precipitation ;
2022-11-08 06:18:47 +01:00
forecast . minTemperature = forecastXHours . details ? . air _temperature _min ;
forecast . maxTemperature = forecastXHours . details ? . air _temperature _max ;
return this . getWeatherDataFrom ( forecast , stellarData , weatherData . properties . meta . units ) ;
} ,
2023-12-25 08:17:11 +01:00
getWeatherData ( ) {
2022-11-08 06:18:47 +01:00
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.
let shouldWait = localStorage . getItem ( "yrIsFetchingWeatherData" ) ;
if ( shouldWait ) {
const checkForGo = setInterval ( function ( ) {
shouldWait = localStorage . getItem ( "yrIsFetchingWeatherData" ) ;
} , 100 ) ;
setTimeout ( function ( ) {
clearInterval ( checkForGo ) ;
shouldWait = false ;
} , 5000 ) ; //Assume other fetch finished but failed to remove lock
const attemptFetchWeather = setInterval ( ( ) => {
if ( ! shouldWait ) {
clearInterval ( checkForGo ) ;
clearInterval ( attemptFetchWeather ) ;
this . getWeatherDataFromYrOrCache ( resolve , reject ) ;
}
} , 100 ) ;
} else {
this . getWeatherDataFromYrOrCache ( resolve , reject ) ;
}
} ) ;
} ,
2023-12-25 08:17:11 +01:00
getWeatherDataFromYrOrCache ( resolve , reject ) {
2022-11-08 06:18:47 +01:00
localStorage . setItem ( "yrIsFetchingWeatherData" , "true" ) ;
let weatherData = this . getWeatherDataFromCache ( ) ;
if ( this . weatherDataIsValid ( weatherData ) ) {
localStorage . removeItem ( "yrIsFetchingWeatherData" ) ;
Log . debug ( "Weather data found in cache." ) ;
resolve ( weatherData ) ;
} else {
this . getWeatherDataFromYr ( weatherData ? . downloadedAt )
. then ( ( weatherData ) => {
Log . debug ( "Got weather data from yr." ) ;
2023-04-16 17:38:39 +02:00
let data ;
2022-11-08 06:18:47 +01:00
if ( weatherData ) {
this . cacheWeatherData ( weatherData ) ;
2023-04-16 17:38:39 +02:00
data = weatherData ;
2022-11-08 06:18:47 +01:00
} else {
//Undefined if unchanged
2023-04-16 17:38:39 +02:00
data = this . getWeatherDataFromCache ( ) ;
2022-11-08 06:18:47 +01:00
}
2023-04-16 17:38:39 +02:00
resolve ( data ) ;
2022-11-08 06:18:47 +01:00
} )
. catch ( ( err ) => {
Log . error ( err ) ;
reject ( "Unable to get weather data from Yr." ) ;
} )
. finally ( ( ) => {
localStorage . removeItem ( "yrIsFetchingWeatherData" ) ;
} ) ;
}
} ,
2023-12-25 08:17:11 +01:00
weatherDataIsValid ( weatherData ) {
2022-11-08 06:18:47 +01:00
return (
2023-12-25 08:17:11 +01:00
weatherData
&& weatherData . timeout
&& 0 < moment ( weatherData . timeout ) . diff ( moment ( ) )
&& ( ! weatherData . geometry || ! weatherData . geometry . coordinates || ! weatherData . geometry . coordinates . length < 2 || ( weatherData . geometry . coordinates [ 0 ] === this . config . lat && weatherData . geometry . coordinates [ 1 ] === this . config . lon ) )
2022-11-08 06:18:47 +01:00
) ;
} ,
2023-12-25 08:17:11 +01:00
getWeatherDataFromCache ( ) {
2022-11-08 06:18:47 +01:00
const weatherData = localStorage . getItem ( "weatherData" ) ;
if ( weatherData ) {
return JSON . parse ( weatherData ) ;
} else {
return undefined ;
}
} ,
2023-12-25 08:17:11 +01:00
getWeatherDataFromYr ( currentDataFetchedAt ) {
2022-11-08 06:18:47 +01:00
const requestHeaders = [ { name : "Accept" , value : "application/json" } ] ;
if ( currentDataFetchedAt ) {
requestHeaders . push ( { name : "If-Modified-Since" , value : currentDataFetchedAt } ) ;
}
const expectedResponseHeaders = [ "expires" , "date" ] ;
return this . fetchData ( this . getForecastUrl ( ) , "json" , requestHeaders , expectedResponseHeaders )
. then ( ( data ) => {
if ( ! data || ! data . headers ) return data ;
data . timeout = data . headers . find ( ( header ) => header . name === "expires" ) . value ;
data . downloadedAt = data . headers . find ( ( header ) => header . name === "date" ) . value ;
data . headers = undefined ;
return data ;
} )
. catch ( ( err ) => {
Log . error ( "Could not load weather data." , err ) ;
throw new Error ( err ) ;
} ) ;
} ,
2023-12-25 08:17:11 +01:00
getConfigOptions ( ) {
2022-11-08 06:18:47 +01:00
if ( ! this . config . lat ) {
Log . error ( "Latitude not provided." ) ;
throw new Error ( "Latitude not provided." ) ;
}
if ( ! this . config . lon ) {
Log . error ( "Longitude not provided." ) ;
throw new Error ( "Longitude not provided." ) ;
}
let lat = this . config . lat . toString ( ) ;
let lon = this . config . lon . toString ( ) ;
const altitude = this . config . altitude ? ? 0 ;
2023-10-15 13:25:44 +02:00
return { lat , lon , altitude } ;
} ,
2023-12-25 08:17:11 +01:00
getForecastUrl ( ) {
2023-10-15 13:25:44 +02:00
let { lat , lon , altitude } = this . getConfigOptions ( ) ;
2022-11-08 06:18:47 +01:00
if ( lat . includes ( "." ) && lat . split ( "." ) [ 1 ] . length > 4 ) {
Log . warn ( "Latitude is too specific for weather data. Do not use more than four decimals. Trimming to maximum length." ) ;
const latParts = lat . split ( "." ) ;
lat = ` ${ latParts [ 0 ] } . ${ latParts [ 1 ] . substring ( 0 , 4 ) } ` ;
}
if ( lon . includes ( "." ) && lon . split ( "." ) [ 1 ] . length > 4 ) {
Log . warn ( "Longitude is too specific for weather data. Do not use more than four decimals. Trimming to maximum length." ) ;
const lonParts = lon . split ( "." ) ;
lon = ` ${ lonParts [ 0 ] } . ${ lonParts [ 1 ] . substring ( 0 , 4 ) } ` ;
}
2023-10-15 13:25:44 +02:00
return ` ${ this . config . apiBase } /locationforecast/ ${ this . config . forecastApiVersion } /complete?&altitude= ${ altitude } &lat= ${ lat } &lon= ${ lon } ` ;
2022-11-08 06:18:47 +01:00
} ,
2023-12-25 08:17:11 +01:00
cacheWeatherData ( weatherData ) {
2022-11-08 06:18:47 +01:00
localStorage . setItem ( "weatherData" , JSON . stringify ( weatherData ) ) ;
} ,
2023-12-25 08:17:11 +01:00
getStellarData ( ) {
2022-11-08 06:18:47 +01:00
// 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 ) {
const checkForGo = setInterval ( function ( ) {
shouldWait = localStorage . getItem ( "yrIsFetchingStellarData" ) ;
} , 100 ) ;
setTimeout ( function ( ) {
clearInterval ( checkForGo ) ;
shouldWait = false ;
} , 5000 ) ; //Assume other fetch finished but failed to remove lock
const attemptFetchWeather = setInterval ( ( ) => {
if ( ! shouldWait ) {
clearInterval ( checkForGo ) ;
clearInterval ( attemptFetchWeather ) ;
this . getStellarDataFromYrOrCache ( resolve , reject ) ;
}
} , 100 ) ;
} else {
this . getStellarDataFromYrOrCache ( resolve , reject ) ;
}
} ) ;
} ,
2023-12-25 08:17:11 +01:00
getStellarDataFromYrOrCache ( resolve , reject ) {
2022-11-08 06:18:47 +01:00
localStorage . setItem ( "yrIsFetchingStellarData" , "true" ) ;
let stellarData = this . getStellarDataFromCache ( ) ;
const today = moment ( ) . format ( "YYYY-MM-DD" ) ;
const tomorrow = moment ( ) . add ( 1 , "days" ) . format ( "YYYY-MM-DD" ) ;
if ( stellarData && stellarData . today && stellarData . today . date === today && stellarData . tomorrow && stellarData . tomorrow . date === tomorrow ) {
Log . debug ( "Stellar data found in cache." ) ;
localStorage . removeItem ( "yrIsFetchingStellarData" ) ;
resolve ( stellarData ) ;
} else if ( stellarData && stellarData . tomorrow && stellarData . tomorrow . date === today ) {
Log . debug ( "stellar data for today found in cache, but not for tomorrow." ) ;
stellarData . today = stellarData . tomorrow ;
this . getStellarDataFromYr ( tomorrow )
. then ( ( data ) => {
if ( data ) {
data . date = tomorrow ;
stellarData . tomorrow = data ;
this . cacheStellarData ( stellarData ) ;
resolve ( stellarData ) ;
} else {
2023-03-19 14:32:23 +01:00
reject ( ` No stellar data returned from Yr for ${ tomorrow } ` ) ;
2022-11-08 06:18:47 +01:00
}
} )
. catch ( ( err ) => {
Log . error ( err ) ;
2023-03-19 14:32:23 +01:00
reject ( ` Unable to get stellar data from Yr for ${ tomorrow } ` ) ;
2022-11-08 06:18:47 +01:00
} )
. finally ( ( ) => {
localStorage . removeItem ( "yrIsFetchingStellarData" ) ;
} ) ;
} else {
this . getStellarDataFromYr ( today , 2 )
. then ( ( stellarData ) => {
if ( stellarData ) {
2023-04-16 17:38:39 +02:00
const data = {
2022-11-08 06:18:47 +01:00
today : stellarData
} ;
2023-04-16 17:38:39 +02:00
data . tomorrow = Object . assign ( { } , data . today ) ;
data . today . date = today ;
data . tomorrow . date = tomorrow ;
this . cacheStellarData ( data ) ;
resolve ( data ) ;
2022-11-08 06:18:47 +01:00
} else {
2023-03-19 14:32:23 +01:00
Log . error ( ` Something went wrong when fetching stellar data. Responses: ${ stellarData } ` ) ;
2022-11-08 06:18:47 +01:00
reject ( stellarData ) ;
}
} )
. catch ( ( err ) => {
Log . error ( err ) ;
reject ( "Unable to get stellar data from Yr." ) ;
} )
. finally ( ( ) => {
localStorage . removeItem ( "yrIsFetchingStellarData" ) ;
} ) ;
}
} ,
2023-12-25 08:17:11 +01:00
getStellarDataFromCache ( ) {
2022-11-08 06:18:47 +01:00
const stellarData = localStorage . getItem ( "stellarData" ) ;
if ( stellarData ) {
return JSON . parse ( stellarData ) ;
} else {
return undefined ;
}
} ,
2023-12-25 08:17:11 +01:00
getStellarDataFromYr ( date , days = 1 ) {
2022-11-08 06:18:47 +01:00
const requestHeaders = [ { name : "Accept" , value : "application/json" } ] ;
2023-10-15 13:25:44 +02:00
return this . fetchData ( this . getStellarDataUrl ( date , days ) , "json" , requestHeaders )
2022-11-08 06:18:47 +01:00
. then ( ( data ) => {
Log . debug ( "Got stellar data from yr." ) ;
return data ;
} )
. catch ( ( err ) => {
Log . error ( "Could not load weather data." , err ) ;
throw new Error ( err ) ;
} ) ;
} ,
2023-12-25 08:17:11 +01:00
getStellarDataUrl ( date , days ) {
2023-10-15 13:25:44 +02:00
let { lat , lon , altitude } = this . getConfigOptions ( ) ;
2022-11-08 06:18:47 +01:00
if ( lat . includes ( "." ) && lat . split ( "." ) [ 1 ] . length > 4 ) {
Log . warn ( "Latitude is too specific for stellar data. Do not use more than four decimals. Trimming to maximum length." ) ;
const latParts = lat . split ( "." ) ;
lat = ` ${ latParts [ 0 ] } . ${ latParts [ 1 ] . substring ( 0 , 4 ) } ` ;
}
if ( lon . includes ( "." ) && lon . split ( "." ) [ 1 ] . length > 4 ) {
Log . warn ( "Longitude is too specific for stellar data. Do not use more than four decimals. Trimming to maximum length." ) ;
const lonParts = lon . split ( "." ) ;
lon = ` ${ lonParts [ 0 ] } . ${ lonParts [ 1 ] . substring ( 0 , 4 ) } ` ;
}
let utcOffset = moment ( ) . utcOffset ( ) / 60 ;
let utcOffsetPrefix = "%2B" ;
if ( utcOffset < 0 ) {
utcOffsetPrefix = "-" ;
}
utcOffset = Math . abs ( utcOffset ) ;
let minutes = "00" ;
if ( utcOffset % 1 !== 0 ) {
minutes = "30" ;
}
let hours = Math . floor ( utcOffset ) . toString ( ) ;
if ( hours . length < 2 ) {
hours = ` 0 ${ hours } ` ;
}
2023-10-15 13:25:44 +02:00
return ` ${ this . config . apiBase } /sunrise/ ${ this . config . sunriseApiVersion } /sun?lat= ${ lat } &lon= ${ lon } &date= ${ date } &offset= ${ utcOffsetPrefix } ${ hours } %3A ${ minutes } ` ;
2022-11-08 06:18:47 +01:00
} ,
2023-12-25 08:17:11 +01:00
cacheStellarData ( data ) {
2022-11-08 06:18:47 +01:00
localStorage . setItem ( "stellarData" , JSON . stringify ( data ) ) ;
} ,
2023-12-25 08:17:11 +01:00
getWeatherDataFrom ( forecast , stellarData , units ) {
Tidy up precipitation (#3023)
Fixes #2953
This is an attempt to fix the issue with precipitation amount and
percentage mixup. I have created a separate
`precipitationPercentage`-variable where the probability of rain can be
stored.
The config options now has the old `showPrecipitationAmount` in addition
to a new setting: `showPrecipitationProbability` (shows the likelihood
of rain).
<details>
<summary>Examples</summary>
### Yr
I tested the Yr weather provider for a Norwegian city Bergen that has a
lot of rain. I have removed properties that are irrelevant for this demo
from the config-samples below.
Config:
```js
{
module: "weather",
config: {
weatherProvider: "yr",
type: "current",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "hourly",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "daily",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
}
```
Result:<br/>
<img width="444" alt="screenshot"
src="https://user-images.githubusercontent.com/34011212/216775423-4e37345c-f915-47e5-8551-7c544ebd24b1.png">
</details>
---------
Co-authored-by: Magnus Marthinsen <magmar@online.no>
Co-authored-by: Veeck <github@veeck.de>
2023-02-04 19:02:55 +01:00
const weather = new WeatherObject ( ) ;
2022-11-08 06:18:47 +01:00
weather . date = moment ( forecast . time ) ;
weather . windSpeed = forecast . data . instant . details . wind _speed ;
2023-01-21 22:40:08 +01:00
weather . windFromDirection = forecast . data . instant . details . wind _from _direction ;
2022-11-08 06:18:47 +01:00
weather . temperature = forecast . data . instant . details . air _temperature ;
weather . minTemperature = forecast . minTemperature ;
weather . maxTemperature = forecast . maxTemperature ;
weather . weatherType = forecast . weatherType ;
weather . humidity = forecast . data . instant . details . relative _humidity ;
Tidy up precipitation (#3023)
Fixes #2953
This is an attempt to fix the issue with precipitation amount and
percentage mixup. I have created a separate
`precipitationPercentage`-variable where the probability of rain can be
stored.
The config options now has the old `showPrecipitationAmount` in addition
to a new setting: `showPrecipitationProbability` (shows the likelihood
of rain).
<details>
<summary>Examples</summary>
### Yr
I tested the Yr weather provider for a Norwegian city Bergen that has a
lot of rain. I have removed properties that are irrelevant for this demo
from the config-samples below.
Config:
```js
{
module: "weather",
config: {
weatherProvider: "yr",
type: "current",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "hourly",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "daily",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
}
```
Result:<br/>
<img width="444" alt="screenshot"
src="https://user-images.githubusercontent.com/34011212/216775423-4e37345c-f915-47e5-8551-7c544ebd24b1.png">
</details>
---------
Co-authored-by: Magnus Marthinsen <magmar@online.no>
Co-authored-by: Veeck <github@veeck.de>
2023-02-04 19:02:55 +01:00
weather . precipitationAmount = forecast . precipitationAmount ;
weather . precipitationProbability = forecast . precipitationProbability ;
2022-11-08 06:18:47 +01:00
weather . precipitationUnits = units . precipitation _amount ;
2023-09-20 11:37:01 +02:00
weather . sunrise = stellarData ? . today ? . properties ? . sunrise ? . time ;
weather . sunset = stellarData ? . today ? . properties ? . sunset ? . time ;
2022-11-08 06:18:47 +01:00
return weather ;
} ,
2023-12-25 08:17:11 +01:00
convertWeatherType ( weatherType , weatherTime ) {
2022-11-08 06:18:47 +01:00
const weatherHour = moment ( weatherTime ) . format ( "HH" ) ;
const weatherTypes = {
clearsky _day : "day-sunny" ,
clearsky _night : "night-clear" ,
clearsky _polartwilight : weatherHour < 14 ? "sunrise" : "sunset" ,
cloudy : "cloudy" ,
fair _day : "day-sunny-overcast" ,
fair _night : "night-alt-partly-cloudy" ,
fair _polartwilight : "day-sunny-overcast" ,
fog : "fog" ,
heavyrain : "rain" , // Possibly raindrops or raindrop
heavyrainandthunder : "thunderstorm" ,
heavyrainshowers _day : "day-rain" ,
heavyrainshowers _night : "night-alt-rain" ,
heavyrainshowers _polartwilight : "day-rain" ,
heavyrainshowersandthunder _day : "day-thunderstorm" ,
heavyrainshowersandthunder _night : "night-alt-thunderstorm" ,
heavyrainshowersandthunder _polartwilight : "day-thunderstorm" ,
heavysleet : "sleet" ,
heavysleetandthunder : "day-sleet-storm" ,
heavysleetshowers _day : "day-sleet" ,
heavysleetshowers _night : "night-alt-sleet" ,
heavysleetshowers _polartwilight : "day-sleet" ,
heavysleetshowersandthunder _day : "day-sleet-storm" ,
heavysleetshowersandthunder _night : "night-alt-sleet-storm" ,
heavysleetshowersandthunder _polartwilight : "day-sleet-storm" ,
heavysnow : "snow-wind" ,
heavysnowandthunder : "day-snow-thunderstorm" ,
heavysnowshowers _day : "day-snow-wind" ,
heavysnowshowers _night : "night-alt-snow-wind" ,
heavysnowshowers _polartwilight : "day-snow-wind" ,
heavysnowshowersandthunder _day : "day-snow-thunderstorm" ,
heavysnowshowersandthunder _night : "night-alt-snow-thunderstorm" ,
heavysnowshowersandthunder _polartwilight : "day-snow-thunderstorm" ,
lightrain : "rain-mix" ,
lightrainandthunder : "thunderstorm" ,
lightrainshowers _day : "day-rain-mix" ,
lightrainshowers _night : "night-alt-rain-mix" ,
lightrainshowers _polartwilight : "day-rain-mix" ,
lightrainshowersandthunder _day : "thunderstorm" ,
lightrainshowersandthunder _night : "thunderstorm" ,
lightrainshowersandthunder _polartwilight : "thunderstorm" ,
lightsleet : "day-sleet" ,
lightsleetandthunder : "day-sleet-storm" ,
lightsleetshowers _day : "day-sleet" ,
lightsleetshowers _night : "night-alt-sleet" ,
lightsleetshowers _polartwilight : "day-sleet" ,
lightsnow : "snowflake-cold" ,
lightsnowandthunder : "day-snow-thunderstorm" ,
lightsnowshowers _day : "day-snow-wind" ,
lightsnowshowers _night : "night-alt-snow-wind" ,
lightsnowshowers _polartwilight : "day-snow-wind" ,
lightssleetshowersandthunder _day : "day-sleet-storm" ,
lightssleetshowersandthunder _night : "night-alt-sleet-storm" ,
lightssleetshowersandthunder _polartwilight : "day-sleet-storm" ,
lightssnowshowersandthunder _day : "day-snow-thunderstorm" ,
lightssnowshowersandthunder _night : "night-alt-snow-thunderstorm" ,
lightssnowshowersandthunder _polartwilight : "day-snow-thunderstorm" ,
partlycloudy _day : "day-cloudy" ,
partlycloudy _night : "night-alt-cloudy" ,
partlycloudy _polartwilight : "day-cloudy" ,
rain : "rain" ,
rainandthunder : "thunderstorm" ,
rainshowers _day : "day-rain" ,
rainshowers _night : "night-alt-rain" ,
rainshowers _polartwilight : "day-rain" ,
rainshowersandthunder _day : "thunderstorm" ,
rainshowersandthunder _night : "lightning" ,
rainshowersandthunder _polartwilight : "thunderstorm" ,
sleet : "sleet" ,
sleetandthunder : "day-sleet-storm" ,
sleetshowers _day : "day-sleet" ,
sleetshowers _night : "night-alt-sleet" ,
sleetshowers _polartwilight : "day-sleet" ,
sleetshowersandthunder _day : "day-sleet-storm" ,
sleetshowersandthunder _night : "night-alt-sleet-storm" ,
sleetshowersandthunder _polartwilight : "day-sleet-storm" ,
snow : "snowflake-cold" ,
snowandthunder : "lightning" ,
snowshowers _day : "day-snow-wind" ,
snowshowers _night : "night-alt-snow-wind" ,
snowshowers _polartwilight : "day-snow-wind" ,
snowshowersandthunder _day : "day-snow-thunderstorm" ,
snowshowersandthunder _night : "night-alt-snow-thunderstorm" ,
snowshowersandthunder _polartwilight : "day-snow-thunderstorm"
} ;
return weatherTypes . hasOwnProperty ( weatherType ) ? weatherTypes [ weatherType ] : null ;
} ,
2023-12-25 08:17:11 +01:00
getForecastForXHoursFrom ( weather ) {
2022-11-08 06:18:47 +01:00
if ( this . config . currentForecastHours === 1 ) {
if ( weather . next _1 _hours ) {
return weather . next _1 _hours ;
} else if ( weather . next _6 _hours ) {
return weather . next _6 _hours ;
} else {
return weather . next _12 _hours ;
}
} else if ( this . config . currentForecastHours === 6 ) {
if ( weather . next _6 _hours ) {
return weather . next _6 _hours ;
} else if ( weather . next _12 _hours ) {
return weather . next _12 _hours ;
} else {
return weather . next _1 _hours ;
}
} else {
if ( weather . next _12 _hours ) {
return weather . next _12 _hours ;
} else if ( weather . next _6 _hours ) {
return weather . next _6 _hours ;
} else {
return weather . next _1 _hours ;
}
}
} ,
2023-12-25 08:17:11 +01:00
fetchWeatherHourly ( ) {
2022-11-08 06:18:47 +01:00
this . getWeatherForecast ( "hourly" )
. then ( ( forecast ) => {
this . setWeatherHourly ( forecast ) ;
this . updateAvailable ( ) ;
} )
. catch ( ( error ) => {
Log . error ( error ) ;
throw new Error ( error ) ;
} ) ;
} ,
2023-12-25 08:17:11 +01:00
async getWeatherForecast ( type ) {
2023-10-15 13:25:44 +02:00
const [ weatherData , stellarData ] = await Promise . all ( [ this . getWeatherData ( ) , this . getStellarData ( ) ] ) ;
2022-11-08 06:18:47 +01:00
if ( ! weatherData . properties . timeseries || ! weatherData . properties . timeseries [ 0 ] ) {
Log . error ( "No weather data available." ) ;
return ;
}
if ( ! stellarData ) {
2023-01-21 22:40:08 +01:00
Log . warn ( "No stellar data available." ) ;
2022-11-08 06:18:47 +01:00
}
let forecasts ;
switch ( type ) {
case "hourly" :
forecasts = this . getHourlyForecastFrom ( weatherData ) ;
break ;
case "daily" :
default :
forecasts = this . getDailyForecastFrom ( weatherData ) ;
break ;
}
const series = [ ] ;
for ( const forecast of forecasts ) {
series . push ( this . getWeatherDataFrom ( forecast , stellarData , weatherData . properties . meta . units ) ) ;
}
return series ;
} ,
2023-12-25 08:17:11 +01:00
getHourlyForecastFrom ( weatherData ) {
2022-11-08 06:18:47 +01:00
const series = [ ] ;
for ( const forecast of weatherData . properties . timeseries ) {
forecast . symbol = forecast . data . next _1 _hours ? . summary ? . symbol _code ;
Tidy up precipitation (#3023)
Fixes #2953
This is an attempt to fix the issue with precipitation amount and
percentage mixup. I have created a separate
`precipitationPercentage`-variable where the probability of rain can be
stored.
The config options now has the old `showPrecipitationAmount` in addition
to a new setting: `showPrecipitationProbability` (shows the likelihood
of rain).
<details>
<summary>Examples</summary>
### Yr
I tested the Yr weather provider for a Norwegian city Bergen that has a
lot of rain. I have removed properties that are irrelevant for this demo
from the config-samples below.
Config:
```js
{
module: "weather",
config: {
weatherProvider: "yr",
type: "current",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "hourly",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "daily",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
}
```
Result:<br/>
<img width="444" alt="screenshot"
src="https://user-images.githubusercontent.com/34011212/216775423-4e37345c-f915-47e5-8551-7c544ebd24b1.png">
</details>
---------
Co-authored-by: Magnus Marthinsen <magmar@online.no>
Co-authored-by: Veeck <github@veeck.de>
2023-02-04 19:02:55 +01:00
forecast . precipitationAmount = forecast . data . next _1 _hours ? . details ? . precipitation _amount ;
forecast . precipitationProbability = forecast . data . next _1 _hours ? . details ? . probability _of _precipitation ;
2022-11-08 06:18:47 +01:00
forecast . minTemperature = forecast . data . next _1 _hours ? . details ? . air _temperature _min ;
forecast . maxTemperature = forecast . data . next _1 _hours ? . details ? . air _temperature _max ;
forecast . weatherType = this . convertWeatherType ( forecast . symbol , forecast . time ) ;
series . push ( forecast ) ;
}
return series ;
} ,
2023-12-25 08:17:11 +01:00
getDailyForecastFrom ( weatherData ) {
2022-11-08 06:18:47 +01:00
const series = [ ] ;
const days = weatherData . properties . timeseries . reduce ( function ( days , forecast ) {
const date = moment ( forecast . time ) . format ( "YYYY-MM-DD" ) ;
days [ date ] = days [ date ] || [ ] ;
days [ date ] . push ( forecast ) ;
return days ;
} , Object . create ( null ) ) ;
2023-10-15 13:25:44 +02:00
Object . keys ( days ) . forEach ( function ( time ) {
2022-11-08 06:18:47 +01:00
let minTemperature = undefined ;
let maxTemperature = undefined ;
//Default to first entry
let forecast = days [ time ] [ 0 ] ;
forecast . symbol = forecast . data . next _12 _hours ? . summary ? . symbol _code ;
forecast . precipitation = forecast . data . next _12 _hours ? . details ? . precipitation _amount ;
//Coming days
let forecastDiffToEight = undefined ;
for ( const timeseries of days [ time ] ) {
if ( ! timeseries . data . next _6 _hours ) continue ; //next_6_hours has the most data
if ( ! minTemperature || timeseries . data . next _6 _hours . details . air _temperature _min < minTemperature ) minTemperature = timeseries . data . next _6 _hours . details . air _temperature _min ;
if ( ! maxTemperature || maxTemperature < timeseries . data . next _6 _hours . details . air _temperature _max ) maxTemperature = timeseries . data . next _6 _hours . details . air _temperature _max ;
let closestTime = Math . abs ( moment ( timeseries . time ) . local ( ) . set ( { hour : 8 , minute : 0 , second : 0 , millisecond : 0 } ) . diff ( moment ( timeseries . time ) . local ( ) ) ) ;
if ( ( forecastDiffToEight === undefined || closestTime < forecastDiffToEight ) && timeseries . data . next _12 _hours ) {
forecastDiffToEight = closestTime ;
forecast = timeseries ;
}
}
const forecastXHours = forecast . data . next _12 _hours ? ? forecast . data . next _6 _hours ? ? forecast . data . next _1 _hours ;
if ( forecastXHours ) {
forecast . symbol = forecastXHours . summary ? . symbol _code ;
Tidy up precipitation (#3023)
Fixes #2953
This is an attempt to fix the issue with precipitation amount and
percentage mixup. I have created a separate
`precipitationPercentage`-variable where the probability of rain can be
stored.
The config options now has the old `showPrecipitationAmount` in addition
to a new setting: `showPrecipitationProbability` (shows the likelihood
of rain).
<details>
<summary>Examples</summary>
### Yr
I tested the Yr weather provider for a Norwegian city Bergen that has a
lot of rain. I have removed properties that are irrelevant for this demo
from the config-samples below.
Config:
```js
{
module: "weather",
config: {
weatherProvider: "yr",
type: "current",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "hourly",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
},
{
module: "weather",
config: {
weatherProvider: "yr",
type: "daily",
showPrecipitationAmount: true,
showPrecipitationProbability: true
}
}
```
Result:<br/>
<img width="444" alt="screenshot"
src="https://user-images.githubusercontent.com/34011212/216775423-4e37345c-f915-47e5-8551-7c544ebd24b1.png">
</details>
---------
Co-authored-by: Magnus Marthinsen <magmar@online.no>
Co-authored-by: Veeck <github@veeck.de>
2023-02-04 19:02:55 +01:00
forecast . precipitationAmount = forecastXHours . details ? . precipitation _amount ? ? forecast . data . next _6 _hours ? . details ? . precipitation _amount ; // 6 hours is likely to have precipitation amount even if 12 hours does not
forecast . precipitationProbability = forecastXHours . details ? . probability _of _precipitation ;
2022-11-08 06:18:47 +01:00
forecast . minTemperature = minTemperature ;
forecast . maxTemperature = maxTemperature ;
series . push ( forecast ) ;
}
} ) ;
for ( const forecast of series ) {
forecast . weatherType = this . convertWeatherType ( forecast . symbol , forecast . time ) ;
}
return series ;
} ,
2023-12-25 08:17:11 +01:00
fetchWeatherForecast ( ) {
2022-11-08 06:18:47 +01:00
this . getWeatherForecast ( "daily" )
. then ( ( forecast ) => {
this . setWeatherForecast ( forecast ) ;
this . updateAvailable ( ) ;
} )
. catch ( ( error ) => {
Log . error ( error ) ;
throw new Error ( error ) ;
} ) ;
}
} ) ;