#include #include #include #include #include #include #include #include #include "notification.h" #include "notification_messages.h" #include "notification_app.h" #define TAG "NotificationSrv" #define COLOR_COUNT (sizeof(colors) / sizeof(RGBBacklightColor)) static const uint8_t minimal_delay = 100; static const uint8_t led_off_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; static const uint8_t reset_red_mask = 1 << 0; static const uint8_t reset_green_mask = 1 << 1; static const uint8_t reset_blue_mask = 1 << 2; static const uint8_t reset_vibro_mask = 1 << 3; static const uint8_t reset_sound_mask = 1 << 4; static const uint8_t reset_display_mask = 1 << 5; static const uint8_t reset_blink_mask = 1 << 6; static void notification_vibro_on(bool force); static void notification_vibro_off(void); static void notification_sound_on(float freq, float volume, bool force); static void notification_sound_off(void); static uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value); static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value); static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app); // --- RGB BACKLIGHT --- // local variable for local use uint8_t rgb_backlight_installed_variable = 0; typedef struct { char* name; uint8_t red; uint8_t green; uint8_t blue; } RGBBacklightColor; // use one type RGBBacklightColor for current_leds_settings and for static colors definition static RGBBacklightColor current_led[] = { {"LED0", 0, 0, 0}, {"LED1", 0, 0, 0}, {"LED2", 0, 0, 0}, }; static const RGBBacklightColor colors[] = { {"Orange", 255, 60, 0}, {"Yellow", 255, 144, 0}, {"Spring", 167, 255, 0}, {"Lime", 0, 255, 0}, {"Aqua", 0, 255, 127}, {"Cyan", 0, 210, 210}, {"Azure", 0, 127, 255}, {"Blue", 0, 0, 255}, {"Purple", 127, 0, 255}, {"Magenta", 210, 0, 210}, {"Pink", 255, 0, 127}, {"Red", 255, 0, 0}, {"White", 254, 210, 200}, {"OFF", 0, 0, 0}, }; uint8_t rgb_backlight_get_color_count(void) { return COLOR_COUNT; } const char* rgb_backlight_get_color_text(uint8_t index) { return colors[index].name; } // function for changind local variable from outside; void set_rgb_backlight_installed_variable(uint8_t var) { rgb_backlight_installed_variable = var; } // update led current colors by static void rgb_backlight_set_led_static_color(uint8_t led, uint8_t index) { if(led < SK6805_get_led_count()) { uint8_t r = colors[index].red; uint8_t g = colors[index].green; uint8_t b = colors[index].blue; current_led[led].red = r; current_led[led].green = g; current_led[led].blue = b; SK6805_set_led_color(led, r, g, b); } } // HSV to RGB based on // https://www.radiokot.ru/forum/viewtopic.php?p=3000181&ysclid=m88wvoz34w244644702 // https://radiolaba.ru/microcotrollers/tsvetnaya-lampa.html#comment-1790 // https://alexgyver.ru/lessons/arduino-rgb/?ysclid=m88voflppa24464916 // led number (0-2), hue (0..255), sat (0..255), val (0...1) void rgb_backlight_set_led_custom_hsv_color(uint8_t led, uint16_t hue, uint8_t sat, float V) { // init value float r = 1.0f; float g = 1.0f; float b = 1.0f; // from (0..255) to (0..1) float H = hue / 255.0f; float S = sat / 255.0f; uint8_t i = trunc(H * 6); float f = H * 6 - i; float p = V * (1 - S); float q = V * (1 - f * S); float t = V * (1 - (1 - f) * S); switch(i) { case 0: r = V, g = t, b = p; break; case 1: r = q, g = V, b = p; break; case 2: r = p, g = V, b = t; break; case 3: r = p, g = q, b = V; break; case 4: r = t, g = p, b = V; break; case 5: r = V, g = p, b = q; break; } // from (0..1) to (0..255) current_led[led].red = r * 255; current_led[led].green = g * 255; current_led[led].blue = b * 255; } // set current_* colors to led and update backlight void rgb_backlight_update(float brightness) { if(rgb_backlight_installed_variable > 0) { for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { uint8_t r = current_led[i].red * brightness * 1.0f; uint8_t g = current_led[i].green * brightness * 1.0f; uint8_t b = current_led[i].blue * brightness * 1.0f; SK6805_set_led_color(i, r, g, b); } SK6805_update(); } } // start furi timer for rainbow void rainbow_timer_start(NotificationApp* app) { if(furi_timer_is_running(app->rainbow_timer)) { furi_timer_stop(app->rainbow_timer); } furi_timer_start(app->rainbow_timer, furi_ms_to_ticks(app->settings.rgb.rainbow_speed_ms)); } // stop furi timer for rainbow void rainbow_timer_stop(NotificationApp* app) { if(furi_timer_is_running(app->rainbow_timer)) { furi_timer_stop(app->rainbow_timer); } } // if rgb_backlight_installed then apply rainbow colors to backlight and start/restart/stop rainbow_timer void rainbow_timer_starter(NotificationApp* app) { if((app->settings.rgb.rainbow_mode > 0) && (app->settings.rgb.rgb_backlight_installed)) { rainbow_timer_start(app); } } static void rainbow_timer_callback(void* context) { furi_assert(context); NotificationApp* app = context; if(app->settings.rgb.rgb_backlight_installed) { app->rainbow_hue += app->settings.rgb.rainbow_step; if(app->rainbow_hue > 254) { app->rainbow_hue = 0; } uint8_t wide = app->settings.rgb.rainbow_wide; switch(app->settings.rgb.rainbow_mode) { //rainbow mode case 1: for(uint8_t i = 0; i < SK6805_get_led_count(); i++) { rgb_backlight_set_led_custom_hsv_color( i, app->rainbow_hue, app->settings.rgb.rainbow_saturation, app->settings.display_brightness); } break; //wave mode case 2: uint16_t j = app->rainbow_hue + wide; uint16_t k = app->rainbow_hue + wide * 2; if(app->rainbow_hue > (254 - wide)) { j = j - 255; } if(app->rainbow_hue > (254 - wide * 2)) { k = k - 255; } rgb_backlight_set_led_custom_hsv_color( 0, app->rainbow_hue, app->settings.rgb.rainbow_saturation, app->settings.display_brightness); rgb_backlight_set_led_custom_hsv_color( 1, j, app->settings.rgb.rainbow_saturation, app->settings.display_brightness); rgb_backlight_set_led_custom_hsv_color( 2, k, app->settings.rgb.rainbow_saturation, app->settings.display_brightness); break; default: break; } rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); } } // --- RGB BACKLIGHT END--- // --- NIGHT SHIFT --- void night_shift_timer_start(NotificationApp* app) { if(app->settings.night_shift != 1) { if(furi_timer_is_running(app->night_shift_timer)) { furi_timer_stop(app->night_shift_timer); } furi_timer_start(app->night_shift_timer, furi_ms_to_ticks(2000)); } } void night_shift_timer_stop(NotificationApp* app) { if(furi_timer_is_running(app->night_shift_timer)) { furi_timer_stop(app->night_shift_timer); } } // every callback time we check current time and current night_shift_settings value void night_shift_timer_callback(void* context) { furi_assert(context); NotificationApp* app = context; DateTime current_date_time; // take system time and convert to minutes furi_hal_rtc_get_datetime(¤t_date_time); uint32_t time = current_date_time.hour * 60 + current_date_time.minute; // if current time not in night_shift range then current_night_shift = 1 else = settings value; // set values to stock and rgb backlights if((time > app->settings.night_shift_end) && (time < app->settings.night_shift_start)) { app->current_night_shift = 1.0f; } else { app->current_night_shift = app->settings.night_shift; } } // --- NIGHT SHIFT END --- void notification_message_save_settings(NotificationApp* app) { NotificationAppMessage m = { .type = SaveSettingsMessage, .back_event = furi_event_flag_alloc()}; furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); furi_event_flag_wait( m.back_event, NOTIFICATION_EVENT_COMPLETE, FuriFlagWaitAny, FuriWaitForever); furi_event_flag_free(m.back_event); } // internal layer static void notification_apply_internal_led_layer(NotificationLedLayer* layer, uint8_t layer_value) { furi_assert(layer); furi_assert(layer->index < LayerMAX); // set value layer->value[LayerInternal] = layer_value; // apply if current layer is internal if(layer->index == LayerInternal) { furi_hal_light_set(layer->light, layer->value[LayerInternal]); } } static void notification_apply_lcd_contrast(NotificationApp* app) { Gui* gui = furi_record_open(RECORD_GUI); u8x8_d_st756x_set_contrast(&gui->canvas->fb.u8x8, app->settings.contrast); furi_record_close(RECORD_GUI); } static bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp* app) { bool result = false; if((app->led[0].index == LayerInternal) || (app->led[1].index == LayerInternal) || (app->led[2].index == LayerInternal)) { if((app->led[0].value[LayerInternal] != 0x00) || (app->led[1].value[LayerInternal] != 0x00) || (app->led[2].value[LayerInternal] != 0x00)) { result = true; } } return result; } // notification layer static void notification_apply_notification_led_layer( NotificationLedLayer* layer, const uint8_t layer_value) { furi_assert(layer); furi_assert(layer->index < LayerMAX); // set value layer->index = LayerNotification; // set layer layer->value[LayerNotification] = layer_value; // apply furi_hal_light_set(layer->light, layer->value[LayerNotification]); } static void notification_reset_notification_led_layer(NotificationLedLayer* layer) { furi_assert(layer); furi_assert(layer->index < LayerMAX); // set value layer->value[LayerNotification] = 0; // set layer layer->index = LayerInternal; // apply furi_hal_light_set(layer->light, layer->value[LayerInternal]); } static void notification_reset_notification_layer( NotificationApp* app, uint8_t reset_mask, float display_brightness_set) { if(reset_mask & reset_blink_mask) { furi_hal_light_blink_stop(); } if(reset_mask & reset_red_mask) { notification_reset_notification_led_layer(&app->led[0]); } if(reset_mask & reset_green_mask) { notification_reset_notification_led_layer(&app->led[1]); } if(reset_mask & reset_blue_mask) { notification_reset_notification_led_layer(&app->led[2]); } if(reset_mask & reset_vibro_mask) { notification_vibro_off(); } if(reset_mask & reset_sound_mask) { notification_sound_off(); } if(reset_mask & reset_display_mask) { if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { // --- NIGHT SHIFT --- furi_hal_light_set( LightBacklight, app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); // --- NIGHT SHIFT END--- } if(app->settings.display_off_delay_ms > 0) { furi_timer_start( app->display_timer, notification_settings_display_off_delay_ticks(app)); } } } static void notification_apply_notification_leds(NotificationApp* app, const uint8_t* values) { for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { notification_apply_notification_led_layer( &app->led[i], notification_settings_get_rgb_led_brightness(app, values[i])); } } // settings uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value) { return value * app->settings.display_brightness; } static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value) { return value * app->settings.led_brightness; } static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app) { return (float)(app->settings.display_off_delay_ms) / (1000.0f / furi_kernel_get_tick_frequency()); } // generics static void notification_vibro_on(bool force) { if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { furi_hal_vibro_on(true); } } static void notification_vibro_off(void) { furi_hal_vibro_on(false); } static void notification_sound_on(float freq, float volume, bool force) { if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { furi_hal_speaker_start(freq, volume); } } } static void notification_sound_off(void) { if(furi_hal_speaker_is_mine()) { furi_hal_speaker_stop(); furi_hal_speaker_release(); } } // display timer static void notification_display_timer(void* ctx) { furi_assert(ctx); NotificationApp* app = ctx; notification_message(app, &sequence_display_backlight_off); } // message processing static void notification_process_notification_message( NotificationApp* app, NotificationAppMessage* message) { uint32_t notification_message_index = 0; bool force_volume = false; bool force_vibro = false; const NotificationMessage* notification_message; notification_message = (*message->sequence)[notification_message_index]; bool led_active = false; uint8_t led_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; bool reset_notifications = true; float speaker_volume_setting = app->settings.speaker_volume; bool vibro_setting = app->settings.vibro_on; float display_brightness_setting = app->settings.display_brightness; uint8_t reset_mask = 0; while(notification_message != NULL) { switch(notification_message->type) { case NotificationMessageTypeLedDisplayBacklight: // if on - switch on and start timer // if off - switch off and stop timer // on timer - switch off // --- NIGHT SHIFT --- if(notification_message->data.led.value > 0x00) { notification_apply_notification_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); reset_mask |= reset_display_mask; //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too rainbow_timer_starter(app); // --- NIGHT SHIFT END --- } else { reset_mask &= ~reset_display_mask; notification_reset_notification_led_layer(&app->display); if(furi_timer_is_running(app->display_timer)) { furi_timer_stop(app->display_timer); } //stop rgb_mod_rainbow_timer when display backlight is OFF if(furi_timer_is_running(app->rainbow_timer)) { rainbow_timer_stop(app); } } break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: // --- NIGHT SHIFT --- if(!app->display_led_lock) { app->display_led_lock = true; notification_apply_internal_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); } break; case NotificationMessageTypeLedDisplayBacklightEnforceAuto: if(app->display_led_lock) { app->display_led_lock = false; notification_apply_internal_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); // --- NIGHT SHIFT END --- } else { FURI_LOG_E(TAG, "Incorrect BacklightEnforce use"); } break; case NotificationMessageTypeLedRed: // store and send on delay or after seq led_active = true; led_values[0] = notification_message->data.led.value; app->led[0].value_last[LayerNotification] = led_values[0]; reset_mask |= reset_red_mask; break; case NotificationMessageTypeLedGreen: // store and send on delay or after seq led_active = true; led_values[1] = notification_message->data.led.value; app->led[1].value_last[LayerNotification] = led_values[1]; reset_mask |= reset_green_mask; break; case NotificationMessageTypeLedBlue: // store and send on delay or after seq led_active = true; led_values[2] = notification_message->data.led.value; app->led[2].value_last[LayerNotification] = led_values[2]; reset_mask |= reset_blue_mask; break; case NotificationMessageTypeLedBlinkStart: // store and send on delay or after seq led_active = true; furi_hal_light_blink_start( notification_message->data.led_blink.color, app->settings.led_brightness * 255, notification_message->data.led_blink.on_time, notification_message->data.led_blink.period); reset_mask |= reset_blink_mask; reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; break; case NotificationMessageTypeLedBlinkColor: led_active = true; furi_hal_light_blink_set_color(notification_message->data.led_blink.color); break; case NotificationMessageTypeLedBlinkStop: furi_hal_light_blink_stop(); reset_mask &= ~reset_blink_mask; reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; break; case NotificationMessageTypeVibro: if(notification_message->data.vibro.on) { if(vibro_setting) notification_vibro_on(force_vibro); } else { notification_vibro_off(); } reset_mask |= reset_vibro_mask; break; case NotificationMessageTypeSoundOn: notification_sound_on( notification_message->data.sound.frequency, notification_message->data.sound.volume * speaker_volume_setting, force_volume); reset_mask |= reset_sound_mask; break; case NotificationMessageTypeSoundOff: notification_sound_off(); reset_mask |= reset_sound_mask; break; case NotificationMessageTypeDelay: if(led_active) { if(notification_is_any_led_layer_internal_and_not_empty(app)) { notification_apply_notification_leds(app, led_off_values); furi_delay_ms(minimal_delay); } led_active = false; notification_apply_notification_leds(app, led_values); reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; } furi_delay_ms(notification_message->data.delay.length); break; case NotificationMessageTypeDoNotReset: reset_notifications = false; break; case NotificationMessageTypeForceSpeakerVolumeSetting: speaker_volume_setting = notification_message->data.forced_settings.speaker_volume; force_volume = true; break; case NotificationMessageTypeForceVibroSetting: vibro_setting = notification_message->data.forced_settings.vibro; force_vibro = true; break; case NotificationMessageTypeForceDisplayBrightnessSetting: display_brightness_setting = notification_message->data.forced_settings.display_brightness; break; case NotificationMessageTypeLedBrightnessSettingApply: led_active = true; for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { led_values[i] = app->led[i].value_last[LayerNotification]; } reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; break; case NotificationMessageTypeLcdContrastUpdate: notification_apply_lcd_contrast(app); break; } notification_message_index++; notification_message = (*message->sequence)[notification_message_index]; }; // send and do minimal delay if(led_active) { bool need_minimal_delay = false; if(notification_is_any_led_layer_internal_and_not_empty(app)) { need_minimal_delay = true; } notification_apply_notification_leds(app, led_values); reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; if((need_minimal_delay) && (reset_notifications)) { notification_apply_notification_leds(app, led_off_values); furi_delay_ms(minimal_delay); } } if(reset_notifications) { notification_reset_notification_layer(app, reset_mask, display_brightness_setting); } } static void notification_process_internal_message(NotificationApp* app, NotificationAppMessage* message) { uint32_t notification_message_index = 0; const NotificationMessage* notification_message; notification_message = (*message->sequence)[notification_message_index]; while(notification_message != NULL) { switch(notification_message->type) { case NotificationMessageTypeLedDisplayBacklight: notification_apply_internal_led_layer( &app->display, notification_settings_get_display_brightness( app, notification_message->data.led.value)); break; case NotificationMessageTypeLedRed: app->led[0].value_last[LayerInternal] = notification_message->data.led.value; notification_apply_internal_led_layer( &app->led[0], notification_settings_get_rgb_led_brightness( app, notification_message->data.led.value)); break; case NotificationMessageTypeLedGreen: app->led[1].value_last[LayerInternal] = notification_message->data.led.value; notification_apply_internal_led_layer( &app->led[1], notification_settings_get_rgb_led_brightness( app, notification_message->data.led.value)); break; case NotificationMessageTypeLedBlue: app->led[2].value_last[LayerInternal] = notification_message->data.led.value; notification_apply_internal_led_layer( &app->led[2], notification_settings_get_rgb_led_brightness( app, notification_message->data.led.value)); break; case NotificationMessageTypeLedBrightnessSettingApply: for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { uint8_t new_val = notification_settings_get_rgb_led_brightness( app, app->led[i].value_last[LayerInternal]); notification_apply_internal_led_layer(&app->led[i], new_val); } break; default: break; } notification_message_index++; notification_message = (*message->sequence)[notification_message_index]; } } static bool notification_load_settings(NotificationApp* app) { NotificationSettings settings; File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); const size_t settings_size = sizeof(NotificationSettings); FURI_LOG_I(TAG, "Loading \"%s\"", NOTIFICATION_SETTINGS_PATH); bool fs_result = storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); if(fs_result) { size_t bytes_count = storage_file_read(file, &settings, settings_size); if(bytes_count != settings_size) { fs_result = false; } } if(fs_result) { if(settings.version != NOTIFICATION_SETTINGS_VERSION) { FURI_LOG_E( TAG, "version(%d != %d) mismatch", settings.version, NOTIFICATION_SETTINGS_VERSION); } else { furi_kernel_lock(); memcpy(&app->settings, &settings, settings_size); furi_kernel_unlock(); } } else { FURI_LOG_E(TAG, "Load failed, %s", storage_file_get_error_desc(file)); } storage_file_close(file); storage_file_free(file); furi_record_close(RECORD_STORAGE); return fs_result; } static bool notification_save_settings(NotificationApp* app) { NotificationSettings settings; File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); const size_t settings_size = sizeof(NotificationSettings); FURI_LOG_I(TAG, "Saving \"%s\"", NOTIFICATION_SETTINGS_PATH); furi_kernel_lock(); memcpy(&settings, &app->settings, settings_size); furi_kernel_unlock(); bool fs_result = storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); if(fs_result) { size_t bytes_count = storage_file_write(file, &settings, settings_size); if(bytes_count != settings_size) { fs_result = false; } } if(fs_result) { } else { FURI_LOG_E(TAG, "Save failed, %s", storage_file_get_error_desc(file)); } storage_file_close(file); storage_file_free(file); furi_record_close(RECORD_STORAGE); return fs_result; } static void input_event_callback(const void* value, void* context) { furi_assert(value); furi_assert(context); NotificationApp* app = context; notification_message(app, &sequence_display_backlight_on); } // App alloc static NotificationApp* notification_app_alloc(void) { NotificationApp* app = malloc(sizeof(NotificationApp)); app->queue = furi_message_queue_alloc(8, sizeof(NotificationAppMessage)); app->display_timer = furi_timer_alloc(notification_display_timer, FuriTimerTypeOnce, app); app->settings.speaker_volume = 1.0f; app->settings.display_brightness = 1.0f; app->settings.led_brightness = 1.0f; app->settings.display_off_delay_ms = 30000; app->settings.vibro_on = true; app->display.value[LayerInternal] = 0x00; app->display.value[LayerNotification] = 0x00; app->display.index = LayerInternal; app->display.light = LightBacklight; app->led[0].value[LayerInternal] = 0x00; app->led[0].value[LayerNotification] = 0x00; app->led[0].index = LayerInternal; app->led[0].light = LightRed; app->led[1].value[LayerInternal] = 0x00; app->led[1].value[LayerNotification] = 0x00; app->led[1].index = LayerInternal; app->led[1].light = LightGreen; app->led[2].value[LayerInternal] = 0x00; app->led[2].value[LayerNotification] = 0x00; app->led[2].index = LayerInternal; app->led[2].light = LightBlue; app->settings.version = NOTIFICATION_SETTINGS_VERSION; // display backlight control app->event_record = furi_record_open(RECORD_INPUT_EVENTS); furi_pubsub_subscribe(app->event_record, input_event_callback, app); notification_message(app, &sequence_display_backlight_on); // --- NIGHT SHIFT --- app->current_night_shift = 1.0f; app->current_night_shift = 1.0f; app->settings.night_shift = 1.0f; app->settings.night_shift_start = 1020; app->settings.night_shift_end = 300; app->night_shift_timer = furi_timer_alloc(night_shift_timer_callback, FuriTimerTypePeriodic, app); // --- NIGHT SHIFT END --- // init working variables app->rainbow_hue = 1; app->current_night_shift = 1.0f; // init rgb.segings values app->settings.rgb.rgb_backlight_installed = 0; app->settings.rgb.led_2_color_index = 0; app->settings.rgb.led_1_color_index = 0; app->settings.rgb.led_0_color_index = 0; app->settings.rgb.rainbow_speed_ms = 100; app->settings.rgb.rainbow_step = 1; app->settings.rgb.rainbow_saturation = 255; app->settings.rgb.rainbow_wide = 50; // set inital value, later it will be rewriten by loading settings from file app->settings.lcd_inversion = false; return app; } static void notification_storage_callback(const void* message, void* context) { furi_assert(context); NotificationApp* app = context; const StorageEvent* event = message; if(event->type == StorageEventTypeCardMount) { NotificationAppMessage m = { .type = LoadSettingsMessage, }; furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); } } static void notification_apply_settings(NotificationApp* app) { if(!notification_load_settings(app)) { notification_save_settings(app); } notification_apply_lcd_contrast(app); // --- NIGHT SHIFT --- // if night_shift enabled then start timer for controlling current_night_shift multiplicator value depent from current time if(app->settings.night_shift != 1) { night_shift_timer_start(app); } // --- NIGHT SHIFT END --- // check RECORD_GUI is exist (insurance on boot time) then use it to setup lcd inversion mode from loaded settings; if(furi_record_exists(RECORD_GUI)) { Gui* gui = furi_record_open(RECORD_GUI); u8x8_d_st756x_set_inversion(&gui->canvas->fb.u8x8, app->settings.lcd_inversion); furi_record_close(RECORD_GUI); } } static void notification_init_settings(NotificationApp* app) { Storage* storage = furi_record_open(RECORD_STORAGE); furi_pubsub_subscribe(storage_get_pubsub(storage), notification_storage_callback, app); if(storage_sd_status(storage) != FSE_OK) { FURI_LOG_D(TAG, "SD Card not ready, skipping settings"); return; } notification_apply_settings(app); } // App int32_t notification_srv(void* p) { UNUSED(p); NotificationApp* app = notification_app_alloc(); notification_init_settings(app); notification_vibro_off(); notification_sound_off(); notification_apply_internal_led_layer(&app->display, 0x00); notification_apply_internal_led_layer(&app->led[0], 0x00); notification_apply_internal_led_layer(&app->led[1], 0x00); notification_apply_internal_led_layer(&app->led[2], 0x00); furi_record_create(RECORD_NOTIFICATION, app); // --- RGB BACKLIGHT SECTION --- //setup local variable set_rgb_backlight_installed_variable(app->settings.rgb.rgb_backlight_installed); // define rainbow_timer and they callback app->rainbow_timer = furi_timer_alloc(rainbow_timer_callback, FuriTimerTypePeriodic, app); // if rgb_backlight_installed then start rainbow or set leds colors from saved settings (default index = 0) if(app->settings.rgb.rgb_backlight_installed) { if(app->settings.rgb.rainbow_mode > 0) { rainbow_timer_start(app); } else { rgb_backlight_set_led_static_color(2, app->settings.rgb.led_2_color_index); rgb_backlight_set_led_static_color(1, app->settings.rgb.led_1_color_index); rgb_backlight_set_led_static_color(0, app->settings.rgb.led_0_color_index); rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); } // if rgb_backlight not installed then set default static orange color(index=0) to all leds (0-2) and force light on } else { rgb_backlight_set_led_static_color(2, 0); rgb_backlight_set_led_static_color(1, 0); rgb_backlight_set_led_static_color(0, 0); SK6805_update(); } // --- RGB BACKLIGHT SECTION END --- NotificationAppMessage message; while(1) { furi_check(furi_message_queue_get(app->queue, &message, FuriWaitForever) == FuriStatusOk); switch(message.type) { case NotificationLayerMessage: notification_process_notification_message(app, &message); break; case InternalLayerMessage: notification_process_internal_message(app, &message); break; case SaveSettingsMessage: notification_save_settings(app); break; case LoadSettingsMessage: notification_load_settings(app); break; } if(message.back_event != NULL) { furi_event_flag_set(message.back_event, NOTIFICATION_EVENT_COMPLETE); } } return 0; }