Переглянути джерело

initial weather page and other small stuff

Thomas Buck 1 рік тому
джерело
коміт
ed856ac93f
7 змінених файлів з 240 додано та 6 видалено
  1. 1
    0
      .gitignore
  2. 1
    1
      README.md
  3. 8
    1
      config.py
  4. 14
    2
      qr.py
  5. 1
    1
      telegram.py
  6. 1
    1
      test.py
  7. 214
    0
      weather.py

+ 1
- 0
.gitignore Переглянути файл

@@ -1,2 +1,3 @@
1 1
 __pycache__
2 2
 tmp
3
+venv

+ 1
- 1
README.md Переглянути файл

@@ -14,7 +14,7 @@ and go from there.
14 14
 
15 15
 You always need:
16 16
 
17
-    pip install Pillow bdfparser "qrcode[pil]" evdev
17
+    pip install Pillow bdfparser "qrcode[pil]" evdev wetterdienst
18 18
 
19 19
 For evdev to find all devices you may need to add your user to the `input` group or run the scripts as root.
20 20
 

+ 8
- 1
config.py Переглянути файл

@@ -10,5 +10,12 @@
10 10
 class Config:
11 11
     networks = [
12 12
         ("SSID_1", "PASS_1"),
13
-        ("SSID_2", "PASS_2"),
13
+        ("SSID_2", "USER_2", "PASS_2"),
14 14
     ]
15
+
16
+    telegram_key = "API_KEY_HERE"
17
+    telegram_whitelist = [
18
+        "ALLOWED_CHAT_ID_HERE",
19
+    ]
20
+
21
+    weather_latlon = (YOUR_LAT_HERE, YOUR_LON_HERE)

+ 14
- 2
qr.py Переглянути файл

@@ -53,6 +53,12 @@ class QRScreen:
53 53
                     self.image = qr.make_image(fill_color = "white", back_color = "black")
54 54
             else:
55 55
                 self.image = qr.make_image(fill_color = self.c1, back_color = self.c2)
56
+
57
+            # enlarge small images
58
+            if ((self.image.width * 2) <= self.gui.width) and ((self.image.height * 2) <= self.gui.height):
59
+                from PIL import Image
60
+                self.image = self.image.crop(self.image.getbbox())
61
+                self.image = self.image.resize((self.image.width * 2, self.image.height * 2), Image.Resampling.NEAREST)
56 62
         else:
57 63
             # Show pre-generated QR code image
58 64
             self.image = PicoImage()
@@ -95,9 +101,15 @@ class QRScreen:
95 101
         if self.heading != None:
96 102
             off = 0
97 103
             if self.font == "bitmap6":
98
-                off = -14
104
+                if self.gui.height == 64:
105
+                    off = -14 - 16
106
+                else:
107
+                    off = -14
99 108
             elif self.font == "tom-thumb":
100
-                off = -11
109
+                if self.gui.height == 64:
110
+                    off = -11 - 16
111
+                else:
112
+                    off = -11
101 113
 
102 114
             self.text.draw(0, off)
103 115
 

+ 1
- 1
telegram.py Переглянути файл

@@ -85,7 +85,7 @@ class TelegramBot:
85 85
             print()
86 86
             return None
87 87
 
88
-if True:#__name__ == "__main__":
88
+if __name__ == "__main__":
89 89
     b = TelegramBot()
90 90
     b.message("Hello World!")
91 91
     while True:

+ 1
- 1
test.py Переглянути файл

@@ -13,7 +13,7 @@
13 13
 import pygame
14 14
 
15 15
 class TestGUI:
16
-    def __init__(self, width = 32 * 2, height = 32 * 2, multiplier = 16):
16
+    def __init__(self, width = 32 * 2, height = 32 * 2, multiplier = 8):
17 17
         self.width = width
18 18
         self.height = height
19 19
         self.multiplier = multiplier

+ 214
- 0
weather.py Переглянути файл

