Python RGB Matrix games and animations https://www.xythobuz.de/ledmatrix_v2.html
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

weather.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #!/usr/bin/env python3
  2. # ----------------------------------------------------------------------------
  3. # "THE BEER-WARE LICENSE" (Revision 42):
  4. # <xythobuz@xythobuz.de> wrote this file. As long as you retain this notice
  5. # you can do whatever you want with this stuff. If we meet some day, and you
  6. # think this stuff is worth it, you can buy me a beer in return. Thomas Buck
  7. # ----------------------------------------------------------------------------
  8. import util
  9. from image import ImageScreen
  10. import time
  11. from datetime import datetime, timezone, timedelta
  12. from wetterdienst import Settings
  13. from wetterdienst.provider.dwd.mosmix import (
  14. DwdForecastDate,
  15. DwdMosmixRequest,
  16. DwdMosmixType,
  17. )
  18. import polars
  19. def get_first_capitalized_word(s):
  20. words = s.split()
  21. for w in words:
  22. if w[0].isupper():
  23. return w
  24. return None
  25. class WeatherScreen:
  26. def __init__(self, g, i, latlon, timestep = 5.0, refresh = (60.0 * 60.0)):
  27. self.gui = g
  28. self.input = i
  29. self.latlon = latlon
  30. self.timestep = timestep
  31. DrawText = util.getTextDrawer()
  32. self.t_head = DrawText(self.gui, (255, 255, 255), (0, 0, 0))
  33. self.t_sub = DrawText(self.gui, (0, 255, 255), (0, 0, 0))
  34. self.t_val = DrawText(self.gui, (255, 0, 255), (0, 0, 0))
  35. self.t_aux = DrawText(self.gui, (255, 255, 0), (0, 0, 0))
  36. self.params = [
  37. "weather_significant",
  38. "temperature_air_mean_200",
  39. "cloud_cover_effective",
  40. ]
  41. self.num_state = 4
  42. self.descriptions = [
  43. (95, 'leichtes oder mäßiges Gewitter mit Regen oder Schnee', 'weather_icon_95.png'),
  44. (57, 'mäßiger oder starker gefrierender Sprühregen', 'weather_icon_57.png'),
  45. (56, 'leichter gefrierender Sprühregen', 'weather_icon_56.png'),
  46. (67, 'mäßiger bis starker gefrierender Regen', 'weather_icon_67.png'),
  47. (66, 'leichter gefrierender Regen', 'weather_icon_66.png'),
  48. (86, 'mäßiger bis starker Schneeschauer', 'weather_icon_86.png'),
  49. (85, 'leichter Schneeschauer', 'weather_icon_85.png'),
  50. (84, 'mäßiger oder starker Schneeregenschauer', 'weather_icon_84.png'),
  51. (83, 'leichter Schneeregenschauer', 'weather_icon_83.png'),
  52. (82, 'äußerst heftiger Regenschauer', 'weather_icon_82.png'),
  53. (81, 'mäßiger oder starker Regenschauer', 'weather_icon_81.png'),
  54. (80, 'leichter Regenschauer', 'weather_icon_80.png'),
  55. (75, 'durchgehend starker Schneefall', 'weather_icon_75.png'),
  56. (73, 'durchgehend mäßiger Schneefall', 'weather_icon_73.png'),
  57. (71, 'durchgehend leichter Schneefall', 'weather_icon_71.png'),
  58. (69, 'mäßger oder starker Schneeregen', 'weather_icon_69.png'),
  59. (68, 'leichter Schneeregen', 'weather_icon_68.png'),
  60. (55, 'durchgehend starker Sprühregen', 'weather_icon_55.png'),
  61. (53, 'durchgehend mäßiger Sprühregen', 'weather_icon_53.png'),
  62. (51, 'durchgehend leichter Sprühregen', 'weather_icon_51.png'),
  63. (65, 'durchgehend starker Regen', 'weather_icon_65.png'),
  64. (63, 'durchgehend mäßiger Regen', 'weather_icon_63.png'),
  65. (61, 'durchgehend leichter Regen', 'weather_icon_61.png'),
  66. (49, 'Nebel mit Reifansatz, Himmel nicht erkennbar, unverändert', 'weather_icon_49.png'),
  67. (45, 'Nebel, Himmel nicht erkennbar', 'weather_icon_45.png'),
  68. (3, 'Bewölkung zunehmend', None),
  69. (2, 'Bewölkung unverändert', None),
  70. (1, 'Bewölkung abnehmend', None),
  71. (0, 'keine Bewölkungsentwicklung', None),
  72. ]
  73. self.find_station()
  74. self.restart()
  75. def restart(self):
  76. self.state = 0
  77. self.old_keys = {
  78. "left": False,
  79. "right": False,
  80. "up": False,
  81. "down": False,
  82. "a": False,
  83. "b": False,
  84. "x": False,
  85. "y": False,
  86. "l": False,
  87. "r": False,
  88. "start": False,
  89. "select": False,
  90. }
  91. self.done = False
  92. self.last = time.time()
  93. def find_station(self):
  94. self.gui.loop_start()
  95. self.t_head.setText("Weather:", "lemon")
  96. self.t_head.draw(3, -self.gui.height / 2 + 7)
  97. self.t_sub.setText("Loading...", "tom-thumb")
  98. self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 4)
  99. self.t_val.setText("Station...", "tom-thumb")
  100. self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 6)
  101. self.gui.loop_end()
  102. settings = Settings(ts_shape=True, ts_humanize=True, cache_disable=False)
  103. request = DwdMosmixRequest(
  104. parameter=self.params,
  105. start_issue=DwdForecastDate.LATEST,
  106. mosmix_type=DwdMosmixType.LARGE,
  107. settings=settings,
  108. )
  109. stations = request.filter_by_distance(latlon=self.latlon, distance=30)
  110. self.station = stations.df[0]
  111. print("Found station '{}' ({}) in {:.2f} km distance".format(
  112. self.station["name"][0],
  113. self.station["station_id"][0],
  114. self.station["distance"][0]
  115. ))
  116. self.forecast = request.filter_by_station_id(station_id=[
  117. self.station["station_id"][0],
  118. ])
  119. self.parse_forecast()
  120. def get_forecast(self):
  121. self.gui.loop_start()
  122. self.t_head.setText("Weather:", "lemon")
  123. self.t_head.draw(3, -self.gui.height / 2 + 7)
  124. self.t_sub.setText("Loading...", "tom-thumb")
  125. self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 4)
  126. self.t_val.setText("Refresh...", "tom-thumb")
  127. self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 6)
  128. self.gui.loop_end()
  129. settings = Settings(ts_shape=True, ts_humanize=True, cache_disable=False)
  130. request = DwdMosmixRequest(
  131. parameter=self.params,
  132. start_issue=DwdForecastDate.LATEST,
  133. mosmix_type=DwdMosmixType.LARGE,
  134. settings=settings,
  135. )
  136. self.forecast = request.filter_by_station_id(station_id=[self.station["station_id"][0]])
  137. self.parse_forecast()
  138. def parse_forecast(self):
  139. self.gui.loop_start()
  140. self.t_head.setText("Weather:", "lemon")
  141. self.t_head.draw(3, -self.gui.height / 2 + 7)
  142. self.t_sub.setText("Parsing", "tom-thumb")
  143. self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 4)
  144. self.t_val.setText("Forecast", "tom-thumb")
  145. self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 6)
  146. self.gui.loop_end()
  147. self.last_forecast = time.time()
  148. response = next(self.forecast.values.query())
  149. response_subset = response.df.select(["date", "parameter", "value"])
  150. # only datapoints from -1h to +12h from now
  151. start_time = datetime.now(timezone.utc) - timedelta(hours=1)
  152. end_time = datetime.now(timezone.utc) + timedelta(hours=12)
  153. self.data = response_subset.filter((polars.col("date") >= start_time) & (polars.col("date") <= end_time))
  154. #print(self.data)
  155. #for p in self.params:
  156. # print(self.data.filter(polars.col("parameter") == p)[0])
  157. #for i in range(0, len(self.data)):
  158. # print(self.data[i])
  159. def buttons(self):
  160. keys = self.input.get()
  161. if keys["up"] and (not self.old_keys["up"]) and (not self.old_keys["select"]):
  162. self.state = (self.state + 1) % self.num_state
  163. self.last = time.time()
  164. elif keys["down"] and (not self.old_keys["down"]) and (not self.old_keys["select"]):
  165. self.state = (self.state - 1) % self.num_state
  166. self.last = time.time()
  167. self.old_keys = keys.copy()
  168. def finished(self):
  169. return self.done
  170. def draw_station_info(self):
  171. # heading
  172. self.t_head.setText("Weather:", "lemon")
  173. self.t_head.draw(3, -self.gui.height / 2 + 7)
  174. # station info (2 lines)
  175. self.t_sub.setText(self.station["name"][0], "tom-thumb")
  176. self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 2)
  177. self.t_sub.draw(self.gui.width, -self.gui.height / 2 + 5 + 6 * 3)
  178. # distance
  179. self.t_val.setText("Distance:", "tom-thumb")
  180. self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 5)
  181. self.t_val.setText("{:.2f} km".format(self.station["distance"][0]), "tom-thumb")
  182. self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 6)
  183. # lat lon
  184. self.t_aux.setText("Lat: {:.2f}".format(self.station["latitude"][0]), "tom-thumb")
  185. self.t_aux.draw(0, -self.gui.height / 2 + 5 + 6 * 8)
  186. self.t_aux.setText("Lon: {:.2f}".format(self.station["longitude"][0]), "tom-thumb")
  187. self.t_aux.draw(0, -self.gui.height / 2 + 5 + 6 * 9)
  188. def draw_weather(self):
  189. # heading
  190. self.t_head.setText("Weather:", "lemon")
  191. self.t_head.draw(3, -self.gui.height / 2 + 7)
  192. # convert weather id
  193. val = self.data.filter(polars.col("parameter") == self.params[0])[0]["value"][0]
  194. name = "ID {} Unknown".format(val)
  195. img = None
  196. for i, d in enumerate(self.descriptions):
  197. if d[0] != val:
  198. continue
  199. if isinstance(d[2], str):
  200. w = 50
  201. target_size = (w, w)
  202. iscr = ImageScreen(self.gui, d[2], 0.2, 1, 5.0, None, target_size, False)
  203. self.descriptions[i] = (d[0], d[1], iscr)
  204. iscr.xOff = (self.gui.width - w) / 2
  205. iscr.yOff = self.gui.height - w + 5
  206. img = iscr
  207. else:
  208. img = d[2]
  209. name = get_first_capitalized_word(d[1])
  210. # print weather description text
  211. self.t_sub.setText(name, "tom-thumb")
  212. self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 1 + 2)
  213. #self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 2)
  214. #self.t_sub.draw(self.gui.width, -self.gui.height / 2 + 5 + 6 * 3)
  215. #self.t_sub.draw(self.gui.width * 2, -self.gui.height / 2 + 5 + 6 * 4)
  216. # show image if it exists
  217. if img != None:
  218. img.draw()
  219. # id of weather / image
  220. self.t_aux.setText("{}".format(int(val)), "tom-thumb")
  221. self.t_aux.draw(0, -self.gui.height / 2 + 5 + 6 * 9 + 3)
  222. def draw_temperature(self):
  223. # heading
  224. self.t_head.setText("Temps:", "lemon")
  225. self.t_head.draw(3, -self.gui.height / 2 + 7)
  226. self.t_sub.setText("Current:", "lemon")
  227. self.t_sub.draw(3, -self.gui.height / 2 + 20)
  228. # current temperature
  229. val = self.data.filter(polars.col("parameter") == self.params[1])[0]["value"][0]
  230. val = val - 273.15 # kelvin to celsius
  231. self.t_val.setText("{:.1f}°C".format(val), "tom-thumb")
  232. self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 5)
  233. def draw_cloud_cover(self):
  234. # heading
  235. self.t_head.setText("Cloud", "lemon")
  236. self.t_head.draw(3, -self.gui.height / 2 + 7)
  237. self.t_sub.setText("Coverage:", "lemon")
  238. self.t_sub.draw(3, -self.gui.height / 2 + 20)
  239. val = self.data.filter(polars.col("parameter") == self.params[2])[0]["value"][0]
  240. self.t_val.setText("{:.1f}%".format(val), "tom-thumb")
  241. self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 5)
  242. def draw(self):
  243. # handle button input
  244. if self.input != None:
  245. self.buttons()
  246. # draw screen contents
  247. if self.state == 0:
  248. self.draw_station_info()
  249. elif self.state == 1:
  250. self.draw_weather()
  251. elif self.state == 2:
  252. self.draw_temperature()
  253. elif self.state == 3:
  254. self.draw_cloud_cover()
  255. # advance to next screen after time has passed
  256. if (time.time() - self.last) >= self.timestep:
  257. self.state = (self.state + 1) % self.num_state
  258. if self.state == 0:
  259. self.done = True
  260. self.last = time.time()
  261. # draw progress bar on bottom most row
  262. elapsed = (time.time() - self.last)
  263. ratio = elapsed / self.timestep
  264. for i in range(0, int(ratio * self.gui.width) + 1):
  265. self.gui.set_pixel(i, self.gui.height - 1, (0, 255, 0))
  266. if __name__ == "__main__":
  267. from config import Config
  268. i = util.getInput()
  269. t = util.getTarget(i)
  270. s = WeatherScreen(t, i, Config.weather_latlon, 5.0, 60.0 * 10.0)
  271. util.loop(t, s.draw)