From 45ebcf6037ee1d0bfdbcc34d6bb9a60bf91493d0 Mon Sep 17 00:00:00 2001
From: moon2 <moon2protonmail@protonmail.com>
Date: Thu, 21 Sep 2023 01:42:54 +0000
Subject: [PATCH] leds: faster refresh rate, better slew rate control, alpha
 setters

---
 components/micropython/usermodule/mp_leds.c | 40 +++++++++++
 components/st3m/st3m_leds.c                 | 74 ++++++++++++++++-----
 components/st3m/st3m_leds.h                 | 10 ++-
 python_payload/apps/appearance/__init__.py  | 16 ++---
 python_payload/apps/led_painter/__init__.py |  1 +
 python_payload/mypystubs/leds.pyi           | 48 +++++++------
 python_payload/st3m/application.py          | 11 +--
 python_payload/st3m/run.py                  |  9 +--
 python_payload/st3m/settings.py             |  2 +-
 9 files changed, 142 insertions(+), 69 deletions(-)

diff --git a/components/micropython/usermodule/mp_leds.c b/components/micropython/usermodule/mp_leds.c
index 68880e7f56..f98e8cfb34 100644
--- a/components/micropython/usermodule/mp_leds.c
+++ b/components/micropython/usermodule/mp_leds.c
@@ -70,6 +70,19 @@ STATIC mp_obj_t mp_led_set_rgb(size_t n_args, const mp_obj_t *args) {
 STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_led_set_rgb_obj, 4, 4,
                                            mp_led_set_rgb);
 
+STATIC mp_obj_t mp_led_set_rgba(size_t n_args, const mp_obj_t *args) {
+    uint8_t index = mp_obj_get_int(args[0]);
+    float red = mp_obj_get_float(args[1]);
+    float green = mp_obj_get_float(args[2]);
+    float blue = mp_obj_get_float(args[3]);
+    float alpha = mp_obj_get_float(args[4]);
+
+    st3m_leds_set_single_rgba(index, red, green, blue, alpha);
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_led_set_rgba_obj, 5, 5,
+                                           mp_led_set_rgba);
+
 STATIC mp_obj_t mp_led_set_hsv(size_t n_args, const mp_obj_t *args) {
     uint8_t index = mp_obj_get_int(args[0]);
     float hue = mp_obj_get_float(args[1]);
@@ -90,6 +103,17 @@ STATIC mp_obj_t mp_led_set_all_rgb(mp_obj_t r, mp_obj_t g, mp_obj_t b) {
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_led_set_all_rgb_obj, mp_led_set_all_rgb);
 
+STATIC mp_obj_t mp_led_set_all_rgba(size_t n_args, const mp_obj_t *args) {
+    float red = mp_obj_get_float(args[0]);
+    float green = mp_obj_get_float(args[1]);
+    float blue = mp_obj_get_float(args[2]);
+    float alpha = mp_obj_get_float(args[3]);
+    st3m_leds_set_all_rgba(red, green, blue, alpha);
+    return mp_const_none;
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_led_set_all_rgba_obj, 4, 4,
+                                           mp_led_set_all_rgba);
+
 STATIC mp_obj_t mp_led_set_all_hsv(mp_obj_t h, mp_obj_t s, mp_obj_t v) {
     float hue = mp_obj_get_float(h);
     float sat = mp_obj_get_float(s);
@@ -99,6 +123,19 @@ STATIC mp_obj_t mp_led_set_all_hsv(mp_obj_t h, mp_obj_t s, mp_obj_t v) {
 }
 STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_led_set_all_hsv_obj, mp_led_set_all_hsv);
 
+STATIC mp_obj_t mp_led_get_rgb(mp_obj_t led_index) {
+    uint8_t index = mp_obj_get_int(led_index);
+    float red;
+    float green;
+    float blue;
+
+    st3m_leds_get_single_rgb(index, &red, &green, &blue);
+    mp_obj_t items[] = { mp_obj_new_float(red), mp_obj_new_float(green),
+                         mp_obj_new_float(blue) };
+    return mp_obj_new_tuple(3, items);
+}
+STATIC MP_DEFINE_CONST_FUN_OBJ_1(mp_led_get_rgb_obj, mp_led_get_rgb);
+
 STATIC mp_obj_t mp_leds_update() {
     st3m_leds_update();
     return mp_const_none;
@@ -108,9 +145,12 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_leds_update_obj, mp_leds_update);
 STATIC const mp_rom_map_elem_t mp_module_leds_globals_table[] = {
     { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_leds) },
     { MP_ROM_QSTR(MP_QSTR_set_rgb), MP_ROM_PTR(&mp_led_set_rgb_obj) },
+    { MP_ROM_QSTR(MP_QSTR_set_rgba), MP_ROM_PTR(&mp_led_set_rgba_obj) },
     { MP_ROM_QSTR(MP_QSTR_set_hsv), MP_ROM_PTR(&mp_led_set_hsv_obj) },
     { MP_ROM_QSTR(MP_QSTR_set_all_rgb), MP_ROM_PTR(&mp_led_set_all_rgb_obj) },
+    { MP_ROM_QSTR(MP_QSTR_set_all_rgba), MP_ROM_PTR(&mp_led_set_all_rgba_obj) },
     { MP_ROM_QSTR(MP_QSTR_set_all_hsv), MP_ROM_PTR(&mp_led_set_all_hsv_obj) },
+    { MP_ROM_QSTR(MP_QSTR_get_rgb), MP_ROM_PTR(&mp_led_get_rgb_obj) },
     { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&mp_leds_update_obj) },
     { MP_ROM_QSTR(MP_QSTR_get_brightness),
       MP_ROM_PTR(&mp_leds_get_brightness_obj) },
