Browse Source

Add second remotes

Willy-JL 1 year ago
parent
commit
44adb5baa5

+ 0 - 1
fmf_to_sub/.gitsubtree

@@ -1 +0,0 @@
-https://github.com/jamisonderek/flipper-zero-tutorials main subghz/fmf_to_sub

+ 0 - 64
fmf_to_sub/README.md

@@ -1,64 +0,0 @@
-# Music to Sub-GHz Radio
-
-## Overview
-
-The `Music to Sub-GHz Radio` application converts Flipper Music Files (.FMF) into a RAW .SUB file format that can be transmitted over the Sub-GHz radio! The Flipper Zero can receive the music and play it back.
-
-There are large collections of songs available for the Flipper Zero that can be converted, for example [UberGuidoZ collection](https://github.com/UberGuidoZ/Flipper/tree/main/Music_Player). The `Music to Sub-GHz Radio` application supports converting both .FMF (Flipper Music Format) files and .TXT files into the .SUB file format.
-
-To listen to the music without using the Sub-GHz radio, you can use the [Flipper Zero Music Player](https://lab.flipper.net/apps/music_player) application.
-
-## How to convert music
-
-1. Download [Music to Sub-GHz Radio](https://lab.flipper.net/apps/fmf_to_sub) from lab.flipper.net.
-2. Open the `Music to Sub-GHz Radio` application.
-3. Select the `Configure` option.
-4. Choose the `Frequency` you want to transmit on.
-5. Choose the `Modulation` you want to use (AM650 is a good default choice).
-6. Click the `Back` button.
-7. Select the `Convert` option.  This will display the number of the sub file.
-8. You can use `Left` and `Right` buttons to change the number of the sub file.
-9. Click the `OK` button and then choose the music file you want to convert.
-
-- You will see a status of "Converting..."
-- After a few seconds you should see the message "Saved in Sub-GHz folder"
-- A new file will be saved in `SD Card/subghz/` with a name like "Flip5.sub".
-
-## Send music with Flipboard Signal
-
-The easiest way to send music is to use the [Flipboard Signal application](https://lab.flipper.net/apps/flipboard_signal).
-
-1. Download [Flipboard Signal](https://lab.flipper.net/apps/flipboard_signal) from lab.flipper.net.
-2. Connect your [FlipBoard](https://github.com/makeithackin/flipboard) to your Flipper Zero.
-3. Open the `Flipboard signal` application.
-3. Choose `Start application`.
-4. Click a button (or button combination) on your FlipBoard.
-- NOTE: You may want to go to configuration and disable playing a tone (press `left` button until you get to the `Off` value).
-
-## Receive music with Flipper Zero
-
-1. Open the `Sub-GHz` application on your Flipper Zero.
-2. Choose `Read RAW`.
-3. Click `Left` to go to `Configure`.
-4. Choose the `Frequency` & `Modulation` you want to receive on.
-5. Set `Sound` to `On`.
-6. Choose `Back`.
-7. Click `OK` to start `Rec`.
-
-## Send music with Sub-GHz Radio
-
-1. Open the `Sub-GHz` application on your Flipper Zero.
-2. Choose `Read RAW`.
-3. Click `Left` to go to `Configure`.
-4. Set `Sound` to `On`.
-5. Choose `Back`.
-6. Choose `Back` to the main Sub-GHz menu.
-7. Choose `Saved`
-8. Select the `Flip#.sub` file you want to send.
-9. Click `OK` to send.
-
-## Support
-
-If you have need help, we are here for you. Also, we would love your feedback on cool ideas for future FlipBoard applications!  The best way to get support is to join the Flipper Zero Tutorials (Unofficial) Discord community. Here is a [Discord invite](https://discord.gg/KTThkQHj5B) to join my `Flipper Zero Tutorials (Unofficial)` community.
-
-If you want to support my work, you can donate via [https://ko-fi.com/codeallnight](https://ko-fi.com/codeallnight) or you can [buy a FlipBoard](https://www.tindie.com/products/makeithackin/flipboard-macropad-keyboard-for-flipper-zero/) from MakeItHackin with software & tutorials from me (@CodeAllNight).

+ 0 - 912
fmf_to_sub/app.c

@@ -1,912 +0,0 @@
-#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 <dialogs/dialogs.h>
-#include <storage/storage.h>
-#include <flipper_format.h>
-#include "fmf_to_sub_icons.h"
-
-#define TAG "FMF to SUB"
-
-#define FMF_FILE_EXTENSION ".fmf"
-
-#define FMF_LOAD_PATH     \
-    EXT_PATH("apps_data") \
-    "/"                   \
-    "music_player"
-
-// Our application menu.
-typedef enum {
-    Fmf2SubSubmenuIndexConfigure,
-    Fmf2SubSubmenuIndexConvert,
-    Fmf2SubSubmenuIndexAbout,
-} Fmf2SubSubmenuIndex;
-
-// Each view is a screen we show the user.
-typedef enum {
-    Fmf2SubViewSubmenu, // The menu when the app starts
-    Fmf2SubViewTextInput, // Input for configuring text settings
-    Fmf2SubViewConfigure, // The configuration screen
-    Fmf2SubViewConvert, // The main screen
-    Fmf2SubViewAbout, // The about screen with directions, link to social channel, etc.
-} Fmf2SubView;
-
-typedef enum {
-    Fmf2SubEventIdRedrawScreen = 0, // Custom event to redraw the screen
-    Fmf2SubEventIdOkPressed = 1, // Custom event to process OK button getting pressed down
-    Fmf2SubEventIdLoadFile = 2, // Custom event to load the file
-    Fmf2SubEventIdCreateSub = 3, // Custom event to create the sub file
-} Fmf2SubEventId;
-
-typedef struct {
-    ViewDispatcher* view_dispatcher; // Switches between our views
-    DialogsApp* dialogs; // Shows dialogs like file browser
-    Submenu* submenu; // The application menu
-    TextInput* text_input; // The text input screen
-    VariableItemList* variable_item_list_config; // The configuration screen
-    VariableItem* variable_item_button; // The button on FlipBoard
-    View* view_convert; // The main screen
-    Widget* widget_about; // The about screen
-    FuriString* file_path; // The path to the file
-    char* temp_buffer; // Temporary buffer for text input
-    uint32_t temp_buffer_size; // Size of temporary buffer
-    FuriTimer* timer; // Timer for redrawing the screen
-} Fmf2SubApp;
-
-typedef enum {
-    Fmf2SubStateIdle,
-    Fmf2SubStateLoading,
-    Fmf2SubStateConverting,
-    Fmf2SubStateConverted,
-    Fmf2SubStateError,
-} Fmf2SubState;
-
-typedef struct {
-    uint32_t bpm;
-    uint32_t duration;
-    uint32_t octave;
-    FuriString* notes;
-} Fmf2SubData;
-
-typedef struct {
-    uint8_t setting_frequency_index; // The frequency
-    uint8_t setting_modulation_index; // The modulation
-    uint8_t setting_button_index; // The button on FlipBoard
-    Fmf2SubState state; // The state of the application
-    Fmf2SubData data; // The data from the file
-} Fmf2SubConvertModel;
-
-/**
- * @brief      Callback for exiting the application.
- * @details    This function is called when user press back button.  We return VIEW_NONE to
- *            indicate that we want to exit the application.
- * @param      _context  The context - unused
- * @return     next view id (VIEW_NONE)
-*/
-static uint32_t fmf2sub_navigation_exit_callback(void* _context) {
-    UNUSED(_context);
-    return VIEW_NONE;
-}
-
-/**
- * @brief      Callback for returning to submenu.
- * @details    This function is called when user press back button.  We return ViewSubmenu to
- *            indicate that we want to navigate to the submenu.
- * @param      _context  The context - unused
- * @return     next view id (ViewSubmenu)
-*/
-static uint32_t fmf2sub_navigation_submenu_callback(void* _context) {
-    UNUSED(_context);
-    return Fmf2SubViewSubmenu;
-}
-
-/**
- * @brief      Handle submenu item selection.
- * @details    This function is called when user selects an item from the submenu.
- * @param      context  The context - Fmf2SubApp object.
- * @param      index     The Fmf2SubSubmenuIndex item that was clicked.
-*/
-static void fmf2sub_submenu_callback(void* context, uint32_t index) {
-    Fmf2SubApp* app = (Fmf2SubApp*)context;
-    switch(index) {
-    case Fmf2SubSubmenuIndexConfigure:
-        view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewConfigure);
-        break;
-    case Fmf2SubSubmenuIndexConvert:
-        view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewConvert);
-        break;
-    case Fmf2SubSubmenuIndexAbout:
-        view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewAbout);
-        break;
-    default:
-        break;
-    }
-}
-
-/**
- * Frequency settings and values.
-*/
-static const char* setting_frequency_label = "Frequency";
-static char* setting_frequency_values[] = {
-    "300000000", "302757000", "303875000", "303900000", "304250000", "307000000", "307500000",
-    "307800000", "309000000", "310000000", "312000000", "312100000", "313000000", "313850000",
-    "314000000", "314350000", "314980000", "315000000", "318000000", "330000000", "345000000",
-    "348000000", "387000000", "390000000", "418000000", "430000000", "431000000", "431500000",
-    "433075000", "433220000", "433420000", "433657070", "433889000", "433920000", "434075000",
-    "434176948", "434390000", "434420000", "434775000", "438900000", "440175000", "464000000",
-    "779000000", "868350000", "868400000", "868800000", "868950000", "906400000", "915000000",
-    "925000000", "928000000"};
-static char* setting_frequency_names[] = {
-    "300.00", "302.75", "303.88", "303.90", "304.25", "307.00", "307.50", "307.80", "309.00",
-    "310.00", "312.00", "312.10", "313.00", "313.85", "314.00", "314.35", "314.98", "315.00",
-    "318.00", "330.00", "345.00", "348.00", //
-    "387.00", "390.00", "418.00", "430.00", "431.00", "431.50", "433.07", "433.22", "433.42",
-    "433.66", "433.89", "433.92", "434.07", "434.18", "434.39", "434.42", "434.78", "438.90",
-    "440.18", "464.00", //
-    "779.00", "868.35", "868.40", "868.80", "868.95", "906.40", "915.00", "925.00", "928.00"};
-static void fmf2sub_setting_frequency_change(VariableItem* item) {
-    Fmf2SubApp* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, setting_frequency_names[index]);
-    Fmf2SubConvertModel* model = view_get_model(app->view_convert);
-    model->setting_frequency_index = index;
-}
-
-/**
- * Modulation
-*/
-static const char* setting_modulation_label = "Modulation";
-static char* setting_modulation_values[] = {
-    "FuriHalSubGhzPresetOok270Async",
-    "FuriHalSubGhzPresetOok650Async",
-    "FuriHalSubGhzPreset2FSKDev238Async",
-    "FuriHalSubGhzPreset2FSKDev476Async"};
-static char* setting_modulation_names[] = {"AM270", "AM650", "FM238", "FM476"};
-static void fmf2sub_setting_modulation_change(VariableItem* item) {
-    Fmf2SubApp* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, setting_modulation_names[index]);
-    Fmf2SubConvertModel* model = view_get_model(app->view_convert);
-    model->setting_modulation_index = index;
-}
-
-/**
- * Flipboard button
-*/
-static const char* setting_button_label = "FlipButtons";
-static char* setting_button_values[] = {
-    "Flip1.sub",
-    "Flip2.sub",
-    "Flip3.sub",
-    "Flip4.sub",
-    "Flip5.sub",
-    "Flip6.sub",
-    "Flip7.sub",
-    "Flip8.sub",
-    "Flip9.sub",
-    "Flip10.sub",
-    "Flip11.sub",
-    "Flip12.sub",
-    "Flip13.sub",
-    "Flip14.sub",
-    "Flip15.sub"};
-static char* setting_button_names[] = {
-    "1",
-    "2",
-    "1+2",
-    "3",
-    "1+3",
-    "2+3",
-    "1+2+3",
-    "4",
-    "1+4",
-    "2+4",
-    "1+2+4",
-    "3+4",
-    "1+3+4",
-    "2+3+4",
-    "All"};
-static void fmf2sub_setting_button_change(VariableItem* item) {
-    Fmf2SubApp* app = variable_item_get_context(item);
-    uint8_t index = variable_item_get_current_value_index(item);
-    variable_item_set_current_value_text(item, setting_button_names[index]);
-    Fmf2SubConvertModel* model = view_get_model(app->view_convert);
-    model->setting_button_index = index;
-}
-
-/**
- * @brief      Callback for drawing the convert screen.
- * @details    This function is called when the screen needs to be redrawn, like when the model gets updated.
- * @param      canvas  The canvas to draw on.
- * @param      model   The model - MyModel object.
-*/
-static void fmf2sub_view_convert_draw_callback(Canvas* canvas, void* model) {
-    Fmf2SubConvertModel* my_model = (Fmf2SubConvertModel*)model;
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str(canvas, 1, 10, "Press OK to select Flipper");
-    canvas_draw_str(canvas, 1, 20, "Music File (.FMF) to convert");
-    canvas_draw_str(canvas, 1, 30, "to Sub-GHz format (.SUB).");
-    canvas_draw_str(canvas, 10, 40, "FlipBoard buttons:");
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str(canvas, 90, 40, setting_button_names[my_model->setting_button_index]);
-
-    if(my_model->data.notes && my_model->state == Fmf2SubStateConverting) {
-        canvas_draw_str(canvas, 1, 50, furi_string_get_cstr(my_model->data.notes));
-    } else {
-        canvas_draw_str(canvas, 40, 50, setting_button_values[my_model->setting_button_index]);
-    }
-
-    canvas_set_font(canvas, FontPrimary);
-    if(my_model->state == Fmf2SubStateLoading) {
-        canvas_draw_str(canvas, 1, 60, "Loading...");
-    } else if(my_model->state == Fmf2SubStateError) {
-        canvas_draw_str(canvas, 1, 60, "Error!");
-    } else if(my_model->state == Fmf2SubStateConverting) {
-        canvas_draw_str(canvas, 1, 60, "Converting...");
-    } else if(my_model->state == Fmf2SubStateConverted) {
-        canvas_draw_str(canvas, 1, 60, "Saved in Sub-GHz folder");
-    } else {
-        canvas_draw_str(canvas, 1, 60, "Press OK to choose file");
-    }
-}
-
-/**
- * @brief      Callback when the user starts the convert screen.
- * @details    This function is called when the user enters the convert screen.
- * @param      context  The context - Fmf2SubApp object.
-*/
-static void fmf2sub_view_convert_enter_callback(void* context) {
-    UNUSED(context);
-}
-
-/**
- * @brief      Callback when the user exits the convert screen.
- * @details    This function is called when the user exits the convert screen.
- * @param      context  The context - Fmf2SubApp object.
-*/
-static void fmf2sub_view_convert_exit_callback(void* context) {
-    Fmf2SubApp* app = (Fmf2SubApp*)context;
-    with_view_model(
-        app->view_convert,
-        Fmf2SubConvertModel * model,
-        {
-            model->state = Fmf2SubStateIdle;
-            if(model->data.notes) {
-                furi_string_free(model->data.notes);
-                model->data.notes = NULL;
-            }
-        },
-        false);
-}
-
-static uint32_t
-    fmf2sub_extract_param(FuriString* song_settings, char param, uint32_t default_value) {
-    uint16_t value = 0;
-    char param_equal[3] = {param, '=', 0};
-    size_t index = furi_string_search_str(song_settings, param_equal);
-    if(index != FURI_STRING_FAILURE) {
-        index += 2;
-        do {
-            char ch = furi_string_get_char(song_settings, index++);
-            if(ch < '0' || ch > '9') {
-                break;
-            }
-            value *= 10;
-            value += ch - '0';
-        } while(true);
-    } else {
-        value = default_value;
-    }
-    return value;
-}
-
-static void fmf2sub_load_txt_file(Fmf2SubApp* app, Fmf2SubConvertModel* model) {
-    UNUSED(app);
-    UNUSED(model);
-
-    bool error = false;
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    File* file = storage_file_alloc(storage);
-    do {
-        if(storage_file_open(
-               file, furi_string_get_cstr(app->file_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
-            char ch;
-            while(storage_file_read(file, &ch, 1) && !storage_file_eof(file)) {
-                if(ch == ':') {
-                    break;
-                }
-            }
-            if(storage_file_eof(file)) {
-                FURI_LOG_E(TAG, "Failed to find first delimiter.");
-                error = true;
-                break;
-            }
-
-            FuriString* song_settings = furi_string_alloc();
-            while(storage_file_read(file, &ch, 1) && !storage_file_eof(file)) {
-                if(ch == ':') {
-                    break;
-                }
-                furi_string_push_back(song_settings, ch);
-            }
-            model->data.duration = fmf2sub_extract_param(song_settings, 'd', 4);
-            model->data.octave = fmf2sub_extract_param(song_settings, 'o', 5);
-            model->data.bpm = fmf2sub_extract_param(song_settings, 'b', 120);
-            furi_string_free(song_settings);
-
-            if(storage_file_eof(file)) {
-                FURI_LOG_E(TAG, "Failed to find second delimiter.");
-                error = true;
-                break;
-            }
-            model->data.notes = furi_string_alloc();
-            while(storage_file_read(file, &ch, 1) && !storage_file_eof(file)) {
-                furi_string_push_back(model->data.notes, ch);
-            }
-        }
-    } while(false);
-    storage_file_close(file);
-    storage_file_free(file);
-    furi_record_close(RECORD_STORAGE);
-
-    if(error) {
-        model->state = Fmf2SubStateError;
-    } else {
-        view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdCreateSub);
-    }
-}
-
-static void fmf2sub_load_fmf_file(Fmf2SubApp* app, Fmf2SubConvertModel* model) {
-    UNUSED(model);
-    FlipperFormat* ff;
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    FuriString* buf = furi_string_alloc();
-
-    bool error = false;
-
-    if(model->data.notes) {
-        furi_string_free(model->data.notes);
-    }
-    model->data.notes = NULL;
-
-    ff = flipper_format_buffered_file_alloc(storage);
-    do {
-        uint32_t format_version;
-        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(app->file_path))) {
-            FURI_LOG_E(TAG, "Failed to open file: %s", furi_string_get_cstr(app->file_path));
-            error = true;
-            break;
-        }
-        if(!flipper_format_read_header(ff, buf, &format_version)) {
-            FURI_LOG_E(TAG, "Failed to read settings header.");
-            error = true;
-            break;
-        }
-
-        flipper_format_read_uint32(ff, "BPM", &(model->data.bpm), 120);
-        flipper_format_read_uint32(ff, "Duration", &(model->data.duration), 4);
-        flipper_format_read_uint32(ff, "Octave", &(model->data.octave), 5);
-        model->data.notes = furi_string_alloc();
-        if(!flipper_format_read_string(ff, "Notes", model->data.notes)) {
-            FURI_LOG_E(TAG, "Failed to read notes.");
-            furi_string_free(model->data.notes);
-            model->data.notes = NULL;
-            error = true;
-        }
-    } while(false);
-
-    flipper_format_buffered_file_close(ff);
-    flipper_format_free(ff);
-    furi_record_close(RECORD_STORAGE);
-    furi_string_free(buf);
-
-    if(error) {
-        fmf2sub_load_txt_file(app, model);
-    } else {
-        view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdCreateSub);
-    }
-}
-
-static void fmf2sub_file_write(File* file, const char* str) {
-    storage_file_write(file, str, strlen(str));
-}
-
-void fmf2sub_save_sub_file(Fmf2SubApp* app, Fmf2SubConvertModel* model) {
-    UNUSED(app);
-
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    File* file = storage_file_alloc(storage);
-    FuriString* file_path = furi_string_alloc();
-    furi_string_cat_printf(
-        file_path,
-        "%s/%s",
-        EXT_PATH("/subghz/"),
-        setting_button_values[model->setting_button_index]);
-    if(storage_file_open(file, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
-        storage_file_truncate(file);
-        FuriString* tmp_string = furi_string_alloc();
-        fmf2sub_file_write(file, "Filetype: Flipper SubGhz RAW File\n");
-        fmf2sub_file_write(file, "Version: 1\n");
-        fmf2sub_file_write(file, "Frequency: ");
-        fmf2sub_file_write(file, setting_frequency_values[model->setting_frequency_index]);
-        fmf2sub_file_write(file, "\nPreset: ");
-        fmf2sub_file_write(file, setting_modulation_values[model->setting_modulation_index]);
-        fmf2sub_file_write(file, "\nProtocol: RAW");
-
-        // process the notes
-        FuriString* notes = model->data.notes;
-        int16_t duration = -1;
-        int16_t octave = -1;
-        bool dot = false;
-
-        for(size_t i = 0; i < furi_string_size(notes); i++) {
-            char ch = furi_string_get_char(notes, i);
-            if(ch == ' ' || ch == ',') {
-                // skip spaces and commas.
-                continue;
-            }
-
-            // is is a duration?
-            while(ch >= '0' && ch <= '9') {
-                if(duration == -1) {
-                    duration = 0;
-                }
-                duration *= 10;
-                duration += ch - '0';
-                ch = furi_string_get_char(notes, ++i);
-            }
-            if(duration == -1) {
-                duration = model->data.duration;
-            }
-
-            // it should be note.
-            if(ch < 'A' && ch > 'G' && ch < 'a' && ch > 'g' && ch != 'P' && ch != 'p') {
-                FURI_LOG_D(TAG, "Invalid note: %c", ch);
-                // invalid note
-                continue;
-            }
-            bool sharp = furi_string_get_char(notes, i + 1) == '#';
-
-            // convert to frequency (octave 2)
-            float frequency = 0;
-            ch = toupper(ch);
-            if(ch == 'P') {
-                frequency = 6.0;
-            } else if(ch == 'C') {
-                frequency = !sharp ? 130.0 : 138.6;
-            } else if(ch == 'D') {
-                frequency = !sharp ? 146.8 : 155.6;
-            } else if(ch == 'E') {
-                frequency = 164.8;
-            } else if(ch == 'F') {
-                frequency = !sharp ? 174.6 : 185.0;
-            } else if(ch == 'G') {
-                frequency = !sharp ? 196.0 : 207.7;
-            } else if(ch == 'A') {
-                frequency = !sharp ? 220.0 : 233.1;
-            } else if(ch == 'B') {
-                frequency = 246.9;
-            } else {
-                FURI_LOG_D(TAG, "Invalid note: %c, %d", ch, (int)ch);
-                // invalid note
-                continue;
-            }
-
-            if(sharp) {
-                i++;
-            }
-
-            ch = furi_string_get_char(notes, ++i);
-            if(ch == '.') {
-                dot = true; // 50% longer
-                ch = furi_string_get_char(notes, ++i);
-            }
-
-            while(ch >= '0' && ch <= '9') {
-                if(octave == -1) {
-                    octave = 0;
-                }
-                octave *= 10;
-                octave += ch - '0';
-                ch = furi_string_get_char(notes, ++i);
-            }
-            if(octave == -1) {
-                octave = model->data.octave;
-            }
-
-            if(ch == '.') {
-                dot = true; // 50% longer
-            }
-
-            if(octave < 2) {
-                frequency /= 2.0;
-            } else {
-                for(int i = 2; i < octave; i++) {
-                    frequency *= 2.0;
-                }
-            }
-
-            uint32_t pulse = (1000000 / frequency) / 2;
-            // 4/4 timing, duration of quarter note (4) is 500ms.
-            float count = (1000000.0 / (pulse * 2.0)) * 2.0 / duration;
-            count *= 120.0f;
-            count /= model->data.bpm;
-
-            if(dot) {
-                count += (count / 2.0f);
-            }
-
-            if(count < 1.0f) {
-                count = 1.0;
-            }
-
-            if(duration <= 1) {
-                count *= 0.98; // whole note.
-            } else if(duration <= 2) {
-                count *= 0.95; // half note.
-            } else if(duration <= 4) {
-                count *= 0.90; // quarter note.
-            } else {
-                count *= 0.90;
-            }
-
-            float duration_tone = ((uint32_t)count) * pulse * 2;
-            float duration_beat =
-                (1000000.0 * 2.0 / duration * 120.0 / model->data.bpm) * (dot ? 1.5 : 1.0);
-            float duration_rem = (duration_beat - duration_tone) / 2.0f;
-            uint32_t rem_counter = 1;
-            while(duration_rem > 20000.0f) {
-                rem_counter *= 2;
-                duration_rem /= 2.0;
-            }
-
-            FURI_LOG_D(
-                TAG,
-                "octave: %d, duration: %d, freq: %f, dot: %c, pulse: %ld, count: %f, bpm: %ld",
-                octave,
-                duration,
-                (double)frequency,
-                dot ? 'Y' : 'N',
-                pulse,
-                (double)count,
-                model->data.bpm);
-
-            FURI_LOG_D(
-                TAG,
-                "beat: %f  tone: %f  rem-us: %f  rem-cnt: %ld",
-                (double)duration_beat,
-                (double)duration_tone,
-                (double)duration_rem,
-                rem_counter);
-
-            fmf2sub_file_write(file, "\nRAW_Data:");
-            furi_string_printf(tmp_string, " %ld %ld", pulse, -pulse);
-            for(uint32_t i = 0; i < (uint32_t)count; i++) {
-                if(i % 256 == 255) {
-                    fmf2sub_file_write(file, "\nRAW_Data:");
-                }
-                fmf2sub_file_write(file, furi_string_get_cstr(tmp_string));
-            }
-
-            fmf2sub_file_write(file, "\nRAW_Data:");
-            furi_string_printf(
-                tmp_string, " %ld -%ld", (uint32_t)duration_rem, (uint32_t)duration_rem);
-            for(uint32_t i = 0; i < rem_counter; i++) {
-                fmf2sub_file_write(file, furi_string_get_cstr(tmp_string));
-            }
-
-            octave = -1;
-            duration = -1;
-            dot = false;
-        }
-        furi_string_free(tmp_string);
-
-        // write file
-        model->state = Fmf2SubStateConverted;
-    } else {
-        FURI_LOG_D(TAG, "Failed to create file: %s", furi_string_get_cstr(file_path));
-        model->state = Fmf2SubStateError;
-    }
-
-    storage_file_close(file);
-    storage_file_free(file);
-    furi_record_close(RECORD_STORAGE);
-    furi_string_free(file_path);
-}
-
-/**
- * @brief      Callback for custom events.
- * @details    This function is called when a custom event is sent to the view dispatcher.
- * @param      event    The event id - Fmf2SubEventId value.
- * @param      context  The context - Fmf2SubApp object.
-*/
-static bool fmf2sub_view_convert_custom_event_callback(uint32_t event, void* context) {
-    Fmf2SubApp* app = (Fmf2SubApp*)context;
-    switch(event) {
-    case Fmf2SubEventIdRedrawScreen:
-        // Redraw screen by passing true to last parameter of with_view_model.
-        {
-            bool redraw = true;
-            with_view_model(
-                app->view_convert, Fmf2SubConvertModel * _model, { UNUSED(_model); }, redraw);
-            return true;
-        }
-    case Fmf2SubEventIdOkPressed: {
-        with_view_model(
-            app->view_convert,
-            Fmf2SubConvertModel * model,
-            { model->state = Fmf2SubStateIdle; },
-            false);
-        DialogsFileBrowserOptions browser_options;
-        dialog_file_browser_set_basic_options(&browser_options, "", &I_fmf_10x10);
-        browser_options.hide_dot_files = true;
-        browser_options.hide_ext = false;
-        browser_options.base_path = FMF_LOAD_PATH;
-        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)) {
-            view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdLoadFile);
-        }
-        return true;
-    }
-    case Fmf2SubEventIdLoadFile: {
-        with_view_model(
-            app->view_convert,
-            Fmf2SubConvertModel * model,
-            {
-                model->state = Fmf2SubStateLoading;
-                fmf2sub_load_fmf_file(app, model);
-            },
-            true);
-        return true;
-    }
-    case Fmf2SubEventIdCreateSub: {
-        with_view_model(
-            app->view_convert,
-            Fmf2SubConvertModel * model,
-            {
-                model->state = Fmf2SubStateConverting;
-                FURI_LOG_D(TAG, "Loaded file: %s", furi_string_get_cstr(app->file_path));
-                FURI_LOG_D(TAG, "BPM: %ld", model->data.bpm);
-                FURI_LOG_D(TAG, "Duration: %ld", model->data.duration);
-                FURI_LOG_D(TAG, "Octave: %ld", model->data.octave);
-                FURI_LOG_D(TAG, "Notes: %s", furi_string_get_cstr(model->data.notes));
-
-                fmf2sub_save_sub_file(app, model);
-            },
-            true);
-        return true;
-    }
-    default:
-        return false;
-    }
-}
-
-/**
- * @brief      Callback for convert screen input.
- * @details    This function is called when the user presses a button while on the convert screen.
- * @param      event    The event - InputEvent object.
- * @param      context  The context - Fmf2SubApp object.
- * @return     true if the event was handled, false otherwise.
-*/
-static bool fmf2sub_view_convert_input_callback(InputEvent* event, void* context) {
-    Fmf2SubApp* app = (Fmf2SubApp*)context;
-    UNUSED(app);
-
-    if(event->type == InputTypeShort) {
-        if(event->key == InputKeyLeft) {
-            bool redraw = true;
-            with_view_model(
-                app->view_convert,
-                Fmf2SubConvertModel * model,
-                {
-                    if(model->setting_button_index > 0) {
-                        model->setting_button_index--;
-                        variable_item_set_current_value_text(
-                            app->variable_item_button,
-                            setting_button_names[model->setting_button_index]);
-                        variable_item_set_current_value_index(
-                            app->variable_item_button, model->setting_button_index);
-                    }
-                },
-                redraw);
-        } else if(event->key == InputKeyRight) {
-            bool redraw = true;
-            with_view_model(
-                app->view_convert,
-                Fmf2SubConvertModel * model,
-                {
-                    if(model->setting_button_index + 1 <
-                       (uint8_t)COUNT_OF(setting_button_values)) {
-                        model->setting_button_index++;
-                        variable_item_set_current_value_text(
-                            app->variable_item_button,
-                            setting_button_names[model->setting_button_index]);
-                        variable_item_set_current_value_index(
-                            app->variable_item_button, model->setting_button_index);
-                    }
-                },
-                redraw);
-        }
-    } else if(event->type == InputTypePress) {
-        if(event->key == InputKeyOk) {
-            view_dispatcher_send_custom_event(app->view_dispatcher, Fmf2SubEventIdOkPressed);
-            return true;
-        }
-    }
-
-    return false;
-}
-
-/**
- * @brief      Allocate the fmf2sub application.
- * @details    This function allocates the fmf2sub application resources.
- * @return     Fmf2SubApp object.
-*/
-static Fmf2SubApp* fmf2sub_app_alloc() {
-    Fmf2SubApp* app = (Fmf2SubApp*)malloc(sizeof(Fmf2SubApp));
-
-    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->submenu = submenu_alloc();
-    submenu_add_item(
-        app->submenu, "Configure", Fmf2SubSubmenuIndexConfigure, fmf2sub_submenu_callback, app);
-    submenu_add_item(
-        app->submenu, "Convert", Fmf2SubSubmenuIndexConvert, fmf2sub_submenu_callback, app);
-    submenu_add_item(
-        app->submenu, "About", Fmf2SubSubmenuIndexAbout, fmf2sub_submenu_callback, app);
-    view_set_previous_callback(submenu_get_view(app->submenu), fmf2sub_navigation_exit_callback);
-    view_dispatcher_add_view(
-        app->view_dispatcher, Fmf2SubViewSubmenu, submenu_get_view(app->submenu));
-    view_dispatcher_switch_to_view(app->view_dispatcher, Fmf2SubViewSubmenu);
-
-    app->text_input = text_input_alloc();
-    view_dispatcher_add_view(
-        app->view_dispatcher, Fmf2SubViewTextInput, text_input_get_view(app->text_input));
-    app->temp_buffer_size = 32;
-    app->temp_buffer = (char*)malloc(app->temp_buffer_size);
-
-    app->variable_item_list_config = variable_item_list_alloc();
-    variable_item_list_reset(app->variable_item_list_config);
-    //variable_item_list_set_header(app->variable_item_list_config, "Flipboard Signal Config");
-    VariableItem* item = variable_item_list_add(
-        app->variable_item_list_config,
-        setting_frequency_label,
-        COUNT_OF(setting_frequency_values),
-        fmf2sub_setting_frequency_change,
-        app);
-    uint8_t setting_frequency_index = 33;
-    variable_item_set_current_value_index(item, setting_frequency_index);
-    variable_item_set_current_value_text(item, setting_frequency_names[setting_frequency_index]);
-
-    item = variable_item_list_add(
-        app->variable_item_list_config,
-        setting_modulation_label,
-        COUNT_OF(setting_modulation_values),
-        fmf2sub_setting_modulation_change,
-        app);
-    uint8_t setting_modulation_index = 1;
-    variable_item_set_current_value_index(item, setting_modulation_index);
-    variable_item_set_current_value_text(item, setting_modulation_names[setting_modulation_index]);
-
-    app->variable_item_button = variable_item_list_add(
-        app->variable_item_list_config,
-        setting_button_label,
-        COUNT_OF(setting_button_values),
-        fmf2sub_setting_button_change,
-        app);
-    uint8_t setting_button_index = 0;
-    variable_item_set_current_value_index(app->variable_item_button, setting_button_index);
-    variable_item_set_current_value_text(
-        app->variable_item_button, setting_button_names[setting_button_index]);
-
-    view_set_previous_callback(
-        variable_item_list_get_view(app->variable_item_list_config),
-        fmf2sub_navigation_submenu_callback);
-
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        Fmf2SubViewConfigure,
-        variable_item_list_get_view(app->variable_item_list_config));
-
-    app->view_convert = view_alloc();
-    view_set_draw_callback(app->view_convert, fmf2sub_view_convert_draw_callback);
-    view_set_input_callback(app->view_convert, fmf2sub_view_convert_input_callback);
-    view_set_previous_callback(app->view_convert, fmf2sub_navigation_submenu_callback);
-    view_set_enter_callback(app->view_convert, fmf2sub_view_convert_enter_callback);
-    view_set_exit_callback(app->view_convert, fmf2sub_view_convert_exit_callback);
-    view_set_context(app->view_convert, app);
-    view_set_custom_callback(app->view_convert, fmf2sub_view_convert_custom_event_callback);
-    view_allocate_model(app->view_convert, ViewModelTypeLockFree, sizeof(Fmf2SubConvertModel));
-    Fmf2SubConvertModel* model = view_get_model(app->view_convert);
-    model->setting_frequency_index = setting_frequency_index;
-    model->setting_modulation_index = setting_modulation_index;
-    model->setting_button_index = setting_button_index;
-    view_dispatcher_add_view(app->view_dispatcher, Fmf2SubViewConvert, app->view_convert);
-
-    app->widget_about = widget_alloc();
-    widget_add_text_scroll_element(
-        app->widget_about,
-        0,
-        0,
-        128,
-        64,
-        "Music to Sub-GHz  v1.2!\n\n"
-        "Converts music files (.FMF)\n"
-        "or (.TXT) to Sub-GHz format\n"
-        "(.SUB) Files.   Flip#.sub is\n"
-        "written to the SD Card's\n"
-        "subghz folder. Another\n"
-        "Flipper Zero with sound\n"
-        "turned on doing a Read RAW\n"
-        "in the Sub-GHz app can\n"
-        "listen to the music!\n"
-        "Use Flipboard Signal app to\n"
-        "send signals or use the\n"
-        "Sub-GHz app. Enjoy!\n\n"
-        "author: @codeallnight\nhttps://discord.com/invite/NsjCvqwPAd\nhttps://youtube.com/@MrDerekJamison");
-    view_set_previous_callback(
-        widget_get_view(app->widget_about), fmf2sub_navigation_submenu_callback);
-    view_dispatcher_add_view(
-        app->view_dispatcher, Fmf2SubViewAbout, widget_get_view(app->widget_about));
-
-    app->file_path = furi_string_alloc();
-    app->dialogs = furi_record_open(RECORD_DIALOGS);
-
-    return app;
-}
-
-/**
- * @brief      Free the fmf2sub application.
- * @details    This function frees the fmf2sub application resources.
- * @param      app  The fmf2sub application object.
-*/
-static void fmf2sub_app_free(Fmf2SubApp* app) {
-    view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewTextInput);
-    text_input_free(app->text_input);
-    free(app->temp_buffer);
-    view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewAbout);
-    widget_free(app->widget_about);
-    view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewConvert);
-    view_free(app->view_convert);
-    view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewConfigure);
-    variable_item_list_free(app->variable_item_list_config);
-    view_dispatcher_remove_view(app->view_dispatcher, Fmf2SubViewSubmenu);
-    submenu_free(app->submenu);
-    view_dispatcher_free(app->view_dispatcher);
-    furi_record_close(RECORD_GUI);
-
-    furi_string_free(app->file_path);
-    furi_record_close(RECORD_DIALOGS);
-
-    free(app);
-}
-
-/**
- * @brief      Main function for fmf2sub application.
- * @details    This function is the entry point for the fmf2sub application.  It should be defined in
- *           application.fam as the entry_point setting.
- * @param      _p  Input parameter - unused
- * @return     0 - Success
-*/
-int32_t fmf_to_sub_app(void* _p) {
-    UNUSED(_p);
-
-    Fmf2SubApp* app = fmf2sub_app_alloc();
-    view_dispatcher_run(app->view_dispatcher);
-
-    fmf2sub_app_free(app);
-    return 0;
-}

