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"]
|
||||
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.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
|
||||
|
||||
|
||||
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:
|
||||
def __init__(
|
||||
self,
|
||||
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",
|
||||
wind_unit: WIND_UNITS = "meters_sec",
|
||||
language: str = "en",
|
||||
tz_name: str = "UTC"
|
||||
tz_name: str = "UTC",
|
||||
) -> None:
|
||||
self.api_key = api_key
|
||||
self.city_id = city_id
|
||||
self.temp_unit = temp_unit
|
||||
self.wind_unit = wind_unit
|
||||
self.language = language
|
||||
self._api_version = "2.5"
|
||||
self._base_url = f"https://api.openweathermap.org/data/{self._api_version}"
|
||||
self._api_version = 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)
|
||||
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:
|
||||
"""
|
||||
Gets current weather status from this API: https://openweathermap.org/current
|
||||
Decodes the OWM current weather data for our purposes
|
||||
:return:
|
||||
Current weather as dictionary
|
||||
"""
|
||||
# Gets weather forecast from this API:
|
||||
current_weather_url = (
|
||||
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_data = self.get_weather_data_from_owm(weather="current")
|
||||
|
||||
current_weather = {}
|
||||
current_weather["detailed_status"] = current_data["weather"][0]["description"]
|
||||
current_weather["weather_icon_name"] = current_data["weather"][0]["icon"]
|
||||
@ -80,10 +113,17 @@ class OpenWeatherMap:
|
||||
if "gust" in current_data["wind"]:
|
||||
current_weather["wind_gust"] = self.get_converted_windspeed(current_data["wind"]["gust"])
|
||||
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["uvi"] = None # TODO: this is no longer supported with 2.5 API, find alternative
|
||||
current_weather["sunrise"] = datetime.fromtimestamp(current_data["sys"]["sunrise"], tz=self.tz_zone) # unix timestamp -> to our timezone
|
||||
if "uvi" in current_data: # this is only supported in v3.0 API
|
||||
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)
|
||||
|
||||
self.current_weather = current_weather
|
||||
@ -92,21 +132,13 @@ class OpenWeatherMap:
|
||||
|
||||
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.
|
||||
:return:
|
||||
Forecasts data dictionary
|
||||
"""
|
||||
#
|
||||
forecast_url = (
|
||||
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"]
|
||||
forecast_data = self.get_weather_data_from_owm(weather="forecast")
|
||||
|
||||
# Add forecast data to hourly_data_dict list of dictionaries
|
||||
hourly_forecasts = []
|
||||
@ -134,10 +166,12 @@ class OpenWeatherMap:
|
||||
"precip_probability": forecast["pop"]
|
||||
* 100.0, # OWM value is unitless, directly converting to % scale
|
||||
"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
|
||||
|
||||
@ -155,7 +189,7 @@ class OpenWeatherMap:
|
||||
"""
|
||||
# Make sure hourly forecasts are up to date
|
||||
_ = self.get_weather_forecast()
|
||||
|
||||
|
||||
# Calculate the start and end times for the specified number of days from now
|
||||
current_time = datetime.now(tz=self.tz_zone)
|
||||
start_time = (
|
||||
@ -292,8 +326,9 @@ def main():
|
||||
|
||||
current_weather = owm.get_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))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
@ -76,13 +76,15 @@ class Fullweather(inkycal_module):
|
||||
"api_key": {
|
||||
"label": "Please enter openweathermap api-key. You can create one for free on openweathermap",
|
||||
},
|
||||
"location": {
|
||||
"label": "Please enter your location ID found in the url "
|
||||
+ "e.g. https://openweathermap.org/city/4893171 -> ID is 4893171"
|
||||
},
|
||||
"latitude": {"label": "Please enter your location' geographical latitude. E.g. 51.51 for London."},
|
||||
"longitude": {"label": "Please enter your location' geographical longitude. E.g. -0.13 for London."},
|
||||
}
|
||||
|
||||
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"]},
|
||||
"temp_unit": {
|
||||
"label": "Which temperature unit should be used?",
|
||||
@ -142,10 +144,15 @@ class Fullweather(inkycal_module):
|
||||
|
||||
# required parameters
|
||||
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"])
|
||||
|
||||
# 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:
|
||||
self.orientation = config["orientation"]
|
||||
assert self.orientation in ["horizontal", "vertical"]
|
||||
@ -310,7 +317,8 @@ class Fullweather(inkycal_module):
|
||||
self.image.paste(uvIcon, (15, ux_y))
|
||||
|
||||
# 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)
|
||||
image_draw.text((65, ux_y), uvString, font=uvFont, fill=(255, 255, 255))
|
||||
|
||||
@ -602,7 +610,9 @@ class Fullweather(inkycal_module):
|
||||
# Get the weather
|
||||
self.my_owm = OpenWeatherMap(
|
||||
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,
|
||||
wind_unit=self.wind_unit,
|
||||
language=self.language,
|
||||
|
Loading…
Reference in New Issue
Block a user