1212except ImportError :
1313 pass
1414
15- from mpos import Activity , MposKeyboard
15+ from mpos import Activity , MposKeyboard , DownloadManager
1616
1717import ujson
1818import utime
@@ -55,22 +55,87 @@ class WData:
5555 99 : "Thunderstorm + hail" ,
5656 }
5757
58+ def init (self ):
59+ pass
60+
5861 def code_to_text (self , code ):
5962 return self .WMO_CODES .get (int (code ), "Unknown" )
6063
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" ])
64+ def get (self , v , cw , ind ):
65+ if ind == None :
66+ return cw [v ]
67+ else :
68+ return cw [v ][ind ]
69+
70+ def full (self ):
71+ return f"{ self .code } \n Temp { self .temp :.1f} dew { self .dew :.1f} pres { self .pres :1f} \n " \
72+ f"Precip { self .precip } \n Wind { self .wind } gust { self .gust } "
73+
74+ def short (self ):
75+ r = f"{ self .code } { self .temp :.1f} °C"
76+ if self .dew + 3 > self .temp :
77+ r += f" dew { self .dew :.1f} °C"
78+ if self .gust > self .wind + 5 :
79+ r += f" { self .gust :.0f} g"
80+ elif self .wind > 10 :
81+ r += f" { self .wind :.0f} w"
82+ # FIXME: add precip
83+ return r
84+
85+ def similar (self , prev ):
86+ if self .code != prev .code :
87+ return False
88+ if abs (self .temp - prev .temp ) > 3 :
89+ return False
90+ if abs (self .wind - prev .wind ) > 10 :
91+ return False
92+ if abs (self .gust - prev .gust ) > 10 :
93+ return False
94+ return True
6695
6796 def summarize (self ):
68- return f"{ self .code } \n Temp { self .temp } \n Wind { self .wind } "
97+ return self .ftime () + self .short ()
98+
99+ class Hourly (WData ):
100+ def init (self , cw , ind ):
101+ super ().init ()
102+ self .time = None
103+ self .temp = self .get ("temperature_2m" , cw , ind )
104+ self .dew = self .get ("dewpoint_2m" , cw , ind )
105+ self .pres = self .get ("pressure_msl" , cw , ind )
106+ self .precip = self .get ("precipitation" , cw , ind )
107+ self .wind = self .get ("wind_speed_10m" , cw , ind )
108+ self .gust = self .get ("wind_gusts_10m" , cw , ind )
109+ self .raw_code = self .get ("weather_code" , cw , ind )
110+ self .code = self .code_to_text (self .raw_code )
111+
112+ def ftime (self ):
113+ if self .time :
114+ return self .time [11 :13 ] + "h "
115+ return ""
116+
117+ class Daily (WData ):
118+ def init (self , cw , ind ):
119+ super ().init ()
120+ self .temp = self .get ("temperature_2m_max" , cw , ind )
121+ self .temp_min = self .get ("temperature_2m_min" , cw , ind )
122+ self .dew = self .get ("dewpoint_2m_max" , cw , ind )
123+ self .dew_min = self .get ("dewpoint_2m_min" , cw , ind )
124+ self .pres = None
125+ self .precip = self .get ("precipitation_sum" , cw , ind )
126+ self .wind = self .get ("wind_speed_10m_max" , cw , ind )
127+ self .gust = self .get ("wind_gusts_10m_max" , cw , ind )
128+ self .raw_code = self .get ("weather_code" , cw , ind )
129+ self .code = self .code_to_text (self .raw_code )
130+
131+ def ftime (self ):
132+ return self .time [8 :10 ] + ". "
69133
70134class Weather :
71135 name = "Prague"
72- lat = 50.08
73- lon = 14.44
136+ # LKPR airport
137+ lat = 50 + 6 / 60.
138+ lon = 14 + 15 / 60.
74139
75140 def __init__ (self ):
76141 self .now = None
@@ -84,68 +149,102 @@ def fetch(self):
84149 # See https://open-meteo.com/en/docs?forecast_days=1¤t=relative_humidity_2m
85150
86151 host = "api.open-meteo.com"
87- port = 80 # HTTP only
88152 path = (
89153 "/v1/forecast?"
90154 "latitude={}&longitude={}"
91- "¤t=temperature_2m,dewpoint_2m,pressure_msl,precipitation,weather_code,windspeed"
155+ "¤t=temperature_2m,dewpoint_2m,pressure_msl,precipitation,weather_code,wind_speed_10m,wind_gusts_10m"
156+ "&forecast_hours=8"
157+ "&hourly=temperature_2m,dewpoint_2m,pressure_msl,precipitation,weather_code,wind_speed_10m,wind_gusts_10m"
158+ "&forecast_days=10"
159+ "&daily=temperature_2m_max,temperature_2m_min,dewpoint_2m_min,dewpoint_2m_max,pressure_msl_min,pressure_msl_max,precipitation_sum,weather_code,wind_speed_10m_max,wind_gusts_10m_max"
92160 "&timezone=auto"
93161 ).format (self .lat , self .lon )
94162
95163 print ("Weather fetch: " , path )
164+ data = DownloadManager .download_url ("https://" + host + path )
165+ if not data :
166+ self .summary = "Download error"
167+ return
168+
169+ #print("Have result:", body.decode())
96170
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
171+ # Parse JSON
172+ data = ujson .loads (data )
133173
134- s .close ()
174+ # ---- Extract data ----
175+ print ("\n \n " )
135176
136- # Strip non-json parts
137- body = body [5 :]
138- body = body [:- 7 ]
177+ s = ""
139178
140- print ("Have result:" , body .decode ())
179+ print ("---- " )
180+ cw = data ["current" ]
181+ self .now = Hourly ()
182+ self .now .init (cw , None )
183+ prev = self .now
184+ t = self .now .summarize ()
185+ s += t + "\n "
186+ print (t )
141187
142- # Parse JSON
143- data = ujson .loads (body )
188+ self .hourly = []
189+ d = data ["hourly" ]
190+ times = d ["time" ]
191+ #print(d)
192+
193+ print ("---- " )
194+ for i in range (len (times )):
195+ h = Hourly ()
196+ h .init (d , i )
197+ h .time = times [i ]
198+ self .hourly .append (h )
199+ if not h .similar (prev ):
200+ t = h .summarize ()
201+ s += t + "\n "
202+ print (t )
203+ prev = h
144204
145- # ---- Extract data ----
146- cw = data ["current" ]
147- self .now = Hourly (cw )
148- self .summary = self .now .summarize ()
205+ self .daily = []
206+ d = data ["daily" ]
207+ times = d ["time" ]
208+ #print(d)
209+
210+ print ("---- " )
211+ for i in range (len (times )):
212+ h = Daily ()
213+ h .init (d , i )
214+ h .time = times [i ]
215+ self .daily .append (h )
216+ if i == 0 :
217+ prev = h
218+ elif not h .similar (prev ):
219+ t = h .summarize ()
220+ s += t + "\n "
221+ print (t )
222+ prev = h
223+
224+
225+ self .summary = s
226+
227+ def summarize_future ():
228+ now = utime .time ()
229+
230+ # Rain detection in next 24h
231+ for h in weather .hourly [:24 ]:
232+ if h ["precip" ] >= 1.0 :
233+ return "Rain soon"
234+
235+ # Temperature trend
236+ if len (weather .hourly ) > 24 :
237+ t0 = weather .hourly [0 ]["temp" ]
238+ t24 = weather .hourly [24 ]["temp" ]
239+ if abs (t24 - t0 ) < 2 :
240+ return "No change expected"
241+ if t24 > t0 :
242+ return "Getting warmer"
243+ else :
244+ return "Getting cooler"
245+
246+ return "Stable weather"
247+
149248
150249weather = Weather ()
151250
@@ -167,32 +266,38 @@ def onCreate(self):
167266
168267 # ---- MAIN SCREEN ----
169268
269+ label_weather = lv .label (scr_main )
270+ label_weather .set_text (f"{ weather .name } ({ weather .lat } , { weather .lon } )" )
271+ label_weather .align (lv .ALIGN .TOP_LEFT , 10 , 24 )
272+ label_weather .set_style_text_font (lv .font_montserrat_14 , 0 )
273+ self .label_weather = label_weather
274+
275+ btn_hourly = lv .button (scr_main )
276+ btn_hourly .align (lv .ALIGN .TOP_RIGHT , - 5 , 24 )
277+ lv .label (btn_hourly ).set_text ("Reload" )
278+ btn_hourly .add_event_cb (lambda x : self .do_load (), lv .EVENT .CLICKED , None )
279+
170280 label_time = lv .label (scr_main )
171281 label_time .set_text ("(time)" )
172- label_time .align ( lv .ALIGN .TOP_LEFT , 10 , 40 )
282+ label_time .align_to ( btn_hourly , lv .ALIGN .TOP_LEFT , - 85 , - 10 )
173283 label_time .set_style_text_font (lv .font_montserrat_24 , 0 )
174284 self .label_time = label_time
175285
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-
182286 label_summary = lv .label (scr_main )
183287 label_summary .set_text ("(weather)" )
184288 #label_summary.set_long_mode(lv.label.LONG.WRAP)
185- label_summary .set_width (300 )
289+ # label_summary.set_width(300)
186290 label_summary .align_to (label_weather , lv .ALIGN .OUT_BOTTOM_LEFT , 0 , 5 )
187291 label_summary .set_style_text_font (lv .font_montserrat_24 , 0 )
188292 self .label_summary = label_summary
189293
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" )
194294
195- btn_hourly .add_event_cb (lambda x : self .do_load (), lv .EVENT .CLICKED , None )
295+ if False :
296+ btn_daily = lv .button (scr_main )
297+ btn_daily .set_size (100 , 40 )
298+ btn_daily .align (lv .ALIGN .BOTTOM_RIGHT , - 10 , - 10 )
299+ lv .label (btn_daily ).set_text ("Daily" )
300+
196301
197302 self .setContentView (self .screen )
198303
@@ -223,3 +328,87 @@ def do_load(self):
223328 self .label_summary .set_text ("Requesting..." )
224329 weather .fetch ()
225330
331+ # --------------------
332+
333+ def code ():
334+ # -----------------------------
335+ # LVGL UI
336+ # -----------------------------
337+
338+ scr_main = lv .obj ()
339+ scr_hourly = lv .obj ()
340+ scr_daily = lv .obj ()
341+
342+
343+ # ---- HOURLY SCREEN ----
344+
345+ hourly_list = lv .list (scr_hourly )
346+ hourly_list .set_size (320 , 200 )
347+ hourly_list .align (lv .ALIGN .TOP_MID , 0 , 10 )
348+
349+ btn_back1 = lv .button (scr_hourly )
350+ btn_back1 .set_size (80 , 30 )
351+ btn_back1 .align (lv .ALIGN .BOTTOM_MID , 0 , - 5 )
352+ lv .label (btn_back1 ).set_text ("Back" )
353+
354+ # ---- DAILY SCREEN ----
355+
356+ daily_list = lv .list (scr_daily )
357+ daily_list .set_size (320 , 200 )
358+ daily_list .align (lv .ALIGN .TOP_MID , 0 , 10 )
359+
360+ btn_back2 = lv .button (scr_daily )
361+ btn_back2 .set_size (80 , 30 )
362+ btn_back2 .align (lv .ALIGN .BOTTOM_MID , 0 , - 5 )
363+ lv .label (btn_back2 ).set_text ("Back" )
364+
365+ def foo ():
366+ btn_hourly .add_event_cb (go_hourly , lv .EVENT .CLICKED , None )
367+ btn_daily .add_event_cb (go_daily , lv .EVENT .CLICKED , None )
368+ btn_back1 .add_event_cb (go_back , lv .EVENT .CLICKED , None )
369+ btn_back2 .add_event_cb (go_back , lv .EVENT .CLICKED , None )
370+
371+ # -----------------------------
372+ # STARTUP
373+ # -----------------------------
374+
375+ def go_hourly (e ):
376+ populate_hourly ()
377+ lv .scr_load (scr_hourly )
378+
379+ def go_daily (e ):
380+ populate_daily ()
381+ lv .scr_load (scr_daily )
382+
383+ def go_back (e ):
384+ lv .scr_load (scr_main )
385+
386+ def update_ui ():
387+ if weather .current_temp is not None :
388+ text = "%s %.1f C" % (
389+ weather_code_to_text (weather .current_code ),
390+ weather .current_temp
391+ )
392+ label_weather .set_text (text )
393+
394+ label_summary .set_text (weather .summary )
395+
396+ def populate_hourly ():
397+ hourly_list .clean ()
398+ for h in weather .hourly [:24 ]:
399+ line = "%s %.1fC %.1fmm" % (
400+ h ["time" ][11 :16 ],
401+ h ["temp" ],
402+ h ["precip" ]
403+ )
404+ hourly_list .add_text (line )
405+
406+ def populate_daily ():
407+ daily_list .clean ()
408+ for d in weather .daily :
409+ line = "%s %.1f/%.1f" % (
410+ d ["date" ],
411+ d ["high" ],
412+ d ["low" ]
413+ )
414+ daily_list .add_text (line )
0 commit comments