#!/usr/bin/env python3
-from dataclasses import dataclass
+# © Copyright 2021-2022, Scott Gasch
+
+"""How's the weather going to be tomorrow?"""
+
import datetime
import logging
import os
import urllib.request
+from dataclasses import dataclass
+from typing import Any
import astral # type: ignore
+import pytz
from astral.sun import sun # type: ignore
from bs4 import BeautifulSoup # type: ignore
-import pytz
+from overrides import overrides
import argparse_utils
import config
-import datetime_utils
import dateparse.dateparse_utils as dp
+import datetime_utils
import persistent
+import smart_home.thermometers as temps
import text_utils
logger = logging.getLogger(__name__)
cfg = config.add_commandline_args(
f'Cached Weather Forecast ({__file__})',
- 'Arguments controlling detailed weather rendering'
+ 'Arguments controlling detailed weather rendering',
)
cfg.add_argument(
'--weather_forecast_cachefile',
type=str,
- default=f'{os.environ["HOME"]}/.weather_forecast_cache',
+ default=f'{os.environ["HOME"]}/cache/.weather_forecast_cache',
metavar='FILENAME',
- help='File in which to cache weather data'
+ help='File in which to cache weather data',
)
cfg.add_argument(
'--weather_forecast_stalest_acceptable',
type=argparse_utils.valid_duration,
- default=datetime.timedelta(seconds=7200), # 2 hours
+ default=datetime.timedelta(seconds=7200), # 2 hours
metavar='DURATION',
- help='Maximum acceptable age of cached data. If zero, forces a refetch'
+ help='Maximum acceptable age of cached data. If zero, forces a refetch',
)
@dataclass
class WeatherForecast:
- date: datetime.date # The date
- sunrise: datetime.datetime # Sunrise datetime
- sunset: datetime.datetime # Sunset datetime
- description: str # Textual description of weather
+ date: datetime.date # The date
+ sunrise: datetime.datetime # Sunrise datetime
+ sunset: datetime.datetime # Sunset datetime
+ description: str # Textual description of weather
-class CachedDetailedWeatherForecast(object):
- def __init__(self, forecasts = None):
+class CachedDetailedWeatherForecast(persistent.Persistent):
+ def __init__(self, forecasts=None):
if forecasts is not None:
self.forecasts = forecasts
return
self.forecasts = {}
# Ask the raspberry pi about the outside temperature.
- www = urllib.request.urlopen(
- "http://10.0.0.75/~pi/outside_temp"
+ current_temp = temps.ThermometerRegistry().read_temperature(
+ 'house_outside', convert_to_fahrenheit=True
)
- current_temp = www.read().decode("utf-8")
- current_temp = float(current_temp)
- current_temp *= (9/5)
- current_temp += 32.0
- current_temp = round(current_temp)
- www.close()
# Get a weather forecast for Bellevue.
www = urllib.request.urlopen(
last_dt = now
dt = now
for (day, txt) in zip(
- forecast.find_all('b'),
- forecast.find_all(class_='col-sm-10 forecast-text')
+ forecast.find_all('b'),
+ forecast.find_all(class_='col-sm-10 forecast-text'),
):
last_dt = dt
try:
assert dt is not None
# Compute sunrise/sunset times on dt.
- city = astral.LocationInfo(
- "Bellevue", "USA", "US/Pacific", 47.653, -122.171
- )
+ city = astral.LocationInfo("Bellevue", "USA", "US/Pacific", 47.653, -122.171)
s = sun(city.observer, date=dt, tzinfo=pytz.timezone("US/Pacific"))
sunrise = s['sunrise']
sunset = s['sunset']
- if dt.date == now.date and not said_temp:
- blurb = f'{day.get_text()}: The current outside tempterature is {current_temp}. ' + txt.get_text()
+ if dt.date() == now.date() and not said_temp and current_temp is not None:
+ blurb = f'{day.get_text()}: The current outside tempterature is {current_temp}. '
+ blurb += txt.get_text()
said_temp = True
else:
blurb = f'{day.get_text()}: {txt.get_text()}'
self.forecasts[dt.date()].description += '\n' + blurb
else:
self.forecasts[dt.date()] = WeatherForecast(
- date = dt,
- sunrise = sunrise,
- sunset = sunset,
- description = blurb,
+ date=dt,
+ sunrise=sunrise,
+ sunset=sunset,
+ description=blurb,
)
@classmethod
- def load(cls):
+ @overrides
+ def load(cls) -> Any:
if persistent.was_file_written_within_n_seconds(
- config.config['weather_forecast_cachefile'],
- config.config['weather_forecast_stalest_acceptable'].total_seconds(),
+ config.config['weather_forecast_cachefile'],
+ config.config['weather_forecast_stalest_acceptable'].total_seconds(),
):
import pickle
+
with open(config.config['weather_forecast_cachefile'], 'rb') as rf:
weather_data = pickle.load(rf)
return cls(weather_data)
return None
- def save(self):
+ @overrides
+ def save(self) -> bool:
import pickle
+
with open(config.config['weather_forecast_cachefile'], 'wb') as wf:
pickle.dump(
self.forecasts,
wf,
pickle.HIGHEST_PROTOCOL,
)
+ return True