__init__.py 11.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 Clock:
Daniel Hoffend's avatar
Daniel Hoffend committed
15
16
17
18
19
20
21
22
23
24
25
26
27
    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,
    ):
28
29
30
        self.sizex = sizex
        self.sizey = sizey
        self.radius = radius
Daniel Hoffend's avatar
Daniel Hoffend committed
31
        self.center = (int(self.sizex / 2), int(self.sizey / 2))
32
33
34
35
        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
36
37
38
        self.update_interval = (
            update_interval if update_interval != 0 else (1 if self.second_hand else 30)
        )
39
40
        self.run_once = run_once
        self.offsetx = offsetx
41
42
43
44
45
46
47
48
49
50
51
        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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
            {
                "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],
            },
79
80
81
82
83
84
85
86
87
88
89
            {
                "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
90
91
92
93
94
95

        # check for config file
        if CONFIG_NAME in os.listdir("."):
            self.readConfig()
        else:
            self.writeConfig()
96
97
98
99

        # load colors
        self.setTheme(self.theme)

100
    def readConfig(self):
Daniel Hoffend's avatar
Daniel Hoffend committed
101
        with open(CONFIG_NAME, "r") as f:
102
103
            try:
                c = ujson.loads(f.read())
Daniel Hoffend's avatar
Daniel Hoffend committed
104
105
106
107
108
                if (
                    "themes" in c
                    and len(c["themes"]) > 0
                    and isinstance(c["themes"], list)
                ):
109
110
111
112
113
114
115
                    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
116
117
        with open(CONFIG_NAME, "w") as f:
            f.write(ujson.dumps({"theme": self.theme, "themes": self.themes}))
118

119
120
121
    def setTheme(self, theme):
        self.theme = theme % len(self.themes)
        self.background_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
122
123
124
            self.themes[self.theme]["background"]
            if "background" in self.themes[self.theme]
            else self.default_themes[0]["background"]
125
126
        )
        self.center_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
127
128
129
            self.themes[self.theme]["center"]
            if "center" in self.themes[self.theme]
            else self.default_themes[0]["center"]
130
131
        )
        self.m1_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
132
133
134
            self.themes[self.theme]["m1"]
            if "m1" in self.themes[self.theme]
            else self.default_themes[0]["m1"]
135
136
        )
        self.m5_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
137
138
139
            self.themes[self.theme]["m5"]
            if "m5" in self.themes[self.theme]
            else self.default_themes[0]["m5"]
140
141
        )
        self.hour_hand_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
142
143
144
            self.themes[self.theme]["hour_hand"]
            if "hour_hand" in self.themes[self.theme]
            else self.default_themes[0]["hour_hand"]
145
146
        )
        self.minute_hand_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
147
148
149
            self.themes[self.theme]["minute_hand"]
            if "minute_hand" in self.themes[self.theme]
            else self.default_themes[0]["minute_hand"]
150
151
        )
        self.second_hand_col = (
Daniel Hoffend's avatar
Daniel Hoffend committed
152
153
154
            self.themes[self.theme]["second_hand"]
            if "second_hand" in self.themes[self.theme]
            else self.default_themes[0]["second_hand"]
155
        )
156
157
158
159
160
161

    def loop(self):
        colored = False
        try:
            with display.open() as disp:
                while True:
markus's avatar
markus committed
162
163
                    localtime = utime.localtime()
                    self.updateClock(disp, localtime)
164
165
                    if self.run_once:
                        break
166
167

                    # check for button presses
markus's avatar
markus committed
168
169
170
171
                    v = buttons.read(
                        buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT
                    )
                    button_pressed = v != 0
172

markus's avatar
markus committed
173
                    if button_pressed and v & buttons.BOTTOM_LEFT != 0:
174
                        self.setTheme(self.theme - 1)
175
                        self.writeConfig()
markus's avatar
markus committed
176
                    elif button_pressed and v & buttons.BOTTOM_RIGHT != 0:
177
                        self.setTheme(self.theme + 1)
178
                        self.writeConfig()
markus's avatar
markus committed
179
180
181
182
                    elif button_pressed and v & buttons.TOP_RIGHT != 0:
                        self.setTime(disp, localtime)

                    utime.sleep_ms(23)
183

184
185
186
187
188
        except KeyboardInterrupt:
            for i in range(11):
                leds.set(i, (0, 0, 0))
            return

markus's avatar
markus committed
189
    def updateClock(self, disp, localtime):
190
        disp.clear(self.background_col)
191

Daniel Hoffend's avatar
Daniel Hoffend committed
192
193
194
195
196
197
198
199
200
201
        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))
202
203

        for i in range(60):
Daniel Hoffend's avatar
Daniel Hoffend committed
204
205
            degree = i * 6 + 90
            radian = -math.radians(degree)
206
207
208
            coords = self.circlePoint(radian)

            if not i % 5:
