diff --git a/applications/settings/notification_settings/rgb_backlight.c b/applications/settings/notification_settings/rgb_backlight.c index 20f2b98a9f0..c3c0a8145bb 100644 --- a/applications/settings/notification_settings/rgb_backlight.c +++ b/applications/settings/notification_settings/rgb_backlight.c @@ -29,18 +29,65 @@ #define RGB_BACKLIGHT_DEFAULT_RGB \ { 255, 79, 0 } /* Orange */ +// Pin mapping for backlight to virtual LED (TimedSignal & TimedRainbow modes) +#define RGB_BACKLIGHT_RAINBOW_S0 4 +#define RGB_BACKLIGHT_RAINBOW_R0 2 +#define RGB_BACKLIGHT_RAINBOW_R1 4 +#define RGB_BACKLIGHT_RAINBOW_R2 6 + +// Comment the next line when using real hardware: +// #define USE_DEBUG_LED_STRIP 1 + static RGBBacklightSettings rgb_settings = { .version = RGB_BACKLIGHT_SETTINGS_VERSION, .backlight_colors = {RGB_BACKLIGHT_DEFAULT_RGB, RGB_BACKLIGHT_DEFAULT_RGB, RGB_BACKLIGHT_DEFAULT_RGB}, .backlight_mode = BacklightModeConstant, - .internal_pattern_index = 1, // Orange - .internal_brightness = 0, // By default internal lights are off + .internal_pattern_index = 2, // Rainbow + .internal_brightness = 0.05f, .internal_mode = InternalModeMatch, .settings_loaded = false, .internal_color = {0, 0, 0}, + .rainbow_width = 270, // degrees Hue + .rainbow_update_time = 250, // ms update delay + .rainbow_spin_increment = 10, // degrees Hue increment +}; + +typedef struct { + FuriTimer* timer; + uint8_t last_display_brightness; + int16_t offset; +} RGBBacklightState; + +static RGBBacklightState rgb_state = { + .last_display_brightness = 123, + .offset = 80, + .timer = NULL, }; +void rgb_backlight_timer_callback(void* context) { + UNUSED(context); + if(!rgb_settings.settings_loaded) { + return; + } + + if(rgb_settings.backlight_mode == BacklightModeConstant && + rgb_settings.internal_pattern_index != 1 && rgb_settings.internal_pattern_index != 2) { + return; + } + + if(rgb_settings.rainbow_update_time) { + rgb_state.offset += rgb_settings.rainbow_spin_increment; + if(rgb_state.offset >= 360) { + rgb_state.offset -= 360; + } else if(rgb_state.offset < 0) { + rgb_state.offset += 360; + } + } + + rgb_backlight_update(rgb_state.last_display_brightness); +} + void rgb_backlight_load_settings(void) { FuriHalRtcBootMode bm = furi_hal_rtc_get_boot_mode(); if(bm == FuriHalRtcBootModeDfu) { @@ -88,6 +135,14 @@ void rgb_backlight_load_settings(void) { storage_file_free(file); furi_record_close(RECORD_STORAGE); rgb_settings.settings_loaded = true; + + if(bm == FuriHalRtcBootModeNormal && rgb_settings.rainbow_update_time) { + if(rgb_state.timer == NULL) { + rgb_state.timer = + furi_timer_alloc(rgb_backlight_timer_callback, FuriTimerTypePeriodic, NULL); + furi_timer_start(rgb_state.timer, rgb_settings.rainbow_update_time); + } + } }; void rgb_backlight_save_settings(void) { @@ -188,6 +243,47 @@ uint8_t rgb_backlight_find_index(uint8_t led_number) { return nearest_index; } +BacklightMode rgb_backlight_get_mode(void) { + return rgb_settings.backlight_mode; +} + +void rgb_backlight_set_mode(BacklightMode mode) { + rgb_settings.backlight_mode = mode; +} + +void rgb_rainbow_set_width(uint16_t width) { + rgb_settings.rainbow_width = width; +} + +uint16_t rgb_rainbow_get_width(void) { + return rgb_settings.rainbow_width; +} + +int8_t rgb_rainbow_get_spin(void) { + return rgb_settings.rainbow_spin_increment; +} + +void rgb_rainbow_set_spin(int8_t width) { + rgb_settings.rainbow_spin_increment = width; +} + +uint16_t rgb_rainbow_get_update_time(void) { + return rgb_settings.rainbow_update_time; +} + +void rgb_rainbow_set_update_time(uint16_t width) { + rgb_settings.rainbow_update_time = width; + + FuriHalRtcBootMode bm = furi_hal_rtc_get_boot_mode(); + if(bm == FuriHalRtcBootModeNormal && rgb_settings.rainbow_update_time) { + if(rgb_state.timer == NULL) { + rgb_state.timer = + furi_timer_alloc(rgb_backlight_timer_callback, FuriTimerTypePeriodic, NULL); + } + furi_timer_restart(rgb_state.timer, rgb_settings.rainbow_update_time); + } +} + uint8_t rgb_internal_pattern_count(void) { return COUNT_OF(internal_pattern); } @@ -329,20 +425,42 @@ static uint8_t mapped_internal_led(uint8_t led_number) { return mapped_index; } +// hue=0..359. n=5 (red), n=3 (green), n=1 (blue) +static uint8_t rainbow(uint16_t hue, uint8_t n) { + hue = hue % 360; + float v1 = hue / 60.0f; + v1 += n; + while(v1 >= 6.0f) { + v1 -= 6.0f; + } + if((4 - v1) < v1) { + v1 = 4 - v1; + } + if(v1 > 1) { + v1 = 1; + } + if(v1 < 0) { + v1 = 0; + } + + float s = 0.9; + float b = 255.0; + return b * (1 - s * v1); +} + void rgb_backlight_update(uint8_t brightness) { if(!rgb_settings.settings_loaded) { rgb_backlight_load_settings(); } static uint32_t last_display_color[LED_BACKLIGHT_COUNT][3] = {0}; - static uint8_t last_display_brightness = 123; static uint8_t last_internal_pattern_index = 255; static uint32_t last_internal_color[3] = {0}; static float last_internal_brightness = 1.1f; uint8_t led_count = 0; - if(last_display_brightness == brightness && + if(rgb_state.last_display_brightness == brightness && last_internal_pattern_index == rgb_settings.internal_pattern_index && last_internal_color[0] == rgb_settings.internal_color[0] && last_internal_color[1] == rgb_settings.internal_color[1] && @@ -358,12 +476,12 @@ void rgb_backlight_update(uint8_t brightness) { } } - if(same_color) { + if(same_color && rgb_settings.rainbow_width == 0) { return; } } - last_display_brightness = brightness; + rgb_state.last_display_brightness = brightness; for(int i = 0; i < LED_BACKLIGHT_COUNT; i++) { for(int rgb = 0; rgb < 3; rgb++) { last_display_color[i][rgb] = rgb_settings.backlight_colors[i][rgb]; @@ -376,6 +494,23 @@ void rgb_backlight_update(uint8_t brightness) { for(uint8_t i = 0; i < SK6805_get_led_backlight_count(); i++) { uint8_t red, green, blue; rgb_backlight_led_get_color(i, &red, &green, &blue); + + if(rgb_settings.backlight_mode != BacklightModeConstant) { + uint8_t mapped_index = 0; + if(rgb_settings.backlight_mode == BacklightModeTimedSingle) { + mapped_index = RGB_BACKLIGHT_RAINBOW_S0; + } else if(rgb_settings.backlight_mode == BacklightModeTimedRainbow) { + mapped_index = (i == 0) ? RGB_BACKLIGHT_RAINBOW_R0 : + (i == 1) ? RGB_BACKLIGHT_RAINBOW_R1 : + RGB_BACKLIGHT_RAINBOW_R2; + } + + uint8_t mult = rgb_settings.rainbow_width ? rgb_settings.rainbow_width / 8 : 0; + red = rainbow(rgb_state.offset + (mapped_index * mult), 5); + green = rainbow(rgb_state.offset + (mapped_index * mult), 3); + blue = rainbow(rgb_state.offset + (mapped_index * mult), 1); + } + uint8_t r = red * (brightness / 255.0f); uint8_t g = green * (brightness / 255.0f); uint8_t b = blue * (brightness / 255.0f); @@ -388,7 +523,7 @@ void rgb_backlight_update(uint8_t brightness) { rgb_backlight_connected() ? SK6805_get_led_backlight_count() : 0; float internal_brightness = rgb_settings.internal_brightness; - if(rgb_settings.internal_mode == InternalModeOn && + if(rgb_settings.rainbow_width == 0 && rgb_settings.internal_mode == InternalModeOn && (last_internal_pattern_index == rgb_settings.internal_pattern_index) && last_internal_color[0] == rgb_settings.internal_color[0] && last_internal_color[1] == rgb_settings.internal_color[1] && @@ -401,10 +536,35 @@ void rgb_backlight_update(uint8_t brightness) { } uint8_t color_index = rgb_settings.internal_pattern_index; + uint8_t mult = rgb_settings.rainbow_width / 8; + for(uint8_t i = 0; i < SK6805_get_led_internal_count(); i++) { uint8_t red, green, blue; uint8_t mapped_index = mapped_internal_led(i); rgb_internal_color(mapped_index, color_index, &red, &green, &blue); + +#ifdef USE_DEBUG_LED_STRIP + mapped_index = i; +#endif + + if(color_index == 1) { + red = rainbow(rgb_state.offset + (RGB_BACKLIGHT_RAINBOW_S0 * mult), 5); + green = rainbow(rgb_state.offset + (RGB_BACKLIGHT_RAINBOW_S0 * mult), 3); + blue = rainbow(rgb_state.offset + (RGB_BACKLIGHT_RAINBOW_S0 * mult), 1); + } else if(color_index == 2) { + red = rainbow(rgb_state.offset + (mapped_index * mult), 5); + green = rainbow(rgb_state.offset + (mapped_index * mult), 3); + blue = rainbow(rgb_state.offset + (mapped_index * mult), 1); + } + +#ifdef USE_DEBUG_LED_STRIP + if(mapped_index >= 8) { + red = 0; + green = 0; + blue = 0; + } +#endif + uint8_t r = red * internal_brightness; uint8_t g = green * internal_brightness; uint8_t b = blue * internal_brightness; diff --git a/applications/settings/notification_settings/rgb_backlight.h b/applications/settings/notification_settings/rgb_backlight.h index b24134c0766..c5eed77361d 100644 --- a/applications/settings/notification_settings/rgb_backlight.h +++ b/applications/settings/notification_settings/rgb_backlight.h @@ -41,6 +41,9 @@ typedef struct { typedef enum { BacklightModeConstant, + BacklightModeTimedSingle, + BacklightModeTimedRainbow, + BacklightModeCount, // Must be last item. This is the total number of modes. } BacklightMode; typedef enum { @@ -58,6 +61,9 @@ typedef struct { InternalMode internal_mode; bool settings_loaded; uint8_t internal_color[3]; // RGB + uint16_t rainbow_width; // 0 - 359 degrees Hue (0 for off) + int8_t rainbow_spin_increment; // degrees hue increment + uint16_t rainbow_update_time; // ms update delay (0 for off) } RGBBacklightSettings; /** @@ -136,6 +142,18 @@ void rgb_backlight_led_get_color(uint8_t led_number, uint8_t* red, uint8_t* gree */ uint8_t rgb_backlight_find_index(uint8_t led_number); +BacklightMode rgb_backlight_get_mode(void); +void rgb_backlight_set_mode(BacklightMode); + +uint16_t rgb_rainbow_get_width(void); +void rgb_rainbow_set_width(uint16_t width); + +int8_t rgb_rainbow_get_spin(void); +void rgb_rainbow_set_spin(int8_t width); + +uint16_t rgb_rainbow_get_update_time(void); +void rgb_rainbow_set_update_time(uint16_t width); + /** * @brief Returns the number of patterns available to set the LED to. * diff --git a/applications/settings/notification_settings/rgb_backlight_colors.h b/applications/settings/notification_settings/rgb_backlight_colors.h index 24ce811b5df..106f7020a37 100644 --- a/applications/settings/notification_settings/rgb_backlight_colors.h +++ b/applications/settings/notification_settings/rgb_backlight_colors.h @@ -40,6 +40,8 @@ static const RGBColor rgb_colors[ColorCount] = { static const InternalPattern internal_pattern[] = { {"Custom", .length = 1, {ColorNameCustom}}, + {"Slide", .length = 2, {ColorNameCustom, ColorNameCustom}}, + {"Rainbow", .length = 3, {ColorNameCustom, ColorNameCustom, ColorNameCustom}}, {"Orange", .length = 1, {ColorNameOrange}}, {"Yellow", .length = 1, {ColorNameYellow}}, {"Spring", .length = 1, {ColorNameSpring}}, diff --git a/applications/settings/rgb_settings/rgb_settings_app.c b/applications/settings/rgb_settings/rgb_settings_app.c index 11325436af2..48cda62d6f6 100644 --- a/applications/settings/rgb_settings/rgb_settings_app.c +++ b/applications/settings/rgb_settings/rgb_settings_app.c @@ -32,15 +32,55 @@ typedef enum { RGBSettingsViewInput, } RGBSettingsViews; -#define BRIGHTNESS_COUNT 21 +#define BRIGHTNESS_COUNT 22 static const char* const brightness_text[BRIGHTNESS_COUNT] = { - "0%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", "50%", - "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", + "0%", "3%", "5%", "10%", "15%", "20%", "25%", "30%", "35%", "40%", "45%", + "50%", "55%", "60%", "65%", "70%", "75%", "80%", "85%", "90%", "95%", "100%", }; static const float brightness_value[BRIGHTNESS_COUNT] = { - 0.00f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, 0.50f, - 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, + 0.00f, 0.03f, 0.05f, 0.10f, 0.15f, 0.20f, 0.25f, 0.30f, 0.35f, 0.40f, 0.45f, + 0.50f, 0.55f, 0.60f, 0.65f, 0.70f, 0.75f, 0.80f, 0.85f, 0.90f, 0.95f, 1.00f, }; +static const char* const backlight_mode_text[BacklightModeCount] = { + [BacklightModeConstant] = "Default", + [BacklightModeTimedSingle] = "Slide", + [BacklightModeTimedRainbow] = "Rainbow", +}; +static const BacklightMode backlight_mode_value[BacklightModeCount] = { + [BacklightModeConstant] = BacklightModeConstant, + [BacklightModeTimedSingle] = BacklightModeTimedSingle, + [BacklightModeTimedRainbow] = BacklightModeTimedRainbow, +}; + +#define RAINBOW_WIDTH_COUNT 6 +static const char* const rainbow_width_text[RAINBOW_WIDTH_COUNT] = + {"60", "90", "120", "180", "270", "360"}; +static const int16_t rainbow_width_value[RAINBOW_WIDTH_COUNT] = {60, 90, 120, 180, 270, 360}; + +#define RAINBOW_SPIN_COUNT 14 +static const char* const rainbow_spin_text[RAINBOW_SPIN_COUNT] = + {"-30", "-20", "-15", "-10", "-5", "-3", "-1", "1", "3", "5", "10", "15", "20", "30"}; +static const int16_t rainbow_spin_value[RAINBOW_SPIN_COUNT] = + {-30, -20, -15, -10, -5, -3, -1, 1, 3, 5, 10, 15, 20, 30}; + +#define RAINBOW_UPDATE_COUNT 13 +static const char* const rainbow_update_text[RAINBOW_UPDATE_COUNT] = { + ".1s", + ".15s", + ".2s", + ".25s", + ".5s", + ".75s", + "1.0s", + "1.5s", + "2.0s", + "2.5s", + "3.0s", + "4.0s", + "5.0s"}; +static const uint16_t rainbow_update_value[RAINBOW_UPDATE_COUNT] = + {100, 150, 200, 250, 500, 750, 1000, 1500, 2000, 2500, 3000, 4000, 5000}; + static const char* const internal_mode_text[InternalModeCount] = { [InternalModeMatch] = "Auto", [InternalModeOn] = "On", @@ -123,6 +163,39 @@ static void backlight_color_changed_3(VariableItem* item) { notification_message(app->notification, &sequence_display_backlight_on); } +static void backlight_mode_changed(VariableItem* item) { + RgbSettingsApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + rgb_backlight_set_mode(backlight_mode_value[index]); + variable_item_set_current_value_text(item, backlight_mode_text[index]); + notification_message(app->notification, &sequence_display_backlight_on); +} + +static void rainbow_width_changed(VariableItem* item) { + RgbSettingsApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + rgb_rainbow_set_width(rainbow_width_value[index]); + variable_item_set_current_value_text(item, rainbow_width_text[index]); + notification_message(app->notification, &sequence_display_backlight_on); +} + +static void rainbow_spin_changed(VariableItem* item) { + RgbSettingsApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + rgb_rainbow_set_spin(rainbow_spin_value[index]); + variable_item_set_current_value_text(item, rainbow_spin_text[index]); + notification_message(app->notification, &sequence_display_backlight_on); +} + +static void rainbow_update_changed(VariableItem* item) { + RgbSettingsApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + rgb_rainbow_set_update_time(rainbow_update_value[index]); + variable_item_set_current_value_text(item, rainbow_update_text[index]); + notification_message(app->notification, &sequence_display_backlight_on); +} + static void internal_pattern_changed(VariableItem* item) { RgbSettingsApp* app = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -205,8 +278,8 @@ static void rgb_settings_byte_input_result(void* context) { static void rgb_settings_list_enter(void* context, uint32_t index) { // First three settings (0-2) are RGB colors. - // Setting 4 is internal pattern. - if(index > 2 && index != 4) { + // Setting 8 is internal pattern. + if(index > 2 && index != 8) { return; } @@ -301,6 +374,47 @@ static RgbSettingsApp* alloc_settings() { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, brightness_text[value_index]); + item = variable_item_list_add( + app->variable_item_list, "LCD Mode", BacklightModeCount, backlight_mode_changed, app); + value_index = rgb_backlight_get_mode(); // 1:1 mapped + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, backlight_mode_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, "Rainbow Width", RAINBOW_WIDTH_COUNT, rainbow_width_changed, app); + int16_t rainbow_width = rgb_rainbow_get_width(); + value_index = 0; + while(value_index < RAINBOW_WIDTH_COUNT && rainbow_width > rainbow_width_value[value_index]) { + value_index++; + } + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rainbow_width_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, "Rainbow Spin", RAINBOW_SPIN_COUNT, rainbow_spin_changed, app); + int8_t rainbow_spin = rgb_rainbow_get_spin(); + value_index = 0; + while(value_index < RAINBOW_SPIN_COUNT && rainbow_spin > rainbow_spin_value[value_index]) { + value_index++; + } + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rainbow_spin_text[value_index]); + + item = variable_item_list_add( + app->variable_item_list, + "Rainbow Update", + RAINBOW_UPDATE_COUNT, + rainbow_update_changed, + app); + uint16_t rainbow_update_time = rgb_rainbow_get_update_time(); + value_index = 0; + while(value_index < RAINBOW_UPDATE_COUNT && + rainbow_update_time > rainbow_update_value[value_index]) { + value_index++; + } + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, rainbow_update_text[value_index]); + app->items[3] = variable_item_list_add( app->variable_item_list, "Internal Pattern",