from datetime import datetime
import json
-import re
from typing import Dict, List
-import urllib.request, urllib.error, urllib.parse
+import urllib.request
+import urllib.error
+import urllib.parse
import file_writer
import renderer
-import secrets
-import random
+import kiosk_secrets as secrets
-class weather_renderer(renderer.debuggable_abstaining_renderer):
+class weather_renderer(renderer.abstaining_renderer):
"""A renderer to fetch forecast from wunderground."""
def __init__(self, name_to_timeout_dict: Dict[str, int], file_prefix: str) -> None:
- super(weather_renderer, self).__init__(name_to_timeout_dict, False)
+ super().__init__(name_to_timeout_dict)
self.file_prefix = file_prefix
- def debug_prefix(self) -> str:
- return f"weather({self.file_prefix})"
-
def periodic_render(self, key: str) -> bool:
return self.fetch_weather()
- def describe_time(self, index: int) -> str:
- if index <= 1:
- return "overnight"
- elif index <= 3:
- return "morning"
- elif index <= 5:
- return "afternoon"
- else:
- return "evening"
-
- def describe_wind(self, mph: float) -> str:
- if mph <= 0.3:
- return "calm"
- elif mph <= 5.0:
- return "light"
- elif mph < 15.0:
- return "breezy"
- elif mph <= 25.0:
- return "gusty"
- else:
- return "heavy"
-
- def describe_magnitude(self, mm: float) -> str:
- if mm < 2.0:
- return "light"
- elif mm < 10.0:
- return "moderate"
- else:
- return "heavy"
-
- def describe_precip(self, rain: float, snow: float) -> str:
- if rain == 0.0 and snow == 0.0:
- return "no precipitation"
- magnitude = rain + snow
- if rain > 0 and snow > 0:
- return f"a {self.describe_magnitude(magnitude)} mix of rain and snow"
- elif rain > 0:
- return f"{self.describe_magnitude(magnitude)} rain"
- elif snow > 0:
- return f"{self.describe_magnitude(magnitude)} snow"
- return "rain"
-
- def fix_caps(self, s: str) -> str:
- r = ""
- s = s.lower()
- for x in s.split("."):
- x = x.strip()
- r += x.capitalize() + ". "
- r = r.replace(". .", ".")
- return r
-
def pick_icon(
self, conditions: List[str], rain: List[float], snow: List[float]
) -> str:
return "partlysunny.gif"
return "clear.gif"
- def describe_weather(
- self,
- high: float,
- low: float,
- wind: List[float],
- conditions: List[str],
- rain: List[float],
- snow: List[float],
- ) -> str:
- # High temp: 65
- # Low temp: 44
- # -onight------ -morning----- -afternoon-- -evening----
- # 12a-3a 3a-6a 6a-9a 9a-12p 12p-3p 3p-6p 6p-9p 9p-12p
- # Wind: [12.1 3.06 3.47 4.12 3.69 3.31 2.73 2.1]
- # Conditions: [Clouds Clouds Clouds Clouds Clouds Clouds Clear Clear]
- # Rain: [0.4 0.2 0 0 0 0 0 0]
- # Snow: [0 0 0 0 0 0 0 0]
- high = int(high)
- low = int(low)
- count = min(len(wind), len(conditions), len(rain), len(snow))
- descr = ""
-
- lcondition = ""
- lwind = ""
- lprecip = ""
- ltime = ""
- for x in range(0, count):
- time = self.describe_time(x)
- current = ""
- chunks = 0
-
- txt = conditions[x]
- if txt == "Clouds":
- txt = "cloudy"
- elif txt == "Rain":
- txt = "rainy"
-
- if txt != lcondition:
- if txt != "Snow" and txt != "Rain":
- current += txt
- chunks += 1
- lcondition = txt
-
- txt = self.describe_wind(wind[x])
- if txt != lwind:
- if len(current) > 0:
- current += " with "
- current += txt + " winds"
- lwind = txt
- chunks += 1
-
- txt = self.describe_precip(rain[x], snow[x])
- if txt != lprecip:
- if len(current) > 0:
- if chunks > 1:
- current += " and "
- else:
- current += " with "
- chunks += 1
- current += txt
- lprecip = txt
-
- if len(current):
- if ltime != time:
- if random.randint(0, 3) == 0:
- if time != "overnight":
- descr += current + " in the " + time + ". "
- descr += current + " overnight. "
- else:
- if time != "overnight":
- descr += "In the "
- descr += time + ", " + current + ". "
- else:
- current = current.replace("cloudy", "clouds")
- descr += current + " developing. "
- ltime = time
- if ltime == "overnight" or ltime == "morning":
- descr += "Conditions continuing the rest of the day. "
- descr = descr.replace("with breezy winds", "and breezy")
- descr = descr.replace("Clear developing", "Skies clearing")
- descr = self.fix_caps(descr)
- return descr
-
def fetch_weather(self) -> bool:
if self.file_prefix == "stevens":
text_location = "Stevens Pass, WA"
# "dt_txt":"2017-01-30 18:00:00"
# },
# {"dt":1485810000,....
- with file_writer.file_writer("weather-%s_3_10800.html" % self.file_prefix) as f:
- f.write(
- f"""
-<h1>Weather at {text_location}:</h1>
+
+ with file_writer.file_writer(f"weather-{self.file_prefix}_3_10800.html") as f:
+ f.write(f"""
+<h1>Upcoming weather at {text_location}:</h1>
<hr>
+<script src="/kiosk/Chart.js"></script>""")
+ f.write("""
+<script>
+function makePrecipChart(name, xValues, yValues) {
+ const config = {
+ type: 'line',
+ data: {
+ labels: xValues,
+ datasets: [
+ {
+ fill: true,
+ lineTension: 0,
+ backgroundColor: "rgba(0,0,255,0.1)",
+ borderColor: "rgba(0,0,255,0.9)",
+ data: yValues
+ }
+ ]
+ },
+ options: {
+ plugins: {
+ legend: {
+ display: false,
+ },
+ },
+ scales: {
+ x: {
+ grid: {
+ display: false,
+ }
+ },
+ y: {
+ display: false,
+ beginAtZero: true,
+ min: 0.0,
+ max: 10.0,
+ grid: {
+ display: false,
+ },
+ }
+ }
+ },
+ };
+ return new Chart(name, config);
+}
+</script>
<center>
-<table width=99% cellspacing=10 border=0>
- <tr>"""
- )
+""")
count = parsed_json["cnt"]
ts = {}
conditions: Dict[str, List[str]] = {}
rain: Dict[str, List[float]] = {}
snow: Dict[str, List[float]] = {}
+ precip: Dict[str, List[float]] = {}
+
for x in range(0, count):
data = parsed_json["list"][x]
dt = data["dt_txt"] # 2019-10-07 18:00:00
- date = dt.split(" ")[0]
- time = dt.split(" ")[1]
+ (date, time) = dt.split(' ')
wind[date] = []
conditions[date] = []
- highs[date] = -99999
- lows[date] = +99999
+ highs[date] = None
+ lows[date] = None
rain[date] = []
snow[date] = []
+ precip[date] = []
ts[date] = 0
for x in range(0, count):
data = parsed_json["list"][x]
dt = data["dt_txt"] # 2019-10-07 18:00:00
- date = dt.split(" ")[0]
- time = dt.split(" ")[1]
+ (date, time) = dt.split(' ')
_ = data["dt"]
if _ > ts[date]:
ts[date] = _
temp = data["main"]["temp"]
- if highs[date] < temp:
+ if highs[date] is None or highs[date] < temp:
highs[date] = temp
- if temp < lows[date]:
+ if lows[date] is None or temp < lows[date]:
lows[date] = temp
wind[date].append(data["wind"]["speed"])
conditions[date].append(data["weather"][0]["main"])
# },
# u'wind': {u'speed': 6.31, u'deg': 10.09}}
- # Next 5 half-days
- # for x in xrange(0, 5):
- # fcast = parsed_json['forecast']['txt_forecast']['forecastday'][x]
- # text = fcast['fcttext']
- # text = re.subn(r' ([0-9]+)F', r' \1°F', text)[0]
- # f.write('<td style="vertical-align:top;font-size:75%%"><P STYLE="padding:8px;">%s</P></td>' % text)
- # f.write('</tr></table>')
- # f.close()
- # return True
-
- # f.write("<table border=0 cellspacing=10>\n")
- days_seen = {}
+ days_seen = set()
for date in sorted(highs.keys()):
- today = datetime.fromtimestamp(ts[date])
- formatted_date = today.strftime("%a %e %b")
+ day = datetime.fromtimestamp(ts[date])
+ formatted_date = day.strftime("%a %e %b")
if formatted_date in days_seen:
continue
- days_seen[formatted_date] = True
- num_days = len(list(days_seen.keys()))
-
- days_seen = {}
- for date in sorted(highs.keys()):
- precip = 0.0
- for _ in rain[date]:
- precip += _
- for _ in snow[date]:
- precip += _
-
- today = datetime.fromtimestamp(ts[date])
- formatted_date = today.strftime("%a %e %b")
+ days_seen.add(formatted_date)
+ total = len(days_seen)
+
+ first_day = True
+ days_seen = set()
+ for n, date in enumerate(sorted(highs.keys())):
+ if n % 3 == 0:
+ if n > 0:
+ f.write("</div>")
+ f.write('<div STYLE="overflow:hidden; width:100%; height:500px">')
+ remaining = total - n
+ if remaining >= 3:
+ width = "33%"
+ else:
+ width = f'{100/remaining}%'
+
+ precip[date] = []
+ aggregate_precip = 0.0
+ for r, s in zip(rain[date], snow[date]):
+ aggregate = r + s
+ aggregate_precip += aggregate
+ precip[date].append(aggregate)
+ if first_day:
+ while len(precip[date]) < 8:
+ precip[date].insert(0, 0)
+ first_day = False
+
+ day = datetime.fromtimestamp(ts[date])
+ formatted_date = day.strftime("%a %e %b")
if formatted_date in days_seen:
continue
- days_seen[formatted_date] = True
+ days_seen.add(formatted_date)
f.write(
- '<td width=%d%% style="vertical-align:top;">\n' % (100 / num_days)
+ f'<div style="width:{width}; height:500px; float:left"><table>\n'
)
- f.write("<table border=0>\n")
# Date
- f.write(
- " <tr><td colspan=3 height=50><b><center><font size=6>"
- + formatted_date
- + "</font></center></b></td></tr>\n"
- )
-
- # Icon
- f.write(
- ' <tr><td colspan=3 height=100><center><img src="/kiosk/pages/images/weather/%s" height=125></center></td></tr>\n'
- % self.pick_icon(conditions[date], rain[date], snow[date])
- )
-
- # Low temp
+ f.write(f'''
+<tr>
+ <td colspan=3 height=50>
+ <center>
+ <font size=7><b>{formatted_date}</b></font>
+ </center>
+ </td>
+</tr>''')
+
+ # Conditions icon
+ icon = self.pick_icon(conditions[date], rain[date], snow[date])
+ f.write(f'''
+<tr>
+ <td colspan=3 height=100>
+ <center>
+ <img src="/kiosk/images/weather/{icon}" height=145>
+ </center>
+ </td>
+</tr>''')
+
+ # Low temp -- left
color = "#000099"
if lows[date] <= 32.5:
color = "#009999"
- f.write(
- ' <tr><td width=33%% align=left><font color="%s"><b>%d°F </b></font></td>\n'
- % (color, int(lows[date]))
- )
-
- # Total precip
- precip *= 0.0393701
- if precip > 0.025:
- f.write(
- ' <td width=33%%><center><b><font style="background-color:#dfdfff; color:#003355">%3.1f"</font></b></center></td>\n'
- % precip
- )
+ f.write(f'''
+<tr>
+ <td width=33% align=left>
+ <font color="{color}" size=6>
+ <b>{int(lows[date])}°F </b>
+ </font>
+ </td>
+''')
+
+ # Total aggregate_precip in inches
+ aggregate_precip *= 0.0393701
+ if aggregate_precip > 0.025:
+ f.write(f'''
+ <td width=33%>
+ <center>
+ <font style="background-color:#dfdfff; color:#003355" size=6>
+ <b>{aggregate_precip:3.1f}”</b>
+ </font>
+ </center>
+ </td>
+''')
else:
f.write(" <td width=33%> </td>\n")
- # High temp
+ # High temp + precip chart
color = "#800000"
if highs[date] >= 80:
color = "#AA0000"
- f.write(
- ' <td align=right><font color="%s"><b> %d°F</b></font></td></tr>\n'
- % (color, int(highs[date]))
- )
-
- # Text "description"
- f.write(
- '<tr><td colspan=3 style="vertical-align:top;font-size:75%%">%s</td></tr>\n'
- % self.describe_weather(
- highs[date],
- lows[date],
- wind[date],
- conditions[date],
- rain[date],
- snow[date],
- )
- )
- f.write("</table>\n</td>\n")
- f.write("</tr></table></center>")
+ f.write(f'''
+ <td align=right>
+ <font color="{color}" size=6>
+ <b> {int(highs[date])}°F</b>
+ </font>
+ </td>
+</tr>''')
+
+ # Precip graph
+ f.write(f'''
+<tr>
+ <td colspan=3 style="vertical-align:top;">
+ <canvas id="myChart{n}" style="width:100%;max-width:400px;height:180px;"></canvas>
+ </td>
+</tr>
+<script>
+var yValues{n} = ''')
+ f.write(precip[date].__repr__())
+ f.write(f''';
+var xValues{n} = [ 3, 6, 9, 12, 15, 18, 21, 24 ];
+makePrecipChart("myChart{n}", xValues{n}, yValues{n});
+</script>
+</table>
+</div>
+''')
+ f.write("</div></center>")
return True
-# x = weather_renderer({"Stevens": 1000}, "stevens")
-# x.periodic_render("Stevens")
+#x = weather_renderer({"Stevens": 1000}, "stevens")
+#x.periodic_render("Stevens")