MX 2 лет назад
Родитель
Сommit
8a1923af47
100 измененных файлов с 10898 добавлено и 0 удалено
  1. 12 0
      .gitmodules
  2. 14 0
      base_pack/arkanoid/application.fam
  3. BIN
      base_pack/arkanoid/arkanoid_10px.png
  4. 479 0
      base_pack/arkanoid/arkanoid_game.c
  5. 17 0
      base_pack/avr_isp_programmer/application.fam
  6. BIN
      base_pack/avr_isp_programmer/avr_app_icon_10x10.png
  7. 179 0
      base_pack/avr_isp_programmer/avr_isp_app.c
  8. 31 0
      base_pack/avr_isp_programmer/avr_isp_app_i.c
  9. 44 0
      base_pack/avr_isp_programmer/avr_isp_app_i.h
  10. 496 0
      base_pack/avr_isp_programmer/helpers/avr_isp.c
  11. 70 0
      base_pack/avr_isp_programmer/helpers/avr_isp.h
  12. 23 0
      base_pack/avr_isp_programmer/helpers/avr_isp_event.h
  13. 32 0
      base_pack/avr_isp_programmer/helpers/avr_isp_types.h
  14. 266 0
      base_pack/avr_isp_programmer/helpers/avr_isp_worker.c
  15. 49 0
      base_pack/avr_isp_programmer/helpers/avr_isp_worker.h
  16. 1157 0
      base_pack/avr_isp_programmer/helpers/avr_isp_worker_rw.c
  17. 99 0
      base_pack/avr_isp_programmer/helpers/avr_isp_worker_rw.h
  18. 321 0
      base_pack/avr_isp_programmer/helpers/flipper_i32hex_file.c
  19. 55 0
      base_pack/avr_isp_programmer/helpers/flipper_i32hex_file.h
  20. BIN
      base_pack/avr_isp_programmer/images/avr_app_icon_10x10.png
  21. BIN
      base_pack/avr_isp_programmer/images/avr_wiring.png
  22. BIN
      base_pack/avr_isp_programmer/images/chif_not_found_83x37.png
  23. BIN
      base_pack/avr_isp_programmer/images/chip_error_70x22.png
  24. BIN
      base_pack/avr_isp_programmer/images/chip_long_70x22.png
  25. BIN
      base_pack/avr_isp_programmer/images/chip_not_found_83x37.png
  26. BIN
      base_pack/avr_isp_programmer/images/dolphin_nice_96x59.png
  27. BIN
      base_pack/avr_isp_programmer/images/isp_active_128x53.png
  28. BIN
      base_pack/avr_isp_programmer/images/link_waiting_77x56.png
  29. 386 0
      base_pack/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c
  30. 33 0
      base_pack/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h
  31. 639 0
      base_pack/avr_isp_programmer/lib/driver/avr_isp_prog.c
  32. 16 0
      base_pack/avr_isp_programmer/lib/driver/avr_isp_prog.h
  33. 97 0
      base_pack/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h
  34. 71 0
      base_pack/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c
  35. 24 0
      base_pack/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h
  36. BIN
      base_pack/avr_isp_programmer/lib/driver/clock.png
  37. 30 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene.c
  38. 29 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene.h
  39. 99 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_about.c
  40. 72 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c
  41. 10 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_config.h
  42. 89 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_input_name.c
  43. 22 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_load.c
  44. 28 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_programmer.c
  45. 64 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_reader.c
  46. 75 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_start.c
  47. 44 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_success.c
  48. 21 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_wiring.c
  49. 69 0
      base_pack/avr_isp_programmer/scenes/avr_isp_scene_writer.c
  50. 213 0
      base_pack/avr_isp_programmer/views/avr_isp_view_chip_detect.c
  51. 32 0
      base_pack/avr_isp_programmer/views/avr_isp_view_chip_detect.h
  52. 134 0
      base_pack/avr_isp_programmer/views/avr_isp_view_programmer.c
  53. 27 0
      base_pack/avr_isp_programmer/views/avr_isp_view_programmer.h
  54. 215 0
      base_pack/avr_isp_programmer/views/avr_isp_view_reader.c
  55. 35 0
      base_pack/avr_isp_programmer/views/avr_isp_view_reader.h
  56. 268 0
      base_pack/avr_isp_programmer/views/avr_isp_view_writer.c
  57. 37 0
      base_pack/avr_isp_programmer/views/avr_isp_view_writer.h
  58. 16 0
      base_pack/bad_bt/application.fam
  59. 333 0
      base_pack/bad_bt/bad_bt_app.c
  60. 39 0
      base_pack/bad_bt/bad_bt_app.h
  61. 792 0
      base_pack/bad_bt/helpers/ducky_script.c
  62. 154 0
      base_pack/bad_bt/helpers/ducky_script.h
  63. 201 0
      base_pack/bad_bt/helpers/ducky_script_commands.c
  64. 44 0
      base_pack/bad_bt/helpers/ducky_script_i.h
  65. 78 0
      base_pack/bad_bt/helpers/ducky_script_keycodes.c
  66. BIN
      base_pack/bad_bt/images/badbt_10px.png
  67. 30 0
      base_pack/bad_bt/scenes/bad_bt_scene.c
  68. 29 0
      base_pack/bad_bt/scenes/bad_bt_scene.h
  69. 104 0
      base_pack/bad_bt/scenes/bad_bt_scene_config.c
  70. 7 0
      base_pack/bad_bt/scenes/bad_bt_scene_config.h
  71. 47 0
      base_pack/bad_bt/scenes/bad_bt_scene_config_layout.c
  72. 52 0
      base_pack/bad_bt/scenes/bad_bt_scene_config_mac.c
  73. 45 0
      base_pack/bad_bt/scenes/bad_bt_scene_config_name.c
  74. 61 0
      base_pack/bad_bt/scenes/bad_bt_scene_error.c
  75. 49 0
      base_pack/bad_bt/scenes/bad_bt_scene_file_select.c
  76. 56 0
      base_pack/bad_bt/scenes/bad_bt_scene_work.c
  77. 233 0
      base_pack/bad_bt/views/bad_bt_view.c
  78. 29 0
      base_pack/bad_bt/views/bad_bt_view.h
  79. 22 0
      base_pack/barcode_gen/LICENSE
  80. 88 0
      base_pack/barcode_gen/README.md
  81. 16 0
      base_pack/barcode_gen/application.fam
  82. 348 0
      base_pack/barcode_gen/barcode_app.c
  83. 88 0
      base_pack/barcode_gen/barcode_app.h
  84. 22 0
      base_pack/barcode_gen/barcode_encoding_files/codabar_encodings.txt
  85. 202 0
      base_pack/barcode_gen/barcode_encoding_files/code128_encodings.txt
  86. 106 0
      base_pack/barcode_gen/barcode_encoding_files/code128c_encodings.txt
  87. 44 0
      base_pack/barcode_gen/barcode_encoding_files/code39_encodings.txt
  88. 147 0
      base_pack/barcode_gen/barcode_utils.c
  89. 55 0
      base_pack/barcode_gen/barcode_utils.h
  90. 532 0
      base_pack/barcode_gen/barcode_validator.c
  91. 15 0
      base_pack/barcode_gen/barcode_validator.h
  92. 52 0
      base_pack/barcode_gen/encodings.c
  93. 6 0
      base_pack/barcode_gen/encodings.h
  94. BIN
      base_pack/barcode_gen/images/barcode_10.png
  95. BIN
      base_pack/barcode_gen/screenshots/Codabar Data Example.png
  96. BIN
      base_pack/barcode_gen/screenshots/Creating Barcode.png
  97. BIN
      base_pack/barcode_gen/screenshots/Flipper Barcode.png
  98. BIN
      base_pack/barcode_gen/screenshots/Flipper Box Barcode.png
  99. 510 0
      base_pack/barcode_gen/views/barcode_view.c
  100. 23 0
      base_pack/barcode_gen/views/barcode_view.h

+ 12 - 0
.gitmodules

@@ -0,0 +1,12 @@
+[submodule "base_pack/dap_link/lib/free-dap"]
+	path = base_pack/dap_link/lib/free-dap
+	url = https://github.com/ataradov/free-dap.git
+[submodule "base_pack/subbrute"]
+	path = base_pack/subbrute
+	url = https://github.com/DarkFlippers/flipperzero-subbrute.git
+[submodule "base_pack/multi_fuzzer"]
+	path = base_pack/multi_fuzzer
+	url = https://github.com/DarkFlippers/Multi_Fuzzer.git
+[submodule "base_pack/totp/lib/wolfssl"]
+	path = base_pack/totp/lib/wolfssl
+	url = https://github.com/wolfSSL/wolfssl.git

+ 14 - 0
base_pack/arkanoid/application.fam

@@ -0,0 +1,14 @@
+App(
+    appid="arkanoid",
+    name="Arkanoid",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="arkanoid_game_app",
+    requires=["gui"],
+    stack_size=1 * 1024,
+    order=30,
+    fap_icon="arkanoid_10px.png",
+    fap_category="Games",
+    fap_author="@xMasterX & @gotnull",
+    fap_version="1.0",
+    fap_description="Arkanoid Game",
+)

BIN
base_pack/arkanoid/arkanoid_10px.png


+ 479 - 0
base_pack/arkanoid/arkanoid_game.c

@@ -0,0 +1,479 @@
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <gui/view.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <dolphin/dolphin.h>
+
+#define TAG "Arkanoid"
+
+#define FLIPPER_LCD_WIDTH 128
+#define FLIPPER_LCD_HEIGHT 64
+#define MAX_SPEED 3
+
+typedef enum { EventTypeTick, EventTypeKey } EventType;
+
+typedef struct {
+    //Brick Bounds used in collision detection
+    int leftBrick;
+    int rightBrick;
+    int topBrick;
+    int bottomBrick;
+    bool isHit[4][13]; //Array of if bricks are hit or not
+} BrickState;
+
+typedef struct {
+    int dx; //Initial movement of ball
+    int dy; //Initial movement of ball
+    int xb; //Balls starting possition
+    int yb; //Balls starting possition
+    bool released; //If the ball has been released by the player
+    //Ball Bounds used in collision detection
+    int leftBall;
+    int rightBall;
+    int topBall;
+    int bottomBall;
+} BallState;
+
+typedef struct {
+    FuriMutex* mutex;
+    BallState ball_state;
+    BrickState brick_state;
+    NotificationApp* notify;
+    unsigned int COLUMNS; //Columns of bricks
+    unsigned int ROWS; //Rows of bricks
+    bool initialDraw; //If the inital draw has happened
+    int xPaddle; //X position of paddle
+    char text[16]; //General string buffer
+    bool bounced; //Used to fix double bounce glitch
+    int lives; //Amount of lives
+    int level; //Current level
+    unsigned int score; //Score for the game
+    unsigned int brickCount; //Amount of bricks hit
+    int tick; //Tick counter
+    bool gameStarted; // Did the game start?
+    int speed; // Ball speed
+} ArkanoidState;
+
+typedef struct {
+    EventType type;
+    InputEvent input;
+} GameEvent;
+
+static const NotificationSequence sequence_short_sound = {
+    &message_note_c5,
+    &message_delay_50,
+    &message_sound_off,
+    NULL,
+};
+
+// generate number in range [min,max)
+int rand_range(int min, int max) {
+    return min + rand() % (max - min);
+}
+
+void move_ball(Canvas* canvas, ArkanoidState* st) {
+    st->tick++;
+
+    int current_speed = abs(st->speed - 1 - MAX_SPEED);
+    if(st->tick % current_speed != 0 && st->tick % (current_speed + 1) != 0) {
+        return;
+    }
+
+    if(st->ball_state.released) {
+        //Move ball
+        if(abs(st->ball_state.dx) == 2) {
+            st->ball_state.xb += st->ball_state.dx / 2;
+            // 2x speed is really 1.5 speed
+            if((st->tick / current_speed) % 2 == 0) st->ball_state.xb += st->ball_state.dx / 2;
+        } else {
+            st->ball_state.xb += st->ball_state.dx;
+        }
+        st->ball_state.yb = st->ball_state.yb + st->ball_state.dy;
+
+        //Set bounds
+        st->ball_state.leftBall = st->ball_state.xb;
+        st->ball_state.rightBall = st->ball_state.xb + 2;
+        st->ball_state.topBall = st->ball_state.yb;
+        st->ball_state.bottomBall = st->ball_state.yb + 2;
+
+        //Bounce off top edge
+        if(st->ball_state.yb <= 0) {
+            st->ball_state.yb = 2;
+            st->ball_state.dy = -st->ball_state.dy;
+        }
+
+        //Lose a life if bottom edge hit
+        if(st->ball_state.yb >= FLIPPER_LCD_HEIGHT) {
+            canvas_draw_frame(canvas, st->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
+            st->xPaddle = 54;
+            st->ball_state.yb = 60;
+            st->ball_state.released = false;
+            st->lives--;
+            st->gameStarted = false;
+
+            if(rand_range(0, 2) == 0) {
+                st->ball_state.dx = 1;
+            } else {
+                st->ball_state.dx = -1;
+            }
+        }
+
+        //Bounce off left side
+        if(st->ball_state.xb <= 0) {
+            st->ball_state.xb = 2;
+            st->ball_state.dx = -st->ball_state.dx;
+        }
+
+        //Bounce off right side
+        if(st->ball_state.xb >= FLIPPER_LCD_WIDTH - 2) {
+            st->ball_state.xb = FLIPPER_LCD_WIDTH - 4;
+            st->ball_state.dx = -st->ball_state.dx;
+            // arduboy.tunes.tone(523, 250);
+        }
+
+        //Bounce off paddle
+        if(st->ball_state.xb + 1 >= st->xPaddle && st->ball_state.xb <= st->xPaddle + 12 &&
+           st->ball_state.yb + 2 >= FLIPPER_LCD_HEIGHT - 1 &&
+           st->ball_state.yb <= FLIPPER_LCD_HEIGHT) {
+            st->ball_state.dy = -st->ball_state.dy;
+            st->ball_state.dx =
+                ((st->ball_state.xb - (st->xPaddle + 6)) / 3); //Applies spin on the ball
+            // prevent straight bounce, but not prevent roguuemaster from stealing
+            if(st->ball_state.dx == 0) {
+                st->ball_state.dx = (rand_range(0, 2) == 1) ? 1 : -1;
+            }
+        }
+
+        //Bounce off Bricks
+        for(unsigned int row = 0; row < st->ROWS; row++) {
+            for(unsigned int column = 0; column < st->COLUMNS; column++) {
+                if(!st->brick_state.isHit[row][column]) {
+                    //Sets Brick bounds
+                    st->brick_state.leftBrick = 10 * column;
+                    st->brick_state.rightBrick = 10 * column + 10;
+                    st->brick_state.topBrick = 6 * row + 1;
+                    st->brick_state.bottomBrick = 6 * row + 7;
+
+                    //If A collison has occured
+                    if(st->ball_state.topBall <= st->brick_state.bottomBrick &&
+                       st->ball_state.bottomBall >= st->brick_state.topBrick &&
+                       st->ball_state.leftBall <= st->brick_state.rightBrick &&
+                       st->ball_state.rightBall >= st->brick_state.leftBrick) {
+                        st->score += st->level;
+                        // Blink led when we hit some brick
+                        notification_message(st->notify, &sequence_short_sound);
+                        //notification_message(st->notify, &sequence_blink_white_100);
+
+                        st->brickCount++;
+                        st->brick_state.isHit[row][column] = true;
+                        canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
+
+                        //Vertical collision
+                        if(st->ball_state.bottomBall > st->brick_state.bottomBrick ||
+                           st->ball_state.topBall < st->brick_state.topBrick) {
+                            //Only bounce once each ball move
+                            if(!st->bounced) {
+                                st->ball_state.dy = -st->ball_state.dy;
+                                st->ball_state.yb += st->ball_state.dy;
+                                st->bounced = true;
+                            }
+                        }
+
+                        //Hoizontal collision
+                        if(st->ball_state.leftBall < st->brick_state.leftBrick ||
+                           st->ball_state.rightBall > st->brick_state.rightBrick) {
+                            //Only bounce once brick each ball move
+                            if(!st->bounced) {
+                                st->ball_state.dx = -st->ball_state.dx;
+                                st->ball_state.xb += st->ball_state.dx;
+                                st->bounced = true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        //Reset Bounce
+        st->bounced = false;
+    } else {
+        //Ball follows paddle
+        st->ball_state.xb = st->xPaddle + 5;
+    }
+}
+
+void draw_lives(Canvas* canvas, ArkanoidState* arkanoid_state) {
+    if(arkanoid_state->lives == 3) {
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
+
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
+
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 15);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 15);
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 16);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 16);
+    } else if(arkanoid_state->lives == 2) {
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
+
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 11);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 11);
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 12);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 12);
+    } else {
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 7);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 7);
+        canvas_draw_dot(canvas, 4, FLIPPER_LCD_HEIGHT - 8);
+        canvas_draw_dot(canvas, 3, FLIPPER_LCD_HEIGHT - 8);
+    }
+}
+
+void draw_score(Canvas* canvas, ArkanoidState* arkanoid_state) {
+    snprintf(arkanoid_state->text, sizeof(arkanoid_state->text), "%u", arkanoid_state->score);
+    canvas_draw_str_aligned(
+        canvas,
+        FLIPPER_LCD_WIDTH - 2,
+        FLIPPER_LCD_HEIGHT - 6,
+        AlignRight,
+        AlignBottom,
+        arkanoid_state->text);
+}
+
+void draw_ball(Canvas* canvas, ArkanoidState* ast) {
+    canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb);
+    canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb);
+    canvas_draw_dot(canvas, ast->ball_state.xb, ast->ball_state.yb + 1);
+    canvas_draw_dot(canvas, ast->ball_state.xb + 1, ast->ball_state.yb + 1);
+
+    move_ball(canvas, ast);
+}
+
+void draw_paddle(Canvas* canvas, ArkanoidState* arkanoid_state) {
+    canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
+}
+
+void reset_level(Canvas* canvas, ArkanoidState* arkanoid_state) {
+    //Undraw paddle
+    canvas_draw_frame(canvas, arkanoid_state->xPaddle, FLIPPER_LCD_HEIGHT - 1, 11, 1);
+
+    //Undraw ball
+    canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb);
+    canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb);
+    canvas_draw_dot(canvas, arkanoid_state->ball_state.xb, arkanoid_state->ball_state.yb + 1);
+    canvas_draw_dot(canvas, arkanoid_state->ball_state.xb + 1, arkanoid_state->ball_state.yb + 1);
+
+    //Alter various variables to reset the game
+    arkanoid_state->xPaddle = 54;
+    arkanoid_state->ball_state.yb = 60;
+    arkanoid_state->brickCount = 0;
+    arkanoid_state->ball_state.released = false;
+    arkanoid_state->gameStarted = false;
+
+    // Reset all brick hit states
+    for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
+        for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
+            arkanoid_state->brick_state.isHit[row][column] = false;
+        }
+    }
+}
+
+static void arkanoid_state_init(ArkanoidState* arkanoid_state) {
+    // Init notification
+    arkanoid_state->notify = furi_record_open(RECORD_NOTIFICATION);
+
+    // Set the initial game state
+    arkanoid_state->COLUMNS = 13;
+    arkanoid_state->ROWS = 4;
+    arkanoid_state->ball_state.dx = -1;
+    arkanoid_state->ball_state.dy = -1;
+    arkanoid_state->speed = 2;
+    arkanoid_state->bounced = false;
+    arkanoid_state->lives = 3;
+    arkanoid_state->level = 1;
+    arkanoid_state->score = 0;
+    arkanoid_state->COLUMNS = 13;
+    arkanoid_state->COLUMNS = 13;
+
+    // Reset initial state
+    arkanoid_state->initialDraw = false;
+    arkanoid_state->gameStarted = false;
+}
+
+static void arkanoid_draw_callback(Canvas* const canvas, void* ctx) {
+    furi_assert(ctx);
+    ArkanoidState* arkanoid_state = ctx;
+    furi_mutex_acquire(arkanoid_state->mutex, FuriWaitForever);
+
+    //Initial level draw
+    if(!arkanoid_state->initialDraw) {
+        arkanoid_state->initialDraw = true;
+
+        // Set default font for text
+        canvas_set_font(canvas, FontSecondary);
+
+        //Draws the new level
+        reset_level(canvas, arkanoid_state);
+    }
+
+    //Draws new bricks and resets their values
+    for(unsigned int row = 0; row < arkanoid_state->ROWS; row++) {
+        for(unsigned int column = 0; column < arkanoid_state->COLUMNS; column++) {
+            if(!arkanoid_state->brick_state.isHit[row][column]) {
+                canvas_draw_frame(canvas, 10 * column, 2 + 6 * row, 8, 4);
+            }
+        }
+    }
+
+    if(arkanoid_state->lives > 0) {
+        draw_paddle(canvas, arkanoid_state);
+        draw_ball(canvas, arkanoid_state);
+        draw_score(canvas, arkanoid_state);
+        draw_lives(canvas, arkanoid_state);
+
+        if(arkanoid_state->brickCount == arkanoid_state->ROWS * arkanoid_state->COLUMNS) {
+            arkanoid_state->level++;
+            reset_level(canvas, arkanoid_state);
+        }
+    } else {
+        reset_level(canvas, arkanoid_state);
+        arkanoid_state->initialDraw = false;
+        arkanoid_state->lives = 3;
+        arkanoid_state->score = 0;
+    }
+
+    furi_mutex_release(arkanoid_state->mutex);
+}
+
+static void arkanoid_input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    GameEvent event = {.type = EventTypeKey, .input = *input_event};
+    furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void arkanoid_update_timer_callback(FuriMessageQueue* event_queue) {
+    furi_assert(event_queue);
+
+    GameEvent event = {.type = EventTypeTick};
+    furi_message_queue_put(event_queue, &event, 0);
+}
+
+int32_t arkanoid_game_app(void* p) {
+    UNUSED(p);
+    int32_t return_code = 0;
+
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
+
+    ArkanoidState* arkanoid_state = malloc(sizeof(ArkanoidState));
+    arkanoid_state_init(arkanoid_state);
+
+    arkanoid_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!arkanoid_state->mutex) {
+        FURI_LOG_E(TAG, "Cannot create mutex\r\n");
+        return_code = 255;
+        goto free_and_exit;
+    }
+
+    // Set system callbacks
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, arkanoid_draw_callback, arkanoid_state);
+    view_port_input_callback_set(view_port, arkanoid_input_callback, event_queue);
+
+    FuriTimer* timer =
+        furi_timer_alloc(arkanoid_update_timer_callback, FuriTimerTypePeriodic, event_queue);
+    furi_timer_start(timer, furi_kernel_get_tick_frequency() / 22);
+
+    // Open GUI and register view_port
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    // Call dolphin deed on game start
+    dolphin_deed(DolphinDeedPluginGameStart);
+
+    GameEvent event;
+    for(bool processing = true; processing;) {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
+        furi_mutex_acquire(arkanoid_state->mutex, FuriWaitForever);
+
+        if(event_status == FuriStatusOk) {
+            // Key events
+            if(event.type == EventTypeKey) {
+                if(event.input.type == InputTypePress || event.input.type == InputTypeLong ||
+                   event.input.type == InputTypeRepeat) {
+                    switch(event.input.key) {
+                    case InputKeyBack:
+                        processing = false;
+                        break;
+                    case InputKeyRight:
+                        if(arkanoid_state->xPaddle < FLIPPER_LCD_WIDTH - 12) {
+                            arkanoid_state->xPaddle += 8;
+                        }
+                        break;
+                    case InputKeyLeft:
+                        if(arkanoid_state->xPaddle > 0) {
+                            arkanoid_state->xPaddle -= 8;
+                        }
+                        break;
+                    case InputKeyUp:
+                        if(arkanoid_state->speed < MAX_SPEED) {
+                            arkanoid_state->speed++;
+                        }
+                        break;
+                    case InputKeyDown:
+                        if(arkanoid_state->speed > 1) {
+                            arkanoid_state->speed--;
+                        }
+                        break;
+                    case InputKeyOk:
+                        if(arkanoid_state->gameStarted == false) {
+                            //Release ball if FIRE pressed
+                            arkanoid_state->ball_state.released = true;
+
+                            //Apply random direction to ball on release
+                            if(rand_range(0, 2) == 0) {
+                                arkanoid_state->ball_state.dx = 1;
+                            } else {
+                                arkanoid_state->ball_state.dx = -1;
+                            }
+
+                            //Makes sure the ball heads upwards
+                            arkanoid_state->ball_state.dy = -1;
+                            //start the game flag
+                            arkanoid_state->gameStarted = true;
+                        }
+                        break;
+                    default:
+                        break;
+                    }
+                }
+            }
+        }
+
+        view_port_update(view_port);
+        furi_mutex_release(arkanoid_state->mutex);
+    }
+    furi_timer_free(timer);
+    view_port_enabled_set(view_port, false);
+    gui_remove_view_port(gui, view_port);
+    furi_record_close(RECORD_GUI);
+    furi_record_close(RECORD_NOTIFICATION);
+    view_port_free(view_port);
+    furi_mutex_free(arkanoid_state->mutex);
+
+free_and_exit:
+    free(arkanoid_state);
+    furi_message_queue_free(event_queue);
+
+    return return_code;
+}

+ 17 - 0
base_pack/avr_isp_programmer/application.fam

@@ -0,0 +1,17 @@
+App(
+    appid="avr_isp",
+    name="AVR Flasher",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="avr_isp_app",
+    requires=["gui"],
+    stack_size=4 * 1024,
+    order=20,
+    fap_icon="avr_app_icon_10x10.png",
+    fap_category="GPIO",
+    fap_icon_assets="images",
+    fap_private_libs=[
+        Lib(
+            name="driver",
+        ),
+    ],
+)

BIN
base_pack/avr_isp_programmer/avr_app_icon_10x10.png


+ 179 - 0
base_pack/avr_isp_programmer/avr_isp_app.c

@@ -0,0 +1,179 @@
+#include "avr_isp_app_i.h"
+
+static bool avr_isp_app_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool avr_isp_app_back_event_callback(void* context) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void avr_isp_app_tick_event_callback(void* context) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+AvrIspApp* avr_isp_app_alloc() {
+    AvrIspApp* app = malloc(sizeof(AvrIspApp));
+
+    app->file_path = furi_string_alloc();
+    furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
+    app->error = AvrIspErrorNoError;
+
+    // GUI
+    app->gui = furi_record_open(RECORD_GUI);
+
+    // View Dispatcher
+    app->view_dispatcher = view_dispatcher_alloc();
+    app->scene_manager = scene_manager_alloc(&avr_isp_scene_handlers, app);
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, avr_isp_app_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, avr_isp_app_back_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, avr_isp_app_tick_event_callback, 100);
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    // Open Notification record
+    app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+    // SubMenu
+    app->submenu = submenu_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, AvrIspViewSubmenu, submenu_get_view(app->submenu));
+
+    // Widget
+    app->widget = widget_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, AvrIspViewWidget, widget_get_view(app->widget));
+
+    // Text Input
+    app->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, AvrIspViewTextInput, text_input_get_view(app->text_input));
+
+    // Popup
+    app->popup = popup_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, AvrIspViewPopup, popup_get_view(app->popup));
+
+    //Dialog
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    // Programmer view
+    app->avr_isp_programmer_view = avr_isp_programmer_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewProgrammer,
+        avr_isp_programmer_view_get_view(app->avr_isp_programmer_view));
+
+    // Reader view
+    app->avr_isp_reader_view = avr_isp_reader_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewReader,
+        avr_isp_reader_view_get_view(app->avr_isp_reader_view));
+
+    // Writer view
+    app->avr_isp_writer_view = avr_isp_writer_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewWriter,
+        avr_isp_writer_view_get_view(app->avr_isp_writer_view));
+
+    // Chip detect view
+    app->avr_isp_chip_detect_view = avr_isp_chip_detect_view_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        AvrIspViewChipDetect,
+        avr_isp_chip_detect_view_get_view(app->avr_isp_chip_detect_view));
+
+    // Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering
+    uint8_t attempts = 0;
+    while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
+        furi_hal_power_enable_otg();
+        furi_delay_ms(10);
+    }
+
+    scene_manager_next_scene(app->scene_manager, AvrIspSceneStart);
+
+    return app;
+} //-V773
+
+void avr_isp_app_free(AvrIspApp* app) {
+    furi_assert(app);
+
+    // Disable 5v power
+    if(furi_hal_power_is_otg_enabled()) {
+        furi_hal_power_disable_otg();
+    }
+
+    // Submenu
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewSubmenu);
+    submenu_free(app->submenu);
+
+    //  Widget
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWidget);
+    widget_free(app->widget);
+
+    // TextInput
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewTextInput);
+    text_input_free(app->text_input);
+
+    // Popup
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewPopup);
+    popup_free(app->popup);
+
+    //Dialog
+    furi_record_close(RECORD_DIALOGS);
+
+    // Programmer view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewProgrammer);
+    avr_isp_programmer_view_free(app->avr_isp_programmer_view);
+
+    // Reader view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewReader);
+    avr_isp_reader_view_free(app->avr_isp_reader_view);
+
+    // Writer view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewWriter);
+    avr_isp_writer_view_free(app->avr_isp_writer_view);
+
+    // Chip detect view
+    view_dispatcher_remove_view(app->view_dispatcher, AvrIspViewChipDetect);
+    avr_isp_chip_detect_view_free(app->avr_isp_chip_detect_view);
+
+    // View dispatcher
+    view_dispatcher_free(app->view_dispatcher);
+    scene_manager_free(app->scene_manager);
+
+    // Notifications
+    furi_record_close(RECORD_NOTIFICATION);
+    app->notifications = NULL;
+
+    // Close records
+    furi_record_close(RECORD_GUI);
+
+    // Path strings
+    furi_string_free(app->file_path);
+
+    free(app);
+}
+
+int32_t avr_isp_app(void* p) {
+    UNUSED(p);
+    AvrIspApp* avr_isp_app = avr_isp_app_alloc();
+
+    view_dispatcher_run(avr_isp_app->view_dispatcher);
+
+    avr_isp_app_free(avr_isp_app);
+
+    return 0;
+}

+ 31 - 0
base_pack/avr_isp_programmer/avr_isp_app_i.c

@@ -0,0 +1,31 @@
+#include "avr_isp_app_i.h"
+#include <lib/toolbox/path.h>
+#include <flipper_format/flipper_format_i.h>
+
+#define TAG "AvrIsp"
+
+bool avr_isp_load_from_file(AvrIspApp* app) {
+    furi_assert(app);
+
+    FuriString* file_path = furi_string_alloc();
+    FuriString* file_name = furi_string_alloc();
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, AVR_ISP_APP_EXTENSION, &I_avr_app_icon_10x10);
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+
+    // Input events and views are managed by file_select
+    bool res = dialog_file_browser_show(app->dialogs, file_path, app->file_path, &browser_options);
+
+    if(res) {
+        path_extract_dirname(furi_string_get_cstr(file_path), app->file_path);
+        path_extract_filename(file_path, file_name, true);
+        strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME);
+    }
+
+    furi_string_free(file_name);
+    furi_string_free(file_path);
+
+    return res;
+}

+ 44 - 0
base_pack/avr_isp_programmer/avr_isp_app_i.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#include "helpers/avr_isp_types.h"
+#include <avr_isp_icons.h>
+
+#include "scenes/avr_isp_scene.h"
+#include <gui/gui.h>
+#include <gui/view_dispatcher.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
+#include <notification/notification_messages.h>
+#include <gui/modules/text_input.h>
+#include <dialogs/dialogs.h>
+#include <storage/storage.h>
+#include <gui/modules/popup.h>
+
+#include "views/avr_isp_view_programmer.h"
+#include "views/avr_isp_view_reader.h"
+#include "views/avr_isp_view_writer.h"
+#include "views/avr_isp_view_chip_detect.h"
+
+#define AVR_ISP_MAX_LEN_NAME 64
+
+typedef struct {
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    SceneManager* scene_manager;
+    NotificationApp* notifications;
+    DialogsApp* dialogs;
+    Popup* popup;
+    Submenu* submenu;
+    Widget* widget;
+    TextInput* text_input;
+    FuriString* file_path;
+    char file_name_tmp[AVR_ISP_MAX_LEN_NAME];
+    AvrIspProgrammerView* avr_isp_programmer_view;
+    AvrIspReaderView* avr_isp_reader_view;
+    AvrIspWriterView* avr_isp_writer_view;
+    AvrIspChipDetectView* avr_isp_chip_detect_view;
+    AvrIspError error;
+} AvrIspApp;
+
+bool avr_isp_load_from_file(AvrIspApp* app);

+ 496 - 0
base_pack/avr_isp_programmer/helpers/avr_isp.c

@@ -0,0 +1,496 @@
+#include "avr_isp.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_spi_sw.h"
+
+#include <furi.h>
+
+#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320
+#define TAG "AvrIsp"
+
+struct AvrIsp {
+    AvrIspSpiSw* spi;
+    bool pmode;
+    AvrIspCallback callback;
+    void* context;
+};
+
+AvrIsp* avr_isp_alloc(void) {
+    AvrIsp* instance = malloc(sizeof(AvrIsp));
+    return instance;
+}
+
+void avr_isp_free(AvrIsp* instance) {
+    furi_assert(instance);
+
+    if(instance->spi) avr_isp_end_pmode(instance);
+    free(instance);
+}
+
+void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(context);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+uint8_t avr_isp_spi_transaction(
+    AvrIsp* instance,
+    uint8_t cmd,
+    uint8_t addr_hi,
+    uint8_t addr_lo,
+    uint8_t data) {
+    furi_assert(instance);
+
+    avr_isp_spi_sw_txrx(instance->spi, cmd);
+    avr_isp_spi_sw_txrx(instance->spi, addr_hi);
+    avr_isp_spi_sw_txrx(instance->spi, addr_lo);
+    return avr_isp_spi_sw_txrx(instance->spi, data);
+}
+
+static bool avr_isp_set_pmode(AvrIsp* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+    furi_assert(instance);
+
+    uint8_t res = 0;
+    avr_isp_spi_sw_txrx(instance->spi, a);
+    avr_isp_spi_sw_txrx(instance->spi, b);
+    res = avr_isp_spi_sw_txrx(instance->spi, c);
+    avr_isp_spi_sw_txrx(instance->spi, d);
+    return res == 0x53;
+}
+
+void avr_isp_end_pmode(AvrIsp* instance) {
+    furi_assert(instance);
+
+    if(instance->pmode) {
+        avr_isp_spi_sw_res_set(instance->spi, true);
+        // We're about to take the target out of reset
+        // so configure SPI pins as input
+        if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    instance->pmode = false;
+}
+
+static bool avr_isp_start_pmode(AvrIsp* instance, AvrIspSpiSwSpeed spi_speed) {
+    furi_assert(instance);
+
+    // Reset target before driving PIN_SCK or PIN_MOSI
+
+    // SPI.begin() will configure SS as output,
+    // so SPI master mode is selected.
+    // We have defined RESET as pin 10,
+    // which for many arduino's is not the SS pin.
+    // So we have to configure RESET as output here,
+    // (reset_target() first sets the correct level)
+    if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+    instance->spi = avr_isp_spi_sw_init(spi_speed);
+
+    avr_isp_spi_sw_res_set(instance->spi, false);
+    // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm":
+
+    // Pulse RESET after PIN_SCK is low:
+    avr_isp_spi_sw_sck_set(instance->spi, false);
+
+    // discharge PIN_SCK, value arbitrally chosen
+    furi_delay_ms(20);
+    avr_isp_spi_sw_res_set(instance->spi, true);
+
+    // Pulse must be minimum 2 target CPU speed cycles
+    // so 100 usec is ok for CPU speeds above 20KHz
+    furi_delay_ms(1);
+
+    avr_isp_spi_sw_res_set(instance->spi, false);
+
+    // Send the enable programming command:
+    // datasheet: must be > 20 msec
+    furi_delay_ms(50);
+    if(avr_isp_set_pmode(instance, AVR_ISP_SET_PMODE)) {
+        instance->pmode = true;
+        return true;
+    }
+    return false;
+}
+
+bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance) {
+    furi_assert(instance);
+
+    AvrIspSpiSwSpeed spi_speed[] = {
+        AvrIspSpiSwSpeed1Mhz,
+        AvrIspSpiSwSpeed400Khz,
+        AvrIspSpiSwSpeed250Khz,
+        AvrIspSpiSwSpeed125Khz,
+        AvrIspSpiSwSpeed60Khz,
+        AvrIspSpiSwSpeed40Khz,
+        AvrIspSpiSwSpeed20Khz,
+        AvrIspSpiSwSpeed10Khz,
+        AvrIspSpiSwSpeed5Khz,
+        AvrIspSpiSwSpeed1Khz,
+    };
+    for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) {
+        if(avr_isp_start_pmode(instance, spi_speed[i])) {
+            AvrIspSignature sig = avr_isp_read_signature(instance);
+            AvrIspSignature sig_examination = avr_isp_read_signature(instance); //-V656
+            uint8_t y = 0;
+            while(y < 8) {
+                if(memcmp((uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspSignature)) !=
+                   0)
+                    break;
+                sig_examination = avr_isp_read_signature(instance);
+                y++;
+            }
+            if(y == 8) {
+                if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) {
+                    if(i < (COUNT_OF(spi_speed) - 1)) {
+                        avr_isp_end_pmode(instance);
+                        i++;
+                        return avr_isp_start_pmode(instance, spi_speed[i]);
+                    }
+                }
+                return true;
+            }
+        }
+    }
+
+    if(instance->spi) {
+        avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    return false;
+}
+
+static void avr_isp_commit(AvrIsp* instance, uint16_t addr, uint8_t data) {
+    furi_assert(instance);
+
+    avr_isp_spi_transaction(instance, AVR_ISP_COMMIT(addr));
+    /* polling flash */
+    if(data == 0xFF) {
+        furi_delay_ms(5);
+    } else {
+        /* polling flash */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
+                break;
+            };
+        }
+    }
+}
+
+static uint16_t avr_isp_current_page(AvrIsp* instance, uint32_t addr, uint16_t page_size) {
+    furi_assert(instance);
+
+    uint16_t page = 0;
+    switch(page_size) {
+    case 32:
+        page = addr & 0xFFFFFFF0;
+        break;
+    case 64:
+        page = addr & 0xFFFFFFE0;
+        break;
+    case 128:
+        page = addr & 0xFFFFFFC0;
+        break;
+    case 256:
+        page = addr & 0xFFFFFF80;
+        break;
+
+    default:
+        page = addr;
+        break;
+    }
+
+    return page;
+}
+
+static bool avr_isp_flash_write_pages(
+    AvrIsp* instance,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    size_t x = 0;
+    uint16_t page = avr_isp_current_page(instance, addr, page_size);
+
+    while(x < data_size) {
+        if(page != avr_isp_current_page(instance, addr, page_size)) {
+            avr_isp_commit(instance, page, data[x - 1]);
+            page = avr_isp_current_page(instance, addr, page_size);
+        }
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_LO(addr, data[x++]));
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FLASH_HI(addr, data[x++]));
+        addr++;
+    }
+    avr_isp_commit(instance, page, data[x - 1]);
+    return true;
+}
+
+bool avr_isp_erase_chip(AvrIsp* instance) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(!instance->pmode) avr_isp_auto_set_spi_speed_start_pmode(instance);
+    if(instance->pmode) {
+        avr_isp_spi_transaction(instance, AVR_ISP_ERASE_CHIP);
+        furi_delay_ms(100);
+        avr_isp_end_pmode(instance);
+        ret = true;
+    }
+    return ret;
+}
+
+static bool
+    avr_isp_eeprom_write(AvrIsp* instance, uint16_t addr, uint8_t* data, uint32_t data_size) {
+    furi_assert(instance);
+
+    for(uint16_t i = 0; i < data_size; i++) {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, data[i]));
+        furi_delay_ms(10);
+        addr++;
+    }
+    return true;
+}
+
+bool avr_isp_write_page(
+    AvrIsp* instance,
+    uint32_t mem_type,
+    uint32_t mem_size,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    bool ret = false;
+    switch(mem_type) {
+    case STK_SET_FLASH_TYPE:
+        if((addr + data_size / 2) <= mem_size) {
+            ret = avr_isp_flash_write_pages(instance, addr, page_size, data, data_size);
+        }
+        break;
+
+    case STK_SET_EEPROM_TYPE:
+        if((addr + data_size) <= mem_size) {
+            ret = avr_isp_eeprom_write(instance, addr, data, data_size);
+        }
+        break;
+
+    default:
+        furi_crash(TAG " Incorrect mem type.");
+        break;
+    }
+
+    return ret;
+}
+
+static bool avr_isp_flash_read_page(
+    AvrIsp* instance,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    if(page_size > data_size) return false;
+    for(uint16_t i = 0; i < page_size; i += 2) {
+        data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(addr));
+        data[i + 1] = avr_isp_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr));
+        addr++;
+    }
+    return true;
+}
+
+static bool avr_isp_eeprom_read_page(
+    AvrIsp* instance,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    if(page_size > data_size) return false;
+    for(uint16_t i = 0; i < page_size; i++) {
+        data[i] = avr_isp_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr));
+        addr++;
+    }
+    return true;
+}
+
+bool avr_isp_read_page(
+    AvrIsp* instance,
+    uint32_t mem_type,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+
+    bool res = false;
+    if(mem_type == STK_SET_FLASH_TYPE)
+        res = avr_isp_flash_read_page(instance, addr, page_size, data, data_size);
+    if(mem_type == STK_SET_EEPROM_TYPE)
+        res = avr_isp_eeprom_read_page(instance, addr, page_size, data, data_size);
+
+    return res;
+}
+
+AvrIspSignature avr_isp_read_signature(AvrIsp* instance) {
+    furi_assert(instance);
+
+    AvrIspSignature signature;
+    signature.vendor = avr_isp_spi_transaction(instance, AVR_ISP_READ_VENDOR);
+    signature.part_family = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY);
+    signature.part_number = avr_isp_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER);
+    return signature;
+}
+
+uint8_t avr_isp_read_lock_byte(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_lock_byte(instance) == lock) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_LOCK_BYTE(lock));
+        /* polling lock byte */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_LOCK_BYTE) == lock) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+uint8_t avr_isp_read_fuse_low(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_fuse_low(instance) == lfuse) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_LOW(lfuse));
+        /* polling fuse */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_LOW) == lfuse) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+uint8_t avr_isp_read_fuse_high(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_fuse_high(instance) == hfuse) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_HIGH(hfuse));
+        /* polling fuse */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_HIGH) == hfuse) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+uint8_t avr_isp_read_fuse_extended(AvrIsp* instance) {
+    furi_assert(instance);
+
+    uint8_t data = 0;
+    uint32_t starttime = furi_get_tick();
+    while((furi_get_tick() - starttime) < 300) {
+        data = avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED);
+        if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == data) {
+            break;
+        };
+        data = 0x00;
+    }
+    return data;
+}
+
+bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse) {
+    furi_assert(instance);
+
+    bool ret = false;
+    if(avr_isp_read_fuse_extended(instance) == efuse) {
+        ret = true;
+    } else {
+        avr_isp_spi_transaction(instance, AVR_ISP_WRITE_FUSE_EXTENDED(efuse));
+        /* polling fuse */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_spi_transaction(instance, AVR_ISP_READ_FUSE_EXTENDED) == efuse) {
+                ret = true;
+                break;
+            };
+        }
+    }
+    return ret;
+}
+
+void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr) {
+    furi_assert(instance);
+
+    avr_isp_spi_transaction(instance, AVR_ISP_EXTENDED_ADDR(extended_addr));
+    furi_delay_ms(10);
+}

+ 70 - 0
base_pack/avr_isp_programmer/helpers/avr_isp.h

@@ -0,0 +1,70 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct AvrIsp AvrIsp;
+typedef void (*AvrIspCallback)(void* context);
+
+struct AvrIspSignature {
+    uint8_t vendor;
+    uint8_t part_family;
+    uint8_t part_number;
+};
+
+typedef struct AvrIspSignature AvrIspSignature;
+
+AvrIsp* avr_isp_alloc(void);
+
+void avr_isp_free(AvrIsp* instance);
+
+void avr_isp_set_tx_callback(AvrIsp* instance, AvrIspCallback callback, void* context);
+
+bool avr_isp_auto_set_spi_speed_start_pmode(AvrIsp* instance);
+
+AvrIspSignature avr_isp_read_signature(AvrIsp* instance);
+
+void avr_isp_end_pmode(AvrIsp* instance);
+
+bool avr_isp_erase_chip(AvrIsp* instance);
+
+uint8_t avr_isp_spi_transaction(
+    AvrIsp* instance,
+    uint8_t cmd,
+    uint8_t addr_hi,
+    uint8_t addr_lo,
+    uint8_t data);
+
+bool avr_isp_read_page(
+    AvrIsp* instance,
+    uint32_t memtype,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size);
+
+bool avr_isp_write_page(
+    AvrIsp* instance,
+    uint32_t mem_type,
+    uint32_t mem_size,
+    uint16_t addr,
+    uint16_t page_size,
+    uint8_t* data,
+    uint32_t data_size);
+
+uint8_t avr_isp_read_lock_byte(AvrIsp* instance);
+
+bool avr_isp_write_lock_byte(AvrIsp* instance, uint8_t lock);
+
+uint8_t avr_isp_read_fuse_low(AvrIsp* instance);
+
+bool avr_isp_write_fuse_low(AvrIsp* instance, uint8_t lfuse);
+
+uint8_t avr_isp_read_fuse_high(AvrIsp* instance);
+
+bool avr_isp_write_fuse_high(AvrIsp* instance, uint8_t hfuse);
+
+uint8_t avr_isp_read_fuse_extended(AvrIsp* instance);
+
+bool avr_isp_write_fuse_extended(AvrIsp* instance, uint8_t efuse);
+
+void avr_isp_write_extended_addr(AvrIsp* instance, uint8_t extended_addr);

+ 23 - 0
base_pack/avr_isp_programmer/helpers/avr_isp_event.h

@@ -0,0 +1,23 @@
+#pragma once
+
+typedef enum {
+    //SubmenuIndex
+    SubmenuIndexAvrIspProgrammer = 10,
+    SubmenuIndexAvrIspReader,
+    SubmenuIndexAvrIspWriter,
+    SubmenuIndexAvrIsWiring,
+    SubmenuIndexAvrIspAbout,
+
+    //AvrIspCustomEvent
+    AvrIspCustomEventSceneChipDetectOk = 100,
+    AvrIspCustomEventSceneReadingOk,
+    AvrIspCustomEventSceneWritingOk,
+    AvrIspCustomEventSceneErrorVerification,
+    AvrIspCustomEventSceneErrorReading,
+    AvrIspCustomEventSceneErrorWriting,
+    AvrIspCustomEventSceneErrorWritingFuse,
+    AvrIspCustomEventSceneInputName,
+    AvrIspCustomEventSceneSuccess,
+    AvrIspCustomEventSceneExit,
+    AvrIspCustomEventSceneExitStartMenu,
+} AvrIspCustomEvent;

+ 32 - 0
base_pack/avr_isp_programmer/helpers/avr_isp_types.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+#define AVR_ISP_VERSION_APP "0.1"
+#define AVR_ISP_DEVELOPED "SkorP"
+#define AVR_ISP_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
+
+#define AVR_ISP_APP_FILE_VERSION 1
+#define AVR_ISP_APP_FILE_TYPE "Flipper Dump AVR"
+#define AVR_ISP_APP_EXTENSION ".avr"
+
+typedef enum {
+    //AvrIspViewVariableItemList,
+    AvrIspViewSubmenu,
+    AvrIspViewProgrammer,
+    AvrIspViewReader,
+    AvrIspViewWriter,
+    AvrIspViewWidget,
+    AvrIspViewPopup,
+    AvrIspViewTextInput,
+    AvrIspViewChipDetect,
+} AvrIspView;
+
+typedef enum {
+    AvrIspErrorNoError,
+    AvrIspErrorReading,
+    AvrIspErrorWriting,
+    AvrIspErrorVerification,
+    AvrIspErrorWritingFuse,
+} AvrIspError;

+ 266 - 0
base_pack/avr_isp_programmer/helpers/avr_isp_worker.c

@@ -0,0 +1,266 @@
+#include "avr_isp_worker.h"
+#include <furi_hal_pwm.h>
+#include "../lib/driver/avr_isp_prog.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_chip_arr.h"
+
+#include <furi.h>
+
+#define TAG "AvrIspWorker"
+
+typedef enum {
+    AvrIspWorkerEvtStop = (1 << 0),
+
+    AvrIspWorkerEvtRx = (1 << 1),
+    AvrIspWorkerEvtTxCoplete = (1 << 2),
+    AvrIspWorkerEvtTx = (1 << 3),
+    AvrIspWorkerEvtState = (1 << 4),
+
+    //AvrIspWorkerEvtCfg = (1 << 5),
+
+} AvrIspWorkerEvt;
+
+struct AvrIspWorker {
+    FuriThread* thread;
+    volatile bool worker_running;
+    uint8_t connect_usb;
+    AvrIspWorkerCallback callback;
+    void* context;
+};
+
+#define AVR_ISP_WORKER_PROG_ALL_EVENTS (AvrIspWorkerEvtStop)
+#define AVR_ISP_WORKER_ALL_EVENTS                                                             \
+    (AvrIspWorkerEvtTx | AvrIspWorkerEvtTxCoplete | AvrIspWorkerEvtRx | AvrIspWorkerEvtStop | \
+     AvrIspWorkerEvtState)
+
+//########################/* VCP CDC */#############################################
+#include "usb_cdc.h"
+#include <cli/cli_vcp.h>
+#include <cli/cli.h>
+#include <furi_hal_usb_cdc.h>
+
+#define AVR_ISP_VCP_CDC_CH 1
+#define AVR_ISP_VCP_CDC_PKT_LEN CDC_DATA_SZ
+#define AVR_ISP_VCP_UART_RX_BUF_SIZE (AVR_ISP_VCP_CDC_PKT_LEN * 5)
+
+static void vcp_on_cdc_tx_complete(void* context);
+static void vcp_on_cdc_rx(void* context);
+static void vcp_state_callback(void* context, uint8_t state);
+static void vcp_on_cdc_control_line(void* context, uint8_t state);
+static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config);
+
+static const CdcCallbacks cdc_cb = {
+    vcp_on_cdc_tx_complete,
+    vcp_on_cdc_rx,
+    vcp_state_callback,
+    vcp_on_cdc_control_line,
+    vcp_on_line_config,
+};
+
+/* VCP callbacks */
+
+static void vcp_on_cdc_tx_complete(void* context) {
+    furi_assert(context);
+    AvrIspWorker* instance = context;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTxCoplete);
+}
+
+static void vcp_on_cdc_rx(void* context) {
+    furi_assert(context);
+    AvrIspWorker* instance = context;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx);
+}
+
+static void vcp_state_callback(void* context, uint8_t state) {
+    UNUSED(context);
+
+    AvrIspWorker* instance = context;
+    instance->connect_usb = state;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtState);
+}
+
+static void vcp_on_cdc_control_line(void* context, uint8_t state) {
+    UNUSED(context);
+    UNUSED(state);
+}
+
+static void vcp_on_line_config(void* context, struct usb_cdc_line_coding* config) {
+    UNUSED(context);
+    UNUSED(config);
+}
+
+static void avr_isp_worker_vcp_cdc_init(void* context) {
+    furi_hal_usb_unlock();
+    Cli* cli = furi_record_open(RECORD_CLI);
+    //close cli
+    cli_session_close(cli);
+    //disable callbacks VCP_CDC=0
+    furi_hal_cdc_set_callbacks(0, NULL, NULL);
+    //set 2 cdc
+    furi_check(furi_hal_usb_set_config(&usb_cdc_dual, NULL) == true);
+    //open cli VCP_CDC=0
+    cli_session_open(cli, &cli_vcp);
+    furi_record_close(RECORD_CLI);
+
+    furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, (CdcCallbacks*)&cdc_cb, context);
+}
+
+static void avr_isp_worker_vcp_cdc_deinit(void) {
+    //disable callbacks AVR_ISP_VCP_CDC_CH
+    furi_hal_cdc_set_callbacks(AVR_ISP_VCP_CDC_CH, NULL, NULL);
+
+    Cli* cli = furi_record_open(RECORD_CLI);
+    //close cli
+    cli_session_close(cli);
+    furi_hal_usb_unlock();
+    //set 1 cdc
+    furi_check(furi_hal_usb_set_config(&usb_cdc_single, NULL) == true);
+    //open cli VCP_CDC=0
+    cli_session_open(cli, &cli_vcp);
+    furi_record_close(RECORD_CLI);
+}
+
+//#################################################################################
+
+static int32_t avr_isp_worker_prog_thread(void* context) {
+    AvrIspProg* prog = context;
+    FURI_LOG_D(TAG, "AvrIspProgWorker Start");
+    while(1) {
+        if(furi_thread_flags_get() & AvrIspWorkerEvtStop) break;
+        avr_isp_prog_avrisp(prog);
+    }
+    FURI_LOG_D(TAG, "AvrIspProgWorker Stop");
+    return 0;
+}
+
+static void avr_isp_worker_prog_tx_data(void* context) {
+    furi_assert(context);
+    AvrIspWorker* instance = context;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtTx);
+}
+
+/** Worker thread
+ * 
+ * @param context 
+ * @return exit code 
+ */
+static int32_t avr_isp_worker_thread(void* context) {
+    AvrIspWorker* instance = context;
+    avr_isp_worker_vcp_cdc_init(instance);
+
+    /* start PWM on &gpio_ext_pa4 */
+    furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+
+    AvrIspProg* prog = avr_isp_prog_init();
+    avr_isp_prog_set_tx_callback(prog, avr_isp_worker_prog_tx_data, instance);
+
+    uint8_t buf[AVR_ISP_VCP_UART_RX_BUF_SIZE];
+    size_t len = 0;
+
+    FuriThread* prog_thread =
+        furi_thread_alloc_ex("AvrIspProgWorker", 1024, avr_isp_worker_prog_thread, prog);
+    furi_thread_start(prog_thread);
+
+    FURI_LOG_D(TAG, "Start");
+
+    while(instance->worker_running) {
+        uint32_t events =
+            furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever);
+
+        if(events & AvrIspWorkerEvtRx) {
+            if(avr_isp_prog_spaces_rx(prog) >= AVR_ISP_VCP_CDC_PKT_LEN) {
+                len = furi_hal_cdc_receive(AVR_ISP_VCP_CDC_CH, buf, AVR_ISP_VCP_CDC_PKT_LEN);
+                // for(uint8_t i = 0; i < len; i++) {
+                //     FURI_LOG_I(TAG, "--> %X", buf[i]);
+                // }
+                avr_isp_prog_rx(prog, buf, len);
+            } else {
+                furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtRx);
+            }
+        }
+
+        if((events & AvrIspWorkerEvtTxCoplete) || (events & AvrIspWorkerEvtTx)) {
+            len = avr_isp_prog_tx(prog, buf, AVR_ISP_VCP_CDC_PKT_LEN);
+
+            // for(uint8_t i = 0; i < len; i++) {
+            //     FURI_LOG_I(TAG, "<-- %X", buf[i]);
+            // }
+
+            if(len > 0) furi_hal_cdc_send(AVR_ISP_VCP_CDC_CH, buf, len);
+        }
+
+        if(events & AvrIspWorkerEvtStop) {
+            break;
+        }
+
+        if(events & AvrIspWorkerEvtState) {
+            if(instance->callback)
+                instance->callback(instance->context, (bool)instance->connect_usb);
+        }
+    }
+
+    FURI_LOG_D(TAG, "Stop");
+
+    furi_thread_flags_set(furi_thread_get_id(prog_thread), AvrIspWorkerEvtStop);
+    avr_isp_prog_exit(prog);
+    furi_delay_ms(10);
+    furi_thread_join(prog_thread);
+    furi_thread_free(prog_thread);
+
+    avr_isp_prog_free(prog);
+    furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+    avr_isp_worker_vcp_cdc_deinit();
+    return 0;
+}
+
+AvrIspWorker* avr_isp_worker_alloc(void* context) {
+    furi_assert(context);
+    UNUSED(context);
+    AvrIspWorker* instance = malloc(sizeof(AvrIspWorker));
+
+    instance->thread = furi_thread_alloc_ex("AvrIspWorker", 2048, avr_isp_worker_thread, instance);
+    return instance;
+}
+
+void avr_isp_worker_free(AvrIspWorker* instance) {
+    furi_assert(instance);
+
+    furi_check(!instance->worker_running);
+    furi_thread_free(instance->thread);
+    free(instance);
+}
+
+void avr_isp_worker_set_callback(
+    AvrIspWorker* instance,
+    AvrIspWorkerCallback callback,
+    void* context) {
+    furi_assert(instance);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_worker_start(AvrIspWorker* instance) {
+    furi_assert(instance);
+    furi_assert(!instance->worker_running);
+
+    instance->worker_running = true;
+
+    furi_thread_start(instance->thread);
+}
+
+void avr_isp_worker_stop(AvrIspWorker* instance) {
+    furi_assert(instance);
+    furi_assert(instance->worker_running);
+
+    instance->worker_running = false;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerEvtStop);
+
+    furi_thread_join(instance->thread);
+}
+
+bool avr_isp_worker_is_running(AvrIspWorker* instance) {
+    furi_assert(instance);
+
+    return instance->worker_running;
+}

+ 49 - 0
base_pack/avr_isp_programmer/helpers/avr_isp_worker.h

@@ -0,0 +1,49 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct AvrIspWorker AvrIspWorker;
+
+typedef void (*AvrIspWorkerCallback)(void* context, bool connect_usb);
+
+/** Allocate AvrIspWorker
+ * 
+ * @param context AvrIsp* context
+ * @return AvrIspWorker* 
+ */
+AvrIspWorker* avr_isp_worker_alloc(void* context);
+
+/** Free AvrIspWorker
+ * 
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_free(AvrIspWorker* instance);
+
+/** Callback AvrIspWorker
+ *
+ * @param instance AvrIspWorker instance
+ * @param callback AvrIspWorkerOverrunCallback callback
+ * @param context
+ */
+void avr_isp_worker_set_callback(
+    AvrIspWorker* instance,
+    AvrIspWorkerCallback callback,
+    void* context);
+
+/** Start AvrIspWorker
+ * 
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_start(AvrIspWorker* instance);
+
+/** Stop AvrIspWorker
+ * 
+ * @param instance AvrIspWorker instance
+ */
+void avr_isp_worker_stop(AvrIspWorker* instance);
+
+/** Check if worker is running
+ * @param instance AvrIspWorker instance
+ * @return bool - true if running
+ */
+bool avr_isp_worker_is_running(AvrIspWorker* instance);

+ 1157 - 0
base_pack/avr_isp_programmer/helpers/avr_isp_worker_rw.c

@@ -0,0 +1,1157 @@
+#include "avr_isp_worker_rw.h"
+#include <furi_hal_pwm.h>
+#include "avr_isp_types.h"
+#include "avr_isp.h"
+#include "../lib/driver/avr_isp_prog_cmd.h"
+#include "../lib/driver/avr_isp_chip_arr.h"
+
+#include "flipper_i32hex_file.h"
+#include <flipper_format/flipper_format.h>
+
+#include <furi.h>
+
+#define TAG "AvrIspWorkerRW"
+
+#define NAME_PATERN_FLASH_FILE "flash.hex"
+#define NAME_PATERN_EEPROM_FILE "eeprom.hex"
+
+struct AvrIspWorkerRW {
+    AvrIsp* avr_isp;
+    FuriThread* thread;
+    volatile bool worker_running;
+
+    uint32_t chip_arr_ind;
+    bool chip_detect;
+    uint8_t lfuse;
+    uint8_t hfuse;
+    uint8_t efuse;
+    uint8_t lock;
+    float progress_flash;
+    float progress_eeprom;
+    const char* file_path;
+    const char* file_name;
+    AvrIspSignature signature;
+    AvrIspWorkerRWCallback callback;
+    void* context;
+
+    AvrIspWorkerRWStatusCallback callback_status;
+    void* context_status;
+};
+
+typedef enum {
+    AvrIspWorkerRWEvtStop = (1 << 0),
+
+    AvrIspWorkerRWEvtReading = (1 << 1),
+    AvrIspWorkerRWEvtVerification = (1 << 2),
+    AvrIspWorkerRWEvtWriting = (1 << 3),
+    AvrIspWorkerRWEvtWritingFuse = (1 << 4),
+
+} AvrIspWorkerRWEvt;
+#define AVR_ISP_WORKER_ALL_EVENTS                                                              \
+    (AvrIspWorkerRWEvtWritingFuse | AvrIspWorkerRWEvtWriting | AvrIspWorkerRWEvtVerification | \
+     AvrIspWorkerRWEvtReading | AvrIspWorkerRWEvtStop)
+
+/** Worker thread
+ * 
+ * @param context 
+ * @return exit code 
+ */
+static int32_t avr_isp_worker_rw_thread(void* context) {
+    AvrIspWorkerRW* instance = context;
+
+    /* start PWM on &gpio_ext_pa4 */
+    if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
+        furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+    }
+
+    FURI_LOG_D(TAG, "Start");
+
+    while(1) {
+        uint32_t events =
+            furi_thread_flags_wait(AVR_ISP_WORKER_ALL_EVENTS, FuriFlagWaitAny, FuriWaitForever);
+
+        if(events & AvrIspWorkerRWEvtStop) {
+            break;
+        }
+
+        if(events & AvrIspWorkerRWEvtWritingFuse) {
+            if(avr_isp_worker_rw_write_fuse(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndWritingFuse);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorWritingFuse);
+            }
+        }
+
+        if(events & AvrIspWorkerRWEvtWriting) {
+            if(avr_isp_worker_rw_write_dump(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndWriting);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorWriting);
+            }
+        }
+
+        if(events & AvrIspWorkerRWEvtVerification) {
+            if(avr_isp_worker_rw_verification(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndVerification);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorVerification);
+            }
+        }
+
+        if(events & AvrIspWorkerRWEvtReading) {
+            if(avr_isp_worker_rw_read_dump(instance, instance->file_path, instance->file_name)) {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusEndReading);
+            } else {
+                if(instance->callback_status)
+                    instance->callback_status(
+                        instance->context_status, AvrIspWorkerRWStatusErrorReading);
+            }
+        }
+    }
+    FURI_LOG_D(TAG, "Stop");
+
+    if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
+        furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+    }
+
+    return 0;
+}
+
+bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    FURI_LOG_D(TAG, "Detecting AVR chip");
+
+    instance->chip_detect = false;
+    instance->chip_arr_ind = avr_isp_chip_arr_size + 1;
+
+    /* start PWM on &gpio_ext_pa4 */
+    bool was_pwm_enabled = false;
+    if(!furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4)) {
+        furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 4000000, 50);
+    } else {
+        was_pwm_enabled = true;
+    }
+
+    do {
+        if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+            FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+            break;
+        }
+        instance->signature = avr_isp_read_signature(instance->avr_isp);
+
+        if(instance->signature.vendor != 0x1E) {
+            //No detect chip
+        } else {
+            for(uint32_t ind = 0; ind < avr_isp_chip_arr_size; ind++) {
+                if(avr_isp_chip_arr[ind].avrarch != F_AVR8) continue;
+                if(avr_isp_chip_arr[ind].sigs[1] == instance->signature.part_family) {
+                    if(avr_isp_chip_arr[ind].sigs[2] == instance->signature.part_number) {
+                        FURI_LOG_D(TAG, "Detect AVR chip = \"%s\"", avr_isp_chip_arr[ind].name);
+                        FURI_LOG_D(
+                            TAG,
+                            "Signature = 0x%02X 0x%02X 0x%02X",
+                            instance->signature.vendor,
+                            instance->signature.part_family,
+                            instance->signature.part_number);
+
+                        switch(avr_isp_chip_arr[ind].nfuses) {
+                        case 1:
+                            instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+                            FURI_LOG_D(TAG, "Lfuse = %02X", instance->lfuse);
+                            break;
+                        case 2:
+                            instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+                            instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp);
+                            FURI_LOG_D(
+                                TAG, "Lfuse = %02X Hfuse = %02X", instance->lfuse, instance->hfuse);
+                            break;
+                        case 3:
+                            instance->lfuse = avr_isp_read_fuse_low(instance->avr_isp);
+                            instance->hfuse = avr_isp_read_fuse_high(instance->avr_isp);
+                            instance->efuse = avr_isp_read_fuse_extended(instance->avr_isp);
+                            FURI_LOG_D(
+                                TAG,
+                                "Lfuse = %02X Hfuse = %02X Efuse = %02X",
+                                instance->lfuse,
+                                instance->hfuse,
+                                instance->efuse);
+                            break;
+                        default:
+                            break;
+                        }
+                        if(avr_isp_chip_arr[ind].nlocks == 1) {
+                            instance->lock = avr_isp_read_lock_byte(instance->avr_isp);
+                            FURI_LOG_D(TAG, "Lock = %02X", instance->lock);
+                        }
+                        instance->chip_detect = true;
+                        instance->chip_arr_ind = ind;
+                        break;
+                    }
+                }
+            }
+        }
+        avr_isp_end_pmode(instance->avr_isp);
+
+    } while(0);
+
+    if(furi_hal_pwm_is_running(FuriHalPwmOutputIdLptim2PA4) && !was_pwm_enabled) {
+        furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+    }
+
+    if(instance->callback) {
+        if(instance->chip_arr_ind > avr_isp_chip_arr_size) {
+            instance->callback(instance->context, "No detect", instance->chip_detect, 0);
+        } else if(instance->chip_arr_ind < avr_isp_chip_arr_size) {
+            instance->callback(
+                instance->context,
+                avr_isp_chip_arr[instance->chip_arr_ind].name,
+                instance->chip_detect,
+                avr_isp_chip_arr[instance->chip_arr_ind].flashsize);
+        } else {
+            instance->callback(instance->context, "Unknown", instance->chip_detect, 0);
+        }
+    }
+
+    return instance->chip_detect;
+}
+
+AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context) {
+    furi_assert(context);
+    UNUSED(context);
+
+    AvrIspWorkerRW* instance = malloc(sizeof(AvrIspWorkerRW));
+    instance->avr_isp = avr_isp_alloc();
+
+    instance->thread =
+        furi_thread_alloc_ex("AvrIspWorkerRW", 4096, avr_isp_worker_rw_thread, instance);
+
+    instance->chip_detect = false;
+    instance->lfuse = 0;
+    instance->hfuse = 0;
+    instance->efuse = 0;
+    instance->lock = 0;
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+
+    return instance;
+}
+
+void avr_isp_worker_rw_free(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    avr_isp_free(instance->avr_isp);
+
+    furi_check(!instance->worker_running);
+    furi_thread_free(instance->thread);
+
+    free(instance);
+}
+
+void avr_isp_worker_rw_start(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+    furi_assert(!instance->worker_running);
+
+    instance->worker_running = true;
+
+    furi_thread_start(instance->thread);
+}
+
+void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+    furi_assert(instance->worker_running);
+
+    instance->worker_running = false;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtStop);
+
+    furi_thread_join(instance->thread);
+}
+
+bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    return instance->worker_running;
+}
+
+void avr_isp_worker_rw_set_callback(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWCallback callback,
+    void* context) {
+    furi_assert(instance);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_worker_rw_set_callback_status(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWStatusCallback callback_status,
+    void* context_status) {
+    furi_assert(instance);
+
+    instance->callback_status = callback_status;
+    instance->context_status = context_status;
+}
+
+float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    return instance->progress_flash;
+}
+
+float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance) {
+    furi_assert(instance);
+
+    return instance->progress_eeprom;
+}
+
+static void avr_isp_worker_rw_get_dump_flash(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    FURI_LOG_D(TAG, "Dump FLASH %s", file_path);
+
+    FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_write(
+        file_path, avr_isp_chip_arr[instance->chip_arr_ind].flashoffset);
+
+    uint8_t data[272] = {0};
+    bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+    uint8_t extended_addr = 0;
+
+    for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+        i < avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2;
+        i += avr_isp_chip_arr[instance->chip_arr_ind].pagesize / 2) {
+        if(send_extended_addr) {
+            if(extended_addr <= ((i >> 16) & 0xFF)) {
+                avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+                extended_addr = ((i >> 16) & 0xFF) + 1;
+            }
+        }
+        avr_isp_read_page(
+            instance->avr_isp,
+            STK_SET_FLASH_TYPE,
+            (uint16_t)i,
+            avr_isp_chip_arr[instance->chip_arr_ind].pagesize,
+            data,
+            sizeof(data));
+        flipper_i32hex_file_bin_to_i32hex_set_data(
+            flipper_hex_flash, data, avr_isp_chip_arr[instance->chip_arr_ind].pagesize);
+        //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
+        instance->progress_flash =
+            (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+    }
+    flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_flash);
+    //FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_flash));
+    flipper_i32hex_file_close(flipper_hex_flash);
+    instance->progress_flash = 1.0f;
+}
+
+static void avr_isp_worker_rw_get_dump_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    FURI_LOG_D(TAG, "Dump EEPROM %s", file_path);
+
+    FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_write(
+        file_path, avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset);
+
+    int32_t size_data = 32;
+    uint8_t data[256] = {0};
+
+    if(size_data > avr_isp_chip_arr[instance->chip_arr_ind].eepromsize)
+        size_data = avr_isp_chip_arr[instance->chip_arr_ind].eepromsize;
+
+    for(int32_t i = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+        i < avr_isp_chip_arr[instance->chip_arr_ind].eepromsize;
+        i += size_data) {
+        avr_isp_read_page(
+            instance->avr_isp, STK_SET_EEPROM_TYPE, (uint16_t)i, size_data, data, sizeof(data));
+        flipper_i32hex_file_bin_to_i32hex_set_data(flipper_hex_eeprom, data, size_data);
+        FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom));
+        instance->progress_eeprom =
+            (float)(i) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+    }
+    flipper_i32hex_file_bin_to_i32hex_set_end_line(flipper_hex_eeprom);
+    FURI_LOG_D(TAG, "%s", flipper_i32hex_file_get_string(flipper_hex_eeprom));
+    flipper_i32hex_file_close(flipper_hex_eeprom);
+    instance->progress_eeprom = 1.0f;
+}
+
+bool avr_isp_worker_rw_read_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Read dump chip");
+
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+    bool ret = false;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+    FuriString* file_path_name = furi_string_alloc();
+
+    if(!avr_isp_worker_rw_detect_chip(instance)) {
+        FURI_LOG_E(TAG, "No detect AVR chip");
+    } else {
+        do {
+            furi_string_printf(
+                file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+            if(!flipper_format_file_open_always(
+                   flipper_format, furi_string_get_cstr(file_path_name))) {
+                FURI_LOG_E(TAG, "flipper_format_file_open_always");
+                break;
+            }
+            if(!flipper_format_write_header_cstr(
+                   flipper_format, AVR_ISP_APP_FILE_TYPE, AVR_ISP_APP_FILE_VERSION)) {
+                FURI_LOG_E(TAG, "flipper_format_write_header_cstr");
+                break;
+            }
+            if(!flipper_format_write_string_cstr(
+                   flipper_format, "Chip name", avr_isp_chip_arr[instance->chip_arr_ind].name)) {
+                FURI_LOG_E(TAG, "Chip name");
+                break;
+            }
+            if(!flipper_format_write_hex(
+                   flipper_format,
+                   "Signature",
+                   (uint8_t*)&instance->signature,
+                   sizeof(AvrIspSignature))) {
+                FURI_LOG_E(TAG, "Unable to add Signature");
+                break;
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+                if(!flipper_format_write_hex(flipper_format, "Lfuse", &instance->lfuse, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Lfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+                if(!flipper_format_write_hex(flipper_format, "Hfuse", &instance->hfuse, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Hfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+                if(!flipper_format_write_hex(flipper_format, "Efuse", &instance->efuse, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Efuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+                if(!flipper_format_write_hex(flipper_format, "Lock", &instance->lock, 1)) {
+                    FURI_LOG_E(TAG, "Unable to add Lock");
+                    break;
+                }
+            }
+            furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_FLASH_FILE);
+            if(!flipper_format_write_string_cstr(
+                   flipper_format, "Dump_flash", furi_string_get_cstr(file_path_name))) {
+                FURI_LOG_E(TAG, "Unable to add Dump_flash");
+                break;
+            }
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                furi_string_printf(file_path_name, "%s_%s", file_name, NAME_PATERN_EEPROM_FILE);
+                if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                    if(!flipper_format_write_string_cstr(
+                           flipper_format, "Dump_eeprom", furi_string_get_cstr(file_path_name))) {
+                        FURI_LOG_E(TAG, "Unable to add Dump_eeprom");
+                        break;
+                    }
+                }
+            }
+            ret = true;
+        } while(false);
+    }
+
+    flipper_format_free(flipper_format);
+    furi_record_close(RECORD_STORAGE);
+
+    if(ret) {
+        if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+            //Dump flash
+            furi_string_printf(
+                file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE);
+            avr_isp_worker_rw_get_dump_flash(instance, furi_string_get_cstr(file_path_name));
+            //Dump eeprom
+            if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                furi_string_printf(
+                    file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE);
+                avr_isp_worker_rw_get_dump_eeprom(instance, furi_string_get_cstr(file_path_name));
+            }
+
+            avr_isp_end_pmode(instance->avr_isp);
+        }
+    }
+
+    furi_string_free(file_path_name);
+
+    return true;
+}
+
+void avr_isp_worker_rw_read_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtReading);
+}
+
+static bool avr_isp_worker_rw_verification_flash(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    FURI_LOG_D(TAG, "Verification flash %s", file_path);
+
+    instance->progress_flash = 0.0;
+    bool ret = true;
+
+    FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path);
+
+    uint8_t data_read_flash[272] = {0};
+    uint8_t data_read_hex[272] = {0};
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+    bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+    uint8_t extended_addr = 0;
+
+    FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+        flipper_hex_flash, data_read_hex, sizeof(data_read_hex));
+
+    while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+           (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) &&
+          ret) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+
+            if(send_extended_addr) {
+                if(extended_addr <= ((addr >> 16) & 0xFF)) {
+                    avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+                    extended_addr = ((addr >> 16) & 0xFF) + 1;
+                }
+            }
+
+            avr_isp_read_page(
+                instance->avr_isp,
+                STK_SET_FLASH_TYPE,
+                (uint16_t)addr,
+                flipper_hex_ret.data_size,
+                data_read_flash,
+                sizeof(data_read_flash));
+
+            if(memcmp(data_read_hex, data_read_flash, flipper_hex_ret.data_size) != 0) {
+                ret = false;
+
+                FURI_LOG_E(TAG, "Verification flash error");
+                FURI_LOG_E(TAG, "Addr: 0x%04lX", addr);
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_hex[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_flash[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+            }
+
+            addr += flipper_hex_ret.data_size / 2;
+            instance->progress_flash =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16) / 2;
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+            flipper_hex_flash, data_read_hex, sizeof(data_read_hex));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_flash);
+    instance->progress_flash = 1.0f;
+
+    return ret;
+}
+
+static bool
+    avr_isp_worker_rw_verification_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_assert(file_path);
+
+    FURI_LOG_D(TAG, "Verification eeprom %s", file_path);
+
+    instance->progress_eeprom = 0.0;
+    bool ret = true;
+
+    FlipperI32HexFile* flipper_hex_eeprom = flipper_i32hex_file_open_read(file_path);
+
+    uint8_t data_read_eeprom[272] = {0};
+    uint8_t data_read_hex[272] = {0};
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+
+    FlipperI32HexFileRet flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+        flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex));
+
+    while(((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+           (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) &&
+          ret) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+            avr_isp_read_page(
+                instance->avr_isp,
+                STK_SET_EEPROM_TYPE,
+                (uint16_t)addr,
+                flipper_hex_ret.data_size,
+                data_read_eeprom,
+                sizeof(data_read_eeprom));
+
+            if(memcmp(data_read_hex, data_read_eeprom, flipper_hex_ret.data_size) != 0) {
+                ret = false;
+                FURI_LOG_E(TAG, "Verification eeprom error");
+                FURI_LOG_E(TAG, "Addr: 0x%04lX", addr);
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_hex[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+                for(uint32_t i = 0; i < flipper_hex_ret.data_size; i++) {
+                    FURI_LOG_RAW_E("%02X ", data_read_eeprom[i]);
+                }
+                FURI_LOG_RAW_E("\r\n");
+            }
+
+            addr += flipper_hex_ret.data_size;
+            instance->progress_eeprom =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = (data_read_hex[0] << 24 | data_read_hex[1] << 16);
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+            flipper_hex_eeprom, data_read_hex, sizeof(data_read_hex));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_eeprom);
+    instance->progress_eeprom = 1.0f;
+
+    return ret;
+}
+
+bool avr_isp_worker_rw_verification(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Verification chip");
+
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+    FuriString* file_path_name = furi_string_alloc();
+
+    bool ret = false;
+
+    if(avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+        do {
+            furi_string_printf(
+                file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_FLASH_FILE);
+            if(!avr_isp_worker_rw_verification_flash(
+                   instance, furi_string_get_cstr(file_path_name)))
+                break;
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].eepromsize > 0) {
+                furi_string_printf(
+                    file_path_name, "%s/%s_%s", file_path, file_name, NAME_PATERN_EEPROM_FILE);
+
+                if(!avr_isp_worker_rw_verification_eeprom(
+                       instance, furi_string_get_cstr(file_path_name)))
+                    break;
+            }
+            ret = true;
+        } while(false);
+        avr_isp_end_pmode(instance->avr_isp);
+        furi_string_free(file_path_name);
+    }
+    return ret;
+}
+
+void avr_isp_worker_rw_verification_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtVerification);
+}
+
+static void avr_isp_worker_rw_write_flash(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    instance->progress_flash = 0.0;
+
+    FURI_LOG_D(TAG, "Write Flash %s", file_path);
+
+    uint8_t data[288] = {0};
+
+    FlipperI32HexFile* flipper_hex_flash = flipper_i32hex_file_open_read(file_path);
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].flashoffset;
+    bool send_extended_addr = ((avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2) > 0x10000);
+    uint8_t extended_addr = 0;
+
+    FlipperI32HexFileRet flipper_hex_ret =
+        flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data));
+
+    while((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+          (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+
+            if(send_extended_addr) {
+                if(extended_addr <= ((addr >> 16) & 0xFF)) {
+                    avr_isp_write_extended_addr(instance->avr_isp, extended_addr);
+                    extended_addr = ((addr >> 16) & 0xFF) + 1;
+                }
+            }
+
+            if(!avr_isp_write_page(
+                   instance->avr_isp,
+                   STK_SET_FLASH_TYPE,
+                   avr_isp_chip_arr[instance->chip_arr_ind].flashsize,
+                   (uint16_t)addr,
+                   avr_isp_chip_arr[instance->chip_arr_ind].pagesize,
+                   data,
+                   flipper_hex_ret.data_size)) {
+                break;
+            }
+            addr += flipper_hex_ret.data_size / 2;
+            instance->progress_flash =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].flashsize / 2.0f);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = (data[0] << 24 | data[1] << 16) / 2;
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret =
+            flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_flash, data, sizeof(data));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_flash);
+    instance->progress_flash = 1.0f;
+}
+
+static void avr_isp_worker_rw_write_eeprom(AvrIspWorkerRW* instance, const char* file_path) {
+    furi_assert(instance);
+    furi_check(instance->avr_isp);
+
+    instance->progress_eeprom = 0.0;
+    uint8_t data[288] = {0};
+
+    FURI_LOG_D(TAG, "Write EEPROM %s", file_path);
+
+    FlipperI32HexFile* flipper_hex_eeprom_read = flipper_i32hex_file_open_read(file_path);
+
+    uint32_t addr = avr_isp_chip_arr[instance->chip_arr_ind].eepromoffset;
+    FlipperI32HexFileRet flipper_hex_ret =
+        flipper_i32hex_file_i32hex_to_bin_get_data(flipper_hex_eeprom_read, data, sizeof(data));
+
+    while((flipper_hex_ret.status == FlipperI32HexFileStatusData) ||
+          (flipper_hex_ret.status == FlipperI32HexFileStatusUdateAddr)) {
+        switch(flipper_hex_ret.status) {
+        case FlipperI32HexFileStatusData:
+            if(!avr_isp_write_page(
+                   instance->avr_isp,
+                   STK_SET_EEPROM_TYPE,
+                   avr_isp_chip_arr[instance->chip_arr_ind].eepromsize,
+                   (uint16_t)addr,
+                   avr_isp_chip_arr[instance->chip_arr_ind].eeprompagesize,
+                   data,
+                   flipper_hex_ret.data_size)) {
+                break;
+            }
+            addr += flipper_hex_ret.data_size;
+            instance->progress_eeprom =
+                (float)(addr) / ((float)avr_isp_chip_arr[instance->chip_arr_ind].eepromsize);
+            break;
+
+        case FlipperI32HexFileStatusUdateAddr:
+            addr = data[0] << 24 | data[1] << 16;
+            break;
+
+        default:
+            furi_crash(TAG " Incorrect status.");
+            break;
+        }
+
+        flipper_hex_ret = flipper_i32hex_file_i32hex_to_bin_get_data(
+            flipper_hex_eeprom_read, data, sizeof(data));
+    }
+
+    flipper_i32hex_file_close(flipper_hex_eeprom_read);
+    instance->progress_eeprom = 1.0f;
+}
+
+bool avr_isp_worker_rw_write_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Write dump chip");
+
+    instance->progress_flash = 0.0f;
+    instance->progress_eeprom = 0.0f;
+    bool ret = false;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+    FuriString* file_path_name = furi_string_alloc();
+
+    FuriString* temp_str_1 = furi_string_alloc();
+    FuriString* temp_str_2 = furi_string_alloc();
+    uint32_t temp_data32;
+
+    if(!avr_isp_worker_rw_detect_chip(instance)) {
+        FURI_LOG_E(TAG, "No detect AVR chip");
+    } else {
+        //upload file with description
+        do {
+            furi_string_printf(
+                file_path_name, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+            if(!flipper_format_file_open_existing(
+                   flipper_format, furi_string_get_cstr(file_path_name))) {
+                FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(file_path_name));
+                break;
+            }
+
+            if(!flipper_format_read_header(flipper_format, temp_str_1, &temp_data32)) {
+                FURI_LOG_E(TAG, "Missing or incorrect header");
+                break;
+            }
+
+            if((!strcmp(furi_string_get_cstr(temp_str_1), AVR_ISP_APP_FILE_TYPE)) &&
+               temp_data32 == AVR_ISP_APP_FILE_VERSION) {
+            } else {
+                FURI_LOG_E(TAG, "Type or version mismatch");
+                break;
+            }
+
+            AvrIspSignature sig_read = {0};
+
+            if(!flipper_format_read_hex(
+                   flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) {
+                FURI_LOG_E(TAG, "Missing Signature");
+                break;
+            }
+
+            if(memcmp(
+                   (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) !=
+               0) {
+                FURI_LOG_E(
+                    TAG,
+                    "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)",
+                    instance->signature.vendor,
+                    instance->signature.part_family,
+                    instance->signature.part_number,
+                    sig_read.vendor,
+                    sig_read.part_family,
+                    sig_read.part_number);
+                break;
+            }
+
+            if(!flipper_format_read_string(flipper_format, "Dump_flash", temp_str_1)) {
+                FURI_LOG_E(TAG, "Missing Dump_flash");
+                break;
+            }
+
+            //may not be
+            flipper_format_read_string(flipper_format, "Dump_eeprom", temp_str_2);
+            ret = true;
+        } while(false);
+    }
+    flipper_format_free(flipper_format);
+    furi_record_close(RECORD_STORAGE);
+
+    if(ret) {
+        do {
+            //checking .hex files for errors
+
+            furi_string_printf(
+                file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1));
+
+            FURI_LOG_D(TAG, "Check flash file");
+            FlipperI32HexFile* flipper_hex_flash_read =
+                flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name));
+            if(flipper_i32hex_file_check(flipper_hex_flash_read)) {
+                FURI_LOG_D(TAG, "Check flash file: OK");
+            } else {
+                FURI_LOG_E(TAG, "Check flash file: Error");
+                ret = false;
+            }
+            flipper_i32hex_file_close(flipper_hex_flash_read);
+
+            if(furi_string_size(temp_str_2) > 4) {
+                furi_string_printf(
+                    file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2));
+
+                FURI_LOG_D(TAG, "Check eeprom file");
+                FlipperI32HexFile* flipper_hex_eeprom_read =
+                    flipper_i32hex_file_open_read(furi_string_get_cstr(file_path_name));
+                if(flipper_i32hex_file_check(flipper_hex_eeprom_read)) {
+                    FURI_LOG_D(TAG, "Check eeprom file: OK");
+                } else {
+                    FURI_LOG_E(TAG, "Check eeprom file: Error");
+                    ret = false;
+                }
+                flipper_i32hex_file_close(flipper_hex_eeprom_read);
+            }
+
+            if(!ret) break;
+            ret = false;
+
+            //erase chip
+            FURI_LOG_D(TAG, "Erase chip");
+            if(!avr_isp_erase_chip(instance->avr_isp)) {
+                FURI_LOG_E(TAG, "Erase chip: Error");
+                break;
+            }
+
+            if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+                FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+                break;
+            }
+
+            //write flash
+            furi_string_printf(
+                file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_1));
+            avr_isp_worker_rw_write_flash(instance, furi_string_get_cstr(file_path_name));
+
+            //write eeprom
+            if(furi_string_size(temp_str_2) > 4) {
+                furi_string_printf(
+                    file_path_name, "%s/%s", file_path, furi_string_get_cstr(temp_str_2));
+                avr_isp_worker_rw_write_eeprom(instance, furi_string_get_cstr(file_path_name));
+            }
+            ret = true;
+            avr_isp_end_pmode(instance->avr_isp);
+        } while(false);
+    }
+
+    furi_string_free(file_path_name);
+    furi_string_free(temp_str_1);
+    furi_string_free(temp_str_2);
+
+    return ret;
+}
+
+void avr_isp_worker_rw_write_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWriting);
+}
+
+bool avr_isp_worker_rw_write_fuse(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+    furi_assert(file_path);
+    furi_assert(file_name);
+
+    FURI_LOG_D(TAG, "Write fuse chip");
+
+    bool ret = false;
+    uint8_t lfuse;
+    uint8_t hfuse;
+    uint8_t efuse;
+    uint8_t lock;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+    FuriString* temp_str = furi_string_alloc();
+
+    uint32_t temp_data32;
+
+    if(!avr_isp_worker_rw_detect_chip(instance)) {
+        FURI_LOG_E(TAG, "No detect AVR chip");
+    } else {
+        //upload file with description
+        do {
+            furi_string_printf(temp_str, "%s/%s%s", file_path, file_name, AVR_ISP_APP_EXTENSION);
+            if(!flipper_format_file_open_existing(flipper_format, furi_string_get_cstr(temp_str))) {
+                FURI_LOG_E(TAG, "Error open file %s", furi_string_get_cstr(temp_str));
+                break;
+            }
+
+            if(!flipper_format_read_header(flipper_format, temp_str, &temp_data32)) {
+                FURI_LOG_E(TAG, "Missing or incorrect header");
+                break;
+            }
+
+            if((!strcmp(furi_string_get_cstr(temp_str), AVR_ISP_APP_FILE_TYPE)) &&
+               temp_data32 == AVR_ISP_APP_FILE_VERSION) {
+            } else {
+                FURI_LOG_E(TAG, "Type or version mismatch");
+                break;
+            }
+
+            AvrIspSignature sig_read = {0};
+
+            if(!flipper_format_read_hex(
+                   flipper_format, "Signature", (uint8_t*)&sig_read, sizeof(AvrIspSignature))) {
+                FURI_LOG_E(TAG, "Missing Signature");
+                break;
+            }
+
+            if(memcmp(
+                   (uint8_t*)&instance->signature, (uint8_t*)&sig_read, sizeof(AvrIspSignature)) !=
+               0) {
+                FURI_LOG_E(
+                    TAG,
+                    "Wrong chip. Connected (%02X %02X %02X), read from file (%02X %02X %02X)",
+                    instance->signature.vendor,
+                    instance->signature.part_family,
+                    instance->signature.part_number,
+                    sig_read.vendor,
+                    sig_read.part_family,
+                    sig_read.part_number);
+                break;
+            }
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+                if(!flipper_format_read_hex(flipper_format, "Lfuse", &lfuse, 1)) {
+                    FURI_LOG_E(TAG, "Missing Lfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+                if(!flipper_format_read_hex(flipper_format, "Hfuse", &hfuse, 1)) {
+                    FURI_LOG_E(TAG, "Missing Hfuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+                if(!flipper_format_read_hex(flipper_format, "Efuse", &efuse, 1)) {
+                    FURI_LOG_E(TAG, "Missing Efuse");
+                    break;
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+                if(!flipper_format_read_hex(flipper_format, "Lock", &lock, 1)) {
+                    FURI_LOG_E(TAG, "Missing Lock");
+                    break;
+                }
+            }
+
+            if(!avr_isp_auto_set_spi_speed_start_pmode(instance->avr_isp)) {
+                FURI_LOG_E(TAG, "Well, I managed to enter the mod program");
+                break;
+            }
+
+            ret = true;
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 0) {
+                if(instance->lfuse != lfuse) {
+                    if(!avr_isp_write_fuse_low(instance->avr_isp, lfuse)) {
+                        FURI_LOG_E(TAG, "Write Lfuse: error");
+                        ret = false;
+                    }
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 1) {
+                if(instance->hfuse != hfuse) {
+                    if(!avr_isp_write_fuse_high(instance->avr_isp, hfuse)) {
+                        FURI_LOG_E(TAG, "Write Hfuse: error");
+                        ret = false;
+                    }
+                }
+            }
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nfuses > 2) {
+                if(instance->efuse != efuse) {
+                    if(!avr_isp_write_fuse_extended(instance->avr_isp, efuse)) {
+                        FURI_LOG_E(TAG, "Write Efuse: error");
+                        ret = false;
+                    }
+                }
+            }
+
+            if(avr_isp_chip_arr[instance->chip_arr_ind].nlocks == 1) {
+                FURI_LOG_D(TAG, "Write lock byte");
+                if(instance->lock != lock) {
+                    if(!avr_isp_write_lock_byte(instance->avr_isp, lock)) {
+                        FURI_LOG_E(TAG, "Write Lock byte: error");
+                        ret = false;
+                    }
+                }
+            }
+            avr_isp_end_pmode(instance->avr_isp);
+        } while(false);
+    }
+
+    flipper_format_free(flipper_format);
+    furi_record_close(RECORD_STORAGE);
+    furi_string_free(temp_str);
+    return ret;
+}
+
+void avr_isp_worker_rw_write_fuse_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+    furi_thread_flags_set(furi_thread_get_id(instance->thread), AvrIspWorkerRWEvtWritingFuse);
+}

+ 99 - 0
base_pack/avr_isp_programmer/helpers/avr_isp_worker_rw.h

@@ -0,0 +1,99 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct AvrIspWorkerRW AvrIspWorkerRW;
+
+typedef void (*AvrIspWorkerRWCallback)(
+    void* context,
+    const char* name,
+    bool detect_chip,
+    uint32_t flash_size);
+
+typedef enum {
+    AvrIspWorkerRWStatusILDE = 0,
+    AvrIspWorkerRWStatusEndReading = 1,
+    AvrIspWorkerRWStatusEndVerification = 2,
+    AvrIspWorkerRWStatusEndWriting = 3,
+    AvrIspWorkerRWStatusEndWritingFuse = 4,
+
+    AvrIspWorkerRWStatusErrorReading = (-1),
+    AvrIspWorkerRWStatusErrorVerification = (-2),
+    AvrIspWorkerRWStatusErrorWriting = (-3),
+    AvrIspWorkerRWStatusErrorWritingFuse = (-4),
+
+    AvrIspWorkerRWStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization.
+} AvrIspWorkerRWStatus;
+
+typedef void (*AvrIspWorkerRWStatusCallback)(void* context, AvrIspWorkerRWStatus status);
+
+AvrIspWorkerRW* avr_isp_worker_rw_alloc(void* context);
+
+void avr_isp_worker_rw_free(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_start(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_stop(AvrIspWorkerRW* instance);
+
+bool avr_isp_worker_rw_is_running(AvrIspWorkerRW* instance);
+
+void avr_isp_worker_rw_set_callback(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWCallback callback,
+    void* context);
+
+void avr_isp_worker_rw_set_callback_status(
+    AvrIspWorkerRW* instance,
+    AvrIspWorkerRWStatusCallback callback_status,
+    void* context_status);
+
+bool avr_isp_worker_rw_detect_chip(AvrIspWorkerRW* instance);
+
+float avr_isp_worker_rw_get_progress_flash(AvrIspWorkerRW* instance);
+
+float avr_isp_worker_rw_get_progress_eeprom(AvrIspWorkerRW* instance);
+
+bool avr_isp_worker_rw_read_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_read_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_verification(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_verification_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_check_hex(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_write_dump(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_write_dump_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+bool avr_isp_worker_rw_write_fuse(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_worker_rw_write_fuse_start(
+    AvrIspWorkerRW* instance,
+    const char* file_path,
+    const char* file_name);

+ 321 - 0
base_pack/avr_isp_programmer/helpers/flipper_i32hex_file.c

@@ -0,0 +1,321 @@
+#include "flipper_i32hex_file.h"
+#include <string.h>
+#include <storage/storage.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
+#include <toolbox/hex.h>
+
+//https://en.wikipedia.org/wiki/Intel_HEX
+
+#define TAG "FlipperI32HexFile"
+
+#define COUNT_BYTE_PAYLOAD 32 //how much payload will be used
+
+#define I32HEX_TYPE_DATA 0x00
+#define I32HEX_TYPE_END_OF_FILE 0x01
+#define I32HEX_TYPE_EXT_LINEAR_ADDR 0x04
+#define I32HEX_TYPE_START_LINEAR_ADDR 0x05
+
+struct FlipperI32HexFile {
+    uint32_t addr;
+    uint32_t addr_last;
+    Storage* storage;
+    Stream* stream;
+    FuriString* str_data;
+    FlipperI32HexFileStatus file_open;
+};
+
+FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr) {
+    furi_assert(name);
+
+    FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile));
+    instance->addr = start_addr;
+    instance->addr_last = 0;
+    instance->storage = furi_record_open(RECORD_STORAGE);
+    instance->stream = file_stream_alloc(instance->storage);
+
+    if(file_stream_open(instance->stream, name, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
+        instance->file_open = FlipperI32HexFileStatusOpenFileWrite;
+        FURI_LOG_D(TAG, "Open write file %s", name);
+    } else {
+        FURI_LOG_E(TAG, "Failed to open file %s", name);
+        instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile;
+    }
+    instance->str_data = furi_string_alloc(instance->storage);
+
+    return instance;
+}
+
+FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name) {
+    furi_assert(name);
+
+    FlipperI32HexFile* instance = malloc(sizeof(FlipperI32HexFile));
+    instance->addr = 0;
+    instance->addr_last = 0;
+    instance->storage = furi_record_open(RECORD_STORAGE);
+    instance->stream = file_stream_alloc(instance->storage);
+
+    if(file_stream_open(instance->stream, name, FSAM_READ, FSOM_OPEN_EXISTING)) {
+        instance->file_open = FlipperI32HexFileStatusOpenFileRead;
+        FURI_LOG_D(TAG, "Open read file %s", name);
+    } else {
+        FURI_LOG_E(TAG, "Failed to open file %s", name);
+        instance->file_open = FlipperI32HexFileStatusErrorNoOpenFile;
+    }
+    instance->str_data = furi_string_alloc(instance->storage);
+
+    return instance;
+}
+
+void flipper_i32hex_file_close(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    furi_string_free(instance->str_data);
+    file_stream_close(instance->stream);
+    stream_free(instance->stream);
+    furi_record_close(RECORD_STORAGE);
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) {
+        ret.status = FlipperI32HexFileStatusErrorFileWrite;
+    }
+    uint8_t count_byte = 0;
+    uint32_t ind = 0;
+    uint8_t crc = 0;
+
+    furi_string_reset(instance->str_data);
+
+    if((instance->addr_last & 0xFF0000) < (instance->addr & 0xFF0000)) {
+        crc = 0x02 + 0x04 + ((instance->addr >> 24) & 0xFF) + ((instance->addr >> 16) & 0xFF);
+        crc = 0x01 + ~crc;
+        //I32HEX_TYPE_EXT_LINEAR_ADDR
+        furi_string_cat_printf(
+            instance->str_data, ":02000004%04lX%02X\r\n", (instance->addr >> 16), crc);
+        instance->addr_last = instance->addr;
+    }
+
+    while(ind < data_size) {
+        if((ind + COUNT_BYTE_PAYLOAD) > data_size) {
+            count_byte = data_size - ind;
+        } else {
+            count_byte = COUNT_BYTE_PAYLOAD;
+        }
+        //I32HEX_TYPE_DATA
+        furi_string_cat_printf(
+            instance->str_data, ":%02X%04lX00", count_byte, (instance->addr & 0xFFFF));
+        crc = count_byte + ((instance->addr >> 8) & 0xFF) + (instance->addr & 0xFF);
+
+        for(uint32_t i = 0; i < count_byte; i++) {
+            furi_string_cat_printf(instance->str_data, "%02X", *data);
+            crc += *data++;
+        }
+        crc = 0x01 + ~crc;
+        furi_string_cat_printf(instance->str_data, "%02X\r\n", crc);
+
+        ind += count_byte;
+        instance->addr += count_byte;
+    }
+    if(instance->file_open) stream_write_string(instance->stream, instance->str_data);
+    return ret;
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileWrite) {
+        ret.status = FlipperI32HexFileStatusErrorFileWrite;
+    }
+    furi_string_reset(instance->str_data);
+    //I32HEX_TYPE_END_OF_FILE
+    furi_string_cat_printf(instance->str_data, ":00000001FF\r\n");
+    if(instance->file_open) stream_write_string(instance->stream, instance->str_data);
+    return ret;
+}
+
+void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr) {
+    furi_assert(instance);
+
+    instance->addr = addr;
+}
+
+const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    return furi_string_get_cstr(instance->str_data);
+}
+
+static FlipperI32HexFileRet flipper_i32hex_file_parse_line(
+    FlipperI32HexFile* instance,
+    const char* str,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    char* str1;
+    uint32_t data_wrire_ind = 0;
+    uint32_t data_len = 0;
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusErrorData, .data_size = 0};
+
+    //Search for start of data I32HEX
+    str1 = strstr(str, ":");
+    do {
+        if(str1 == NULL) {
+            ret.status = FlipperI32HexFileStatusErrorData;
+            break;
+        }
+        str1++;
+        if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) {
+            ret.status = FlipperI32HexFileStatusErrorData;
+            break;
+        }
+        str1++;
+        if(++data_wrire_ind > data_size) {
+            ret.status = FlipperI32HexFileStatusErrorOverflow;
+            break;
+        }
+        data_len = 5 + data[0]; // +5 bytes per header and crc
+        while(data_len > data_wrire_ind) {
+            str1++;
+            if(!hex_char_to_uint8(*str1, str1[1], data + data_wrire_ind)) {
+                ret.status = FlipperI32HexFileStatusErrorData;
+                break;
+            }
+            str1++;
+            if(++data_wrire_ind > data_size) {
+                ret.status = FlipperI32HexFileStatusErrorOverflow;
+                break;
+            }
+        }
+        ret.status = FlipperI32HexFileStatusOK;
+        ret.data_size = data_wrire_ind;
+
+    } while(0);
+    return ret;
+}
+
+static bool flipper_i32hex_file_check_data(uint8_t* data, uint32_t data_size) {
+    furi_assert(data);
+
+    uint8_t crc = 0;
+    uint32_t data_read_ind = 0;
+    if(data[0] > data_size) return false;
+    while(data_read_ind < data_size - 1) {
+        crc += data[data_read_ind++];
+    }
+    return data[data_size - 1] == ((1 + ~crc) & 0xFF);
+}
+
+static FlipperI32HexFileRet flipper_i32hex_file_parse(
+    FlipperI32HexFile* instance,
+    const char* str,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    FlipperI32HexFileRet ret = flipper_i32hex_file_parse_line(instance, str, data, data_size);
+
+    if((ret.status == FlipperI32HexFileStatusOK) && (ret.data_size > 4)) {
+        switch(data[3]) {
+        case I32HEX_TYPE_DATA:
+            if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+                ret.data_size -= 5;
+                memcpy(data, data + 4, ret.data_size);
+                ret.status = FlipperI32HexFileStatusData;
+            } else {
+                ret.status = FlipperI32HexFileStatusErrorCrc;
+                ret.data_size = 0;
+            }
+            break;
+        case I32HEX_TYPE_END_OF_FILE:
+            if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+                ret.status = FlipperI32HexFileStatusEofFile;
+                ret.data_size = 0;
+            } else {
+                ret.status = FlipperI32HexFileStatusErrorCrc;
+                ret.data_size = 0;
+            }
+            break;
+        case I32HEX_TYPE_EXT_LINEAR_ADDR:
+            if(flipper_i32hex_file_check_data(data, ret.data_size)) {
+                data[0] = data[4];
+                data[1] = data[5];
+                data[3] = 0;
+                data[4] = 0;
+                ret.status = FlipperI32HexFileStatusUdateAddr;
+                ret.data_size = 4;
+            } else {
+                ret.status = FlipperI32HexFileStatusErrorCrc;
+                ret.data_size = 0;
+            }
+            break;
+        case I32HEX_TYPE_START_LINEAR_ADDR:
+            ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand;
+            ret.data_size = 0;
+            break;
+        default:
+            ret.status = FlipperI32HexFileStatusErrorUnsupportedCommand;
+            ret.data_size = 0;
+            break;
+        }
+    } else {
+        ret.status = FlipperI32HexFileStatusErrorData;
+        ret.data_size = 0;
+    }
+    return ret;
+}
+
+bool flipper_i32hex_file_check(FlipperI32HexFile* instance) {
+    furi_assert(instance);
+
+    uint32_t data_size = 280;
+    uint8_t data[280] = {0};
+    bool ret = true;
+
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) {
+        FURI_LOG_E(TAG, "File is not open");
+        ret = false;
+    } else {
+        stream_rewind(instance->stream);
+
+        while(stream_read_line(instance->stream, instance->str_data)) {
+            FlipperI32HexFileRet parse_ret = flipper_i32hex_file_parse(
+                instance, furi_string_get_cstr(instance->str_data), data, data_size);
+
+            if(parse_ret.status < 0) {
+                ret = false;
+            }
+        }
+        stream_rewind(instance->stream);
+    }
+    return ret;
+}
+
+FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size) {
+    furi_assert(instance);
+    furi_assert(data);
+
+    FlipperI32HexFileRet ret = {.status = FlipperI32HexFileStatusOK, .data_size = 0};
+    if(instance->file_open != FlipperI32HexFileStatusOpenFileRead) {
+        ret.status = FlipperI32HexFileStatusErrorFileRead;
+    } else {
+        stream_read_line(instance->stream, instance->str_data);
+        ret = flipper_i32hex_file_parse(
+            instance, furi_string_get_cstr(instance->str_data), data, data_size);
+    }
+
+    return ret;
+}

+ 55 - 0
base_pack/avr_isp_programmer/helpers/flipper_i32hex_file.h

@@ -0,0 +1,55 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef struct FlipperI32HexFile FlipperI32HexFile;
+
+typedef enum {
+    FlipperI32HexFileStatusOK = 0,
+    FlipperI32HexFileStatusData = 2,
+    FlipperI32HexFileStatusUdateAddr = 3,
+    FlipperI32HexFileStatusEofFile = 4,
+    FlipperI32HexFileStatusOpenFileWrite = 5,
+    FlipperI32HexFileStatusOpenFileRead = 6,
+
+    // Errors
+    FlipperI32HexFileStatusErrorCrc = (-1),
+    FlipperI32HexFileStatusErrorOverflow = (-2),
+    FlipperI32HexFileStatusErrorData = (-3),
+    FlipperI32HexFileStatusErrorUnsupportedCommand = (-4),
+    FlipperI32HexFileStatusErrorNoOpenFile = (-5),
+    FlipperI32HexFileStatusErrorFileWrite = (-6),
+    FlipperI32HexFileStatusErrorFileRead = (-7),
+
+    FlipperI32HexFileStatusReserved =
+        0x7FFFFFFF, ///< Prevents enum down-size compiler optimization.
+} FlipperI32HexFileStatus;
+
+typedef struct {
+    FlipperI32HexFileStatus status;
+    uint32_t data_size;
+} FlipperI32HexFileRet;
+
+FlipperI32HexFile* flipper_i32hex_file_open_write(const char* name, uint32_t start_addr);
+
+FlipperI32HexFile* flipper_i32hex_file_open_read(const char* name);
+
+void flipper_i32hex_file_close(FlipperI32HexFile* instance);
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size);
+
+FlipperI32HexFileRet flipper_i32hex_file_bin_to_i32hex_set_end_line(FlipperI32HexFile* instance);
+
+const char* flipper_i32hex_file_get_string(FlipperI32HexFile* instance);
+
+void flipper_i32hex_file_bin_to_i32hex_set_addr(FlipperI32HexFile* instance, uint32_t addr);
+
+bool flipper_i32hex_file_check(FlipperI32HexFile* instance);
+
+FlipperI32HexFileRet flipper_i32hex_file_i32hex_to_bin_get_data(
+    FlipperI32HexFile* instance,
+    uint8_t* data,
+    uint32_t data_size);

BIN
base_pack/avr_isp_programmer/images/avr_app_icon_10x10.png


BIN
base_pack/avr_isp_programmer/images/avr_wiring.png


BIN
base_pack/avr_isp_programmer/images/chif_not_found_83x37.png


BIN
base_pack/avr_isp_programmer/images/chip_error_70x22.png


BIN
base_pack/avr_isp_programmer/images/chip_long_70x22.png


BIN
base_pack/avr_isp_programmer/images/chip_not_found_83x37.png


BIN
base_pack/avr_isp_programmer/images/dolphin_nice_96x59.png


BIN
base_pack/avr_isp_programmer/images/isp_active_128x53.png


BIN
base_pack/avr_isp_programmer/images/link_waiting_77x56.png


+ 386 - 0
base_pack/avr_isp_programmer/lib/driver/avr_isp_chip_arr.c

@@ -0,0 +1,386 @@
+#include "avr_isp_chip_arr.h"
+
+#include <furi.h>
+
+//https://github.com/avrdudes/avrdude/blob/master/src/avrintel.c
+
+const AvrIspChipArr avr_isp_chip_arr[] = {   // Value of -1 typically means unknown
+  //{mcu_name,       mcuid,  family, {sig,    na, ture}, flstart,  flsize, pgsiz, nb, bootsz, eestart, eesize, ep, rambeg, ramsiz, nf, nl,  ni}, // Source
+  {"ATtiny4",            0, F_AVR8L, {0x1E, 0x8F, 0x0A},       0, 0x00200, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny5",            1, F_AVR8L, {0x1E, 0x8F, 0x09},       0, 0x00200, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny9",            2, F_AVR8L, {0x1E, 0x90, 0x08},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny10",           3, F_AVR8L, {0x1E, 0x90, 0x03},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  11}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny20",           4, F_AVR8L, {0x1E, 0x91, 0x0F},       0, 0x00800, 0x020,  0,      0,       0,      0,  0, 0x0040, 0x0080,  1,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny40",           5, F_AVR8L, {0x1E, 0x92, 0x0E},       0, 0x01000, 0x040,  0,      0,       0,      0,  0, 0x0040, 0x0100,  1,  1,  18}, // atdf, avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATtiny102",          6, F_AVR8L, {0x1E, 0x90, 0x0C},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  16}, // atdf, avrdude, boot size (manual)
+  {"ATtiny104",          7, F_AVR8L, {0x1E, 0x90, 0x0B},       0, 0x00400, 0x010,  0,      0,       0,      0,  0, 0x0040, 0x0020,  1,  1,  16}, // atdf, avrdude, boot size (manual)
+
+  {"ATtiny11",           8,  F_AVR8, {0x1E, 0x90, 0x04},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  1, 0x0060, 0x0020,  1,  1,   5}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny12",           9,  F_AVR8, {0x1E, 0x90, 0x05},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  2, 0x0060, 0x0020,  1,  1,   6}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny13",          10,  F_AVR8, {0x1E, 0x90, 0x07},       0, 0x00400, 0x020,  0,      0,       0, 0x0040,  4, 0x0060, 0x0040,  2,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny13A",         11,  F_AVR8, {0x1E, 0x90, 0x07},       0, 0x00400, 0x020,  0,      0,       0, 0x0040,  4, 0x0060, 0x0040,  2,  1,  10}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny15",          12,  F_AVR8, {0x1E, 0x90, 0x06},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  2, 0x0060, 0x0020,  1,  1,   9}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny22",          13,  F_AVR8, {0x1E, 0x91, 0x06},       0, 0x00800,    -1,  0,      0,      -1,     -1, -1, 0x0060, 0x0080,  1,  1,   3}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATtiny24",          14,  F_AVR8, {0x1E, 0x91, 0x0B},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny24A",         15,  F_AVR8, {0x1E, 0x91, 0x0B},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny25",          16,  F_AVR8, {0x1E, 0x91, 0x08},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  15}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny26",          17,  F_AVR8, {0x1E, 0x91, 0x09},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  2,  1,  12}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny28",          18,  F_AVR8, {0x1E, 0x91, 0x07},       0, 0x00800, 0x002,  0,      0,       0,      0,  0, 0x0060, 0x0020,  1,  1,   6}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny43U",         19,  F_AVR8, {0x1E, 0x92, 0x0C},       0, 0x01000, 0x040,  0,      0,       0, 0x0040,  4, 0x0060, 0x0100,  3,  1,  16}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny44",          20,  F_AVR8, {0x1E, 0x92, 0x07},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny44A",         21,  F_AVR8, {0x1E, 0x92, 0x07},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny45",          22,  F_AVR8, {0x1E, 0x92, 0x06},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  15}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny48",          23,  F_AVR8, {0x1E, 0x92, 0x09},       0, 0x01000, 0x040,  0,      0,       0, 0x0040,  4, 0x0100, 0x0100,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny84",          24,  F_AVR8, {0x1E, 0x93, 0x0C},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny84A",         25,  F_AVR8, {0x1E, 0x93, 0x0C},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny85",          26,  F_AVR8, {0x1E, 0x93, 0x0B},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  15}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny87",          27,  F_AVR8, {0x1E, 0x93, 0x87},       0, 0x02000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny88",          28,  F_AVR8, {0x1E, 0x93, 0x11},       0, 0x02000, 0x040,  0,      0,       0, 0x0040,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny167",         29,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny261",         30,  F_AVR8, {0x1E, 0x91, 0x0C},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny261A",        31,  F_AVR8, {0x1E, 0x91, 0x0C},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny441",         32,  F_AVR8, {0x1E, 0x92, 0x15},       0, 0x01000, 0x010,  0,      0,       0, 0x0100,  4, 0x0100, 0x0100,  3,  1,  30}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny461",         33,  F_AVR8, {0x1E, 0x92, 0x08},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny461A",        34,  F_AVR8, {0x1E, 0x92, 0x08},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny828",         35,  F_AVR8, {0x1E, 0x93, 0x14},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny828R",        36,  F_AVR8, {0x1E, 0x93, 0x14},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // avrdude, from ATtiny828
+  {"ATtiny841",         37,  F_AVR8, {0x1E, 0x93, 0x15},       0, 0x02000, 0x010,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  30}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny861",         38,  F_AVR8, {0x1E, 0x93, 0x0D},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny861A",        39,  F_AVR8, {0x1E, 0x93, 0x0D},       0, 0x02000, 0x040,  0,      0,       0, 0x0200,  4, 0x0060, 0x0200,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1634",        40,  F_AVR8, {0x1E, 0x94, 0x12},       0, 0x04000, 0x020,  0,      0,       0, 0x0100,  4, 0x0100, 0x0400,  3,  1,  28}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1634R",       41,  F_AVR8, {0x1E, 0x94, 0x12},       0, 0x04000, 0x020,  0,      0,       0, 0x0100,  4, 0x0100, 0x0400,  3,  1,  28}, // avrdude, from ATtiny1634
+  {"ATtiny2313",        42,  F_AVR8, {0x1E, 0x91, 0x0A},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny2313A",       43,  F_AVR8, {0x1E, 0x91, 0x0A},       0, 0x00800, 0x020,  0,      0,       0, 0x0080,  4, 0x0060, 0x0080,  3,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny4313",        44,  F_AVR8, {0x1E, 0x92, 0x0D},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0060, 0x0100,  3,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8",           45,  F_AVR8, {0x1E, 0x93, 0x07},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8A",          46,  F_AVR8, {0x1E, 0x93, 0x07},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  19}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8HVA",        47,  F_AVR8, {0x1E, 0x93, 0x10},       0, 0x02000, 0x080,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  1,  1,  21}, // atdf, avr-gcc 12.2.0
+  {"ATmega8U2",         48,  F_AVR8, {0x1E, 0x93, 0x89},       0, 0x02000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16",          49,  F_AVR8, {0x1E, 0x94, 0x03},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16A",         50,  F_AVR8, {0x1E, 0x94, 0x03},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0400,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16HVA",       51,  F_AVR8, {0x1E, 0x94, 0x0C},       0, 0x04000, 0x080,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  1,  1,  21}, // atdf, avr-gcc 12.2.0
+  {"ATmega16HVB",       52,  F_AVR8, {0x1E, 0x94, 0x0D},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega16HVBrevB",   53,  F_AVR8, {0x1E, 0x94, 0x0D},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega16M1",        54,  F_AVR8, {0x1E, 0x94, 0x84},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATmega16HVA2",      55,  F_AVR8, {0x1E, 0x94, 0x0E},       0, 0x04000, 0x080, -1,     -1,      -1,     -1, -1, 0x0100, 0x0400,  2,  1,  22}, // avr-gcc 12.2.0
+  {"ATmega16U2",        56,  F_AVR8, {0x1E, 0x94, 0x89},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega16U4",        57,  F_AVR8, {0x1E, 0x94, 0x88},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0500,  3,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32",          58,  F_AVR8, {0x1E, 0x95, 0x02},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0060, 0x0800,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32A",         59,  F_AVR8, {0x1E, 0x95, 0x02},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0060, 0x0800,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32HVB",       60,  F_AVR8, {0x1E, 0x95, 0x10},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega32HVBrevB",   61,  F_AVR8, {0x1E, 0x95, 0x10},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  2,  1,  29}, // atdf, avr-gcc 12.2.0
+  {"ATmega32C1",        62,  F_AVR8, {0x1E, 0x95, 0x86},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATmega32M1",        63,  F_AVR8, {0x1E, 0x95, 0x84},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32U2",        64,  F_AVR8, {0x1E, 0x95, 0x8A},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0400,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32U4",        65,  F_AVR8, {0x1E, 0x95, 0x87},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0a00,  3,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega32U6",        66,  F_AVR8, {0x1E, 0x95, 0x88},       0, 0x08000, 0x080,  4, 0x0200,      -1,     -1, -1, 0x0100, 0x0a00,  3,  1,  38}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATmega48",          67,  F_AVR8, {0x1E, 0x92, 0x05},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48A",         68,  F_AVR8, {0x1E, 0x92, 0x05},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48P",         69,  F_AVR8, {0x1E, 0x92, 0x0A},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48PA",        70,  F_AVR8, {0x1E, 0x92, 0x0A},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega48PB",        71,  F_AVR8, {0x1E, 0x92, 0x10},       0, 0x01000, 0x040,  0,      0,       0, 0x0100,  4, 0x0100, 0x0200,  3,  1,  27}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64",          72,  F_AVR8, {0x1E, 0x96, 0x02},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64A",         73,  F_AVR8, {0x1E, 0x96, 0x02},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64HVE",       74,  F_AVR8, {0x1E, 0x96, 0x10},       0, 0x10000, 0x080,  4, 0x0400,      -1,     -1, -1, 0x0100, 0x1000,  2,  1,  25}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATmega64C1",        75,  F_AVR8, {0x1E, 0x96, 0x86},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATmega64M1",        76,  F_AVR8, {0x1E, 0x96, 0x84},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega64HVE2",      77,  F_AVR8, {0x1E, 0x96, 0x10},       0, 0x10000, 0x080,  4, 0x0400,       0, 0x0400,  4, 0x0100, 0x1000,  2,  1,  25}, // atdf, avr-gcc 12.2.0
+  {"ATmega64RFR2",      78,  F_AVR8, {0x1E, 0xA6, 0x02},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0200, 0x2000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88",          79,  F_AVR8, {0x1E, 0x93, 0x0A},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88A",         80,  F_AVR8, {0x1E, 0x93, 0x0A},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88P",         81,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88PA",        82,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega88PB",        83,  F_AVR8, {0x1E, 0x93, 0x16},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  27}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega103",         84,  F_AVR8, {0x1E, 0x97, 0x01},       0, 0x20000, 0x100,  0,      0,       0, 0x1000,  1, 0x0060, 0x0fa0,  1,  1,  24}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega128",         85,  F_AVR8, {0x1E, 0x97, 0x02},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega128A",        86,  F_AVR8, {0x1E, 0x97, 0x02},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x1000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega128RFA1",     87,  F_AVR8, {0x1E, 0xA7, 0x01},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x4000,  3,  1,  72}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega128RFR2",     88,  F_AVR8, {0x1E, 0xA7, 0x02},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x4000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega161",         89,  F_AVR8, {0x1E, 0x94, 0x01},       0, 0x04000, 0x080,  1, 0x0400,       0, 0x0200,  1, 0x0060, 0x0400,  1,  1,  21}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega162",         90,  F_AVR8, {0x1E, 0x94, 0x04},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  28}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega163",         91,  F_AVR8, {0x1E, 0x94, 0x02},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  1, 0x0060, 0x0400,  2,  1,  18}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega164A",        92,  F_AVR8, {0x1E, 0x94, 0x0F},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega164P",        93,  F_AVR8, {0x1E, 0x94, 0x0A},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega164PA",       94,  F_AVR8, {0x1E, 0x94, 0x0A},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega165",         95,  F_AVR8, {0x1E, 0x94, 0x10},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega165A",        96,  F_AVR8, {0x1E, 0x94, 0x10},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega165P",        97,  F_AVR8, {0x1E, 0x94, 0x07},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega165PA",       98,  F_AVR8, {0x1E, 0x94, 0x07},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168",         99,  F_AVR8, {0x1E, 0x94, 0x06},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168A",       100,  F_AVR8, {0x1E, 0x94, 0x06},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168P",       101,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168PA",      102,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega168PB",      103,  F_AVR8, {0x1E, 0x94, 0x15},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  27}, // atdf, avr-gcc 7.3.0, avrdude
+  {"ATmega169",        104,  F_AVR8, {0x1E, 0x94, 0x05},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"ATmega169A",       105,  F_AVR8, {0x1E, 0x94, 0x11},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega169P",       106,  F_AVR8, {0x1E, 0x94, 0x05},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega169PA",      107,  F_AVR8, {0x1E, 0x94, 0x05},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega256RFR2",    108,  F_AVR8, {0x1E, 0xA8, 0x02},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x2000,  8, 0x0200, 0x8000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega323",        109,  F_AVR8, {0x1E, 0x95, 0x01},       0, 0x08000, 0x080,  4, 0x0200,      -1,     -1, -1, 0x0060, 0x0800,  2,  1,  21}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATmega324A",       110,  F_AVR8, {0x1E, 0x95, 0x15},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega324P",       111,  F_AVR8, {0x1E, 0x95, 0x08},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega324PA",      112,  F_AVR8, {0x1E, 0x95, 0x11},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega324PB",      113,  F_AVR8, {0x1E, 0x95, 0x17},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  51}, // atdf, avrdude
+  {"ATmega325",        114,  F_AVR8, {0x1E, 0x95, 0x05},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega325A",       115,  F_AVR8, {0x1E, 0x95, 0x05},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega325P",       116,  F_AVR8, {0x1E, 0x95, 0x0D},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega325PA",      117,  F_AVR8, {0x1E, 0x95, 0x0D},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega328",        118,  F_AVR8, {0x1E, 0x95, 0x14},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega328P",       119,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega328PB",      120,  F_AVR8, {0x1E, 0x95, 0x16},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  45}, // atdf, avr-gcc 7.3.0, avrdude
+  {"ATmega329",        121,  F_AVR8, {0x1E, 0x95, 0x03},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega329A",       122,  F_AVR8, {0x1E, 0x95, 0x03},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega329P",       123,  F_AVR8, {0x1E, 0x95, 0x0B},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega329PA",      124,  F_AVR8, {0x1E, 0x95, 0x0B},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega406",        125,  F_AVR8, {0x1E, 0x95, 0x07},       0, 0x0a000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0800,  2,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega640",        126,  F_AVR8, {0x1E, 0x96, 0x08},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644",        127,  F_AVR8, {0x1E, 0x96, 0x09},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  28}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644A",       128,  F_AVR8, {0x1E, 0x96, 0x09},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644P",       129,  F_AVR8, {0x1E, 0x96, 0x0A},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644PA",      130,  F_AVR8, {0x1E, 0x96, 0x0A},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega644RFR2",    131,  F_AVR8, {0x1E, 0xA6, 0x03},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0200, 0x2000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega645",        132,  F_AVR8, {0x1E, 0x96, 0x05},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega645A",       133,  F_AVR8, {0x1E, 0x96, 0x05},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega645P",       134,  F_AVR8, {0x1E, 0x96, 0x0D},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  22}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega649",        135,  F_AVR8, {0x1E, 0x96, 0x03},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega649A",       136,  F_AVR8, {0x1E, 0x96, 0x03},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega649P",       137,  F_AVR8, {0x1E, 0x96, 0x0B},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  23}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1280",       138,  F_AVR8, {0x1E, 0x97, 0x03},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1281",       139,  F_AVR8, {0x1E, 0x97, 0x04},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1284",       140,  F_AVR8, {0x1E, 0x97, 0x06},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x4000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1284P",      141,  F_AVR8, {0x1E, 0x97, 0x05},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x4000,  3,  1,  35}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1284RFR2",   142,  F_AVR8, {0x1E, 0xA7, 0x03},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x4000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega2560",       143,  F_AVR8, {0x1E, 0x98, 0x01},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega2561",       144,  F_AVR8, {0x1E, 0x98, 0x02},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0200, 0x2000,  3,  1,  57}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega2564RFR2",   145,  F_AVR8, {0x1E, 0xA8, 0x03},       0, 0x40000, 0x100,  4, 0x0400,       0, 0x2000,  8, 0x0200, 0x8000,  3,  1,  77}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250",       146,  F_AVR8, {0x1E, 0x95, 0x06},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250A",      147,  F_AVR8, {0x1E, 0x95, 0x06},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250P",      148,  F_AVR8, {0x1E, 0x95, 0x0E},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3250PA",     149,  F_AVR8, {0x1E, 0x95, 0x0E},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290",       150,  F_AVR8, {0x1E, 0x95, 0x04},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290A",      151,  F_AVR8, {0x1E, 0x95, 0x04},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290P",      152,  F_AVR8, {0x1E, 0x95, 0x0C},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3290PA",     153,  F_AVR8, {0x1E, 0x95, 0x0C},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6450",       154,  F_AVR8, {0x1E, 0x96, 0x06},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6450A",      155,  F_AVR8, {0x1E, 0x96, 0x06},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6450P",      156,  F_AVR8, {0x1E, 0x96, 0x0E},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6490",       157,  F_AVR8, {0x1E, 0x96, 0x04},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6490A",      158,  F_AVR8, {0x1E, 0x96, 0x04},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega6490P",      159,  F_AVR8, {0x1E, 0x96, 0x0C},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  25}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8515",       160,  F_AVR8, {0x1E, 0x93, 0x06},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0200,  2,  1,  17}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega8535",       161,  F_AVR8, {0x1E, 0x93, 0x08},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0060, 0x0200,  2,  1,  21}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT43USB320",       162,  F_AVR8, {0xff,   -1,   -1},       0, 0x10000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0200, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT43USB355",       163,  F_AVR8, {0xff,   -1,   -1},       0, 0x06000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0400, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT76C711",         164,  F_AVR8, {0xff,   -1,   -1},       0, 0x04000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x07a0, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT86RF401",        165,  F_AVR8, {0x1E, 0x91, 0x81},       0, 0x00800,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0080,  0,  1,   3}, // avr-gcc 12.2.0
+  {"AT90PWM1",         166,  F_AVR8, {0x1E, 0x93, 0x83},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0
+  {"AT90PWM2",         167,  F_AVR8, {0x1E, 0x93, 0x81},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90PWM2B",        168,  F_AVR8, {0x1E, 0x93, 0x83},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM3",         169,  F_AVR8, {0x1E, 0x93, 0x81},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM3B",        170,  F_AVR8, {0x1E, 0x93, 0x83},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90CAN32",        171,  F_AVR8, {0x1E, 0x95, 0x81},       0, 0x08000, 0x100,  4, 0x0400,       0, 0x0400,  8, 0x0100, 0x0800,  3,  1,  37}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90CAN64",        172,  F_AVR8, {0x1E, 0x96, 0x81},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  37}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM81",        173,  F_AVR8, {0x1E, 0x93, 0x88},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0100,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"AT90USB82",        174,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90SCR100",       175,  F_AVR8, {0x1E, 0x96, 0xC1},       0, 0x10000, 0x100,  4, 0x0200,      -1,     -1, -1, 0x0100, 0x1000,  3,  1,  38}, // avr-gcc 12.2.0, boot size (manual)
+  {"AT90CAN128",       176,  F_AVR8, {0x1E, 0x97, 0x81},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x1000,  3,  1,  37}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM161",       177,  F_AVR8, {0x1E, 0x94, 0x8B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"AT90USB162",       178,  F_AVR8, {0x1E, 0x94, 0x82},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  29}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM216",       179,  F_AVR8, {0x1E, 0x94, 0x83},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90PWM316",       180,  F_AVR8, {0x1E, 0x94, 0x83},       0, 0x04000, 0x080,  4, 0x0200,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  32}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90USB646",       181,  F_AVR8, {0x1E, 0x96, 0x82},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90USB647",       182,  F_AVR8, {0x1E, 0x96, 0x82},       0, 0x10000, 0x100,  4, 0x0400,       0, 0x0800,  8, 0x0100, 0x1000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90S1200",        183,  F_AVR8, {0x1E, 0x90, 0x01},       0, 0x00400, 0x001,  0,      0,       0, 0x0040,  1, 0x0060, 0x0020,  1,  1,   4}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90USB1286",      184,  F_AVR8, {0x1E, 0x97, 0x82},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x2000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90USB1287",      185,  F_AVR8, {0x1E, 0x97, 0x82},       0, 0x20000, 0x100,  4, 0x0400,       0, 0x1000,  8, 0x0100, 0x2000,  3,  1,  38}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AT90S2313",        186,  F_AVR8, {0x1E, 0x91, 0x01},       0, 0x00800, 0x001,  0,      0,       0, 0x0080,  1, 0x0060, 0x0080,  1,  1,  11}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S2323",        187,  F_AVR8, {0x1E, 0x91, 0x02},       0, 0x00800,    -1,  0,      0,      -1,     -1, -1, 0x0060, 0x0080,  1,  1,   3}, // avr-gcc 12.2.0, boot size (manual)
+  {"AT90S2333",        188,  F_AVR8, {0x1E, 0x91, 0x05},       0, 0x00800, 0x001,  0,      0,       0, 0x0080,  1, 0x0060, 0x0080, -1, -1,  14}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S2343",        189,  F_AVR8, {0x1E, 0x91, 0x03},       0, 0x00800, 0x001,  0,      0,       0, 0x0080,  1, 0x0060, 0x0080,  1,  1,   3}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S4414",        190,  F_AVR8, {0x1E, 0x92, 0x01},       0, 0x01000, 0x001,  0,      0,       0, 0x0100,  1, 0x0060, 0x0100,  1,  1,  13}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S4433",        191,  F_AVR8, {0x1E, 0x92, 0x03},       0, 0x01000, 0x001,  0,      0,       0, 0x0100,  1, 0x0060, 0x0080,  1,  1,  14}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S4434",        192,  F_AVR8, {0x1E, 0x92, 0x02},       0, 0x01000, 0x001,  0,      0,       0, 0x0100,  1, 0x0060, 0x0100,  1,  1,  17}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90S8515",        193,  F_AVR8, {0x1E, 0x93, 0x01},       0, 0x02000, 0x001,  0,      0,       0, 0x0200,  1, 0x0060, 0x0200,  1,  1,  13}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT90C8534",        194,  F_AVR8, {0xff,   -1,   -1},       0, 0x02000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0100, -1, -1,   0}, // avr-gcc 12.2.0
+  {"AT90S8535",        195,  F_AVR8, {0x1E, 0x93, 0x03},       0, 0x02000, 0x001,  0,      0,       0, 0x0200,  1, 0x0060, 0x0200,  1,  1,  17}, // avr-gcc 12.2.0, avrdude, boot size (manual)
+  {"AT94K",            196,  F_AVR8, {0xff,   -1,   -1},       0, 0x08000,    -1, -1,     -1,      -1,     -1, -1, 0x0060, 0x0fa0, -1, -1,   0}, // avr-gcc 12.2.0
+  {"ATA5272",          197,  F_AVR8, {0x1E, 0x93, 0x87},       0, 0x02000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  37}, // atdf, avr-gcc 12.2.0
+  {"ATA5505",          198,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"ATA5700M322",      199,  F_AVR8, {0x1E, 0x95, 0x67}, 0x08000, 0x08000, 0x040,  0,      0,       0, 0x0880, 16, 0x0200, 0x0400,  1,  1,  51}, // atdf
+  {"ATA5702M322",      200,  F_AVR8, {0x1E, 0x95, 0x69}, 0x08000, 0x08000, 0x040,  0,      0,       0, 0x0880, 16, 0x0200, 0x0400,  1,  1,  51}, // atdf, avr-gcc 12.2.0
+  {"ATA5781",          201,  F_AVR8, {0x1E, 0x95, 0x64},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5782",          202,  F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 12.2.0
+  {"ATA5783",          203,  F_AVR8, {0x1E, 0x95, 0x66},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5787",          204,  F_AVR8, {0x1E, 0x94, 0x6C}, 0x08000, 0x05200, 0x040,  0,      0,       0, 0x0400, 16, 0x0200, 0x0800,  1,  1,  44}, // atdf
+  {"ATA5790",          205,  F_AVR8, {0x1E, 0x94, 0x61},       0, 0x04000, 0x080,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  30}, // atdf, avr-gcc 12.2.0
+  {"ATA5790N",         206,  F_AVR8, {0x1E, 0x94, 0x62},       0, 0x04000, 0x080,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  31}, // atdf, avr-gcc 12.2.0
+  {"ATA5791",          207,  F_AVR8, {0x1E, 0x94, 0x62},       0, 0x04000, 0x080,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  31}, // atdf, avr-gcc 7.3.0
+  {"ATA5795",          208,  F_AVR8, {0x1E, 0x93, 0x61},       0, 0x02000, 0x040,  1, 0x0800,       0, 0x0800, 16, 0x0100, 0x0200,  1,  1,  23}, // atdf, avr-gcc 12.2.0
+  {"ATA5831",          209,  F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 12.2.0
+  {"ATA5832",          210,  F_AVR8, {0x1E, 0x95, 0x62},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5833",          211,  F_AVR8, {0x1E, 0x95, 0x63},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA5835",          212,  F_AVR8, {0x1E, 0x94, 0x6B}, 0x08000, 0x05200, 0x040,  0,      0,       0, 0x0400, 16, 0x0200, 0x0800,  1,  1,  44}, // atdf
+  {"ATA6285",          213,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0140,  4, 0x0100, 0x0200,  2,  1,  27}, // atdf, avr-gcc 12.2.0
+  {"ATA6286",          214,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0140,  4, 0x0100, 0x0200,  2,  1,  27}, // atdf, avr-gcc 12.2.0
+  {"ATA6289",          215,  F_AVR8, {0x1E, 0x93, 0x82},       0, 0x02000, 0x040,  4, 0x0100,      -1,     -1, -1, 0x0100, 0x0200,  2,  1,  27}, // avr-gcc 12.2.0, boot size (manual)
+  {"ATA6612C",         216,  F_AVR8, {0x1E, 0x93, 0x0A},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0
+  {"ATA6613C",         217,  F_AVR8, {0x1E, 0x94, 0x06},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // atdf, avr-gcc 12.2.0
+  {"ATA6614Q",         218,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // atdf, avr-gcc 12.2.0
+  {"ATA6616C",         219,  F_AVR8, {0x1E, 0x93, 0x87},       0, 0x02000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"ATA6617C",         220,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"ATA8210",          221,  F_AVR8, {0x1E, 0x95, 0x65}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 7.3.0
+  {"ATA8215",          222,  F_AVR8, {0x1E, 0x95, 0x64},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA8510",          223,  F_AVR8, {0x1E, 0x95, 0x61}, 0x08000, 0x05000, 0x040,  1, 0x5000,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf, avr-gcc 7.3.0
+  {"ATA8515",          224,  F_AVR8, {0x1E, 0x95, 0x63},      -1,      -1,    -1,  0,      0,       0, 0x0400, 16, 0x0200, 0x0400,  1,  1,  42}, // atdf
+  {"ATA664251",        225,  F_AVR8, {0x1E, 0x94, 0x87},       0, 0x04000, 0x080,  0,      0,       0, 0x0200,  4, 0x0100, 0x0200,  3,  1,  20}, // atdf, avr-gcc 12.2.0
+  {"M3000",            226,  F_AVR8, {0xff,   -1,   -1},       0, 0x10000,    -1, -1,     -1,      -1,     -1, -1, 0x1000, 0x1000, -1, -1,   0}, // avr-gcc 12.2.0
+  {"LGT8F88P",         227,  F_AVR8, {0x1E, 0x93, 0x0F},       0, 0x02000, 0x040,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // avrdude, from ATmega88
+  {"LGT8F168P",        228,  F_AVR8, {0x1E, 0x94, 0x0B},       0, 0x04000, 0x080,  4, 0x0100,       0, 0x0200,  4, 0x0100, 0x0400,  3,  1,  26}, // avrdude, from ATmega168P
+  {"LGT8F328P",        229,  F_AVR8, {0x1E, 0x95, 0x0F},       0, 0x08000, 0x080,  4, 0x0200,       0, 0x0400,  4, 0x0100, 0x0800,  3,  1,  26}, // avrdude, from ATmega328P
+
+  {"ATxmega8E5",       230, F_XMEGA, {0x1E, 0x93, 0x41},       0, 0x02800, 0x080,  1, 0x0800,       0, 0x0200, 32, 0x2000, 0x0400,  7,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16A4",      231, F_XMEGA, {0x1E, 0x94, 0x41},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1,  94}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16A4U",     232, F_XMEGA, {0x1E, 0x94, 0x41},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16C4",      233, F_XMEGA, {0x1E, 0x94, 0x43},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16D4",      234, F_XMEGA, {0x1E, 0x94, 0x42},       0, 0x05000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x0800,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega16E5",      235, F_XMEGA, {0x1E, 0x94, 0x45},       0, 0x05000, 0x080,  1, 0x1000,       0, 0x0200, 32, 0x2000, 0x0800,  7,  1,  43}, // atdf, avr-gcc 7.3.0, avrdude
+  {"ATxmega32C3",      236, F_XMEGA, {0x1E, 0x95, 0x49},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0
+  {"ATxmega32D3",      237, F_XMEGA, {0x1E, 0x95, 0x4A},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 114}, // atdf, avr-gcc 12.2.0
+  {"ATxmega32A4",      238, F_XMEGA, {0x1E, 0x95, 0x41},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1,  94}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32A4U",     239, F_XMEGA, {0x1E, 0x95, 0x41},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32C4",      240, F_XMEGA, {0x1E, 0x95, 0x44},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32D4",      241, F_XMEGA, {0x1E, 0x95, 0x42},       0, 0x09000, 0x100,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega32E5",      242, F_XMEGA, {0x1E, 0x95, 0x4C},       0, 0x09000, 0x080,  1, 0x1000,       0, 0x0400, 32, 0x2000, 0x1000,  7,  1,  43}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A1",      243, F_XMEGA, {0x1E, 0x96, 0x4E},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 125}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A1U",     244, F_XMEGA, {0x1E, 0x96, 0x4E},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64B1",      245, F_XMEGA, {0x1E, 0x96, 0x52},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1,  81}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A3",      246, F_XMEGA, {0x1E, 0x96, 0x42},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A3U",     247, F_XMEGA, {0x1E, 0x96, 0x42},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64B3",      248, F_XMEGA, {0x1E, 0x96, 0x51},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1,  54}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64C3",      249, F_XMEGA, {0x1E, 0x96, 0x49},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64D3",      250, F_XMEGA, {0x1E, 0x96, 0x4A},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64A4",      251, F_XMEGA, {0x1E, 0x96, 0x46},       0, 0x11000, 0x100, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega64A4U",     252, F_XMEGA, {0x1E, 0x96, 0x46},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega64D4",      253, F_XMEGA, {0x1E, 0x96, 0x47},       0, 0x11000, 0x100,  1, 0x1000,       0, 0x0800, 32, 0x2000, 0x1000,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A1",     254, F_XMEGA, {0x1E, 0x97, 0x4C},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 125}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A1revD", 255, F_XMEGA, {0x1E, 0x97, 0x41},       0, 0x22000, 0x200, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega128A1U",    256, F_XMEGA, {0x1E, 0x97, 0x4C},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128B1",     257, F_XMEGA, {0x1E, 0x97, 0x4D},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1,  81}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A3",     258, F_XMEGA, {0x1E, 0x97, 0x42},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A3U",    259, F_XMEGA, {0x1E, 0x97, 0x42},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128B3",     260, F_XMEGA, {0x1E, 0x97, 0x4B},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1,  54}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128C3",     261, F_XMEGA, {0x1E, 0x97, 0x52},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128D3",     262, F_XMEGA, {0x1E, 0x97, 0x48},       0, 0x22000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128A4",     263, F_XMEGA, {0x1E, 0x97, 0x46},       0, 0x22000, 0x200, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega128A4U",    264, F_XMEGA, {0x1E, 0x97, 0x46},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega128D4",     265, F_XMEGA, {0x1E, 0x97, 0x47},       0, 0x22000, 0x100,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x2000,  6,  1,  91}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192A1",     266, F_XMEGA, {0x1E, 0x97, 0x4E},       0, 0x32000, 0x200, -1,     -1,       0, 0x0800, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega192A3",     267, F_XMEGA, {0x1E, 0x97, 0x44},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192A3U",    268, F_XMEGA, {0x1E, 0x97, 0x44},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192C3",     269, F_XMEGA, {0x1E, 0x97, 0x51},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega192D3",     270, F_XMEGA, {0x1E, 0x97, 0x49},       0, 0x32000, 0x200,  1, 0x2000,       0, 0x0800, 32, 0x2000, 0x4000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A1",     271, F_XMEGA, {0x1E, 0x98, 0x46},       0, 0x42000, 0x200, -1,     -1,       0, 0x1000, 32,     -1,     -1, -1, -1,   0}, // avrdude
+  {"ATxmega256A3",     272, F_XMEGA, {0x1E, 0x98, 0x42},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A3B",    273, F_XMEGA, {0x1E, 0x98, 0x43},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 122}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A3BU",   274, F_XMEGA, {0x1E, 0x98, 0x43},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256A3U",    275, F_XMEGA, {0x1E, 0x98, 0x42},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256C3",     276, F_XMEGA, {0x1E, 0x98, 0x46},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega256D3",     277, F_XMEGA, {0x1E, 0x98, 0x44},       0, 0x42000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x4000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega384C3",     278, F_XMEGA, {0x1E, 0x98, 0x45},       0, 0x62000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x8000,  6,  1, 127}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATxmega384D3",     279, F_XMEGA, {0x1E, 0x98, 0x47},       0, 0x62000, 0x200,  1, 0x2000,       0, 0x1000, 32, 0x2000, 0x8000,  6,  1, 114}, // atdf, avr-gcc 12.2.0, avrdude
+
+  {"ATtiny202",        280, F_AVR8X, {0x1E, 0x91, 0x23},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny204",        281, F_AVR8X, {0x1E, 0x91, 0x22},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny212",        282, F_AVR8X, {0x1E, 0x91, 0x21},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny214",        283, F_AVR8X, {0x1E, 0x91, 0x20},       0, 0x00800, 0x040,  1,      0, 0x01400, 0x0040, 32, 0x3f80, 0x0080, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny402",        284, F_AVR8X, {0x1E, 0x92, 0x27},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny404",        285, F_AVR8X, {0x1E, 0x92, 0x26},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny406",        286, F_AVR8X, {0x1E, 0x92, 0x25},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny412",        287, F_AVR8X, {0x1E, 0x92, 0x23},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny414",        288, F_AVR8X, {0x1E, 0x92, 0x22},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny416",        289, F_AVR8X, {0x1E, 0x92, 0x21},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny416auto",    290, F_AVR8X, {0x1E, 0x92, 0x28},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf
+  {"ATtiny417",        291, F_AVR8X, {0x1E, 0x92, 0x20},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3f00, 0x0100, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny424",        292, F_AVR8X, {0x1E, 0x92, 0x2C},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny426",        293, F_AVR8X, {0x1E, 0x92, 0x2B},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny427",        294, F_AVR8X, {0x1E, 0x92, 0x2A},       0, 0x01000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny804",        295, F_AVR8X, {0x1E, 0x93, 0x25},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny806",        296, F_AVR8X, {0x1E, 0x93, 0x24},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny807",        297, F_AVR8X, {0x1E, 0x93, 0x23},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny814",        298, F_AVR8X, {0x1E, 0x93, 0x22},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny816",        299, F_AVR8X, {0x1E, 0x93, 0x21},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny817",        300, F_AVR8X, {0x1E, 0x93, 0x20},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3e00, 0x0200, 10,  1,  26}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny824",        301, F_AVR8X, {0x1E, 0x93, 0x29},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny826",        302, F_AVR8X, {0x1E, 0x93, 0x28},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny827",        303, F_AVR8X, {0x1E, 0x93, 0x27},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0080, 32, 0x3c00, 0x0400, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny1604",       304, F_AVR8X, {0x1E, 0x94, 0x25},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1606",       305, F_AVR8X, {0x1E, 0x94, 0x24},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1607",       306, F_AVR8X, {0x1E, 0x94, 0x23},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1614",       307, F_AVR8X, {0x1E, 0x94, 0x22},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1616",       308, F_AVR8X, {0x1E, 0x94, 0x21},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1617",       309, F_AVR8X, {0x1E, 0x94, 0x20},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny1624",       310, F_AVR8X, {0x1E, 0x94, 0x2A},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny1626",       311, F_AVR8X, {0x1E, 0x94, 0x29},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny1627",       312, F_AVR8X, {0x1E, 0x94, 0x28},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny3214",       313, F_AVR8X, {0x1E, 0x95, 0x20},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10,  1,  31}, // avr-gcc 12.2.0
+  {"ATtiny3216",       314, F_AVR8X, {0x1E, 0x95, 0x21},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny3217",       315, F_AVR8X, {0x1E, 0x95, 0x22},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3800, 0x0800, 10,  1,  31}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATtiny3224",       316, F_AVR8X, {0x1E, 0x95, 0x28},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny3226",       317, F_AVR8X, {0x1E, 0x95, 0x27},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10,  1,  30}, // atdf, avrdude
+  {"ATtiny3227",       318, F_AVR8X, {0x1E, 0x95, 0x26},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3400, 0x0c00, 10,  1,  30}, // atdf, avrdude
+  {"ATmega808",        319, F_AVR8X, {0x1E, 0x93, 0x26},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega809",        320, F_AVR8X, {0x1E, 0x93, 0x2A},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3c00, 0x0400, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1608",       321, F_AVR8X, {0x1E, 0x94, 0x27},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega1609",       322, F_AVR8X, {0x1E, 0x94, 0x26},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0100, 32, 0x3800, 0x0800, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3208",       323, F_AVR8X, {0x1E, 0x95, 0x30},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega3209",       324, F_AVR8X, {0x1E, 0x95, 0x31},       0, 0x08000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x3000, 0x1000, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega4808",       325, F_AVR8X, {0x1E, 0x96, 0x50},       0, 0x0c000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10,  1,  36}, // atdf, avr-gcc 12.2.0, avrdude
+  {"ATmega4809",       326, F_AVR8X, {0x1E, 0x96, 0x51},       0, 0x0c000, 0x080,  1,      0, 0x01400, 0x0100, 64, 0x2800, 0x1800, 10,  1,  40}, // atdf, avr-gcc 12.2.0, avrdude
+  {"AVR8EA28",         327, F_AVR8X, {0x1E, 0x93, 0x2C},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR8EA32",         328, F_AVR8X, {0x1E, 0x93, 0x2B},       0, 0x02000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR16DD14",        329, F_AVR8X, {0x1E, 0x94, 0x34},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16DD20",        330, F_AVR8X, {0x1E, 0x94, 0x33},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16DD28",        331, F_AVR8X, {0x1E, 0x94, 0x32},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16EA28",        332, F_AVR8X, {0x1E, 0x94, 0x37},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR16DD32",        333, F_AVR8X, {0x1E, 0x94, 0x31},       0, 0x04000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7800, 0x0800, 16,  4,  36}, // atdf, avrdude
+  {"AVR16EA32",        334, F_AVR8X, {0x1E, 0x94, 0x36},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR16EA48",        335, F_AVR8X, {0x1E, 0x94, 0x35},       0, 0x04000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR32DD14",        336, F_AVR8X, {0x1E, 0x95, 0x3B},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32DD20",        337, F_AVR8X, {0x1E, 0x95, 0x3A},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32DA28",        338, F_AVR8X, {0x1E, 0x95, 0x34},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  41}, // atdf, avrdude
+  {"AVR32DB28",        339, F_AVR8X, {0x1E, 0x95, 0x37},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  42}, // atdf, avrdude
+  {"AVR32DD28",        340, F_AVR8X, {0x1E, 0x95, 0x39},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32EA28",        341, F_AVR8X, {0x1E, 0x95, 0x3E},       0, 0x08000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR32DA32",        342, F_AVR8X, {0x1E, 0x95, 0x33},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  44}, // atdf, avrdude
+  {"AVR32DB32",        343, F_AVR8X, {0x1E, 0x95, 0x36},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  44}, // atdf, avrdude
+  {"AVR32DD32",        344, F_AVR8X, {0x1E, 0x95, 0x38},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x7000, 0x1000, 16,  4,  36}, // atdf, avrdude
+  {"AVR32EA32",        345, F_AVR8X, {0x1E, 0x95, 0x3D},       0, 0x08000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR32DA48",        346, F_AVR8X, {0x1E, 0x95, 0x32},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  58}, // atdf, avrdude
+  {"AVR32DB48",        347, F_AVR8X, {0x1E, 0x95, 0x35},       0, 0x08000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x7000, 0x1000, 16,  4,  61}, // atdf, avrdude
+  {"AVR32EA48",        348, F_AVR8X, {0x1E, 0x95, 0x3C},       0, 0x08000, 0x040,  1,      0, 0x01400, 0x0200,  8,     -1,     -1, -1, -1,   0}, // avrdude
+  {"AVR64DD14",        349, F_AVR8X, {0x1E, 0x96, 0x1D},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64DD20",        350, F_AVR8X, {0x1E, 0x96, 0x1C},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64DA28",        351, F_AVR8X, {0x1E, 0x96, 0x15},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  41}, // atdf, avrdude
+  {"AVR64DB28",        352, F_AVR8X, {0x1E, 0x96, 0x19},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  42}, // atdf, avrdude
+  {"AVR64DD28",        353, F_AVR8X, {0x1E, 0x96, 0x1B},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64EA28",        354, F_AVR8X, {0x1E, 0x96, 0x20},       0, 0x10000, 0x080,  1,      0, 0x01400, 0x0200,  8, 0x6800, 0x1800, 16,  4,  37}, // atdf, avrdude
+  {"AVR64DA32",        355, F_AVR8X, {0x1E, 0x96, 0x14},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  44}, // atdf, avrdude
+  {"AVR64DB32",        356, F_AVR8X, {0x1E, 0x96, 0x18},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  44}, // atdf, avrdude
+  {"AVR64DD32",        357, F_AVR8X, {0x1E, 0x96, 0x1A},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0100,  1, 0x6000, 0x2000, 16,  4,  36}, // atdf, avrdude
+  {"AVR64EA32",        358, F_AVR8X, {0x1E, 0x96, 0x1F},       0, 0x10000, 0x080,  1,      0, 0x01400, 0x0200,  8, 0x6800, 0x1800, 16,  4,  37}, // atdf, avrdude
+  {"AVR64DA48",        359, F_AVR8X, {0x1E, 0x96, 0x13},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  58}, // atdf, avrdude
+  {"AVR64DB48",        360, F_AVR8X, {0x1E, 0x96, 0x17},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  61}, // atdf, avrdude
+  {"AVR64EA48",        361, F_AVR8X, {0x1E, 0x96, 0x1E},       0, 0x10000, 0x080,  1,      0, 0x01400, 0x0200,  8, 0x6800, 0x1800, 16,  4,  45}, // atdf, avrdude
+  {"AVR64DA64",        362, F_AVR8X, {0x1E, 0x96, 0x12},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  64}, // atdf, avrdude
+  {"AVR64DB64",        363, F_AVR8X, {0x1E, 0x96, 0x16},       0, 0x10000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x6000, 0x2000, 16,  4,  65}, // atdf, avrdude
+  {"AVR128DA28",       364, F_AVR8X, {0x1E, 0x97, 0x0A},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  41}, // atdf, avrdude
+  {"AVR128DB28",       365, F_AVR8X, {0x1E, 0x97, 0x0E},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  42}, // atdf, avrdude
+  {"AVR128DA32",       366, F_AVR8X, {0x1E, 0x97, 0x09},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  44}, // atdf, avrdude
+  {"AVR128DB32",       367, F_AVR8X, {0x1E, 0x97, 0x0D},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  44}, // atdf, avrdude
+  {"AVR128DA48",       368, F_AVR8X, {0x1E, 0x97, 0x08},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  58}, // atdf, avrdude
+  {"AVR128DB48",       369, F_AVR8X, {0x1E, 0x97, 0x0C},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  61}, // atdf, avrdude
+  {"AVR128DA64",       370, F_AVR8X, {0x1E, 0x97, 0x07},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  64}, // atdf, avrdude
+  {"AVR128DB64",       371, F_AVR8X, {0x1E, 0x97, 0x0B},       0, 0x20000, 0x200,  1,      0, 0x01400, 0x0200,  1, 0x4000, 0x4000, 16,  4,  65}, // atdf, avrdude
+};
+
+const size_t avr_isp_chip_arr_size = COUNT_OF(avr_isp_chip_arr);

+ 33 - 0
base_pack/avr_isp_programmer/lib/driver/avr_isp_chip_arr.h

@@ -0,0 +1,33 @@
+#pragma once
+
+#include <furi_hal.h>
+
+#define F_AVR8L 1 // TPI programming, ATtiny(4|5|9|10|20|40|102|104)
+#define F_AVR8 2 // ISP programming with SPI, "classic" AVRs
+#define F_XMEGA 4 // PDI programming, ATxmega family
+#define F_AVR8X 8 // UPDI programming, newer 8-bit MCUs
+
+struct AvrIspChipArr { // Value of -1 typically means unknown
+    const char* name; // Name of part
+    uint16_t mcuid; // ID of MCU in 0..2039
+    uint8_t avrarch; // F_AVR8L, F_AVR8, F_XMEGA or F_AVR8X
+    uint8_t sigs[3]; // Signature bytes
+    int32_t flashoffset; // Flash offset
+    int32_t flashsize; // Flash size
+    int16_t pagesize; // Flash page size
+    int8_t nboots; // Number of supported boot sectors
+    int16_t bootsize; // Size of (smallest) boot sector
+    int32_t eepromoffset; // EEPROM offset
+    int32_t eepromsize; // EEPROM size
+    int32_t eeprompagesize; // EEPROM page size
+    int32_t sramstart; // SRAM offset
+    int32_t sramsize; // SRAM size
+    int8_t nfuses; // Number of fuse bytes
+    int8_t nlocks; // Number of lock bytes
+    uint8_t ninterrupts; // Number of vectors in interrupt vector table
+};
+
+typedef struct AvrIspChipArr AvrIspChipArr;
+
+extern const AvrIspChipArr avr_isp_chip_arr[];
+extern const size_t avr_isp_chip_arr_size;

+ 639 - 0
base_pack/avr_isp_programmer/lib/driver/avr_isp_prog.c

@@ -0,0 +1,639 @@
+#include "avr_isp_prog.h"
+#include "avr_isp_prog_cmd.h"
+
+#include <furi.h>
+
+#define AVR_ISP_PROG_TX_RX_BUF_SIZE 320
+#define TAG "AvrIspProg"
+
+struct AvrIspProgSignature {
+    uint8_t vendor;
+    uint8_t part_family;
+    uint8_t part_number;
+};
+
+typedef struct AvrIspProgSignature AvrIspProgSignature;
+
+struct AvrIspProgCfgDevice {
+    uint8_t devicecode;
+    uint8_t revision;
+    uint8_t progtype;
+    uint8_t parmode;
+    uint8_t polling;
+    uint8_t selftimed;
+    uint8_t lockbytes;
+    uint8_t fusebytes;
+    uint8_t flashpoll;
+    uint16_t eeprompoll;
+    uint16_t pagesize;
+    uint16_t eepromsize;
+    uint32_t flashsize;
+};
+
+typedef struct AvrIspProgCfgDevice AvrIspProgCfgDevice;
+
+struct AvrIspProg {
+    AvrIspSpiSw* spi;
+    AvrIspProgCfgDevice* cfg;
+    FuriStreamBuffer* stream_rx;
+    FuriStreamBuffer* stream_tx;
+
+    uint16_t error;
+    uint16_t addr;
+    bool pmode;
+    bool exit;
+    bool rst_active_high;
+    uint8_t buff[AVR_ISP_PROG_TX_RX_BUF_SIZE];
+
+    AvrIspProgCallback callback;
+    void* context;
+};
+
+static void avr_isp_prog_end_pmode(AvrIspProg* instance);
+
+AvrIspProg* avr_isp_prog_init(void) {
+    AvrIspProg* instance = malloc(sizeof(AvrIspProg));
+    instance->cfg = malloc(sizeof(AvrIspProgCfgDevice));
+    instance->stream_rx =
+        furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t));
+    instance->stream_tx =
+        furi_stream_buffer_alloc(sizeof(int8_t) * AVR_ISP_PROG_TX_RX_BUF_SIZE, sizeof(int8_t));
+    instance->rst_active_high = false;
+    instance->exit = false;
+    return instance;
+}
+
+void avr_isp_prog_free(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(instance->spi) avr_isp_prog_end_pmode(instance);
+    furi_stream_buffer_free(instance->stream_tx);
+    furi_stream_buffer_free(instance->stream_rx);
+    free(instance->cfg);
+    free(instance);
+}
+
+size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) {
+    return furi_stream_buffer_spaces_available(instance->stream_rx);
+}
+
+bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len) {
+    furi_assert(instance);
+    furi_assert(data);
+    furi_assert(len != 0);
+    size_t ret = furi_stream_buffer_send(instance->stream_rx, data, sizeof(uint8_t) * len, 0);
+    return ret == sizeof(uint8_t) * len;
+}
+
+size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len) {
+    furi_assert(instance);
+    return furi_stream_buffer_receive(instance->stream_tx, data, sizeof(int8_t) * max_len, 0);
+}
+
+void avr_isp_prog_exit(AvrIspProg* instance) {
+    furi_assert(instance);
+    instance->exit = true;
+}
+
+void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context) {
+    furi_assert(instance);
+    furi_assert(context);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+static void avr_isp_prog_tx_ch(AvrIspProg* instance, uint8_t data) {
+    furi_assert(instance);
+    furi_stream_buffer_send(instance->stream_tx, &data, sizeof(uint8_t), FuriWaitForever);
+}
+
+static uint8_t avr_isp_prog_getch(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t data[1] = {0};
+    while(furi_stream_buffer_receive(instance->stream_rx, &data, sizeof(int8_t), 30) == 0) {
+        if(instance->exit) break;
+    };
+    return data[0];
+}
+
+static void avr_isp_prog_fill(AvrIspProg* instance, size_t len) {
+    furi_assert(instance);
+    for(size_t x = 0; x < len; x++) {
+        instance->buff[x] = avr_isp_prog_getch(instance);
+    }
+}
+
+static void avr_isp_prog_reset_target(AvrIspProg* instance, bool reset) {
+    furi_assert(instance);
+    avr_isp_spi_sw_res_set(instance->spi, (reset == instance->rst_active_high) ? true : false);
+}
+
+static uint8_t avr_isp_prog_spi_transaction(
+    AvrIspProg* instance,
+    uint8_t cmd,
+    uint8_t addr_hi,
+    uint8_t addr_lo,
+    uint8_t data) {
+    furi_assert(instance);
+
+    avr_isp_spi_sw_txrx(instance->spi, cmd);
+    avr_isp_spi_sw_txrx(instance->spi, addr_hi);
+    avr_isp_spi_sw_txrx(instance->spi, addr_lo);
+    return avr_isp_spi_sw_txrx(instance->spi, data);
+}
+
+static void avr_isp_prog_empty_reply(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(avr_isp_prog_getch(instance) == CRC_EOP) {
+        avr_isp_prog_tx_ch(instance, STK_INSYNC);
+        avr_isp_prog_tx_ch(instance, STK_OK);
+    } else {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+}
+
+static void avr_isp_prog_breply(AvrIspProg* instance, uint8_t data) {
+    furi_assert(instance);
+    if(avr_isp_prog_getch(instance) == CRC_EOP) {
+        avr_isp_prog_tx_ch(instance, STK_INSYNC);
+        avr_isp_prog_tx_ch(instance, data);
+        avr_isp_prog_tx_ch(instance, STK_OK);
+    } else {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+}
+
+static void avr_isp_prog_get_version(AvrIspProg* instance, uint8_t data) {
+    furi_assert(instance);
+    switch(data) {
+    case STK_HW_VER:
+        avr_isp_prog_breply(instance, AVR_ISP_HWVER);
+        break;
+    case STK_SW_MAJOR:
+        avr_isp_prog_breply(instance, AVR_ISP_SWMAJ);
+        break;
+    case STK_SW_MINOR:
+        avr_isp_prog_breply(instance, AVR_ISP_SWMIN);
+        break;
+    case AVP_ISP_CONNECT_TYPE:
+        avr_isp_prog_breply(instance, AVP_ISP_SERIAL_CONNECT_TYPE);
+        break;
+    default:
+        avr_isp_prog_breply(instance, AVR_ISP_RESP_0);
+    }
+}
+
+static void avr_isp_prog_set_cfg(AvrIspProg* instance) {
+    furi_assert(instance);
+    // call this after reading cfg packet into buff[]
+    instance->cfg->devicecode = instance->buff[0];
+    instance->cfg->revision = instance->buff[1];
+    instance->cfg->progtype = instance->buff[2];
+    instance->cfg->parmode = instance->buff[3];
+    instance->cfg->polling = instance->buff[4];
+    instance->cfg->selftimed = instance->buff[5];
+    instance->cfg->lockbytes = instance->buff[6];
+    instance->cfg->fusebytes = instance->buff[7];
+    instance->cfg->flashpoll = instance->buff[8];
+    // ignore (instance->buff[9] == instance->buff[8]) //FLASH polling value. Same as �flashpoll�
+    instance->cfg->eeprompoll = instance->buff[10] << 8 | instance->buff[11];
+    instance->cfg->pagesize = instance->buff[12] << 8 | instance->buff[13];
+    instance->cfg->eepromsize = instance->buff[14] << 8 | instance->buff[15];
+    instance->cfg->flashsize = instance->buff[16] << 24 | instance->buff[17] << 16 |
+                               instance->buff[18] << 8 | instance->buff[19];
+
+    // avr devices have active low reset, at89sx are active high
+    instance->rst_active_high = (instance->cfg->devicecode >= 0xe0);
+}
+static bool
+    avr_isp_prog_set_pmode(AvrIspProg* instance, uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
+    furi_assert(instance);
+    uint8_t res = 0;
+    avr_isp_spi_sw_txrx(instance->spi, a);
+    avr_isp_spi_sw_txrx(instance->spi, b);
+    res = avr_isp_spi_sw_txrx(instance->spi, c);
+    avr_isp_spi_sw_txrx(instance->spi, d);
+    return res == 0x53;
+}
+
+static void avr_isp_prog_end_pmode(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(instance->pmode) {
+        avr_isp_prog_reset_target(instance, false);
+        // We're about to take the target out of reset
+        // so configure SPI pins as input
+
+        if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    instance->pmode = false;
+}
+
+static bool avr_isp_prog_start_pmode(AvrIspProg* instance, AvrIspSpiSwSpeed spi_speed) {
+    furi_assert(instance);
+    // Reset target before driving PIN_SCK or PIN_MOSI
+
+    // SPI.begin() will configure SS as output,
+    // so SPI master mode is selected.
+    // We have defined RESET as pin 10,
+    // which for many arduino's is not the SS pin.
+    // So we have to configure RESET as output here,
+    // (reset_target() first sets the correct level)
+    if(instance->spi) avr_isp_spi_sw_free(instance->spi);
+    instance->spi = avr_isp_spi_sw_init(spi_speed);
+
+    avr_isp_prog_reset_target(instance, true);
+    // See avr datasheets, chapter "SERIAL_PRG Programming Algorithm":
+
+    // Pulse RESET after PIN_SCK is low:
+    avr_isp_spi_sw_sck_set(instance->spi, false);
+
+    // discharge PIN_SCK, value arbitrally chosen
+    furi_delay_ms(20);
+    avr_isp_prog_reset_target(instance, false);
+
+    // Pulse must be minimum 2 target CPU speed cycles
+    // so 100 usec is ok for CPU speeds above 20KHz
+    furi_delay_ms(1);
+
+    avr_isp_prog_reset_target(instance, true);
+
+    // Send the enable programming command:
+    // datasheet: must be > 20 msec
+    furi_delay_ms(50);
+    if(avr_isp_prog_set_pmode(instance, AVR_ISP_SET_PMODE)) {
+        instance->pmode = true;
+        return true;
+    }
+    return false;
+}
+
+static AvrIspProgSignature avr_isp_prog_check_signature(AvrIspProg* instance) {
+    furi_assert(instance);
+    AvrIspProgSignature signature;
+    signature.vendor = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR);
+    signature.part_family = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY);
+    signature.part_number = avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER);
+    return signature;
+}
+
+static bool avr_isp_prog_auto_set_spi_speed_start_pmode(AvrIspProg* instance) {
+    AvrIspSpiSwSpeed spi_speed[] = {
+        AvrIspSpiSwSpeed1Mhz,
+        AvrIspSpiSwSpeed400Khz,
+        AvrIspSpiSwSpeed250Khz,
+        AvrIspSpiSwSpeed125Khz,
+        AvrIspSpiSwSpeed60Khz,
+        AvrIspSpiSwSpeed40Khz,
+        AvrIspSpiSwSpeed20Khz,
+        AvrIspSpiSwSpeed10Khz,
+        AvrIspSpiSwSpeed5Khz,
+        AvrIspSpiSwSpeed1Khz,
+    };
+    for(uint8_t i = 0; i < COUNT_OF(spi_speed); i++) {
+        if(avr_isp_prog_start_pmode(instance, spi_speed[i])) {
+            AvrIspProgSignature sig = avr_isp_prog_check_signature(instance);
+            AvrIspProgSignature sig_examination = avr_isp_prog_check_signature(instance); //-V656
+            uint8_t y = 0;
+            while(y < 8) {
+                if(memcmp(
+                       (uint8_t*)&sig, (uint8_t*)&sig_examination, sizeof(AvrIspProgSignature)) !=
+                   0)
+                    break;
+                sig_examination = avr_isp_prog_check_signature(instance);
+                y++;
+            }
+            if(y == 8) {
+                if(spi_speed[i] > AvrIspSpiSwSpeed1Mhz) {
+                    if(i < (COUNT_OF(spi_speed) - 1)) {
+                        avr_isp_prog_end_pmode(instance);
+                        i++;
+                        return avr_isp_prog_start_pmode(instance, spi_speed[i]);
+                    }
+                }
+                return true;
+            }
+        }
+    }
+
+    if(instance->spi) {
+        avr_isp_spi_sw_free(instance->spi);
+        instance->spi = NULL;
+    }
+
+    return false;
+}
+
+static void avr_isp_prog_universal(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t data;
+
+    avr_isp_prog_fill(instance, 4);
+    data = avr_isp_prog_spi_transaction(
+        instance, instance->buff[0], instance->buff[1], instance->buff[2], instance->buff[3]);
+    avr_isp_prog_breply(instance, data);
+}
+
+static void avr_isp_prog_commit(AvrIspProg* instance, uint16_t addr, uint8_t data) {
+    furi_assert(instance);
+    avr_isp_prog_spi_transaction(instance, AVR_ISP_COMMIT(addr));
+    /* polling flash */
+    if(data == 0xFF) {
+        furi_delay_ms(5);
+    } else {
+        /* polling flash */
+        uint32_t starttime = furi_get_tick();
+        while((furi_get_tick() - starttime) < 30) {
+            if(avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(addr)) != 0xFF) {
+                break;
+            };
+        }
+    }
+}
+
+static uint16_t avr_isp_prog_current_page(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint16_t page = 0;
+    switch(instance->cfg->pagesize) {
+    case 32:
+        page = instance->addr & 0xFFFFFFF0;
+        break;
+    case 64:
+        page = instance->addr & 0xFFFFFFE0;
+        break;
+    case 128:
+        page = instance->addr & 0xFFFFFFC0;
+        break;
+    case 256:
+        page = instance->addr & 0xFFFFFF80;
+        break;
+
+    default:
+        page = instance->addr;
+        break;
+    }
+
+    return page;
+}
+
+static uint8_t avr_isp_prog_write_flash_pages(AvrIspProg* instance, size_t length) {
+    furi_assert(instance);
+    size_t x = 0;
+    uint16_t page = avr_isp_prog_current_page(instance);
+    while(x < length) {
+        if(page != avr_isp_prog_current_page(instance)) {
+            --x;
+            avr_isp_prog_commit(instance, page, instance->buff[x++]);
+            page = avr_isp_prog_current_page(instance);
+        }
+        avr_isp_prog_spi_transaction(
+            instance, AVR_ISP_WRITE_FLASH_LO(instance->addr, instance->buff[x++]));
+
+        avr_isp_prog_spi_transaction(
+            instance, AVR_ISP_WRITE_FLASH_HI(instance->addr, instance->buff[x++]));
+        instance->addr++;
+    }
+
+    avr_isp_prog_commit(instance, page, instance->buff[--x]);
+    return STK_OK;
+}
+
+static void avr_isp_prog_write_flash(AvrIspProg* instance, size_t length) {
+    furi_assert(instance);
+    avr_isp_prog_fill(instance, length);
+    if(avr_isp_prog_getch(instance) == CRC_EOP) {
+        avr_isp_prog_tx_ch(instance, STK_INSYNC);
+        avr_isp_prog_tx_ch(instance, avr_isp_prog_write_flash_pages(instance, length));
+    } else {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+}
+
+// write (length) bytes, (start) is a byte address
+static uint8_t
+    avr_isp_prog_write_eeprom_chunk(AvrIspProg* instance, uint16_t start, uint16_t length) {
+    furi_assert(instance);
+    // this writes byte-by-byte,
+    // page writing may be faster (4 bytes at a time)
+    avr_isp_prog_fill(instance, length);
+    for(uint16_t x = 0; x < length; x++) {
+        uint16_t addr = start + x;
+        avr_isp_prog_spi_transaction(instance, AVR_ISP_WRITE_EEPROM(addr, instance->buff[x]));
+        furi_delay_ms(10);
+    }
+    return STK_OK;
+}
+
+static uint8_t avr_isp_prog_write_eeprom(AvrIspProg* instance, size_t length) {
+    furi_assert(instance);
+    // here is a word address, get the byte address
+    uint16_t start = instance->addr * 2;
+    uint16_t remaining = length;
+    if(length > instance->cfg->eepromsize) {
+        instance->error++;
+        return STK_FAILED;
+    }
+    while(remaining > AVR_ISP_EECHUNK) {
+        avr_isp_prog_write_eeprom_chunk(instance, start, AVR_ISP_EECHUNK);
+        start += AVR_ISP_EECHUNK;
+        remaining -= AVR_ISP_EECHUNK;
+    }
+    avr_isp_prog_write_eeprom_chunk(instance, start, remaining);
+    return STK_OK;
+}
+
+static void avr_isp_prog_program_page(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t result = STK_FAILED;
+    uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance);
+    uint8_t memtype = avr_isp_prog_getch(instance);
+    // flash memory @addr, (length) bytes
+    if(memtype == STK_SET_FLASH_TYPE) {
+        avr_isp_prog_write_flash(instance, length);
+        return;
+    }
+    if(memtype == STK_SET_EEPROM_TYPE) {
+        result = avr_isp_prog_write_eeprom(instance, length);
+        if(avr_isp_prog_getch(instance) == CRC_EOP) {
+            avr_isp_prog_tx_ch(instance, STK_INSYNC);
+            avr_isp_prog_tx_ch(instance, result);
+
+        } else {
+            instance->error++;
+            avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        }
+        return;
+    }
+    avr_isp_prog_tx_ch(instance, STK_FAILED);
+    return;
+}
+
+static uint8_t avr_isp_prog_flash_read_page(AvrIspProg* instance, uint16_t length) {
+    furi_assert(instance);
+    for(uint16_t x = 0; x < length; x += 2) {
+        avr_isp_prog_tx_ch(
+            instance,
+            avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_LO(instance->addr)));
+        avr_isp_prog_tx_ch(
+            instance,
+            avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_FLASH_HI(instance->addr)));
+        instance->addr++;
+    }
+    return STK_OK;
+}
+
+static uint8_t avr_isp_prog_eeprom_read_page(AvrIspProg* instance, uint16_t length) {
+    furi_assert(instance);
+    // here again we have a word address
+    uint16_t start = instance->addr * 2;
+    for(uint16_t x = 0; x < length; x++) {
+        uint16_t addr = start + x;
+        avr_isp_prog_tx_ch(
+            instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_EEPROM(addr)));
+    }
+    return STK_OK;
+}
+
+static void avr_isp_prog_read_page(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t result = STK_FAILED;
+    uint16_t length = avr_isp_prog_getch(instance) << 8 | avr_isp_prog_getch(instance);
+    uint8_t memtype = avr_isp_prog_getch(instance);
+    if(avr_isp_prog_getch(instance) != CRC_EOP) {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        return;
+    }
+    avr_isp_prog_tx_ch(instance, STK_INSYNC);
+    if(memtype == STK_SET_FLASH_TYPE) result = avr_isp_prog_flash_read_page(instance, length);
+    if(memtype == STK_SET_EEPROM_TYPE) result = avr_isp_prog_eeprom_read_page(instance, length);
+    avr_isp_prog_tx_ch(instance, result);
+}
+
+static void avr_isp_prog_read_signature(AvrIspProg* instance) {
+    furi_assert(instance);
+    if(avr_isp_prog_getch(instance) != CRC_EOP) {
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        return;
+    }
+    avr_isp_prog_tx_ch(instance, STK_INSYNC);
+
+    avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_VENDOR));
+    avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_FAMILY));
+    avr_isp_prog_tx_ch(instance, avr_isp_prog_spi_transaction(instance, AVR_ISP_READ_PART_NUMBER));
+
+    avr_isp_prog_tx_ch(instance, STK_OK);
+}
+
+void avr_isp_prog_avrisp(AvrIspProg* instance) {
+    furi_assert(instance);
+    uint8_t ch = avr_isp_prog_getch(instance);
+
+    switch(ch) {
+    case STK_GET_SYNC:
+        FURI_LOG_D(TAG, "cmd STK_GET_SYNC");
+        instance->error = 0;
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_GET_SIGN_ON:
+        FURI_LOG_D(TAG, "cmd STK_GET_SIGN_ON");
+        if(avr_isp_prog_getch(instance) == CRC_EOP) {
+            avr_isp_prog_tx_ch(instance, STK_INSYNC);
+
+            avr_isp_prog_tx_ch(instance, 'A');
+            avr_isp_prog_tx_ch(instance, 'V');
+            avr_isp_prog_tx_ch(instance, 'R');
+            avr_isp_prog_tx_ch(instance, ' ');
+            avr_isp_prog_tx_ch(instance, 'I');
+            avr_isp_prog_tx_ch(instance, 'S');
+            avr_isp_prog_tx_ch(instance, 'P');
+
+            avr_isp_prog_tx_ch(instance, STK_OK);
+        } else {
+            instance->error++;
+            avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        }
+        break;
+    case STK_GET_PARAMETER:
+        FURI_LOG_D(TAG, "cmd STK_GET_PARAMETER");
+        avr_isp_prog_get_version(instance, avr_isp_prog_getch(instance));
+        break;
+    case STK_SET_DEVICE:
+        FURI_LOG_D(TAG, "cmd STK_SET_DEVICE");
+        avr_isp_prog_fill(instance, 20);
+        avr_isp_prog_set_cfg(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_SET_DEVICE_EXT: // ignore for now
+        FURI_LOG_D(TAG, "cmd STK_SET_DEVICE_EXT");
+        avr_isp_prog_fill(instance, 5);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_ENTER_PROGMODE:
+        FURI_LOG_D(TAG, "cmd STK_ENTER_PROGMODE");
+        if(!instance->pmode) avr_isp_prog_auto_set_spi_speed_start_pmode(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_LOAD_ADDRESS:
+        FURI_LOG_D(TAG, "cmd STK_LOAD_ADDRESS");
+        instance->addr = avr_isp_prog_getch(instance) | avr_isp_prog_getch(instance) << 8;
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_PROG_FLASH: // ignore for now
+        FURI_LOG_D(TAG, "cmd STK_PROG_FLASH");
+        avr_isp_prog_getch(instance);
+        avr_isp_prog_getch(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_PROG_DATA: // ignore for now
+        FURI_LOG_D(TAG, "cmd STK_PROG_DATA");
+        avr_isp_prog_getch(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_PROG_PAGE:
+        FURI_LOG_D(TAG, "cmd STK_PROG_PAGE");
+        avr_isp_prog_program_page(instance);
+        break;
+    case STK_READ_PAGE:
+        FURI_LOG_D(TAG, "cmd STK_READ_PAGE");
+        avr_isp_prog_read_page(instance);
+        break;
+    case STK_UNIVERSAL:
+        FURI_LOG_D(TAG, "cmd STK_UNIVERSAL");
+        avr_isp_prog_universal(instance);
+        break;
+    case STK_LEAVE_PROGMODE:
+        FURI_LOG_D(TAG, "cmd STK_LEAVE_PROGMODE");
+        instance->error = 0;
+        if(instance->pmode) avr_isp_prog_end_pmode(instance);
+        avr_isp_prog_empty_reply(instance);
+        break;
+    case STK_READ_SIGN:
+        FURI_LOG_D(TAG, "cmd STK_READ_SIGN");
+        avr_isp_prog_read_signature(instance);
+        break;
+    // expecting a command, not CRC_EOP
+    // this is how we can get back in sync
+    case CRC_EOP:
+        FURI_LOG_D(TAG, "cmd CRC_EOP");
+        instance->error++;
+        avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+        break;
+    // anything else we will return STK_UNKNOWN
+    default:
+        FURI_LOG_D(TAG, "cmd STK_ERROR_CMD");
+        instance->error++;
+        if(avr_isp_prog_getch(instance) == CRC_EOP)
+            avr_isp_prog_tx_ch(instance, STK_UNKNOWN);
+        else
+            avr_isp_prog_tx_ch(instance, STK_NOSYNC);
+    }
+
+    if(instance->callback) {
+        instance->callback(instance->context);
+    }
+}

+ 16 - 0
base_pack/avr_isp_programmer/lib/driver/avr_isp_prog.h

@@ -0,0 +1,16 @@
+#pragma once
+
+#include "avr_isp_spi_sw.h"
+#include <furi_hal.h>
+
+typedef struct AvrIspProg AvrIspProg;
+typedef void (*AvrIspProgCallback)(void* context);
+
+AvrIspProg* avr_isp_prog_init(void);
+void avr_isp_prog_free(AvrIspProg* instance);
+size_t avr_isp_prog_spaces_rx(AvrIspProg* instance) ;
+bool avr_isp_prog_rx(AvrIspProg* instance, uint8_t* data, size_t len);
+size_t avr_isp_prog_tx(AvrIspProg* instance, uint8_t* data, size_t max_len);
+void avr_isp_prog_avrisp(AvrIspProg* instance);
+void avr_isp_prog_exit(AvrIspProg* instance);
+void avr_isp_prog_set_tx_callback(AvrIspProg* instance, AvrIspProgCallback callback, void* context);

+ 97 - 0
base_pack/avr_isp_programmer/lib/driver/avr_isp_prog_cmd.h

@@ -0,0 +1,97 @@
+#pragma once
+
+// http://ww1.microchip.com/downloads/en/appnotes/atmel-0943-in-system-programming_applicationnote_avr910.pdf
+// AVR ISP Definitions
+#define AVR_ISP_HWVER 0X02
+#define AVR_ISP_SWMAJ 0X01
+#define AVR_ISP_SWMIN 0X12
+#define AVP_ISP_SERIAL_CONNECT_TYPE 0X53
+#define AVP_ISP_CONNECT_TYPE 0x93
+#define AVR_ISP_RESP_0 0X00
+
+#define AVR_ISP_SET_PMODE 0xAC, 0x53, 0x00, 0x00
+#define AVR_ISP_READ_VENDOR 0x30, 0x00, 0x00, 0x00
+#define AVR_ISP_READ_PART_FAMILY 0x30, 0x00, 0x01, 0x00
+#define AVR_ISP_READ_PART_NUMBER 0x30, 0x00, 0x02, 0x00
+#define AVR_ISP_ERASE_CHIP \
+    0xAC, 0x80, 0x00, 0x00 //Erase Chip, Wait N ms, Release RESET to end the erase.
+//The only way to end a Chip Erase cycle is by temporarily releasing the Reset line
+
+#define AVR_ISP_EXTENDED_ADDR(data) 0x4D, 0x00, data, 0x00
+#define AVR_ISP_WRITE_FLASH_LO(add, data) 0x40, (add >> 8) & 0xFF, add & 0xFF, data
+#define AVR_ISP_WRITE_FLASH_HI(add, data) 0x48, (add >> 8) & 0xFF, add & 0xFF, data
+#define AVR_ISP_READ_FLASH_LO(add) 0x20, (add >> 8) & 0xFF, add & 0xFF, 0x00
+#define AVR_ISP_READ_FLASH_HI(add) 0x28, (add >> 8) & 0xFF, add & 0xFF, 0x00
+
+#define AVR_ISP_WRITE_EEPROM(add, data) \
+    0xC0, (add >> 8) & 0xFF, add & 0xFF, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_EEPROM(add) 0xA0, (add >> 8) & 0xFF, add & 0xFF, 0xFF
+
+#define AVR_ISP_COMMIT(add) \
+    0x4C, (add >> 8) & 0xFF, add & 0xFF, 0x00 //Send cmd, polling read last addr page
+
+#define AVR_ISP_OSCCAL(add) 0x38, 0x00, add, 0x00
+
+#define AVR_ISP_WRITE_LOCK_BYTE(data) 0xAC, 0xE0, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_LOCK_BYTE 0x58, 0x00, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_LOW(data) 0xAC, 0xA0, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_FUSE_LOW 0x50, 0x00, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_HIGH(data) 0xAC, 0xA8, 0x00, data //Send cmd, Wait N ms
+#define AVR_ISP_READ_FUSE_HIGH 0x58, 0x08, 0x00, 0x00
+#define AVR_ISP_WRITE_FUSE_EXTENDED(data) 0xAC, 0xA4, 0x00, data //Send cmd, Wait N ms (~write)
+#define AVR_ISP_READ_FUSE_EXTENDED 0x50, 0x08, 0x00, 0x00
+
+#define AVR_ISP_EECHUNK 0x20
+
+// https://www.microchip.com/content/dam/mchp/documents/OTH/ApplicationNotes/ApplicationNotes/doc2525.pdf
+// STK Definitions
+#define STK_OK 0x10
+#define STK_FAILED 0x11
+#define STK_UNKNOWN 0x12
+#define STK_INSYNC 0x14
+#define STK_NOSYNC 0x15
+#define CRC_EOP 0x20
+
+#define STK_GET_SYNC 0x30
+#define STK_GET_SIGN_ON 0x31
+#define STK_SET_PARAMETER 0x40
+#define STK_GET_PARAMETER 0x41
+#define STK_SET_DEVICE 0x42
+#define STK_SET_DEVICE_EXT 0x45
+#define STK_ENTER_PROGMODE 0x50
+#define STK_LEAVE_PROGMODE 0x51
+#define STK_CHIP_ERASE 0x52
+#define STK_CHECK_AUTOINC 0x53
+#define STK_LOAD_ADDRESS 0x55
+#define STK_UNIVERSAL 0x56
+#define STK_UNIVERSAL_MULTI 0x57
+#define STK_PROG_FLASH 0x60
+#define STK_PROG_DATA 0x61
+#define STK_PROG_FUSE 0x62
+#define STK_PROG_FUSE_EXT 0x65
+#define STK_PROG_LOCK 0x63
+#define STK_PROG_PAGE 0x64
+#define STK_READ_FLASH 0x70
+#define STK_READ_DATA 0x71
+#define STK_READ_FUSE 0x72
+#define STK_READ_LOCK 0x73
+#define STK_READ_PAGE 0x74
+#define STK_READ_SIGN 0x75
+#define STK_READ_OSCCAL 0x76
+#define STK_READ_FUSE_EXT 0x77
+#define STK_READ_OSCCAL_EXT 0x78
+#define STK_HW_VER 0x80
+#define STK_SW_MAJOR 0x81
+#define STK_SW_MINOR 0x82
+#define STK_LEDS 0x83
+#define STK_VTARGET 0x84
+#define STK_VADJUST 0x85
+#define STK_OSC_PSCALE 0x86
+#define STK_OSC_CMATCH 0x87
+#define STK_SCK_DURATION 0x89
+#define STK_BUFSIZEL 0x90
+#define STK_BUFSIZEH 0x91
+#define STK_STK500_TOPCARD_DETECT 0x98
+
+#define STK_SET_EEPROM_TYPE 0X45
+#define STK_SET_FLASH_TYPE 0X46

+ 71 - 0
base_pack/avr_isp_programmer/lib/driver/avr_isp_spi_sw.c

@@ -0,0 +1,71 @@
+#include "avr_isp_spi_sw.h"
+
+#include <furi.h>
+
+#define AVR_ISP_SPI_SW_MISO &gpio_ext_pa6
+#define AVR_ISP_SPI_SW_MOSI &gpio_ext_pa7
+#define AVR_ISP_SPI_SW_SCK &gpio_ext_pb3
+#define AVR_ISP_RESET &gpio_ext_pb2
+
+struct AvrIspSpiSw {
+    AvrIspSpiSwSpeed speed_wait_time;
+    const GpioPin* miso;
+    const GpioPin* mosi;
+    const GpioPin* sck;
+    const GpioPin* res;
+};
+
+AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed) {
+    AvrIspSpiSw* instance = malloc(sizeof(AvrIspSpiSw));
+    instance->speed_wait_time = speed;
+    instance->miso = AVR_ISP_SPI_SW_MISO;
+    instance->mosi = AVR_ISP_SPI_SW_MOSI;
+    instance->sck = AVR_ISP_SPI_SW_SCK;
+    instance->res = AVR_ISP_RESET;
+
+    furi_hal_gpio_init(instance->miso, GpioModeInput, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_write(instance->mosi, false);
+    furi_hal_gpio_init(instance->mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_write(instance->sck, false);
+    furi_hal_gpio_init(instance->sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+    furi_hal_gpio_init(instance->res, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
+
+    return instance;
+}
+
+void avr_isp_spi_sw_free(AvrIspSpiSw* instance) {
+    furi_assert(instance);
+    furi_hal_gpio_init(instance->res, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_gpio_init(instance->miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_gpio_init(instance->mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    furi_hal_gpio_init(instance->sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
+    free(instance);
+}
+
+uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data) {
+    furi_assert(instance);
+    for(uint8_t i = 0; i < 8; ++i) {
+        furi_hal_gpio_write(instance->mosi, (data & 0x80) ? true : false);
+
+        furi_hal_gpio_write(instance->sck, true);
+        if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz)
+            furi_delay_us(instance->speed_wait_time - 1);
+
+        data = (data << 1) | furi_hal_gpio_read(instance->miso); //-V792
+
+        furi_hal_gpio_write(instance->sck, false);
+        if(instance->speed_wait_time != AvrIspSpiSwSpeed1Mhz)
+            furi_delay_us(instance->speed_wait_time - 1);
+    }
+    return data;
+}
+
+void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state) {
+    furi_assert(instance);
+    furi_hal_gpio_write(instance->res, state);
+}
+
+void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state) {
+    furi_assert(instance);
+    furi_hal_gpio_write(instance->sck, state);
+}

+ 24 - 0
base_pack/avr_isp_programmer/lib/driver/avr_isp_spi_sw.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <furi_hal.h>
+
+typedef enum {
+    AvrIspSpiSwSpeed1Mhz = 0,
+    AvrIspSpiSwSpeed400Khz = 1,
+    AvrIspSpiSwSpeed250Khz = 2,
+    AvrIspSpiSwSpeed125Khz = 4,
+    AvrIspSpiSwSpeed60Khz = 8,
+    AvrIspSpiSwSpeed40Khz = 12,
+    AvrIspSpiSwSpeed20Khz = 24,
+    AvrIspSpiSwSpeed10Khz = 48,
+    AvrIspSpiSwSpeed5Khz = 96,
+    AvrIspSpiSwSpeed1Khz = 480,
+} AvrIspSpiSwSpeed;
+
+typedef struct AvrIspSpiSw AvrIspSpiSw;
+
+AvrIspSpiSw* avr_isp_spi_sw_init(AvrIspSpiSwSpeed speed);
+void avr_isp_spi_sw_free(AvrIspSpiSw* instance);
+uint8_t avr_isp_spi_sw_txrx(AvrIspSpiSw* instance, uint8_t data);
+void avr_isp_spi_sw_res_set(AvrIspSpiSw* instance, bool state);
+void avr_isp_spi_sw_sck_set(AvrIspSpiSw* instance, bool state);

BIN
base_pack/avr_isp_programmer/lib/driver/clock.png


+ 30 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene.c

@@ -0,0 +1,30 @@
+#include "../avr_isp_app_i.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const avr_isp_scene_on_enter_handlers[])(void*) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const avr_isp_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const avr_isp_scene_on_exit_handlers[])(void* context) = {
+#include "avr_isp_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers avr_isp_scene_handlers = {
+    .on_enter_handlers = avr_isp_scene_on_enter_handlers,
+    .on_event_handlers = avr_isp_scene_on_event_handlers,
+    .on_exit_handlers = avr_isp_scene_on_exit_handlers,
+    .scene_num = AvrIspSceneNum,
+};

+ 29 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) AvrIspScene##id,
+typedef enum {
+#include "avr_isp_scene_config.h"
+    AvrIspSceneNum,
+} AvrIspScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers avr_isp_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "avr_isp_scene_config.h"
+#undef ADD_SCENE

+ 99 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_about.c

@@ -0,0 +1,99 @@
+#include "../avr_isp_app_i.h"
+#include "../helpers/avr_isp_types.h"
+
+void avr_isp_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(type == InputTypeShort) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, result);
+    }
+}
+
+void avr_isp_scene_about_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    FuriString* temp_str = furi_string_alloc();
+    furi_string_printf(temp_str, "\e#%s\n", "Information");
+
+    furi_string_cat_printf(temp_str, "Version: %s\n", AVR_ISP_VERSION_APP);
+    furi_string_cat_printf(temp_str, "Developed by: %s\n", AVR_ISP_DEVELOPED);
+    furi_string_cat_printf(temp_str, "Github: %s\n\n", AVR_ISP_GITHUB);
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
+    furi_string_cat_printf(
+        temp_str,
+        "This application is an AVR in-system programmer based on stk500mk1. It is compatible with AVR-based"
+        " microcontrollers including Arduino. You can also use it to repair the chip if you accidentally"
+        " corrupt the bootloader.\n\n");
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "What it can do:");
+    furi_string_cat_printf(temp_str, "- Create a dump of your chip on an SD card\n");
+    furi_string_cat_printf(temp_str, "- Flash your chip firmware from the SD card\n");
+    furi_string_cat_printf(temp_str, "- Act as a wired USB ISP using avrdude software\n\n");
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "Supported chip series:");
+    furi_string_cat_printf(
+        temp_str,
+        "Example command for avrdude flashing: avrdude.exe -p m328p -c stk500v1 -P COMxx -U flash:r:"
+        "X:\\sketch_sample.hex"
+        ":i\n");
+    furi_string_cat_printf(
+        temp_str,
+        "Where: "
+        "-p m328p"
+        " brand of your chip, "
+        "-P COMxx"
+        " com port number in the system when "
+        "ISP Programmer"
+        " is enabled\n\n");
+
+    furi_string_cat_printf(temp_str, "\e#%s\n", "Info");
+    furi_string_cat_printf(
+        temp_str,
+        "ATtinyXXXX\nATmegaXXXX\nAT43Uxxx\nAT76C711\nAT86RF401\nAT90xxxxx\nAT94K\n"
+        "ATAxxxxx\nATA664251\nM3000\nLGT8F88P\nLGT8F168P\nLGT8F328P\n");
+
+    furi_string_cat_printf(
+        temp_str, "For a more detailed list of supported chips, see AVRDude help\n");
+
+    widget_add_text_box_element(
+        app->widget,
+        0,
+        0,
+        128,
+        14,
+        AlignCenter,
+        AlignBottom,
+        "\e#\e!                                                      \e!\n",
+        false);
+    widget_add_text_box_element(
+        app->widget,
+        0,
+        2,
+        128,
+        14,
+        AlignCenter,
+        AlignBottom,
+        "\e#\e!        ISP Programmer       \e!\n",
+        false);
+    widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str));
+    furi_string_free(temp_str);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
+}
+
+bool avr_isp_scene_about_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void avr_isp_scene_about_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    // Clear views
+    widget_reset(app->widget);
+}

+ 72 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_chip_detect.c

@@ -0,0 +1,72 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_chip_detect_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_chip_detect_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    switch(app->error) {
+    case AvrIspErrorReading:
+    case AvrIspErrorWriting:
+    case AvrIspErrorWritingFuse:
+        avr_isp_chip_detect_set_state(
+            app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorOccured);
+        break;
+    case AvrIspErrorVerification:
+        avr_isp_chip_detect_set_state(
+            app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateErrorVerification);
+        break;
+
+    default:
+        avr_isp_chip_detect_set_state(
+            app->avr_isp_chip_detect_view, AvrIspChipDetectViewStateNoDetect);
+        break;
+    }
+    app->error = AvrIspErrorNoError;
+    avr_isp_chip_detect_view_set_callback(
+        app->avr_isp_chip_detect_view, avr_isp_scene_chip_detect_callback, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewChipDetect);
+}
+
+bool avr_isp_scene_chip_detect_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case AvrIspCustomEventSceneChipDetectOk:
+
+            if(scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+               AvrIspViewProgrammer) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneProgrammer);
+            } else if(
+                scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+                AvrIspViewReader) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneInputName);
+            } else if(
+                scene_manager_get_scene_state(app->scene_manager, AvrIspSceneChipDetect) ==
+                AvrIspViewWriter) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneLoad);
+            }
+
+            consumed = true;
+            break;
+        default:
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+    }
+    return consumed;
+}
+
+void avr_isp_scene_chip_detect_on_exit(void* context) {
+    UNUSED(context);
+}

+ 10 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_config.h

@@ -0,0 +1,10 @@
+ADD_SCENE(avr_isp, start, Start)
+ADD_SCENE(avr_isp, about, About)
+ADD_SCENE(avr_isp, programmer, Programmer)
+ADD_SCENE(avr_isp, reader, Reader)
+ADD_SCENE(avr_isp, input_name, InputName)
+ADD_SCENE(avr_isp, load, Load)
+ADD_SCENE(avr_isp, writer, Writer)
+ADD_SCENE(avr_isp, wiring, Wiring)
+ADD_SCENE(avr_isp, chip_detect, ChipDetect)
+ADD_SCENE(avr_isp, success, Success)

+ 89 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_input_name.c

@@ -0,0 +1,89 @@
+#include "../avr_isp_app_i.h"
+#include <gui/modules/validators.h>
+
+#define MAX_TEXT_INPUT_LEN 22
+
+void avr_isp_scene_input_name_text_callback(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneInputName);
+}
+
+void avr_isp_scene_input_name_get_timefilename(FuriString* name) {
+    FuriHalRtcDateTime datetime = {0};
+    furi_hal_rtc_get_datetime(&datetime);
+    furi_string_printf(
+        name,
+        "AVR_dump-%.4d%.2d%.2d-%.2d%.2d%.2d",
+        datetime.year,
+        datetime.month,
+        datetime.day,
+        datetime.hour,
+        datetime.minute,
+        datetime.second);
+}
+
+void avr_isp_scene_input_name_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    // Setup view
+    TextInput* text_input = app->text_input;
+    bool dev_name_empty = false;
+
+    FuriString* file_name = furi_string_alloc();
+
+    avr_isp_scene_input_name_get_timefilename(file_name);
+    furi_string_set(app->file_path, STORAGE_APP_DATA_PATH_PREFIX);
+    //highlighting the entire filename by default
+    dev_name_empty = true;
+
+    strncpy(app->file_name_tmp, furi_string_get_cstr(file_name), AVR_ISP_MAX_LEN_NAME);
+    text_input_set_header_text(text_input, "Name dump");
+    text_input_set_result_callback(
+        text_input,
+        avr_isp_scene_input_name_text_callback,
+        app,
+        app->file_name_tmp,
+        MAX_TEXT_INPUT_LEN, // buffer size
+        dev_name_empty);
+
+    ValidatorIsFile* validator_is_file =
+        validator_is_file_alloc_init(STORAGE_APP_DATA_PATH_PREFIX, AVR_ISP_APP_EXTENSION, "");
+    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
+
+    furi_string_free(file_name);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewTextInput);
+}
+
+bool avr_isp_scene_input_name_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == AvrIspCustomEventSceneInputName) {
+            if(strcmp(app->file_name_tmp, "") != 0) {
+                scene_manager_next_scene(app->scene_manager, AvrIspSceneReader);
+            } else {
+            }
+        }
+    }
+    return false;
+}
+
+void avr_isp_scene_input_name_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    // Clear validator
+    void* validator_context = text_input_get_validator_callback_context(app->text_input);
+    text_input_set_validator(app->text_input, NULL, NULL);
+    validator_is_file_free(validator_context);
+    // Clear view
+    text_input_reset(app->text_input);
+}

+ 22 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_load.c

@@ -0,0 +1,22 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_load_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(avr_isp_load_from_file(app)) {
+        scene_manager_next_scene(app->scene_manager, AvrIspSceneWriter);
+    } else {
+        scene_manager_previous_scene(app->scene_manager);
+    }
+}
+
+bool avr_isp_scene_load_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void avr_isp_scene_load_on_exit(void* context) {
+    UNUSED(context);
+}

+ 28 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_programmer.c

@@ -0,0 +1,28 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_programmer_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_programmer_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    avr_isp_programmer_view_set_callback(
+        app->avr_isp_programmer_view, avr_isp_scene_programmer_callback, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewProgrammer);
+}
+
+bool avr_isp_scene_programmer_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void avr_isp_scene_programmer_on_exit(void* context) {
+    UNUSED(context);
+}

+ 64 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_reader.c

@@ -0,0 +1,64 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_reader_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_reader_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    avr_isp_reader_set_file_path(
+        app->avr_isp_reader_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
+    avr_isp_reader_view_set_callback(app->avr_isp_reader_view, avr_isp_scene_reader_callback, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewReader);
+}
+
+bool avr_isp_scene_reader_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        //do not handle exit on "Back"
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case AvrIspCustomEventSceneReadingOk:
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneSuccess);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneExit:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorVerification:
+            app->error = AvrIspErrorVerification;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorReading:
+            app->error = AvrIspErrorReading;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        default:
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        avr_isp_reader_update_progress(app->avr_isp_reader_view);
+    }
+    return consumed;
+}
+
+void avr_isp_scene_reader_on_exit(void* context) {
+    UNUSED(context);
+}

+ 75 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_start.c

@@ -0,0 +1,75 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_start_submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
+    AvrIspApp* app = context;
+
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void avr_isp_scene_start_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    Submenu* submenu = app->submenu;
+    submenu_add_item(
+        submenu, "Dump AVR", SubmenuIndexAvrIspReader, avr_isp_scene_start_submenu_callback, app);
+    submenu_add_item(
+        submenu, "Flash AVR", SubmenuIndexAvrIspWriter, avr_isp_scene_start_submenu_callback, app);
+    submenu_add_item(
+        submenu,
+        "ISP Programmer",
+        SubmenuIndexAvrIspProgrammer,
+        avr_isp_scene_start_submenu_callback,
+        app);
+    submenu_add_item(
+        submenu, "Wiring", SubmenuIndexAvrIsWiring, avr_isp_scene_start_submenu_callback, app);
+    submenu_add_item(
+        submenu, "About", SubmenuIndexAvrIspAbout, avr_isp_scene_start_submenu_callback, app);
+
+    submenu_set_selected_item(
+        submenu, scene_manager_get_scene_state(app->scene_manager, AvrIspSceneStart));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewSubmenu);
+}
+
+bool avr_isp_scene_start_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexAvrIspAbout) {
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneAbout);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIspProgrammer) {
+            scene_manager_set_scene_state(
+                app->scene_manager, AvrIspSceneChipDetect, AvrIspViewProgrammer);
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIspReader) {
+            scene_manager_set_scene_state(
+                app->scene_manager, AvrIspSceneChipDetect, AvrIspViewReader);
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIspWriter) {
+            scene_manager_set_scene_state(
+                app->scene_manager, AvrIspSceneChipDetect, AvrIspViewWriter);
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+        } else if(event.event == SubmenuIndexAvrIsWiring) {
+            scene_manager_next_scene(app->scene_manager, AvrIspSceneWiring);
+            consumed = true;
+        }
+        scene_manager_set_scene_state(app->scene_manager, AvrIspSceneStart, event.event);
+    }
+
+    return consumed;
+}
+
+void avr_isp_scene_start_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    submenu_reset(app->submenu);
+}

+ 44 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_success.c

@@ -0,0 +1,44 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_success_popup_callback(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, AvrIspCustomEventSceneSuccess);
+}
+
+void avr_isp_scene_success_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    Popup* popup = app->popup;
+    popup_set_icon(popup, 32, 5, &I_dolphin_nice_96x59);
+    popup_set_header(popup, "Success!", 8, 22, AlignLeft, AlignBottom);
+    popup_set_timeout(popup, 1500);
+    popup_set_context(popup, app);
+    popup_set_callback(popup, avr_isp_scene_success_popup_callback);
+    popup_enable_timeout(popup);
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewPopup);
+}
+
+bool avr_isp_scene_success_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == AvrIspCustomEventSceneSuccess) {
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneStart);
+            return true;
+        }
+    }
+    return false;
+}
+
+void avr_isp_scene_success_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    Popup* popup = app->popup;
+    popup_reset(popup);
+}

+ 21 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_wiring.c

@@ -0,0 +1,21 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_wiring_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    widget_add_icon_element(app->widget, 0, 0, &I_avr_wiring);
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWidget);
+}
+
+bool avr_isp_scene_wiring_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+void avr_isp_scene_wiring_on_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    widget_reset(app->widget);
+}

+ 69 - 0
base_pack/avr_isp_programmer/scenes/avr_isp_scene_writer.c

@@ -0,0 +1,69 @@
+#include "../avr_isp_app_i.h"
+
+void avr_isp_scene_writer_callback(AvrIspCustomEvent event, void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void avr_isp_scene_writer_on_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    avr_isp_writer_set_file_path(
+        app->avr_isp_writer_view, furi_string_get_cstr(app->file_path), app->file_name_tmp);
+    avr_isp_writer_view_set_callback(app->avr_isp_writer_view, avr_isp_scene_writer_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, AvrIspViewWriter);
+}
+
+bool avr_isp_scene_writer_on_event(void* context, SceneManagerEvent event) {
+    furi_assert(context);
+
+    AvrIspApp* app = context;
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeBack) {
+        //do not handle exit on "Back"
+        consumed = true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case AvrIspCustomEventSceneExitStartMenu:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneStart);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneExit:
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorVerification:
+            app->error = AvrIspErrorVerification;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorWriting:
+            app->error = AvrIspErrorWriting;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        case AvrIspCustomEventSceneErrorWritingFuse:
+            app->error = AvrIspErrorWritingFuse;
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, AvrIspSceneChipDetect);
+            consumed = true;
+            break;
+        default:
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        avr_isp_writer_update_progress(app->avr_isp_writer_view);
+    }
+    return consumed;
+}
+
+void avr_isp_scene_writer_on_exit(void* context) {
+    UNUSED(context);
+}

+ 213 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_chip_detect.c

@@ -0,0 +1,213 @@
+#include "avr_isp_view_chip_detect.h"
+#include <avr_isp_icons.h>
+#include <gui/elements.h>
+
+#include "../helpers/avr_isp_worker_rw.h"
+
+struct AvrIspChipDetectView {
+    View* view;
+    AvrIspWorkerRW* avr_isp_worker_rw;
+    AvrIspChipDetectViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint16_t idx;
+    const char* name_chip;
+    uint32_t flash_size;
+    AvrIspChipDetectViewState state;
+} AvrIspChipDetectViewModel;
+
+void avr_isp_chip_detect_view_set_callback(
+    AvrIspChipDetectView* instance,
+    AvrIspChipDetectViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, AvrIspChipDetectViewModel * model, { model->state = state; }, true);
+}
+
+void avr_isp_chip_detect_view_draw(Canvas* canvas, AvrIspChipDetectViewModel* model) {
+    canvas_clear(canvas);
+
+    char str_buf[64] = {0};
+    canvas_set_font(canvas, FontPrimary);
+
+    switch(model->state) {
+    case AvrIspChipDetectViewStateDetected:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip detected!");
+        canvas_draw_icon(canvas, 29, 14, &I_chip_long_70x22);
+        canvas_set_font(canvas, FontSecondary);
+        snprintf(str_buf, sizeof(str_buf), "%ld Kb", model->flash_size / 1024);
+        canvas_draw_str_aligned(canvas, 64, 25, AlignCenter, AlignCenter, str_buf);
+        canvas_draw_str_aligned(canvas, 64, 45, AlignCenter, AlignCenter, model->name_chip);
+        elements_button_right(canvas, "Next");
+        break;
+    case AvrIspChipDetectViewStateErrorOccured:
+        canvas_draw_str_aligned(
+            canvas, 64, 5, AlignCenter, AlignCenter, "Error occured, try again!");
+        canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas, 64, 45, AlignCenter, AlignCenter, "Check the wiring and retry");
+        break;
+    case AvrIspChipDetectViewStateErrorVerification:
+        canvas_draw_str_aligned(
+            canvas, 64, 5, AlignCenter, AlignCenter, "Data verification failed");
+        canvas_draw_icon(canvas, 29, 14, &I_chip_error_70x22);
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str_aligned(
+            canvas, 64, 45, AlignCenter, AlignCenter, "Try to restart the process");
+        break;
+
+    default:
+        //AvrIspChipDetectViewStateNoDetect
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "AVR chip not found!");
+        canvas_draw_icon(canvas, 29, 12, &I_chif_not_found_83x37);
+
+        break;
+    }
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_left(canvas, "Retry");
+}
+
+bool avr_isp_chip_detect_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+
+    if(event->type == InputTypeShort) {
+        if(event->key == InputKeyBack) {
+            return false;
+        } else if(event->key == InputKeyRight) {
+            with_view_model(
+                instance->view,
+                AvrIspChipDetectViewModel * model,
+                {
+                    if(model->state == AvrIspChipDetectViewStateDetected) {
+                        if(instance->callback)
+                            instance->callback(
+                                AvrIspCustomEventSceneChipDetectOk, instance->context);
+                    }
+                },
+                false);
+
+        } else if(event->key == InputKeyLeft) {
+            bool detect_chip = false;
+            with_view_model(
+                instance->view,
+                AvrIspChipDetectViewModel * model,
+                {
+                    if(model->state != AvrIspChipDetectViewStateDetecting) {
+                        model->state = AvrIspChipDetectViewStateDetecting;
+                        detect_chip = true;
+                    }
+                },
+                false);
+            if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw);
+        }
+    } else {
+        return false;
+    }
+
+    return true;
+}
+
+static void avr_isp_chip_detect_detect_chip_callback(
+    void* context,
+    const char* name,
+    bool detect_chip,
+    uint32_t flash_size) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspChipDetectViewModel * model,
+        {
+            model->name_chip = name;
+            model->flash_size = flash_size;
+            if(detect_chip) {
+                model->state = AvrIspChipDetectViewStateDetected;
+            } else {
+                model->state = AvrIspChipDetectViewStateNoDetect;
+            }
+        },
+        true);
+}
+void avr_isp_chip_detect_view_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+    bool detect_chip = false;
+    with_view_model(
+        instance->view,
+        AvrIspChipDetectViewModel * model,
+        {
+            if(model->state == AvrIspChipDetectViewStateNoDetect ||
+               model->state == AvrIspChipDetectViewStateDetected) {
+                detect_chip = true;
+            }
+        },
+        false);
+
+    //Start avr_isp_worker_rw
+    instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+    avr_isp_worker_rw_set_callback(
+        instance->avr_isp_worker_rw, avr_isp_chip_detect_detect_chip_callback, instance);
+
+    if(detect_chip) avr_isp_worker_rw_detect_chip(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_chip_detect_view_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspChipDetectView* instance = context;
+
+    avr_isp_worker_rw_set_callback(instance->avr_isp_worker_rw, NULL, NULL);
+    avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspChipDetectView* avr_isp_chip_detect_view_alloc() {
+    AvrIspChipDetectView* instance = malloc(sizeof(AvrIspChipDetectView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspChipDetectViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_chip_detect_view_draw);
+    view_set_input_callback(instance->view, avr_isp_chip_detect_view_input);
+    view_set_enter_callback(instance->view, avr_isp_chip_detect_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_chip_detect_view_exit);
+
+    with_view_model(
+        instance->view,
+        AvrIspChipDetectViewModel * model,
+        { model->state = AvrIspChipDetectViewStateNoDetect; },
+        false);
+    return instance;
+}
+
+void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 32 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_chip_detect.h

@@ -0,0 +1,32 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspChipDetectView AvrIspChipDetectView;
+
+typedef void (*AvrIspChipDetectViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspChipDetectViewStateNoDetect,
+    AvrIspChipDetectViewStateDetecting,
+    AvrIspChipDetectViewStateDetected,
+    AvrIspChipDetectViewStateErrorOccured,
+    AvrIspChipDetectViewStateErrorVerification,
+} AvrIspChipDetectViewState;
+
+void avr_isp_chip_detect_view_set_callback(
+    AvrIspChipDetectView* instance,
+    AvrIspChipDetectViewCallback callback,
+    void* context);
+
+void avr_isp_chip_detect_set_state(AvrIspChipDetectView* instance, AvrIspChipDetectViewState state);
+
+AvrIspChipDetectView* avr_isp_chip_detect_view_alloc();
+
+void avr_isp_chip_detect_view_free(AvrIspChipDetectView* instance);
+
+View* avr_isp_chip_detect_view_get_view(AvrIspChipDetectView* instance);
+
+void avr_isp_chip_detect_view_exit(void* context);

+ 134 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_programmer.c

@@ -0,0 +1,134 @@
+#include "avr_isp_view_programmer.h"
+#include <avr_isp_icons.h>
+
+#include "../helpers/avr_isp_worker.h"
+#include <gui/elements.h>
+
+struct AvrIspProgrammerView {
+    View* view;
+    AvrIspWorker* worker;
+    AvrIspProgrammerViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    AvrIspProgrammerViewStatus status;
+} AvrIspProgrammerViewModel;
+
+void avr_isp_programmer_view_set_callback(
+    AvrIspProgrammerView* instance,
+    AvrIspProgrammerViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_programmer_view_draw(Canvas* canvas, AvrIspProgrammerViewModel* model) {
+    canvas_clear(canvas);
+
+    if(model->status == AvrIspProgrammerViewStatusUSBConnect) {
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_icon(canvas, 0, 0, &I_isp_active_128x53);
+        elements_multiline_text(canvas, 45, 10, "ISP mode active");
+    } else {
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_icon(canvas, 51, 6, &I_link_waiting_77x56);
+        elements_multiline_text(canvas, 0, 25, "Waiting for\nsoftware\nconnection");
+    }
+}
+
+bool avr_isp_programmer_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    UNUSED(context);
+
+    if(event->key == InputKeyBack || event->type != InputTypeShort) {
+        return false;
+    }
+
+    return true;
+}
+
+static void avr_isp_programmer_usb_connect_callback(void* context, bool status_connect) {
+    furi_assert(context);
+    AvrIspProgrammerView* instance = context;
+
+    with_view_model(
+        instance->view,
+        AvrIspProgrammerViewModel * model,
+        {
+            if(status_connect) {
+                model->status = AvrIspProgrammerViewStatusUSBConnect;
+            } else {
+                model->status = AvrIspProgrammerViewStatusNoUSBConnect;
+            }
+        },
+        true);
+}
+
+void avr_isp_programmer_view_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspProgrammerView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspProgrammerViewModel * model,
+        { model->status = AvrIspProgrammerViewStatusNoUSBConnect; },
+        true);
+
+    //Start worker
+    instance->worker = avr_isp_worker_alloc(instance->context);
+
+    avr_isp_worker_set_callback(
+        instance->worker, avr_isp_programmer_usb_connect_callback, instance);
+
+    avr_isp_worker_start(instance->worker);
+}
+
+void avr_isp_programmer_view_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspProgrammerView* instance = context;
+    //Stop worker
+    if(avr_isp_worker_is_running(instance->worker)) {
+        avr_isp_worker_stop(instance->worker);
+    }
+    avr_isp_worker_set_callback(instance->worker, NULL, NULL);
+    avr_isp_worker_free(instance->worker);
+}
+
+AvrIspProgrammerView* avr_isp_programmer_view_alloc() {
+    AvrIspProgrammerView* instance = malloc(sizeof(AvrIspProgrammerView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspProgrammerViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_programmer_view_draw);
+    view_set_input_callback(instance->view, avr_isp_programmer_view_input);
+    view_set_enter_callback(instance->view, avr_isp_programmer_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_programmer_view_exit);
+
+    with_view_model(
+        instance->view,
+        AvrIspProgrammerViewModel * model,
+        { model->status = AvrIspProgrammerViewStatusNoUSBConnect; },
+        false);
+    return instance;
+}
+
+void avr_isp_programmer_view_free(AvrIspProgrammerView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 27 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_programmer.h

@@ -0,0 +1,27 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspProgrammerView AvrIspProgrammerView;
+
+typedef void (*AvrIspProgrammerViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspProgrammerViewStatusNoUSBConnect,
+    AvrIspProgrammerViewStatusUSBConnect,
+} AvrIspProgrammerViewStatus;
+
+void avr_isp_programmer_view_set_callback(
+    AvrIspProgrammerView* instance,
+    AvrIspProgrammerViewCallback callback,
+    void* context);
+
+AvrIspProgrammerView* avr_isp_programmer_view_alloc();
+
+void avr_isp_programmer_view_free(AvrIspProgrammerView* instance);
+
+View* avr_isp_programmer_view_get_view(AvrIspProgrammerView* instance);
+
+void avr_isp_programmer_view_exit(void* context);

+ 215 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_reader.c

@@ -0,0 +1,215 @@
+#include "avr_isp_view_reader.h"
+#include <gui/elements.h>
+
+#include "../helpers/avr_isp_worker_rw.h"
+
+struct AvrIspReaderView {
+    View* view;
+    AvrIspWorkerRW* avr_isp_worker_rw;
+    const char* file_path;
+    const char* file_name;
+    AvrIspReaderViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    AvrIspReaderViewStatus status;
+    float progress_flash;
+    float progress_eeprom;
+} AvrIspReaderViewModel;
+
+void avr_isp_reader_update_progress(AvrIspReaderView* instance) {
+    with_view_model(
+        instance->view,
+        AvrIspReaderViewModel * model,
+        {
+            model->progress_flash =
+                avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw);
+            model->progress_eeprom =
+                avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw);
+        },
+        true);
+}
+
+void avr_isp_reader_view_set_callback(
+    AvrIspReaderView* instance,
+    AvrIspReaderViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_reader_set_file_path(
+    AvrIspReaderView* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+}
+
+void avr_isp_reader_view_draw(Canvas* canvas, AvrIspReaderViewModel* model) {
+    canvas_clear(canvas);
+    char str_buf[64] = {0};
+
+    canvas_set_font(canvas, FontPrimary);
+    switch(model->status) {
+    case AvrIspReaderViewStatusIDLE:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to dump");
+        canvas_set_font(canvas, FontSecondary);
+        elements_button_center(canvas, "Start");
+        break;
+    case AvrIspReaderViewStatusReading:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Reading dump");
+        break;
+    case AvrIspReaderViewStatusVerification:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifyng dump");
+        break;
+
+    default:
+        break;
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 0, 27, "Flash");
+    snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100));
+    elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_buf);
+    canvas_draw_str(canvas, 0, 43, "EEPROM");
+    snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+    elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_buf);
+}
+
+bool avr_isp_reader_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    AvrIspReaderView* instance = context;
+
+    bool ret = true;
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspReaderViewModel * model,
+            {
+                if(model->status == AvrIspReaderViewStatusIDLE) {
+                    if(instance->callback)
+                        instance->callback(AvrIspCustomEventSceneExit, instance->context);
+                    ret = false;
+                }
+            },
+            false);
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspReaderViewModel * model,
+            {
+                if(model->status == AvrIspReaderViewStatusIDLE) {
+                    model->status = AvrIspReaderViewStatusReading;
+                    avr_isp_worker_rw_read_dump_start(
+                        instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                }
+            },
+            false);
+    }
+    return ret;
+}
+
+static void avr_isp_reader_callback_status(void* context, AvrIspWorkerRWStatus status) {
+    furi_assert(context);
+    AvrIspReaderView* instance = context;
+
+    with_view_model(
+        instance->view,
+        AvrIspReaderViewModel * model,
+        {
+            switch(status) {
+            case AvrIspWorkerRWStatusEndReading:
+                model->status = AvrIspReaderViewStatusVerification;
+                avr_isp_worker_rw_verification_start(
+                    instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                model->status = AvrIspReaderViewStatusVerification;
+                break;
+            case AvrIspWorkerRWStatusEndVerification:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneReadingOk, instance->context);
+                break;
+            case AvrIspWorkerRWStatusErrorVerification:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context);
+                break;
+
+            default:
+                //AvrIspWorkerRWStatusErrorReading;
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorReading, instance->context);
+                break;
+            }
+        },
+        true);
+}
+
+void avr_isp_reader_view_enter(void* context) {
+    furi_assert(context);
+    AvrIspReaderView* instance = context;
+
+    with_view_model(
+        instance->view,
+        AvrIspReaderViewModel * model,
+        {
+            model->status = AvrIspReaderViewStatusIDLE;
+            model->progress_flash = 0.0f;
+            model->progress_eeprom = 0.0f;
+        },
+        true);
+
+    //Start avr_isp_worker_rw
+    instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+    avr_isp_worker_rw_set_callback_status(
+        instance->avr_isp_worker_rw, avr_isp_reader_callback_status, instance);
+
+    avr_isp_worker_rw_start(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_reader_view_exit(void* context) {
+    furi_assert(context);
+
+    AvrIspReaderView* instance = context;
+    //Stop avr_isp_worker_rw
+    if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) {
+        avr_isp_worker_rw_stop(instance->avr_isp_worker_rw);
+    }
+
+    avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspReaderView* avr_isp_reader_view_alloc() {
+    AvrIspReaderView* instance = malloc(sizeof(AvrIspReaderView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspReaderViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_reader_view_draw);
+    view_set_input_callback(instance->view, avr_isp_reader_view_input);
+    view_set_enter_callback(instance->view, avr_isp_reader_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_reader_view_exit);
+
+    return instance;
+}
+
+void avr_isp_reader_view_free(AvrIspReaderView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_reader_view_get_view(AvrIspReaderView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 35 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_reader.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspReaderView AvrIspReaderView;
+
+typedef void (*AvrIspReaderViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspReaderViewStatusIDLE,
+    AvrIspReaderViewStatusReading,
+    AvrIspReaderViewStatusVerification,
+} AvrIspReaderViewStatus;
+
+void avr_isp_reader_update_progress(AvrIspReaderView* instance);
+
+void avr_isp_reader_set_file_path(
+    AvrIspReaderView* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_reader_view_set_callback(
+    AvrIspReaderView* instance,
+    AvrIspReaderViewCallback callback,
+    void* context);
+
+AvrIspReaderView* avr_isp_reader_view_alloc();
+
+void avr_isp_reader_view_free(AvrIspReaderView* instance);
+
+View* avr_isp_reader_view_get_view(AvrIspReaderView* instance);
+
+void avr_isp_reader_view_exit(void* context);

+ 268 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_writer.c

@@ -0,0 +1,268 @@
+#include "avr_isp_view_writer.h"
+#include <gui/elements.h>
+
+#include "../helpers/avr_isp_worker_rw.h"
+#include <float_tools.h>
+
+struct AvrIspWriterView {
+    View* view;
+    AvrIspWorkerRW* avr_isp_worker_rw;
+    const char* file_path;
+    const char* file_name;
+    AvrIspWriterViewCallback callback;
+    void* context;
+};
+
+typedef struct {
+    AvrIspWriterViewStatus status;
+    float progress_flash;
+    float progress_eeprom;
+} AvrIspWriterViewModel;
+
+void avr_isp_writer_update_progress(AvrIspWriterView* instance) {
+    with_view_model(
+        instance->view,
+        AvrIspWriterViewModel * model,
+        {
+            model->progress_flash =
+                avr_isp_worker_rw_get_progress_flash(instance->avr_isp_worker_rw);
+            model->progress_eeprom =
+                avr_isp_worker_rw_get_progress_eeprom(instance->avr_isp_worker_rw);
+        },
+        true);
+}
+
+void avr_isp_writer_view_set_callback(
+    AvrIspWriterView* instance,
+    AvrIspWriterViewCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void avr_isp_writer_set_file_path(
+    AvrIspWriterView* instance,
+    const char* file_path,
+    const char* file_name) {
+    furi_assert(instance);
+
+    instance->file_path = file_path;
+    instance->file_name = file_name;
+}
+
+void avr_isp_writer_view_draw(Canvas* canvas, AvrIspWriterViewModel* model) {
+    canvas_clear(canvas);
+    char str_flash[32] = {0};
+    char str_eeprom[32] = {0};
+
+    canvas_set_font(canvas, FontPrimary);
+
+    switch(model->status) {
+    case AvrIspWriterViewStatusIDLE:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Press start to write");
+        canvas_set_font(canvas, FontSecondary);
+        elements_button_center(canvas, "Start");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        break;
+    case AvrIspWriterViewStatusWriting:
+        if(float_is_equal(model->progress_flash, 0.f)) {
+            canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying firmware");
+            snprintf(str_flash, sizeof(str_flash), "***");
+            snprintf(str_eeprom, sizeof(str_eeprom), "***");
+        } else {
+            canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing dump");
+            snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+            snprintf(
+                str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        }
+        break;
+    case AvrIspWriterViewStatusVerification:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Verifying dump");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        break;
+    case AvrIspWriterViewStatusWritingFuse:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Writing fuse");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        break;
+    case AvrIspWriterViewStatusWritingFuseOk:
+        canvas_draw_str_aligned(canvas, 64, 5, AlignCenter, AlignCenter, "Done!");
+        snprintf(str_flash, sizeof(str_flash), "%d%%", (uint8_t)(model->progress_flash * 100));
+        snprintf(str_eeprom, sizeof(str_eeprom), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+        canvas_set_font(canvas, FontSecondary);
+        elements_button_center(canvas, "Reflash");
+        elements_button_right(canvas, "Exit");
+        break;
+
+    default:
+        break;
+    }
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 0, 27, "Flash");
+    // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_flash * 100));
+    elements_progress_bar_with_text(canvas, 44, 17, 84, model->progress_flash, str_flash);
+    canvas_draw_str(canvas, 0, 43, "EEPROM");
+    // snprintf(str_buf, sizeof(str_buf), "%d%%", (uint8_t)(model->progress_eeprom * 100));
+    elements_progress_bar_with_text(canvas, 44, 34, 84, model->progress_eeprom, str_eeprom);
+}
+
+bool avr_isp_writer_view_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    AvrIspWriterView* instance = context;
+
+    bool ret = true;
+    if(event->key == InputKeyBack && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspWriterViewModel * model,
+            {
+                if((model->status == AvrIspWriterViewStatusIDLE) ||
+                   (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+                    if(instance->callback)
+                        instance->callback(AvrIspCustomEventSceneExit, instance->context);
+                    ret = false;
+                }
+            },
+            false);
+    } else if(event->key == InputKeyOk && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspWriterViewModel * model,
+            {
+                if((model->status == AvrIspWriterViewStatusIDLE) ||
+                   (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+                    model->status = AvrIspWriterViewStatusWriting;
+
+                    avr_isp_worker_rw_write_dump_start(
+                        instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                }
+            },
+            false);
+    } else if(event->key == InputKeyRight && event->type == InputTypeShort) {
+        with_view_model(
+            instance->view,
+            AvrIspWriterViewModel * model,
+            {
+                if((model->status == AvrIspWriterViewStatusIDLE) ||
+                   (model->status == AvrIspWriterViewStatusWritingFuseOk)) {
+                    if(instance->callback)
+                        instance->callback(AvrIspCustomEventSceneExitStartMenu, instance->context);
+                    ret = false;
+                }
+            },
+            false);
+    }
+    return ret;
+}
+
+static void avr_isp_writer_callback_status(void* context, AvrIspWorkerRWStatus status) {
+    furi_assert(context);
+
+    AvrIspWriterView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspWriterViewModel * model,
+        {
+            switch(status) {
+            case AvrIspWorkerRWStatusEndWriting:
+                model->status = AvrIspWriterViewStatusVerification;
+                avr_isp_worker_rw_verification_start(
+                    instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                model->status = AvrIspWriterViewStatusVerification;
+                break;
+            case AvrIspWorkerRWStatusErrorVerification:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorVerification, instance->context);
+                break;
+            case AvrIspWorkerRWStatusEndVerification:
+                avr_isp_worker_rw_write_fuse_start(
+                    instance->avr_isp_worker_rw, instance->file_path, instance->file_name);
+                model->status = AvrIspWriterViewStatusWritingFuse;
+                break;
+            case AvrIspWorkerRWStatusErrorWritingFuse:
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorWritingFuse, instance->context);
+                break;
+            case AvrIspWorkerRWStatusEndWritingFuse:
+                model->status = AvrIspWriterViewStatusWritingFuseOk;
+                break;
+
+            default:
+                //AvrIspWorkerRWStatusErrorWriting;
+                if(instance->callback)
+                    instance->callback(AvrIspCustomEventSceneErrorWriting, instance->context);
+                break;
+            }
+        },
+        true);
+}
+
+void avr_isp_writer_view_enter(void* context) {
+    furi_assert(context);
+
+    AvrIspWriterView* instance = context;
+    with_view_model(
+        instance->view,
+        AvrIspWriterViewModel * model,
+        {
+            model->status = AvrIspWriterViewStatusIDLE;
+            model->progress_flash = 0.0f;
+            model->progress_eeprom = 0.0f;
+        },
+        true);
+
+    //Start avr_isp_worker_rw
+    instance->avr_isp_worker_rw = avr_isp_worker_rw_alloc(instance->context);
+
+    avr_isp_worker_rw_set_callback_status(
+        instance->avr_isp_worker_rw, avr_isp_writer_callback_status, instance);
+
+    avr_isp_worker_rw_start(instance->avr_isp_worker_rw);
+}
+
+void avr_isp_writer_view_exit(void* context) {
+    furi_assert(context);
+    AvrIspWriterView* instance = context;
+
+    //Stop avr_isp_worker_rw
+    if(avr_isp_worker_rw_is_running(instance->avr_isp_worker_rw)) {
+        avr_isp_worker_rw_stop(instance->avr_isp_worker_rw);
+    }
+
+    avr_isp_worker_rw_free(instance->avr_isp_worker_rw);
+}
+
+AvrIspWriterView* avr_isp_writer_view_alloc() {
+    AvrIspWriterView* instance = malloc(sizeof(AvrIspWriterView));
+
+    // View allocation and configuration
+    instance->view = view_alloc();
+
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(AvrIspWriterViewModel));
+    view_set_context(instance->view, instance);
+    view_set_draw_callback(instance->view, (ViewDrawCallback)avr_isp_writer_view_draw);
+    view_set_input_callback(instance->view, avr_isp_writer_view_input);
+    view_set_enter_callback(instance->view, avr_isp_writer_view_enter);
+    view_set_exit_callback(instance->view, avr_isp_writer_view_exit);
+
+    return instance;
+}
+
+void avr_isp_writer_view_free(AvrIspWriterView* instance) {
+    furi_assert(instance);
+
+    view_free(instance->view);
+    free(instance);
+}
+
+View* avr_isp_writer_view_get_view(AvrIspWriterView* instance) {
+    furi_assert(instance);
+
+    return instance->view;
+}

+ 37 - 0
base_pack/avr_isp_programmer/views/avr_isp_view_writer.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/avr_isp_types.h"
+#include "../helpers/avr_isp_event.h"
+
+typedef struct AvrIspWriterView AvrIspWriterView;
+
+typedef void (*AvrIspWriterViewCallback)(AvrIspCustomEvent event, void* context);
+
+typedef enum {
+    AvrIspWriterViewStatusIDLE,
+    AvrIspWriterViewStatusWriting,
+    AvrIspWriterViewStatusVerification,
+    AvrIspWriterViewStatusWritingFuse,
+    AvrIspWriterViewStatusWritingFuseOk,
+} AvrIspWriterViewStatus;
+
+void avr_isp_writer_update_progress(AvrIspWriterView* instance);
+
+void avr_isp_writer_set_file_path(
+    AvrIspWriterView* instance,
+    const char* file_path,
+    const char* file_name);
+
+void avr_isp_writer_view_set_callback(
+    AvrIspWriterView* instance,
+    AvrIspWriterViewCallback callback,
+    void* context);
+
+AvrIspWriterView* avr_isp_writer_view_alloc();
+
+void avr_isp_writer_view_free(AvrIspWriterView* instance);
+
+View* avr_isp_writer_view_get_view(AvrIspWriterView* instance);
+
+void avr_isp_writer_view_exit(void* context);

+ 16 - 0
base_pack/bad_bt/application.fam

@@ -0,0 +1,16 @@
+App(
+    appid="bad_bt",
+    name="Bad BT",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="bad_bt_app",
+    requires=[
+        "gui",
+        "dialogs",
+    ],
+    stack_size=2 * 1024,
+    order=70,
+    fap_libs=["assets"],
+    fap_category="Bluetooth",
+    fap_icon="images/badbt_10px.png",
+    fap_icon_assets="images",
+)

+ 333 - 0
base_pack/bad_bt/bad_bt_app.c

@@ -0,0 +1,333 @@
+#include "bad_bt_app.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <storage/storage.h>
+#include <lib/toolbox/path.h>
+#include <lib/flipper_format/flipper_format.h>
+
+#include <bt/bt_service/bt_i.h>
+#include <bt/bt_service/bt.h>
+
+#define BAD_BT_SETTINGS_FILE_NAME ".badbt.settings"
+#define BAD_BT_APP_PATH_BOUND_KEYS_FOLDER EXT_PATH("badbt")
+#define BAD_BT_APP_PATH_BOUND_KEYS_FILE BAD_BT_APP_PATH_BOUND_KEYS_FOLDER "/.badbt.keys"
+
+#define BAD_BT_SETTINGS_PATH BAD_BT_APP_BASE_CONFIG_FOLDER "/" BAD_BT_SETTINGS_FILE_NAME
+
+static bool bad_bt_app_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    BadBtApp* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+static bool bad_bt_app_back_event_callback(void* context) {
+    furi_assert(context);
+    BadBtApp* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+static void bad_bt_app_tick_event_callback(void* context) {
+    furi_assert(context);
+    BadBtApp* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+static void bad_bt_load_settings(BadBtApp* app) {
+    furi_string_reset(app->keyboard_layout);
+    strcpy(app->config.bt_name, "");
+    memcpy(
+        app->config.bt_mac,
+        furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+        BAD_BT_MAC_ADDRESS_LEN);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* file = flipper_format_file_alloc(storage);
+    if(flipper_format_file_open_existing(file, BAD_BT_SETTINGS_PATH)) {
+        FuriString* tmp_str = furi_string_alloc();
+        if(!flipper_format_read_string(file, "Keyboard_Layout", app->keyboard_layout)) {
+            furi_string_reset(app->keyboard_layout);
+        }
+        if(!flipper_format_read_bool(file, "BT_Remember", &(app->bt_remember), 1)) {
+            app->bt_remember = false;
+        }
+        if(flipper_format_read_string(file, "Bt_Name", tmp_str) && !furi_string_empty(tmp_str)) {
+            strcpy(app->config.bt_name, furi_string_get_cstr(tmp_str));
+        } else {
+            strcpy(app->config.bt_name, "");
+        }
+        if(!flipper_format_read_hex(
+               file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN)) {
+            memcpy(
+                app->config.bt_mac,
+                furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+                BAD_BT_MAC_ADDRESS_LEN);
+        }
+        furi_string_free(tmp_str);
+        flipper_format_file_close(file);
+    }
+    flipper_format_free(file);
+
+    if(!furi_string_empty(app->keyboard_layout)) {
+        FileInfo layout_file_info;
+        FS_Error file_check_err = storage_common_stat(
+            storage, furi_string_get_cstr(app->keyboard_layout), &layout_file_info);
+        if(file_check_err != FSE_OK) {
+            furi_string_reset(app->keyboard_layout);
+            return;
+        }
+        if(layout_file_info.size != 256) {
+            furi_string_reset(app->keyboard_layout);
+        }
+    }
+
+    furi_record_close(RECORD_STORAGE);
+}
+
+static void bad_bt_save_settings(BadBtApp* app) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* file = flipper_format_file_alloc(storage);
+    if(flipper_format_file_open_always(file, BAD_BT_SETTINGS_PATH)) {
+        flipper_format_write_string(file, "Keyboard_Layout", app->keyboard_layout);
+        flipper_format_write_bool(file, "BT_Remember", &(app->bt_remember), 1);
+        flipper_format_write_string_cstr(file, "Bt_Name", app->config.bt_name);
+        flipper_format_write_hex(
+            file, "Bt_Mac", (uint8_t*)&app->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN);
+        flipper_format_file_close(file);
+    }
+    flipper_format_free(file);
+    furi_record_close(RECORD_STORAGE);
+}
+
+void bad_bt_reload_worker(BadBtApp* app) {
+    bad_bt_script_close(app->bad_bt_script);
+    app->bad_bt_script = bad_bt_script_open(app->file_path, app->bt, app);
+    bad_bt_script_set_keyboard_layout(app->bad_bt_script, app->keyboard_layout);
+}
+
+void bad_kb_config_refresh_menu(BadBtApp* app) {
+    scene_manager_next_scene(app->scene_manager, BadBtSceneConfig);
+    scene_manager_previous_scene(app->scene_manager);
+}
+
+int32_t bad_bt_config_switch_mode(BadBtApp* app) {
+    bad_bt_reload_worker(app);
+    furi_hal_bt_start_advertising();
+    bad_kb_config_refresh_menu(app);
+    return 0;
+}
+
+void bad_bt_config_switch_remember_mode(BadBtApp* app) {
+    if(app->bt_remember) {
+        furi_hal_bt_set_profile_pairing_method(
+            FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo);
+        bt_set_profile_mac_address(app->bt, (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS);
+        bt_enable_peer_key_update(app->bt);
+    } else {
+        furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone);
+        bt_set_profile_mac_address(app->bt, app->config.bt_mac);
+        bt_disable_peer_key_update(app->bt);
+    }
+    bad_bt_reload_worker(app);
+}
+
+int32_t bad_bt_connection_init(BadBtApp* app) {
+    // Set original name and mac address in prev config
+    strcpy(
+        app->prev_config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard));
+
+    memcpy(app->prev_config.bt_mac, furi_hal_version_get_ble_mac(), BAD_BT_MAC_ADDRESS_LEN);
+
+    bt_timeout = bt_hid_delays[LevelRssi39_0];
+    bt_disconnect(app->bt);
+    // Wait 2nd core to update nvm storage
+    furi_delay_ms(200);
+    bt_keys_storage_set_storage_path(app->bt, BAD_BT_APP_PATH_BOUND_KEYS_FILE);
+    if(strcmp(app->config.bt_name, "") != 0) {
+        furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->config.bt_name);
+    }
+    if(app->bt_remember) {
+        furi_hal_bt_set_profile_mac_addr(
+            FuriHalBtProfileHidKeyboard, (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS);
+        furi_hal_bt_set_profile_pairing_method(
+            FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo);
+    } else {
+        if(memcmp(
+               app->config.bt_mac, (uint8_t*)&BAD_BT_EMPTY_MAC_ADDRESS, BAD_BT_MAC_ADDRESS_LEN) !=
+           0) {
+            furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->config.bt_mac);
+        }
+        furi_hal_bt_set_profile_pairing_method(FuriHalBtProfileHidKeyboard, GapPairingNone);
+    }
+    bt_set_profile(app->bt, BtProfileHidKeyboard);
+    if(strcmp(app->config.bt_name, "") == 0) {
+        strcpy(app->config.bt_name, furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard));
+    }
+    if(memcmp(app->config.bt_mac, (uint8_t*)&BAD_BT_EMPTY_MAC_ADDRESS, BAD_BT_MAC_ADDRESS_LEN) ==
+       0) {
+        memcpy(
+            app->config.bt_mac,
+            furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+            BAD_BT_MAC_ADDRESS_LEN);
+    }
+
+    furi_hal_bt_start_advertising();
+    if(app->bt_remember) {
+        bt_enable_peer_key_update(app->bt);
+    } else {
+        bt_disable_peer_key_update(app->bt);
+    }
+
+    return 0;
+}
+
+void bad_bt_connection_deinit(BadBtApp* app) {
+    bt_disconnect(app->bt);
+    // Wait 2nd core to update nvm storage
+    furi_delay_ms(200);
+    bt_keys_storage_set_default_path(app->bt);
+    furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, app->prev_config.bt_name);
+    furi_hal_bt_set_profile_mac_addr(FuriHalBtProfileHidKeyboard, app->prev_config.bt_mac);
+    furi_hal_bt_set_profile_pairing_method(
+        FuriHalBtProfileHidKeyboard, GapPairingPinCodeVerifyYesNo);
+    bt_set_profile(app->bt, BtProfileSerial);
+    bt_enable_peer_key_update(app->bt);
+}
+
+BadBtApp* bad_bt_app_alloc(char* arg) {
+    BadBtApp* app = malloc(sizeof(BadBtApp));
+
+    app->bad_bt_script = NULL;
+
+    app->file_path = furi_string_alloc();
+    app->keyboard_layout = furi_string_alloc();
+    if(arg && strlen(arg)) {
+        furi_string_set(app->file_path, arg);
+    }
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    storage_simply_mkdir(storage, BAD_BT_APP_BASE_CONFIG_FOLDER);
+    furi_record_close(RECORD_STORAGE);
+
+    bad_bt_load_settings(app);
+
+    app->gui = furi_record_open(RECORD_GUI);
+    app->notifications = furi_record_open(RECORD_NOTIFICATION);
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    app->scene_manager = scene_manager_alloc(&bad_bt_scene_handlers, app);
+
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, bad_bt_app_tick_event_callback, 500);
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, bad_bt_app_custom_event_callback);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, bad_bt_app_back_event_callback);
+
+    Bt* bt = furi_record_open(RECORD_BT);
+    app->bt = bt;
+    app->bt->suppress_pin_screen = true;
+
+    // Custom Widget
+    app->widget = widget_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BadBtAppViewError, widget_get_view(app->widget));
+
+    app->var_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BadBtAppViewConfig, variable_item_list_get_view(app->var_item_list));
+
+    app->bad_bt_view = bad_bt_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BadBtAppViewWork, bad_bt_get_view(app->bad_bt_view));
+
+    app->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BadBtAppViewConfigName, text_input_get_view(app->text_input));
+
+    app->byte_input = byte_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, BadBtAppViewConfigMac, byte_input_get_view(app->byte_input));
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    app->conn_init_thread = furi_thread_alloc_ex(
+        "BadBtConnInit", 1024, (FuriThreadCallback)bad_bt_connection_init, app);
+    furi_thread_start(app->conn_init_thread);
+    if(!furi_string_empty(app->file_path)) {
+        app->bad_bt_script = bad_bt_script_open(app->file_path, app->bt, app);
+        bad_bt_script_set_keyboard_layout(app->bad_bt_script, app->keyboard_layout);
+        scene_manager_next_scene(app->scene_manager, BadBtSceneWork);
+    } else {
+        furi_string_set(app->file_path, BAD_BT_APP_BASE_FOLDER);
+        scene_manager_next_scene(app->scene_manager, BadBtSceneFileSelect);
+    }
+
+    return app;
+}
+
+void bad_bt_app_free(BadBtApp* app) {
+    furi_assert(app);
+
+    if(app->bad_bt_script) {
+        bad_bt_script_close(app->bad_bt_script);
+        app->bad_bt_script = NULL;
+    }
+
+    // Views
+    view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewWork);
+    bad_bt_free(app->bad_bt_view);
+
+    // Custom Widget
+    view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewError);
+    widget_free(app->widget);
+
+    // Variable item list
+    view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfig);
+    variable_item_list_free(app->var_item_list);
+
+    // Text Input
+    view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfigName);
+    text_input_free(app->text_input);
+
+    // Byte Input
+    view_dispatcher_remove_view(app->view_dispatcher, BadBtAppViewConfigMac);
+    byte_input_free(app->byte_input);
+
+    // View dispatcher
+    view_dispatcher_free(app->view_dispatcher);
+    scene_manager_free(app->scene_manager);
+
+    // Restore bt config
+    app->bt->suppress_pin_screen = false;
+    if(app->conn_init_thread) {
+        furi_thread_join(app->conn_init_thread);
+        furi_thread_free(app->conn_init_thread);
+        bad_bt_connection_deinit(app);
+    }
+
+    // Close records
+    furi_record_close(RECORD_GUI);
+    furi_record_close(RECORD_NOTIFICATION);
+    furi_record_close(RECORD_DIALOGS);
+    furi_record_close(RECORD_BT);
+
+    bad_bt_save_settings(app);
+
+    furi_string_free(app->file_path);
+    furi_string_free(app->keyboard_layout);
+
+    free(app);
+}
+
+int32_t bad_bt_app(void* p) {
+    BadBtApp* bad_bt_app = bad_bt_app_alloc((char*)p);
+
+    view_dispatcher_run(bad_bt_app->view_dispatcher);
+
+    bad_bt_app_free(bad_bt_app);
+    return 0;
+}

+ 39 - 0
base_pack/bad_bt/bad_bt_app.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include "scenes/bad_bt_scene.h"
+#include "helpers/ducky_script.h"
+
+#include <gui/gui.h>
+#include <assets_icons.h>
+#include <gui/scene_manager.h>
+#include <dialogs/dialogs.h>
+#include <notification/notification_messages.h>
+#include "bad_bt_icons.h"
+
+#define BAD_BT_APP_BASE_FOLDER EXT_PATH("badusb")
+#define BAD_BT_APP_BASE_CONFIG_FOLDER EXT_PATH("badbt")
+#define BAD_BT_APP_PATH_LAYOUT_FOLDER BAD_BT_APP_BASE_FOLDER "/assets/layouts"
+#define BAD_BT_APP_SCRIPT_EXTENSION ".txt"
+#define BAD_BT_APP_LAYOUT_EXTENSION ".kl"
+
+typedef enum BadBtCustomEvent {
+    BadBtAppCustomEventTextEditResult,
+    BadBtAppCustomEventByteInputDone,
+    BadBtCustomEventErrorBack
+} BadBtCustomEvent;
+
+typedef enum {
+    BadBtAppViewError,
+    BadBtAppViewWork,
+    BadBtAppViewConfig,
+    BadBtAppViewConfigMac,
+    BadBtAppViewConfigName
+} BadBtAppView;
+
+void bad_bt_config_switch_remember_mode(BadBtApp* app);
+
+int32_t bad_bt_connection_init(BadBtApp* app);
+
+void bad_bt_connection_deinit(BadBtApp* app);
+
+void bad_kb_config_refresh_menu(BadBtApp* app);

+ 792 - 0
base_pack/bad_bt/helpers/ducky_script.c

@@ -0,0 +1,792 @@
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <lib/toolbox/args.h>
+#include <furi_hal_bt_hid.h>
+#include <bt/bt_service/bt.h>
+#include <storage/storage.h>
+#include "ducky_script.h"
+#include "ducky_script_i.h"
+#include <dolphin/dolphin.h>
+#include <toolbox/hex.h>
+#include "../bad_bt_app.h"
+
+const uint8_t BAD_BT_BOUND_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN] =
+    {0x41, 0x4a, 0xef, 0xb6, 0xa9, 0xd4};
+const uint8_t BAD_BT_EMPTY_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN] =
+    {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+#define TAG "BadBT"
+#define WORKER_TAG TAG "Worker"
+
+#define BADBT_ASCII_TO_KEY(script, x) \
+    (((uint8_t)x < 128) ? (script->layout[(uint8_t)x]) : HID_KEYBOARD_NONE)
+
+/**
+ * Delays for waiting between HID key press and key release
+*/
+const uint8_t bt_hid_delays[LevelRssiNum] = {
+    60, // LevelRssi122_100
+    55, // LevelRssi99_80
+    50, // LevelRssi79_60
+    47, // LevelRssi59_40
+    34, // LevelRssi39_0
+};
+
+uint8_t bt_timeout = 0;
+
+static LevelRssiRange bt_remote_rssi_range(Bt* bt) {
+    uint8_t rssi;
+
+    if(!bt_remote_rssi(bt, &rssi)) return LevelRssiError;
+
+    if(rssi <= 39)
+        return LevelRssi39_0;
+    else if(rssi <= 59)
+        return LevelRssi59_40;
+    else if(rssi <= 79)
+        return LevelRssi79_60;
+    else if(rssi <= 99)
+        return LevelRssi99_80;
+    else if(rssi <= 122)
+        return LevelRssi122_100;
+
+    return LevelRssiError;
+}
+
+static inline void update_bt_timeout(Bt* bt) {
+    LevelRssiRange r = bt_remote_rssi_range(bt);
+    if(r < LevelRssiNum) {
+        bt_timeout = bt_hid_delays[r];
+        FURI_LOG_D(WORKER_TAG, "BLE Key timeout : %u", bt_timeout);
+    }
+}
+
+typedef enum {
+    WorkerEvtStartStop = (1 << 0),
+    WorkerEvtPauseResume = (1 << 1),
+    WorkerEvtEnd = (1 << 2),
+    WorkerEvtConnect = (1 << 3),
+    WorkerEvtDisconnect = (1 << 4),
+} WorkerEvtFlags;
+
+static const char ducky_cmd_id[] = {"ID"};
+static const char ducky_cmd_bt_id[] = {"BT_ID"};
+
+static const uint8_t numpad_keys[10] = {
+    HID_KEYPAD_0,
+    HID_KEYPAD_1,
+    HID_KEYPAD_2,
+    HID_KEYPAD_3,
+    HID_KEYPAD_4,
+    HID_KEYPAD_5,
+    HID_KEYPAD_6,
+    HID_KEYPAD_7,
+    HID_KEYPAD_8,
+    HID_KEYPAD_9,
+};
+
+uint32_t ducky_get_command_len(const char* line) {
+    uint32_t len = strlen(line);
+    for(uint32_t i = 0; i < len; i++) {
+        if(line[i] == ' ') return i;
+    }
+    return 0;
+}
+
+bool ducky_is_line_end(const char chr) {
+    return ((chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'));
+}
+
+uint16_t ducky_get_keycode(BadBtScript* bad_bt, const char* param, bool accept_chars) {
+    uint16_t keycode = ducky_get_keycode_by_name(param);
+    if(keycode != HID_KEYBOARD_NONE) {
+        return keycode;
+    }
+
+    if((accept_chars) && (strlen(param) > 0)) {
+        return (BADBT_ASCII_TO_KEY(bad_bt, param[0]) & 0xFF);
+    }
+    return 0;
+}
+
+bool ducky_get_number(const char* param, uint32_t* val) {
+    uint32_t value = 0;
+    if(sscanf(param, "%lu", &value) == 1) {
+        *val = value;
+        return true;
+    }
+    return false;
+}
+
+void ducky_numlock_on(BadBtScript* bad_bt) {
+    UNUSED(bad_bt);
+    if((furi_hal_bt_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
+        furi_hal_bt_hid_kb_press(HID_KEYBOARD_LOCK_NUM_LOCK);
+        furi_delay_ms(bt_timeout);
+        furi_hal_bt_hid_kb_release(HID_KEYBOARD_LOCK_NUM_LOCK);
+    }
+}
+
+bool ducky_numpad_press(BadBtScript* bad_bt, const char num) {
+    UNUSED(bad_bt);
+    if((num < '0') || (num > '9')) return false;
+
+    uint16_t key = numpad_keys[num - '0'];
+    furi_hal_bt_hid_kb_press(key);
+    furi_delay_ms(bt_timeout);
+    furi_hal_bt_hid_kb_release(key);
+
+    return true;
+}
+
+bool ducky_altchar(BadBtScript* bad_bt, const char* charcode) {
+    uint8_t i = 0;
+    bool state = false;
+
+    furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT);
+
+    while(!ducky_is_line_end(charcode[i])) {
+        state = ducky_numpad_press(bad_bt, charcode[i]);
+        if(state == false) break;
+        i++;
+    }
+
+    furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT);
+
+    return state;
+}
+
+bool ducky_altstring(BadBtScript* bad_bt, const char* param) {
+    uint32_t i = 0;
+    bool state = false;
+
+    while(param[i] != '\0') {
+        if((param[i] < ' ') || (param[i] > '~')) {
+            i++;
+            continue; // Skip non-printable chars
+        }
+
+        char temp_str[4];
+        snprintf(temp_str, 4, "%u", param[i]);
+
+        state = ducky_altchar(bad_bt, temp_str);
+        if(state == false) break;
+        i++;
+    }
+    return state;
+}
+
+int32_t ducky_error(BadBtScript* bad_bt, const char* text, ...) {
+    va_list args;
+    va_start(args, text);
+
+    vsnprintf(bad_bt->st.error, sizeof(bad_bt->st.error), text, args);
+
+    va_end(args);
+    return SCRIPT_STATE_ERROR;
+}
+
+bool ducky_string(BadBtScript* bad_bt, const char* param) {
+    uint32_t i = 0;
+
+    while(param[i] != '\0') {
+        if(param[i] != '\n') {
+            uint16_t keycode = BADBT_ASCII_TO_KEY(bad_bt, param[i]);
+            if(keycode != HID_KEYBOARD_NONE) {
+                furi_hal_bt_hid_kb_press(keycode);
+                furi_delay_ms(bt_timeout);
+                furi_hal_bt_hid_kb_release(keycode);
+            }
+        } else {
+            furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN);
+            furi_delay_ms(bt_timeout);
+            furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN);
+        }
+        i++;
+    }
+    bad_bt->stringdelay = 0;
+    return true;
+}
+
+static bool ducky_string_next(BadBtScript* bad_bt) {
+    if(bad_bt->string_print_pos >= furi_string_size(bad_bt->string_print)) {
+        return true;
+    }
+
+    char print_char = furi_string_get_char(bad_bt->string_print, bad_bt->string_print_pos);
+
+    if(print_char != '\n') {
+        uint16_t keycode = BADBT_ASCII_TO_KEY(bad_bt, print_char);
+        if(keycode != HID_KEYBOARD_NONE) {
+            furi_hal_bt_hid_kb_press(keycode);
+            furi_delay_ms(bt_timeout);
+            furi_hal_bt_hid_kb_release(keycode);
+        }
+    } else {
+        furi_hal_bt_hid_kb_press(HID_KEYBOARD_RETURN);
+        furi_delay_ms(bt_timeout);
+        furi_hal_bt_hid_kb_release(HID_KEYBOARD_RETURN);
+    }
+
+    bad_bt->string_print_pos++;
+
+    return false;
+}
+
+static int32_t ducky_parse_line(BadBtScript* bad_bt, FuriString* line) {
+    uint32_t line_len = furi_string_size(line);
+    const char* line_tmp = furi_string_get_cstr(line);
+
+    if(line_len == 0) {
+        return SCRIPT_STATE_NEXT_LINE; // Skip empty lines
+    }
+    FURI_LOG_D(WORKER_TAG, "line:%s", line_tmp);
+
+    // Ducky Lang Functions
+    int32_t cmd_result = ducky_execute_cmd(bad_bt, line_tmp);
+    if(cmd_result != SCRIPT_STATE_CMD_UNKNOWN) {
+        return cmd_result;
+    }
+
+    // Special keys + modifiers
+    uint16_t key = ducky_get_keycode(bad_bt, line_tmp, false);
+    if(key == HID_KEYBOARD_NONE) {
+        return ducky_error(bad_bt, "No keycode defined for %s", line_tmp);
+    }
+    if((key & 0xFF00) != 0) {
+        // It's a modifier key
+        uint32_t offset = ducky_get_command_len(line_tmp) + 1;
+        // ducky_get_command_len() returns 0 without space, so check for != 1
+        if(offset != 1 && line_len > offset) {
+            // It's also a key combination
+            key |= ducky_get_keycode(bad_bt, line_tmp + offset, true);
+        }
+    }
+    furi_hal_bt_hid_kb_press(key);
+    furi_delay_ms(bt_timeout);
+    furi_hal_bt_hid_kb_release(key);
+
+    return 0;
+}
+
+static bool ducky_set_bt_id(BadBtScript* bad_bt, const char* line) {
+    size_t line_len = strlen(line);
+    size_t mac_len = BAD_BT_MAC_ADDRESS_LEN * 3;
+    if(line_len < mac_len + 1) return false; // MAC + at least 1 char for name
+
+    uint8_t mac[BAD_BT_MAC_ADDRESS_LEN];
+    for(size_t i = 0; i < BAD_BT_MAC_ADDRESS_LEN; i++) {
+        char a = line[i * 3];
+        char b = line[i * 3 + 1];
+        if((a < 'A' && a > 'F') || (a < '0' && a > '9') || (b < 'A' && b > 'F') ||
+           (b < '0' && b > '9') || !hex_char_to_uint8(a, b, &mac[i])) {
+            return false;
+        }
+    }
+    furi_hal_bt_reverse_mac_addr(mac);
+
+    furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, line + mac_len);
+    bt_set_profile_mac_address(bad_bt->bt, mac);
+    return true;
+}
+
+static bool ducky_script_preload(BadBtScript* bad_bt, File* script_file) {
+    uint8_t ret = 0;
+    uint32_t line_len = 0;
+
+    furi_string_reset(bad_bt->line);
+
+    do {
+        ret = storage_file_read(script_file, bad_bt->file_buf, FILE_BUFFER_LEN);
+        for(uint16_t i = 0; i < ret; i++) {
+            if(bad_bt->file_buf[i] == '\n' && line_len > 0) {
+                bad_bt->st.line_nb++;
+                line_len = 0;
+            } else {
+                if(bad_bt->st.line_nb == 0) { // Save first line
+                    furi_string_push_back(bad_bt->line, bad_bt->file_buf[i]);
+                }
+                line_len++;
+            }
+        }
+        if(storage_file_eof(script_file)) {
+            if(line_len > 0) {
+                bad_bt->st.line_nb++;
+                break;
+            }
+        }
+    } while(ret > 0);
+
+    const char* line_tmp = furi_string_get_cstr(bad_bt->line);
+    if(bad_bt->app->switch_mode_thread) {
+        furi_thread_join(bad_bt->app->switch_mode_thread);
+        furi_thread_free(bad_bt->app->switch_mode_thread);
+        bad_bt->app->switch_mode_thread = NULL;
+    }
+    // Looking for ID or BT_ID command at first line
+    bad_bt->set_usb_id = false;
+    bad_bt->set_bt_id = false;
+    bad_bt->has_usb_id = strncmp(line_tmp, ducky_cmd_id, strlen(ducky_cmd_id)) == 0;
+    // TODO: We setting has_usb_id to its value but ignoring it for now and not using anywhere here, may be used in a future to detect script type
+    bad_bt->has_bt_id = strncmp(line_tmp, ducky_cmd_bt_id, strlen(ducky_cmd_bt_id)) == 0;
+    if(bad_bt->has_bt_id) {
+        if(!bad_bt->app->bt_remember) {
+            bad_bt->set_bt_id = ducky_set_bt_id(bad_bt, &line_tmp[strlen(ducky_cmd_bt_id) + 1]);
+        }
+    }
+
+    bad_kb_config_refresh_menu(bad_bt->app);
+
+    if(!bad_bt->set_bt_id) {
+        const char* bt_name = bad_bt->app->config.bt_name;
+        const uint8_t* bt_mac = bad_bt->app->bt_remember ? (uint8_t*)&BAD_BT_BOUND_MAC_ADDRESS :
+                                                           bad_bt->app->config.bt_mac;
+        bool reset_name = strncmp(
+            bt_name,
+            furi_hal_bt_get_profile_adv_name(FuriHalBtProfileHidKeyboard),
+            BAD_BT_ADV_NAME_MAX_LEN);
+        bool reset_mac = memcmp(
+            bt_mac,
+            furi_hal_bt_get_profile_mac_addr(FuriHalBtProfileHidKeyboard),
+            BAD_BT_MAC_ADDRESS_LEN);
+        if(reset_name && reset_mac) {
+            furi_hal_bt_set_profile_adv_name(FuriHalBtProfileHidKeyboard, bt_name);
+        } else if(reset_name) {
+            bt_set_profile_adv_name(bad_bt->bt, bt_name);
+        }
+        if(reset_mac) {
+            bt_set_profile_mac_address(bad_bt->bt, bt_mac);
+        }
+    }
+
+    storage_file_seek(script_file, 0, true);
+    furi_string_reset(bad_bt->line);
+
+    return true;
+}
+
+static int32_t ducky_script_execute_next(BadBtScript* bad_bt, File* script_file) {
+    int32_t delay_val = 0;
+
+    if(bad_bt->repeat_cnt > 0) {
+        bad_bt->repeat_cnt--;
+        delay_val = ducky_parse_line(bad_bt, bad_bt->line_prev);
+        if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
+            return 0;
+        } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays
+            return delay_val;
+        } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button
+            return delay_val;
+        } else if(delay_val < 0) { // Script error
+            bad_bt->st.error_line = bad_bt->st.line_cur - 1;
+            FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_bt->st.line_cur - 1U);
+            return SCRIPT_STATE_ERROR;
+        } else {
+            return (delay_val + bad_bt->defdelay);
+        }
+    }
+
+    furi_string_set(bad_bt->line_prev, bad_bt->line);
+    furi_string_reset(bad_bt->line);
+
+    while(1) {
+        if(bad_bt->buf_len == 0) {
+            bad_bt->buf_len = storage_file_read(script_file, bad_bt->file_buf, FILE_BUFFER_LEN);
+            if(storage_file_eof(script_file)) {
+                if((bad_bt->buf_len < FILE_BUFFER_LEN) && (bad_bt->file_end == false)) {
+                    bad_bt->file_buf[bad_bt->buf_len] = '\n';
+                    bad_bt->buf_len++;
+                    bad_bt->file_end = true;
+                }
+            }
+
+            bad_bt->buf_start = 0;
+            if(bad_bt->buf_len == 0) return SCRIPT_STATE_END;
+        }
+        for(uint8_t i = bad_bt->buf_start; i < (bad_bt->buf_start + bad_bt->buf_len); i++) {
+            if(bad_bt->file_buf[i] == '\n' && furi_string_size(bad_bt->line) > 0) {
+                bad_bt->st.line_cur++;
+                bad_bt->buf_len = bad_bt->buf_len + bad_bt->buf_start - (i + 1);
+                bad_bt->buf_start = i + 1;
+                furi_string_trim(bad_bt->line);
+                delay_val = ducky_parse_line(bad_bt, bad_bt->line);
+                if(delay_val == SCRIPT_STATE_NEXT_LINE) { // Empty line
+                    return 0;
+                } else if(delay_val == SCRIPT_STATE_STRING_START) { // Print string with delays
+                    return delay_val;
+                } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // wait for button
+                    return delay_val;
+                } else if(delay_val < 0) {
+                    bad_bt->st.error_line = bad_bt->st.line_cur;
+                    FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_bt->st.line_cur);
+                    return SCRIPT_STATE_ERROR;
+                } else {
+                    return (delay_val + bad_bt->defdelay);
+                }
+            } else {
+                furi_string_push_back(bad_bt->line, bad_bt->file_buf[i]);
+            }
+        }
+        bad_bt->buf_len = 0;
+        if(bad_bt->file_end) return SCRIPT_STATE_END;
+    }
+
+    return 0;
+}
+
+static void bad_bt_bt_hid_state_callback(BtStatus status, void* context) {
+    furi_assert(context);
+    BadBtScript* bad_bt = context;
+    bool state = (status == BtStatusConnected);
+
+    if(state == true) {
+        LevelRssiRange r = bt_remote_rssi_range(bad_bt->bt);
+        if(r != LevelRssiError) {
+            bt_timeout = bt_hid_delays[r];
+        }
+        furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtConnect);
+    } else {
+        furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtDisconnect);
+    }
+}
+
+static uint32_t bad_bt_flags_get(uint32_t flags_mask, uint32_t timeout) {
+    uint32_t flags = furi_thread_flags_get();
+    furi_check((flags & FuriFlagError) == 0);
+    if(flags == 0) {
+        flags = furi_thread_flags_wait(flags_mask, FuriFlagWaitAny, timeout);
+        furi_check(((flags & FuriFlagError) == 0) || (flags == (unsigned)FuriFlagErrorTimeout));
+    } else {
+        uint32_t state = furi_thread_flags_clear(flags);
+        furi_check((state & FuriFlagError) == 0);
+    }
+    return flags;
+}
+
+static int32_t bad_bt_worker(void* context) {
+    BadBtScript* bad_bt = context;
+
+    BadBtWorkerState worker_state = BadBtStateInit;
+    int32_t delay_val = 0;
+
+    FURI_LOG_I(WORKER_TAG, "Init");
+    File* script_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
+    bad_bt->line = furi_string_alloc();
+    bad_bt->line_prev = furi_string_alloc();
+    bad_bt->string_print = furi_string_alloc();
+
+    bt_set_status_changed_callback(bad_bt->bt, bad_bt_bt_hid_state_callback, bad_bt);
+
+    while(1) {
+        if(worker_state == BadBtStateInit) { // State: initialization
+            if(storage_file_open(
+                   script_file,
+                   furi_string_get_cstr(bad_bt->file_path),
+                   FSAM_READ,
+                   FSOM_OPEN_EXISTING)) {
+                if((ducky_script_preload(bad_bt, script_file)) && (bad_bt->st.line_nb > 0)) {
+                    if(furi_hal_bt_is_connected()) {
+                        worker_state = BadBtStateIdle; // Ready to run
+                    } else {
+                        worker_state = BadBtStateNotConnected; // Not connected
+                    }
+
+                } else {
+                    worker_state = BadBtStateScriptError; // Script preload error
+                }
+            } else {
+                FURI_LOG_E(WORKER_TAG, "File open error");
+                worker_state = BadBtStateFileError; // File open error
+            }
+            bad_bt->st.state = worker_state;
+
+        } else if(worker_state == BadBtStateNotConnected) { // State: Not connected
+            uint32_t flags = bad_bt_flags_get(
+                WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop,
+                FuriWaitForever);
+
+            if(flags & WorkerEvtEnd) {
+                break;
+            } else if(flags & WorkerEvtConnect) {
+                worker_state = BadBtStateIdle; // Ready to run
+            } else if(flags & WorkerEvtStartStop) {
+                worker_state = BadBtStateWillRun; // Will run when connected
+            }
+            bad_bt->st.state = worker_state;
+
+        } else if(worker_state == BadBtStateIdle) { // State: ready to start
+            uint32_t flags = bad_bt_flags_get(
+                WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtConnect | WorkerEvtDisconnect,
+                FuriWaitForever);
+
+            if(flags & WorkerEvtEnd) {
+                break;
+            } else if(flags & WorkerEvtStartStop) { // Start executing script
+                delay_val = 0;
+                bad_bt->buf_len = 0;
+                bad_bt->st.line_cur = 0;
+                bad_bt->defdelay = 0;
+                bad_bt->stringdelay = 0;
+                bad_bt->repeat_cnt = 0;
+                bad_bt->key_hold_nb = 0;
+                bad_bt->file_end = false;
+                storage_file_seek(script_file, 0, true);
+                bad_bt_script_set_keyboard_layout(bad_bt, bad_bt->keyboard_layout);
+                worker_state = BadBtStateRunning;
+            } else if(flags & WorkerEvtDisconnect) {
+                worker_state = BadBtStateNotConnected; // Disconnected
+            }
+            bad_bt->st.state = worker_state;
+
+        } else if(worker_state == BadBtStateWillRun) { // State: start on connection
+            uint32_t flags = bad_bt_flags_get(
+                WorkerEvtEnd | WorkerEvtConnect | WorkerEvtDisconnect | WorkerEvtStartStop,
+                FuriWaitForever);
+
+            if(flags & WorkerEvtEnd) {
+                break;
+            } else if(flags & WorkerEvtConnect) { // Start executing script
+                delay_val = 0;
+                bad_bt->buf_len = 0;
+                bad_bt->st.line_cur = 0;
+                bad_bt->defdelay = 0;
+                bad_bt->stringdelay = 0;
+                bad_bt->repeat_cnt = 0;
+                bad_bt->file_end = false;
+                storage_file_seek(script_file, 0, true);
+                // extra time for PC to recognize Flipper as keyboard
+                flags = furi_thread_flags_wait(
+                    WorkerEvtEnd | WorkerEvtDisconnect | WorkerEvtStartStop,
+                    FuriFlagWaitAny | FuriFlagNoClear,
+                    1500);
+                if(flags == (unsigned)FuriFlagErrorTimeout) {
+                    // If nothing happened - start script execution
+                    worker_state = BadBtStateRunning;
+                } else if(flags & WorkerEvtStartStop) {
+                    worker_state = BadBtStateIdle;
+                    furi_thread_flags_clear(WorkerEvtStartStop);
+                }
+
+                update_bt_timeout(bad_bt->bt);
+
+                bad_bt_script_set_keyboard_layout(bad_bt, bad_bt->keyboard_layout);
+            } else if(flags & WorkerEvtStartStop) { // Cancel scheduled execution
+                worker_state = BadBtStateNotConnected;
+            }
+            bad_bt->st.state = worker_state;
+
+        } else if(worker_state == BadBtStateRunning) { // State: running
+            uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
+            uint32_t flags = furi_thread_flags_wait(
+                WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtConnect | WorkerEvtDisconnect,
+                FuriFlagWaitAny,
+                delay_cur);
+
+            delay_val -= delay_cur;
+            if(!(flags & FuriFlagError)) {
+                if(flags & WorkerEvtEnd) {
+                    break;
+                } else if(flags & WorkerEvtStartStop) {
+                    worker_state = BadBtStateIdle; // Stop executing script
+
+                    furi_hal_bt_hid_kb_release_all();
+
+                } else if(flags & WorkerEvtDisconnect) {
+                    worker_state = BadBtStateNotConnected; // Disconnected
+
+                    furi_hal_bt_hid_kb_release_all();
+                }
+                bad_bt->st.state = worker_state;
+                continue;
+            } else if(
+                (flags == (unsigned)FuriFlagErrorTimeout) ||
+                (flags == (unsigned)FuriFlagErrorResource)) {
+                if(delay_val > 0) {
+                    bad_bt->st.delay_remain--;
+                    continue;
+                }
+                bad_bt->st.state = BadBtStateRunning;
+                delay_val = ducky_script_execute_next(bad_bt, script_file);
+                if(delay_val == SCRIPT_STATE_ERROR) { // Script error
+                    delay_val = 0;
+                    worker_state = BadBtStateScriptError;
+                    bad_bt->st.state = worker_state;
+
+                    furi_hal_bt_hid_kb_release_all();
+
+                } else if(delay_val == SCRIPT_STATE_END) { // End of script
+                    delay_val = 0;
+                    worker_state = BadBtStateIdle;
+                    bad_bt->st.state = BadBtStateDone;
+
+                    furi_hal_bt_hid_kb_release_all();
+
+                    continue;
+                } else if(delay_val == SCRIPT_STATE_STRING_START) { // Start printing string with delays
+                    delay_val = bad_bt->defdelay;
+                    bad_bt->string_print_pos = 0;
+                    worker_state = BadBtStateStringDelay;
+                } else if(delay_val == SCRIPT_STATE_WAIT_FOR_BTN) { // set state to wait for user input
+                    worker_state = BadBtStateWaitForBtn;
+                    bad_bt->st.state = BadBtStateWaitForBtn; // Show long delays
+                } else if(delay_val > 1000) {
+                    bad_bt->st.state = BadBtStateDelay; // Show long delays
+                    bad_bt->st.delay_remain = delay_val / 1000;
+                }
+            } else {
+                furi_check((flags & FuriFlagError) == 0);
+            }
+        } else if(worker_state == BadBtStateWaitForBtn) { // State: Wait for button Press
+            uint16_t delay_cur = (delay_val > 1000) ? (1000) : (delay_val);
+            uint32_t flags = furi_thread_flags_wait(
+                WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect |
+                    WorkerEvtDisconnect,
+                FuriFlagWaitAny,
+                delay_cur);
+            if(!(flags & FuriFlagError)) {
+                if(flags & WorkerEvtEnd) {
+                    break;
+                } else if(flags & WorkerEvtStartStop) {
+                    delay_val = 0;
+                    worker_state = BadBtStateRunning;
+                } else if(flags & WorkerEvtDisconnect) {
+                    worker_state = BadBtStateNotConnected; // Disconnected
+                    furi_hal_hid_kb_release_all();
+                }
+                bad_bt->st.state = worker_state;
+                continue;
+            }
+        } else if(worker_state == BadBtStateStringDelay) { // State: print string with delays
+            uint32_t flags = furi_thread_flags_wait(
+                WorkerEvtEnd | WorkerEvtStartStop | WorkerEvtPauseResume | WorkerEvtConnect |
+                    WorkerEvtDisconnect,
+                FuriFlagWaitAny,
+                bad_bt->stringdelay);
+
+            if(!(flags & FuriFlagError)) {
+                if(flags & WorkerEvtEnd) {
+                    break;
+                } else if(flags & WorkerEvtStartStop) {
+                    worker_state = BadBtStateIdle; // Stop executing script
+
+                    furi_hal_bt_hid_kb_release_all();
+
+                } else if(flags & WorkerEvtDisconnect) {
+                    worker_state = BadBtStateNotConnected; // Disconnected
+
+                    furi_hal_bt_hid_kb_release_all();
+                }
+                bad_bt->st.state = worker_state;
+                continue;
+            } else if(
+                (flags == (unsigned)FuriFlagErrorTimeout) ||
+                (flags == (unsigned)FuriFlagErrorResource)) {
+                bool string_end = ducky_string_next(bad_bt);
+                if(string_end) {
+                    bad_bt->stringdelay = 0;
+                    worker_state = BadBtStateRunning;
+                }
+            } else {
+                furi_check((flags & FuriFlagError) == 0);
+            }
+        } else if(
+            (worker_state == BadBtStateFileError) ||
+            (worker_state == BadBtStateScriptError)) { // State: error
+            uint32_t flags =
+                bad_bt_flags_get(WorkerEvtEnd, FuriWaitForever); // Waiting for exit command
+
+            if(flags & WorkerEvtEnd) {
+                break;
+            }
+        }
+
+        update_bt_timeout(bad_bt->bt);
+    }
+
+    bt_set_status_changed_callback(bad_bt->bt, NULL, NULL);
+
+    storage_file_close(script_file);
+    storage_file_free(script_file);
+    furi_string_free(bad_bt->line);
+    furi_string_free(bad_bt->line_prev);
+    furi_string_free(bad_bt->string_print);
+
+    FURI_LOG_I(WORKER_TAG, "End");
+
+    return 0;
+}
+
+static void bad_bt_script_set_default_keyboard_layout(BadBtScript* bad_bt) {
+    furi_assert(bad_bt);
+    furi_string_set_str(bad_bt->keyboard_layout, "");
+    memset(bad_bt->layout, HID_KEYBOARD_NONE, sizeof(bad_bt->layout));
+    memcpy(bad_bt->layout, hid_asciimap, MIN(sizeof(hid_asciimap), sizeof(bad_bt->layout)));
+}
+
+BadBtScript* bad_bt_script_open(FuriString* file_path, Bt* bt, BadBtApp* app) {
+    furi_assert(file_path);
+
+    BadBtScript* bad_bt = malloc(sizeof(BadBtScript));
+    bad_bt->app = app;
+    bad_bt->file_path = furi_string_alloc();
+    furi_string_set(bad_bt->file_path, file_path);
+    bad_bt->keyboard_layout = furi_string_alloc();
+    bad_bt_script_set_default_keyboard_layout(bad_bt);
+
+    bad_bt->st.state = BadBtStateInit;
+    bad_bt->st.error[0] = '\0';
+
+    bad_bt->bt = bt;
+
+    bad_bt->thread = furi_thread_alloc_ex("BadBtWorker", 2048, bad_bt_worker, bad_bt);
+    furi_thread_start(bad_bt->thread);
+    return bad_bt;
+}
+
+void bad_bt_script_close(BadBtScript* bad_bt) {
+    furi_assert(bad_bt);
+    furi_record_close(RECORD_STORAGE);
+    furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtEnd);
+    furi_thread_join(bad_bt->thread);
+    furi_thread_free(bad_bt->thread);
+    furi_string_free(bad_bt->file_path);
+    furi_string_free(bad_bt->keyboard_layout);
+    free(bad_bt);
+}
+
+void bad_bt_script_set_keyboard_layout(BadBtScript* bad_bt, FuriString* layout_path) {
+    furi_assert(bad_bt);
+
+    if((bad_bt->st.state == BadBtStateRunning) || (bad_bt->st.state == BadBtStateDelay)) {
+        // do not update keyboard layout while a script is running
+        return;
+    }
+
+    File* layout_file = storage_file_alloc(furi_record_open(RECORD_STORAGE));
+    if(!furi_string_empty(layout_path)) { //-V1051
+        furi_string_set(bad_bt->keyboard_layout, layout_path);
+        if(storage_file_open(
+               layout_file, furi_string_get_cstr(layout_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
+            uint16_t layout[128];
+            if(storage_file_read(layout_file, layout, sizeof(layout)) == sizeof(layout)) {
+                memcpy(bad_bt->layout, layout, sizeof(layout));
+            }
+        }
+        storage_file_close(layout_file);
+    } else {
+        bad_bt_script_set_default_keyboard_layout(bad_bt);
+    }
+    storage_file_free(layout_file);
+}
+
+void bad_bt_script_toggle(BadBtScript* bad_bt) {
+    furi_assert(bad_bt);
+    furi_thread_flags_set(furi_thread_get_id(bad_bt->thread), WorkerEvtStartStop);
+}
+
+BadBtState* bad_bt_script_get_state(BadBtScript* bad_bt) {
+    furi_assert(bad_bt);
+    return &(bad_bt->st);
+}

+ 154 - 0
base_pack/bad_bt/helpers/ducky_script.h

@@ -0,0 +1,154 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <bt/bt_service/bt_i.h>
+
+#include <gui/view_dispatcher.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/byte_input.h>
+#include "../views/bad_bt_view.h"
+
+#define FILE_BUFFER_LEN 16
+
+typedef enum {
+    LevelRssi122_100,
+    LevelRssi99_80,
+    LevelRssi79_60,
+    LevelRssi59_40,
+    LevelRssi39_0,
+    LevelRssiNum,
+    LevelRssiError = 0xFF,
+} LevelRssiRange;
+
+extern const uint8_t bt_hid_delays[LevelRssiNum];
+
+extern uint8_t bt_timeout;
+
+typedef enum {
+    BadBtStateInit,
+    BadBtStateNotConnected,
+    BadBtStateIdle,
+    BadBtStateWillRun,
+    BadBtStateRunning,
+    BadBtStateDelay,
+    BadBtStateStringDelay,
+    BadBtStateWaitForBtn,
+    BadBtStateDone,
+    BadBtStateScriptError,
+    BadBtStateFileError,
+} BadBtWorkerState;
+
+struct BadBtState {
+    BadBtWorkerState state;
+    uint32_t pin;
+    uint16_t line_cur;
+    uint16_t line_nb;
+    uint32_t delay_remain;
+    uint16_t error_line;
+    char error[64];
+};
+
+typedef struct BadBtApp BadBtApp;
+
+typedef struct {
+    FuriHalUsbHidConfig hid_cfg;
+    FuriThread* thread;
+    BadBtState st;
+
+    FuriString* file_path;
+    FuriString* keyboard_layout;
+    uint8_t file_buf[FILE_BUFFER_LEN + 1];
+    uint8_t buf_start;
+    uint8_t buf_len;
+    bool file_end;
+
+    uint32_t defdelay;
+    uint32_t stringdelay;
+    uint16_t layout[128];
+
+    FuriString* line;
+    FuriString* line_prev;
+    uint32_t repeat_cnt;
+    uint8_t key_hold_nb;
+
+    bool set_usb_id;
+    bool set_bt_id;
+    bool has_usb_id;
+    bool has_bt_id;
+
+    FuriString* string_print;
+    size_t string_print_pos;
+
+    Bt* bt;
+    BadBtApp* app;
+} BadBtScript;
+
+BadBtScript* bad_bt_script_open(FuriString* file_path, Bt* bt, BadBtApp* app);
+
+void bad_bt_script_close(BadBtScript* bad_bt);
+
+void bad_bt_script_set_keyboard_layout(BadBtScript* bad_bt, FuriString* layout_path);
+
+void bad_bt_script_start(BadBtScript* bad_bt);
+
+void bad_bt_script_stop(BadBtScript* bad_bt);
+
+void bad_bt_script_toggle(BadBtScript* bad_bt);
+
+BadBtState* bad_bt_script_get_state(BadBtScript* bad_bt);
+
+#define BAD_BT_ADV_NAME_MAX_LEN FURI_HAL_BT_ADV_NAME_LENGTH
+#define BAD_BT_MAC_ADDRESS_LEN GAP_MAC_ADDR_SIZE
+
+// this is the MAC address used when we do not forget paired device (BOUND STATE)
+extern const uint8_t BAD_BT_BOUND_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN];
+extern const uint8_t BAD_BT_EMPTY_MAC_ADDRESS[BAD_BT_MAC_ADDRESS_LEN];
+
+typedef enum {
+    BadBtAppErrorNoFiles,
+    BadBtAppErrorCloseRpc,
+} BadBtAppError;
+
+typedef struct {
+    char bt_name[BAD_BT_ADV_NAME_MAX_LEN];
+    uint8_t bt_mac[BAD_BT_MAC_ADDRESS_LEN];
+    GapPairing bt_mode;
+} BadBtConfig;
+
+struct BadBtApp {
+    Gui* gui;
+    ViewDispatcher* view_dispatcher;
+    SceneManager* scene_manager;
+    NotificationApp* notifications;
+    DialogsApp* dialogs;
+    Widget* widget;
+    VariableItemList* var_item_list;
+    TextInput* text_input;
+    ByteInput* byte_input;
+
+    BadBtAppError error;
+    FuriString* file_path;
+    FuriString* keyboard_layout;
+    BadBt* bad_bt_view;
+    BadBtScript* bad_bt_script;
+
+    Bt* bt;
+    bool bt_remember;
+    BadBtConfig config;
+    BadBtConfig prev_config;
+    FuriThread* conn_init_thread;
+    FuriThread* switch_mode_thread;
+};
+
+int32_t bad_bt_config_switch_mode(BadBtApp* app);
+
+#ifdef __cplusplus
+}
+#endif

+ 201 - 0
base_pack/bad_bt/helpers/ducky_script_commands.c

@@ -0,0 +1,201 @@
+#include <furi_hal.h>
+#include <furi_hal_bt_hid.h>
+#include "ducky_script.h"
+#include "ducky_script_i.h"
+
+typedef int32_t (*DuckyCmdCallback)(BadBtScript* bad_bt, const char* line, int32_t param);
+
+typedef struct {
+    char* name;
+    DuckyCmdCallback callback;
+    int32_t param;
+} DuckyCmd;
+
+static int32_t ducky_fnc_delay(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint32_t delay_val = 0;
+    bool state = ducky_get_number(line, &delay_val);
+    if((state) && (delay_val > 0)) {
+        return (int32_t)delay_val;
+    }
+
+    return ducky_error(bad_bt, "Invalid number %s", line);
+}
+
+static int32_t ducky_fnc_defdelay(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    bool state = ducky_get_number(line, &bad_bt->defdelay);
+    if(!state) {
+        return ducky_error(bad_bt, "Invalid number %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_strdelay(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    bool state = ducky_get_number(line, &bad_bt->stringdelay);
+    if(!state) {
+        return ducky_error(bad_bt, "Invalid number %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_string(BadBtScript* bad_bt, const char* line, int32_t param) {
+    line = &line[ducky_get_command_len(line) + 1];
+    furi_string_set_str(bad_bt->string_print, line);
+    if(param == 1) {
+        furi_string_cat(bad_bt->string_print, "\n");
+    }
+
+    if(bad_bt->stringdelay == 0) { // stringdelay not set - run command immidiately
+        bool state = ducky_string(bad_bt, furi_string_get_cstr(bad_bt->string_print));
+        if(!state) {
+            return ducky_error(bad_bt, "Invalid string %s", line);
+        }
+    } else { // stringdelay is set - run command in thread to keep handling external events
+        return SCRIPT_STATE_STRING_START;
+    }
+
+    return 0;
+}
+
+static int32_t ducky_fnc_repeat(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    bool state = ducky_get_number(line, &bad_bt->repeat_cnt);
+    if((!state) || (bad_bt->repeat_cnt == 0)) {
+        return ducky_error(bad_bt, "Invalid number %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_sysrq(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint16_t key = ducky_get_keycode(bad_bt, line, true);
+
+    furi_hal_bt_hid_kb_press(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
+    furi_hal_bt_hid_kb_press(key);
+    furi_delay_ms(bt_timeout);
+    furi_hal_bt_hid_kb_release(key);
+    furi_hal_bt_hid_kb_release(KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN);
+    return 0;
+}
+
+static int32_t ducky_fnc_altchar(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    ducky_numlock_on(bad_bt);
+    bool state = ducky_altchar(bad_bt, line);
+    if(!state) {
+        return ducky_error(bad_bt, "Invalid altchar %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_altstring(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    ducky_numlock_on(bad_bt);
+    bool state = ducky_altstring(bad_bt, line);
+    if(!state) {
+        return ducky_error(bad_bt, "Invalid altstring %s", line);
+    }
+    return 0;
+}
+
+static int32_t ducky_fnc_hold(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint16_t key = ducky_get_keycode(bad_bt, line, true);
+    if(key == HID_KEYBOARD_NONE) {
+        return ducky_error(bad_bt, "No keycode defined for %s", line);
+    }
+    bad_bt->key_hold_nb++;
+    if(bad_bt->key_hold_nb > (HID_KB_MAX_KEYS - 1)) {
+        return ducky_error(bad_bt, "Too many keys are hold");
+    }
+    furi_hal_bt_hid_kb_press(key);
+
+    return 0;
+}
+
+static int32_t ducky_fnc_release(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+
+    line = &line[ducky_get_command_len(line) + 1];
+    uint16_t key = ducky_get_keycode(bad_bt, line, true);
+    if(key == HID_KEYBOARD_NONE) {
+        return ducky_error(bad_bt, "No keycode defined for %s", line);
+    }
+    if(bad_bt->key_hold_nb == 0) {
+        return ducky_error(bad_bt, "No keys are hold");
+    }
+    bad_bt->key_hold_nb--;
+    furi_hal_bt_hid_kb_release(key);
+    return 0;
+}
+
+static int32_t ducky_fnc_waitforbutton(BadBtScript* bad_bt, const char* line, int32_t param) {
+    UNUSED(param);
+    UNUSED(bad_bt);
+    UNUSED(line);
+
+    return SCRIPT_STATE_WAIT_FOR_BTN;
+}
+
+static const DuckyCmd ducky_commands[] = {
+    {"REM", NULL, -1},
+    {"ID", NULL, -1},
+    {"BT_ID", NULL, -1},
+    {"DELAY", ducky_fnc_delay, -1},
+    {"STRING", ducky_fnc_string, 0},
+    {"STRINGLN", ducky_fnc_string, 1},
+    {"DEFAULT_DELAY", ducky_fnc_defdelay, -1},
+    {"DEFAULTDELAY", ducky_fnc_defdelay, -1},
+    {"STRINGDELAY", ducky_fnc_strdelay, -1},
+    {"STRING_DELAY", ducky_fnc_strdelay, -1},
+    {"REPEAT", ducky_fnc_repeat, -1},
+    {"SYSRQ", ducky_fnc_sysrq, -1},
+    {"ALTCHAR", ducky_fnc_altchar, -1},
+    {"ALTSTRING", ducky_fnc_altstring, -1},
+    {"ALTCODE", ducky_fnc_altstring, -1},
+    {"HOLD", ducky_fnc_hold, -1},
+    {"RELEASE", ducky_fnc_release, -1},
+    {"WAIT_FOR_BUTTON_PRESS", ducky_fnc_waitforbutton, -1},
+};
+
+#define TAG "BadBT"
+#define WORKER_TAG TAG "Worker"
+
+int32_t ducky_execute_cmd(BadBtScript* bad_bt, const char* line) {
+    size_t cmd_word_len = strcspn(line, " ");
+    for(size_t i = 0; i < COUNT_OF(ducky_commands); i++) {
+        size_t cmd_compare_len = strlen(ducky_commands[i].name);
+
+        if(cmd_compare_len != cmd_word_len) {
+            continue;
+        }
+
+        if(strncmp(line, ducky_commands[i].name, cmd_compare_len) == 0) {
+            if(ducky_commands[i].callback == NULL) {
+                return 0;
+            } else {
+                return ((ducky_commands[i].callback)(bad_bt, line, ducky_commands[i].param));
+            }
+        }
+    }
+
+    return SCRIPT_STATE_CMD_UNKNOWN;
+}

+ 44 - 0
base_pack/bad_bt/helpers/ducky_script_i.h

@@ -0,0 +1,44 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <furi.h>
+#include <furi_hal.h>
+#include "ducky_script.h"
+
+#define SCRIPT_STATE_ERROR (-1)
+#define SCRIPT_STATE_END (-2)
+#define SCRIPT_STATE_NEXT_LINE (-3)
+#define SCRIPT_STATE_CMD_UNKNOWN (-4)
+#define SCRIPT_STATE_STRING_START (-5)
+#define SCRIPT_STATE_WAIT_FOR_BTN (-6)
+
+uint16_t ducky_get_keycode(BadBtScript* bad_bt, const char* param, bool accept_chars);
+
+uint32_t ducky_get_command_len(const char* line);
+
+bool ducky_is_line_end(const char chr);
+
+uint16_t ducky_get_keycode_by_name(const char* param);
+
+bool ducky_get_number(const char* param, uint32_t* val);
+
+void ducky_numlock_on(BadBtScript* bad_bt);
+
+bool ducky_numpad_press(BadBtScript* bad_bt, const char num);
+
+bool ducky_altchar(BadBtScript* bad_bt, const char* charcode);
+
+bool ducky_altstring(BadBtScript* bad_bt, const char* param);
+
+bool ducky_string(BadBtScript* bad_bt, const char* param);
+
+int32_t ducky_execute_cmd(BadBtScript* bad_bt, const char* line);
+
+int32_t ducky_error(BadBtScript* bad_bt, const char* text, ...);
+
+#ifdef __cplusplus
+}
+#endif

+ 78 - 0
base_pack/bad_bt/helpers/ducky_script_keycodes.c

@@ -0,0 +1,78 @@
+#include <furi_hal.h>
+#include "ducky_script_i.h"
+
+typedef struct {
+    char* name;
+    uint16_t keycode;
+} DuckyKey;
+
+static const DuckyKey ducky_keys[] = {
+    {"CTRL-ALT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_ALT},
+    {"CTRL-SHIFT", KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT},
+    {"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
+    {"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
+    {"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
+    {"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
+
+    {"CTRL", KEY_MOD_LEFT_CTRL},
+    {"CONTROL", KEY_MOD_LEFT_CTRL},
+    {"SHIFT", KEY_MOD_LEFT_SHIFT},
+    {"ALT", KEY_MOD_LEFT_ALT},
+    {"GUI", KEY_MOD_LEFT_GUI},
+    {"WINDOWS", KEY_MOD_LEFT_GUI},
+
+    {"DOWNARROW", HID_KEYBOARD_DOWN_ARROW},
+    {"DOWN", HID_KEYBOARD_DOWN_ARROW},
+    {"LEFTARROW", HID_KEYBOARD_LEFT_ARROW},
+    {"LEFT", HID_KEYBOARD_LEFT_ARROW},
+    {"RIGHTARROW", HID_KEYBOARD_RIGHT_ARROW},
+    {"RIGHT", HID_KEYBOARD_RIGHT_ARROW},
+    {"UPARROW", HID_KEYBOARD_UP_ARROW},
+    {"UP", HID_KEYBOARD_UP_ARROW},
+
+    {"ENTER", HID_KEYBOARD_RETURN},
+    {"BREAK", HID_KEYBOARD_PAUSE},
+    {"PAUSE", HID_KEYBOARD_PAUSE},
+    {"CAPSLOCK", HID_KEYBOARD_CAPS_LOCK},
+    {"DELETE", HID_KEYBOARD_DELETE_FORWARD},
+    {"BACKSPACE", HID_KEYBOARD_DELETE},
+    {"END", HID_KEYBOARD_END},
+    {"ESC", HID_KEYBOARD_ESCAPE},
+    {"ESCAPE", HID_KEYBOARD_ESCAPE},
+    {"HOME", HID_KEYBOARD_HOME},
+    {"INSERT", HID_KEYBOARD_INSERT},
+    {"NUMLOCK", HID_KEYPAD_NUMLOCK},
+    {"PAGEUP", HID_KEYBOARD_PAGE_UP},
+    {"PAGEDOWN", HID_KEYBOARD_PAGE_DOWN},
+    {"PRINTSCREEN", HID_KEYBOARD_PRINT_SCREEN},
+    {"SCROLLLOCK", HID_KEYBOARD_SCROLL_LOCK},
+    {"SPACE", HID_KEYBOARD_SPACEBAR},
+    {"TAB", HID_KEYBOARD_TAB},
+    {"MENU", HID_KEYBOARD_APPLICATION},
+    {"APP", HID_KEYBOARD_APPLICATION},
+
+    {"F1", HID_KEYBOARD_F1},
+    {"F2", HID_KEYBOARD_F2},
+    {"F3", HID_KEYBOARD_F3},
+    {"F4", HID_KEYBOARD_F4},
+    {"F5", HID_KEYBOARD_F5},
+    {"F6", HID_KEYBOARD_F6},
+    {"F7", HID_KEYBOARD_F7},
+    {"F8", HID_KEYBOARD_F8},
+    {"F9", HID_KEYBOARD_F9},
+    {"F10", HID_KEYBOARD_F10},
+    {"F11", HID_KEYBOARD_F11},
+    {"F12", HID_KEYBOARD_F12},
+};
+
+uint16_t ducky_get_keycode_by_name(const char* param) {
+    for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) {
+        size_t key_cmd_len = strlen(ducky_keys[i].name);
+        if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
+           (ducky_is_line_end(param[key_cmd_len]))) {
+            return ducky_keys[i].keycode;
+        }
+    }
+
+    return HID_KEYBOARD_NONE;
+}

BIN
base_pack/bad_bt/images/badbt_10px.png


+ 30 - 0
base_pack/bad_bt/scenes/bad_bt_scene.c

@@ -0,0 +1,30 @@
+#include "bad_bt_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const bad_bt_scene_on_enter_handlers[])(void*) = {
+#include "bad_bt_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_event handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
+bool (*const bad_bt_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "bad_bt_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
+void (*const bad_bt_scene_on_exit_handlers[])(void* context) = {
+#include "bad_bt_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers bad_bt_scene_handlers = {
+    .on_enter_handlers = bad_bt_scene_on_enter_handlers,
+    .on_event_handlers = bad_bt_scene_on_event_handlers,
+    .on_exit_handlers = bad_bt_scene_on_exit_handlers,
+    .scene_num = BadBtSceneNum,
+};

+ 29 - 0
base_pack/bad_bt/scenes/bad_bt_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) BadBtScene##id,
+typedef enum {
+#include "bad_bt_scene_config.h"
+    BadBtSceneNum,
+} BadBtScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers bad_bt_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "bad_bt_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_event handlers declaration
+#define ADD_SCENE(prefix, name, id) \
+    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
+#include "bad_bt_scene_config.h"
+#undef ADD_SCENE
+
+// Generate scene on_exit handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
+#include "bad_bt_scene_config.h"
+#undef ADD_SCENE

+ 104 - 0
base_pack/bad_bt/scenes/bad_bt_scene_config.c

@@ -0,0 +1,104 @@
+#include "../bad_bt_app.h"
+#include "../helpers/ducky_script.h"
+#include "furi_hal_power.h"
+
+enum VarItemListIndex {
+    VarItemListIndexKeyboardLayout,
+    VarItemListIndexBtRemember,
+    VarItemListIndexBtDeviceName,
+    VarItemListIndexBtMacAddress,
+    VarItemListIndexRandomizeBtMac,
+};
+
+void bad_bt_scene_config_bt_remember_callback(VariableItem* item) {
+    BadBtApp* bad_bt = variable_item_get_context(item);
+    bad_bt->bt_remember = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, bad_bt->bt_remember ? "ON" : "OFF");
+    view_dispatcher_send_custom_event(bad_bt->view_dispatcher, VarItemListIndexBtRemember);
+}
+
+void bad_bt_scene_config_var_item_list_callback(void* context, uint32_t index) {
+    BadBtApp* bad_bt = context;
+    view_dispatcher_send_custom_event(bad_bt->view_dispatcher, index);
+}
+
+void bad_bt_scene_config_on_enter(void* context) {
+    BadBtApp* bad_bt = context;
+    VariableItemList* var_item_list = bad_bt->var_item_list;
+    VariableItem* item;
+
+    item = variable_item_list_add(var_item_list, "Keyboard layout", 0, NULL, bad_bt);
+
+    item = variable_item_list_add(
+        var_item_list, "BT Remember", 2, bad_bt_scene_config_bt_remember_callback, bad_bt);
+    variable_item_set_current_value_index(item, bad_bt->bt_remember);
+    variable_item_set_current_value_text(item, bad_bt->bt_remember ? "ON" : "OFF");
+
+    item = variable_item_list_add(var_item_list, "BT Device Name", 0, NULL, bad_bt);
+    if(bad_bt->bad_bt_script->set_bt_id) {
+        variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset Name!");
+    }
+
+    item = variable_item_list_add(var_item_list, "BT MAC Address", 0, NULL, bad_bt);
+    if(bad_bt->bt_remember) {
+        variable_item_set_locked(item, true, "Remember\nmust be Off!");
+    } else if(bad_bt->bad_bt_script->set_bt_id) {
+        variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!");
+    }
+
+    item = variable_item_list_add(var_item_list, "Randomize BT MAC", 0, NULL, bad_bt);
+    if(bad_bt->bt_remember) {
+        variable_item_set_locked(item, true, "Remember\nmust be Off!");
+    } else if(bad_bt->bad_bt_script->set_bt_id) {
+        variable_item_set_locked(item, true, "Script has\nBT_ID cmd!\nLocked to\nset MAC!");
+    }
+
+    variable_item_list_set_enter_callback(
+        var_item_list, bad_bt_scene_config_var_item_list_callback, bad_bt);
+
+    variable_item_list_set_selected_item(
+        var_item_list, scene_manager_get_scene_state(bad_bt->scene_manager, BadBtSceneConfig));
+
+    view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfig);
+}
+
+bool bad_bt_scene_config_on_event(void* context, SceneManagerEvent event) {
+    BadBtApp* bad_bt = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        scene_manager_set_scene_state(bad_bt->scene_manager, BadBtSceneConfig, event.event);
+        consumed = true;
+        switch(event.event) {
+        case VarItemListIndexKeyboardLayout:
+            scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigLayout);
+            break;
+        case VarItemListIndexBtRemember:
+            bad_bt_config_switch_remember_mode(bad_bt);
+            scene_manager_previous_scene(bad_bt->scene_manager);
+            scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfig);
+            break;
+        case VarItemListIndexBtDeviceName:
+            scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigName);
+            break;
+        case VarItemListIndexBtMacAddress:
+            scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneConfigMac);
+            break;
+        case VarItemListIndexRandomizeBtMac:
+            furi_hal_random_fill_buf(bad_bt->config.bt_mac, BAD_BT_MAC_ADDRESS_LEN);
+            bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac);
+            break;
+        default:
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void bad_bt_scene_config_on_exit(void* context) {
+    BadBtApp* bad_bt = context;
+    VariableItemList* var_item_list = bad_bt->var_item_list;
+
+    variable_item_list_reset(var_item_list);
+}

+ 7 - 0
base_pack/bad_bt/scenes/bad_bt_scene_config.h

@@ -0,0 +1,7 @@
+ADD_SCENE(bad_bt, file_select, FileSelect)
+ADD_SCENE(bad_bt, work, Work)
+ADD_SCENE(bad_bt, error, Error)
+ADD_SCENE(bad_bt, config, Config)
+ADD_SCENE(bad_bt, config_layout, ConfigLayout)
+ADD_SCENE(bad_bt, config_name, ConfigName)
+ADD_SCENE(bad_bt, config_mac, ConfigMac)

+ 47 - 0
base_pack/bad_bt/scenes/bad_bt_scene_config_layout.c

@@ -0,0 +1,47 @@
+#include "../bad_bt_app.h"
+#include "furi_hal_power.h"
+#include <storage/storage.h>
+
+static bool bad_bt_layout_select(BadBtApp* bad_bt) {
+    furi_assert(bad_bt);
+
+    FuriString* predefined_path;
+    predefined_path = furi_string_alloc();
+    if(!furi_string_empty(bad_bt->keyboard_layout)) {
+        furi_string_set(predefined_path, bad_bt->keyboard_layout);
+    } else {
+        furi_string_set(predefined_path, BAD_BT_APP_PATH_LAYOUT_FOLDER);
+    }
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, BAD_BT_APP_LAYOUT_EXTENSION, &I_keyboard_10px);
+    browser_options.base_path = BAD_BT_APP_PATH_LAYOUT_FOLDER;
+    browser_options.skip_assets = false;
+
+    // Input events and views are managed by file_browser
+    bool res = dialog_file_browser_show(
+        bad_bt->dialogs, bad_bt->keyboard_layout, predefined_path, &browser_options);
+
+    furi_string_free(predefined_path);
+    return res;
+}
+
+void bad_bt_scene_config_layout_on_enter(void* context) {
+    BadBtApp* bad_bt = context;
+
+    if(bad_bt_layout_select(bad_bt)) {
+        bad_bt_script_set_keyboard_layout(bad_bt->bad_bt_script, bad_bt->keyboard_layout);
+    }
+    scene_manager_previous_scene(bad_bt->scene_manager);
+}
+
+bool bad_bt_scene_config_layout_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    return false;
+}
+
+void bad_bt_scene_config_layout_on_exit(void* context) {
+    UNUSED(context);
+}

+ 52 - 0
base_pack/bad_bt/scenes/bad_bt_scene_config_mac.c

@@ -0,0 +1,52 @@
+#include "../bad_bt_app.h"
+
+#define TAG "BadBtConfigMac"
+
+void bad_bt_scene_config_mac_byte_input_callback(void* context) {
+    BadBtApp* bad_bt = context;
+
+    view_dispatcher_send_custom_event(bad_bt->view_dispatcher, BadBtAppCustomEventByteInputDone);
+}
+
+void bad_bt_scene_config_mac_on_enter(void* context) {
+    BadBtApp* bad_bt = context;
+
+    furi_hal_bt_reverse_mac_addr(bad_bt->config.bt_mac);
+
+    // Setup view
+    ByteInput* byte_input = bad_bt->byte_input;
+    byte_input_set_header_text(byte_input, "Set BT MAC address");
+    byte_input_set_result_callback(
+        byte_input,
+        bad_bt_scene_config_mac_byte_input_callback,
+        NULL,
+        bad_bt,
+        bad_bt->config.bt_mac,
+        GAP_MAC_ADDR_SIZE);
+    view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfigMac);
+}
+
+bool bad_bt_scene_config_mac_on_event(void* context, SceneManagerEvent event) {
+    BadBtApp* bad_bt = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == BadBtAppCustomEventByteInputDone) {
+            scene_manager_previous_scene(bad_bt->scene_manager);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void bad_bt_scene_config_mac_on_exit(void* context) {
+    BadBtApp* bad_bt = context;
+
+    furi_hal_bt_reverse_mac_addr(bad_bt->config.bt_mac);
+
+    bt_set_profile_mac_address(bad_bt->bt, bad_bt->config.bt_mac);
+
+    // Clear view
+    byte_input_set_result_callback(bad_bt->byte_input, NULL, NULL, NULL, NULL, 0);
+    byte_input_set_header_text(bad_bt->byte_input, "");
+}

+ 45 - 0
base_pack/bad_bt/scenes/bad_bt_scene_config_name.c

@@ -0,0 +1,45 @@
+#include "../bad_bt_app.h"
+
+static void bad_bt_scene_config_name_text_input_callback(void* context) {
+    BadBtApp* bad_bt = context;
+
+    view_dispatcher_send_custom_event(bad_bt->view_dispatcher, BadBtAppCustomEventTextEditResult);
+}
+
+void bad_bt_scene_config_name_on_enter(void* context) {
+    BadBtApp* bad_bt = context;
+    TextInput* text_input = bad_bt->text_input;
+
+    text_input_set_header_text(text_input, "Set BT device name");
+
+    text_input_set_result_callback(
+        text_input,
+        bad_bt_scene_config_name_text_input_callback,
+        bad_bt,
+        bad_bt->config.bt_name,
+        BAD_BT_ADV_NAME_MAX_LEN,
+        true);
+
+    view_dispatcher_switch_to_view(bad_bt->view_dispatcher, BadBtAppViewConfigName);
+}
+
+bool bad_bt_scene_config_name_on_event(void* context, SceneManagerEvent event) {
+    BadBtApp* bad_bt = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        consumed = true;
+        if(event.event == BadBtAppCustomEventTextEditResult) {
+            bt_set_profile_adv_name(bad_bt->bt, bad_bt->config.bt_name);
+        }
+        scene_manager_previous_scene(bad_bt->scene_manager);
+    }
+    return consumed;
+}
+
+void bad_bt_scene_config_name_on_exit(void* context) {
+    BadBtApp* bad_bt = context;
+    TextInput* text_input = bad_bt->text_input;
+
+    text_input_reset(text_input);
+}

+ 61 - 0
base_pack/bad_bt/scenes/bad_bt_scene_error.c

@@ -0,0 +1,61 @@
+#include "../bad_bt_app.h"
+
+static void
+    bad_bt_scene_error_event_callback(GuiButtonType result, InputType type, void* context) {
+    furi_assert(context);
+    BadBtApp* app = context;
+
+    if((result == GuiButtonTypeLeft) && (type == InputTypeShort)) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, BadBtCustomEventErrorBack);
+    }
+}
+
+void bad_bt_scene_error_on_enter(void* context) {
+    BadBtApp* app = context;
+
+    if(app->error == BadBtAppErrorNoFiles) {
+        widget_add_icon_element(app->widget, 0, 0, &I_SDQuestion_35x43);
+        widget_add_string_multiline_element(
+            app->widget,
+            81,
+            4,
+            AlignCenter,
+            AlignTop,
+            FontSecondary,
+            "No SD card or\napp data found.\nThis app will not\nwork without\nrequired files.");
+        widget_add_button_element(
+            app->widget, GuiButtonTypeLeft, "Back", bad_bt_scene_error_event_callback, app);
+    } else if(app->error == BadBtAppErrorCloseRpc) {
+        widget_add_icon_element(app->widget, 78, 0, &I_ActiveConnection_50x64);
+        widget_add_string_multiline_element(
+            app->widget, 3, 2, AlignLeft, AlignTop, FontPrimary, "Connection\nis active!");
+        widget_add_string_multiline_element(
+            app->widget,
+            3,
+            30,
+            AlignLeft,
+            AlignTop,
+            FontSecondary,
+            "Disconnect from\nPC or phone to\nuse this function.");
+    }
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, BadBtAppViewError);
+}
+
+bool bad_bt_scene_error_on_event(void* context, SceneManagerEvent event) {
+    BadBtApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == BadBtCustomEventErrorBack) {
+            view_dispatcher_stop(app->view_dispatcher);
+            consumed = true;
+        }
+    }
+    return consumed;
+}
+
+void bad_bt_scene_error_on_exit(void* context) {
+    BadBtApp* app = context;
+    widget_reset(app->widget);
+}

+ 49 - 0
base_pack/bad_bt/scenes/bad_bt_scene_file_select.c

@@ -0,0 +1,49 @@
+#include "../bad_bt_app.h"
+#include <furi_hal_power.h>
+#include <storage/storage.h>
+
+static bool bad_bt_file_select(BadBtApp* bad_bt) {
+    furi_assert(bad_bt);
+
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, BAD_BT_APP_SCRIPT_EXTENSION, &I_badbt_10px);
+    browser_options.base_path = BAD_BT_APP_BASE_FOLDER;
+    browser_options.skip_assets = true;
+
+    // Input events and views are managed by file_browser
+    bool res = dialog_file_browser_show(
+        bad_bt->dialogs, bad_bt->file_path, bad_bt->file_path, &browser_options);
+
+    return res;
+}
+
+void bad_bt_scene_file_select_on_enter(void* context) {
+    BadBtApp* bad_bt = context;
+
+    if(bad_bt->bad_bt_script) {
+        bad_bt_script_close(bad_bt->bad_bt_script);
+        bad_bt->bad_bt_script = NULL;
+    }
+
+    if(bad_bt_file_select(bad_bt)) {
+        bad_bt->bad_bt_script = bad_bt_script_open(bad_bt->file_path, bad_bt->bt, bad_bt);
+        bad_bt_script_set_keyboard_layout(bad_bt->bad_bt_script, bad_bt->keyboard_layout);
+
+        scene_manager_next_scene(bad_bt->scene_manager, BadBtSceneWork);
+    } else {
+        view_dispatcher_stop(bad_bt->view_dispatcher);
+    }
+}
+
+bool bad_bt_scene_file_select_on_event(void* context, SceneManagerEvent event) {
+    UNUSED(context);
+    UNUSED(event);
+    // BadBtApp* bad_bt = context;
+    return false;
+}
+
+void bad_bt_scene_file_select_on_exit(void* context) {
+    UNUSED(context);
+    // BadBtApp* bad_bt = context;
+}

+ 56 - 0
base_pack/bad_bt/scenes/bad_bt_scene_work.c

@@ -0,0 +1,56 @@
+#include "../helpers/ducky_script.h"
+#include "../bad_bt_app.h"
+#include "../views/bad_bt_view.h"
+#include <furi_hal.h>
+#include "toolbox/path.h"
+
+void bad_bt_scene_work_button_callback(InputKey key, void* context) {
+    furi_assert(context);
+    BadBtApp* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, key);
+}
+
+bool bad_bt_scene_work_on_event(void* context, SceneManagerEvent event) {
+    BadBtApp* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == InputKeyLeft) {
+            if(bad_bt_is_idle_state(app->bad_bt_view)) {
+                scene_manager_next_scene(app->scene_manager, BadBtSceneConfig);
+            }
+            consumed = true;
+        } else if(event.event == InputKeyOk) {
+            bad_bt_script_toggle(app->bad_bt_script);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        bad_bt_set_state(app->bad_bt_view, bad_bt_script_get_state(app->bad_bt_script));
+    }
+    return consumed;
+}
+
+void bad_bt_scene_work_on_enter(void* context) {
+    BadBtApp* app = context;
+
+    FuriString* file_name;
+    file_name = furi_string_alloc();
+    path_extract_filename(app->file_path, file_name, true);
+    bad_bt_set_file_name(app->bad_bt_view, furi_string_get_cstr(file_name));
+    furi_string_free(file_name);
+
+    FuriString* layout;
+    layout = furi_string_alloc();
+    path_extract_filename(app->keyboard_layout, layout, true);
+    bad_bt_set_layout(app->bad_bt_view, furi_string_get_cstr(layout));
+    furi_string_free(layout);
+
+    bad_bt_set_state(app->bad_bt_view, bad_bt_script_get_state(app->bad_bt_script));
+
+    bad_bt_set_button_callback(app->bad_bt_view, bad_bt_scene_work_button_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, BadBtAppViewWork);
+}
+
+void bad_bt_scene_work_on_exit(void* context) {
+    UNUSED(context);
+}

+ 233 - 0
base_pack/bad_bt/views/bad_bt_view.c

@@ -0,0 +1,233 @@
+#include "bad_bt_view.h"
+#include "../helpers/ducky_script.h"
+#include "../bad_bt_app.h"
+#include <toolbox/path.h>
+#include <gui/elements.h>
+#include <assets_icons.h>
+
+#define MAX_NAME_LEN 64
+
+typedef struct {
+    char file_name[MAX_NAME_LEN];
+    char layout[MAX_NAME_LEN];
+    BadBtState state;
+    uint8_t anim_frame;
+} BadBtModel;
+
+static void bad_bt_draw_callback(Canvas* canvas, void* _model) {
+    BadBtModel* model = _model;
+
+    FuriString* disp_str;
+    disp_str = furi_string_alloc_set("(BT) ");
+    furi_string_cat_str(disp_str, model->file_name);
+    elements_string_fit_width(canvas, disp_str, 128 - 2);
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 2, 8, furi_string_get_cstr(disp_str));
+
+    if(strlen(model->layout) == 0) {
+        furi_string_set(disp_str, "(default)");
+    } else {
+        furi_string_reset(disp_str);
+        furi_string_push_back(disp_str, '(');
+        for(size_t i = 0; i < strlen(model->layout); i++)
+            furi_string_push_back(disp_str, model->layout[i]);
+        furi_string_push_back(disp_str, ')');
+    }
+    if(model->state.pin) {
+        furi_string_cat_printf(disp_str, "  PIN: %ld", model->state.pin);
+    }
+    elements_string_fit_width(canvas, disp_str, 128 - 2);
+    canvas_draw_str(
+        canvas, 2, 8 + canvas_current_font_height(canvas), furi_string_get_cstr(disp_str));
+
+    furi_string_reset(disp_str);
+
+    if((model->state.state == BadBtStateIdle) || (model->state.state == BadBtStateDone) ||
+       (model->state.state == BadBtStateNotConnected)) {
+        elements_button_center(canvas, "Run");
+        elements_button_left(canvas, "Config");
+    } else if((model->state.state == BadBtStateRunning) || (model->state.state == BadBtStateDelay)) {
+        elements_button_center(canvas, "Stop");
+    } else if(model->state.state == BadBtStateWaitForBtn) {
+        elements_button_center(canvas, "Press to continue");
+    } else if(model->state.state == BadBtStateWillRun) {
+        elements_button_center(canvas, "Cancel");
+    }
+
+    if(model->state.state == BadBtStateNotConnected) {
+        canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Connect to");
+        canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "a device");
+    } else if(model->state.state == BadBtStateWillRun) {
+        canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "Will run");
+        canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "on connect");
+    } else if(model->state.state == BadBtStateFileError) {
+        canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 127, 31, AlignRight, AlignBottom, "File");
+        canvas_draw_str_aligned(canvas, 127, 43, AlignRight, AlignBottom, "ERROR");
+    } else if(model->state.state == BadBtStateScriptError) {
+        canvas_draw_icon(canvas, 4, 26, &I_Error_18x18);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
+        canvas_set_font(canvas, FontSecondary);
+        furi_string_printf(disp_str, "line %u", model->state.error_line);
+        canvas_draw_str_aligned(
+            canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+        furi_string_reset(disp_str);
+        furi_string_set_str(disp_str, model->state.error);
+        elements_string_fit_width(canvas, disp_str, canvas_width(canvas));
+        canvas_draw_str_aligned(
+            canvas, 127, 56, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+        furi_string_reset(disp_str);
+    } else if(model->state.state == BadBtStateIdle) {
+        canvas_draw_icon(canvas, 4, 26, &I_Smile_18x18);
+        canvas_set_font(canvas, FontBigNumbers);
+        canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "0");
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
+    } else if(model->state.state == BadBtStateRunning) {
+        if(model->anim_frame == 0) {
+            canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
+        } else {
+            canvas_draw_icon(canvas, 4, 23, &I_EviSmile2_18x21);
+        }
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(
+            disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
+        canvas_draw_str_aligned(
+            canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+        furi_string_reset(disp_str);
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
+    } else if(model->state.state == BadBtStateDone) {
+        canvas_draw_icon(canvas, 4, 23, &I_EviSmile1_18x21);
+        canvas_set_font(canvas, FontBigNumbers);
+        canvas_draw_str_aligned(canvas, 114, 40, AlignRight, AlignBottom, "100");
+        furi_string_reset(disp_str);
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
+    } else if(model->state.state == BadBtStateDelay) {
+        if(model->anim_frame == 0) {
+            canvas_draw_icon(canvas, 4, 23, &I_EviWaiting1_18x21);
+        } else {
+            canvas_draw_icon(canvas, 4, 23, &I_EviWaiting2_18x21);
+        }
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(
+            disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
+        canvas_draw_str_aligned(
+            canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+        furi_string_reset(disp_str);
+        canvas_draw_icon(canvas, 117, 26, &I_Percent_10x14);
+        canvas_set_font(canvas, FontSecondary);
+        furi_string_printf(disp_str, "delay %lus", model->state.delay_remain);
+        canvas_draw_str_aligned(
+            canvas, 127, 50, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
+        furi_string_reset(disp_str);
+    } else {
+        canvas_draw_icon(canvas, 4, 26, &I_Clock_18x18);
+    }
+
+    furi_string_free(disp_str);
+}
+
+static bool bad_bt_input_callback(InputEvent* event, void* context) {
+    furi_assert(context);
+    BadBt* bad_bt = context;
+    bool consumed = false;
+
+    if(event->type == InputTypeShort) {
+        if((event->key == InputKeyLeft) || (event->key == InputKeyOk)) {
+            consumed = true;
+            furi_assert(bad_bt->callback);
+            bad_bt->callback(event->key, bad_bt->context);
+        }
+    }
+
+    return consumed;
+}
+
+BadBt* bad_bt_alloc() {
+    BadBt* bad_bt = malloc(sizeof(BadBt));
+
+    bad_bt->view = view_alloc();
+    view_allocate_model(bad_bt->view, ViewModelTypeLocking, sizeof(BadBtModel));
+    view_set_context(bad_bt->view, bad_bt);
+    view_set_draw_callback(bad_bt->view, bad_bt_draw_callback);
+    view_set_input_callback(bad_bt->view, bad_bt_input_callback);
+
+    return bad_bt;
+}
+
+void bad_bt_free(BadBt* bad_bt) {
+    furi_assert(bad_bt);
+    view_free(bad_bt->view);
+    free(bad_bt);
+}
+
+View* bad_bt_get_view(BadBt* bad_bt) {
+    furi_assert(bad_bt);
+    return bad_bt->view;
+}
+
+void bad_bt_set_button_callback(BadBt* bad_bt, BadBtButtonCallback callback, void* context) {
+    furi_assert(bad_bt);
+    furi_assert(callback);
+    with_view_model(
+        bad_bt->view,
+        BadBtModel * model,
+        {
+            UNUSED(model);
+            bad_bt->callback = callback;
+            bad_bt->context = context;
+        },
+        true);
+}
+
+void bad_bt_set_file_name(BadBt* bad_bt, const char* name) {
+    furi_assert(name);
+    with_view_model(
+        bad_bt->view, BadBtModel * model, { strlcpy(model->file_name, name, MAX_NAME_LEN); }, true);
+}
+
+void bad_bt_set_layout(BadBt* bad_bt, const char* layout) {
+    furi_assert(layout);
+    with_view_model(
+        bad_bt->view, BadBtModel * model, { strlcpy(model->layout, layout, MAX_NAME_LEN); }, true);
+}
+
+void bad_bt_set_state(BadBt* bad_bt, BadBtState* st) {
+    furi_assert(st);
+    uint32_t pin = 0;
+    if(bad_bt->context != NULL) {
+        BadBtApp* app = bad_bt->context;
+        if(app->bt != NULL) {
+            pin = app->bt->pin;
+        }
+    }
+    st->pin = pin;
+    with_view_model(
+        bad_bt->view,
+        BadBtModel * model,
+        {
+            memcpy(&(model->state), st, sizeof(BadBtState));
+            model->anim_frame ^= 1;
+        },
+        true);
+}
+
+bool bad_bt_is_idle_state(BadBt* bad_bt) {
+    bool is_idle = false;
+    with_view_model(
+        bad_bt->view,
+        BadBtModel * model,
+        {
+            if((model->state.state == BadBtStateIdle) || (model->state.state == BadBtStateDone) ||
+               (model->state.state == BadBtStateNotConnected)) {
+                is_idle = true;
+            }
+        },
+        false);
+    return is_idle;
+}

+ 29 - 0
base_pack/bad_bt/views/bad_bt_view.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/view.h>
+
+typedef void (*BadBtButtonCallback)(InputKey key, void* context);
+
+typedef struct {
+    View* view;
+    BadBtButtonCallback callback;
+    void* context;
+} BadBt;
+
+typedef struct BadBtState BadBtState;
+
+BadBt* bad_bt_alloc();
+
+void bad_bt_free(BadBt* bad_bt);
+
+View* bad_bt_get_view(BadBt* bad_bt);
+
+void bad_bt_set_button_callback(BadBt* bad_bt, BadBtButtonCallback callback, void* context);
+
+void bad_bt_set_file_name(BadBt* bad_bt, const char* name);
+
+void bad_bt_set_layout(BadBt* bad_bt, const char* layout);
+
+void bad_bt_set_state(BadBt* bad_bt, BadBtState* st);
+
+bool bad_bt_is_idle_state(BadBt* bad_bt);

+ 22 - 0
base_pack/barcode_gen/LICENSE

@@ -0,0 +1,22 @@
+
+MIT License
+
+Copyright (c) 2023 Alan Tsui
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 88 - 0
base_pack/barcode_gen/README.md

@@ -0,0 +1,88 @@
+<p align="center">
+	<h1 align="center">Barcode Generator</h1>  
+  <p align="center">
+
+A barcode generator for the Flipper Zero that supports **UPC-A**, **EAN-8**, **EAN-13**, **Code-39**, **Codabar**, and **Code-128**[1]
+</p>
+
+Note: Barcode save locations have been moved from `/barcodes` to `/apps_data/barcodes`
+
+## Table of Contents
+- [Table of Contents](#table-of-contents)
+- [Installing](#installing)
+- [Building](#building)
+- [Usage](#usage)
+  - [Creating a barcode](#creating-a-barcode)
+  - [Editing a barcode](#editing-a-barcode)
+  - [Deleting a barcode](#deleting-a-barcode)
+  - [Viewing a barcode](#viewing-a-barcode)
+- [Screenshots](#screenshots)
+- [Credits](#credits)
+
+
+## Installing
+1) Download the `.zip` file from the release section
+2) Extract/unzip the `.zip` file onto your computer
+3) Open qFlipper and go to the file manager
+4) Navigate to the `apps` folder
+5) Drag & drop the `.fap` file into the `apps` folder
+6) Navigate back to the root folder of the SD card and create the folder `apps_data`, if not already there
+7) Navigate into `apps_data` and create another folder called `barcode_data`
+8) Navigate into `barcode_data`
+9) Drag & drop the encoding txts (`code39_encodings.txt`, `code128_encodings.txt` & `codabar_encodings.txt`) into the `barcode_data` folder
+
+## Building
+1) Clone the [flipperzero-firmware](https://github.com/flipperdevices/flipperzero-firmware) repository or a firmware of your choice
+2) Clone this repository and put it in the `applications_user` folder
+3) Build this app by using the command `./fbt fap_Barcode_App`
+4) Copy the `.fap` from `build\f7-firmware-D\.extapps\Barcode_App.fap` to `apps\Misc` using the qFlipper app
+5) While still in the qFlipper app, navigate to the root folder of the SD card and create the folder `apps_data`, if not already there
+6) Navigate into `apps_data` and create another folder called `barcode_data`
+7) Navigate into `barcode_data`
+8) Drag & drop the encoding txts (`code39_encodings.txt`, `code128_encodings.txt` & `codabar_encodings.txt`) from the `encoding_tables` folder in this repository into the `barcode_data` folder
+
+## Usage
+
+### Creating a barcode
+1) To create a barcode click on `Create Barcode`
+2) Next select your type using the left and right arrows
+3) Enter your filename and then your barcode data
+4) Click save
+
+**Note**: For Codabar barcodes, you must manually add the start and stop codes to the barcode data
+Start/Stop codes can be A, B, C, or D
+For example, if you wanted to represent `1234` as a barcode you will need to enter something like `A1234A`. (You can replace the letters A with either A, B, C, or D)
+
+![Codabar Data Example](screenshots/Codabar%20Data%20Example.png "Codabar Data Example")
+
+### Editing a barcode
+1) To edit a barcode click on `Edit Barcode`
+2) Next select the barcode file you want to edit
+3) Edit the type, name, or data
+4) Click save
+
+### Deleting a barcode
+1) To delete a barcode click on `Edit Barcode`
+2) Next select the barcode file you want to delete
+3) Scroll all the way to the bottom
+4) Click delete
+
+### Viewing a barcode
+1) To view a barcode click on `Load Barcode`
+2) Next select the barcode file you want to view
+
+## Screenshots
+![Barcode Create Screen](screenshots/Creating%20Barcode.png "Barcode Create Screen")
+
+![Flipper Code-128 Barcode](screenshots/Flipper%20Barcode.png "Flipper Code-128 Barcode")
+
+![Flipper Box EAN-13 Barcode](screenshots/Flipper%20Box%20Barcode.png "Flipper Box EAN-13 Barcode")
+
+## Credits
+
+- [Kingal1337](https://github.com/Kingal1337) - Developer
+- [Z0wl](https://github.com/Z0wl) - Added Code128-C Support
+- [@teeebor](https://github.com/teeebor) - Menu Code Snippet
+
+
+[1] - supports Set B (only the characters from 0-94). Also supports Set C

+ 16 - 0
base_pack/barcode_gen/application.fam

@@ -0,0 +1,16 @@
+App(
+    appid="barcode_app",
+    name="Barcode App",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="barcode_main",
+    requires=["gui", "storage"],
+    stack_size=2 * 1024,
+    fap_category="Tools",
+    fap_icon="images/barcode_10.png",
+    fap_icon_assets="images",
+    fap_file_assets="barcode_encoding_files",
+    fap_author="@Kingal1337",
+    fap_weburl="https://github.com/Kingal1337/flipper-barcode-generator",
+    fap_version="1.1",
+    fap_description="App allows you to display various barcodes on flipper screen",
+)

+ 348 - 0
base_pack/barcode_gen/barcode_app.c

@@ -0,0 +1,348 @@
+#include "barcode_app.h"
+
+#include "barcode_app_icons.h"
+
+/**
+ * Opens a file browser dialog and returns the filepath of the selected file
+ * 
+ * @param folder  the folder to view when the browser opens
+ * @param file_path a string pointer for the file_path when a file is selected, 
+ *                  file_path will be the folder path is nothing is selected
+ * @returns true if a file is selected
+*/
+static bool select_file(const char* folder, FuriString* file_path) {
+    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
+    DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(&browser_options, "", &I_barcode_10);
+    browser_options.base_path = DEFAULT_USER_BARCODES;
+    furi_string_set(file_path, folder);
+
+    bool res = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
+
+    furi_record_close(RECORD_DIALOGS);
+
+    return res;
+}
+
+/**
+ * Reads the data from a file and stores them in the FuriStrings raw_type and raw_data
+*/
+ErrorCode read_raw_data(FuriString* file_path, FuriString* raw_type, FuriString* raw_data) {
+    //Open Storage
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    ErrorCode reason = OKCode;
+
+    if(!flipper_format_file_open_existing(ff, furi_string_get_cstr(file_path))) {
+        FURI_LOG_E(TAG, "Could not open file %s", furi_string_get_cstr(file_path));
+        reason = FileOpening;
+    } else {
+        if(!flipper_format_read_string(ff, "Type", raw_type)) {
+            FURI_LOG_E(TAG, "Could not read \"Type\" string");
+            reason = InvalidFileData;
+        }
+        if(!flipper_format_read_string(ff, "Data", raw_data)) {
+            FURI_LOG_E(TAG, "Could not read \"Data\" string");
+            reason = InvalidFileData;
+        }
+    }
+
+    //Close Storage
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+
+    return reason;
+}
+
+/**
+ * Gets the file name from a file path
+ * @param file_path  the file path
+ * @param file_name  the FuriString to store the file name
+ * @param remove_extension  true if the extension should be removed, otherwise false
+*/
+bool get_file_name_from_path(FuriString* file_path, FuriString* file_name, bool remove_extension) {
+    if(file_path == NULL || file_name == NULL) {
+        return false;
+    }
+    uint32_t slash_index = furi_string_search_rchar(file_path, '/', 0);
+    if(slash_index == FURI_STRING_FAILURE || slash_index >= (furi_string_size(file_path) - 1)) {
+        return false;
+    }
+
+    furi_string_set(file_name, file_path);
+    furi_string_right(file_name, slash_index + 1);
+    if(remove_extension) {
+        uint32_t ext_index = furi_string_search_rchar(file_name, '.', 0);
+        if(ext_index != FURI_STRING_FAILURE && ext_index < (furi_string_size(file_path))) {
+            furi_string_left(file_name, ext_index);
+        }
+    }
+
+    return true;
+}
+
+/**
+ * Creates the barcode folder
+*/
+void init_folder() {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FURI_LOG_I(TAG, "Creating barcodes folder");
+    if(storage_simply_mkdir(storage, DEFAULT_USER_BARCODES)) {
+        FURI_LOG_I(TAG, "Barcodes folder successfully created!");
+    } else {
+        FURI_LOG_I(TAG, "Barcodes folder already exists.");
+    }
+    furi_record_close(RECORD_STORAGE);
+}
+
+void select_barcode_item(BarcodeApp* app) {
+    FuriString* file_path = furi_string_alloc();
+    FuriString* raw_type = furi_string_alloc();
+    FuriString* raw_data = furi_string_alloc();
+
+    //this determines if the data was read correctly or if the
+    bool loaded_success = true;
+    ErrorCode reason = OKCode;
+
+    bool file_selected = select_file(DEFAULT_USER_BARCODES, file_path);
+    if(file_selected) {
+        FURI_LOG_I(TAG, "The file selected is %s", furi_string_get_cstr(file_path));
+        Barcode* barcode = app->barcode_view;
+
+        reason = read_raw_data(file_path, raw_type, raw_data);
+        if(reason != OKCode) {
+            loaded_success = false;
+            FURI_LOG_E(TAG, "Could not read data correctly");
+        }
+
+        //Free the data from the previous barcode
+        barcode_free_model(barcode);
+
+        with_view_model(
+            barcode->view,
+            BarcodeModel * model,
+            {
+                model->file_path = furi_string_alloc_set(file_path);
+
+                model->data = malloc(sizeof(BarcodeData));
+                model->data->valid = loaded_success;
+
+                if(loaded_success) {
+                    model->data->raw_data = furi_string_alloc_set(raw_data);
+                    model->data->correct_data = furi_string_alloc();
+
+                    model->data->type_obj = get_type(raw_type);
+
+                    barcode_loader(model->data);
+                } else {
+                    model->data->reason = reason;
+                }
+            },
+            true);
+
+        view_dispatcher_switch_to_view(app->view_dispatcher, BarcodeView);
+    }
+
+    furi_string_free(raw_type);
+    furi_string_free(raw_data);
+    furi_string_free(file_path);
+}
+
+void edit_barcode_item(BarcodeApp* app) {
+    FuriString* file_path = furi_string_alloc();
+    FuriString* file_name = furi_string_alloc();
+    FuriString* raw_type = furi_string_alloc();
+    FuriString* raw_data = furi_string_alloc();
+
+    //this determines if the data was read correctly or if the
+    ErrorCode reason = OKCode;
+
+    bool file_selected = select_file(DEFAULT_USER_BARCODES, file_path);
+    if(file_selected) {
+        FURI_LOG_I(TAG, "The file selected is %s", furi_string_get_cstr(file_path));
+        CreateView* create_view_object = app->create_view;
+
+        reason = read_raw_data(file_path, raw_type, raw_data);
+        if(reason != OKCode) {
+            FURI_LOG_E(TAG, "Could not read data correctly");
+            with_view_model(
+                app->message_view->view,
+                MessageViewModel * model,
+                { model->message = get_error_code_message(reason); },
+                true);
+
+            view_dispatcher_switch_to_view(
+                create_view_object->barcode_app->view_dispatcher, MessageErrorView);
+
+        } else {
+            BarcodeTypeObj* type_obj = get_type(raw_type);
+            if(type_obj->type == UNKNOWN) {
+                type_obj = barcode_type_objs[0];
+            }
+            get_file_name_from_path(file_path, file_name, true);
+
+            create_view_free_model(create_view_object);
+            with_view_model(
+                create_view_object->view,
+                CreateViewModel * model,
+                {
+                    model->selected_menu_item = 0;
+                    model->barcode_type = type_obj;
+                    model->file_path = furi_string_alloc_set(file_path);
+                    model->file_name = furi_string_alloc_set(file_name);
+                    model->barcode_data = furi_string_alloc_set(raw_data);
+                    model->mode = EditMode;
+                },
+                true);
+            view_dispatcher_switch_to_view(app->view_dispatcher, CreateBarcodeView);
+        }
+    }
+
+    furi_string_free(raw_type);
+    furi_string_free(raw_data);
+    furi_string_free(file_name);
+    furi_string_free(file_path);
+}
+
+void create_barcode_item(BarcodeApp* app) {
+    CreateView* create_view_object = app->create_view;
+
+    create_view_free_model(create_view_object);
+
+    with_view_model(
+        create_view_object->view,
+        CreateViewModel * model,
+        {
+            model->selected_menu_item = 0;
+            model->barcode_type = barcode_type_objs[0];
+            model->file_path = furi_string_alloc();
+            model->file_name = furi_string_alloc();
+            model->barcode_data = furi_string_alloc();
+            model->mode = NewMode;
+        },
+        true);
+    view_dispatcher_switch_to_view(app->view_dispatcher, CreateBarcodeView);
+}
+
+void submenu_callback(void* context, uint32_t index) {
+    furi_assert(context);
+
+    BarcodeApp* app = context;
+
+    if(index == SelectBarcodeItem) {
+        select_barcode_item(app);
+    } else if(index == EditBarcodeItem) {
+        edit_barcode_item(app);
+    } else if(index == CreateBarcodeItem) {
+        create_barcode_item(app);
+    }
+}
+
+uint32_t create_view_callback(void* context) {
+    UNUSED(context);
+    return CreateBarcodeView;
+}
+
+uint32_t main_menu_callback(void* context) {
+    UNUSED(context);
+    return MainMenuView;
+}
+
+uint32_t exit_callback(void* context) {
+    UNUSED(context);
+    return VIEW_NONE;
+}
+
+void free_app(BarcodeApp* app) {
+    FURI_LOG_I(TAG, "Freeing Data");
+
+    init_folder();
+    free_types();
+
+    view_dispatcher_remove_view(app->view_dispatcher, TextInputView);
+    text_input_free(app->text_input);
+
+    view_dispatcher_remove_view(app->view_dispatcher, MessageErrorView);
+    message_view_free(app->message_view);
+
+    view_dispatcher_remove_view(app->view_dispatcher, MainMenuView);
+    submenu_free(app->main_menu);
+
+    view_dispatcher_remove_view(app->view_dispatcher, CreateBarcodeView);
+    create_view_free(app->create_view);
+
+    view_dispatcher_remove_view(app->view_dispatcher, BarcodeView);
+    barcode_free(app->barcode_view);
+
+    //free the dispatcher
+    view_dispatcher_free(app->view_dispatcher);
+
+    furi_message_queue_free(app->event_queue);
+
+    furi_record_close(RECORD_GUI);
+    app->gui = NULL;
+
+    free(app);
+}
+
+int32_t barcode_main(void* p) {
+    UNUSED(p);
+    BarcodeApp* app = malloc(sizeof(BarcodeApp));
+    init_types();
+    app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
+
+    // Register view port in GUI
+    app->gui = furi_record_open(RECORD_GUI);
+
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    app->main_menu = submenu_alloc();
+    submenu_add_item(app->main_menu, "Load Barcode", SelectBarcodeItem, submenu_callback, app);
+    view_set_previous_callback(submenu_get_view(app->main_menu), exit_callback);
+    view_dispatcher_add_view(app->view_dispatcher, MainMenuView, submenu_get_view(app->main_menu));
+
+    submenu_add_item(app->main_menu, "Edit Barcode", EditBarcodeItem, submenu_callback, app);
+
+    /*****************************
+     * Creating Text Input View
+     ******************************/
+    app->text_input = text_input_alloc();
+    view_set_previous_callback(text_input_get_view(app->text_input), create_view_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, TextInputView, text_input_get_view(app->text_input));
+
+    /*****************************
+     * Creating Message View
+     ******************************/
+    app->message_view = message_view_allocate(app);
+    view_dispatcher_add_view(
+        app->view_dispatcher, MessageErrorView, message_get_view(app->message_view));
+
+    /*****************************
+     * Creating Create View
+     ******************************/
+    app->create_view = create_view_allocate(app);
+    submenu_add_item(app->main_menu, "Create Barcode", CreateBarcodeItem, submenu_callback, app);
+    view_set_previous_callback(create_get_view(app->create_view), main_menu_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, CreateBarcodeView, create_get_view(app->create_view));
+
+    /*****************************
+     * Creating Barcode View
+     ******************************/
+    app->barcode_view = barcode_view_allocate(app);
+    view_set_previous_callback(barcode_get_view(app->barcode_view), main_menu_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, BarcodeView, barcode_get_view(app->barcode_view));
+
+    //switch view to submenu and run dispatcher
+    view_dispatcher_switch_to_view(app->view_dispatcher, MainMenuView);
+    view_dispatcher_run(app->view_dispatcher);
+
+    free_app(app);
+
+    return 0;
+}

+ 88 - 0
base_pack/barcode_gen/barcode_app.h

@@ -0,0 +1,88 @@
+#pragma once
+#include <furi.h>
+#include <furi_hal.h>
+
+#include <gui/gui.h>
+#include <input/input.h>
+#include <dialogs/dialogs.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/text_input.h>
+
+#include <flipper_format/flipper_format.h>
+
+#include "barcode_utils.h"
+
+#define TAG "BARCODE"
+#define VERSION "1.1"
+#define FILE_VERSION "1"
+
+#define TEXT_BUFFER_SIZE 128
+
+#define BARCODE_HEIGHT 50
+#define BARCODE_Y_START 3
+
+//the folder where the codabar encoding table is located
+#define CODABAR_DICT_FILE_PATH APP_ASSETS_PATH("codabar_encodings.txt")
+
+//the folder where the code 39 encoding table is located
+#define CODE39_DICT_FILE_PATH APP_ASSETS_PATH("code39_encodings.txt")
+
+//the folder where the code 128 encoding table is located
+#define CODE128_DICT_FILE_PATH APP_ASSETS_PATH("code128_encodings.txt")
+
+//the folder where the code 128 C encoding table is located
+#define CODE128C_DICT_FILE_PATH APP_ASSETS_PATH("code128c_encodings.txt")
+
+//the folder where the user stores their barcodes
+#define DEFAULT_USER_BARCODES EXT_PATH("apps_data/barcodes")
+
+//The extension barcode files use
+#define BARCODE_EXTENSION ".txt"
+#define BARCODE_EXTENSION_LENGTH 4
+
+#include "views/barcode_view.h"
+#include "views/create_view.h"
+#include "views/message_view.h"
+#include "barcode_validator.h"
+
+typedef struct BarcodeApp BarcodeApp;
+
+struct BarcodeApp {
+    Submenu* main_menu;
+    ViewDispatcher* view_dispatcher;
+    Gui* gui;
+
+    FuriMessageQueue* event_queue;
+
+    CreateView* create_view;
+    Barcode* barcode_view;
+
+    MessageView* message_view;
+    TextInput* text_input;
+};
+
+enum SubmenuItems {
+    SelectBarcodeItem,
+    EditBarcodeItem,
+
+    CreateBarcodeItem
+};
+
+enum Views {
+    TextInputView,
+    MessageErrorView,
+    MainMenuView,
+    CreateBarcodeView,
+
+    BarcodeView
+};
+
+void submenu_callback(void* context, uint32_t index);
+
+uint32_t main_menu_callback(void* context);
+
+uint32_t exit_callback(void* context);
+
+int32_t barcode_main(void* p);

+ 22 - 0
base_pack/barcode_gen/barcode_encoding_files/codabar_encodings.txt

@@ -0,0 +1,22 @@
+# alternates between bars and spaces, always begins with bar
+# 0 for narrow, 1 for wide
+0: 0000011
+1: 0000110
+2: 0001001
+3: 1100000
+4: 0010010
+5: 1000010
+6: 0100001
+7: 0100100
+8: 0110000
+9: 1001000
+-: 0001100
+$: 0011000
+:: 1000101
+/: 1010001
+.: 1010100
++: 0010101
+A: 0011010
+B: 0101001
+C: 0001011
+D: 0001110

+ 202 - 0
base_pack/barcode_gen/barcode_encoding_files/code128_encodings.txt

@@ -0,0 +1,202 @@
+ : 00
+!: 01
+": 02
+#: 03
+$: 04
+%: 05
+&: 06
+': 07
+(: 08
+): 09
+*: 10
++: 11
+,: 12
+-: 13
+.: 14
+/: 15
+0: 16
+1: 17
+2: 18
+3: 19
+4: 20
+5: 21
+6: 22
+7: 23
+8: 24
+9: 25
+:: 26
+;: 27
+<: 28
+=: 29
+>: 30
+?: 31
+@: 32
+A: 33
+B: 34
+C: 35
+D: 36
+E: 37
+F: 38
+G: 39
+H: 40
+I: 41
+J: 42
+K: 43
+L: 44
+M: 45
+N: 46
+O: 47
+P: 48
+Q: 49
+R: 50
+S: 51
+T: 52
+U: 53
+V: 54
+W: 55
+X: 56
+Y: 57
+Z: 58
+[: 59
+\: 60
+]: 61
+^: 62
+_: 63
+`: 64
+a: 65
+b: 66
+c: 67
+d: 68
+e: 69
+f: 70
+g: 71
+h: 72
+i: 73
+j: 74
+k: 75
+l: 76
+m: 77
+n: 78
+o: 79
+p: 80
+q: 81
+r: 82
+s: 83
+t: 84
+u: 85
+v: 86
+w: 87
+x: 88
+y: 89
+z: 90
+{: 91
+|: 92
+}: 93
+~: 94
+
+00: 11011001100
+01: 11001101100
+02: 11001100110
+03: 10010011000
+04: 10010001100
+05: 10001001100
+06: 10011001000
+07: 10011000100
+08: 10001100100
+09: 11001001000
+10: 11001000100
+11: 11000100100
+12: 10110011100
+13: 10011011100
+14: 10011001110
+15: 10111001100
+16: 10011101100
+17: 10011100110
+18: 11001110010
+19: 11001011100
+20: 11001001110
+21: 11011100100
+22: 11001110100
+23: 11101101110
+24: 11101001100
+25: 11100101100
+26: 11100100110
+27: 11101100100
+28: 11100110100
+29: 11100110010
+30: 11011011000
+31: 11011000110
+32: 11000110110
+33: 10100011000
+34: 10001011000
+35: 10001000110
+36: 10110001000
+37: 10001101000
+38: 10001100010
+39: 11010001000
+40: 11000101000
+41: 11000100010
+42: 10110111000
+43: 10110001110
+44: 10001101110
+45: 10111011000
+46: 10111000110
+47: 10001110110
+48: 11101110110
+49: 11010001110
+50: 11000101110
+51: 11011101000
+52: 11011100010
+53: 11011101110
+54: 11101011000
+55: 11101000110
+56: 11100010110
+57: 11101101000
+58: 11101100010
+59: 11100011010
+60: 11101111010
+61: 11001000010
+62: 11110001010
+63: 10100110000
+64: 10100001100
+65: 10010110000
+66: 10010000110
+67: 10000101100
+68: 10000100110
+69: 10110010000
+70: 10110000100
+71: 10011010000
+72: 10011000010
+73: 10000110100
+74: 10000110010
+75: 11000010010
+76: 11001010000
+77: 11110111010
+78: 11000010100
+79: 10001111010
+80: 10100111100
+81: 10010111100
+82: 10010011110
+83: 10111100100
+84: 10011110100
+85: 10011110010
+86: 11110100100
+87: 11110010100
+88: 11110010010
+89: 11011011110
+90: 11011110110
+91: 11110110110
+92: 10101111000
+93: 10100011110
+94: 10001011110
+95: 10111101000
+96: 10111100010
+97: 11110101000
+98: 11110100010
+99: 10111011110
+100: 10111101110
+101: 11101011110
+102: 11110101110
+103: 11010000100
+104: 11010010000
+105: 11010011100

+ 106 - 0
base_pack/barcode_gen/barcode_encoding_files/code128c_encodings.txt

@@ -0,0 +1,106 @@
+00: 11011001100
+01: 11001101100
+02: 11001100110
+03: 10010011000
+04: 10010001100
+05: 10001001100
+06: 10011001000
+07: 10011000100
+08: 10001100100
+09: 11001001000
+10: 11001000100
+11: 11000100100
+12: 10110011100
+13: 10011011100
+14: 10011001110
+15: 10111001100
+16: 10011101100
+17: 10011100110
+18: 11001110010
+19: 11001011100
+20: 11001001110
+21: 11011100100
+22: 11001110100
+23: 11101101110
+24: 11101001100
+25: 11100101100
+26: 11100100110
+27: 11101100100
+28: 11100110100
+29: 11100110010
+30: 11011011000
+31: 11011000110
+32: 11000110110
+33: 10100011000
+34: 10001011000
+35: 10001000110
+36: 10110001000
+37: 10001101000
+38: 10001100010
+39: 11010001000
+40: 11000101000
+41: 11000100010
+42: 10110111000
+43: 10110001110
+44: 10001101110
+45: 10111011000
+46: 10111000110
+47: 10001110110
+48: 11101110110
+49: 11010001110
+50: 11000101110
+51: 11011101000
+52: 11011100010
+53: 11011101110
+54: 11101011000
+55: 11101000110
+56: 11100010110
+57: 11101101000
+58: 11101100010
+59: 11100011010
+60: 11101111010
+61: 11001000010
+62: 11110001010
+63: 10100110000
+64: 10100001100
+65: 10010110000
+66: 10010000110
+67: 10000101100
+68: 10000100110
+69: 10110010000
+70: 10110000100
+71: 10011010000
+72: 10011000010
+73: 10000110100
+74: 10000110010
+75: 11000010010
+76: 11001010000
+77: 11110111010
+78: 11000010100
+79: 10001111010
+80: 10100111100
+81: 10010111100
+82: 10010011110
+83: 10111100100
+84: 10011110100
+85: 10011110010
+86: 11110100100
+87: 11110010100
+88: 11110010010
+89: 11011011110
+90: 11011110110
+91: 11110110110
+92: 10101111000
+93: 10100011110
+94: 10001011110
+95: 10111101000
+96: 10111100010
+97: 11110101000
+98: 11110100010
+99: 10111011110
+100: 10111101110
+101: 11101011110
+102: 11110101110
+103: 11010000100
+104: 11010010000
+105: 11010011100

+ 44 - 0
base_pack/barcode_gen/barcode_encoding_files/code39_encodings.txt

@@ -0,0 +1,44 @@
+0: 000110100
+1: 100100001
+2: 001100001
+3: 101100000
+4: 000110001
+5: 100110000
+6: 001110000
+7: 000100101
+8: 100100100
+9: 001100100
+A: 100001001
+B: 001001001
+C: 101001000
+D: 000011001
+E: 100011000
+F: 001011000
+G: 000001101
+H: 100001100
+I: 001001100
+J: 000011100
+K: 100000011
+L: 001000011
+M: 101000010
+N: 000010011
+O: 100010010
+P: 001010010
+Q: 000000111
+R: 100000110
+S: 001000110
+T: 000010110
+U: 110000001
+V: 011000001
+W: 111000000
+X: 010010001
+Y: 110010000
+Z: 011010000
+-: 010000101
+.: 110000100
+ : 011000100
+*: 010010100
+$: 010101000
+/: 010100010
++: 010001010
+%: 000101010

+ 147 - 0
base_pack/barcode_gen/barcode_utils.c

@@ -0,0 +1,147 @@
+#include "barcode_utils.h"
+
+BarcodeTypeObj* barcode_type_objs[NUMBER_OF_BARCODE_TYPES] = {NULL};
+
+void init_types() {
+    BarcodeTypeObj* upc_a = malloc(sizeof(BarcodeTypeObj));
+    upc_a->name = "UPC-A";
+    upc_a->type = UPCA;
+    upc_a->min_digits = 11;
+    upc_a->max_digits = 12;
+    upc_a->start_pos = 16;
+    barcode_type_objs[UPCA] = upc_a;
+
+    BarcodeTypeObj* ean_8 = malloc(sizeof(BarcodeTypeObj));
+    ean_8->name = "EAN-8";
+    ean_8->type = EAN8;
+    ean_8->min_digits = 7;
+    ean_8->max_digits = 8;
+    ean_8->start_pos = 32;
+    barcode_type_objs[EAN8] = ean_8;
+
+    BarcodeTypeObj* ean_13 = malloc(sizeof(BarcodeTypeObj));
+    ean_13->name = "EAN-13";
+    ean_13->type = EAN13;
+    ean_13->min_digits = 12;
+    ean_13->max_digits = 13;
+    ean_13->start_pos = 16;
+    barcode_type_objs[EAN13] = ean_13;
+
+    BarcodeTypeObj* code_39 = malloc(sizeof(BarcodeTypeObj));
+    code_39->name = "CODE-39";
+    code_39->type = CODE39;
+    code_39->min_digits = 1;
+    code_39->max_digits = -1;
+    code_39->start_pos = 0;
+    barcode_type_objs[CODE39] = code_39;
+
+    BarcodeTypeObj* code_128 = malloc(sizeof(BarcodeTypeObj));
+    code_128->name = "CODE-128";
+    code_128->type = CODE128;
+    code_128->min_digits = 1;
+    code_128->max_digits = -1;
+    code_128->start_pos = 0;
+    barcode_type_objs[CODE128] = code_128;
+
+    BarcodeTypeObj* code_128c = malloc(sizeof(BarcodeTypeObj));
+    code_128c->name = "CODE-128C";
+    code_128c->type = CODE128C;
+    code_128c->min_digits = 2;
+    code_128c->max_digits = -1;
+    code_128c->start_pos = 0;
+    barcode_type_objs[CODE128C] = code_128c;
+
+    BarcodeTypeObj* codabar = malloc(sizeof(BarcodeTypeObj));
+    codabar->name = "Codabar";
+    codabar->type = CODABAR;
+    codabar->min_digits = 1;
+    codabar->max_digits = -1;
+    codabar->start_pos = 0;
+    barcode_type_objs[CODABAR] = codabar;
+
+    BarcodeTypeObj* unknown = malloc(sizeof(BarcodeTypeObj));
+    unknown->name = "Unknown";
+    unknown->type = UNKNOWN;
+    unknown->min_digits = 0;
+    unknown->max_digits = 0;
+    unknown->start_pos = 0;
+    barcode_type_objs[UNKNOWN] = unknown;
+}
+
+void free_types() {
+    for(int i = 0; i < NUMBER_OF_BARCODE_TYPES; i++) {
+        free(barcode_type_objs[i]);
+    }
+}
+
+BarcodeTypeObj* get_type(FuriString* type_string) {
+    if(furi_string_cmp_str(type_string, "UPC-A") == 0) {
+        return barcode_type_objs[UPCA];
+    }
+    if(furi_string_cmp_str(type_string, "EAN-8") == 0) {
+        return barcode_type_objs[EAN8];
+    }
+    if(furi_string_cmp_str(type_string, "EAN-13") == 0) {
+        return barcode_type_objs[EAN13];
+    }
+    if(furi_string_cmp_str(type_string, "CODE-39") == 0) {
+        return barcode_type_objs[CODE39];
+    }
+    if(furi_string_cmp_str(type_string, "CODE-128") == 0) {
+        return barcode_type_objs[CODE128];
+    }
+    if(furi_string_cmp_str(type_string, "CODE-128C") == 0) {
+        return barcode_type_objs[CODE128C];
+    }
+    if(furi_string_cmp_str(type_string, "Codabar") == 0) {
+        return barcode_type_objs[CODABAR];
+    }
+
+    return barcode_type_objs[UNKNOWN];
+}
+
+const char* get_error_code_name(ErrorCode error_code) {
+    switch(error_code) {
+    case WrongNumberOfDigits:
+        return "Wrong Number Of Digits";
+    case InvalidCharacters:
+        return "Invalid Characters";
+    case UnsupportedType:
+        return "Unsupported Type";
+    case FileOpening:
+        return "File Opening Error";
+    case InvalidFileData:
+        return "Invalid File Data";
+    case MissingEncodingTable:
+        return "Missing Encoding Table";
+    case EncodingTableError:
+        return "Encoding Table Error";
+    case OKCode:
+        return "OK";
+    default:
+        return "Unknown Code";
+    };
+}
+
+const char* get_error_code_message(ErrorCode error_code) {
+    switch(error_code) {
+    case WrongNumberOfDigits:
+        return "Wrong # of characters";
+    case InvalidCharacters:
+        return "Invalid characters";
+    case UnsupportedType:
+        return "Unsupported barcode type";
+    case FileOpening:
+        return "Could not open file";
+    case InvalidFileData:
+        return "Invalid file data";
+    case MissingEncodingTable:
+        return "Missing encoding table";
+    case EncodingTableError:
+        return "Encoding table error";
+    case OKCode:
+        return "OK";
+    default:
+        return "Could not read barcode data";
+    };
+}

+ 55 - 0
base_pack/barcode_gen/barcode_utils.h

@@ -0,0 +1,55 @@
+
+#pragma once
+#include <furi.h>
+#include <furi_hal.h>
+
+#define NUMBER_OF_BARCODE_TYPES 8
+
+typedef enum {
+    WrongNumberOfDigits, //There is too many or too few digits in the barcode
+    InvalidCharacters, //The barcode contains invalid characters
+    UnsupportedType, //the barcode type is not supported
+    FileOpening, //A problem occurred when opening the barcode data file
+    InvalidFileData, //One of the key in the file doesn't exist or there is a typo
+    MissingEncodingTable, //The encoding table txt for the barcode type is missing
+    EncodingTableError, //Something is wrong with the encoding table, probably missing data or typo
+    OKCode
+} ErrorCode;
+
+typedef enum {
+    UPCA,
+    EAN8,
+    EAN13,
+    CODE39,
+    CODE128,
+    CODE128C,
+    CODABAR,
+
+    UNKNOWN
+} BarcodeType;
+
+typedef struct {
+    char* name; //The name of the barcode type
+    BarcodeType type; //The barcode type enum
+    int min_digits; //the minimum number of digits
+    int max_digits; //the maximum number of digits
+    int start_pos; //where to start drawing the barcode, set to -1 to dynamically draw barcode
+} BarcodeTypeObj;
+
+typedef struct {
+    BarcodeTypeObj* type_obj;
+    int check_digit; //A place to store the check digit
+    FuriString* raw_data; //the data directly from the file
+    FuriString* correct_data; //the corrected/processed data
+    bool valid; //true if the raw data is correctly formatted, such as correct num of digits, valid characters, etc.
+    ErrorCode reason; //the reason why this barcode is invalid
+} BarcodeData;
+
+//All available barcode types
+extern BarcodeTypeObj* barcode_type_objs[NUMBER_OF_BARCODE_TYPES];
+
+void init_types();
+void free_types();
+BarcodeTypeObj* get_type(FuriString* type_string);
+const char* get_error_code_name(ErrorCode error_code);
+const char* get_error_code_message(ErrorCode error_code);

+ 532 - 0
base_pack/barcode_gen/barcode_validator.c

@@ -0,0 +1,532 @@
+#include "barcode_validator.h"
+
+void barcode_loader(BarcodeData* barcode_data) {
+    switch(barcode_data->type_obj->type) {
+    case UPCA:
+    case EAN8:
+    case EAN13:
+        ean_upc_loader(barcode_data);
+        break;
+    case CODE39:
+        code_39_loader(barcode_data);
+        break;
+    case CODE128:
+        code_128_loader(barcode_data);
+        break;
+    case CODE128C:
+        code_128c_loader(barcode_data);
+        break;
+    case CODABAR:
+        codabar_loader(barcode_data);
+        break;
+    case UNKNOWN:
+        barcode_data->reason = UnsupportedType;
+        barcode_data->valid = false;
+    default:
+        break;
+    }
+}
+
+/**
+ * Calculates the check digit of a barcode if they have one
+ * @param barcode_data the barcode data
+ * @returns a check digit or -1 for either an invalid 
+*/
+int calculate_check_digit(BarcodeData* barcode_data) {
+    int check_digit = -1;
+    switch(barcode_data->type_obj->type) {
+    case UPCA:
+    case EAN8:
+    case EAN13:
+        check_digit = calculate_ean_upc_check_digit(barcode_data);
+        break;
+    case CODE39:
+    case CODE128:
+    case CODE128C:
+    case CODABAR:
+    case UNKNOWN:
+    default:
+        break;
+    }
+
+    return check_digit;
+}
+
+/**
+ * Calculates the check digit of barcode types UPC-A, EAN-8, & EAN-13
+*/
+int calculate_ean_upc_check_digit(BarcodeData* barcode_data) {
+    int check_digit = 0;
+    int odd = 0;
+    int even = 0;
+
+    int length = barcode_data->type_obj->min_digits;
+
+    //Get sum of odd digits
+    for(int i = 0; i < length; i += 2) {
+        odd += furi_string_get_char(barcode_data->raw_data, i) - '0';
+    }
+
+    //Get sum of even digits
+    for(int i = 1; i < length; i += 2) {
+        even += furi_string_get_char(barcode_data->raw_data, i) - '0';
+    }
+
+    if(barcode_data->type_obj->type == EAN13) {
+        check_digit = even * 3 + odd;
+    } else {
+        check_digit = odd * 3 + even;
+    }
+
+    check_digit = check_digit % 10;
+
+    return (10 - check_digit) % 10;
+}
+
+/**
+ * Loads and validates Barcode Types EAN-8, EAN-13, and UPC-A
+ * barcode_data and its strings should already be allocated;
+*/
+void ean_upc_loader(BarcodeData* barcode_data) {
+    int barcode_length = furi_string_size(barcode_data->raw_data);
+
+    int min_digits = barcode_data->type_obj->min_digits;
+    int max_digit = barcode_data->type_obj->max_digits;
+
+    //check the length of the barcode
+    if(barcode_length < min_digits || barcode_length > max_digit) {
+        barcode_data->reason = WrongNumberOfDigits;
+        barcode_data->valid = false;
+        return;
+    }
+
+    //checks if the barcode contains any characters that aren't a number
+    for(int i = 0; i < barcode_length; i++) {
+        char character = furi_string_get_char(barcode_data->raw_data, i);
+        int digit = character - '0'; //convert the number into an int (also the index)
+        if(digit < 0 || digit > 9) {
+            barcode_data->reason = InvalidCharacters;
+            barcode_data->valid = false;
+            return;
+        }
+    }
+
+    int check_digit = calculate_check_digit(barcode_data);
+    char check_digit_char = check_digit + '0';
+
+    barcode_data->check_digit = check_digit;
+
+    //if the barcode length is at max length then we will verify if the check digit is correct
+    if(barcode_length == max_digit) {
+        //append the raw_data to the correct data string
+        furi_string_cat(barcode_data->correct_data, barcode_data->raw_data);
+
+        //append the check digit to the correct data string
+        furi_string_set_char(barcode_data->correct_data, min_digits, check_digit_char);
+    }
+    //if the barcode length is at min length, we will calculate the check digit
+    if(barcode_length == min_digits) {
+        //append the raw_data to the correct data string
+        furi_string_cat(barcode_data->correct_data, barcode_data->raw_data);
+
+        //append the check digit to the correct data string
+        furi_string_push_back(barcode_data->correct_data, check_digit_char);
+    }
+}
+
+void code_39_loader(BarcodeData* barcode_data) {
+    int barcode_length = furi_string_size(barcode_data->raw_data);
+
+    int min_digits = barcode_data->type_obj->min_digits;
+
+    //check the length of the barcode, must contain atleast a character,
+    //this can have as many characters as it wants, it might not fit on the screen
+    if(barcode_length < min_digits) {
+        barcode_data->reason = WrongNumberOfDigits;
+        barcode_data->valid = false;
+        return;
+    }
+
+    FuriString* barcode_bits = furi_string_alloc();
+    FuriString* temp_string = furi_string_alloc();
+
+    //add starting and ending *
+    if(!furi_string_start_with(barcode_data->raw_data, "*")) {
+        furi_string_push_back(temp_string, '*');
+        furi_string_cat(temp_string, barcode_data->raw_data);
+        furi_string_set(barcode_data->raw_data, temp_string);
+    }
+
+    if(!furi_string_end_with(barcode_data->raw_data, "*")) {
+        furi_string_push_back(barcode_data->raw_data, '*');
+    }
+
+    furi_string_free(temp_string);
+    barcode_length = furi_string_size(barcode_data->raw_data);
+
+    //Open Storage
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    if(!flipper_format_file_open_existing(ff, CODE39_DICT_FILE_PATH)) {
+        FURI_LOG_E(TAG, "Could not open file %s", CODE39_DICT_FILE_PATH);
+        barcode_data->reason = MissingEncodingTable;
+        barcode_data->valid = false;
+    } else {
+        FuriString* char_bits = furi_string_alloc();
+        for(int i = 0; i < barcode_length; i++) {
+            char barcode_char = toupper(furi_string_get_char(barcode_data->raw_data, i));
+
+            //convert a char into a string so it used in flipper_format_read_string
+            char current_character[2];
+            snprintf(current_character, 2, "%c", barcode_char);
+
+            if(!flipper_format_read_string(ff, current_character, char_bits)) {
+                FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char);
+                barcode_data->reason = InvalidCharacters;
+                barcode_data->valid = false;
+                break;
+            } else {
+                FURI_LOG_I(
+                    TAG, "\"%c\" string: %s", barcode_char, furi_string_get_cstr(char_bits));
+                furi_string_cat(barcode_bits, char_bits);
+            }
+            flipper_format_rewind(ff);
+        }
+        furi_string_free(char_bits);
+    }
+
+    //Close Storage
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+
+    furi_string_cat(barcode_data->correct_data, barcode_bits);
+    furi_string_free(barcode_bits);
+}
+
+/**
+ * Loads a code 128 barcode
+ * 
+ * Only supports character set B
+*/
+void code_128_loader(BarcodeData* barcode_data) {
+    int barcode_length = furi_string_size(barcode_data->raw_data);
+
+    //the start code for character set B
+    int start_code_value = 104;
+
+    //The bits for the start code
+    const char* start_code_bits = "11010010000";
+
+    //The bits for the stop code
+    const char* stop_code_bits = "1100011101011";
+
+    int min_digits = barcode_data->type_obj->min_digits;
+
+    /**
+     * A sum of all of the characters values
+     * Ex: 
+     * Barcode Data : ABC
+     * A has a value of 33
+     * B has a value of 34
+     * C has a value of 35
+     * 
+     * the checksum_adder would be (33 * 1) + (34 * 2) + (35 * 3) + 104 = 310
+     * 
+     * Add 104 since we are using set B
+     */
+    int checksum_adder = start_code_value;
+    /**
+     * Checksum digits is the number of characters it has read so far
+     * In the above example the checksum_digits would be 3
+    */
+    int checksum_digits = 0;
+
+    //the calculated check digit
+    int final_check_digit = 0;
+
+    //check the length of the barcode, must contain atleast a character,
+    //this can have as many characters as it wants, it might not fit on the screen
+    if(barcode_length < min_digits) {
+        barcode_data->reason = WrongNumberOfDigits;
+        barcode_data->valid = false;
+        return;
+    }
+
+    //Open Storage
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    FuriString* barcode_bits = furi_string_alloc();
+
+    //add the start code
+    furi_string_cat(barcode_bits, start_code_bits);
+
+    if(!flipper_format_file_open_existing(ff, CODE128_DICT_FILE_PATH)) {
+        FURI_LOG_E(TAG, "Could not open file %s", CODE128_DICT_FILE_PATH);
+        barcode_data->reason = MissingEncodingTable;
+        barcode_data->valid = false;
+    } else {
+        FuriString* value = furi_string_alloc();
+        FuriString* char_bits = furi_string_alloc();
+        for(int i = 0; i < barcode_length; i++) {
+            char barcode_char = furi_string_get_char(barcode_data->raw_data, i);
+
+            //convert a char into a string so it used in flipper_format_read_string
+            char current_character[2];
+            snprintf(current_character, 2, "%c", barcode_char);
+
+            //get the value of the character
+            if(!flipper_format_read_string(ff, current_character, value)) {
+                FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char);
+                barcode_data->reason = InvalidCharacters;
+                barcode_data->valid = false;
+                break;
+            }
+            //using the value of the character, get the characters bits
+            if(!flipper_format_read_string(ff, furi_string_get_cstr(value), char_bits)) {
+                FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char);
+                barcode_data->reason = EncodingTableError;
+                barcode_data->valid = false;
+                break;
+            } else {
+                //add the bits to the full barcode
+                furi_string_cat(barcode_bits, char_bits);
+
+                //calculate the checksum
+                checksum_digits += 1;
+                checksum_adder += (atoi(furi_string_get_cstr(value)) * checksum_digits);
+
+                FURI_LOG_D(
+                    TAG,
+                    "\"%c\" string: %s : %s : %d : %d : %d",
+                    barcode_char,
+                    furi_string_get_cstr(char_bits),
+                    furi_string_get_cstr(value),
+                    checksum_digits,
+                    (atoi(furi_string_get_cstr(value)) * checksum_digits),
+                    checksum_adder);
+            }
+            //bring the file pointer back to the beginning
+            flipper_format_rewind(ff);
+        }
+
+        //calculate the check digit and convert it into a c string for lookup in the encoding table
+        final_check_digit = checksum_adder % 103;
+        int length = snprintf(NULL, 0, "%d", final_check_digit);
+        char* final_check_digit_string = malloc(length + 1);
+        snprintf(final_check_digit_string, length + 1, "%d", final_check_digit);
+
+        //after the checksum has been calculated, add the bits to the full barcode
+        if(!flipper_format_read_string(ff, final_check_digit_string, char_bits)) {
+            FURI_LOG_E(TAG, "Could not read \"%s\" string", final_check_digit_string);
+            barcode_data->reason = EncodingTableError;
+            barcode_data->valid = false;
+        } else {
+            //add the check digit bits to the full barcode
+            furi_string_cat(barcode_bits, char_bits);
+
+            FURI_LOG_D(
+                TAG,
+                "\"%s\" string: %s",
+                final_check_digit_string,
+                furi_string_get_cstr(char_bits));
+        }
+
+        free(final_check_digit_string);
+        furi_string_free(value);
+        furi_string_free(char_bits);
+    }
+
+    //add the stop code
+    furi_string_cat(barcode_bits, stop_code_bits);
+
+    //Close Storage
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+
+    furi_string_cat(barcode_data->correct_data, barcode_bits);
+    furi_string_free(barcode_bits);
+}
+
+/**
+ * Loads a code 128 C barcode
+*/
+void code_128c_loader(BarcodeData* barcode_data) {
+    int barcode_length = furi_string_size(barcode_data->raw_data);
+
+    //the start code for character set C
+    int start_code_value = 105;
+
+    //The bits for the start code
+    const char* start_code_bits = "11010011100";
+
+    //The bits for the stop code
+    const char* stop_code_bits = "1100011101011";
+
+    int min_digits = barcode_data->type_obj->min_digits;
+
+    int checksum_adder = start_code_value;
+    int checksum_digits = 0;
+
+    //the calculated check digit
+    int final_check_digit = 0;
+
+    // check the length of the barcode, must contain atleast 2 character,
+    // this can have as many characters as it wants, it might not fit on the screen
+    // code 128 C: the length must be even
+    if((barcode_length < min_digits) || (barcode_length & 1)) {
+        barcode_data->reason = WrongNumberOfDigits;
+        barcode_data->valid = false;
+        return;
+    }
+    //Open Storage
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    FuriString* barcode_bits = furi_string_alloc();
+
+    //add the start code
+    furi_string_cat(barcode_bits, start_code_bits);
+
+    if(!flipper_format_file_open_existing(ff, CODE128C_DICT_FILE_PATH)) {
+        FURI_LOG_E(TAG, "c128c Could not open file %s", CODE128C_DICT_FILE_PATH);
+        barcode_data->reason = MissingEncodingTable;
+        barcode_data->valid = false;
+    } else {
+        FuriString* value = furi_string_alloc();
+        FuriString* char_bits = furi_string_alloc();
+        for(int i = 0; i < barcode_length; i += 2) {
+            char barcode_char1 = furi_string_get_char(barcode_data->raw_data, i);
+            char barcode_char2 = furi_string_get_char(barcode_data->raw_data, i + 1);
+            FURI_LOG_I(TAG, "c128c bc1='%c' bc2='%c'", barcode_char1, barcode_char2);
+
+            char current_chars[4];
+            snprintf(current_chars, 3, "%c%c", barcode_char1, barcode_char2);
+            FURI_LOG_I(TAG, "c128c current_chars='%s'", current_chars);
+
+            //using the value of the characters, get the characters bits
+            if(!flipper_format_read_string(ff, current_chars, char_bits)) {
+                FURI_LOG_E(TAG, "c128c Could not read \"%s\" string", current_chars);
+                barcode_data->reason = EncodingTableError;
+                barcode_data->valid = false;
+                break;
+            } else {
+                //add the bits to the full barcode
+                furi_string_cat(barcode_bits, char_bits);
+
+                // calculate the checksum
+                checksum_digits += 1;
+                checksum_adder += (atoi(current_chars) * checksum_digits);
+
+                FURI_LOG_I(
+                    TAG,
+                    "c128c \"%s\" string: %s : %s : %d : %d : %d",
+                    current_chars,
+                    furi_string_get_cstr(char_bits),
+                    furi_string_get_cstr(value),
+                    checksum_digits,
+                    (atoi(furi_string_get_cstr(value)) * checksum_digits),
+                    checksum_adder);
+            }
+            //bring the file pointer back to the begining
+            flipper_format_rewind(ff);
+        }
+        //calculate the check digit and convert it into a c string for lookup in the encoding table
+        final_check_digit = checksum_adder % 103;
+        FURI_LOG_I(TAG, "c128c finale_check_digit=%d", final_check_digit);
+
+        int length = snprintf(NULL, 0, "%d", final_check_digit);
+        if(final_check_digit < 100) length = 2;
+        char* final_check_digit_string = malloc(length + 1);
+        snprintf(final_check_digit_string, length + 1, "%02d", final_check_digit);
+
+        //after the checksum has been calculated, add the bits to the full barcode
+        if(!flipper_format_read_string(ff, final_check_digit_string, char_bits)) {
+            FURI_LOG_E(TAG, "c128c cksum Could not read \"%s\" string", final_check_digit_string);
+            barcode_data->reason = EncodingTableError;
+            barcode_data->valid = false;
+        } else {
+            //add the check digit bits to the full barcode
+            furi_string_cat(barcode_bits, char_bits);
+
+            FURI_LOG_I(
+                TAG,
+                "check digit \"%s\" string: %s",
+                final_check_digit_string,
+                furi_string_get_cstr(char_bits));
+        }
+
+        free(final_check_digit_string);
+        furi_string_free(value);
+        furi_string_free(char_bits);
+    }
+
+    //add the stop code
+    furi_string_cat(barcode_bits, stop_code_bits);
+
+    //Close Storage
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+
+    FURI_LOG_I(TAG, "c128c %s", furi_string_get_cstr(barcode_bits));
+    furi_string_cat(barcode_data->correct_data, barcode_bits);
+    furi_string_free(barcode_bits);
+}
+
+void codabar_loader(BarcodeData* barcode_data) {
+    int barcode_length = furi_string_size(barcode_data->raw_data);
+
+    int min_digits = barcode_data->type_obj->min_digits;
+
+    //check the length of the barcode, must contain atleast a character,
+    //this can have as many characters as it wants, it might not fit on the screen
+    if(barcode_length < min_digits) {
+        barcode_data->reason = WrongNumberOfDigits;
+        barcode_data->valid = false;
+        return;
+    }
+
+    FuriString* barcode_bits = furi_string_alloc();
+
+    barcode_length = furi_string_size(barcode_data->raw_data);
+
+    //Open Storage
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    if(!flipper_format_file_open_existing(ff, CODABAR_DICT_FILE_PATH)) {
+        FURI_LOG_E(TAG, "Could not open file %s", CODABAR_DICT_FILE_PATH);
+        barcode_data->reason = MissingEncodingTable;
+        barcode_data->valid = false;
+    } else {
+        FuriString* char_bits = furi_string_alloc();
+        for(int i = 0; i < barcode_length; i++) {
+            char barcode_char = toupper(furi_string_get_char(barcode_data->raw_data, i));
+
+            //convert a char into a string so it used in flipper_format_read_string
+            char current_character[2];
+            snprintf(current_character, 2, "%c", barcode_char);
+
+            if(!flipper_format_read_string(ff, current_character, char_bits)) {
+                FURI_LOG_E(TAG, "Could not read \"%c\" string", barcode_char);
+                barcode_data->reason = InvalidCharacters;
+                barcode_data->valid = false;
+                break;
+            } else {
+                FURI_LOG_I(
+                    TAG, "\"%c\" string: %s", barcode_char, furi_string_get_cstr(char_bits));
+                furi_string_cat(barcode_bits, char_bits);
+            }
+            flipper_format_rewind(ff);
+        }
+        furi_string_free(char_bits);
+    }
+
+    //Close Storage
+    flipper_format_free(ff);
+    furi_record_close(RECORD_STORAGE);
+
+    furi_string_cat(barcode_data->correct_data, barcode_bits);
+    furi_string_free(barcode_bits);
+}

+ 15 - 0
base_pack/barcode_gen/barcode_validator.h

@@ -0,0 +1,15 @@
+#pragma once
+
+#include "barcode_app.h"
+
+int calculate_check_digit(BarcodeData* barcode_data);
+int calculate_ean_upc_check_digit(BarcodeData* barcode_data);
+void ean_upc_loader(BarcodeData* barcode_data);
+void upc_a_loader(BarcodeData* barcode_data);
+void ean_8_loader(BarcodeData* barcode_data);
+void ean_13_loader(BarcodeData* barcode_data);
+void code_39_loader(BarcodeData* barcode_data);
+void code_128_loader(BarcodeData* barcode_data);
+void code_128c_loader(BarcodeData* barcode_data);
+void codabar_loader(BarcodeData* barcode_data);
+void barcode_loader(BarcodeData* barcode_data);

+ 52 - 0
base_pack/barcode_gen/encodings.c

@@ -0,0 +1,52 @@
+#include "encodings.h"
+
+const char EAN_13_STRUCTURE_CODES[10][6] = {
+    "LLLLLL",
+    "LLGLGG",
+    "LLGGLG",
+    "LLGGGL",
+    "LGLLGG",
+    "LGGLLG",
+    "LGGGLL",
+    "LGLGLG",
+    "LGLGGL",
+    "LGGLGL"};
+
+const char UPC_EAN_L_CODES[10][8] = {
+    "0001101", // 0
+    "0011001", // 1
+    "0010011", // 2
+    "0111101", // 3
+    "0100011", // 4
+    "0110001", // 5
+    "0101111", // 6
+    "0111011", // 7
+    "0110111", // 8
+    "0001011" // 9
+};
+
+const char EAN_G_CODES[10][8] = {
+    "0100111", // 0
+    "0110011", // 1
+    "0011011", // 2
+    "0100001", // 3
+    "0011101", // 4
+    "0111001", // 5
+    "0000101", // 6
+    "0010001", // 7
+    "0001001", // 8
+    "0010111" // 9
+};
+
+const char UPC_EAN_R_CODES[10][8] = {
+    "1110010", // 0
+    "1100110", // 1
+    "1101100", // 2
+    "1000010", // 3
+    "1011100", // 4
+    "1001110", // 5
+    "1010000", // 6
+    "1000100", // 7
+    "1001000", // 8
+    "1110100" // 9
+};

+ 6 - 0
base_pack/barcode_gen/encodings.h

@@ -0,0 +1,6 @@
+#pragma once
+
+extern const char EAN_13_STRUCTURE_CODES[10][6];
+extern const char UPC_EAN_L_CODES[10][8];
+extern const char EAN_G_CODES[10][8];
+extern const char UPC_EAN_R_CODES[10][8];

BIN
base_pack/barcode_gen/images/barcode_10.png


BIN
base_pack/barcode_gen/screenshots/Codabar Data Example.png


BIN
base_pack/barcode_gen/screenshots/Creating Barcode.png


BIN
base_pack/barcode_gen/screenshots/Flipper Barcode.png


BIN
base_pack/barcode_gen/screenshots/Flipper Box Barcode.png


+ 510 - 0
base_pack/barcode_gen/views/barcode_view.c

@@ -0,0 +1,510 @@
+#include "../barcode_app.h"
+#include "barcode_view.h"
+#include "../encodings.h"
+
+/**
+ * @brief Draws a single bit from a barcode at a specified location
+ * @param canvas 
+ * @param bit  a 1 or a 0 to signify a bit of data
+ * @param x  the top left x coordinate
+ * @param y  the top left y coordinate
+ * @param width  the width of the bit
+ * @param height  the height of the bit
+ */
+static void draw_bit(Canvas* canvas, int bit, int x, int y, int width, int height) {
+    if(bit == 1) {
+        canvas_set_color(canvas, ColorBlack);
+    } else {
+        canvas_set_color(canvas, ColorWhite);
+    }
+    canvas_draw_box(canvas, x, y, width, height);
+}
+
+/**
+ * 
+*/
+static void draw_error_str(Canvas* canvas, const char* error) {
+    canvas_clear(canvas);
+    canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error);
+}
+
+/**
+ * @param bits  a string of 1's and 0's
+ * @returns the x coordinate after the bits have been drawn, useful for drawing the next section of bits
+*/
+static int draw_bits(Canvas* canvas, const char* bits, int x, int y, int width, int height) {
+    int bits_length = strlen(bits);
+    for(int i = 0; i < bits_length; i++) {
+        char c = bits[i];
+        int num = c - '0';
+
+        draw_bit(canvas, num, x, y, width, height);
+
+        x += width;
+    }
+    return x;
+}
+
+/**
+ * Draws an EAN-8 type barcode, does not check if the barcode is valid
+ * @param canvas  the canvas
+ * @param barcode_digits  the digits in the barcode, must be 8 characters long
+*/
+static void draw_ean_8(Canvas* canvas, BarcodeData* barcode_data) {
+    FuriString* barcode_digits = barcode_data->correct_data;
+    BarcodeTypeObj* type_obj = barcode_data->type_obj;
+
+    int barcode_length = furi_string_size(barcode_digits);
+
+    int x = type_obj->start_pos;
+    int y = BARCODE_Y_START;
+    int width = 1;
+    int height = BARCODE_HEIGHT;
+
+    //the guard patterns for the beginning, center, ending
+    const char* end_bits = "101";
+    const char* center_bits = "01010";
+
+    //draw the starting guard pattern
+    x = draw_bits(canvas, end_bits, x, y, width, height + 5);
+
+    FuriString* code_part = furi_string_alloc();
+
+    //loop through each digit, find the encoding, and draw it
+    for(int i = 0; i < barcode_length; i++) {
+        char current_digit = furi_string_get_char(barcode_digits, i);
+
+        //the actual number and the index of the bits
+        int index = current_digit - '0';
+        //use the L-codes for the first 4 digits and the R-Codes for the last 4 digits
+        if(i <= 3) {
+            furi_string_set_str(code_part, UPC_EAN_L_CODES[index]);
+        } else {
+            furi_string_set_str(code_part, UPC_EAN_R_CODES[index]);
+        }
+
+        //convert the current_digit char into a string so it can be printed
+        char current_digit_string[2];
+        snprintf(current_digit_string, 2, "%c", current_digit);
+
+        //set the canvas color to black to print the digit
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_str(canvas, x + 1, y + height + 8, current_digit_string);
+
+        //draw the bits of the barcode
+        x = draw_bits(canvas, furi_string_get_cstr(code_part), x, y, width, height);
+
+        //if the index has reached 3, that means 4 digits have been drawn and now draw the center guard pattern
+        if(i == 3) {
+            x = draw_bits(canvas, center_bits, x, y, width, height + 5);
+        }
+    }
+    furi_string_free(code_part);
+
+    //draw the ending guard pattern
+    x = draw_bits(canvas, end_bits, x, y, width, height + 5);
+}
+
+static void draw_ean_13(Canvas* canvas, BarcodeData* barcode_data) {
+    FuriString* barcode_digits = barcode_data->correct_data;
+    BarcodeTypeObj* type_obj = barcode_data->type_obj;
+
+    int barcode_length = furi_string_size(barcode_digits);
+
+    int x = type_obj->start_pos;
+    int y = BARCODE_Y_START;
+    int width = 1;
+    int height = BARCODE_HEIGHT;
+
+    //the guard patterns for the beginning, center, ending
+    const char* end_bits = "101";
+    const char* center_bits = "01010";
+
+    //draw the starting guard pattern
+    x = draw_bits(canvas, end_bits, x, y, width, height + 5);
+
+    FuriString* left_structure = furi_string_alloc();
+    FuriString* code_part = furi_string_alloc();
+
+    //loop through each digit, find the encoding, and draw it
+    for(int i = 0; i < barcode_length; i++) {
+        char current_digit = furi_string_get_char(barcode_digits, i);
+        int index = current_digit - '0';
+
+        if(i == 0) {
+            furi_string_set_str(left_structure, EAN_13_STRUCTURE_CODES[index]);
+
+            //convert the current_digit char into a string so it can be printed
+            char current_digit_string[2];
+            snprintf(current_digit_string, 2, "%c", current_digit);
+
+            //set the canvas color to black to print the digit
+            canvas_set_color(canvas, ColorBlack);
+            canvas_draw_str(canvas, x - 10, y + height + 8, current_digit_string);
+
+            continue;
+        } else {
+            //use the L-codes for the first 6 digits and the R-Codes for the last 6 digits
+            if(i <= 6) {
+                //get the encoding type at the current barcode bit position
+                char encoding_type = furi_string_get_char(left_structure, i - 1);
+                if(encoding_type == 'L') {
+                    furi_string_set_str(code_part, UPC_EAN_L_CODES[index]);
+                } else {
+                    furi_string_set_str(code_part, EAN_G_CODES[index]);
+                }
+            } else {
+                furi_string_set_str(code_part, UPC_EAN_R_CODES[index]);
+            }
+
+            //convert the current_digit char into a string so it can be printed
+            char current_digit_string[2];
+            snprintf(current_digit_string, 2, "%c", current_digit);
+
+            //set the canvas color to black to print the digit
+            canvas_set_color(canvas, ColorBlack);
+            canvas_draw_str(canvas, x + 1, y + height + 8, current_digit_string);
+
+            //draw the bits of the barcode
+            x = draw_bits(canvas, furi_string_get_cstr(code_part), x, y, width, height);
+
+            //if the index has reached 6, that means 6 digits have been drawn and we now draw the center guard pattern
+            if(i == 6) {
+                x = draw_bits(canvas, center_bits, x, y, width, height + 5);
+            }
+        }
+    }
+
+    furi_string_free(left_structure);
+    furi_string_free(code_part);
+
+    //draw the ending guard pattern
+    x = draw_bits(canvas, end_bits, x, y, width, height + 5);
+}
+
+/**
+ * Draw a UPC-A barcode
+*/
+static void draw_upc_a(Canvas* canvas, BarcodeData* barcode_data) {
+    FuriString* barcode_digits = barcode_data->correct_data;
+    BarcodeTypeObj* type_obj = barcode_data->type_obj;
+
+    int barcode_length = furi_string_size(barcode_digits);
+
+    int x = type_obj->start_pos;
+    int y = BARCODE_Y_START;
+    int width = 1;
+    int height = BARCODE_HEIGHT;
+
+    //the guard patterns for the beginning, center, ending
+    char* end_bits = "101";
+    char* center_bits = "01010";
+
+    //draw the starting guard pattern
+    x = draw_bits(canvas, end_bits, x, y, width, height + 5);
+
+    FuriString* code_part = furi_string_alloc();
+
+    //loop through each digit, find the encoding, and draw it
+    for(int i = 0; i < barcode_length; i++) {
+        char current_digit = furi_string_get_char(barcode_digits, i);
+        int index = current_digit - '0'; //convert the number into an int (also the index)
+
+        //use the L-codes for the first 6 digits and the R-Codes for the last 6 digits
+        if(i <= 5) {
+            furi_string_set_str(code_part, UPC_EAN_L_CODES[index]);
+        } else {
+            furi_string_set_str(code_part, UPC_EAN_R_CODES[index]);
+        }
+
+        //convert the current_digit char into a string so it can be printed
+        char current_digit_string[2];
+        snprintf(current_digit_string, 2, "%c", current_digit);
+
+        //set the canvas color to black to print the digit
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_str(canvas, x + 1, y + height + 8, current_digit_string);
+
+        //draw the bits of the barcode
+        x = draw_bits(canvas, furi_string_get_cstr(code_part), x, y, width, height);
+
+        //if the index has reached 6, that means 6 digits have been drawn and we now draw the center guard pattern
+        if(i == 5) {
+            x = draw_bits(canvas, center_bits, x, y, width, height + 5);
+        }
+    }
+
+    furi_string_free(code_part);
+
+    //draw the ending guard pattern
+    x = draw_bits(canvas, end_bits, x, y, width, height + 5);
+}
+
+static void draw_code_39(Canvas* canvas, BarcodeData* barcode_data) {
+    FuriString* raw_data = barcode_data->raw_data;
+    FuriString* barcode_digits = barcode_data->correct_data;
+    //BarcodeTypeObj* type_obj = barcode_data->type_obj;
+
+    int barcode_length = furi_string_size(barcode_digits);
+    int total_pixels = 0;
+
+    for(int i = 0; i < barcode_length; i++) {
+        //1 for wide, 0 for narrow
+        char wide_or_narrow = furi_string_get_char(barcode_digits, i);
+        int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit
+
+        if(wn_digit == 1) {
+            total_pixels += 3;
+        } else {
+            total_pixels += 1;
+        }
+        if((i + 1) % 9 == 0) {
+            total_pixels += 1;
+        }
+    }
+
+    int x = (128 - total_pixels) / 2;
+    int y = BARCODE_Y_START;
+    int width = 1;
+    int height = BARCODE_HEIGHT;
+    bool filled_in = true;
+
+    //set the canvas color to black to print the digit
+    canvas_set_color(canvas, ColorBlack);
+    // canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error);
+    canvas_draw_str_aligned(
+        canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data));
+
+    for(int i = 0; i < barcode_length; i++) {
+        //1 for wide, 0 for narrow
+        char wide_or_narrow = furi_string_get_char(barcode_digits, i);
+        int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit
+
+        if(filled_in) {
+            if(wn_digit == 1) {
+                x = draw_bits(canvas, "111", x, y, width, height);
+            } else {
+                x = draw_bits(canvas, "1", x, y, width, height);
+            }
+            filled_in = false;
+        } else {
+            if(wn_digit == 1) {
+                x = draw_bits(canvas, "000", x, y, width, height);
+            } else {
+                x = draw_bits(canvas, "0", x, y, width, height);
+            }
+            filled_in = true;
+        }
+        if((i + 1) % 9 == 0) {
+            x = draw_bits(canvas, "0", x, y, width, height);
+            filled_in = true;
+        }
+    }
+}
+
+static void draw_code_128(Canvas* canvas, BarcodeData* barcode_data) {
+    FuriString* raw_data = barcode_data->raw_data;
+    FuriString* barcode_digits = barcode_data->correct_data;
+
+    int barcode_length = furi_string_size(barcode_digits);
+
+    int x = (128 - barcode_length) / 2;
+    int y = BARCODE_Y_START;
+    int width = 1;
+    int height = BARCODE_HEIGHT;
+
+    x = draw_bits(canvas, furi_string_get_cstr(barcode_digits), x, y, width, height);
+
+    //set the canvas color to black to print the digit
+    canvas_set_color(canvas, ColorBlack);
+    // canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error);
+    canvas_draw_str_aligned(
+        canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data));
+}
+
+static void draw_codabar(Canvas* canvas, BarcodeData* barcode_data) {
+    FuriString* raw_data = barcode_data->raw_data;
+    FuriString* barcode_digits = barcode_data->correct_data;
+    //BarcodeTypeObj* type_obj = barcode_data->type_obj;
+
+    int barcode_length = furi_string_size(barcode_digits);
+    int total_pixels = 0;
+
+    for(int i = 0; i < barcode_length; i++) {
+        //1 for wide, 0 for narrow
+        char wide_or_narrow = furi_string_get_char(barcode_digits, i);
+        int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit
+
+        if(wn_digit == 1) {
+            total_pixels += 3;
+        } else {
+            total_pixels += 1;
+        }
+        if((i + 1) % 7 == 0) {
+            total_pixels += 1;
+        }
+    }
+
+    int x = (128 - total_pixels) / 2;
+    int y = BARCODE_Y_START;
+    int width = 1;
+    int height = BARCODE_HEIGHT;
+    bool filled_in = true;
+
+    //set the canvas color to black to print the digit
+    canvas_set_color(canvas, ColorBlack);
+    // canvas_draw_str_aligned(canvas, 62, 30, AlignCenter, AlignCenter, error);
+    canvas_draw_str_aligned(
+        canvas, 62, y + height + 8, AlignCenter, AlignBottom, furi_string_get_cstr(raw_data));
+
+    for(int i = 0; i < barcode_length; i++) {
+        //1 for wide, 0 for narrow
+        char wide_or_narrow = furi_string_get_char(barcode_digits, i);
+        int wn_digit = wide_or_narrow - '0'; //wide(1) or narrow(0) digit
+
+        if(filled_in) {
+            if(wn_digit == 1) {
+                x = draw_bits(canvas, "111", x, y, width, height);
+            } else {
+                x = draw_bits(canvas, "1", x, y, width, height);
+            }
+            filled_in = false;
+        } else {
+            if(wn_digit == 1) {
+                x = draw_bits(canvas, "000", x, y, width, height);
+            } else {
+                x = draw_bits(canvas, "0", x, y, width, height);
+            }
+            filled_in = true;
+        }
+        if((i + 1) % 7 == 0) {
+            x = draw_bits(canvas, "0", x, y, width, height);
+            filled_in = true;
+        }
+    }
+}
+
+static void barcode_draw_callback(Canvas* canvas, void* ctx) {
+    furi_assert(ctx);
+    BarcodeModel* barcode_model = ctx;
+    BarcodeData* data = barcode_model->data;
+    // const char* barcode_digits =;
+
+    canvas_clear(canvas);
+    if(data->valid) {
+        switch(data->type_obj->type) {
+        case UPCA:
+            draw_upc_a(canvas, data);
+            break;
+        case EAN8:
+            draw_ean_8(canvas, data);
+            break;
+        case EAN13:
+            draw_ean_13(canvas, data);
+            break;
+        case CODE39:
+            draw_code_39(canvas, data);
+            break;
+        case CODE128:
+        case CODE128C:
+            draw_code_128(canvas, data);
+            break;
+        case CODABAR:
+            draw_codabar(canvas, data);
+            break;
+        case UNKNOWN:
+        default:
+            break;
+        }
+    } else {
+        switch(data->reason) {
+        case WrongNumberOfDigits:
+            draw_error_str(canvas, "Wrong # of characters");
+            break;
+        case InvalidCharacters:
+            draw_error_str(canvas, "Invalid characters");
+            break;
+        case UnsupportedType:
+            draw_error_str(canvas, "Unsupported barcode type");
+            break;
+        case FileOpening:
+            draw_error_str(canvas, "Could not open file");
+            break;
+        case InvalidFileData:
+            draw_error_str(canvas, "Invalid file data");
+            break;
+        case MissingEncodingTable:
+            draw_error_str(canvas, "Missing encoding table");
+            break;
+        case EncodingTableError:
+            draw_error_str(canvas, "Encoding table error");
+            break;
+        default:
+            draw_error_str(canvas, "Could not read barcode data");
+            break;
+        }
+    }
+}
+
+bool barcode_input_callback(InputEvent* input_event, void* ctx) {
+    UNUSED(ctx);
+    //furi_assert(ctx);
+
+    //Barcode* test_view_object = ctx;
+
+    if(input_event->key == InputKeyBack) {
+        return false;
+    } else {
+        return true;
+    }
+}
+
+Barcode* barcode_view_allocate(BarcodeApp* barcode_app) {
+    furi_assert(barcode_app);
+
+    Barcode* barcode = malloc(sizeof(Barcode));
+
+    barcode->view = view_alloc();
+    barcode->barcode_app = barcode_app;
+
+    view_set_context(barcode->view, barcode);
+    view_allocate_model(barcode->view, ViewModelTypeLocking, sizeof(BarcodeModel));
+    view_set_draw_callback(barcode->view, barcode_draw_callback);
+    view_set_input_callback(barcode->view, barcode_input_callback);
+
+    return barcode;
+}
+
+void barcode_free_model(Barcode* barcode) {
+    with_view_model(
+        barcode->view,
+        BarcodeModel * model,
+        {
+            if(model->file_path != NULL) {
+                furi_string_free(model->file_path);
+            }
+            if(model->data != NULL) {
+                if(model->data->raw_data != NULL) {
+                    furi_string_free(model->data->raw_data);
+                }
+                if(model->data->correct_data != NULL) {
+                    furi_string_free(model->data->correct_data);
+                }
+                free(model->data);
+            }
+        },
+        false);
+}
+
+void barcode_free(Barcode* barcode) {
+    furi_assert(barcode);
+
+    barcode_free_model(barcode);
+    view_free(barcode->view);
+    free(barcode);
+}
+
+View* barcode_get_view(Barcode* barcode) {
+    furi_assert(barcode);
+    return barcode->view;
+}

+ 23 - 0
base_pack/barcode_gen/views/barcode_view.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include <gui/view.h>
+
+typedef struct BarcodeApp BarcodeApp;
+
+typedef struct {
+    View* view;
+    BarcodeApp* barcode_app;
+} Barcode;
+
+typedef struct {
+    FuriString* file_path;
+    BarcodeData* data;
+} BarcodeModel;
+
+Barcode* barcode_view_allocate(BarcodeApp* barcode_app);
+
+void barcode_free_model(Barcode* barcode);
+
+void barcode_free(Barcode* barcode);
+
+View* barcode_get_view(Barcode* barcode);

Некоторые файлы не были показаны из-за большого количества измененных файлов