Просмотр исходного кода

[FL-1237] Notifications app (#476)

* Notification app: init
* Notification app: separate message sequences
* Notification app: rename notifications to notification
* Notification app: rework api
* Notification app: new sequences for charger
* Power app: add state for better led handling
* Power app: NotificationSequence type, notification led process
* Blink app: use notifications
* Notification app: sound and vibro notifications
* Notification app: note messages
* Notification app: more messages
* Notification app: update note message generator
* Blink app: fix state counter
* Notification app: fix delay event
* App sd-filesystem: notifications
* App notifications: headers c++ compatibility
* App notifications: Cmaj success chord sequence
* App iButton: use notifications
* App notification: display backlight notifications
* App notification: add "display on" message to success and error sequences
* App accessor: use notifications
* App ibutton: guard onewire key read
* Lib-RFAL: remove api_hal_light usage
* App notification: add blocking mode, rework display api
* Cli led command: use internal notification instead of direc access to leds.
* App unit test: use notifications
* App lfrfid: use notifications
* Apps: close notification record
* App subghz: rough use of notifications
* App notificaton: ignore reset flag
* App strobe: removed
* Lib irda decoder: fix nec decoding
* App irda: fix assert, use notifications
* Apps: use notifications
* Fix IRDA tests
* Cli: better var naming
* App notification: readable sources

Co-authored-by: Albert Kharisov <albert@flipperdevices.com>
Co-authored-by: あく <alleteam@gmail.com>
SG 4 лет назад
Родитель
Сommit
2daf65b62b
40 измененных файлов с 1991 добавлено и 363 удалено
  1. 7 23
      applications/accessor/accessor-app.cpp
  2. 4 2
      applications/accessor/accessor-app.h
  3. 5 0
      applications/applications.c
  4. 6 0
      applications/applications.mk
  5. 5 3
      applications/backlight-control/backlight-control.c
  6. 26 8
      applications/cli/cli_commands.c
  7. 20 20
      applications/examples/blink.c
  8. 0 46
      applications/examples/strobe.c
  9. 10 9
      applications/examples/vibro.c
  10. 7 4
      applications/gpio-tester/gpio-tester.c
  11. 4 2
      applications/ibutton/helpers/key-reader.cpp
  12. 21 50
      applications/ibutton/ibutton-app.cpp
  13. 6 7
      applications/ibutton/ibutton-app.h
  14. 3 0
      applications/ibutton/scene/ibutton-scene-read-crc-error.cpp
  15. 3 0
      applications/ibutton/scene/ibutton-scene-read-not-key-error.cpp
  16. 3 1
      applications/ibutton/scene/ibutton-scene-read-success.cpp
  17. 3 2
      applications/ibutton/scene/ibutton-scene-write-success.cpp
  18. 8 6
      applications/irda/irda_app.c
  19. 4 7
      applications/irda_monitor/irda_monitor.c
  20. 6 16
      applications/lf-rfid/lf-rfid-app.cpp
  21. 4 3
      applications/lf-rfid/lf-rfid-app.h
  22. 2 2
      applications/lf-rfid/scene/lf-rfid-scene-read-normal.cpp
  23. 1 1
      applications/lf-rfid/scene/lf-rfid-scene-write.cpp
  24. 37 0
      applications/notification/notification-app-api.c
  25. 371 0
      applications/notification/notification-app.c
  26. 46 0
      applications/notification/notification-app.h
  27. 568 0
      applications/notification/notification-messages-notes.c
  28. 119 0
      applications/notification/notification-messages-notes.h
  29. 323 0
      applications/notification/notification-messages.c
  30. 94 0
      applications/notification/notification-messages.h
  31. 76 0
      applications/notification/notification.h
  32. 27 8
      applications/power/power.c
  33. 8 13
      applications/sd-card-test/sd-card-test.cpp
  34. 79 34
      applications/sd-filesystem/sd-filesystem.c
  35. 3 0
      applications/sd-filesystem/sd-filesystem.h
  36. 5 2
      applications/subghz/subghz_static.c
  37. 55 55
      applications/tests/irda_decoder/test_data/irda_decoder_nec_test_data.h
  38. 14 17
      applications/tests/test_index.c
  39. 0 8
      lib/ST25RFAL002/platform.h
  40. 8 14
      lib/irda/nec/irda_decoder_nec.c

+ 7 - 23
applications/accessor/accessor-app.cpp

@@ -8,8 +8,6 @@ void AccessorApp::run(void) {
     bool consumed;
     bool exit = false;
 
-    notify_init();
-
     wiegand.begin();
     onewire_master.start();
 
@@ -36,9 +34,14 @@ void AccessorApp::run(void) {
 AccessorApp::AccessorApp()
     : onewire_master{&ibutton_gpio} {
     api_hal_power_insomnia_enter();
+    notification = static_cast<NotificationApp*>(furi_record_open("notification"));
+    notify_init();
+    api_hal_power_enable_otg();
 }
 
 AccessorApp::~AccessorApp() {
+    api_hal_power_disable_otg();
+    furi_record_close("notification");
     api_hal_power_insomnia_exit();
 }
 
@@ -102,11 +105,6 @@ AccessorApp::Scene AccessorApp::get_previous_scene() {
 /***************************** NOTIFY *******************************/
 
 void AccessorApp::notify_init() {
-    // TODO open record
-    const GpioPin* vibro_record = &vibro_gpio;
-    hal_gpio_init(vibro_record, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-    hal_gpio_write(vibro_record, false);
-
     GPIO_InitTypeDef GPIO_InitStruct = {0};
 
     GPIO_InitStruct.Pin = PB3_Pin;
@@ -118,35 +116,21 @@ void AccessorApp::notify_init() {
 }
 
 void AccessorApp::notify_green_blink() {
-    api_hal_light_set(LightGreen, 0xFF);
-    delay(10);
-    api_hal_light_set(LightGreen, 0x00);
-}
-
-void AccessorApp::notify_green_on() {
-    api_hal_light_set(LightGreen, 0xFF);
-}
-
-void AccessorApp::notify_green_off() {
-    api_hal_light_set(LightGreen, 0x00);
+    notification_message(notification, &sequence_blink_green_10);
 }
 
 void AccessorApp::notify_success() {
-    api_hal_light_set(LightBacklight, 0xFF);
+    notification_message(notification, &sequence_success);
 
     hal_pwm_set(0.5, 1760 / 2, &htim2, TIM_CHANNEL_2);
-    notify_green_on();
     delay(100);
     hal_pwm_stop(&htim2, TIM_CHANNEL_2);
-    notify_green_off();
 
     delay(100);
 
     hal_pwm_set(0.5, 1760, &htim2, TIM_CHANNEL_2);
-    notify_green_on();
     delay(100);
     hal_pwm_stop(&htim2, TIM_CHANNEL_2);
-    notify_green_off();
 }
 
 /*************************** TEXT STORE *****************************/

+ 4 - 2
applications/accessor/accessor-app.h

@@ -9,6 +9,8 @@
 
 #include <one_wire_master.h>
 
+#include <notification/notification-messages.h>
+
 class AccessorApp {
 public:
     void run(void);
@@ -29,8 +31,6 @@ public:
 
     void notify_init();
     void notify_green_blink();
-    void notify_green_on();
-    void notify_green_off();
 
     void notify_success();
 
@@ -55,4 +55,6 @@ private:
 
     WIEGAND wiegand;
     OneWireMaster onewire_master;
+
+    NotificationApp* notification;
 };

+ 5 - 0
applications/applications.c

@@ -38,6 +38,7 @@ int32_t passport(void* p);
 int32_t app_accessor(void* p);
 int32_t internal_storage_task(void* p);
 int32_t app_archive(void* p);
+int32_t notification_app(void* p);
 
 // On system start hooks declaration
 void nfc_cli_init();
@@ -155,6 +156,10 @@ const FlipperApplication FLIPPER_SERVICES[] = {
     {.app = app_accessor, .name = "accessor", .stack_size = 4096, .icon = A_Plugins_14},
 #endif
 
+#ifdef SRV_NOTIFICATION
+    {.app = notification_app, .name = "notification", .stack_size = 1024, .icon = A_Plugins_14},
+#endif
+
 };
 
 const size_t FLIPPER_SERVICES_COUNT = sizeof(FLIPPER_SERVICES) / sizeof(FlipperApplication);

+ 6 - 0
applications/applications.mk

@@ -19,6 +19,7 @@ SRV_CLI = 1
 SRV_SD_FILESYSTEM = 1
 SRV_INTERNAL_STORAGE = 1
 SRV_DOLPHIN = 1
+SRV_NOTIFICATION = 1
 
 # Main Apps
 APP_IRDA  = 1
@@ -328,3 +329,8 @@ ifeq ($(SRV_CLI), 1)
 SRV_GUI		= 1
 CFLAGS		+= -DSRV_CLI
 endif
+
+SRV_NOTIFICATION ?= 0
+ifeq ($(SRV_NOTIFICATION), 1)
+CFLAGS		+= -DSRV_NOTIFICATION
+endif

+ 5 - 3
applications/backlight-control/backlight-control.c

@@ -1,5 +1,6 @@
 #include <furi.h>
 #include <api-hal.h>
+#include <notification/notification-messages.h>
 
 #define BACKLIGHT_TIME 30000
 #define BACKLIGHT_FLAG_ACTIVITY 0x00000001U
@@ -10,18 +11,19 @@ static void event_cb(const void* value, void* ctx) {
 
 int32_t backlight_control(void* p) {
     // open record
+    NotificationApp* notifications = furi_record_open("notification");
     PubSub* event_record = furi_record_open("input_events");
     subscribe_pubsub(event_record, event_cb, (void*)osThreadGetId());
 
-    api_hal_light_set(LightBacklight, 0xFF);
+    notification_internal_message(notifications, &sequence_display_on);
 
     while(1) {
         // wait for event
         if(osThreadFlagsWait(BACKLIGHT_FLAG_ACTIVITY, osFlagsWaitAny, BACKLIGHT_TIME) ==
            BACKLIGHT_FLAG_ACTIVITY) {
-            api_hal_light_set(LightBacklight, 0xFF);
+            notification_internal_message(notifications, &sequence_display_on);
         } else {
-            api_hal_light_set(LightBacklight, 0x00);
+            notification_internal_message(notifications, &sequence_display_off);
         }
     }
 

+ 26 - 8
applications/cli/cli_commands.c

@@ -4,6 +4,7 @@
 #include <rtc.h>
 #include <task-control-block.h>
 #include <time.h>
+#include <notification/notification-messages.h>
 
 void cli_command_help(Cli* cli, string_t args, void* context) {
     (void)args;
@@ -106,9 +107,13 @@ void cli_command_hw_info(Cli* cli, string_t args, void* context) {
 
 void cli_command_vibro(Cli* cli, string_t args, void* context) {
     if(!string_cmp(args, "0")) {
-        api_hal_vibro_on(false);
+        NotificationApp* notification = furi_record_open("notification");
+        notification_message_block(notification, &sequence_reset_vibro);
+        furi_record_close("notification");
     } else if(!string_cmp(args, "1")) {
-        api_hal_vibro_on(true);
+        NotificationApp* notification = furi_record_open("notification");
+        notification_message_block(notification, &sequence_set_vibro_on);
+        furi_record_close("notification");
     } else {
         printf("Wrong input");
     }
@@ -116,7 +121,7 @@ void cli_command_vibro(Cli* cli, string_t args, void* context) {
 
 void cli_command_led(Cli* cli, string_t args, void* context) {
     // Get first word as light name
-    Light light;
+    NotificationMessage notification_led_message;
     string_t light_name;
     string_init(light_name);
     size_t ws = string_search_char(args, ' ');
@@ -131,13 +136,13 @@ void cli_command_led(Cli* cli, string_t args, void* context) {
     }
     // Check light name
     if(!string_cmp(light_name, "r")) {
-        light = LightRed;
+        notification_led_message.type = NotificationMessageTypeLedRed;
     } else if(!string_cmp(light_name, "g")) {
-        light = LightGreen;
+        notification_led_message.type = NotificationMessageTypeLedGreen;
     } else if(!string_cmp(light_name, "b")) {
-        light = LightBlue;
+        notification_led_message.type = NotificationMessageTypeLedBlue;
     } else if(!string_cmp(light_name, "bl")) {
-        light = LightBacklight;
+        notification_led_message.type = NotificationMessageTypeLedDisplay;
     } else {
         printf("Wrong argument");
         string_clear(light_name);
@@ -151,7 +156,20 @@ void cli_command_led(Cli* cli, string_t args, void* context) {
         printf("Wrong argument");
         return;
     }
-    api_hal_light_set(light, value);
+
+    // Set led value
+    notification_led_message.data.led.value = value;
+
+    // Form notification sequence
+    const NotificationSequence notification_sequence = {
+        &notification_led_message,
+        NULL,
+    };
+
+    // Send notification
+    NotificationApp* notification = furi_record_open("notification");
+    notification_internal_message_block(notification, &notification_sequence);
+    furi_record_close("notification");
 }
 
 void cli_command_gpio_set(Cli* cli, string_t args, void* context) {

+ 20 - 20
applications/examples/blink.c

@@ -4,6 +4,10 @@
 #include <gui/gui.h>
 #include <input/input.h>
 
+#include <notification/notification-messages.h>
+
+#define BLINK_COLOR_COUNT 7
+
 typedef enum {
     EventTypeTick,
     EventTypeKey,
@@ -14,12 +18,6 @@ typedef struct {
     InputEvent input;
 } BlinkEvent;
 
-void rgb_set(bool r, bool g, bool b) {
-    api_hal_light_set(LightRed, r ? 0xFF : 0x00);
-    api_hal_light_set(LightGreen, g ? 0xFF : 0x00);
-    api_hal_light_set(LightBlue, b ? 0xFF : 0x00);
-}
-
 void blink_update(void* ctx) {
     furi_assert(ctx);
     osMessageQueueId_t event_queue = ctx;
@@ -57,16 +55,18 @@ int32_t application_blink(void* p) {
     Gui* gui = furi_record_open("gui");
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
-    bool blink_color[][3] = {
-        {1, 0, 0},
-        {0, 1, 0},
-        {0, 0, 1},
-        {1, 1, 0},
-        {0, 1, 1},
-        {1, 0, 1},
-        {1, 1, 1},
-        {0, 0, 0},
+    NotificationApp* notifications = furi_record_open("notification");
+
+    const NotificationSequence* colors[BLINK_COLOR_COUNT] = {
+        &sequence_blink_red_100,
+        &sequence_blink_green_100,
+        &sequence_blink_blue_100,
+        &sequence_blink_yellow_100,
+        &sequence_blink_cyan_100,
+        &sequence_blink_magenta_100,
+        &sequence_blink_white_100,
     };
+
     uint8_t state = 0;
     BlinkEvent event;
 
@@ -74,7 +74,7 @@ int32_t application_blink(void* p) {
         furi_check(osMessageQueueGet(event_queue, &event, NULL, osWaitForever) == osOK);
         if(event.type == EventTypeKey) {
             if((event.input.type == InputTypeShort) && (event.input.key == InputKeyBack)) {
-                rgb_set(0, 0, 0);
+                furi_record_close("notification");
                 view_port_enabled_set(view_port, false);
                 gui_remove_view_port(gui, view_port);
                 view_port_free(view_port);
@@ -84,12 +84,12 @@ int32_t application_blink(void* p) {
                 return 0;
             }
         } else {
-            if(state < sizeof(blink_color) / sizeof(blink_color[0])) {
-                state++;
-            } else {
+            notification_message(notifications, colors[state]);
+
+            state++;
+            if(state >= BLINK_COLOR_COUNT) {
                 state = 0;
             }
-            rgb_set(blink_color[state][0], blink_color[state][1], blink_color[state][2]);
         }
     }
 

+ 0 - 46
applications/examples/strobe.c

@@ -1,46 +0,0 @@
-#include <furi.h>
-#include <api-hal.h>
-#include <input/input.h>
-
-static void event_cb(const void* value, void* ctx) {
-    const InputEvent* event = value;
-
-    uint32_t* delay_time = acquire_mutex(ctx, 0);
-    if(delay_time == NULL) return;
-
-    if(event->key == InputKeyUp && *delay_time < 1000) {
-        *delay_time += 5;
-    }
-
-    if(event->key == InputKeyDown && *delay_time > 10) {
-        *delay_time -= 5;
-    }
-    release_mutex(ctx, delay_time);
-}
-
-void application_strobe(void* p) {
-    // WAT
-    osDelay(100);
-
-    uint32_t delay_time_holder = 100;
-    ValueMutex delay_mutex;
-    init_mutex(&delay_mutex, &delay_time_holder, sizeof(delay_time_holder));
-
-    PubSub* event_record = furi_record_open("input_events");
-    subscribe_pubsub(event_record, event_cb, &delay_mutex);
-
-    while(1) {
-        uint32_t delay_time = 100;
-        read_mutex_block(&delay_mutex, &delay_time, sizeof(delay_time));
-
-        api_hal_light_set(LightRed, 0x00);
-        api_hal_light_set(LightGreen, 0x00);
-        api_hal_light_set(LightBlue, 0x00);
-        osDelay(delay_time / 10);
-
-        api_hal_light_set(LightRed, 0xFF);
-        api_hal_light_set(LightGreen, 0xFF);
-        api_hal_light_set(LightBlue, 0xFF);
-        osDelay(delay_time);
-    }
-}

+ 10 - 9
applications/examples/vibro.c

@@ -3,6 +3,7 @@
 
 #include <gui/gui.h>
 #include <input/input.h>
+#include <notification/notification-messages.h>
 
 typedef struct {
     InputEvent input;
@@ -27,7 +28,6 @@ void vibro_input_callback(InputEvent* input_event, void* ctx) {
 }
 
 int32_t application_vibro(void* p) {
-    GpioPin* gpio = (GpioPin*)&vibro_gpio;
     osMessageQueueId_t event_queue = osMessageQueueNew(8, sizeof(VibroEvent), NULL);
 
     // Configure view port
@@ -40,15 +40,16 @@ int32_t application_vibro(void* p) {
     Gui* gui = furi_record_open("gui");
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
-    hal_gpio_init(gpio, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-    hal_gpio_write(gpio, false);
+    NotificationApp* notification = furi_record_open("notification");
+
     VibroEvent event;
 
     while(1) {
         furi_check(osMessageQueueGet(event_queue, &event, NULL, osWaitForever) == osOK);
         if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
-            hal_gpio_write(gpio, false);
-            api_hal_light_set(LightGreen, 0);
+            notification_message(notification, &sequence_reset_vibro);
+            notification_message(notification, &sequence_reset_green);
+            furi_record_close("notification");
             view_port_enabled_set(view_port, false);
             gui_remove_view_port(gui, view_port);
             view_port_free(view_port);
@@ -58,11 +59,11 @@ int32_t application_vibro(void* p) {
         }
         if(event.input.key == InputKeyOk) {
             if(event.input.type == InputTypePress) {
-                hal_gpio_write(gpio, true);
-                api_hal_light_set(LightGreen, 255);
+                notification_message(notification, &sequence_set_vibro_on);
+                notification_message(notification, &sequence_set_green_255);
             } else if(event.input.type == InputTypeRelease) {
-                hal_gpio_write(gpio, false);
-                api_hal_light_set(LightGreen, 0);
+                notification_message(notification, &sequence_reset_vibro);
+                notification_message(notification, &sequence_reset_green);
             }
         }
     }

+ 7 - 4
applications/gpio-tester/gpio-tester.c

@@ -3,6 +3,7 @@
 
 #include <gui/gui.h>
 #include <input/input.h>
+#include <notification/notification-messages.h>
 
 typedef struct {
     const char* name;
@@ -81,6 +82,8 @@ int32_t app_gpio_test(void* p) {
     Gui* gui = furi_record_open("gui");
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
+    NotificationApp* notification = furi_record_open("notification");
+
     // configure pin
     for(uint8_t i = 0; i < sizeof(GPIO_PINS) / sizeof(GPIO_PINS[0]); i++) {
         hal_gpio_init(
@@ -97,8 +100,8 @@ int32_t app_gpio_test(void* p) {
                 if(event.value.input.type == InputTypeShort &&
                    event.value.input.key == InputKeyBack) {
                     printf("[gpio-tester] bye!\r\n");
-
-                    api_hal_light_set(LightGreen, 0x00);
+                    notification_message(notification, &sequence_reset_green);
+                    furi_record_close("notification");
 
                     view_port_enabled_set(view_port, false);
                     gui_remove_view_port(gui, view_port);
@@ -124,10 +127,10 @@ int32_t app_gpio_test(void* p) {
                 if(event.value.input.key == InputKeyOk) {
                     if(event.value.input.type == InputTypePress) {
                         hal_gpio_write((GpioPin*)&GPIO_PINS[state->gpio_index].pin, true);
-                        api_hal_light_set(LightGreen, 0xFF);
+                        notification_message(notification, &sequence_set_green_255);
                     } else if(event.value.input.type == InputTypeRelease) {
                         hal_gpio_write((GpioPin*)&GPIO_PINS[state->gpio_index].pin, false);
-                        api_hal_light_set(LightGreen, 0x00);
+                        notification_message(notification, &sequence_reset_green);
                     }
                 }
             }

+ 4 - 2
applications/ibutton/helpers/key-reader.cpp

@@ -61,6 +61,7 @@ bool KeyReader::read_key(iButtonKeyType* key_type, uint8_t* data, uint8_t data_s
 
     switch(read_mode) {
     case ReadMode::DALLAS:
+        __disable_irq();
         if(onewire_master->search(data)) {
             onewire_master->reset_search();
             readed = true;
@@ -68,6 +69,7 @@ bool KeyReader::read_key(iButtonKeyType* key_type, uint8_t* data, uint8_t data_s
         } else {
             onewire_master->reset_search();
         }
+        __enable_irq();
         break;
     case ReadMode::CYFRAL_METAKOM:
         if(cyfral_decoder.read(data, 2)) {
@@ -89,7 +91,7 @@ bool KeyReader::verify_key(iButtonKeyType key_type, const uint8_t* const data, u
     switch(key_type) {
     case iButtonKeyType::KeyDallas:
         switch_to(ReadMode::DALLAS);
-
+        __disable_irq();
         if(onewire_master->reset()) {
             onewire_master->write(DS1990::CMD_READ_ROM);
             for(uint8_t i = 0; i < data_size; i++) {
@@ -101,7 +103,7 @@ bool KeyReader::verify_key(iButtonKeyType key_type, const uint8_t* const data, u
             result = false;
             break;
         }
-
+        __enable_irq();
         break;
 
     default:

+ 21 - 50
applications/ibutton/ibutton-app.cpp

@@ -178,7 +178,6 @@ void iButtonApp::cli_send_event(iButtonApp::CliEvent scene) {
 }
 
 iButtonApp::iButtonApp() {
-    notify_init();
     api_hal_power_insomnia_enter();
 
     cli_event_result = osMessageQueueNew(1, sizeof(iButtonApp::Scene), NULL);
@@ -186,6 +185,8 @@ iButtonApp::iButtonApp() {
     sd_ex_api = static_cast<SdCard_Api*>(furi_record_open("sdcard-ex"));
     fs_api = static_cast<FS_Api*>(furi_record_open("sdcard"));
     cli = static_cast<Cli*>(furi_record_open("cli"));
+    notification = static_cast<NotificationApp*>(furi_record_open("notification"));
+
     auto callback = cbc::obtain_connector(this, &iButtonApp::cli_cmd_callback);
     cli_add_command(cli, "tm", callback, cli);
 
@@ -194,10 +195,13 @@ iButtonApp::iButtonApp() {
 }
 
 iButtonApp::~iButtonApp() {
+    cli_delete_command(cli, "tm");
+
     furi_record_close("sdcard-ex");
     furi_record_close("sdcard");
-    cli_delete_command(cli, "tm");
     furi_record_close("cli");
+    furi_record_close("notification");
+
     osMessageQueueDelete(cli_event_result);
 
     for(std::map<Scene, iButtonScene*>::iterator it = scenes.begin(); it != scenes.end(); ++it) {
@@ -294,73 +298,40 @@ uint8_t iButtonApp::get_file_name_size() {
     return file_name_size;
 }
 
-void iButtonApp::notify_init() {
-    // TODO open record
-    const GpioPin* vibro_record = &vibro_gpio;
-    hal_gpio_init(vibro_record, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-    hal_gpio_write(vibro_record, false);
-}
-
 void iButtonApp::notify_green_blink() {
-    notify_green_on();
-    delay(10);
-    notify_green_off();
+    notification_message(notification, &sequence_blink_green_10);
 }
 
 void iButtonApp::notify_yellow_blink() {
-    notify_red_on();
-    notify_green_on();
-    delay(10);
-    notify_green_off();
-    notify_red_off();
+    notification_message(notification, &sequence_blink_yellow_10);
 }
 
 void iButtonApp::notify_red_blink() {
-    notify_red_on();
-    delay(10);
-    notify_red_off();
-}
-
-void iButtonApp::notify_green_on() {
-    api_hal_light_set(LightGreen, 0xFF);
-}
-
-void iButtonApp::notify_green_off() {
-    api_hal_light_set(LightGreen, 0x00);
+    notification_message(notification, &sequence_blink_red_10);
 }
 
-void iButtonApp::notify_red_on() {
-    api_hal_light_set(LightRed, 0xFF);
+void iButtonApp::notify_error() {
+    notification_message(notification, &sequence_error);
 }
 
-void iButtonApp::notify_red_off() {
-    api_hal_light_set(LightRed, 0x00);
+void iButtonApp::notify_success() {
+    notification_message(notification, &sequence_success);
 }
 
-void iButtonApp::notify_error() {
-    notify_vibro_on();
-    delay(50);
-    notify_vibro_off();
-    delay(100);
-    notify_vibro_on();
-    delay(50);
-    notify_vibro_off();
+void iButtonApp::notify_green_on() {
+    notification_message_block(notification, &sequence_set_green_255);
 }
 
-void iButtonApp::notify_success() {
-    notify_vibro_on();
-    hal_pwm_set(0.5, 1760, &SPEAKER_TIM, SPEAKER_CH);
-    delay(50);
-    hal_pwm_stop(&SPEAKER_TIM, SPEAKER_CH);
-    notify_vibro_off();
+void iButtonApp::notify_green_off() {
+    notification_message(notification, &sequence_reset_green);
 }
 
-void iButtonApp::notify_vibro_on() {
-    hal_gpio_write(&vibro_gpio, true);
+void iButtonApp::notify_red_on() {
+    notification_message_block(notification, &sequence_set_red_255);
 }
 
-void iButtonApp::notify_vibro_off() {
-    hal_gpio_write(&vibro_gpio, false);
+void iButtonApp::notify_red_off() {
+    notification_message(notification, &sequence_reset_red);
 }
 
 void iButtonApp::set_text_store(const char* text...) {

+ 6 - 7
applications/ibutton/ibutton-app.h

@@ -36,6 +36,8 @@
 #include "maxim_crc.h"
 #include "ibutton-key.h"
 
+#include <notification/notification-messages.h>
+
 class iButtonApp {
 public:
     void run(void);
@@ -92,17 +94,13 @@ public:
     void notify_yellow_blink();
     void notify_red_blink();
 
+    void notify_error();
+    void notify_success();
     void notify_green_on();
     void notify_green_off();
     void notify_red_on();
     void notify_red_off();
 
-    void notify_error();
-    void notify_success();
-
-    void notify_vibro_on();
-    void notify_vibro_off();
-
     void set_text_store(const char* text...);
     char* get_text_store();
     uint8_t get_text_store_size();
@@ -160,7 +158,8 @@ private:
     static const uint8_t text_store_size = 128;
     char text_store[text_store_size + 1];
 
-    void notify_init();
+    NotificationApp* notification;
+
     bool read_hex_byte(string_t arg, uint8_t* byte);
     void print_key_data(void);
 };

+ 3 - 0
applications/ibutton/scene/ibutton-scene-read-crc-error.cpp

@@ -33,6 +33,7 @@ void iButtonSceneReadCRCError::on_enter(iButtonApp* app) {
 
     view_manager->switch_to(iButtonAppViewManager::Type::iButtonAppViewDialogEx);
     app->notify_error();
+    app->notify_red_on();
 }
 
 bool iButtonSceneReadCRCError::on_event(iButtonApp* app, iButtonEvent* event) {
@@ -62,6 +63,8 @@ void iButtonSceneReadCRCError::on_exit(iButtonApp* app) {
     dialog_ex_set_left_button_text(dialog_ex, NULL);
     dialog_ex_set_result_callback(dialog_ex, NULL);
     dialog_ex_set_context(dialog_ex, NULL);
+
+    app->notify_red_off();
 }
 
 void iButtonSceneReadCRCError::dialog_ex_callback(DialogExResult result, void* context) {

+ 3 - 0
applications/ibutton/scene/ibutton-scene-read-not-key-error.cpp

@@ -33,6 +33,7 @@ void iButtonSceneReadNotKeyError::on_enter(iButtonApp* app) {
 
     view_manager->switch_to(iButtonAppViewManager::Type::iButtonAppViewDialogEx);
     app->notify_error();
+    app->notify_red_on();
 }
 
 bool iButtonSceneReadNotKeyError::on_event(iButtonApp* app, iButtonEvent* event) {
@@ -62,6 +63,8 @@ void iButtonSceneReadNotKeyError::on_exit(iButtonApp* app) {
     dialog_ex_set_left_button_text(dialog_ex, NULL);
     dialog_ex_set_result_callback(dialog_ex, NULL);
     dialog_ex_set_context(dialog_ex, NULL);
+
+    app->notify_red_off();
 }
 
 void iButtonSceneReadNotKeyError::dialog_ex_callback(DialogExResult result, void* context) {

+ 3 - 1
applications/ibutton/scene/ibutton-scene-read-success.cpp

@@ -42,8 +42,9 @@ void iButtonSceneReadSuccess::on_enter(iButtonApp* app) {
     dialog_ex_set_context(dialog_ex, app);
 
     view_manager->switch_to(iButtonAppViewManager::Type::iButtonAppViewDialogEx);
-    app->notify_green_on();
+
     app->notify_success();
+    app->notify_green_on();
 }
 
 bool iButtonSceneReadSuccess::on_event(iButtonApp* app, iButtonEvent* event) {
@@ -74,6 +75,7 @@ void iButtonSceneReadSuccess::on_exit(iButtonApp* app) {
     dialog_ex_set_result_callback(dialog_ex, NULL);
     dialog_ex_set_context(dialog_ex, NULL);
     dialog_ex_set_icon(dialog_ex, -1, -1, I_ButtonCenter_7x7);
+
     app->notify_green_off();
 }
 

+ 3 - 2
applications/ibutton/scene/ibutton-scene-write-success.cpp

@@ -19,8 +19,9 @@ void iButtonSceneWriteSuccess::on_enter(iButtonApp* app) {
     popup_enable_timeout(popup);
 
     view_manager->switch_to(iButtonAppViewManager::Type::iButtonAppViewPopup);
-    app->notify_green_on();
+
     app->notify_success();
+    app->notify_green_on();
 }
 
 bool iButtonSceneWriteSuccess::on_event(iButtonApp* app, iButtonEvent* event) {
@@ -44,7 +45,6 @@ void iButtonSceneWriteSuccess::on_exit(iButtonApp* app) {
     popup_disable_timeout(popup);
     popup_set_context(popup, NULL);
     popup_set_callback(popup, NULL);
-    app->notify_green_off();
 }
 
 void iButtonSceneWriteSuccess::popup_callback(void* context) {
@@ -52,4 +52,5 @@ void iButtonSceneWriteSuccess::popup_callback(void* context) {
     iButtonEvent event;
     event.type = iButtonEvent::Type::EventTypeBack;
     app->get_view_manager()->send_event(&event);
+    app->notify_green_off();
 }

+ 8 - 6
applications/irda/irda_app.c

@@ -3,6 +3,7 @@
 #include <gui/gui.h>
 #include <input/input.h>
 #include <cli/cli.h>
+#include <notification/notification-messages.h>
 
 #include <api-hal-irda.h>
 #include "irda.h"
@@ -284,7 +285,8 @@ void irda_rx_callback(void* ctx, bool level, uint32_t duration) {
 
     if(message) {
         event.value.rx = *message;
-        furi_assert(osOK == osMessageQueuePut(isr_context->event_queue, &event, 0, 0));
+        osStatus_t result = osMessageQueuePut(isr_context->event_queue, &event, 0, 0);
+        furi_assert(osOK == result);
     }
 }
 
@@ -305,6 +307,8 @@ int32_t irda(void* p) {
     irda_app.cli_ir_rx_queue = osMessageQueueNew(1, sizeof(IrDAPacket), NULL);
     irda_app.cli_cmd_is_active = false;
 
+    NotificationApp* notification = furi_record_open("notification");
+
     for(uint8_t i = 0; i < IRDA_PACKET_COUNT; i++) {
         init_packet(&_state, i, 0, 0, 0);
     }
@@ -367,6 +371,7 @@ int32_t irda(void* p) {
                     cli_delete_command(irda_app.cli, "ir_rx");
                     cli_delete_command(irda_app.cli, "ir_tx");
                     furi_record_close("cli");
+                    furi_record_close("notification");
                     api_hal_irda_rx_irq_deinit();
                     irda_free_decoder(isr_context.handler);
 
@@ -394,9 +399,7 @@ int32_t irda(void* p) {
                 view_port_update(view_port);
 
             } else if(event.type == EventTypeRX) {
-                api_hal_light_set(LightRed, 0xFF);
-                delay(60);
-                api_hal_light_set(LightRed, 0xFF);
+                notification_message(notification, &sequence_blink_red_10);
 
                 // save only if we in packet mode
                 State* state = (State*)acquire_mutex_block(&state_mutex);
@@ -422,8 +425,7 @@ int32_t irda(void* p) {
                 view_port_update(view_port);
 
                 // blink anyway
-                api_hal_light_set(LightGreen, 0xFF);
-                api_hal_light_set(LightGreen, 0x00);
+                notification_message(notification, &sequence_blink_green_10);
             }
 
         } else {

+ 4 - 7
applications/irda_monitor/irda_monitor.c

@@ -2,6 +2,7 @@
 #include <furi.h>
 #include <api-hal-irda.h>
 #include <api-hal.h>
+#include <notification/notification-messages.h>
 
 #define IRDA_TIMINGS_SIZE 2000
 
@@ -30,6 +31,7 @@ int32_t irda_monitor_app(void* p) {
     static uint32_t counter = 0;
 
     IrdaDelaysArray* delays = furi_alloc(sizeof(IrdaDelaysArray));
+    NotificationApp* notification = furi_record_open("notification");
 
     api_hal_irda_rx_irq_init();
     api_hal_irda_rx_irq_set_callback(irda_rx_callback, delays);
@@ -38,13 +40,8 @@ int32_t irda_monitor_app(void* p) {
         delay(20);
 
         if(counter != delays->timing_cnt) {
-            api_hal_light_set(LightRed, 0x00);
-            api_hal_light_set(LightGreen, 0x00);
-            api_hal_light_set(LightBlue, 0xFF);
-            delay(20);
-            api_hal_light_set(LightRed, 0x00);
-            api_hal_light_set(LightGreen, 0x00);
-            api_hal_light_set(LightBlue, 0x00);
+            notification_message(notification, &sequence_blink_blue_100);
+
             counter = delays->timing_cnt;
         }
 

+ 6 - 16
applications/lf-rfid/lf-rfid-app.cpp

@@ -27,6 +27,7 @@ void LfrfidApp::run(void) {
 
 LfrfidApp::LfrfidApp() {
     api_hal_power_insomnia_enter();
+    notification = static_cast<NotificationApp*>(furi_record_open("notification"));
 }
 
 LfrfidApp::~LfrfidApp() {
@@ -35,6 +36,8 @@ LfrfidApp::~LfrfidApp() {
         scenes.erase(it);
     }
 
+    furi_record_close("notification");
+
     api_hal_power_insomnia_exit();
 }
 
@@ -97,25 +100,12 @@ LfrfidApp::Scene LfrfidApp::get_previous_scene() {
 
 /***************************** NOTIFY *******************************/
 
-void LfrfidApp::notify_init() {
-    // TODO open record
-    const GpioPin* vibro_record = &vibro_gpio;
-    hal_gpio_init(vibro_record, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
-    hal_gpio_write(vibro_record, false);
-}
-
 void LfrfidApp::notify_green_blink() {
-    api_hal_light_set(LightGreen, 0xFF);
-    delay(10);
-    api_hal_light_set(LightGreen, 0x00);
-}
-
-void LfrfidApp::notify_green_on() {
-    api_hal_light_set(LightGreen, 0xFF);
+    notification_message(notification, &sequence_blink_green_10);
 }
 
-void LfrfidApp::notify_green_off() {
-    api_hal_light_set(LightGreen, 0x00);
+void LfrfidApp::notify_success() {
+    notification_message(notification, &sequence_success);
 }
 
 /*************************** TEXT STORE *****************************/

+ 4 - 3
applications/lf-rfid/lf-rfid-app.h

@@ -15,6 +15,8 @@
 #include "helpers/rfid-reader.h"
 #include "helpers/rfid-timer-emulator.h"
 
+#include <notification/notification-messages.h>
+
 class LfrfidApp {
 public:
     void run(void);
@@ -40,10 +42,8 @@ public:
     bool switch_to_previous_scene(uint8_t count = 1);
     Scene get_previous_scene();
 
-    void notify_init();
     void notify_green_blink();
-    void notify_green_on();
-    void notify_green_off();
+    void notify_success();
 
     char* get_text_store();
     uint8_t get_text_store_size();
@@ -72,6 +72,7 @@ private:
     static const uint8_t text_store_size = 128;
     char text_store[text_store_size + 1];
 
+    NotificationApp* notification;
     RfidReader reader;
     RfidTimerEmulator emulator;
     RfidWriter writer;

+ 2 - 2
applications/lf-rfid/scene/lf-rfid-scene-read-normal.cpp

@@ -25,13 +25,13 @@ bool LfrfidSceneReadNormal::on_event(LfrfidApp* app, LfrfidEvent* event) {
         LfrfidKeyType type;
 
         if(app->get_reader()->read(&type, data, data_size)) {
-            app->notify_green_blink();
-
             if(memcmp(last_data, data, data_size) == 0) {
                 success_reads++;
+                app->notify_green_blink();
             } else {
                 success_reads = 1;
                 memcpy(last_data, data, data_size);
+                app->notify_success();
             }
 
             switch(type) {

+ 1 - 1
applications/lf-rfid/scene/lf-rfid-scene-write.cpp

@@ -31,7 +31,7 @@ bool LfrfidSceneWrite::on_event(LfrfidApp* app, LfrfidEvent* event) {
         app->get_writer()->start();
         app->get_writer()->write_em(em_data);
         app->get_writer()->stop();
-        delay(100);
+        delay(200);
         app->get_reader()->start(RfidReader::Type::Normal);
     } else {
         uint8_t data[LFRFID_KEY_SIZE];

+ 37 - 0
applications/notification/notification-app-api.c

@@ -0,0 +1,37 @@
+#include <furi.h>
+#include <api-hal.h>
+#include "notification.h"
+#include "notification-messages.h"
+#include "notification-app.h"
+
+void notification_message(NotificationApp* app, const NotificationSequence* sequence) {
+    NotificationAppMessage m = {
+        .type = NotificationLayerMessage, .sequence = sequence, .back_event = NULL};
+    furi_check(osMessageQueuePut(app->queue, &m, 0, osWaitForever) == osOK);
+};
+
+void notification_internal_message(NotificationApp* app, const NotificationSequence* sequence) {
+    NotificationAppMessage m = {
+        .type = InternalLayerMessage, .sequence = sequence, .back_event = NULL};
+    furi_check(osMessageQueuePut(app->queue, &m, 0, osWaitForever) == osOK);
+};
+
+void notification_message_block(NotificationApp* app, const NotificationSequence* sequence) {
+    NotificationAppMessage m = {
+        .type = NotificationLayerMessage,
+        .sequence = sequence,
+        .back_event = osEventFlagsNew(NULL)};
+    furi_check(osMessageQueuePut(app->queue, &m, 0, osWaitForever) == osOK);
+    osEventFlagsWait(m.back_event, NOTIFICATION_EVENT_COMPLETE, osFlagsWaitAny, osWaitForever);
+    osEventFlagsDelete(m.back_event);
+};
+
+void notification_internal_message_block(
+    NotificationApp* app,
+    const NotificationSequence* sequence) {
+    NotificationAppMessage m = {
+        .type = InternalLayerMessage, .sequence = sequence, .back_event = osEventFlagsNew(NULL)};
+    furi_check(osMessageQueuePut(app->queue, &m, 0, osWaitForever) == osOK);
+    osEventFlagsWait(m.back_event, NOTIFICATION_EVENT_COMPLETE, osFlagsWaitAny, osWaitForever);
+    osEventFlagsDelete(m.back_event);
+};

+ 371 - 0
applications/notification/notification-app.c

@@ -0,0 +1,371 @@
+#include <furi.h>
+#include <api-hal.h>
+#include "notification.h"
+#include "notification-messages.h"
+#include "notification-app.h"
+
+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;
+
+void notification_vibro_on();
+void notification_vibro_off();
+void notification_sound_on(float pwm, float freq);
+void notification_sound_off();
+
+uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value);
+uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value);
+uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app);
+
+// internal layer
+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) {
+        api_hal_light_set(layer->light, layer->value[LayerInternal]);
+    }
+}
+
+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
+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
+    api_hal_light_set(layer->light, layer->value[LayerNotification]);
+}
+
+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
+    api_hal_light_set(layer->light, layer->value[LayerInternal]);
+}
+
+void notification_reset_notification_layer(NotificationApp* app, uint8_t reset_mask) {
+    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) {
+        osTimerStart(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_display_brightness(app, values[i]));
+    }
+}
+
+// settings
+uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value) {
+    return (value * app->settings.display_brightness);
+}
+
+uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value) {
+    return (value * app->settings.led_brightness);
+}
+
+uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app) {
+    return ((float)(app->settings.display_off_delay_ms) / (1000.0f / osKernelGetTickFreq()));
+}
+
+// generics
+void notification_vibro_on() {
+    api_hal_vibro_on(true);
+}
+
+void notification_vibro_off() {
+    api_hal_vibro_on(false);
+}
+
+void notification_sound_on(float pwm, float freq) {
+    hal_pwm_set(pwm, freq, &SPEAKER_TIM, SPEAKER_CH);
+}
+
+void notification_sound_off() {
+    hal_pwm_stop(&SPEAKER_TIM, SPEAKER_CH);
+}
+
+// display timer
+static void notification_display_timer(void* ctx) {
+    furi_assert(ctx);
+    NotificationApp* app = ctx;
+    notification_message(app, &sequence_display_off);
+}
+
+// message processing
+void notification_process_notification_message(
+    NotificationApp* app,
+    NotificationAppMessage* message) {
+    uint32_t notification_message_index = 0;
+    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;
+
+    uint8_t reset_mask = 0;
+
+    while(notification_message != NULL) {
+        switch(notification_message->type) {
+        case NotificationMessageTypeLedDisplay:
+            // if on - switch on and start timer
+            // if off - switch off and stop timer
+            // on timer - switch off
+            if(notification_message->data.led.value > 0x00) {
+                notification_apply_notification_led_layer(
+                    &app->display,
+                    notification_settings_get_display_brightness(
+                        app, notification_message->data.led.value));
+            } else {
+                notification_reset_notification_led_layer(&app->display);
+                if(osTimerIsRunning(app->display_timer)) {
+                    osTimerStop(app->display_timer);
+                }
+            }
+            reset_mask |= reset_display_mask;
+            break;
+        case NotificationMessageTypeLedRed:
+            // store and send on delay or after seq
+            led_active = true;
+            led_values[0] = notification_message->data.led.value;
+            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;
+            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;
+            reset_mask |= reset_blue_mask;
+            break;
+        case NotificationMessageTypeVibro:
+            if(notification_message->data.vibro.on) {
+                notification_vibro_on();
+            } else {
+                notification_vibro_off();
+            }
+            reset_mask |= reset_vibro_mask;
+            break;
+        case NotificationMessageTypeSoundOn:
+            notification_sound_on(
+                notification_message->data.sound.pwm, notification_message->data.sound.frequency);
+            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);
+                    delay(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;
+            }
+
+            delay(notification_message->data.delay.length);
+            break;
+        case NotificationMessageTypeDoNotReset:
+            reset_notifications = false;
+            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;
+        }
+
+        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;
+
+        if(need_minimal_delay) {
+            notification_apply_notification_leds(app, led_off_values);
+            delay(minimal_delay);
+        }
+    }
+
+    if(reset_notifications) {
+        notification_reset_notification_layer(app, reset_mask);
+    }
+
+    if(message->back_event != NULL) {
+        osEventFlagsSet(message->back_event, NOTIFICATION_EVENT_COMPLETE);
+    }
+}
+
+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 NotificationMessageTypeLedDisplay:
+            notification_apply_internal_led_layer(
+                &app->display,
+                notification_settings_get_display_brightness(
+                    app, notification_message->data.led.value));
+            break;
+        case NotificationMessageTypeLedRed:
+            notification_apply_internal_led_layer(
+                &app->led[0],
+                notification_settings_get_rgb_led_brightness(
+                    app, notification_message->data.led.value));
+            break;
+        case NotificationMessageTypeLedGreen:
+            notification_apply_internal_led_layer(
+                &app->led[1],
+                notification_settings_get_rgb_led_brightness(
+                    app, notification_message->data.led.value));
+            break;
+        case NotificationMessageTypeLedBlue:
+            notification_apply_internal_led_layer(
+                &app->led[2],
+                notification_settings_get_rgb_led_brightness(
+                    app, notification_message->data.led.value));
+            break;
+        default:
+            break;
+        }
+        notification_message_index++;
+        notification_message = (*message->sequence)[notification_message_index];
+    }
+
+    if(message->back_event != NULL) {
+        osEventFlagsSet(message->back_event, NOTIFICATION_EVENT_COMPLETE);
+    }
+}
+
+// App alloc
+static NotificationApp* notification_app_alloc() {
+    NotificationApp* app = furi_alloc(sizeof(NotificationApp));
+    app->queue = osMessageQueueNew(8, sizeof(NotificationAppMessage), NULL);
+    app->display_timer = osTimerNew(notification_display_timer, osTimerOnce, app, NULL);
+
+    app->settings.display_brightness = 1.0f;
+    app->settings.led_brightness = 1.0f;
+    app->settings.display_off_delay_ms = 30000;
+
+    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;
+
+    return app;
+};
+
+// App
+int32_t notification_app(void* p) {
+    NotificationApp* app = notification_app_alloc();
+
+    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("notification", app);
+
+    NotificationAppMessage message;
+    while(1) {
+        furi_check(osMessageQueueGet(app->queue, &message, NULL, osWaitForever) == osOK);
+
+        switch(message.type) {
+        case NotificationLayerMessage:
+            notification_process_notification_message(app, &message);
+            break;
+        case InternalLayerMessage:
+            notification_process_internal_message(app, &message);
+        }
+    }
+
+    return 0;
+};

+ 46 - 0
applications/notification/notification-app.h

@@ -0,0 +1,46 @@
+#include <furi.h>
+#include <api-hal.h>
+#include "notification.h"
+#include "notification-messages.h"
+
+#define NOTIFICATION_LED_COUNT 3
+#define NOTIFICATION_EVENT_COMPLETE 0x00000001U
+
+typedef enum {
+    NotificationLayerMessage,
+    InternalLayerMessage,
+} NotificationAppMessageType;
+
+typedef struct {
+    const NotificationSequence* sequence;
+    NotificationAppMessageType type;
+    osEventFlagsId_t back_event;
+} NotificationAppMessage;
+
+typedef enum {
+    LayerInternal = 0,
+    LayerNotification = 1,
+    LayerMAX = 2,
+} NotificationLedLayerIndex;
+
+typedef struct {
+    uint8_t value[LayerMAX];
+    NotificationLedLayerIndex index;
+    Light light;
+} NotificationLedLayer;
+
+typedef struct {
+    float display_brightness;
+    float led_brightness;
+    uint32_t display_off_delay_ms;
+} NotificationSettings;
+
+struct NotificationApp {
+    osMessageQueueId_t queue;
+    osTimerId_t display_timer;
+
+    NotificationLedLayer display;
+    NotificationLedLayer led[NOTIFICATION_LED_COUNT];
+
+    NotificationSettings settings;
+};

+ 568 - 0
applications/notification/notification-messages-notes.c

@@ -0,0 +1,568 @@
+#include "notification.h"
+
+/*
+Python script for note messages generation
+
+# coding: utf-8
+# Python script for note messages generation
+from typing import List
+
+note_names: List = ['c', 'cs', 'd', 'ds', 'e', 'f', 'fs', 'g', 'gs', 'a', 'as', 'b']
+base_note: float = 16.3515979
+cf: float = 2 ** (1.0 / 12)
+
+note: float = base_note
+for octave in range(9):
+    for name in note_names:
+        print(f"const NotificationMessage message_note_{name}{octave}" + " = {\n"
+              "\t.type = NotificationMessageTypeSoundOn,\n"
+              f"\t.data.sound.frequency = {round(note, 2)}f,\n"
+              "\t.data.sound.pwm = 0.5f,\n"
+              "};")
+        note = note * cf
+
+for octave in range(9):
+    for name in note_names:
+        print(f"extern const NotificationMessage message_note_{name}{octave};")
+*/
+
+const NotificationMessage message_note_c0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 16.35f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 17.32f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 18.35f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 19.45f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 20.6f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 21.83f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 23.12f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 24.5f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 25.96f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 27.5f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 29.14f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b0 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 30.87f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 32.7f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 34.65f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 36.71f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 38.89f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 41.2f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 43.65f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 46.25f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 49.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 51.91f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 55.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 58.27f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b1 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 61.74f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 65.41f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 69.3f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 73.42f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 77.78f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 82.41f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 87.31f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 92.5f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 98.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 103.83f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 110.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 116.54f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b2 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 123.47f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 130.81f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 138.59f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 146.83f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 155.56f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 164.81f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 174.61f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 185.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 196.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 207.65f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 220.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 233.08f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b3 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 246.94f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 261.63f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 277.18f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 293.66f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 311.13f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 329.63f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 349.23f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 369.99f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 392.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 415.3f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 440.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 466.16f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b4 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 493.88f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 523.25f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 554.37f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 587.33f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 622.25f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 659.26f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 698.46f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 739.99f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 783.99f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 830.61f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 880.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 932.33f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b5 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 987.77f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1046.5f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1108.73f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1174.66f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1244.51f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1318.51f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1396.91f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1479.98f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1567.98f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1661.22f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1760.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1864.66f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b6 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 1975.53f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 2093.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 2217.46f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 2349.32f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 2489.02f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 2637.02f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 2793.83f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 2959.96f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 3135.96f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 3322.44f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 3520.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 3729.31f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b7 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 3951.07f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_c8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 4186.01f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_cs8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 4434.92f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_d8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 4698.64f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_ds8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 4978.03f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_e8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 5274.04f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_f8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 5587.65f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_fs8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 5919.91f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_g8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 6271.93f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_gs8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 6644.88f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_a8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 7040.0f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_as8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 7458.62f,
+    .data.sound.pwm = 0.5f,
+};
+const NotificationMessage message_note_b8 = {
+    .type = NotificationMessageTypeSoundOn,
+    .data.sound.frequency = 7902.13f,
+    .data.sound.pwm = 0.5f,
+};

+ 119 - 0
applications/notification/notification-messages-notes.h

@@ -0,0 +1,119 @@
+#pragma once
+#include "notification.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const NotificationMessage message_note_c0;
+extern const NotificationMessage message_note_cs0;
+extern const NotificationMessage message_note_d0;
+extern const NotificationMessage message_note_ds0;
+extern const NotificationMessage message_note_e0;
+extern const NotificationMessage message_note_f0;
+extern const NotificationMessage message_note_fs0;
+extern const NotificationMessage message_note_g0;
+extern const NotificationMessage message_note_gs0;
+extern const NotificationMessage message_note_a0;
+extern const NotificationMessage message_note_as0;
+extern const NotificationMessage message_note_b0;
+extern const NotificationMessage message_note_c1;
+extern const NotificationMessage message_note_cs1;
+extern const NotificationMessage message_note_d1;
+extern const NotificationMessage message_note_ds1;
+extern const NotificationMessage message_note_e1;
+extern const NotificationMessage message_note_f1;
+extern const NotificationMessage message_note_fs1;
+extern const NotificationMessage message_note_g1;
+extern const NotificationMessage message_note_gs1;
+extern const NotificationMessage message_note_a1;
+extern const NotificationMessage message_note_as1;
+extern const NotificationMessage message_note_b1;
+extern const NotificationMessage message_note_c2;
+extern const NotificationMessage message_note_cs2;
+extern const NotificationMessage message_note_d2;
+extern const NotificationMessage message_note_ds2;
+extern const NotificationMessage message_note_e2;
+extern const NotificationMessage message_note_f2;
+extern const NotificationMessage message_note_fs2;
+extern const NotificationMessage message_note_g2;
+extern const NotificationMessage message_note_gs2;
+extern const NotificationMessage message_note_a2;
+extern const NotificationMessage message_note_as2;
+extern const NotificationMessage message_note_b2;
+extern const NotificationMessage message_note_c3;
+extern const NotificationMessage message_note_cs3;
+extern const NotificationMessage message_note_d3;
+extern const NotificationMessage message_note_ds3;
+extern const NotificationMessage message_note_e3;
+extern const NotificationMessage message_note_f3;
+extern const NotificationMessage message_note_fs3;
+extern const NotificationMessage message_note_g3;
+extern const NotificationMessage message_note_gs3;
+extern const NotificationMessage message_note_a3;
+extern const NotificationMessage message_note_as3;
+extern const NotificationMessage message_note_b3;
+extern const NotificationMessage message_note_c4;
+extern const NotificationMessage message_note_cs4;
+extern const NotificationMessage message_note_d4;
+extern const NotificationMessage message_note_ds4;
+extern const NotificationMessage message_note_e4;
+extern const NotificationMessage message_note_f4;
+extern const NotificationMessage message_note_fs4;
+extern const NotificationMessage message_note_g4;
+extern const NotificationMessage message_note_gs4;
+extern const NotificationMessage message_note_a4;
+extern const NotificationMessage message_note_as4;
+extern const NotificationMessage message_note_b4;
+extern const NotificationMessage message_note_c5;
+extern const NotificationMessage message_note_cs5;
+extern const NotificationMessage message_note_d5;
+extern const NotificationMessage message_note_ds5;
+extern const NotificationMessage message_note_e5;
+extern const NotificationMessage message_note_f5;
+extern const NotificationMessage message_note_fs5;
+extern const NotificationMessage message_note_g5;
+extern const NotificationMessage message_note_gs5;
+extern const NotificationMessage message_note_a5;
+extern const NotificationMessage message_note_as5;
+extern const NotificationMessage message_note_b5;
+extern const NotificationMessage message_note_c6;
+extern const NotificationMessage message_note_cs6;
+extern const NotificationMessage message_note_d6;
+extern const NotificationMessage message_note_ds6;
+extern const NotificationMessage message_note_e6;
+extern const NotificationMessage message_note_f6;
+extern const NotificationMessage message_note_fs6;
+extern const NotificationMessage message_note_g6;
+extern const NotificationMessage message_note_gs6;
+extern const NotificationMessage message_note_a6;
+extern const NotificationMessage message_note_as6;
+extern const NotificationMessage message_note_b6;
+extern const NotificationMessage message_note_c7;
+extern const NotificationMessage message_note_cs7;
+extern const NotificationMessage message_note_d7;
+extern const NotificationMessage message_note_ds7;
+extern const NotificationMessage message_note_e7;
+extern const NotificationMessage message_note_f7;
+extern const NotificationMessage message_note_fs7;
+extern const NotificationMessage message_note_g7;
+extern const NotificationMessage message_note_gs7;
+extern const NotificationMessage message_note_a7;
+extern const NotificationMessage message_note_as7;
+extern const NotificationMessage message_note_b7;
+extern const NotificationMessage message_note_c8;
+extern const NotificationMessage message_note_cs8;
+extern const NotificationMessage message_note_d8;
+extern const NotificationMessage message_note_ds8;
+extern const NotificationMessage message_note_e8;
+extern const NotificationMessage message_note_f8;
+extern const NotificationMessage message_note_fs8;
+extern const NotificationMessage message_note_g8;
+extern const NotificationMessage message_note_gs8;
+extern const NotificationMessage message_note_a8;
+extern const NotificationMessage message_note_as8;
+extern const NotificationMessage message_note_b8;
+
+#ifdef __cplusplus
+}
+#endif

+ 323 - 0
applications/notification/notification-messages.c

@@ -0,0 +1,323 @@
+#include "notification.h"
+#include "notification-messages-notes.h"
+#include <stddef.h>
+
+/*********************************** Messages **********************************/
+
+// Display
+const NotificationMessage message_display_on = {
+    .type = NotificationMessageTypeLedDisplay,
+    .data.led.value = 0xFF,
+};
+
+const NotificationMessage message_display_off = {
+    .type = NotificationMessageTypeLedDisplay,
+    .data.led.value = 0x00,
+};
+
+// Led ON
+const NotificationMessage message_red_255 = {
+    .type = NotificationMessageTypeLedRed,
+    .data.led.value = 0xFF,
+};
+
+const NotificationMessage message_green_255 = {
+    .type = NotificationMessageTypeLedGreen,
+    .data.led.value = 0xFF,
+};
+
+const NotificationMessage message_blue_255 = {
+    .type = NotificationMessageTypeLedBlue,
+    .data.led.value = 0xFF,
+};
+
+// Led OFF
+const NotificationMessage message_red_0 = {
+    .type = NotificationMessageTypeLedRed,
+    .data.led.value = 0x00,
+};
+
+const NotificationMessage message_green_0 = {
+    .type = NotificationMessageTypeLedGreen,
+    .data.led.value = 0x00,
+};
+
+const NotificationMessage message_blue_0 = {
+    .type = NotificationMessageTypeLedBlue,
+    .data.led.value = 0x00,
+};
+
+// Delay
+const NotificationMessage message_delay_10 = {
+    .type = NotificationMessageTypeDelay,
+    .data.delay.length = 10,
+};
+
+const NotificationMessage message_delay_25 = {
+    .type = NotificationMessageTypeDelay,
+    .data.delay.length = 25,
+};
+
+const NotificationMessage message_delay_50 = {
+    .type = NotificationMessageTypeDelay,
+    .data.delay.length = 50,
+};
+
+const NotificationMessage message_delay_100 = {
+    .type = NotificationMessageTypeDelay,
+    .data.delay.length = 100,
+};
+
+const NotificationMessage message_delay_250 = {
+    .type = NotificationMessageTypeDelay,
+    .data.delay.length = 250,
+};
+
+const NotificationMessage message_delay_500 = {
+    .type = NotificationMessageTypeDelay,
+    .data.delay.length = 500,
+};
+
+const NotificationMessage message_delay_1000 = {
+    .type = NotificationMessageTypeDelay,
+    .data.delay.length = 1000,
+};
+
+// Sound
+const NotificationMessage message_sound_off = {
+    .type = NotificationMessageTypeSoundOff,
+};
+
+// Vibro
+const NotificationMessage message_vibro_on = {
+    .type = NotificationMessageTypeVibro,
+    .data.vibro.on = true,
+};
+
+const NotificationMessage message_vibro_off = {
+    .type = NotificationMessageTypeVibro,
+    .data.vibro.on = false,
+};
+
+// Reset
+const NotificationMessage message_do_not_reset = {
+    .type = NotificationMessageTypeDoNotReset,
+};
+
+/****************************** Message sequences ******************************/
+
+// Reset
+const NotificationSequence sequence_reset_red = {
+    &message_red_0,
+    NULL,
+};
+
+const NotificationSequence sequence_reset_green = {
+    &message_blue_0,
+    NULL,
+};
+
+const NotificationSequence sequence_reset_blue = {
+    &message_green_0,
+    NULL,
+};
+
+const NotificationSequence sequence_reset_rgb = {
+    &message_red_0,
+    &message_blue_0,
+    &message_green_0,
+    NULL,
+};
+
+const NotificationSequence sequence_reset_display = {
+    &message_display_off,
+    NULL,
+};
+
+const NotificationSequence sequence_reset_sound = {
+    &message_sound_off,
+    NULL,
+};
+
+const NotificationSequence sequence_reset_vibro = {
+    &message_vibro_off,
+    NULL,
+};
+
+// Vibro
+const NotificationSequence sequence_set_vibro_on = {
+    &message_vibro_on,
+    &message_do_not_reset,
+    NULL,
+};
+
+// Display
+const NotificationSequence sequence_display_on = {
+    &message_display_on,
+    NULL,
+};
+
+const NotificationSequence sequence_display_off = {
+    &message_display_off,
+    NULL,
+};
+
+// Charging
+const NotificationSequence sequence_charging = {
+    &message_red_255,
+    &message_green_0,
+    NULL,
+};
+
+const NotificationSequence sequence_charged = {
+    &message_green_255,
+    &message_red_0,
+    NULL,
+};
+
+const NotificationSequence sequence_not_charging = {
+    &message_red_0,
+    &message_green_0,
+    NULL,
+};
+
+// Light up
+const NotificationSequence sequence_set_only_red_255 = {
+    &message_red_255,
+    &message_green_0,
+    &message_blue_0,
+    &message_do_not_reset,
+    NULL,
+};
+
+const NotificationSequence sequence_set_only_green_255 = {
+    &message_red_0,
+    &message_green_255,
+    &message_blue_0,
+    &message_do_not_reset,
+    NULL,
+};
+
+const NotificationSequence sequence_set_only_blue_255 = {
+    &message_red_0,
+    &message_green_0,
+    &message_blue_255,
+    &message_do_not_reset,
+    NULL,
+};
+
+const NotificationSequence sequence_set_red_255 = {
+    &message_red_255,
+    &message_do_not_reset,
+    NULL,
+};
+
+const NotificationSequence sequence_set_green_255 = {
+    &message_green_255,
+    &message_do_not_reset,
+    NULL,
+};
+
+const NotificationSequence sequence_set_blue_255 = {
+    &message_blue_255,
+    &message_do_not_reset,
+    NULL,
+};
+
+// Blink
+const NotificationSequence sequence_blink_red_10 = {
+    &message_red_255,
+    &message_delay_10,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_green_10 = {
+    &message_green_255,
+    &message_delay_10,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_yellow_10 = {
+    &message_red_255,
+    &message_green_255,
+    &message_delay_10,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_red_100 = {
+    &message_red_255,
+    &message_delay_100,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_green_100 = {
+    &message_green_255,
+    &message_delay_100,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_blue_100 = {
+    &message_blue_255,
+    &message_delay_100,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_yellow_100 = {
+    &message_red_255,
+    &message_green_255,
+    &message_delay_100,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_cyan_100 = {
+    &message_green_255,
+    &message_blue_255,
+    &message_delay_100,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_magenta_100 = {
+    &message_red_255,
+    &message_blue_255,
+    &message_delay_100,
+    NULL,
+};
+
+const NotificationSequence sequence_blink_white_100 = {
+    &message_red_255,
+    &message_green_255,
+    &message_blue_255,
+    &message_delay_100,
+    NULL,
+};
+
+// General
+const NotificationSequence sequence_success = {
+    &message_display_on,
+    &message_green_255,
+    &message_vibro_on,
+    &message_note_c5,
+    &message_delay_50,
+    &message_vibro_off,
+    &message_note_e5,
+    &message_delay_50,
+    &message_note_g5,
+    &message_delay_50,
+    &message_note_c6,
+    &message_delay_50,
+    &message_sound_off,
+    NULL,
+};
+
+const NotificationSequence sequence_error = {
+    &message_display_on,
+    &message_red_255,
+    &message_vibro_on,
+    &message_delay_50,
+    &message_vibro_off,
+    &message_delay_100,
+    &message_vibro_on,
+    &message_delay_50,
+    &message_vibro_off,
+    NULL,
+};

+ 94 - 0
applications/notification/notification-messages.h

@@ -0,0 +1,94 @@
+#pragma once
+#include "notification.h"
+#include "notification-messages-notes.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************************** Messages **********************************/
+
+// Display
+extern const NotificationMessage message_display_on;
+extern const NotificationMessage message_display_off;
+
+// Led ON
+extern const NotificationMessage message_red_255;
+extern const NotificationMessage message_green_255;
+extern const NotificationMessage message_blue_255;
+
+// Led OFF
+extern const NotificationMessage message_red_0;
+extern const NotificationMessage message_green_0;
+extern const NotificationMessage message_blue_0;
+
+// Delay
+extern const NotificationMessage message_delay_10;
+extern const NotificationMessage message_delay_25;
+extern const NotificationMessage message_delay_50;
+extern const NotificationMessage message_delay_100;
+extern const NotificationMessage message_delay_250;
+extern const NotificationMessage message_delay_500;
+extern const NotificationMessage message_delay_1000;
+
+// Sound
+extern const NotificationMessage message_sound_off;
+
+// Vibro
+extern const NotificationMessage message_vibro_on;
+extern const NotificationMessage message_vibro_off;
+
+// Reset
+extern const NotificationMessage message_do_not_reset;
+
+/****************************** Message sequences ******************************/
+
+// Reset
+extern const NotificationSequence sequence_reset_red;
+extern const NotificationSequence sequence_reset_green;
+extern const NotificationSequence sequence_reset_blue;
+extern const NotificationSequence sequence_reset_rgb;
+extern const NotificationSequence sequence_reset_display;
+extern const NotificationSequence sequence_reset_sound;
+extern const NotificationSequence sequence_reset_vibro;
+
+// Vibro
+extern const NotificationSequence sequence_set_vibro_on;
+
+// Display
+extern const NotificationSequence sequence_display_on;
+extern const NotificationSequence sequence_display_off;
+
+// Charging
+extern const NotificationSequence sequence_charging;
+extern const NotificationSequence sequence_charged;
+extern const NotificationSequence sequence_not_charging;
+
+// Light up
+extern const NotificationSequence sequence_set_only_red_255;
+extern const NotificationSequence sequence_set_only_green_255;
+extern const NotificationSequence sequence_set_only_blue_255;
+extern const NotificationSequence sequence_set_red_255;
+extern const NotificationSequence sequence_set_green_255;
+extern const NotificationSequence sequence_set_blue_255;
+
+// Blink
+extern const NotificationSequence sequence_blink_red_10;
+extern const NotificationSequence sequence_blink_green_10;
+extern const NotificationSequence sequence_blink_yellow_10;
+
+extern const NotificationSequence sequence_blink_red_100;
+extern const NotificationSequence sequence_blink_green_100;
+extern const NotificationSequence sequence_blink_blue_100;
+extern const NotificationSequence sequence_blink_yellow_100;
+extern const NotificationSequence sequence_blink_cyan_100;
+extern const NotificationSequence sequence_blink_magenta_100;
+extern const NotificationSequence sequence_blink_white_100;
+
+// General
+extern const NotificationSequence sequence_success;
+extern const NotificationSequence sequence_error;
+
+#ifdef __cplusplus
+}
+#endif

+ 76 - 0
applications/notification/notification.h

@@ -0,0 +1,76 @@
+#pragma once
+#include "stdint.h"
+#include "stdbool.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct NotificationApp NotificationApp;
+typedef struct {
+    float frequency;
+    float pwm;
+} NotificationMessageDataSound;
+
+typedef struct {
+    uint8_t value;
+} NotificationMessageDataLed;
+
+typedef struct {
+    bool on;
+} NotificationMessageDataVibro;
+
+typedef struct {
+    uint32_t length;
+} NotificationMessageDataDelay;
+
+typedef union {
+    NotificationMessageDataSound sound;
+    NotificationMessageDataLed led;
+    NotificationMessageDataVibro vibro;
+    NotificationMessageDataDelay delay;
+} NotificationMessageData;
+
+typedef enum {
+    NotificationMessageTypeVibro,
+    NotificationMessageTypeSoundOn,
+    NotificationMessageTypeSoundOff,
+    NotificationMessageTypeLedRed,
+    NotificationMessageTypeLedGreen,
+    NotificationMessageTypeLedBlue,
+    NotificationMessageTypeDelay,
+    NotificationMessageTypeLedDisplay,
+    NotificationMessageTypeDoNotReset,
+} NotificationMessageType;
+
+typedef struct {
+    NotificationMessageType type;
+    NotificationMessageData data;
+} NotificationMessage;
+
+typedef const NotificationMessage* NotificationSequence[];
+
+void notification_message(NotificationApp* app, const NotificationSequence* sequence);
+void notification_message_block(NotificationApp* app, const NotificationSequence* sequence);
+
+/**
+ * @brief Send internal (apply to permanent layer) notification message. Think twice before use.
+ * 
+ * @param app notification record content
+ * @param sequence notification sequence
+ */
+void notification_internal_message(NotificationApp* app, const NotificationSequence* sequence);
+
+/**
+ * @brief Send internal (apply to permanent layer) notification message and wait for notification end. Think twice before use.
+ * 
+ * @param app notification record content
+ * @param sequence notification sequence
+ */
+void notification_internal_message_block(
+    NotificationApp* app,
+    const NotificationSequence* sequence);
+
+#ifdef __cplusplus
+}
+#endif

+ 27 - 8
applications/power/power.c

@@ -16,8 +16,16 @@
 #include <assets_icons.h>
 #include <stm32wbxx.h>
 
+#include <notification/notification-messages.h>
+
 #define POWER_OFF_TIMEOUT 30
 
+typedef enum {
+    PowerStateNotCharging,
+    PowerStateCharging,
+    PowerStateCharged,
+} PowerState;
+
 struct Power {
     ViewDispatcher* view_dispatcher;
     View* info_view;
@@ -32,6 +40,8 @@ struct Power {
     ValueMutex* menu_vm;
     Cli* cli;
     MenuItem* menu;
+
+    PowerState state;
 };
 
 void power_draw_battery_callback(Canvas* canvas, void* context) {
@@ -91,6 +101,8 @@ void power_menu_info_callback(void* context) {
 Power* power_alloc() {
     Power* power = furi_alloc(sizeof(Power));
 
+    power->state = PowerStateNotCharging;
+
     power->menu_vm = furi_record_open("menu");
 
     power->cli = furi_record_open("cli");
@@ -159,20 +171,26 @@ void power_reset(Power* power, PowerBootMode mode) {
     api_hal_power_reset();
 }
 
-static void power_charging_indication_handler() {
+static void power_charging_indication_handler(Power* power, NotificationApp* notifications) {
     if(api_hal_power_is_charging()) {
         if(api_hal_power_get_pct() == 100) {
-            api_hal_light_set(LightRed, 0x00);
-            api_hal_light_set(LightGreen, 0xFF);
+            if(power->state != PowerStateCharged) {
+                notification_internal_message(notifications, &sequence_charged);
+                power->state = PowerStateCharged;
+            }
         } else {
-            api_hal_light_set(LightGreen, 0x00);
-            api_hal_light_set(LightRed, 0xFF);
+            if(power->state != PowerStateCharging) {
+                notification_internal_message(notifications, &sequence_charging);
+                power->state = PowerStateCharging;
+            }
         }
     }
 
     if(!api_hal_power_is_charging()) {
-        api_hal_light_set(LightRed, 0x00);
-        api_hal_light_set(LightGreen, 0x00);
+        if(power->state != PowerStateNotCharging) {
+            notification_internal_message(notifications, &sequence_not_charging);
+            power->state = PowerStateNotCharging;
+        }
     }
 }
 
@@ -180,6 +198,7 @@ int32_t power_task(void* p) {
     (void)p;
     Power* power = power_alloc();
 
+    NotificationApp* notifications = furi_record_open("notification");
     Gui* gui = furi_record_open("gui");
     gui_add_view_port(gui, power->battery_view_port, GuiLayerStatusBarRight);
     view_dispatcher_attach_to_gui(power->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
@@ -238,7 +257,7 @@ int32_t power_task(void* p) {
                 return true;
             });
 
-        power_charging_indication_handler();
+        power_charging_indication_handler(power, notifications);
 
         view_port_update(power->battery_view_port);
 

+ 8 - 13
applications/sd-card-test/sd-card-test.cpp

@@ -4,6 +4,7 @@
 #include "filesystem-api.h"
 #include "cli/cli.h"
 #include "callback-connector.h"
+#include <notification/notification-messages.h>
 
 // event enumeration type
 typedef uint8_t event_t;
@@ -46,6 +47,7 @@ public:
     const uint32_t benchmark_data_size = 4096;
     uint8_t* benchmark_data;
     FS_Api* fs_api;
+    NotificationApp* notification;
 
     // consts
     static const uint32_t BENCHMARK_ERROR = UINT_MAX;
@@ -58,7 +60,6 @@ public:
     void wait_for_button(InputKey input_button);
     bool ask(InputKey input_button_cancel, InputKey input_button_ok);
     void blink_red();
-    void set_red();
     void blink_green();
 
     // "tests"
@@ -91,6 +92,7 @@ void SdTest::run() {
     app_ready();
 
     fs_api = static_cast<FS_Api*>(furi_record_open("sdcard"));
+    notification = static_cast<NotificationApp*>(furi_record_open("notification"));
 
     if(fs_api == NULL) {
         set_error({"cannot get sdcard api"});
@@ -132,6 +134,8 @@ void SdTest::run() {
         "press BACK to exit",
     });
     wait_for_button(InputKeyBack);
+
+    furi_record_close("notification");
     exit();
 }
 
@@ -830,27 +834,18 @@ bool SdTest::ask(InputKey input_button_cancel, InputKey input_button_ok) {
 
 // blink red led
 void SdTest::blink_red() {
-    api_hal_light_set(LightRed, 0xFF);
-    delay(50);
-    api_hal_light_set(LightRed, 0x00);
-}
-
-// light up red led
-void SdTest::set_red() {
-    api_hal_light_set(LightRed, 0x00);
+    notification_message(notification, &sequence_blink_red_100);
 }
 
 // blink green led
 void SdTest::blink_green() {
-    api_hal_light_set(LightGreen, 0xFF);
-    delay(50);
-    api_hal_light_set(LightGreen, 0x00);
+    notification_message(notification, &sequence_blink_green_100);
 }
 
 // set text, but with infinite loop
 template <class T> void SdTest::set_error(std::initializer_list<T> list) {
     set_text(list);
-    set_red();
+    blink_red();
     wait_for_button(InputKeyBack);
     exit();
 }

+ 79 - 34
applications/sd-filesystem/sd-filesystem.c

@@ -206,41 +206,85 @@ void app_sd_format_internal(SdApp* sd_app) {
     _fs_unlock(&sd_app->info);
 }
 
-void app_sd_notify_wait_on() {
-    api_hal_light_set(LightRed, 0xFF);
-    api_hal_light_set(LightBlue, 0xFF);
+const NotificationSequence sd_sequence_success = {
+    &message_green_255,
+    &message_delay_50,
+    &message_green_0,
+    &message_delay_50,
+    &message_green_255,
+    &message_delay_50,
+    &message_green_0,
+    &message_delay_50,
+    &message_green_255,
+    &message_delay_50,
+    &message_green_0,
+    &message_delay_50,
+    NULL,
+};
+
+const NotificationSequence sd_sequence_error = {
+    &message_red_255,
+    &message_delay_50,
+    &message_red_0,
+    &message_delay_50,
+    &message_red_255,
+    &message_delay_50,
+    &message_red_0,
+    &message_delay_50,
+    &message_red_255,
+    &message_delay_50,
+    &message_red_0,
+    &message_delay_50,
+    NULL,
+};
+
+const NotificationSequence sd_sequence_eject = {
+    &message_blue_255,
+    &message_delay_50,
+    &message_blue_0,
+    &message_delay_50,
+    &message_blue_255,
+    &message_delay_50,
+    &message_blue_0,
+    &message_delay_50,
+    &message_blue_255,
+    &message_delay_50,
+    &message_blue_0,
+    &message_delay_50,
+    NULL,
+};
+
+const NotificationSequence sd_sequence_wait = {
+    &message_red_255,
+    &message_blue_255,
+    &message_do_not_reset,
+    NULL,
+};
+
+const NotificationSequence sd_sequence_wait_off = {
+    &message_red_0,
+    &message_blue_0,
+    NULL,
+};
+
+void app_sd_notify_wait(SdApp* sd_app) {
+    notification_message(sd_app->notifications, &sd_sequence_wait);
 }
 
-void app_sd_notify_wait_off() {
-    api_hal_light_set(LightRed, 0x00);
-    api_hal_light_set(LightBlue, 0x00);
+void app_sd_notify_wait_off(SdApp* sd_app) {
+    notification_message(sd_app->notifications, &sd_sequence_wait_off);
 }
 
-void app_sd_notify_success() {
-    for(uint8_t i = 0; i < 3; i++) {
-        delay(50);
-        api_hal_light_set(LightGreen, 0xFF);
-        delay(50);
-        api_hal_light_set(LightGreen, 0x00);
-    }
+void app_sd_notify_success(SdApp* sd_app) {
+    notification_message(sd_app->notifications, &sd_sequence_success);
 }
 
-void app_sd_notify_eject() {
-    for(uint8_t i = 0; i < 3; i++) {
-        delay(50);
-        api_hal_light_set(LightBlue, 0xFF);
-        delay(50);
-        api_hal_light_set(LightBlue, 0x00);
-    }
+void app_sd_notify_eject(SdApp* sd_app) {
+    notification_message(sd_app->notifications, &sd_sequence_eject);
 }
 
-void app_sd_notify_error() {
-    for(uint8_t i = 0; i < 3; i++) {
-        delay(50);
-        api_hal_light_set(LightRed, 0xFF);
-        delay(50);
-        api_hal_light_set(LightRed, 0x00);
-    }
+void app_sd_notify_error(SdApp* sd_app) {
+    notification_message(sd_app->notifications, &sd_sequence_error);
 }
 
 bool app_sd_mount_card(SdApp* sd_app) {
@@ -252,7 +296,7 @@ bool app_sd_mount_card(SdApp* sd_app) {
     _fs_lock(&sd_app->info);
 
     while(result == false && counter > 0 && hal_sd_detect()) {
-        app_sd_notify_wait_on();
+        app_sd_notify_wait(sd_app);
 
         if((counter % 10) == 0) {
             // power reset sd card
@@ -278,7 +322,7 @@ bool app_sd_mount_card(SdApp* sd_app) {
                 }
             }
         }
-        app_sd_notify_wait_off();
+        app_sd_notify_wait_off(sd_app);
 
         if(!result) {
             delay(1000);
@@ -586,6 +630,7 @@ int32_t sd_filesystem(void* p) {
 
     Gui* gui = furi_record_open("gui");
     Cli* cli = furi_record_open("cli");
+    sd_app->notifications = furi_record_open("notification");
     ValueMutex* menu_vm = furi_record_open("menu");
 
     gui_add_view_port(gui, sd_app->icon.view_port, GuiLayerStatusBarLeft);
@@ -637,10 +682,10 @@ int32_t sd_filesystem(void* p) {
                         "SD FILESYSTEM",
                         "sd init error: %s",
                         fs_error_get_internal_desc(sd_app->info.status));
-                    app_sd_notify_error();
+                    app_sd_notify_error(sd_app);
                 } else {
                     FURI_LOG_I("SD FILESYSTEM", "sd init ok");
-                    app_sd_notify_success();
+                    app_sd_notify_success(sd_app);
                 }
 
                 view_port_enabled_set(sd_app->icon.view_port, true);
@@ -661,7 +706,7 @@ int32_t sd_filesystem(void* p) {
                 view_port_enabled_set(sd_app->icon.view_port, false);
                 app_sd_unmount_card(sd_app);
                 sd_was_present = true;
-                app_sd_notify_eject();
+                app_sd_notify_eject(sd_app);
             }
         }
 
@@ -686,7 +731,7 @@ int32_t sd_filesystem(void* p) {
                     sd_app->sd_app_state = SdAppStateFormatInProgress;
                     delay(100);
                     app_sd_format_internal(sd_app);
-                    app_sd_notify_success();
+                    app_sd_notify_success(sd_app);
                     dialog_ex_set_left_button_text(dialog, "Back");
                     dialog_ex_set_header(
                         dialog, "SD card formatted", 64, 10, AlignCenter, AlignCenter);
@@ -708,7 +753,7 @@ int32_t sd_filesystem(void* p) {
                         AlignCenter);
                     sd_app->sd_app_state = SdAppStateEjected;
                     app_sd_unmount_card(sd_app);
-                    app_sd_notify_eject();
+                    app_sd_notify_eject(sd_app);
                 }; break;
                 case SdAppStateFileSelect: {
                     SdAppFileSelectResultEvent retval = {.result = true};

+ 3 - 0
applications/sd-filesystem/sd-filesystem.h

@@ -7,6 +7,7 @@
 #include <m-string.h>
 #include "sd-card-api.h"
 #include "view_holder.h"
+#include <notification/notification-messages.h>
 
 #define SD_FS_MAX_FILES _FS_LOCK
 #define SD_STATE_LINES_COUNT 6
@@ -100,6 +101,8 @@ struct SdApp {
 
     osMessageQueueId_t event_queue;
     string_t text_holder;
+
+    NotificationApp* notifications;
 };
 
 /* core api fns */

+ 5 - 2
applications/subghz/subghz_static.c

@@ -5,6 +5,7 @@
 #include <furi.h>
 #include <api-hal.h>
 #include <input/input.h>
+#include <notification/notification-messages.h>
 
 static const uint8_t subghz_static_keys[][4] = {
     {0x74, 0xBA, 0xDE},
@@ -105,7 +106,8 @@ bool subghz_static_input(InputEvent* event, void* context) {
                 if(event->type == InputTypePress) {
                     const uint8_t* key = subghz_static_keys[model->button];
 
-                    api_hal_light_set(LightRed, 0xff);
+                    NotificationApp* notification = furi_record_open("notification");
+                    notification_message_block(notification, &sequence_set_red_255);
                     __disable_irq();
                     for(uint8_t r = 0; r < 20; r++) {
                         //Payload
@@ -127,7 +129,8 @@ bool subghz_static_input(InputEvent* event, void* context) {
                         delay_us(10600);
                     }
                     __enable_irq();
-                    api_hal_light_set(LightRed, 0x00);
+                    notification_message(notification, &sequence_reset_red);
+                    furi_record_close("notification");
                 }
             }
 

+ 55 - 55
applications/tests/irda_decoder/test_data/irda_decoder_nec_test_data.h

@@ -11,9 +11,9 @@ const uint32_t test_nec_input1[] = {
 1415838, 9080, 4436, 611, 494, 600, 505, 578, 500, 608, 501, 602, 502, 580, 498, 606, 508, 605, 500, 583, 1633, 608, 1608, 611, 1631, 578, 1638, 602, 1614, 606, 1637, 583, 1633, 607, 1609, 611, 494, 600, 505, 570, 500, 604, 501, 602, 502, 581, 497, 606, 499, 605, 499, 583, 1633, 617, 1608, 611, 1631, 579, 1638, 602};
 
 const IrdaMessage test_nec_expected1[] = {
-    {IrdaProtocolNEC,     0,      0,  false},
-    {IrdaProtocolNEC,     0,      0,  true},
-    {IrdaProtocolNEC,     0,      0,  false},
+    {IrdaProtocolNEC,     0xFF00,      0,  false},
+    {IrdaProtocolNEC,     0xFF00,      0,  true},
+    {IrdaProtocolNEC,     0xFF00,      0,  false},
 };
 
 const uint32_t test_nec_input2[] = {
@@ -124,57 +124,57 @@ const uint32_t test_nec_input2[] = {
 };
 
 const IrdaMessage test_nec_expected2[] = {
-    {IrdaProtocolNEC,     0,      0x02,   false},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   false},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x02,   true},
-    {IrdaProtocolNEC,     0,      0x06,   false},
-    {IrdaProtocolNEC,     0,      0x06,   true},
-    {IrdaProtocolNEC,     0,      0x04,   false},
-    {IrdaProtocolNEC,     0,      0x04,   true},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   true},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   true},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x09,   false},
-    {IrdaProtocolNEC,     0,      0x09,   false},
-    {IrdaProtocolNEC,     0,      0x09,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x0A,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   true},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x08,   true},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x0A,   false},
-    {IrdaProtocolNEC,     0,      0x08,   false},
-    {IrdaProtocolNEC,     0,      0x0A,   false},
-    {IrdaProtocolNEC,     0,      0x0A,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x02,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x06,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x06,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x04,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x04,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x09,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x09,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x09,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x0A,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   true},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x0A,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x08,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x0A,   false},
+    {IrdaProtocolNEC,     0xFF00,      0x0A,   true},
 };
 

+ 14 - 17
applications/tests/test_index.c

@@ -2,6 +2,7 @@
 #include <furi.h>
 #include <api-hal.h>
 #include "minunit_vars.h"
+#include <notification/notification-messages.h>
 
 int run_minunit();
 int run_minunit_test_irda_decoder();
@@ -9,28 +10,24 @@ int run_minunit_test_irda_decoder();
 int32_t flipper_test_app(void* p) {
     uint32_t test_result = 0;
 
-    api_hal_light_set(LightRed, 0x00);
-    api_hal_light_set(LightGreen, 0x00);
-    api_hal_light_set(LightBlue, 0xFF);
+    NotificationApp* notification = furi_record_open("notification");
+
+    notification_message_block(notification, &sequence_set_only_blue_255);
 
     test_result |= run_minunit();
     test_result |= run_minunit_test_irda_decoder();
 
-    /* power_charging_indication_handler() breaks 1 sec light on */
-    for(int i = 0; i < 1000; ++i) {
-        if(test_result == 0) {
-            // test passed
-            api_hal_light_set(LightRed, 0x00);
-            api_hal_light_set(LightGreen, 0xFF);
-            api_hal_light_set(LightBlue, 0x00);
-        } else {
-            // test failed
-            api_hal_light_set(LightRed, 0xFF);
-            api_hal_light_set(LightGreen, 0x00);
-            api_hal_light_set(LightBlue, 0x00);
-        }
-        delay(1);
+    if(test_result == 0) {
+        // test passed
+        notification_message(notification, &sequence_success);
+        notification_message(notification, &sequence_set_only_green_255);
+    } else {
+        // test failed
+        notification_message(notification, &sequence_error);
+        notification_message(notification, &sequence_set_only_red_255);
     }
 
+    furi_record_close("notification");
+
     return 0;
 }

+ 0 - 8
lib/ST25RFAL002/platform.h

@@ -29,11 +29,6 @@ void platformUnprotectST25RComm();
 #define ST25R_INT_PIN NFC_IRQ_Pin
 #define ST25R_INT_PORT NFC_IRQ_GPIO_Port
 
-#define PLATFORM_LED_RX_PIN LightRed
-#define PLATFORM_LED_RX_PORT NULL
-#define PLATFORM_LED_FIELD_PIN LightBlue
-#define PLATFORM_LED_FIELD_PORT NULL
-
 #define RFAL_FEATURE_LISTEN_MODE               true       /*!< Enable/Disable RFAL support for Listen Mode                               */
 #define RFAL_FEATURE_WAKEUP_MODE               true       /*!< Enable/Disable RFAL support for the Wake-Up mode                          */
 #define RFAL_FEATURE_LOWPOWER_MODE             true       /*!< Enable/Disable RFAL support for the Low Power mode                        */
@@ -65,9 +60,6 @@ void platformUnprotectST25RComm();
 #define platformProtectST25RIrqStatus()             platformProtectST25RComm()                          /*!< Protect unique access to IRQ status var - IRQ disable on single thread environment (MCU) ; Mutex lock on a multi thread environment */
 #define platformUnprotectST25RIrqStatus()           platformUnprotectST25RComm()                        /*!< Unprotect the IRQ status var - IRQ enable on a single thread environment (MCU) ; Mutex unlock on a multi thread environment         */
 
-#define platformLedOff( port, pin )                 api_hal_light_set(pin, 0x00)
-#define platformLedOn( port, pin )                  api_hal_light_set(pin, 0xFF)
-
 #define platformGpioSet( port, pin )                HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET)          /*!< Turns the given GPIO High                   */
 #define platformGpioClear( port, pin )              HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET)        /*!< Turns the given GPIO Low                    */
 #define platformGpioToogle( port, pin )             HAL_GPIO_TogglePin(port, pin)                       /*!< Toogles the given GPIO                      */

+ 8 - 14
lib/irda/nec/irda_decoder_nec.c

@@ -3,11 +3,9 @@
 #include <furi.h>
 #include "../irda_i.h"
 
-
 static bool interpret_nec(IrdaCommonDecoder* decoder);
 static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder);
 
-
 static const IrdaCommonProtocolSpec protocol_nec = {
     {
         IRDA_NEC_PREAMBULE_MARK,
@@ -25,17 +23,15 @@ static const IrdaCommonProtocolSpec protocol_nec = {
     decode_repeat_nec,
 };
 
-
 static bool interpret_nec(IrdaCommonDecoder* decoder) {
     furi_assert(decoder);
 
     bool result = false;
-    uint8_t address = decoder->data[0];
-    uint8_t address_inverse = decoder->data[1];
+    uint16_t address = decoder->data[0] | (decoder->data[1] << 8);
     uint8_t command = decoder->data[2];
     uint8_t command_inverse = decoder->data[3];
 
-    if ((command == (uint8_t) ~command_inverse) && (address == (uint8_t) ~address_inverse)) {
+    if((command == (uint8_t)~command_inverse)) {
         decoder->message.command = command;
         decoder->message.address = address;
         decoder->message.repeat = false;
@@ -53,14 +49,13 @@ static DecodeStatus decode_repeat_nec(IrdaCommonDecoder* decoder) {
     uint32_t bit_tolerance = decoder->protocol->timings.bit_tolerance;
     DecodeStatus status = DecodeStatusError;
 
-    if (decoder->timings_cnt < 4)
-        return DecodeStatusOk;
+    if(decoder->timings_cnt < 4) return DecodeStatusOk;
 
-    if ((decoder->timings[0] > IRDA_NEC_REPEAT_PAUSE_MIN)
-        && (decoder->timings[0] < IRDA_NEC_REPEAT_PAUSE_MAX)
-        && MATCH_PREAMBLE_TIMING(decoder->timings[1], IRDA_NEC_REPEAT_MARK, preamble_tolerance)
-        && MATCH_PREAMBLE_TIMING(decoder->timings[2], IRDA_NEC_REPEAT_SPACE, preamble_tolerance)
-        && MATCH_BIT_TIMING(decoder->timings[3], decoder->protocol->timings.bit1_mark, bit_tolerance)) {
+    if((decoder->timings[0] > IRDA_NEC_REPEAT_PAUSE_MIN) &&
+       (decoder->timings[0] < IRDA_NEC_REPEAT_PAUSE_MAX) &&
+       MATCH_PREAMBLE_TIMING(decoder->timings[1], IRDA_NEC_REPEAT_MARK, preamble_tolerance) &&
+       MATCH_PREAMBLE_TIMING(decoder->timings[2], IRDA_NEC_REPEAT_SPACE, preamble_tolerance) &&
+       MATCH_BIT_TIMING(decoder->timings[3], decoder->protocol->timings.bit1_mark, bit_tolerance)) {
         status = DecodeStatusReady;
         decoder->timings_cnt = 0;
     } else {
@@ -81,4 +76,3 @@ IrdaMessage* irda_decoder_nec_decode(void* decoder, bool level, uint32_t duratio
 void irda_decoder_nec_free(void* decoder) {
     irda_common_decoder_free(decoder);
 }
-