MX 2 سال پیش
والد
کامیت
6cc7a38f65
29فایلهای تغییر یافته به همراه576 افزوده شده و 48 حذف شده
  1. 1 1
      ReadMe.md
  2. BIN
      apps/NFC/seader.fap
  3. BIN
      apps/Tools/flipp_pomodoro.fap
  4. 1 0
      apps_source_code/flipp_pomodoro/flipp_pomodoro_app.h
  5. 3 3
      apps_source_code/flipp_pomodoro/modules/flipp_pomodoro.c
  6. 85 8
      apps_source_code/flipp_pomodoro/scenes/flipp_pomodoro_scene_timer.c
  7. 1 1
      apps_source_code/flipp_pomodoro/views/flipp_pomodoro_info_view.c
  8. 112 17
      apps_source_code/flipp_pomodoro/views/flipp_pomodoro_timer_view.c
  9. 11 2
      apps_source_code/flipp_pomodoro/views/flipp_pomodoro_timer_view.h
  10. 1 0
      non_catalog_apps/seader/application.fam
  11. 2 0
      non_catalog_apps/seader/ccid.c
  12. BIN
      non_catalog_apps/seader/icons/ArrowUpEmpty_14x15.png
  13. BIN
      non_catalog_apps/seader/icons/ArrowUpFilled_14x15.png
  14. 1 1
      non_catalog_apps/seader/scenes/seader_scene_card_menu.c
  15. 1 0
      non_catalog_apps/seader/scenes/seader_scene_config.h
  16. 13 2
      non_catalog_apps/seader/scenes/seader_scene_credential_info.c
  17. 11 1
      non_catalog_apps/seader/scenes/seader_scene_read_card_success.c
  18. 3 0
      non_catalog_apps/seader/scenes/seader_scene_sam_missing.c
  19. 20 6
      non_catalog_apps/seader/scenes/seader_scene_sam_present.c
  20. 101 0
      non_catalog_apps/seader/scenes/seader_scene_uart.c
  21. 9 0
      non_catalog_apps/seader/seader.c
  22. 1 1
      non_catalog_apps/seader/seader_bridge.h
  23. 4 0
      non_catalog_apps/seader/seader_credential.c
  24. 5 0
      non_catalog_apps/seader/seader_custom_event.h
  25. 5 0
      non_catalog_apps/seader/seader_i.h
  26. 9 4
      non_catalog_apps/seader/seader_worker.c
  27. 0 1
      non_catalog_apps/seader/uart.c
  28. 152 0
      non_catalog_apps/seader/views/seader_uart_view.c
  29. 24 0
      non_catalog_apps/seader/views/seader_uart_view.h

+ 1 - 1
ReadMe.md

@@ -20,7 +20,7 @@ Sources of "integrated/bundled" apps are added now in this repo too, to allow pu
 
 The Flipper and its community wouldn't be as rich as it is without your contributions and support. Thank you for all you have done.
 
