Skip to content

Commit e537502

Browse files
Merge pull request #62 from pavelmachek/m_6_weather
weather: start simple weather application
2 parents 2e07cc7 + 4646800 commit e537502

3 files changed

Lines changed: 249 additions & 0 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "Weather",
3+
"publisher": "Pavel Machek",
4+
"short_description": "Display weather information.",
5+
"long_description": "This displays weather information from open-meteo.com.",
6+
"icon_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.weather/icons/cz.ucw.pavel.weather_0.0.1_64x64.png",
7+
"download_url": "https://apps.micropythonos.com/apps/cz.ucw.pavel.weather/mpks/cz.ucw.pavel.weather_0.0.1.mpk",
8+
"fullname": "cz.ucw.pavel.weather",
9+
"version": "0.0.1",
10+
"category": "utilities",
11+
"activities": [
12+
{
13+
"entrypoint": "assets/main.py",
14+
"classname": "Main",
15+
"intent_filters": [
16+
{
17+
"action": "main",
18+
"category": "launcher"
19+
}
20+
]
21+
}
22+
]
23+
}
24+
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
from mpos import Activity
2+
3+
"""
4+
Look at https://open-meteo.com/en/docs , then design an application that would display current time and weather, and summary of forecast ("no change expected for 2 days" or maybe "rain in 5 hours"), with a way to access detailed forecast.
5+
"""
6+
7+
import time
8+
import os
9+
10+
try:
11+
import lvgl as lv
12+
except ImportError:
13+
pass
14+
15+
from mpos import Activity, MposKeyboard
16+
17+
import ujson
18+
import utime
19+
import usocket as socket
20+
import ujson
21+
22+
# -----------------------------
23+
# WEATHER DATA MODEL
24+
# -----------------------------
25+
26+
class WData:
27+
WMO_CODES = {
28+
0: "Clear sky",
29+
1: "Mainly clear",
30+
2: "Partly cloudy",
31+
3: "Overcast",
32+
45: "Fog",
33+
48: "Rime fog",
34+
51: "Light drizzle",
35+
53: "Drizzle",
36+
55: "Heavy drizzle",
37+
56: "Freezing drizzle",
38+
57: "Freezing drizzle",
39+
61: "Light rain",
40+
63: "Rain",
41+
65: "Heavy rain",
42+
66: "Freezing rain",
43+
67: "Freezing rain",
44+
71: "Light snow",
45+
73: "Snow",
46+
75: "Heavy snow",
47+
77: "Snow grains",
48+
80: "Rain showers",
49+
81: "Rain showers",
50+
82: "Heavy rain showers",
51+
85: "Snow showers",
52+
86: "Heavy snow showers",
53+
95: "Thunderstorm",
54+
96: "Thunderstorm + hail",
55+
99: "Thunderstorm + hail",
56+
}
57+
58+
def code_to_text(self, code):
59+
return self.WMO_CODES.get(int(code), "Unknown")
60+
61+
class Hourly(WData):
62+
def __init__(self, cw):
63+
self.temp = cw["temperature_2m"]
64+
self.wind = cw["windspeed"]
65+
self.code = self.code_to_text(cw["weather_code"])
66+
67+
def summarize(self):
68+
return f"{self.code}\nTemp {self.temp}\nWind {self.wind}"
69+
70+
class Weather:
71+
name = "Prague"
72+
lat = 50.08
73+
lon = 14.44
74+
75+
def __init__(self):
76+
self.now = None
77+
self.hourly = []
78+
self.daily = []
79+
self.summary = "(no weather)"
80+
81+
def fetch(self):
82+
self.summary = "...fetching..."
83+
84+
# See https://open-meteo.com/en/docs?forecast_days=1&current=relative_humidity_2m
85+
86+
host = "api.open-meteo.com"
87+
port = 80 # HTTP only
88+
path = (
89+
"/v1/forecast?"
90+
"latitude={}&longitude={}"
91+
"&current=temperature_2m,dewpoint_2m,pressure_msl,precipitation,weather_code,windspeed"
92+
"&timezone=auto"
93+
).format(self.lat, self.lon)
94+
95+
print("Weather fetch: ", path)
96+
97+
# Resolve DNS
98+
addr = socket.getaddrinfo(host, port, socket.AF_INET)[0][-1]
99+
print("DNS", addr)
100+
101+
s = socket.socket()
102+
s.connect(addr)
103+
104+
# Send HTTP request
105+
request = (
106+
"GET {} HTTP/1.1\r\n"
107+
"Host: {}\r\n"
108+
"Connection: close\r\n\r\n"
109+
).format(path, host)
110+
111+
s.send(request.encode())
112+
113+
# ---- Read response ----
114+
# Skip HTTP headers
115+
buffer = b""
116+
while True:
117+
chunk = s.recv(256)
118+
if not chunk:
119+
raise Exception("No response")
120+
buffer += chunk
121+
header_end = buffer.find(b"\r\n\r\n")
122+
if header_end != -1:
123+
body = buffer[header_end + 4:]
124+
break
125+
126+
127+
# Read remaining body
128+
while True:
129+
chunk = s.recv(512)
130+
if not chunk:
131+
break
132+
body += chunk
133+
134+
s.close()
135+
136+
# Strip non-json parts
137+
body = body[5:]
138+
body = body[:-7]
139+
140+
print("Have result:", body.decode())
141+
142+
# Parse JSON
143+
data = ujson.loads(body)
144+
145+
# ---- Extract data ----
146+
cw = data["current"]
147+
self.now = Hourly(cw)
148+
self.summary = self.now.summarize()
149+
150+
weather = Weather()
151+
152+
# ------------------------------------------------------------
153+
# Main activity
154+
# ------------------------------------------------------------
155+
156+
class Main(Activity):
157+
def __init__(self):
158+
self.last_hour = 0
159+
super().__init__()
160+
161+
# --------------------
162+
163+
def onCreate(self):
164+
self.screen = lv.obj()
165+
#self.screen.remove_flag(lv.obj.FLAG.SCROLLABLE)
166+
scr_main = self.screen
167+
168+
# ---- MAIN SCREEN ----
169+
170+
label_time = lv.label(scr_main)
171+
label_time.set_text("(time)")
172+
label_time.align(lv.ALIGN.TOP_LEFT, 10, 40)
173+
label_time.set_style_text_font(lv.font_montserrat_24, 0)
174+
self.label_time = label_time
175+
176+
label_weather = lv.label(scr_main)
177+
label_weather.set_text(f"Weather for {weather.name} ({weather.lat}, {weather.lon})")
178+
label_weather.align_to(label_time, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 10)
179+
label_weather.set_style_text_font(lv.font_montserrat_14, 0)
180+
self.label_weather = label_weather
181+
182+
label_summary = lv.label(scr_main)
183+
label_summary.set_text("(weather)")
184+
#label_summary.set_long_mode(lv.label.LONG.WRAP)
185+
label_summary.set_width(300)
186+
label_summary.align_to(label_weather, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5)
187+
label_summary.set_style_text_font(lv.font_montserrat_24, 0)
188+
self.label_summary = label_summary
189+
190+
btn_hourly = lv.button(scr_main)
191+
btn_hourly.set_size(100, 40)
192+
btn_hourly.align(lv.ALIGN.BOTTOM_LEFT, 10, -10)
193+
lv.label(btn_hourly).set_text("Reload")
194+
195+
btn_hourly.add_event_cb(lambda x: self.do_load(), lv.EVENT.CLICKED, None)
196+
197+
self.setContentView(self.screen)
198+
199+
def onResume(self, screen):
200+
self.timer = lv.timer_create(self.tick, 15000, None)
201+
self.tick(0)
202+
203+
def onPause(self, screen):
204+
if self.timer:
205+
self.timer.delete()
206+
self.timer = None
207+
208+
# --------------------
209+
210+
def tick(self, t):
211+
now = time.localtime()
212+
y, m, d = now[0], now[1], now[2]
213+
hh, mm, ss = now[3], now[4], now[5]
214+
215+
if hh != self.last_hour:
216+
self.last_hour = hh
217+
self.do_load()
218+
219+
self.label_time.set_text("%02d:%02d" % (hh, mm))
220+
self.label_summary.set_text(weather.summary)
221+
222+
def do_load(self):
223+
self.label_summary.set_text("Requesting...")
224+
weather.fetch()
225+
12.1 KB
Loading

0 commit comments

Comments
 (0)