__init__.py 10.1 KB
Newer Older
1
2
3
4
5
6
# Adapted from https://github.com/muccc/flipdots/blob/master/scripts/clock.py
import display
from utime import sleep
import utime
import math
import leds
7
import buttons
8
9
10
11
import ujson
import os

CONFIG_NAME = "clock.json"
12

Daniel Hoffend's avatar
Daniel Hoffend committed
13

14
class Time:
Daniel Hoffend's avatar
Daniel Hoffend committed
15
    def __init__(self, start=0):
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
        self.time = start
        self.wait_time = 0.95

    def tick(self):
        sleep(self.wait_time)
        self.time += 1

    @property
    def second(self):
        return self.time % 60

    @property
    def minute(self):
        return (self.time / 60) % 60

    @property
    def hour(self):
        return (self.time / 3600) % 24

Daniel Hoffend's avatar
Daniel Hoffend committed
35

36
class Clock:
Daniel Hoffend's avatar
Daniel Hoffend committed
37
38
39
40
41
42
43
44
45
46
47
48
49
    def __init__(
        self,
        sizex=80,
        sizey=80,
        radius=38,
        offsetx=30,
        hour_hand=True,
        minute_hand=True,
        second_hand=True,
        console_out=False,
        run_once=False,
        update_interval=0,
    ):
50
51
52
        self.sizex = sizex
        self.sizey = sizey
        self.radius = radius
Daniel Hoffend's avatar
Daniel Hoffend committed
53
        self.center = (int(self.sizex / 2), int(self.sizey / 2))
54
55
56
57
        self.hour_hand = hour_hand
        self.minute_hand = minute_hand
        self.second_hand = second_hand
        self.console_out = console_out
Daniel Hoffend's avatar
Daniel Hoffend committed
58
59
60
        self.update_interval = (
            update_interval if update_interval != 0 else (1 if self.second_hand else 30)
        )
61
62
63
        self.run_once = run_once
        self.offsetx = offsetx
        self.time = Time()
64
65
66
67
68
69
70
71
72
73
74
        self.theme = 0
        self.default_themes = [
            {
                "background": [0, 0, 0],
                "center": [255, 255, 255],
                "m1": [255, 255, 255],
                "m5": [255, 255, 255],
                "hour_hand": [255, 255, 255],
                "minute_hand": [255, 255, 255],
                "second_hand": [255, 255, 255],
            },
Daniel Hoffend's avatar
Daniel Hoffend committed
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
            {
                "background": [130, 30, 70],
                "center": [255, 255, 255],
                "m1": [255, 255, 255],
                "m5": [255, 255, 255],
                "hour_hand": [255, 255, 255],
                "minute_hand": [255, 255, 255],
                "second_hand": [255, 255, 255],
            },
            {
                "background": [0, 80, 0],
                "center": [255, 255, 255],
                "m1": [255, 255, 255],
                "m5": [255, 255, 255],
                "hour_hand": [255, 255, 255],
                "minute_hand": [255, 255, 255],
                "second_hand": [255, 255, 255],
            },
            {
                "background": [0, 80, 80],
                "center": [255, 255, 255],
                "m1": [255, 255, 255],
                "m5": [255, 255, 255],
                "hour_hand": [255, 255, 255],
                "minute_hand": [255, 255, 255],
                "second_hand": [255, 255, 255],
            },
102
103
104
105
106
107
108
109
110
111
112
            {
                "background": [255, 255, 255],
                "center": [0, 0, 0],
                "m1": [0, 0, 0],
                "m5": [0, 0, 0],
                "hour_hand": [0, 0, 0],
                "minute_hand": [0, 0, 0],
                "second_hand": [0, 0, 0],
            },
        ]
        self.themes = self.default_themes
113
114
115
116
117
118

        # check for config file
        if CONFIG_NAME in os.listdir("."):
            self.readConfig()
        else:
            self.writeConfig()
119
120
121
122

        # load colors
        self.setTheme(self.theme)

123
    def readConfig(self):
Daniel Hoffend's avatar
Daniel Hoffend committed
124
        with open(CONFIG_NAME, "r") as f:
