Add owm 3.0 API capabilities to get UVI reading into the fullscreen weather (again)
This commit is contained in:
parent
a88663defe
commit
d4f9a7a845
@ -17,6 +17,10 @@ from dateutil import tz
|
|||||||
|
|
||||||
TEMP_UNITS = Literal["celsius", "fahrenheit"]
|
TEMP_UNITS = Literal["celsius", "fahrenheit"]
|
||||||
WIND_UNITS = Literal["meters_sec", "km_hour", "miles_hour", "knots", "beaufort"]
|
WIND_UNITS = Literal["meters_sec", "km_hour", "miles_hour", "knots", "beaufort"]
|
||||||
|
WEATHER_TYPE = Literal["current", "forecast"]
|
||||||
|
API_VERSIONS = Literal["2.5", "3.0"]
|
||||||
|
|
||||||
|
API_BASE_URL = "https://api.openweathermap.org/data"
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
logger.setLevel(level=logging.INFO)
|
logger.setLevel(level=logging.INFO)
|
||||||
@ -27,43 +31,72 @@ def is_timestamp_within_range(timestamp: datetime, start_time: datetime, end_tim
|
|||||||
return start_time <= timestamp <= end_time
|
return start_time <= timestamp <= end_time
|
||||||
|
|
||||||
|
|
||||||
|
def get_json_from_url(request_url):
|
||||||
|
response = requests.get(request_url)
|
||||||
|
if not response.ok:
|
||||||
|
raise AssertionError(
|
||||||
|
f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}"
|
||||||
|
)
|
||||||
|
return json.loads(response.text)
|
||||||
|
|
||||||
|
|
||||||
class OpenWeatherMap:
|
class OpenWeatherMap:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
city_id: int,
|
city_id: int = None,
|
||||||
|
lat: float = None,
|
||||||
|
lon: float = None,
|
||||||
|
api_version: API_VERSIONS = "2.5",
|
||||||
temp_unit: TEMP_UNITS = "celsius",
|
temp_unit: TEMP_UNITS = "celsius",
|
||||||
wind_unit: WIND_UNITS = "meters_sec",
|
wind_unit: WIND_UNITS = "meters_sec",
|
||||||
language: str = "en",
|
language: str = "en",
|
||||||
tz_name: str = "UTC"
|
tz_name: str = "UTC",
|
||||||
) -> None:
|
) -> None:
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.city_id = city_id
|
|
||||||
self.temp_unit = temp_unit
|
self.temp_unit = temp_unit
|
||||||
self.wind_unit = wind_unit
|
self.wind_unit = wind_unit
|
||||||
self.language = language
|
self.language = language
|
||||||
self._api_version = "2.5"
|
self._api_version = api_version
|
||||||
self._base_url = f"https://api.openweathermap.org/data/{self._api_version}"
|
if self._api_version == "3.0":
|
||||||
|
assert type(lat) is float and type(lon) is float
|
||||||
|
self.location_substring = (
|
||||||
|
f"lat={str(lat)}&lon={str(lon)}" if (lat is not None and lon is not None) else f"id={str(city_id)}"
|
||||||
|
)
|
||||||
|
|
||||||
self.tz_zone = tz.gettz(tz_name)
|
self.tz_zone = tz.gettz(tz_name)
|
||||||
logger.info(f"OWM wrapper initialized for city id {self.city_id}, language {self.language} and timezone {tz_name}.")
|
logger.info(
|
||||||
|
f"OWM wrapper initialized for API version {self._api_version}, language {self.language} and timezone {tz_name}."
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_weather_data_from_owm(self, weather: WEATHER_TYPE):
|
||||||
|
# Gets current weather or forecast from the configured OWM API.
|
||||||
|
|
||||||
|
if weather == "current":
|
||||||
|
# Gets current weather status from the 2.5 API: https://openweathermap.org/current
|
||||||
|
# This is primarily using the 2.5 API since the 3.0 API actually has less info
|
||||||
|
weather_url = f"{API_BASE_URL}/2.5/weather?{self.location_substring}&appid={self.api_key}&units=Metric&lang={self.language}"
|
||||||
|
weather_data = get_json_from_url(weather_url)
|
||||||
|
# Only if we do have a 3.0 API-enabled key, we can also get the UVI reading from that endpoint: https://openweathermap.org/api/one-call-3
|
||||||
|
if self._api_version == "3.0":
|
||||||
|
weather_url = f"{API_BASE_URL}/3.0/onecall?{self.location_substring}&appid={self.api_key}&exclude=minutely,hourly,daily&units=Metric&lang={self.language}"
|
||||||
|
weather_data["uvi"] = get_json_from_url(weather_url)["current"]["uvi"]
|
||||||
|
elif weather == "forecast":
|
||||||
|
# Gets weather forecasts from the 2.5 API: https://openweathermap.org/forecast5
|
||||||
|
# This is only using the 2.5 API since the 3.0 API actually has less info
|
||||||
|
weather_url = f"{API_BASE_URL}/2.5/forecast?{self.location_substring}&appid={self.api_key}&units=Metric&lang={self.language}"
|
||||||
|
weather_data = get_json_from_url(weather_url)["list"]
|
||||||
|
return weather_data
|
||||||
|
|
||||||
def get_current_weather(self) -> Dict:
|
def get_current_weather(self) -> Dict:
|
||||||
"""
|
"""
|
||||||
Gets current weather status from this API: https://openweathermap.org/current
|
Decodes the OWM current weather data for our purposes
|
||||||
:return:
|
:return:
|
||||||
Current weather as dictionary
|
Current weather as dictionary
|
||||||
"""
|
"""
|
||||||
# Gets weather forecast from this API:
|
|
||||||
current_weather_url = (
|
current_data = self.get_weather_data_from_owm(weather="current")
|
||||||
f"{self._base_url}/weather?id={self.city_id}&appid={self.api_key}&units=Metric&lang={self.language}"
|
|
||||||
)
|
|
||||||
response = requests.get(current_weather_url)
|
|
||||||
if not response.ok:
|
|
||||||
raise AssertionError(
|
|
||||||
f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}"
|
|
||||||
)
|
|
||||||
current_data = json.loads(response.text)
|
|
||||||
|
|
||||||
current_weather = {}
|
current_weather = {}
|
||||||
current_weather["detailed_status"] = current_data["weather"][0]["description"]
|
current_weather["detailed_status"] = current_data["weather"][0]["description"]
|
||||||
current_weather["weather_icon_name"] = current_data["weather"][0]["icon"]
|
current_weather["weather_icon_name"] = current_data["weather"][0]["icon"]
|
||||||
@ -80,10 +113,17 @@ class OpenWeatherMap:
|
|||||||
if "gust" in current_data["wind"]:
|
if "gust" in current_data["wind"]:
|
||||||
current_weather["wind_gust"] = self.get_converted_windspeed(current_data["wind"]["gust"])
|
current_weather["wind_gust"] = self.get_converted_windspeed(current_data["wind"]["gust"])
|
||||||
else:
|
else:
|
||||||
logger.info(f"OpenWeatherMap response did not contain a wind gust speed. Using base wind: {current_weather['wind']} m/s.")
|
logger.info(
|
||||||
|
f"OpenWeatherMap response did not contain a wind gust speed. Using base wind: {current_weather['wind']} m/s."
|
||||||
|
)
|
||||||
current_weather["wind_gust"] = current_weather["wind"]
|
current_weather["wind_gust"] = current_weather["wind"]
|
||||||
current_weather["uvi"] = None # TODO: this is no longer supported with 2.5 API, find alternative
|
if "uvi" in current_data: # this is only supported in v3.0 API
|
||||||
current_weather["sunrise"] = datetime.fromtimestamp(current_data["sys"]["sunrise"], tz=self.tz_zone) # unix timestamp -> to our timezone
|
current_weather["uvi"] = current_data["uvi"]
|
||||||
|
else:
|
||||||
|
current_weather["uvi"] = None
|
||||||
|
current_weather["sunrise"] = datetime.fromtimestamp(
|
||||||
|
current_data["sys"]["sunrise"], tz=self.tz_zone
|
||||||
|
) # unix timestamp -> to our timezone
|
||||||
current_weather["sunset"] = datetime.fromtimestamp(current_data["sys"]["sunset"], tz=self.tz_zone)
|
current_weather["sunset"] = datetime.fromtimestamp(current_data["sys"]["sunset"], tz=self.tz_zone)
|
||||||
|
|
||||||
self.current_weather = current_weather
|
self.current_weather = current_weather
|
||||||
@ -92,21 +132,13 @@ class OpenWeatherMap:
|
|||||||
|
|
||||||
def get_weather_forecast(self) -> List[Dict]:
|
def get_weather_forecast(self) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
Gets weather forecasts from this API: https://openweathermap.org/forecast5
|
Decodes the OWM weather forecast for our purposes
|
||||||
What you get is a list of 40 forecasts for 3-hour time slices, totaling to 5 days.
|
What you get is a list of 40 forecasts for 3-hour time slices, totaling to 5 days.
|
||||||
:return:
|
:return:
|
||||||
Forecasts data dictionary
|
Forecasts data dictionary
|
||||||
"""
|
"""
|
||||||
#
|
#
|
||||||
forecast_url = (
|
forecast_data = self.get_weather_data_from_owm(weather="forecast")
|
||||||
f"{self._base_url}/forecast?id={self.city_id}&appid={self.api_key}&units=Metric&lang={self.language}"
|
|
||||||
)
|
|
||||||
response = requests.get(forecast_url)
|
|
||||||
if not response.ok:
|
|
||||||
raise AssertionError(
|
|
||||||
f"Failure getting the current weather: code {response.status_code}. Reason: {response.text}"
|
|
||||||
)
|
|
||||||
forecast_data = json.loads(response.text)["list"]
|
|
||||||
|
|
||||||
# Add forecast data to hourly_data_dict list of dictionaries
|
# Add forecast data to hourly_data_dict list of dictionaries
|
||||||
hourly_forecasts = []
|
hourly_forecasts = []
|
||||||
@ -134,10 +166,12 @@ class OpenWeatherMap:
|
|||||||
"precip_probability": forecast["pop"]
|
"precip_probability": forecast["pop"]
|
||||||
* 100.0, # OWM value is unitless, directly converting to % scale
|
* 100.0, # OWM value is unitless, directly converting to % scale
|
||||||
"icon": forecast["weather"][0]["icon"],
|
"icon": forecast["weather"][0]["icon"],
|
||||||
"datetime": datetime.fromtimestamp(forecast["dt"], tz=self.tz_zone)
|
"datetime": datetime.fromtimestamp(forecast["dt"], tz=self.tz_zone),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
logger.debug(f"Added rain forecast at {datetime.fromtimestamp(forecast['dt'], tz=self.tz_zone)}: {precip_mm}")
|
logger.debug(
|
||||||
|
f"Added rain forecast at {datetime.fromtimestamp(forecast['dt'], tz=self.tz_zone)}: {precip_mm}"
|
||||||
|
)
|
||||||
|
|
||||||
self.hourly_forecasts = hourly_forecasts
|
self.hourly_forecasts = hourly_forecasts
|
||||||
|
|
||||||
@ -155,7 +189,7 @@ class OpenWeatherMap:
|
|||||||
"""
|
"""
|
||||||
# Make sure hourly forecasts are up to date
|
# Make sure hourly forecasts are up to date
|
||||||
_ = self.get_weather_forecast()
|
_ = self.get_weather_forecast()
|
||||||
|
|
||||||
# Calculate the start and end times for the specified number of days from now
|
# Calculate the start and end times for the specified number of days from now
|
||||||
current_time = datetime.now(tz=self.tz_zone)
|
current_time = datetime.now(tz=self.tz_zone)
|
||||||
start_time = (
|
start_time = (
|
||||||
@ -292,8 +326,9 @@ def main():
|
|||||||
|
|
||||||
current_weather = owm.get_current_weather()
|
current_weather = owm.get_current_weather()
|
||||||
print(current_weather)
|
print(current_weather)
|
||||||
hourly_forecasts = owm.get_weather_forecast()
|
_ = owm.get_weather_forecast()
|
||||||
print(owm.get_forecast_for_day(days_from_today=2))
|
print(owm.get_forecast_for_day(days_from_today=2))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -76,13 +76,15 @@ class Fullweather(inkycal_module):
|
|||||||
"api_key": {
|
"api_key": {
|
||||||
"label": "Please enter openweathermap api-key. You can create one for free on openweathermap",
|
"label": "Please enter openweathermap api-key. You can create one for free on openweathermap",
|
||||||
},
|
},
|
||||||
"location": {
|
"latitude": {"label": "Please enter your location' geographical latitude. E.g. 51.51 for London."},
|
||||||
"label": "Please enter your location ID found in the url "
|
"longitude": {"label": "Please enter your location' geographical longitude. E.g. -0.13 for London."},
|
||||||
+ "e.g. https://openweathermap.org/city/4893171 -> ID is 4893171"
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
optional = {
|
optional = {
|
||||||
|
"api_version": {
|
||||||
|
"label": "Please enter openweathermap api version. Default is '2.5'.",
|
||||||
|
"options": ["2.5", "3.0"],
|
||||||
|
},
|
||||||
"orientation": {"label": "Please select the desired orientation", "options": ["vertical", "horizontal"]},
|
"orientation": {"label": "Please select the desired orientation", "options": ["vertical", "horizontal"]},
|
||||||
"temp_unit": {
|
"temp_unit": {
|
||||||
"label": "Which temperature unit should be used?",
|
"label": "Which temperature unit should be used?",
|
||||||
@ -142,10 +144,15 @@ class Fullweather(inkycal_module):
|
|||||||
|
|
||||||
# required parameters
|
# required parameters
|
||||||
self.api_key = config["api_key"]
|
self.api_key = config["api_key"]
|
||||||
self.location = int(config["location"])
|
self.location_lat = float(config["latitude"])
|
||||||
|
self.location_lon = float(config["longitude"])
|
||||||
self.font_size = int(config["fontsize"])
|
self.font_size = int(config["fontsize"])
|
||||||
|
|
||||||
# optional parameters
|
# optional parameters
|
||||||
|
if "api_version" in config and config["api_version"] == "3.0":
|
||||||
|
self.owm_api_version = "3.0"
|
||||||
|
else:
|
||||||
|
self.owm_api_version = "2.5"
|
||||||
if "orientation" in config:
|
if "orientation" in config:
|
||||||
self.orientation = config["orientation"]
|
self.orientation = config["orientation"]
|
||||||
assert self.orientation in ["horizontal", "vertical"]
|
assert self.orientation in ["horizontal", "vertical"]
|
||||||
@ -310,7 +317,8 @@ class Fullweather(inkycal_module):
|
|||||||
self.image.paste(uvIcon, (15, ux_y))
|
self.image.paste(uvIcon, (15, ux_y))
|
||||||
|
|
||||||
# uvindex
|
# uvindex
|
||||||
uvString = f"{self.current_weather['uvi'] if self.current_weather['uvi'] else '0'}"
|
uvi = self.current_weather["uvi"] if self.current_weather["uvi"] else 0.0
|
||||||
|
uvString = f"{uvi:.1f}"
|
||||||
uvFont = self.get_font("Bold", self.font_size + 8)
|
uvFont = self.get_font("Bold", self.font_size + 8)
|
||||||
image_draw.text((65, ux_y), uvString, font=uvFont, fill=(255, 255, 255))
|
image_draw.text((65, ux_y), uvString, font=uvFont, fill=(255, 255, 255))
|
||||||
|
|
||||||
@ -602,7 +610,9 @@ class Fullweather(inkycal_module):
|
|||||||
# Get the weather
|
# Get the weather
|
||||||
self.my_owm = OpenWeatherMap(
|
self.my_owm = OpenWeatherMap(
|
||||||
api_key=self.api_key,
|
api_key=self.api_key,
|
||||||
city_id=self.location,
|
api_version=self.owm_api_version,
|
||||||
|
lat=self.location_lat,
|
||||||
|
lon=self.location_lon,
|
||||||
temp_unit=self.temp_unit,
|
temp_unit=self.temp_unit,
|
||||||
wind_unit=self.wind_unit,
|
wind_unit=self.wind_unit,
|
||||||
language=self.language,
|
language=self.language,
|
||||||
|
Loading…
Reference in New Issue
Block a user