__init__.py 10.9 KB
Newer Older
schneider's avatar
schneider committed
1
2
3
4
5
6
7
8
9
import utime
import display
import leds
import ledfx
import buttons
import light_sensor
import ujson
import os

Rahix's avatar
Rahix committed
10
11
12
FILENAME = "nickname.txt"
FILENAME_ADV = "nickname.json"
ANIM_TYPES = ["none", "led", "fade", "gay", "rainbow", "rnd_led"]
schneider's avatar
schneider committed
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42


def wheel(pos):
    """
    Taken from https://badge.team/projects/rainbow_name
    Input a value 0 to 255 to get a color value.
    The colours are a transition r - g - b - back to r.
    :param pos: input position
    :return: rgb value
    """
    if pos < 0:
        return 0, 0, 0
    if pos > 255:
        pos -= 255
    if pos < 85:
        return int(255 - pos * 3), int(pos * 3), 0
    if pos < 170:
        pos -= 85
        return 0, int(255 - pos * 3), int(pos * 3)
    pos -= 170
    return int(pos * 3), 0, int(255 - (pos * 3))


def random_rgb():
    """
    Generates a random RGB value
    :return: RGB array
    """
    rgb = []
    for i in range(0, 3):
Rahix's avatar
Rahix committed
43
        rand = int.from_bytes(os.urandom(1), "little")
schneider's avatar
schneider committed
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
        if rand > 255:
            rand = 255
        rgb.append(rand)
    return rgb


def blink_led(led):
    """
    Turns off leds, blinks given led for 100ms
    can be used as an indicator
    :param led: led to blink
    """
    leds.clear()
    utime.sleep(0.1)
    leds.set(led, [255, 0, 0])
    utime.sleep(0.1)
    leds.clear()
schneider's avatar
schneider committed
61
62
63


def render_error(err1, err2):
schneider's avatar
schneider committed
64
65
66
67
68
    """
    Function to render two lines of text (each max 11 chars). Useful to display error messages
    :param err1: line one
    :param err2: line two
    """
schneider's avatar
schneider committed
69
70
71
72
73
74
75
76
    with display.open() as disp:
        disp.clear()
        disp.print(err1, posx=80 - round(len(err1) / 2 * 14), posy=18)
        disp.print(err2, posx=80 - round(len(err2) / 2 * 14), posy=42)
        disp.update()
        disp.close()


schneider's avatar
schneider committed
77
78
79
80
81
82
def get_bat_color(bat):
    """
    Function determines the color of the battery indicator. Colors can be set in config.
    Voltage threshold's are currently estimates as voltage isn't that great of an indicator for
    battery charge.
    :param bat: battery config tuple (boolean: indicator on/off, array: good rgb, array: ok rgb, array: bad rgb)
83
    :return: battery status tuple (float: battery voltage, false if old firmware, RGB color array otherwise)
schneider's avatar
schneider committed
84
85
86
87
    """
    try:
        v = os.read_battery()
        if v > 3.8:
88
            return (v, bat[1])
schneider's avatar
schneider committed
89
        if v > 3.6:
90
91
            return (v, bat[2])
        return (v, bat[3])
schneider's avatar
schneider committed
92
    except AttributeError:
93
        return (0, False)
schneider's avatar
schneider committed
94
95
96
97
98
99
100
101
102


def render_battery(disp, bat):
    """
    Adds the battery indicator to the display. Does not call update or clear so it can be used in addition to
    other display code.
    :param disp: open display
    :param bat: battery config tuple (boolean: indicator on/off, array: good rgb, array: ok rgb, array: bad rgb)
    """
103
    v, c = get_bat_color(bat)
schneider's avatar
schneider committed
104
105
    if not c:
        return
106
107
108
109
110
111
    if v > 4.0:
        disp.rect(140, 2, 155, 9, filled=True, col=c)
    else:
        disp.rect(140, 2, 154, 8, filled=False, col=c)
        if v > 3.5:
            disp.rect(141, 3, 142 + int((v - 3.5) * 24), 8, filled=True, col=c)