diff --git a/components/st3m/st3m_leds.c b/components/st3m/st3m_leds.c
index a9b314549f..7bc911d4a5 100644
--- a/components/st3m/st3m_leds.c
+++ b/components/st3m/st3m_leds.c
@@ -20,6 +20,12 @@ typedef struct {
     uint8_t lut[256];
 } st3m_leds_gamma_table_t;
 
+typedef struct {
+    uint16_t r;
+    uint16_t g;
+    uint16_t b;
+} st3m_leds_rgb_t;
+
 typedef struct {
     uint8_t brightness;
     uint8_t slew_rate;
@@ -31,7 +37,7 @@ typedef struct {
 
     st3m_rgb_t target[40];
     st3m_rgb_t target_buffer[40];
-    st3m_rgb_t hardware_value[40];
+    st3m_leds_rgb_t slew_output[40];
 } st3m_leds_state_t;
 
 static st3m_leds_state_t state;
@@ -49,7 +55,11 @@ static void set_single_led(uint8_t index, st3m_rgb_t c) {
     flow3r_bsp_leds_set_pixel(index, c.r, c.g, c.b);
 }
 
-uint8_t led_get_slew(int16_t old, int16_t new, int16_t slew) {
+static uint16_t led_get_slew(uint16_t old, uint16_t new, uint16_t slew) {
+    new = new << 8;
+    if (slew == 255) return new;
+    slew = 30 + (slew << 2) + ((slew * slew) >> 3);
+
     if (new > old + slew) {
         return old + slew;
     } else if (new > old) {
@@ -81,21 +91,22 @@ void st3m_leds_update_hardware() {
     if (state.auto_update) leds_update_target();
 
     for (int i = 0; i < 40; i++) {
-        st3m_rgb_t c = state.target[i];
-        c.r = c.r * state.brightness / 255;
-        c.g = c.g * state.brightness / 255;
-        c.b = c.b * state.brightness / 255;
-
-        c.r = state.gamma_red.lut[c.r];
-        c.g = state.gamma_red.lut[c.g];
-        c.b = state.gamma_red.lut[c.b];
-
-        c.r = led_get_slew(state.hardware_value[i].r, c.r, state.slew_rate);
-        c.g = led_get_slew(state.hardware_value[i].g, c.g, state.slew_rate);
-        c.b = led_get_slew(state.hardware_value[i].b, c.b, state.slew_rate);
-        state.hardware_value[i] = c;
-
-        set_single_led(i, c);
+        st3m_rgb_t ret = state.target[i];
+        st3m_leds_rgb_t c;
+        c.r = led_get_slew(state.slew_output[i].r, ret.r, state.slew_rate);
+        c.g = led_get_slew(state.slew_output[i].g, ret.g, state.slew_rate);
+        c.b = led_get_slew(state.slew_output[i].b, ret.b, state.slew_rate);
+        state.slew_output[i] = c;
+
+        c.r = ((uint32_t)c.r * state.brightness) >> 8;
+        c.g = ((uint32_t)c.g * state.brightness) >> 8;
+        c.b = ((uint32_t)c.b * state.brightness) >> 8;
+
+        ret.r = state.gamma_red.lut[c.r >> 8];
+        ret.g = state.gamma_red.lut[c.g >> 8];
+        ret.b = state.gamma_red.lut[c.b >> 8];
+
+        set_single_led(i, ret);
     }
     UNLOCK;
 
@@ -105,6 +116,18 @@ void st3m_leds_update_hardware() {
     }
 }
 
+void st3m_leds_set_single_rgba(uint8_t index, float red, float green,
+                               float blue, float alpha) {
+    float r, g, b;
+    st3m_leds_get_single_rgb(index, &r, &g, &b);
+
+    float nalpha = 1 - alpha;
+    r = alpha * red + nalpha * r;
+    g = alpha * green + nalpha * g;
+    b = alpha * blue + nalpha * blue;
+    st3m_leds_set_single_rgb(index, r, g, b);
+}
+
 void st3m_leds_set_single_rgb(uint8_t index, float red, float green,
                               float blue) {
     if (red > 1.0) red /= 255.0;
@@ -118,6 +141,15 @@ void st3m_leds_set_single_rgb(uint8_t index, float red, float green,
     UNLOCK_INCOMING;
 }
 
+void st3m_leds_get_single_rgb(uint8_t index, float *red, float *green,
+                              float *blue) {
+    LOCK_INCOMING;
+    *red = ((float)state.target_buffer[index].r) / 255;
+    *green = ((float)state.target_buffer[index].g) / 255;
+    *blue = ((float)state.target_buffer[index].b) / 255;
+    UNLOCK_INCOMING;
+}
+
 void st3m_leds_set_single_hsv(uint8_t index, float hue, float sat, float val) {
     st3m_hsv_t hsv = {
         .h = hue,
@@ -135,6 +167,12 @@ void st3m_leds_set_all_rgb(float red, float green, float blue) {
     }
 }
 
+void st3m_leds_set_all_rgba(float red, float green, float blue, float alpha) {
+    for (int i = 0; i < 40; i++) {
+        st3m_leds_set_single_rgba(i, red, green, blue, alpha);
+    }
+}
+
 void st3m_leds_set_all_hsv(float h, float s, float v) {
     for (int i = 0; i < 40; i++) {
         st3m_leds_set_single_hsv(i, h, s, v);
@@ -148,7 +186,7 @@ static void _leds_task(void *_data) {
 
     TickType_t last_wake = xTaskGetTickCount();
     while (true) {
-        vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(100));  // 10 Hz
+        vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(20));  // 50 Hz
         st3m_leds_update_hardware();
     }
 }
diff --git a/components/st3m/st3m_leds.h b/components/st3m/st3m_leds.h
index 10b817f5a4..ba05cbd0a1 100644
--- a/components/st3m/st3m_leds.h
+++ b/components/st3m/st3m_leds.h
@@ -25,9 +25,14 @@ void st3m_leds_init();
 // autoupdates.
 void st3m_leds_set_single_rgb(uint8_t index, float red, float green,
                               float blue);
+void st3m_leds_set_single_rgba(uint8_t index, float red, float green,
+                               float blue, float alpha);
 void st3m_leds_set_single_hsv(uint8_t index, float hue, float sat, float value);
 void st3m_leds_set_all_rgb(float red, float green, float blue);
+void st3m_leds_set_all_rgba(float red, float green, float blue, float alpha);
 void st3m_leds_set_all_hsv(float hue, float sat, float value);
+void st3m_leds_get_single_rgb(uint8_t index, float* red, float* green,
+                              float* blue);
 
 // Set/get global LED brightness, 0-255. Default 69.
 //
@@ -36,10 +41,13 @@ void st3m_leds_set_brightness(uint8_t brightness);
 uint8_t st3m_leds_get_brightness();
 
 // Set/get maximum change rate of brightness. Set to 1-3 for fade effects, set
-// to 255 to disable. Currently clocks at 10Hz.
+// to 255 to disable. Currently clocks at 50Hz.
 void st3m_leds_set_slew_rate(uint8_t slew_rate);
 uint8_t st3m_leds_get_slew_rate();
 
+void st3m_leds_set_max_slew_rate(uint8_t slew_rate);
+uint8_t st3m_leds_get_max_slew_rate();
+
 // Update LEDs. Ie., copy the LED state from the first buffer into the second
 // buffer, effectively scheduling the LED state to be presented to the user.
 void st3m_leds_update();
diff --git a/python_payload/apps/appearance/__init__.py b/python_payload/apps/appearance/__init__.py
index fd7b0a5318..a3b1105f61 100644
--- a/python_payload/apps/appearance/__init__.py
+++ b/python_payload/apps/appearance/__init__.py
@@ -180,21 +180,13 @@ class App(Application):
             leds.set_brightness(settings.num_leds_brightness.value)
 
         tmp = self.draw_number("led speed", 7, int(settings.num_leds_speed.value))
-        if tmp > settings.num_leds_speed.value:
-            tmp = settings.num_leds_speed.value * 2
-        elif tmp < settings.num_leds_speed.value:
-            tmp = (settings.num_leds_speed.value + 1) // 2
-        if tmp < 1:
-            tmp = 1
+        if tmp < 0:
+            tmp = 0
         elif tmp > 255:
             tmp = 255
         if tmp != settings.num_leds_speed.value:
             settings.num_leds_speed.set_value(tmp)
             leds.set_slew_rate(settings.num_leds_speed.value)
-            if 255 == settings.num_leds_speed.value:
-                leds.set_auto_update(0)
-            else:
-                leds.set_auto_update(1)
 
         tmp = self.draw_number(
             "display brightness",
@@ -230,8 +222,8 @@ class App(Application):
         if self.input.buttons.app.middle.pressed:
             self.select_pressed = True
 
-        while self.led_accumulator_ms > 500:
-            self.led_accumulator_ms = self.led_accumulator_ms % 500
+        while self.led_accumulator_ms > 2000:
+            self.led_accumulator_ms = self.led_accumulator_ms % 2000
             self.leds_toggle()
 
     def leds_toggle(self):
diff --git a/python_payload/apps/led_painter/__init__.py b/python_payload/apps/led_painter/__init__.py
index 5be84a8569..cc93e92f88 100644
--- a/python_payload/apps/led_painter/__init__.py
+++ b/python_payload/apps/led_painter/__init__.py
@@ -114,6 +114,7 @@ class LEDPainter(Application):
     def on_enter(self, vm):
         super().on_enter(vm)
         self._load_settings()
+        leds.set_slew_rate(255)
 
     def on_exit(self):
         self._save_settings()
diff --git a/python_payload/mypystubs/leds.pyi b/python_payload/mypystubs/leds.pyi
index c6febb771e..08bb35e505 100644
--- a/python_payload/mypystubs/leds.pyi
+++ b/python_payload/mypystubs/leds.pyi
@@ -1,3 +1,5 @@
+from typing import Tuple
+
 """
 Leds API.
 
@@ -8,22 +10,19 @@ There are 8 LEDs per top petal, or 4 LEDs per petal.
 After you're ready setting up your blink, call update(), or enable autoupdates.
 """
 
-def set_rgb(ix: int, r: float, g: float, b: float) -> None:
-    """Set LED `ix` to rgb value r, g, b
+def set_rgb(i: int, r: float, g: float, b: float) -> None:
+    """Set LED i to rgb value r, g, b
 
-    :param ix: LED index, from 0 to 39
+    :param i: LED index, from 0 to 39
     :param r: Red value, from 0.0 to 1.0
     :param g: Green value, from 0.0 to 1.0
     :param b: Blue value, from 0.0 to 1.0
     """
 
-def set_hsv(ix: int, hue: float, sat: float, val: float) -> None:
-    """Set LED `ix` to hsv value hue, sat, val
+def get_rgb(i: int) -> Tuple[float, float, float]:
+    """Get rgb tuple of LED i
 
-    :param ix: LED index, from 0 to 39
-    :param hue: Hue, from 0 to 360
-    :param sat: Saturation, from 0.0 to 1.0
-    :param val: Value, from 0.0 to 1.0
+    :param i: LED index, from 0 to 39
     """
 
 def set_all_rgb(r: float, g: float, b: float) -> None:
@@ -34,12 +33,23 @@ def set_all_rgb(r: float, g: float, b: float) -> None:
     :param b: Blue value, from 0.0 to 1.0
     """
 
-def set_all_hsv(h: float, s: float, v: float) -> None:
-    """Set all LEDs to hsv value hue, sat, val
+def set_rgba(ix: int, r: float, g: float, b: float, a: float) -> None:
+    """Set LED i to rgb alpha value r, g, b, a
+
+    :param ix: LED index, from 0 to 39
+    :param r: Red value, from 0.0 to 1.0
+    :param g: Green value, from 0.0 to 1.0
+    :param b: Blue value, from 0.0 to 1.0
+    :param a: Alpha value, from 0.0 to 1.0
+    """
+
+def set_all_rgba(r: float, g: float, b: float, a: float) -> None:
+    """Set all LEDs to rgb alpha value r, g, b, a
 
-    :param hue: Hue, from 0 to 360
-    :param sat: Saturation, from 0.0 to 1.0
-    :param val: Value, from 0.0 to 1.0
+    :param r: Red value, from 0.0 to 1.0
+    :param g: Green value, from 0.0 to 1.0
+    :param b: Blue value, from 0.0 to 1.0
+    :param a: Alpha value, from 0.0 to 1.0
     """
 
 def update() -> None:
@@ -72,12 +82,6 @@ def set_auto_update(on: bool) -> None:
     low slew rates.
     """
 
-def set_gamma(r: float, g: float, b: float) -> None:
-    """
-    Bend the rgb curves with an exponent each. (1,1,1) is default, (2,2,2) works
-    well too If someone wants to do color calibration, this is ur friend
-    """
-
 def get_slew_rate() -> int:
     """
     Get maximum change rate of brightness. See set_slew_rate()
@@ -85,6 +89,6 @@ def get_slew_rate() -> int:
 
 def set_slew_rate(b: int) -> None:
     """
-    Set maximum change rate of brightness. Set to 1-3 for fade effects, set
-    to 255 to disable. Currently clocks at 10Hz.
+    Set maximum change rate of channel brightness. Set to 255 to disable.
+    Animations render to the LEDs at 50Hz.
     """
diff --git a/python_payload/st3m/application.py b/python_payload/st3m/application.py
index b49557aad2..ac401056bf 100644
--- a/python_payload/st3m/application.py
+++ b/python_payload/st3m/application.py
@@ -68,10 +68,6 @@ class Application(BaseView):
         elif self._wifi_preference is False:
             st3m.wifi.disable()
         leds.set_slew_rate(settings.num_leds_speed.value)
-        if 255 == settings.num_leds_speed.value:
-            leds.set_auto_update(0)
-        else:
-            leds.set_auto_update(1)
         super().on_enter(vm)
 
     def on_exit(self) -> None:
@@ -86,16 +82,15 @@ class Application(BaseView):
         if fully_exiting:
             sys_display.set_mode(0)
         if fully_exiting:
-            leds.set_slew_rate(10)
-            leds.set_auto_update(1)
+            leds.set_slew_rate(90)
             led_patterns.set_menu_colors()
 
     def on_exit_done(self):
         fully_exiting = self.vm.direction == ViewTransitionDirection.BACKWARD
         if fully_exiting:
-            leds.set_slew_rate(10)
-            leds.set_auto_update(1)
+            leds.set_slew_rate(40)
             led_patterns.set_menu_colors()
+            leds.update()
 
     def think(self, ins: InputState, delta_ms: int) -> None:
         super().think(ins, delta_ms)
diff --git a/python_payload/st3m/run.py b/python_payload/st3m/run.py
index f609dead4f..8be8c1c9ef 100644
--- a/python_payload/st3m/run.py
+++ b/python_payload/st3m/run.py
@@ -155,11 +155,6 @@ def run_main() -> None:
     audio.headphones_set_maximum_volume_dB(settings.num_headphones_max_db.value)
     audio.speaker_set_maximum_volume_dB(settings.num_speaker_max_db.value)
     leds.set_brightness(settings.num_leds_brightness.value)
-    leds.set_slew_rate(settings.num_leds_speed.value)
-    if 255 == settings.num_leds_speed.value:
-        leds.set_auto_update(0)
-    else:
-        leds.set_auto_update(1)
     sys_display.set_backlight(settings.num_display_brightness.value)
 
     leds.set_rgb(0, 255, 0, 0)
@@ -169,9 +164,9 @@ def run_main() -> None:
 
     leds.set_rgb(0, 0, 0, 0)
     leds.update()
-    leds.set_slew_rate(2)
-    leds.set_auto_update(1)
     led_patterns.set_menu_colors()
+    leds.set_slew_rate(20)
+    leds.update()
 
     try:
         network.hostname(
diff --git a/python_payload/st3m/settings.py b/python_payload/st3m/settings.py
index 48f62a22bc..92ebedf159 100644
--- a/python_payload/st3m/settings.py
+++ b/python_payload/st3m/settings.py
@@ -243,7 +243,7 @@ num_speaker_max_db = StringTunable(
 num_display_brightness = StringTunable(
     "Display Brightness", "system.brightness.display", 100
 )
-num_leds_brightness = StringTunable("LED Brightness", "system.brightness.leds", 200)
+num_leds_brightness = StringTunable("LED Brightness", "system.brightness.leds", 69)
 
 num_leds_speed = StringTunable("LED speed", "system.brightness.leds_speed", 255)
 
-- 
GitLab