3 from datetime import datetime
6 from typing import Dict, List
7 import urllib.request, urllib.error, urllib.parse
15 class weather_renderer(renderer.debuggable_abstaining_renderer):
16 """A renderer to fetch forecast from wunderground."""
18 def __init__(self, name_to_timeout_dict: Dict[str, int], file_prefix: str) -> None:
19 super(weather_renderer, self).__init__(name_to_timeout_dict, False)
20 self.file_prefix = file_prefix
22 def debug_prefix(self) -> str:
23 return f"weather({self.file_prefix})"
25 def periodic_render(self, key: str) -> bool:
26 return self.fetch_weather()
28 def describe_time(self, index: int) -> str:
38 def describe_wind(self, mph: float) -> str:
50 def describe_magnitude(self, mm: float) -> str:
58 def describe_precip(self, rain: float, snow: float) -> str:
59 if rain == 0.0 and snow == 0.0:
60 return "no precipitation"
61 magnitude = rain + snow
62 if rain > 0 and snow > 0:
63 return f"a {self.describe_magnitude(magnitude)} mix of rain and snow"
65 return f"{self.describe_magnitude(magnitude)} rain"
67 return f"{self.describe_magnitude(magnitude)} snow"
70 def fix_caps(self, s: str) -> str:
73 for x in s.split("."):
75 r += x.capitalize() + ". "
76 r = r.replace(". .", ".")
80 self, conditions: List[str], rain: List[float], snow: List[float]
82 # rain snow clouds sun
86 # mostlycloudy.gif F F 6+ X
87 # partlycloudy.gif F F 4+ 4-
89 # partlysunny.gif F F X 5+
90 # mostlysunny.gif F F X 6+
93 # flurries.gif F T X X (<1")
94 # snow.gif F T X X (else)
102 count = min(len(conditions), len(rain), len(snow))
103 for x in range(0, count):
104 seen_rain = rain[x] > 0
105 seen_snow = snow[x] > 0
106 total_snow += snow[x]
107 txt = conditions[x].lower()
110 if "clear" in txt or "sun" in txt:
113 if seen_rain and seen_snow:
120 return "flurries.gif"
126 return "mostlycloudy.gif"
127 elif cloud_count >= 4:
128 return "partlycloudy.gif"
131 elif clear_count >= 6:
132 return "mostlysunny.gif"
133 elif clear_count >= 4:
134 return "partlysunny.gif"
137 def describe_weather(
142 conditions: List[str],
148 # -onight------ -morning----- -afternoon-- -evening----
149 # 12a-3a 3a-6a 6a-9a 9a-12p 12p-3p 3p-6p 6p-9p 9p-12p
150 # Wind: [12.1 3.06 3.47 4.12 3.69 3.31 2.73 2.1]
151 # Conditions: [Clouds Clouds Clouds Clouds Clouds Clouds Clear Clear]
152 # Rain: [0.4 0.2 0 0 0 0 0 0]
153 # Snow: [0 0 0 0 0 0 0 0]
156 count = min(len(wind), len(conditions), len(rain), len(snow))
163 for x in range(0, count):
164 time = self.describe_time(x)
174 if txt != lcondition:
175 if txt != "Snow" and txt != "Rain":
180 txt = self.describe_wind(wind[x])
184 current += txt + " winds"
188 txt = self.describe_precip(rain[x], snow[x])
201 if random.randint(0, 3) == 0:
202 if time != "overnight":
203 descr += current + " in the " + time + ". "
204 descr += current + " overnight. "
206 if time != "overnight":
208 descr += time + ", " + current + ". "
210 current = current.replace("cloudy", "clouds")
211 descr += current + " developing. "
213 if ltime == "overnight" or ltime == "morning":
214 descr += "Conditions continuing the rest of the day. "
215 descr = descr.replace("with breezy winds", "and breezy")
216 descr = descr.replace("Clear developing", "Skies clearing")
217 descr = self.fix_caps(descr)
220 def fetch_weather(self) -> bool:
221 if self.file_prefix == "stevens":
222 text_location = "Stevens Pass, WA"
223 param = "lat=47.74&lon=-121.08"
224 elif self.file_prefix == "telma":
225 text_location = "Telma, WA"
226 param = "lat=47.84&lon=-120.81"
228 text_location = "Bellevue, WA"
231 www = urllib.request.urlopen(
232 "http://api.openweathermap.org/data/2.5/forecast?%s&APPID=%s&units=imperial"
233 % (param, secrets.openweather_key)
235 response = www.read()
237 parsed_json = json.loads(response)
239 # https://openweathermap.org/forecast5
245 # "main":{"temp":261.45,"temp_min":259.086,"temp_max":261.45,"pressure":1023.48,"sea_level":1045.39,"grnd_level":1023.48,"humidity":79,"temp_kf":2.37},
247 # {"id":800,"main":"Clear","description":"clear sky","icon":"02n"}
249 # "clouds":{"all":8},
250 # "wind":{"speed":4.77,"deg":232.505},
253 # "dt_txt":"2017-01-30 18:00:00"
255 # {"dt":1485810000,....
256 with file_writer.file_writer("weather-%s_3_10800.html" % self.file_prefix) as f:
259 <h1>Weather at {text_location}:</h1>
262 <table width=99% cellspacing=10 border=0>
265 count = parsed_json["cnt"]
270 wind: Dict[str, List[float]] = {}
271 conditions: Dict[str, List[str]] = {}
272 rain: Dict[str, List[float]] = {}
273 snow: Dict[str, List[float]] = {}
274 for x in range(0, count):
275 data = parsed_json["list"][x]
276 dt = data["dt_txt"] # 2019-10-07 18:00:00
277 date = dt.split(" ")[0]
278 time = dt.split(" ")[1]
280 conditions[date] = []
287 for x in range(0, count):
288 data = parsed_json["list"][x]
289 dt = data["dt_txt"] # 2019-10-07 18:00:00
290 date = dt.split(" ")[0]
291 time = dt.split(" ")[1]
295 temp = data["main"]["temp"]
296 if highs[date] < temp:
298 if temp < lows[date]:
300 wind[date].append(data["wind"]["speed"])
301 conditions[date].append(data["weather"][0]["main"])
302 if "rain" in data and "3h" in data["rain"]:
303 rain[date].append(data["rain"]["3h"])
306 if "snow" in data and "3h" in data["snow"]:
307 snow[date].append(data["snow"]["3h"])
311 # {u'clouds': {u'all': 0},
312 # u'sys': {u'pod': u'd'},
313 # u'dt_txt': u'2019-10-09 21:00:00',
315 # {u'main': u'Clear',
318 # u'description': u'clear sky'}
324 # u'grnd_level': 1018.95,
325 # u'temp_max': 54.74,
326 # u'sea_level': 1026.46,
328 # u'pressure': 1026.46,
331 # u'wind': {u'speed': 6.31, u'deg': 10.09}}
334 # for x in xrange(0, 5):
335 # fcast = parsed_json['forecast']['txt_forecast']['forecastday'][x]
336 # text = fcast['fcttext']
337 # text = re.subn(r' ([0-9]+)F', r' \1°F', text)[0]
338 # f.write('<td style="vertical-align:top;font-size:75%%"><P STYLE="padding:8px;">%s</P></td>' % text)
339 # f.write('</tr></table>')
343 # f.write("<table border=0 cellspacing=10>\n")
345 for date in sorted(highs.keys()):
346 today = datetime.fromtimestamp(ts[date])
347 formatted_date = today.strftime("%a %e %b")
348 if formatted_date in days_seen:
350 days_seen[formatted_date] = True
351 num_days = len(list(days_seen.keys()))
354 for date in sorted(highs.keys()):
361 today = datetime.fromtimestamp(ts[date])
362 formatted_date = today.strftime("%a %e %b")
363 if formatted_date in days_seen:
365 days_seen[formatted_date] = True
367 '<td width=%d%% style="vertical-align:top;">\n' % (100 / num_days)
369 f.write("<table border=0>\n")
373 " <tr><td colspan=3 height=50><b><center><font size=6>"
375 + "</font></center></b></td></tr>\n"
380 ' <tr><td colspan=3 height=100><center><img src="/kiosk/pages/images/weather/%s" height=125></center></td></tr>\n'
381 % self.pick_icon(conditions[date], rain[date], snow[date])
386 if lows[date] <= 32.5:
389 ' <tr><td width=33%% align=left><font color="%s"><b>%d°F </b></font></td>\n'
390 % (color, int(lows[date]))
397 ' <td width=33%%><center><b><font style="background-color:#dfdfff; color:#003355">%3.1f"</font></b></center></td>\n'
401 f.write(" <td width=33%> </td>\n")
405 if highs[date] >= 80:
408 ' <td align=right><font color="%s"><b> %d°F</b></font></td></tr>\n'
409 % (color, int(highs[date]))
414 '<tr><td colspan=3 style="vertical-align:top;font-size:75%%">%s</td></tr>\n'
415 % self.describe_weather(
424 f.write("</table>\n</td>\n")
425 f.write("</tr></table></center>")
429 # x = weather_renderer({"Stevens": 1000}, "stevens")
430 # x.periodic_render("Stevens")