+ 0 - 16
fmf_to_sub/application.fam

@@ -1,16 +0,0 @@
-App(
-    appid="fmf_to_sub",
-    name="Music to Sub-GHz Radio",
-    apptype=FlipperAppType.EXTERNAL,
-    entry_point="fmf_to_sub_app",
-    stack_size=4 * 1024,
-    requires=[
-        "gui",
-    ],
-    order=10,
-    fap_version="1.2",
-    fap_icon="fmf.png",
-    fap_category="Sub-GHz",
-    fap_icon_assets="assets",
-    fap_description="Converts Flipper music files (.FMF and .TXT) into Sub-GHz files (.SUB).",
-)

BIN
fmf_to_sub/assets/fmf_10x10.png


BIN
fmf_to_sub/fmf.png


BIN
fmf_to_sub/gallery/00-main-menu.png


BIN
fmf_to_sub/gallery/01-about1.png


BIN
fmf_to_sub/gallery/02-about2.png


BIN
fmf_to_sub/gallery/03-configure.png


BIN
fmf_to_sub/gallery/04-convert.png


BIN
fmf_to_sub/gallery/05-file.png


+ 0 - 11
fmf_to_sub/gallery/CHANGELOG.md

@@ -1,11 +0,0 @@
-## 1.2
- - Improved timing of notes
-
-## 1.1
- - Bug fix: super fast notes/pauses causing SUB file to fail.
- - Repro... A-Team:d=4,o=5,b=125:4d#6,8a#,2d#6,16p,8g#,4a#,4d#.,8p,16g,16a#,8d#6,8a#,8f6,2d#6,16p,8c#.6,16c6,16a#,8g#.,2a#
-
-## 1.0
- - Initial release
- - Converts FMF or TXT files to SUB format
- - Outputs file: SD Card/subghz/flip##.sub

