3 from datetime import datetime
5 from typing import Dict, List
12 import kiosk_secrets as secrets
16 class weather_renderer(renderer.abstaining_renderer):
17 """A renderer to fetch forecast from wunderground."""
19 def __init__(self, name_to_timeout_dict: Dict[str, int], file_prefix: str) -> None:
20 super().__init__(name_to_timeout_dict)
21 self.file_prefix = file_prefix
23 def debug_prefix(self) -> str:
24 return f"weather({self.file_prefix})"
26 def periodic_render(self, key: str) -> bool:
27 return self.fetch_weather()
29 def describe_time(self, index: int) -> str:
39 def describe_wind(self, mph: float) -> str:
51 def describe_magnitude(self, mm: float) -> str:
59 def describe_precip(self, rain: float, snow: float) -> str:
60 if rain == 0.0 and snow == 0.0:
61 return "no precipitation"
62 magnitude = rain + snow
63 if rain > 0 and snow > 0:
64 return f"a {self.describe_magnitude(magnitude)} mix of rain and snow"
66 return f"{self.describe_magnitude(magnitude)} rain"
68 return f"{self.describe_magnitude(magnitude)} snow"
71 def fix_caps(self, s: str) -> str:
74 for x in s.split("."):
76 r += x.capitalize() + ". "
77 r = r.replace(". .", ".")
81 self, conditions: List[str], rain: List[float], snow: List[float]
83 # rain snow clouds sun
87 # mostlycloudy.gif F F 6+ X
88 # partlycloudy.gif F F 4+ 4-
90 # partlysunny.gif F F X 5+
91 # mostlysunny.gif F F X 6+
94 # flurries.gif F T X X (<1")
95 # snow.gif F T X X (else)
103 count = min(len(conditions), len(rain), len(snow))
104 for x in range(0, count):
105 seen_rain = rain[x] > 0
106 seen_snow = snow[x] > 0
107 total_snow += snow[x]
108 txt = conditions[x].lower()
111 if "clear" in txt or "sun" in txt:
114 if seen_rain and seen_snow:
121 return "flurries.gif"
127 return "mostlycloudy.gif"
128 elif cloud_count >= 4:
129 return "partlycloudy.gif"
132 elif clear_count >= 6:
133 return "mostlysunny.gif"
134 elif clear_count >= 4:
135 return "partlysunny.gif"
138 def describe_weather(
143 conditions: List[str],
149 # -onight------ -morning----- -afternoon-- -evening----
150 # 12a-3a 3a-6a 6a-9a 9a-12p 12p-3p 3p-6p 6p-9p 9p-12p
151 # Wind: [12.1 3.06 3.47 4.12 3.69 3.31 2.73 2.1]
152 # Conditions: [Clouds Clouds Clouds Clouds Clouds Clouds Clear Clear]
153 # Rain: [0.4 0.2 0 0 0 0 0 0]
154 # Snow: [0 0 0 0 0 0 0 0]
157 count = min(len(wind), len(conditions), len(rain), len(snow))
164 for x in range(0, count):
165 time = self.describe_time(x)
175 if txt != lcondition:
176 if txt != "Snow" and txt != "Rain":
181 txt = self.describe_wind(wind[x])
185 current += txt + " winds"
189 txt = self.describe_precip(rain[x], snow[x])
202 if random.randint(0, 3) == 0:
203 if time != "overnight":
204 descr += current + " in the " + time + ". "
205 descr += current + " overnight. "
207 if time != "overnight":
209 descr += time + ", " + current + ". "
211 current = current.replace("cloudy", "clouds")
212 descr += current + " developing. "
214 if ltime == "overnight" or ltime == "morning":
215 descr += "Conditions continuing the rest of the day. "
216 descr = descr.replace("with breezy winds", "and breezy")
217 descr = descr.replace("Clear developing", "Skies clearing")
218 descr = self.fix_caps(descr)
221 def fetch_weather(self) -> bool:
222 if self.file_prefix == "stevens":
223 text_location = "Stevens Pass, WA"
224 param = "lat=47.74&lon=-121.08"
225 elif self.file_prefix == "telma":
226 text_location = "Telma, WA"
227 param = "lat=47.84&lon=-120.81"
229 text_location = "Bellevue, WA"
232 www = urllib.request.urlopen(
233 "http://api.openweathermap.org/data/2.5/forecast?%s&APPID=%s&units=imperial"
234 % (param, secrets.openweather_key)
236 response = www.read()
238 parsed_json = json.loads(response)
240 # https://openweathermap.org/forecast5
246 # "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},
248 # {"id":800,"main":"Clear","description":"clear sky","icon":"02n"}
250 # "clouds":{"all":8},
251 # "wind":{"speed":4.77,"deg":232.505},
254 # "dt_txt":"2017-01-30 18:00:00"
256 # {"dt":1485810000,....
257 with file_writer.file_writer("weather-%s_3_10800.html" % self.file_prefix) as f:
260 <h1>Weather at {text_location}:</h1>
263 <table width=99% cellspacing=10 border=0>
266 count = parsed_json["cnt"]
271 wind: Dict[str, List[float]] = {}
272 conditions: Dict[str, List[str]] = {}
273 rain: Dict[str, List[float]] = {}
274 snow: Dict[str, List[float]] = {}
275 for x in range(0, count):
276 data = parsed_json["list"][x]
277 dt = data["dt_txt"] # 2019-10-07 18:00:00
278 date = dt.split(" ")[0]
279 time = dt.split(" ")[1]
281 conditions[date] = []
288 for x in range(0, count):
289 data = parsed_json["list"][x]
290 dt = data["dt_txt"] # 2019-10-07 18:00:00
291 date = dt.split(" ")[0]
292 time = dt.split(" ")[1]
296 temp = data["main"]["temp"]
297 if highs[date] < temp:
299 if temp < lows[date]:
301 wind[date].append(data["wind"]["speed"])
302 conditions[date].append(data["weather"][0]["main"])
303 if "rain" in data and "3h" in data["rain"]:
304 rain[date].append(data["rain"]["3h"])
307 if "snow" in data and "3h" in data["snow"]:
308 snow[date].append(data["snow"]["3h"])
312 # {u'clouds': {u'all': 0},
313 # u'sys': {u'pod': u'd'},
314 # u'dt_txt': u'2019-10-09 21:00:00',
316 # {u'main': u'Clear',
319 # u'description': u'clear sky'}
325 # u'grnd_level': 1018.95,
326 # u'temp_max': 54.74,
327 # u'sea_level': 1026.46,
329 # u'pressure': 1026.46,
332 # u'wind': {u'speed': 6.31, u'deg': 10.09}}
335 # for x in xrange(0, 5):
336 # fcast = parsed_json['forecast']['txt_forecast']['forecastday'][x]
337 # text = fcast['fcttext']
338 # text = re.subn(r' ([0-9]+)F', r' \1°F', text)[0]
339 # f.write('<td style="vertical-align:top;font-size:75%%"><P STYLE="padding:8px;">%s</P></td>' % text)
340 # f.write('</tr></table>')
344 # f.write("<table border=0 cellspacing=10>\n")
346 for date in sorted(highs.keys()):
347 today = datetime.fromtimestamp(ts[date])
348 formatted_date = today.strftime("%a %e %b")
349 if formatted_date in days_seen:
351 days_seen[formatted_date] = True
352 num_days = len(list(days_seen.keys()))
355 for date in sorted(highs.keys()):
362 today = datetime.fromtimestamp(ts[date])
363 formatted_date = today.strftime("%a %e %b")
364 if formatted_date in days_seen:
366 days_seen[formatted_date] = True
368 '<td width=%d%% style="vertical-align:top;">\n' % (100 / num_days)
370 f.write("<table border=0>\n")
374 " <tr><td colspan=3 height=50><b><center><font size=6>"
376 + "</font></center></b></td></tr>\n"
381 ' <tr><td colspan=3 height=100><center><img src="/kiosk/images/weather/%s" height=125></center></td></tr>\n'
382 % self.pick_icon(conditions[date], rain[date], snow[date])
387 if lows[date] <= 32.5:
390 ' <tr><td width=33%% align=left><font color="%s"><b>%d°F </b></font></td>\n'
391 % (color, int(lows[date]))
398 ' <td width=33%%><center><b><font style="background-color:#dfdfff; color:#003355">%3.1f"</font></b></center></td>\n'
402 f.write(" <td width=33%> </td>\n")
406 if highs[date] >= 80:
409 ' <td align=right><font color="%s"><b> %d°F</b></font></td></tr>\n'
410 % (color, int(highs[date]))
415 '<tr><td colspan=3 style="vertical-align:top;font-size:75%%">%s</td></tr>\n'
416 % self.describe_weather(
425 f.write("</table>\n</td>\n")
426 f.write("</tr></table></center>")
430 # x = weather_renderer({"Stevens": 1000}, "stevens")
431 # x.periodic_render("Stevens")