-### Apps checked & updated at `14 Jul 17:04 GMT +3`
+### Apps checked & updated at `18 Jul 02:22 GMT +3`
 
 ## Games
 - [Pong (By nmrr)](https://github.com/nmrr/flipperzero-pong) - Modified by [SimplyMinimal](https://github.com/SimplyMinimal/FlipperZero-Pong)

BIN
apps/NFC/seader.fap


BIN
apps/Tools/flipp_pomodoro.fap


+ 1 - 0
apps_source_code/flipp_pomodoro/flipp_pomodoro_app.h

@@ -17,6 +17,7 @@ typedef enum {
     FlippPomodoroAppCustomEventStageSkip = 100,
     FlippPomodoroAppCustomEventStageComplete, // By Expiration
     FlippPomodoroAppCustomEventTimerTick,
+    FlippPomodoroAppCustomEventTimerAskHint,
     FlippPomodoroAppCustomEventStateUpdated,
     FlippPomodoroAppCustomEventResumeTimer,
 } FlippPomodoroAppCustomEvent;

+ 3 - 3
apps_source_code/flipp_pomodoro/modules/flipp_pomodoro.c

@@ -18,9 +18,9 @@ PomodoroStage stages_sequence[] = {
 };
 
 char* current_stage_label[] = {
-    [FlippPomodoroStageFocus] = "Continue focus for:",
-    [FlippPomodoroStageRest] = "Keep rest for:",
-    [FlippPomodoroStageLongBreak] = "Long Break for:",
+    [FlippPomodoroStageFocus] = "Focusing...",
+    [FlippPomodoroStageRest] = "Short Break...",
+    [FlippPomodoroStageLongBreak] = "Long Break...",
 };
 
 char* next_stage_label[] = {

+ 85 - 8
apps_source_code/flipp_pomodoro/scenes/flipp_pomodoro_scene_timer.c

@@ -8,6 +8,49 @@
 
 enum { SceneEventConusmed = true, SceneEventNotConusmed = false };
 
+static char* work_hints[] = {
+    "Can you explain the problem as if I'm five?",
+    "Expected output vs. reality: what's the difference?",
+    "Ever thought of slicing the problem into bite-sized pieces?",
+    "What's the story when you walk through the code?",
+    "Any error messages gossiping about the issue?",
+    "What tricks have you tried to fix this?",
+    "Did you test the code, or just hoping for the best?",
+    "How's this code mingling with the rest of the app?",
+    "Any sneaky side effects causing mischief?",
+    "What are you assuming, and is it safe to do so?",
+    "Did you remember to invite all the edge cases to the party?",
+    "What happens in the isolation chamber (running code separately)?",
+    "Can you make the issue appear on command?",
+    "What's the scene at the crime spot when the error occurs?",
+    "Did you seek wisdom from the grand oracle (Google)?",
+    "What if you take a different path to solve this?",
+    "Did you take a coffee break to reboot your brain?"};
+
+static char* break_hints[] = {
+    "Time to stretch! Remember, your body isn't made of code.",
+    "Hydrate or diedrate! Grab a glass of water.",
+    "Blink! Your eyes need a break too.",
+    "How about a quick dance-off with your shadow?",
+    "Ever tried chair yoga? Now's the time!",
+    "Time for a quick peek out the window. The outside world still exists!",
+    "Quick, think about kittens! Or puppies! Or baby turtles!",
+    "Time for a laugh. Look up a joke or two!",
+    "Sing a song. Bonus points for making up your own lyrics.",
+    "Do a quick tidy-up. A clean space is a happy space!",
+    "Time to play 'air' musical instrument for a minute.",
+    "How about a quick doodle? Unleash your inner Picasso!",
+    "Practice your superhero pose. Feel the power surge!",
+    "Quick, tell yourself a joke. Don't worry, I won't judge.",
+    "Time to practice your mime skills. Stuck in a box, anyone?",
+    "Ever tried juggling? Now's your chance!",
+    "Do a quick self high-five, you're doing great!"};
+
+static char* random_string_of_list(char** hints, size_t num_hints) {
+    int random_index = rand() % num_hints;
+    return hints[random_index];
+}
+
 void flipp_pomodoro_scene_timer_sync_view_state(void* ctx) {
     furi_assert(ctx);
 
@@ -25,6 +68,12 @@ void flipp_pomodoro_scene_timer_on_next_stage(void* ctx) {
     view_dispatcher_send_custom_event(app->view_dispatcher, FlippPomodoroAppCustomEventStageSkip);
 };
 
+void flipp_pomodoro_scene_timer_on_ask_hint(void* ctx) {
+    FlippPomodoroApp* app = ctx;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, FlippPomodoroAppCustomEventTimerAskHint);
+}
+
 void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
     furi_assert(ctx);
 
@@ -37,21 +86,49 @@ void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
     flipp_pomodoro_scene_timer_sync_view_state(app);
+
+    flipp_pomodoro_view_timer_set_callback_context(app->timer_view, app);
+
+    flipp_pomodoro_view_timer_set_on_ok_cb(
+        app->timer_view, flipp_pomodoro_scene_timer_on_ask_hint);
+
     flipp_pomodoro_view_timer_set_on_right_cb(
-        app->timer_view, flipp_pomodoro_scene_timer_on_next_stage, app);
+        app->timer_view, flipp_pomodoro_scene_timer_on_next_stage);
 };
 