@@ -0,0 +1,214 @@
1
+#!/usr/bin/env python3
2
+
3
+# ----------------------------------------------------------------------------
4
+# "THE BEER-WARE LICENSE" (Revision 42):
5
+# <xythobuz@xythobuz.de> wrote this file.  As long as you retain this notice
6
+# you can do whatever you want with this stuff. If we meet some day, and you
7
+# think this stuff is worth it, you can buy me a beer in return.   Thomas Buck
8
+# ----------------------------------------------------------------------------
9
+
10
+import util
11
+import time
12
+from datetime import datetime, timezone, timedelta
13
+
14
+from wetterdienst import Settings
15
+from wetterdienst.provider.dwd.mosmix import (
16
+    DwdForecastDate,
17
+    DwdMosmixRequest,
18
+    DwdMosmixType,
19
+)
20
+import polars
21
+
22
+class WeatherScreen:
23
+    def __init__(self, g, i, latlon, timestep = 5.0, refresh = (60.0 * 60.0)):
24
+        self.gui = g
25
+        self.input = i
26
+        self.latlon = latlon
27
+        self.timestep = timestep
28
+
29
+        DrawText = util.getTextDrawer()
30
+        self.t_head = DrawText(self.gui, (255, 255, 255), (0, 0, 0))
31
+        self.t_sub = DrawText(self.gui, (0, 255, 255), (0, 0, 0))
32
+        self.t_val = DrawText(self.gui, (255, 0, 255), (0, 0, 0))
33
+        self.t_aux = DrawText(self.gui, (255, 255, 0), (0, 0, 0))
34
+
35
+        self.params = [
36
+            "weather_significant",
37
+            "temperature_air_mean_200",
38
+            "cloud_cover_effective",
39
+        ]
40
+        self.num_state = len(self.params) + 1
41
+
42
+        self.find_station()
43
+        self.restart()
44
+
45
+    def restart(self):
46
+        self.state = 0
47
+        self.old_keys = {
48
+            "left": False,
49
+            "right": False,
50
+            "up": False,
51
+            "down": False,
52
+            "a": False,
53
+            "b": False,
54
+            "x": False,
55
+            "y": False,
56
+            "l": False,
57
+            "r": False,
58
+            "start": False,
59
+            "select": False,
60
+        }
61
+        self.last = time.time()
62
+
63
+    def find_station(self):
64
+        settings = Settings(ts_shape=True, ts_humanize=True)
65
+        request = DwdMosmixRequest(
66
+            parameter=self.params,
67
+            start_issue=DwdForecastDate.LATEST,
68
+            mosmix_type=DwdMosmixType.LARGE,
69
+            settings=settings,
70
+        )
71
+        stations = request.filter_by_distance(latlon=self.latlon, distance=30)
72
+        self.station = stations.df[0]
73
+
74
+        print("Found station '{}' ({}) in {:.2f} km distance".format(
75
+            self.station["name"][0],
76
+            self.station["station_id"][0],
77
+            self.station["distance"][0]
78
+        ))
79
+
80
+        self.forecast = request.filter_by_station_id(station_id=[
81
+            self.station["station_id"][0],
82
+        ])
83
+        self.parse_forecast()
84
+
85
+    def get_forecast(self):
86
+        settings = Settings(ts_shape=True, ts_humanize=True)
87
+        request = DwdMosmixRequest(
88
+            parameter=self.params,
89
+            start_issue=DwdForecastDate.LATEST,
90
+            mosmix_type=DwdMosmixType.LARGE,
91
+            settings=settings,
92
+        )
93
+        self.forecast = request.filter_by_station_id(station_id=[self.station["station_id"][0]])
94
+        self.parse_forecast()
95
+
96
+    def parse_forecast(self):
97
+        self.last_forecast = time.time()
98
+
99
+        response = next(self.forecast.values.query())
100
+        response_subset = response.df.select(["date", "parameter", "value"])
101
+
102
+        # only datapoints from -1h to +12h from now
103
+        start_time = datetime.now(timezone.utc) - timedelta(hours=1)
104
+        end_time = datetime.now(timezone.utc) + timedelta(hours=12)
105
+        self.data = response_subset.filter((polars.col("date") >= start_time) & (polars.col("date") <= end_time))
106
+
107
+        #print(self.data)
108
+        #for p in self.params:
109
+        #    print(self.data.filter(polars.col("parameter") == p)[0])
110
+        #for i in range(0, len(self.data)):
111
+        #    print(self.data[i])
112
+
113
+    def buttons(self):
114
+        keys = self.input.get()
115
+
116
+        if keys["up"] and (not self.old_keys["up"]) and (not self.old_keys["select"]):
117
+            self.state = (self.state + 1) % self.num_state
118
+            self.last = time.time()
119
+        elif keys["down"] and (not self.old_keys["select"]):
120
+            self.state = (self.state - 1) % self.num_state
121
+            self.last = time.time()
122
+
123
+        self.old_keys = keys.copy()
124
+
125
+    def finished(self):
126
+        return False # TODO
127
+
128
+    def draw_station_info(self):
129
+        # heading
130
+        self.t_head.setText("Weather:", "lemon")
131
+        self.t_head.draw(3, -self.gui.height / 2 + 7)
132
+
133
+        # station info (2 lines)
134
+        self.t_sub.setText(self.station["name"][0], "tom-thumb")
135
+        self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 2)
136
+        self.t_sub.draw(self.gui.width, -self.gui.height / 2 + 5 + 6 * 3)
137
+
138
+        # distance
139
+        self.t_val.setText("Distance:", "tom-thumb")
140
+        self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 5)
141
+        self.t_val.setText("{:.2f} km".format(self.station["distance"][0]), "tom-thumb")
142
+        self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 6)
143
+
144
+        # lat lon
145
+        self.t_aux.setText("Lat: {:.2f}".format(self.station["latitude"][0]), "tom-thumb")
146
+        self.t_aux.draw(0, -self.gui.height / 2 + 5 + 6 * 8)
147
+        self.t_aux.setText("Lon: {:.2f}".format(self.station["longitude"][0]), "tom-thumb")
148
+        self.t_aux.draw(0, -self.gui.height / 2 + 5 + 6 * 9)
149
+
150
+    def draw_weather(self):
151
+        # heading
152
+        self.t_head.setText("Weather:", "lemon")
153
+        self.t_head.draw(3, -self.gui.height / 2 + 7)
154
+
155
+        val = self.data.filter(polars.col("parameter") == self.params[0])[0]["value"][0]
156
+        self.t_val.setText("{:.0f}".format(val), "tom-thumb")
157
+        self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 5)
158
+
159
+    def draw_temperature(self):
160
+        # heading
161
+        self.t_head.setText("Temps:", "lemon")
162
+        self.t_head.draw(3, -self.gui.height / 2 + 7)
163
+
164
+        # current temperature
165
+        self.t_sub.setText("Current:", "tom-thumb")
166
+        self.t_sub.draw(0, -self.gui.height / 2 + 5 + 6 * 2)
167
+        val = self.data.filter(polars.col("parameter") == self.params[1])[0]["value"][0]
168
+        val = val - 273.15 # kelvin to celsius
169
+        self.t_val.setText("{:.1f} °C".format(val), "tom-thumb")
170
+        self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 3)
171
+
172
+    def draw_cloud_cover(self):
173
+        # heading
174
+        self.t_head.setText("Clouds:", "lemon")
175
+        self.t_head.draw(3, -self.gui.height / 2 + 7)
176
+
177
+        val = self.data.filter(polars.col("parameter") == self.params[2])[0]["value"][0]
178
+        self.t_val.setText("{:.1f} %".format(val), "tom-thumb")
179
+        self.t_val.draw(0, -self.gui.height / 2 + 5 + 6 * 5)
180
+
181
+    def draw(self):
182
+        # handle button input
183
+        if self.input != None:
184
+            self.buttons()
185
+
186
+        # draw screen contents
187
+        if self.state == 0:
188
+            self.draw_station_info()
189
+        elif self.state == 1:
190
+            self.draw_weather()
191
+        elif self.state == 2:
192
+            self.draw_temperature()
193
+        elif self.state == 3:
194
+            self.draw_cloud_cover()
195
+
196
+        # advance to next screen after time has passed
197
+        if (time.time() - self.last) >= self.timestep:
198
+            self.state = (self.state + 1) % self.num_state
199
+            self.last = time.time()
200
+
201
+        # draw progress bar on bottom most row
202
+        elapsed = (time.time() - self.last)
203
+        ratio = elapsed / self.timestep
204
+        for i in range(0, int(ratio * self.gui.width)):
205
+            self.gui.set_pixel(i, self.gui.height - 1, (255, 0, 0))
206
+
207
+if __name__ == "__main__":
208
+    from config import Config
209
+
210
+    i = util.getInput()
211
+    t = util.getTarget(i)
212
+
213
+    s = WeatherScreen(t, i, Config.weather_latlon, 2.0, 60.0 * 10.0)
214
+    util.loop(t, s.draw)

Завантаження…
Відмінити
Зберегти