+ 0 - 64
fmf_to_sub/gallery/README.md

@@ -1,64 +0,0 @@
-# Music to Sub-GHz Radio
-
-## Overview
-
-The "Music to Sub-GHz Radio" application converts Flipper Music Files (.FMF) into a RAW .SUB file format that can be transmitted over the Sub-GHz radio! The Flipper Zero can receive the music and play it back.
-
-There are large collections of songs available for the Flipper Zero that can be converted, for example [UberGuidoZ collection](https://github.com/UberGuidoZ/Flipper/tree/main/Music_Player). The "Music to Sub-GHz Radio" application supports converting both .FMF (Flipper Music Format) files and .TXT files into the .SUB file format.
-
-To listen to the music without using the Sub-GHz radio, you can use the [Flipper Zero Music Player](https://lab.flipper.net/apps/music_player) application.
-
-## How to convert music
-
-1. Download [Music to Sub-GHz Radio](https://lab.flipper.net/apps/fmf_to_sub) from lab.flipper.net.
-2. Open the "Music to Sub-GHz Radio" application.
-3. Select the "Configure" option.
-4. Choose the "Frequency" you want to transmit on.
-5. Choose the "Modulation" you want to use (AM650 is a good default choice).
-6. Click the "Back" button.
-7. Select the "Convert" option.  This will display the number of the sub file.
-8. You can use "Left" and "Right" buttons to change the number of the sub file.
-9. Click the "OK" button and then choose the music file you want to convert.
-
-- You will see a status of "Converting..."
-- After a few seconds you should see the message "Saved in Sub-GHz folder"
-- A new file will be saved in "SD Card/subghz/" with a name like "Flip5.sub".
-
-## Send music with Flipboard Signal
-
-The easiest way to send music is to use the [Flipboard Signal application](https://lab.flipper.net/apps/flipboard_signal).
-
-1. Download [Flipboard Signal](https://lab.flipper.net/apps/flipboard_signal) from lab.flipper.net.
-2. Connect your [FlipBoard](https://github.com/makeithackin/flipboard) to your Flipper Zero.
-3. Open the "Flipboard signal" application.
-3. Choose "Start application".
-4. Click a button (or button combination) on your FlipBoard.
-- NOTE: You may want to go to configuration and disable playing a tone (press "left" button until you get to the "Off" value).
-
-## Receive music with Flipper Zero
-
-1. Open the "Sub-GHz" application on your Flipper Zero.
-2. Choose "Read RAW".
-3. Click "Left" to go to "Configure".
-4. Choose the "Frequency" & "Modulation" you want to receive on.
-5. Set "Sound" to "On".
-6. Choose "Back".
-7. Click "OK" to start "Rec".
-
-## Send music with Sub-GHz Radio
-
-1. Open the "Sub-GHz" application on your Flipper Zero.
-2. Choose "Read RAW".
-3. Click "Left" to go to "Configure".
-4. Set "Sound" to "On".
-5. Choose "Back".
-6. Choose "Back" to the main Sub-GHz menu.
-7. Choose "Saved"
-8. Select the "Flip#.sub" file you want to send.
-9. Click "OK" to send.
-
-## Support
-
-If you have need help, we are here for you. Also, we would love your feedback on cool ideas for future FlipBoard applications!  The best way to get support is to join the Flipper Zero Tutorials (Unofficial) Discord community. Here is a [Discord invite](https://discord.gg/KTThkQHj5B) to join my "Flipper Zero Tutorials (Unofficial)" community.
-
-If you want to support my work, you can donate via [https://ko-fi.com/codeallnight](https://ko-fi.com/codeallnight) or you can [buy a FlipBoard](https://www.tindie.com/products/makeithackin/flipboard-macropad-keyboard-for-flipper-zero/) from MakeItHackin with software & tutorials from me (@CodeAllNight).

+ 0 - 6
fmf_to_sub/rick.fmf

@@ -1,6 +0,0 @@
-Filetype: Flipper Music Format
-Version: 0
-BPM: 120
-Duration: 4
-Octave: 4
-Notes: 8F#., 8F#., 4E, 16A3, 16B3, 16D, 16B3, 8E., 8E., 8D., 16C#, 8B3, 16A3, 16B3, 16D, 16C#, 4D, 8E, 8C#., 16B3, 16A3, 8A3, 8A3, 4E, 2D, 16A3, 16B3, 16D, 16B3, 8F#., 8F#., 4E., 16A3, 16B3, 16D, 16B3, 4A, 8C#, 8D., 16C#, 8B3, 16A3, 16B3, 16D, 16B3

+ 0 - 1
fmf_to_sub/rick.txt

@@ -1 +0,0 @@
-Rick : d=4,o=4,b=120:8F#., 8F#., 4E, 16A3, 16B3, 16D, 16B3, 8E., 8E., 8D., 16C#, 8B3, 16A3, 16B3, 16D, 16C#, 4D, 8E, 8C#., 16B3, 16A3, 8A3, 8A3, 4E, 2D, 16A3, 16B3, 16D, 16B3, 8F#., 8F#., 4E., 16A3, 16B3, 16D, 16B3, 4A, 8C#, 8D., 16C#, 8B3, 16A3, 16B3, 16D, 16B3

+ 0 - 2
key_copier/.gitattributes

@@ -1,2 +0,0 @@
-# Auto detect text files and perform LF normalization
-* text=auto

+ 0 - 36
key_copier/.github/workflows/ufbt_build.yaml

@@ -1,36 +0,0 @@
-name: "uFBT Build"
-on:
-  workflow_dispatch:
-  push:
-    branches: 
-      - main
-      - develop
-  pull_request:
-  # schedule: 
-    # do a build every day
-    # - cron: "1 1 * * *"
-jobs:
-  ufbt-build-action:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        include:
-          - name: dev channel
-            sdk-channel: dev
-          - name: release channel
-            sdk-channel: release
-    name: 'ufbt: Build for ${{ matrix.name }}'
-    steps:
-      - name: Checkout
-        uses: actions/checkout@v4
-      - name: Build with ufbt
-        uses: flipperdevices/flipperzero-ufbt-action@v0.1.3
-        id: build-app
-        with:
-          sdk-channel: ${{ matrix.sdk-channel }}
-          sdk-index-url: ${{ matrix.sdk-index-url }}
-      - name: Upload app artifacts
-        uses: actions/upload-artifact@v3
-        with:
-          name: ${{ github.event.repository.name }}-${{ steps.build-app.outputs.suffix }}
-          path: ${{ steps.build-app.outputs.fap-artifacts }}

+ 0 - 54
key_copier/.gitignore

@@ -1,54 +0,0 @@
-# Prerequisites
-*.d
-
-# Object files
-*.o
-*.ko
-*.obj
-*.elf
-
-# Linker output
-*.ilk
-*.map
-*.exp
-
-# Precompiled Headers
-*.gch
-*.pch
-
-# Libraries
-*.lib
-*.a
-*.la
-*.lo
-
-# Shared objects (inc. Windows DLLs)
-*.dll
-*.so
-*.so.*
-*.dylib
-
-# Executables
-*.exe
-*.out
-*.app
-*.i*86
-*.x86_64
-*.hex
-
-# Debug files
-*.dSYM/
-*.su
-*.idb
-*.pdb
-
-# Kernel Module Compile Results
-*.mod*
-*.cmd
-.tmp_versions/
-modules.order
-Module.symvers
-Mkfile.old
-dkms.conf
-
-.DS_Store

+ 0 - 1
key_copier/.gitsubtree

@@ -1 +0,0 @@
-https://github.com/zinongli/KeyCopier main /

+ 0 - 4
key_copier/CHANGELOG.md

@@ -1,4 +0,0 @@
-## 1.0
-- Initial release.
-- Supports measuring, saving, and loading keys with .keycopy extension.
-- Supports Kwikset KW1 and Schalge SC4 formats as of this version.

+ 0 - 21
key_copier/LICENSE

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

+ 0 - 15
key_copier/README.md

@@ -1,15 +0,0 @@
-# Key Copier App
-A Flipper Zero app for measuring key bitting patterns.
-
-## Instruction
-To measure your key: 
-1. Place it on top of the screen.
-2. Use the contour to align your key.
-3. Adjust each pin's depth until they match. It's easier if you look with one eye closed.
-
-## 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. 
-- [Project channel](https://discord.com/channels/1112390971250974782/1264067969634402356)
-
-
-

+ 0 - 17
key_copier/application.fam

@@ -1,17 +0,0 @@
-App(
-    appid="key_copier",
-    name="Key Copier",
-    apptype=FlipperAppType.EXTERNAL,
-    entry_point="main_key_copier_app",
-    stack_size=4 * 1024,
-    requires=[
-        "gui",
-    ],
-    order=10,
-    fap_icon="icon.png",
-    fap_category="Tools",
-    fap_icon_assets="assets",
-    fap_description="@README.md",
-    fap_version="1.0",
-    fap_author="Torron",
-)

BIN
key_copier/assets/.DS_Store


BIN
key_copier/assets/arrow_down.png


BIN
key_copier/assets/icon.png


BIN
key_copier/icon.png


+ 0 - 678
key_copier/key_copier.c

@@ -1,678 +0,0 @@
-#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;
-}

+ 0 - 10
key_copier/key_copier.h

@@ -1,10 +0,0 @@
-#define KEY_COPIER_FILE_EXTENSION ".keycopy"
-#define INCHES_PER_PX             0.00978
-
-static inline int min(int a, int b) {
-    return (a < b) ? a : b;
-}
-
-static inline int max(int a, int b) {
-    return (a > b) ? a : b;
-}

+ 0 - 40
key_copier/key_formats.c

@@ -1,40 +0,0 @@
-#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}};

+ 0 - 31
key_copier/key_formats.h

@@ -1,31 +0,0 @@
-#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

BIN
key_copier/screenshots/config.png


BIN
key_copier/screenshots/kw1.png


BIN
key_copier/screenshots/load.png


BIN
key_copier/screenshots/main_menu.png