+char* flipp_pomodoro_scene_timer_get_contextual_hint(FlippPomodoroApp* app) {
+    switch(flipp_pomodoro__get_stage(app->state)) {
+    case FlippPomodoroStageFocus:
+        return random_string_of_list(work_hints, sizeof(work_hints) / sizeof(work_hints[0]));
+    case FlippPomodoroStageRest:
+    case FlippPomodoroStageLongBreak:
+        return random_string_of_list(break_hints, sizeof(break_hints) / sizeof(break_hints[0]));
+    default:
+        return "What's up?";
+    }
+}
+
 void flipp_pomodoro_scene_timer_handle_custom_event(
     FlippPomodoroApp* app,
     FlippPomodoroAppCustomEvent custom_event) {
-    if(custom_event == FlippPomodoroAppCustomEventTimerTick &&
-       flipp_pomodoro__is_stage_expired(app->state)) {
-        view_dispatcher_send_custom_event(
-            app->view_dispatcher, FlippPomodoroAppCustomEventStageComplete);
-    }
-
-    if(custom_event == FlippPomodoroAppCustomEventStateUpdated) {
+    switch(custom_event) {
+    case FlippPomodoroAppCustomEventTimerTick:
+        if(flipp_pomodoro__is_stage_expired(app->state)) {
+            view_dispatcher_send_custom_event(
+                app->view_dispatcher, FlippPomodoroAppCustomEventStageComplete);
+        }
+        break;
+    case FlippPomodoroAppCustomEventStateUpdated:
         flipp_pomodoro_scene_timer_sync_view_state(app);
+        break;
+    case FlippPomodoroAppCustomEventTimerAskHint:
+        flipp_pomodoro_view_timer_display_hint(
+            flipp_pomodoro_view_timer_get_view(app->timer_view),
+            flipp_pomodoro_scene_timer_get_contextual_hint(app));
+        break;
+    default:
+        // optional: code to be executed if custom_event doesn't match any cases
+        break;
     }
 };
 

+ 1 - 1
apps_source_code/flipp_pomodoro/views/flipp_pomodoro_info_view.c

@@ -29,7 +29,7 @@ static void
 
     furi_string_printf(
         stats_string,
-        "So Long,\nand Thanks for All the Focus...\nand for completing\n%i pomodoro(s)",
+        "So Long,\nand Thanks for All the Focus...\nand for completing\n\e#%i\e# pomodoro(s)",
         model->pomodoros_completed);
     const char* stats_string_formatted = furi_string_get_cstr(stats_string);
 

+ 112 - 17
apps_source_code/flipp_pomodoro/views/flipp_pomodoro_timer_view.c

@@ -18,12 +18,15 @@ enum {
 struct FlippPomodoroTimerView {
     View* view;
     FlippPomodoroTimerViewInputCb right_cb;
-    void* right_cb_ctx;
+    FlippPomodoroTimerViewInputCb ok_cb;
+    void* callback_context;
 };
 
 typedef struct {
     IconAnimation* icon;
     FlippPomodoroState* state;
+    size_t scroll_counter;
+    char* current_hint;
 } FlippPomodoroTimerViewModel;
 
 static const Icon* stage_background_image[] = {
@@ -89,6 +92,55 @@ static void
         flipp_pomodoro__current_stage_label(state));
 }
 
+static void
+    flipp_pomodoro_view_timer_draw_hint(Canvas* canvas, FlippPomodoroTimerViewModel* model) {
+    size_t MAX_SCROLL_COUNTER = 300;
+    uint8_t SCROLL_DELAY_FRAMES = 3;
+
+    if(model->scroll_counter >= MAX_SCROLL_COUNTER || model->current_hint == NULL) {
+        return;
+    }
+
+    uint8_t hint_width = 90;
+    uint8_t hint_height = 18;
+
+    uint8_t hint_x = canvas_width(canvas) - hint_width - 6;
+    uint8_t hint_y = 35;
+
+    FuriString* displayed_hint_string = furi_string_alloc();
+
+    furi_string_printf(displayed_hint_string, "%s", model->current_hint);
+
+    size_t perfect_duration = furi_string_size(displayed_hint_string) * 1.5;
+
+    if(model->scroll_counter > perfect_duration) {
+        model->scroll_counter = MAX_SCROLL_COUNTER;
+        furi_string_free(displayed_hint_string);
+        return;
+    }
+
+    size_t scroll_offset = (model->scroll_counter < SCROLL_DELAY_FRAMES) ?
+                               0 :
+                               model->scroll_counter - SCROLL_DELAY_FRAMES;
+
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_box(canvas, hint_x, hint_y, hint_width + 3, hint_height);
+    canvas_set_color(canvas, ColorBlack);
+
+    elements_bubble(canvas, hint_x, hint_y, hint_width, hint_height);
+
+    elements_scrollable_text_line(
+        canvas,
+        hint_x + 6,
+        hint_y + 12,
+        hint_width - 4,
+        displayed_hint_string,
+        scroll_offset,
+        true);
+    furi_string_free(displayed_hint_string);
+    model->scroll_counter++;
+}
+
 static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model) {
     if(!_model) {
         return;
@@ -105,10 +157,12 @@ static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model
         canvas, flipp_pomodoro__stage_remaining_duration(model->state));
 
     flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state);
+
     canvas_set_color(canvas, ColorBlack);
 
     canvas_set_font(canvas, FontSecondary);
     elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state));
+    flipp_pomodoro_view_timer_draw_hint(canvas, model);
 };
 
 bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
@@ -116,18 +170,22 @@ bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
     furi_assert(event);
     FlippPomodoroTimerView* timer = ctx;
 
-    const bool should_trigger_right_event_cb = (event->type == InputTypePress) &&
-                                               (event->key == InputKeyRight) &&
-                                               (timer->right_cb != NULL);
+    const bool is_press_event = event->type == InputTypePress;
 
-    if(should_trigger_right_event_cb) {
-        furi_assert(timer->right_cb);
-        furi_assert(timer->right_cb_ctx);
-        timer->right_cb(timer->right_cb_ctx);
-        return ViewInputConsumed;
-    };
+    if(!is_press_event) {
+        return ViewInputNotConusmed;
+    }
 
-    return ViewInputNotConusmed;
+    switch(event->key) {
+    case InputKeyRight:
+        timer->right_cb(timer->callback_context);
+        return ViewInputConsumed;
+    case InputKeyOk:
+        timer->ok_cb(timer->callback_context);
+        return ViewInputConsumed;
+    default:
+        return ViewInputNotConusmed;
+    }
 };
 
 View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
@@ -135,12 +193,22 @@ View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
     return timer->view;
 };
 
+void flipp_pomodoro_view_timer_display_hint(View* view, char* hint) {
+    with_view_model(
+        view,
+        FlippPomodoroTimerViewModel * model,
+        {
+            model->scroll_counter = 0;
+            model->current_hint = hint;
+        },
+        true);
+}
+
 void flipp_pomodoro_view_timer_assign_animation(View* view) {
     with_view_model(
         view,
         FlippPomodoroTimerViewModel * model,
         {
-            furi_assert(model->state);
             if(model->icon) {
                 icon_animation_free(model->icon);
             }
@@ -160,28 +228,55 @@ FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() {
         flipp_pomodoro_view_timer_get_view(timer),
         ViewModelTypeLockFree,
         sizeof(FlippPomodoroTimerViewModel));
+
     view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer);
     view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback);
     view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback);
 
+    with_view_model(
+        flipp_pomodoro_view_timer_get_view(timer),
+        FlippPomodoroTimerViewModel * model,
+        { model->scroll_counter = 0; },
+        false);
+
     return timer;
 };
 
