8 from datetime import datetime
9 from collections import defaultdict
10 from typing import Dict, List
14 import kiosk_secrets as secrets
16 logger = logging.getLogger(__name__)
19 class weather_renderer(renderer.abstaining_renderer):
20 """A renderer to fetch forecast from wunderground."""
22 def __init__(self, name_to_timeout_dict: Dict[str, int], file_prefix: str) -> None:
23 super().__init__(name_to_timeout_dict)
24 self.file_prefix = file_prefix
27 def pick_icon(conditions: List[str], rain: List[float], snow: List[float]) -> str:
28 # rain snow clouds sun
32 # mostlycloudy.gif F F 6+ X
33 # partlycloudy.gif F F 4+ 4-
35 # partlysunny.gif F F X 5+
36 # mostlysunny.gif F F X 6+
39 # flurries.gif F T X X (<1")
40 # snow.gif F T X X (else)
48 count = min(len(conditions), len(rain), len(snow))
49 for x in range(0, count):
50 seen_rain = rain[x] > 0
51 seen_snow = snow[x] > 0
53 txt = conditions[x].lower()
56 if "clear" in txt or "sun" in txt:
59 if seen_rain and seen_snow:
72 return "mostlycloudy.gif"
73 elif cloud_count >= 4:
74 return "partlycloudy.gif"
77 elif clear_count >= 6:
78 return "mostlysunny.gif"
79 elif clear_count >= 4:
80 return "partlysunny.gif"
83 def periodic_render(self, key: str) -> bool:
84 return self.fetch_weather()
86 def fetch_weather(self) -> bool:
87 if self.file_prefix == "stevens":
88 text_location = "Stevens Pass, WA"
89 param = "lat=47.7322&lon=-121.1025"
90 elif self.file_prefix == "telma":
91 text_location = "Telma, WA"
92 param = "lat=47.84&lon=-120.81"
94 text_location = "Bellevue, WA"
96 secret = secrets.openweather_key
97 url = f"http://api.openweathermap.org/data/2.5/forecast?{param}&APPID={secret}&units=imperial"
98 logger.info(f"GETting {url}")
99 www = urllib.request.urlopen(url)
100 response = www.read()
102 if www.getcode() != 200:
103 logger.error("Bad response: {response}")
104 raise Exception(response)
105 parsed_json = json.loads(response)
106 logger.info("URL read ok")
108 # https://openweathermap.org/forecast5
114 # "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},
116 # {"id":800,"main":"Clear","description":"clear sky","icon":"02n"}
118 # "clouds":{"all":8},
119 # "wind":{"speed":4.77,"deg":232.505},
122 # "dt_txt":"2017-01-30 18:00:00"
124 # {"dt":1485810000,....
126 with file_writer.file_writer(f"weather-{self.file_prefix}_3_10800.html") as f:
129 <h1>Upcoming weather at {text_location}:</h1>
131 <script src="/kiosk/Chart.js"></script>"""
136 function makePrecipChart(name, xValues, yValues) {
145 backgroundColor: "rgba(0,0,255,0.1)",
146 borderColor: "rgba(0,0,255,0.9)",
175 return new Chart(name, config);
181 count = parsed_json["cnt"]
186 wind: Dict[str, List[float]] = defaultdict(list)
187 conditions: Dict[str, List[str]] = defaultdict(list)
188 rain: Dict[str, List[float]] = defaultdict(list)
189 snow: Dict[str, List[float]] = defaultdict(list)
190 precip: Dict[str, List[float]] = defaultdict(list)
192 for x in range(0, count):
193 data = parsed_json["list"][x]
194 dt = data["dt_txt"] # 2019-10-07 18:00:00
195 (date, time) = dt.split(" ")
197 if _ not in ts or _ > ts[date]:
199 temp = data["main"]["temp"]
202 if date not in highs or highs[date] < temp:
204 if date not in lows or lows[date] > temp:
207 # Windspeed and conditions
208 wind[date].append(data["wind"]["speed"])
209 conditions[date].append(data["weather"][0]["main"])
211 # 3h precipitation (rain / snow)
212 if "rain" in data and "3h" in data["rain"]:
213 rain[date].append(data["rain"]["3h"])
216 if "snow" in data and "3h" in data["snow"]:
217 snow[date].append(data["snow"]["3h"])
221 # {u'clouds': {u'all': 0},
222 # u'sys': {u'pod': u'd'},
223 # u'dt_txt': u'2019-10-09 21:00:00',
225 # {u'main': u'Clear',
228 # u'description': u'clear sky'}
234 # u'grnd_level': 1018.95,
235 # u'temp_max': 54.74,
236 # u'sea_level': 1026.46,
238 # u'pressure': 1026.46,
241 # u'wind': {u'speed': 6.31, u'deg': 10.09}}
244 for date in sorted(highs.keys()):
245 day = datetime.fromtimestamp(ts[date])
246 formatted_date = day.strftime("%a %e %b")
247 if formatted_date in days_seen:
249 days_seen.add(formatted_date)
250 total = len(days_seen)
254 for n, date in enumerate(sorted(highs.keys())):
258 f.write('<div STYLE="overflow:hidden; width:100%; height:500px">')
259 remaining = total - n
263 width = f"{100/remaining}%"
265 aggregate_daily_precip = 0.0
266 for r, s in zip(rain[date], snow[date]):
267 hourly_aggregate = r + s
269 # The weather report is always way wrong about Stevens.
270 if self.file_prefix == "stevens":
271 hourly_aggregate *= 3.5
272 aggregate_daily_precip += hourly_aggregate
273 precip[date].append(hourly_aggregate)
275 f"Aggregate precip on {date} was {aggregate_daily_precip} mm"
278 while len(precip[date]) < 8:
279 precip[date].insert(0, 0)
282 day = datetime.fromtimestamp(ts[date])
283 formatted_date = day.strftime("%a %e %b")
284 if formatted_date in days_seen:
286 days_seen.add(formatted_date)
288 f'<div style="width:{width}; height:500px; float:left"><table>\n'
295 <td colspan=3 height=50>
297 <font size=7><b>{formatted_date}</b></font>
304 icon = weather_renderer.pick_icon(
305 conditions[date], rain[date], snow[date]
310 <td colspan=3 height=100>
312 <img src="/kiosk/images/weather/{icon}" height=145>
320 if lows[date] <= 32.5:
325 <td width=33% align=left>
326 <font color="{color}" size=6>
327 <b>{int(lows[date])}°F </b>
333 # Total aggregate_precip in inches (from mm)
334 aggregate_daily_precip /= 25.4
335 if aggregate_daily_precip > 0.001:
340 <font style="background-color:#dfdfff; color:#003355" size=6>
341 <b>{aggregate_daily_precip:3.1f}”</b>
348 f.write(" <td width=33%> </td>\n")
350 # High temp + precip chart
352 if highs[date] >= 80:
357 <font color="{color}" size=6>
358 <b> {int(highs[date])}°F</b>
368 <td colspan=3 style="vertical-align:top;">
369 <canvas id="myChart{n}" style="width:100%;max-width:400px;height:180px;"></canvas>
375 f.write(precip[date].__repr__())
378 var xValues{n} = [ 3, 6, 9, 12, 15, 18, 21, 24 ];
379 makePrecipChart("myChart{n}", xValues{n}, yValues{n});
385 f.write("</div></center>")
389 # x = weather_renderer({"Stevens": 1000}, "stevens")
390 # x.periodic_render("Stevens")