3 from dataclasses import dataclass
8 import astral # type: ignore
9 from astral.sun import sun # type: ignore
10 from bs4 import BeautifulSoup # type: ignore
16 import dateparse.dateparse_utils as dp
20 logger = logging.getLogger(__name__)
22 cfg = config.add_commandline_args(
23 f'Cached Weather Forecast ({__file__})',
24 'Arguments controlling detailed weather rendering'
27 '--weather_forecast_cachefile',
29 default='/home/scott/.weather_forecast_cache',
31 help='File in which to cache weather data'
34 '--weather_forecast_stalest_acceptable',
35 type=argparse_utils.valid_duration,
36 default=datetime.timedelta(seconds=7200), # 2 hours
38 help='Maximum acceptable age of cached data. If zero, forces a refetch'
43 class WeatherForecast:
44 date: datetime.date # The date
45 sunrise: datetime.datetime # Sunrise datetime
46 sunset: datetime.datetime # Sunset datetime
47 description: str # Textual description of weather
50 @persistent.persistent_autoloaded_singleton()
51 class CachedDetailedWeatherForecast(object):
52 def __init__(self, forecasts = None):
53 if forecasts is not None:
54 self.forecasts = forecasts
57 now = datetime_utils.now_pacific()
60 # Ask the raspberry pi about the outside temperature.
61 www = urllib.request.urlopen(
62 "http://10.0.0.75/~pi/outside_temp"
64 current_temp = www.read().decode("utf-8")
65 current_temp = float(current_temp)
68 current_temp = round(current_temp)
71 # Get a weather forecast for Bellevue.
72 www = urllib.request.urlopen(
73 "https://forecast.weather.gov/MapClick.php?lat=47.652775&lon=-122.170716"
75 forecast_response = www.read()
78 soup = BeautifulSoup(forecast_response, "html.parser")
79 forecast = soup.find(id='detailed-forecast-body')
80 parser = dp.DateParser()
84 for (day, txt) in zip(
85 forecast.find_all('b'),
86 forecast.find_all(class_='col-sm-10 forecast-text')
90 dt = parser.parse(day.get_text())
95 # Compute sunrise/sunset times on dt.
96 city = astral.LocationInfo(
97 "Bellevue", "USA", "US/Pacific", 47.653, -122.171
99 s = sun(city.observer, date=dt, tzinfo=pytz.timezone("US/Pacific"))
100 sunrise = s['sunrise']
103 if dt.date == now.date:
104 blurb = f'{day.get_text()}: The current outside tempterature is {current_temp}. ' + txt.get_text()
106 blurb = f'{day.get_text()}: {txt.get_text()}'
107 blurb = text_utils.wrap_string(blurb, 80)
109 if dt.date() in self.forecasts:
110 self.forecasts[dt.date()].description += '\n' + blurb
112 self.forecasts[dt.date()] = WeatherForecast(
121 if persistent.was_file_written_within_n_seconds(
122 config.config['weather_forecast_cachefile'],
123 config.config['weather_forecast_stalest_acceptable'].total_seconds(),
126 with open(config.config['weather_forecast_cachefile'], 'rb') as rf:
127 weather_data = pickle.load(rf)
128 return cls(weather_data)
133 with open(config.config['weather_forecast_cachefile'], 'wb') as wf:
137 pickle.HIGHEST_PROTOCOL,