schneider's avatar
schneider committed
112
113
114
    disp.rect(155, 4, 157, 7, filled=True, col=c)


115
def get_time():
schneider's avatar
schneider committed
116
117
118
119
    """
    Generates a nice timestamp in format hh:mm:ss from the devices localtime
    :return: timestamp
    """
Rahix's avatar
Rahix committed
120
    timestamp = ""
121
    if utime.localtime()[3] < 10:
Rahix's avatar
Rahix committed
122
123
        timestamp = timestamp + "0"
    timestamp = timestamp + str(utime.localtime()[3]) + ":"
124
    if utime.localtime()[4] < 10:
Rahix's avatar
Rahix committed
125
126
        timestamp = timestamp + "0"
    timestamp = timestamp + str(utime.localtime()[4]) + ":"
127
    if utime.localtime()[5] < 10:
Rahix's avatar
Rahix committed
128
        timestamp = timestamp + "0"
129
130
131
132
133
    timestamp = timestamp + str(utime.localtime()[5])
    return timestamp


def toggle_rockets(state):
schneider's avatar
schneider committed
134
135
136
137
    """
    Turns all rocked LEDs on or off.
    :param state: True=on, False=off
    """
138
139
140
141
142
143
144
145
    brightness = 15
    if not state:
        brightness = 0
    leds.set_rocket(0, brightness)
    leds.set_rocket(1, brightness)
    leds.set_rocket(2, brightness)


schneider's avatar
schneider committed
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def render_nickname(title, sub, fg, bg, fg_sub, bg_sub, main_bg, mode, bat):
    """
    Main function to render the nickname on screen.
    Pretty ugly but not time for cleanup right now (and some APIs missing)
    :param title: first row of text
    :param sub: second row of text
    :param fg: tuple of (day, night) rgb for title text color
    :param bg: tuple of (day, night) rgb for title background color
    :param fg_sub: tuple of (day, night) rgb for subtitle text color
    :param bg_sub: tuple of (day, night) rgb for subtitle background color
    :param main_bg: tuple of (day, night) rgb for general background color
    :param mode: default animation to start in (index of ANIM_TYPES array)
    :param bat: battery config tuple (boolean: indicator on/off, array: good rgb, array: ok rgb, array: bad rgb)
    """
    anim = mode
schneider's avatar
schneider committed
161
    posy = 30
Rahix's avatar
Rahix committed
162
    if sub != "":
schneider's avatar
schneider committed
163
164
165
166
        posy = 18
    r = 255
    g = 0
    b = 0
schneider's avatar
schneider committed
167
    rainbow_led_pos = 0
168
169
    r_sub = sub
    last_btn_poll = utime.time() - 2
schneider's avatar
schneider committed
170
    while True:
171
        sleep = 0.5
Rahix's avatar
Rahix committed
172
        if sub == "#time":
173
            r_sub = get_time()
schneider's avatar
schneider committed
174
        dark = 0
schneider's avatar
schneider committed
175
        if light_sensor.get_reading() < 30:
schneider's avatar
schneider committed
176
177
178
179
180
181
            dark = 1
        r_fg_color = fg[dark]
        r_bg_color = bg[dark]
        r_fg_sub_color = fg_sub[dark]
        r_bg_sub_color = bg_sub[dark]
        r_bg = main_bg[dark]
182
        # Button handling
Rahix's avatar
Rahix committed
183
        pressed = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT)
184
185
186
187
188
189
        if utime.time() - last_btn_poll >= 1:
            last_btn_poll = utime.time()
            if pressed & buttons.BOTTOM_RIGHT != 0:
                anim = anim + 1
                if anim >= len(ANIM_TYPES):
                    anim = 0
schneider's avatar
schneider committed
190
                blink_led(0)
191
192
193
194
            if pressed & buttons.BOTTOM_LEFT != 0:
                anim = anim - 1
                if anim < 0:
                    anim = len(ANIM_TYPES) - 1
