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

Merge pull request #20 from HonestLocksmith/Updates

Support for Double Sided Key and Multiple New Key Formats
Zinong Li 1 год назад
Родитель
Сommit
78e51bd691
4 измененных файлов с 1234 добавлено и 753 удалено
  1. 2 0
      README.md
  2. 800 678
      key_copier.c
  3. 400 44
      key_formats.c
  4. 32 31
      key_formats.h

+ 2 - 0
README.md

@@ -9,7 +9,9 @@ To measure your key:
 
 ## Special Thanks
 - Thank [@jamisonderek](https://github.com/jamisonderek) for his [Flipper Zero Tutorial repository](https://github.com/jamisonderek/flipper-zero-tutorials) and [YouTube channel](https://github.com/jamisonderek/flipper-zero-tutorials#:~:text=YouTube%3A%20%40MrDerekJamison)! This app is built with his Skeleton App and GPIO Wiegand app as references. 
+- Thank [@HonestLocksmith](https://github.com/HonestLocksmith) for PR #13 and #20. TONS of new key formats and supports for DOUBLE-SIDED keys are added. We have car keys now! 
 - [Project channel](https://discord.com/channels/1112390971250974782/1264067969634402356)
 
 
 
+

+ 800 - 678
key_copier.c

@@ -1,678 +1,800 @@
-#include <furi.h>
-#include <furi_hal.h>
-#include <gui/gui.h>
-#include <gui/view.h>
-#include <gui/view_dispatcher.h>
-#include <gui/modules/submenu.h>
-#include <gui/modules/text_input.h>
-#include <gui/modules/widget.h>
-#include <gui/modules/variable_item_list.h>
-#include <notification/notification.h>
-#include <notification/notification_messages.h>
-#include <applications/services/storage/storage.h>
-#include <applications/services/dialogs/dialogs.h>
-#include <stdbool.h>
-#include <math.h>
-#include <flipper_format.h>
-#include "key_copier_icons.h"
-#include "key_formats.h"
-#include "key_copier.h"
-#define TAG "KeyCopier"
-
-#define BACKLIGHT_ON 1
-
-typedef enum {
-    KeyCopierSubmenuIndexMeasure,
-    KeyCopierSubmenuIndexConfigure,
-    KeyCopierSubmenuIndexSave,
-    KeyCopierSubmenuIndexLoad,
-    KeyCopierSubmenuIndexAbout,
-} KeyCopierSubmenuIndex;
-
-typedef enum {
-    KeyCopierViewSubmenu, 
-    KeyCopierViewTextInput,
-    KeyCopierViewConfigure_i, 
-    KeyCopierViewConfigure_e,
-    KeyCopierViewSave,
-    KeyCopierViewLoad,
-    KeyCopierViewMeasure,
-    KeyCopierViewAbout,
-} KeyCopierView;
-
-typedef struct {
-    ViewDispatcher* view_dispatcher; 
-    NotificationApp* notifications; 
-    Submenu* submenu; 
-    TextInput* text_input; 
-    VariableItemList* variable_item_list_config; 
-    View* view_measure; 
-    View* view_config_e; 
-    View* view_save;
-    View* view_load; 
-    Widget* widget_about; 
-    VariableItem* key_name_item; 
-    VariableItem* format_item;
-    char* temp_buffer; 
-    uint32_t temp_buffer_size;
-
-    DialogsApp* dialogs;
-    FuriString* file_path;
-} KeyCopierApp;
-
-typedef struct {
-    uint32_t format_index; 
-    FuriString* key_name_str; 
-    uint8_t pin_slc; // The pin that is being adjusted
-    uint8_t* depth; // The cutting depth
-    bool data_loaded;
-    KeyFormat format;
-} KeyCopierModel;
-
-void initialize_model(KeyCopierModel* model) {
-    if(model->depth != NULL) {
-        free(model->depth);
-    }
-    model->format_index = 0;
-    memcpy(&model->format, &all_formats[model->format_index], sizeof(KeyFormat));
-    model->depth = (uint8_t*)malloc((model->format.pin_num + 1) * sizeof(uint8_t));
-    for(uint8_t i = 0; i <= model->format.pin_num; i++) {
-        model->depth[i] = model->format.min_depth_ind;
-    }
-    model->pin_slc = 1;
-    model->data_loaded = 0;
-    model->key_name_str = furi_string_alloc();
-}
-
-static uint32_t key_copier_navigation_exit_callback(void* _context) {
-    UNUSED(_context);
-    return VIEW_NONE;
-}
-
-static uint32_t key_copier_navigation_submenu_callback(void* _context) {
-    UNUSED(_context);
-    return KeyCopierViewSubmenu;
-}
-
-static void key_copier_submenu_callback(void* context, uint32_t index) {
-    KeyCopierApp* app = (KeyCopierApp*)context;
-    switch(index) {
-    case KeyCopierSubmenuIndexMeasure:
-        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewMeasure);
-        break;
-    case KeyCopierSubmenuIndexConfigure:
-        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewConfigure_e);
-        break;
-    case KeyCopierSubmenuIndexSave:
-        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSave);
-        break;
-    case KeyCopierSubmenuIndexLoad:
-        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewLoad);
-        break;
-    case KeyCopierSubmenuIndexAbout:
-        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewAbout);
-        break;
-    default:
-        break;
-    }
-}
-
-char* manufacturers[COUNT_OF(all_formats)];
-void initialize_manufacturers(char** manufacturers) {
-    // Populate the manufacturers array
-    for(size_t i = 0; i < COUNT_OF(all_formats); i++) {
-        manufacturers[i] = all_formats[i].manufacturer;
-    }
-}
-
-static void key_copier_format_change(VariableItem* item) {
-    KeyCopierApp* app = variable_item_get_context(item);
-    KeyCopierModel* model = view_get_model(app->view_measure);
-    if(model->data_loaded) {
-        variable_item_set_current_value_index(item, model->format_index);
-    }
-    uint8_t format_index = variable_item_get_current_value_index(item);
-    if(format_index != model->format_index) {
-        model->format_index = format_index;
-        model->format = all_formats[format_index];
-        if(model->depth != NULL) {
-            free(model->depth);
-        }
-        model->depth = (uint8_t*)malloc((model->format.pin_num + 1) * sizeof(uint8_t));
-        for(uint8_t i = 0; i <= model->format.pin_num; i++) {
-            model->depth[i] = model->format.min_depth_ind;
-        }
-        model->pin_slc = 1;
-    }
-    model->data_loaded = false;
-    variable_item_set_current_value_text(item, model->format.format_name);
-    model->format = all_formats[model->format_index];
-}
-static const char* format_config_label = "Key Format";
-static void key_copier_config_enter_callback(void* context) {
-    KeyCopierApp* app = (KeyCopierApp*)context;
-    KeyCopierModel* my_model = view_get_model(app->view_measure);
-    variable_item_list_reset(app->variable_item_list_config);
-    // Recreate this view every time we enter it so that it's always updated
-    app->format_item = variable_item_list_add(
-        app->variable_item_list_config,
-        format_config_label,
-        COUNT_OF(all_formats),
-        key_copier_format_change,
-        app);
-
-    View* view_config_i = variable_item_list_get_view(app->variable_item_list_config);
-    variable_item_set_current_value_index(app->format_item, my_model->format_index);
-    key_copier_format_change(app->format_item);
-    view_set_previous_callback(view_config_i, key_copier_navigation_submenu_callback);
-    view_dispatcher_remove_view(
-        app->view_dispatcher, KeyCopierViewConfigure_i); // delete the last one
-    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_config_i);
-    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewConfigure_i); // recreate it
-}
-
-static const char* key_name_entry_text = "Enter name";
-static void key_copier_file_saver(void* context) {
-    KeyCopierApp* app = (KeyCopierApp*)context;
-    KeyCopierModel* model = view_get_model(app->view_measure);
-    bool redraw = true;
-    with_view_model(
-        app->view_measure,
-        KeyCopierModel * model,
-        { furi_string_set(model->key_name_str, app->temp_buffer); },
-        redraw);
-    FuriString* file_path = furi_string_alloc();
-    furi_string_printf(
-        file_path,
-        "%s/%s%s",
-        STORAGE_APP_DATA_PATH_PREFIX,
-        furi_string_get_cstr(model->key_name_str),
-        KEY_COPIER_FILE_EXTENSION);
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
-    FURI_LOG_D(TAG, "mkdir finished");
-    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
-    do {
-        const uint32_t version = 1;
-        const uint32_t pin_num_buffer = (uint32_t)model->format.pin_num;
-        const uint32_t macs_buffer = (uint32_t)model->format.macs;
-        FuriString* buffer = furi_string_alloc();
-        if(!flipper_format_file_open_always(flipper_format, furi_string_get_cstr(file_path)))
-            break;
-        if(!flipper_format_write_header_cstr(flipper_format, "Flipper Key Copier File", version))
-            break;
-        if(!flipper_format_write_string_cstr(
-               flipper_format, "Manufacturer", model->format.manufacturer))
-            break;
-        if(!flipper_format_write_string_cstr(
-               flipper_format, "Format Name", model->format.format_name))
-            break;
-        if(!flipper_format_write_string_cstr(
-               flipper_format, "Data Sheet", model->format.format_link))
-            break;
-        if(!flipper_format_write_uint32(flipper_format, "Number of Pins", &pin_num_buffer, 1))
-            break;
-        if(!flipper_format_write_uint32(
-               flipper_format, "Maximum Adjacent Cut Specification (MACS)", &macs_buffer, 1))
-            break;
-        for(int i = 0; i < model->format.pin_num; i++) {
-            if(i < model->format.pin_num - 1) {
-                furi_string_cat_printf(buffer, "%d-", model->depth[i]);
-            } else {
-                furi_string_cat_printf(buffer, "%d", model->depth[i]);
-            }
-        }
-        if(!flipper_format_write_string(flipper_format, "Bitting Pattern", buffer)) break;
-        furi_string_free(buffer);
-        // signal that the file was written successfully
-    } while(0);
-    flipper_format_free(flipper_format);
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
-}
-
-static void key_copier_view_save_callback(void* context) {
-    KeyCopierApp* app = (KeyCopierApp*)context;
-    // Header to display on the text input screen.
-    text_input_set_header_text(app->text_input, key_name_entry_text);
-
-    // Copy the current name into the temporary buffer.
-    bool redraw = false;
-    with_view_model(
-        app->view_measure,
-        KeyCopierModel * model,
-        {
-            strncpy(
-                app->temp_buffer,
-                furi_string_get_cstr(model->key_name_str),
-                app->temp_buffer_size);
-        },
-        redraw);
-
-    // Configure the text input.  When user enters text and clicks OK, key_copier_file_saver be called.
-    bool clear_previous_text = false;
-    text_input_set_result_callback(
-        app->text_input,
-        key_copier_file_saver,
-        app,
-        app->temp_buffer,
-        app->temp_buffer_size,
-        clear_previous_text);
-
-    view_set_previous_callback(
-        text_input_get_view(app->text_input), key_copier_navigation_submenu_callback);
-
-    // Show text input dialog.
-    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewTextInput);
-}
-
-static void key_copier_view_load_callback(void* context) {
-    KeyCopierApp* app = (KeyCopierApp*)context;
-    KeyCopierModel* model = view_get_model(app->view_measure);
-    DialogsFileBrowserOptions browser_options;
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
-    dialog_file_browser_set_basic_options(&browser_options, KEY_COPIER_FILE_EXTENSION, &I_icon);
-    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
-    furi_string_set(app->file_path, browser_options.base_path);
-    if(dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) {
-        FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
-        do {
-            if(!flipper_format_file_open_existing(
-                   flipper_format, furi_string_get_cstr(app->file_path)))
-                break;
-            FuriString* format_buffer = furi_string_alloc();
-            FuriString* depth_buffer = furi_string_alloc();
-            if(!flipper_format_read_string(flipper_format, "Format Name", format_buffer)) break;
-            if(!flipper_format_read_string(flipper_format, "Bitting Pattern", depth_buffer)) break;
-            for(size_t i = 0; i < COUNT_OF(all_formats); i++) {
-                if(!strcmp(furi_string_get_cstr(format_buffer), all_formats[i].format_name)) {
-                    model->format_index = (uint32_t)i;
-                    model->format = all_formats[model->format_index];
-                }
-            }
-
-            for(int i = 0; i < model->format.pin_num; i++) {
-                model->depth[i] = (uint8_t)(furi_string_get_char(depth_buffer, i * 2) - '0');
-            }
-            model->data_loaded = true;
-            // signal that the file was read successfully
-        } while(0);
-        flipper_format_free(flipper_format);
-        furi_record_close(RECORD_STORAGE);
-    }
-    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
-}
-
-static void key_copier_view_measure_draw_callback(Canvas* canvas, void* model) {
-    static double inches_per_px = (double)INCHES_PER_PX;
-    canvas_set_bitmap_mode(canvas, true);
-    KeyCopierModel* my_model = (KeyCopierModel*)model;
-    KeyFormat my_format = my_model->format;
-    FuriString* buffer = furi_string_alloc();
-    int pin_half_width_px = (int)round((my_format.pin_width_inch / inches_per_px) / 2);
-    int pin_step_px = (int)round(my_format.pin_increment_inch / inches_per_px);
-    double drill_radians =
-        (180 - my_format.drill_angle) / 2 / 180 * (double)M_PI; // Convert angle to radians
-    double tangent = tan(drill_radians);
-    int top_contour_px = (int)round(63 - my_format.uncut_depth_inch / inches_per_px);
-    int post_extra_x_px = 0;
-    int pre_extra_x_px = 0;
-    for(int current_pin = 1; current_pin <= my_model->format.pin_num; current_pin += 1) {
-        double current_center_px =
-            my_format.first_pin_inch + (current_pin - 1) * my_format.pin_increment_inch;
-        int pin_center_px = (int)round(current_center_px / inches_per_px);
-
-        furi_string_printf(buffer, "%d", my_model->depth[current_pin - 1]);
-        canvas_draw_str_aligned(
-            canvas,
-            pin_center_px,
-            top_contour_px - 12,
-            AlignCenter,
-            AlignCenter,
-            furi_string_get_cstr(buffer));
-
-        canvas_draw_line(
-            canvas,
-            pin_center_px,
-            top_contour_px - 5,
-            pin_center_px,
-            top_contour_px); // the vertical line to indicate pin center
-        int current_depth = my_model->depth[current_pin - 1] - my_format.min_depth_ind;
-        int current_depth_px =
-            (int)round(current_depth * my_format.depth_step_inch / inches_per_px);
-        canvas_draw_line(
-            canvas,
-            pin_center_px - pin_half_width_px,
-            top_contour_px + current_depth_px,
-            pin_center_px + pin_half_width_px,
-            top_contour_px + current_depth_px); // draw pin width horizontal line
-        int last_depth = my_model->depth[current_pin - 2] - my_format.min_depth_ind;
-        int next_depth = my_model->depth[current_pin] - my_format.min_depth_ind;
-        if(current_pin == 1) {
-            canvas_draw_line(
-                canvas,
-                0,
-                top_contour_px,
-                pin_center_px - pin_half_width_px - current_depth_px,
-                top_contour_px);
-            last_depth = 0;
-            pre_extra_x_px = max(current_depth_px + pin_half_width_px, 0);
-        }
-        if(current_pin == my_model->format.pin_num) {
-            next_depth = 0;
-        }
-        if((last_depth + current_depth) > my_format.clearance) { //yes intersection
-
-            if(current_pin != 1) {
-                pre_extra_x_px =
-                    min(max(pin_step_px - post_extra_x_px, pin_half_width_px),
-                        pin_step_px - pin_half_width_px);
-            }
-            canvas_draw_line(
-                canvas,
-                pin_center_px - pre_extra_x_px,
-                top_contour_px +
-                    max((int)round(
-                            (current_depth_px - (pre_extra_x_px - pin_half_width_px)) * tangent),
-                        0),
-                pin_center_px - pin_half_width_px,
-                top_contour_px + (int)round(current_depth_px * tangent));
-        } else {
-            int last_depth_px = (int)round(last_depth * my_format.depth_step_inch / inches_per_px);
-            int down_slope_start_x_px = pin_center_px - pin_half_width_px - current_depth_px;
-            canvas_draw_line(
-                canvas,
-                pin_center_px - pin_half_width_px - current_depth_px,
-                top_contour_px,
-                pin_center_px - pin_half_width_px,
-                top_contour_px + (int)round(current_depth_px * tangent));
-            canvas_draw_line(
-                canvas,
-                min(pin_center_px - pin_step_px + pin_half_width_px + last_depth_px,
-                    down_slope_start_x_px),
-                top_contour_px,
-                down_slope_start_x_px,
-                top_contour_px);
-        }
-        if((current_depth + next_depth) > my_format.clearance) { //yes intersection
-            double numerator = (double)current_depth;
-            double denominator = (double)(current_depth + next_depth);
-            double product = (numerator / denominator) * pin_step_px;
-            post_extra_x_px =
-                (int)min(max(product, pin_half_width_px), pin_step_px - pin_half_width_px);
-            canvas_draw_line(
-                canvas,
-                pin_center_px + pin_half_width_px,
-                top_contour_px + current_depth_px,
-                pin_center_px + post_extra_x_px,
-                top_contour_px +
-                    max(current_depth_px -
-                            (int)round((post_extra_x_px - pin_half_width_px) * tangent),
-                        0));
-        } else { // no intersection
-            canvas_draw_line(
-                canvas,
-                pin_center_px + pin_half_width_px,
-                top_contour_px + (int)round(current_depth_px * tangent),
-                pin_center_px + pin_half_width_px + current_depth_px,
-                top_contour_px);
-        }
-    }
-
-    int level_contour_px =
-        (int)round((my_format.last_pin_inch + my_format.elbow_inch) / inches_per_px);
-    int elbow_px = (int)round(my_format.elbow_inch / inches_per_px);
-    canvas_draw_line(canvas, 0, 62, level_contour_px, 62);
-    canvas_draw_line(canvas, level_contour_px, 62, level_contour_px + elbow_px, 62 - elbow_px);
-
-    int slc_pin_px = (int)round(
-        (my_format.first_pin_inch + (my_model->pin_slc - 1) * my_format.pin_increment_inch) /
-        inches_per_px);
-    canvas_draw_icon(canvas, slc_pin_px - 2, top_contour_px - 25, &I_arrow_down);
-
-    furi_string_printf(buffer, "%s", my_format.format_name);
-    canvas_draw_str(canvas, 110, 10, furi_string_get_cstr(buffer));
-    furi_string_free(buffer);
-}
-
-static bool key_copier_view_measure_input_callback(InputEvent* event, void* context) {
-    KeyCopierApp* app = (KeyCopierApp*)context;
-    if(event->type == InputTypeShort) {
-        switch(event->key) {
-        case InputKeyLeft: {
-            bool redraw = true;
-            with_view_model(
-                app->view_measure,
-                KeyCopierModel * model,
-                {
-                    if(model->pin_slc > 1) {
-                        model->pin_slc--;
-                    }
-                },
-                redraw);
-            break;
-        }
-        case InputKeyRight: {
-            bool redraw = true;
-            with_view_model(
-                app->view_measure,
-                KeyCopierModel * model,
-                {
-                    if(model->pin_slc < model->format.pin_num) {
-                        model->pin_slc++;
-                    }
-                },
-                redraw);
-            break;
-        }
-        case InputKeyUp: {
-            bool redraw = true;
-            with_view_model(
-                app->view_measure,
-                KeyCopierModel * model,
-                {
-                    if(model->depth[model->pin_slc - 1] > model->format.min_depth_ind) {
-                        if(model->pin_slc == 1) { //first pin only limited by the next one
-                            if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
-                               model->format.macs)
-                                model->depth[model->pin_slc - 1]--;
-                        } else if(
-                            model->pin_slc ==
-                            model->format.pin_num) { //last pin only limited by the previous one
-                            if(model->depth[model->pin_slc - 2] -
-                                   model->depth[model->pin_slc - 1] <
-                               model->format.macs) {
-                                model->depth[model->pin_slc - 1]--;
-                            }
-                        } else {
-                            if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
-                                   model->format.macs &&
-                               model->depth[model->pin_slc - 2] -
-                                       model->depth[model->pin_slc - 1] <
-                                   model->format.macs) {
-                                model->depth[model->pin_slc - 1]--;
-                            }
-                        }
-                    }
-                },
-                redraw);
-            break;
-        }
-        case InputKeyDown: {
-            bool redraw = true;
-            with_view_model(
-                app->view_measure,
-                KeyCopierModel * model,
-                {
-                    if(model->depth[model->pin_slc - 1] < model->format.max_depth_ind) {
-                        if(model->pin_slc == 1) { //first pin only limited by the next one
-                            if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
-                               model->format.macs)
-                                model->depth[model->pin_slc - 1]++;
-                        } else if(
-                            model->pin_slc ==
-                            model->format.pin_num) { //last pin only limited by the previous one
-                            if(model->depth[model->pin_slc - 1] -
-                                   model->depth[model->pin_slc - 2] <
-                               model->format.macs) {
-                                model->depth[model->pin_slc - 1]++;
-                            }
-                        } else {
-                            if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
-                                   model->format.macs &&
-                               model->depth[model->pin_slc - 1] -
-                                       model->depth[model->pin_slc - 2] <
-                                   model->format.macs) {
-                                model->depth[model->pin_slc - 1]++;
-                            }
-                        }
-                    }
-                },
-                redraw);
-            break;
-        }
-        default:
-            // Handle other keys or do nothing
-            break;
-        }
-    }
-
-    return false;
-}
-
-static KeyCopierApp* key_copier_app_alloc() {
-    KeyCopierApp* app = (KeyCopierApp*)malloc(sizeof(KeyCopierApp));
-
-    Gui* 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, gui, ViewDispatcherTypeFullscreen);
-    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
-    app->dialogs = furi_record_open(RECORD_DIALOGS);
-    app->file_path = furi_string_alloc();
-    app->submenu = submenu_alloc();
-    submenu_add_item(
-        app->submenu, "Measure", KeyCopierSubmenuIndexMeasure, key_copier_submenu_callback, app);
-    submenu_add_item(
-        app->submenu, "Config", KeyCopierSubmenuIndexConfigure, key_copier_submenu_callback, app);
-    submenu_add_item(
-        app->submenu, "Save", KeyCopierSubmenuIndexSave, key_copier_submenu_callback, app);
-    submenu_add_item(
-        app->submenu, "Load", KeyCopierSubmenuIndexLoad, key_copier_submenu_callback, app);
-    submenu_add_item(
-        app->submenu, "About", KeyCopierSubmenuIndexAbout, key_copier_submenu_callback, app);
-    view_set_previous_callback(
-        submenu_get_view(app->submenu), key_copier_navigation_exit_callback);
-    view_dispatcher_add_view(
-        app->view_dispatcher, KeyCopierViewSubmenu, submenu_get_view(app->submenu));
-    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
-
-    app->text_input = text_input_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, KeyCopierViewTextInput, text_input_get_view(app->text_input));
-    app->temp_buffer_size = 32;
-    app->temp_buffer = (char*)malloc(app->temp_buffer_size);
-    app->temp_buffer = "";
-
-    app->view_measure = view_alloc();
-    view_set_draw_callback(app->view_measure, key_copier_view_measure_draw_callback);
-    view_set_input_callback(app->view_measure, key_copier_view_measure_input_callback);
-    view_set_previous_callback(app->view_measure, key_copier_navigation_submenu_callback);
-    view_set_context(app->view_measure, app);
-    view_allocate_model(app->view_measure, ViewModelTypeLockFree, sizeof(KeyCopierModel));
-    KeyCopierModel* model = view_get_model(app->view_measure);
-
-    initialize_model(model);
-    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewMeasure, app->view_measure);
-
-    app->variable_item_list_config = variable_item_list_alloc();
-    app->view_config_e = view_alloc();
-    view_set_context(app->view_config_e, app);
-    view_set_previous_callback(app->view_config_e, key_copier_navigation_submenu_callback);
-    view_set_enter_callback(app->view_config_e, key_copier_config_enter_callback);
-    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_e, app->view_config_e);
-
-    View* view_buffer = view_alloc();
-    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_buffer);
-
-    app->view_save = view_alloc();
-    view_set_context(app->view_save, app);
-    view_set_enter_callback(app->view_save, key_copier_view_save_callback);
-    view_set_previous_callback(app->view_save, key_copier_navigation_submenu_callback);
-    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewSave, app->view_save);
-
-    app->view_load = view_alloc();
-    view_set_context(app->view_load, app);
-    view_set_enter_callback(app->view_load, key_copier_view_load_callback);
-    view_set_previous_callback(app->view_load, key_copier_navigation_submenu_callback);
-    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewLoad, app->view_load);
-
-    app->widget_about = widget_alloc();
-    widget_add_text_scroll_element(
-        app->widget_about,
-        0,
-        0,
-        128,
-        64,
-        "Key Maker App 1.0\nAuthor: @Torron\n\nTo measure your key:\n\n1. Place it on top of the screen.\n\n2. Use the contour to align your key.\n\n3. Adjust each pin's depth until they match. It's easier if you look with one eye closed.\n\nGithub: github.com/zinongli/KeyCopier \n\nSpecial thanks to Derek Jamison's Skeleton App Template.");
-    view_set_previous_callback(
-        widget_get_view(app->widget_about), key_copier_navigation_submenu_callback);
-    view_dispatcher_add_view(
-        app->view_dispatcher, KeyCopierViewAbout, widget_get_view(app->widget_about));
-
-    app->notifications = furi_record_open(RECORD_NOTIFICATION);
-
-#ifdef BACKLIGHT_ON
-    notification_message(app->notifications, &sequence_display_backlight_enforce_on);
-#endif
-
-    return app;
-}
-
-static void key_copier_app_free(KeyCopierApp* app) {
-#ifdef BACKLIGHT_ON
-    notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
-#endif
-    furi_record_close(RECORD_NOTIFICATION);
-
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewTextInput);
-    text_input_free(app->text_input);
-    free(app->temp_buffer);
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewAbout);
-    widget_free(app->widget_about);
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewMeasure);
-    with_view_model(
-        app->view_measure,
-        KeyCopierModel * model,
-        {
-            if(model->depth != NULL) {
-                free(model->depth);
-            }
-        },
-        false);
-    view_free(app->view_measure);
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_e);
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_i);
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSave);
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewLoad);
-    variable_item_list_free(app->variable_item_list_config);
-    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSubmenu);
-    submenu_free(app->submenu);
-    view_dispatcher_free(app->view_dispatcher);
-    furi_record_close(RECORD_GUI);
-
-    free(app);
-}
-
-int32_t main_key_copier_app(void* _p) {
-    UNUSED(_p);
-
-    KeyCopierApp* app = key_copier_app_alloc();
-    view_dispatcher_run(app->view_dispatcher);
-
-    key_copier_app_free(app);
-    return 0;
-}
+#include "key_copier.h"
+#include "key_copier_icons.h"
+#include "key_formats.h"
+#include <applications/services/dialogs/dialogs.h>
+#include <applications/services/storage/storage.h>
+#include <flipper_format.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/widget.h>
+#include <gui/view.h>
+#include <gui/view_dispatcher.h>
+#include <math.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <stdbool.h>
+
+#define TAG "KeyCopier"
+
+#define BACKLIGHT_ON 1
+
+typedef enum {
+    KeyCopierSubmenuIndexMeasure,
+    KeyCopierSubmenuIndexConfigure,
+    KeyCopierSubmenuIndexSave,
+    KeyCopierSubmenuIndexLoad,
+    KeyCopierSubmenuIndexAbout,
+} KeyCopierSubmenuIndex;
+
+typedef enum {
+    KeyCopierViewSubmenu,
+    KeyCopierViewTextInput,
+    KeyCopierViewConfigure_i,
+    KeyCopierViewConfigure_e,
+    KeyCopierViewSave,
+    KeyCopierViewLoad,
+    KeyCopierViewMeasure,
+    KeyCopierViewAbout,
+} KeyCopierView;
+
+typedef struct {
+    ViewDispatcher* view_dispatcher;
+    NotificationApp* notifications;
+    Submenu* submenu;
+    TextInput* text_input;
+    VariableItemList* variable_item_list_config;
+    View* view_measure;
+    View* view_config_e;
+    View* view_save;
+    View* view_load;
+    Widget* widget_about;
+    VariableItem* key_name_item;
+    VariableItem* format_item;
+    char* temp_buffer;
+    uint32_t temp_buffer_size;
+
+    DialogsApp* dialogs;
+    FuriString* file_path;
+} KeyCopierApp;
+
+typedef struct {
+    uint32_t format_index;
+    FuriString* key_name_str;
+    uint8_t pin_slc; // The pin that is being adjusted
+    uint8_t* depth; // The cutting depth
+    bool data_loaded;
+    KeyFormat format;
+} KeyCopierModel;
+
+void initialize_model(KeyCopierModel* model) {
+    if(model->depth != NULL) {
+        free(model->depth);
+    }
+    model->format_index = 0;
+    memcpy(&model->format, &all_formats[model->format_index], sizeof(KeyFormat));
+    model->depth = (uint8_t*)malloc((model->format.pin_num + 1) * sizeof(uint8_t));
+    for(uint8_t i = 0; i <= model->format.pin_num; i++) {
+        model->depth[i] = model->format.min_depth_ind;
+    }
+    model->pin_slc = 1;
+    model->data_loaded = 0;
+    model->key_name_str = furi_string_alloc();
+}
+
+static uint32_t key_copier_navigation_exit_callback(void* _context) {
+    UNUSED(_context);
+    return VIEW_NONE;
+}
+
+static uint32_t key_copier_navigation_submenu_callback(void* _context) {
+    UNUSED(_context);
+    return KeyCopierViewSubmenu;
+}
+
+static void key_copier_submenu_callback(void* context, uint32_t index) {
+    KeyCopierApp* app = (KeyCopierApp*)context;
+    switch(index) {
+    case KeyCopierSubmenuIndexMeasure:
+        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewMeasure);
+        break;
+    case KeyCopierSubmenuIndexConfigure:
+        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewConfigure_e);
+        break;
+    case KeyCopierSubmenuIndexSave:
+        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSave);
+        break;
+    case KeyCopierSubmenuIndexLoad:
+        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewLoad);
+        break;
+    case KeyCopierSubmenuIndexAbout:
+        view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewAbout);
+        break;
+    default:
+        break;
+    }
+}
+
+char* manufacturers[COUNT_OF(all_formats)];
+void initialize_manufacturers(char** manufacturers) {
+    // Populate the manufacturers array
+    for(size_t i = 0; i < COUNT_OF(all_formats); i++) {
+        manufacturers[i] = all_formats[i].manufacturer;
+    }
+}
+
+static void key_copier_format_change(VariableItem* item) {
+    KeyCopierApp* app = variable_item_get_context(item);
+    KeyCopierModel* model = view_get_model(app->view_measure);
+    if(model->data_loaded) {
+        variable_item_set_current_value_index(item, model->format_index);
+    }
+    uint8_t format_index = variable_item_get_current_value_index(item);
+    if(format_index != model->format_index) {
+        model->format_index = format_index;
+        model->format = all_formats[format_index];
+        if(model->depth != NULL) {
+            free(model->depth);
+        }
+        model->depth = (uint8_t*)malloc((model->format.pin_num + 1) * sizeof(uint8_t));
+        for(uint8_t i = 0; i <= model->format.pin_num; i++) {
+            model->depth[i] = model->format.min_depth_ind;
+        }
+        model->pin_slc = 1;
+    }
+    model->data_loaded = false;
+    variable_item_set_current_value_text(item, model->format.format_name);
+    model->format = all_formats[model->format_index];
+}
+static const char* format_config_label = "Key Format";
+static void key_copier_config_enter_callback(void* context) {
+    KeyCopierApp* app = (KeyCopierApp*)context;
+    KeyCopierModel* my_model = view_get_model(app->view_measure);
+    variable_item_list_reset(app->variable_item_list_config);
+    // Recreate this view every time we enter it so that it's always updated
+    app->format_item = variable_item_list_add(
+        app->variable_item_list_config,
+        format_config_label,
+        COUNT_OF(all_formats),
+        key_copier_format_change,
+        app);
+
+    View* view_config_i = variable_item_list_get_view(app->variable_item_list_config);
+    variable_item_set_current_value_index(app->format_item, my_model->format_index);
+    key_copier_format_change(app->format_item);
+    view_set_previous_callback(view_config_i, key_copier_navigation_submenu_callback);
+    view_dispatcher_remove_view(
+        app->view_dispatcher,
+        KeyCopierViewConfigure_i); // delete the last one
+    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_config_i);
+    view_dispatcher_switch_to_view(app->view_dispatcher,
+                                   KeyCopierViewConfigure_i); // recreate it
+}
+
+static const char* key_name_entry_text = "Enter name";
+static void key_copier_file_saver(void* context) {
+    KeyCopierApp* app = (KeyCopierApp*)context;
+    KeyCopierModel* model = view_get_model(app->view_measure);
+    bool redraw = true;
+    with_view_model(
+        app->view_measure,
+        KeyCopierModel * model,
+        { furi_string_set(model->key_name_str, app->temp_buffer); },
+        redraw);
+    FuriString* file_path = furi_string_alloc();
+    furi_string_printf(
+        file_path,
+        "%s/%s%s",
+        STORAGE_APP_DATA_PATH_PREFIX,
+        furi_string_get_cstr(model->key_name_str),
+        KEY_COPIER_FILE_EXTENSION);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
+    FURI_LOG_D(TAG, "mkdir finished");
+    FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+    do {
+        const uint32_t version = 1;
+        const uint32_t pin_num_buffer = (uint32_t)model->format.pin_num;
+        const uint32_t macs_buffer = (uint32_t)model->format.macs;
+        FuriString* buffer = furi_string_alloc();
+        if(!flipper_format_file_open_always(flipper_format, furi_string_get_cstr(file_path)))
+            break;
+        if(!flipper_format_write_header_cstr(flipper_format, "Flipper Key Copier File", version))
+            break;
+        if(!flipper_format_write_string_cstr(
+               flipper_format, "Manufacturer", model->format.manufacturer))
+            break;
+        if(!flipper_format_write_string_cstr(
+               flipper_format, "Format Name", model->format.format_name))
+            break;
+        if(!flipper_format_write_string_cstr(
+               flipper_format, "Data Sheet", model->format.format_link))
+            break;
+        if(!flipper_format_write_uint32(flipper_format, "Number of Pins", &pin_num_buffer, 1))
+            break;
+        if(!flipper_format_write_uint32(
+               flipper_format, "Maximum Adjacent Cut Specification (MACS)", &macs_buffer, 1))
+            break;
+        for(int i = 0; i < model->format.pin_num; i++) {
+            if(i < model->format.pin_num - 1) {
+                furi_string_cat_printf(buffer, "%d-", model->depth[i]);
+            } else {
+                furi_string_cat_printf(buffer, "%d", model->depth[i]);
+            }
+        }
+        if(!flipper_format_write_string(flipper_format, "Bitting Pattern", buffer)) break;
+        furi_string_free(buffer);
+        // signal that the file was written successfully
+    } while(0);
+    flipper_format_free(flipper_format);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
+}
+
+static void key_copier_view_save_callback(void* context) {
+    KeyCopierApp* app = (KeyCopierApp*)context;
+    // Header to display on the text input screen.
+    text_input_set_header_text(app->text_input, key_name_entry_text);
+
+    // Copy the current name into the temporary buffer.
+    bool redraw = false;
+    with_view_model(
+        app->view_measure,
+        KeyCopierModel * model,
+        {
+            strncpy(
+                app->temp_buffer,
+                furi_string_get_cstr(model->key_name_str),
+                app->temp_buffer_size);
+        },
+        redraw);
+
+    // Configure the text input.  When user enters text and clicks OK,
+    // key_copier_file_saver be called.
+    bool clear_previous_text = false;
+    text_input_set_result_callback(
+        app->text_input,
+        key_copier_file_saver,
+        app,
+        app->temp_buffer,
+        app->temp_buffer_size,
+        clear_previous_text);
+
+    view_set_previous_callback(
+        text_input_get_view(app->text_input), key_copier_navigation_submenu_callback);
+
+    // Show text input dialog.
+    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewTextInput);
+}
+
+static void key_copier_view_load_callback(void* context) {
+    KeyCopierApp* app = (KeyCopierApp*)context;
+    KeyCopierModel* model = view_get_model(app->view_measure);
+    DialogsFileBrowserOptions browser_options;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    storage_simply_mkdir(storage, STORAGE_APP_DATA_PATH_PREFIX);
+    dialog_file_browser_set_basic_options(&browser_options, KEY_COPIER_FILE_EXTENSION, &I_icon);
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+    furi_string_set(app->file_path, browser_options.base_path);
+    if(dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) {
+        FlipperFormat* flipper_format = flipper_format_file_alloc(storage);
+        do {
+            if(!flipper_format_file_open_existing(
+                   flipper_format, furi_string_get_cstr(app->file_path)))
+                break;
+            FuriString* format_buffer = furi_string_alloc();
+            FuriString* depth_buffer = furi_string_alloc();
+            if(!flipper_format_read_string(flipper_format, "Format Name", format_buffer)) break;
+            if(!flipper_format_read_string(flipper_format, "Bitting Pattern", depth_buffer)) break;
+            for(size_t i = 0; i < COUNT_OF(all_formats); i++) {
+                if(!strcmp(furi_string_get_cstr(format_buffer), all_formats[i].format_name)) {
+                    model->format_index = (uint32_t)i;
+                    model->format = all_formats[model->format_index];
+                }
+            }
+
+            for(int i = 0; i < model->format.pin_num; i++) {
+                model->depth[i] = (uint8_t)(furi_string_get_char(depth_buffer, i * 2) - '0');
+            }
+            model->data_loaded = true;
+            // signal that the file was read successfully
+        } while(0);
+        flipper_format_free(flipper_format);
+        furi_record_close(RECORD_STORAGE);
+    }
+    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
+}
+
+static void key_copier_view_measure_draw_callback(Canvas* canvas, void* model) {
+    static double inches_per_px = (double)INCHES_PER_PX;
+    canvas_set_bitmap_mode(canvas, true);
+    KeyCopierModel* my_model = (KeyCopierModel*)model;
+    KeyFormat my_format = my_model->format;
+    FuriString* buffer = furi_string_alloc();
+    int pin_half_width_px = (int)round((my_format.pin_width_inch / inches_per_px) / 2);
+    int pin_step_px = (int)round(my_format.pin_increment_inch / inches_per_px);
+    double drill_radians =
+        (180 - my_format.drill_angle) / 2 / 180 * (double)M_PI; // Convert angle to radians
+    double tangent = tan(drill_radians);
+    int top_contour_px = (int)round(62 - my_format.uncut_depth_inch / inches_per_px);
+    int bottom_contour_px = 0;
+
+    if(my_format.sides == 2)
+        bottom_contour_px =
+            top_contour_px + (int)round(my_format.uncut_depth_inch / inches_per_px);
+    int post_extra_x_px = 0;
+    int pre_extra_x_px = 0;
+    int bottom_post_extra_x_px = 0; // new
+    int bottom_pre_extra_x_px = 0; // new
+    int level_contour_px =
+    (int)round((my_format.last_pin_inch + my_format.elbow_inch) / inches_per_px);
+    for(int current_pin = 1; current_pin <= my_model->format.pin_num; current_pin += 1) {
+        double current_center_px =
+            my_format.first_pin_inch + (current_pin - 1) * my_format.pin_increment_inch;
+        int pin_center_px = (int)round(current_center_px / inches_per_px);
+
+        furi_string_printf(buffer, "%d", my_model->depth[current_pin - 1]);
+        canvas_draw_str_aligned(
+            canvas,
+            pin_center_px,
+            top_contour_px - 12,
+            AlignCenter,
+            AlignCenter,
+            furi_string_get_cstr(buffer));
+
+        canvas_draw_line(
+            canvas,
+            pin_center_px,
+            top_contour_px - 5,
+            pin_center_px,
+            top_contour_px); // the vertical line to indicate pin center
+        int current_depth = my_model->depth[current_pin - 1] - my_format.min_depth_ind;
+        int current_depth_px =
+            (int)round(current_depth * my_format.depth_step_inch / inches_per_px);
+        canvas_draw_line(
+            canvas,
+            pin_center_px - pin_half_width_px,
+            top_contour_px + current_depth_px,
+            pin_center_px + pin_half_width_px,
+            top_contour_px + current_depth_px); // draw top pin width horizontal line
+
+        if(my_format.sides == 2) { // new
+            int last_depth = my_model->depth[current_pin - 2] - my_format.min_depth_ind;
+            int next_depth = my_model->depth[current_pin] - my_format.min_depth_ind;
+            int current_depth = my_model->depth[current_pin - 1] - my_format.min_depth_ind;
+            int current_depth_px =
+                (int)round(current_depth * my_format.depth_step_inch / inches_per_px);
+
+            // Draw horizontal line for bottom pin
+            canvas_draw_line(
+                canvas,
+                pin_center_px - pin_half_width_px,
+                bottom_contour_px - current_depth_px,
+                pin_center_px + pin_half_width_px,
+                bottom_contour_px - current_depth_px);
+
+            // Handle first pin for bottom
+            if(current_pin == 1) {
+                canvas_draw_line(
+                    canvas,
+                    0,
+                    bottom_contour_px,
+                    pin_center_px - pin_half_width_px - current_depth_px,
+                    bottom_contour_px);
+                last_depth = 0;
+                bottom_pre_extra_x_px = max(current_depth_px + pin_half_width_px, 0);
+            }
+
+            // Handle left side intersection for bottom
+            if((last_depth + current_depth) > my_format.clearance) {
+                if(current_pin != 1) {
+                    bottom_pre_extra_x_px =
+                        min(max(pin_step_px - bottom_post_extra_x_px, pin_half_width_px),
+                            pin_step_px - pin_half_width_px);
+                }
+                canvas_draw_line(
+                    canvas,
+                    pin_center_px - bottom_pre_extra_x_px,
+                    bottom_contour_px -
+                        max((int)round(
+                                (current_depth_px - (bottom_pre_extra_x_px - pin_half_width_px)) *
+                                tangent),
+                            0),
+                    pin_center_px - pin_half_width_px,
+                    bottom_contour_px - (int)round(current_depth_px * tangent));
+            } else {
+                int last_depth_px =
+                    (int)round(last_depth * my_format.depth_step_inch / inches_per_px);
+                int up_slope_start_x_px = pin_center_px - pin_half_width_px - current_depth_px;
+                canvas_draw_line(
+                    canvas,
+                    pin_center_px - pin_half_width_px - current_depth_px,
+                    bottom_contour_px,
+                    pin_center_px - pin_half_width_px,
+                    bottom_contour_px - (int)round(current_depth_px * tangent));
+                canvas_draw_line(
+                    canvas,
+                    min(pin_center_px - pin_step_px + pin_half_width_px + last_depth_px,
+                        up_slope_start_x_px),
+                    bottom_contour_px,
+                    up_slope_start_x_px,
+                    bottom_contour_px);
+            }
+
+            // Handle right side intersection for bottom
+            if((current_depth + next_depth) > my_format.clearance) {
+                double numerator = (double)current_depth;
+                double denominator = (double)(current_depth + next_depth);
+                double product = (numerator / denominator) * pin_step_px;
+                bottom_post_extra_x_px =
+                    (int)min(max(product, pin_half_width_px), pin_step_px - pin_half_width_px);
+                canvas_draw_line(
+                    canvas,
+                    pin_center_px + pin_half_width_px,
+                    bottom_contour_px - current_depth_px,
+                    pin_center_px + bottom_post_extra_x_px,
+                    bottom_contour_px -
+                        max(current_depth_px -
+                                (int)round((bottom_post_extra_x_px - pin_half_width_px) * tangent),
+                            0));
+            } else {
+                canvas_draw_line(
+                    canvas,
+                    pin_center_px + pin_half_width_px,
+                    bottom_contour_px - (int)round(current_depth_px * tangent),
+                    pin_center_px + pin_half_width_px + current_depth_px,
+                    bottom_contour_px);
+            }
+        }
+        // new end
+
+        int last_depth = my_model->depth[current_pin - 2] - my_format.min_depth_ind;
+        int next_depth = my_model->depth[current_pin] - my_format.min_depth_ind;
+        if(current_pin == 1) {
+            canvas_draw_line(
+                canvas,
+                0,
+                top_contour_px,
+                pin_center_px - pin_half_width_px - current_depth_px,
+                top_contour_px); // draw top shoulder
+            last_depth = 0;
+            pre_extra_x_px = max(current_depth_px + pin_half_width_px, 0);
+            if(my_format.sides == 2) {
+                canvas_draw_line(
+                    canvas,
+                    0,
+                    bottom_contour_px,
+                    pin_center_px - pin_half_width_px - current_depth_px,
+                    bottom_contour_px); // draw bottom shoulder (hidden by level contour)
+            } else {
+                canvas_draw_line(canvas, 0, 62, level_contour_px, 62);
+            }
+        }
+        if(current_pin == my_model->format.pin_num) {
+            next_depth = 0;
+        }
+        if((last_depth + current_depth) > my_format.clearance) { // yes
+            // intersection
+
+            if(current_pin != 1) {
+                pre_extra_x_px =
+                    min(max(pin_step_px - post_extra_x_px, pin_half_width_px),
+                        pin_step_px - pin_half_width_px);
+            }
+            canvas_draw_line(
+                canvas,
+                pin_center_px - pre_extra_x_px,
+                top_contour_px +
+                    max((int)round(
+                            (current_depth_px - (pre_extra_x_px - pin_half_width_px)) * tangent),
+                        0),
+                pin_center_px - pin_half_width_px,
+                top_contour_px + (int)round(current_depth_px * tangent));
+        } else {
+            int last_depth_px = (int)round(last_depth * my_format.depth_step_inch / inches_per_px);
+            int down_slope_start_x_px = pin_center_px - pin_half_width_px - current_depth_px;
+            canvas_draw_line(
+                canvas,
+                pin_center_px - pin_half_width_px - current_depth_px,
+                top_contour_px,
+                pin_center_px - pin_half_width_px,
+                top_contour_px + (int)round(current_depth_px * tangent));
+            canvas_draw_line(
+                canvas,
+                min(pin_center_px - pin_step_px + pin_half_width_px + last_depth_px,
+                    down_slope_start_x_px),
+                top_contour_px,
+                down_slope_start_x_px,
+                top_contour_px);
+        }
+        if((current_depth + next_depth) > my_format.clearance) { // yes
+            // intersection
+            double numerator = (double)current_depth;
+            double denominator = (double)(current_depth + next_depth);
+            double product = (numerator / denominator) * pin_step_px;
+            post_extra_x_px =
+                (int)min(max(product, pin_half_width_px), pin_step_px - pin_half_width_px);
+            canvas_draw_line(
+                canvas,
+                pin_center_px + pin_half_width_px,
+                top_contour_px + current_depth_px,
+                pin_center_px + post_extra_x_px,
+                top_contour_px +
+                    max(current_depth_px -
+                            (int)round((post_extra_x_px - pin_half_width_px) * tangent),
+                        0));
+        } else { // no intersection
+            canvas_draw_line(
+                canvas,
+                pin_center_px + pin_half_width_px,
+                top_contour_px + (int)round(current_depth_px * tangent),
+                pin_center_px + pin_half_width_px + current_depth_px,
+                top_contour_px);
+        }
+    }
+
+    int elbow_px = (int)round(my_format.elbow_inch / inches_per_px);
+    canvas_draw_line(canvas, level_contour_px, 62, level_contour_px + elbow_px, 62 - elbow_px);
+    canvas_draw_line(canvas, 0, top_contour_px - 6, 0, top_contour_px);
+    if(my_format.stop == 2) {
+        // Draw a line using level_contour_px if stop equals 2 elbow must be firt pin inch
+        canvas_draw_line(canvas, level_contour_px, top_contour_px, level_contour_px, 63);
+        //  } else {
+        // Otherwise, draw a default line
+        //    canvas_draw_line(canvas, 0, top_contour_px, 0, 63); // too confusing but may want later
+    }
+
+    int slc_pin_px = (int)round(
+        (my_format.first_pin_inch + (my_model->pin_slc - 1) * my_format.pin_increment_inch) /
+        inches_per_px);
+    canvas_draw_icon(canvas, slc_pin_px - 2, top_contour_px - 25, &I_arrow_down);
+
+    furi_string_printf(buffer, "%s", my_format.format_name);
+    canvas_draw_str(canvas, 100, 10, furi_string_get_cstr(buffer));
+    furi_string_free(buffer);
+}
+
+static bool key_copier_view_measure_input_callback(InputEvent* event, void* context) {
+    KeyCopierApp* app = (KeyCopierApp*)context;
+    if(event->type == InputTypeShort) {
+        switch(event->key) {
+        case InputKeyLeft: {
+            bool redraw = true;
+            with_view_model(
+                app->view_measure,
+                KeyCopierModel * model,
+                {
+                    if(model->pin_slc > 1) {
+                        model->pin_slc--;
+                    }
+                },
+                redraw);
+            break;
+        }
+        case InputKeyRight: {
+            bool redraw = true;
+            with_view_model(
+                app->view_measure,
+                KeyCopierModel * model,
+                {
+                    if(model->pin_slc < model->format.pin_num) {
+                        model->pin_slc++;
+                    }
+                },
+                redraw);
+            break;
+        }
+        case InputKeyUp: {
+            bool redraw = true;
+            with_view_model(
+                app->view_measure,
+                KeyCopierModel * model,
+                {
+                    if(model->depth[model->pin_slc - 1] > model->format.min_depth_ind) {
+                        if(model->pin_slc == 1) { // first pin only limited by the next one
+                            if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
+                               model->format.macs)
+                                model->depth[model->pin_slc - 1]--;
+                        } else if(model->pin_slc == model->format.pin_num) { // last pin only limited by
+                            // the previous one
+                            if(model->depth[model->pin_slc - 2] -
+                                   model->depth[model->pin_slc - 1] <
+                               model->format.macs) {
+                                model->depth[model->pin_slc - 1]--;
+                            }
+                        } else {
+                            if(model->depth[model->pin_slc] - model->depth[model->pin_slc - 1] <
+                                   model->format.macs &&
+                               model->depth[model->pin_slc - 2] -
+                                       model->depth[model->pin_slc - 1] <
+                                   model->format.macs) {
+                                model->depth[model->pin_slc - 1]--;
+                            }
+                        }
+                    }
+                },
+                redraw);
+            break;
+        }
+        case InputKeyDown: {
+            bool redraw = true;
+            with_view_model(
+                app->view_measure,
+                KeyCopierModel * model,
+                {
+                    if(model->depth[model->pin_slc - 1] < model->format.max_depth_ind) {
+                        if(model->pin_slc == 1) { // first pin only limited by the next one
+                            if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
+                               model->format.macs)
+                                model->depth[model->pin_slc - 1]++;
+                        } else if(model->pin_slc == model->format.pin_num) { // last pin only limited by
+                            // the previous one
+                            if(model->depth[model->pin_slc - 1] -
+                                   model->depth[model->pin_slc - 2] <
+                               model->format.macs) {
+                                model->depth[model->pin_slc - 1]++;
+                            }
+                        } else {
+                            if(model->depth[model->pin_slc - 1] - model->depth[model->pin_slc] <
+                                   model->format.macs &&
+                               model->depth[model->pin_slc - 1] -
+                                       model->depth[model->pin_slc - 2] <
+                                   model->format.macs) {
+                                model->depth[model->pin_slc - 1]++;
+                            }
+                        }
+                    }
+                },
+                redraw);
+            break;
+        }
+        default:
+            // Handle other keys or do nothing
+            break;
+        }
+    }
+
+    return false;
+}
+
+static KeyCopierApp* key_copier_app_alloc() {
+    KeyCopierApp* app = (KeyCopierApp*)malloc(sizeof(KeyCopierApp));
+
+    Gui* gui = furi_record_open(RECORD_GUI);
+
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_alloc();
+    app->submenu = submenu_alloc();
+    submenu_add_item(
+        app->submenu, "Measure", KeyCopierSubmenuIndexMeasure, key_copier_submenu_callback, app);
+    submenu_add_item(
+        app->submenu, "Config", KeyCopierSubmenuIndexConfigure, key_copier_submenu_callback, app);
+    submenu_add_item(
+        app->submenu, "Save", KeyCopierSubmenuIndexSave, key_copier_submenu_callback, app);
+    submenu_add_item(
+        app->submenu, "Load", KeyCopierSubmenuIndexLoad, key_copier_submenu_callback, app);
+    submenu_add_item(
+        app->submenu, "About", KeyCopierSubmenuIndexAbout, key_copier_submenu_callback, app);
+    view_set_previous_callback(
+        submenu_get_view(app->submenu), key_copier_navigation_exit_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, KeyCopierViewSubmenu, submenu_get_view(app->submenu));
+    view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu);
+
+    app->text_input = text_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, KeyCopierViewTextInput, text_input_get_view(app->text_input));
+    app->temp_buffer_size = 32;
+    app->temp_buffer = (char*)malloc(app->temp_buffer_size);
+    app->temp_buffer = "";
+
+    app->view_measure = view_alloc();
+    view_set_draw_callback(app->view_measure, key_copier_view_measure_draw_callback);
+    view_set_input_callback(app->view_measure, key_copier_view_measure_input_callback);
+    view_set_previous_callback(app->view_measure, key_copier_navigation_submenu_callback);
+    view_set_context(app->view_measure, app);
+    view_allocate_model(app->view_measure, ViewModelTypeLockFree, sizeof(KeyCopierModel));
+    KeyCopierModel* model = view_get_model(app->view_measure);
+
+    initialize_model(model);
+    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewMeasure, app->view_measure);
+
+    app->variable_item_list_config = variable_item_list_alloc();
+    app->view_config_e = view_alloc();
+    view_set_context(app->view_config_e, app);
+    view_set_previous_callback(app->view_config_e, key_copier_navigation_submenu_callback);
+    view_set_enter_callback(app->view_config_e, key_copier_config_enter_callback);
+    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_e, app->view_config_e);
+
+    View* view_buffer = view_alloc();
+    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewConfigure_i, view_buffer);
+
+    app->view_save = view_alloc();
+    view_set_context(app->view_save, app);
+    view_set_enter_callback(app->view_save, key_copier_view_save_callback);
+    view_set_previous_callback(app->view_save, key_copier_navigation_submenu_callback);
+    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewSave, app->view_save);
+
+    app->view_load = view_alloc();
+    view_set_context(app->view_load, app);
+    view_set_enter_callback(app->view_load, key_copier_view_load_callback);
+    view_set_previous_callback(app->view_load, key_copier_navigation_submenu_callback);
+    view_dispatcher_add_view(app->view_dispatcher, KeyCopierViewLoad, app->view_load);
+
+    app->widget_about = widget_alloc();
+    widget_add_text_scroll_element(
+        app->widget_about,
+        0,
+        0,
+        128,
+        64,
+        "Key Maker App 1.1\nAuthor: @Torron\n\nTo measure your key:\n\n1. Place "
+        "it on top of the screen.\n\n2. Use the contour to align your key.\n\n3. "
+        "Adjust each pin's depth until they match. It's easier if you look with "
+        "one eye closed.\n\nGithub: github.com/zinongli/KeyCopier \n\nSpecial "
+        "thanks to Derek Jamison's Skeleton App Template.");
+    view_set_previous_callback(
+        widget_get_view(app->widget_about), key_copier_navigation_submenu_callback);
+    view_dispatcher_add_view(
+        app->view_dispatcher, KeyCopierViewAbout, widget_get_view(app->widget_about));
+
+    app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+#ifdef BACKLIGHT_ON
+    notification_message(app->notifications, &sequence_display_backlight_enforce_on);
+#endif
+
+    return app;
+}
+
+static void key_copier_app_free(KeyCopierApp* app) {
+#ifdef BACKLIGHT_ON
+    notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
+#endif
+    furi_record_close(RECORD_NOTIFICATION);
+
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewTextInput);
+    text_input_free(app->text_input);
+    free(app->temp_buffer);
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewAbout);
+    widget_free(app->widget_about);
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewMeasure);
+    with_view_model(
+        app->view_measure,
+        KeyCopierModel * model,
+        {
+            if(model->depth != NULL) {
+                free(model->depth);
+            }
+        },
+        false);
+    view_free(app->view_measure);
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_e);
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewConfigure_i);
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSave);
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewLoad);
+    variable_item_list_free(app->variable_item_list_config);
+    view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSubmenu);
+    submenu_free(app->submenu);
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+
+    free(app);
+}
+
+int32_t main_key_copier_app(void* _p) {
+    UNUSED(_p);
+
+    KeyCopierApp* app = key_copier_app_alloc();
+    view_dispatcher_run(app->view_dispatcher);
+
+    key_copier_app_free(app);
+    return 0;
+}

