/* RGB backlight FlipperZero driver Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n) Heavily modified by Willy-JL and Z3bro This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "rgb_backlight.h" #include "rgb_backlight_filename.h" #include #include #include #define RGB_BACKLIGHT_SETTINGS_MAGIC 0x15 #define RGB_BACKLIGHT_SETTINGS_VERSION 6 static struct { RgbColor colors[SK6805_LED_COUNT]; RGBBacklightRainbowMode rainbow_mode; uint8_t rainbow_speed; uint32_t rainbow_interval; uint32_t rainbow_saturation; } rgb_settings = { .colors = { {{255, 69, 0}}, {{255, 69, 0}}, {{255, 69, 0}}, }, .rainbow_mode = RGBBacklightRainbowModeOff, .rainbow_speed = 5, .rainbow_interval = 250, .rainbow_saturation = 255, }; static struct { bool settings_loaded; FuriMutex* mutex; bool enabled; uint8_t last_brightness; FuriTimer* rainbow_timer; HsvColor rainbow_hsv; } rgb_state = { .settings_loaded = false, .mutex = NULL, .enabled = false, .last_brightness = 0, .rainbow_timer = NULL, .rainbow_hsv = {{0, 255, 255}}, }; void rgb_backlight_load_settings(bool enabled) { // This function is called from momentum_settings_load() // No need to setup storage pubsub, just expect multiple calls rgb_state.settings_loaded = false; rgb_state.enabled = enabled; if(!rgb_state.mutex) { rgb_state.mutex = furi_mutex_alloc(FuriMutexTypeRecursive); } // Do not load and apply settings when booting in update/RAM mode if(!furi_hal_is_normal_boot()) { rgb_state.settings_loaded = true; return; } saved_struct_load( RGB_BACKLIGHT_SETTINGS_PATH, &rgb_settings, sizeof(rgb_settings), RGB_BACKLIGHT_SETTINGS_MAGIC, RGB_BACKLIGHT_SETTINGS_VERSION); rgb_state.settings_loaded = true; rgb_backlight_reconfigure(rgb_state.enabled); } void rgb_backlight_save_settings(void) { if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); saved_struct_save( RGB_BACKLIGHT_SETTINGS_PATH, &rgb_settings, sizeof(rgb_settings), RGB_BACKLIGHT_SETTINGS_MAGIC, RGB_BACKLIGHT_SETTINGS_VERSION); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } void rgb_backlight_set_color(uint8_t index, const RgbColor* color) { if(index >= COUNT_OF(rgb_settings.colors)) return; if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); memcpy(&rgb_settings.colors[index], color, sizeof(RgbColor)); rgb_backlight_reconfigure(rgb_state.enabled); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } void rgb_backlight_get_color(uint8_t index, RgbColor* color) { if(index >= COUNT_OF(rgb_settings.colors)) return; if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); memcpy(color, &rgb_settings.colors[index], sizeof(RgbColor)); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } void rgb_backlight_set_rainbow_mode(RGBBacklightRainbowMode rainbow_mode) { if(rainbow_mode >= RGBBacklightRainbowModeCount) return; if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); rgb_settings.rainbow_mode = rainbow_mode; rgb_backlight_reconfigure(rgb_state.enabled); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } RGBBacklightRainbowMode rgb_backlight_get_rainbow_mode(void) { if(!rgb_state.settings_loaded) return 0; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); RGBBacklightRainbowMode rainbow_mode = rgb_settings.rainbow_mode; furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); return rainbow_mode; } void rgb_backlight_set_rainbow_speed(uint8_t rainbow_speed) { if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); rgb_settings.rainbow_speed = rainbow_speed; furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } uint8_t rgb_backlight_get_rainbow_speed(void) { if(!rgb_state.settings_loaded) return 0; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); uint8_t rainbow_speed = rgb_settings.rainbow_speed; furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); return rainbow_speed; } void rgb_backlight_set_rainbow_interval(uint32_t rainbow_interval) { if(rainbow_interval < 100) return; if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); rgb_settings.rainbow_interval = rainbow_interval; rgb_backlight_reconfigure(rgb_state.enabled); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } uint32_t rgb_backlight_get_rainbow_interval(void) { if(!rgb_state.settings_loaded) return 0; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); uint32_t rainbow_interval = rgb_settings.rainbow_interval; furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); return rainbow_interval; } void rgb_backlight_set_rainbow_saturation(uint8_t rainbow_saturation) { if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); rgb_settings.rainbow_saturation = rainbow_saturation; rgb_backlight_reconfigure(rgb_state.enabled); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } uint8_t rgb_backlight_get_rainbow_saturation(void) { if(!rgb_state.settings_loaded) return 0; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); uint8_t rainbow_saturation = rgb_settings.rainbow_saturation; furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); return rainbow_saturation; } void rainbow_timer(void* ctx) { if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); if(!rgb_state.enabled || rgb_settings.rainbow_mode == RGBBacklightRainbowModeOff || !rgb_state.last_brightness) { furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); return; } rgb_state.rainbow_hsv.h += rgb_settings.rainbow_speed; RgbColor rgb; hsv2rgb(&rgb_state.rainbow_hsv, &rgb); switch(rgb_settings.rainbow_mode) { case RGBBacklightRainbowModeWave: { HsvColor hsv = rgb_state.rainbow_hsv; for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { if(i) { hsv.h += (50 * i); hsv2rgb(&hsv, &rgb); } SK6805_set_led_color(i, rgb.r, rgb.g, rgb.b); } break; } case RGBBacklightRainbowModeSolid: for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { SK6805_set_led_color(i, rgb.r, rgb.g, rgb.b); } break; default: break; } SK6805_update(); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); UNUSED(ctx); } void rgb_backlight_reconfigure(bool enabled) { if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); rgb_state.enabled = enabled; if(rgb_state.enabled && rgb_settings.rainbow_mode != RGBBacklightRainbowModeOff) { if(rgb_state.rainbow_timer == NULL) { rgb_state.rainbow_timer = furi_timer_alloc(rainbow_timer, FuriTimerTypePeriodic, NULL); } else { furi_timer_stop(rgb_state.rainbow_timer); } furi_timer_start(rgb_state.rainbow_timer, rgb_settings.rainbow_interval); } else if(rgb_state.rainbow_timer != NULL) { furi_timer_stop(rgb_state.rainbow_timer); furi_timer_free(rgb_state.rainbow_timer); rgb_state.rainbow_timer = NULL; } rgb_state.rainbow_hsv.s = rgb_settings.rainbow_saturation; rgb_backlight_update(rgb_state.last_brightness, true); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); } void rgb_backlight_update(uint8_t brightness, bool forced) { if(!rgb_state.settings_loaded) return; furi_check(furi_mutex_acquire(rgb_state.mutex, FuriWaitForever) == FuriStatusOk); if(!rgb_state.enabled || (brightness == rgb_state.last_brightness && !forced)) { furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); return; } switch(rgb_settings.rainbow_mode) { case RGBBacklightRainbowModeOff: { float bright = brightness / 255.0f; for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { SK6805_set_led_color( i, rgb_settings.colors[i].r * bright, rgb_settings.colors[i].g * bright, rgb_settings.colors[i].b * bright); } break; } case RGBBacklightRainbowModeWave: case RGBBacklightRainbowModeSolid: { if(!brightness) { furi_timer_stop(rgb_state.rainbow_timer); for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { SK6805_set_led_color(i, 0, 0, 0); } } else if(!rgb_state.last_brightness) furi_timer_start(rgb_state.rainbow_timer, rgb_settings.rainbow_interval); rgb_state.rainbow_hsv.v = brightness; break; } default: furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); return; } rgb_state.last_brightness = brightness; SK6805_update(); furi_check(furi_mutex_release(rgb_state.mutex) == FuriStatusOk); }