schneider's avatar
schneider committed
195
                blink_led(0)
196
        # Animations
Rahix's avatar
Rahix committed
197
        if ANIM_TYPES[anim] == "fade":
198
199
200
            sleep = 0.1
            leds.clear()
            toggle_rockets(False)
schneider's avatar
schneider committed
201
            if r > 0 and b == 0:
schneider's avatar
schneider committed
202
203
                r -= 1
                g += 1
schneider's avatar
schneider committed
204
            if g > 0 and r == 0:
schneider's avatar
schneider committed
205
206
                g -= 1
                b += 1
schneider's avatar
schneider committed
207
            if b > 0 and g == 0:
schneider's avatar
schneider committed
208
209
                r += 1
                b -= 1
schneider's avatar
schneider committed
210
            r_bg = [r, g, b]
211
212
            r_bg_color = r_bg
            r_bg_sub_color = r_bg
Rahix's avatar
Rahix committed
213
        if ANIM_TYPES[anim] == "led":
214
215
216
217
218
219
220
221
222
            if dark == 1:
                for i in range(0, 11):
                    leds.prep(i, r_bg)
                leds.update()
                leds.dim_top(4)
                toggle_rockets(True)
            else:
                leds.clear()
                toggle_rockets(False)
Rahix's avatar
Rahix committed
223
        if ANIM_TYPES[anim] == "rnd_led":
schneider's avatar
schneider committed
224
225
226
227
228
229
230
231
232
            if dark == 1:
                for i in range(0, 11):
                    leds.prep(i, random_rgb())
                leds.update()
                leds.dim_top(4)
                toggle_rockets(True)
            else:
                leds.clear()
                toggle_rockets(False)
Rahix's avatar
Rahix committed
233
        if ANIM_TYPES[anim] == "gay":
234
235
            toggle_rockets(False)
            leds.gay(0.4)
Rahix's avatar
Rahix committed
236
        if ANIM_TYPES[anim] == "rainbow":
schneider's avatar
schneider committed
237
238
239
240
241
242
243
244
245
            for i in range(0, 11):
                lr, lg, lb = wheel(rainbow_led_pos + i * 3)
                leds.prep(i, [lr, lg, lb])
            rainbow_led_pos += 1
            if rainbow_led_pos > 255:
                rainbow_led_pos = 0
            leds.update()
            leds.dim_top(3)
            toggle_rockets(True)
Rahix's avatar
Rahix committed
246
        if ANIM_TYPES[anim] == "none":
schneider's avatar
schneider committed
247
            leds.clear()
248
            toggle_rockets(False)
schneider's avatar
schneider committed
249
250
        with display.open() as disp:
            disp.rect(0, 0, 160, 80, col=r_bg, filled=True)
schneider's avatar
schneider committed
251
252
            if bat[0]:
                render_battery(disp, bat)
Rahix's avatar
Rahix committed
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
            disp.print(
                title,
                fg=r_fg_color,
                bg=r_bg_color,
                posx=80 - round(len(title) / 2 * 14),
                posy=posy,
            )
            if r_sub != "":
                disp.print(
                    r_sub,
                    fg=r_fg_sub_color,
                    bg=r_bg_sub_color,
                    posx=80 - round(len(r_sub) / 2 * 14),
                    posy=42,
                )
schneider's avatar
schneider committed
268
269
            disp.update()
            disp.close()
270
        utime.sleep(sleep)
schneider's avatar
schneider committed
271
272
273


def get_key(json, key, default):
schneider's avatar
schneider committed
274
275
276
277
278
279
280
    """
    Gets a defined key from a json object or returns a default if the key cant be found
    :param json: json object to search key in
    :param key: key to search for
    :param default: default to return if no key is found
    :return:
    """
schneider's avatar
schneider committed
281
282
283
284
285
    try:
        return json[key]
    except KeyError:
        return default

