diff --git a/CHANGELOG.md b/CHANGELOG.md index cfbfc39..b20626d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ +## 1.5 +* Added Suzuki SUZ18 key format support by @RIcePatrol + +## 1.4 +* Added Weiser WR3 key format support by @lightos + +## 1.3 +* A new UI/workflow for key format selection by @Offreds's PR +* Added QR code directing user to @TalkingSasquach's awesome video from decoding keys to 3D-printing copies. + +## 1.2 +Bug fixes + ## 1.1 -## What's Changed * Support for double sided key and multiple new key formats by @HonestLocksmith * New formats: Manufacturer-Format Name-Data Sheet(if applicable) diff --git a/README.md b/README.md index 17468a1..e471cd0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # Key Copier App A Flipper Zero app for measuring key bitting patterns. +## Installation +The best way to install the latest build would be download it from Flipper Zero's Official App Store: +https://lab.flipper.net/apps/key_copier + ## Instruction To measure your key: 1. Place it on top of the screen. @@ -10,9 +14,11 @@ 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! +- Thank [@Offreds](https://github.com/Offreds) for PR #32. We now have a more streamlined workflow for key format selection. +- Thank [@lightos](https://github.com/lightos) for PR #36. Weiser WR3 key format was added. +- Thank [@RIcePatrol](https://github.com/RIcePatrol) for PR #37. Suzuki SUZ18 key format was added. - Hey! We are on [Adam Savage's show](https://youtu.be/c8q2YVRiOAE?t=485)! Thanks for featuring my app! -- [Project channel](https://discord.com/channels/1112390971250974782/1264067969634402356) - +- We also made it to @TalkingSasquach's YouTube! He's made an awesome walkthrough from decoding the key bitting to 3D-printing copies. [Check it out!](https://www.youtube.com/watch?v=P3-KhSJE1as) diff --git a/application.fam b/application.fam index 9e1c81d..ab46b67 100644 --- a/application.fam +++ b/application.fam @@ -12,6 +12,6 @@ App( fap_category="Tools", fap_icon_assets="assets", fap_description="@README.md", - fap_version="1.1", + fap_version="1.5", fap_author="Torron" ) diff --git a/assets/QR_Code.png b/assets/QR_Code.png new file mode 100644 index 0000000..5997b8a Binary files /dev/null and b/assets/QR_Code.png differ diff --git a/key_copier.c b/key_copier.c index d16e199..7cc0462 100644 --- a/key_copier.c +++ b/key_copier.c @@ -1,75 +1,12 @@ #include "key_copier.h" -#include "key_copier_icons.h" -#include "key_formats.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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; - VariableItem* format_name_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 exit_widget_callback(GuiButtonType result, InputType type, void* context) { + KeyCopierApp* app = context; + UNUSED(result); + if(type == InputTypeShort) { + view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewSubmenu); + } +} void initialize_model(KeyCopierModel* model) { if(model->depth != NULL) { @@ -96,6 +33,11 @@ static uint32_t key_copier_navigation_submenu_callback(void* _context) { return KeyCopierViewSubmenu; } +static uint32_t key_copier_navigation_manufacturer_list_callback(void* _context) { + UNUSED(_context); + return KeyCopierViewManufacturerList; +} + static void key_copier_submenu_callback(void* context, uint32_t index) { KeyCopierApp* app = (KeyCopierApp*)context; switch(index) { @@ -114,6 +56,9 @@ static void key_copier_submenu_callback(void* context, uint32_t index) { case KeyCopierSubmenuIndexAbout: view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewAbout); break; + case KeyCopierSubmenuIndexQRCode: + view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewQRCode); + break; default: break; } @@ -127,56 +72,41 @@ void initialize_manufacturers(char** manufacturers) { } } -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); - variable_item_set_current_value_text(app->format_name_item, model->format.manufacturer); - model->format = all_formats[model->format_index]; -} +static void manufacturer_selected_callback(void* context, uint32_t index); +static void format_selected_callback(void* context, uint32_t index); -static const char* format_config_label = "Key Format"; -static const char* format_name_config_label = "Brand"; 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); - app->format_name_item = variable_item_list_add( - app->variable_item_list_config, format_name_config_label, 0, NULL, NULL); - 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); - variable_item_set_current_value_text(app->format_name_item, my_model->format.manufacturer); - 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 + // Clear manufacturer list + submenu_reset(app->manufacturer_list); + + // Track added manufacturers to avoid duplicates + char* added_manufacturers[COUNT_OF(all_formats)]; + size_t added_count = 0; + + // Add unique manufacturers + for(size_t i = 0; i < COUNT_OF(all_formats); i++) { + bool already_added = false; + for(size_t j = 0; j < added_count; j++) { + if(strcmp(all_formats[i].manufacturer, added_manufacturers[j]) == 0) { + already_added = true; + break; + } + } + + if(!already_added) { + submenu_add_item( + app->manufacturer_list, + all_formats[i].manufacturer, + i, + manufacturer_selected_callback, + app); + added_manufacturers[added_count++] = all_formats[i].manufacturer; + } + } + + view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewManufacturerList); } static const char* key_name_entry_text = "Enter name"; @@ -335,7 +265,7 @@ static void key_copier_view_measure_draw_callback(Canvas* canvas, void* model) { 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); + (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; @@ -664,6 +594,43 @@ static bool key_copier_view_measure_input_callback(InputEvent* event, void* cont return false; } +static void manufacturer_selected_callback(void* context, uint32_t index) { + KeyCopierApp* app = context; + app->selected_manufacturer = all_formats[index].manufacturer; + + // Clear and populate format list for selected manufacturer + submenu_reset(app->format_list); + + // Add all formats for this manufacturer + for(size_t i = 0; i < COUNT_OF(all_formats); i++) { + if(strcmp(all_formats[i].manufacturer, app->selected_manufacturer) == 0) { + submenu_add_item( + app->format_list, all_formats[i].format_name, i, format_selected_callback, app); + } + } + + view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewFormatList); +} + +static void format_selected_callback(void* context, uint32_t index) { + KeyCopierApp* app = context; + KeyCopierModel* model = view_get_model(app->view_measure); + + model->format_index = index; + model->format = all_formats[index]; + if(model->depth != NULL) { + free(model->depth); + } + model->depth = 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; + + view_dispatcher_switch_to_view(app->view_dispatcher, KeyCopierViewMeasure); +} + static KeyCopierApp* key_copier_app_alloc() { KeyCopierApp* app = (KeyCopierApp*)malloc(sizeof(KeyCopierApp)); @@ -675,10 +642,10 @@ static KeyCopierApp* key_copier_app_alloc() { app->dialogs = furi_record_open(RECORD_DIALOGS); app->file_path = furi_string_alloc(); app->submenu = submenu_alloc(); - submenu_set_header(app->submenu, "Key Copier v1.1"); + submenu_set_header(app->submenu, "Key Copier v1.5"); submenu_add_item( app->submenu, - "Select Template", + "Select Key Format", KeyCopierSubmenuIndexConfigure, key_copier_submenu_callback, app); @@ -690,6 +657,13 @@ static KeyCopierApp* key_copier_app_alloc() { app->submenu, "Load", KeyCopierSubmenuIndexLoad, key_copier_submenu_callback, app); submenu_add_item( app->submenu, "Help", KeyCopierSubmenuIndexAbout, key_copier_submenu_callback, app); + submenu_add_item( + app->submenu, + "Video Instruction", + KeyCopierSubmenuIndexQRCode, + key_copier_submenu_callback, + app); + view_set_previous_callback( submenu_get_view(app->submenu), key_copier_navigation_exit_callback); view_dispatcher_add_view( @@ -701,7 +675,6 @@ static KeyCopierApp* key_copier_app_alloc() { 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); @@ -743,7 +716,7 @@ static KeyCopierApp* key_copier_app_alloc() { 0, 128, 64, - "Key Maker App 1.1\nAuthor: @Torron\n\nTo measure your key:\n\n1. Place " + "Key Maker App 1.5\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 " @@ -753,10 +726,43 @@ static KeyCopierApp* key_copier_app_alloc() { view_dispatcher_add_view( app->view_dispatcher, KeyCopierViewAbout, widget_get_view(app->widget_about)); + app->widget_qr_code = widget_alloc(); + widget_add_icon_element(app->widget_qr_code, 92, 7, &I_QR_Code); + widget_add_string_element( + app->widget_qr_code, 0, 10, AlignLeft, AlignBottom, FontSecondary, "Check out"); + widget_add_string_element( + app->widget_qr_code, 0, 23, AlignLeft, AlignBottom, FontSecondary, "@TalkingSasquach's"); + widget_add_string_element( + app->widget_qr_code, 0, 36, AlignLeft, AlignBottom, FontSecondary, "video from decoding"); + widget_add_string_element( + app->widget_qr_code, 0, 49, AlignLeft, AlignBottom, FontSecondary, "a key to eventually"); + widget_add_string_element( + app->widget_qr_code, 0, 62, AlignLeft, AlignBottom, FontSecondary, "3D-printing a copy!"); + widget_add_button_element( + app->widget_qr_code, GuiButtonTypeRight, "Back", exit_widget_callback, app); + view_set_previous_callback( + widget_get_view(app->widget_qr_code), key_copier_navigation_submenu_callback); + view_dispatcher_add_view( + app->view_dispatcher, KeyCopierViewQRCode, widget_get_view(app->widget_qr_code)); + + app->manufacturer_list = submenu_alloc(); + view_set_previous_callback( + submenu_get_view(app->manufacturer_list), key_copier_navigation_submenu_callback); + view_dispatcher_add_view( + app->view_dispatcher, + KeyCopierViewManufacturerList, + submenu_get_view(app->manufacturer_list)); + + app->format_list = submenu_alloc(); + view_set_previous_callback( + submenu_get_view(app->format_list), key_copier_navigation_manufacturer_list_callback); + view_dispatcher_add_view( + app->view_dispatcher, KeyCopierViewFormatList, submenu_get_view(app->format_list)); + app->notifications = furi_record_open(RECORD_NOTIFICATION); #ifdef BACKLIGHT_ON - notification_message(app->notifications, &sequence_display_backlight_enforce_on); + notification_message(app->notifications, &sequence_display_backlight_on); #endif return app; @@ -773,6 +779,8 @@ static void key_copier_app_free(KeyCopierApp* app) { free(app->temp_buffer); view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewAbout); widget_free(app->widget_about); + view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewQRCode); + widget_free(app->widget_qr_code); view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewMeasure); with_view_model( app->view_measure, @@ -791,6 +799,10 @@ static void key_copier_app_free(KeyCopierApp* app) { variable_item_list_free(app->variable_item_list_config); view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewSubmenu); submenu_free(app->submenu); + view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewManufacturerList); + submenu_free(app->manufacturer_list); + view_dispatcher_remove_view(app->view_dispatcher, KeyCopierViewFormatList); + submenu_free(app->format_list); view_dispatcher_free(app->view_dispatcher); furi_record_close(RECORD_GUI); diff --git a/key_copier.h b/key_copier.h index c2c8154..5411392 100644 --- a/key_copier.h +++ b/key_copier.h @@ -1,5 +1,84 @@ +#include "key_copier_icons.h" +#include "key_formats.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TAG "KeyCopier" + +#define BACKLIGHT_ON 1 #define KEY_COPIER_FILE_EXTENSION ".keycopy" -#define INCHES_PER_PX 0.00978 +#define INCHES_PER_PX 0.00978 + +typedef enum { + KeyCopierSubmenuIndexMeasure, + KeyCopierSubmenuIndexConfigure, + KeyCopierSubmenuIndexSave, + KeyCopierSubmenuIndexLoad, + KeyCopierSubmenuIndexAbout, + KeyCopierSubmenuIndexQRCode, +} KeyCopierSubmenuIndex; + +typedef enum { + KeyCopierViewSubmenu, + KeyCopierViewTextInput, + KeyCopierViewConfigure_i, + KeyCopierViewConfigure_e, + KeyCopierViewSave, + KeyCopierViewLoad, + KeyCopierViewMeasure, + KeyCopierViewAbout, + KeyCopierViewQRCode, + KeyCopierViewManufacturerList, + KeyCopierViewFormatList, +} 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; + Widget* widget_qr_code; + VariableItem* key_name_item; + VariableItem* format_item; + VariableItem* format_name_item; + char* temp_buffer; + uint32_t temp_buffer_size; + + DialogsApp* dialogs; + FuriString* file_path; + Submenu* manufacturer_list; + Submenu* format_list; + char* selected_manufacturer; +} 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; static inline int min(int a, int b) { return (a < b) ? a : b; diff --git a/key_formats.c b/key_formats.c index cd5b632..6e76188 100644 --- a/key_formats.c +++ b/key_formats.c @@ -20,6 +20,43 @@ const KeyFormat all_formats[] = { .macs = 4, .clearance = 3}, + {.manufacturer = "Kwikset", + .format_name = "KW5", + .format_link = "https://lsamichigan.org/Tech/Kwikset_KeySpecs.pdf", + .first_pin_inch = 0.247, + .last_pin_inch = 0.997, + .pin_increment_inch = 0.15, + .pin_num = 6, + .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 = "SC1", + .format_link = "https://lsamichigan.org/Tech/SCHLAGE_KeySpecs.pdf", + .first_pin_inch = 0.231, + .last_pin_inch = 0.8558, + .pin_increment_inch = 0.1562, + .pin_num = 5, + .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 = "Schlage", .format_name = "SC4", .format_link = "https://lsamichigan.org/Tech/SCHLAGE_KeySpecs.pdf", @@ -254,6 +291,24 @@ const KeyFormat all_formats[] = { .max_depth_ind = 6, .macs = 5, .clearance = 3}, + + {.manufacturer = "Weiser", + .format_name = "WR3", + .format_link = "https://www.lockwiki.com/index.php/Weiser_Classic", + .first_pin_inch = 0.237, + .last_pin_inch = 0.861, + .pin_increment_inch = 0.156, + .pin_num = 5, + .pin_width_inch = 0.090, + .elbow_inch = 0.150, + .drill_angle = 90, + .uncut_depth_inch = 0.315, + .deepest_depth_inch = 0.153, + .depth_step_inch = 0.018, + .min_depth_ind = 0, + .max_depth_ind = 10, + .macs = 6, + .clearance = 3}, {.manufacturer = "Ford", .format_name = "H75", @@ -338,6 +393,26 @@ const KeyFormat all_formats[] = { .macs = 4, .clearance = 3}, + {.manufacturer = "Suzuki", + .format_name = "SUZ18", + .sides = 2, + .format_link = "X241", + .first_pin_inch = 0.16, + .last_pin_inch = 0.73, + .pin_increment_inch = 0.095, + .pin_num = 7, + .pin_width_inch = 0.045, + .elbow_inch = 0.1, // this should be equal to first pin inch for tip + // stopped key line + .drill_angle = 90, + .uncut_depth_inch = 0.28, + .deepest_depth_inch = 0.22, + .depth_step_inch = 0.020, + .min_depth_ind = 1, + .max_depth_ind = 4, + .macs = 4, + .clearance = 3}, + {.manufacturer = "Yamaha", .format_name = "YM63", .sides = 2, @@ -397,3 +472,4 @@ const KeyFormat all_formats[] = { .max_depth_ind = 3, .macs = 3, .clearance = 1}}; + diff --git a/key_formats.h b/key_formats.h index 67fb471..92a8de7 100644 --- a/key_formats.h +++ b/key_formats.h @@ -1,7 +1,7 @@ #ifndef KEY_FORMATS_H #define KEY_FORMATS_H -#define FORMAT_NUM 21 +#define FORMAT_NUM 25 typedef struct { char* manufacturer; @@ -30,3 +30,4 @@ typedef struct { extern const KeyFormat all_formats[FORMAT_NUM]; #endif // KEY_FORMATS_H + diff --git a/screenshots/config.png b/screenshots/config.png index aae6eab..2860e30 100644 Binary files a/screenshots/config.png and b/screenshots/config.png differ diff --git a/screenshots/main_menu.png b/screenshots/main_menu.png index 2f43ea0..b517aef 100644 Binary files a/screenshots/main_menu.png and b/screenshots/main_menu.png differ