+void flipp_pomodoro_view_timer_set_callback_context(
+    FlippPomodoroTimerView* timer,
+    void* callback_ctx) {
+    furi_assert(timer);
+    furi_assert(callback_ctx);
+    timer->callback_context = callback_ctx;
+}
+
 void flipp_pomodoro_view_timer_set_on_right_cb(
     FlippPomodoroTimerView* timer,
-    FlippPomodoroTimerViewInputCb right_cb,
-    void* right_cb_ctx) {
+    FlippPomodoroTimerViewInputCb right_cb) {
+    furi_assert(timer);
     furi_assert(right_cb);
-    furi_assert(right_cb_ctx);
     timer->right_cb = right_cb;
-    timer->right_cb_ctx = right_cb_ctx;
 };
 
+void flipp_pomodoro_view_timer_set_on_ok_cb(
+    FlippPomodoroTimerView* timer,
+    FlippPomodoroTimerViewInputCb ok_kb) {
+    furi_assert(ok_kb);
+    furi_assert(timer);
+    timer->ok_cb = ok_kb;
+}
+
 void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state) {
     furi_assert(view);
     furi_assert(state);
     with_view_model(
-        view, FlippPomodoroTimerViewModel * model, { model->state = state; }, false);
+        view,
+        FlippPomodoroTimerViewModel * model,
+        {
+            model->state = state;
+            model->current_hint = NULL;
+        },
+        false);
     flipp_pomodoro_view_timer_assign_animation(view);
 };
 

+ 11 - 2
apps_source_code/flipp_pomodoro/views/flipp_pomodoro_timer_view.h

@@ -15,7 +15,16 @@ void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer);
 
 void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state);
 
+void flipp_pomodoro_view_timer_set_callback_context(
+    FlippPomodoroTimerView* timer,
+    void* callback_ctx);
+
 void flipp_pomodoro_view_timer_set_on_right_cb(
     FlippPomodoroTimerView* timer,
-    FlippPomodoroTimerViewInputCb right_cb,
-    void* right_cb_ctx);
+    FlippPomodoroTimerViewInputCb right_cb);
+
+void flipp_pomodoro_view_timer_set_on_ok_cb(
+    FlippPomodoroTimerView* timer,
+    FlippPomodoroTimerViewInputCb ok_cb);
+
+void flipp_pomodoro_view_timer_display_hint(View* view, char* hint);

+ 1 - 0
non_catalog_apps/seader/application.fam

@@ -19,6 +19,7 @@ App(
       "seader_worker.c",
       "seader_credential.c",
       "seader_icons.c",
+      "views/*.c",
     ],
     fap_icon="icons/logo.png",
     fap_category="NFC",

+ 2 - 0
non_catalog_apps/seader/ccid.c