Daniel Hoffend's avatar
Daniel Hoffend committed
209
                self.addLine(disp, coords, self.center, 3, 1, col=self.m5_col)
210
            else:
Daniel Hoffend's avatar
Daniel Hoffend committed
211
                self.addLine(disp, coords, self.center, 1, col=self.m1_col)
212
213

        if self.hour_hand:
Daniel Hoffend's avatar
Daniel Hoffend committed
214
215
216
217
218
219
220
221
            self.addLine(
                disp,
                self.center,
                hour_coords,
                int(self.radius / 3),
                1,
                col=self.hour_hand_col,
            )
222
        if self.minute_hand:
Daniel Hoffend's avatar
Daniel Hoffend committed
223
224
225
226
227
228
229
            self.addLine(
                disp,
                self.center,
                minute_coords,
                int(self.radius / 2),
                col=self.minute_hand_col,
            )
230
        if self.second_hand:
Daniel Hoffend's avatar
Daniel Hoffend committed
231
232
233
234
235
236
237
            self.addLine(
                disp,
                self.center,
                second_coords,
                self.radius - int(self.radius / 8.0),
                col=self.second_hand_col,
            )
238
239

        if self.console_out:
Daniel Hoffend's avatar
Daniel Hoffend committed
240
            for y in range(self.radius * 2):
241
                line = ""
Daniel Hoffend's avatar
Daniel Hoffend committed
242
243
244
245
246
247
248
249
                for x in range(self.radius * 2):
                    line = line + (
                        "."
                        if image[(self.center[1] - self.radius) + y][
                            (self.center[0] - self.radius) + x
                        ]
                        else " "
                    )
250
251
252
253
                print(line)

        disp.update()

markus's avatar
markus committed
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
    def setTime(self, disp, localtime):
        accepted = False
        previously_pressed_button = buttons.TOP_RIGHT
        button_repeat_counter = 0
        set_seconds = utime.mktime(localtime)

        while not accepted:
            v = buttons.read(
                buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT
            )
            button_pressed = v != 0

            if button_pressed:
                if v & previously_pressed_button != 0:
                    button_repeat_counter += 1
                else:
                    if v & buttons.BOTTOM_LEFT != 0:
                        previously_pressed_button = buttons.BOTTOM_LEFT
                    elif v & buttons.BOTTOM_RIGHT != 0:
                        previously_pressed_button = buttons.BOTTOM_RIGHT
                    elif (
                        v & buttons.TOP_RIGHT != 0
                        and previously_pressed_button != buttons.TOP_RIGHT
                    ):
                        accepted = True
                    else:
                        previously_pressed_button = 0
            else:
                previously_pressed_button = 0
                button_repeat_counter = 0

            seconds_change = int(min(1.1 ** button_repeat_counter, 60 * 23 + 1))
            if previously_pressed_button == buttons.BOTTOM_LEFT:
                set_seconds -= seconds_change
            elif previously_pressed_button == buttons.BOTTOM_RIGHT:
                set_seconds += seconds_change

            self.updateClock(disp, utime.localtime(set_seconds))
            utime.sleep_ms(23)

        utime.set_time(int(set_seconds))
        utime.sleep_ms(123)

297
    def circlePoint(self, t):
Daniel Hoffend's avatar
Daniel Hoffend committed
298
299
300
301
        return (
            int(round(self.radius * math.cos(t))) + self.center[0],
            int(round(self.radius * math.sin(t))) + self.center[1],
        )
302

Daniel Hoffend's avatar
Daniel Hoffend committed
303
    def addLine(self, disp, source, aim, length, thickness=1, col=(255, 255, 255)):
304
305
306
307
        vector = self.subVector(aim, source)
        vector = self.normVector(vector)
        destination = self.addVector(source, self.multiplyVector(vector, length))

Daniel Hoffend's avatar
Daniel Hoffend committed
308
309
310
311
312
313
314
315
        disp.line(
            round(source[0]) + self.offsetx,
            round(source[1]),
            round(destination[0]) + self.offsetx,
            round(destination[1]),
            col=col,
            size=thickness,
        )
316
317

    def normVector(self, v):
Daniel Hoffend's avatar
Daniel Hoffend committed
318
        length = math.sqrt(sum([i ** 2 for i in v]))
319
320
        new_v = []
        for i in range(len(v)):
Daniel Hoffend's avatar
Daniel Hoffend committed
321
            new_v.append(v[i] / length)
322
323
324
325
326
        return tuple(new_v)

    def subVector(self, v1, v2):
        res = []
        for i in range(len(v1)):
Daniel Hoffend's avatar
Daniel Hoffend committed
327
            res.append(v1[i] - v2[i])
328
329
330
331
332
        return tuple(res)

    def addVector(self, v1, v2):
        res = []
        for i in range(len(v1)):
Daniel Hoffend's avatar
Daniel Hoffend committed
333
            res.append(v1[i] + v2[i])
334
335
336
        return tuple(res)

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

339
340
341

clock = Clock()
clock.loop()