125
126
            try:
                c = ujson.loads(f.read())
Daniel Hoffend's avatar
Daniel Hoffend committed
127
128
129
130
131
                if (
                    "themes" in c
                    and len(c["themes"]) > 0
                    and isinstance(c["themes"], list)
                ):
132
133
134
135
136
137
138
                    self.themes = c["themes"]
                if "theme" and isinstance(c["theme"], int):
                    self.theme = c["theme"]
            except ValueError:
                print("parsing %s failed" % CONFIG_NAME)

    def writeConfig(self):
Daniel Hoffend's avatar
Daniel Hoffend committed
139
140
        with open(CONFIG_NAME, "w") as f:
            f.write(ujson.dumps({"theme": self.theme, "themes": self.themes}))
141

142
143
144
    def setTheme(self, theme):
        self.theme = theme % len(self.themes)
        self.background_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
145
146
147
            self.themes[self.theme]["background"]
            if "background" in self.themes[self.theme]
            else self.default_themes[0]["background"]
148
149
        )
        self.center_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
150
151
152
            self.themes[self.theme]["center"]
            if "center" in self.themes[self.theme]
            else self.default_themes[0]["center"]
153
154
        )
        self.m1_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
155
156
157
            self.themes[self.theme]["m1"]
            if "m1" in self.themes[self.theme]
            else self.default_themes[0]["m1"]
158
159
        )
        self.m5_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
160
161
162
            self.themes[self.theme]["m5"]
            if "m5" in self.themes[self.theme]
            else self.default_themes[0]["m5"]
163
164
        )
        self.hour_hand_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
165
166
167
            self.themes[self.theme]["hour_hand"]
            if "hour_hand" in self.themes[self.theme]
            else self.default_themes[0]["hour_hand"]
168
169
        )
        self.minute_hand_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
170
171
172
            self.themes[self.theme]["minute_hand"]
            if "minute_hand" in self.themes[self.theme]
            else self.default_themes[0]["minute_hand"]
173
174
        )
        self.second_hand_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
175
176
177
            self.themes[self.theme]["second_hand"]
            if "second_hand" in self.themes[self.theme]
            else self.default_themes[0]["second_hand"]
178
        )
179
180
181
182
183

    def loop(self):
        colored = False
        try:
            with display.open() as disp:
184
                button_pressed = False
185
186
187
188
                while True:
                    self.updateClock(disp)
                    if self.run_once:
                        break
189
190
191
192
193
194
195
196
197

                    # check for button presses
                    v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT)
                    if v == 0:
                        button_pressed = False

                    if not button_pressed and v & buttons.BOTTOM_LEFT != 0:
                        button_pressed = True
                        self.setTheme(self.theme - 1)
198
                        self.writeConfig()
199
200
201
                    elif not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
                        button_pressed = True
                        self.setTheme(self.theme + 1)
202
                        self.writeConfig()
203

204
205
206
207
208
209
210
211
212
213
        except KeyboardInterrupt:
            for i in range(11):
                leds.set(i, (0, 0, 0))
            return

    def drawImage(self, image):
        with display.open() as d:
            d.clear()
            for x in range(len(image)):
                for y in range(len(image[x])):
Daniel Hoffend's avatar
Daniel Hoffend committed
214
215
216
217
218
                    d.pixel(
                        x + self.offsetx,
                        y,
                        col=(255, 255, 255) if image[x][y] else (0, 0, 0),
                    )
219
220
221
            d.update()

    def updateClock(self, disp):
222
        disp.clear(self.background_col)
223
224
        localtime = utime.localtime()

Daniel Hoffend's avatar
Daniel Hoffend committed
225
226
227
228
229
230
231
232
233
234
        disp.pixel(self.center[0] + self.offsetx, self.center[1], col=self.center_col)
        hour_coords = self.circlePoint(
            math.radians(
                (((localtime[3] % 12) / 12.0) if localtime[3] else 0) * 360
                + 270
                + (localtime[4] / 2)
            )
        )
        minute_coords = self.circlePoint(math.radians(localtime[4] * 6 + 270))
        second_coords = self.circlePoint(math.radians(localtime[5] * 6 + 270))