+ 400 - 44
key_formats.c

@@ -1,44 +1,400 @@
-#include "key_formats.h"
-// all lengths in inches since it's all American formats
-// angle is in degrees
-const KeyFormat all_formats[] = {
-    {
-    .manufacturer = "Kwikset",
-    .format_name = "KW1",
-    .format_link = "https://lsamichigan.org/Tech/Kwikset_KeySpecs.pdf",
-    .first_pin_inch = 0.247,
-    .last_pin_inch = 0.847,
-    .pin_increment_inch = 0.15,
-    .pin_num = 5,
-    .pin_width_inch = 0.084,
-    .elbow_inch = 0.15,
-    .drill_angle = 90,
-    .uncut_depth_inch = 0.329,
-    .deepest_depth_inch = 0.191,
-    .depth_step_inch = 0.023,
-    .min_depth_ind = 1,
-    .max_depth_ind = 7,
-    .macs = 4,
-    .clearance = 3
-    },
-
-    {
-    .manufacturer = "Schlage",
-    .format_name = "SC4",
-    .format_link = "https://lsamichigan.org/Tech/SCHLAGE_KeySpecs.pdf",
-    .first_pin_inch = 0.231,
-    .last_pin_inch = 1.012,
-    .pin_increment_inch = 0.1562,
-    .pin_num = 6,
-    .pin_width_inch = 0.031,
-    .elbow_inch = 0.1,
-    .drill_angle = 90, // This should actually be 100 but the current resolution will make 100 degrees very ugly and unsuable
-    .uncut_depth_inch = 0.335,
-    .deepest_depth_inch = 0.2,
-    .depth_step_inch = 0.015,
-    .min_depth_ind = 0,
-    .max_depth_ind = 9,
-    .macs = 7,
-    .clearance = 8
-    }
-};
+#include "key_formats.h"
+// all lengths in inches since it's all American formats
+// angle is in degrees
+const KeyFormat all_formats[] = {
+    {.manufacturer = "Kwikset",
+     .format_name = "KW1",
+     .format_link = "https://lsamichigan.org/Tech/Kwikset_KeySpecs.pdf",
+     .first_pin_inch = 0.247,
+     .last_pin_inch = 0.847,
+     .pin_increment_inch = 0.15,
+     .pin_num = 5,
+     .pin_width_inch = 0.084,
+     .elbow_inch = 0.15,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.329,
+     .deepest_depth_inch = 0.191,
+     .depth_step_inch = 0.023,
+     .min_depth_ind = 1,
+     .max_depth_ind = 7,
+     .macs = 4,
+     .clearance = 3},
+
+    {.manufacturer = "Schlage",
+     .format_name = "SC4",
+     .format_link = "https://lsamichigan.org/Tech/SCHLAGE_KeySpecs.pdf",
+     .first_pin_inch = 0.231,
+     .last_pin_inch = 1.012,
+     .pin_increment_inch = 0.1562,
+     .pin_num = 6,
+     .pin_width_inch = 0.031,
+     .elbow_inch = 0.1,
+     .drill_angle = 90, // This should actually be 100 but the current resolution will make
+     // 100 degrees very ugly and unsuable
+     .uncut_depth_inch = 0.335,
+     .deepest_depth_inch = 0.2,
+     .depth_step_inch = 0.015,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 7,
+     .clearance = 8},
+
+    {.manufacturer = "Arrow",
+     .format_name = "AR4",
+     .format_link = "C2",
+     .first_pin_inch = 0.265,
+     .last_pin_inch = 1.040,
+     .pin_increment_inch = 0.155,
+     .pin_num = 6,
+     .pin_width_inch = 0.060,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.312,
+     .deepest_depth_inch = 0.186,
+     .depth_step_inch = 0.014,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 6,
+     .clearance = 7},
+
+    {.manufacturer = "Master Lock",
+     .format_name = "M1",
+     .format_link = "C35",
+     .first_pin_inch = 0.185,
+     .last_pin_inch = 0.689,
+     .pin_increment_inch = 0.126,
+     .pin_num = 5,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.276,
+     .deepest_depth_inch = 0.171,
+     .depth_step_inch = 0.015,
+     .min_depth_ind = 0,
+     .max_depth_ind = 7,
+     .macs = 7,
+     .clearance = 6},
+
+    {.manufacturer = "American",
+     .format_name = "AM7",
+     .format_link = "C80",
+     .first_pin_inch = 0.157,
+     .last_pin_inch = 0.781,
+     .pin_increment_inch = 0.125,
+     .pin_num = 6,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.283,
+     .deepest_depth_inch = 0.173,
+     .depth_step_inch = 0.016,
+     .min_depth_ind = 1,
+     .max_depth_ind = 8,
+     .macs = 7,
+     .clearance = 5},
+
+    {.manufacturer = "Yale",
+     .format_name = "Y2",
+     .format_link = ".025",
+     .first_pin_inch = 0.250,
+     .last_pin_inch = 1.095,
+     .pin_increment_inch = 0.185,
+     .pin_num = 6,
+     .pin_width_inch = 0.054,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.320,
+     .deepest_depth_inch = 0.145,
+     .depth_step_inch = 0.025,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 9,
+     .clearance = 4},
+
+    {.manufacturer = "Yale",
+     .format_name = "Y11",
+     .format_link = "CX55",
+     .first_pin_inch = 0.124,
+     .last_pin_inch = 0.502,
+     .pin_increment_inch = 0.095,
+     .pin_num = 5,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.246,
+     .deepest_depth_inch = 0.167,
+     .depth_step_inch = 0.020,
+     .min_depth_ind = 1,
+     .max_depth_ind = 5,
+     .macs = 7,
+     .clearance = 3},
+
+    {.manufacturer = "Sargent",
+     .format_name = "S22",
+     .format_link = "C44",
+     .first_pin_inch = 0.216,
+     .last_pin_inch = 0.996,
+     .pin_increment_inch = 0.156,
+     .pin_num = 6,
+     .pin_width_inch = 0.063,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.328, // double check
+     .deepest_depth_inch = 0.148,
+     .depth_step_inch = 0.020,
+     .min_depth_ind = 1,
+     .max_depth_ind = 10,
+     .macs = 7,
+     .clearance = 5},
+
+    {.manufacturer = "National",
+     .format_name = "NA25",
+     .format_link = "C40",
+     .first_pin_inch = 0.250,
+     .last_pin_inch = 0.874,
+     .pin_increment_inch = 0.156,
+     .pin_num = 5,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.304,
+     .deepest_depth_inch = 0.191,
+     .depth_step_inch = 0.012,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 7,
+     .clearance = 8},
+
+    {.manufacturer = "Corbin",
+     .format_name = "CO88",
+     .format_link = "C14",
+     .first_pin_inch = 0.250,
+     .last_pin_inch = 1.030,
+     .pin_increment_inch = 0.156,
+     .pin_num = 6,
+     .pin_width_inch = 0.047,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.343,
+     .deepest_depth_inch = 0.217,
+     .depth_step_inch = 0.014,
+     .min_depth_ind = 1,
+     .max_depth_ind = 10,
+     .macs = 7,
+     .clearance = 8},
+
+    {.manufacturer = "Lockwood",
+     .format_name = "LW4",
+     .format_link = "",
+     .first_pin_inch = 0.245,
+     .last_pin_inch = 0.870,
+     .pin_increment_inch = 0.1562,
+     .pin_num = 5,
+     .pin_width_inch = 0.031,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.344,
+     .deepest_depth_inch = 0.203,
+     .depth_step_inch = 0.014,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 9,
+     .clearance = 8},
+
+    {.manufacturer = "Lockwood",
+     .format_name = "LW5",
+     .format_link = "",
+     .first_pin_inch = 0.245,
+     .last_pin_inch = 1.0262,
+     .pin_increment_inch = 0.1562,
+     .pin_num = 6,
+     .pin_width_inch = 0.031,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.344,
+     .deepest_depth_inch = 0.203,
+     .depth_step_inch = 0.014,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 9,
+     .clearance = 8},
+
+    {.manufacturer = "National",
+     .format_name = "NA12",
+     .format_link = "C39",
+     .first_pin_inch = 0.150,
+     .last_pin_inch = 0.710,
+     .pin_increment_inch = 0.140,
+     .pin_num = 5,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.270,
+     .deepest_depth_inch = 0.157,
+     .depth_step_inch = 0.013,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 7,
+     .clearance = 8},
+
+    {.manufacturer = "Russwin",
+     .format_name = "RU45",
+     .format_link = "CX6",
+     .first_pin_inch = 0.250,
+     .last_pin_inch = 1.030,
+     .pin_increment_inch = 0.156,
+     .pin_num = 6,
+     .pin_width_inch = 0.053,
+     .elbow_inch = 0.1,
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.343,
+     .deepest_depth_inch = 0.203,
+     .depth_step_inch = 0.028,
+     .min_depth_ind = 1,
+     .max_depth_ind = 6,
+     .macs = 5,
+     .clearance = 3},
+
+    {.manufacturer = "Ford",
+     .format_name = "H75",
+     .sides = 2,
+     .stop = 2,
+     .format_link = "CX101",
+     .first_pin_inch = 0.201,
+     .last_pin_inch = 0.845,
+     .pin_increment_inch = 0.092,
+     .pin_num = 8,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.201, // this should be equal to first pin inch for tip
+     // stopped key line
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.354,
+     .deepest_depth_inch = 0.254,
+     .depth_step_inch = 0.025,
+     .min_depth_ind = 1,
+     .max_depth_ind = 5,
+     .macs = 5,
+     .clearance = 2},
+
+    {.manufacturer = "Chevrolet",
+     .format_name = "B102",
+     .sides = 2,
+     .stop = 2,
+     .format_link = "",
+     .first_pin_inch = 0.205,
+     .last_pin_inch = 1.037,
+     .pin_increment_inch = 0.093,
+     .pin_num = 10,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.205, // this should be equal to first pin inch for tip
+     // stopped key line
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.315,
+     .deepest_depth_inch = 0.161,
+     .depth_step_inch = 0.026,
+     .min_depth_ind = 1,
+     .max_depth_ind = 4,
+     .macs = 5,
+     .clearance = 2},
+
+    {.manufacturer = "Dodge",
+     .format_name = "Y159",
+     .sides = 2,
+     .stop = 2,
+     .format_link = "CX102",
+     .first_pin_inch = 0.297,
+     .last_pin_inch = 0.941,
+     .pin_increment_inch = 0.092,
+     .pin_num = 8,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.297, // this should be equal to first pin inch for tip
+     // stopped key line
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.339,
+     .deepest_depth_inch = 0.197,
+     .depth_step_inch = 0.047,
+     .min_depth_ind = 1,
+     .max_depth_ind = 4,
+     .macs = 5,
+     .clearance = 1},
+
+    {.manufacturer = "Kawasaki",
+     .format_name = "KA14",
+     .sides = 2,
+     .format_link = "CMC50",
+     .first_pin_inch = 0.098,
+     .last_pin_inch = 0.591,
+     .pin_increment_inch = 0.098,
+     .pin_num = 6,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.1, // this should be equal to first pin inch for tip
+     // stopped key line
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.258,
+     .deepest_depth_inch = 0.198,
+     .depth_step_inch = 0.020,
+     .min_depth_ind = 1,
+     .max_depth_ind = 4,
+     .macs = 4,
+     .clearance = 3},
+
+    {.manufacturer = "Yamaha",
+     .format_name = "YM63",
+     .sides = 2,
+     .format_link = "CMC71",
+     .first_pin_inch = 0.157,
+     .last_pin_inch = 0.748,
+     .pin_increment_inch = 0.098,
+     .pin_num = 7,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.1, // this should be equal to first pin inch for tip
+     // stopped key line
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.295,
+     .deepest_depth_inch = 0.236,
+     .depth_step_inch = 0.020,
+     .min_depth_ind = 1,
+     .max_depth_ind = 4,
+     .macs = 4,
+     .clearance = 3},
+
+    {.manufacturer = "Best (A2)",
+     .format_name = "SFIC",
+     .sides = 2,
+     .stop = 2,
+     .format_link = "C3",
+     .first_pin_inch = 0.170,
+     .last_pin_inch = 1.067,
+     .pin_increment_inch = 0.149,
+     .pin_num = 6,
+     .pin_width_inch = 0.051,
+     .elbow_inch = 0.081, // this should be equal to first pin inch for tip
+     // stopped key line
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.318,
+     .deepest_depth_inch = 0.206,
+     .depth_step_inch = 0.025,
+     .min_depth_ind = 0,
+     .max_depth_ind = 9,
+     .macs = 5,
+     .clearance = 3},
+
+    {.manufacturer = "RV (FIC,GL,Bauer)",
+     .format_name = "RV",
+     .sides = 2,
+     .format_link = "Card",
+     .first_pin_inch = 0.126,
+     .last_pin_inch = 0.504,
+     .pin_increment_inch = 0.094,
+     .pin_num = 5,
+     .pin_width_inch = 0.039,
+     .elbow_inch = 0.126, // this should be equal to first pin inch for tip
+     // stopped key line
+     .drill_angle = 90,
+     .uncut_depth_inch = 0.260,
+     .deepest_depth_inch = 0.181,
+     .depth_step_inch = 0.040,
+     .min_depth_ind = 1,
+     .max_depth_ind = 3,
+     .macs = 3,
+     .clearance = 1}};