schneider's avatar
schneider committed
286

schneider's avatar
schneider committed
287
288
289
290
291
leds.clear()
with display.open() as disp:
    disp.clear().update()
    disp.close()
if FILENAME_ADV in os.listdir("."):
Rahix's avatar
Rahix committed
292
    f = open(FILENAME_ADV, "r")
schneider's avatar
schneider committed
293
294
295
296
    try:
        c = ujson.loads(f.read())
        f.close()
        # parse config
Rahix's avatar
Rahix committed
297
298
299
        nick = get_key(c, "nickname", "no nick")
        sub = get_key(c, "subtitle", "")
        mode = get_key(c, "mode", 0)
schneider's avatar
schneider committed
300
        # battery
Rahix's avatar
Rahix committed
301
302
303
304
        battery_show = get_key(c, "battery", True)
        battery_c_good = get_key(c, "battery_color_good", [0, 230, 00])
        battery_c_ok = get_key(c, "battery_color_ok", [255, 215, 0])
        battery_c_bad = get_key(c, "battery_color_bad", [255, 0, 0])
schneider's avatar
schneider committed
305
        # daytime values
Rahix's avatar
Rahix committed
306
307
308
309
310
        background = get_key(c, "background", [0, 0, 0])
        fg_color = get_key(c, "fg_color", [255, 255, 255])
        bg_color = get_key(c, "bg_color", background)
        fg_sub_color = get_key(c, "fg_sub_color", [255, 255, 255])
        bg_sub_color = get_key(c, "bg_sub_color", background)
schneider's avatar
schneider committed
311
        # nighttime values
Rahix's avatar
Rahix committed
312
313
314
315
316
        background_night = get_key(c, "background_night", [0, 0, 0])
        fg_color_night = get_key(c, "fg_color_night", [255, 255, 255])
        bg_color_night = get_key(c, "bg_color_night", background_night)
        fg_sub_color_night = get_key(c, "fg_sub_color_night", [255, 255, 255])
        bg_sub_color_night = get_key(c, "bg_sub_color_night", background_night)
schneider's avatar
schneider committed
317
        # render nickname
Rahix's avatar
Rahix committed
318
319
320
321
322
323
324
325
326
327
328
        render_nickname(
            nick,
            sub,
            (fg_color, fg_color_night),
            (bg_color, bg_color_night),
            (fg_sub_color, fg_sub_color_night),
            (bg_sub_color, bg_sub_color_night),
            (background, background_night),
            mode,
            (battery_show, battery_c_good, battery_c_ok, battery_c_bad),
        )
schneider's avatar
schneider committed
329
    except ValueError:
Rahix's avatar
Rahix committed
330
        render_error("invalid", "json")
schneider's avatar
schneider committed
331
332
else:
    if FILENAME not in os.listdir("."):
Rahix's avatar
Rahix committed
333
        render_error("file not", "found")
schneider's avatar
schneider committed
334
    else:
Rahix's avatar
Rahix committed
335
        f = open(FILENAME, "r")
schneider's avatar
schneider committed
336
337
338
        nick = f.read()
        f.close()
        if len(nick) > 11:
Rahix's avatar
Rahix committed
339
            render_error("name too", "long")
schneider's avatar
schneider committed
340
        if len(nick) < 1:
Rahix's avatar
Rahix committed
341
            render_error("nick file", "empty")
schneider's avatar
schneider committed
342
        else:
Rahix's avatar
Rahix committed
343
344
345
346
347
348
349
350
            render_nickname(
                nick,
                "",
                ([255, 255, 255], [255, 255, 255]),
                ([0, 0, 0], [0, 0, 0]),
                ([255, 255, 255], [255, 255, 255]),
                ([0, 0, 0], [0, 0, 0]),
                ([0, 0, 0], [0, 0, 0]),
351
352
                0,
                (0, [255, 0, 255], [255, 0, 255], [255, 0, 255]),
Rahix's avatar
Rahix committed
353
            )