import utime import display import leds import ledfx import buttons import light_sensor import ujson import os FILENAME = "nickname.txt" FILENAME_ADV = "nickname.json" ANIM_TYPES = ["none", "led", "fade", "gay", "rainbow", "rnd_led"] 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): rand = int.from_bytes(os.urandom(1), "little") 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() def render_error(err1, err2): """ Function to render two lines of text (each max 11 chars). Useful to display error messages :param err1: line one :param err2: line two """ 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() 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) :return: battery status tuple (float: battery voltage, false if old firmware, RGB color array otherwise) """ try: v = os.read_battery() if v > 3.8: return (v, bat[1]) if v > 3.6: return (v, bat[2]) return (v, bat[3]) except AttributeError: return (0, False) 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) """ v, c = get_bat_color(bat) if not c: return 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) disp.rect(155, 4, 157, 7, filled=True, col=c) def get_time(): """ Generates a nice timestamp in format hh:mm:ss from the devices localtime :return: timestamp """ timestamp = "" if utime.localtime()[3] < 10: timestamp = timestamp + "0" timestamp = timestamp + str(utime.localtime()[3]) + ":" if utime.localtime()[4] < 10: timestamp = timestamp + "0" timestamp = timestamp + str(utime.localtime()[4]) + ":" if utime.localtime()[5] < 10: timestamp = timestamp + "0" timestamp = timestamp + str(utime.localtime()[5]) return timestamp def toggle_rockets(state): """ Turns all rocked LEDs on or off. :param state: True=on, False=off """ brightness = 15 if not state: brightness = 0 leds.set_rocket(0, brightness) leds.set_rocket(1, brightness) leds.set_rocket(2, brightness) 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 posy = 30 if sub != "": posy = 18 r = 255 g = 0 b = 0 rainbow_led_pos = 0 r_sub = sub last_btn_poll = utime.time() - 2 while True: sleep = 0.5 if sub == "#time": r_sub = get_time() dark = 0 if light_sensor.get_reading() < 30: 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] # Button handling pressed = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT) 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 blink_led(0) if pressed & buttons.BOTTOM_LEFT != 0: anim = anim - 1 if anim < 0: anim = len(ANIM_TYPES) - 1 blink_led(0) # Animations if ANIM_TYPES[anim] == "fade": sleep = 0.1 leds.clear() toggle_rockets(False) if r > 0 and b == 0: r -= 1 g += 1 if g > 0 and r == 0: g -= 1 b += 1 if b > 0 and g == 0: r += 1 b -= 1 r_bg = [r, g, b] r_bg_color = r_bg r_bg_sub_color = r_bg if ANIM_TYPES[anim] == "led": 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) if ANIM_TYPES[anim] == "rnd_led": 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) if ANIM_TYPES[anim] == "gay": toggle_rockets(False) leds.gay(0.4) if ANIM_TYPES[anim] == "rainbow": 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) if ANIM_TYPES[anim] == "none": leds.clear() toggle_rockets(False) with display.open() as disp: disp.rect(0, 0, 160, 80, col=r_bg, filled=True) if bat[0]: render_battery(disp, bat) 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, ) disp.update() disp.close() utime.sleep(sleep) def get_key(json, key, default): """ 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: """ try: return json[key] except KeyError: return default leds.clear() with display.open() as disp: disp.clear().update() disp.close() if FILENAME_ADV in os.listdir("."): f = open(FILENAME_ADV, "r") try: c = ujson.loads(f.read()) f.close() # parse config nick = get_key(c, "nickname", "no nick") sub = get_key(c, "subtitle", "") mode = get_key(c, "mode", 0) # battery 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]) # daytime values 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) # nighttime values 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) # render nickname 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), ) except ValueError: render_error("invalid", "json") else: if FILENAME not in os.listdir("."): render_error("file not", "found") else: f = open(FILENAME, "r") nick = f.read() f.close() if len(nick) > 11: render_error("name too", "long") if len(nick) < 1: render_error("nick file", "empty") else: 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]), 0, (True, [0, 230, 0], [255, 215, 0], [255, 0, 0]), )