diff --git a/docs/badge/usage.rst b/docs/badge/usage.rst index e982b59151b0f114a970450ae85cdaf2f880a24c..c4a2de211b76f95fd17c96538d506e598daec092 100644 --- a/docs/badge/usage.rst +++ b/docs/badge/usage.rst @@ -171,6 +171,11 @@ IMU Demo Tilt to accelerate a circle with gravity. +LED Painter +^^^^^^^^^^^ + +Use the petals to set a color and the application button to move and lift the brush to paint on the LEDs! On exit the LED data is stored in flash at ``/menu_leds.json``. This file sets the default LED color pattern for the main menus and is not specific to LED Painter, other applications may read and write to it. + Scroll Demo ^^^^^^^^^^^ diff --git a/python_payload/apps/demo_harmonic/__init__.py b/python_payload/apps/demo_harmonic/__init__.py index a995239d357617366725f17709ec6316f078e6be..ec52fc126baf066fa6b7e105940a47a9a42e1ab3 100644 --- a/python_payload/apps/demo_harmonic/__init__.py +++ b/python_payload/apps/demo_harmonic/__init__.py @@ -617,6 +617,7 @@ class HarmonicApp(Application): self.blm.free = True self.blm = None self._save_settings() + super().on_exit() if __name__ == "__main__": diff --git a/python_payload/apps/led_painter/__init__.py b/python_payload/apps/led_painter/__init__.py index a51cae1bc4e9445299e59e7f1b87f37075815ed6..5be84a8569c52c5c34a34988f2862ad304bc234b 100644 --- a/python_payload/apps/led_painter/__init__.py +++ b/python_payload/apps/led_painter/__init__.py @@ -2,6 +2,8 @@ import random import time import math +import json +import errno # flow3r imports from st3m.application import Application, ApplicationContext @@ -27,9 +29,6 @@ class LEDPainter(Application): def __init__(self, app_ctx: ApplicationContext) -> None: super().__init__(app_ctx) self.input = InputController() - # self.scroll_R = CapScrollController() - # self.scroll_G = CapScrollController() - # self.scroll_B = CapScrollController() self._cursor = 0 self._draw = True self.STEPS = 30 @@ -58,65 +57,152 @@ class LEDPainter(Application): (39, 94), (70, 51), ] - # self.PETAL_POS.reverse() + self.PETAL_POS = [ + tuple([(k - 120) * 1.1 + 120 for k in g]) for g in self.PETAL_POS + ] + + def _try_load_settings(self, path): + try: + with open(path, "r") as f: + return json.load(f) + except OSError as e: + if e.errno != errno.ENOENT: + raise # ignore file not found + + def _try_save_settings(self, path, settings): + try: + with open(path, "w+") as f: + f.write(json.dumps(settings)) + f.close() + except OSError as e: + if e.errno != errno.ENOENT: + raise # ignore file not found + + def _load_settings(self): + settings_path = "/flash/menu_leds.json" + settings = self._try_load_settings(settings_path) + if settings is None: + return + self.LEDS = settings["leds"] + for i in range(40): + col = settings["leds"][i] + leds.set_rgb(i, col[0], col[1], col[2]) + + def _save_settings(self): + settings_path = "/flash/menu_leds.json" + old_settings = self._try_load_settings(settings_path) + file_is_different = False + if old_settings is None: + file_is_different = True + else: + try: + ref_LEDS = old_settings["leds"] + for l in range(40): + if file_is_different: + break + for c in range(3): + if self.LEDS[l][c] != ref_LEDS[l][c]: + file_is_different = True + except: + file_is_different = True + + if file_is_different: + settings = {} + settings["leds"] = self.LEDS + self._try_save_settings(settings_path, settings) + + def on_enter(self, vm): + super().on_enter(vm) + self._load_settings() + + def on_exit(self): + self._save_settings() + super().on_exit() def draw(self, ctx: Context) -> None: ctx.font = ctx.get_font_name(1) ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill() + ctx.rgb(1, 1, 1).rectangle(-31, -31, 62, 62).fill() ctx.rgb(self.r / 255, self.g / 255, self.b / 255).rectangle( -30, -30, 60, 60 ).fill() - if (self.r == 0) and (self.g == 0) and (self.b == 0): - ctx.font_size = 20 - ctx.move_to(-30, 0).rgb(255, 255, 255).text("BLACK") + # if (self.r == 0) and (self.g == 0) and (self.b == 0): + # ctx.move_to(0, 0).rgb(255, 255, 255).text("BLACK") + + ctx.font_size = 16 + + ctx.text_align = ctx.LEFT + ctx.move_to(39, 5 - 17) + ctx.rgb(1, 0, 0).text(str(self.r)) + ctx.move_to(39, 5) + ctx.rgb(0, 1, 0).text(str(self.g)) + ctx.move_to(39, 5 + 17) + ctx.rgb(0, 0, 1).text(str(self.b)) + ctx.rgb(0.7, 0.7, 0.7) + text_shift = -20 + ctx.text_align = ctx.RIGHT + ctx.move_to(text_shift - 5, -62).text("brush:") + ctx.move_to(text_shift - 5, -42).text("<-/->:") + + ctx.text_align = ctx.LEFT if self._draw: self.LEDS[self._cursor] = [self.r, self.g, self.b] - ctx.font_size = 14 - ctx.move_to(-80, -40).rgb(255, 255, 255).text("(Center L) Brush Down") + ctx.move_to(text_shift, -60).text("down") + ctx.move_to(text_shift, -44).text("draw") for i in range(len(self.LEDS)): leds.set_rgb(i, self.LEDS[i][0], self.LEDS[i][1], self.LEDS[i][2]) else: - ctx.font_size = 14 - ctx.move_to(-80, -40).rgb(255, 255, 255).text("(Center L) Brush Up") + ctx.move_to(text_shift, -60).text("up") + ctx.move_to(text_shift, -44).text("move") for i in range(len(self.LEDS)): leds.set_rgb(i, self.LEDS[i][0], self.LEDS[i][1], self.LEDS[i][2]) leds.set_rgb(self._cursor, 255, 255, 255) if (self.r == 255) and (self.g == 255) and (self.b == 255): leds.set_rgb(self._cursor, 0, 0, 255) - leds.update() - off_x = 130 - off_y = 110 + ctx.text_align = ctx.CENTER ctx.font_size = 20 + + leds.update() + off_x = 120 + off_y = 120 ctx.move_to(self.PETAL_POS[0][0] - off_x, self.PETAL_POS[0][1] - off_y).rgb( - 255, 255, 255 + 1, 0, 0 ).text("R+") ctx.move_to(self.PETAL_POS[1][0] - off_x, self.PETAL_POS[1][1] - off_y).rgb( - 255, 255, 255 + 1, 0, 0 ).text("R-") ctx.move_to(self.PETAL_POS[2][0] - off_x, self.PETAL_POS[2][1] - off_y).rgb( - 255, 255, 255 + 0, 1, 0 ).text("G+") ctx.move_to(self.PETAL_POS[3][0] - off_x, self.PETAL_POS[3][1] - off_y).rgb( - 255, 255, 255 + 0, 1, 0 ).text("G-") ctx.move_to(self.PETAL_POS[4][0] - off_x, self.PETAL_POS[4][1] - off_y).rgb( - 255, 255, 255 + 0, 0, 1 ).text("B+") ctx.move_to(self.PETAL_POS[5][0] - off_x, self.PETAL_POS[5][1] - off_y).rgb( - 255, 255, 255 + 0, 0, 1 ).text("B-") - ctx.move_to(self.PETAL_POS[6][0] - off_x, self.PETAL_POS[6][1] - off_y).rgb( - 255, 255, 255 - ).text("B") - ctx.move_to(self.PETAL_POS[7][0] - off_x, self.PETAL_POS[7][1] - off_y).rgb( - 255, 255, 255 - ).text("W") + + ctx.font_size = 16 + pos_x = self.PETAL_POS[6][0] - off_x + pos_y = self.PETAL_POS[6][1] - off_y + ctx.move_to(pos_x, pos_y).rgb(255, 255, 255) + ctx.rectangle(pos_x - 19, pos_y - 18, 38, 26).stroke() + ctx.text("BLK") + + pos_x = self.PETAL_POS[7][0] - off_x + pos_y = self.PETAL_POS[7][1] - off_y + ctx.move_to(pos_x, pos_y).rgb(255, 255, 255) + ctx.rectangle(pos_x - 19, pos_y - 18, 38, 26).fill() + ctx.rgb(0, 0, 0) + ctx.text("WHT") def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) diff --git a/python_payload/st3m/application.py b/python_payload/st3m/application.py index 33217cb18ca1f4f6770f93b59f175bc023e0a6dc..3a5354fb610710152a5dbc6ff275623a841c6b9c 100644 --- a/python_payload/st3m/application.py +++ b/python_payload/st3m/application.py @@ -11,6 +11,8 @@ from st3m.goose import Optional, List, Dict from st3m.logging import Log from st3m import settings from ctx import Context +from st3m import led_patterns +import leds import toml import os @@ -65,6 +67,8 @@ class Application(BaseView): st3m.wifi.setup_wifi() elif self._wifi_preference is False: st3m.wifi.disable() + leds.set_auto_update(0) + leds.set_slew_rate(255) super().on_enter(vm) def on_exit(self) -> None: @@ -74,10 +78,14 @@ class Application(BaseView): if fully_exiting and self._wifi_preference is not None: st3m.wifi._onoff_wifi_update() super().on_exit() - # set the default graphics mode, this is a no-op if - # it is already set if fully_exiting: + # set the default graphics mode, this is a no-op if + # it is already set sys_display.set_mode(0) + # read menu led config from flash and set it + led_patterns.set_menu_colors() + leds.set_slew_rate(10) + leds.set_auto_update(1) def think(self, ins: InputState, delta_ms: int) -> None: super().think(ins, delta_ms) diff --git a/python_payload/st3m/led_patterns.py b/python_payload/st3m/led_patterns.py new file mode 100644 index 0000000000000000000000000000000000000000..72adec34735b24ed8711c83e965bafc3c4db9150 --- /dev/null +++ b/python_payload/st3m/led_patterns.py @@ -0,0 +1,32 @@ +import leds +import json + + +def set_menu_colors(): + """ + set all LEDs to the configured menu colors if provided in + /flash/menu_leds.json. leds.update() must be called externally. + """ + path = "/flash/menu_leds.json" + try: + with open(path, "r") as f: + settings = json.load(f) + except OSError: + leds.set_all_rgb(0, 0, 0) + return + for i in range(40): + col = settings["leds"][i] + leds.set_rgb(i, col[0], col[1], col[2]) + + +def highlight_petal(num, r, g, b, num_leds=5): + """ + Sets the LED closest to the petal and num_leds-1 around it to + the color. If num_leds is uneven the appearance will be symmetric. + leds.update() must be called externally. + """ + num = num % 10 + if num_leds < 0: + num_leds = 0 + for i in range(num_leds): + leds.set_rgb((num * 4 + i - num_leds // 2) % 40, r, g, b) diff --git a/python_payload/st3m/run.py b/python_payload/st3m/run.py index d74ea7e0e005d1ea87864376515955ab913da896..e2b1fd61830a6648d7295096ede9ee2aa4c50913 100644 --- a/python_payload/st3m/run.py +++ b/python_payload/st3m/run.py @@ -19,6 +19,7 @@ from st3m.application import ( ) from st3m.about import About from st3m import settings_menu as settings, logging, processors, wifi +from st3m import led_patterns import captouch, audio, leds, gc, sys_buttons, sys_display, sys_mode import os @@ -158,6 +159,12 @@ def run_main() -> None: bundles = BundleManager() bundles.update() + leds.set_rgb(0, 0, 0, 0) + leds.update() + leds.set_slew_rate(2) + leds.set_auto_update(1) + led_patterns.set_menu_colors() + try: network.hostname( settings.str_hostname.value if settings.str_hostname.value else "flow3r" diff --git a/sim/fakes/leds.py b/sim/fakes/leds.py index 7dd26ef90dd948b2f1cb731dc27b7c72ef83814a..6df4deb57d7c42ccf4769186be3c927a29a55a46 100644 --- a/sim/fakes/leds.py +++ b/sim/fakes/leds.py @@ -43,3 +43,7 @@ def set_slew_rate(b: int): def update(): _sim.leds_update() _sim.render_gui_lazy() + + +def set_auto_update(b: int): + pass