X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=weather_renderer.py;h=8da01ab73bae05e880c3e3e9e3ac32191608e899;hb=ea1ee5f817c01c3736a64d73d496cf35cbd383e5;hp=fdd4fe125d3257a1303797b338d6981dcaca56fc;hpb=75b27cc68871343681f01e3f5b04cae84b1b7b2a;p=kiosk.git
diff --git a/weather_renderer.py b/weather_renderer.py
index fdd4fe1..8da01ab 100644
--- a/weather_renderer.py
+++ b/weather_renderer.py
@@ -1,78 +1,30 @@
+#!/usr/bin/env python3
+
+import logging
+import json
+import urllib.request
+import urllib.error
+import urllib.parse
from datetime import datetime
+from collections import defaultdict
+from typing import Dict, List
+
import file_writer
import renderer
-import json
-import re
-import secrets
-import urllib.request, urllib.error, urllib.parse
-import random
-
-class weather_renderer(renderer.debuggable_abstaining_renderer):
- """A renderer to fetch forecast from wunderground."""
-
- def __init__(self,
- name_to_timeout_dict,
- file_prefix):
- super(weather_renderer, self).__init__(name_to_timeout_dict, False)
- self.file_prefix = file_prefix
-
- def debug_prefix(self):
- return "weather(%s)" % (self.file_prefix)
+import kiosk_secrets as secrets
- def periodic_render(self, key):
- return self.fetch_weather()
+logger = logging.getLogger(__name__)
- def describe_time(self, index):
- if (index <= 1):
- return "overnight"
- elif (index <= 3):
- return "morning"
- elif (index <= 5):
- return "afternoon"
- else:
- return "evening"
- def describe_wind(self, mph):
- 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):
- if (mm < 2):
- return "light"
- elif (mm < 10):
- return "moderate"
- else:
- return "heavy"
-
- def describe_precip(self, rain, snow):
- if rain == 0 and snow == 0:
- return "no precipitation"
- magnitude = rain + snow
- if rain > 0 and snow > 0:
- return "a %s mix of rain and snow" % self.describe_magnitude(magnitude)
- elif rain > 0:
- return "%s rain" % self.describe_magnitude(magnitude)
- elif snow > 0:
- return "%s snow" % self.describe_magnitude(magnitude)
+class weather_renderer(renderer.abstaining_renderer):
+ """A renderer to fetch forecast from wunderground."""
- def fix_caps(self, s):
- r = ""
- s = s.lower()
- for x in s.split("."):
- x = x.strip()
- r += x.capitalize() + ". "
- r = r.replace(". .", ".")
- return r
+ def __init__(self, name_to_timeout_dict: Dict[str, int], file_prefix: str) -> None:
+ super().__init__(name_to_timeout_dict)
+ self.file_prefix = file_prefix
- def pick_icon(self, conditions, rain, snow):
+ @staticmethod
+ def pick_icon(conditions: List[str], rain: List[float], snow: List[float]) -> str:
# rain snow clouds sun
# fog.gif
# hazy.gif
@@ -92,135 +44,66 @@ class weather_renderer(renderer.debuggable_abstaining_renderer):
seen_snow = False
cloud_count = 0
clear_count = 0
- total_snow = 0
+ total_snow = 0.0
count = min(len(conditions), len(rain), len(snow))
for x in range(0, count):
- seen_rain = rain[x] > 0;
- seen_snow = snow[x] > 0;
+ seen_rain = rain[x] > 0
+ seen_snow = snow[x] > 0
total_snow += snow[x]
txt = conditions[x].lower()
- if ("cloud" in txt):
+ if "cloud" in txt:
cloud_count += 1
- if ("clear" in txt or "sun" in txt):
+ if "clear" in txt or "sun" in txt:
clear_count += 1
- if (seen_rain and seen_snow):
- if (total_snow < 10):
+ if seen_rain and seen_snow:
+ if total_snow < 10:
return "sleet.gif"
else:
return "snow.gif"
- if (seen_snow):
- if (total_snow < 10):
+ if seen_snow:
+ if total_snow < 10:
return "flurries.gif"
else:
return "snow.gif"
- if (seen_rain):
+ if seen_rain:
return "rain.gif"
- if (cloud_count >= 6):
+ if cloud_count >= 6:
return "mostlycloudy.gif"
- elif (cloud_count >= 4):
+ elif cloud_count >= 4:
return "partlycloudy.gif"
- if (clear_count >= 7):
+ if clear_count >= 7:
return "sunny.gif"
- elif (clear_count >= 6):
+ elif clear_count >= 6:
return "mostlysunny.gif"
- elif (clear_count >= 4):
+ elif clear_count >= 4:
return "partlysunny.gif"
return "clear.gif"
- def describe_weather(self,
- high, low,
- wind, conditions, rain, snow):
- # 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 periodic_render(self, key: str) -> bool:
+ return self.fetch_weather()
- def fetch_weather(self):
+ def fetch_weather(self) -> bool:
if self.file_prefix == "stevens":
text_location = "Stevens Pass, WA"
- param = "lat=47.74&lon=-121.08"
+ param = "lat=47.7322&lon=-121.1025"
elif self.file_prefix == "telma":
text_location = "Telma, WA"
param = "lat=47.84&lon=-120.81"
else:
text_location = "Bellevue, WA"
param = "id=5786882"
-
- www = urllib.request.urlopen('http://api.openweathermap.org/data/2.5/forecast?%s&APPID=%s&units=imperial' % (
- param, secrets.openweather_key))
+ secret = secrets.openweather_key
+ url = f"http://api.openweathermap.org/data/2.5/forecast?{param}&APPID={secret}&units=imperial"
+ logger.info(f"GETting {url}")
+ www = urllib.request.urlopen(url)
response = www.read()
www.close()
+ if www.getcode() != 200:
+ logger.error("Bad response: {response}")
+ raise Exception(response)
parsed_json = json.loads(response)
+ logger.info("URL read ok")
# https://openweathermap.org/forecast5
# {"cod":"200",
@@ -239,152 +122,269 @@ class weather_renderer(renderer.debuggable_abstaining_renderer):
# "dt_txt":"2017-01-30 18:00:00"
# },
# {"dt":1485810000,....
- f = file_writer.file_writer('weather-%s_3_10800.html' % self.file_prefix)
- f.write("""
-
Weather at %s:
+
+ with file_writer.file_writer(f"weather-{self.file_prefix}_3_10800.html") as f:
+ f.write(
+ f"""
+Upcoming weather at {text_location}:
+"""
+ )
+ f.write(
+ """
+
-
- """ % text_location)
- count = parsed_json['cnt']
-
- ts = {}
- highs = {}
- lows = {}
- wind = {}
- conditions = {}
- rain = {}
- snow = {}
- 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]
- wind[date] = []
- conditions[date] = []
- highs[date] = -99999
- lows[date] = +99999
- rain[date] = []
- snow[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]
- _ = data['dt']
- if (_ > ts[date]):
- ts[date] = _
- temp = data["main"]["temp"]
- if (highs[date] < temp):
- highs[date] = temp
- if (temp < lows[date]):
- lows[date] = temp
- wind[date].append(data["wind"]["speed"])
- conditions[date].append(data["weather"][0]["main"])
- if "rain" in data and "3h" in data["rain"]:
- rain[date].append(data["rain"]["3h"])
- else:
- rain[date].append(0)
- if "snow" in data and "3h" in data["snow"]:
- snow[date].append(data["snow"]["3h"])
- else:
- snow[date].append(0)
-
- # {u'clouds': {u'all': 0},
- # u'sys': {u'pod': u'd'},
- # u'dt_txt': u'2019-10-09 21:00:00',
- # u'weather': [
- # {u'main': u'Clear',
- # u'id': 800,
- # u'icon': u'01d',
- # u'description': u'clear sky'}
- # ],
- # u'dt': 1570654800,
- # u'main': {
- # u'temp_kf': 0,
- # u'temp': 54.74,
- # u'grnd_level': 1018.95,
- # u'temp_max': 54.74,
- # u'sea_level': 1026.46,
- # u'humidity': 37,
- # u'pressure': 1026.46,
- # u'temp_min': 54.74
- # },
- # 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('%s | ' % text)
- #f.write('
')
- #f.close()
- #return True
-
- #f.write("\n")
- days_seen = {}
- for date in sorted(highs.keys()):
- today = datetime.fromtimestamp(ts[date])
- formatted_date = today.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')
- if (formatted_date in days_seen):
- continue;
- days_seen[formatted_date] = True
- f.write('\n' % (100 / num_days))
- f.write('\n')
-
- # Date
- f.write(' ' + formatted_date + ' | \n')
-
- # Icon
- f.write(' | \n' %
- self.pick_icon(conditions[date], rain[date], snow[date]))
-
- # Low temp
- color = "#000099"
- if (lows[date] <= 32.5):
- color = "#009999"
- f.write(' %d°F | \n' % (
- color, int(lows[date])))
-
- # Total precip
- precip *= 0.0393701
- if (precip > 0.025):
- f.write(' %3.1f" | \n' % precip)
- else:
- f.write(' | \n')
-
- # High temp
- color = "#800000"
- if (highs[date] >= 80):
- color = "#AA0000"
- f.write(' %d°F | \n' % (
- color, int(highs[date])))
-
- # Text "description"
- f.write('%s | \n' %
- self.describe_weather(highs[date], lows[date], wind[date], conditions[date], rain[date], snow[date]))
- f.write(' \n | \n')
- f.write("
")
+"""
+ )
+ count = parsed_json["cnt"]
+
+ ts = {}
+ highs = {}
+ lows = {}
+ wind: Dict[str, List[float]] = defaultdict(list)
+ conditions: Dict[str, List[str]] = defaultdict(list)
+ rain: Dict[str, List[float]] = defaultdict(list)
+ snow: Dict[str, List[float]] = defaultdict(list)
+ precip: Dict[str, List[float]] = defaultdict(list)
+
+ for x in range(0, count):
+ data = parsed_json["list"][x]
+ dt = data["dt_txt"] # 2019-10-07 18:00:00
+ (date, time) = dt.split(" ")
+ _ = data["dt"]
+ if _ not in ts or _ > ts[date]:
+ ts[date] = _
+ temp = data["main"]["temp"]
+
+ # High and low temp
+ if date not in highs or highs[date] < temp:
+ highs[date] = temp
+ if date not in lows or lows[date] > temp:
+ lows[date] = temp
+
+ # Windspeed and conditions
+ wind[date].append(data["wind"]["speed"])
+ conditions[date].append(data["weather"][0]["main"])
+
+ # 3h precipitation (rain / snow)
+ if "rain" in data and "3h" in data["rain"]:
+ rain[date].append(data["rain"]["3h"])
+ else:
+ rain[date].append(0)
+ if "snow" in data and "3h" in data["snow"]:
+ snow[date].append(data["snow"]["3h"])
+ else:
+ snow[date].append(0)
+
+ # {u'clouds': {u'all': 0},
+ # u'sys': {u'pod': u'd'},
+ # u'dt_txt': u'2019-10-09 21:00:00',
+ # u'weather': [
+ # {u'main': u'Clear',
+ # u'id': 800,
+ # u'icon': u'01d',
+ # u'description': u'clear sky'}
+ # ],
+ # u'dt': 1570654800,
+ # u'main': {
+ # u'temp_kf': 0,
+ # u'temp': 54.74,
+ # u'grnd_level': 1018.95,
+ # u'temp_max': 54.74,
+ # u'sea_level': 1026.46,
+ # u'humidity': 37,
+ # u'pressure': 1026.46,
+ # u'temp_min': 54.74
+ # },
+ # u'wind': {u'speed': 6.31, u'deg': 10.09}}
+
+ days_seen = set()
+ for date in sorted(highs.keys()):
+ day = datetime.fromtimestamp(ts[date])
+ formatted_date = day.strftime("%a %e %b")
+ if formatted_date in days_seen:
+ continue
+ 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("")
+ f.write('')
+ remaining = total - n
+ if remaining >= 3:
+ width = "33%"
+ else:
+ width = f"{100/remaining}%"
+
+ aggregate_daily_precip = 0.0
+ for r, s in zip(rain[date], snow[date]):
+ hourly_aggregate = r + s
+
+ # The weather report is always way wrong about Stevens.
+ if self.file_prefix == "stevens":
+ hourly_aggregate *= 3.5
+ aggregate_daily_precip += hourly_aggregate
+ precip[date].append(hourly_aggregate)
+ logger.debug(
+ f"Aggregate precip on {date} was {aggregate_daily_precip} mm"
+ )
+ 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.add(formatted_date)
+ f.write(
+ f'
\n'
+ )
+
+ # Date
+ f.write(
+ f"""
+
+
+
+ {formatted_date}
+
+ |
+
"""
+ )
+
+ # Conditions icon
+ icon = weather_renderer.pick_icon(
+ conditions[date], rain[date], snow[date]
+ )
+ f.write(
+ f"""
+
+
+
+
+
+ |
+
"""
+ )
+
+ # Low temp -- left
+ color = "#000099"
+ if lows[date] <= 32.5:
+ color = "#009999"
+ f.write(
+ f"""
+
+
+
+ {int(lows[date])}°F
+
+ |
+"""
+ )
+
+ # Total aggregate_precip in inches (from mm)
+ aggregate_daily_precip /= 25.4
+ if aggregate_daily_precip > 0.001:
+ f.write(
+ f"""
+
+
+
+ {aggregate_daily_precip:3.1f}”
+
+
+ |
+"""
+ )
+ else:
+ f.write(" | \n")
+
+ # High temp + precip chart
+ color = "#800000"
+ if highs[date] >= 80:
+ color = "#AA0000"
+ f.write(
+ f"""
+
+
+ {int(highs[date])}°F
+
+ |
+
"""
+ )
+
+ # Precip graph
+ f.write(
+ f"""
+
+
+
+ |
+
+
+
+
+"""
+ )
+ f.write("
")
return True
-#x = weather_renderer({"Stevens": 1000},
-# "stevens")
-#x.periodic_render("Stevens")
+
+# x = weather_renderer({"Stevens": 1000}, "stevens")
+# x.periodic_render("Stevens")