Repository navigation
Expand file tree
/
Copy pathforecast_print.py
More file actions
95 lines (81 loc) · 4.01 KB
/
Copy pathforecast_print.py
File metadata and controls
95 lines (81 loc) · 4.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/usr/bin/env python3
"""Print a 7-day weather forecast on the S01 thermal printer.
Geocodes a city (Open-Meteo, no key), draws a high/low temperature range chart,
and lists each day's conditions, temps, and rain chance.
Examples:
python forecast_print.py --city "Tokyo"
python forecast_print.py # random world city
python forecast_print.py --no-print
"""
from __future__ import annotations
import argparse
from datetime import datetime
from pathlib import Path
import random
import urllib.parse
from PIL import Image, ImageDraw
from print_common import ROOT, Card, font, get_json
GEO = "https://geocoding-api.open-meteo.com/v1/search?name={q}&count=1"
FC = ("https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lng}"
"&daily=temperature_2m_max,temperature_2m_min,precipitation_probability_max,weather_code"
"&timezone=auto&forecast_days=7")
CITIES = ["Tokyo", "Paris", "London", "New York", "Sydney", "Reykjavik", "Cairo",
"Berlin", "Rio de Janeiro", "Cape Town", "Reykjavik", "Singapore",
"Oslo", "Lisbon", "Nairobi", "Vancouver", "Athens", "Helsinki"]
WMO = {0: "Clear", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast", 45: "Fog",
48: "Rime fog", 51: "Lt drizzle", 53: "Drizzle", 55: "Hvy drizzle",
61: "Lt rain", 63: "Rain", 65: "Heavy rain", 66: "Frz rain", 67: "Frz rain",
71: "Lt snow", 73: "Snow", 75: "Heavy snow", 77: "Snow", 80: "Showers",
81: "Showers", 82: "Hvy showers", 85: "Snow shwr", 86: "Snow shwr",
95: "Thunder", 96: "Thunderstorm", 99: "Thunderstorm"}
def chart(maxs, mins, days, w, h=130):
img = Image.new("L", (w, h), 255)
d = ImageDraw.Draw(img)
tiny = font(False, 11)
lo, hi = min(mins), max(maxs)
span = (hi - lo) or 1
top, bot = 8, h - 18
plot = bot - top
n = len(maxs)
slot = w / n
for i in range(n):
cx = slot * (i + 0.5)
yhi = top + (1 - (maxs[i] - lo) / span) * plot
ylo = top + (1 - (mins[i] - lo) / span) * plot
bw = slot * 0.16
d.rectangle((cx - bw, yhi, cx + bw, ylo), fill=0)
d.text((cx, yhi - 1), f"{round(maxs[i])}", fill=0, font=tiny, anchor="mb")
d.text((cx, bot + 2), days[i][:1], fill=0, font=tiny, anchor="ma")
return img
def main() -> int:
parser = argparse.ArgumentParser(description="Print a 7-day weather forecast on the S01 thermal printer.")
parser.add_argument("--city", default=None, help="City name (default: random).")
parser.add_argument("--out", type=Path, default=ROOT / "forecast.png")
parser.add_argument("--darkness", type=int, choices=range(1, 6), default=3)
parser.add_argument("--bottom-feed", type=int, default=24)
parser.add_argument("--no-print", action="store_true")
args = parser.parse_args()
city = args.city or random.choice(CITIES)
print(f"Forecast for {city} ...")
geo = get_json(GEO.format(q=urllib.parse.quote(city))).get("results")
if not geo:
raise SystemExit(f"City not found: {city!r}")
g = geo[0]
daily = get_json(FC.format(lat=g["latitude"], lng=g["longitude"]))["daily"]
days = [datetime.strptime(t, "%Y-%m-%d").strftime("%a") for t in daily["time"]]
maxs, mins = daily["temperature_2m_max"], daily["temperature_2m_min"]
precip, codes = daily["precipitation_probability_max"], daily["weather_code"]
card = Card()
card.title("7-DAY FORECAST")
card.heading(f"{g['name']}, {g.get('country_code', '')}", size=15)
card.gap(4).image(chart(maxs, mins, days, card.inner_w), border=True)
card.line("daily high/low (C)", size=11, bold=False, center=True)
card.gap(2).divider()
for i in range(len(days)):
cond = WMO.get(codes[i], "?")
row = f"{days[i]:<3} {cond:<12} {round(maxs[i]):>2}/{round(mins[i]):>2} {precip[i] or 0:>3}%"
card.line(row, size=13, bold=False)
card.footer("open-meteo.com", datetime.now().strftime("%Y-%m-%d"))
return card.finish(args.out, args.bottom_feed, args.darkness, do_print=not args.no_print)
if __name__ == "__main__":
raise SystemExit(main())