235
236

        for i in range(60):
Daniel Hoffend's avatar
Daniel Hoffend committed
237
238
            degree = i * 6 + 90
            radian = -math.radians(degree)
239
240
241
            coords = self.circlePoint(radian)

            if not i % 5:
Daniel Hoffend's avatar
Daniel Hoffend committed
242
                self.addLine(disp, coords, self.center, 3, 1, col=self.m5_col)
243
            else:
Daniel Hoffend's avatar
Daniel Hoffend committed
244
                self.addLine(disp, coords, self.center, 1, col=self.m1_col)
245
246

        if self.hour_hand:
Daniel Hoffend's avatar
Daniel Hoffend committed
247
248
249
250
251
252
253
254
            self.addLine(
                disp,
                self.center,
                hour_coords,
                int(self.radius / 3),
                1,
                col=self.hour_hand_col,
            )
255
        if self.minute_hand:
Daniel Hoffend's avatar
Daniel Hoffend committed
256
257
258
259
260
261
262
            self.addLine(
                disp,
                self.center,
                minute_coords,
                int(self.radius / 2),
                col=self.minute_hand_col,
            )
263
        if self.second_hand:
Daniel Hoffend's avatar
Daniel Hoffend committed
264
265
266
267
268
269
270
            self.addLine(
                disp,
                self.center,
                second_coords,
                self.radius - int(self.radius / 8.0),
                col=self.second_hand_col,
            )
271
272

        if self.console_out:
Daniel Hoffend's avatar
Daniel Hoffend committed
273
            for y in range(self.radius * 2):
274
                line = ""
Daniel Hoffend's avatar
Daniel Hoffend committed
275
276
277
278
279
280
281
282
                for x in range(self.radius * 2):
                    line = line + (
                        "."
                        if image[(self.center[1] - self.radius) + y][
                            (self.center[0] - self.radius) + x
                        ]
                        else " "
                    )
283
284
285
286
287
                print(line)

        disp.update()

    def circlePoint(self, t):
Daniel Hoffend's avatar
Daniel Hoffend committed
288
289
290
291
        return (
            int(round(self.radius * math.cos(t))) + self.center[0],
            int(round(self.radius * math.sin(t))) + self.center[1],
        )
292

Daniel Hoffend's avatar
Daniel Hoffend committed
293
    def addLine(self, disp, source, aim, length, thickness=1, col=(255, 255, 255)):
294
295
296
297
        vector = self.subVector(aim, source)
        vector = self.normVector(vector)
        destination = self.addVector(source, self.multiplyVector(vector, length))

Daniel Hoffend's avatar
Daniel Hoffend committed
298
299
300
301
302
303
304
305
        disp.line(
            round(source[0]) + self.offsetx,
            round(source[1]),
            round(destination[0]) + self.offsetx,
            round(destination[1]),
            col=col,
            size=thickness,
        )
306
307

    def normVector(self, v):
Daniel Hoffend's avatar
Daniel Hoffend committed
308
        length = math.sqrt(sum([i ** 2 for i in v]))
309
310
        new_v = []
        for i in range(len(v)):
Daniel Hoffend's avatar
Daniel Hoffend committed
311
            new_v.append(v[i] / length)
312
313
314
315
316
        return tuple(new_v)

    def subVector(self, v1, v2):
        res = []
        for i in range(len(v1)):
Daniel Hoffend's avatar
Daniel Hoffend committed
317
            res.append(v1[i] - v2[i])
318
319
320
321
322
        return tuple(res)

    def addVector(self, v1, v2):
        res = []
        for i in range(len(v1)):
Daniel Hoffend's avatar
Daniel Hoffend committed
323
            res.append(v1[i] + v2[i])
324
325
326
        return tuple(res)

    def multiplyVector(self, v, multiplier):
Daniel Hoffend's avatar
Daniel Hoffend committed
327
328
        return tuple([i * multiplier for i in v])

329
330
331

clock = Clock()
clock.loop()