@@ -135,6 +135,8 @@ size_t processCCID(SeaderWorker* seader_worker, uint8_t* cmd, size_t cmd_len) {
             switch(cmd[1]) {
             case CARD_OUT:
                 FURI_LOG_D(TAG, "Card removed");
+                powered = false;
+                hasSAM = false;
                 retries = 3;
                 break;
             case CARD_IN_1:

BIN
non_catalog_apps/seader/icons/ArrowUpEmpty_14x15.png


BIN
non_catalog_apps/seader/icons/ArrowUpFilled_14x15.png


+ 1 - 1
non_catalog_apps/seader/scenes/seader_scene_card_menu.c

@@ -34,7 +34,7 @@ void seader_scene_card_menu_on_enter(void* context) {
         seader_scene_card_menu_submenu_callback,
         seader);
     if(seader->is_debug_enabled) {
-        if(credential->sio[0] != 0) {
+        if(credential->sio[0] == 0x30) {
             submenu_add_item(
                 submenu,
                 "Save SR",

+ 1 - 0
non_catalog_apps/seader/scenes/seader_scene_config.h

@@ -14,3 +14,4 @@ ADD_SCENE(seader, delete, Delete)
 ADD_SCENE(seader, delete_success, DeleteSuccess)
 ADD_SCENE(seader, credential_info, CredentialInfo)
 ADD_SCENE(seader, sam_info, SamInfo)
+ADD_SCENE(seader, uart, Uart)

+ 13 - 2
non_catalog_apps/seader/scenes/seader_scene_credential_info.c

@@ -21,6 +21,7 @@ void seader_scene_credential_info_on_enter(void* context) {
     FuriString* type_str = furi_string_alloc();
     FuriString* bitlength_str = furi_string_alloc();
     FuriString* credential_str = furi_string_alloc();
+    FuriString* sio_str = furi_string_alloc();
 
     dolphin_deed(DolphinDeedNfcReadSuccess);
 
@@ -29,11 +30,14 @@ void seader_scene_credential_info_on_enter(void* context) {
 
     furi_string_set(credential_str, "");
     furi_string_set(bitlength_str, "");
+    furi_string_set(sio_str, "");
     if(credential->bit_length > 0) {
         furi_string_cat_printf(bitlength_str, "%d bit", credential->bit_length);
         furi_string_cat_printf(credential_str, "0x%llX", credential->credential);
 
-        if(credential->type == SeaderCredentialType14A) {
+        if(credential->type == SeaderCredentialTypeNone) {
+            furi_string_set(type_str, "None");
+        } else if(credential->type == SeaderCredentialType14A) {
             furi_string_set(type_str, "14443A");
         } else if(credential->type == SeaderCredentialTypePicopass) {
             furi_string_set(type_str, "Picopass");
@@ -68,9 +72,16 @@ void seader_scene_credential_info_on_enter(void* context) {
         FontSecondary,
         furi_string_get_cstr(credential_str));
 
+    if(credential->sio[0] == 0x30) {
+        furi_string_set(sio_str, "+SIO");
+        widget_add_string_element(
+            widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
+    }
+
     furi_string_free(bitlength_str);
-    furi_string_free(credential_str);
     furi_string_free(type_str);
+    furi_string_free(credential_str);
+    furi_string_free(sio_str);
 
     view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewWidget);
 }

+ 11 - 1
non_catalog_apps/seader/scenes/seader_scene_read_card_success.c

@@ -21,6 +21,7 @@ void seader_scene_read_card_success_on_enter(void* context) {
     FuriString* type_str = furi_string_alloc();
     FuriString* bitlength_str = furi_string_alloc();
     FuriString* credential_str = furi_string_alloc();
+    FuriString* sio_str = furi_string_alloc();
 
     dolphin_deed(DolphinDeedNfcReadSuccess);
 
@@ -29,11 +30,14 @@ void seader_scene_read_card_success_on_enter(void* context) {
 
     furi_string_set(credential_str, "");
     furi_string_set(bitlength_str, "");
+    furi_string_set(sio_str, "");
     if(credential->bit_length > 0) {
         furi_string_cat_printf(bitlength_str, "%d bit", credential->bit_length);
         furi_string_cat_printf(credential_str, "0x%llX", credential->credential);
 
-        if(credential->type == SeaderCredentialType14A) {
+        if(credential->type == SeaderCredentialTypeNone) {
+            furi_string_set(type_str, "None");
+        } else if(credential->type == SeaderCredentialType14A) {
             furi_string_set(type_str, "14443A");
         } else if(credential->type == SeaderCredentialTypePicopass) {
             furi_string_set(type_str, "Picopass");
@@ -68,10 +72,16 @@ void seader_scene_read_card_success_on_enter(void* context) {
         AlignCenter,
         FontSecondary,
         furi_string_get_cstr(credential_str));
+    if(credential->sio[0] == 0x30) {
+        furi_string_set(sio_str, "+SIO");
+        widget_add_string_element(
+            widget, 64, 48, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(sio_str));
+    }
 
     furi_string_free(credential_str);
     furi_string_free(bitlength_str);
     furi_string_free(type_str);
+    furi_string_free(sio_str);
 
     view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewWidget);
 }

+ 3 - 0
non_catalog_apps/seader/scenes/seader_scene_sam_missing.c

@@ -40,6 +40,9 @@ bool seader_scene_sam_missing_on_event(void* context, SceneManagerEvent event) {
         } else if(event.event == SubmenuIndexSaved) {
             scene_manager_next_scene(seader->scene_manager, SeaderSceneFileSelect);
             consumed = true;
+        } else if(event.event == SeaderWorkerEventSamPresent) {
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneSamPresent);
+            consumed = true;
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_stop(seader->scene_manager);

+ 20 - 6
non_catalog_apps/seader/scenes/seader_scene_sam_present.c

@@ -66,14 +66,28 @@ bool seader_scene_sam_present_on_event(void* context, SceneManagerEvent event) {
 
     if(event.type == SceneManagerEventTypeCustom) {
         if(event.event == SubmenuIndexReadPicopass) {
-            scene_manager_set_scene_state(
-                seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexReadPicopass);
-            scene_manager_next_scene(seader->scene_manager, SeaderSceneReadPicopass);
+            if(seader->is_debug_enabled) {
+                seader->credential->type = SeaderCredentialTypePicopass;
+                scene_manager_set_scene_state(
+                    seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexReadPicopass);
+                scene_manager_next_scene(seader->scene_manager, SeaderSceneUart);
+            } else {
+                scene_manager_set_scene_state(
+                    seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexReadPicopass);
+                scene_manager_next_scene(seader->scene_manager, SeaderSceneReadPicopass);
+            }
             consumed = true;
         } else if(event.event == SubmenuIndexRead14a) {
-            scene_manager_set_scene_state(
-                seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexRead14a);
-            scene_manager_next_scene(seader->scene_manager, SeaderSceneRead14a);
+            if(seader->is_debug_enabled) {
+                seader->credential->type = SeaderCredentialType14A;
+                scene_manager_set_scene_state(
+                    seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexRead14a);
+                scene_manager_next_scene(seader->scene_manager, SeaderSceneUart);
+            } else {
+                scene_manager_set_scene_state(
+                    seader->scene_manager, SeaderSceneSamPresent, SubmenuIndexRead14a);
+                scene_manager_next_scene(seader->scene_manager, SeaderSceneRead14a);
+            }
             consumed = true;
         } else if(event.event == SubmenuIndexSamInfo) {
             scene_manager_set_scene_state(

+ 101 - 0
non_catalog_apps/seader/scenes/seader_scene_uart.c

@@ -0,0 +1,101 @@
+#include "../seader_i.h"
+#include "../seader_bridge.h"
+#define TAG "SeaderSceneUart"
+
+typedef struct {
+    SeaderUartConfig cfg;
+    SeaderUartState state;
+} SceneUartBridge;
+
+static SceneUartBridge* scene_uart;
+
+void seader_uart_worker_callback(SeaderWorkerEvent event, void* context) {
+    UNUSED(event);
+    Seader* seader = context;
+    view_dispatcher_send_custom_event(seader->view_dispatcher, SeaderCustomEventWorkerExit);
+}
+
+void seader_scene_uart_callback(SeaderCustomEvent event, void* context) {
+    furi_assert(context);
+    Seader* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void seader_scene_uart_on_enter(void* context) {
+    Seader* app = context;
+    uint32_t prev_state = scene_manager_get_scene_state(app->scene_manager, SeaderViewUart);
+    if(prev_state == 0) {
+        scene_uart = malloc(sizeof(SceneUartBridge));
+        scene_uart->cfg.uart_ch = 0;
+        scene_uart->cfg.flow_pins = 0;
+        scene_uart->cfg.baudrate_mode = 0;
+        scene_uart->cfg.baudrate = 0;
+    }
+
+    seader_uart_get_config(app->uart, &scene_uart->cfg);
+    seader_uart_get_state(app->uart, &scene_uart->state);
+
+    seader_uart_view_set_callback(app->seader_uart_view, seader_scene_uart_callback, app);
+    scene_manager_set_scene_state(app->scene_manager, SeaderSceneUart, 0);
+    view_dispatcher_switch_to_view(app->view_dispatcher, SeaderViewUart);
+    notification_message(app->notifications, &sequence_display_backlight_enforce_on);
+
+    Seader* seader = app;
+
+    if(seader->credential->type == SeaderCredentialTypePicopass) {
+        seader_worker_start(
+            seader->worker,
+            SeaderWorkerStateReadPicopass,
+            seader->uart,
+            seader->credential,
+            seader_uart_worker_callback,
+            seader);
+    } else if(seader->credential->type == SeaderCredentialType14A) {
+        seader_worker_start(
+            seader->worker,
+            SeaderWorkerStateRead14a,
+            seader->uart,
+            seader->credential,
+            seader_uart_worker_callback,
+            seader);
+    }
+}
+
+bool seader_scene_uart_on_event(void* context, SceneManagerEvent event) {
+    Seader* app = context;
+    Seader* seader = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SeaderCustomEventWorkerExit) {
+            scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess);
+            consumed = true;
+        } else if(event.type == SceneManagerEventTypeTick) {
+            scene_manager_set_scene_state(app->scene_manager, SeaderSceneUart, 1);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        uint32_t tx_cnt_last = scene_uart->state.tx_cnt;
+        uint32_t rx_cnt_last = scene_uart->state.rx_cnt;
+        seader_uart_get_state(app->uart, &scene_uart->state);
+        if(seader->credential->type == SeaderCredentialTypePicopass) {
+            scene_uart->state.protocol = FrameProtocol_iclass;
+        } else if(seader->credential->type == SeaderCredentialType14A) {
+            scene_uart->state.protocol = FrameProtocol_nfc;
+        }
+        seader_uart_view_update_state(app->seader_uart_view, &scene_uart->cfg, &scene_uart->state);
+        if(tx_cnt_last != scene_uart->state.tx_cnt) {
+            notification_message(app->notifications, &sequence_blink_blue_10);
+        }
+        if(rx_cnt_last != scene_uart->state.rx_cnt) {
+            notification_message(app->notifications, &sequence_blink_green_10);
+        }
+    }
+    return consumed;
+}
+
+void seader_scene_uart_on_exit(void* context) {
+    Seader* app = context;
+    seader_worker_stop(app->worker);
+    notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
+}

+ 9 - 0
non_catalog_apps/seader/seader.c

@@ -78,6 +78,12 @@ Seader* seader_alloc() {
     view_dispatcher_add_view(
         seader->view_dispatcher, SeaderViewWidget, widget_get_view(seader->widget));
 
+    seader->seader_uart_view = seader_uart_view_alloc();
+    view_dispatcher_add_view(
+        seader->view_dispatcher,
+        SeaderViewUart,
+        seader_uart_view_get_view(seader->seader_uart_view));
+
     return seader;
 }
 
@@ -114,6 +120,9 @@ void seader_free(Seader* seader) {
     view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewWidget);
     widget_free(seader->widget);
 
+    view_dispatcher_remove_view(seader->view_dispatcher, SeaderViewUart);
+    seader_uart_view_free(seader->seader_uart_view);
+
     // Worker
     seader_worker_stop(seader->worker);
     seader_worker_free(seader->worker);

+ 1 - 1
non_catalog_apps/seader/seader_bridge.h

@@ -21,7 +21,7 @@ typedef struct {
 typedef struct {
     uint32_t rx_cnt;
     uint32_t tx_cnt;
-    uint32_t baudrate_cur;
+    uint8_t protocol;
 } SeaderUartState;
 
 struct SeaderUartBridge {

+ 4 - 0
non_catalog_apps/seader/seader_credential.c

@@ -54,6 +54,7 @@ static bool seader_credential_load(SeaderCredential* cred, FuriString* path, boo
     FuriString* temp_str;
     temp_str = furi_string_alloc();
     bool deprecated_version = false;
+    cred->type = SeaderCredentialTypeNone;
 
     if(cred->loading_cb) {
         cred->loading_cb(cred->loading_cb_ctx, true);
@@ -367,9 +368,12 @@ bool seader_file_select(SeaderCredential* cred) {
 
 void seader_credential_clear(SeaderCredential* cred) {
     furi_assert(cred);
+    memset(cred->name, 0, sizeof(cred->name));
     cred->credential = 0;
     cred->bit_length = 0;
     cred->type = SeaderCredentialTypeNone;
+    memset(cred->sio, 0, sizeof(cred->sio));
+    memset(cred->diversifier, 0, sizeof(cred->diversifier));
     furi_string_reset(cred->load_path);
 }
 

+ 5 - 0
non_catalog_apps/seader/seader_custom_event.h

@@ -0,0 +1,5 @@
+#pragma once
+
+typedef enum {
+    SeaderStartEventNone = 0,
+} SeaderCustomEvent;

+ 5 - 0
non_catalog_apps/seader/seader_i.h

@@ -33,6 +33,7 @@
 #include <FrameProtocol.h>
 
 #include "scenes/seader_scene.h"
+#include "views/seader_uart_view.h"
 
 #include "seader_bridge.h"
 #include "seader.h"
@@ -93,6 +94,9 @@ struct Seader {
     Loading* loading;
     TextInput* text_input;
     Widget* widget;
+
+    //Custom views
+    SeaderUartView* seader_uart_view;
 };
 
 typedef enum {
@@ -101,6 +105,7 @@ typedef enum {
     SeaderViewLoading,
     SeaderViewTextInput,
     SeaderViewWidget,
+    SeaderViewUart,
 } SeaderView;
 
 void seader_text_store_set(Seader* seader, const char* text, ...);

+ 9 - 4
non_catalog_apps/seader/seader_worker.c

@@ -378,10 +378,13 @@ bool unpack_pacs(
             FURI_LOG_D(TAG, "Received pac: %s", pacDebug);
 
             memset(display, 0, sizeof(display));
-            for(uint8_t i = 0; i < sizeof(seader_credential->sio); i++) {
-                snprintf(display + (i * 2), sizeof(display), "%02x", seader_credential->sio[i]);
+            if(seader_credential->sio[0] == 0x30) {
+                for(uint8_t i = 0; i < sizeof(seader_credential->sio); i++) {
+                    snprintf(
+                        display + (i * 2), sizeof(display), "%02x", seader_credential->sio[i]);
+                }
+                FURI_LOG_D(TAG, "SIO %s", display);
             }
-            FURI_LOG_D(TAG, "SIO %s", display);
         }
 
         if(pac->size <= sizeof(seader_credential->credential)) {
@@ -565,7 +568,7 @@ bool iso15693Transmit(SeaderWorker* seader_worker, uint8_t* buffer, size_t len)
             snprintf(display + (i * 2), sizeof(display), "%02x", rxBuffer[i]);
         }
         // FURI_LOG_D(TAG, "Result %d %s", recvLen, display);
-        if(memcmp(buffer, readBlock6, len) == 0) {
+        if(memcmp(buffer, readBlock6, len) == 0 && rxBuffer[0] == 0x30) {
             memcpy(credential->sio, rxBuffer, 32);
         } else if(memcmp(buffer, readBlock9, len) == 0) {
             memcpy(credential->sio + 32, rxBuffer + 8, 24);
@@ -871,6 +874,7 @@ int32_t seader_worker_task(void* context) {
         FURI_LOG_D(TAG, "Read Picopass");
         requestPacs = true;
         seader_credential_clear(seader_worker->credential);
+        seader_worker->credential->type = SeaderCredentialTypePicopass;
         seader_worker_enable_field();
         if(picopass_card_read(seader_worker) != ERR_NONE) {
             // Turn off if cancelled / no card found
@@ -880,6 +884,7 @@ int32_t seader_worker_task(void* context) {
         FURI_LOG_D(TAG, "Read 14a");
         requestPacs = true;
         seader_credential_clear(seader_worker->credential);
+        seader_worker->credential->type = SeaderCredentialType14A;
         nfc_scene_field_on_enter();
         if(!detect_nfc(seader_worker)) {
             // Turn off if cancelled / no card found

+ 0 - 1
non_catalog_apps/seader/uart.c

@@ -33,7 +33,6 @@ void seader_uart_serial_deinit(SeaderUartBridge* seader_uart, uint8_t uart_ch) {
 void seader_uart_set_baudrate(SeaderUartBridge* seader_uart, uint32_t baudrate) {
     if(baudrate != 0) {
         furi_hal_uart_set_br(seader_uart->cfg.uart_ch, baudrate);
-        seader_uart->st.baudrate_cur = baudrate;
     } else {
         FURI_LOG_I(TAG, "No baudrate specified");
     }

+ 152 - 0
non_catalog_apps/seader/views/seader_uart_view.c

@@ -0,0 +1,152 @@
+#include "../seader_bridge.h"
+#include "../seader_i.h"
+#include <furi_hal.h>
+#include <gui/elements.h>
+
+struct SeaderUartView {
+    View* view;
+    SeaderUartViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint32_t tx_cnt;
+    uint32_t rx_cnt;
+    bool tx_active;
+    bool rx_active;
+    uint8_t protocol;
+} SeaderUartViewModel;
+
+static void seader_uart_view_draw_callback(Canvas* canvas, void* _model) {
+    SeaderUartViewModel* model = _model;
+    char temp_str[18];
+
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, "PICC <=> SAM");
+    canvas_draw_str(canvas, 3, 25, "TX:");
+    canvas_draw_str(canvas, 3, 42, "RX:");
+
+    if(model->protocol == FrameProtocol_iclass) {
+        canvas_draw_str_aligned(canvas, 64, 62, AlignCenter, AlignBottom, "Detecting picopass");
+    } else if(model->protocol == FrameProtocol_nfc) {
+        canvas_draw_str_aligned(canvas, 64, 62, AlignCenter, AlignBottom, "Detecting 14a");
+    }
+
+    if(model->tx_cnt < 100000000) {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 127, 24, AlignRight, AlignBottom, "B.");
+        canvas_set_font(canvas, FontKeyboard);
+        snprintf(temp_str, 18, "%lu", model->tx_cnt);
+        canvas_draw_str_aligned(canvas, 116, 24, AlignRight, AlignBottom, temp_str);
+    } else {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 127, 24, AlignRight, AlignBottom, "KiB.");
+        canvas_set_font(canvas, FontKeyboard);
+        snprintf(temp_str, 18, "%lu", model->tx_cnt / 1024);
+        canvas_draw_str_aligned(canvas, 111, 24, AlignRight, AlignBottom, temp_str);
+    }
+
+    if(model->rx_cnt < 100000000) {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 127, 41, AlignRight, AlignBottom, "B.");
+        canvas_set_font(canvas, FontKeyboard);
+        snprintf(temp_str, 18, "%lu", model->rx_cnt);
+        canvas_draw_str_aligned(canvas, 116, 41, AlignRight, AlignBottom, temp_str);
+    } else {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(canvas, 127, 41, AlignRight, AlignBottom, "KiB.");
+        canvas_set_font(canvas, FontKeyboard);
+        snprintf(temp_str, 18, "%lu", model->rx_cnt / 1024);
+        canvas_draw_str_aligned(canvas, 111, 41, AlignRight, AlignBottom, temp_str);
+    }
+
+    if(model->tx_active) {
+        canvas_draw_icon(canvas, 48, 14, &I_ArrowUpFilled_14x15);
+    } else {
+        canvas_draw_icon(canvas, 48, 14, &I_ArrowUpEmpty_14x15);
+    }
+
+    if(model->rx_active) {
+        canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpFilled_14x15, IconRotation180);
+    } else {
+        canvas_draw_icon_ex(canvas, 48, 34, &I_ArrowUpEmpty_14x15, IconRotation180);
+    }
+}
+
+static bool seader_uart_view_input_callback(InputEvent* event, void* context) {
+    furi_assert(context);
+    SeaderUartView* seader_uart_view = context;
+    bool consumed = false;
+
+    if(event->type == InputTypeShort) {
+        if(event->key == InputKeyLeft) {
+            consumed = true;
+            furi_assert(seader_uart_view->callback);
+            // seader_uart_view->callback(SeaderUartViewEventConfig, seader_uart_view->context);
+        }
+    }
+
+    return consumed;
+}
+
+SeaderUartView* seader_uart_view_alloc() {
+    SeaderUartView* seader_uart_view = malloc(sizeof(SeaderUartView));
+
+    seader_uart_view->view = view_alloc();
+    view_allocate_model(seader_uart_view->view, ViewModelTypeLocking, sizeof(SeaderUartViewModel));
+    view_set_context(seader_uart_view->view, seader_uart_view);
+    view_set_draw_callback(seader_uart_view->view, seader_uart_view_draw_callback);
+    view_set_input_callback(seader_uart_view->view, seader_uart_view_input_callback);
+
+    return seader_uart_view;
+}
+
+void seader_uart_view_free(SeaderUartView* seader_uart_view) {
+    furi_assert(seader_uart_view);
+    view_free(seader_uart_view->view);
+    free(seader_uart_view);
+}
+
+View* seader_uart_view_get_view(SeaderUartView* seader_uart_view) {
+    furi_assert(seader_uart_view);
+    return seader_uart_view->view;
+}
+
+void seader_uart_view_set_callback(
+    SeaderUartView* seader_uart_view,
+    SeaderUartViewCallback callback,
+    void* context) {
+    furi_assert(seader_uart_view);
+    furi_assert(callback);
+
+    with_view_model(
+        seader_uart_view->view,
+        SeaderUartViewModel * model,
+        {
+            UNUSED(model);
+            seader_uart_view->callback = callback;
+            seader_uart_view->context = context;
+        },
+        false);
+}
+
+void seader_uart_view_update_state(
+    SeaderUartView* instance,
+    SeaderUartConfig* cfg,
+    SeaderUartState* st) {
+    furi_assert(instance);
+    furi_assert(cfg);
+    furi_assert(st);
+
+    with_view_model(
+        instance->view,
+        SeaderUartViewModel * model,
+        {
+            model->tx_active = (model->tx_cnt != st->tx_cnt);
+            model->rx_active = (model->rx_cnt != st->rx_cnt);
+            model->tx_cnt = st->tx_cnt;
+            model->rx_cnt = st->rx_cnt;
+            model->protocol = st->protocol;
+        },
+        true);
+}

+ 24 - 0
non_catalog_apps/seader/views/seader_uart_view.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../seader_custom_event.h"
+#include "../seader_bridge.h"
+
+typedef struct SeaderUartView SeaderUartView;
+typedef void (*SeaderUartViewCallback)(SeaderCustomEvent event, void* context);
+
+SeaderUartView* seader_uart_view_alloc();
+
+void seader_uart_view_free(SeaderUartView* seader_uart_view);
+
+View* seader_uart_view_get_view(SeaderUartView* seader_uart_view);
+
+void seader_uart_view_set_callback(
+    SeaderUartView* seader_uart_view,
+    SeaderUartViewCallback callback,
+    void* context);
+
+void seader_uart_view_update_state(
+    SeaderUartView* instance,
+    SeaderUartConfig* cfg,
+    SeaderUartState* st);