+ 32 - 31
key_formats.h

@@ -1,31 +1,32 @@
-#ifndef KEY_FORMATS_H
-#define KEY_FORMATS_H
-
-#define FORMAT_NUM 2
-
-typedef struct {
-    char* manufacturer; 
-    char* format_name;
-    char* format_link;
-
-    double first_pin_inch;
-    double last_pin_inch;
-    double pin_increment_inch;
-    int pin_num;
-    double pin_width_inch;
-    double drill_angle;
-    double elbow_inch;
-
-    double uncut_depth_inch;
-    double deepest_depth_inch;
-    double depth_step_inch;
-    int min_depth_ind;
-    int max_depth_ind;
-
-    int macs;
-    int clearance;
-} KeyFormat;
-
-extern const KeyFormat all_formats[FORMAT_NUM];
-
-#endif // KEY_FORMATS_H
+#ifndef KEY_FORMATS_H
+#define KEY_FORMATS_H
+
+#define FORMAT_NUM 21
+
+typedef struct {
+    char* manufacturer;
+    char* format_name;
+    char* format_link;
+    int sides;
+    int stop;
+    double first_pin_inch;
+    double last_pin_inch;
+    double pin_increment_inch;
+    int pin_num;
+    double pin_width_inch;
+    double drill_angle;
+    double elbow_inch;
+
+    double uncut_depth_inch;
+    double deepest_depth_inch;
+    double depth_step_inch;
+    int min_depth_ind;
+    int max_depth_ind;
+
+    int macs;
+    int clearance;
+} KeyFormat;
+
+extern const KeyFormat all_formats[FORMAT_NUM];
+
+#endif // KEY_FORMATS_H